diff --git a/CHANGELOG.md b/CHANGELOG.md index 59418196b5b..44039ae1102 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + + +### Bug Fixes + +* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700) +* **modal:** prevent browser hang when using ModalController in Angular ([#30845](https://github.com/ionic-team/ionic-framework/issues/30845)) ([b164516](https://github.com/ionic-team/ionic-framework/commit/b1645168a7fb9378dc39a081c207b2de0e180089)) +* **popover:** recalculate the content dimensions after the header has fully loaded ([#30853](https://github.com/ionic-team/ionic-framework/issues/30853)) ([99dcf38](https://github.com/ionic-team/ionic-framework/commit/99dcf3810a0c32416996d1e992ddf63359965cfc)) +* **select, action-sheet:** use radio role for options ([#30769](https://github.com/ionic-team/ionic-framework/issues/30769)) ([1c89cf0](https://github.com/ionic-team/ionic-framework/commit/1c89cf06ac959f9c9a35a66f811227c244d3198b)) + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d1a86679387..3edf5cd89c3 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -3,6 +3,20 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + + +### Bug Fixes + +* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700) +* **modal:** prevent browser hang when using ModalController in Angular ([#30845](https://github.com/ionic-team/ionic-framework/issues/30845)) ([b164516](https://github.com/ionic-team/ionic-framework/commit/b1645168a7fb9378dc39a081c207b2de0e180089)) +* **popover:** recalculate the content dimensions after the header has fully loaded ([#30853](https://github.com/ionic-team/ionic-framework/issues/30853)) ([99dcf38](https://github.com/ionic-team/ionic-framework/commit/99dcf3810a0c32416996d1e992ddf63359965cfc)) +* **select, action-sheet:** use radio role for options ([#30769](https://github.com/ionic-team/ionic-framework/issues/30769)) ([1c89cf0](https://github.com/ionic-team/ionic-framework/commit/1c89cf06ac959f9c9a35a66f811227c244d3198b)) + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) diff --git a/core/package-lock.json b/core/package-lock.json index b4f227b4c43..362a93bb797 100644 --- a/core/package-lock.json +++ b/core/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ionic/core", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/core", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", @@ -50,6 +50,9 @@ "serve": "^14.0.1", "stylelint": "^13.13.1", "stylelint-order": "^4.1.0" + }, + "engines": { + "node": "24.x" } }, "custom-rules": { @@ -91,7 +94,6 @@ "version": "7.16.12", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -629,7 +631,6 @@ "version": "7.4.4", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -861,7 +862,6 @@ "version": "4.33.0", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "4.33.0", "@typescript-eslint/types": "4.33.0", @@ -1799,7 +1799,6 @@ "node_modules/@stencil/core": { "version": "4.38.0", "license": "MIT", - "peer": true, "bin": { "stencil": "bin/stencil" }, @@ -2224,7 +2223,6 @@ "version": "6.7.2", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "6.7.2", "@typescript-eslint/types": "6.7.2", @@ -2450,6 +2448,7 @@ "integrity": "sha512-vay5/oQJdsNHmliWoZfHPoVZZRmnSWhug0BYT34njkYTPqClh3DNWLkZNJBVSjsNMrg0CCrBfoKkjZQPM/QVUw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.5", "@vue/shared": "3.5.25", @@ -2464,6 +2463,7 @@ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "engines": { "node": ">=0.12" }, @@ -2476,7 +2476,8 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@vue/compiler-dom": { "version": "3.5.25", @@ -2484,6 +2485,7 @@ "integrity": "sha512-4We0OAcMZsKgYoGlMjzYvaoErltdFI2/25wqanuTu+S4gismOTRTBPi4IASOjxWdzIwrYSjnqONfKvuqkXzE2Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-core": "3.5.25", "@vue/shared": "3.5.25" @@ -2495,6 +2497,7 @@ "integrity": "sha512-PUgKp2rn8fFsI++lF2sO7gwO2d9Yj57Utr5yEsDf3GNaQcowCLKL7sf+LvVFvtJDXUp/03+dC6f2+LCv5aK1ag==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.28.5", "@vue/compiler-core": "3.5.25", @@ -2512,7 +2515,8 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@vue/compiler-sfc/node_modules/postcss": { "version": "8.5.6", @@ -2534,6 +2538,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -2549,6 +2554,7 @@ "integrity": "sha512-ritPSKLBcParnsKYi+GNtbdbrIE1mtuFEJ4U1sWeuOMlIziK5GtOL85t5RhsNy4uWIXPgk+OUdpnXiTdzn8o3A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.25", "@vue/shared": "3.5.25" @@ -2560,6 +2566,7 @@ "integrity": "sha512-5xfAypCQepv4Jog1U4zn8cZIcbKKFka3AgWHEFQeK65OW+Ys4XybP6z2kKgws4YB43KGpqp5D/K3go2UPPunLA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/shared": "3.5.25" } @@ -2570,6 +2577,7 @@ "integrity": "sha512-Z751v203YWwYzy460bzsYQISDfPjHTl+6Zzwo/a3CsAf+0ccEjQ8c+0CdX1WsumRTHeywvyUFtW6KvNukT/smA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.25", "@vue/shared": "3.5.25" @@ -2581,6 +2589,7 @@ "integrity": "sha512-a4WrkYFbb19i9pjkz38zJBg8wa/rboNERq3+hRRb0dHiJh13c+6kAbgqCPfMaJ2gg4weWD3APZswASOfmKwamA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/reactivity": "3.5.25", "@vue/runtime-core": "3.5.25", @@ -2594,6 +2603,7 @@ "integrity": "sha512-UJaXR54vMG61i8XNIzTSf2Q7MOqZHpp8+x3XLGtE3+fL+nQd+k7O5+X3D/uWrnQXOdMw5VPih+Uremcw+u1woQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.25", "@vue/shared": "3.5.25" @@ -2607,7 +2617,8 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.25.tgz", "integrity": "sha512-AbOPdQQnAnzs58H2FrrDxYj/TJfmeS2jdfEEhgiKINy+bnOANmVizIEgq1r+C5zsbs6l1CCQxtcj71rwNQ4jWg==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@zeit/schemas": { "version": "2.21.0", @@ -2630,7 +2641,6 @@ "version": "7.4.0", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3791,7 +3801,8 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/debug": { "version": "2.6.9", @@ -4085,7 +4096,6 @@ "version": "7.32.0", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.4.3", @@ -7281,6 +7291,7 @@ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } @@ -7602,6 +7613,7 @@ } ], "license": "MIT", + "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -7956,7 +7968,6 @@ "integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "playwright-core": "cli.js" }, @@ -7968,7 +7979,6 @@ "version": "7.0.35", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^2.4.2", "source-map": "^0.6.1", @@ -8074,7 +8084,6 @@ "version": "0.36.2", "dev": true, "license": "MIT", - "peer": true, "peerDependencies": { "postcss": ">=5.0.0" } @@ -8123,7 +8132,6 @@ "version": "2.6.1", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -8481,7 +8489,6 @@ "version": "2.35.1", "dev": true, "license": "MIT", - "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -8703,6 +8710,7 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", + "peer": true, "engines": { "node": ">=0.10.0" } diff --git a/core/package.json b/core/package.json index b0f0db7b189..11f78142274 100644 --- a/core/package.json +++ b/core/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/core", - "version": "8.7.11", + "version": "8.7.12", "description": "Base components for Ionic", "engines": { "node": "24.x" diff --git a/core/src/components/modal/modal.tsx b/core/src/components/modal/modal.tsx index b1a494430d0..174ac2f9d8a 100644 --- a/core/src/components/modal/modal.tsx +++ b/core/src/components/modal/modal.tsx @@ -71,7 +71,7 @@ export class Modal implements ComponentInterface, OverlayInterface { private gesture?: Gesture; private coreDelegate: FrameworkDelegate = CoreDelegate(); private sheetTransition?: Promise; - 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; @@ -1183,6 +1279,20 @@ export class Modal implements ComponentInterface, OverlayInterface { return; } + /** + * Don't observe for controller-based modals or when the parent is the + * app root (document.body or ion-app). These parents won't be removed, + * and observing document.body with subtree: true causes performance + * issues with frameworks like Angular during change detection. + */ + if ( + this.hasController || + this.cachedOriginalParent === document.body || + this.cachedOriginalParent.tagName === 'ION-APP' + ) { + return; + } + this.parentRemovalObserver = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.type === 'childList' && mutation.removedNodes.length > 0) { diff --git a/core/src/components/modal/test/basic/modal.e2e.ts b/core/src/components/modal/test/basic/modal.e2e.ts index 325c4b3fbb5..1c9ac92743c 100644 --- a/core/src/components/modal/test/basic/modal.e2e.ts +++ b/core/src/components/modal/test/basic/modal.e2e.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; -import { configs, test, Viewports } from '@utils/test/playwright'; import type { E2EPage } from '@utils/test/playwright'; +import { configs, test, Viewports } from '@utils/test/playwright'; configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { test.describe(title('modal: focus trapping'), () => { @@ -104,6 +104,28 @@ configs().forEach(({ title, screenshot, config }) => { }); configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) => { + test.describe(title('modal: parent removal observer'), () => { + test('should not set up parentRemovalObserver for controller-created modals', async ({ page }, testInfo) => { + testInfo.annotations.push({ + type: 'issue', + description: 'FW-6766', + }); + + await page.goto('/src/components/modal/test/basic', config); + const ionModalDidPresent = await page.spyOnEvent('ionModalDidPresent'); + + await page.click('#basic-modal'); + await ionModalDidPresent.next(); + + const modal = page.locator('ion-modal'); + const hasObserver = await modal.evaluate((el: any) => { + return el.parentRemovalObserver !== undefined; + }); + + expect(hasObserver).toBe(false); + }); + }); + test.describe(title('modal: backdrop'), () => { test.beforeEach(async ({ page }) => { await page.goto('/src/components/modal/test/basic', config); diff --git a/core/src/css/core.scss b/core/src/css/core.scss index cf7560bd348..db694fc6a07 100644 --- a/core/src/css/core.scss +++ b/core/src/css/core.scss @@ -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, diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 5b983158aa5..472a57559d3 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -38,6 +38,20 @@ let lastId = 0; export const activeAnimations = new WeakMap(); +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 = (tagName: string) => { return { create(options: Opts): Promise { @@ -539,11 +553,9 @@ export const present = async ( * 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 ( * 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 diff --git a/lerna.json b/lerna.json index 5134a3baffe..3e932560e2a 100644 --- a/lerna.json +++ b/lerna.json @@ -3,5 +3,5 @@ "core", "packages/*" ], - "version": "8.7.11" + "version": "8.7.12" } \ No newline at end of file diff --git a/packages/angular-server/CHANGELOG.md b/packages/angular-server/CHANGELOG.md index 55109a07286..cdce885d0d8 100644 --- a/packages/angular-server/CHANGELOG.md +++ b/packages/angular-server/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + +**Note:** Version bump only for package @ionic/angular-server + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/angular-server diff --git a/packages/angular-server/package-lock.json b/packages/angular-server/package-lock.json index fdddce185b1..4eee627f8dc 100644 --- a/packages/angular-server/package-lock.json +++ b/packages/angular-server/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular-server", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/angular-server", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.11" + "@ionic/core": "^8.7.12" }, "devDependencies": { "@angular-eslint/eslint-plugin": "^16.0.0", @@ -1031,14 +1031,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/eslint-config": { @@ -7306,9 +7309,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", diff --git a/packages/angular-server/package.json b/packages/angular-server/package.json index 47aafb0182f..b5c6bd7ea15 100644 --- a/packages/angular-server/package.json +++ b/packages/angular-server/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular-server", - "version": "8.7.11", + "version": "8.7.12", "description": "Angular SSR Module for Ionic", "keywords": [ "ionic", @@ -62,6 +62,6 @@ }, "prettier": "@ionic/prettier-config", "dependencies": { - "@ionic/core": "^8.7.11" + "@ionic/core": "^8.7.12" } } diff --git a/packages/angular/CHANGELOG.md b/packages/angular/CHANGELOG.md index af9a74674e4..41c9a25bec4 100644 --- a/packages/angular/CHANGELOG.md +++ b/packages/angular/CHANGELOG.md @@ -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. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + + +### Bug Fixes + +* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700) + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/angular diff --git a/packages/angular/package-lock.json b/packages/angular/package-lock.json index 2f8f71c7358..18d9d9e5f79 100644 --- a/packages/angular/package-lock.json +++ b/packages/angular/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/angular", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/angular", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" @@ -1398,14 +1398,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/eslint-config": { diff --git a/packages/angular/package.json b/packages/angular/package.json index 6dc90b223c4..2c015fd9212 100644 --- a/packages/angular/package.json +++ b/packages/angular/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/angular", - "version": "8.7.11", + "version": "8.7.12", "description": "Angular specific wrappers for @ionic/core", "keywords": [ "ionic", @@ -48,7 +48,7 @@ } }, "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "ionicons": "^8.0.13", "jsonc-parser": "^3.0.0", "tslib": "^2.3.0" diff --git a/packages/angular/test/base/e2e/src/standalone/modal-child-route.spec.ts b/packages/angular/test/base/e2e/src/standalone/modal-child-route.spec.ts new file mode 100644 index 00000000000..d479599bccb --- /dev/null +++ b/packages/angular/test/base/e2e/src/standalone/modal-child-route.spec.ts @@ -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'); + }); +}); diff --git a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts index 007743f905f..667ef672e8b 100644 --- a/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts +++ b/packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts @@ -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) }, diff --git a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html index 7ac9c619180..6dbad643eb2 100644 --- a/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html +++ b/packages/angular/test/base/src/app/standalone/home-page/home-page.component.html @@ -100,6 +100,11 @@ Modal Dynamic Wrapper Test + + + Modal Child Route Test + + Programmatic Modal Test diff --git a/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-child.component.ts b/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-child.component.ts new file mode 100644 index 00000000000..6fa573fa197 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-child.component.ts @@ -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: ` + + + + + Modal in Child Route + + + + + + + + `, + standalone: true, + imports: [CommonModule, IonContent, IonHeader, IonModal, IonTitle, IonToolbar], +}) +export class ModalChildRouteChildComponent {} diff --git a/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-parent.component.ts b/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-parent.component.ts new file mode 100644 index 00000000000..fdd5465ad11 --- /dev/null +++ b/packages/angular/test/base/src/app/standalone/modal-child-route/modal-child-route-parent.component.ts @@ -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: ` + + + Parent Page with Nested Route + + + +
+ - +

