Удаление элементов

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

Удаление элементов из DOM влияет на результаты функций tabbable, focusable и isTabbable. При изменении структуры документа необходимо понимать, каким образом библиотека пересчитывает список доступных для фокусировки узлов и какие последствия возникают для клавиатурной навигации.


Влияние удаления элементов на порядок фокуса

Библиотека определяет tabbable-элементы на основании текущего состояния DOM. При каждом вызове функции выполняется обход дерева и формируется новый список подходящих узлов.

Если элемент удалён из документа:

  • он больше не участвует в вычислении tabbable-списка;
  • его позиция в последовательности табуляции исчезает;
  • соседние элементы автоматически смещаются в порядке обхода.

Пример структуры:

<div id="container">
  <button id="first">Первый</button>
  <button id="second">Второй</button>
  <button id="third">Третий</button>
</div>

Получение списка tabbable:

import { tabbable } from 'tabbable';

const container = document.getElementById('container');
const elements = tabbable(container);

console.log(elements);

Результат:

[firstButton, secondButton, thirdButton]

Удаление второго элемента:

document.getElementById('second').remove();

Повторный вызов:

const updated = tabbable(container);
console.log(updated);

Результат:

[firstButton, thirdButton]

Библиотека не хранит внутреннего состояния списка элементов. Каждая операция вычисления выполняется заново, поэтому удалённые узлы автоматически исчезают из результатов.


Удаление элемента, содержащего текущий фокус

Особая ситуация возникает, когда удаляется элемент, на котором в данный момент находится фокус.

Поведение определяется браузером:

  1. Фокус теряется.
  2. document.activeElement обычно становится body.
  3. дальнейшая табуляция продолжается от начала документа.

Пример:

const button = document.getElementById('delete-me');
button.focus();

button.remove();

После удаления:

console.log(document.activeElement);

Вывод:

<body>

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


Обновление списка после динамических изменений

В большинстве интерфейсов удаление элементов происходит в рамках реактивных обновлений интерфейса. В таких случаях рекомендуется пересчитывать список tabbable-узлов после изменения DOM.

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

import { tabbable } from 'tabbable';

function updateFocusables(container) {
  return tabbable(container);
}

После удаления элемента:

item.remove();

const focusableElements = updateFocusables(container);

Такой подход гарантирует:

  • актуальный порядок табуляции;
  • отсутствие ссылок на несуществующие элементы;
  • корректную работу навигации.

Удаление контейнера с интерактивными элементами

Удаление родительского узла автоматически исключает из документа всех его потомков. Для Tabbable это означает, что весь набор элементов внутри контейнера исчезает из tabbable-результата.

Пример:

<div id="dialog">
  <button>OK</button>
  <button>Cancel</button>
</div>

Удаление диалога:

document.getElementById('dialog').remove();

Любой последующий вызов:

tabbable(document.body);

уже не включает кнопки из удалённого диалога.

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


Взаимодействие с фокус-ловушками

Библиотека Tabbable часто используется совместно с системами управления фокусом, например с библиотекой focus-trap. В таких системах список tabbable-элементов ограничивает область навигации клавишей Tab.

Удаление элемента внутри такой области требует пересчёта списка.

Пример логики:

const trapElements = tabbable(container);

После удаления:

removedElement.remove();

необходимо повторно выполнить:

const updatedTrap = tabbable(container);

Без пересчёта возможны проблемы:

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

Удаление элементов с положительным tabindex

Элементы с положительным tabindex имеют приоритет в порядке табуляции. При их удалении изменяется логика перехода между элементами.

Исходная разметка:

<button tabindex="3">A</button>
<button tabindex="1">B</button>
<button tabindex="2">C</button>
<button>D</button>

Порядок табуляции:

B → C → A → D

Удаление элемента C:

document.querySelector('[tabindex="2"]').remove();

Новый порядок:

B → A → D

Библиотека автоматически перестраивает список, учитывая оставшиеся значения tabindex.


Удаление скрытых элементов

Некоторые элементы удаляются не физически, а через изменение состояния отображения (display: none, visibility: hidden, hidden). В таких случаях Tabbable также исключает их из результата, поскольку проверяет видимость элементов.

Однако при фактическом удалении:

element.remove();

проверка становится быстрее, поскольку библиотека не анализирует стили отсутствующего узла.


Удаление элементов в виртуальных DOM-фреймворках

В современных приложениях элементы чаще удаляются через фреймворки, использующие виртуальный DOM:

  • React
  • Vue
  • Svelte
  • Angular

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

Пример React-компонента:

function List({ items }) {
  return (
    <div>
      {items.map(item => (
        <button key={item.id}>{item.label}</button>
      ))}
    </div>
  );
}

Если элемент исчезает из массива items, соответствующая кнопка удаляется из DOM.

Следующий вызов tabbable(container) автоматически отражает новое состояние интерфейса.


Наблюдение за удалением элементов

В сложных интерфейсах используется MutationObserver, позволяющий отслеживать изменения DOM и автоматически пересчитывать tabbable-элементы.

Пример наблюдателя:

const observer = new MutationObserver(() => {
  focusableElements = tabbable(container);
});

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

Когда элемент удаляется:

node.remove();

наблюдатель получает уведомление и выполняет обновление списка.


Удаление элементов во время навигации клавишей Tab

Иногда элемент удаляется в момент обработки события клавиатуры. Например, кнопка закрывает диалог и удаляет себя из DOM.

Пример:

button.addEventListener('keydown', (event) => {
  if (event.key === 'Tab') {
    button.remove();
  }
});

В этом случае:

  1. элемент исчезает из DOM;
  2. следующая операция поиска tabbable-элементов уже не включает его;
  3. фокус должен быть перенаправлен на ближайший доступный элемент.

Для корректного поведения часто применяется ручная установка фокуса:

nextElement.focus();

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

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

Пример проверки:

if (document.contains(element)) {
  element.focus();
}

Это предотвращает ситуации, когда код пытается работать с уже удалённым DOM-узлом.


Производительность при частом удалении

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

Поскольку Tabbable не кэширует список элементов, частые вызовы функции могут создавать дополнительную нагрузку.

Оптимизационные подходы:

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

Пример:

const focusable = tabbable(modalElement);

вместо

const focusable = tabbable(document.body);

Это значительно уменьшает объём обхода DOM.


Типичные ошибки при удалении элементов

Использование устаревшего списка

const list = tabbable(container);

// элемент удалён
element.remove();

// попытка использовать старый список
list[1].focus();

Если list[1] был удалён, возникнет ошибка.


Неправильная обработка потери фокуса

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


Отсутствие пересчёта в фокус-ловушках

Старый список элементов внутри модального окна может нарушить циклическую табуляцию.


Практический пример: удаляемый список

<ul id="todo">
  <li><button>Задача 1</button></li>
  <li><button>Задача 2</button></li>
  <li><button>Задача 3</button></li>
</ul>

Получение tabbable-элементов:

const container = document.getElementById('todo');
let buttons = tabbable(container);

Удаление задачи:

container.children[1].remove();

Обновление:

buttons = tabbable(container);

Теперь список содержит только две кнопки.


Ключевые особенности поведения

Tabbable при удалении элементов:

  • не хранит кэш элементов;
  • пересчитывает список при каждом вызове;
  • автоматически исключает удалённые узлы;
  • учитывает изменения порядка табуляции;
  • корректно работает с динамическими интерфейсами.

Эти свойства делают библиотеку устойчивой к изменениям DOM и удобной для использования в интерфейсах с интенсивной динамикой элементов.