Вообще говоря, вам следует избегать ловушек фокуса, за исключением модальных контекстов, таких как модальный диалог.
Предлагаемое здесь решение вместо этого создаст фокусную группу с ровингом Вкладка index , в которой вы можете перемещаться по фокусу с помощью клавиш со стрелками.Затем TAB покинет группу.
Имейте в виду, что вы используете фокус-группу только для шаблонов пользовательского интерфейса, в которых поведение можно ожидать.В стандарте ARIA упоминается клавиатурная навигация внутри компонентов :
[…] настоятельно рекомендуется использовать те же привязки клавиш, что и в аналогичных компонентах в обычных операционных системах с графическим интерфейсом, как показано в § 3. Шаблоны проектирования и виджеты .
Вы также можете, например, визуально отформатировать свои кнопки таким образом, чтобы они четко составляли одну группу, подобно Панели инструментов .
class focusGroup {
constructor(el, cyclical: false) {
this.cyclical = cyclical;
// store children sorted by data-tabindex attribute
this.children = Array.from(el.querySelectorAll('[data-tabindex]')).sort((el1, el2) => el1.getAttribute('data-tabindex') > el2.getAttribute('data-tabindex'));
// remove tab index for all children
this.children.forEach(childEl => childEl.setAttribute('tabindex', '-1'));
// make first child tabbable
this.children[0].setAttribute('tabindex', '0');
this.i = 0;
// bind arrow keys
el.addEventListener('keyup', e => {
if (e.key === 'ArrowRight' || e.key === 'ArrowDown') this.next();
if (e.key === 'ArrowLeft' || e.key === 'ArrowUp') this.prev();
});
}
next() {
if (this.i < this.children.length -1) this.i += 1
else if (this.cyclical) this.i = 0;
this.updateFocus();
}
prev() {
if (this.i > 0) this.i -= 1
else if (this.cyclical) this.i = this.children.length -1;
this.updateFocus();
}
updateFocus() {
this.children.forEach(el => el.setAttribute('tabindex', '-1'));
this.children[this.i].setAttribute('tabindex', '0');
this.children[this.i].focus();
}
}
document.querySelectorAll('.focus-trap:not(.cyclical)').forEach(el => new focusGroup(el));
document.querySelectorAll('.focus-trap.cyclical').forEach(el => new focusGroup(el, true));
button {
padding: 6px 8px;
margin: 12px 0;
}
.focus-trap{
padding: 24px;
background: rgba(150, 150, 48, .5);
}
.now-tabbable {
display: inline-flex;
padding: 15px 8px;
background: pink;
margin: 12px;
min-width: 45px;
justify-content: center;
}
<!--
.focus-traps:
- Are meant to declare that tab focus are order by the programmer.
- Focus-traps can be cyclical. currently denoted by class but up for any ideas data-attr etc.
.now-tabbable:
- Are meant to declare that this element is part of the tab flow.
-->
<div class="focus-trap">
<button data-tabindex="0">ZERO × escape</button>
<button data-tabindex="2">two</button>
<button data-tabindex="1">one</button>
<button data-tabindex="3">three</button>
</div>
<div class="focus-trap cyclical">
<button data-tabindex>four - B</button>
<button data-tabindex>four - C</button>
<button data-tabindex>four - A × escape</button>
</div>
<div>
<button class="now-tabbable">seven</button>
<div class="now-tabbable">five</div>
<div class="now-tabbable">six</div>
</div>
API для управления порядком табуляции, который выходит из потока с порядком DOM.
Визуальныйпорядок тогда нужно будет выровнять с порядком фокуса.В примере кода вы можете использовать data-tabindex="i"
для управления
Ловушками фокуса, которые МОГУТ быть циклическими и должны быть явно экранированы.
Вы можете вызвать класс иукажите второй аргумент как true
, чтобы установить обтекание или циклический порядок.
Метод для размещения элементов без вкладок в потоке табуляции.
Только элементы с атрибутом data-tabindex
будут фокусироваться.
Нет положительных значений tabIndex.
Вам нужно будет использоватьположительное значение data-tabindexes
, но пример кода всегда будет использовать только tabindex="0"
, чтобы сделать один отдельный элемент фокусируемым.Это означает, что если вы повторно войдете в группу с помощью вкладки, последний фокусированный элемент снова будет сфокусирован.