Защитное программирование

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

Ключевые моменты:

  • Фокусируемость зависит не только от тега элемента, но и от его состояния (disabled, hidden, aria-hidden, tabindex).
  • Порядок табуляции определяется атрибутом tabindex. Элементы с tabindex="0" участвуют в естественном порядке, отрицательные значения исключают элемент из порядка табуляции, положительные — задают явный приоритет.
  • Элементы <a> считаются фокусируемыми только при наличии атрибута href. Кнопки <button> и поля ввода <input> фокусируются по умолчанию.

Установка и базовое использование

Установка через npm:

npm install tabbable

Импорт в проект:

import { tabbable } from 'tabbable';

Простейший пример получения всех фокусируемых элементов внутри контейнера:

const container = document.getElementById('form-container');
const focusableElements = tabbable(container);
console.log(focusableElements);

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

Работа с параметрами

Функция tabbable поддерживает объект конфигурации с несколькими ключевыми параметрами:

  • includeContainer (boolean) — включает или исключает сам контейнер из списка фокусируемых элементов.
  • getShadowRoot (function) — позволяет работать с веб-компонентами и теневыми DOM-деревьями.
  • displayCheck (function) — пользовательская проверка видимости элемента, может учитывать CSS-свойства display, visibility и opacity.

Пример с пользовательской проверкой видимости:

const visibleElements = tabbable(container, {
  displayCheck: (node) => window.getComputedStyle(node).visibility !== 'hidden'
});

Особенности работы с Shadow DOM

Tabbable корректно обрабатывает элементы внутри Shadow DOM, что позволяет создавать доступные веб-компоненты. Использование параметра getShadowRoot гарантирует, что вложенные элементы будут учитываться:

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

Библиотека рекурсивно обходит все вложенные тени, собирая элементы в порядке табуляции.

Взаимодействие с ARIA и скрытыми элементами

Tabbable учитывает ARIA-атрибуты и CSS-свойства, которые могут скрывать элемент:

  • aria-hidden="true" исключает элемент из списка.
  • display: none и visibility: hidden делают элемент недоступным для фокуса.
  • Элементы с tabindex="-1" игнорируются при навигации по клавише Tab, но могут быть сфокусированы программно (element.focus()).

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

  1. Модальные окна — библиотека позволяет ограничить фокус внутри модального окна, создавая focus trap:
const modal = document.getElementById('modal');
const focusable = tabbable(modal);
focusable[0].focus();

modal.addEventListener('keydown', (e) => {
  if (e.key === 'Tab') {
    const first = focusable[0];
    const last = focusable[focusable.length - 1];
    
    if (!e.shiftKey && document.activeElement === last) {
      e.preventDefault();
      first.focus();
    } else if (e.shiftKey && document.activeElement === first) {
      e.preventDefault();
      last.focus();
    }
  }
});
  1. Кастомные компоненты — кнопки, селекты и выпадающие меню могут использовать tabbable для контроля навигации и предотвращения выхода фокуса за пределы компонента.

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

Важные рекомендации

  • Всегда использовать tabbable в сочетании с логикой ограничения фокуса при работе с модальными окнами или всплывающими меню.
  • Проверять наличие disabled, hidden и aria-hidden для исключения невидимых элементов.
  • При работе с динамическим контентом обновлять массив фокусируемых элементов после изменения DOM.

Оптимизация и безопасность

  • Избегать постоянного вызова tabbable на больших контейнерах в цикле, лучше кэшировать результат при неизменяемом DOM.
  • Для компонентов с большим количеством вложенных элементов использовать getShadowRoot, чтобы избежать потери элементов в Shadow DOM.
  • Обеспечивать корректный порядок tabindex, чтобы элементы с положительным tabindex не нарушали естественную последовательность и доступность.

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