Фокус на перетаскиваемых элементах

Библиотека Tabbable предназначена для определения элементов на веб-странице, которые могут получать фокус с помощью клавиши Tab. Это критически важно при работе с интерактивными интерфейсами, где необходимо управлять навигацией между элементами формы, кнопками, ссылками или кастомными компонентами.

Элемент считается табулируемым, если он отвечает следующим условиям:

  • Он видим на странице (display не none, visibility не hidden, размер больше нуля).
  • Он не имеет атрибута disabled.
  • Он имеет стандартный для табуляции элемент, например <input>, <button>, <a> с href, либо кастомный элемент с tabindex ≥ 0.

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

  • tabbable(element) — возвращает массив всех элементов внутри указанного контейнера, которые можно табулировать.
  • isTabbable(element) — проверяет, можно ли конкретный элемент выделить с помощью клавиши Tab.

Эти методы учитывают как естественные элементы формы, так и элементы с явно заданным tabindex.


Фокусировка на перетаскиваемых элементах

Перетаскиваемые элементы (draggable) требуют особого внимания, так как пользователь может изменять их положение, что влияет на логическую последовательность табуляции. В стандартной HTML-структуре табуляция происходит по порядку DOM, что иногда ломает UX при динамическом перемещении элементов.

Проблемы, возникающие при перетаскивании:

  1. Изменение визуального порядка. Если элемент перемещается вниз списка, но в DOM остаётся в начале, при нажатии Tab фокус может перескочить на неподходящий элемент.
  2. Скрытые элементы. Во время перетаскивания некоторые элементы могут становиться невидимыми (opacity: 0, display: none) — Tabbable исключает их из навигации.
  3. Временные контейнеры. Drag-and-drop библиотеки часто создают временные обёртки (ghost element). Без корректного управления tabindex такие элементы могут либо быть недоступны для фокуса, либо блокировать табуляцию.

Работа с tabindex для перетаскиваемых элементов

Tabbable учитывает tabindex и позволяет управлять последовательностью фокуса независимо от DOM. Основные подходы:

  • Позитивный tabindex (tabindex="1", "2", …) задаёт явный порядок табуляции. Используется для приоритетной фокусировки.
  • tabindex="0" делает элемент доступным для табуляции в стандартном порядке DOM.
  • tabindex="-1" убирает элемент из последовательности табуляции, но позволяет программно фокусироваться через .focus().

Для динамически перемещаемых элементов рекомендуется:

  1. При начале drag-события временно задавать tabindex="-1" у перетаскиваемого элемента, чтобы он не мешал последовательности табуляции.
  2. По окончании drag-события пересчитывать tabindex для всех элементов контейнера в соответствии с новым визуальным порядком.

Пример интеграции с drag-and-drop

import { tabbable, isTabbable } from 'tabbable';

const container = document.querySelector('.draggable-list');
const items = Array.from(container.querySelectorAll('.draggable'));

items.forEach(item => {
  item.addEventListener('dragstart', () => {
    item.setAttribute('tabindex', '-1');
  });

  item.addEventListener('dragend', () => {
    updateTabOrder(container);
  });
});

function updateTabOrder(container) {
  const tabbableItems = tabbable(container);
  tabbableItems.forEach((el, index) => {
    el.setAttribute('tabindex', '0');
  });
}

В этом примере:

  • tabbable(container) возвращает актуальный список всех элементов, доступных для табуляции.
  • dragstart убирает элемент из последовательности, чтобы избежать прерывания навигации.
  • dragend пересчитывает порядок табуляции для всех видимых элементов.

Особенности при работе с вложенными контейнерами

Если перетаскиваемый элемент находится внутри нескольких вложенных контейнеров, важно учитывать скрытие родительских элементов. Tabbable исключает элементы, которые находятся внутри display: none или visibility: hidden. Это предотвращает потерю фокуса при манипуляциях с DOM, но требует пересчёта после каждого изменения структуры.

Рекомендации:

  • Всегда вызывать tabbable(container) после завершения всех DOM-операций.
  • Не полагаться на заранее вычисленный список элементов — состояние страницы может измениться.
  • Для сложных и динамичных интерфейсов можно поддерживать отдельный массив “активных” элементов, обновляемый через Tabbable.

Проверка доступности элементов для табуляции

Функция isTabbable(element) позволяет определить, будет ли элемент доступен для клавиши Tab после всех манипуляций с DOM и стилями. Это особенно полезно для перетаскиваемых компонентов, так как:

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

Пример использования:

if (isTabbable(draggedElement)) {
  draggedElement.focus();
}

Такой подход гарантирует, что фокус попадёт только на элемент, который реально доступен пользователю.


Итоговая логика управления фокусом

  1. Вычисление всех табулируемых элементов после каждой операции drag-and-drop.
  2. Временное исключение активного перетаскиваемого элемента из последовательности.
  3. Пересчет tabindex в соответствии с новым визуальным порядком.
  4. Проверка доступности элементов перед программным фокусом.

Использование Tabbable в сочетании с drag-and-drop позволяет сохранять интуитивную и доступную навигацию, даже при динамических изменениях интерфейса.