From ab25c71f6f937814c42eb302adc3196498a47a06 Mon Sep 17 00:00:00 2001 From: tobiloeb Date: Thu, 9 Jan 2025 10:37:22 +0100 Subject: [PATCH 1/4] fix(toast): remove backdrop-no-scroll for last toast overlay In case last overlay is a toast, the backdrop-no-scroll css class is removed from body. closes #30112 --- core/src/utils/overlays.ts | 11 +++++-- .../overlays/overlays-scroll-blocking.spec.ts | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 90a4062840e..a6e3fed3260 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -646,6 +646,14 @@ export const dismiss = async ( return false; } + const presentedOverlays = doc !== undefined ? getPresentedOverlays(doc) : []; + + const isLastOverlay = presentedOverlays.length === 1; + + if (isLastOverlay) { + document.body.classList.remove(BACKDROP_NO_SCROLL); + } + /** * For accessibility, toasts lack focus traps and don’t receive * `aria-hidden` on the root element when presented. @@ -657,7 +665,7 @@ export const dismiss = async ( * Therefore, we must remove `aria-hidden` from the root element * when the last non-toast overlay is dismissed. */ - const overlaysNotToast = doc !== undefined ? getPresentedOverlays(doc).filter((o) => o.tagName !== 'ION-TOAST') : []; + const overlaysNotToast = presentedOverlays.filter((o) => o.tagName !== 'ION-TOAST'); const lastOverlayNotToast = overlaysNotToast.length === 1 && overlaysNotToast[0].id === overlay.el.id; @@ -667,7 +675,6 @@ export const dismiss = async ( */ if (lastOverlayNotToast) { setRootAriaHidden(false); - document.body.classList.remove(BACKDROP_NO_SCROLL); } overlay.presented = false; diff --git a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts index 2fcafadd9fd..e4aca09cdf4 100644 --- a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts +++ b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts @@ -1,6 +1,7 @@ import { newSpecPage } from '@stencil/core/testing'; import { Modal } from '../../../components/modal/modal'; +import { Toast } from '../../../components/toast/toast'; describe('overlays: scroll blocking', () => { it('should not block scroll when the overlay is created', async () => { @@ -85,4 +86,34 @@ describe('overlays: scroll blocking', () => { expect(body).not.toHaveClass('backdrop-no-scroll'); }); + + it('should not enable scroll until last toast overlay is dismissed', async () => { + const page = await newSpecPage({ + components: [Toast], + html: ` + + + `, + }); + + const toastOne = page.body.querySelector('#one') as HTMLIonToastElement; + const toastTwo = page.body.querySelector('#two') as HTMLIonToastElement; + const body = page.doc.querySelector('body')!; + + await toastOne.present(); + + expect(body).toHaveClass('backdrop-no-scroll'); + + await toastTwo.present(); + + expect(body).toHaveClass('backdrop-no-scroll'); + + await toastOne.dismiss(); + + expect(body).toHaveClass('backdrop-no-scroll'); + + await toastTwo.dismiss(); + + expect(body).not.toHaveClass('backdrop-no-scroll'); + }); }); From 94a5ec7de2a44d1a36a9ed2bd1c2db9db3eaecc4 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Fri, 21 Mar 2025 14:09:22 -0700 Subject: [PATCH 2/4] refactor(overlays): update code order and add GH issue --- core/src/utils/overlays.ts | 8 ++++---- .../utils/test/overlays/overlays-scroll-blocking.spec.ts | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index a6e3fed3260..3aa97e697e5 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -650,10 +650,6 @@ export const dismiss = async ( const isLastOverlay = presentedOverlays.length === 1; - if (isLastOverlay) { - document.body.classList.remove(BACKDROP_NO_SCROLL); - } - /** * For accessibility, toasts lack focus traps and don’t receive * `aria-hidden` on the root element when presented. @@ -677,6 +673,10 @@ export const dismiss = async ( setRootAriaHidden(false); } + if (isLastOverlay) { + document.body.classList.remove(BACKDROP_NO_SCROLL); + } + overlay.presented = false; try { diff --git a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts index e4aca09cdf4..4e6b269448e 100644 --- a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts +++ b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts @@ -87,6 +87,7 @@ describe('overlays: scroll blocking', () => { expect(body).not.toHaveClass('backdrop-no-scroll'); }); + // Fixes https://github.com/ionic-team/ionic-framework/issues/30112 it('should not enable scroll until last toast overlay is dismissed', async () => { const page = await newSpecPage({ components: [Toast], From 2b9a58fddd9ea22f885abf3bdfb663593d67682d Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 25 Mar 2025 14:32:57 -0700 Subject: [PATCH 3/4] fix(overlays): add class when overlay is not toast --- core/src/utils/overlays.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/core/src/utils/overlays.ts b/core/src/utils/overlays.ts index 3aa97e697e5..4a392ac5370 100644 --- a/core/src/utils/overlays.ts +++ b/core/src/utils/overlays.ts @@ -520,10 +520,9 @@ export const present = async ( */ if (overlay.el.tagName !== 'ION-TOAST') { setRootAriaHidden(true); + document.body.classList.add(BACKDROP_NO_SCROLL); } - document.body.classList.add(BACKDROP_NO_SCROLL); - hideUnderlyingOverlaysFromScreenReaders(overlay.el); hideAnimatingOverlayFromScreenReaders(overlay.el); @@ -648,8 +647,6 @@ export const dismiss = async ( const presentedOverlays = doc !== undefined ? getPresentedOverlays(doc) : []; - const isLastOverlay = presentedOverlays.length === 1; - /** * For accessibility, toasts lack focus traps and don’t receive * `aria-hidden` on the root element when presented. @@ -671,9 +668,6 @@ export const dismiss = async ( */ if (lastOverlayNotToast) { setRootAriaHidden(false); - } - - if (isLastOverlay) { document.body.classList.remove(BACKDROP_NO_SCROLL); } From 6a5ce914a2e16d27ac300614589f982db29733d9 Mon Sep 17 00:00:00 2001 From: Maria Hutt Date: Tue, 25 Mar 2025 16:21:43 -0700 Subject: [PATCH 4/4] test(overlays): verify toast does not get block scroll class --- .../overlays/overlays-scroll-blocking.spec.ts | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts index 4e6b269448e..28bd17dd263 100644 --- a/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts +++ b/core/src/utils/test/overlays/overlays-scroll-blocking.spec.ts @@ -88,32 +88,22 @@ describe('overlays: scroll blocking', () => { }); // Fixes https://github.com/ionic-team/ionic-framework/issues/30112 - it('should not enable scroll until last toast overlay is dismissed', async () => { + it('should not block scroll when the toast overlay is presented', async () => { const page = await newSpecPage({ components: [Toast], html: ` - - + `, }); - const toastOne = page.body.querySelector('#one') as HTMLIonToastElement; - const toastTwo = page.body.querySelector('#two') as HTMLIonToastElement; + const toast = page.body.querySelector('ion-toast')!; const body = page.doc.querySelector('body')!; - await toastOne.present(); + await toast.present(); - expect(body).toHaveClass('backdrop-no-scroll'); - - await toastTwo.present(); - - expect(body).toHaveClass('backdrop-no-scroll'); - - await toastOne.dismiss(); - - expect(body).toHaveClass('backdrop-no-scroll'); + expect(body).not.toHaveClass('backdrop-no-scroll'); - await toastTwo.dismiss(); + await toast.dismiss(); expect(body).not.toHaveClass('backdrop-no-scroll'); });