Как зациклить перемещение по элементам с помощью Tab в пределах определенной группы элементов?
Настраиваю доступность с клавиатуры в проекте на Angular 11.
Есть такой сценарий использования, когда при нажатию на картинку появляется увеличенная во все окно картинка и по бокам стрелки вправо и влево, чтобы пролистать другие картинки (если они есть), кнопка закрытия этого просмотра и кнопка загрузки дополнительной картинки в эту карусель и удаления картинки.
Область, которая не заполнена картинкой или кнопками, т.е. фон - просто цвет, затемняющий перекрытый интерфейс.
При нажатию на "маленькую" картинку вызывается дополнительный Angular компонент, в котором как раз и отображается карусель с некоторыми кнопками. Эта карусель появляется поверх всех элементов.
Я сделал так, чтобы при ngAfterViewInit фокус в этой каруселе появлялся на нужной мне кнопке.
Проблема в том, что если нажимать Tab, то когда закончатся в этом компоненте элементы, на которых можно сфокусироваться, то дальше буду фокусироваться элементы, которые находятся "под" каруселью, т.е. будут фокусироваться элементы, перекрытого каруселью интерфейса.
Это, на мой взгляд, нелогичное поведение и я хочу, чтобы табом можно было перемещаться по элементам только в появившейся карусели, которая является отдельным Angular компонентом. Как это сделать?
Я вот так реализовал в моем компоненте это циклическое перемещение с помощью Tab'а
/** удержание фокуса внутри шаблона компонента */
@HostListener('document:keydown', ['$event'])
public tabCycle(event: KeyboardEvent): void {
// выделенный элемент в шаблоне
this.lastestFocused = event.target as HTMLElement;
// крайний первый элемент, такая конструкция нужна потому что стрелка влево может быть скрыта
const firstEdge = this.firstElement ? this.firstElement : this.secondElement;
// флаг того, что выделенный элемент находится внутри шаблона компонента
let isElementInsideComponent: boolean;
// если это первый граничный элемент и мы пытаемся выделить элемент вне шаблона компонента
if (this.lastestFocused.isEqualNode(firstEdge.nativeElement) && event.shiftKey && event.code === 'Tab') {
event.preventDefault();
this.lastElement.nativeElement.focus();
return;
}
// если это последний граничный элемент и мы пытаемся выделить элемент вне шаблона компонента
if (this.lastestFocused.isEqualNode(this.lastElement.nativeElement) && !event.shiftKey && event.code === 'Tab') {
event.preventDefault();
firstEdge.nativeElement.focus();
return;
}
// если это не один из граничных элементов
// определяем, что выделенный элемент находится внутри шаблона компонента
event.composedPath().forEach((item: HTMLElement) => {
if (item.tagName && item.tagName.toLowerCase() === this.host.nativeElement.tagName.toLowerCase()) {
isElementInsideComponent = true;
}
});
// если выделенный элемент вне шаблона компонента, то переводим фокус на первый граничный элемент
if (!isElementInsideComponent) {
firstEdge.nativeElement.focus();
}
}
Если убрать фокусировку в первых двух if'ах, то цикличности не будет, фокус будет просто останавливаться на граничных элементах.