mirror of
https://github.com/ionic-team/ionic-framework.git
synced 2026-03-13 10:22:08 +08:00
fix(modal): allow interaction with parent content through sheet modals in child routes (#30839)
Issue number: resolves #30700 --------- ## What is the current behavior? When a sheet modal with showBackdrop=false is rendered in a child route (nested ion-router-outlet), the parent content becomes non-interactive. Clicks on buttons or other interactive elements in the parent component are blocked, even though showBackdrop=false should allow background interaction. Two separate issues contributed to this bug: 1. **Root locking with `backdropBreakpoint`**: The `shouldLockRoot` logic in `overlays.ts` didn't account for `backdropBreakpoint`. Modals with `backdropBreakpoint > 0` were still locking the root with `aria-hidden`, even though developers expect background interaction when the modal is below the backdrop breakpoint. 2. **Child route wrapper blocking**: When a modal is in a child route, the child route's page wrapper (`ion-page`) and its parent `ion-router-outlet` remain in the DOM with `position: absolute` covering the viewport. Even after the modal is moved to `ion-app` and has `pointer-events: none`, these wrapper elements block clicks to the parent page's content. This issue stems from [#30563](https://github.com/ionic-team/ionic-framework/pull/30563), which added root-locking behavior that didn't account for modals that allow background interaction. A partial fix in [#30689](https://github.com/ionic-team/ionic-framework/pull/30689) partially addressed `showBackdrop=false` and `focusTrap=false`, but missed `backdropBreakpoint`. ## What is the new behavior? Sheet modals with showBackdrop=false or focusTrap=false now correctly allow interaction with parent content when the modal is in a child route. Improvements: - Recalculates isSheetModal in present() to handle Angular binding timing - Sets pointer-events: none on the modal element and its original parent elements when background interaction should be allowed - Cleans up pointer-events on dismiss - Adds regression tests ## Does this introduce a breaking change? - [ ] Yes - [X] No ## Other information <!-- Any other information that is important to this PR such as screenshots of how the component looks before and after the change. --> Dev build: ``` 8.7.12-dev.11765060985.14ad27fb ```
This commit is contained in:
@@ -71,7 +71,7 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
private gesture?: Gesture;
|
||||
private coreDelegate: FrameworkDelegate = CoreDelegate();
|
||||
private sheetTransition?: Promise<any>;
|
||||
private isSheetModal = false;
|
||||
@State() private isSheetModal = false;
|
||||
private currentBreakpoint?: number;
|
||||
private wrapperEl?: HTMLElement;
|
||||
private backdropEl?: HTMLIonBackdropElement;
|
||||
@@ -100,6 +100,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
private parentRemovalObserver?: MutationObserver;
|
||||
// Cached original parent from before modal is moved to body during presentation
|
||||
private cachedOriginalParent?: HTMLElement;
|
||||
// Cached ion-page ancestor for child route passthrough
|
||||
private cachedPageParent?: HTMLElement | null;
|
||||
|
||||
lastFocus?: HTMLElement;
|
||||
animation?: Animation;
|
||||
@@ -644,7 +646,14 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
window.addEventListener(KEYBOARD_DID_OPEN, this.keyboardOpenCallback);
|
||||
}
|
||||
|
||||
if (this.isSheetModal) {
|
||||
/**
|
||||
* Recalculate isSheetModal because framework bindings (e.g., Angular)
|
||||
* may not have been applied when componentWillLoad ran.
|
||||
*/
|
||||
const isSheetModal = this.breakpoints !== undefined && this.initialBreakpoint !== undefined;
|
||||
this.isSheetModal = isSheetModal;
|
||||
|
||||
if (isSheetModal) {
|
||||
this.initSheetGesture();
|
||||
} else if (hasCardModal) {
|
||||
this.initSwipeToClose();
|
||||
@@ -753,6 +762,91 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
this.moveSheetToBreakpoint = moveSheetToBreakpoint;
|
||||
|
||||
this.gesture.enable(true);
|
||||
|
||||
/**
|
||||
* When backdrop interaction is allowed, nested router outlets from child routes
|
||||
* may block pointer events to parent content. Apply passthrough styles only when
|
||||
* the modal was the sole content of a child route page.
|
||||
* See https://github.com/ionic-team/ionic-framework/issues/30700
|
||||
*/
|
||||
const backdropNotBlocking = this.showBackdrop === false || this.focusTrap === false || backdropBreakpoint > 0;
|
||||
if (backdropNotBlocking) {
|
||||
this.setupChildRoutePassthrough();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For sheet modals that allow background interaction, sets up pointer-events
|
||||
* passthrough on child route page wrappers and nested router outlets.
|
||||
*/
|
||||
private setupChildRoutePassthrough() {
|
||||
// Cache the page parent for cleanup
|
||||
this.cachedPageParent = this.getOriginalPageParent();
|
||||
const pageParent = this.cachedPageParent;
|
||||
|
||||
// Skip ion-app (controller modals) and pages with visible sibling content next to the modal
|
||||
if (!pageParent || pageParent.tagName === 'ION-APP') {
|
||||
return;
|
||||
}
|
||||
|
||||
const hasVisibleContent = Array.from(pageParent.children).some(
|
||||
(child) =>
|
||||
child !== this.el &&
|
||||
!(child instanceof HTMLElement && window.getComputedStyle(child).display === 'none') &&
|
||||
child.tagName !== 'TEMPLATE' &&
|
||||
child.tagName !== 'SLOT' &&
|
||||
!(child.nodeType === Node.TEXT_NODE && !child.textContent?.trim())
|
||||
);
|
||||
|
||||
if (hasVisibleContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Child route case: page only contained the modal
|
||||
pageParent.classList.add('ion-page-overlay-passthrough');
|
||||
|
||||
// Also make nested router outlets passthrough
|
||||
const routerOutlet = pageParent.parentElement;
|
||||
if (routerOutlet?.tagName === 'ION-ROUTER-OUTLET' && routerOutlet.parentElement?.tagName !== 'ION-APP') {
|
||||
routerOutlet.style.setProperty('pointer-events', 'none');
|
||||
routerOutlet.setAttribute('data-overlay-passthrough', 'true');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the ion-page ancestor of the modal's original parent location.
|
||||
*/
|
||||
private getOriginalPageParent(): HTMLElement | null {
|
||||
if (!this.cachedOriginalParent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let pageParent: HTMLElement | null = this.cachedOriginalParent;
|
||||
while (pageParent && !pageParent.classList.contains('ion-page')) {
|
||||
pageParent = pageParent.parentElement;
|
||||
}
|
||||
return pageParent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes passthrough styles added by setupChildRoutePassthrough.
|
||||
*/
|
||||
private cleanupChildRoutePassthrough() {
|
||||
const pageParent = this.cachedPageParent;
|
||||
if (!pageParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
pageParent.classList.remove('ion-page-overlay-passthrough');
|
||||
|
||||
const routerOutlet = pageParent.parentElement;
|
||||
if (routerOutlet?.hasAttribute('data-overlay-passthrough')) {
|
||||
routerOutlet.style.removeProperty('pointer-events');
|
||||
routerOutlet.removeAttribute('data-overlay-passthrough');
|
||||
}
|
||||
|
||||
// Clear the cached reference
|
||||
this.cachedPageParent = undefined;
|
||||
}
|
||||
|
||||
private sheetOnDismiss() {
|
||||
@@ -862,6 +956,8 @@ export class Modal implements ComponentInterface, OverlayInterface {
|
||||
}
|
||||
this.cleanupViewTransitionListener();
|
||||
this.cleanupParentRemovalObserver();
|
||||
|
||||
this.cleanupChildRoutePassthrough();
|
||||
}
|
||||
this.currentBreakpoint = undefined;
|
||||
this.animation = undefined;
|
||||
|
||||
@@ -181,6 +181,15 @@ html.ios ion-modal.modal-card .ion-page {
|
||||
z-index: $z-index-page-container;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows pointer events to pass through child route page wrappers
|
||||
* when they only contain a sheet modal that permits background interaction.
|
||||
* https://github.com/ionic-team/ionic-framework/issues/30700
|
||||
*/
|
||||
.ion-page.ion-page-overlay-passthrough {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/**
|
||||
* When making custom dialogs, using
|
||||
* ion-content is not required. As a result,
|
||||
|
||||
@@ -38,6 +38,20 @@ let lastId = 0;
|
||||
|
||||
export const activeAnimations = new WeakMap<OverlayInterface, Animation[]>();
|
||||
|
||||
type OverlayWithFocusTrapProps = HTMLIonOverlayElement & {
|
||||
focusTrap?: boolean;
|
||||
showBackdrop?: boolean;
|
||||
backdropBreakpoint?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines if the overlay's backdrop is always blocking (no background interaction).
|
||||
* Returns false if showBackdrop=false or backdropBreakpoint > 0.
|
||||
*/
|
||||
const isBackdropAlwaysBlocking = (el: OverlayWithFocusTrapProps): boolean => {
|
||||
return el.showBackdrop !== false && !((el.backdropBreakpoint ?? 0) > 0);
|
||||
};
|
||||
|
||||
const createController = <Opts extends object, HTMLElm>(tagName: string) => {
|
||||
return {
|
||||
create(options: Opts): Promise<HTMLElm> {
|
||||
@@ -539,11 +553,9 @@ export const present = async <OverlayPresentOptions>(
|
||||
* view container subtree, skip adding aria-hidden/inert there
|
||||
* to avoid disabling the overlay.
|
||||
*/
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
const overlayEl = overlay.el as OverlayWithFocusTrapProps;
|
||||
const shouldTrapFocus = overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false;
|
||||
// Only lock out root content when backdrop is active. Developers relying on showBackdrop=false
|
||||
// expect background interaction to remain enabled.
|
||||
const shouldLockRoot = shouldTrapFocus && overlayEl.showBackdrop !== false;
|
||||
const shouldLockRoot = shouldTrapFocus && isBackdropAlwaysBlocking(overlayEl);
|
||||
|
||||
overlay.presented = true;
|
||||
overlay.willPresent.emit();
|
||||
@@ -680,12 +692,12 @@ export const dismiss = async <OverlayDismissOptions>(
|
||||
* is dismissed.
|
||||
*/
|
||||
const overlaysLockingRoot = presentedOverlays.filter((o) => {
|
||||
const el = o as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && el.showBackdrop !== false;
|
||||
const el = o as OverlayWithFocusTrapProps;
|
||||
return el.tagName !== 'ION-TOAST' && el.focusTrap !== false && isBackdropAlwaysBlocking(el);
|
||||
});
|
||||
const overlayEl = overlay.el as HTMLIonOverlayElement & { focusTrap?: boolean; showBackdrop?: boolean };
|
||||
const overlayEl = overlay.el as OverlayWithFocusTrapProps;
|
||||
const locksRoot =
|
||||
overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && overlayEl.showBackdrop !== false;
|
||||
overlayEl.tagName !== 'ION-TOAST' && overlayEl.focusTrap !== false && isBackdropAlwaysBlocking(overlayEl);
|
||||
|
||||
/**
|
||||
* If this is the last visible overlay that is trapping focus
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,14 @@ export const routes: Routes = [
|
||||
{ path: 'modal', loadComponent: () => import('../modal/modal.component').then(c => c.ModalComponent) },
|
||||
{ path: 'modal-sheet-inline', loadComponent: () => import('../modal-sheet-inline/modal-sheet-inline.component').then(c => c.ModalSheetInlineComponent) },
|
||||
{ path: 'modal-dynamic-wrapper', loadComponent: () => import('../modal-dynamic-wrapper/modal-dynamic-wrapper.component').then(c => c.ModalDynamicWrapperComponent) },
|
||||
{ path: 'modal-child-route', redirectTo: '/standalone/modal-child-route/child', pathMatch: 'full' },
|
||||
{
|
||||
path: 'modal-child-route',
|
||||
loadComponent: () => import('../modal-child-route/modal-child-route-parent.component').then(c => c.ModalChildRouteParentComponent),
|
||||
children: [
|
||||
{ path: 'child', loadComponent: () => import('../modal-child-route/modal-child-route-child.component').then(c => c.ModalChildRouteChildComponent) },
|
||||
]
|
||||
},
|
||||
{ path: 'programmatic-modal', loadComponent: () => import('../programmatic-modal/programmatic-modal.component').then(c => c.ProgrammaticModalComponent) },
|
||||
{ path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(c => c.RouterOutletComponent) },
|
||||
{ path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(c => c.BackButtonComponent) },
|
||||
|
||||
@@ -100,6 +100,11 @@
|
||||
Modal Dynamic Wrapper Test
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item routerLink="/standalone/modal-child-route">
|
||||
<ion-label>
|
||||
Modal Child Route Test
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
<ion-item routerLink="/standalone/programmatic-modal">
|
||||
<ion-label>
|
||||
Programmatic Modal Test
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@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 {}
|
||||
@@ -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--;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -15,6 +15,7 @@ import AlertComponent from './AlertComponent';
|
||||
import LoadingComponent from './LoadingComponent';
|
||||
import ModalComponent from './ModalComponent';
|
||||
import ModalFocusTrap from './ModalFocusTrap';
|
||||
import ModalSheetChildRoute from './ModalSheetChildRoute';
|
||||
import ModalTeleport from './ModalTeleport';
|
||||
import PickerComponent from './PickerComponent';
|
||||
import PopoverComponent from './PopoverComponent';
|
||||
@@ -32,6 +33,7 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
|
||||
<Route path="/overlay-components/loading" component={LoadingComponent} />
|
||||
<Route path="/overlay-components/modal-basic" component={ModalComponent} />
|
||||
<Route path="/overlay-components/modal-focus-trap" component={ModalFocusTrap} />
|
||||
<Route path="/overlay-components/modal-sheet-child-route" component={ModalSheetChildRoute} />
|
||||
<Route path="/overlay-components/modal-teleport" component={ModalTeleport} />
|
||||
<Route path="/overlay-components/picker" component={PickerComponent} />
|
||||
<Route path="/overlay-components/popover" component={PopoverComponent} />
|
||||
@@ -62,6 +64,10 @@ const OverlayHooks: React.FC<OverlayHooksProps> = () => {
|
||||
<IonIcon icon={star} />
|
||||
<IonLabel>Modal Teleport</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="modalSheetChildRoute" href="/overlay-components/modal-sheet-child-route/child">
|
||||
<IonIcon icon={star} />
|
||||
<IonLabel>Sheet Child</IonLabel>
|
||||
</IonTabButton>
|
||||
<IonTabButton tab="picker" href="/overlay-components/picker">
|
||||
<IonIcon icon={logoIonic} />
|
||||
<IonLabel>Picker</IonLabel>
|
||||
|
||||
@@ -5,7 +5,9 @@ describe('IonModal: focusTrap regression', () => {
|
||||
|
||||
it('should allow interacting with background when focusTrap=false', () => {
|
||||
cy.get('#open-non-trapped-modal').click();
|
||||
cy.get('ion-modal').should('be.visible');
|
||||
// Use 'exist' instead of 'be.visible' because the modal has pointer-events: none
|
||||
// to allow background interaction, which Cypress interprets as "covered"
|
||||
cy.get('ion-modal.show-modal').should('exist');
|
||||
|
||||
cy.get('#background-action').click();
|
||||
cy.get('#background-action-count').should('have.text', '1');
|
||||
@@ -13,7 +15,7 @@ describe('IonModal: focusTrap regression', () => {
|
||||
|
||||
it('should prevent interacting with background when focusTrap=true', () => {
|
||||
cy.get('#open-trapped-modal').click();
|
||||
cy.get('ion-modal').should('be.visible');
|
||||
cy.get('ion-modal.show-modal').should('be.visible');
|
||||
|
||||
// Ensure backdrop is active and capturing pointer events
|
||||
cy.get('ion-backdrop').should('exist');
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Tests for sheet modals in child routes with showBackdrop=false.
|
||||
* See https://github.com/ionic-team/ionic-framework/issues/30700
|
||||
*/
|
||||
describe('IonModal: Sheet in Child Route with Nested Routing', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('/overlay-components/modal-sheet-child-route/child');
|
||||
});
|
||||
|
||||
it('should render parent content and child modal', () => {
|
||||
cy.get('#increment-btn').should('exist');
|
||||
cy.get('#decrement-btn').should('exist');
|
||||
cy.get('#background-action-count').should('have.text', '0');
|
||||
cy.get('ion-modal.show-modal').should('exist');
|
||||
cy.get('#modal-content-loaded').should('exist');
|
||||
});
|
||||
|
||||
it('should allow interacting with parent content while modal is open in child route', () => {
|
||||
// Wait for modal to be presented
|
||||
cy.get('ion-modal.show-modal').should('exist');
|
||||
|
||||
// Click the increment button in the parent content
|
||||
cy.get('#increment-btn').click();
|
||||
cy.get('#background-action-count').should('have.text', '1');
|
||||
});
|
||||
|
||||
it('should allow multiple interactions with parent content while modal is open', () => {
|
||||
cy.get('ion-modal.show-modal').should('exist');
|
||||
|
||||
cy.get('#increment-btn').click();
|
||||
cy.get('#increment-btn').click();
|
||||
cy.get('#background-action-count').should('have.text', '2');
|
||||
|
||||
cy.get('#decrement-btn').click();
|
||||
cy.get('#background-action-count').should('have.text', '1');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user