Главная » Статьи » Обеспечение доступности стало проще с помощью Angular CDK

Обеспечение доступности стало проще с помощью Angular CDK

Обеспечение доступности стало проще с помощью Angular CDK

От автора: выделение фокусом и навигацию с помощью клавиатуры необходимо учитывать при разработке для обеспечения доступности. Многие пользователи при браузинге полагаются на клавиатуру. Если пользователь не использует другие механизмы ввода, им будет сложно взаимодействовать и перемещаться по сайту, если клавиатура будет недоступна для навигации.

В этой статье мы рассмотрим, как Angular CDK упрощает добавление этой возможности. Мы создадим список поиска и добавим параметр для выбора элементов с помощью клавиатуры. Вот демо-версия, которая иллюстрирует наш окончательный результат:

Начнем с создания компонента списка:

@Component({ selector: 'app-list-item', template: `<ng-content></ng-content>`
})
export class ListItemComponent { @Input() item;
}

Это простой компонент. Он получает элемент через ввод и отображает содержимое через ng-content. Теперь давайте создадим компонент приложения:

<input placeholder="Search..." (keyup)="onKeydown($event)"> <app-list-item *ngFor="let user of users" [item]="user"> {{user.name}}
</app-list-item>

Это основа. Мы перебираем массив пользователей и отображаем имя пользователя. Теперь у нас есть список для поиска, поэтому давайте добавим взаимодействие с клавиатурой.

Класс ListKeyManager

ListKeyManager управляет активными параметрами в списке элементов на основе взаимодействия с клавиатурой. Он предназначен для использования с компонентами, которые соответствуют шаблону role=»menu» или role=»listbox».
Любой компонент, который использует ListKeyManager, должен выполнять следующее:

Создавать запрос @ViewChildren для управляемых параметров.

Инициализировать ListKeyManager, передавая параметры.

Передать события клавиатуры из управляемого компонента в ListKeyManager.

Существует две разновидности ListKeyManager, ActiveDescendantKeyManager и FocusKeyManager. Начнем с ActiveDescendantKeyManager.

Класс ActiveDescendantKeyManager

import { ActiveDescendantKeyManager } from '@angular/cdk/a11y';
import { ENTER } from '@angular/cdk/keycodes'; @Component({ ...
})
export class AppComponent { @ViewChildren(ListItemComponent) items: QueryList<ListItemComponent>; users = [...] private keyManager: ActiveDescendantKeyManager<ListItemComponent>; ngAfterViewInit() { this.keyManager = new ActiveDescendantKeyManager(this.items).withWrap(); } onKeydown(event) { if (event.keyCode === ENTER) { this.model = this.keyManager.activeItem.item.name; } else { this.keyManager.onKeydown(event); } }
}

Мы создаем экземпляр класса ActiveDescendantKeyManager, который расширяет класс ListKeyManager и передает ему список запросов.

Мы также вызываем метод withWrap(), так как хотим закрыть ним список, когда в заданном направлении (циклическом) больше нет элементов.

Мы прослушиваем событие ввода через нажатие клавиши и передаем событие методу onKeyDown() объекта keyManager. Активный элемент будет определяться на основе навигации пользователя с помощью клавиатуры.

Когда пользователь нажимает кнопку ввода, мы можем захватить активный элемент с помощью свойства activeItem объекта keyManager.

Теперь давайте посмотрим, как настроить активный элемент. Каждый управляемый элемент должен реализовать интерфейс Highlightable:

interface Highlightable { setActiveStyles(): void; setInactiveStyles(): void;
}

Когда пользователь переходит, Angular вызывает метод setActiveStyles() для нового активного элемента опции и setInActiveStyles() для предыдущего элемента. Это позволяет нам составлять наш компонент.

export class ListItemComponent implements Highlightable { @Input() item; private _isActive = false; @HostBinding('class.active') get isActive() { return this._isActive; }; setActiveStyles() { this._isActive = true; }; setInactiveStyles() { this._isActive = false; }
}

Класс FocusKeyManager

Класс FocusKeyManager используется, когда элементы списка выделяются фокусом из браузера напрямую. Каждый управляемый элемент должен реализовывать интерфейс FocusableOption:

interface FocusableOption { focus(): void;
}

Когда пользователь перемещается по списку, Angular вызывает метод focus() для нового активного элемента списка.

this.keyManager = new FocusKeyManager<ListItemComponent>(this.items).withWrap();

@Component({ host: { 'tabindex': '-1' }, styles: [ `:host:focus { background: lightblue; color: #fff; }` ]
})
export class ListItemComponent { constructor(private host: ElementRef) {} focus() { this.host.nativeElement.focus(); }
}

<section (keydown)="onKeydown($event)" tabindex="0"> <app-list-item *ngFor="let user of users" [item]="user"> {{user.name}} </app-list-item> </section>

Выделение фокусом через табуляцию

Пропуск элементов

У нас есть два варианта пропуска элементов во время навигации. Первый — установить для свойства элемента disabled значение true. Например:

export class ListItemComponent implements Highlightable { ... @Input() disabled = false; ...
}

Второй вариант — определить метод skipPredicate(). Например:

this.keyManager = new ActiveDescendantKeyManager(this.items) .withWrap()
.skipPredicate(item => item.someCondition)

Режим typeahead

Мы можем включить режим typeahead. Это позволяет пользователям устанавливать активный элемент, введя его. Это означает, что когда пользователь выполняет поиск, первый результат автоматически устанавливается как активный. Давайте добавим функцию поиска.

<input (keyup)="onKeydown($event)" #input> <app-list-item *ngFor="let user of users | filter:'name':input.value;" [item]="user"> {{user.name}}
</app-list-item>

this.keyManager = new ActiveDescendantKeyManager(this.items) .withWrap()
.withTypeAhead();

export class ListItemComponent implements Highlightable { ... getLabel() { return this.item.name; } }

Обратите внимание, что в режиме headhead мы должны реализовать метод getLabel().

И, конечно же, помимо всего этого вы можете динамически устанавливать активный элемент, вызывая такие методы, как: setFirstItemActive(), setLastItemActive(), updateActiveItem(index), setActiveItem(item) и многое другое.

FocusTrap

Возможно, наступит время, когда вы обнаружите, что вам необходимо выделить фокусом узел DOM — таким образом, когда пользователи нажимают Tab или Shift + Tab или нажимают на них, они не могут выйти из определенного цикла фокусируемых элементов (это полезно для модалов , например).

Директива cdkTrapFocus удерживает фокус при нажатии клавиши табуляции внутри элемента. Давайте рассмотрим пример:

<section> <div cdkTrapFocus> <input placeholder="Email"> <input type="password" placeholder="Password"> <button type="submit">Submit</button> </div> <h1>Outside the trap</h1> <input placeholder="Outside" /> </section>

Это все. Ниже приведены примеры:

ActiveDescendantKeyManager

FocusKeyManager

Автор: Netanel Basal

Источник: https://netbasal.com/

Редакция: Команда webformyself.