Делегирование управления

Tabbable — это небольшая, но мощная библиотека на JavaScript, предназначенная для управления навигацией с клавиатуры и определения элементов, доступных для фокусировки через клавишу Tab. Она используется в интерфейсах, где критична доступность (accessibility), и позволяет разработчику программно получать список фокусируемых элементов, исключая скрытые или недоступные.

Ключевая функция библиотеки — tabbable(container, options), которая возвращает массив элементов внутри указанного контейнера, доступных для табуляции.

import { tabbable } from 'tabbable';

const focusableElements = tabbable(document.body);
console.log(focusableElements); // массив всех tabbable элементов на странице

Критерии фокусируемости

Tabbable использует строгие правила для определения элементов:

  1. Элемент должен быть видимым (display не none, visibility не hidden).
  2. Элемент не должен иметь атрибут tabindex="-1".
  3. Формы и интерактивные элементы (<input>, <button>, <select>, <textarea>, <a href="">) включаются по умолчанию.
  4. Элементы с кастомным tabindex="0" также считаются фокусируемыми.

Использование опций

Библиотека предоставляет объект опций, который позволяет гибко контролировать поведение:

  • includeContainer — включает сам контейнер в список, если он фокусируемый.
  • getShadowRoot — функция для обработки теневых DOM, возвращает shadow root для рекурсивного поиска.
  • strategy — может принимать значение "strict" или "default". В режиме "strict" игнорируются элементы с display: contents.

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

const tabbableElements = tabbable(document.querySelector('#menu'), {
  includeContainer: true,
  strategy: 'strict'
});

Делегирование фокуса

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

  1. Определение фокусируемых элементов внутри компонента.
  2. Навигация между ними через программное управление фокусом.
  3. Поддержка циклического обхода (looping) для модальных окон.

Пример реализации цикла внутри модального окна:

const modal = document.querySelector('#modal');
const elements = tabbable(modal);

modal.addEventListener('keydown', (event) => {
  if (event.key === 'Tab') {
    const first = elements[0];
    const last = elements[elements.length - 1];

    if (event.shiftKey && document.activeElement === first) {
      event.preventDefault();
      last.focus();
    } else if (!event.shiftKey && document.activeElement === last) {
      event.preventDefault();
      first.focus();
    }
  }
});

Интеграция с компонентами UI

Tabbable часто используется в крупных фреймворках и библиотечных решениях для создания:

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

Для сложных компонентов удобно использовать tabbable совместно с событиями focusin и focusout, чтобы динамически отслеживать активный элемент и предотвращать уход фокуса за пределы интерактивной области.

Рекурсивный поиск и Shadow DOM

Одним из преимуществ библиотеки является поддержка теневых DOM-деревьев. Через функцию getShadowRoot можно обрабатывать вложенные компоненты:

const allTabbables = tabbable(document.body, {
  getShadowRoot: (el) => el.shadowRoot
});

Библиотека автоматически обходит вложенные shadow root и возвращает полный список доступных элементов для табуляции, что особенно важно для современных веб-компонентов.

Фильтрация элементов

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

const tabbableElements = tabbable(document.body).filter(el => !el.classList.contains('ignore'));

Это позволяет делегировать управление фокусом строго нужным элементам, игнорируя скрытые или декоративные кнопки.

Производительность

Tabbable оптимизирована для больших DOM-деревьев:

  • Поиск производится только внутри указанного контейнера.
  • Рекурсивная обработка shadow DOM минимизируется за счет getShadowRoot.
  • Возвращаемый массив можно кешировать для повторного использования при динамических обновлениях DOM.

Практические рекомендации

  • Всегда использовать tabbable при работе с модальными окнами или диалогами, чтобы исключить уход фокуса за пределы компонента.
  • Комбинировать с focus() для программного управления порядком табуляции.
  • Использовать strategy: 'strict' при сложной верстке, чтобы избежать непредсказуемых фокусируемых элементов.
  • Для динамических интерфейсов повторно вызывать tabbable при изменении DOM, так как список элементов может меняться.

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