Библиотека Focus-trap предназначена для управления фокусом в веб-приложениях, особенно в случаях модальных окон, всплывающих панелей и кастомных интерфейсных компонентов. Основная задача — удержание фокуса внутри заданного контейнера, предотвращение ухода фокуса на элементы вне него и корректная работа клавиатурной навигации.
При работе с Shadow DOM необходимо учитывать, что
внутренние элементы инкапсулированы, а обычные методы поиска фокуса
(например, querySelectorAll) по дереву документа не видят
элементы внутри shadow-root. Focus-trap предоставляет возможности для
интеграции с shadow-root, но требует явного указания контейнера.
Для инициализации используется функция createFocusTrap,
которая принимает контейнер и объект с опциями. В случае Shadow DOM
контейнером должен быть корень shadow-root или элемент
внутри него.
import { createFocusTrap } from 'focus-trap';
const shadowHost = document.querySelector('#shadow-host');
const shadowRoot = shadowHost.shadowRoot;
const trap = createFocusTrap(shadowRoot, {
escapeDeactivates: true,
clickOutsideDeactivates: false,
initialFocus: shadowRoot.querySelector('button:first-of-type'),
});
Ключевые моменты:
Focus-trap использует метод tabbable
для определения фокусируемых элементов. В Shadow DOM стандартный поиск
не работает, поэтому требуется передавать опцию
tabbableOptions с кастомной функцией поиска элементов:
import { tabbable } from 'tabbable';
const trap = createFocusTrap(shadowRoot, {
tabbableOptions: {
getShadowRoot: () => shadowRoot
}
});
Важные детали:
Focus-trap предоставляет методы activate() и
deactivate(), которые контролируют жизненный цикл ловушки
фокуса.
trap.activate(); // активирует ловушку, фокус будет ограничен shadow-root
trap.deactivate(); // снимает ограничение
Особенности для Shadow DOM:
Shadow DOM инкапсулирует события, что может влиять на работу focus-trap:
document в Focus-trap по умолчанию использует
глобальный document, но внутри Shadow DOM корректнее
передавать кастомный объект с addEventListener и
removeEventListener, чтобы события правильно
отслеживались.Пример:
const trap = createFocusTrap(shadowRoot, {
document: shadowRoot,
escapeDeactivates: true
});
Если внутри shadow-root появляются новые интерактивные элементы после активации trap, их необходимо добавить в список tabbable:
trap.updateTabbableNodes(); // пересчитывает доступные элементы
Это особенно важно при создании модальных окон с асинхронным содержимым, где кнопки или поля ввода рендерятся после инициализации ловушки.
Shadow DOM часто используется в веб-компонентах. Для корректной работы focus-trap:
initialFocus, чтобы избежать
проблем с фокусом в Shadow DOM.Focus-trap в сочетании с Shadow DOM позволяет создавать полностью инкапсулированные интерактивные компоненты с корректной навигацией клавиатурой, обеспечивая доступность и предсказуемое поведение интерфейсов даже в сложных веб-компонентах.