From f5b4382fd5728365e4badf39bc1dd0c149b45c2c Mon Sep 17 00:00:00 2001 From: Sean Perkins Date: Tue, 11 Jan 2022 15:13:35 -0500 Subject: [PATCH] fix(overlays): getTop now returns the top-most presented overlay (#24547) Resolves #19111 --- core/src/utils/overlays.ts | 36 ++-- core/src/utils/test/overlays/index.html | 186 ++++++++++++------- core/src/utils/test/overlays/overlays.e2e.ts | 26 ++- 3 files changed, 149 insertions(+), 99 deletions(-) diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index c8ac471231..19426edf9c 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -120,6 +120,8 @@ export const focusFirstDescendant = (ref: Element, overlay: HTMLIonOverlayElemen } }; +const isOverlayHidden = (overlay: Element) => overlay.classList.contains('overlay-hidden'); + const focusLastDescendant = (ref: Element, overlay: HTMLIonOverlayElement) => { const inputs = Array.from(ref.querySelectorAll(focusableQueryString)) as HTMLElement[]; let lastInput = inputs.length > 0 ? inputs[inputs.length - 1] : null; @@ -291,7 +293,7 @@ export const connectListeners = (doc: Document) => { // handle back-button click doc.addEventListener('ionBackButton', ev => { - const lastOverlay = getTopOpenOverlay(doc); + const lastOverlay = getOverlay(doc); if (lastOverlay && lastOverlay.backdropDismiss) { (ev as BackButtonEvent).detail.register(OVERLAY_BACK_BUTTON_PRIORITY, () => { return lastOverlay.dismiss(undefined, BACKDROP); @@ -302,7 +304,7 @@ export const connectListeners = (doc: Document) => { // handle ESC to close overlay doc.addEventListener('keyup', ev => { if (ev.key === 'Escape') { - const lastOverlay = getTopOpenOverlay(doc); + const lastOverlay = getOverlay(doc); if (lastOverlay && lastOverlay.backdropDismiss) { lastOverlay.dismiss(undefined, BACKDROP); } @@ -328,30 +330,14 @@ export const getOverlays = (doc: Document, selector?: string): HTMLIonOverlayEle }; /** - * Gets the top-most/last opened - * overlay that is currently presented. + * Returns an overlay element + * @param doc The document to find the element within. + * @param overlayTag The selector for the overlay, defaults to Ionic overlay components. + * @param id The unique identifier for the overlay instance. + * @returns The overlay element or `undefined` if no overlay element is found. */ -const getTopOpenOverlay = (doc: Document): HTMLIonOverlayElement | undefined => { - const overlays = getOverlays(doc); - for (let i = overlays.length - 1; i >= 0; i--) { - const overlay = overlays[i]; - - /** - * Only consider overlays that - * are presented. Presented overlays - * will not have the .overlay-hidden - * class on the host. - */ - if (!overlay.classList.contains('overlay-hidden')) { - return overlay; - } - } - - return; -} - -export const getOverlay = (doc: Document, overlayTag?: string, id?: string): HTMLIonOverlayElement | undefined => { - const overlays = getOverlays(doc, overlayTag); +const getOverlay = (doc: Document, overlayTag?: string, id?: string): HTMLIonOverlayElement | undefined => { + const overlays = getOverlays(doc, overlayTag).filter(o => !isOverlayHidden(o)); return (id === undefined) ? overlays[overlays.length - 1] : overlays.find(o => o.id === id); diff --git a/core/src/utils/test/overlays/index.html b/core/src/utils/test/overlays/index.html index abba3376a9..63ece09198 100644 --- a/core/src/utils/test/overlays/index.html +++ b/core/src/utils/test/overlays/index.html @@ -1,44 +1,48 @@ - - - Overlays - - - - - - - - - - - Open Modal - - -
- - - Modal - Inline - - - - Create a Modal - Present a Hidden Modal - Create and Present a Modal - Simulate Hardware Back Button - -
-
+ + + Overlays + + + + + + - + return modal; + + }; + + window.Ionic = { + config: { + hardwareBackButton: true + } + } + + + - diff --git a/core/src/utils/test/overlays/overlays.e2e.ts b/core/src/utils/test/overlays/overlays.e2e.ts index 058968722c..293d0ba475 100644 --- a/core/src/utils/test/overlays/overlays.e2e.ts +++ b/core/src/utils/test/overlays/overlays.e2e.ts @@ -1,6 +1,6 @@ import { newE2EPage } from '@stencil/core/testing'; -test('overlays: hardware back button: should dismss a presented overlay', async () => { +test('overlays: hardware back button: should dismiss a presented overlay', async () => { const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' }); const createAndPresentButton = await page.find('#create-and-present'); @@ -24,7 +24,7 @@ test('overlays: hardware back button: should dismss a presented overlay', async await page.waitForSelector('ion-modal', { hidden: true }) }); -test('overlays: hardware back button: should dismss the presented overlay, even though another hidden modal was added last', async () => { +test('overlays: hardware back button: should dismiss the presented overlay, even though another hidden modal was added last', async () => { const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' }); const createAndPresentButton = await page.find('#create-and-present'); @@ -56,7 +56,7 @@ test('overlays: hardware back button: should dismss the presented overlay, even expect(await modals[1].evaluate(node => node.classList.contains('overlay-hidden'))).toEqual(true); }); -test('overlays: Esc: should dismss a presented overlay', async () => { +test('overlays: Esc: should dismiss a presented overlay', async () => { const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' }); const createAndPresentButton = await page.find('#create-and-present'); @@ -78,7 +78,7 @@ test('overlays: Esc: should dismss a presented overlay', async () => { }); -test('overlays: Esc: should dismss the presented overlay, even though another hidden modal was added last', async () => { +test('overlays: Esc: should dismiss the presented overlay, even though another hidden modal was added last', async () => { const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' }); const createAndPresentButton = await page.find('#create-and-present'); @@ -102,3 +102,21 @@ test('overlays: Esc: should dismss the presented overlay, even though another hi await page.waitForSelector('ion-modal#ion-overlay-1', { hidden: true }); }); + +test('overlays: Nested: should dismiss the top overlay', async () => { + const page = await newE2EPage({ url: '/src/utils/test/overlays?ionic:_testing=true' }); + + const createNestedButton = await page.find('#create-nested'); + + await createNestedButton.click(); + + const modal = await page.find('ion-modal'); + expect(modal).not.toBe(null); + + const dismissNestedOverlayButton = await page.find('#dismiss-modal-nested-overlay'); + await dismissNestedOverlayButton.click(); + + const modals = await page.$$('ion-modal'); + expect(modals.length).toEqual(0); + +});