Навигация с клавиатуры

Работа клавиатурной навигации в Awesomplete строится вокруг управления списком подсказок без использования мыши. Основная задача механизма — обеспечить предсказуемое перемещение по элементам списка, быстрый выбор значения и корректное закрытие/сброс выпадающего меню. Поведение навигации интегрировано в стандартные DOM-события keydown, а также в внутреннюю логику управления состоянием экземпляра автодополнения.

Клавиатурное взаимодействие в Awesomplete опирается на несколько ключевых клавиш, каждая из которых связана с конкретным действием.

Стрелки вверх и вниз

ArrowDown / ArrowUp используются для перемещения по списку предложений:

  • ArrowDown — переход к следующему элементу списка
  • ArrowUp — переход к предыдущему элементу списка

При достижении границ списка поведение зависит от конфигурации:

  • по умолчанию навигация ограничена границами (циклирование отсутствует)
  • при кастомной модификации возможно зацикливание индекса

Внутри библиотеки поддерживается индекс выделенного элемента, который обновляется при каждом нажатии клавиш.

Пример базовой логики:

input.addEventListener("keydown", function (e) {
  if (e.key === "ArrowDown") {
    awesomplete.next();
  }
  if (e.key === "ArrowUp") {
    awesomplete.previous();
  }
});

Выбор элемента

Enter

Клавиша Enter используется для подтверждения текущего выделенного элемента.

Поведение:

  • если элемент списка подсвечен — он выбирается
  • значение подставляется в input
  • список закрывается

Важно учитывать, что Enter может конфликтовать с отправкой формы, если поле находится внутри <form>. В таком случае требуется управление событием:

input.addEventListener("keydown", function (e) {
  if (e.key === "Enter" && awesomplete.opened) {
    e.preventDefault();
    awesomplete.select();
  }
});

Tab

Tab применяется для быстрого автозаполнения:

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

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

input.addEventListener("keydown", function (e) {
  if (e.key === "Tab" && awesomplete.opened) {
    awesomplete.select();
  }
});

Отмена и закрытие списка

Escape

Клавиша Escape полностью закрывает список подсказок:

  • снимается активное выделение
  • список скрывается
  • внутреннее состояние сбрасывается
input.addEventListener("keydown", function (e) {
  if (e.key === "Escape") {
    awesomplete.close();
  }
});

Логика выделения элементов

Внутренне навигация опирается на индекс текущего элемента:

  • index = -1 означает отсутствие выделения
  • index >= 0 — активный элемент списка

При движении вниз индекс увеличивается, при движении вверх — уменьшается.

Особенность реализации заключается в том, что визуальное выделение синхронизируется с ARIA-атрибутами:

  • aria-selected="true" — активный элемент
  • остальные элементы получают false

Это обеспечивает доступность для экранных считывателей.

Автоматическое выделение первого элемента

Опция autoFirst влияет на поведение клавиатурной навигации:

  • при true первый элемент списка автоматически становится активным при открытии
  • при false выделение отсутствует до первого нажатия стрелок

Пример конфигурации:

new Awesomplete(input, {
  autoFirst: true
});

С точки зрения UX это снижает количество нажатий клавиш при выборе наиболее релевантного результата.

Связь с открытием списка

Навигация становится активной только при открытом списке:

  • ввод символов → фильтрация → открытие списка
  • навигация возможна только после рендера результатов

При изменении значения input список пересоздаётся, что сбрасывает индекс:

  • при новом вводе индекс снова становится -1 или 0 (в зависимости от autoFirst)

Поведение при достижении границ

При достижении начала или конца списка стандартная реализация:

  • останавливает движение курсора
  • не перескакивает на противоположный конец

Однако при кастомизации можно реализовать циклическую навигацию:

awesomplete.next = function () {
  this.index = (this.index + 1) % this.ul.children.length;
  this.goto(this.index);
};

awesomplete.previous = function () {
  this.index =
    (this.index - 1 + this.ul.children.length) %
    this.ul.children.length;
  this.goto(this.index);
};

Синхронизация с вводом текста

Клавиатурная навигация тесно связана с изменением значения input:

  • при перемещении стрелками значение input не меняется
  • значение обновляется только при выборе (Enter или Tab)

Это разделение важно для предотвращения преждевременного изменения данных.

Обработка быстрых нажатий

При быстром удержании клавиш стрелок:

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

Оптимизация достигается за счёт минимального DOM-обновления: изменяется только активный элемент, без полного перерендера списка.

Доступность и ARIA-навигация

Клавиатурная навигация в Awesomplete интегрирована с ARIA-ролями:

  • role="listbox" для контейнера списка
  • role="option" для элементов
  • aria-activedescendant для текущего выбора

Это позволяет:

  • корректную работу screen readers
  • семантическое отражение текущего состояния
  • синхронизацию визуального и логического выделения

Кастомизация поведения клавиш

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

input.addEventListener("keydown", function (e) {
  switch (e.key) {
    case "ArrowDown":
      awesomplete.next();
      break;

    case "ArrowUp":
      awesomplete.previous();
      break;

    case "Enter":
      if (awesomplete.opened) awesomplete.select();
      break;

    case "Escape":
      awesomplete.close();
      break;
  }
});

Такой подход позволяет:

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

Ограничения стандартной навигации

Стандартная реализация имеет ряд особенностей:

  • отсутствие циклической навигации
  • зависимость от DOM-структуры списка
  • фиксированная логика выбора (без multi-select)
  • невозможность частичного выбора элементов

Эти ограничения обычно компенсируются расширением API и переопределением методов экземпляра.