Skip to content

Commit 0156be6

Browse files
authored
fix(nav): swipe to go back works inside card modal (#25333)
resolves #25327
1 parent 311c634 commit 0156be6

File tree

6 files changed

+182
-39
lines changed

6 files changed

+182
-39
lines changed

core/src/components/modal/gestures/swipe-to-close.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export const createSwipeToCloseGesture = (el: HTMLIonModalElement, animation: An
277277
const gesture = createGesture({
278278
el,
279279
gestureName: 'modalSwipeToClose',
280-
gesturePriority: 40,
280+
gesturePriority: 39,
281281
direction: 'y',
282282
threshold: 10,
283283
canStart,
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!DOCTYPE html>
2+
<html lang="en" dir="ltr">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<title>Modal - Card + Nav</title>
6+
<meta name="apple-mobile-web-app-capable" content="yes" />
7+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
8+
<meta
9+
name="viewport"
10+
content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"
11+
/>
12+
<link href="../../../../../css/ionic.bundle.css" rel="stylesheet" />
13+
<link href="../../../../../scripts/testing/styles.css" rel="stylesheet" />
14+
<script src="../../../../../scripts/testing/scripts.js"></script>
15+
<script type="module" src="../../../../../dist/ionic/ionic.esm.js"></script>
16+
</head>
17+
18+
<body>
19+
<script>
20+
class AppNav extends HTMLElement {
21+
connectedCallback() {
22+
this.innerHTML = `
23+
<ion-content>
24+
<ion-nav root="page-one"></ion-nav>
25+
</ion-content>
26+
`;
27+
}
28+
}
29+
class PageOne extends HTMLElement {
30+
connectedCallback() {
31+
this.innerHTML = `
32+
<ion-header>
33+
<ion-toolbar>
34+
<ion-title>Page One</ion-title>
35+
</ion-toolbar>
36+
</ion-header>
37+
<ion-content class="ion-padding">
38+
<h1>Page One</h1>
39+
<ion-nav-link router-direction="forward" component="page-two">
40+
<ion-button id="go-page-two">Go to Page Two</ion-button>
41+
</ion-nav-link>
42+
</ion-content>
43+
`;
44+
}
45+
}
46+
class PageTwo extends HTMLElement {
47+
connectedCallback() {
48+
this.innerHTML = `
49+
<ion-header>
50+
<ion-toolbar>
51+
<ion-buttons slot="start">
52+
<ion-back-button></ion-back-button>
53+
</ion-buttons>
54+
<ion-title>Page Two</ion-title>
55+
</ion-toolbar>
56+
</ion-header>
57+
<ion-content class="ion-padding page-two-content"></ion-content>
58+
`;
59+
}
60+
}
61+
customElements.define('page-one', PageOne);
62+
customElements.define('page-two', PageTwo);
63+
customElements.define('app-nav', AppNav);
64+
</script>
65+
<ion-app>
66+
<div class="ion-page">
67+
<ion-header>
68+
<ion-toolbar>
69+
<ion-title>Card</ion-title>
70+
</ion-toolbar>
71+
</ion-header>
72+
73+
<ion-content class="ion-padding">
74+
<ion-button id="open-modal">Open Modal</ion-button>
75+
76+
<ion-modal trigger="open-modal" component="app-nav"></ion-modal>
77+
</ion-content>
78+
</div>
79+
</ion-app>
80+
81+
<script>
82+
const modal = document.querySelector('ion-modal');
83+
const nav = document.querySelector('ion-nav');
84+
modal.canDismiss = true;
85+
modal.presentingElement = document.querySelector('.ion-page');
86+
</script>
87+
</body>
88+
</html>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { expect } from '@playwright/test';
2+
import { test, dragElementBy } from '@utils/test/playwright';
3+
4+
import { CardModalPage } from '../fixtures';
5+
6+
test.describe('card modal - nav', () => {
7+
let cardModalPage: CardModalPage;
8+
test.beforeEach(async ({ page, browserName }, testInfo) => {
9+
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');
10+
test.skip(
11+
testInfo.project.metadata.rtl === true,
12+
'This test only verifies that the gesture activates inside of a modal.'
13+
);
14+
test.skip(browserName !== 'chromium', 'dragElementBy is flaky outside of Chrome browsers.');
15+
16+
cardModalPage = new CardModalPage(page);
17+
await cardModalPage.navigate('/src/components/modal/test/card-nav?ionic:_testing=false');
18+
});
19+
test('it should swipe to go back', async ({ page }) => {
20+
await cardModalPage.openModalByTrigger('#open-modal');
21+
22+
const nav = page.locator('ion-nav') as any;
23+
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
24+
25+
await page.click('#go-page-two');
26+
27+
await ionNavDidChange.next();
28+
29+
const pageOne = page.locator('page-one');
30+
expect(pageOne).toHaveClass(/ion-page-hidden/);
31+
32+
const content = page.locator('.page-two-content');
33+
34+
await dragElementBy(content, page, 1000, 0, 10);
35+
36+
await ionNavDidChange.next();
37+
});
38+
test('should swipe to close', async ({ page }) => {
39+
await cardModalPage.openModalByTrigger('#open-modal');
40+
41+
const nav = page.locator('ion-nav') as any;
42+
const ionNavDidChange = await nav.spyOnEvent('ionNavDidChange');
43+
44+
await page.click('#go-page-two');
45+
46+
await ionNavDidChange.next();
47+
48+
await cardModalPage.swipeToCloseModal('ion-modal ion-content.page-two-content');
49+
});
50+
});

core/src/components/modal/test/card/modal.e2e.ts

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,15 @@
11
import { expect } from '@playwright/test';
2-
import { dragElementBy, test, Viewports } from '@utils/test/playwright';
3-
import type { E2EPage, EventSpy } from '@utils/test/playwright';
4-
5-
class CardModalPage {
6-
private ionModalDidPresent!: EventSpy;
7-
private ionModalDidDismiss!: EventSpy;
8-
private page: E2EPage;
9-
10-
constructor(page: E2EPage) {
11-
this.page = page;
12-
}
13-
async navigate() {
14-
const { page } = this;
15-
await page.goto('/src/components/modal/test/card');
16-
this.ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
17-
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
18-
}
19-
async openModalByTrigger(selector: string) {
20-
await this.page.click(selector);
21-
await this.ionModalDidPresent.next();
22-
23-
return this.page.locator('ion-modal');
24-
}
25-
26-
async swipeToCloseModal(selector: string, waitForDismiss = true, swipeY = 500) {
27-
const { page } = this;
28-
const elementRef = await page.locator(selector);
29-
await dragElementBy(elementRef, page, 0, swipeY);
30-
31-
if (waitForDismiss) {
32-
await this.ionModalDidDismiss.next();
33-
}
34-
}
35-
}
2+
import { test, Viewports } from '@utils/test/playwright';
3+
4+
import { CardModalPage } from '../fixtures';
365

376
test.describe('card modal', () => {
387
let cardModalPage: CardModalPage;
398
test.beforeEach(async ({ page }, testInfo) => {
409
test.skip(testInfo.project.metadata.mode !== 'ios', 'Card style modal is only available on iOS');
4110

4211
cardModalPage = new CardModalPage(page);
43-
await cardModalPage.navigate();
12+
await cardModalPage.navigate('/src/components/modal/test/card');
4413
});
4514
test.describe('card modal: rendering', () => {
4615
test('should not have visual regressions', async ({ page }) => {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { dragElementBy } from '@utils/test/playwright';
2+
import type { E2EPage, EventSpy } from '@utils/test/playwright';
3+
4+
export class CardModalPage {
5+
private ionModalDidPresent!: EventSpy;
6+
private ionModalDidDismiss!: EventSpy;
7+
private page: E2EPage;
8+
9+
constructor(page: E2EPage) {
10+
this.page = page;
11+
}
12+
async navigate(url: string) {
13+
const { page } = this;
14+
await page.goto(url);
15+
this.ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
16+
this.ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
17+
}
18+
async openModalByTrigger(selector: string) {
19+
await this.page.click(selector);
20+
await this.ionModalDidPresent.next();
21+
22+
return this.page.locator('ion-modal');
23+
}
24+
25+
async swipeToCloseModal(selector: string, waitForDismiss = true, swipeY = 500) {
26+
const { page } = this;
27+
const elementRef = await page.locator(selector);
28+
await dragElementBy(elementRef, page, 0, swipeY);
29+
30+
if (waitForDismiss) {
31+
await this.ionModalDidDismiss.next();
32+
}
33+
}
34+
}

core/src/utils/test/playwright/drag-element.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export const dragElementBy = async (
66
el: Locator | ElementHandle<SVGElement | HTMLElement>,
77
page: E2EPage,
88
dragByX = 0,
9-
dragByY = 0
9+
dragByY = 0,
10+
startXCoord?: number,
11+
startYCoord?: number
1012
) => {
1113
const boundingBox = await el.boundingBox();
1214

@@ -16,8 +18,8 @@ export const dragElementBy = async (
1618
);
1719
}
1820

19-
const startX = boundingBox.x + boundingBox.width / 2;
20-
const startY = boundingBox.y + boundingBox.height / 2;
21+
const startX = startXCoord === undefined ? boundingBox.x + boundingBox.width / 2 : startXCoord;
22+
const startY = startYCoord === undefined ? boundingBox.y + boundingBox.height / 2 : startYCoord;
2123

2224
const midX = startX + dragByX / 2;
2325
const midY = startY + dragByY / 2;

0 commit comments

Comments
 (0)