fix(vue): correctly switch tabs after going back (#22309)

resolves #22307
This commit is contained in:
Liam DeBeasi
2020-11-04 12:50:31 -05:00
committed by GitHub
parent a9b2260100
commit daf6a92127
6 changed files with 188 additions and 27 deletions

View File

@ -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}` : '';
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;

View File

@ -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 () => {

View File

@ -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()
)
}
}

View File

@ -17,7 +17,7 @@
<ExploreContainer name="Tab 1 page" />
<ion-item router-link="tab1/child-one" id="child-one">
<ion-item router-link="/tabs/tab1/child-one" id="child-one">
<ion-label>Go to Tab 1 Child 1</ion-label>
</ion-item>
<ion-item router-link="/nested" id="nested">

View File

@ -1,5 +1,5 @@
<template>
<ion-page>
<ion-page data-pageid="tab3-secondary">
<ion-header>
<ion-toolbar>
<ion-title>Tab 3 - Secondary</ion-title>

View File

@ -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', () => {