Сворачивание и разворачивание

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

Ключевые моменты работы с Tabbable:

  • Focusable vs Tabbable: Все tabbable-элементы являются focusable, но не все focusable-элементы являются tabbable. Например, элементы с tabindex="-1" можно программно фокусировать, но они не участвуют в обычной последовательности Tab.
  • Селекторы по умолчанию: Tabbable учитывает элементы <input>, <button>, <select>, <textarea>, <a> с атрибутом href, а также любые элементы с tabindex >= 0.
  • Учет состояния элемента: Элемент, скрытый через display: none или visibility: hidden, а также элементы с атрибутом disabled, исключаются из списка tabbable.

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

  1. tabbable(element, options) Возвращает массив элементов, которые могут принимать фокус внутри указанного родителя.

    import tabbable from 'tabbable';
    
    const container = document.getElementById('modal');
    const focusableElements = tabbable(container);

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

    • includeContainer — включить сам контейнер в проверку.
    • getShadowRoot — учитывать элементы внутри Shadow DOM.
  2. isTabbable(element) Проверяет, является ли конкретный элемент tabbable.

    import { isTabbable } from 'tabbable';
    
    const button = document.querySelector('button');
    if (isTabbable(button)) {
        button.focus();
    }
  3. focusFirst / focusLast Удобные вспомогательные функции для установки фокуса на первый или последний tabbable-элемент в контейнере. Особенно полезны при реализации модальных окон и аккордеонов.

    import { focusFirst, focusLast } from 'tabbable';
    
    focusFirst(container); // фокус на первый tabbable элемент
    focusLast(container);  // фокус на последний tabbable элемент

Сворачивание и разворачивание элементов

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

Подходы к реализации:

  • Изменение атрибута tabindex: При сворачивании блока всем вложенным элементам устанавливается tabindex="-1", чтобы исключить их из табуляции. При разворачивании значение восстанавливается.

    function setTabbableState(container, state) {
        const elements = container.querySelectorAll('*');
        elements.forEach(el => {
            if (state) {
                el.removeAttribute('tabindex');
            } else {
                el.setAttribute('tabindex', -1);
            }
        });
    }
  • Удаление элементов из DOM: Иногда элементы временно удаляются из DOM при сворачивании, что автоматически делает их недоступными для табуляции.

  • Использование библиотек для управления фокусом: Tabbable позволяет легко динамически проверять состояние элементов при каждом изменении видимости.

Примеры применения

  1. Аккордеон: Сворачиваемый блок должен скрывать все tabbable-элементы. При открытии блока следует автоматически установить фокус на первый элемент внутри.

    const accordionSection = document.querySelector('#section1');
    
    function toggleAccordion(section) {
        const isOpen = section.classList.toggle('open');
        if (isOpen) {
            focusFirst(section);
        } else {
            setTabbableState(section, false);
        }
    }
  2. Модальные окна: При открытии модального окна фокус блокируется внутри модального контейнера, предотвращая уход Tab за его пределы. При закрытии окно освобождает фокус.

    const modal = document.getElementById('modal');
    const triggerButton = document.getElementById('openModal');
    
    triggerButton.addEventListener('click', () => {
        modal.style.display = 'block';
        focusFirst(modal);
    });
    
    modal.addEventListener('keydown', (e) => {
        if (e.key === 'Tab') {
            const tabbables = tabbable(modal);
            if (e.shiftKey && document.activeElement === tabbables[0]) {
                tabbables[tabbables.length - 1].focus();
                e.preventDefault();
            } else if (!e.shiftKey && document.activeElement === tabbables[tabbables.length - 1]) {
                tabbables[0].focus();
                e.preventDefault();
            }
        }
    });

Советы по организации кода

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

Особенности интеграции с современными фреймворками

  • React / Vue: Tabbable удобно использовать внутри компонентов, где состояние видимости управляется через props или reactive переменные.
  • Shadow DOM: Функция getShadowRoot позволяет проверять элементы внутри shadow-корней, что важно для веб-компонентов.
  • Динамическая генерация элементов: При добавлении или удалении элементов в контейнере важно пересчитывать tabbable-элементы, иначе пользователь может столкнуться с неожиданным поведением Tab.

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