MOBILE-4065 a11y: Fix pointer cancellation in tabs

main
Dani Palou 2022-11-21 11:19:22 +01:00
parent d66effda8e
commit 17d21a53b6
5 changed files with 32 additions and 11 deletions

View File

@ -16,6 +16,8 @@ export class CoreAriaRoleTab<T = unknown> {
componentInstance: T; componentInstance: T;
protected selectTabCandidate?: string;
constructor(componentInstance: T) { constructor(componentInstance: T) {
this.componentInstance = componentInstance; this.componentInstance = componentInstance;
} }
@ -23,9 +25,10 @@ export class CoreAriaRoleTab<T = unknown> {
/** /**
* A11y key functionality that prevents keyDown events. * A11y key functionality that prevents keyDown events.
* *
* @param tabFindIndex Tab findable index.
* @param e Event. * @param e Event.
*/ */
keyDown(e: KeyboardEvent): void { keyDown(tabFindIndex: string, e: KeyboardEvent): void {
if (e.key == ' ' || if (e.key == ' ' ||
e.key == 'Enter' || e.key == 'Enter' ||
e.key == 'Home' || e.key == 'Home' ||
@ -35,6 +38,11 @@ export class CoreAriaRoleTab<T = unknown> {
) { ) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
e.stopImmediatePropagation();
}
if (e.key == ' ' || e.key == 'Enter') {
this.selectTabCandidate = tabFindIndex;
} }
} }
@ -48,20 +56,25 @@ export class CoreAriaRoleTab<T = unknown> {
* End: When a tab has focus, moves focus to the last tab. * End: When a tab has focus, moves focus to the last tab.
* https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-2/tabs.html * https://www.w3.org/TR/wai-aria-practices-1.1/examples/tabs/tabs-2/tabs.html
* *
* @param tabFindIndex Tab finable index. * @param tabFindIndex Tab findable index.
* @param e Event. * @param e Event.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
keyUp(tabFindIndex: string, e: KeyboardEvent): void { keyUp(tabFindIndex: string, e: KeyboardEvent): void {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
if (e.key == ' ' || e.key == 'Enter') { if (e.key == ' ' || e.key == 'Enter') {
if (this.selectTabCandidate === tabFindIndex) {
this.selectTab(tabFindIndex, e); this.selectTab(tabFindIndex, e);
}
this.selectTabCandidate = undefined;
return; return;
} }
e.preventDefault();
e.stopPropagation();
const tabs = this.getSelectableTabs(); const tabs = this.getSelectableTabs();
let index = tabs.findIndex((tab) => tabFindIndex == tab.findIndex); let index = tabs.findIndex((tab) => tabFindIndex == tab.findIndex);

View File

@ -10,7 +10,7 @@
<ng-container *ngFor="let tab of tabs"> <ng-container *ngFor="let tab of tabs">
<ion-slide role="presentation" [id]="tab.id! + '-tab'" tabindex="-1" [class.selected]="selected == tab.id" <ion-slide role="presentation" [id]="tab.id! + '-tab'" tabindex="-1" [class.selected]="selected == tab.id"
class="{{tab.class}}"> class="{{tab.class}}">
<ion-tab-button (ionTabButtonClick)="selectTab(tab.id, $event)" (keydown)="tabAction.keyDown($event)" <ion-tab-button (ionTabButtonClick)="selectTab(tab.id, $event)" (keydown)="tabAction.keyDown(tab.id, $event)"
(keyup)="tabAction.keyUp(tab.id, $event)" [tab]="tab.page" [layout]="layout" role="tab" (keyup)="tabAction.keyUp(tab.id, $event)" [tab]="tab.page" [layout]="layout" role="tab"
[attr.aria-controls]="tab.id" [attr.aria-selected]="selected == tab.id" [attr.aria-controls]="tab.id" [attr.aria-selected]="selected == tab.id"
[tabindex]="selected == tab.id ? 0 : -1"> [tabindex]="selected == tab.id ? 0 : -1">

View File

@ -9,7 +9,7 @@
<ng-container *ngFor="let tab of tabs"> <ng-container *ngFor="let tab of tabs">
<ion-slide *ngIf="tab.enabled" role="presentation" [id]="tab.id! + '-tab'" [class.selected]="selected == tab.id" <ion-slide *ngIf="tab.enabled" role="presentation" [id]="tab.id! + '-tab'" [class.selected]="selected == tab.id"
class="{{tab.class}}"> class="{{tab.class}}">
<ion-tab-button (click)="selectTab(tab.id, $event)" (keydown)="tabAction.keyDown($event)" <ion-tab-button (click)="selectTab(tab.id, $event)" (keydown)="tabAction.keyDown(tab.id, $event)"
(keyup)="tabAction.keyUp(tab.id, $event)" [layout]="layout" role="tab" [attr.aria-controls]="tab.id" (keyup)="tabAction.keyUp(tab.id, $event)" [layout]="layout" role="tab" [attr.aria-controls]="tab.id"
[attr.aria-selected]="selected == tab.id" [tabindex]="selected == tab.id ? 0 : -1"> [attr.aria-selected]="selected == tab.id" [tabindex]="selected == tab.id ? 0 : -1">
<ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon> <ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon>

View File

@ -5,7 +5,7 @@
<core-user-menu-button *ngIf="loaded && tabsPlacement == 'side'" [alwaysShow]="true"></core-user-menu-button> <core-user-menu-button *ngIf="loaded && tabsPlacement == 'side'" [alwaysShow]="true"></core-user-menu-button>
<ion-tab-button *ngFor="let tab of tabs" (keydown)="tabAction.keyDown($event)" (keyup)="tabAction.keyUp(tab.page, $event)" <ion-tab-button *ngFor="let tab of tabs" (keydown)="tabAction.keyDown(tab.page, $event)" (keyup)="tabAction.keyUp(tab.page, $event)"
[hidden]="!loaded && tab.hide" [tab]="tab.page" [disabled]="tab.hide" layout="label-hide" class="{{tab.class}}" [hidden]="!loaded && tab.hide" [tab]="tab.page" [disabled]="tab.hide" layout="label-hide" class="{{tab.class}}"
[selected]="tab.page === selectedTab" [tabindex]="selectedTab == tab.page ? 0 : -1" [attr.aria-controls]="tab.id"> [selected]="tab.page === selectedTab" [tabindex]="selectedTab == tab.page ? 0 : -1" [attr.aria-controls]="tab.id">
<ion-icon class="core-tab-icon" [name]="tab.icon" aria-hidden="true"></ion-icon> <ion-icon class="core-tab-icon" [name]="tab.icon" aria-hidden="true"></ion-icon>
@ -17,8 +17,9 @@
</span> </span>
</ion-tab-button> </ion-tab-button>
<ion-tab-button (keydown)="tabAction.keyDown($event)" (keyup)="tabAction.keyUp(morePageName, $event)" [hidden]="!loaded" <ion-tab-button (keydown)="tabAction.keyDown(morePageName, $event)" (keyup)="tabAction.keyUp(morePageName, $event)"
[tab]="morePageName" layout="label-hide" [tabindex]="selectedTab == morePageName ? 0 : -1" [attr.aria-controls]="morePageName"> [hidden]="!loaded" [tab]="morePageName" layout="label-hide" [tabindex]="selectedTab == morePageName ? 0 : -1"
[attr.aria-controls]="morePageName">
<ion-icon class="core-tab-icon" name="ellipsis-horizontal" aria-hidden="true"></ion-icon> <ion-icon class="core-tab-icon" name="ellipsis-horizontal" aria-hidden="true"></ion-icon>
<ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label> <ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label>
<span class="sr-only">{{ 'core.more' | translate }}</span> <span class="sr-only">{{ 'core.more' | translate }}</span>

View File

@ -362,4 +362,11 @@ class CoreMainMenuRoleTab extends CoreAriaRoleTab<CoreMainMenuPage> {
return this.componentInstance.tabsPlacement == 'bottom'; return this.componentInstance.tabsPlacement == 'bottom';
} }
/**
* @inheritdoc
*/
selectTab(tabId: string): void {
this.componentInstance.mainTabs?.select(tabId);
}
} }