Files
2021-12-07 14:57:29 -05:00

179 lines
5.3 KiB
TypeScript

import { h, defineComponent, getCurrentInstance, inject, VNode } from 'vue';
import { defineCustomElement } from '../utils';
import { IonTabBar as IonTabBarCmp } from '@ionic/core/components/ion-tab-bar.js';
interface TabState {
activeTab?: string;
tabs: { [k: string]: Tab };
}
interface Tab {
originalHref: string;
currentHref: string,
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: () => {} }
},
data() {
return {
tabState: {
activeTab: undefined,
tabs: {}
},
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,
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;
});
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 => {
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) => {
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 = this.$data.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)) {
const search = (currentRoute.search !== undefined) ? `?${currentRoute.search}` : '';
tabs[activeTab] = {
...tabs[activeTab],
currentHref: currentRoute.pathname + 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) => isTabButton(child) && child.props?.tab === activeTab);
const tabBar = this.$refs.ionTabBar;
const tabDidChange = activeTab !== prevActiveTab;
if (tabBar) {
if (activeChild) {
tabDidChange && this.$props._tabsWillChange(activeTab);
ionRouter.handleSetCurrentTab(activeTab);
tabBar.selectedTab = tabState.activeTab = activeTab;
tabDidChange && this.$props._tabsDidChange(activeTab);
/**
* When going to a tab that does
* not have an associated ion-tab-button
* we need to remove the selected state from
* the old tab.
*/
} else {
tabBar.selectedTab = tabState.activeTab = '';
}
}
}
},
mounted() {
const ionRouter: any = inject('navManager');
this.setupTabState(ionRouter);
ionRouter.registerHistoryChangeListener(() => this.checkActiveTab(ionRouter));
},
setup(_, { slots }) {
defineCustomElement('ion-tab-bar', IonTabBarCmp);
return () => {
return h(
'ion-tab-bar',
{ ref: 'ionTabBar' },
slots.default && slots.default()
)
}
}
});