Restore on Backspace

Поведение удаления в мультиселект-интерфейсах часто требует обратимого действия: пользователь удаляет последний выбранный элемент и ожидает, что он не исчезнет полностью, а вернётся в поле ввода для немедленного редактирования. В Tom Select это реализуется через механизм восстановления последнего элемента при нажатии Backspace в пустом поле ввода.

Ключевая логика основана на перехвате клавиатурного события и анализе состояния инпута: если текст отсутствует и курсор находится в начале строки, Backspace интерпретируется не как удаление символа, а как команда на извлечение последнего выбранного элемента из коллекции.

Внутри Tom Select обработка клавиатуры централизована. Событие keydown анализируется на уровне основного input-элемента. Для Backspace применяется дополнительная проверка состояния:

  • поле ввода пустое
  • нет выделенного текста
  • текущий режим допускает редактирование выбранных элементов
  • есть хотя бы один выбранный элемент (в режиме multiple)

При совпадении условий инициируется логика “restore”.

input.addEventListener('keydown', function(e) {
    if (e.key === 'Backspace') {
        if (self.items.length && !self.inputValue.length) {
            self.restoreLastItem();
            e.preventDefault();
        }
    }
});

Фактическая реализация в библиотеке более сложная, но концептуально опирается на этот паттерн.

Поведение restoreLastItem

Функция восстановления не просто удаляет элемент из массива выбранных значений. Она выполняет обратную операцию по отношению к стандартному добавлению:

  1. Берётся последний элемент из this.items
  2. Удаляется из текущего состояния выбора
  3. Перемещается обратно в input-буфер
  4. Активируется режим редактирования
  5. При необходимости открывается dropdown
restoreLastItem() {
    const value = this.items.pop();
    const option = this.options[value];

    this.removeItem(value, true);
    this.setTextboxValue(option.text);
    this.refreshOptions();
}

Главная идея заключается в том, что пользователь не теряет контекст: удалённый элемент становится редактируемым значением.

Подключение функциональности через plugin API

Tom Select использует модульную систему плагинов, где restore-on-backspace подключается как независимое расширение поведения.

new TomSelect('#select', {
    plugins: ['restore_on_backspace']
});

При инициализации плагин регистрирует обработчики клавиатуры и расширяет стандартный цикл обработки input-событий.

В некоторых конфигурациях плагин комбинируется с другими расширениями:

new TomSelect('#select', {
    plugins: [
        'remove_button',
        'restore_on_backspace'
    ]
});

В таком случае поведение удаления через кнопку и через клавиатуру становится согласованным.

Взаимодействие с режимом multiple

Наиболее заметный эффект проявляется в multiple режиме. При отсутствии restore-on-backspace стандартное поведение выглядит так:

  • Backspace удаляет последний item
  • элемент исчезает без возможности быстрого восстановления

С включённым механизмом:

  • элемент удаляется из списка
  • его значение сразу возвращается в input
  • пользователь может модифицировать его и повторно добавить

Это делает интерфейс ближе к “редактируемым тегам”, а не к статичному списку.

Влияние состояния input и caret position

Критическим условием является состояние каретки. Если курсор не в начале строки, Backspace ведёт себя стандартно — удаляет символы.

Логика упрощённо:

if (input.selectionStart === 0 && input.value === '') {
    restoreLastItem();
}

Это предотвращает конфликт между редактированием текста и управлением выбранными значениями.

Сценарии взаимодействия с create-поведением

При включённой опции create: true восстановленный элемент может сразу становиться редактируемым новым значением.

new TomSelect('#select', {
    create: true,
    plugins: ['restore_on_backspace']
});

Сценарий:

  1. пользователь вводит “Apple”
  2. добавляет как новый option
  3. нажимает Backspace при пустом input
  4. “Apple” возвращается в input как текст
  5. редактируется и создаётся новая модификация

Таким образом достигается псевдо-undo без отдельной истории изменений.

Сочетание с hideSelected

При активном hideSelected поведение становится более контекстным. Поскольку выбранные элементы скрываются из dropdown, восстановление через Backspace фактически возвращает их в доступный список.

new TomSelect('#select', {
    hideSelected: true,
    plugins: ['restore_on_backspace']
});

Логика обновления списка опций должна учитывать синхронизацию скрытых элементов после восстановления.

Особенности при maxItems

При ограничении maxItems восстановление становится способом “переноса” элемента обратно в input для замены:

  • удаляется последний элемент
  • освобождается слот
  • значение доступно для редактирования
new TomSelect('#select', {
    maxItems: 3,
    plugins: ['restore_on_backspace']
});

Если лимит достигнут, восстановленный элемент может быть повторно добавлен только после удаления или модификации текущего состояния.

Конфликты с custom keyboard handlers

При добавлении пользовательских обработчиков клавиатуры важно учитывать порядок выполнения. Tom Select обрабатывает события до или после внешних listeners в зависимости от capture-фазы.

Типичный конфликт:

  • внешний handler перехватывает Backspace
  • restore_on_backspace не срабатывает
  • или наоборот: restore блокирует кастомную логику

Решение заключается в контроле preventDefault и проверке состояния input до выполнения пользовательской логики.

Мобильные устройства и IME

На мобильных устройствах и при использовании IME (например, азиатских методов ввода) поведение Backspace может отличаться:

  • событие keydown может задерживаться
  • input value обновляется асинхронно
  • восстановление может срабатывать с задержкой или не срабатывать вовсе

Поэтому в ряде реализаций добавляется дополнительная проверка через input event, а не только keydown.

Внутреннее состояние и синхронизация

Tom Select поддерживает три связанных слоя состояния:

  • items — выбранные значения
  • options — доступные элементы
  • inputValue — текущее значение поля

restore_on_backspace синхронизирует эти слои следующим образом:

  • items уменьшается на 1
  • соответствующий элемент возвращается в inputValue
  • options обновляется через refreshOptions

Любая рассинхронизация приводит к визуальным артефактам: “потерянным” тегам или дублированию.

Расширенная логика поведения

В некоторых кастомных сборках добавляется дополнительная семантика:

  • восстановление только при двойном Backspace
  • восстановление только если элемент не был создан вручную
  • ограничение на восстановление для определённых типов данных

Пример кастомного расширения:

TomSelect.define('smart_restore', function(options) {
    this.onKeyD own = (function() {
        const original = this.onKeyDown;
        return function(e) {
            if (e.key === 'Backspace' && this.items.length > 0 && this.inputValue === '') {
                if (this.items.length > 1) {
                    this.restoreLastItem();
                }
                return;
            }
            return original.apply(this, arguments);
        };
    })();
});

UX-аспект поведения

С точки зрения взаимодействия с интерфейсом, восстановление через Backspace превращает поле выбора в гибрид между текстовым редактором и списком тегов. Последний выбранный элемент становится частью редактируемого потока, а не статичной сущностью.

Такой подход особенно полезен в сценариях:

  • редактирование email-списков
  • управление фильтрами
  • выбор категорий с частой корректировкой
  • динамические теги в CMS

При этом важно учитывать риск случайного восстановления при быстром удалении нескольких элементов подряд, что может требовать дополнительной задержки или debounce-логики на уровне событий клавиатуры.