Закрытие по ESC и blur

Библиотека ally.js предоставляет мощный набор инструментов для управления доступностью веб-интерфейсов, включая управление фокусом, обработку событий клавиатуры и взаимодействие с интерактивными элементами. Одной из часто встречающихся задач является закрытие модальных окон, выпадающих меню и других интерактивных панелей при нажатии клавиши ESC или при потере фокуса (blur).

Управление клавишей ESC

Для обработки клавиши ESC в ally.js используется утилита tabEvent или стандартные слушатели событий клавиатуры, совмещённые с проверкой фокуса. Ключевой момент — корректно определить, что фокус находится внутри целевого элемента, прежде чем инициировать закрытие.

Пример базового подхода:

import ally from 'ally.js';

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

document.addEventListener('keydown', (event) => {
  if (event.key === 'Escape') {
    if (ally.hasFocus(modal)) {
      closeModal();
    }
  }
});

function closeModal() {
  modal.style.display = 'none';
  modal.setAttribute('aria-hidden', 'true');
}

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

  • Метод ally.hasFocus(element) проверяет, находится ли текущий фокус внутри указанного элемента.
  • Закрытие по ESC должно сопровождаться обновлением атрибутов ARIA, чтобы сохранить корректную доступность.
  • Использование event.key === 'Escape' предпочтительнее, чем проверка кодов клавиш, для лучшей поддержки разных устройств и браузеров.

Закрытие при потере фокуса (blur)

В ряде случаев необходимо закрывать элементы, когда пользователь кликает вне элемента или фокус покидает его. ally.js предоставляет утилиты для отслеживания фокуса, включая методы focusWithin и события focusout.

Пример реализации:

const dropdown = document.querySelector('#dropdown');

dropdown.addEventListener('focusout', (event) => {
  // Проверка, что фокус ушёл полностью из элемента и его потомков
  if (!dropdown.contains(event.relatedTarget)) {
    closeDropdown();
  }
});

function closeDropdown() {
  dropdown.style.display = 'none';
  dropdown.setAttribute('aria-hidden', 'true');
}

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

  • Событие focusout срабатывает на элементе, когда фокус покидает его или любой дочерний элемент.
  • event.relatedTarget указывает на элемент, который получает фокус после текущего. Проверка !dropdown.contains(event.relatedTarget) гарантирует, что закрытие произойдет только если фокус полностью покинул элемент.
  • Для элементов с динамическим контентом следует учитывать делегирование событий, чтобы правильно обрабатывать новые дочерние узлы.

Совмещение ESC и blur

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

  1. ESC закрывает активный элемент, независимо от позиции мыши.
  2. Потеря фокуса закрывает элемент, если пользователь кликает вне него.

Пример объединённого подхода:

function setupCloseBehavior(element, closeCallback) {
  document.addEventListener('keydown', (event) => {
    if (event.key === 'Escape' && ally.hasFocus(element)) {
      closeCallback();
    }
  });

  element.addEventListener('focusout', (event) => {
    if (!element.contains(event.relatedTarget)) {
      closeCallback();
    }
  });
}

setupCloseBehavior(modal, closeModal);
setupCloseBehavior(dropdown, closeDropdown);

Преимущества такого подхода:

  • Централизованная обработка позволяет легко масштабировать закрытие на несколько элементов интерфейса.
  • Использование ally.js гарантирует корректное определение фокуса и доступность для пользователей клавиатуры и вспомогательных технологий.
  • Минимизируется вероятность ошибок, связанных с динамическим DOM, поскольку проверка contains учитывает всех потомков.

Практические рекомендации

  • Всегда обновлять ARIA-атрибуты (aria-hidden, aria-expanded), чтобы состояние интерфейса было доступно для скринридеров.
  • Использовать ally.hasFocus вместо прямого сравнения document.activeElement, чтобы корректно обрабатывать элементы с вложенными интерактивными компонентами.
  • В случаях с множественными модальными окнами поддерживать стек открытых элементов и обрабатывать ESC только для верхнего уровня.
  • Для элементов с сложным поведением blur лучше комбинировать focusout с mousedown или click на документе для уверенного определения кликов вне элемента.

Интеграция с динамическим контентом

Ally.js позволяет безопасно работать с динамически добавляемыми элементами:

const observer = new MutationObserver(() => {
  const dynamicModals = document.querySelectorAll('.modal');
  dynamicModals.forEach(modal => setupCloseBehavior(modal, () => modal.style.display = 'none'));
});

observer.observe(document.body, { childList: true, subtree: true });

Такой подход обеспечивает автоматическое подключение ESC и blur поведения к новым модальным окнам, появляющимся после AJAX-загрузок или других динамических изменений DOM.

Закрытие элементов по ESC и blur в ally.js требует внимательной работы с фокусом, событиями и атрибутами ARIA, но обеспечивает высокую доступность и предсказуемое поведение интерфейсов.