-
Notifications
You must be signed in to change notification settings - Fork 13.4k
fix(modal): allow interaction with parent content through sheet modals in child routes #30839
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 6 commits
ea8a22d
cc6727a
ef60cf1
4454872
38743ff
4ad27fb
6780fad
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { expect, test } from '@playwright/test'; | ||
|
|
||
| /** | ||
| * Tests for sheet modals in child routes with showBackdrop=false. | ||
| * Parent has buttons + nested outlet; child route contains only the modal. | ||
| * See https://github.com/ionic-team/ionic-framework/issues/30700 | ||
| */ | ||
| test.describe('Modals: Inline Sheet in Child Route (standalone)', () => { | ||
| test.beforeEach(async ({ page }) => { | ||
| await page.goto('/standalone/modal-child-route/child'); | ||
| }); | ||
|
|
||
| test('should render parent content and child modal', async ({ page }) => { | ||
| await expect(page.locator('#increment-btn')).toBeVisible(); | ||
| await expect(page.locator('#decrement-btn')).toBeVisible(); | ||
| await expect(page.locator('#background-action-count')).toHaveText('0'); | ||
| await expect(page.locator('ion-modal.show-modal')).toBeVisible(); | ||
| await expect(page.locator('#modal-content-loaded')).toBeVisible(); | ||
| }); | ||
|
|
||
| test('should allow interacting with parent content while modal is open in child route', async ({ page }) => { | ||
| await expect(page.locator('ion-modal.show-modal')).toBeVisible(); | ||
|
|
||
| await page.locator('#increment-btn').click(); | ||
| await expect(page.locator('#background-action-count')).toHaveText('1'); | ||
| }); | ||
|
Comment on lines
+21
to
+26
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was testing this scenario manually and I'm not able to interact with the background. This is happening on Firefox, Chrome, and Safari. Screen.Recording.2025-12-08.at.11.49.19.AM.mov
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This works great for me in my testing, and worked for kumibrr here, so I'm unsure of what's going on 🤔 Maybe pull and try with my latest changes? That's real weird though! Screenshot.2025-12-08.at.15.10.34.mp4
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I still had issues with the new code but I've approved it since the issue seems to be only on my end. |
||
|
|
||
| test('should allow multiple interactions with parent content while modal is open', async ({ page }) => { | ||
| await expect(page.locator('ion-modal.show-modal')).toBeVisible(); | ||
|
|
||
| await page.locator('#increment-btn').click(); | ||
| await page.locator('#increment-btn').click(); | ||
| await expect(page.locator('#background-action-count')).toHaveText('2'); | ||
|
|
||
| await page.locator('#decrement-btn').click(); | ||
| await expect(page.locator('#background-action-count')).toHaveText('1'); | ||
| }); | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import { CommonModule } from '@angular/common'; | ||
| import { Component } from '@angular/core'; | ||
| import { IonContent, IonHeader, IonModal, IonTitle, IonToolbar } from '@ionic/angular/standalone'; | ||
|
|
||
| /** | ||
| * Child route component containing only the sheet modal with showBackdrop=false. | ||
| * Verifies issue https://github.com/ionic-team/ionic-framework/issues/30700 | ||
| */ | ||
ShaneK marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| @Component({ | ||
| selector: 'app-modal-child-route-child', | ||
| template: ` | ||
| <ion-modal | ||
| [isOpen]="true" | ||
| [breakpoints]="[0.2, 0.5, 0.7]" | ||
| [initialBreakpoint]="0.5" | ||
| [showBackdrop]="false" | ||
| > | ||
| <ng-template> | ||
| <ion-header> | ||
| <ion-toolbar> | ||
| <ion-title>Modal in Child Route</ion-title> | ||
| </ion-toolbar> | ||
| </ion-header> | ||
| <ion-content class="ion-padding"> | ||
| <p id="modal-content-loaded">Modal content loaded in child route</p> | ||
| </ion-content> | ||
| </ng-template> | ||
| </ion-modal> | ||
| `, | ||
| standalone: true, | ||
| imports: [CommonModule, IonContent, IonHeader, IonModal, IonTitle, IonToolbar], | ||
| }) | ||
| export class ModalChildRouteChildComponent {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import { Component } from '@angular/core'; | ||
| import { IonButton, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar } from '@ionic/angular/standalone'; | ||
|
|
||
| /** | ||
| * Parent with interactive buttons and nested outlet for child route modal. | ||
| * See https://github.com/ionic-team/ionic-framework/issues/30700 | ||
| */ | ||
| @Component({ | ||
| selector: 'app-modal-child-route-parent', | ||
| template: ` | ||
| <ion-header> | ||
| <ion-toolbar> | ||
| <ion-title>Parent Page with Nested Route</ion-title> | ||
| </ion-toolbar> | ||
| </ion-header> | ||
| <ion-content class="ion-padding"> | ||
| <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> | ||
| <ion-button id="decrement-btn" (click)="decrement()">-</ion-button> | ||
| <p id="background-action-count">{{ count }}</p> | ||
| <ion-button id="increment-btn" (click)="increment()">+</ion-button> | ||
| </div> | ||
| <ion-router-outlet></ion-router-outlet> | ||
| </ion-content> | ||
| `, | ||
| standalone: true, | ||
| imports: [IonButton, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar], | ||
| }) | ||
| export class ModalChildRouteParentComponent { | ||
| count = 0; | ||
|
|
||
| increment() { | ||
| this.count++; | ||
| } | ||
|
|
||
| decrement() { | ||
| this.count--; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import React, { useState } from 'react'; | ||
| import { | ||
| IonButton, | ||
| IonContent, | ||
| IonHeader, | ||
| IonModal, | ||
| IonPage, | ||
| IonRouterOutlet, | ||
| IonTitle, | ||
| IonToolbar, | ||
| } from '@ionic/react'; | ||
| import { Route } from 'react-router'; | ||
|
|
||
| /** | ||
| * Parent component with counter buttons and nested router outlet. | ||
| * This reproduces the issue from https://github.com/ionic-team/ionic-framework/issues/30700 | ||
| * where sheet modals in child routes with showBackdrop=false block interaction with parent content. | ||
| */ | ||
| const ModalSheetChildRouteParent: React.FC = () => { | ||
| const [count, setCount] = useState(0); | ||
|
|
||
| return ( | ||
| <IonPage> | ||
| <IonHeader> | ||
| <IonToolbar> | ||
| <IonTitle>Parent Page with Nested Route</IonTitle> | ||
| </IonToolbar> | ||
| </IonHeader> | ||
| <IonContent className="ion-padding"> | ||
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}> | ||
| <IonButton id="decrement-btn" onClick={() => setCount((c) => c - 1)}> | ||
| - | ||
| </IonButton> | ||
| <p id="background-action-count">{count}</p> | ||
| <IonButton id="increment-btn" onClick={() => setCount((c) => c + 1)}> | ||
| + | ||
| </IonButton> | ||
| </div> | ||
| </IonContent> | ||
| <IonRouterOutlet> | ||
| <Route path="/overlay-components/modal-sheet-child-route/child" component={ModalSheetChildRouteChild} /> | ||
| </IonRouterOutlet> | ||
| </IonPage> | ||
| ); | ||
| }; | ||
|
|
||
| const ModalSheetChildRouteChild: React.FC = () => { | ||
| return ( | ||
| <IonPage> | ||
| <IonModal | ||
| isOpen={true} | ||
| breakpoints={[0.2, 0.5, 0.7]} | ||
| initialBreakpoint={0.5} | ||
| showBackdrop={false} | ||
| > | ||
| <IonHeader> | ||
| <IonToolbar> | ||
| <IonTitle>Modal in Child Route</IonTitle> | ||
| </IonToolbar> | ||
| </IonHeader> | ||
| <IonContent className="ion-padding"> | ||
| <p id="modal-content-loaded">Modal content loaded in child route</p> | ||
| </IonContent> | ||
| </IonModal> | ||
| </IonPage> | ||
| ); | ||
| }; | ||
|
|
||
| export default ModalSheetChildRouteParent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that
getOriginalPageParent()gets called twice, here andcleanupChildRoutePassthrough(). Is it possible to save thepageParentas a private variable instead? So we don't have to run the while loop more than once?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!