diff --git a/frontend/src/detail/PreviewZoomOverlay.jsx b/frontend/src/detail/PreviewZoomOverlay.jsx index eeaa901..4388437 100644 --- a/frontend/src/detail/PreviewZoomOverlay.jsx +++ b/frontend/src/detail/PreviewZoomOverlay.jsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef, useState } from 'react'; import { createPortal } from 'react-dom'; import { ArrowLeftIcon, ArrowRightIcon } from '../ui/icons'; +import usePointerTap from '../ui/usePointerTap'; const noop = () => {}; @@ -36,7 +37,6 @@ const PreviewZoomOverlay = ({ const previouslyFocusedRef = useRef(null); const visibilityTimerRef = useRef(null); const displayTimerRef = useRef(null); - const clickTimerRef = useRef(null); useEffect(() => { if (display?.url) { @@ -89,20 +89,12 @@ const PreviewZoomOverlay = ({ if (displayTimerRef.current) { cancelAnimationFrame(displayTimerRef.current); } - if (clickTimerRef.current) { - clearTimeout(clickTimerRef.current); - clickTimerRef.current = null; - } }, []); useEffect(() => { setIsNativeScale(false); setNaturalSize({ width: null, height: null }); focusRef.current = null; - if (clickTimerRef.current) { - clearTimeout(clickTimerRef.current); - clickTimerRef.current = null; - } const scrollEl = scrollRef.current; if (scrollEl) { scrollEl.scrollLeft = 0; @@ -203,13 +195,6 @@ const PreviewZoomOverlay = ({ } }; - if (!renderBackdrop || !activeDisplay?.url || !portalTarget) { - return null; - } - - const effectiveDisplay = activeDisplay; - const navVisible = Boolean(effectiveDisplay?.canGoPrev || effectiveDisplay?.canGoNext); - const toggleZoomAtPoint = (clientX, clientY) => { const img = imageRef.current; setIsNativeScale((current) => { @@ -228,26 +213,27 @@ const PreviewZoomOverlay = ({ }); }; + const pointerTapHandler = usePointerTap({ + delay: CLICK_DELAY_MS, + onSingle: () => { + onClose(); + }, + onDouble: ({ clientX, clientY }) => { + toggleZoomAtPoint(clientX, clientY); + }, + }); + const handleImagePointerDown = (event) => { event.stopPropagation(); - if (event.button && event.button !== 0) { - return; - } - if (event.pointerType === 'touch' && event.isPrimary === false) { - return; - } - if (clickTimerRef.current) { - clearTimeout(clickTimerRef.current); - clickTimerRef.current = null; - toggleZoomAtPoint(event.clientX, event.clientY); - return; - } - - clickTimerRef.current = setTimeout(() => { - clickTimerRef.current = null; - onClose(); - }, CLICK_DELAY_MS); + pointerTapHandler(event); }; + + if (!renderBackdrop || !activeDisplay?.url || !portalTarget) { + return null; + } + + const effectiveDisplay = activeDisplay; + const navVisible = Boolean(effectiveDisplay?.canGoPrev || effectiveDisplay?.canGoNext); const stageClassName = [ 'preview-zoom__stage', ]