mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-11-01 01:18:27 +08:00
fix(tabs): respect stencil lifecycle order for tab selection (#30702)
Issue number: resolves #30611 --------- <!-- Please do not submit updates to dependencies unless it fixes an issue. --> <!-- Please try to limit your pull request to one type (bugfix, feature, etc). Submit multiple pull requests if needed. --> ## What is the current behavior? <!-- Please describe the current behavior that you are modifying. --> Currently, the way tabs are set in the tab bar abuses a bug that existed in older versions of Stencil where children would be rendered out of the correct order. This worked in the tab and tab bar's favor previously, but after the fix it broke our implementation so tabs would no longer correctly indicate the selected tab on direct navigation. ## What is the new behavior? We had a temporary fix before we knew what actually caused this issue before, which was basically just a timeout. That blindly worked because it triggered after the child was fully rendered. This change embraces the new, and correct, way these components render and triggers tab updates correctly. ## Does this introduce a breaking change? - [ ] Yes - [X] No <!-- If this introduces a breaking change: 1. Describe the impact and migration path for existing applications below. 2. Update the BREAKING.md file with the breaking change. 3. Add "BREAKING CHANGE: [...]" to the commit description when merging. See https://github.com/ionic-team/ionic-framework/blob/main/docs/CONTRIBUTING.md#footer for more information. --> ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Current dev build: ``` 8.7.6-dev.11759345401.165fca78 ``` --------- Co-authored-by: ionitron <hi@ionicframework.com>
This commit is contained in:
@ -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<void>;
|
||||
|
||||
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);
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.6 KiB |
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tabBar.selectedTab = tab;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user