From e3ccb76b033dadf6624ec4782f76bd9142ffa911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 10 Jun 2026 16:53:58 +0200 Subject: [PATCH] Lock page scroll while cookie panel is open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The preference panel is a fixed, backdrop-less card whose only scrollable region is the category list. Wheel events over the header, footer, padding, or at the list edges chained to the host page, so the page scrolled instead of the panel. Treat the open panel as the modal it already claims to be: lock document scroll while state is "panel" (compensating for scrollbar width to avoid layout shift) and add overscroll-behavior: contain to the list. Restore scroll on close and on disconnect so the page is never left frozen. Signed-off-by: Émile Ré --- .../cookie-banner/src/themed-banner/styles.ts | 1 + .../src/themed-banner/themed-banner.ts | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/cookie-banner/src/themed-banner/styles.ts b/packages/cookie-banner/src/themed-banner/styles.ts index a1ff907a8..7584fb4dc 100644 --- a/packages/cookie-banner/src/themed-banner/styles.ts +++ b/packages/cookie-banner/src/themed-banner/styles.ts @@ -106,6 +106,7 @@ export const THEMED_STYLES = ` probo-preference-panel probo-category-list { overflow-y: auto; + overscroll-behavior: contain; flex: 1; min-height: 0; } diff --git a/packages/cookie-banner/src/themed-banner/themed-banner.ts b/packages/cookie-banner/src/themed-banner/themed-banner.ts index 999922512..3d44c18b4 100644 --- a/packages/cookie-banner/src/themed-banner/themed-banner.ts +++ b/packages/cookie-banner/src/themed-banner/themed-banner.ts @@ -21,6 +21,9 @@ import { THEMED_STYLES } from "./styles"; export class ProboThemedBanner extends HTMLElement { private shadow: ShadowRoot; + private scrollLocked = false; + private prevOverflow = ""; + private prevPaddingRight = ""; constructor() { super(); @@ -150,6 +153,11 @@ export class ProboThemedBanner extends HTMLElement { } }); + root.addEventListener("probo-state", (e: Event) => { + const { state } = (e as CustomEvent).detail; + this.setScrollLock(state === "panel"); + }); + this.shadow.querySelector("[data-action=back]")?.addEventListener("click", () => { root.setState(root.client.hasConsent ? "hidden" : "banner"); }); @@ -176,6 +184,31 @@ export class ProboThemedBanner extends HTMLElement { }); } + disconnectedCallback(): void { + this.setScrollLock(false); + } + + private setScrollLock(locked: boolean): void { + if (locked === this.scrollLocked) return; + + const root = document.documentElement; + + if (locked) { + const scrollbarWidth = window.innerWidth - root.clientWidth; + this.prevOverflow = root.style.overflow; + this.prevPaddingRight = root.style.paddingRight; + root.style.overflow = "hidden"; + if (scrollbarWidth > 0) { + root.style.paddingRight = `${scrollbarWidth}px`; + } + } else { + root.style.overflow = this.prevOverflow; + root.style.paddingRight = this.prevPaddingRight; + } + + this.scrollLocked = locked; + } + private applyTexts(config: BannerConfig): void { const texts = config.texts ?? {};