Shadow DOM

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


Основные концепции Shadow DOM

Shadow DOM создаётся с помощью метода attachShadow:

const host = document.querySelector('#my-element');
const shadowRoot = host.attachShadow({ mode: 'open' });
shadowRoot.innerHTML = `
  <button>Кнопка 1</button>
  <button>Кнопка 2</button>
`;

Элементы внутри shadowRoot не видимы из внешнего DOM напрямую, что влияет на стандартные методы поиска фокусируемых элементов (querySelectorAll, getElementsByTagName). Tabbable учитывает это ограничение и позволяет рекурсивно обходить Shadow DOM для корректного списка элементов, доступных для навигации через Tab.


Использование Tabbable с Shadow DOM

Базовый метод Tabbable:

import { tabbable } from 'tabbable';

const shadowButtons = tabbable(shadowRoot);
console.log(shadowButtons);
  • tabbable(root) принимает любой корневой узел: document, элемент или ShadowRoot.
  • Возвращает массив элементов, которые могут получить фокус при навигации с клавиатуры.
  • Автоматически игнорирует элементы с display: none, visibility: hidden, disabled и элементы с tabindex="-1".

Рекурсивная обработка

Если Shadow DOM вложен несколько уровней, простой вызов tabbable на внешнем корне не включит внутренние элементы. Для полной обработки необходимо рекурсивно обходить все ShadowRoot:

function getAllTabbable(root) {
  let results = tabbable(root);
  
  const shadowHosts = Array.from(root.querySelectorAll('*'))
    .filter(el => el.shadowRoot);

  shadowHosts.forEach(host => {
    results = results.concat(getAllTabbable(host.shadowRoot));
  });

  return results;
}

const allFocusable = getAllTabbable(document.body);
console.log(allFocusable);
  • Каждое обнаруженное дерево Shadow DOM обрабатывается отдельно.
  • Сохраняется порядок табуляции, соответствующий визуальному порядку элементов на странице.

Особенности фокусируемых элементов в Shadow DOM

Tabbable учитывает следующие нюансы:

  1. Слоты и <slot> Элементы, вставленные через <slot>, учитываются в табуляции, как если бы они находились в хосте. Это позволяет корректно обрабатывать динамически вставленные компоненты.

  2. Скрытые слоты Если элемент в <slot> имеет display: none или visibility: hidden, он исключается из списка.

  3. Только видимые элементы Shadow DOM может содержать элементы с opacity: 0 или position: absolute; left: -9999px. Библиотека учитывает только видимые на странице элементы, что важно для доступности.

  4. Tabindex и фокусируемость Элементы с положительным tabindex будут первыми в порядке табуляции, независимо от их позиции в Shadow DOM. Это поведение полностью соответствует спецификации HTML и стандартным методам браузеров.


Примеры сложных сценариев

Вложенные компоненты с фокусируемыми элементами

const outer = document.createElement('div');
outer.attachShadow({ mode: 'open' }).innerHTML = `
  <button>Внешняя кнопка</button>
  <inner-component></inner-component>
`;

class InnerComponent extends HTMLElement {
  constructor() {
    super();
    const shadow = this.attachShadow({ mode: 'open' });
    shadow.innerHTML = `<input type="text" /><button>Внутренняя кнопка</button>`;
  }
}

customElements.define('inner-component', InnerComponent);

document.body.appendChild(outer);

console.log(getAllTabbable(document.body));
  • Рекурсивный обход позволяет получить все три элемента, несмотря на вложенность Shadow DOM.
  • Порядок табуляции сохраняется: внешний <button><input> → внутренний <button>.

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

  • Компоненты UI: модальные окна, вкладки, кастомные селекторы. Shadow DOM обеспечивает инкапсуляцию стилей, а Tabbable гарантирует правильный порядок табуляции.
  • Доступность (a11y): корректное управление фокусом внутри сложных компонентов делает интерфейсы доступными для клавиатурных пользователей.
  • Тестирование: автоматизированные тесты пользовательских интерфейсов могут использовать Tabbable для проверки фокусируемых элементов в Shadow DOM без зависимости от реализации компонентов.

Оптимизация работы с большим количеством Shadow DOM

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

Итоги по работе Tabbable с Shadow DOM

  • Tabbable поддерживает Shadow DOM нативно, но для сложных, многослойных деревьев рекомендуется рекурсивный обход.
  • Учитываются все ключевые аспекты доступности: видимость, tabindex, слоты, вложенность компонентов.
  • Правильное применение позволяет создавать сложные веб-компоненты с корректным управлением фокусом, полностью совместимые с требованиями WCAG.