2021-02-01 12:32:10 +01:00
|
|
|
// (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.
|
|
|
|
|
|
|
|
import {
|
|
|
|
Component,
|
|
|
|
Input,
|
|
|
|
Output,
|
|
|
|
EventEmitter,
|
|
|
|
OnInit,
|
|
|
|
OnChanges,
|
|
|
|
OnDestroy,
|
|
|
|
AfterViewInit,
|
|
|
|
ViewChild,
|
2021-03-05 14:48:00 +01:00
|
|
|
SimpleChange,
|
2022-03-31 11:02:52 +02:00
|
|
|
ElementRef,
|
2021-02-01 12:32:10 +01:00
|
|
|
} from '@angular/core';
|
|
|
|
import { IonSlides } from '@ionic/angular';
|
2022-03-30 16:14:30 +02:00
|
|
|
import { BackButtonEvent } from '@ionic/core';
|
2021-02-01 12:32:10 +01:00
|
|
|
import { Subscription } from 'rxjs';
|
|
|
|
|
2022-06-20 18:19:18 +02:00
|
|
|
import { Translate } from '@singletons';
|
2021-03-02 16:08:40 +01:00
|
|
|
import { CoreSettingsHelper } from '@features/settings/services/settings-helper';
|
2021-05-04 17:19:57 +02:00
|
|
|
import { CoreAriaRoleTab, CoreAriaRoleTabFindable } from './aria-role-tab';
|
2022-03-14 13:09:41 +01:00
|
|
|
import { CoreEventObserver } from '@singletons/events';
|
2022-03-22 17:22:14 +01:00
|
|
|
import { CoreDom } from '@singletons/dom';
|
2022-03-30 15:51:55 +02:00
|
|
|
import { CoreUtils } from '@services/utils/utils';
|
|
|
|
import { CoreError } from './errors/error';
|
2022-03-31 11:02:52 +02:00
|
|
|
import { CorePromisedValue } from './promised-value';
|
|
|
|
import { AsyncComponent } from './async-component';
|
|
|
|
import { CoreComponentsRegistry } from '@singletons/components-registry';
|
2022-06-20 18:19:18 +02:00
|
|
|
import { CorePlatform } from '@services/platform';
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Class to abstract some common code for tabs.
|
|
|
|
*/
|
|
|
|
@Component({
|
|
|
|
template: '',
|
|
|
|
})
|
2022-03-31 11:02:52 +02:00
|
|
|
export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, AfterViewInit, OnChanges, OnDestroy, AsyncComponent {
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
// Minimum tab's width.
|
|
|
|
protected static readonly MIN_TAB_WIDTH = 107;
|
|
|
|
|
2021-07-27 14:31:18 +02:00
|
|
|
@Input() selectedIndex = 0; // Index of the tab to select.
|
2021-02-01 12:32:10 +01:00
|
|
|
@Input() hideUntil = false; // Determine when should the contents be shown.
|
|
|
|
@Output() protected ionChange = new EventEmitter<T>(); // Emitted when the tab changes.
|
|
|
|
|
|
|
|
@ViewChild(IonSlides) protected slides?: IonSlides;
|
|
|
|
|
|
|
|
tabs: T[] = []; // List of tabs.
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
hideTabs = false;
|
2021-02-01 12:32:10 +01:00
|
|
|
selected?: string; // Selected tab id.
|
|
|
|
showPrevButton = false;
|
|
|
|
showNextButton = false;
|
|
|
|
maxSlides = 3;
|
|
|
|
numTabsShown = 0;
|
|
|
|
direction = 'ltr';
|
|
|
|
description = '';
|
|
|
|
slidesOpts = {
|
|
|
|
initialSlide: 0,
|
|
|
|
slidesPerView: 3,
|
|
|
|
centerInsufficientSlides: true,
|
2022-03-30 15:51:55 +02:00
|
|
|
threshold: 10,
|
2021-02-01 12:32:10 +01:00
|
|
|
};
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
protected slidesElement?: HTMLIonSlidesElement;
|
2021-02-01 12:32:10 +01:00
|
|
|
protected initialized = false;
|
|
|
|
|
2022-03-14 13:09:41 +01:00
|
|
|
protected resizeListener?: CoreEventObserver;
|
2021-02-01 12:32:10 +01:00
|
|
|
protected isDestroyed = false;
|
|
|
|
protected isCurrentView = true;
|
|
|
|
protected shouldSlideToInitial = false; // Whether we need to slide to the initial slide because it's out of view.
|
|
|
|
protected hasSliddenToInitial = false; // Whether we've already slidden to the initial slide or there was no need.
|
|
|
|
protected selectHistory: string[] = [];
|
|
|
|
|
|
|
|
protected firstSelectedTab?: string; // ID of the first selected tab to control history.
|
2021-02-25 12:16:23 +01:00
|
|
|
protected backButtonFunction: (event: BackButtonEvent) => void;
|
2022-02-24 10:42:12 +01:00
|
|
|
// Swiper 6 documentation: https://swiper6.vercel.app/
|
|
|
|
protected isInTransition = false; // Wether Slides is in transition.
|
2022-03-30 15:51:55 +02:00
|
|
|
protected subscriptions: Subscription[] = [];
|
2022-03-31 11:02:52 +02:00
|
|
|
protected onReadyPromise = new CorePromisedValue<void>();
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2021-05-04 17:19:57 +02:00
|
|
|
tabAction: CoreTabsRoleTab<T>;
|
|
|
|
|
2022-03-31 11:02:52 +02:00
|
|
|
constructor(element: ElementRef) {
|
2022-10-04 15:06:14 +02:00
|
|
|
this.backButtonFunction = (event) => this.backButtonClicked(event);
|
2021-10-21 13:20:00 +02:00
|
|
|
|
2021-05-04 17:19:57 +02:00
|
|
|
this.tabAction = new CoreTabsRoleTab(this);
|
2022-03-31 11:02:52 +02:00
|
|
|
|
|
|
|
CoreComponentsRegistry.register(element.nativeElement, this);
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-30 15:51:55 +02:00
|
|
|
* @inheritdoc
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
2022-03-30 15:51:55 +02:00
|
|
|
async ngOnInit(): Promise<void> {
|
2022-06-20 18:19:18 +02:00
|
|
|
this.direction = CorePlatform.isRTL ? 'rtl' : 'ltr';
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
// Change the side when the language changes.
|
2022-03-30 15:51:55 +02:00
|
|
|
this.subscriptions.push(Translate.onLangChange.subscribe(() => {
|
2021-02-01 12:32:10 +01:00
|
|
|
setTimeout(() => {
|
2022-06-20 18:19:18 +02:00
|
|
|
this.direction = CorePlatform.isRTL ? 'rtl' : 'ltr';
|
2021-02-01 12:32:10 +01:00
|
|
|
});
|
2022-03-30 15:51:55 +02:00
|
|
|
}));
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-30 15:51:55 +02:00
|
|
|
* @inheritdoc
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
2022-03-30 15:51:55 +02:00
|
|
|
ngAfterViewInit(): void {
|
2021-02-01 12:32:10 +01:00
|
|
|
if (this.isDestroyed) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
this.init();
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-17 11:46:37 +01:00
|
|
|
* @inheritdoc
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
2021-03-05 14:48:00 +01:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
ngOnChanges(changes: Record<string, SimpleChange>): void {
|
2022-03-30 15:51:55 +02:00
|
|
|
this.init();
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* User entered the page that contains the component.
|
|
|
|
*/
|
|
|
|
ionViewDidEnter(): void {
|
|
|
|
this.isCurrentView = true;
|
|
|
|
|
|
|
|
this.calculateSlides();
|
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
document.addEventListener('ionBackButton', this.backButtonFunction);
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-02-25 12:16:23 +01:00
|
|
|
* Back button clicked.
|
|
|
|
*
|
|
|
|
* @param event Event.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
2021-02-25 12:16:23 +01:00
|
|
|
protected backButtonClicked(event: BackButtonEvent): void {
|
|
|
|
event.detail.register(40, (processNextHandler: () => void) => {
|
2021-02-01 12:32:10 +01:00
|
|
|
if (this.selectHistory.length > 1) {
|
2021-02-25 12:16:23 +01:00
|
|
|
// The previous page in history is not the last one, we need the previous one.
|
|
|
|
const previousTabId = this.selectHistory[this.selectHistory.length - 2];
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
// Remove curent and previous tabs from history.
|
2021-02-25 12:16:23 +01:00
|
|
|
this.selectHistory = this.selectHistory.filter((tabId) => this.selected != tabId && previousTabId != tabId);
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
this.selectTab(previousTabId);
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.firstSelectedTab && this.selected != this.firstSelectedTab) {
|
2021-02-01 12:32:10 +01:00
|
|
|
// All history is gone but we are not in the first selected tab.
|
|
|
|
this.selectHistory = [];
|
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
this.selectTab(this.firstSelectedTab);
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
return;
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2021-02-25 12:16:23 +01:00
|
|
|
processNextHandler();
|
|
|
|
});
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* User left the page that contains the component.
|
|
|
|
*/
|
|
|
|
ionViewDidLeave(): void {
|
2021-02-25 12:16:23 +01:00
|
|
|
// Unregister the custom back button action for this component.
|
|
|
|
document.removeEventListener('ionBackButton', this.backButtonFunction);
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
this.isCurrentView = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate slides.
|
|
|
|
*/
|
|
|
|
protected async calculateSlides(): Promise<void> {
|
|
|
|
if (!this.isCurrentView || !this.initialized) {
|
|
|
|
// Don't calculate if component isn't in current view, the calculations are wrong.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
this.numTabsShown = this.tabs.reduce((prev: number, current) => current.enabled ? prev + 1 : prev, 0);
|
|
|
|
|
|
|
|
if (this.numTabsShown <= 1) {
|
|
|
|
this.hideTabs = true;
|
|
|
|
|
|
|
|
// Only one, nothing to do here.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this.hideTabs = false;
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
await this.calculateMaxSlides();
|
|
|
|
|
2021-06-03 09:16:40 +02:00
|
|
|
await this.updateSlides();
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the tab on a index.
|
|
|
|
*
|
|
|
|
* @param tabId Tab ID.
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Selected tab.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
protected getTabIndex(tabId: string): number {
|
|
|
|
return this.tabs.findIndex((tab) => tabId == tab.id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the current selected tab.
|
|
|
|
*
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Selected tab.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
getSelected(): T | undefined {
|
|
|
|
const index = this.selected && this.getTabIndex(this.selected);
|
|
|
|
|
|
|
|
return index !== undefined && index >= 0 ? this.tabs[index] : undefined;
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
/**
|
|
|
|
* Init the component.
|
|
|
|
*/
|
|
|
|
protected async init(): Promise<void> {
|
|
|
|
if (!this.hideUntil) {
|
|
|
|
// Hidden, do nothing.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await this.initializeSlider();
|
|
|
|
await this.initializeTabs();
|
|
|
|
} catch {
|
|
|
|
// Something went wrong, ignore.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Initialize the slider elements.
|
|
|
|
*/
|
|
|
|
protected async initializeSlider(): Promise<void> {
|
|
|
|
if (this.initialized) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.slidesElement) {
|
|
|
|
// Already initializated, await for ready.
|
|
|
|
await this.slidesElement.componentOnReady();
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!this.slides) {
|
|
|
|
await CoreUtils.nextTick();
|
|
|
|
}
|
|
|
|
const slidesSwiper = await this.slides?.getSwiper();
|
|
|
|
if (!slidesSwiper || !this.slides) {
|
|
|
|
throw new CoreError('Swiper not found, will try on next change.');
|
|
|
|
}
|
|
|
|
|
|
|
|
this.slidesElement = <HTMLIonSlidesElement>slidesSwiper.el;
|
|
|
|
await this.slidesElement.componentOnReady();
|
|
|
|
|
|
|
|
this.initialized = true;
|
|
|
|
|
|
|
|
// Subscribe to changes.
|
|
|
|
this.subscriptions.push(this.slides.ionSlideDidChange.subscribe(() => {
|
|
|
|
this.slideChanged();
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
/**
|
|
|
|
* Initialize the tabs, determining the first tab to be shown.
|
|
|
|
*/
|
|
|
|
protected async initializeTabs(): Promise<void> {
|
2022-03-30 15:51:55 +02:00
|
|
|
if (!this.initialized || !this.slidesElement) {
|
|
|
|
return;
|
|
|
|
}
|
2021-02-02 08:10:58 +01:00
|
|
|
|
2022-03-18 15:10:02 +01:00
|
|
|
const selectedTab = this.calculateInitialTab();
|
2021-02-01 12:32:10 +01:00
|
|
|
if (!selectedTab) {
|
2022-03-30 15:51:55 +02:00
|
|
|
// No enabled tabs, return.
|
|
|
|
throw new CoreError('No enabled tabs.');
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
this.firstSelectedTab = selectedTab.id;
|
|
|
|
if (this.firstSelectedTab !== undefined) {
|
|
|
|
this.selectTab(this.firstSelectedTab);
|
|
|
|
}
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
// Check which arrows should be shown.
|
|
|
|
this.calculateSlides();
|
2022-03-30 15:51:55 +02:00
|
|
|
|
|
|
|
this.resizeListener = CoreDom.onWindowResize(() => {
|
|
|
|
this.calculateSlides();
|
|
|
|
});
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-18 15:10:02 +01:00
|
|
|
/**
|
|
|
|
* Calculate the initial tab to load.
|
|
|
|
*
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Initial tab, undefined if no valid tab found.
|
2022-03-18 15:10:02 +01:00
|
|
|
*/
|
|
|
|
protected calculateInitialTab(): T | undefined {
|
|
|
|
const selectedTab: T | undefined = this.tabs[this.selectedIndex || 0] || undefined;
|
|
|
|
|
|
|
|
if (selectedTab && selectedTab.enabled) {
|
|
|
|
return selectedTab;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The tab is not enabled or not shown. Get the first tab that is enabled.
|
|
|
|
return this.tabs.find((tab) => tab.enabled) || undefined;
|
|
|
|
}
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
/**
|
|
|
|
* Method executed when the slides are changed.
|
|
|
|
*/
|
|
|
|
async slideChanged(): Promise<void> {
|
2022-03-30 15:51:55 +02:00
|
|
|
if (!this.slidesElement) {
|
2021-02-01 12:32:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.isInTransition = false;
|
|
|
|
const slidesCount = await this.slides?.length() || 0;
|
|
|
|
if (slidesCount > 0) {
|
|
|
|
this.showPrevButton = !await this.slides?.isBeginning();
|
|
|
|
this.showNextButton = !await this.slides?.isEnd();
|
|
|
|
} else {
|
|
|
|
this.showPrevButton = false;
|
|
|
|
this.showNextButton = false;
|
|
|
|
}
|
|
|
|
|
2021-02-03 10:55:01 +01:00
|
|
|
const currentIndex = await this.slides?.getActiveIndex();
|
2021-02-01 12:32:10 +01:00
|
|
|
if (this.shouldSlideToInitial && currentIndex != this.selectedIndex) {
|
|
|
|
// Current tab has changed, don't slide to initial anymore.
|
|
|
|
this.shouldSlideToInitial = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Updates the number of slides to show.
|
|
|
|
*/
|
|
|
|
protected async updateSlides(): Promise<void> {
|
2022-03-30 15:51:55 +02:00
|
|
|
if (!this.slides) {
|
|
|
|
return;
|
|
|
|
}
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
this.slidesOpts = { ...this.slidesOpts, slidesPerView: Math.min(this.maxSlides, this.numTabsShown) };
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
await this.slideChanged();
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
await this.slides.update();
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
if (!this.hasSliddenToInitial && this.selectedIndex && this.selectedIndex >= this.slidesOpts.slidesPerView) {
|
|
|
|
this.hasSliddenToInitial = true;
|
|
|
|
this.shouldSlideToInitial = true;
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
if (this.shouldSlideToInitial) {
|
2022-03-30 15:51:55 +02:00
|
|
|
this.slides?.slideTo(this.selectedIndex, 0);
|
2021-02-01 12:32:10 +01:00
|
|
|
this.shouldSlideToInitial = false;
|
|
|
|
}
|
|
|
|
}, 400);
|
|
|
|
|
|
|
|
return;
|
|
|
|
} else if (this.selectedIndex) {
|
|
|
|
this.hasSliddenToInitial = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
this.slideChanged(); // Call slide changed again, sometimes the slide active index takes a while to be updated.
|
|
|
|
}, 400);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Calculate the number of slides that can fit on the screen.
|
|
|
|
*/
|
|
|
|
protected async calculateMaxSlides(): Promise<void> {
|
2022-03-30 15:51:55 +02:00
|
|
|
if (!this.slidesElement || !this.slides) {
|
2021-02-01 12:32:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.maxSlides = 3;
|
2022-03-30 15:51:55 +02:00
|
|
|
await CoreUtils.nextTick();
|
|
|
|
|
|
|
|
let width: number = this.slidesElement.getBoundingClientRect().width;
|
2021-02-01 12:32:10 +01:00
|
|
|
if (!width) {
|
2022-03-30 15:51:55 +02:00
|
|
|
const slidesSwiper = await this.slides.getSwiper();
|
2021-06-02 13:49:14 +02:00
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
await slidesSwiper.updateSize();
|
|
|
|
await CoreUtils.nextTick();
|
|
|
|
|
|
|
|
width = slidesSwiper.width;
|
2021-06-02 13:49:14 +02:00
|
|
|
if (!width) {
|
2022-03-30 15:51:55 +02:00
|
|
|
|
2021-06-02 13:49:14 +02:00
|
|
|
return;
|
|
|
|
}
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2021-03-02 16:08:40 +01:00
|
|
|
const zoomLevel = await CoreSettingsHelper.getZoom();
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2021-03-02 16:08:40 +01:00
|
|
|
this.maxSlides = Math.floor(width / (zoomLevel / 100 * CoreTabsBaseComponent.MIN_TAB_WIDTH));
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method that shows the next tab.
|
|
|
|
*/
|
|
|
|
async slideNext(): Promise<void> {
|
|
|
|
// Stop if slides are in transition.
|
2022-03-14 13:09:41 +01:00
|
|
|
if (!this.showNextButton || this.isInTransition || !this.slides) {
|
2021-02-01 12:32:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:09:41 +01:00
|
|
|
if (await this.slides.isBeginning()) {
|
2021-02-01 12:32:10 +01:00
|
|
|
// Slide to the second page.
|
2022-03-14 13:09:41 +01:00
|
|
|
this.slides.slideTo(this.maxSlides);
|
2021-02-01 12:32:10 +01:00
|
|
|
} else {
|
2022-03-14 13:09:41 +01:00
|
|
|
const currentIndex = await this.slides.getActiveIndex();
|
2021-12-16 10:46:40 +01:00
|
|
|
if (currentIndex !== undefined) {
|
2021-02-01 12:32:10 +01:00
|
|
|
const nextSlideIndex = currentIndex + this.maxSlides;
|
|
|
|
this.isInTransition = true;
|
|
|
|
if (nextSlideIndex < this.numTabsShown) {
|
|
|
|
// Slide to the next page.
|
2022-03-14 13:09:41 +01:00
|
|
|
await this.slides.slideTo(nextSlideIndex);
|
2021-02-01 12:32:10 +01:00
|
|
|
} else {
|
|
|
|
// Slide to the latest slide.
|
2022-03-14 13:09:41 +01:00
|
|
|
await this.slides.slideTo(this.numTabsShown - 1);
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Method that shows the previous tab.
|
|
|
|
*/
|
|
|
|
async slidePrev(): Promise<void> {
|
|
|
|
// Stop if slides are in transition.
|
2022-03-14 13:09:41 +01:00
|
|
|
if (!this.showPrevButton || this.isInTransition || !this.slides) {
|
2021-02-01 12:32:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-14 13:09:41 +01:00
|
|
|
if (await this.slides.isEnd()) {
|
|
|
|
this.slides.slideTo(this.numTabsShown - this.maxSlides * 2);
|
2021-02-01 12:32:10 +01:00
|
|
|
// Slide to the previous of the latest page.
|
|
|
|
} else {
|
2022-03-14 13:09:41 +01:00
|
|
|
const currentIndex = await this.slides.getActiveIndex();
|
2021-12-16 10:46:40 +01:00
|
|
|
if (currentIndex !== undefined) {
|
2021-02-01 12:32:10 +01:00
|
|
|
const prevSlideIndex = currentIndex - this.maxSlides;
|
|
|
|
this.isInTransition = true;
|
|
|
|
if (prevSlideIndex >= 0) {
|
|
|
|
// Slide to the previous page.
|
2022-03-14 13:09:41 +01:00
|
|
|
await this.slides.slideTo(prevSlideIndex);
|
2021-02-01 12:32:10 +01:00
|
|
|
} else {
|
|
|
|
// Slide to the first page.
|
2022-03-14 13:09:41 +01:00
|
|
|
await this.slides.slideTo(0);
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Select a tab by ID.
|
|
|
|
*
|
|
|
|
* @param tabId Tab ID.
|
|
|
|
* @param e Event.
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Promise resolved when done.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
async selectTab(tabId: string, e?: Event): Promise<void> {
|
|
|
|
const index = this.tabs.findIndex((tab) => tabId == tab.id);
|
2022-04-08 14:11:52 +02:00
|
|
|
if (index < 0) {
|
|
|
|
return;
|
|
|
|
}
|
2021-02-01 12:32:10 +01:00
|
|
|
|
|
|
|
return this.selectByIndex(index, e);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Select a tab by index.
|
|
|
|
*
|
|
|
|
* @param index Index to select.
|
|
|
|
* @param e Event.
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Promise resolved when done.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
async selectByIndex(index: number, e?: Event): Promise<void> {
|
2021-02-09 17:04:04 +01:00
|
|
|
e?.preventDefault();
|
|
|
|
e?.stopPropagation();
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
if (index < 0 || index >= this.tabs.length) {
|
|
|
|
if (this.selected) {
|
|
|
|
// Invalid index do not change tab.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Index isn't valid, select the first one.
|
|
|
|
index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
const tabToSelect = this.tabs[index];
|
2022-03-18 15:10:02 +01:00
|
|
|
if (!tabToSelect || !tabToSelect.enabled) {
|
|
|
|
// Not enabled.
|
2021-02-01 12:32:10 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
if (this.selected && this.slides) {
|
2021-05-05 11:22:35 +02:00
|
|
|
// Check if we need to slide to the tab because it's not visible.
|
2022-03-30 15:51:55 +02:00
|
|
|
const firstVisibleTab = await this.slides.getActiveIndex();
|
2021-05-05 11:22:35 +02:00
|
|
|
const lastVisibleTab = firstVisibleTab + this.slidesOpts.slidesPerView - 1;
|
|
|
|
if (index < firstVisibleTab || index > lastVisibleTab) {
|
2022-03-30 15:51:55 +02:00
|
|
|
await this.slides.slideTo(index, 0, true);
|
2021-05-05 11:22:35 +02:00
|
|
|
}
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-18 15:10:02 +01:00
|
|
|
if (tabToSelect.id === this.selected) {
|
|
|
|
// Already selected.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
const suceeded = await this.loadTab(tabToSelect);
|
2021-02-01 12:32:10 +01:00
|
|
|
|
2022-03-30 15:51:55 +02:00
|
|
|
if (suceeded !== false) {
|
2022-03-18 15:10:02 +01:00
|
|
|
this.tabSelected(tabToSelect, index);
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
2022-03-31 11:02:52 +02:00
|
|
|
this.onReadyPromise.resolve();
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
2022-03-18 15:10:02 +01:00
|
|
|
/**
|
|
|
|
* Update selected tab.
|
|
|
|
*
|
|
|
|
* @param tab Tab.
|
|
|
|
* @param tabIndex Tab index.
|
|
|
|
*/
|
|
|
|
protected tabSelected(tab: T, tabIndex: number): void {
|
|
|
|
this.selectHistory.push(tab.id ?? '');
|
|
|
|
this.selected = tab.id;
|
|
|
|
this.selectedIndex = tabIndex;
|
|
|
|
|
|
|
|
this.ionChange.emit(tab);
|
|
|
|
}
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
/**
|
|
|
|
* Load the tab.
|
|
|
|
*
|
|
|
|
* @param tabToSelect Tab to load.
|
2022-12-01 12:31:00 +01:00
|
|
|
* @returns Promise resolved with true if tab is successfully loaded.
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
|
|
protected async loadTab(tabToSelect: T): Promise<boolean> {
|
|
|
|
// Each implementation should override this function.
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2022-03-31 11:02:52 +02:00
|
|
|
* @inheritdoc
|
|
|
|
*/
|
|
|
|
async ready(): Promise<void> {
|
2022-08-31 16:49:31 +02:00
|
|
|
return this.onReadyPromise;
|
2022-03-31 11:02:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @inheritdoc
|
2021-02-01 12:32:10 +01:00
|
|
|
*/
|
|
|
|
ngOnDestroy(): void {
|
|
|
|
this.isDestroyed = true;
|
|
|
|
|
2022-03-14 13:09:41 +01:00
|
|
|
this.resizeListener?.off();
|
2022-03-30 15:51:55 +02:00
|
|
|
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
|
2021-02-01 12:32:10 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-05-04 17:19:57 +02:00
|
|
|
/**
|
|
|
|
* 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) => ({
|
2022-03-30 15:51:55 +02:00
|
|
|
id: tab.id || '',
|
|
|
|
findIndex: tab.id || '',
|
2021-05-04 17:19:57 +02:00
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2021-02-01 12:32:10 +01:00
|
|
|
/**
|
|
|
|
* Data for each tab.
|
|
|
|
*/
|
|
|
|
export type CoreTabBase = {
|
|
|
|
title: string; // The translatable tab title.
|
|
|
|
id?: string; // Unique tab id.
|
|
|
|
class?: string; // Class, if needed.
|
|
|
|
icon?: string; // The tab icon.
|
|
|
|
badge?: string; // A badge to add in the tab.
|
|
|
|
badgeStyle?: string; // The badge color.
|
2021-05-14 12:44:58 +02:00
|
|
|
badgeA11yText?: string; // Accessibility text to add on the badge.
|
2021-02-01 12:32:10 +01:00
|
|
|
enabled?: boolean; // Whether the tab is enabled.
|
|
|
|
};
|