Uno dei vantaggi dell’utilizzo dei controlli Angular Material è che sono dotati di accessibilità integrata. In quanto tali, includono ruolo e aria-* attributi, oltre al supporto per la navigazione da tastiera standard. Se decidi di creare i tuoi widget, dovrai considerare anche l’accessibilità, altrimenti rischi di perdere alcuni utenti. Caso in questione, abbiamo recentemente trasformato alcuni elementi HTML in un componente Angular 11 personalizzato, che poi refactoring in un menu a discesa multilivello. In entrambi i casi, il nostro controllo non includeva alcuna funzione di accessibilità. Quindi, non c’era modo per gli screen reader di identificare il componente come un menu o di navigare tra i suoi elementi su laptop e computer desktop senza un mouse.
In questo tutorial, rimedieremo a queste limitazioni rendendo completamente accessibile il nostro menu a discesa a livello singolo originale.
Commutazione del menu in Angular
L’apertura e la chiusura del menu è il dominio del componente che ospita il componente del menu a discesa, ovvero il genitore. Nel frattempo, il componente del menu a discesa gestirà la navigazione da tastiera. Pertanto, possiamo esaminarli entrambi separatamente.
Nel app.component.html modello, aggiungeremo:
- il #menuTrigger variabile modello
- un tabindex attributo, per attivare il menu a tabstop
- un keydown gestore di eventi
<div class="menu-container" (click)="menuOpened = true" (mouseenter)="menuOpened = true" (mouseleave)="menuOpened = false" (keydown)="onKeyDown($event)"> <span #menuTrigger tabindex="0">Menu</span> <dropdown-menu [ngClass]="{ 'menu-opened': menuOpened }" class="dropdown-menu" [menuItems]="investmentClasses" (itemSelected)="onItemSelected($event)"> </dropdown-menu> </div>
Perché keydown per gli eventi del menu angolare?
Forse ti starai chiedendo perché sto vincolando il nostro gestore delle chiavi a keydown, al contrario di pressione dei tasti o keyup? Dei tre eventi chiave, preferisco keydown perché, a differenza del pressione dei tasti evento, il keydown l’evento viene attivato per tutte le chiavi, indipendentemente dal fatto che producano un valore di carattere. entrambi keydown e keyup gli eventi forniscono un codice che indica quale tasto viene premuto, mentre pressione dei tasti indica solo quale carattere è stato inserito. Dal momento che non elaboriamo affatto i caratteri, pressione dei tasti non è l’evento giusto per noi. Keyup è considerato da molti sviluppatori, me compreso, essere keydown fratello ingenuo. Riceve sempre il messaggio troppo tardi per fare qualcosa di utile con esso.
Il gestore di eventi onKeyDown()
Le tre chiavi che ascolteremo sono Fuga, accedere, e tab. Ecco cosa faremo per ciascuno:
- Fuga: Chiude il menu e imposta il focus sul trigger del menu.
- accedere: Aprire il menu e impostare il focus sulla prima voce di menu.
- tab: Chiude il menu (se aperto).
Puoi vedere la gestione dei tasti nel onKeyDown() metodo del app.component.ts file:
public onKeyDown(event: KeyboardEvent) { switch (event.key) { case 'Escape': this.menuOpened = false; this.menuTrigger.nativeElement.focus(); break; case 'Enter': this.menuOpened = true; // make sure that the menu is open before setting focus setTimeout(() => this.multiLevelDropDown.setFocusOnFirstMenuItem(), 1); break; case 'Tab': if (this.menuOpened) { this.menuOpened = false; } break; } }
Riferimento all’attivazione del menu e al componente del menu a discesa
Essendo un framework altamente supponente, Angular prescrive il proprio modo di fare riferimento agli elementi DOM usando il @ViewBambino decoratore di proprietà. Ho visto molti casi di document.getElementById(), document.getElementsByClassName(), ecc., nelle applicazioni angolari. Questo è un anti-pattern perché rompe l’architettura basata sui componenti di Angular. Ecco il modo corretto per fare riferimento al #menuTrigger e Menu a discesaComponente:
@ViewChild('menuTrigger', { read: ElementRef, static: true }) private menuTrigger : ElementRef; @ViewChild(DropdownMenuComponent) private dropDownMenu : DropdownMenuComponent;
Il primo @ViewBambino si riferisce a SPAGNOLO elemento a cui abbiamo aggiunto il #menuTrigger variabile modello. Dal momento che è solo un normale elemento HTML e non un corretto Componente angolare, lo leggeremo come un ElementRef. Il statico: vero La proprietà dei metadati dice ad Angular che è corretto risolvere i risultati della query prima che venga eseguito il rilevamento delle modifiche, poiché l’elemento non è dinamico.
Il secondo @ViewBambino fa riferimento al nostro menu a discesa come a Menu a discesaComponente class in modo da poter accedere ai suoi membri pubblici. Ogni volta che hai un solo componente in un modello, puoi omettere la variabile del modello e fare riferimento direttamente al componente.
Impostazione della messa a fuoco sulla prima voce di menu
Il DropdownMenuComponent è ben posizionato per porre l’accento sulla prima voce di menu poiché ha il menuArticoliRif riferimento a tutti i suoi elementi della voce di menu. Il Elenco query tipo fornisce il primo e scorso proprietà per facilitare l’accesso a tali elementi:
public setFocusOnFirstMenuItem(): void { if (this.menuItemsRef != null && this.menuItemsRef.first != null) { this.menuItemsRef.first.nativeElement.focus(); } }
Dovremmo anche aggiornare il CSS in modo che la voce di menu attiva sia evidenziata:
a:hover, a:focus { background-color: #a12f42; }
Attraversare le voci del menu
Utilizzeremo i tasti freccia su e giù per navigare tra le voci di menu. Nel modello dropdown-menu.component.html, aggiungeremo a tabindex di -1 per disabilitare il tab chiave e a keydown gestore, simile a quello del Componente dell’app:
<a tabindex="-1" #menuItems *ngFor="let mi of menuItems;let i = index" (click)="onClick($event, i)" (keydown)="onKeyDown($event, i)"> <mat-icon mat-list-icon>{{ mi.icon }}</mat-icon> <span class="dropdown-menu-item">{{ mi.value }}</span> </a>
Nel onKeyDown() gestore, possiamo accedere agli elementi del menu tramite indice e aggiungere o sottrarre 1 all’indice corrente per spostare lo stato attivo sull’elemento successivo. Con un piccolo sforzo in più, possiamo anche consentire alla navigazione del menu di tornare al primo o all’ultimo elemento quando non ci sono più elementi:
public onKeyDown(event: KeyboardEvent, index: number) { switch (event.key) { case "ArrowUp": event.stopPropagation(); (index === 0 ? this.menuItemsRef.last : this.menuItemsRef.get(index - 1) ).nativeElement.focus(); break; case "ArrowDown": event.stopPropagation(); (index === this.menuItemsRef.length - 1 ? this.menuItemsRef.first : this.menuItemsRef.get(index + 1) ).nativeElement.focus(); break; } }
La dimostrazione
Sopra codeandbox.io, troverai la demo con tutto il codice presentato in questo articolo.
Conclusione
Creare i tuoi controlli Angular è un’alternativa perfettamente valida ai controlli Material quando non vuoi tutti i campanelli e i fischietti che forniscono o stai cercando un comportamento e/o un aspetto diversi. La chiave è fornire le funzionalità di accessibilità che gli utenti si aspettano nelle moderne app Web.