Compare commits
18 Commits
v6.1.6
...
v6.1.7-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbae83fd14 | ||
|
|
0156be61cb | ||
|
|
311c634d20 | ||
|
|
a9893640b7 | ||
|
|
5aa610709d | ||
|
|
8130f2c509 | ||
|
|
61e571e585 | ||
|
|
60054310af | ||
|
|
6034418b33 | ||
|
|
51f3179bcc | ||
|
|
48a3794c16 | ||
|
|
fdc55c0727 | ||
|
|
7111370dd7 | ||
|
|
08587bcd9f | ||
|
|
05ae8e2072 | ||
|
|
5e23fb1ce4 | ||
|
|
ba0f8caba8 | ||
|
|
aa5899db72 |
18
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-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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
18
angular/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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>>;
|
||||
|
||||
|
||||
19
angular/test/test-app/e2e/src/accordion.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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>
|
||||
@@ -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() {}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
|
||||
@@ -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' }),
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
4
core/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ionic/core",
|
||||
"version": "6.1.6",
|
||||
"version": "6.1.7-nightly.20220524",
|
||||
"description": "Base components for Ionic",
|
||||
"keywords": [
|
||||
"ionic",
|
||||
|
||||
6
core/src/components.d.ts
vendored
@@ -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;
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
37
core/src/components/app/test/safe-area/app.e2e.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 28 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 82 KiB |
@@ -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');
|
||||
});
|
||||
@@ -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`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
@@ -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>
|
||||
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -46,6 +46,7 @@ export class List implements ComponentInterface {
|
||||
const { lines, inset } = this;
|
||||
return (
|
||||
<Host
|
||||
role="list"
|
||||
class={{
|
||||
[mode]: true,
|
||||
|
||||
|
||||
24
core/src/components/list/test/a11y/index.html
Normal 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>
|
||||
13
core/src/components/list/test/a11y/list.e2e.ts
Normal 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([]);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
88
core/src/components/modal/test/card-nav/index.html
Normal 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>
|
||||
50
core/src/components/modal/test/card-nav/modal.e2e.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 372 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 234 KiB |
|
Before Width: | Height: | Size: 682 KiB After Width: | Height: | Size: 719 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 198 KiB |
|
Before Width: | Height: | Size: 439 KiB After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 680 KiB After Width: | Height: | Size: 718 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 439 KiB After Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 373 KiB After Width: | Height: | Size: 394 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 84 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 395 KiB |
|
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 225 KiB After Width: | Height: | Size: 235 KiB |
|
Before Width: | Height: | Size: 689 KiB After Width: | Height: | Size: 727 KiB |
|
Before Width: | Height: | Size: 193 KiB After Width: | Height: | Size: 199 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 688 KiB After Width: | Height: | Size: 726 KiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 200 KiB |
|
Before Width: | Height: | Size: 440 KiB After Width: | Height: | Size: 459 KiB |
|
Before Width: | Height: | Size: 369 KiB After Width: | Height: | Size: 391 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 371 KiB After Width: | Height: | Size: 392 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 232 KiB |
|
Before Width: | Height: | Size: 673 KiB After Width: | Height: | Size: 711 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 451 KiB |
|
Before Width: | Height: | Size: 671 KiB After Width: | Height: | Size: 710 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 196 KiB |
|
Before Width: | Height: | Size: 431 KiB After Width: | Height: | Size: 451 KiB |