// Body scroll-lock for open dialogs/sheets. While any locker is active, the // document body can't scroll behind the overlay. Ref-counted so that nested or // stacked dialogs (e.g. a ConfirmSheet opened from the LightboxModal) don't let // the first one to close unlock the page out from under the others. Compensates // for the vanished scrollbar width so the layout doesn't jump on lock. import type { ActionReturn } from 'svelte/action'; let lockCount = 0; let savedOverflow = ''; let savedPaddingRight = ''; function lock() { if (typeof document === 'undefined') return; if (lockCount === 0) { const body = document.body; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; savedOverflow = body.style.overflow; savedPaddingRight = body.style.paddingRight; body.style.overflow = 'hidden'; if (scrollbarWidth > 0) { const current = parseFloat(getComputedStyle(body).paddingRight) || 0; body.style.paddingRight = `${current + scrollbarWidth}px`; } } lockCount++; } function unlock() { if (typeof document === 'undefined') return; lockCount = Math.max(0, lockCount - 1); if (lockCount === 0) { document.body.style.overflow = savedOverflow; document.body.style.paddingRight = savedPaddingRight; } } /** * Locks body scroll for the lifetime of the node. Mount the node only while the * dialog is open (e.g. inside `{#if open}` or alongside a `class:` toggle) so the * action's create/destroy line up with open/close. */ export function scrollLock(_node: HTMLElement): ActionReturn { lock(); return { destroy() { unlock(); } }; }