diff --git a/packages/vue-router/src/router.ts b/packages/vue-router/src/router.ts
index b90160f515..60e8a409bd 100644
--- a/packages/vue-router/src/router.ts
+++ b/packages/vue-router/src/router.ts
@@ -234,15 +234,33 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
const [pathname] = path.split('?');
if (routeInfo) {
- incomingRouteParams = Object.assign(Object.assign({}, routeInfo), { routerAction: 'push', routerDirection: 'none' });
-
const search = (routeInfo.search) ? `?${routeInfo.search}` : '';
- router.push(routeInfo.pathname + search);
+
+ incomingRouteParams = {
+ ...incomingRouteParams,
+ routerAction: 'push',
+ routerDirection: 'none',
+ tab
+ }
+
+ /**
+ * When going back to a tab
+ * you just left, it's possible
+ * for the route info to be incorrect
+ * as the tab you want is not the
+ * tab you are on.
+ */
+ if (routeInfo.pathname === pathname) {
+ router.push(routeInfo.pathname + search);
+ } else {
+ router.push(pathname + search);
+ }
}
else {
handleNavigate(pathname, 'push', 'none', undefined, tab);
}
}
+
const handleSetCurrentTab = (tab: string) => {
currentTab = tab;
diff --git a/packages/vue/src/components/IonTabBar.ts b/packages/vue/src/components/IonTabBar.ts
index 85079abd8d..4c7a470b25 100644
--- a/packages/vue/src/components/IonTabBar.ts
+++ b/packages/vue/src/components/IonTabBar.ts
@@ -1,25 +1,117 @@
-import { h, defineComponent, inject } from 'vue';
+import { h, defineComponent, getCurrentInstance, inject, VNode } from 'vue';
+
+interface TabState {
+ activeTab?: string;
+ tabs: { [k: string]: Tab };
+}
+
+interface Tab {
+ originalHref: string;
+ currentHref: string,
+ ref: VNode
+}
export const IonTabBar = defineComponent({
name: 'IonTabBar',
mounted() {
const ionRouter: any = inject('navManager');
+ const tabState: TabState = {
+ activeTab: undefined,
+ tabs: {}
+ };
+ const currentInstance = getCurrentInstance();
+ /**
+ * 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.
+ */
+ (currentInstance.subTree.children as VNode[]).forEach((child: VNode) => {
+ if (child.type && (child.type as any).name === 'IonTabButton') {
+ tabState.tabs[child.props.tab] = {
+ originalHref: child.props.href,
+ currentHref: child.props.href,
+ ref: child
+ }
+
+ /**
+ * Passing this prop to each tab button
+ * lets it be aware of the state that
+ * ion-tab-bar is managing for it.
+ */
+ child.component.props._getTabState = () => tabState;
+ }
+ });
const checkActiveTab = (currentRoute: any) => {
- // TODO types
- const tabs = Array.from(this.$el.querySelectorAll('ion-tab-button')) as any[];
- const activeTab = tabs.find(tab => currentRoute.pathname.startsWith(tab.href));
+ const childNodes = currentInstance.subTree.children as VNode[];
+ const { tabs, activeTab: prevActiveTab } = tabState;
+ const tabKeys = Object.keys(tabs);
+ const activeTab = tabKeys
+ .find(key => {
+ const href = tabs[key].originalHref;
+ return currentRoute.pathname.startsWith(href);
+ });
+
+ /**
+ * For each tab, check to see if the
+ * base href has changed. If so, update
+ * it in the tabs state.
+ */
+ childNodes.forEach((child: VNode) => {
+ if (child.type && (child.type as any).name === 'IonTabButton') {
+ 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
+ }
+ }
+ }
+ });
+
+ if (activeTab && prevActiveTab) {
+ const prevHref = tabState.tabs[prevActiveTab].currentHref;
+ /**
+ * If the tabs change or the url changes,
+ * update the currentHref for the active tab.
+ * Ex: url changes from /tabs/tab1 --> /tabs/tab1/child
+ * If we went to tab2 then back to tab1, we should
+ * land on /tabs/tab1/child instead of /tabs/tab1.
+ */
+ if (activeTab !== prevActiveTab || (prevHref !== currentRoute.pathname)) {
+ tabs[activeTab] = {
+ ...tabs[activeTab],
+ currentHref: currentRoute.pathname + (currentRoute.search || '')
+ }
+ }
+
+ /**
+ * If navigating back and the tabs change,
+ * set the previous tab back to its original href.
+ */
+ if (currentRoute.routerAction === 'pop' && (activeTab !== prevActiveTab)) {
+ tabs[prevActiveTab] = {
+ ...tabs[prevActiveTab],
+ currentHref: tabs[prevActiveTab].originalHref
+ }
+ }
+ }
+
+ const activeChild = childNodes.find((child: VNode) => child.el.tab === activeTab);
const tabBar = this.$refs.ionTabBar;
- if (activeTab && tabBar) {
- ionRouter.handleSetCurrentTab(activeTab.tab);
- tabBar.selectedTab = activeTab.tab;
+ if (activeChild && tabBar) {
+ ionRouter.handleSetCurrentTab(activeTab);
+ tabBar.selectedTab = tabState.activeTab = activeTab;
}
- }
+ };
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
checkActiveTab(ionRouter.getCurrentRouteInfo());
-
},
setup(_, { slots }) {
return () => {
diff --git a/packages/vue/src/components/IonTabButton.ts b/packages/vue/src/components/IonTabButton.ts
index 400d2ed278..a2b788be8d 100644
--- a/packages/vue/src/components/IonTabButton.ts
+++ b/packages/vue/src/components/IonTabButton.ts
@@ -2,35 +2,61 @@ import { h, defineComponent, inject } from 'vue';
export const IonTabButton = defineComponent({
name: 'IonTabButton',
- setup(_, { attrs, slots }) {
+ props: {
+ _getTabState: { type: Function, default: () => { return {} } },
+ disabled: Boolean,
+ download: String,
+ href: String,
+ rel: String,
+ layout: String,
+ selected: Boolean,
+ tab: String,
+ target: String
+ },
+ setup(props, { slots }) {
const ionRouter: any = inject('navManager');
const onClick = (ev: Event) => {
-
if (ev.cancelable) {
ev.preventDefault();
}
- const { tab, href } = attrs;
- const currentRoute = ionRouter.getCurrentRouteInfo();
+ /**
+ * Keeping track of the originalHref
+ * (i.e. /tabs/tab1) lets us redirect
+ * users back to a child page using currentHref
+ * (i.e. /tabs/tab1/child).
+ */
+ const { tab, href, _getTabState } = props;
+ const tabState = _getTabState();
+ const tappedTab = tabState.tabs[tab] || {};
+ const originalHref = tappedTab.originalHref || href;
+ const currentHref = tappedTab.currentHref || href;
+ const prevActiveTab = tabState.activeTab;
- if (currentRoute.tab === tab) {
- if (href !== currentRoute.pathname) {
- ionRouter.resetTab(tab, href);
+ /**
+ * If we are still on the same
+ * tab as before, but the base href
+ * does not equal the current href,
+ * then we must be on a child page and
+ * should direct users back to the root
+ * of the tab.
+ */
+ if (prevActiveTab === tab) {
+ if (originalHref !== currentHref) {
+ ionRouter.resetTab(tab, originalHref);
}
} else {
- // TODO tabs will change/did change
- ionRouter.changeTab(tab, href)
+ ionRouter.changeTab(tab, currentHref)
}
- }
+ };
return () => {
- const children = slots.default && slots.default()
return h(
'ion-tab-button',
{
onClick,
- ...attrs
+ ...props
},
- children
+ slots.default && slots.default()
)
}
}
diff --git a/packages/vue/test-app/src/views/Tab1.vue b/packages/vue/test-app/src/views/Tab1.vue
index e8b10ae8f8..4205fd3e35 100644
--- a/packages/vue/test-app/src/views/Tab1.vue
+++ b/packages/vue/test-app/src/views/Tab1.vue
@@ -17,7 +17,7 @@
-
+
Go to Tab 1 Child 1
diff --git a/packages/vue/test-app/src/views/Tab3Secondary.vue b/packages/vue/test-app/src/views/Tab3Secondary.vue
index ae5cc438d4..315757ea32 100644
--- a/packages/vue/test-app/src/views/Tab3Secondary.vue
+++ b/packages/vue/test-app/src/views/Tab3Secondary.vue
@@ -1,5 +1,5 @@
-
+
Tab 3 - Secondary
diff --git a/packages/vue/test-app/tests/e2e/specs/tabs.js b/packages/vue/test-app/tests/e2e/specs/tabs.js
index dec8cc5e98..63eef2114b 100644
--- a/packages/vue/test-app/tests/e2e/specs/tabs.js
+++ b/packages/vue/test-app/tests/e2e/specs/tabs.js
@@ -122,6 +122,31 @@ describe('Tabs', () => {
cy.ionPageDoesNotExist('tab3');
cy.ionPageVisible('tabs');
});
+
+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/22307
+ it('should select correct tab after going back', () => {
+ cy.visit('http://localhost:8080/tabs-secondary/tab1');
+
+ cy.get('ion-tab-button#tab-button-tab2-secondary').click();
+ cy.ionPageVisible('tab2-secondary');
+ cy.ionPageHidden('tab1-secondary');
+
+ cy.get('ion-tab-button#tab-button-tab3-secondary').click();
+ cy.ionPageVisible('tab3-secondary');
+ cy.ionPageHidden('tab2-secondary');
+
+ cy.go('back');
+ cy.ionPageVisible('tab2-secondary');
+ cy.ionPageHidden('tab3-secondary');
+
+ cy.go('back');
+ cy.ionPageVisible('tab1-secondary');
+ cy.ionPageHidden('tab2-secondary');
+
+ cy.get('ion-tab-button#tab-button-tab3-secondary').click();
+ cy.ionPageVisible('tab3-secondary');
+ cy.ionPageHidden('tab1-secondary');
+ });
})
describe('Tabs - Swipe to Go Back', () => {