Skip to content

Commit 61b588c

Browse files
committed
Merge branch 'main' of github.com:ionic-team/ionic-framework into FW-6830
2 parents 4b7f2fa + e5634d4 commit 61b588c

2 files changed

Lines changed: 181 additions & 0 deletions

File tree

core/src/components/modal/modal.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1213,6 +1213,11 @@ export class Modal implements ComponentInterface, OverlayInterface {
12131213
}
12141214

12151215
private handleViewTransition() {
1216+
// Only run view transitions when the modal is presented
1217+
if (!this.presented) {
1218+
return;
1219+
}
1220+
12161221
const isPortrait = window.innerWidth < 768;
12171222

12181223
// Only transition if view state actually changed
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { expect } from '@playwright/test';
2+
import { configs, test } from '@utils/test/playwright';
3+
4+
configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => {
5+
test.describe(title('card modal: viewport resize'), () => {
6+
test.beforeEach(async ({ page }) => {
7+
// Start in portrait mode (mobile)
8+
await page.setViewportSize({ width: 375, height: 667 });
9+
10+
await page.setContent(
11+
`
12+
<ion-app>
13+
<div class="ion-page" id="main-page">
14+
<ion-header>
15+
<ion-toolbar>
16+
<ion-title>Card Viewport Resize Test</ion-title>
17+
</ion-toolbar>
18+
</ion-header>
19+
<ion-content class="ion-padding">
20+
<p>This page tests that viewport resize does not trigger card modal animation when modal is closed.</p>
21+
<ion-button id="open-modal">Open Card Modal</ion-button>
22+
<ion-modal id="card-modal">
23+
<ion-header>
24+
<ion-toolbar>
25+
<ion-title>Card Modal</ion-title>
26+
<ion-buttons slot="end">
27+
<ion-button id="close-modal">Close</ion-button>
28+
</ion-buttons>
29+
</ion-toolbar>
30+
</ion-header>
31+
<ion-content class="ion-padding">
32+
<p>Modal content</p>
33+
</ion-content>
34+
</ion-modal>
35+
</ion-content>
36+
</div>
37+
</ion-app>
38+
39+
<script>
40+
const modal = document.querySelector('#card-modal');
41+
const mainPage = document.querySelector('#main-page');
42+
modal.presentingElement = mainPage;
43+
44+
document.querySelector('#open-modal').addEventListener('click', () => {
45+
modal.present();
46+
});
47+
48+
document.querySelector('#close-modal').addEventListener('click', () => {
49+
modal.dismiss();
50+
});
51+
</script>
52+
`,
53+
config
54+
);
55+
});
56+
57+
test('should not animate presenting element when viewport resizes and modal is closed', async ({
58+
page,
59+
}, testInfo) => {
60+
testInfo.annotations.push({
61+
type: 'issue',
62+
description: 'https://github.com/ionic-team/ionic-framework/issues/30679',
63+
});
64+
65+
const mainPage = page.locator('#main-page');
66+
67+
// Verify the presenting element has no transform initially
68+
const initialTransform = await mainPage.evaluate((el) => {
69+
return window.getComputedStyle(el).transform;
70+
});
71+
expect(initialTransform).toBe('none');
72+
73+
// Resize from portrait to landscape (crossing the 768px threshold)
74+
await page.setViewportSize({ width: 900, height: 375 });
75+
76+
// Wait for the debounced resize handler (50ms) plus some buffer
77+
await page.waitForTimeout(150);
78+
79+
// The presenting element should still have no transform
80+
// If the bug exists, it would have scale(0.93) or similar applied
81+
const afterResizeTransform = await mainPage.evaluate((el) => {
82+
return window.getComputedStyle(el).transform;
83+
});
84+
expect(afterResizeTransform).toBe('none');
85+
});
86+
87+
test('should not animate presenting element when resizing multiple times with modal closed', async ({ page }) => {
88+
const mainPage = page.locator('#main-page');
89+
90+
// Multiple resize cycles should not trigger the animation
91+
for (let i = 0; i < 3; i++) {
92+
// Portrait to landscape
93+
await page.setViewportSize({ width: 900, height: 375 });
94+
await page.waitForTimeout(150);
95+
96+
let transform = await mainPage.evaluate((el) => {
97+
return window.getComputedStyle(el).transform;
98+
});
99+
expect(transform).toBe('none');
100+
101+
// Landscape to portrait
102+
await page.setViewportSize({ width: 375, height: 667 });
103+
await page.waitForTimeout(150);
104+
105+
transform = await mainPage.evaluate((el) => {
106+
return window.getComputedStyle(el).transform;
107+
});
108+
expect(transform).toBe('none');
109+
}
110+
});
111+
112+
test('should still animate presenting element correctly when modal is open and viewport resizes', async ({
113+
page,
114+
}) => {
115+
const mainPage = page.locator('#main-page');
116+
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
117+
118+
// Open the modal
119+
await page.click('#open-modal');
120+
await ionModalDidPresent.next();
121+
122+
// When modal is open in portrait, presenting element should be transformed
123+
let transform = await mainPage.evaluate((el) => {
124+
return window.getComputedStyle(el).transform;
125+
});
126+
// The presenting element should have a scale transform when modal is open
127+
expect(transform).not.toBe('none');
128+
129+
// Resize to landscape while modal is open
130+
await page.setViewportSize({ width: 900, height: 375 });
131+
await page.waitForTimeout(150);
132+
133+
// The modal transitions correctly - in landscape mode the presenting element
134+
// should have different (or no) transform than portrait
135+
transform = await mainPage.evaluate((el) => {
136+
return window.getComputedStyle(el).transform;
137+
});
138+
139+
// Note: The exact transform depends on the landscape handling
140+
// The main point is that when modal IS open, the transition should work
141+
// This test just ensures we don't break existing functionality
142+
});
143+
144+
test('presenting element should return to normal after modal is dismissed', async ({ page }) => {
145+
const mainPage = page.locator('#main-page');
146+
const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent');
147+
const ionModalDidDismiss = await page.spyOnEvent('ionModalDidDismiss');
148+
149+
// Open the modal
150+
await page.click('#open-modal');
151+
await ionModalDidPresent.next();
152+
153+
// Close the modal
154+
await page.click('#close-modal');
155+
await ionModalDidDismiss.next();
156+
157+
// Wait for animations to complete
158+
await page.waitForTimeout(500);
159+
160+
// The presenting element should be back to normal
161+
const transform = await mainPage.evaluate((el) => {
162+
return window.getComputedStyle(el).transform;
163+
});
164+
expect(transform).toBe('none');
165+
166+
// Now resize the viewport - should not trigger animation
167+
await page.setViewportSize({ width: 900, height: 375 });
168+
await page.waitForTimeout(150);
169+
170+
const afterResizeTransform = await mainPage.evaluate((el) => {
171+
return window.getComputedStyle(el).transform;
172+
});
173+
expect(afterResizeTransform).toBe('none');
174+
});
175+
});
176+
});

0 commit comments

Comments
 (0)