diff --git a/core/stencil.config.ts b/core/stencil.config.ts index 7f7113c22b9..7177e88e692 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', diff --git a/packages/angular/common/src/directives/navigation/tabs.ts b/packages/angular/common/src/directives/navigation/tabs.ts index 7906e890736..73e8c0cc777 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, + AfterViewInit, + QueryList, } from '@angular/core'; import { NavController } from '../../providers/nav-controller'; @@ -17,14 +19,15 @@ 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. */ abstract outlet: any; abstract tabBar: any; - abstract tabBars: any; + abstract tabBars: QueryList; + abstract tabs: QueryList; @ViewChild('tabsInner', { read: ElementRef, static: true }) tabsInner: ElementRef; @@ -39,8 +42,29 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { private tabBarSlot = 'bottom'; + private hasTab = false; + private selectedTab?: { tab: string }; + private leavingTab?: any; + constructor(private navCtrl: NavController) {} + ngAfterViewInit(): void { + /** + * Developers must pass at least one ion-tab + * inside of ion-tabs if they want to use a + * basic tab-based navigation without the + * history stack or URL updates associated + * with the router. + */ + const firstTab = this.tabs.length > 0 ? this.tabs.first : undefined; + + if (firstTab) { + this.hasTab = true; + this.setActiveTab(firstTab.tab); + this.tabSwitch(); + } + } + ngAfterContentInit(): void { this.detectSlotChanges(); } @@ -96,6 +120,19 @@ 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 the tabs are not using the router, then + * the tab switch logic is handled by the tabs + * component itself. + */ + if (this.hasTab) { + this.setActiveTab(tab); + this.tabSwitch(); + + return; + } + const alreadySelected = this.outlet.getActiveStackId() === tab; const tabRootUrl = `${this.outlet.tabsPrefix}/${tab}`; @@ -142,7 +179,46 @@ export abstract class IonTabs implements AfterContentInit, AfterContentChecked { } } + private setActiveTab(tab: string): void { + const tabs = this.tabs; + const selectedTab = tabs.find((t: any) => t.tab === tab); + + if (!selectedTab) { + console.error(`[Ionic Error]: Tab with id: "${tab}" does not exist`); + return; + } + + this.leavingTab = this.selectedTab; + this.selectedTab = selectedTab; + + this.ionTabsWillChange.emit({ tab }); + + selectedTab.el.active = true; + } + + private tabSwitch(): void { + const { selectedTab, leavingTab } = this; + + if (this.tabBar && selectedTab) { + this.tabBar.selectedTab = selectedTab.tab; + } + + if (leavingTab?.tab !== selectedTab?.tab) { + if (leavingTab?.el) { + leavingTab.el.active = false; + } + } + + if (selectedTab) { + this.ionTabsDidChange.emit({ tab: selectedTab.tab }); + } + } + getSelected(): string | undefined { + if (this.hasTab) { + return this.selectedTab?.tab; + } + return this.outlet.getActiveStackId(); } 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..65819cde79f 100644 --- a/packages/angular/src/directives/navigation/ion-tabs.ts +++ b/packages/angular/src/directives/navigation/ion-tabs.ts @@ -1,7 +1,7 @@ import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; import { IonTabs as IonTabsBase } from '@ionic/angular/common'; -import { IonTabBar } from '../proxies'; +import { IonTabBar, IonTab } from '../proxies'; import { IonRouterOutlet } from './ion-router-outlet'; @@ -11,11 +11,13 @@ import { IonRouterOutlet } from './ion-router-outlet';
+
`, @@ -52,4 +54,5 @@ export class IonTabs extends IonTabsBase { @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; @ContentChildren(IonTabBar) tabBars: QueryList; + @ContentChildren(IonTab) tabs: QueryList; } 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..61cbd12d088 100644 --- a/packages/angular/standalone/src/navigation/tabs.ts +++ b/packages/angular/standalone/src/navigation/tabs.ts @@ -1,7 +1,8 @@ +import { NgIf } from '@angular/common'; import { Component, ContentChild, ContentChildren, ViewChild, QueryList } from '@angular/core'; import { IonTabs as IonTabsBase } from '@ionic/angular/common'; -import { IonTabBar } from '../directives/proxies'; +import { IonTabBar, IonTab } from '../directives/proxies'; import { IonRouterOutlet } from './router-outlet'; @@ -11,11 +12,13 @@ import { IonRouterOutlet } from './router-outlet';
+
`, @@ -46,7 +49,7 @@ import { IonRouterOutlet } from './router-outlet'; } `, ], - imports: [IonRouterOutlet], + imports: [IonRouterOutlet, NgIf], }) // eslint-disable-next-line @angular-eslint/component-class-suffix export class IonTabs extends IonTabsBase { @@ -54,4 +57,5 @@ export class IonTabs extends IonTabsBase { @ContentChild(IonTabBar, { static: false }) tabBar: IonTabBar | undefined; @ContentChildren(IonTabBar) tabBars: QueryList; + @ContentChildren(IonTab) tabs: QueryList; } diff --git a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts index e7c4b51cfbf..ac31d74060b 100644 --- a/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/lazy/tabs.spec.ts @@ -1,436 +1,462 @@ describe('Tabs', () => { - beforeEach(() => { - cy.visit('/lazy/tabs'); - }) - - describe('entry url - /tabs', () => { - it('should redirect and load tab-account', () => { - testTabTitle('Tab 1 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']); - testState(1, 'account'); - }); - - it('should navigate between tabs and ionChange events should be dispatched', () => { - let tab = testTabTitle('Tab 1 - Page 1'); - tab.find('.segment-changed').should('have.text', 'false'); - - cy.get('#tab-button-contact').click(); - tab = testTabTitle('Tab 2 - Page 1'); - tab.find('.segment-changed').should('have.text', 'false'); - }); - - describe('when navigating between tabs', () => { - - it('should emit ionTabsWillChange before setting the selected tab', () => { - cy.get('#ionTabsWillChangeCounter').should('have.text', '1'); - cy.get('#ionTabsWillChangeEvent').should('have.text', 'account'); - cy.get('#ionTabsWillChangeSelectedTab').should('have.text', ''); - - cy.get('#ionTabsDidChangeCounter').should('have.text', '1'); - cy.get('#ionTabsDidChangeEvent').should('have.text', 'account'); - cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'account'); - + describe('With IonRouterOutlet', () => { + beforeEach(() => { + cy.visit('/lazy/tabs'); + }) + + describe('entry url - /tabs', () => { + it('should redirect and load tab-account', () => { + testTabTitle('Tab 1 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1']); + testState(1, 'account'); + }); + + it('should navigate between tabs and ionChange events should be dispatched', () => { + let tab = testTabTitle('Tab 1 - Page 1'); + tab.find('.segment-changed').should('have.text', 'false'); + cy.get('#tab-button-contact').click(); - - cy.get('#ionTabsWillChangeCounter').should('have.text', '2'); - cy.get('#ionTabsWillChangeEvent').should('have.text', 'contact'); - cy.get('#ionTabsWillChangeSelectedTab').should('have.text', 'account'); - - cy.get('#ionTabsDidChangeCounter').should('have.text', '2'); - cy.get('#ionTabsDidChangeEvent').should('have.text', 'contact'); - cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'contact'); - }) - - }); - - it('should simulate stack + double tab click', () => { - let tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']); - testState(1, 'account'); - - // When you call find on tab above it changes the value of tab - // so we need to redefine it - tab = getSelectedTab(); - tab.find('ion-back-button').should('be.visible'); - - cy.get('#tab-button-contact').click(); - testTabTitle('Tab 2 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']); - testState(2, 'contact'); - - cy.get('#tab-button-account').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']); - testState(3, 'account'); - - tab = getSelectedTab(); - tab.find('ion-back-button').should('be.visible'); - - cy.get('#tab-button-account').click(); - testTabTitle('Tab 1 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); - testState(3, 'account'); - }); - - it('should simulate stack + back button click', () => { - const tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - testState(1, 'account'); - - cy.get('#tab-button-contact').click(); - testTabTitle('Tab 2 - Page 1'); - testState(2, 'contact'); - - cy.get('#tab-button-account').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - testState(3, 'account'); - - cy.get('ion-back-button').click(); - testTabTitle('Tab 1 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); - testState(3, 'account'); - }); - - it('should navigate deep then go home', () => { - const tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - cy.ionPageVisible('app-tabs-tab1-nested'); - cy.ionPageHidden('app-tabs-tab1'); - - testTabTitle('Tab 1 - Page 2 (1)'); - - cy.get('#goto-next').click(); - cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); - cy.ionPageHidden('app-tabs-tab1-nested:first-of-type'); - - testTabTitle('Tab 1 - Page 2 (2)'); - - cy.get('#tab-button-contact').click(); - cy.ionPageVisible('app-tabs-tab2'); - cy.ionPageHidden('app-tabs-tab1-nested:last-of-type'); - - testTabTitle('Tab 2 - Page 1'); - - cy.get('#tab-button-account').click(); - cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); - cy.ionPageHidden('app-tabs-tab2'); - - testTabTitle('Tab 1 - Page 2 (2)'); - cy.testStack('ion-tabs ion-router-outlet', [ - 'app-tabs-tab1', - 'app-tabs-tab1-nested', - 'app-tabs-tab1-nested', - 'app-tabs-tab2' - ]); - - cy.get('#tab-button-account').click(); - - /** - * Wait for the leaving view to - * be unmounted otherwise testTabTitle - * may get the leaving view before it - * is unmounted. - */ - cy.ionPageVisible('app-tabs-tab1'); - cy.ionPageDoesNotExist('app-tabs-tab1-nested'); - - testTabTitle('Tab 1 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', [ - 'app-tabs-tab1', - 'app-tabs-tab2' - ]); - }); - - it('should switch tabs and go back', () => { - cy.get('#tab-button-contact').click(); - const tab = testTabTitle('Tab 2 - Page 1'); - - tab.find('#goto-tab1-page1').click(); - testTabTitle('Tab 1 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); - }); - - it('should switch tabs and go to nested', () => { - cy.get('#tab-button-contact').click(); - const tab = testTabTitle('Tab 2 - Page 1'); - - tab.find('#goto-tab1-page2').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']); - }); - - it('should load lazy loaded tab', () => { - cy.get('#tab-button-lazy').click(); - cy.ionPageVisible('app-tabs-tab3'); - testTabTitle('Tab 3 - Page 1'); - }); - - it('should use ion-back-button defaultHref', () => { - let tab = getSelectedTab(); - tab.find('#goto-tab3-page2').click(); - testTabTitle('Tab 3 - Page 2'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']); - - tab = getSelectedTab(); - tab.find('ion-back-button').click(); - testTabTitle('Tab 3 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']); - }); - - it('should preserve navigation extras when switching tabs', () => { - const expectUrlToContain = 'search=hello#fragment'; - let tab = getSelectedTab(); - tab.find('#goto-nested-page1-with-query-params').click(); - testTabTitle('Tab 1 - Page 2 (1)'); - testUrlContains(expectUrlToContain); - - cy.get('#tab-button-contact').click(); - testTabTitle('Tab 2 - Page 1'); - - cy.get('#tab-button-account').click(); - tab = testTabTitle('Tab 1 - Page 2 (1)'); - testUrlContains(expectUrlToContain); - }); - - it('should set root when clicking on an active tab to navigate to the root', () => { + tab = testTabTitle('Tab 2 - Page 1'); + tab.find('.segment-changed').should('have.text', 'false'); + }); + + describe('when navigating between tabs', () => { + + it('should emit ionTabsWillChange before setting the selected tab', () => { + cy.get('#ionTabsWillChangeCounter').should('have.text', '1'); + cy.get('#ionTabsWillChangeEvent').should('have.text', 'account'); + cy.get('#ionTabsWillChangeSelectedTab').should('have.text', ''); + + cy.get('#ionTabsDidChangeCounter').should('have.text', '1'); + cy.get('#ionTabsDidChangeEvent').should('have.text', 'account'); + cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'account'); + + cy.get('#tab-button-contact').click(); + + cy.get('#ionTabsWillChangeCounter').should('have.text', '2'); + cy.get('#ionTabsWillChangeEvent').should('have.text', 'contact'); + cy.get('#ionTabsWillChangeSelectedTab').should('have.text', 'account'); + + cy.get('#ionTabsDidChangeCounter').should('have.text', '2'); + cy.get('#ionTabsDidChangeEvent').should('have.text', 'contact'); + cy.get('#ionTabsDidChangeSelectedTab').should('have.text', 'contact'); + }) + + }); + + it('should simulate stack + double tab click', () => { + let tab = getSelectedTab(); + tab.find('#goto-tab1-page2').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested']); + testState(1, 'account'); + + // When you call find on tab above it changes the value of tab + // so we need to redefine it + tab = getSelectedTab(); + tab.find('ion-back-button').should('be.visible'); + + cy.get('#tab-button-contact').click(); + testTabTitle('Tab 2 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']); + testState(2, 'contact'); + + cy.get('#tab-button-account').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab1-nested', 'app-tabs-tab2']); + testState(3, 'account'); + + tab = getSelectedTab(); + tab.find('ion-back-button').should('be.visible'); + + cy.get('#tab-button-account').click(); + testTabTitle('Tab 1 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); + testState(3, 'account'); + }); + + it('should simulate stack + back button click', () => { + const tab = getSelectedTab(); + tab.find('#goto-tab1-page2').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + testState(1, 'account'); + + cy.get('#tab-button-contact').click(); + testTabTitle('Tab 2 - Page 1'); + testState(2, 'contact'); + + cy.get('#tab-button-account').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + testState(3, 'account'); + + cy.get('ion-back-button').click(); + testTabTitle('Tab 1 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); + testState(3, 'account'); + }); + + it('should navigate deep then go home', () => { + const tab = getSelectedTab(); + tab.find('#goto-tab1-page2').click(); + cy.ionPageVisible('app-tabs-tab1-nested'); + cy.ionPageHidden('app-tabs-tab1'); + + testTabTitle('Tab 1 - Page 2 (1)'); + + cy.get('#goto-next').click(); + cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); + cy.ionPageHidden('app-tabs-tab1-nested:first-of-type'); + + testTabTitle('Tab 1 - Page 2 (2)'); + + cy.get('#tab-button-contact').click(); + cy.ionPageVisible('app-tabs-tab2'); + cy.ionPageHidden('app-tabs-tab1-nested:last-of-type'); + + testTabTitle('Tab 2 - Page 1'); + + cy.get('#tab-button-account').click(); + cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); + cy.ionPageHidden('app-tabs-tab2'); + + testTabTitle('Tab 1 - Page 2 (2)'); + cy.testStack('ion-tabs ion-router-outlet', [ + 'app-tabs-tab1', + 'app-tabs-tab1-nested', + 'app-tabs-tab1-nested', + 'app-tabs-tab2' + ]); + + cy.get('#tab-button-account').click(); + + /** + * Wait for the leaving view to + * be unmounted otherwise testTabTitle + * may get the leaving view before it + * is unmounted. + */ + cy.ionPageVisible('app-tabs-tab1'); + cy.ionPageDoesNotExist('app-tabs-tab1-nested'); + + testTabTitle('Tab 1 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', [ + 'app-tabs-tab1', + 'app-tabs-tab2' + ]); + }); + + it('should switch tabs and go back', () => { + cy.get('#tab-button-contact').click(); + const tab = testTabTitle('Tab 2 - Page 1'); + + tab.find('#goto-tab1-page1').click(); + testTabTitle('Tab 1 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab2']); + }); + + it('should switch tabs and go to nested', () => { + cy.get('#tab-button-contact').click(); + const tab = testTabTitle('Tab 2 - Page 1'); + + tab.find('#goto-tab1-page2').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab2', 'app-tabs-tab1-nested']); + }); + + it('should load lazy loaded tab', () => { + cy.get('#tab-button-lazy').click(); + cy.ionPageVisible('app-tabs-tab3'); + testTabTitle('Tab 3 - Page 1'); + }); + + it('should use ion-back-button defaultHref', () => { + let tab = getSelectedTab(); + tab.find('#goto-tab3-page2').click(); + testTabTitle('Tab 3 - Page 2'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3-nested']); + + tab = getSelectedTab(); + tab.find('ion-back-button').click(); + testTabTitle('Tab 3 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1', 'app-tabs-tab3']); + }); + + it('should preserve navigation extras when switching tabs', () => { + const expectUrlToContain = 'search=hello#fragment'; + let tab = getSelectedTab(); + tab.find('#goto-nested-page1-with-query-params').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + testUrlContains(expectUrlToContain); + + cy.get('#tab-button-contact').click(); + testTabTitle('Tab 2 - Page 1'); + + cy.get('#tab-button-account').click(); + tab = testTabTitle('Tab 1 - Page 2 (1)'); + testUrlContains(expectUrlToContain); + }); + + it('should set root when clicking on an active tab to navigate to the root', () => { + const expectNestedTabUrlToContain = 'search=hello#fragment'; + cy.url().then(url => { + const tab = getSelectedTab(); + tab.find('#goto-nested-page1-with-query-params').click(); + testTabTitle('Tab 1 - Page 2 (1)'); + testUrlContains(expectNestedTabUrlToContain); + + cy.get('#tab-button-account').click(); + testTabTitle('Tab 1 - Page 1'); + + testUrlEquals(url); + }) + }); + }) + + describe('entry tab contains navigation extras', () => { const expectNestedTabUrlToContain = 'search=hello#fragment'; - cy.url().then(url => { + const rootUrlParams = 'test=123#rootFragment'; + const rootUrl = `/lazy/tabs/account?${rootUrlParams}`; + + beforeEach(() => { + cy.visit(rootUrl); + }) + + it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', () => { const tab = getSelectedTab(); tab.find('#goto-nested-page1-with-query-params').click(); + testTabTitle('Tab 1 - Page 2 (1)'); testUrlContains(expectNestedTabUrlToContain); - + cy.get('#tab-button-account').click(); testTabTitle('Tab 1 - Page 1'); - - testUrlEquals(url); - }) - }); - }) - - describe('entry tab contains navigation extras', () => { - const expectNestedTabUrlToContain = 'search=hello#fragment'; - const rootUrlParams = 'test=123#rootFragment'; - const rootUrl = `/lazy/tabs/account?${rootUrlParams}`; - - beforeEach(() => { - cy.visit(rootUrl); + + testUrlContains(rootUrl); + }); + + it('should preserve root url navigation extras when changing tabs', () => { + getSelectedTab(); + cy.get('#tab-button-contact').click(); + testTabTitle('Tab 2 - Page 1'); + + cy.get('#tab-button-account').click(); + testTabTitle('Tab 1 - Page 1'); + + testUrlContains(rootUrl); + }); + + it('should navigate deep then go home and preserve navigation extras', () => { + let tab = getSelectedTab(); + tab.find('#goto-tab1-page2').click(); + cy.ionPageVisible('app-tabs-tab1-nested'); + cy.ionPageHidden('app-tabs-tab1'); + + tab = testTabTitle('Tab 1 - Page 2 (1)'); + + tab.find('#goto-next').click(); + cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); + cy.ionPageHidden('app-tabs-tab1-nested:first-of-type'); + + testTabTitle('Tab 1 - Page 2 (2)'); + + cy.ionTabClick('Tab Two'); + cy.ionPageVisible('app-tabs-tab2'); + cy.ionPageHidden('app-tabs-tab1-nested:last-of-type'); + + testTabTitle('Tab 2 - Page 1'); + + cy.ionTabClick('Tab One'); + cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); + cy.ionPageHidden('app-tabs-tab2'); + + testTabTitle('Tab 1 - Page 2 (2)'); + + cy.ionTabClick('Tab One'); + /** + * Wait for the leaving view to + * be unmounted otherwise testTabTitle + * may get the leaving view before it + * is unmounted. + */ + cy.ionPageVisible('app-tabs-tab1'); + cy.ionPageDoesNotExist('app-tabs-tab1-nested'); + + testTabTitle('Tab 1 - Page 1'); + + testUrlContains(rootUrl); + }); }) - - it('should preserve root url navigation extras when clicking on an active tab to navigate to the root', () => { - const tab = getSelectedTab(); - tab.find('#goto-nested-page1-with-query-params').click(); - - testTabTitle('Tab 1 - Page 2 (1)'); - testUrlContains(expectNestedTabUrlToContain); - - cy.get('#tab-button-account').click(); - testTabTitle('Tab 1 - Page 1'); - - testUrlContains(rootUrl); - }); - - it('should preserve root url navigation extras when changing tabs', () => { - getSelectedTab(); - cy.get('#tab-button-contact').click(); - testTabTitle('Tab 2 - Page 1'); - - cy.get('#tab-button-account').click(); - testTabTitle('Tab 1 - Page 1'); - - testUrlContains(rootUrl); - }); - - it('should navigate deep then go home and preserve navigation extras', () => { - let tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - cy.ionPageVisible('app-tabs-tab1-nested'); - cy.ionPageHidden('app-tabs-tab1'); - - tab = testTabTitle('Tab 1 - Page 2 (1)'); - - tab.find('#goto-next').click(); - cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); - cy.ionPageHidden('app-tabs-tab1-nested:first-of-type'); - - testTabTitle('Tab 1 - Page 2 (2)'); - - cy.ionTabClick('Tab Two'); - cy.ionPageVisible('app-tabs-tab2'); - cy.ionPageHidden('app-tabs-tab1-nested:last-of-type'); - - testTabTitle('Tab 2 - Page 1'); - - cy.ionTabClick('Tab One'); - cy.ionPageVisible('app-tabs-tab1-nested:last-of-type'); - cy.ionPageHidden('app-tabs-tab2'); - - testTabTitle('Tab 1 - Page 2 (2)'); - - cy.ionTabClick('Tab One'); - /** - * Wait for the leaving view to - * be unmounted otherwise testTabTitle - * may get the leaving view before it - * is unmounted. - */ - cy.ionPageVisible('app-tabs-tab1'); - cy.ionPageDoesNotExist('app-tabs-tab1-nested'); - - testTabTitle('Tab 1 - Page 1'); - - testUrlContains(rootUrl); - }); - }) - - describe('entry url - /tabs/account', () => { - beforeEach(() => { - cy.visit('/lazy/tabs/account'); - }); - it('should pop to previous view when leaving tabs outlet', () => { - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); - - cy.get('#goto-tab1-page2').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); - - cy.get('#goto-global').click(); - - cy.get('ion-title').should('contain.text', 'Global Page'); - - cy.get('#goto-prev-pop').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); - - cy.get('#goto-prev').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); - - /** - * Verifies that when entering the tabs outlet directly, - * the navController.pop() method does not pop the previous view, - * when you are at the root of the tabs outlet. - */ - cy.get('#goto-previous-page').click(); - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + + describe('entry url - /tabs/account', () => { + beforeEach(() => { + cy.visit('/lazy/tabs/account'); + }); + it('should pop to previous view when leaving tabs outlet', () => { + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + + cy.get('#goto-tab1-page2').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); + + cy.get('#goto-global').click(); + + cy.get('ion-title').should('contain.text', 'Global Page'); + + cy.get('#goto-prev-pop').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); + + cy.get('#goto-prev').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + + /** + * Verifies that when entering the tabs outlet directly, + * the navController.pop() method does not pop the previous view, + * when you are at the root of the tabs outlet. + */ + cy.get('#goto-previous-page').click(); + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + }); }); - }); - - describe('entry url - /', () => { - it('should pop to the root outlet from the tabs outlet', () => { - cy.visit('/lazy/'); - - cy.get('ion-title').should('contain.text', 'Test App'); - - cy.get('ion-item').contains('Tabs test').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); - - cy.get('#goto-tab1-page2').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); - - cy.get('#goto-global').click(); - - cy.get('ion-title').should('contain.text', 'Global Page'); - - cy.get('#goto-prev-pop').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); - - cy.get('#goto-prev').click(); - - cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); - - cy.get('#goto-previous-page').click(); - - cy.get('ion-title').should('contain.text', 'Test App'); - + + describe('entry url - /', () => { + it('should pop to the root outlet from the tabs outlet', () => { + cy.visit('/lazy/'); + + cy.get('ion-title').should('contain.text', 'Test App'); + + cy.get('ion-item').contains('Tabs test').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + + cy.get('#goto-tab1-page2').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); + + cy.get('#goto-global').click(); + + cy.get('ion-title').should('contain.text', 'Global Page'); + + cy.get('#goto-prev-pop').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 2 (1)'); + + cy.get('#goto-prev').click(); + + cy.get('ion-title').should('contain.text', 'Tab 1 - Page 1'); + + cy.get('#goto-previous-page').click(); + + cy.get('ion-title').should('contain.text', 'Test App'); + + }); }); - }); - - - describe('entry url - /tabs/account/nested/1', () => { - beforeEach(() => { - cy.visit('/lazy/tabs/account/nested/1'); + + + describe('entry url - /tabs/account/nested/1', () => { + beforeEach(() => { + cy.visit('/lazy/tabs/account/nested/1'); + }) + + it('should only display the back-button when there is a page in the stack', () => { + let tab = getSelectedTab(); + tab.find('ion-back-button').should('not.be.visible'); + testTabTitle('Tab 1 - Page 2 (1)'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']); + + cy.get('#tab-button-account').click(); + tab = testTabTitle('Tab 1 - Page 1'); + + tab.find('#goto-tab1-page2').click(); + tab = testTabTitle('Tab 1 - Page 2 (1)'); + tab.find('ion-back-button').should('be.visible'); + }); + + it('should not reuse the same page', () => { + let tab = testTabTitle('Tab 1 - Page 2 (1)'); + tab.find('#goto-next').click(); + tab = testTabTitle('Tab 1 - Page 2 (2)'); + + tab.find('#goto-next').click(); + tab = testTabTitle('Tab 1 - Page 2 (3)'); + + cy.testStack('ion-tabs ion-router-outlet', [ + 'app-tabs-tab1-nested', + 'app-tabs-tab1-nested', + 'app-tabs-tab1-nested' + ]); + + tab = getSelectedTab(); + tab.find('ion-back-button').click(); + tab = testTabTitle('Tab 1 - Page 2 (2)'); + tab.find('ion-back-button').click(); + tab = testTabTitle('Tab 1 - Page 2 (1)'); + + tab.find('ion-back-button').should('not.be.visible'); + + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']); + }); + }) + + describe('entry url - /tabs/lazy', () => { + beforeEach(() => { + cy.visit('/lazy/tabs/lazy'); + }); + + it('should not display the back-button if coming from a different stack', () => { + let tab = testTabTitle('Tab 3 - Page 1'); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']); + + tab = getSelectedTab(); + tab.find('#goto-tab1-page2').click(); + cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']); + + tab = testTabTitle('Tab 1 - Page 2 (1)'); + tab.find('ion-back-button').should('not.be.visible'); + }); + }) + + describe('enter url - /tabs/contact/one', () => { + beforeEach(() => { + cy.visit('/lazy/tabs/contact/one'); + }); + + it('should return to correct tab after going to page in different outlet', () => { + const tab = getSelectedTab(); + tab.find('#goto-nested-page1').click(); + cy.testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']); + + const nestedOutlet = cy.get('app-nested-outlet'); + nestedOutlet.find('ion-back-button').click(); + + testTabTitle('Tab 2 - Page 1'); + }); }) - - it('should only display the back-button when there is a page in the stack', () => { - let tab = getSelectedTab(); - tab.find('ion-back-button').should('not.be.visible'); - testTabTitle('Tab 1 - Page 2 (1)'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']); - - cy.get('#tab-button-account').click(); - tab = testTabTitle('Tab 1 - Page 1'); - - tab.find('#goto-tab1-page2').click(); - tab = testTabTitle('Tab 1 - Page 2 (1)'); - tab.find('ion-back-button').should('be.visible'); - }); - - it('should not reuse the same page', () => { - let tab = testTabTitle('Tab 1 - Page 2 (1)'); - tab.find('#goto-next').click(); - tab = testTabTitle('Tab 1 - Page 2 (2)'); - - tab.find('#goto-next').click(); - tab = testTabTitle('Tab 1 - Page 2 (3)'); - - cy.testStack('ion-tabs ion-router-outlet', [ - 'app-tabs-tab1-nested', - 'app-tabs-tab1-nested', - 'app-tabs-tab1-nested' - ]); - - tab = getSelectedTab(); - tab.find('ion-back-button').click(); - tab = testTabTitle('Tab 1 - Page 2 (2)'); - tab.find('ion-back-button').click(); - tab = testTabTitle('Tab 1 - Page 2 (1)'); - - tab.find('ion-back-button').should('not.be.visible'); - - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab1-nested']); - }); }) - describe('entry url - /tabs/lazy', () => { + describe('Without IonRouterOutlet', () => { beforeEach(() => { - cy.visit('/lazy/tabs/lazy'); - }); + cy.visit('/lazy/tabs-basic'); + }) - it('should not display the back-button if coming from a different stack', () => { - let tab = testTabTitle('Tab 3 - Page 1'); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3']); + it('should show correct tab when clicking the tab button', () => { + cy.get('ion-tab[tab="tab1"]').should('be.visible'); + cy.get('ion-tab[tab="tab2"]').should('not.be.visible'); - tab = getSelectedTab(); - tab.find('#goto-tab1-page2').click(); - cy.testStack('ion-tabs ion-router-outlet', ['app-tabs-tab3', 'app-tabs-tab1-nested']); + cy.get('ion-tab-button[tab="tab2"]').click(); - tab = testTabTitle('Tab 1 - Page 2 (1)'); - tab.find('ion-back-button').should('not.be.visible'); - }); - }) - - describe('enter url - /tabs/contact/one', () => { - beforeEach(() => { - cy.visit('/lazy/tabs/contact/one'); + cy.get('ion-tab[tab="tab1"]').should('not.be.visible'); + cy.get('ion-tab[tab="tab2"]').should('be.visible'); }); - it('should return to correct tab after going to page in different outlet', () => { - const tab = getSelectedTab(); - tab.find('#goto-nested-page1').click(); - cy.testStack('app-nested-outlet ion-router-outlet', ['app-nested-outlet-page']); + it('should not change the URL when clicking the tab button', () => { + cy.url().should('include', '/tabs-basic'); - const nestedOutlet = cy.get('app-nested-outlet'); - nestedOutlet.find('ion-back-button').click(); + cy.get('ion-tab-button[tab="tab2"]').click(); - testTabTitle('Tab 2 - Page 1'); + cy.url().should('include', '/tabs-basic'); }); }) }) diff --git a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts index bb0901a1127..f1798cbdabf 100644 --- a/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts +++ b/packages/angular/test/base/e2e/src/standalone/tabs.spec.ts @@ -1,21 +1,47 @@ describe('Tabs', () => { - beforeEach(() => { - cy.visit('/standalone/tabs'); + describe('Without IonRouterOutlet', () => { + beforeEach(() => { + cy.visit('/standalone/tabs'); + }); + + it('should redirect to the default tab', () => { + cy.get('app-tab-one').should('be.visible'); + cy.contains('Tab 1'); + }); + + it('should render new content when switching tabs', () => { + cy.get('#tab-button-tab-two').click(); + cy.get('app-tab-two').should('be.visible'); + cy.contains('Tab 2'); + }); + + // Issue: https://github.com/ionic-team/ionic-framework/issues/28417 + it('parentOutlet should be defined', () => { + cy.get('#parent-outlet span').should('have.text', 'true'); + }); }); - it('should redirect to the default tab', () => { - cy.get('app-tab-one').should('be.visible'); - cy.contains('Tab 1'); - }); + describe('Without IonRouterOutlet', () => { + beforeEach(() => { + cy.visit('/standalone/tabs-basic'); + }) - it('should render new content when switching tabs', () => { - cy.get('#tab-button-tab-two').click(); - cy.get('app-tab-two').should('be.visible'); - cy.contains('Tab 2'); - }); + it('should show correct tab when clicking the tab button', () => { + cy.get('ion-tab[tab="tab1"]').should('be.visible'); + cy.get('ion-tab[tab="tab2"]').should('not.be.visible'); + + cy.get('ion-tab-button[tab="tab2"]').click(); + + cy.get('ion-tab[tab="tab1"]').should('not.be.visible'); + cy.get('ion-tab[tab="tab2"]').should('be.visible'); + }); + + it('should not change the URL when clicking the tab button', () => { + cy.url().should('include', '/tabs-basic'); + + cy.get('ion-tab-button[tab="tab2"]').click(); - // Fixes https://github.com/ionic-team/ionic-framework/issues/28417 - it('parentOutlet should be defined', () => { - cy.get('#parent-outlet span').should('have.text', 'true'); + cy.url().should('include', '/tabs-basic'); + }); }); }); diff --git a/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts index c7c911da16e..caf27670d2d 100644 --- a/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts +++ b/packages/angular/test/base/src/app/lazy/app-lazy/app.module.ts @@ -27,6 +27,7 @@ import { NavigationPage3Component } from '../navigation-page3/navigation-page3.c import { AlertComponent } from '../alert/alert.component'; import { AccordionComponent } from '../accordion/accordion.component'; import { AccordionModalComponent } from '../accordion/accordion-modal/accordion-modal.component'; +import { TabsBasicComponent } from '../tabs-basic/tabs-basic.component'; @NgModule({ declarations: [ @@ -51,7 +52,8 @@ import { AccordionModalComponent } from '../accordion/accordion-modal/accordion- NavigationPage3Component, AlertComponent, AccordionComponent, - AccordionModalComponent + AccordionModalComponent, + TabsBasicComponent ], imports: [ CommonModule, diff --git a/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts index 8f96df5270f..0e15ea2867d 100644 --- a/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts +++ b/packages/angular/test/base/src/app/lazy/app-lazy/app.routes.ts @@ -18,6 +18,7 @@ import { NavigationPage2Component } from '../navigation-page2/navigation-page2.c import { NavigationPage3Component } from '../navigation-page3/navigation-page3.component'; import { AlertComponent } from '../alert/alert.component'; import { AccordionComponent } from '../accordion/accordion.component'; +import { TabsBasicComponent } from '../tabs-basic/tabs-basic.component'; export const routes: Routes = [ { @@ -65,6 +66,7 @@ export const routes: Routes = [ path: 'tabs-slots', loadComponent: () => import('../tabs-slots.component').then(c => c.TabsSlotsComponent) }, + { path: 'tabs-basic', component: TabsBasicComponent }, { path: 'nested-outlet', component: NestedOutletComponent, diff --git a/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html index 3c78cea6951..7fdca9c50d8 100644 --- a/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/lazy/home-page/home-page.component.html @@ -37,6 +37,11 @@ Tabs test + + + Basic Tabs test + + Nested ion-router-outlet diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css new file mode 100644 index 00000000000..f6f95679b81 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.css @@ -0,0 +1,5 @@ +#test { + position: absolute; + bottom: 100px; + left: 0; +} diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html new file mode 100644 index 00000000000..e000d6932f7 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.html @@ -0,0 +1,53 @@ + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + + + Tab 1 Content + + + Tab 2 Content + + + Tab 3 Content + + + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
diff --git a/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts new file mode 100644 index 00000000000..371dbb8a794 --- /dev/null +++ b/packages/angular/test/base/src/app/lazy/tabs-basic/tabs-basic.component.ts @@ -0,0 +1,35 @@ +import { Component, ViewChild } from '@angular/core'; +import { IonTabBar } from '@ionic/angular'; + +@Component({ + selector: 'app-tabs-basic', + templateUrl: './tabs-basic.component.html', + styleUrls: ['./tabs-basic.component.css'] +}) +export class TabsBasicComponent { + constructor() { } + + tabsWillChangeCounter = 0; + tabsWillChangeEvent = ''; + tabsWillChangeSelectedTab? = ''; + + tabsDidChangeCounter = 0; + tabsDidChangeEvent = ''; + tabsDidChangeSelectedTab? = ''; + + @ViewChild(IonTabBar) tabBar!: IonTabBar; + + onTabWillChange(ev: { tab: string }) { + console.log('ionTabsWillChange', this.tabBar.selectedTab); + this.tabsWillChangeCounter++; + this.tabsWillChangeEvent = ev.tab; + this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; + } + + onTabDidChange(ev: { tab: string }) { + console.log('ionTabsDidChange', this.tabBar.selectedTab); + this.tabsDidChangeCounter++; + this.tabsDidChangeEvent = ev.tab; + this.tabsDidChangeSelectedTab = this.tabBar.selectedTab; + } +} diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 68213d8375e..40afc7a68de 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -28,6 +28,7 @@ export const routes: Routes = [ { path: 'tab-three', loadComponent: () => import('../tabs/tab3.component').then(c => c.TabThreeComponent) } ] }, + { path: 'tabs-basic', loadComponent: () => import('../tabs-basic/tabs-basic.component').then(c => c.TabsBasicComponent) }, { path: 'value-accessors', children: [ diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css new file mode 100644 index 00000000000..f6f95679b81 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.css @@ -0,0 +1,5 @@ +#test { + position: absolute; + bottom: 100px; + left: 0; +} diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html new file mode 100644 index 00000000000..e000d6932f7 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.html @@ -0,0 +1,53 @@ + + + + Tab One + + + + + Tab Two + + + + + Tab Three + + + + + + Tab 1 Content + + + Tab 2 Content + + + Tab 3 Content + + + +
+
    +
  • + ionTabsWillChange counter: {{ tabsWillChangeCounter }} +
  • +
  • + ionTabsWillChange event: {{ tabsWillChangeEvent }} +
  • +
  • + ionTabsWillChange selectedTab: {{ tabsWillChangeSelectedTab }} +
  • +
+
    +
  • + ionTabsDidChange counter: {{ tabsDidChangeCounter }} +
  • +
  • + ionTabsDidChange event: {{ tabsDidChangeEvent }} +
  • +
  • + ionTabsDidChange selectedTab: {{ tabsDidChangeSelectedTab }} +
  • +
+
diff --git a/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts new file mode 100644 index 00000000000..d66a5924df0 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/tabs-basic/tabs-basic.component.ts @@ -0,0 +1,39 @@ +import { Component, ViewChild } from '@angular/core'; +import { IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs, IonTab } from '@ionic/angular/standalone'; +import { addIcons } from 'ionicons'; +import { add, logoIonic, save } from 'ionicons/icons'; + +addIcons({ add, logoIonic, save }); + +@Component({ + selector: 'app-tabs-basic', + templateUrl: './tabs-basic.component.html', + styleUrls: ['./tabs-basic.component.css'], + standalone: true, + imports: [IonTabBar, IonTabButton, IonIcon, IonLabel, IonTabs, IonTab] +}) +export class TabsBasicComponent { + tabsDidChangeCounter = 0; + tabsDidChangeEvent = ''; + tabsDidChangeSelectedTab? = ''; + + tabsWillChangeCounter = 0; + tabsWillChangeEvent = ''; + tabsWillChangeSelectedTab? = ''; + + @ViewChild(IonTabBar) tabBar!: IonTabBar; + + onTabWillChange(ev: { tab: string }) { + console.log('ionTabsWillChange', this.tabBar.selectedTab); + this.tabsWillChangeCounter++; + this.tabsWillChangeEvent = ev.tab; + this.tabsWillChangeSelectedTab = this.tabBar.selectedTab; + } + + onTabDidChange(ev: { tab: string }) { + console.log('ionTabsDidChange', this.tabBar.selectedTab); + this.tabsDidChangeCounter++; + this.tabsDidChangeEvent = ev.tab; + this.tabsDidChangeSelectedTab = this.tabBar.selectedTab; + } +}