/
githubmirror
/
ionic-framework
Обзор
Документация
Войти
/
githubmirror
/
ionic-framework
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
core/src/components/tabs/tabs.tsx
239 строк
7 KB
Shane
fix(tabs): respect stencil lifecycle order for tab selection (#30702)
06 окт 2025, 17:54
Не верифицирован
06 окт 2025, 17:54
7bb9535
Код
Авторство
О чём код?
import type { EventEmitter } from '@stencil/core'; import { Component, Element, Event, Host, Method, Prop, State, h } from '@stencil/core'; import { printIonError } from '@utils/logging'; import type { NavOutlet, RouteID, RouteWrite } from '../router/utils/interface'; import type { TabButtonClickEventDetail } from '../tab-bar/tab-bar-interface'; /** * @slot - Content is placed between the named slots if provided without a slot. * @slot top - Content is placed at the top of the screen. * @slot bottom - Content is placed at the bottom of the screen. */ @Component({ tag: 'ion-tabs', styleUrl: 'tabs.scss', shadow: true, }) export class Tabs implements NavOutlet { private transitioning = false; private leavingTab?: HTMLIonTabElement; @Element() el!: HTMLIonTabsElement; @State() selectedTab?: HTMLIonTabElement; /** @internal */ @Prop({ mutable: true }) useRouter = false; /** * Emitted when the navigation will load a component. * @internal */ @Event() ionNavWillLoad!: EventEmitter<void>; /** * Emitted when the navigation is about to transition to a new component. */ @Event({ bubbles: false }) ionTabsWillChange!: EventEmitter<{ tab: string }>; /** * Emitted when the navigation has finished transitioning to a new component. */ @Event({ bubbles: false }) ionTabsDidChange!: EventEmitter<{ tab: string }>; async componentWillLoad() { if (!this.useRouter) { /** * JavaScript and StencilJS use `ion-router`, while * the other frameworks use `ion-router-outlet`. * * If either component is present then tabs will not use * a basic tab-based navigation. It will use the history * stack or URL updates associated with the router. */ this.useRouter = (!!this.el.querySelector('ion-router-outlet') || !!document.querySelector('ion-router')) && !this.el.closest('[no-router]'); } if (!this.useRouter) { const tabs = this.tabs; if (tabs.length > 0) { await this.select(tabs[0]); } } this.ionNavWillLoad.emit(); } componentDidLoad() { this.updateTabBar(); } componentDidUpdate() { this.updateTabBar(); } private updateTabBar() { const tabBar = this.el.querySelector('ion-tab-bar'); if (!tabBar) { return; } const tab = this.selectedTab ? this.selectedTab.tab : undefined; // If tabs has no selected tab but tab-bar already has a selected-tab set, // don't overwrite it. This handles cases where tab-bar is used without ion-tab elements. if (tab === undefined) { return; } if (tabBar.selectedTab === tab) { return; } tabBar.selectedTab = tab; } /** * Select a tab by the value of its `tab` property or an element reference. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router. * * @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property. */ @Method() async select(tab: string | HTMLIonTabElement): Promise<boolean> { const selectedTab = getTab(this.tabs, tab); if (!this.shouldSwitch(selectedTab)) { return false; } await this.setActive(selectedTab); await this.notifyRouter(); this.tabSwitch(); return true; } /** * Get a specific tab by the value of its `tab` property or an element reference. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router. * * @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property. */ @Method() async getTab(tab: string | HTMLIonTabElement): Promise<HTMLIonTabElement | undefined> { return getTab(this.tabs, tab); } /** * Get the currently selected tab. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router. */ @Method() getSelected(): Promise<string | undefined> { return Promise.resolve(this.selectedTab ? this.selectedTab.tab : undefined); } /** @internal */ @Method() async setRouteId(id: string): Promise<RouteWrite> { const selectedTab = getTab(this.tabs, id); if (!this.shouldSwitch(selectedTab)) { return { changed: false, element: this.selectedTab }; } await this.setActive(selectedTab); return { changed: true, element: this.selectedTab, markVisible: () => this.tabSwitch(), }; } /** @internal */ @Method() async getRouteId(): Promise<RouteID | undefined> { const tabId = this.selectedTab?.tab; return tabId !== undefined ? { id: tabId, element: this.selectedTab } : undefined; } private setActive(selectedTab: HTMLIonTabElement): Promise<void> { if (this.transitioning) { return Promise.reject('transitioning already happening'); } this.transitioning = true; this.leavingTab = this.selectedTab; this.selectedTab = selectedTab; this.ionTabsWillChange.emit({ tab: selectedTab.tab }); selectedTab.active = true; this.updateTabBar(); return Promise.resolve(); } private tabSwitch() { const selectedTab = this.selectedTab; const leavingTab = this.leavingTab; this.leavingTab = undefined; this.transitioning = false; if (!selectedTab) { return; } if (leavingTab !== selectedTab) { if (leavingTab) { leavingTab.active = false; } this.ionTabsDidChange.emit({ tab: selectedTab.tab }); } } private notifyRouter() { if (this.useRouter) { const router = document.querySelector('ion-router'); if (router) { return router.navChanged('forward'); } } return Promise.resolve(false); } private shouldSwitch(selectedTab: HTMLIonTabElement | undefined): selectedTab is HTMLIonTabElement { const leavingTab = this.selectedTab; return selectedTab !== undefined && selectedTab !== leavingTab && !this.transitioning; } private get tabs() { return Array.from(this.el.querySelectorAll('ion-tab')); } private onTabClicked = (ev: CustomEvent<TabButtonClickEventDetail>) => { const { href, tab } = ev.detail; if (this.useRouter && href !== undefined) { const router = document.querySelector('ion-router'); if (router) { router.push(href); } } else { this.select(tab); } }; render() { return ( <Host onIonTabButtonClick={this.onTabClicked}> <slot name="top"></slot> <div class="tabs-inner"> <slot></slot> </div> <slot name="bottom"></slot> </Host> ); } } const getTab = (tabs: HTMLIonTabElement[], tab: string | HTMLIonTabElement): HTMLIonTabElement | undefined => { const tabEl = typeof tab === 'string' ? tabs.find((t) => t.tab === tab) : tab; if (!tabEl) { printIonError(`[ion-tabs] - Tab with id: "${tabEl}" does not exist`); } return tabEl; };