Интеграция в условную логику

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

Tabbable не полагается исключительно на стандартные селекторы CSS, такие как :focusable, а учитывает следующие факторы:

  • видимость элемента (display: none, visibility: hidden, opacity: 0)
  • отключённость элемента (disabled)
  • наличие атрибута tabindex и его значение
  • семантика элемента (<a> с href, <button>, <input> и т.д.)

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

Для интеграции Tabbable используется функция tabbable() из пакета:

import { tabbable } from 'tabbable';

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

Пояснения к коду:

  • tabbable(container) возвращает массив элементов, которые доступны для навигации через Tab в указанном контейнере.
  • Порядок элементов в массиве соответствует их порядку в DOM и логике табуляции браузера.
  • Элементы с tabindex="-1" не включаются, так как они исключены из стандартного порядка табуляции.

Использование с условной логикой

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

const radioButtons = document.querySelectorAll('input[name="mode"]');
const formFields = document.querySelector('#dynamic-fields');

radioButtons.forEach(radio => {
  radio.addEventListener('change', () => {
    const activeElements = tabbable(formFields);
    activeElements.forEach(el => el.disabled = true);

    if (radio.value === 'advanced') {
      const advancedInputs = formFields.querySelectorAll('.advanced');
      advancedInputs.forEach(el => el.disabled = false);
    }
  });
});

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

  • Выявление фокусируемых элементов выполняется динамически при каждом изменении состояния.
  • Прямое управление атрибутом disabled позволяет исключать или включать элементы в последовательность табуляции без изменения DOM-структуры.
  • Сочетание Tabbable с условной логикой обеспечивает точный контроль над доступностью интерфейса.

Фильтрация по пользовательским условиям

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

const focusableInputs = tabbable(formFields).filter(el => !el.classList.contains('readonly'));

Пояснения:

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

Интеграция с компонентами, управляемыми JavaScript

Tabbable хорошо сочетается с современными фреймворками (React, Vue, Svelte), где структура DOM может изменяться динамически. Основные принципы интеграции:

  1. Доступ к контейнеру: использовать рефы или querySelector для получения DOM-элемента.
  2. Реакция на обновления: пересчитывать фокусируемые элементы после изменений состояния или рендера.
  3. Условные блокировки: при необходимости исключать элементы из табуляции, изменяя tabindex или disabled.

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

import { useEffect, useRef, useState } from 'react';
import { tabbable } from 'tabbable';

function DynamicForm({ mode }) {
  const containerRef = useRef(null);

  useEffect(() => {
    const elements = tabbable(containerRef.current);
    elements.forEach(el => {
      if (mode === 'basic') {
        el.disabled = el.classList.contains('advanced');
      } else {
        el.disabled = false;
      }
    });
  }, [mode]);

  return (
    <div ref={containerRef}>
      <input className="basic" />
      <input className="advanced" />
    </div>
  );
}

Управление при сложных интерфейсах

Для интерфейсов с модальными окнами, аккордеонами или вкладками Tabbable позволяет реализовать:

  • Фокусировку только активной области: исключение элементов вне видимой панели.
  • Локальные циклы табуляции: ограничение табуляции рамками модального окна.
  • Адаптацию к изменению состояния: пересчет элементов при открытии/закрытии модального окна или вкладки.

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

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

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

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

Особенности реализации:

  • Обеспечивает циклическую табуляцию внутри модального окна.
  • Исключает элементы вне модального окна, предотвращая потерю фокуса.
  • Работает с динамически добавляемыми элементами, если пересчитать массив через tabbable(modal) после изменений DOM.

Советы по оптимизации

  • Кэширование массива элементов: если интерфейс статичен, массив фокусируемых элементов можно сохранять для уменьшения количества пересчетов.
  • Комбинация с MutationObserver: позволяет автоматически пересчитывать элементы при изменении DOM.
  • Избегание изменения DOM: предпочтительно управлять disabled и tabindex, а не удалять элементы, чтобы сохранить целостность интерфейса.

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