Переиспользуемые компоненты

Focus-trap — это библиотека на JavaScript, предназначенная для управления фокусом внутри ограниченной области DOM, обычно модальных окон, выпадающих меню, сайдбаров и других интерактивных компонентов. Основная задача — предотвратить уход фокуса за пределы заданного контейнера при использовании клавиатуры, что критично для доступности интерфейсов.

Библиотека предоставляет удобный API для создания «ловушек фокуса» и их управления. Основной объект создаётся с помощью функции createFocusTrap:

import { createFocusTrap } from 'focus-trap';

const modalTrap = createFocusTrap('#modal', {
  onActivate: () => console.log('Trap activated'),
  onDeactivate: () => console.log('Trap deactivated'),
  escapeDeactivates: true,
  clickOutsideDeactivates: false,
  initialFocus: '#modal input:first-of-type'
});

Здесь ключевые параметры:

  • onActivate и onDeactivate — коллбэки при активации и деактивации ловушки.
  • escapeDeactivates — разрешает закрытие ловушки при нажатии Esc.
  • clickOutsideDeactivates — позволяет деактивировать ловушку при клике вне контейнера.
  • initialFocus — задаёт элемент, который получит фокус при активации.

Методы управления

Focus-trap предоставляет несколько методов для динамического контроля:

  • activate() — активирует ловушку фокуса.
  • deactivate() — деактивирует ловушку и возвращает фокус на элемент, который был активен до активации.
  • pause() и unpause() — временно приостанавливают работу ловушки без её полной деактивации. Это полезно для вложенных модальных окон.

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

modalTrap.activate();

// Внутри модального окна открылось другое меню
modalTrap.pause();
// Работа внутреннего меню с фокусом
modalTrap.unpause();

modalTrap.deactivate();

Настройка поведения фокуса

Focus-trap позволяет гибко контролировать:

  • allowOutsideClick — можно передать функцию, которая проверяет, разрешён ли клик за пределами контейнера.
  • fallbackFocus — элемент, на который фокус попадёт, если initialFocus не найден.
  • tabbableOptions — объект с параметрами для библиотеки tabbable, позволяющий исключить элементы из обхода фокуса или изменить порядок навигации.

Пример:

const trap = createFocusTrap('#menu', {
  fallbackFocus: '#menu',
  tabbableOptions: { displayCheck: 'none' }
});

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

Для переиспользуемых компонентов важно создавать ловушку фокуса один раз и управлять её активацией в зависимости от состояния UI. Это уменьшает дублирование кода и предотвращает ошибки при работе с несколькими модальными окнами.

Пример с динамическим модальным окном:

function openModal(modalId) {
  const modal = document.getElementById(modalId);
  const trap = createFocusTrap(modal, {
    initialFocus: modal.querySelector('input') || modal
  });
  trap.activate();
  modal.style.display = 'block';

  modal.querySelector('.close').addEventListener('click', () => {
    trap.deactivate();
    modal.style.display = 'none';
  });
}

Вложенные ловушки фокуса

В ситуациях, когда внутри модального окна открывается ещё один интерактивный компонент (например, подсказка или дополнительное меню), важно использовать pause и unpause для родительской ловушки, чтобы не нарушать пользовательский поток:

parentTrap.activate();
childTrap.activate(); // вложенная ловушка
parentTrap.pause();

// После закрытия дочернего компонента
childTrap.deactivate();
parentTrap.unpause();

Доступность и управление клавишами

Focus-trap обеспечивает правильное поведение клавиш:

  • Tab / Shift+Tab — циклическая навигация внутри контейнера.
  • Escape — деактивация (если escapeDeactivates: true).
  • Поддержка любых кастомных обработчиков для клавиатуры через коллбэки.

Особое внимание уделяется элементам, которые могут быть скрыты или временно недоступны. Опция tabbableOptions.displayCheck позволяет исключать скрытые элементы из обхода фокуса, что предотвращает неожиданное поведение при динамическом изменении DOM.

Рекомендации по проектированию компонентов

  • Создавать ловушку фокуса один раз при инициализации компонента и управлять её состоянием через activate и deactivate.
  • Использовать initialFocus для улучшения пользовательского опыта и доступности.
  • Применять pause и unpause для вложенных интерактивных элементов.
  • Гарантировать, что всегда есть элемент для fallbackFocus, чтобы предотвратить потерю фокуса.

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