diff --git a/core/src/components/tab-bar/tab-bar.tsx b/core/src/components/tab-bar/tab-bar.tsx index 7dea85e7417..d17bb0ba145 100644 --- a/core/src/components/tab-bar/tab-bar.tsx +++ b/core/src/components/tab-bar/tab-bar.tsx @@ -22,6 +22,7 @@ import type { TabBarChangedEventDetail } from './tab-bar-interface'; }) export class TabBar implements ComponentInterface { private keyboardCtrl: KeyboardController | null = null; + private didLoad = false; @Element() el!: HTMLElement; @@ -40,6 +41,12 @@ export class TabBar implements ComponentInterface { @Prop() selectedTab?: string; @Watch('selectedTab') selectedTabChanged() { + // Skip the initial watcher call that happens during component load + // We handle that in componentDidLoad to ensure children are ready + if (!this.didLoad) { + return; + } + if (this.selectedTab !== undefined) { this.ionTabBarChanged.emit({ tab: this.selectedTab, @@ -65,8 +72,19 @@ export class TabBar implements ComponentInterface { */ @Event() ionTabBarLoaded!: EventEmitter; - componentWillLoad() { - this.selectedTabChanged(); + componentDidLoad() { + this.ionTabBarLoaded.emit(); + // Set the flag to indicate the component has loaded + // This allows the watcher to emit changes from this point forward + this.didLoad = true; + + // Emit the initial selected tab after the component is fully loaded + // This ensures all child components (ion-tab-button) are ready + if (this.selectedTab !== undefined) { + this.ionTabBarChanged.emit({ + tab: this.selectedTab, + }); + } } async connectedCallback() { @@ -90,10 +108,6 @@ export class TabBar implements ComponentInterface { } } - componentDidLoad() { - this.ionTabBarLoaded.emit(); - } - render() { const { color, translucent, keyboardVisible } = this; const mode = getIonMode(this); diff --git a/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Chrome-linux.png index 23aa68062ec..e451765985d 100644 Binary files a/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Chrome-linux.png and b/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Safari-linux.png b/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Safari-linux.png index 8b93748ae5c..6ca8470ec8c 100644 Binary files a/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Safari-linux.png and b/core/src/components/tab-bar/test/translucent/tab-bar.e2e.ts-snapshots/tab-bar-translucent-container-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index ada833446ea..20f4d84d136 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -65,32 +65,33 @@ export class Tabs implements NavOutlet { this.ionNavWillLoad.emit(); } - componentWillRender() { + componentDidLoad() { + this.updateTabBar(); + } + + componentDidUpdate() { + this.updateTabBar(); + } + + private updateTabBar() { const tabBar = this.el.querySelector('ion-tab-bar'); - if (tabBar) { - let tab = this.selectedTab ? this.selectedTab.tab : undefined; - - // Fallback: if no selectedTab is set but we're using router mode, - // determine the active tab from the current URL. This works around - // timing issues in React Router integration where setRouteId may not - // be called in time for the initial render. - // TODO(FW-6724): Remove this with React Router upgrade - if (!tab && this.useRouter && typeof window !== 'undefined') { - const currentPath = window.location.pathname; - const tabButtons = this.el.querySelectorAll('ion-tab-button'); - - // Look for a tab button that matches the current path pattern - for (const tabButton of tabButtons) { - const tabId = tabButton.getAttribute('tab'); - if (tabId && currentPath.includes(tabId)) { - tab = tabId; - break; - } - } - } + if (!tabBar) { + return; + } - tabBar.selectedTab = tab; + 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; } /** @@ -162,6 +163,7 @@ export class Tabs implements NavOutlet { this.selectedTab = selectedTab; this.ionTabsWillChange.emit({ tab: selectedTab.tab }); selectedTab.active = true; + this.updateTabBar(); return Promise.resolve(); }