Навигация фокуса FocusNavigator
FocusNavigator ищет элементы, которые могут получить фокус, и программно перемещает фокус внутри DOM-контейнера. Используйте его, когда нужно управлять фокусом в панели, форме, меню или виджете без включения ловушки фокуса.
В Bitrix Framework за навигацию фокуса отвечает расширение ui.a11y. В нем доступны класс FocusNavigator и событие RESTORE_FOCUS_EVENT для возврата фокуса.
Подключить расширение
Если вы подключаете расширение из PHP, загрузите ui.a11y.
\Bitrix\Main\UI\Extension::load('ui.a11y');
Если вы работаете в модульном JavaScript, импортируйте FocusNavigator из ui.a11y.
import { FocusNavigator } from 'ui.a11y';
Если нужно обработать событие возврата фокуса, импортируйте также RESTORE_FOCUS_EVENT.
import { FocusNavigator, RESTORE_FOCUS_EVENT } from 'ui.a11y';
Найти элемент без перемещения фокуса
Методы поиска возвращают подходящий HTMLElement или null, если элемент не найден. Они не вызывают focus() и не меняют активный элемент страницы.
|
Метод |
Что возвращает |
|
|
Первый элемент внутри контейнера, доступный для фокуса. |
|
|
Последний элемент внутри контейнера, доступный для фокуса. |
|
|
Следующий элемент относительно текущего активного элемента или |
|
|
Предыдущий элемент относительно текущего активного элемента или |
Методы поиска не меняют tabindex контейнера и не вызывают событие возврата фокуса. Если подходящего элемента нет или фильтр accept отклонил все элементы, метод возвращает null.
import { FocusNavigator } from 'ui.a11y';
const container = document.querySelector('#settings-panel');
if (!container)
{
throw new Error('Settings panel was not found.');
}
const firstField = FocusNavigator.getFirst(container);
if (firstField)
{
firstField.classList.add('panel-field—first');
}
Переместить фокус
Методы перемещения находят элемент и вызывают для него focus(). Если подходящий элемент не найден, метод возвращает null и текущий фокус не меняется.
|
Метод |
Что делает |
Возвращает |
|
|
Перемещает фокус на первый подходящий элемент внутри контейнера. |
|
|
|
Перемещает фокус на последний подходящий элемент внутри контейнера. |
|
|
|
Перемещает фокус на следующий подходящий элемент. |
|
|
|
Перемещает фокус на предыдущий подходящий элемент. |
|
|
|
Перемещает фокус на контейнер. Если у контейнера нет |
|
|
|
Перемещает фокус на первый элемент, который совпадает с CSS-селектором и проходит параметры обхода. |
|
|
|
Перемещает фокус на переданный элемент. Если передан |
|
Проверяйте результат методов перемещения перед действиями с найденным элементом. Если метод вернул null, оставьте текущий фокус без изменений или выберите запасную точку фокуса, например контейнер.
Передавайте в focusBySelector() валидный CSS-селектор. Если селектор не совпал ни с одним подходящим элементом, метод возвращает null.
import { FocusNavigator } from 'ui.a11y';
const container = document.querySelector('#filter-panel');
if (!container)
{
throw new Error('Filter panel was not found.');
}
const focusedElement = FocusNavigator.focusBySelector(container, '[name="search"]');
if (!focusedElement)
{
FocusNavigator.focusContainer(container);
}
Передать параметры обхода
Параметры обхода задают начальную точку, направление и дополнительные правила выбора элементов.
Методы поиска и перемещения фокуса принимают необязательный объект FocusNavigatorOptions.
type FocusNavigatorOptions = {
from?: HTMLElement;
tabbableOnly?: boolean;
wrap?: boolean;
accept?: (el: HTMLElement) => boolean;
preventScroll?: boolean;
focusVisible?: boolean;
};
|
Параметр |
Тип |
По умолчанию |
Описание |
|
|
|
Текущий активный элемент внутри контейнера. |
Задает элемент, от которого начинается поиск следующего или предыдущего элемента. |
|
|
|
|
Учитывает только элементы, доступные для перехода по |
|
|
|
|
Разрешает циклический переход: после последнего элемента поиск продолжается с первого, а перед первым — с последнего. |
|
|
|
Все найденные элементы проходят фильтр. |
Фильтрует найденные элементы. Верните |
|
|
|
Поведение |
Передается в |
|
|
|
Поведение |
Передается в |
Если from не передан и текущий активный элемент находится вне контейнера, поиск следующего элемента начинается с начала контейнера. Для getPrevious() и focusPrevious() в такой ситуации подходящий предыдущий элемент не определяется, если не включен wrap.
Если from передан, используйте элемент из того же контейнера. Так следующий или предыдущий элемент будет выбран относительно ожидаемой точки.
import { FocusNavigator } from 'ui.a11y';
const container = document.querySelector('#toolbar');
if (!container)
{
throw new Error('Toolbar was not found.');
}
const currentButton = container.querySelector('[data-role="save"]');
if (!currentButton)
{
throw new Error('Save button was not found.');
}
FocusNavigator.focusNext(container, {
from: currentButton,
wrap: true,
preventScroll: true,
});
Используйте accept, если нужно исключить часть элементов из навигации без изменения их DOM-атрибутов.
import { FocusNavigator } from 'ui.a11y';
const container = document.querySelector('#actions-menu');
if (!container)
{
throw new Error('Actions menu was not found.');
}
FocusNavigator.focusFirst(container, {
accept: (element) => element.dataset.hiddenAction !== 'true',
});
Вернуть фокус
Метод restoreFocus(target, options?) возвращает фокус на переданный элемент. Перед вызовом focus() метод отправляет на этот элемент событие a11y:restore-focus. Если обработчик события вызывает preventDefault(), фокус не перемещается, а метод возвращает null.
Используйте метод restoreFocus(), когда после закрытия панели, меню или временного элемента нужно вернуть фокус на кнопку, которая открыла интерфейс.
import { FocusNavigator, RESTORE_FOCUS_EVENT } from 'ui.a11y';
const openButton = document.querySelector('#open-menu-button');
if (!openButton)
{
throw new Error('Open menu button was not found.');
}
openButton.addEventListener(RESTORE_FOCUS_EVENT, (event) => {
if (openButton.disabled)
{
event.preventDefault();
}
});
FocusNavigator.restoreFocus(openButton, {
preventScroll: true,
});
Контракты методов
|
Метод |
Параметры |
Результат |
Эффект и ограничения |
|
|
|
|
Только ищет элемент. Фокус и |
|
|
|
|
Только ищет элемент. Фокус и |
|
|
|
|
Если |
|
|
|
|
Если активный элемент вне контейнера и |
|
|
|
|
Находит первый подходящий элемент и вызывает для него |
|
|
|
|
Находит последний подходящий элемент и вызывает для него |
|
|
|
|
Перемещает фокус на следующий подходящий элемент. При |
|
|
|
|
Перемещает фокус на предыдущий подходящий элемент. При |
|
|
|
|
Перемещает фокус на контейнер. Если у контейнера нет |
|
|
|
|
Перемещает фокус на первый подходящий элемент по селектору. Если совпадений нет, фокус не меняется. |
|
|
|
|
Перемещает фокус на переданный элемент. Если передан |
|
|
|
|
Перед перемещением фокуса отправляет событие |
|
|
|
|
Возвращает активный элемент документа или активный элемент внутри доступного |
|
|
|
Объект обхода элементов. |
Учитывает |
Служебные методы
Используйте служебные методы, если ваш компонент строит собственную навигацию по фокусируемым элементам.
|
Метод |
Что делает |
|
|
Возвращает активный |
|
|
Создает объект обхода элементов внутри контейнера с учетом |
getActiveElement() проверяет активный элемент внутри доступного iframe. Если документ iframe недоступен, используйте активный элемент текущего документа как запасной сценарий.
createWalker() нужен для собственного обхода, когда стандартных методов getFirst(), getNext() или focusNext() недостаточно. Передайте tabbableOnly: true, если обход должен учитывать только элементы, доступные через Tab.
Связанные материалы
-
Ловушка фокуса FocusTrap — удержание фокуса внутри модального окна, диалога или выпадающего меню.
-
Объявления для скринридера LiveAnnouncer — передача сообщений через скринридер без перемещения фокуса.
-
Всплывающие окна и меню main.popup — всплывающее окно или выпадающее меню рядом с элементом страницы.
-
Системный диалог — модальное окно в актуальном системном оформлении.
-
Расширения — подключение JavaScript-расширений Bitrix Framework.