diff --git a/packages/vue/src/components/IonTabBar.ts b/packages/vue/src/components/IonTabBar.ts
index 259c64cb48..d5a39ccda1 100644
--- a/packages/vue/src/components/IonTabBar.ts
+++ b/packages/vue/src/components/IonTabBar.ts
@@ -11,30 +11,53 @@ interface Tab {
ref: VNode
}
+const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
+
+const getTabs = (nodes: VNode[]) => {
+ let tabs: VNode[] = [];
+ nodes.forEach((node: VNode) => {
+ if (isTabButton(node)) {
+ tabs.push(node);
+ } else if (Array.isArray(node.children) && node.children.length > 1) {
+ const childTabs = getTabs(node.children as VNode[]);
+ tabs = [...tabs, ...childTabs];
+ }
+ });
+
+ return tabs;
+}
+
export const IonTabBar = defineComponent({
name: 'IonTabBar',
props: {
_tabsWillChange: { type: Function, default: () => {} },
_tabsDidChange: { type: Function, default: () => {} }
},
- mounted() {
- const ionRouter: any = inject('navManager');
- const tabState: TabState = {
+ data() {
+ return {
+ tabState: {
activeTab: undefined,
tabs: {}
- };
- const currentInstance = getCurrentInstance();
- const isTabButton = (child: any) => child.type?.name === 'IonTabButton';
- /**
- * For each tab, we need to keep track of its
- * base href as well as any child page that
- * is active in its stack so that when we go back
- * to a tab from another tab, we can correctly
- * show any child pages if necessary.
- */
- const children = (currentInstance.subTree.children || []) as VNode[];
- children.forEach((child: VNode) => {
- if (isTabButton(child)) {
+ },
+ tabVnodes: []
+ }
+ },
+ updated() {
+ this.setupTabState(inject('navManager'));
+ },
+ methods: {
+ setupTabState(ionRouter: any) {
+ /**
+ * For each tab, we need to keep track of its
+ * base href as well as any child page that
+ * is active in its stack so that when we go back
+ * to a tab from another tab, we can correctly
+ * show any child pages if necessary.
+ */
+ const tabState: TabState = this.$data.tabState;
+ const currentInstance = getCurrentInstance();
+ const tabs = this.$data.tabVnodes = getTabs((currentInstance.subTree.children || []) as VNode[]);
+ tabs.forEach(child => {
tabState.tabs[child.props.tab] = {
originalHref: child.props.href,
currentHref: child.props.href,
@@ -47,12 +70,15 @@ export const IonTabBar = defineComponent({
* ion-tab-bar is managing for it.
*/
child.component.props._getTabState = () => tabState;
- }
- });
+ });
- const checkActiveTab = (currentRoute: any) => {
- const childNodes = (currentInstance.subTree.children || []) as VNode[];
- const { tabs, activeTab: prevActiveTab } = tabState;
+ this.checkActiveTab(ionRouter);
+ },
+ checkActiveTab(ionRouter: any) {
+ const currentRoute = ionRouter.getCurrentRouteInfo();
+ const childNodes = this.$data.tabVnodes;
+ const { tabs, activeTab: prevActiveTab } = this.$data.tabState;
+ const tabState = this.$data.tabState;
const tabKeys = Object.keys(tabs);
const activeTab = tabKeys
.find(key => {
@@ -66,21 +92,19 @@ export const IonTabBar = defineComponent({
* it in the tabs state.
*/
childNodes.forEach((child: VNode) => {
- if (isTabButton(child)) {
- const tab = tabs[child.props.tab];
- if (!tab || (tab.originalHref !== child.props.href)) {
+ const tab = tabs[child.props.tab];
+ if (!tab || (tab.originalHref !== child.props.href)) {
- tabs[child.props.tab] = {
- originalHref: child.props.href,
- currentHref: child.props.href,
- ref: child
- }
+ tabs[child.props.tab] = {
+ originalHref: child.props.href,
+ currentHref: child.props.href,
+ ref: child
}
}
});
if (activeTab && prevActiveTab) {
- const prevHref = tabState.tabs[prevActiveTab].currentHref;
+ const prevHref = this.$data.tabState.tabs[prevActiveTab].currentHref;
/**
* If the tabs change or the url changes,
* update the currentHref for the active tab.
@@ -128,10 +152,14 @@ export const IonTabBar = defineComponent({
tabBar.selectedTab = tabState.activeTab = '';
}
}
- };
+ }
+ },
+ mounted() {
+ const ionRouter: any = inject('navManager');
- ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
- checkActiveTab(ionRouter.getCurrentRouteInfo());
+ this.setupTabState(ionRouter);
+
+ ionRouter.registerHistoryChangeListener(() => this.checkActiveTab(ionRouter));
},
setup(_, { slots }) {
return () => {
diff --git a/packages/vue/test-app/src/views/Tabs.vue b/packages/vue/test-app/src/views/Tabs.vue
index 5b91e6716f..3c371b8230 100644
--- a/packages/vue/test-app/src/views/Tabs.vue
+++ b/packages/vue/test-app/src/views/Tabs.vue
@@ -3,20 +3,17 @@
-
-
- Tab 1
+
+
+ Tab {{ tab.id }}
-
-
- Tab 2
-
-
-
-
- Tab 3
-
+ Add Tab
@@ -24,18 +21,33 @@
diff --git a/packages/vue/test-app/tests/e2e/specs/tabs.js b/packages/vue/test-app/tests/e2e/specs/tabs.js
index fddd9f7648..b7b8971799 100644
--- a/packages/vue/test-app/tests/e2e/specs/tabs.js
+++ b/packages/vue/test-app/tests/e2e/specs/tabs.js
@@ -264,6 +264,27 @@ describe('Tabs', () => {
cy.url().should('include', '/tabs/tab1');
});
+
+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22847
+ it('should support dynamic tabs', () => {
+ cy.visit('http://localhost:8080/tabs/tab1');
+
+ cy.ionPageVisible('tab1');
+
+ cy.get('ion-tab-button').its('length').should('equal', 3);
+ cy.get('ion-tab-button#tab-button-tab1').should('have.class', 'tab-selected');
+
+ cy.get('#add-tab').click();
+
+ cy.get('ion-tab-button').its('length').should('equal', 4);
+
+ cy.get('ion-tab-button#tab-button-tab4').click();
+ cy.ionPageVisible('tab4');
+ cy.ionPageHidden('tab1');
+
+ cy.get('ion-tab-button#tab-button-tab1').should('not.have.class', 'tab-selected');
+ cy.get('ion-tab-button#tab-button-tab4').should('have.class', 'tab-selected');
+ });
})
describe('Tabs - Swipe to Go Back', () => {