{{ count }}

+ + +
+ +
+ `, + standalone: true, + imports: [IonButton, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar], +}) +export class ModalChildRouteParentComponent { + count = 0; + + increment() { + this.count++; + } + + decrement() { + this.count--; + } +} diff --git a/packages/docs/CHANGELOG.md b/packages/docs/CHANGELOG.md index 63638865fae..4cc3c8f7f8a 100644 --- a/packages/docs/CHANGELOG.md +++ b/packages/docs/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + +**Note:** Version bump only for package @ionic/docs + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/docs diff --git a/packages/docs/package-lock.json b/packages/docs/package-lock.json index 3c1391b8cf3..999970780cd 100644 --- a/packages/docs/package-lock.json +++ b/packages/docs/package-lock.json @@ -1,13 +1,13 @@ { "name": "@ionic/docs", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/docs", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT" } } -} \ No newline at end of file +} diff --git a/packages/docs/package.json b/packages/docs/package.json index 018947033c8..0c0f81bbf97 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/docs", - "version": "8.7.11", + "version": "8.7.12", "description": "Pre-packaged API documentation for the Ionic docs.", "main": "core.json", "types": "core.d.ts", diff --git a/packages/react-router/CHANGELOG.md b/packages/react-router/CHANGELOG.md index dcc56dc20c8..5b01d669edf 100644 --- a/packages/react-router/CHANGELOG.md +++ b/packages/react-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + +**Note:** Version bump only for package @ionic/react-router + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/react-router diff --git a/packages/react-router/package-lock.json b/packages/react-router/package-lock.json index ca60000c58b..56b884f97c4 100644 --- a/packages/react-router/package-lock.json +++ b/packages/react-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react-router", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/react-router", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/react": "^8.7.11", + "@ionic/react": "^8.7.12", "tslib": "*" }, "devDependencies": { @@ -238,14 +238,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/eslint-config": { @@ -415,12 +418,12 @@ } }, "node_modules/@ionic/react": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.11.tgz", - "integrity": "sha512-h4j2SVRMgoxZBdr1bluKGrb0xNYEqEDcjHDuHsok669tKH3RnTMfD276zytfhFh3R8gIKWIqxb76NIsW/hfZcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.12.tgz", + "integrity": "sha512-gNm5L++aiwkwJrUFKhcHUUgjqnj9n03gK7UcoL7Oz+271arzmwF/FNd47G85b6PovwiYQXY2CUBvNJ7Nh4qE/A==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.11", + "@ionic/core": "8.7.12", "ionicons": "^8.0.13", "tslib": "*" }, @@ -4175,9 +4178,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", @@ -4281,11 +4284,11 @@ "requires": {} }, "@ionic/react": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.11.tgz", - "integrity": "sha512-h4j2SVRMgoxZBdr1bluKGrb0xNYEqEDcjHDuHsok669tKH3RnTMfD276zytfhFh3R8gIKWIqxb76NIsW/hfZcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/react/-/react-8.7.12.tgz", + "integrity": "sha512-gNm5L++aiwkwJrUFKhcHUUgjqnj9n03gK7UcoL7Oz+271arzmwF/FNd47G85b6PovwiYQXY2CUBvNJ7Nh4qE/A==", "requires": { - "@ionic/core": "8.7.11", + "@ionic/core": "8.7.12", "ionicons": "^8.0.13", "tslib": "*" } diff --git a/packages/react-router/package.json b/packages/react-router/package.json index 6caca04b3dc..7e2f11fc05b 100644 --- a/packages/react-router/package.json +++ b/packages/react-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react-router", - "version": "8.7.11", + "version": "8.7.12", "description": "React Router wrapper for @ionic/react", "keywords": [ "ionic", @@ -36,7 +36,7 @@ "dist/" ], "dependencies": { - "@ionic/react": "^8.7.11", + "@ionic/react": "^8.7.12", "tslib": "*" }, "peerDependencies": { diff --git a/packages/react/CHANGELOG.md b/packages/react/CHANGELOG.md index d0dfb6d60de..550acf8a07c 100644 --- a/packages/react/CHANGELOG.md +++ b/packages/react/CHANGELOG.md @@ -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. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + + +### Bug Fixes + +* **modal:** allow interaction with parent content through sheet modals in child routes ([#30839](https://github.com/ionic-team/ionic-framework/issues/30839)) ([b9e3cf0](https://github.com/ionic-team/ionic-framework/commit/b9e3cf0f5aae79a1f27a07b102c77e51f24825f4)), closes [#30700](https://github.com/ionic-team/ionic-framework/issues/30700) + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/react diff --git a/packages/react/package-lock.json b/packages/react/package-lock.json index 909959cda1b..3f35a39caad 100644 --- a/packages/react/package-lock.json +++ b/packages/react/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/react", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/react", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "ionicons": "^8.0.13", "tslib": "*" }, @@ -736,14 +736,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/eslint-config": { diff --git a/packages/react/package.json b/packages/react/package.json index f9407abde68..bb2bfa74b77 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/react", - "version": "8.7.11", + "version": "8.7.12", "description": "React specific wrapper for @ionic/core", "keywords": [ "ionic", @@ -40,7 +40,7 @@ "css/" ], "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "ionicons": "^8.0.13", "tslib": "*" }, diff --git a/packages/react/test/base/src/pages/overlay-components/ModalSheetChildRoute.tsx b/packages/react/test/base/src/pages/overlay-components/ModalSheetChildRoute.tsx new file mode 100644 index 00000000000..e686bbd00eb --- /dev/null +++ b/packages/react/test/base/src/pages/overlay-components/ModalSheetChildRoute.tsx @@ -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 ( + + + + Parent Page with Nested Route + + + +
+ setCount((c) => c - 1)}> + - + +

{count}

+ setCount((c) => c + 1)}> + + + +
+
+ + + +
+ ); +}; + +const ModalSheetChildRouteChild: React.FC = () => { + return ( + + + + + Modal in Child Route + + + + + + + + ); +}; + +export default ModalSheetChildRouteParent; diff --git a/packages/react/test/base/src/pages/overlay-components/OverlayComponents.tsx b/packages/react/test/base/src/pages/overlay-components/OverlayComponents.tsx index 19aebc9081c..75bf98bb734 100644 --- a/packages/react/test/base/src/pages/overlay-components/OverlayComponents.tsx +++ b/packages/react/test/base/src/pages/overlay-components/OverlayComponents.tsx @@ -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 = () => { + @@ -62,6 +64,10 @@ const OverlayHooks: React.FC = () => { Modal Teleport + + + Sheet Child + Picker diff --git a/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalFocusTrap.cy.ts b/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalFocusTrap.cy.ts index 395c642dcc4..78ca7a581e3 100644 --- a/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalFocusTrap.cy.ts +++ b/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalFocusTrap.cy.ts @@ -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'); diff --git a/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalSheetChildRoute.cy.ts b/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalSheetChildRoute.cy.ts new file mode 100644 index 00000000000..ff791f3aade --- /dev/null +++ b/packages/react/test/base/tests/e2e/specs/overlay-components/IonModalSheetChildRoute.cy.ts @@ -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'); + }); +}); diff --git a/packages/vue-router/CHANGELOG.md b/packages/vue-router/CHANGELOG.md index 21cc70cb953..a80b6d0a24f 100644 --- a/packages/vue-router/CHANGELOG.md +++ b/packages/vue-router/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + +**Note:** Version bump only for package @ionic/vue-router + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/vue-router diff --git a/packages/vue-router/package-lock.json b/packages/vue-router/package-lock.json index fa908748990..f7e737b3747 100644 --- a/packages/vue-router/package-lock.json +++ b/packages/vue-router/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue-router", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@ionic/vue-router", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/vue": "^8.7.11" + "@ionic/vue": "^8.7.12" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", @@ -673,14 +673,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/eslint-config": { @@ -865,12 +868,12 @@ } }, "node_modules/@ionic/vue": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.11.tgz", - "integrity": "sha512-HDEcjhxWfimVQxvXfghrqlAWpXnJvcUDTIVE2Mvq8ul+s7gL/OZCpXTAODJOfFCBAGJ0o9QXyC7OPjyN4UbO8Q==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.12.tgz", + "integrity": "sha512-fH/acQ7dYYd1XY1HFqKf0Th6klbfNNzhlnYUf9kNQpkHqUpMamSzA8TWDOV5f4PNKYq6X4oKAPPeIQA4DOayUQ==", "license": "MIT", "dependencies": { - "@ionic/core": "8.7.11", + "@ionic/core": "8.7.12", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } @@ -8041,9 +8044,9 @@ "dev": true }, "@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "requires": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", @@ -8156,11 +8159,11 @@ "requires": {} }, "@ionic/vue": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.11.tgz", - "integrity": "sha512-HDEcjhxWfimVQxvXfghrqlAWpXnJvcUDTIVE2Mvq8ul+s7gL/OZCpXTAODJOfFCBAGJ0o9QXyC7OPjyN4UbO8Q==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/vue/-/vue-8.7.12.tgz", + "integrity": "sha512-fH/acQ7dYYd1XY1HFqKf0Th6klbfNNzhlnYUf9kNQpkHqUpMamSzA8TWDOV5f4PNKYq6X4oKAPPeIQA4DOayUQ==", "requires": { - "@ionic/core": "8.7.11", + "@ionic/core": "8.7.12", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" } diff --git a/packages/vue-router/package.json b/packages/vue-router/package.json index 2d7e95b40f6..cee8f5e4e54 100644 --- a/packages/vue-router/package.json +++ b/packages/vue-router/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue-router", - "version": "8.7.11", + "version": "8.7.12", "description": "Vue Router integration for @ionic/vue", "scripts": { "test.spec": "jest", @@ -44,7 +44,7 @@ }, "homepage": "https://github.com/ionic-team/ionic-framework#readme", "dependencies": { - "@ionic/vue": "^8.7.11" + "@ionic/vue": "^8.7.12" }, "devDependencies": { "@ionic/eslint-config": "^0.3.0", diff --git a/packages/vue/CHANGELOG.md b/packages/vue/CHANGELOG.md index 2d82d381e83..e00ea2c3773 100644 --- a/packages/vue/CHANGELOG.md +++ b/packages/vue/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [8.7.12](https://github.com/ionic-team/ionic-framework/compare/v8.7.11...v8.7.12) (2025-12-10) + +**Note:** Version bump only for package @ionic/vue + + + + + ## [8.7.11](https://github.com/ionic-team/ionic-framework/compare/v8.7.10...v8.7.11) (2025-11-26) **Note:** Version bump only for package @ionic/vue diff --git a/packages/vue/package-lock.json b/packages/vue/package-lock.json index 1c127c6af94..12314e4f7a6 100644 --- a/packages/vue/package-lock.json +++ b/packages/vue/package-lock.json @@ -1,15 +1,15 @@ { "name": "@ionic/vue", - "version": "8.7.11", + "version": "8.7.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ionic/vue", - "version": "8.7.11", + "version": "8.7.12", "license": "MIT", "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" }, @@ -222,14 +222,17 @@ "dev": true }, "node_modules/@ionic/core": { - "version": "8.7.11", - "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.11.tgz", - "integrity": "sha512-9UX9IeEztWWXymi+xCUMEBnnY+TbaR8crZLOwFnxPUEq4FFWSUCSv5XeHHQBpgZjBO2MJuDGcNv0GCQumIjVcQ==", + "version": "8.7.12", + "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.7.12.tgz", + "integrity": "sha512-+QnytOHsMMDEz45hi/t9AN8ATaWMNZ7jNdx621BGSHi0JkEl1c4NylL3cfYIPJ/78y40ZG5NzprwNiR9sXdswg==", "license": "MIT", "dependencies": { "@stencil/core": "4.38.0", "ionicons": "^8.0.13", "tslib": "^2.1.0" + }, + "engines": { + "node": "24.x" } }, "node_modules/@ionic/core/node_modules/tslib": { diff --git a/packages/vue/package.json b/packages/vue/package.json index b10b5b65a00..d89aa3cf61e 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "@ionic/vue", - "version": "8.7.11", + "version": "8.7.12", "description": "Vue specific wrapper for @ionic/core", "scripts": { "eslint": "eslint src", @@ -68,7 +68,7 @@ "vue-router": "^4.0.16" }, "dependencies": { - "@ionic/core": "^8.7.11", + "@ionic/core": "^8.7.12", "@stencil/vue-output-target": "0.10.7", "ionicons": "^8.0.13" },