From 16f683078dcf3f077e7c003f1d92d62ef6c9f156 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Mon, 29 Jul 2024 15:59:50 -0700 Subject: [PATCH 1/2] feat(many): use tabs without router outlet --- core/api.txt | 1 + core/src/components.d.ts | 2 + core/src/components/tabs/tabs.tsx | 8 ++- .../src/components/tabs/test/basic/index.html | 4 +- core/stencil.config.ts | 2 - .../common/src/directives/navigation/tabs.ts | 70 +++++++++++++++++-- packages/angular/src/app-initialize.ts | 2 +- .../src/directives/navigation/ion-tabs.ts | 8 ++- .../angular/src/directives/proxies-list.ts | 1 + packages/angular/src/directives/proxies.ts | 23 ++++++ .../standalone/src/directives/proxies.ts | 26 +++++++ .../angular/standalone/src/navigation/tabs.ts | 8 ++- .../react/src/components/inner-proxies.ts | 5 ++ .../src/components/navigation/IonTabs.tsx | 16 ++++- packages/react/test/base/src/App.tsx | 2 + packages/react/test/base/src/pages/Main.tsx | 3 + .../test/base/src/pages/TabsNoOutlet.tsx | 27 +++++++ packages/vue/src/components/IonTabs.ts | 29 ++++++-- packages/vue/src/proxies.ts | 9 +++ packages/vue/src/vue-component-lib/utils.ts | 13 +++- packages/vue/test/base/src/router/index.ts | 32 +-------- packages/vue/test/base/src/views/Tabs.vue | 19 +++-- 22 files changed, 252 insertions(+), 58 deletions(-) create mode 100644 packages/react/test/base/src/pages/TabsNoOutlet.tsx diff --git a/core/api.txt b/core/api.txt index aa94edeaf24..fe80478f4d6 100644 --- a/core/api.txt +++ b/core/api.txt @@ -1754,6 +1754,7 @@ ion-tab-button,css-prop,--ripple-color,md ion-tab-button,part,native ion-tabs,shadow +ion-tabs,prop,noOutlet,boolean,false,false,false ion-tabs,method,getSelected,getSelected() => Promise ion-tabs,method,getTab,getTab(tab: string | HTMLIonTabElement) => Promise ion-tabs,method,select,select(tab: string | HTMLIonTabElement) => Promise diff --git a/core/src/components.d.ts b/core/src/components.d.ts index b41be1c22fb..71bf9a4b17d 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -2953,6 +2953,7 @@ export namespace Components { * @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property. */ "getTab": (tab: string | HTMLIonTabElement) => Promise; + "noOutlet": boolean; /** * Select a tab by the value of its `tab` property or an element reference. This method is only available for vanilla JavaScript projects. The Angular, React, and Vue implementations of tabs are coupled to each framework's router. * @param tab The tab instance to select. If passed a string, it should be the value of the tab's `tab` property. @@ -7727,6 +7728,7 @@ declare namespace LocalJSX { "target"?: string | undefined; } interface IonTabs { + "noOutlet"?: boolean; /** * Emitted when the navigation will load a component. */ diff --git a/core/src/components/tabs/tabs.tsx b/core/src/components/tabs/tabs.tsx index 5b744db1ad9..5a3a2b587c7 100644 --- a/core/src/components/tabs/tabs.tsx +++ b/core/src/components/tabs/tabs.tsx @@ -22,6 +22,8 @@ export class Tabs implements NavOutlet { @State() selectedTab?: HTMLIonTabElement; + @Prop() noOutlet = false; + /** @internal */ @Prop({ mutable: true }) useRouter = false; @@ -42,11 +44,15 @@ export class Tabs implements NavOutlet { @Event({ bubbles: false }) ionTabsDidChange!: EventEmitter<{ tab: string }>; async componentWillLoad() { - if (!this.useRouter) { + if (!this.useRouter && !this.noOutlet) { this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]'); } + // if (!this.useRouter) { + // this.useRouter = !!document.querySelector('ion-router') && !this.el.closest('[no-router]') && !!this.el.querySelector('ion-router-outlet') + // } if (!this.useRouter) { const tabs = this.tabs; + console.log(tabs.length); if (tabs.length > 0) { await this.select(tabs[0]); } diff --git a/core/src/components/tabs/test/basic/index.html b/core/src/components/tabs/test/basic/index.html index 8cf28f1a706..d8767330f79 100644 --- a/core/src/components/tabs/test/basic/index.html +++ b/core/src/components/tabs/test/basic/index.html @@ -119,7 +119,7 @@

Hidden Tab

- --> diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 7f7113c22b9..f2e10d10119 100644 --- a/core/stencil.config.ts +++ b/core/stencil.config.ts @@ -26,7 +26,6 @@ const getAngularOutputTargets = () => { // tabs 'ion-tabs', - 'ion-tab', // auxiliar 'ion-picker-legacy-column', @@ -177,7 +176,6 @@ export const config: Config = { 'ion-back-button', 'ion-tab-button', 'ion-tabs', - 'ion-tab', 'ion-tab-bar', // Overlays diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 7906e890736..9c85bc051d9 100644 --- a/packages/angular/common/src/directives/navigation/tabs.ts +++ b/packages/angular/common/src/directives/navigation/tabs.ts @@ -7,6 +7,8 @@ import { HostListener, Output, ViewChild, + Input, + AfterViewInit, } from '@angular/core'; import { NavController } from '../../providers/nav-controller'; @@ -17,7 +19,7 @@ import { StackDidChangeEvent, StackWillChangeEvent } from './stack-utils'; selector: 'ion-tabs', }) // eslint-disable-next-line @angular-eslint/directive-class-suffix -export abstract class IonTabs implements AfterContentInit, AfterContentChecked { +export abstract class IonTabs implements AfterViewInit, AfterContentInit, AfterContentChecked { /** * Note: These must be redeclared on each child class since it needs * access to generated components such as IonRouterOutlet and IonTabBar. @@ -25,6 +27,10 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { abstract outlet: any; abstract tabBar: any; abstract tabBars: any; + abstract tabs: any; + + private selectedTab?: any; + private leavingTab?: any; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; @@ -39,8 +45,23 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { private tabBarSlot = 'bottom'; + @Input() noOutlet = false; + constructor(private navCtrl: NavController) {} + ngAfterViewInit(): void { + if (this.noOutlet) { + const tabs = this.tabs; + const selectedTab = tabs.get(0); + + if (tabs.length > 0) { + this.select(selectedTab.tab); + } + + this.tabBar.selectedTab = selectedTab.tab; + } + } + ngAfterContentInit(): void { this.detectSlotChanges(); } @@ -53,6 +74,10 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { * @internal */ onStackWillChange({ enteringView, tabSwitch }: StackWillChangeEvent): void { + if (this.noOutlet) { + return; + } + const stackId = enteringView.stackId; if (tabSwitch && stackId !== undefined) { this.ionTabsWillChange.emit({ tab: stackId }); @@ -63,12 +88,13 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { * @internal */ onStackDidChange({ enteringView, tabSwitch }: StackDidChangeEvent): void { + if (this.noOutlet) { + return; + } + const stackId = enteringView.stackId; if (tabSwitch && stackId !== undefined) { - if (this.tabBar) { - this.tabBar.selectedTab = stackId; - } - this.ionTabsDidChange.emit({ tab: stackId }); + this.tabSwitch(stackId); } } @@ -96,6 +122,18 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { select(tabOrEvent: string | CustomEvent): Promise | undefined { const isTabString = typeof tabOrEvent === 'string'; const tab = isTabString ? tabOrEvent : (tabOrEvent as CustomEvent).detail.tab; + + if (this.noOutlet) { + const selectedTab = this.tabs.find((t: any) => t.tab === tab); + this.leavingTab = this.selectedTab; + this.selectedTab = selectedTab; + + this.ionTabsWillChange.emit({ tab: selectedTab.tab }); + this.tabSwitch(); + selectedTab.setActive(); + return Promise.resolve(true); + } + const alreadySelected = this.outlet.getActiveStackId() === tab; const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`; @@ -143,6 +181,10 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { } getSelected(): string | undefined { + if (this.noOutlet) { + return this.selectedTab?.tab; + } + return this.outlet.getActiveStackId(); } @@ -189,4 +231,22 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { this.tabsInner.nativeElement.after(tabBar); } } + + private tabSwitch(stackId?: string): void { + const selectedTab = this.selectedTab; + const selectedTabValue = stackId ?? selectedTab.tab; + const leavingTab = this.leavingTab; + + if (this.tabBar) { + this.tabBar.selectedTab = selectedTabValue; + } + + if (this.noOutlet && leavingTab.tab !== selectedTab.tab) { + if (leavingTab) { + leavingTab.active = false; + } + } + + this.ionTabsDidChange.emit({ tab: selectedTabValue }); + } } diff --git a/packages/angular/src/app-initialize.ts b/packages/angular/src/app-initialize.ts index 69cf4030f43..7b1062f82a9 100644 --- a/packages/angular/src/app-initialize.ts +++ b/packages/angular/src/app-initialize.ts @@ -20,7 +20,7 @@ export const appInitialize = (config: Config, doc: Document, zone: NgZone) => { return applyPolyfills().then(() => { return defineCustomElements(win, { - exclude: ['ion-tabs', 'ion-tab'], + exclude: ['ion-tabs'], syncQueue: true, raf, jmp: (h: any) => zone.runOutsideAngular(h), diff --git a/packages/angular/src/directives/navigation/ion-tabs.ts b/packages/angular/src/directives/navigation/ion-tabs.ts index 9b7737a5774..8049e10a00b 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -1,7 +1,8 @@ -import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; +import { Component, ContentChild, ContentChildren, ViewChild, QueryList, Input } from '@angular/core'; import { IonTabs as IonTabsBase } from '@ionic/angular/common'; import { IonTabBar } from '../proxies'; +import { IonTab } from '../proxies'; import { IonRouterOutlet } from './ion-router-outlet'; @@ -10,7 +11,9 @@ import { IonRouterOutlet } from './ion-router-outlet'; template: `
+ ; + @ContentChildren(IonTab) tabs: QueryList; + + @Input() noOutlet = false; } diff --git a/packages/angular/src/directives/proxies-list.ts b/packages/angular/src/directives/proxies-list.ts index 8a0d9eb03ec..1874d0bfe2d 100644 --- a/packages/angular/src/directives/proxies-list.ts +++ b/packages/angular/src/directives/proxies-list.ts @@ -74,6 +74,7 @@ export const DIRECTIVES = [ d.IonSkeletonText, d.IonSpinner, d.IonSplitPane, + d.IonTab, d.IonTabBar, d.IonTabButton, d.IonText, diff --git a/packages/angular/src/directives/proxies.ts b/packages/angular/src/directives/proxies.ts index 8db34fa7b89..f448236a161 100644 --- a/packages/angular/src/directives/proxies.ts +++ b/packages/angular/src/directives/proxies.ts @@ -2148,6 +2148,29 @@ export declare interface IonSplitPane extends Components.IonSplitPane { } +@ProxyCmp({ + inputs: ['component', 'tab'], + methods: ['setActive'] +}) +@Component({ + selector: 'ion-tab', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['component', 'tab'], +}) +export class IonTab { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonTab extends Components.IonTab {} + + @ProxyCmp({ inputs: ['color', 'mode', 'selectedTab', 'translucent'] }) diff --git a/packages/angular/standalone/src/directives/proxies.ts b/packages/angular/standalone/src/directives/proxies.ts index 0db2a471286..c84717dfd1c 100644 --- a/packages/angular/standalone/src/directives/proxies.ts +++ b/packages/angular/standalone/src/directives/proxies.ts @@ -69,6 +69,7 @@ import { defineCustomElement as defineIonSelectOption } from '@ionic/core/compon import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; import { defineCustomElement as defineIonSplitPane } from '@ionic/core/components/ion-split-pane.js'; +import { defineCustomElement as defineIonTab } from '@ionic/core/components/ion-tab.js'; import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/ion-tab-bar.js'; import { defineCustomElement as defineIonTabButton } from '@ionic/core/components/ion-tab-button.js'; import { defineCustomElement as defineIonText } from '@ionic/core/components/ion-text.js'; @@ -1939,6 +1940,31 @@ export declare interface IonSplitPane extends Components.IonSplitPane { } +@ProxyCmp({ + defineCustomElementFn: defineIonTab, + inputs: ['component', 'tab'], + methods: ['setActive'] +}) +@Component({ + selector: 'ion-tab', + changeDetection: ChangeDetectionStrategy.OnPush, + template: '', + // eslint-disable-next-line @angular-eslint/no-inputs-metadata-property + inputs: ['component', 'tab'], + standalone: true +}) +export class IonTab { + protected el: HTMLElement; + constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { + c.detach(); + this.el = r.nativeElement; + } +} + + +export declare interface IonTab extends Components.IonTab {} + + @ProxyCmp({ defineCustomElementFn: defineIonTabBar, inputs: ['color', 'mode', 'selectedTab', 'translucent'] diff --git a/packages/angular/standalone/src/navigation/tabs.ts b/packages/angular/standalone/src/navigation/tabs.ts index 50c85f1593f..2fd44b65b22 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -1,7 +1,8 @@ -import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; +import { Component, ContentChild, ContentChildren, ViewChild, QueryList, Input } from '@angular/core'; import { IonTabs as IonTabsBase } from '@ionic/angular/common'; import { IonTabBar } from '../directives/proxies'; +import { IonTab } from '../directives/proxies'; import { IonRouterOutlet } from './router-outlet'; @@ -10,7 +11,9 @@ import { IonRouterOutlet } from './router-outlet'; template: `
+ ; + @ContentChildren(IonTab) tabs: QueryList; + + @Input() noOutlet = false; } diff --git a/packages/react/src/components/inner-proxies.ts b/packages/react/src/components/inner-proxies.ts index a2d39091efe..fcd0adc8cf6 100644 --- a/packages/react/src/components/inner-proxies.ts +++ b/packages/react/src/components/inner-proxies.ts @@ -4,6 +4,7 @@ import { defineCustomElement as defineIonBackButton } from '@ionic/core/componen import { defineCustomElement as defineIonRouterOutlet } from '@ionic/core/components/ion-router-outlet.js'; import { defineCustomElement as defineIonTabBar } from '@ionic/core/components/ion-tab-bar.js'; import { defineCustomElement as defineIonTabButton } from '@ionic/core/components/ion-tab-button.js'; +import { defineCustomElement as defineIonTabs } from '@ionic/core/components/ion-tabs.js'; import type { JSX as IoniconsJSX } from 'ionicons'; import { defineCustomElement as defineIonIcon } from 'ionicons/components/ion-icon.js'; @@ -19,6 +20,10 @@ export const IonTabBarInner = /*@__PURE__*/ createReactComponent('ion-tabs', undefined, undefined, defineIonTabs); export const IonBackButtonInner = /*@__PURE__*/ createReactComponent< Omit, HTMLIonBackButtonElement diff --git a/packages/react/src/components/navigation/IonTabs.tsx b/packages/react/src/components/navigation/IonTabs.tsx index c4d2df2c35d..150c0cb0706 100644 --- a/packages/react/src/components/navigation/IonTabs.tsx +++ b/packages/react/src/components/navigation/IonTabs.tsx @@ -5,6 +5,7 @@ import { NavContext } from '../../contexts/NavContext'; import PageManager from '../../routing/PageManager'; import { HTMLElementSSR } from '../../utils/HTMLElementSSR'; import { IonRouterOutlet } from '../IonRouterOutlet'; +import { IonTabsInner } from '../inner-proxies'; import { IonTabBar } from './IonTabBar'; import type { IonTabsContextState } from './IonTabsContext'; @@ -91,7 +92,7 @@ export const IonTabs = /*@__PURE__*/ (() => render() { let outlet: React.ReactElement<{}> | undefined; let tabBar: React.ReactElement | undefined; - const { className, onIonTabsDidChange, onIonTabsWillChange, ...props } = this.props; + const { className, onIonTabsDidChange, onIonTabsWillChange, noOutlet, ...props } = this.props; const children = typeof this.props.children === 'function' @@ -144,13 +145,24 @@ export const IonTabs = /*@__PURE__*/ (() => } }); - if (!outlet) { + if (!outlet && !noOutlet) { throw new Error('IonTabs must contain an IonRouterOutlet'); } + + // Should we also warn about including IonTab when noOutlet is false? + + if (noOutlet && outlet) { + throw new Error('IonTabs must not contain an IonRouterOutlet when using the noOutlet prop'); + } + if (!tabBar) { throw new Error('IonTabs needs a IonTabBar'); } + if (!outlet) { + return ; + } + return ( {this.context.hasIonicRouter() ? ( diff --git a/packages/react/test/base/src/App.tsx b/packages/react/test/base/src/App.tsx index 9278550f153..2e614eb5768 100644 --- a/packages/react/test/base/src/App.tsx +++ b/packages/react/test/base/src/App.tsx @@ -26,6 +26,7 @@ import OverlayHooks from './pages/overlay-hooks/OverlayHooks'; import OverlayComponents from './pages/overlay-components/OverlayComponents'; import KeepContentsMounted from './pages/overlay-components/KeepContentsMounted'; import Tabs from './pages/Tabs'; +import TabsNoOutlet from './pages/TabsNoOutlet'; import Icons from './pages/Icons'; import NavComponent from './pages/navigation/NavComponent'; import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling'; @@ -60,6 +61,7 @@ const App: React.FC = () => ( + diff --git a/packages/react/test/base/src/pages/Main.tsx b/packages/react/test/base/src/pages/Main.tsx index 0d95f9d0eff..eeb9779cd7d 100644 --- a/packages/react/test/base/src/pages/Main.tsx +++ b/packages/react/test/base/src/pages/Main.tsx @@ -37,6 +37,9 @@ const Main: React.FC = () => { Tabs + + Tabs with No Outlet + Icons diff --git a/packages/react/test/base/src/pages/TabsNoOutlet.tsx b/packages/react/test/base/src/pages/TabsNoOutlet.tsx new file mode 100644 index 00000000000..a801bb999e2 --- /dev/null +++ b/packages/react/test/base/src/pages/TabsNoOutlet.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { IonLabel, IonTabBar, IonTabButton, IonTabs, IonTab } from '@ionic/react'; + +interface TabsProps {} + +const TabsNoOutlet: React.FC = () => { + return ( + + + Tab 1 Content + + + Tab 2 Content + + + + Tabs 1 + + + Tab 2 + + + + ); +}; + +export default TabsNoOutlet; diff --git a/packages/vue/src/components/IonTabs.ts b/packages/vue/src/components/IonTabs.ts index 432c2479b59..a115ef65552 100644 --- a/packages/vue/src/components/IonTabs.ts +++ b/packages/vue/src/components/IonTabs.ts @@ -1,3 +1,4 @@ +import { defineCustomElement } from "@ionic/core/components/ion-tabs.js"; import type { VNode } from "vue"; import { h, defineComponent } from "vue"; @@ -31,8 +32,18 @@ const isTabBar = (node: VNode) => { export const IonTabs = /*@__PURE__*/ defineComponent({ name: "IonTabs", emits: [WILL_CHANGE, DID_CHANGE], + setup(props, { slots, emit }) { + // Define the custom element + defineCustomElement(); + + return { + props, + slots, + emit, + }; + }, render() { - const { $slots: slots, $emit } = this; + const { slots, emit, props } = this; const slottedContent = slots.default && slots.default(); let routerOutlet; @@ -47,8 +58,16 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ } if (!routerOutlet) { - throw new Error( - "IonTabs must contain an IonRouterOutlet. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information." + // throw new Error( + // "IonTabs must contain an IonRouterOutlet. See https://ionicframework.com/docs/vue/navigation#working-with-tabs for more information." + // ); + // render as is + return h( + "ion-tabs", + { + ...props + }, + slottedContent ); } @@ -99,9 +118,9 @@ export const IonTabs = /*@__PURE__*/ defineComponent({ * so we do not have code split across two components. */ slottedTabBar.props._tabsWillChange = (tab: string) => - $emit(WILL_CHANGE, { tab }); + emit(WILL_CHANGE, { tab }); slottedTabBar.props._tabsDidChange = (tab: string) => - $emit(DID_CHANGE, { tab }); + emit(DID_CHANGE, { tab }); } if (hasTopSlotTabBar) { diff --git a/packages/vue/src/proxies.ts b/packages/vue/src/proxies.ts index 936b1e1278b..347673a7461 100644 --- a/packages/vue/src/proxies.ts +++ b/packages/vue/src/proxies.ts @@ -72,6 +72,7 @@ import { defineCustomElement as defineIonSelectOption } from '@ionic/core/compon import { defineCustomElement as defineIonSkeletonText } from '@ionic/core/components/ion-skeleton-text.js'; import { defineCustomElement as defineIonSpinner } from '@ionic/core/components/ion-spinner.js'; import { defineCustomElement as defineIonSplitPane } from '@ionic/core/components/ion-split-pane.js'; +import { defineCustomElement as defineIonTab } from '@ionic/core/components/ion-tab.js'; import { defineCustomElement as defineIonText } from '@ionic/core/components/ion-text.js'; import { defineCustomElement as defineIonTextarea } from '@ionic/core/components/ion-textarea.js'; import { defineCustomElement as defineIonThumbnail } from '@ionic/core/components/ion-thumbnail.js'; @@ -809,6 +810,14 @@ export const IonSplitPane = /*@__PURE__*/ defineContainer('ion ]); +export const IonTab = /*@__PURE__*/ defineContainer('ion-tab', defineIonTab, [ + 'active', + 'delegate', + 'tab', + 'component' +]); + + export const IonText = /*@__PURE__*/ defineContainer('ion-text', defineIonText, [ 'color' ]); diff --git a/packages/vue/src/vue-component-lib/utils.ts b/packages/vue/src/vue-component-lib/utils.ts index 136041073e8..492ee5d4104 100644 --- a/packages/vue/src/vue-component-lib/utils.ts +++ b/packages/vue/src/vue-component-lib/utils.ts @@ -91,8 +91,17 @@ export const defineContainer = ( const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent]; eventsNames.forEach((eventName: string) => { el.addEventListener(eventName.toLowerCase(), (e: Event) => { - modelPropValue = (e?.target as any)[modelProp]; - emit(UPDATE_VALUE_EVENT, modelPropValue); + /** + * Only update the v-model binding if the event's target is the element we are + * listening on. For example, Component A could emit ionChange, but it could also + * have a descendant Component B that also emits ionChange. We only want to update + * the v-model for Component A when ionChange originates from that element and not + * when ionChange bubbles up from Component B. + */ + if (e.target.tagName === el.tagName) { + modelPropValue = (e?.target as any)[modelProp]; + emit(UPDATE_VALUE_EVENT, modelPropValue); + } }); }); }, diff --git a/packages/vue/test/base/src/router/index.ts b/packages/vue/test/base/src/router/index.ts index f55aec808af..64bf4e79e1d 100644 --- a/packages/vue/test/base/src/router/index.ts +++ b/packages/vue/test/base/src/router/index.ts @@ -106,38 +106,8 @@ const routes: Array = [ ] }, { - path: '/tabs/', + path: '/tabs', component: () => import('@/views/Tabs.vue'), - children: [ - { - path: '', - redirect: '/tabs/tab1' - }, - { - path: 'tab1', - component: () => import('@/views/Tab1.vue'), - }, - { - path: 'tab1/:id', - component: () => import('@/views/Tab1Parameter.vue'), - props: true - }, - { - path: 'tab2', - component: () => import('@/views/Tab2.vue') - }, - { - path: 'tab3', - beforeEnter: (to, from, next) => { - next({ path: '/tabs/tab1' }); - }, - component: () => import('@/views/Tab3.vue') - }, - { - path: 'tab4', - component: () => import('@/views/Tab4.vue') - } - ] }, { path: '/tabs-secondary/', diff --git a/packages/vue/test/base/src/views/Tabs.vue b/packages/vue/test/base/src/views/Tabs.vue index dc07a09bae5..5caa2282a4f 100644 --- a/packages/vue/test/base/src/views/Tabs.vue +++ b/packages/vue/test/base/src/views/Tabs.vue @@ -2,33 +2,42 @@ - + Tab {{ tab.id }} - Add Tab + + + + Tab 1 + + + Tab 2 + + + Tab 3 + diff --git a/packages/vue/src/vue-component-lib/utils.ts b/packages/vue/src/vue-component-lib/utils.ts index 492ee5d4104..136041073e8 100644 --- a/packages/vue/src/vue-component-lib/utils.ts +++ b/packages/vue/src/vue-component-lib/utils.ts @@ -91,17 +91,8 @@ export const defineContainer = ( const eventsNames = Array.isArray(modelUpdateEvent) ? modelUpdateEvent : [modelUpdateEvent]; eventsNames.forEach((eventName: string) => { el.addEventListener(eventName.toLowerCase(), (e: Event) => { - /** - * Only update the v-model binding if the event's target is the element we are - * listening on. For example, Component A could emit ionChange, but it could also - * have a descendant Component B that also emits ionChange. We only want to update - * the v-model for Component A when ionChange originates from that element and not - * when ionChange bubbles up from Component B. - */ - if (e.target.tagName === el.tagName) { - modelPropValue = (e?.target as any)[modelProp]; - emit(UPDATE_VALUE_EVENT, modelPropValue); - } + modelPropValue = (e?.target as any)[modelProp]; + emit(UPDATE_VALUE_EVENT, modelPropValue); }); }); },