Compare commits

...

18 Commits

Author SHA1 Message Date
github-actions
bbae83fd14 v6.1.7-nightly.20220524 2022-05-24 06:08:37 +00:00
Liam DeBeasi
0156be61cb fix(nav): swipe to go back works inside card modal (#25333)
resolves #25327
2022-05-23 16:49:36 -04:00
Liam DeBeasi
311c634d20 fix(item, list): list aria roles are added (#25336)
resolves #19939
2022-05-23 15:50:28 -04:00
Liam DeBeasi
a9893640b7 test(playwright): add spyOnEvent for Locators (#25328) 2022-05-23 10:03:33 -04:00
Liam DeBeasi
5aa610709d test(app): migrate tests to playwright (#25323) 2022-05-23 09:38:53 -04:00
Sean Perkins
8130f2c509 test(button): ripple effect screenshot tests (#25295) 2022-05-20 12:47:06 -04:00
Liam DeBeasi
61e571e585 fix(accordion): accordions expand when using binding (#25322)
resolves #25307
2022-05-20 09:43:04 -04:00
Liam DeBeasi
60054310af fix(menu): rtl menu no longer disappears on ios 15 (#25309)
resolves #25192
2022-05-19 14:16:52 -04:00
Sean Perkins
6034418b33 fix(react): IonTabButton will call custom onClick handlers (#25313)
Resolves #22511
2022-05-19 12:19:34 -04:00
Liam DeBeasi
51f3179bcc chore(): add stronger typing to the tapclick utility (#25316) 2022-05-19 09:31:21 -04:00
Sean Perkins
48a3794c16 docs(content): set scrollEvents to true to listen to scroll events (#25314)
Resolves #25308
2022-05-18 22:20:24 -04:00
Liam DeBeasi
fdc55c0727 fix(modal): swipe to close on content blocks scroll in ion-nav (#25300)
resolves #25298
2022-05-18 15:16:18 -04:00
Amanda Johnston
7111370dd7 fix(react): add param types to useIonPopover dismiss function (#25311) 2022-05-18 13:15:35 -05:00
Sean Perkins
08587bcd9f test(range): migrate tests to playwright (#25299) 2022-05-18 12:24:27 -04:00
Sean Perkins
05ae8e2072 test(picker-column): migrate tests to playwright (#25303) 2022-05-18 11:53:12 -04:00
Liam DeBeasi
5e23fb1ce4 fix(vue): correct views are now unmounted in tabs (#25270)
resolves #25255
2022-05-18 10:10:02 -04:00
Liam DeBeasi
ba0f8caba8 merge release-6.1.6
Release 6.1.6
2022-05-18 09:18:13 -04:00
Liam DeBeasi
aa5899db72 chore(): update package lock files 2022-05-18 08:43:37 -04:00
248 changed files with 1194 additions and 474 deletions

View File

@@ -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-nightly.20220524](https://github.com/ionic-team/ionic-framework/compare/v6.1.6...v6.1.7-nightly.20220524) (2022-05-24)
### 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)
* **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)
* **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)
* **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)

View File

@@ -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-nightly.20220524](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7-nightly.20220524) (2022-05-24)
### 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)
## [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

View File

@@ -1,15 +1,15 @@
{
"name": "@ionic/angular",
"version": "6.1.6",
"version": "6.1.7-nightly.20220524",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/angular",
"version": "6.1.5",
"version": "6.1.6",
"license": "MIT",
"dependencies": {
"@ionic/core": "^6.1.5",
"@ionic/core": "^6.1.6",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},
@@ -1023,9 +1023,9 @@
"dev": true
},
"node_modules/@ionic/core": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.5.tgz",
"integrity": "sha512-YEpFheFDGV7lifbYNqctcPXRPqEOKiDy5KgSPriFzrrPUbwrv/tnXHZq7hFVPCMUYFBS9QJts4r5FOYTqAfvtw==",
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz",
"integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==",
"dependencies": {
"@stencil/core": "^2.14.2",
"ionicons": "^6.0.0",
@@ -7951,9 +7951,9 @@
"dev": true
},
"@ionic/core": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.5.tgz",
"integrity": "sha512-YEpFheFDGV7lifbYNqctcPXRPqEOKiDy5KgSPriFzrrPUbwrv/tnXHZq7hFVPCMUYFBS9QJts4r5FOYTqAfvtw==",
"version": "6.1.6",
"resolved": "https://registry.npmjs.org/@ionic/core/-/core-6.1.6.tgz",
"integrity": "sha512-AsYGEHKVHy082RST3RBrIiOZX6VXNy6qYSYtf6TwOwmF/YV+/ASaB1TqVO/jP658ML106nNcjUM0fTkbm9UXRA==",
"requires": {
"@stencil/core": "^2.14.2",
"ionicons": "^6.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/angular",
"version": "6.1.6",
"version": "6.1.7-nightly.20220524",
"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-nightly.20220524",
"jsonc-parser": "^3.0.0",
"tslib": "^2.0.0"
},

View File

@@ -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<CustomEvent<IContentScrollBaseDetail>>;
/**
* Emitted while scrolling. This event is disabled by default.
Look at the property: `scrollEvents`
Set `scrollEvents` to `true` to enable.
*/
ionScroll: EventEmitter<CustomEvent<IContentScrollDetail>>;
/**
* 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<CustomEvent<IContentScrollBaseDetail>>;

View File

@@ -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');
});
});

View File

@@ -0,0 +1,17 @@
<ion-content>
<ion-button id="dismiss" (click)="modal.dismiss()">Dismiss Modal</ion-button>
<ion-accordion-group [value]="'a'">
<ion-accordion value="a">
<ion-item slot="header">
<ion-label>A</ion-label>
</ion-item>
<div slot="content">A content</div>
</ion-accordion>
<ion-accordion value="b">
<ion-item slot="header">
<ion-label>B</ion-label>
</ion-item>
<div slot="content">B content</div>
</ion-accordion>
</ion-accordion-group>
</ion-content>

View File

@@ -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() {}
}

View File

@@ -0,0 +1,13 @@
<ion-header>
<ion-toolbar>
<ion-buttons>
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>
Accordion test
</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-button id="open-modal" (click)="open()">Open Modal</ion-button>
</ion-content>

View File

@@ -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();
}
}

View File

@@ -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 },

View File

@@ -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' }),

View File

@@ -62,5 +62,10 @@
Providers
</ion-label>
</ion-item>
<ion-item routerLink="/accordions">
<ion-label>
Accordions Test
</ion-label>
</ion-item>
</ion-list>
</ion-content>

View File

@@ -3,6 +3,21 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [6.1.7-nightly.20220524](https://github.com/ionic-team/ionic/compare/v6.1.6...v6.1.7-nightly.20220524) (2022-05-24)
### 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)
* **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)
## [6.1.6](https://github.com/ionic-team/ionic/compare/v6.1.5...v6.1.6) (2022-05-18)

View File

@@ -1,12 +1,12 @@
{
"name": "@ionic/core",
"version": "6.1.6",
"version": "6.1.7-nightly.20220524",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@ionic/core",
"version": "6.1.5",
"version": "6.1.6",
"license": "MIT",
"dependencies": {
"@stencil/core": "^2.14.2",

View File

@@ -1,6 +1,6 @@
{
"name": "@ionic/core",
"version": "6.1.6",
"version": "6.1.7-nightly.20220524",
"description": "Base components for Ionic",
"keywords": [
"ionic",

View File

@@ -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<ScrollDetail>) => 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<ScrollBaseDetail>) => 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<ScrollBaseDetail>) => void;
/**

View File

@@ -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);
}

View File

@@ -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');
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@@ -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');
});

View File

@@ -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`
);
});
});

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -24,7 +24,7 @@
<ion-content class="ion-padding ion-text-center" id="content" no-bounce>
<p>
<ion-button>Default</ion-button>
<ion-button id="default">Default</ion-button>
<ion-button class="ion-focused">Default.focused</ion-button>
<ion-button class="ion-activated">Default.activated</ion-button>
</p>

View File

@@ -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<ScrollBaseDetail>;
/**
* Emitted while scrolling. This event is disabled by default.
* Look at the property: `scrollEvents`
* Set `scrollEvents` to `true` to enable.
*/
@Event() ionScroll!: EventEmitter<ScrollDetail>;
/**
* 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<ScrollBaseDetail>;

View File

@@ -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 (
<Host
aria-disabled={ariaDisabled}
@@ -402,13 +403,14 @@ export class Item implements ComponentInterface, AnchorInterface, ButtonInterfac
[`item-fill-${fillValue}`]: true,
[`item-shape-${shape}`]: shape !== undefined,
'item-disabled': disabled,
'in-list': hostContext('ion-list', this.el),
'in-list': inList,
'item-multiple-inputs': this.multipleInputs,
'ion-activatable': canActivate,
'ion-focusable': this.focusable,
'item-rtl': document.dir === 'rtl',
}),
}}
role={inList ? 'listitem' : null}
>
<TagType {...attrs} class="item-native" part="native" disabled={disabled} {...clickFn}>
<slot name="start"></slot>

View File

@@ -46,6 +46,7 @@ export class List implements ComponentInterface {
const { lines, inset } = this;
return (
<Host
role="list"
class={{
[mode]: true,

View File

@@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>List - a11y</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0" />
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script nomodule src="../../../../../dist/ionic/ionic.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<main>
<h1>List - a11y</h1>
<ion-list>
<ion-item>Item 1</ion-item>
<ion-item>Item 2</ion-item>
<ion-item>Item 3</ion-item>
</ion-list>
</main>
</body>
</html>

View File

@@ -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([]);
});
});

View File

@@ -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;

View File

@@ -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,

View File

@@ -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.

View File

@@ -0,0 +1,88 @@
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="UTF-8" />
<title>Modal - Card + Nav</title>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta
name="viewport"
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
/>
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
<script src="../../../../../scripts/testing/scripts.js"></script>
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
</head>
<body>
<script>
class AppNav extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-content>
<ion-nav root="page-one"></ion-nav>
</ion-content>
`;
}
}
class PageOne extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-title>Page One</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<h1>Page One</h1>
<ion-nav-link router-direction="forward" component="page-two">
<ion-button id="go-page-two">Go to Page Two</ion-button>
</ion-nav-link>
</ion-content>
`;
}
}
class PageTwo extends HTMLElement {
connectedCallback() {
this.innerHTML = `
<ion-header>
<ion-toolbar>
<ion-buttons slot="start">
<ion-back-button></ion-back-button>
</ion-buttons>
<ion-title>Page Two</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding page-two-content"></ion-content>
`;
}
}
customElements.define('page-one', PageOne);
customElements.define('page-two', PageTwo);
customElements.define('app-nav', AppNav);
</script>
<ion-app>
<div class="ion-page">
<ion-header>
<ion-toolbar>
<ion-title>Card</ion-title>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
<ion-button id="open-modal">Open Modal</ion-button>
<ion-modal trigger="open-modal" component="app-nav"></ion-modal>
</ion-content>
</div>
</ion-app>
<script>
const modal = document.querySelector('ion-modal');
const nav = document.querySelector('ion-nav');
modal.canDismiss = true;
modal.presentingElement = document.querySelector('.ion-page');
</script>
</body>
</html>

View File

@@ -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');
});
});

View File

@@ -21,6 +21,11 @@
ion-modal#custom {
--border-radius: 50px !important;
}
.content-wrapper {
height: 100%;
overflow: hidden;
}
</style>
</head>
@@ -48,71 +53,73 @@
</ion-app>
<script>
const renderContent = () => {
return `<ion-content class="ion-padding">
Hello World!
<br />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
</ion-content>`;
};
async function createModal(presentingEl, opts) {
// create component to open
const element = document.createElement('div');
element.innerHTML = `
<ion-header id="modal-header">
<ion-toolbar>
<ion-title>Contacts</ion-title>
<ion-buttons slot="end">
<ion-button class="add">
<ion-icon name="add" slot="icon-only"></ion-icon>
</ion-button>
<ion-button class="add">Add</ion-button>
<ion-button class="dismiss">Close</ion-button>
<ion-button class="replace">Replace</ion-button>
</ion-buttons>
</ion-toolbar>
</ion-header>
<ion-content class="ion-padding">
Hello World!
<ion-button class="dismiss">Dismiss Modal</ion-button>
<br />
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vitae lobortis felis, eu sodales enim. Nam
risus nibh, placerat at rutrum ac, vehicula vel velit. Lorem ipsum dolor sit amet, consectetur adipiscing
elit. Vestibulum quis elementum ligula, ac aliquet nulla. Mauris non placerat mauris. Aenean dignissim lacinia
porttitor. Praesent fringilla at est et ullamcorper. In ac ante ac massa porta venenatis ut id nibh. Fusce
felis neque, aliquet in velit vitae, venenatis euismod libero. Donec vulputate, urna sed sagittis tempor, mi
arcu tristique lacus, eget fringilla urna sem eget felis. Fusce dignissim lacus a scelerisque vehicula. Nulla
nec enim nunc. Quisque nec dui eu nibh pulvinar bibendum quis ut nunc. Duis ex odio, sollicitudin ac mollis
nec, fringilla non lacus. Maecenas sed tincidunt urna. Nunc feugiat maximus venenatis. Donec porttitor, felis
eget porttitor tempor, quam nulla dapibus nisl, sit amet posuere sapien sapien malesuada tortor. Pellentesque
habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Quisque luctus, sapien nec
tincidunt efficitur, nibh turpis faucibus felis, in sodales massa augue nec erat. Morbi sollicitudin nisi ex,
et gravida nisi euismod eu. Suspendisse hendrerit dapibus orci, non viverra neque vestibulum id. Quisque vitae
interdum ligula, quis consectetur nibh. Phasellus in mi at erat ultrices semper. Fusce sollicitudin at dolor
ac lobortis. Morbi sit amet sem quis nulla pellentesque imperdiet. Nullam eu sem a enim maximus eleifend non
vulputate leo. Proin quis congue lacus. Pellentesque placerat, quam at tempus pulvinar, nisl ligula tempor
risus, quis pretium arcu odio et nulla. Nullam mollis consequat pharetra. Phasellus dictum velit sed purus
mattis maximus. In molestie eget massa ut dignissim. In a interdum elit. In finibus nibh a mauris lobortis
aliquet. Proin rutrum varius consequat. In mollis dapibus nisl, eu finibus urna viverra ac. Quisque
scelerisque nisl eu suscipit consectetur.
</p>
</ion-content>
<div class="content-wrapper">${renderContent()}</div>
<ion-footer>
<ion-toolbar>
@@ -134,6 +141,12 @@
presentModal(topModal, opts);
});
const wrapper = element.querySelector('.content-wrapper');
const replace = element.querySelector('ion-button.replace');
replace.addEventListener('click', () => {
wrapper.innerHTML = renderContent();
});
// present the modal
const modalElement = await modalController.create({
presentingElement: presentingEl,

View File

@@ -1,38 +1,7 @@
import { expect } from '@playwright/test';
import { dragElementBy, test, Viewports } from '@utils/test/playwright';
import type { E2EPage, EventSpy } from '@utils/test/playwright';
import { test, Viewports } from '@utils/test/playwright';
class CardModalPage {
private ionModalDidPresent!: EventSpy;
private ionModalDidDismiss!: EventSpy;
private page: E2EPage;
constructor(page: E2EPage) {
this.page = page;
}
async navigate() {
const { page } = this;
await page.goto('/src/components/modal/test/card');
this.ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
}
async openModalByTrigger(selector: string) {
await this.page.click(selector);
await this.ionModalDidPresent.next();
return this.page.locator('ion-modal');
}
async swipeToCloseModal(selector: string, waitForDismiss = true, swipeY = 500) {
const { page } = this;
const elementRef = await page.locator(selector);
await dragElementBy(elementRef, page, 0, swipeY);
if (waitForDismiss) {
await this.ionModalDidDismiss.next();
}
}
}
import { CardModalPage } from '../fixtures';
test.describe('card modal', () => {
let cardModalPage: CardModalPage;
@@ -40,7 +9,7 @@ test.describe('card modal', () => {
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');
cardModalPage = new CardModalPage(page);
await cardModalPage.navigate();
await cardModalPage.navigate('/src/components/modal/test/card');
});
test.describe('card modal: rendering', () => {
test('should not have visual regressions', async ({ page }) => {
@@ -88,6 +57,21 @@ test.describe('card modal', () => {
await content.waitForElementState('stable');
expect(modal).toBeVisible();
});
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
page,
}) => {
const modal = await cardModalPage.openModalByTrigger('#card');
await page.click('ion-button.replace');
const content = (await page.$('ion-modal ion-content'))!;
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
await content.waitForElementState('stable');
expect(modal).toBeVisible();
});
test('content should be scrollable after gesture ends', async ({ page }) => {
await cardModalPage.openModalByTrigger('#card');
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false, 20);
@@ -152,6 +136,21 @@ test.describe('card modal', () => {
await content.waitForElementState('stable');
expect(modal).toBeVisible();
});
test('it should not swipe to close when swiped on the content but the content is scrolled even when content is replaced', async ({
page,
}) => {
const modal = await cardModalPage.openModalByTrigger('#card');
await page.click('ion-button.replace');
const content = (await page.$('ion-modal ion-content'))!;
await content.evaluate((el: HTMLIonContentElement) => el.scrollToBottom(0));
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false);
await content.waitForElementState('stable');
expect(modal).toBeVisible();
});
test('content should be scrollable after gesture ends', async ({ page }) => {
await cardModalPage.openModalByTrigger('#card');
await cardModalPage.swipeToCloseModal('ion-modal ion-content', false, 20);

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 392 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 234 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 682 KiB

After

Width:  |  Height:  |  Size: 719 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 198 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 458 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 718 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 439 KiB

After

Width:  |  Height:  |  Size: 458 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 373 KiB

After

Width:  |  Height:  |  Size: 394 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 84 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 235 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 374 KiB

After

Width:  |  Height:  |  Size: 395 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

After

Width:  |  Height:  |  Size: 235 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 689 KiB

After

Width:  |  Height:  |  Size: 727 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 193 KiB

After

Width:  |  Height:  |  Size: 199 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 459 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 KiB

After

Width:  |  Height:  |  Size: 726 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 200 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 440 KiB

After

Width:  |  Height:  |  Size: 459 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

After

Width:  |  Height:  |  Size: 391 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 232 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 KiB

After

Width:  |  Height:  |  Size: 392 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 82 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 222 KiB

After

Width:  |  Height:  |  Size: 232 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 KiB

After

Width:  |  Height:  |  Size: 711 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

After

Width:  |  Height:  |  Size: 451 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 671 KiB

After

Width:  |  Height:  |  Size: 710 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

After

Width:  |  Height:  |  Size: 196 KiB

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 431 KiB

After

Width:  |  Height:  |  Size: 451 KiB

Some files were not shown because too many files have changed in this diff Show More