updateContainerElements()

Метод updateContainerElements() используется для динамического обновления контейнеров, внутри которых действует ловушка фокуса. В библиотеке focus-trap контейнеры определяют область DOM, в пределах которой пользователь может перемещать фокус с помощью клавиатуры. Когда структура интерфейса изменяется — например, при добавлении новых элементов, изменении модального окна или переключении активной панели — первоначально заданный список контейнеров может устареть.

updateContainerElements() позволяет изменить этот список без уничтожения текущей ловушки и создания новой.

Метод особенно важен в динамических интерфейсах: SPA-приложениях, сложных модальных системах, интерфейсах с вкладками, аккордеонами и условным рендерингом.


Контейнеры в механизме ловушки фокуса

Внутренний механизм focus-trap строится вокруг понятия контейнеров. Контейнер — это DOM-элемент, внутри которого разрешено перемещение фокуса.

При инициализации ловушки контейнеры передаются в функцию создания:

import { createFocusTrap } from 'focus-trap';

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

В этом примере контейнером является элемент #modal. Все фокусируемые элементы внутри него образуют замкнутую область навигации.

Поддерживается несколько контейнеров:

const trap = createFocusTrap([
  document.querySelector('#panel-left'),
  document.querySelector('#panel-right')
]);

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

Если DOM-структура изменяется (например, один контейнер удаляется, а другой добавляется), первоначальный список контейнеров перестает соответствовать реальному интерфейсу. В такой ситуации применяется updateContainerElements().


Сигнатура метода

trap.updateContainerElements(containerElements)

Параметр

Параметр Тип Описание
containerElements HTMLElement HTMLElement[]

Метод принимает:

  • один DOM-элемент;
  • массив DOM-элементов.

Переданные элементы полностью заменяют предыдущий список контейнеров.


Принцип работы

Внутри focus-trap выполняются несколько этапов:

  1. Получение нового списка контейнеров.
  2. Проверка их существования в DOM.
  3. Пересчет всех фокусируемых элементов внутри контейнеров.
  4. Обновление внутренних ссылок на границы ловушки.
  5. Сохранение активного состояния ловушки (если она активна).

Метод не:

  • деактивирует ловушку;
  • не пересоздает экземпляр trap;
  • не изменяет параметры конфигурации.

Обновляется исключительно область, в которой удерживается фокус.


Простое обновление контейнера

Интерфейс модального окна может изменять внутренний контент, полностью заменяя корневой контейнер.

const trap = createFocusTrap('#modal');
trap.activate();

const newContainer = document.querySelector('#modal-step-2');

trap.updateContainerElements(newContainer);

После вызова метода:

  • старая область ловушки больше не используется;
  • фокус ограничивается новым контейнером.

Использование нескольких контейнеров

Иногда интерфейс состоит из нескольких логически связанных областей. Например:

  • основное окно
  • панель действий
  • дополнительная панель управления
const modal = document.querySelector('#modal');
const toolbar = document.querySelector('#toolbar');

const trap = createFocusTrap([modal, toolbar]);
trap.activate();

Если интерфейс перестраивается:

const newPanel = document.querySelector('#side-panel');

trap.updateContainerElements([
  modal,
  newPanel
]);

После обновления фокус будет перемещаться внутри:

  • modal
  • side-panel

Панель toolbar исключается из ловушки.


Динамические интерфейсы

В современных интерфейсах элементы часто появляются и исчезают без перезагрузки страницы.

Типичные сценарии:

  • пошаговые модальные формы
  • динамические вкладки
  • условные панели настроек
  • лениво загружаемые компоненты

Рассмотрим пошаговую форму.

HTML

<div id="modal">
  <div id="step1">
    <button>Далее</button>
  </div>

  <div id="step2" hidden>
    <input type="text">
    <button>Готово</button>
  </div>
</div>

JavaScript

const step1 = document.querySelector('#step1');
const step2 = document.querySelector('#step2');

const trap = createFocusTrap(step1);
trap.activate();

document.querySelector('#step1 button').addEventListener('click', () => {
  step1.hidden = true;
  step2.hidden = false;

  trap.updateContainerElements(step2);
});

При переходе на второй шаг:

  • первый контейнер исключается;
  • новый становится активной областью фокуса.

Работа с условным рендерингом

В фреймворках (React, Vue, Svelte) DOM может изменяться после обновления состояния.

Пример в React-подобной логике:

useEffect(() => {
  trap.updateContainerElements(ref.current);
}, [currentStep]);

Когда компонент перерисовывается и контейнер меняется, ловушка получает обновлённый DOM-элемент.


Поведение при активной ловушке

