Scroll lock utility
This commit is contained in:
parent
7e201f6af6
commit
18f4869b20
@ -0,0 +1,37 @@
|
|||||||
|
// Global lock counter so nested modals don't prematurely restore scroll
|
||||||
|
let lockCount = 0;
|
||||||
|
let originalOverflow = "";
|
||||||
|
|
||||||
|
export interface ScrollLock {
|
||||||
|
lock: () => void;
|
||||||
|
unlock: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevents body scroll. Uses a reference count so nested overlays
|
||||||
|
* (e.g. a dialog inside a drawer) don't prematurely restore scrolling.
|
||||||
|
*/
|
||||||
|
export function createScrollLock(): ScrollLock {
|
||||||
|
let locked = false;
|
||||||
|
|
||||||
|
return {
|
||||||
|
lock() {
|
||||||
|
if (locked) return;
|
||||||
|
locked = true;
|
||||||
|
if (lockCount === 0) {
|
||||||
|
originalOverflow = document.body.style.overflow;
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
}
|
||||||
|
lockCount++;
|
||||||
|
},
|
||||||
|
unlock() {
|
||||||
|
if (!locked) return;
|
||||||
|
locked = false;
|
||||||
|
lockCount = Math.max(0, lockCount - 1);
|
||||||
|
if (lockCount === 0) {
|
||||||
|
document.body.style.overflow = originalOverflow;
|
||||||
|
originalOverflow = "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
2
packages/core/src/utilities/scroll-lock/index.ts
Normal file
2
packages/core/src/utilities/scroll-lock/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { createScrollLock } from "./create-scroll-lock";
|
||||||
|
export type { ScrollLock } from "./create-scroll-lock";
|
||||||
45
packages/core/tests/utilities/scroll-lock.test.ts
Normal file
45
packages/core/tests/utilities/scroll-lock.test.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { createRoot } from "solid-js";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { createScrollLock } from "../../src/utilities/scroll-lock/create-scroll-lock";
|
||||||
|
|
||||||
|
describe("createScrollLock", () => {
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
document.body.style.paddingRight = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets overflow hidden on body when locked", () => {
|
||||||
|
createRoot((dispose) => {
|
||||||
|
const lock = createScrollLock();
|
||||||
|
lock.lock();
|
||||||
|
expect(document.body.style.overflow).toBe("hidden");
|
||||||
|
lock.unlock();
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restores overflow on unlock", () => {
|
||||||
|
document.body.style.overflow = "auto";
|
||||||
|
createRoot((dispose) => {
|
||||||
|
const lock = createScrollLock();
|
||||||
|
lock.lock();
|
||||||
|
lock.unlock();
|
||||||
|
expect(document.body.style.overflow).toBe("auto");
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple locks — only unlocks when all release", () => {
|
||||||
|
createRoot((dispose) => {
|
||||||
|
const lockA = createScrollLock();
|
||||||
|
const lockB = createScrollLock();
|
||||||
|
lockA.lock();
|
||||||
|
lockB.lock();
|
||||||
|
lockA.unlock();
|
||||||
|
expect(document.body.style.overflow).toBe("hidden"); // still locked by B
|
||||||
|
lockB.unlock();
|
||||||
|
expect(document.body.style.overflow).toBe(""); // now unlocked
|
||||||
|
dispose();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user