From 75b007b5021694dc9c45270cb604daa4acb1334b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Duran?= Date: Thu, 18 Jun 2026 05:19:31 +0200 Subject: [PATCH] Lightbox: Stop slideshow on sphere gestures --- frontend/src/component/lightbox.vue | 28 ++++++++++++++++--- .../vitest/component/lightbox.basic.test.js | 27 ++++++++++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/frontend/src/component/lightbox.vue b/frontend/src/component/lightbox.vue index 046d137e0..02ba0a496 100644 --- a/frontend/src/component/lightbox.vue +++ b/frontend/src/component/lightbox.vue @@ -757,11 +757,17 @@ export default { // reveals the prev/next arrows and top bar on touch devices. To restore that, a short // press without a pan toggles the controls here. Only touch/pen taps do so — mouse // users still get the controls via mousemove, so desktop behavior is unchanged. + // + // Grabbing or wheel-zooming the sphere also stops a running slideshow, matching flat + // slides (whose pointer events reach captureDialogPointerDown — this container swallows + // them first). When a tap is what paused it, the tap-toggle is skipped so the controls + // revealed by the pause stay visible and the user lands on the paused view. trapSphereGestures(el) { const stop = (e) => e.stopPropagation(); let tapX = 0; let tapY = 0; let tapping = false; + let pausedByGesture = false; el.addEventListener( "pointerdown", (e) => { @@ -769,6 +775,10 @@ export default { tapping = e.pointerType !== "mouse"; tapX = e.clientX; tapY = e.clientY; + pausedByGesture = this.slideshow.active; + if (pausedByGesture) { + this.pauseSlideshow(); + } }, { capture: false } ); @@ -776,10 +786,11 @@ export default { "pointerup", (e) => { e.stopPropagation(); - if (tapping && Math.abs(e.clientX - tapX) < SPHERE_TAP_SLOP && Math.abs(e.clientY - tapY) < SPHERE_TAP_SLOP) { + if (tapping && !pausedByGesture && Math.abs(e.clientX - tapX) < SPHERE_TAP_SLOP && Math.abs(e.clientY - tapY) < SPHERE_TAP_SLOP) { this.toggleControls(); } tapping = false; + pausedByGesture = false; }, { capture: false } ); @@ -788,12 +799,21 @@ export default { (e) => { e.stopPropagation(); tapping = false; + pausedByGesture = false; + }, + { capture: false } + ); + el.addEventListener("pointermove", stop, { capture: false }); + el.addEventListener( + "wheel", + (e) => { + e.stopPropagation(); + if (this.slideshow.active) { + this.pauseSlideshow(); + } }, { capture: false } ); - ["pointermove", "wheel"].forEach((type) => { - el.addEventListener(type, stop, { capture: false }); - }); }, // setSphereClass toggles the `pswp--sphere` marker on the PhotoSwipe root element so // CSS can keep the prev/next arrows reachable on touch devices for 360° slides, where diff --git a/frontend/tests/vitest/component/lightbox.basic.test.js b/frontend/tests/vitest/component/lightbox.basic.test.js index 2ffb0d606..3ddf73d24 100644 --- a/frontend/tests/vitest/component/lightbox.basic.test.js +++ b/frontend/tests/vitest/component/lightbox.basic.test.js @@ -1404,6 +1404,8 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => { const trap = () => { const wrapper = mountLightbox(); wrapper.vm.toggleControls = vi.fn(); + wrapper.vm.pauseSlideshow = vi.fn(); + wrapper.vm.slideshow = { active: false }; const registered = []; const el = { addEventListener: (type, handler, opts) => registered.push({ type, handler, opts }) }; wrapper.vm.$options.methods.trapSphereGestures.call(wrapper.vm, el); @@ -1453,6 +1455,31 @@ describe("PLightbox (low-mock, jsdom-friendly)", () => { fire("pointerup", { pointerType: "touch", clientX: 100, clientY: 100 }); expect(vm.toggleControls).not.toHaveBeenCalled(); }); + it("pauses an active slideshow when the sphere is grabbed", () => { + const { vm, fire } = trap(); + vm.slideshow.active = true; + fire("pointerdown", { pointerType: "touch", clientX: 100, clientY: 100 }); + expect(vm.pauseSlideshow).toHaveBeenCalledTimes(1); + }); + it("does NOT pause when no slideshow is running", () => { + const { vm, fire } = trap(); + fire("pointerdown", { pointerType: "mouse", clientX: 100, clientY: 100 }); + expect(vm.pauseSlideshow).not.toHaveBeenCalled(); + }); + it("pauses an active slideshow when zooming the sphere with the wheel", () => { + const { vm, fire } = trap(); + vm.slideshow.active = true; + fire("wheel", {}); + expect(vm.pauseSlideshow).toHaveBeenCalledTimes(1); + }); + it("keeps the controls shown when a touch tap pauses the slideshow", () => { + const { vm, fire } = trap(); + vm.slideshow.active = true; + fire("pointerdown", { pointerType: "touch", clientX: 100, clientY: 100 }); + fire("pointerup", { pointerType: "touch", clientX: 102, clientY: 101 }); + expect(vm.pauseSlideshow).toHaveBeenCalledTimes(1); + expect(vm.toggleControls).not.toHaveBeenCalled(); + }); }); // setSphereClass marks the PhotoSwipe root so CSS can keep the prev/next arrows