mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2025-08-15 17:42:15 +08:00
fix(vue): correctly switch tabs after going back (#22309)
resolves #22307
This commit is contained in:
@ -234,15 +234,33 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
|
|||||||
const [pathname] = path.split('?');
|
const [pathname] = path.split('?');
|
||||||
|
|
||||||
if (routeInfo) {
|
if (routeInfo) {
|
||||||
incomingRouteParams = Object.assign(Object.assign({}, routeInfo), { routerAction: 'push', routerDirection: 'none' });
|
|
||||||
|
|
||||||
const search = (routeInfo.search) ? `?${routeInfo.search}` : '';
|
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);
|
router.push(routeInfo.pathname + search);
|
||||||
|
} else {
|
||||||
|
router.push(pathname + search);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
handleNavigate(pathname, 'push', 'none', undefined, tab);
|
handleNavigate(pathname, 'push', 'none', undefined, tab);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleSetCurrentTab = (tab: string) => {
|
const handleSetCurrentTab = (tab: string) => {
|
||||||
currentTab = tab;
|
currentTab = tab;
|
||||||
|
|
||||||
|
@ -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({
|
export const IonTabBar = defineComponent({
|
||||||
name: 'IonTabBar',
|
name: 'IonTabBar',
|
||||||
mounted() {
|
mounted() {
|
||||||
const ionRouter: any = inject('navManager');
|
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) => {
|
const checkActiveTab = (currentRoute: any) => {
|
||||||
// TODO types
|
const childNodes = currentInstance.subTree.children as VNode[];
|
||||||
const tabs = Array.from(this.$el.querySelectorAll('ion-tab-button')) as any[];
|
const { tabs, activeTab: prevActiveTab } = tabState;
|
||||||
const activeTab = tabs.find(tab => currentRoute.pathname.startsWith(tab.href));
|
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;
|
const tabBar = this.$refs.ionTabBar;
|
||||||
|
|
||||||
if (activeTab && tabBar) {
|
if (activeChild && tabBar) {
|
||||||
ionRouter.handleSetCurrentTab(activeTab.tab);
|
ionRouter.handleSetCurrentTab(activeTab);
|
||||||
tabBar.selectedTab = activeTab.tab;
|
tabBar.selectedTab = tabState.activeTab = activeTab;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
|
ionRouter.registerHistoryChangeListener(checkActiveTab.bind(this));
|
||||||
checkActiveTab(ionRouter.getCurrentRouteInfo());
|
checkActiveTab(ionRouter.getCurrentRouteInfo());
|
||||||
|
|
||||||
},
|
},
|
||||||
setup(_, { slots }) {
|
setup(_, { slots }) {
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -2,35 +2,61 @@ import { h, defineComponent, inject } from 'vue';
|
|||||||
|
|
||||||
export const IonTabButton = defineComponent({
|
export const IonTabButton = defineComponent({
|
||||||
name: 'IonTabButton',
|
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 ionRouter: any = inject('navManager');
|
||||||
const onClick = (ev: Event) => {
|
const onClick = (ev: Event) => {
|
||||||
|
|
||||||
if (ev.cancelable) {
|
if (ev.cancelable) {
|
||||||
ev.preventDefault();
|
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) {
|
* If we are still on the same
|
||||||
ionRouter.resetTab(tab, href);
|
* 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 {
|
} else {
|
||||||
// TODO tabs will change/did change
|
ionRouter.changeTab(tab, currentHref)
|
||||||
ionRouter.changeTab(tab, href)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
return () => {
|
return () => {
|
||||||
const children = slots.default && slots.default()
|
|
||||||
return h(
|
return h(
|
||||||
'ion-tab-button',
|
'ion-tab-button',
|
||||||
{
|
{
|
||||||
onClick,
|
onClick,
|
||||||
...attrs
|
...props
|
||||||
},
|
},
|
||||||
children
|
slots.default && slots.default()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<ExploreContainer name="Tab 1 page" />
|
<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-label>Go to Tab 1 Child 1</ion-label>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item router-link="/nested" id="nested">
|
<ion-item router-link="/nested" id="nested">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ion-page>
|
<ion-page data-pageid="tab3-secondary">
|
||||||
<ion-header>
|
<ion-header>
|
||||||
<ion-toolbar>
|
<ion-toolbar>
|
||||||
<ion-title>Tab 3 - Secondary</ion-title>
|
<ion-title>Tab 3 - Secondary</ion-title>
|
||||||
|
@ -122,6 +122,31 @@ describe('Tabs', () => {
|
|||||||
cy.ionPageDoesNotExist('tab3');
|
cy.ionPageDoesNotExist('tab3');
|
||||||
cy.ionPageVisible('tabs');
|
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', () => {
|
describe('Tabs - Swipe to Go Back', () => {
|
||||||
|
Reference in New Issue
Block a user