Shadow DOM является важной частью современной разработки веб-компонентов. Он позволяет создавать изолированные DOM-деревья внутри элемента, обеспечивая инкапсуляцию стилей и поведения. Однако при работе с управлением фокусом и навигацией по элементам с клавиатуры возникает ряд особенностей. Библиотека Tabbable предоставляет инструменты для точного определения элементов, доступных для табуляции, включая элементы внутри 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:
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);
Tabbable учитывает следующие нюансы:
Слоты и <slot> Элементы,
вставленные через <slot>, учитываются в табуляции,
как если бы они находились в хосте. Это позволяет корректно обрабатывать
динамически вставленные компоненты.
Скрытые слоты Если элемент в
<slot> имеет display: none или
visibility: hidden, он исключается из списка.
Только видимые элементы Shadow DOM может
содержать элементы с opacity: 0 или
position: absolute; left: -9999px. Библиотека учитывает
только видимые на странице элементы, что важно для доступности.
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));
<button> →
<input> → внутренний
<button>.tabbable только на активных частях
страницы, чтобы минимизировать вычисления.tabbable для ускорения обхода.tabindex, слоты, вложенность компонентов.