MOBILE-3745 tabs: Add keyboard a11y to tabs
This commit is contained in:
		
							parent
							
								
									43ed1d9917
								
							
						
					
					
						commit
						f108d0a8d8
					
				
							
								
								
									
										137
									
								
								src/core/classes/aria-role-tab.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/core/classes/aria-role-tab.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,137 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| export class CoreAriaRoleTab<T = unknown> { | ||||
| 
 | ||||
|     componentInstance: T; | ||||
| 
 | ||||
|     constructor(componentInstance: T) { | ||||
|         this.componentInstance = componentInstance; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A11y key functionallity that prevents keyDown events. | ||||
|      * | ||||
|      * @param e Event. | ||||
|      */ | ||||
|     keyDown(e: KeyboardEvent): void { | ||||
|         if (e.key == ' ' || | ||||
|             e.key == 'Enter' || | ||||
|             e.key == 'Home' || | ||||
|             e.key == 'End' || | ||||
|             (this.isHorizontal() && (e.key == 'ArrowRight' || e.key == 'ArrowLeft')) || | ||||
|             (!this.isHorizontal() && (e.key == 'ArrowUp' ||e.key == 'ArrowDown')) | ||||
|         ) { | ||||
|             e.preventDefault(); | ||||
|             e.stopPropagation(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * A11y key functionallity. | ||||
|      * | ||||
|      * Enter or Space: When a tab has focus, activates the tab, causing its associated panel to be displayed. | ||||
|      * Right Arrow: When a tab has focus: Moves focus to the next tab. If focus is on the last tab, moves focus to the first tab. | ||||
|      * Left Arrow: When a tab has focus: Moves focus to the previous tab. If focus is on the first tab, moves focus to the last tab. | ||||
|      * Home: When a tab has focus, moves focus to the first 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
 | ||||
|      * | ||||
|      * @param tabFindIndex Tab finable index. | ||||
|      * @param e Event. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     keyUp(tabFindIndex: string, e: KeyboardEvent): void { | ||||
|         if (e.key == ' ' || e.key == 'Enter') { | ||||
|             this.selectTab(tabFindIndex, e); | ||||
| 
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         e.preventDefault(); | ||||
|         e.stopPropagation(); | ||||
| 
 | ||||
|         const tabs = this.getSelectableTabs(); | ||||
| 
 | ||||
|         let index = tabs.findIndex((tab) => tabFindIndex == tab.findIndex); | ||||
| 
 | ||||
|         const previousKey = this.isHorizontal() ? 'ArrowLeft' : 'ArrowUp'; | ||||
|         const nextKey = this.isHorizontal() ? 'ArrowRight' : 'ArrowDown'; | ||||
| 
 | ||||
|         switch (e.key) { | ||||
|             case nextKey: | ||||
|                 index++; | ||||
|                 if (index >= tabs.length) { | ||||
|                     index = 0; | ||||
|                 } | ||||
|                 break; | ||||
|             case 'Home': | ||||
|                 index = 0; | ||||
|                 break; | ||||
|             case previousKey: | ||||
|                 index--; | ||||
|                 if (index < 0) { | ||||
|                     index = tabs.length - 1; | ||||
|                 } | ||||
|                 break; | ||||
|             case 'End': | ||||
|                 index = tabs.length - 1; | ||||
|                 break; | ||||
|             default: | ||||
|                 return; | ||||
|         } | ||||
| 
 | ||||
|         const tabId = tabs[index].id; | ||||
| 
 | ||||
|         // @todo Pages should match aria-controls id.
 | ||||
|         const tabElement = document.querySelector<HTMLIonTabButtonElement>(`ion-tab-button[aria-controls=${tabId}]`); | ||||
| 
 | ||||
|         tabElement?.focus(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Selects the tab. | ||||
|      * | ||||
|      * @param tabId Tab identifier. | ||||
|      * @param e Event. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     selectTab(tabId: string, e: Event): void { | ||||
|         //
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Return all the selectable tabs. | ||||
|      * | ||||
|      * @returns all the selectable tabs. | ||||
|      */ | ||||
|     getSelectableTabs(): CoreAriaRoleTabFindable[] { | ||||
|         return []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns if tabs are displayed horizontal or not. | ||||
|      * | ||||
|      * @returns Where the tabs are displayed horizontal. | ||||
|      */ | ||||
|     isHorizontal(): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export type CoreAriaRoleTabFindable = { | ||||
|     id: string; | ||||
|     findIndex: string; | ||||
| }; | ||||
| @ -31,6 +31,7 @@ import { Subscription } from 'rxjs'; | ||||
| 
 | ||||
| import { Platform, Translate } from '@singletons'; | ||||
| import { CoreSettingsHelper } from '@features/settings/services/settings-helper'; | ||||
| import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab'; | ||||
| 
 | ||||
| /** | ||||
|  * Class to abstract some common code for tabs. | ||||
| @ -89,10 +90,13 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft | ||||
|     protected slidesSwiperLoaded = false; | ||||
|     protected scrollElements: Record<string | number, HTMLElement> = {}; // Scroll elements for each loaded tab.
 | ||||
| 
 | ||||
|     tabAction: CoreTabsRoleTab<T>; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected element: ElementRef, | ||||
|     ) { | ||||
|         this.backButtonFunction = this.backButtonClicked.bind(this); | ||||
|         this.tabAction = new CoreTabsRoleTab(this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -632,6 +636,30 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Helper class to manage rol tab. | ||||
|  */ | ||||
| class CoreTabsRoleTab<T extends CoreTabBase> extends CoreAriaRoleTab<CoreTabsBaseComponent<T>> { | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     selectTab(tabId: string, e: Event): void { | ||||
|         this.componentInstance.selectTab(tabId, e); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getSelectableTabs(): CoreAriaRoleTabFindable[] { | ||||
|         return this.componentInstance.tabs.filter((tab) => tab.enabled).map((tab) => ({ | ||||
|             id: tab.id!, | ||||
|             findIndex: tab.id!, | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Data for each tab. | ||||
|  */ | ||||
|  | ||||
| @ -7,15 +7,28 @@ | ||||
|             </ion-col> | ||||
|             <ion-col class="ion-no-padding" size="10"> | ||||
|                 <ion-slides (ionSlideDidChange)="slideChanged()" [options]="slidesOpts" [dir]="direction" role="tablist" | ||||
|                     [attr.aria-label]="description" aria-hidden="false"> | ||||
|                     [attr.aria-label]="description"> | ||||
|                     <ng-container *ngFor="let tab of tabs"> | ||||
|                         <ion-slide [hidden]="!hideUntil" [attr.aria-selected]="selected == tab.id" class="tab-slide" role="tab" | ||||
|                             [attr.aria-label]="tab.title | translate" [attr.aria-controls]="tab.id" [id]="tab.id! + '-tab'" | ||||
|                             [tabindex]="selected == tab.id ? null : -1"> | ||||
|                             <ion-tab-button (ionTabButtonClick)="selectTab(tab.id, $event)" [tab]="tab.page" [layout]="layout" | ||||
|                                 class="{{tab.class}}" [attr.aria-label]="tab.title | translate"> | ||||
|                                 <ion-icon *ngIf="tab.icon" aria-hidden="true"></ion-icon> | ||||
|                                 <ion-label aria-hidden="true">{{ tab.title | translate}}</ion-label> | ||||
|                         <ion-slide | ||||
|                             role="presentation" | ||||
|                             [hidden]="!hideUntil" | ||||
|                             [id]="tab.id! + '-tab'" | ||||
|                             class="tab-slide" | ||||
|                             [class.selected]="selected == tab.id"> | ||||
|                             <ion-tab-button | ||||
|                                 (ionTabButtonClick)="selectTab(tab.id, $event)" | ||||
|                                 (keydown)="tabAction.keyDown($event)" | ||||
|                                 (keyup)="tabAction.keyUp(tab.id, $event)" | ||||
|                                 [tab]="tab.page" | ||||
|                                 [layout]="layout" | ||||
|                                 class="{{tab.class}}" | ||||
|                                 role="tab" | ||||
|                                 [attr.aria-controls]="tab.id" | ||||
|                                 [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-label>{{ tab.title | translate}}</ion-label> | ||||
|                                 <ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> | ||||
|                             </ion-tab-button> | ||||
|                         </ion-slide> | ||||
|  | ||||
| @ -45,7 +45,8 @@ import { CoreTabBase, CoreTabsBaseComponent } from '@classes/tabs'; | ||||
|  * Tab contents will only be shown if that tab is selected. | ||||
|  * | ||||
|  * @todo: Test RTL and tab history. | ||||
|  * @todo: This should behave like the split-view in relation to routing (maybe we could reuse some code from CoreItemsListManager). | ||||
|  * @todo: This should behave like the split-view in relation to routing (maybe we could reuse some code from | ||||
|  *  CorePageItemsListManager). | ||||
|  */ | ||||
| @Component({ | ||||
|     selector: 'core-tabs-outlet', | ||||
|  | ||||
| @ -6,15 +6,30 @@ | ||||
|         </ion-col> | ||||
|         <ion-col class="ion-no-padding" size="10"> | ||||
|             <ion-slides (ionSlideDidChange)="slideChanged()" [options]="slidesOpts" [dir]="direction" role="tablist" | ||||
|                 [attr.aria-label]="description" aria-hidden="false"> | ||||
|                 [attr.aria-label]="description"> | ||||
|                 <ng-container *ngFor="let tab of tabs"> | ||||
|                     <ion-slide *ngIf="tab.enabled" [hidden]="!hideUntil" [attr.aria-selected]="selected == tab.id" | ||||
|                         class="tab-slide {{tab.class}}" role="tab" [attr.aria-label]="tab.title | translate" | ||||
|                         [attr.aria-controls]="tab.id" [id]="tab.id! + '-tab'" [tabindex]="selected == tab.id ? null : -1" | ||||
|                         (click)="selectTab(tab.id, $event)" [attr.aria-label]="tab.title | translate"> | ||||
|                         <ion-icon *ngIf="tab.icon" [name]="tab.icon" aria-hidden="true"></ion-icon> | ||||
|                         <ion-label aria-hidden="true">{{ tab.title | translate}}</ion-label> | ||||
|                         <ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> | ||||
|                     <ion-slide | ||||
|                         *ngIf="tab.enabled" | ||||
|                         role="presentation" | ||||
|                         [hidden]="!hideUntil" | ||||
|                         class="tab-slide" | ||||
|                         [id]="tab.id! + '-tab'" | ||||
|                         [class.selected]="selected == tab.id"> | ||||
|                         <ion-tab-button | ||||
|                             (click)="selectTab(tab.id, $event)" | ||||
|                             (keydown)="tabAction.keyDown($event)" | ||||
|                             (keyup)="tabAction.keyUp(tab.id, $event)" | ||||
|                             class="{{tab.class}}" | ||||
|                             [layout]="layout" | ||||
|                             role="tab" | ||||
|                             [attr.aria-controls]="tab.id" | ||||
|                             [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-label>{{ tab.title | translate}}</ion-label> | ||||
|                             <ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> | ||||
|                         </ion-tab-button> | ||||
|                     </ion-slide> | ||||
|                 </ng-container> | ||||
|             </ion-slides> | ||||
|  | ||||
| @ -84,6 +84,7 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase { | ||||
| 
 | ||||
|         this.element.setAttribute('role', 'tabpanel'); | ||||
|         this.element.setAttribute('tabindex', '0'); | ||||
|         this.element.setAttribute('aria-hidden', 'true'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -113,6 +114,7 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase { | ||||
| 
 | ||||
|         this.tabElement = this.tabElement || document.getElementById(this.id + '-tab'); | ||||
|         this.tabElement?.setAttribute('aria-selected', 'true'); | ||||
|         this.element.setAttribute('aria-hidden', 'false'); | ||||
| 
 | ||||
|         this.loaded = true; | ||||
|         this.ionSelect.emit(this); | ||||
| @ -128,6 +130,8 @@ export class CoreTabComponent implements OnInit, OnDestroy, CoreTabBase { | ||||
|     unselectTab(): void { | ||||
|         this.tabElement?.setAttribute('aria-selected', 'false'); | ||||
|         this.element.classList.remove('selected'); | ||||
|         this.element.setAttribute('aria-hidden', 'true'); | ||||
| 
 | ||||
|         this.showHideNavBarButtons(false); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -30,6 +30,7 @@ | ||||
|             overflow: hidden; | ||||
| 
 | ||||
|             ion-tab-button { | ||||
|                 max-width: 100%; | ||||
|                 ion-label { | ||||
|                     font-size: 16px; | ||||
|                     font-weight: 400; | ||||
| @ -44,7 +45,8 @@ | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             &[aria-selected=true] { | ||||
|             &[aria-selected=true], | ||||
|             &.selected { | ||||
|                 color: var(--color-active); | ||||
|                 border-bottom-color: var(--border-color-active); | ||||
|                 ion-tab-button { | ||||
|  | ||||
| @ -45,6 +45,7 @@ import { CoreTabComponent } from './tab'; | ||||
| export class CoreTabsComponent extends CoreTabsBaseComponent<CoreTabComponent> implements AfterViewInit { | ||||
| 
 | ||||
|     @Input() parentScrollable = false; // Determine if the scroll should be in the parent content or the tab itself.
 | ||||
|     @Input() layout: 'icon-top' | 'icon-start' | 'icon-end' | 'icon-bottom' | 'icon-hide' | 'label-hide' = 'icon-hide'; | ||||
| 
 | ||||
|     @ViewChild('originalTabs') originalTabsRef?: ElementRef; | ||||
| 
 | ||||
|  | ||||
| @ -3,17 +3,38 @@ | ||||
|     <ion-tab-bar slot="bottom" [hidden]="hidden"> | ||||
|         <ion-spinner *ngIf="!loaded"></ion-spinner> | ||||
| 
 | ||||
|         <ion-tab-button (ionTabButtonClick)="tabClicked($event, tab.page)" [hidden]="!loaded && tab.hide" *ngFor="let tab of tabs" | ||||
|             [tab]="tab.page" [disabled]="tab.hide" layout="label-hide"  class="{{tab.class}}" | ||||
|             [attr.aria-label]="tab.title | translate"> | ||||
|         <ion-tab-button | ||||
|             *ngFor="let tab of tabs" | ||||
|             (click)="tabClicked($event, tab.page)" | ||||
|             (keydown)="tabAction.keyDown($event)" | ||||
|             (keyup)="tabAction.keyUp(tab.page, $event)" | ||||
|             [hidden]="!loaded && tab.hide" | ||||
|             [tab]="tab.page" | ||||
|             [disabled]="tab.hide" | ||||
|             layout="label-hide" | ||||
|             class="{{tab.class}}" | ||||
|             [tabindex]="selectedTab == tab.page ? 0 : -1" | ||||
|             [attr.aria-controls]="tab.id" | ||||
|             [attr.aria-label]="tab.title | translate" | ||||
|         > | ||||
|             <ion-icon [name]="tab.icon" aria-hidden="true"></ion-icon> | ||||
|             <ion-label aria-hidden="true">{{ tab.title | translate }}</ion-label> | ||||
|             <ion-badge *ngIf="tab.badge">{{ tab.badge }}</ion-badge> | ||||
|         </ion-tab-button> | ||||
| 
 | ||||
|         <ion-tab-button (ionTabButtonClick)="tabClicked($event, morePageName)" [hidden]="!loaded" [tab]="morePageName" layout="label-hide"> | ||||
|             <ion-icon name="fas-bars" [attr.aria-labelledby]="'mainmenu-tab-more'"></ion-icon> | ||||
|             <ion-label id="mainmenu-tab-more">{{ 'core.more' | translate }}</ion-label> | ||||
|         <ion-tab-button | ||||
|             (click)="tabClicked($event, morePageName)" | ||||
|             (keydown)="tabAction.keyDown($event)" | ||||
|             (keyup)="tabAction.keyUp(morePageName, $event)" | ||||
|             [hidden]="!loaded" | ||||
|             [tab]="morePageName" | ||||
|             layout="label-hide" | ||||
|             [tabindex]="selectedTab == morePageName ? 0 : -1" | ||||
|             [attr.aria-controls]="morePageName" | ||||
|             [attr.aria-label]="'core.more' | translate" | ||||
|         > | ||||
|             <ion-icon name="fas-bars" aria-hidden="true"></ion-icon> | ||||
|             <ion-label aria-hidden="true">{{ 'core.more' | translate }}</ion-label> | ||||
|         </ion-tab-button> | ||||
|     </ion-tab-bar> | ||||
| </ion-tabs> | ||||
|  | ||||
| @ -29,7 +29,6 @@ | ||||
|             height: 100%; | ||||
|             flex-direction: column; | ||||
|             ion-tab-button { | ||||
|                 display: contents; | ||||
|                 width: 100%; | ||||
|                 ion-badge { | ||||
|                     top: calc(50% - 20px); | ||||
|  | ||||
| @ -25,6 +25,8 @@ import { CoreMainMenu, CoreMainMenuProvider } from '../../services/mainmenu'; | ||||
| import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from '../../services/mainmenu-delegate'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { Translate } from '@singletons'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from '@classes/aria-role-tab'; | ||||
| 
 | ||||
| /** | ||||
|  * Page that displays the main menu of the app. | ||||
| @ -40,20 +42,22 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { | ||||
|     allHandlers?: CoreMainMenuHandlerToDisplay[]; | ||||
|     loaded = false; | ||||
|     showTabs = false; | ||||
|     tabsPlacement = 'bottom'; | ||||
|     tabsPlacement: 'bottom' | 'side' = 'bottom'; | ||||
|     hidden = false; | ||||
|     morePageName = CoreMainMenuProvider.MORE_PAGE_NAME; | ||||
|     selectedTab?: string; | ||||
| 
 | ||||
|     protected subscription?: Subscription; | ||||
|     protected keyboardObserver?: CoreEventObserver; | ||||
|     protected resizeFunction: () => void; | ||||
|     protected backButtonFunction: (event: BackButtonEvent) => void; | ||||
|     protected selectHistory: string[] = []; | ||||
|     protected selectedTab?: string; | ||||
|     protected firstSelectedTab?: string; | ||||
| 
 | ||||
|     @ViewChild('mainTabs') mainTabs?: IonTabs; | ||||
| 
 | ||||
|     tabAction: CoreMainMenuRoleTab; | ||||
| 
 | ||||
|     constructor( | ||||
|         protected route: ActivatedRoute, | ||||
|         protected changeDetector: ChangeDetectorRef, | ||||
| @ -61,6 +65,7 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { | ||||
|     ) { | ||||
|         this.resizeFunction = this.initHandlers.bind(this); | ||||
|         this.backButtonFunction = this.backButtonClicked.bind(this); | ||||
|         this.tabAction = new CoreMainMenuRoleTab(this); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -111,10 +116,11 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { | ||||
|                 const handler = handlers[i]; | ||||
| 
 | ||||
|                 // Check if the handler is already in the tabs list. If so, use it.
 | ||||
|                 const tab = this.tabs.find((tab) => tab.title == handler.title && tab.icon == handler.icon); | ||||
|                 const tab = this.tabs.find((tab) => tab.page == handler.page); | ||||
| 
 | ||||
|                 tab ? tab.hide = false : null; | ||||
|                 handler.hide = false; | ||||
|                 handler.id = handler.id || 'core-mainmenu-' + CoreUtils.getUniqueId('CoreMainMenuPage'); | ||||
| 
 | ||||
|                 newTabs.push(tab || handler); | ||||
|             } | ||||
| @ -246,3 +252,42 @@ export class CoreMainMenuPage implements OnInit, OnDestroy { | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Helper class to manage rol tab. | ||||
|  */ | ||||
| class CoreMainMenuRoleTab extends CoreAriaRoleTab<CoreMainMenuPage> { | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     selectTab(tabId: string, e: Event): void { | ||||
|         this.componentInstance.tabClicked(e, tabId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     getSelectableTabs(): CoreAriaRoleTabFindable[] { | ||||
|         const allTabs: CoreAriaRoleTabFindable[] = | ||||
|             this.componentInstance.tabs.filter((tab) => !tab.hide).map((tab) => ({ | ||||
|                 id: tab.id || tab.page, | ||||
|                 findIndex: tab.page, | ||||
|             })); | ||||
| 
 | ||||
|         allTabs.push({ | ||||
|             id: this.componentInstance.morePageName, | ||||
|             findIndex: this.componentInstance.morePageName, | ||||
|         }); | ||||
| 
 | ||||
|         return allTabs; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     isHorizontal(): boolean { | ||||
|         return this.componentInstance.tabsPlacement == 'bottom'; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -89,6 +89,11 @@ export interface CoreMainMenuHandlerToDisplay extends CoreDelegateToDisplay, Cor | ||||
|      * Hide tab. Used then resizing. | ||||
|      */ | ||||
|     hide?: boolean; | ||||
| 
 | ||||
|     /** | ||||
|      * Used to control tabs. | ||||
|      */ | ||||
|     id?: string; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | ||||
| @ -179,7 +179,7 @@ export class CoreMainMenuProvider { | ||||
|      * | ||||
|      * @return Tabs placement including side value. | ||||
|      */ | ||||
|     getTabPlacement(): string { | ||||
|     getTabPlacement(): 'bottom' | 'side' { | ||||
|         const tablet = !!(window.innerWidth && window.innerWidth >= 576 && (window.innerHeight >= 576 || | ||||
|                 ((CoreApp.isKeyboardVisible() || CoreApp.isKeyboardOpening()) && window.innerHeight >= 200))); | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user