Если ловушка уже активна, updateContainerElements() выполняет дополнительную проверку текущего фокуса.

Возможны два сценария:

1. Фокус находится внутри нового контейнера

Никаких изменений не требуется. Навигация продолжается нормально.

2. Фокус находится вне новых контейнеров

Focus-trap выполняет перемещение фокуса:

  • на первый доступный элемент
  • или на fallbackFocus, если он указан.

Пример конфигурации:

const trap = createFocusTrap('#modal', {
  fallbackFocus: '#modal'
});

Использование с динамическими списками

Контейнер может содержать элементы, которые появляются асинхронно.

const trap = createFocusTrap('#menu');
trap.activate();

После загрузки новых элементов:

loadMenuItems().then(() => {
  const menu = document.querySelector('#menu');
  trap.updateContainerElements(menu);
});

Это заставляет библиотеку пересканировать фокусируемые элементы.


Пересчёт tabbable-элементов

Focus-trap использует библиотеку tabbable для определения элементов, доступных через клавишу Tab.

Каждый вызов updateContainerElements() инициирует:

  • повторный поиск tabbable элементов
  • обновление границ цикла навигации

Именно поэтому метод применяется после значительных изменений DOM.


Сравнение с пересозданием ловушки

Иногда разработчики создают новый trap вместо обновления контейнера.

Неэффективный подход:

trap.deactivate();
trap = createFocusTrap(newContainer);
trap.activate();

Правильный способ:

trap.updateContainerElements(newContainer);

Преимущества:

  • отсутствие лишних операций
  • сохранение текущего состояния
  • отсутствие мерцания фокуса
  • более высокая производительность

Обработка пустых контейнеров

Если переданный контейнер:

  • не содержит фокусируемых элементов
  • не имеет tabindex

focus-trap может не иметь точки для установки фокуса.

Решение — использование fallbackFocus.

const trap = createFocusTrap('#modal', {
  fallbackFocus: '#modal'
});

Даже если контейнер пуст, фокус будет установлен на сам контейнер.


Проверка существования элементов

Перед обновлением контейнеров важно убедиться, что элементы существуют.

Неправильный пример:

trap.updateContainerElements(document.querySelector('#missing'));

Если элемент отсутствует, возможны ошибки.

Безопасный вариант:

const container = document.querySelector('#panel');

if (container) {
  trap.updateContainerElements(container);
}

Использование с портальными элементами

Некоторые интерфейсы (например, в React) используют порталы, где части модального окна рендерятся в разных частях DOM.

const trap = createFocusTrap([
  modalContent,
  portalFooter
]);

При изменении портала:

trap.updateContainerElements([
  modalContent,
  newPortalFooter
]);

Фокус остается ограниченным этими областями независимо от их положения в DOM.


Частые ошибки

Обновление до появления элемента

trap.updateContainerElements(newElement);

если newElement еще не добавлен в DOM.

Следует выполнять обновление после рендера.


Передача NodeList

Метод ожидает массив элементов, а не NodeList.

Неправильно:

trap.updateContainerElements(document.querySelectorAll('.panel'));

Правильно:

trap.updateContainerElements(
  Array.from(document.querySelectorAll('.panel'))
);

Смешивание строк и элементов

Метод принимает DOM-элементы, а не селекторы.

Неправильно:

trap.updateContainerElements('#modal');

Правильно:

trap.updateContainerElements(document.querySelector('#modal'));

Производительность

updateContainerElements() выполняет пересканирование DOM внутри контейнеров.

На больших интерфейсах частые вызовы могут привести к избыточной нагрузке.

Рекомендуемые практики:

  • вызывать метод только после значительных изменений
  • избегать обновления при каждом рендере
  • использовать batching изменений

Практическое применение в модальных системах

В сложных модальных интерфейсах контейнер может изменяться при:

  • переключении вкладок
  • открытии вложенных панелей
  • загрузке динамических форм
function switchTab(tabElement) {
  document.querySelectorAll('.tab').forEach(el => {
    el.hidden = true;
  });

  tabElement.hidden = false;

  trap.updateContainerElements(tabElement);
}

Фокус всегда ограничен активной вкладкой.


Роль метода в архитектуре доступности

Главная задача focus-trap — предотвращать выход фокуса за пределы активного интерфейса, например модального окна.

updateContainerElements() обеспечивает корректную работу этой логики в динамических интерфейсах, где DOM постоянно изменяется.

Благодаря этому:

  • клавиатурная навигация остается предсказуемой
  • экранные дикторы корректно воспринимают активную область
  • интерфейс соответствует требованиям доступности (WCAG)

Метод является ключевым инструментом управления ловушкой фокуса в приложениях со сложной и изменяемой структурой DOM.