diff --git a/package.json b/package.json index 8df27a51..61514d45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "impro", - "version": "0.18.153", + "version": "0.18.154", "type": "module", "scripts": { "start": "rm -rf \"${BUILD_DIR:-build}\" && NODE_ENV=development eleventy --serve", diff --git a/src/js/components/lightbox-image-group.js b/src/js/components/lightbox-image-group.js index 7cd4742c..602ab378 100644 --- a/src/js/components/lightbox-image-group.js +++ b/src/js/components/lightbox-image-group.js @@ -1,6 +1,7 @@ import { Component, getChildrenFragment } from "/js/components/component.js"; import { html, render } from "/js/lib/lit-html.js"; import { ImageLoader } from "/js/utils.js"; +import { scrollLocks } from "/js/scrollLocks.js"; import { closeWithAnimation } from "/js/dialogHelpers.js"; import { chevronLeftIconTemplate } from "/js/templates/icons/chevronLeft.template.js"; import { chevronRightIconTemplate } from "/js/templates/icons/chevronRight.template.js"; @@ -18,6 +19,7 @@ class LightboxDialog extends Component { this.currentIndex = this.currentIndex || 0; this.images = this.images || []; this.isOpen = false; + this.scrollLock = null; this._imageLoader = this._imageLoader || new ImageLoader(); this.render(); this._initialized = true; @@ -70,7 +72,8 @@ class LightboxDialog extends Component { this.close(); }} @close=${() => { - document.body.style.overflow = ""; + this.scrollLock?.release(); + this.scrollLock = null; document.removeEventListener("keydown", this.handleKeyDown); this.isOpen = false; this._imageLoader.abort(); @@ -130,12 +133,17 @@ class LightboxDialog extends Component { } open() { - document.body.style.overflow = "hidden"; this.isOpen = true; this.handleKeyDown = this.handleKeyDown.bind(this); document.addEventListener("keydown", this.handleKeyDown); this.render(); this.querySelector(".lightbox").showModal(); + this.scrollLock ??= scrollLocks.acquire({ target: this }); + } + + disconnectedCallback() { + this.scrollLock?.release(); + this.scrollLock = null; } close() { diff --git a/tests/unit/specs/components/lightbox-image-group.test.js b/tests/unit/specs/components/lightbox-image-group.test.js index 0a9989f2..f1ebbd8a 100644 --- a/tests/unit/specs/components/lightbox-image-group.test.js +++ b/tests/unit/specs/components/lightbox-image-group.test.js @@ -1,5 +1,6 @@ -import { describe, it, beforeEach } from "node:test"; +import { describe, it, beforeEach, afterEach, mock } from "node:test"; import assert from "node:assert/strict"; +import { scrollLocks } from "/js/scrollLocks.js"; import "/js/components/lightbox-image-group.js"; describe("lightbox-image-group", () => { @@ -323,21 +324,47 @@ describe("lightbox-image-group", () => { assert(eventFired); }); - it("should set body overflow to hidden when opened", () => { - const element = document.createElement("lightbox-dialog"); - element.images = [createTestImage("test.jpg", "Test")]; - document.body.appendChild(element); - element.open(); - assert.deepEqual(document.body.style.overflow, "hidden"); - }); + describe("scroll locking", () => { + let releaseCalls; - it("should restore body overflow when closed", () => { - const element = document.createElement("lightbox-dialog"); - element.images = [createTestImage("test.jpg", "Test")]; - document.body.appendChild(element); - element.open(); - element.close(); - assert.deepEqual(document.body.style.overflow, ""); + beforeEach(() => { + releaseCalls = 0; + mock.method(scrollLocks, "acquire", () => ({ + release: () => { + releaseCalls += 1; + }, + })); + }); + + afterEach(() => { + mock.restoreAll(); + }); + + function openLightbox() { + const element = document.createElement("lightbox-dialog"); + element.images = [createTestImage("test.jpg", "Test")]; + document.body.appendChild(element); + element.open(); + return element; + } + + it("should acquire a scroll lock when opened", () => { + openLightbox(); + assert.deepEqual(scrollLocks.acquire.mock.callCount(), 1); + }); + + it("should release the scroll lock when closed", () => { + const element = openLightbox(); + element.close(); + assert.deepEqual(releaseCalls, 1); + assert.deepEqual(element.scrollLock, null); + }); + + it("should release the scroll lock when disconnected while open", () => { + const element = openLightbox(); + element.remove(); + assert.deepEqual(releaseCalls, 1); + }); }); });