diff --git a/packages/vue/src/components/IonTabBar.ts b/packages/vue/src/components/IonTabBar.ts index 24e81345404..714891c188e 100644 --- a/packages/vue/src/components/IonTabBar.ts +++ b/packages/vue/src/components/IonTabBar.ts @@ -1,5 +1,5 @@ import { defineCustomElement } from "@ionic/core/components/ion-tab-bar.js"; -import type { VNode } from "vue"; +import type { VNode, Ref } from "vue"; import { h, defineComponent, getCurrentInstance, inject } from "vue"; // TODO(FW-2969): types @@ -16,6 +16,12 @@ interface Tab { ref: VNode; } +interface TabBarData { + hasRouterOutlet: boolean; + _tabsWillChange: Function; + _tabsDidChange: Function; +} + const isTabButton = (child: any) => child.type?.name === "IonTabButton"; const getTabs = (nodes: VNode[]) => { @@ -34,20 +40,23 @@ const getTabs = (nodes: VNode[]) => { export const IonTabBar = defineComponent({ name: "IonTabBar", - props: { - /* eslint-disable @typescript-eslint/no-empty-function */ - _tabsWillChange: { type: Function, default: () => {} }, - _tabsDidChange: { type: Function, default: () => {} }, - _hasRouterOutlet: { type: Boolean, default: false }, - /* eslint-enable @typescript-eslint/no-empty-function */ - }, data() { return { tabState: { activeTab: undefined, tabs: {}, + /** + * Passing this prop to each tab button + * lets it be aware of the presence of + * the router outlet. + */ + hasRouterOutlet: false, }, tabVnodes: [], + /* eslint-disable @typescript-eslint/no-empty-function */ + _tabsWillChange: { type: Function, default: () => {} }, + _tabsDidChange: { type: Function, default: () => {} }, + /* eslint-enable @typescript-eslint/no-empty-function */ }; }, updated() { @@ -55,7 +64,7 @@ export const IonTabBar = defineComponent({ }, methods: { setupTabState(ionRouter: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; /** * For each tab, we need to keep track of its * base href as well as any child page that @@ -75,13 +84,6 @@ export const IonTabBar = defineComponent({ ref: child, }; - /** - * Passing this prop to each tab button - * lets it be aware of the presence of - * the router outlet. - */ - tabState.hasRouterOutlet = hasRouterOutlet; - /** * Passing this prop to each tab button * lets it be aware of the state that @@ -126,7 +128,7 @@ export const IonTabBar = defineComponent({ * @param ionRouter */ checkActiveTab(ionRouter: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; const currentRoute = ionRouter?.getCurrentRouteInfo(); const childNodes = this.$data.tabVnodes; const { tabs, activeTab: prevActiveTab } = this.$data.tabState; @@ -216,7 +218,7 @@ export const IonTabBar = defineComponent({ this.tabSwitch(activeTab); }, tabSwitch(activeTab: string, ionRouter?: any) { - const hasRouterOutlet = this.$props._hasRouterOutlet; + const hasRouterOutlet = this.$data.tabState.hasRouterOutlet; const childNodes = this.$data.tabVnodes; const { activeTab: prevActiveTab } = this.$data.tabState; const tabState = this.$data.tabState; @@ -227,7 +229,7 @@ export const IonTabBar = defineComponent({ const tabDidChange = activeTab !== prevActiveTab; if (tabBar) { if (activeChild) { - tabDidChange && this.$props._tabsWillChange(activeTab); + tabDidChange && this.$data._tabsWillChange(activeTab); if (hasRouterOutlet && ionRouter !== null) { ionRouter.handleSetCurrentTab(activeTab); @@ -235,7 +237,7 @@ export const IonTabBar = defineComponent({ tabBar.selectedTab = tabState.activeTab = activeTab; - tabDidChange && this.$props._tabsDidChange(activeTab); + tabDidChange && this.$data._tabsDidChange(activeTab); } else { /** * When going to a tab that does @@ -250,6 +252,17 @@ export const IonTabBar = defineComponent({ }, mounted() { const ionRouter: any = inject("navManager", null); + /** + * Tab bar can be used as a standalone component, + * so it cannot be modified directly through + * IonTabs. Instead, data will be passed through + * the provide/inject. + */ + const tabBarData = inject>("tabBarData"); + + this.$data.tabState.hasRouterOutlet = tabBarData.value.hasRouterOutlet; + this.$data._tabsWillChange = tabBarData.value._tabsWillChange; + this.$data._tabsDidChange = tabBarData.value._tabsDidChange; this.setupTabState(ionRouter); diff --git a/packages/vue/src/components/IonTabs.ts b/packages/vue/src/components/IonTabs.ts index 4fde919bc82..5adde3a3318 100644 --- a/packages/vue/src/components/IonTabs.ts +++ b/packages/vue/src/components/IonTabs.ts @@ -1,6 +1,13 @@ import { defineCustomElement } from "@ionic/core/components/ion-tabs.js"; import type { VNode } from "vue"; -import { h, defineComponent, Fragment, isVNode } from "vue"; +import { + h, + defineComponent, + Fragment, + isVNode, + provide, + shallowRef, +} from "vue"; import { IonTab } from "../proxies"; @@ -9,6 +16,12 @@ const DID_CHANGE = "ionTabsDidChange"; // TODO(FW-2969): types +interface TabBarData { + hasRouterOutlet: boolean; + _tabsWillChange: Function; + _tabsDidChange: Function; +} + /** * Vue 3.2.38 fixed an issue where Web Component * names are respected using kebab case instead of pascal case. @@ -24,13 +37,6 @@ const isRouterOutlet = (node: VNode) => { ); }; -const isTabBar = (node: VNode) => { - return ( - node.type && - ((node.type as any).name === "IonTabBar" || node.type === "ion-tab-bar") - ); -}; - const isTab = (node: VNode): boolean => { // The `ion-tab` component was created with the `v-for` directive. if (node.type === Fragment) { @@ -49,7 +55,43 @@ const isTab = (node: VNode): boolean => { export const IonTabs = /*@__PURE__*/ defineComponent({ name: "IonTabs", emits: [WILL_CHANGE, DID_CHANGE], + data() { + return { + hasRouterOutlet: false, + }; + }, setup(props, { slots, emit }) { + const slottedContent: VNode[] | undefined = + slots.default && slots.default(); + let routerOutlet: VNode | undefined = undefined; + + if (slottedContent && slottedContent.length > 0) { + /** + * Developers must pass an ion-router-outlet + * inside of ion-tabs if they want to use + * the history stack or URL updates associated + * with the router. + */ + routerOutlet = slottedContent.find((child: VNode) => + isRouterOutlet(child) + ); + } + + /** + * Tab bar can be used as a standalone component, + * so it cannot be modified directly through + * IonTabs. Instead, data will be passed through + * the provide/inject. + */ + provide( + "tabBarData", + shallowRef({ + hasRouterOutlet: !!routerOutlet, + _tabsWillChange: (tab: string) => emit(WILL_CHANGE, { tab }), + _tabsDidChange: (tab: string) => emit(DID_CHANGE, { tab }), + }) + ); + return { props, slots, @@ -68,9 +110,10 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ defineCustomElement(); }, render() { - const { slots, emit, props } = this; - const slottedContent = slots.default && slots.default(); - let routerOutlet; + const { slots, props } = this; + const slottedContent: VNode[] | undefined = + slots.default && slots.default(); + let routerOutlet: VNode | undefined = undefined; let hasTab = false; if (slottedContent && slottedContent.length > 0) { @@ -78,7 +121,7 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ * Developers must pass an ion-router-outlet * inside of ion-tabs if they want to use * the history stack or URL updates associated - * wit the router. + * with the router. */ routerOutlet = slottedContent.find((child: VNode) => isRouterOutlet(child) @@ -103,30 +146,6 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ ); } - if (slottedContent && slottedContent.length > 0) { - const slottedTabBar = slottedContent.find((child: VNode) => - isTabBar(child) - ); - - if (slottedTabBar) { - if (!slottedTabBar.props) { - slottedTabBar.props = {}; - } - /** - * ionTabsWillChange and ionTabsDidChange are - * fired from `ion-tabs`, so we need to pass these down - * as props so they can fire when the active tab changes. - * TODO: We may want to move logic from the tab bar into here - * so we do not have code split across two components. - */ - slottedTabBar.props._tabsWillChange = (tab: string) => - emit(WILL_CHANGE, { tab }); - slottedTabBar.props._tabsDidChange = (tab: string) => - emit(DID_CHANGE, { tab }); - slottedTabBar.props._hasRouterOutlet = !!routerOutlet; - } - } - if (hasTab) { return h( "ion-tabs",