diff --git a/CHANGELOG.md b/CHANGELOG.md index c599bde1f8..4e98e25457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic-framework/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **accordion:** accordions expand when using binding ([#25322](https://github.com/ionic-team/ionic-framework/issues/25322)) ([61e571e](https://github.com/ionic-team/ionic-framework/commit/61e571e585ed8ad9b0ca2f98f57bb16616413ba6)), closes [#25307](https://github.com/ionic-team/ionic-framework/issues/25307) +* **datetime:** don't update value on confirm call if no date was selected ([#25338](https://github.com/ionic-team/ionic-framework/issues/25338)) ([9e5b10a](https://github.com/ionic-team/ionic-framework/commit/9e5b10a2155c6b9de565931da384e0e49aeca7b7)) +* **item, list:** list aria roles are added ([#25336](https://github.com/ionic-team/ionic-framework/issues/25336)) ([311c634](https://github.com/ionic-team/ionic-framework/commit/311c634d20e9e597db676d6f54e4b79cfe742a61)), closes [#19939](https://github.com/ionic-team/ionic-framework/issues/19939) +* **menu:** rtl menu no longer disappears on ios 15 ([#25309](https://github.com/ionic-team/ionic-framework/issues/25309)) ([6005431](https://github.com/ionic-team/ionic-framework/commit/60054310afbab6151f6c29ff6e74666acd181a41)), closes [#25192](https://github.com/ionic-team/ionic-framework/issues/25192) +* **modal:** swipe to close on content blocks scroll in ion-nav ([#25300](https://github.com/ionic-team/ionic-framework/issues/25300)) ([fdc55c0](https://github.com/ionic-team/ionic-framework/commit/fdc55c072765c87ad7c783e6d8a238b007f5f3ff)), closes [#25298](https://github.com/ionic-team/ionic-framework/issues/25298) +* **nav:** swipe to go back works inside card modal ([#25333](https://github.com/ionic-team/ionic-framework/issues/25333)) ([0156be6](https://github.com/ionic-team/ionic-framework/commit/0156be61cbf73b25cb3c2cba1bd20adebbb3db4f)), closes [#25327](https://github.com/ionic-team/ionic-framework/issues/25327) +* **range:** interfaces are now correctly exported ([#25342](https://github.com/ionic-team/ionic-framework/issues/25342)) ([15f0c06](https://github.com/ionic-team/ionic-framework/commit/15f0c0669f7598386edf487f408462b90ed91a08)), closes [#25341](https://github.com/ionic-team/ionic-framework/issues/25341) +* **react:** add param types to useIonPopover dismiss function ([#25311](https://github.com/ionic-team/ionic-framework/issues/25311)) ([7111370](https://github.com/ionic-team/ionic-framework/commit/7111370dd787fdec78a1e3368679bc4c73570b98)) +* **react:** IonTabButton will call custom onClick handlers ([#25313](https://github.com/ionic-team/ionic-framework/issues/25313)) ([6034418](https://github.com/ionic-team/ionic-framework/commit/6034418b33c32fdd682c470eaf61b9fcbe86c4bb)), closes [#22511](https://github.com/ionic-team/ionic-framework/issues/22511) +* **refresher:** attach scroll listener to custom scroll target ([#25335](https://github.com/ionic-team/ionic-framework/issues/25335)) ([8f5e4cd](https://github.com/ionic-team/ionic-framework/commit/8f5e4cd9350b10a98afb7c98353c6719eee918bb)), closes [#25318](https://github.com/ionic-team/ionic-framework/issues/25318) +* **types:** improve intellisense with colors ([#25347](https://github.com/ionic-team/ionic-framework/issues/25347)) ([97cfbbb](https://github.com/ionic-team/ionic-framework/commit/97cfbbb65d3e63c32d720e01c7368c68616bb531)) +* **vue:** correct views are now unmounted in tabs ([#25270](https://github.com/ionic-team/ionic-framework/issues/25270)) ([5e23fb1](https://github.com/ionic-team/ionic-framework/commit/5e23fb1ce4e5b6e53828bde59268170f604167ba)), closes [#25255](https://github.com/ionic-team/ionic-framework/issues/25255) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic-framework/compare/v6.1.5...v6.1.6) (2022-05-18) diff --git a/angular/CHANGELOG.md b/angular/CHANGELOG.md index cd3f38f619..f0cb6b427c 100644 --- a/angular/CHANGELOG.md +++ b/angular/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **accordion:** accordions expand when using binding ([#25322](https://github.com/ionic-team/ionic/issues/25322)) ([61e571e](https://github.com/ionic-team/ionic/commit/61e571e585ed8ad9b0ca2f98f57bb16616413ba6)), closes [#25307](https://github.com/ionic-team/ionic/issues/25307) +* **range:** interfaces are now correctly exported ([#25342](https://github.com/ionic-team/ionic/issues/25342)) ([15f0c06](https://github.com/ionic-team/ionic/commit/15f0c0669f7598386edf487f408462b90ed91a08)), closes [#25341](https://github.com/ionic-team/ionic/issues/25341) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/angular diff --git a/angular/package-lock.json b/angular/package-lock.json index 4157deb853..bce7f357d8 100644 --- a/angular/package-lock.json +++ b/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "jsonc-parser": "^3.0.0", "tslib": "^2.0.0" }, @@ -1023,9 +1023,9 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dependencies": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -7951,9 +7951,9 @@ "dev": true }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "requires": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", diff --git a/angular/package.json b/angular/package.json index c479c802e6..2563918ad5 100644 --- a/angular/package.json +++ b/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "6.1.6", + "version": "6.1.7", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -44,7 +44,7 @@ "validate": "npm i && npm run lint && npm run test && npm run build" }, "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "jsonc-parser": "^3.0.0", "tslib": "^2.0.0" }, diff --git a/angular/src/directives/proxies.ts b/angular/src/directives/proxies.ts index 4d13028008..69aa81283b 100644 --- a/angular/src/directives/proxies.ts +++ b/angular/src/directives/proxies.ts @@ -464,16 +464,18 @@ import type { ScrollBaseDetail as IContentScrollBaseDetail } from '@ionic/core'; import type { ScrollDetail as IContentScrollDetail } from '@ionic/core'; export declare interface IonContent extends Components.IonContent { /** - * Emitted when the scroll has started. + * Emitted when the scroll has started. This event is disabled by default. +Set `scrollEvents` to `true` to enable. */ ionScrollStart: EventEmitter>; /** * Emitted while scrolling. This event is disabled by default. -Look at the property: `scrollEvents` +Set `scrollEvents` to `true` to enable. */ ionScroll: EventEmitter>; /** - * Emitted when the scroll has ended. + * Emitted when the scroll has ended. This event is disabled by default. +Set `scrollEvents` to `true` to enable. */ ionScrollEnd: EventEmitter>; diff --git a/angular/src/index.ts b/angular/src/index.ts index 1ad3660a2e..9283675633 100644 --- a/angular/src/index.ts +++ b/angular/src/index.ts @@ -107,6 +107,10 @@ export { PopoverOptions, RadioGroupCustomEvent, RadioGroupChangeEventDetail, + RangeCustomEvent, + RangeChangeEventDetail, + RangeKnobMoveStartEventDetail, + RangeKnobMoveEndEventDetail, RefresherCustomEvent, RefresherEventDetail, RouterEventDetail, diff --git a/angular/test/test-app/e2e/src/accordion.spec.ts b/angular/test/test-app/e2e/src/accordion.spec.ts new file mode 100644 index 0000000000..93455838cf --- /dev/null +++ b/angular/test/test-app/e2e/src/accordion.spec.ts @@ -0,0 +1,19 @@ +describe('Accordion', () => { + beforeEach(() => { + cy.visit('/accordions'); + }); + + it('should correctly expand on multiple modal opens', () => { + cy.get('#open-modal').click(); + + cy.get('ion-accordion:first-of-type').should('have.class', 'accordion-expanded'); + cy.get('ion-accordion:last-of-type').should('not.have.class', 'accordion-expanded'); + + cy.get('#dismiss').click(); + + cy.get('#open-modal').click(); + + cy.get('ion-accordion:first-of-type').should('have.class', 'accordion-expanded'); + cy.get('ion-accordion:last-of-type').should('not.have.class', 'accordion-expanded'); + }); +}); diff --git a/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.html b/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.html new file mode 100644 index 0000000000..2f00ed6bad --- /dev/null +++ b/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.html @@ -0,0 +1,17 @@ + + Dismiss Modal + + + + A + +
A content
+
+ + + B + +
B content
+
+
+
diff --git a/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.ts b/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.ts new file mode 100644 index 0000000000..e73857b225 --- /dev/null +++ b/angular/test/test-app/src/app/accordion/accordion-modal/accordion-modal.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-accordion-modal', + templateUrl: './accordion-modal.component.html', +}) +export class AccordionModalComponent { + modal: HTMLIonModalElement; + + constructor() {} +} diff --git a/angular/test/test-app/src/app/accordion/accordion.component.html b/angular/test/test-app/src/app/accordion/accordion.component.html new file mode 100644 index 0000000000..9545b2b11b --- /dev/null +++ b/angular/test/test-app/src/app/accordion/accordion.component.html @@ -0,0 +1,13 @@ + + + + + + + Accordion test + + + + + Open Modal + diff --git a/angular/test/test-app/src/app/accordion/accordion.component.ts b/angular/test/test-app/src/app/accordion/accordion.component.ts new file mode 100644 index 0000000000..b815863747 --- /dev/null +++ b/angular/test/test-app/src/app/accordion/accordion.component.ts @@ -0,0 +1,22 @@ +import { Component } from '@angular/core'; +import { ModalController } from '@ionic/angular'; +import { AccordionModalComponent } from './accordion-modal/accordion-modal.component'; + +@Component({ + selector: 'app-accordion', + templateUrl: './accordion.component.html', +}) +export class AccordionComponent { + + constructor( + private modalCtrl: ModalController + ) { } + + async open() { + const modal = await this.modalCtrl.create({ + component: AccordionModalComponent, + animated: false, + }); + await modal.present(); + } +} diff --git a/angular/test/test-app/src/app/app-routing.module.ts b/angular/test/test-app/src/app/app-routing.module.ts index 2517081171..be5ad94bb5 100644 --- a/angular/test/test-app/src/app/app-routing.module.ts +++ b/angular/test/test-app/src/app/app-routing.module.ts @@ -20,9 +20,11 @@ import { NavigationPage1Component } from './navigation-page1/navigation-page1.co import { NavigationPage2Component } from './navigation-page2/navigation-page2.component'; import { NavigationPage3Component } from './navigation-page3/navigation-page3.component'; import { AlertComponent } from './alert/alert.component'; +import { AccordionComponent } from './accordion/accordion.component'; const routes: Routes = [ { path: '', component: HomePageComponent }, + { path: 'accordions', component: AccordionComponent }, { path: 'alerts', component: AlertComponent }, { path: 'inputs', component: InputsComponent }, { path: 'form', component: FormComponent }, diff --git a/angular/test/test-app/src/app/app.module.ts b/angular/test/test-app/src/app/app.module.ts index 9eed88d96d..563932b73d 100644 --- a/angular/test/test-app/src/app/app.module.ts +++ b/angular/test/test-app/src/app/app.module.ts @@ -30,6 +30,8 @@ import { NavigationPage1Component } from './navigation-page1/navigation-page1.co import { NavigationPage2Component } from './navigation-page2/navigation-page2.component'; import { NavigationPage3Component } from './navigation-page3/navigation-page3.component'; import { AlertComponent } from './alert/alert.component'; +import { AccordionComponent } from './accordion/accordion.component'; +import { AccordionModalComponent } from './accordion/accordion-modal/accordion-modal.component'; @NgModule({ declarations: [ @@ -56,7 +58,9 @@ import { AlertComponent } from './alert/alert.component'; NavigationPage1Component, NavigationPage2Component, NavigationPage3Component, - AlertComponent + AlertComponent, + AccordionComponent, + AccordionModalComponent ], imports: [ BrowserModule.withServerTransition({ appId: 'serverApp' }), diff --git a/angular/test/test-app/src/app/home-page/home-page.component.html b/angular/test/test-app/src/app/home-page/home-page.component.html index 8609ca6997..741812dc79 100644 --- a/angular/test/test-app/src/app/home-page/home-page.component.html +++ b/angular/test/test-app/src/app/home-page/home-page.component.html @@ -62,5 +62,10 @@ Providers + + + Accordions Test + + diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index 444a2f87e3..f7682576ba 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,24 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **accordion:** accordions expand when using binding ([#25322](https://github.com/ionic-team/ionic/issues/25322)) ([61e571e](https://github.com/ionic-team/ionic/commit/61e571e585ed8ad9b0ca2f98f57bb16616413ba6)), closes [#25307](https://github.com/ionic-team/ionic/issues/25307) +* **datetime:** don't update value on confirm call if no date was selected ([#25338](https://github.com/ionic-team/ionic/issues/25338)) ([9e5b10a](https://github.com/ionic-team/ionic/commit/9e5b10a2155c6b9de565931da384e0e49aeca7b7)) +* **item, list:** list aria roles are added ([#25336](https://github.com/ionic-team/ionic/issues/25336)) ([311c634](https://github.com/ionic-team/ionic/commit/311c634d20e9e597db676d6f54e4b79cfe742a61)), closes [#19939](https://github.com/ionic-team/ionic/issues/19939) +* **menu:** rtl menu no longer disappears on ios 15 ([#25309](https://github.com/ionic-team/ionic/issues/25309)) ([6005431](https://github.com/ionic-team/ionic/commit/60054310afbab6151f6c29ff6e74666acd181a41)), closes [#25192](https://github.com/ionic-team/ionic/issues/25192) +* **modal:** swipe to close on content blocks scroll in ion-nav ([#25300](https://github.com/ionic-team/ionic/issues/25300)) ([fdc55c0](https://github.com/ionic-team/ionic/commit/fdc55c072765c87ad7c783e6d8a238b007f5f3ff)), closes [#25298](https://github.com/ionic-team/ionic/issues/25298) +* **nav:** swipe to go back works inside card modal ([#25333](https://github.com/ionic-team/ionic/issues/25333)) ([0156be6](https://github.com/ionic-team/ionic/commit/0156be61cbf73b25cb3c2cba1bd20adebbb3db4f)), closes [#25327](https://github.com/ionic-team/ionic/issues/25327) +* **refresher:** attach scroll listener to custom scroll target ([#25335](https://github.com/ionic-team/ionic/issues/25335)) ([8f5e4cd](https://github.com/ionic-team/ionic/commit/8f5e4cd9350b10a98afb7c98353c6719eee918bb)), closes [#25318](https://github.com/ionic-team/ionic/issues/25318) +* **types:** improve intellisense with colors ([#25347](https://github.com/ionic-team/ionic/issues/25347)) ([97cfbbb](https://github.com/ionic-team/ionic/commit/97cfbbb65d3e63c32d720e01c7368c68616bb531)) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) diff --git a/core/api.txt b/core/api.txt index 52477ca83f..0c5bcdcf33 100644 --- a/core/api.txt +++ b/core/api.txt @@ -103,7 +103,7 @@ ion-avatar,shadow ion-avatar,css-prop,--border-radius ion-back-button,shadow -ion-back-button,prop,color,string | undefined,undefined,false,true +ion-back-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-back-button,prop,defaultHref,string | undefined,undefined,false,false ion-back-button,prop,disabled,boolean,false,false,true ion-back-button,prop,icon,null | string | undefined,undefined,false,false @@ -154,7 +154,7 @@ ion-backdrop,prop,visible,boolean,true,false,false ion-backdrop,event,ionBackdropTap,void,true ion-badge,shadow -ion-badge,prop,color,string | undefined,undefined,false,true +ion-badge,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-badge,prop,mode,"ios" | "md",undefined,false,false ion-badge,css-prop,--background ion-badge,css-prop,--color @@ -165,7 +165,7 @@ ion-badge,css-prop,--padding-top ion-breadcrumb,shadow ion-breadcrumb,prop,active,boolean,false,false,false -ion-breadcrumb,prop,color,string | undefined,undefined,false,false +ion-breadcrumb,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,false ion-breadcrumb,prop,disabled,boolean,false,false,false ion-breadcrumb,prop,download,string | undefined,undefined,false,false ion-breadcrumb,prop,href,string | undefined,undefined,false,false @@ -187,7 +187,7 @@ ion-breadcrumb,part,native ion-breadcrumb,part,separator ion-breadcrumbs,shadow -ion-breadcrumbs,prop,color,string | undefined,undefined,false,false +ion-breadcrumbs,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,false ion-breadcrumbs,prop,itemsAfterCollapse,number,1,false,false ion-breadcrumbs,prop,itemsBeforeCollapse,number,1,false,false ion-breadcrumbs,prop,maxItems,number | undefined,undefined,false,false @@ -198,7 +198,7 @@ ion-breadcrumbs,css-prop,--color ion-button,shadow ion-button,prop,buttonType,string,'button',false,false -ion-button,prop,color,string | undefined,undefined,false,true +ion-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-button,prop,disabled,boolean,false,false,true ion-button,prop,download,string | undefined,undefined,false,false ion-button,prop,expand,"block" | "full" | undefined,undefined,false,true @@ -245,7 +245,7 @@ ion-buttons,prop,collapse,boolean,false,false,false ion-card,shadow ion-card,prop,button,boolean,false,false,false -ion-card,prop,color,string | undefined,undefined,false,true +ion-card,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-card,prop,disabled,boolean,false,false,false ion-card,prop,download,string | undefined,undefined,false,false ion-card,prop,href,string | undefined,undefined,false,false @@ -263,23 +263,23 @@ ion-card-content,none ion-card-content,prop,mode,"ios" | "md",undefined,false,false ion-card-header,shadow -ion-card-header,prop,color,string | undefined,undefined,false,true +ion-card-header,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-card-header,prop,mode,"ios" | "md",undefined,false,false ion-card-header,prop,translucent,boolean,false,false,false ion-card-subtitle,shadow -ion-card-subtitle,prop,color,string | undefined,undefined,false,true +ion-card-subtitle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-card-subtitle,prop,mode,"ios" | "md",undefined,false,false ion-card-subtitle,css-prop,--color ion-card-title,shadow -ion-card-title,prop,color,string | undefined,undefined,false,true +ion-card-title,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-card-title,prop,mode,"ios" | "md",undefined,false,false ion-card-title,css-prop,--color ion-checkbox,shadow ion-checkbox,prop,checked,boolean,false,false,false -ion-checkbox,prop,color,string | undefined,undefined,false,true +ion-checkbox,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-checkbox,prop,disabled,boolean,false,false,false ion-checkbox,prop,indeterminate,boolean,false,false,false ion-checkbox,prop,mode,"ios" | "md",undefined,false,false @@ -303,7 +303,7 @@ ion-checkbox,part,container ion-checkbox,part,mark ion-chip,shadow -ion-chip,prop,color,string | undefined,undefined,false,true +ion-chip,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-chip,prop,disabled,boolean,false,false,false ion-chip,prop,mode,"ios" | "md",undefined,false,false ion-chip,prop,outline,boolean,false,false,false @@ -344,7 +344,7 @@ ion-col,css-prop,--ion-grid-column-padding-xs ion-col,css-prop,--ion-grid-columns ion-content,shadow -ion-content,prop,color,string | undefined,undefined,false,true +ion-content,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-content,prop,forceOverscroll,boolean | undefined,undefined,false,false ion-content,prop,fullscreen,boolean,false,false,false ion-content,prop,scrollEvents,boolean,false,false,false @@ -373,7 +373,7 @@ ion-content,part,scroll ion-datetime,shadow ion-datetime,prop,cancelText,string,'Cancel',false,false ion-datetime,prop,clearText,string,'Clear',false,false -ion-datetime,prop,color,string | undefined,'primary',false,false +ion-datetime,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,'primary',false,false ion-datetime,prop,dayValues,number | number[] | string | undefined,undefined,false,false ion-datetime,prop,disabled,boolean,false,false,false ion-datetime,prop,doneText,string,'Done',false,false @@ -418,7 +418,7 @@ ion-fab,method,close,close() => Promise ion-fab-button,shadow ion-fab-button,prop,activated,boolean,false,false,false ion-fab-button,prop,closeIcon,string,close,false,false -ion-fab-button,prop,color,string | undefined,undefined,false,true +ion-fab-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-fab-button,prop,disabled,boolean,false,false,false ion-fab-button,prop,download,string | undefined,undefined,false,false ion-fab-button,prop,href,string | undefined,undefined,false,false @@ -515,7 +515,7 @@ ion-input,prop,autocorrect,"off" | "on",'off',false,false ion-input,prop,autofocus,boolean,false,false,false ion-input,prop,clearInput,boolean,false,false,false ion-input,prop,clearOnEdit,boolean | undefined,undefined,false,false -ion-input,prop,color,string | undefined,undefined,false,true +ion-input,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-input,prop,debounce,number,0,false,false ion-input,prop,disabled,boolean,false,false,false ion-input,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false @@ -555,7 +555,7 @@ ion-input,css-prop,--placeholder-opacity ion-item,shadow ion-item,prop,button,boolean,false,false,false -ion-item,prop,color,string | undefined,undefined,false,true +ion-item,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-item,prop,counter,boolean,false,false,false ion-item,prop,counterFormatter,((inputLength: number, maxLength: number) => string) | undefined,undefined,false,false ion-item,prop,detail,boolean | undefined,undefined,false,false @@ -611,7 +611,7 @@ ion-item,part,detail-icon ion-item,part,native ion-item-divider,shadow -ion-item-divider,prop,color,string | undefined,undefined,false,true +ion-item-divider,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-item-divider,prop,mode,"ios" | "md",undefined,false,false ion-item-divider,prop,sticky,boolean,false,false,false ion-item-divider,css-prop,--background @@ -628,7 +628,7 @@ ion-item-divider,css-prop,--padding-top ion-item-group,none ion-item-option,shadow -ion-item-option,prop,color,string | undefined,undefined,false,true +ion-item-option,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-item-option,prop,disabled,boolean,false,false,false ion-item-option,prop,download,string | undefined,undefined,false,false ion-item-option,prop,expandable,boolean,false,false,false @@ -655,7 +655,7 @@ ion-item-sliding,method,open,open(side: Side | undefined) => Promise ion-item-sliding,event,ionDrag,any,true ion-label,scoped -ion-label,prop,color,string | undefined,undefined,false,true +ion-label,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-label,prop,mode,"ios" | "md",undefined,false,false ion-label,prop,position,"fixed" | "floating" | "stacked" | undefined,undefined,false,false ion-label,css-prop,--color @@ -667,7 +667,7 @@ ion-list,prop,mode,"ios" | "md",undefined,false,false ion-list,method,closeSlidingItems,closeSlidingItems() => Promise ion-list-header,shadow -ion-list-header,prop,color,string | undefined,undefined,false,true +ion-list-header,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-list-header,prop,lines,"full" | "inset" | "none" | undefined,undefined,false,false ion-list-header,prop,mode,"ios" | "md",undefined,false,false ion-list-header,css-prop,--background @@ -739,7 +739,7 @@ ion-menu,part,container ion-menu-button,shadow ion-menu-button,prop,autoHide,boolean,true,false,false -ion-menu-button,prop,color,string | undefined,undefined,false,true +ion-menu-button,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-menu-button,prop,disabled,boolean,false,false,false ion-menu-button,prop,menu,string | undefined,undefined,false,false ion-menu-button,prop,mode,"ios" | "md",undefined,false,false @@ -842,7 +842,7 @@ ion-nav-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | und ion-nav-link,prop,routerDirection,"back" | "forward" | "root",'forward',false,false ion-note,shadow -ion-note,prop,color,string | undefined,undefined,false,true +ion-note,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-note,prop,mode,"ios" | "md",undefined,false,false ion-note,css-prop,--color @@ -933,7 +933,7 @@ ion-popover,part,content ion-progress-bar,shadow ion-progress-bar,prop,buffer,number,1,false,false -ion-progress-bar,prop,color,string | undefined,undefined,false,true +ion-progress-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-progress-bar,prop,mode,"ios" | "md",undefined,false,false ion-progress-bar,prop,reversed,boolean,false,false,false ion-progress-bar,prop,type,"determinate" | "indeterminate",'determinate',false,false @@ -946,7 +946,7 @@ ion-progress-bar,part,stream ion-progress-bar,part,track ion-radio,shadow -ion-radio,prop,color,string | undefined,undefined,false,true +ion-radio,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-radio,prop,disabled,boolean,false,false,false ion-radio,prop,mode,"ios" | "md",undefined,false,false ion-radio,prop,name,string,this.inputId,false,false @@ -967,7 +967,7 @@ ion-radio-group,prop,value,any,undefined,false,false ion-radio-group,event,ionChange,RadioGroupChangeEventDetail,true ion-range,shadow -ion-range,prop,color,string | undefined,undefined,false,true +ion-range,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-range,prop,debounce,number,0,false,false ion-range,prop,disabled,boolean,false,false,false ion-range,prop,dualKnobs,boolean,false,false,false @@ -1058,7 +1058,7 @@ ion-router,event,ionRouteDidChange,RouterEventDetail,true ion-router,event,ionRouteWillChange,RouterEventDetail,true ion-router-link,shadow -ion-router-link,prop,color,string | undefined,undefined,false,true +ion-router-link,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-router-link,prop,href,string | undefined,undefined,false,false ion-router-link,prop,rel,string | undefined,undefined,false,false ion-router-link,prop,routerAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false @@ -1081,7 +1081,7 @@ ion-searchbar,prop,autocorrect,"off" | "on",'off',false,false ion-searchbar,prop,cancelButtonIcon,string,config.get('backButtonIcon', arrowBackSharp) as string,false,false ion-searchbar,prop,cancelButtonText,string,'Cancel',false,false ion-searchbar,prop,clearIcon,string | undefined,undefined,false,false -ion-searchbar,prop,color,string | undefined,undefined,false,true +ion-searchbar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-searchbar,prop,debounce,number,250,false,false ion-searchbar,prop,disabled,boolean,false,false,false ion-searchbar,prop,enterkeyhint,"done" | "enter" | "go" | "next" | "previous" | "search" | "send" | undefined,undefined,false,false @@ -1115,7 +1115,7 @@ ion-searchbar,css-prop,--placeholder-font-weight ion-searchbar,css-prop,--placeholder-opacity ion-segment,shadow -ion-segment,prop,color,string | undefined,undefined,false,true +ion-segment,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-segment,prop,disabled,boolean,false,false,false ion-segment,prop,mode,"ios" | "md",undefined,false,false ion-segment,prop,scrollable,boolean,false,false,false @@ -1249,7 +1249,7 @@ ion-slides,css-prop,--scroll-bar-background ion-slides,css-prop,--scroll-bar-background-active ion-spinner,shadow -ion-spinner,prop,color,string | undefined,undefined,false,true +ion-spinner,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-spinner,prop,duration,number | undefined,undefined,false,false ion-spinner,prop,name,"bubbles" | "circles" | "circular" | "crescent" | "dots" | "lines" | "lines-sharp" | "lines-sharp-small" | "lines-small" | undefined,undefined,false,false ion-spinner,prop,paused,boolean,false,false,false @@ -1271,7 +1271,7 @@ ion-tab,prop,tab,string,undefined,true,false ion-tab,method,setActive,setActive() => Promise ion-tab-bar,shadow -ion-tab-bar,prop,color,string | undefined,undefined,false,true +ion-tab-bar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-tab-bar,prop,mode,"ios" | "md",undefined,false,false ion-tab-bar,prop,selectedTab,string | undefined,undefined,false,false ion-tab-bar,prop,translucent,boolean,false,false,false @@ -1310,7 +1310,7 @@ ion-tabs,event,ionTabsDidChange,{ tab: string; },false ion-tabs,event,ionTabsWillChange,{ tab: string; },false ion-text,shadow -ion-text,prop,color,string | undefined,undefined,false,true +ion-text,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-text,prop,mode,"ios" | "md",undefined,false,false ion-textarea,scoped @@ -1318,7 +1318,7 @@ ion-textarea,prop,autoGrow,boolean,false,false,false ion-textarea,prop,autocapitalize,string,'none',false,false ion-textarea,prop,autofocus,boolean,false,false,false ion-textarea,prop,clearOnEdit,boolean,false,false,false -ion-textarea,prop,color,string | undefined,undefined,false,true +ion-textarea,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-textarea,prop,cols,number | undefined,undefined,false,false ion-textarea,prop,debounce,number,0,false,false ion-textarea,prop,disabled,boolean,false,false,false @@ -1358,14 +1358,14 @@ ion-thumbnail,css-prop,--border-radius ion-thumbnail,css-prop,--size ion-title,shadow -ion-title,prop,color,string | undefined,undefined,false,true +ion-title,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-title,prop,size,"large" | "small" | undefined,undefined,false,false ion-title,css-prop,--color ion-toast,shadow ion-toast,prop,animated,boolean,true,false,false ion-toast,prop,buttons,(string | ToastButton)[] | undefined,undefined,false,false -ion-toast,prop,color,string | undefined,undefined,false,true +ion-toast,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-toast,prop,cssClass,string | string[] | undefined,undefined,false,false ion-toast,prop,duration,number,0,false,false ion-toast,prop,enterAnimation,((baseEl: any, opts?: any) => Animation) | undefined,undefined,false,false @@ -1411,7 +1411,7 @@ ion-toast,part,message ion-toggle,shadow ion-toggle,prop,checked,boolean,false,false,false -ion-toggle,prop,color,string | undefined,undefined,false,true +ion-toggle,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-toggle,prop,disabled,boolean,false,false,false ion-toggle,prop,mode,"ios" | "md",undefined,false,false ion-toggle,prop,name,string,this.inputId,false,false @@ -1435,7 +1435,7 @@ ion-toggle,part,handle ion-toggle,part,track ion-toolbar,shadow -ion-toolbar,prop,color,string | undefined,undefined,false,true +ion-toolbar,prop,color,"danger" | "dark" | "light" | "medium" | "primary" | "secondary" | "success" | "tertiary" | "warning" | string & Record | undefined,undefined,false,true ion-toolbar,prop,mode,"ios" | "md",undefined,false,false ion-toolbar,css-prop,--background ion-toolbar,css-prop,--border-color diff --git a/core/package-lock.json b/core/package-lock.json index b3e7ee5cd7..a57bddb5c5 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { "@stencil/core": "^2.14.2", diff --git a/core/package.json b/core/package.json index 9c0016b481..b075592df8 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "6.1.6", + "version": "6.1.7", "description": "Base components for Ionic", "keywords": [ "ionic", diff --git a/core/src/components.d.ts b/core/src/components.d.ts index ebcbcf67ad..5f52e20379 100644 --- a/core/src/components.d.ts +++ b/core/src/components.d.ts @@ -4402,15 +4402,15 @@ declare namespace LocalJSX { */ "fullscreen"?: boolean; /** - * Emitted while scrolling. This event is disabled by default. Look at the property: `scrollEvents` + * Emitted while scrolling. This event is disabled by default. Set `scrollEvents` to `true` to enable. */ "onIonScroll"?: (event: CustomEvent) => void; /** - * Emitted when the scroll has ended. + * Emitted when the scroll has ended. This event is disabled by default. Set `scrollEvents` to `true` to enable. */ "onIonScrollEnd"?: (event: CustomEvent) => void; /** - * Emitted when the scroll has started. + * Emitted when the scroll has started. This event is disabled by default. Set `scrollEvents` to `true` to enable. */ "onIonScrollStart"?: (event: CustomEvent) => void; /** diff --git a/core/src/components/accordion/accordion.tsx b/core/src/components/accordion/accordion.tsx index 50edcf1681..425103509b 100644 --- a/core/src/components/accordion/accordion.tsx +++ b/core/src/components/accordion/accordion.tsx @@ -211,7 +211,8 @@ export class Accordion implements ComponentInterface { }; private expandAccordion = (initialUpdate = false) => { - if (initialUpdate) { + const { contentEl, contentElWrapper } = this; + if (initialUpdate || contentEl === undefined || contentElWrapper === undefined) { this.state = AccordionState.Expanded; return; } @@ -220,11 +221,6 @@ export class Accordion implements ComponentInterface { return; } - const { contentEl, contentElWrapper } = this; - if (contentEl === undefined || contentElWrapper === undefined) { - return; - } - if (this.currentRaf !== undefined) { cancelAnimationFrame(this.currentRaf); } @@ -250,7 +246,8 @@ export class Accordion implements ComponentInterface { }; private collapseAccordion = (initialUpdate = false) => { - if (initialUpdate) { + const { contentEl } = this; + if (initialUpdate || contentEl === undefined) { this.state = AccordionState.Collapsed; return; } @@ -259,11 +256,6 @@ export class Accordion implements ComponentInterface { return; } - const { contentEl } = this; - if (contentEl === undefined) { - return; - } - if (this.currentRaf !== undefined) { cancelAnimationFrame(this.currentRaf); } diff --git a/core/src/components/app/test/safe-area/app.e2e.ts b/core/src/components/app/test/safe-area/app.e2e.ts new file mode 100644 index 0000000000..d754398d54 --- /dev/null +++ b/core/src/components/app/test/safe-area/app.e2e.ts @@ -0,0 +1,37 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; +import type { E2EPage } from '@utils/test/playwright'; + +test.describe('app: safe-area', () => { + const testOverlay = async (page: E2EPage, trigger: string, event: string, screenshotModifier: string) => { + const presentEvent = await page.spyOnEvent(event); + + await page.click(trigger); + await presentEvent.next(); + + // Sometimes the inner content takes a frame or two to render + await page.waitForChanges(); + + expect(await page.screenshot()).toMatchSnapshot(`app-${screenshotModifier}-diff-${page.getSnapshotSettings()}.png`); + }; + test.beforeEach(async ({ page }, testInfo) => { + test.skip( + testInfo.project.metadata.rtl === true, + 'Safe area tests only check top and bottom edges. RTL checks are not required here.' + ); + + await page.goto(`/src/components/app/test/safe-area`); + }); + test('should not have visual regressions with action sheet', async ({ page }) => { + await testOverlay(page, '#show-action-sheet', 'ionActionSheetDidPresent', 'action-sheet'); + }); + test('should not have visual regressions with menu', async ({ page }) => { + await testOverlay(page, '#show-menu', 'ionDidOpen', 'menu'); + }); + test('should not have visual regressions with picker', async ({ page }) => { + await testOverlay(page, '#show-picker', 'ionPickerDidPresent', 'picker'); + }); + test('should not have visual regressions with toast', async ({ page }) => { + await testOverlay(page, '#show-toast', 'ionToastDidPresent', 'toast'); + }); +}); diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..6d73247b6d Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..3b179024e5 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..9ffe8875de Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..c165112096 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..9bc20f116d Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..f0372617b7 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-action-sheet-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..2e3bf0e13d Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..4d8b3c94f5 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ceba1a5fd7 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..38849ef9cc Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..12edf319a7 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..a6412b100e Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-menu-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..88bdf2ddb1 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..97ec9f3ca9 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..ffdafc720d Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..01b1a33646 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..ac8da5bf8c Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..65282b9798 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-picker-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..d1601e3bf2 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..17b2f7d6ae Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..7e22ecdfb4 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..f329571e56 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..6053f90652 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..0528bae260 Binary files /dev/null and b/core/src/components/app/test/safe-area/app.e2e.ts-snapshots/app-toast-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/app/test/safe-area/e2e.ts b/core/src/components/app/test/safe-area/e2e.ts deleted file mode 100644 index 686a2a9c6a..0000000000 --- a/core/src/components/app/test/safe-area/e2e.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { newE2EPage } from '@stencil/core/testing'; - -test('app: safe-area', async () => { - const page = await newE2EPage({ - url: '/src/components/app/test/safe-area?ionic:_testing=true', - }); - - expect(await page.compareScreenshot()).toMatchScreenshot(); - - // Action Sheet - await page.click('#show-action-sheet'); - await page.waitForChanges(); - const actionSheet = await page.find('ion-action-sheet'); - expect(await page.compareScreenshot('action-sheet')).toMatchScreenshot(); - await actionSheet.callMethod('dismiss'); - - // Menu - await page.click('#show-menu'); - await page.waitForChanges(); - const menu = await page.find('ion-menu'); - expect(await page.compareScreenshot('menu')).toMatchScreenshot(); - await menu.callMethod('close'); - - // Picker - await page.click('#show-picker'); - await page.waitForChanges(); - const picker = await page.find('ion-picker'); - expect(await page.compareScreenshot('picker')).toMatchScreenshot(); - await picker.callMethod('dismiss'); - - // Toast - await page.click('#show-toast'); - await page.waitForChanges(); - const toast = await page.find('ion-toast'); - expect(await page.compareScreenshot('toast')).toMatchScreenshot(); - await toast.callMethod('dismiss'); -}); diff --git a/core/src/components/button/test/basic/button.e2e.ts b/core/src/components/button/test/basic/button.e2e.ts index de790135d7..dee4aa6526 100644 --- a/core/src/components/button/test/basic/button.e2e.ts +++ b/core/src/components/button/test/basic/button.e2e.ts @@ -10,3 +10,28 @@ test.describe('button: basic', () => { expect(await page.screenshot({ fullPage: true })).toMatchSnapshot(`button-diff-${page.getSnapshotSettings()}.png`); }); }); + +test.describe('button: ripple effect', () => { + test('should not have visual regressions', async ({ page }, testInfo) => { + test.skip(testInfo.project.metadata.mode !== 'md', 'Ripple effect is only available in MD mode.'); + + await page.goto(`/src/components/button/test/basic?ionic:_testing=false`); + + const button = page.locator('#default'); + + await button.scrollIntoViewIfNeeded(); + + const boundingBox = await button.boundingBox(); + + if (boundingBox) { + await page.mouse.move(boundingBox.x + boundingBox.width / 2, boundingBox.y + boundingBox.height / 2); + await page.mouse.down(); + } + + await page.waitForSelector('#default.ion-activated'); + + expect(await button.screenshot({ animations: 'disabled' })).toMatchSnapshot( + `button-ripple-effect-${page.getSnapshotSettings()}.png` + ); + }); +}); diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Chrome-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..79dc27f0e9 Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Firefox-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b0d4ac7332 Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Safari-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..9c83d88e2a Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Chrome-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..df1d82e03e Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Firefox-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..d5c3b223b2 Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Safari-linux.png b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..55e3434fc8 Binary files /dev/null and b/core/src/components/button/test/basic/button.e2e.ts-snapshots/button-ripple-effect-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/button/test/basic/index.html b/core/src/components/button/test/basic/index.html index 96943e9786..c8b44f5b63 100644 --- a/core/src/components/button/test/basic/index.html +++ b/core/src/components/button/test/basic/index.html @@ -24,7 +24,7 @@

- Default + Default Default.focused Default.activated

diff --git a/core/src/components/content/content.tsx b/core/src/components/content/content.tsx index 20412285ac..0e71c2b8bf 100644 --- a/core/src/components/content/content.tsx +++ b/core/src/components/content/content.tsx @@ -92,18 +92,20 @@ export class Content implements ComponentInterface { @Prop() scrollEvents = false; /** - * Emitted when the scroll has started. + * Emitted when the scroll has started. This event is disabled by default. + * Set `scrollEvents` to `true` to enable. */ @Event() ionScrollStart!: EventEmitter; /** * Emitted while scrolling. This event is disabled by default. - * Look at the property: `scrollEvents` + * Set `scrollEvents` to `true` to enable. */ @Event() ionScroll!: EventEmitter; /** - * Emitted when the scroll has ended. + * Emitted when the scroll has ended. This event is disabled by default. + * Set `scrollEvents` to `true` to enable. */ @Event() ionScrollEnd!: EventEmitter; diff --git a/core/src/components/datetime/datetime.tsx b/core/src/components/datetime/datetime.tsx index 6d2f4f3239..1cd0eb86ed 100644 --- a/core/src/components/datetime/datetime.tsx +++ b/core/src/components/datetime/datetime.tsx @@ -442,18 +442,26 @@ export class Datetime implements ComponentInterface { @Method() async confirm(closeOverlay = false) { /** - * Prevent convertDataToISO from doing any - * kind of transformation based on timezone - * This cancels out any change it attempts to make - * - * Important: Take the timezone offset based on - * the date that is currently selected, otherwise - * there can be 1 hr difference when dealing w/ DST + * If highlightActiveParts is false, this means the datetime was inited + * without a value, and the user hasn't selected one yet. We shouldn't + * update the value in this case, since otherwise it would be mysteriously + * set to today. */ - const date = new Date(convertDataToISO(this.activeParts)); - this.activeParts.tzOffset = date.getTimezoneOffset() * -1; + if (this.highlightActiveParts) { + /** + * Prevent convertDataToISO from doing any + * kind of transformation based on timezone + * This cancels out any change it attempts to make + * + * Important: Take the timezone offset based on + * the date that is currently selected, otherwise + * there can be 1 hr difference when dealing w/ DST + */ + const date = new Date(convertDataToISO(this.activeParts)); + this.activeParts.tzOffset = date.getTimezoneOffset() * -1; - this.value = convertDataToISO(this.activeParts); + this.value = convertDataToISO(this.activeParts); + } if (closeOverlay) { this.closeParentOverlay(); diff --git a/core/src/components/datetime/test/basic/datetime.e2e.ts b/core/src/components/datetime/test/basic/datetime.e2e.ts index 9459ae7cac..9c59c03b01 100644 --- a/core/src/components/datetime/test/basic/datetime.e2e.ts +++ b/core/src/components/datetime/test/basic/datetime.e2e.ts @@ -29,3 +29,20 @@ test.describe('datetime: selecting a day', () => { await testHighlight(page, 'custom-datetime'); }); }); + +test.describe('datetime: confirm date', () => { + test('should not update value if Done was clicked without selecting a day first', async ({ page }) => { + await page.goto('/src/components/datetime/test/basic'); + + const datetime = page.locator('#custom-datetime'); + + const value = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value); + expect(value).toBeUndefined(); + + await datetime.evaluate(async (el: HTMLIonDatetimeElement) => { + await el.confirm(); + }); + const valueAgain = await datetime.evaluate((el: HTMLIonDatetimeElement) => el.value); + expect(valueAgain).toBeUndefined(); + }); +}); diff --git a/core/src/components/item/item.tsx b/core/src/components/item/item.tsx index 859bd06aaa..48dff444e5 100644 --- a/core/src/components/item/item.tsx +++ b/core/src/components/item/item.tsx @@ -389,6 +389,7 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac }); const ariaDisabled = disabled || childStyles['item-interactive-disabled'] ? 'true' : null; const fillValue = fill || 'none'; + const inList = hostContext('ion-list', this.el); return ( diff --git a/core/src/components/list/list.tsx b/core/src/components/list/list.tsx index 2c62050308..11007ca269 100644 --- a/core/src/components/list/list.tsx +++ b/core/src/components/list/list.tsx @@ -46,6 +46,7 @@ export class List implements ComponentInterface { const { lines, inset } = this; return ( + + + + List - a11y + + + + + + + + + +
+

List - a11y

+ + Item 1 + Item 2 + Item 3 + +
+ + diff --git a/core/src/components/list/test/a11y/list.e2e.ts b/core/src/components/list/test/a11y/list.e2e.ts new file mode 100644 index 0000000000..354e82f2ae --- /dev/null +++ b/core/src/components/list/test/a11y/list.e2e.ts @@ -0,0 +1,13 @@ +import AxeBuilder from '@axe-core/playwright'; +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('list: a11y', () => { + test('should not have accessibility violations', async ({ page }) => { + await page.goto(`/src/components/list/test/a11y`); + + const results = await new AxeBuilder({ page }).analyze(); + + expect(results.violations).toEqual([]); + }); +}); diff --git a/core/src/components/menu/menu.scss b/core/src/components/menu/menu.scss index 890c424c2e..343d36c9bf 100644 --- a/core/src/components/menu/menu.scss +++ b/core/src/components/menu/menu.scss @@ -38,7 +38,7 @@ .menu-inner { @include position(0, auto, 0, 0); - @include transform(translate3d(-9999px, 0, 0)); + @include transform(translateX(-9999px)); display: flex; position: absolute; diff --git a/core/src/components/modal/gestures/swipe-to-close.ts b/core/src/components/modal/gestures/swipe-to-close.ts index 375d53565e..c056c174a3 100644 --- a/core/src/components/modal/gestures/swipe-to-close.ts +++ b/core/src/components/modal/gestures/swipe-to-close.ts @@ -1,9 +1,9 @@ import type { Animation } from '../../../interface'; import { getTimeGivenProgression } from '../../../utils/animation/cubic-bezier'; -import { isIonContent } from '../../../utils/content'; +import { isIonContent, findClosestIonContent } from '../../../utils/content'; import type { GestureDetail } from '../../../utils/gesture'; import { createGesture } from '../../../utils/gesture'; -import { clamp } from '../../../utils/helpers'; +import { clamp, getElementRoot } from '../../../utils/helpers'; import { calculateSpringStep, handleCanDismiss } from './utils'; @@ -12,20 +12,16 @@ export const SwipeToCloseDefaults = { MIN_PRESENTING_SCALE: 0.93, }; -export const createSwipeToCloseGesture = ( - el: HTMLIonModalElement, - contentEl: HTMLElement, - scrollEl: HTMLElement, - animation: Animation, - onDismiss: () => void -) => { +export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: Animation, onDismiss: () => void) => { const height = el.offsetHeight; let isOpen = false; let canDismissBlocksGesture = false; + let contentEl: HTMLElement | null = null; + let scrollEl: HTMLElement | null = null; const canDismissMaxStep = 0.2; - const hasRefresherInContent = !!contentEl.querySelector('ion-refresher'); + let initialScrollY = true; const getScrollY = () => { - if (isIonContent(contentEl)) { + if (contentEl && isIonContent(contentEl)) { return (contentEl as HTMLIonContentElement).scrollY; /** * Custom scroll containers are intended to be @@ -36,9 +32,12 @@ export const createSwipeToCloseGesture = ( return true; } }; - const initialScrollY = getScrollY(); const disableContentScroll = () => { + if (!contentEl) { + return; + } + if (isIonContent(contentEl)) { (contentEl as HTMLIonContentElement).scrollY = false; } else { @@ -47,6 +46,10 @@ export const createSwipeToCloseGesture = ( }; const resetContentScroll = () => { + if (!contentEl) { + return; + } + if (isIonContent(contentEl)) { (contentEl as HTMLIonContentElement).scrollY = initialScrollY; } else { @@ -67,9 +70,17 @@ export const createSwipeToCloseGesture = ( * the content is scrolled all the way * to the top so that we do not interfere * with scrolling. + * + * We cannot assume that the `ion-content` + * target will remain consistent between + * swipes. For example, when using + * ion-nav within a card modal it is + * possible to swipe, push a view, and then + * swipe again. The target content will not + * be the same between swipes. */ - const content = target.closest('ion-content'); - if (content) { + contentEl = findClosestIonContent(target); + if (contentEl) { /** * The card should never swipe to close * on the content with a refresher. @@ -78,8 +89,21 @@ export const createSwipeToCloseGesture = ( * than the refresher gesture as the iOS native * refresh gesture uses a scroll listener in * addition to a gesture. + * + * Note: Do not use getScrollElement here + * because we need this to be a synchronous + * operation, and getScrollElement is + * asynchronous. */ - return !hasRefresherInContent && scrollEl.scrollTop === 0; + if (isIonContent(contentEl)) { + const root = getElementRoot(contentEl); + scrollEl = root.querySelector('.inner-scroll'); + } else { + scrollEl = contentEl; + } + + const hasRefresherInContent = !!contentEl.querySelector('ion-refresher'); + return !hasRefresherInContent && scrollEl!.scrollTop === 0; } /** @@ -96,6 +120,14 @@ export const createSwipeToCloseGesture = ( const onStart = (detail: GestureDetail) => { const { deltaY } = detail; + + /** + * Get the initial scrollY value so + * that we can correctly reset the scrollY + * prop when the gesture ends. + */ + initialScrollY = getScrollY(); + /** * If canDismiss is anything other than `true` * then users should be able to swipe down @@ -245,7 +277,7 @@ export const createSwipeToCloseGesture = ( const gesture = createGesture({ el, gestureName: 'modalSwipeToClose', - gesturePriority: 40, + gesturePriority: 39, direction: 'y', threshold: 10, canStart, diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index 655006931f..4d7725b27e 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -15,7 +15,7 @@ import type { OverlayEventDetail, OverlayInterface, } from '../../interface'; -import { getScrollElement, findIonContent, printIonContentErrorMsg } from '../../utils/content'; +import { findIonContent, printIonContentErrorMsg } from '../../utils/content'; import { CoreDelegate, attachComponent, detachComponent } from '../../utils/framework-delegate'; import { raf } from '../../utils/helpers'; import { KEYBOARD_DID_OPEN } from '../../utils/keyboard/keyboard'; @@ -511,7 +511,7 @@ export class Modal implements ComponentInterface, OverlayInterface { this.currentTransition = undefined; } - private async initSwipeToClose() { + private initSwipeToClose() { if (getIonMode(this) !== 'ios') { return; } @@ -529,9 +529,8 @@ export class Modal implements ComponentInterface, OverlayInterface { printIonContentErrorMsg(el); return; } - const scrollEl = await getScrollElement(contentEl); - this.gesture = createSwipeToCloseGesture(el, contentEl, scrollEl, ani, () => { + this.gesture = createSwipeToCloseGesture(el, ani, () => { /** * While the gesture animation is finishing * it is possible for a user to tap the backdrop. diff --git a/core/src/components/modal/test/card-nav/index.html b/core/src/components/modal/test/card-nav/index.html new file mode 100644 index 0000000000..f48c5be8a5 --- /dev/null +++ b/core/src/components/modal/test/card-nav/index.html @@ -0,0 +1,88 @@ + + + + + Modal - Card + Nav + + + + + + + + + + + + +
+ + + Card + + + + + Open Modal + + + +
+
+ + + + diff --git a/core/src/components/modal/test/card-nav/modal.e2e.ts b/core/src/components/modal/test/card-nav/modal.e2e.ts new file mode 100644 index 0000000000..8918d34664 --- /dev/null +++ b/core/src/components/modal/test/card-nav/modal.e2e.ts @@ -0,0 +1,50 @@ +import { expect } from '@playwright/test'; +import { test, dragElementBy } from '@utils/test/playwright'; + +import { CardModalPage } from '../fixtures'; + +test.describe('card modal - nav', () => { + let cardModalPage: CardModalPage; + test.beforeEach(async ({ page, browserName }, testInfo) => { + test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS'); + test.skip( + testInfo.project.metadata.rtl === true, + 'This test only verifies that the gesture activates inside of a modal.' + ); + test.skip(browserName !== 'chromium', 'dragElementBy is flaky outside of Chrome browsers.'); + + cardModalPage = new CardModalPage(page); + await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false'); + }); + test('it should swipe to go back', async ({ page }) => { + await cardModalPage.openModalByTrigger('#open-modal'); + + const nav = page.locator('ion-nav') as any; + const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange'); + + await page.click('#go-page-two'); + + await ionNavDidChange.next(); + + const pageOne = page.locator('page-one'); + expect(pageOne).toHaveClass(/ion-page-hidden/); + + const content = page.locator('.page-two-content'); + + await dragElementBy(content, page, 1000, 0, 10); + + await ionNavDidChange.next(); + }); + test('should swipe to close', async ({ page }) => { + await cardModalPage.openModalByTrigger('#open-modal'); + + const nav = page.locator('ion-nav') as any; + const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange'); + + await page.click('#go-page-two'); + + await ionNavDidChange.next(); + + await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content'); + }); +}); diff --git a/core/src/components/modal/test/card/index.html b/core/src/components/modal/test/card/index.html index 7f948c1f48..68425b8df5 100644 --- a/core/src/components/modal/test/card/index.html +++ b/core/src/components/modal/test/card/index.html @@ -21,6 +21,11 @@ ion-modal#custom { --border-radius: 50px !important; } + .content-wrapper { + height: 100%; + + overflow: hidden; + } @@ -48,71 +53,73 @@ - - - - - - - - - Range - RTL - - - - - - - Range - - - - - - - - - diff --git a/core/src/components/range/test/standalone/e2e.ts b/core/src/components/range/test/standalone/e2e.ts deleted file mode 100644 index 0555e71257..0000000000 --- a/core/src/components/range/test/standalone/e2e.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { newE2EPage } from '@stencil/core/testing'; - -test('range: standalone', async () => { - const page = await newE2EPage({ - url: '/src/components/range/test/standalone?ionic:_testing=true', - }); - - const compare = await page.compareScreenshot(); - expect(compare).toMatchScreenshot(); -}); diff --git a/core/src/components/range/test/standalone/range.e2e.ts b/core/src/components/range/test/standalone/range.e2e.ts new file mode 100644 index 0000000000..f5617da040 --- /dev/null +++ b/core/src/components/range/test/standalone/range.e2e.ts @@ -0,0 +1,12 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +test.describe('range: standalone', () => { + test('should not have visual regressions', async ({ page }) => { + await page.goto(`/src/components/range/test/standalone`); + + await page.setIonViewport(); + + expect(await page.screenshot()).toMatchSnapshot(`range-diff-${page.getSnapshotSettings()}.png`); + }); +}); diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..dad01a0381 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..e80d4dd816 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..966ce35ec0 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..520efa289a Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..eca0cae052 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..651cceb488 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-ios-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Chrome-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..8456d9ac21 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Firefox-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..b2462b6d7e Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Safari-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Safari-linux.png new file mode 100644 index 0000000000..53a0210bdf Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-ltr-Mobile-Safari-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Chrome-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Chrome-linux.png new file mode 100644 index 0000000000..4f8d00d044 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Chrome-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Firefox-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Firefox-linux.png new file mode 100644 index 0000000000..988a34c9a9 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Firefox-linux.png differ diff --git a/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Safari-linux.png b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Safari-linux.png new file mode 100644 index 0000000000..811a089053 Binary files /dev/null and b/core/src/components/range/test/standalone/range.e2e.ts-snapshots/range-diff-md-rtl-Mobile-Safari-linux.png differ diff --git a/core/src/components/refresher/refresher.tsx b/core/src/components/refresher/refresher.tsx index e62342dec9..56b85f7470 100644 --- a/core/src/components/refresher/refresher.tsx +++ b/core/src/components/refresher/refresher.tsx @@ -4,7 +4,12 @@ import { Component, Element, Event, Host, Method, Prop, State, Watch, h, readTas import { getIonMode } from '../../global/ionic-global'; import type { Animation, Gesture, GestureDetail, RefresherEventDetail } from '../../interface'; import { getTimeGivenProgression } from '../../utils/animation/cubic-bezier'; -import { findClosestIonContent, getScrollElement, printIonContentErrorMsg } from '../../utils/content'; +import { + getScrollElement, + ION_CONTENT_CLASS_SELECTOR, + ION_CONTENT_ELEMENT_SELECTOR, + printIonContentErrorMsg, +} from '../../utils/content'; import { clamp, getElementRoot, raf, transitionEndAsync } from '../../utils/helpers'; import { hapticImpact } from '../../utils/native/haptic'; @@ -436,13 +441,21 @@ export class Refresher implements ComponentInterface { return; } - const contentEl = findClosestIonContent(this.el); + const contentEl = this.el.closest(ION_CONTENT_ELEMENT_SELECTOR); if (!contentEl) { printIonContentErrorMsg(this.el); return; } - this.scrollEl = await getScrollElement(contentEl); + const customScrollTarget = contentEl.querySelector(ION_CONTENT_CLASS_SELECTOR); + + /** + * Query the custom scroll target (if available), first. In refresher implementations, + * the ion-refresher element will always be a direct child of ion-content (slot="fixed"). By + * querying the custom scroll target first and falling back to the ion-content element, + * the correct scroll element will be returned by the implementation. + */ + this.scrollEl = await getScrollElement(customScrollTarget ?? contentEl); /** * Query the host `ion-content` directly (if it is available), to use its @@ -452,9 +465,7 @@ export class Refresher implements ComponentInterface { * This makes it so that implementers do not need to re-create the background content * element and styles. */ - const backgroundContentHost = this.el.closest('ion-content') ?? contentEl; - - this.backgroundContentEl = getElementRoot(backgroundContentHost).querySelector( + this.backgroundContentEl = getElementRoot(contentEl ?? customScrollTarget).querySelector( '#background-content' ) as HTMLElement; diff --git a/core/src/components/refresher/test/basic/e2e.ts b/core/src/components/refresher/test/basic/e2e.ts deleted file mode 100644 index 86d9d9117f..0000000000 --- a/core/src/components/refresher/test/basic/e2e.ts +++ /dev/null @@ -1,51 +0,0 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { newE2EPage } from '@stencil/core/testing'; - -import { pullToRefresh } from '../test.utils'; - -describe('refresher: basic', () => { - let page: E2EPage; - - beforeEach(async () => { - page = await newE2EPage({ - url: '/src/components/refresher/test/basic?ionic:_testing=true', - }); - }); - - it('should match existing visual screenshots', async () => { - const compare = await page.compareScreenshot(); - expect(compare).toMatchScreenshot(); - }); - - describe('legacy refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); - - describe('native refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const refresherContent = await page.$('ion-refresher-content'); - refresherContent.evaluate((el: any) => { - // Resets the pullingIcon to enable the native refresher - el.pullingIcon = undefined; - }); - - await page.waitForChanges(); - - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); -}); diff --git a/core/src/components/refresher/test/basic/index.html b/core/src/components/refresher/test/basic/index.html index 51a6e3000f..60982df867 100644 --- a/core/src/components/refresher/test/basic/index.html +++ b/core/src/components/refresher/test/basic/index.html @@ -53,7 +53,7 @@ refresher.complete(); render(); // Custom event consumed by e2e tests - document.dispatchEvent(new CustomEvent('ionRefreshComplete')); + window.dispatchEvent(new CustomEvent('ionRefreshComplete')); }); function render() { diff --git a/core/src/components/refresher/test/basic/refresher.e2e.ts b/core/src/components/refresher/test/basic/refresher.e2e.ts new file mode 100644 index 0000000000..c67fca972a --- /dev/null +++ b/core/src/components/refresher/test/basic/refresher.e2e.ts @@ -0,0 +1,41 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +import { pullToRefresh } from '../test.utils'; + +test.describe('refresher: basic', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/refresher/test/basic'); + }); + + test.describe('legacy refresher', () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page); + + expect(await items.count()).toBe(60); + }); + }); + + test.describe('native refresher', () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const refresherContent = page.locator('ion-refresher-content'); + refresherContent.evaluateHandle((el: any) => { + // Resets the pullingIcon to enable the native refresher + el.pullingIcon = undefined; + }); + + await page.waitForChanges(); + + const items = page.locator('ion-item'); + expect(await items.count()).toBe(30); + + await pullToRefresh(page); + + expect(await items.count()).toBe(60); + }); + }); +}); diff --git a/core/src/components/refresher/test/scroll-target/e2e.ts b/core/src/components/refresher/test/scroll-target/e2e.ts deleted file mode 100644 index 1920e75924..0000000000 --- a/core/src/components/refresher/test/scroll-target/e2e.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { newE2EPage } from '@stencil/core/testing'; - -import { pullToRefresh } from '../test.utils'; - -// TODO(FW-1134) Re-write these tests so that they test correct functionality. -describe.skip('refresher: custom scroll target', () => { - let page: E2EPage; - - beforeEach(async () => { - page = await newE2EPage({ - url: '/src/components/refresher/test/scroll-target?ionic:_testing=true', - }); - }); - - describe('legacy refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); - - describe('native refresher', () => { - it('should load more items when performing a pull-to-refresh', async () => { - const refresherContent = await page.$('ion-refresher-content'); - refresherContent.evaluate((el: any) => { - // Resets the pullingIcon to enable the native refresher - el.pullingIcon = undefined; - }); - - await page.waitForChanges(); - - const initialItems = await page.findAll('ion-item'); - expect(initialItems.length).toBe(30); - - await pullToRefresh(page); - - const items = await page.findAll('ion-item'); - expect(items.length).toBe(60); - }); - }); -}); diff --git a/core/src/components/refresher/test/scroll-target/index.html b/core/src/components/refresher/test/scroll-target/index.html index fe38d97e99..caa467fa3b 100644 --- a/core/src/components/refresher/test/scroll-target/index.html +++ b/core/src/components/refresher/test/scroll-target/index.html @@ -44,8 +44,8 @@ -
-
+
+
@@ -65,7 +65,7 @@ refresher.complete(); render(); // Custom event consumed by e2e tests - document.dispatchEvent(new CustomEvent('ionRefreshComplete')); + window.dispatchEvent(new CustomEvent('ionRefreshComplete')); }); function render() { diff --git a/core/src/components/refresher/test/scroll-target/refresher.e2e.ts b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts new file mode 100644 index 0000000000..751de7c63f --- /dev/null +++ b/core/src/components/refresher/test/scroll-target/refresher.e2e.ts @@ -0,0 +1,42 @@ +import { expect } from '@playwright/test'; +import { test } from '@utils/test/playwright'; + +import { pullToRefresh } from '../test.utils'; + +test.describe('refresher: custom scroll target', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/src/components/refresher/test/scroll-target'); + }); + + test.describe('legacy refresher', () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page, '#inner-scroll'); + + expect(await items.count()).toBe(60); + }); + }); + + test.describe('native refresher', () => { + test('should load more items when performing a pull-to-refresh', async ({ page }) => { + const refresherContent = page.locator('ion-refresher-content'); + refresherContent.evaluateHandle((el: any) => { + // Resets the pullingIcon to enable the native refresher + el.pullingIcon = undefined; + }); + + await page.waitForChanges(); + + const items = page.locator('ion-item'); + + expect(await items.count()).toBe(30); + + await pullToRefresh(page, '#inner-scroll'); + + expect(await items.count()).toBe(60); + }); + }); +}); diff --git a/core/src/components/refresher/test/spec/e2e.ts b/core/src/components/refresher/test/spec/e2e.ts deleted file mode 100644 index 5171c5281a..0000000000 --- a/core/src/components/refresher/test/spec/e2e.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { newE2EPage } from '@stencil/core/testing'; - -test('refresher: spec', async () => { - const page = await newE2EPage({ - url: '/src/components/refresher/test/spec?ionic:_testing=true', - }); - - const compare = await page.compareScreenshot(); - expect(compare).toMatchScreenshot(); -}); diff --git a/core/src/components/refresher/test/spec/index.html b/core/src/components/refresher/test/spec/index.html deleted file mode 100644 index 51e3e708b3..0000000000 --- a/core/src/components/refresher/test/spec/index.html +++ /dev/null @@ -1,131 +0,0 @@ - - - - - Refresher - Spec - - - - - - - - - - - - - - - - All Inboxes - - Edit - - - - - - - - - - - - All Inboxes - - - - - - - - - - - - - - - - - - - diff --git a/core/src/components/refresher/test/test.utils.ts b/core/src/components/refresher/test/test.utils.ts index 7d0732aeb5..40910ce864 100644 --- a/core/src/components/refresher/test/test.utils.ts +++ b/core/src/components/refresher/test/test.utils.ts @@ -1,5 +1,4 @@ -import type { E2EPage } from '@stencil/core/testing'; -import { dragElementBy } from '@utils/test'; +import type { E2EPage } from '@utils/test/playwright'; /** * Emulates a pull-to-refresh drag gesture (pulls down and releases). @@ -12,10 +11,28 @@ import { dragElementBy } from '@utils/test'; * @param selector The element selector to center the drag gesture on. Defaults to `body`. */ const pullToRefresh = async (page: E2EPage, selector = 'body') => { - const target = (await page.$(selector))!; + const target = page.locator(selector); - await dragElementBy(target, page, 0, 200); - const ev = await page.spyOnEvent('ionRefreshComplete', 'document'); + await page.waitForSelector('ion-refresher.hydrated', { state: 'attached' }); + + const ev = await page.spyOnEvent('ionRefreshComplete'); + const boundingBox = await target.boundingBox(); + + if (!boundingBox) { + return; + } + + const startX = boundingBox.x + boundingBox.width / 2; + const startY = boundingBox.y + boundingBox.height / 2; + + await page.mouse.move(startX, startY); + await page.mouse.down(); + + for (let i = 0; i < 400; i += 20) { + await page.mouse.move(startX, startY + i); + } + + await page.mouse.up(); await ev.next(); }; diff --git a/core/src/interface.d.ts b/core/src/interface.d.ts index 74ca4b274a..ad929d324e 100644 --- a/core/src/interface.d.ts +++ b/core/src/interface.d.ts @@ -133,7 +133,10 @@ export type PredefinedColors = | 'light' | 'medium' | 'dark'; -export type Color = PredefinedColors | string; + +type LiteralUnion = T | (U & Record); + +export type Color = LiteralUnion; export type Mode = 'ios' | 'md'; export type ComponentTags = string; // eslint-disable-next-line diff --git a/core/src/utils/content/index.ts b/core/src/utils/content/index.ts index 7e7a88bdf4..0d46e0fbfd 100644 --- a/core/src/utils/content/index.ts +++ b/core/src/utils/content/index.ts @@ -2,8 +2,8 @@ import { componentOnReady } from '../helpers'; import { printRequiredElementError } from '../logging'; const ION_CONTENT_TAG_NAME = 'ION-CONTENT'; -const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content'; -const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host'; +export const ION_CONTENT_ELEMENT_SELECTOR = 'ion-content'; +export const ION_CONTENT_CLASS_SELECTOR = '.ion-content-scroll-host'; /** * Selector used for implementations reliant on `` for scroll event changes. * diff --git a/core/src/utils/tap-click.ts b/core/src/utils/tap-click.ts index b0c0577d37..19a0ce0829 100644 --- a/core/src/utils/tap-click.ts +++ b/core/src/utils/tap-click.ts @@ -57,7 +57,7 @@ export const startTapClick = (config: Config) => { } }; - const pointerDown = (ev: any) => { + const pointerDown = (ev: UIEvent) => { if (activatableEle || isScrolling()) { return; } @@ -165,9 +165,17 @@ export const startTapClick = (config: Config) => { doc.addEventListener('contextmenu', onContextMenu, true); }; -const getActivatableTarget = (ev: any): any => { +const getActivatableTarget = (ev: UIEvent): any => { if (ev.composedPath) { - const path = ev.composedPath() as HTMLElement[]; + /** + * composedPath returns EventTarget[]. However, + * objects other than Element can be targets too. + * For example, AudioContext can be a target. In this + * case, we know that the event is a UIEvent so we + * can assume that the path will contain either Element + * or ShadowRoot. + */ + const path = ev.composedPath() as Element[] | ShadowRoot[]; for (let i = 0; i < path.length - 2; i++) { const el = path[i]; if (!(el instanceof ShadowRoot) && el.classList.contains('ion-activatable')) { @@ -175,7 +183,7 @@ const getActivatableTarget = (ev: any): any => { } } } else { - return ev.target.closest('.ion-activatable'); + return (ev.target as Element).closest('.ion-activatable'); } }; diff --git a/core/src/utils/test/playwright/drag-element.ts b/core/src/utils/test/playwright/drag-element.ts index 8cfe6d36d7..b053cc17d8 100644 --- a/core/src/utils/test/playwright/drag-element.ts +++ b/core/src/utils/test/playwright/drag-element.ts @@ -6,7 +6,9 @@ export const dragElementBy = async ( el: Locator | ElementHandle, page: E2EPage, dragByX = 0, - dragByY = 0 + dragByY = 0, + startXCoord?: number, + startYCoord?: number ) => { const boundingBox = await el.boundingBox(); @@ -16,8 +18,8 @@ export const dragElementBy = async ( ); } - const startX = boundingBox.x + boundingBox.width / 2; - const startY = boundingBox.y + boundingBox.height / 2; + const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord; + const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord; const midX = startX + dragByX / 2; const midY = startY + dragByY / 2; diff --git a/core/src/utils/test/playwright/matchers/index.ts b/core/src/utils/test/playwright/matchers/index.ts index e8c2900067..5e3997ccc7 100644 --- a/core/src/utils/test/playwright/matchers/index.ts +++ b/core/src/utils/test/playwright/matchers/index.ts @@ -1,5 +1,7 @@ import { toHaveReceivedEvent } from './toHaveReceivedEvent'; +import { toHaveReceivedEventDetail } from './toHaveReceivedEventDetail'; export const matchers = { toHaveReceivedEvent, + toHaveReceivedEventDetail, }; diff --git a/core/src/utils/test/playwright/matchers/toHaveReceivedEventDetail.ts b/core/src/utils/test/playwright/matchers/toHaveReceivedEventDetail.ts new file mode 100644 index 0000000000..745a8b2cd2 --- /dev/null +++ b/core/src/utils/test/playwright/matchers/toHaveReceivedEventDetail.ts @@ -0,0 +1,44 @@ +import { expect } from '@playwright/test'; +import deepEqual from 'fast-deep-equal'; + +import type { EventSpy } from '../page/event-spy'; + +export function toHaveReceivedEventDetail(eventSpy: EventSpy, eventDetail: any) { + if (!eventSpy) { + return { + message: () => `toHaveReceivedEventDetail event spy is null`, + pass: false, + }; + } + + if (typeof (eventSpy as any).then === 'function') { + return { + message: () => + `expected spy to have received event, but it was not resolved (did you forget an await operator?).`, + pass: false, + }; + } + + if (!eventSpy.eventName) { + return { + message: () => `toHaveReceivedEventDetail did not receive an event spy`, + pass: false, + }; + } + + if (!eventSpy.lastEvent) { + return { + message: () => `event "${eventSpy.eventName}" was not received`, + pass: false, + }; + } + + const pass = deepEqual(eventSpy.lastEvent.detail, eventDetail); + + expect(eventSpy.lastEvent.detail).toEqual(eventDetail); + + return { + message: () => `expected event "${eventSpy.eventName}" detail to ${pass ? 'not ' : ''}equal`, + pass: pass, + }; +} diff --git a/core/src/utils/test/playwright/page/event-spy.ts b/core/src/utils/test/playwright/page/event-spy.ts index 2fd2fa3843..4d7979a74a 100644 --- a/core/src/utils/test/playwright/page/event-spy.ts +++ b/core/src/utils/test/playwright/page/event-spy.ts @@ -1,3 +1,5 @@ +import type { JSHandle } from '@playwright/test'; + import type { E2EPage } from '../playwright-declarations'; /** @@ -16,7 +18,7 @@ export class EventSpy { */ private cursor = 0; private queuedHandler: (() => void)[] = []; - public events: Event[] = []; + public events: CustomEvent[] = []; constructor(public eventName: string) {} @@ -24,6 +26,14 @@ export class EventSpy { return this.events.length; } + get firstEvent() { + return this.events[0] || null; + } + + get lastEvent() { + return this.events[this.events.length - 1] || null; + } + public next() { const { cursor } = this; this.cursor++; @@ -48,7 +58,7 @@ export class EventSpy { } } - public push(ev: Event) { + public push(ev: CustomEvent) { this.events.push(ev); const next = this.queuedHandler.shift(); if (next) { @@ -82,17 +92,69 @@ export const initPageEvents = async (page: E2EPage) => { * page context to updates the _e2eEvents map * when an event is fired. */ -export const addE2EListener = async (page: E2EPage, eventName: string, callback: (ev: any) => void) => { +export const addE2EListener = async ( + page: E2EPage, + elmHandle: JSHandle, + eventName: string, + callback: (ev: any) => void +) => { const id = page._e2eEventsIds++; page._e2eEvents.set(id, { eventName, callback, }); - await page.evaluate( - ([eventName, id]) => { - window.addEventListener(eventName as string, (ev: Event) => { - (window as any).ionicOnEvent(id, ev); + await elmHandle.evaluate( + (elm, [eventName, id]) => { + (window as any).stencilSerializeEventTarget = (target: any) => { + // BROWSER CONTEXT + if (!target) { + return null; + } + if (target === window) { + return { serializedWindow: true }; + } + if (target === document) { + return { serializedDocument: true }; + } + if (target.nodeType != null) { + const serializedElement: any = { + serializedElement: true, + nodeName: target.nodeName, + nodeValue: target.nodeValue, + nodeType: target.nodeType, + tagName: target.tagName, + className: target.className, + id: target.id, + }; + return serializedElement; + } + return null; + }; + + (window as any).serializeStencilEvent = (orgEv: CustomEvent) => { + const serializedEvent = { + bubbles: orgEv.bubbles, + cancelBubble: orgEv.cancelBubble, + cancelable: orgEv.cancelable, + composed: orgEv.composed, + currentTarget: (window as any).stencilSerializeEventTarget(orgEv.currentTarget), + defaultPrevented: orgEv.defaultPrevented, + detail: orgEv.detail, + eventPhase: orgEv.eventPhase, + isTrusted: orgEv.isTrusted, + returnValue: orgEv.returnValue, + srcElement: (window as any).stencilSerializeEventTarget(orgEv.srcElement), + target: (window as any).stencilSerializeEventTarget(orgEv.target), + timeStamp: orgEv.timeStamp, + type: orgEv.type, + isSerializedEvent: true, + }; + return serializedEvent; + }; + + elm.addEventListener(eventName as string, (ev: Event) => { + (window as any).ionicOnEvent(id, (window as any).serializeStencilEvent(ev)); }); }, [eventName, id] diff --git a/core/src/utils/test/playwright/page/utils/goto.ts b/core/src/utils/test/playwright/page/utils/goto.ts index e0270935d9..f1667b259e 100644 --- a/core/src/utils/test/playwright/page/utils/goto.ts +++ b/core/src/utils/test/playwright/page/utils/goto.ts @@ -7,7 +7,7 @@ import type { Page, TestInfo } from '@playwright/test'; * automatically waits for the Stencil components * to be hydrated before proceeding with the test. */ -export const goto = async (page: Page, url: string, testInfo: TestInfo, originalFn: typeof page.goto) => { +export const goto = async (page: Page, url: string, options: any, testInfo: TestInfo, originalFn: typeof page.goto) => { const { mode, rtl, _testing } = testInfo.project.metadata; const splitUrl = url.split('?'); @@ -38,7 +38,7 @@ export const goto = async (page: Page, url: string, testInfo: TestInfo, original const result = await Promise.all([ page.waitForFunction(() => (window as any).testAppLoaded === true, { timeout: 4750 }), - originalFn(formattedUrl), + originalFn(formattedUrl, options), ]); return result[1]; diff --git a/core/src/utils/test/playwright/page/utils/index.ts b/core/src/utils/test/playwright/page/utils/index.ts index 4bef8cfc41..d5fad6cc12 100644 --- a/core/src/utils/test/playwright/page/utils/index.ts +++ b/core/src/utils/test/playwright/page/utils/index.ts @@ -4,3 +4,4 @@ export * from './get-snapshot-settings'; export * from './set-ion-viewport'; export * from './spy-on-event'; export * from './set-content'; +export * from './locator'; diff --git a/core/src/utils/test/playwright/page/utils/locator.ts b/core/src/utils/test/playwright/page/utils/locator.ts new file mode 100644 index 0000000000..9088d7ca7f --- /dev/null +++ b/core/src/utils/test/playwright/page/utils/locator.ts @@ -0,0 +1,41 @@ +import type { Locator } from '@playwright/test'; + +import type { E2EPage } from '../../playwright-declarations'; +import { EventSpy, addE2EListener } from '../event-spy'; + +export type LocatorOptions = { + hasText?: string | RegExp; + has?: Locator; +}; + +export interface E2ELocator extends Locator { + /** + * Creates a new EventSpy and listens + * on the element for an event. + * The test will timeout if the event + * never fires. + * + * Usage: + * const input = page.locator('ion-input'); + * const ionChange = await locator.spyOnEvent('ionChange'); + * ... + * await ionChange.next(); + */ + spyOnEvent: (eventName: string) => void; +} + +export const locator = ( + page: E2EPage, + originalFn: typeof page.locator, + selector: string, + options?: LocatorOptions +): E2ELocator => { + const locator = originalFn(selector, options) as E2ELocator; + locator.spyOnEvent = async (eventName: string) => { + const spy = new EventSpy(eventName); + const handle = await locator.evaluateHandle((node: HTMLElement) => node); + await addE2EListener(page, handle, eventName, (ev: CustomEvent) => spy.push(ev)); + return spy; + }; + return locator; +}; diff --git a/core/src/utils/test/playwright/page/utils/set-ion-viewport.ts b/core/src/utils/test/playwright/page/utils/set-ion-viewport.ts index a5002bf30a..f655c5ede8 100644 --- a/core/src/utils/test/playwright/page/utils/set-ion-viewport.ts +++ b/core/src/utils/test/playwright/page/utils/set-ion-viewport.ts @@ -13,8 +13,13 @@ import type { Page } from '@playwright/test'; */ export const setIonViewport = async (page: Page) => { const currentViewport = page.viewportSize(); + const ionContent = await page.$('ion-content'); - const pixelAmountRenderedOffscreen = await page.evaluate(() => { + if (ionContent) { + await ionContent.waitForElementState('stable'); + } + + const pixelAmountRenderedOffscreen = await page.evaluate(async () => { const content = document.querySelector('ion-content'); if (content) { const innerScroll = content.shadowRoot!.querySelector('.inner-scroll')!; diff --git a/core/src/utils/test/playwright/page/utils/spy-on-event.ts b/core/src/utils/test/playwright/page/utils/spy-on-event.ts index 777090b36c..a34f719402 100644 --- a/core/src/utils/test/playwright/page/utils/spy-on-event.ts +++ b/core/src/utils/test/playwright/page/utils/spy-on-event.ts @@ -4,7 +4,9 @@ import { addE2EListener, EventSpy } from '../event-spy'; export const spyOnEvent = async (page: E2EPage, eventName: string): Promise => { const spy = new EventSpy(eventName); - await addE2EListener(page, eventName, (ev: Event) => spy.push(ev)); + const handle = await page.evaluateHandle(() => window); + + await addE2EListener(page, handle, eventName, (ev: CustomEvent) => spy.push(ev)); return spy; }; diff --git a/core/src/utils/test/playwright/playwright-declarations.ts b/core/src/utils/test/playwright/playwright-declarations.ts index 11a93886d0..e483724254 100644 --- a/core/src/utils/test/playwright/playwright-declarations.ts +++ b/core/src/utils/test/playwright/playwright-declarations.ts @@ -1,6 +1,7 @@ import type { Page, Response } from '@playwright/test'; import type { EventSpy } from './page/event-spy'; +import type { LocatorOptions, E2ELocator } from './page/utils/locator'; export interface E2EPage extends Page { /** @@ -27,7 +28,41 @@ export interface E2EPage extends Page { * @param url URL to navigate page to. The url should include scheme, e.g. `https://`. When a `baseURL` via the context options was provided and the passed URL is a path, it gets merged via the * [`new URL()`](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL) constructor. */ - goto: (url: string) => Promise; + goto: ( + url: string, + options?: { + /** + * Referer header value. If provided it will take preference over the referer header value set by + * [page.setExtraHTTPHeaders(headers)](https://playwright.dev/docs/api/class-page#page-set-extra-http-headers). + */ + referer?: string; + + /** + * Maximum operation time in milliseconds, defaults to 30 seconds, pass `0` to disable timeout. The default value can be + * changed by using the + * [browserContext.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-navigation-timeout), + * [browserContext.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-browsercontext#browser-context-set-default-timeout), + * [page.setDefaultNavigationTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-navigation-timeout) + * or [page.setDefaultTimeout(timeout)](https://playwright.dev/docs/api/class-page#page-set-default-timeout) methods. + */ + timeout?: number; + + /** + * When to consider operation succeeded, defaults to `load`. Events can be either: + * - `'domcontentloaded'` - consider operation to be finished when the `DOMContentLoaded` event is fired. + * - `'load'` - consider operation to be finished when the `load` event is fired. + * - `'networkidle'` - consider operation to be finished when there are no network connections for at least `500` ms. + * - `'commit'` - consider operation to be finished when network response is received and the document started loading. + */ + waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit'; + } + ) => Promise; + /** + * Find an element by selector. + * See https://playwright.dev/docs/locators for more information. + */ + locator: (selector: string, options?: LocatorOptions) => E2ELocator; + /** * Increases the size of the page viewport to match the `ion-content` contents. * Use this method when taking full-screen screenshots. @@ -50,9 +85,11 @@ export interface E2EPage extends Page { * never fires. * * Usage: + * ```ts * const ionChange = await page.spyOnEvent('ionChange'); * ... * await ionChange.next(); + * ``` */ spyOnEvent: (eventName: string) => Promise; _e2eEventsIds: number; diff --git a/core/src/utils/test/playwright/playwright-page.ts b/core/src/utils/test/playwright/playwright-page.ts index 2cfa61147c..2af4fbfab1 100644 --- a/core/src/utils/test/playwright/playwright-page.ts +++ b/core/src/utils/test/playwright/playwright-page.ts @@ -15,7 +15,9 @@ import { setIonViewport, spyOnEvent, waitForChanges, + locator, } from './page/utils'; +import type { LocatorOptions } from './page/utils'; import type { E2EPage } from './playwright-declarations'; type CustomTestArgs = PlaywrightTestArgs & @@ -29,22 +31,36 @@ type CustomFixtures = { page: E2EPage; }; +/** + * Extends the base `page` test figure within Playwright. + * @param page The page to extend. + * @param testInfo The test info. + * @returns The modified playwright page with extended functionality. + */ +export async function extendPageFixture(page: E2EPage, testInfo: TestInfo) { + const originalGoto = page.goto.bind(page); + const originalLocator = page.locator.bind(page); + + // Overridden Playwright methods + page.goto = (url: string, options) => goToPage(page, url, options, testInfo, originalGoto); + page.setContent = (html: string) => setContent(page, html); + page.locator = (selector: string, options?: LocatorOptions) => locator(page, originalLocator, selector, options); + + // Custom Ionic methods + page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); + page.setIonViewport = () => setIonViewport(page); + page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs); + page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName); + + // Custom event behavior + await initPageEvents(page); + + return page; +} + export const test = base.extend({ page: async ({ page }: CustomTestArgs, use: (r: E2EPage) => Promise, testInfo: TestInfo) => { - const originalGoto = page.goto.bind(page); - - // Overridden Playwright methods - page.goto = (url: string) => goToPage(page, url, testInfo, originalGoto); - page.setContent = (html: string) => setContent(page, html); - // Custom Ionic methods - page.getSnapshotSettings = () => getSnapshotSettings(page, testInfo); - page.setIonViewport = () => setIonViewport(page); - page.waitForChanges = (timeoutMs?: number) => waitForChanges(page, timeoutMs); - page.spyOnEvent = (eventName: string) => spyOnEvent(page, eventName); - - // Custom event behavior - await initPageEvents(page); - + page = await extendPageFixture(page, testInfo); await use(page); }, }); diff --git a/core/src/utils/test/playwright/testExpect.d.ts b/core/src/utils/test/playwright/testExpect.d.ts index d64692a06a..9028360c5c 100644 --- a/core/src/utils/test/playwright/testExpect.d.ts +++ b/core/src/utils/test/playwright/testExpect.d.ts @@ -3,6 +3,11 @@ interface CustomMatchers { * Will check if the event spy received the expected event. */ toHaveReceivedEvent(): R; + /** + * Will check if the event spy received the expected event with the expected detail. + * @param eventDetail The expected detail of the event. + */ + toHaveReceivedEventDetail(eventDetail: any): R; } declare namespace PlaywrightTest { diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 5fb171b955..eb8d0a8d46 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic-docs/compare/v6.1.6...v6.1.7) (2022-05-26) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [6.1.6](https://github.com/ionic-team/ionic-docs/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/docs diff --git a/docs/package-lock.json b/docs/package-lock.json index 9b5e5c7b1f..8f4d59c625 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/docs", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT" } } diff --git a/docs/package.json b/docs/package.json index b8cd0eafbf..a688b75574 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "6.1.6", + "version": "6.1.7", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/lerna.json b/lerna.json index ba301947fb..97b7adf39b 100644 --- a/lerna.json +++ b/lerna.json @@ -5,5 +5,5 @@ "angular", "packages/*" ], - "version": "6.1.6" + "version": "6.1.7" } diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index cd151c1f7a..b1fd7a5979 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index 0f756471f0..f2570b0aff 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/angular-server", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "devDependencies": { "@angular-eslint/eslint-plugin": "^12.6.1", @@ -18,7 +18,7 @@ "@angular/platform-browser": "^12.0.0", "@angular/platform-browser-dynamic": "^12.2.10", "@angular/platform-server": "^12.0.0", - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", "@typescript-eslint/eslint-plugin": "^5.2.0", @@ -786,9 +786,9 @@ "license": "BSD-3-Clause" }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dev": true, "dependencies": { "@stencil/core": "^2.14.2", @@ -7116,9 +7116,9 @@ "dev": true }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dev": true, "requires": { "@stencil/core": "^2.14.2", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 1dc2caa277..cd70bd8c46 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "6.1.6", + "version": "6.1.7", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -56,7 +56,7 @@ "@angular/platform-browser": "^12.0.0", "@angular/platform-browser-dynamic": "^12.2.10", "@angular/platform-server": "^12.0.0", - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "@ionic/eslint-config": "^0.3.0", "@ionic/prettier-config": "^2.0.0", "@typescript-eslint/eslint-plugin": "^5.2.0", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index dda2bcb3e7..28e05a3865 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index 1d9790947a..ec333bc24e 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { - "@ionic/react": "^6.1.6", + "@ionic/react": "^6.1.7", "tslib": "*" }, "devDependencies": { @@ -148,9 +148,9 @@ } }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dependencies": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -158,11 +158,11 @@ } }, "node_modules/@ionic/react": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.1.6.tgz", - "integrity": "sha512-V/NxjN7VbGrFrez90g89Q1HVXcGlLEexGCtVeWhbK80h1lzGy2FMzT4BBW1qTvMGOZz2Umdoj+R1wcK91BbiJQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.1.7.tgz", + "integrity": "sha512-wlBG60Xvj6TU/T6CaBqhjJhep0jvxhwYaTJok+3zg4WNlON7M4BTP4doePNAvTC70U8E0rG069T5eRPlHAFWRA==", "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0", "tslib": "*" }, @@ -4501,9 +4501,9 @@ } }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "requires": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -4511,11 +4511,11 @@ } }, "@ionic/react": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.1.6.tgz", - "integrity": "sha512-V/NxjN7VbGrFrez90g89Q1HVXcGlLEexGCtVeWhbK80h1lzGy2FMzT4BBW1qTvMGOZz2Umdoj+R1wcK91BbiJQ==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-6.1.7.tgz", + "integrity": "sha512-wlBG60Xvj6TU/T6CaBqhjJhep0jvxhwYaTJok+3zg4WNlON7M4BTP4doePNAvTC70U8E0rG069T5eRPlHAFWRA==", "requires": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 071aa0e11a..3a52b86214 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "6.1.6", + "version": "6.1.7", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -37,7 +37,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^6.1.6", + "@ionic/react": "^6.1.7", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index 21a4fb272f..bf3b732aac 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -3,6 +3,19 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **range:** interfaces are now correctly exported ([#25342](https://github.com/ionic-team/ionic/issues/25342)) ([15f0c06](https://github.com/ionic-team/ionic/commit/15f0c0669f7598386edf487f408462b90ed91a08)), closes [#25341](https://github.com/ionic-team/ionic/issues/25341) +* **react:** add param types to useIonPopover dismiss function ([#25311](https://github.com/ionic-team/ionic/issues/25311)) ([7111370](https://github.com/ionic-team/ionic/commit/7111370dd787fdec78a1e3368679bc4c73570b98)) +* **react:** IonTabButton will call custom onClick handlers ([#25313](https://github.com/ionic-team/ionic/issues/25313)) ([6034418](https://github.com/ionic-team/ionic/commit/6034418b33c32fdd682c470eaf61b9fcbe86c4bb)), closes [#22511](https://github.com/ionic-team/ionic/issues/22511) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 004f9c97fb..ad79f99819 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0", "tslib": "*" }, @@ -607,9 +607,9 @@ } }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dependencies": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -9534,9 +9534,9 @@ } }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "requires": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", diff --git a/packages/react/package.json b/packages/react/package.json index ac18dd07b6..ec25da9d98 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "6.1.6", + "version": "6.1.7", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -41,7 +41,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0", "tslib": "*" }, diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index edf717cf89..84c34f0a84 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -78,6 +78,11 @@ export { RadioGroupCustomEvent, RadioGroupChangeEventDetail, + RangeCustomEvent, + RangeChangeEventDetail, + RangeKnobMoveStartEventDetail, + RangeKnobMoveEndEventDetail, + RefresherCustomEvent, RefresherEventDetail, diff --git a/packages/react/src/components/navigation/IonTabBar.tsx b/packages/react/src/components/navigation/IonTabBar.tsx index 5a906a42d7..1a88f8e4b7 100644 --- a/packages/react/src/components/navigation/IonTabBar.tsx +++ b/packages/react/src/components/navigation/IonTabBar.tsx @@ -178,12 +178,22 @@ class IonTabBarUnwrapped extends React.PureComponent + e: CustomEvent<{ href: string; selected: boolean; tab: string; routeOptions: unknown }>, + onClickFn?: (e: any) => void ) { const tappedTab = this.state.tabs[e.detail.tab]; const originalHref = tappedTab.originalHref; const currentHref = e.detail.href; const { activeTab: prevActiveTab } = this.state; + + if (onClickFn) { + /** + * If the user provides an onClick function, we call it + * with the original event. + */ + onClickFn(e); + } + // this.props.onSetCurrentTab(e.detail.tab, this.props.routeInfo); // Clicking the current tab will bring you back to the original href if (prevActiveTab === e.detail.tab) { @@ -232,7 +242,7 @@ class IonTabBarUnwrapped extends React.PureComponent this.onTabButtonClick(ev, child.props.onClick), }); } return null; diff --git a/packages/react/src/components/navigation/IonTabButton.tsx b/packages/react/src/components/navigation/IonTabButton.tsx index 98d6014e97..102e638338 100644 --- a/packages/react/src/components/navigation/IonTabButton.tsx +++ b/packages/react/src/components/navigation/IonTabButton.tsx @@ -34,6 +34,11 @@ export const IonTabButton = /*@__PURE__*/ (() => } render() { + /** + * onClick is excluded from the props, since it has a custom + * implementation within IonTabBar.tsx. Calling onClick within this + * component would result in duplicate handler calls. + */ const { onClick, ...rest } = this.props; return ( void + (data?: any, role?: string) => void ]; diff --git a/packages/react/test-app/cypress/integration/tabs/tabs.spec.ts b/packages/react/test-app/cypress/integration/tabs/tabs.spec.ts new file mode 100644 index 0000000000..8230708188 --- /dev/null +++ b/packages/react/test-app/cypress/integration/tabs/tabs.spec.ts @@ -0,0 +1,15 @@ +describe('IonTabs', () => { + beforeEach(() => { + cy.visit('/tabs/tab1'); + }); + + it('should handle onClick handlers on IonTabButton', () => { + const stub = cy.stub(); + + cy.on('window:alert', stub); + cy.get('ion-tab-button[tab="tab1"]').click().then(() => { + expect(stub.getCall(0)).to.be.calledWith('Tab was clicked') + }); + + }); +}); diff --git a/packages/react/test-app/package.json b/packages/react/test-app/package.json index 6e5adde373..96865667dc 100644 --- a/packages/react/test-app/package.json +++ b/packages/react/test-app/package.json @@ -23,6 +23,7 @@ "eject": "react-scripts eject", "sync": "sh ./scripts/sync.sh", "cypress": "cypress run --headless --browser chrome", + "cypress.open": "cypress open", "e2e": "concurrently \"serve -s build -l 3000\" \"wait-on http-get://localhost:3000 && npm run cypress\" --kill-others --success first" }, "eslintConfig": { diff --git a/packages/react/test-app/src/App.tsx b/packages/react/test-app/src/App.tsx index 28d27fedfc..11cb79fba1 100644 --- a/packages/react/test-app/src/App.tsx +++ b/packages/react/test-app/src/App.tsx @@ -24,6 +24,7 @@ import './theme/variables.css'; import Main from './pages/Main'; import OverlayHooks from './pages/overlay-hooks/OverlayHooks'; import OverlayComponents from './pages/overlay-components/OverlayComponents'; +import Tabs from './pages/Tabs'; setupIonicReact(); @@ -34,6 +35,7 @@ const App: React.FC = () => ( + diff --git a/packages/react/test-app/src/pages/Main.tsx b/packages/react/test-app/src/pages/Main.tsx index 88e21502cb..944ac25419 100644 --- a/packages/react/test-app/src/pages/Main.tsx +++ b/packages/react/test-app/src/pages/Main.tsx @@ -4,11 +4,10 @@ import { IonHeader, IonLabel, IonPage, - IonTitle, IonToolbar, IonItem, - IonList + IonList, } from '@ionic/react'; interface MainProps {} @@ -32,6 +31,11 @@ const Main: React.FC = () => { Overlay Components + + + Tabs + + ); diff --git a/packages/react/test-app/src/pages/Tabs.tsx b/packages/react/test-app/src/pages/Tabs.tsx new file mode 100644 index 0000000000..08a2cfa8af --- /dev/null +++ b/packages/react/test-app/src/pages/Tabs.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { IonLabel, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs } from '@ionic/react'; +import { Route, Redirect } from 'react-router'; + +interface TabsProps {} + +const Tabs: React.FC = () => { + return ( + + + + Tab 1} /> + + + window.alert('Tab was clicked')}> + Click Handler + + + + ); +}; + +export default Tabs; diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index 951c865869..ac0d172092 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **vue:** correct views are now unmounted in tabs ([#25270](https://github.com/ionic-team/ionic/issues/25270)) ([5e23fb1](https://github.com/ionic-team/ionic/commit/5e23fb1ce4e5b6e53828bde59268170f604167ba)), closes [#25255](https://github.com/ionic-team/ionic/issues/25255) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index 9bf4d299ce..0088f71e01 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { - "@ionic/vue": "^6.1.6" + "@ionic/vue": "^6.1.7" }, "devDependencies": { "@types/jest": "^26.0.13", @@ -563,9 +563,9 @@ "license": "MIT" }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dependencies": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -573,11 +573,11 @@ } }, "node_modules/@ionic/vue": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.1.6.tgz", - "integrity": "sha512-j0dELNjYTropC7UiZsgy3UN9mnQhXLD2Od/W92SQqKXx4tbvXOM7NLYuTrio7u2oZVagKnXmK19dpurp6Q0M0Q==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.1.7.tgz", + "integrity": "sha512-TXVFkajBsmNBhMdlQ1P6JV/8aPIOvnR4d3lj8Mo9UVidp9Z7Qc992f2wti/bsdiBUm3iXoDK5YjQridPSPS7pQ==", "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0" } }, @@ -6630,9 +6630,9 @@ "dev": true }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "requires": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -6640,11 +6640,11 @@ } }, "@ionic/vue": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.1.6.tgz", - "integrity": "sha512-j0dELNjYTropC7UiZsgy3UN9mnQhXLD2Od/W92SQqKXx4tbvXOM7NLYuTrio7u2oZVagKnXmK19dpurp6Q0M0Q==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-6.1.7.tgz", + "integrity": "sha512-TXVFkajBsmNBhMdlQ1P6JV/8aPIOvnR4d3lj8Mo9UVidp9Z7Qc992f2wti/bsdiBUm3iXoDK5YjQridPSPS7pQ==", "requires": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0" } }, diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index 1919974aab..cac1d4dbf5 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "6.1.6", + "version": "6.1.7", "description": "Vue Router integration for @ionic/vue", "scripts": { "prepublishOnly": "npm run build", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic#readme", "dependencies": { - "@ionic/vue": "^6.1.6" + "@ionic/vue": "^6.1.7" }, "devDependencies": { "@types/jest": "^26.0.13", diff --git a/packages/vue-router/src/viewStacks.ts b/packages/vue-router/src/viewStacks.ts index 8cae4a0064..3686024a0f 100644 --- a/packages/vue-router/src/viewStacks.ts +++ b/packages/vue-router/src/viewStacks.ts @@ -9,6 +9,15 @@ import { shallowRef } from 'vue'; export const createViewStacks = (router: Router) => { let viewStacks: ViewStacks = {}; + /** + * Returns the number of active stacks. + * This is useful for determining if an app + * is using linear navigation only or non-linear + * navigation. Multiple stacks indiciate an app + * is using non-linear navigation. + */ + const size = () => Object.keys(viewStacks).length; + const clear = (outletId: number) => { delete viewStacks[outletId]; } @@ -211,6 +220,7 @@ export const createViewStacks = (router: Router) => { add, remove, registerIonPage, - getViewStack + getViewStack, + size } } diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 50d124aa70..18dde7b455 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,18 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [6.1.7](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7) (2022-05-26) + + +### Bug Fixes + +* **range:** interfaces are now correctly exported ([#25342](https://github.com/ionic-team/ionic/issues/25342)) ([15f0c06](https://github.com/ionic-team/ionic/commit/15f0c0669f7598386edf487f408462b90ed91a08)), closes [#25341](https://github.com/ionic-team/ionic/issues/25341) +* **vue:** correct views are now unmounted in tabs ([#25270](https://github.com/ionic-team/ionic/issues/25270)) ([5e23fb1](https://github.com/ionic-team/ionic/commit/5e23fb1ce4e5b6e53828bde59268170f604167ba)), closes [#25255](https://github.com/ionic-team/ionic/issues/25255) + + + + + ## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index 2eec5bc4f1..8a5ef51822 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "6.1.6", + "version": "6.1.7", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "6.1.6", + "version": "6.1.7", "license": "MIT", "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0" }, "devDependencies": { @@ -53,9 +53,9 @@ } }, "node_modules/@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "dependencies": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", @@ -633,9 +633,9 @@ } }, "@ionic/core": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz", - "integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==", + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.7.tgz", + "integrity": "sha512-CUbH7xtKcPejHTyMvvUJZq4GIyLbL2YflzFH+mad1PoLN4TLwFTTKTDB1oeFNqwnTzaByeBvhEWSayxCbLgvjQ==", "requires": { "@stencil/core": "^2.14.2", "ionicons": "^6.0.0", diff --git a/packages/vue/package.json b/packages/vue/package.json index ed0418d265..63b3568b74 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "6.1.6", + "version": "6.1.7", "description": "Vue specific wrapper for @ionic/core", "scripts": { "prepublishOnly": "npm run build", @@ -60,7 +60,7 @@ "vue-router": "^4.0.0-rc.4" }, "dependencies": { - "@ionic/core": "^6.1.6", + "@ionic/core": "^6.1.7", "ionicons": "^6.0.0" }, "vetur": { diff --git a/packages/vue/src/components/IonRouterOutlet.ts b/packages/vue/src/components/IonRouterOutlet.ts index dea05d9525..72c1e82b71 100644 --- a/packages/vue/src/components/IonRouterOutlet.ts +++ b/packages/vue/src/components/IonRouterOutlet.ts @@ -317,6 +317,8 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information. leavingEl.classList.add('ion-page-hidden'); leavingEl.setAttribute('aria-hidden', 'true'); + const usingLinearNavigation = viewStacks.size() === 1; + if (routerAction === 'replace') { leavingViewItem.mount = false; leavingViewItem.ionPageElement = undefined; @@ -327,9 +329,18 @@ See https://ionicframework.com/docs/vue/navigation#ionpage for more information. leavingViewItem.mount = false; leavingViewItem.ionPageElement = undefined; leavingViewItem.ionRoute = false; - viewStacks.unmountLeavingViews(id, enteringViewItem, delta); + + /** + * router.go() expects navigation to be + * linear. If an app is using multiple stacks then + * it is not using linear navigation. As a result, router.go() + * will not give the results that developers are expecting. + */ + if (usingLinearNavigation) { + viewStacks.unmountLeavingViews(id, enteringViewItem, delta); + } } - } else { + } else if (usingLinearNavigation) { viewStacks.mountIntermediaryViews(id, leavingViewItem, delta); } diff --git a/packages/vue/src/index.ts b/packages/vue/src/index.ts index 838ee1ddb3..0c1426cb9c 100644 --- a/packages/vue/src/index.ts +++ b/packages/vue/src/index.ts @@ -115,6 +115,11 @@ export { RadioGroupCustomEvent, RadioGroupChangeEventDetail, + RangeCustomEvent, + RangeChangeEventDetail, + RangeKnobMoveStartEventDetail, + RangeKnobMoveEndEventDetail, + RefresherCustomEvent, RefresherEventDetail, diff --git a/packages/vue/test-app/tests/e2e/specs/tabs.js b/packages/vue/test-app/tests/e2e/specs/tabs.js index 643196f76b..496896ea71 100644 --- a/packages/vue/test-app/tests/e2e/specs/tabs.js +++ b/packages/vue/test-app/tests/e2e/specs/tabs.js @@ -66,7 +66,8 @@ describe('Tabs', () => { cy.ionPageVisible('tab1'); - cy.ionPageDoesNotExist('tab1childone'); + // TODO(FW-1420) + //cy.ionPageDoesNotExist('tab1childone'); cy.ionPageDoesNotExist('tab1childtwo'); }) @@ -534,6 +535,51 @@ describe('Tabs', () => { cy.ionPageVisible('tab2'); cy.ionPageHidden('tab1'); }); + + // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/25255 + it('should not error when going back to root tab multiple times', () => { + cy.visit('http://localhost:8080/tabs'); + + cy.routerPush('/tabs/tab1/childone'); + cy.ionPageVisible('tab1childone'); + cy.ionPageHidden('tab1'); + + cy.get('ion-tab-button#tab-button-tab2').click(); + cy.ionPageHidden('tab1childone'); + cy.ionPageVisible('tab2'); + + cy.get('ion-tab-button#tab-button-tab1').click(); + cy.ionPageHidden('tab2'); + cy.ionPageVisible('tab1childone'); + + cy.get('ion-tab-button#tab-button-tab1').click(); + cy.ionPageDoesNotExist('tab1childone'); + cy.ionPageVisible('tab1'); + + cy.get('ion-tab-button#tab-button-tab2').click(); + cy.ionPageHidden('tab1'); + cy.ionPageVisible('tab2'); + + cy.get('ion-tab-button#tab-button-tab1').click(); + cy.ionPageHidden('tab2'); + cy.ionPageVisible('tab1'); + + cy.routerPush('/tabs/tab1/childone'); + cy.ionPageVisible('tab1childone'); + cy.ionPageHidden('tab1'); + + cy.get('ion-tab-button#tab-button-tab2').click(); + cy.ionPageHidden('tab1childone'); + cy.ionPageVisible('tab2'); + + cy.get('ion-tab-button#tab-button-tab1').click(); + cy.ionPageHidden('tab2'); + cy.ionPageVisible('tab1childone'); + + cy.get('ion-tab-button#tab-button-tab1').click(); + cy.ionPageDoesNotExist('tab1childone'); + cy.ionPageVisible('tab1'); + }) }) describe('Tabs - Swipe to Go Back', () => {