Библиотека Tabbable предназначена для определения
всех элементов на странице, которые могут получать фокус через клавишу
Tab. В обычном HTML это включает кнопки, ссылки, поля
ввода, textarea и другие интерактивные элементы. Особый интерес
представляет работа с элементами, имеющими атрибут
contenteditable, так как они сами по себе не являются
стандартными интерактивными элементами, но могут быть фокусируемыми и
изменяемыми пользователем.
Элементы contenteditable позволяют редактировать текст
прямо в DOM без использования дополнительных форм. Однако для корректной
навигации через клавишу Tab требуется учитывать следующие
особенности:
contenteditable="true" может быть получателем фокуса, если
он имеет tabindex или является видимым. Без явно заданного
tabindex браузеры по-разному обрабатывают
Tab.contenteditable могут находиться вложенные элементы
(например, <b>, <i>,
<span>). Tabbable обрабатывает только сам контейнер,
но не отдельные вложенные узлы, если они не имеют собственного
tabindex.Методология библиотеки основана на поиске всех элементов, которые удовлетворяют критериям:
a,
button, input, textarea,
select).tabindex. Значение
>= 0 делает элемент tabbable, -1 исключает
из навигации через Tab.display !== 'none' и
visibility !== 'hidden'), иначе он не считается
tabbable.contenteditable="true" включаются в список tabbable, если
они видимы, даже при отсутствии tabindex.Пример использования:
import tabbable from 'tabbable';
const editableDiv = document.querySelector('#editor');
const tabbableElements = tabbable(editableDiv);
console.log(tabbableElements);
В данном примере tabbable вернет массив элементов внутри
#editor, которые могут принимать фокус, включая сам
#editor, если он contenteditable.
tabindextabindex позволяет явно управлять порядком фокусировки.
Для contenteditable это критически важно, если нужно, чтобы
пользователь переходил к области редактирования после других
интерактивных элементов. Правила:
tabindex="0" — элемент включается в естественный
порядок табуляции.tabindex="-1" — элемент исключается из табуляции, но
остаётся фокусируемым программно (.focus()).tabindex="1" и выше — задают приоритет в табуляции, что
может нарушить естественный порядок, поэтому рекомендуется избегать
больших положительных значений.<div contenteditable="true" tabindex="0">Редактируемый текст</div>
В этом случае элемент будет доступен при переходе по клавише
Tab.
Для сложных contenteditable блоков может потребоваться
исключение некоторых внутренних элементов из tabbable списка или
наоборот — включение элементов, которые по умолчанию не считаются
фокусируемыми. Библиотека позволяет использовать кастомные фильтры:
const tabbableElements = tabbable('#editor', {
includeContainer: true,
getShadowRoot: el => el.shadowRoot, // поддержка shadow DOM
});
Параметр includeContainer обеспечивает включение самого
контейнера contenteditable в список tabbable. Это важно,
когда навигация должна перейти непосредственно к редактируемому блоку, а
не только к вложенным интерактивным элементам.
Разные браузеры по-разному обрабатывают
contenteditable:
tabindex часто получают
фокус, но Safari может игнорировать их.<span> или
<div> внутри contenteditable) по
умолчанию не tabbable, что может вызвать проблемы при навигации.Использование tabbable стандартизирует поведение,
гарантируя единый результат в разных браузерах.
tabindex="0" для элементов
contenteditable, если требуется последовательная
навигация.includeContainer при вызове
tabbable, чтобы включить контейнер в список элементов для
фокусировки.tabbable при динамическом
создании контента, особенно если внутренние элементы добавляются через
JS.contenteditable (например, кнопок или ссылок) убедиться,
что они имеют корректный tabindex, чтобы не нарушить
последовательность табуляции.Tabbable может использоваться вместе с библиотеками
управления фокусом, модальными окнами и редакторами WYSIWYG. В таких
случаях важно:
tabbable после рендера контента, чтобы
получить актуальный список элементов.focus и keydown для
кастомной логики переключения табуляции внутри сложных
contenteditable блоков.getShadowRoot.import tabbable from 'tabbable';
const editorContainer = document.querySelector('#editor');
function focusNextEditable() {
const elements = tabbable(editorContainer, { includeContainer: true });
const activeIndex = elements.indexOf(document.activeElement);
const nextIndex = (activeIndex + 1) % elements.length;
elements[nextIndex].focus();
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
focusNextEditable();
}
});
В этом примере реализована кастомная логика навигации через
Tab внутри contenteditable, обеспечивая
циклическое перемещение фокуса по всем tabbable элементам, включая сам
редактируемый контейнер.