Confirm dialog

Для работы с Focus-trap необходимо установить пакет через npm или yarn:

npm install focus-trap

или

yarn add focus-trap

Подключение в проекте на ES6:

import { createFocusTrap } from 'focus-trap';

Для использования в браузере без сборщика можно подключить через CDN:

<script src="https://unpkg.com/focus-trap/dist/focus-trap.umd.js"></script>

После подключения доступна глобальная функция focusTrap.createFocusTrap.


Основные принципы работы

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

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

  • Блокирует Tab и Shift+Tab, оставляя фокус внутри контейнера.
  • Автоматически перемещает фокус к первому или последнему фокусируемому элементу при необходимости.
  • Позволяет временно отключать ловушку или полностью её деактивировать.

Создание и активация ловушки

Для модального диалога подтверждения:

const confirmDialog = document.getElementById('confirm-dialog');

const focusTrap = createFocusTrap(confirmDialog, {
  initialFocus: '#confirm-yes',
  escapeDeactivates: true,
  clickOutsideDeactivates: true
});

// Активация при открытии диалога
function openDialog() {
  confirmDialog.style.display = 'block';
  focusTrap.activate();
}

// Деактивация при закрытии
function closeDialog() {
  focusTrap.deactivate();
  confirmDialog.style.display = 'none';
}

Параметры настройки:

  • initialFocus — элемент, на который будет установлен фокус при активации.
  • escapeDeactivates — позволяет закрывать ловушку при нажатии Escape.
  • clickOutsideDeactivates — закрывает ловушку при клике вне контейнера.
  • allowOutsideClick — если указано, клики по элементам вне контейнера допускаются без деактивации.
  • returnFocusOnDeactivate — возвращает фокус на элемент, который был активен до активации ловушки.

Управление поведением клавиш

Focus-trap умеет реагировать на клавиши Tab, Shift+Tab и Escape, предотвращая уход фокуса за пределы контейнера.

Для более гибкого контроля можно использовать события активации и деактивации:

const focusTrap = createFocusTrap(confirmDialog, {
  onActivate: () => console.log('Trap activated'),
  onDeactivate: () => console.log('Trap deactivated'),
});

Ловушка с несколькими диалогами

Если в приложении используется несколько модальных окон, важно управлять активными ловушками:

const dialogA = createFocusTrap('#dialogA', { escapeDeactivates: true });
const dialogB = createFocusTrap('#dialogB', { escapeDeactivates: true });

function openDialogA() {
  dialogB.deactivate(); // гарантируем, что другая ловушка отключена
  dialogA.activate();
}

function openDialogB() {
  dialogA.deactivate();
  dialogB.activate();
}

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


Стилизация и скрытие элементов

Для корректной работы Focus-trap элементы должны быть видимыми и доступными для фокуса. Чаще всего используется комбинация display: none и visibility: hidden при закрытии модального окна.

#confirm-dialog {
  display: none;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: white;
  padding: 2rem;
  box-shadow: 0 2px 10px rgba(0,0,0,0.3);
}

При открытии окна display меняется на block, после чего активируется ловушка.


Доступность и ARIA

Для модальных диалогов подтверждения важно использовать правильные атрибуты:

<div id="confirm-dialog" role="dialog" aria-modal="true" aria-labelledby="dialog-title">
  <h2 id="dialog-title">Подтвердите действие</h2>
  <button id="confirm-yes">Да</button>
  <button id="confirm-no">Нет</button>
</div>
  • role="dialog" сообщает вспомогательным технологиям о характере элемента.
  • aria-modal="true" указывает, что взаимодействие за пределами диалога временно недоступно.
  • aria-labelledby связывает заголовок с диалогом для скринридеров.

Расширенные возможности

Focus-trap поддерживает:

  • Таймеры и асинхронные операции: активацию можно откладывать до загрузки контента.
  • Селекторы фокуса: initialFocus можно задать как CSS-селектор, функцию или HTMLElement.
  • Фокус на закрытии: возвращает фокус на исходный элемент при деактивации.

Пример с асинхронной загрузкой контента:

async function openAsyncDialog() {
  await loadDialogContent();
  focusTrap.activate();
}

Практический пример confirm dialog

const dialog = document.getElementById('confirm-dialog');
const openBtn = document.getElementById('open-dialog');
const focusTrap = createFocusTrap(dialog, {
  initialFocus: '#confirm-yes',
  escapeDeactivates: true,
  clickOutsideDeactivates: true,
  returnFocusOnDeactivate: true
});

openBtn.addEventListener('click', () => {
  dialog.style.display = 'block';
  focusTrap.activate();
});

dialog.querySelector('#confirm-no').addEventListener('click', () => {
  focusTrap.deactivate();
  dialog.style.display = 'none';
});

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