Индикаторы и пагинация

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

Основной принцип: Tabbable определяет, какие элементы являются интерактивными и могут быть сфокусированы. К ним относятся:

  • <input>, <textarea>, <select>, <button>
  • Элементы с атрибутом tabindex="0"
  • Элементы с contenteditable="true"
  • Ссылки <a> с атрибутом href

Элементы с tabindex="-1" исключаются из последовательности табуляции, но могут получать фокус программно.

Методы библиотеки

1. tabbable(container) Возвращает массив всех tabbable-элементов внутри контейнера.

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

import { tabbable } from 'tabbable';

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

console.log(focusableElements); // Массив элементов, доступных для Tab

Особенность: порядок элементов соответствует их визуальной последовательности на странице, что особенно важно для доступности.

2. isTabbable(element) Возвращает true, если элемент может быть сфокусирован через Tab, и false, если нет.

import { isTabbable } from 'tabbable';

const button = document.querySelector('button');
console.log(isTabbable(button)); // true или false

Этот метод полезен для динамической проверки доступности элементов, например, при изменении состояния UI.

3. focusable(container) Возвращает все элементы, которые могут получить фокус программно, включая элементы с tabindex="-1". Отличается от tabbable, который учитывает только Tab-последовательность.

import { focusable } from 'tabbable';

const allFocusable = focusable(document.body);

Работа с атрибутом tabindex

tabindex управляет доступностью элемента:

  • 0 — элемент доступен для Tab в стандартном порядке.
  • -1 — элемент исключен из последовательности Tab, но доступен программно.
  • Положительные числа — задают приоритет в Tab-последовательности, меньшие значения получают фокус раньше.

Tabbable автоматически учитывает эти значения при формировании массива фокусируемых элементов.

Интеграция с модальными окнами

Для модальных окон важно ограничить фокус внутри контейнера. Tabbable помогает реализовать focus trap:

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

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

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

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

Индикаторы и визуальные эффекты

Хотя Tabbable не отвечает за визуальное выделение, она тесно связана с реализацией индикаторов фокуса:

  • Выделение элемента рамкой (outline) при получении фокуса
  • Подсветка активного пункта пагинации
  • Активация клавишных шорткатов для выбранного элемента

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

Пример подсветки активного элемента:

const items = tabbable(document.querySelector('#pagination'));
items.forEach(item => {
  item.addEventListener('focus', () => item.classList.add('active'));
  item.addEventListener('blur', () => item.classList.remove('active'));
});

Пагинация и навигация

Для элементов пагинации Tabbable позволяет:

  1. Определить текущий фокус для перехода к следующей или предыдущей странице с клавиатуры.
  2. Управлять циклической навигацией между кнопками пагинации.
  3. Исключать недоступные или скрытые страницы из Tab-последовательности.

Пример управления пагинацией через клавиши стрелок:

const pages = tabbable(document.querySelector('#pagination'));
document.addEventListener('keydown', (e) => {
  const currentIndex = pages.indexOf(document.activeElement);
  if (e.key === 'ArrowRight') {
    e.preventDefault();
    const next = pages[(currentIndex + 1) % pages.length];
    next.focus();
  } else if (e.key === 'ArrowLeft') {
    e.preventDefault();
    const prev = pages[(currentIndex - 1 + pages.length) % pages.length];
    prev.focus();
  }
});

Взаимодействие с динамическим контентом

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

  • После рендеринга новых элементов достаточно снова вызвать tabbable(container) для обновления массива фокусируемых элементов.
  • Можно комбинировать с MutationObserver для автоматического отслеживания изменений DOM.
const observer = new MutationObserver(() => {
  const elements = tabbable(document.querySelector('#dynamic'));
  console.log(elements);
});

observer.observe(document.querySelector('#dynamic'), { childList: true, subtree: true });

Лучшие практики

  • Использовать Tabbable для определения контролируемой последовательности фокуса в сложных компонентах.
  • Не полагаться только на tabindex, а учитывать реальные возможности элемента быть tabbable.
  • Обновлять список tabbable-элементов при динамических изменениях DOM.
  • Комбинировать с CSS для визуального индикатора фокуса и активных элементов пагинации.

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