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 @@