UNPKG

@lobehub/ui

Version:

Lobe UI is an open-source UI component library for building AIGC web apps

144 lines (143 loc) 4.32 kB
import { useCallback, useEffect, useRef, useState } from "react"; //#region src/Image/viewer/useViewerGestures.ts const DRAG_THRESHOLD = 4; const DOUBLE_CLICK_WINDOW = 250; const capturePointer = (node, pointerId) => { if (typeof node.setPointerCapture === "function") node.setPointerCapture(pointerId); }; const isTypingTarget = (target) => target instanceof HTMLElement && (target.isContentEditable || [ "INPUT", "SELECT", "TEXTAREA" ].includes(target.tagName)); const useViewerGestures = ({ dragBy, dragEnd, handleDoubleClick, handleWheel, isClean, isZoomed, onClose, reset, zoomIn, zoomOut }) => { const [popup, setPopup] = useState(null); const [panning, setPanning] = useState(false); const imageNodeRef = useRef(null); const dragRef = useRef(null); const movedRef = useRef(false); const pendingCloseRef = useRef(null); const isCleanRef = useRef(isClean); isCleanRef.current = isClean; const cancelPendingClose = useCallback(() => { if (pendingCloseRef.current === null) return; clearTimeout(pendingCloseRef.current); pendingCloseRef.current = null; }, []); useEffect(() => cancelPendingClose, [cancelPendingClose]); const imageRef = useCallback((node) => { imageNodeRef.current = node; }, []); useEffect(() => { if (!popup) return; const listener = (event) => handleWheel(event); popup.addEventListener("wheel", listener, { passive: false }); return () => popup.removeEventListener("wheel", listener); }, [handleWheel, popup]); useEffect(() => { const listener = (event) => { if (event.altKey || event.ctrlKey || event.metaKey || event.defaultPrevented) return; if (isTypingTarget(event.target)) return; switch (event.key) { case "+": case "=": zoomIn(); break; case "-": zoomOut(); break; case "0": reset(); break; default: return; } event.preventDefault(); }; document.addEventListener("keydown", listener); return () => document.removeEventListener("keydown", listener); }, [ reset, zoomIn, zoomOut ]); const onPointerDown = useCallback((event) => { if (dragRef.current?.panning) return; if (event.button !== 0) return; cancelPendingClose(); const start = { x: event.clientX, y: event.clientY }; const panEligible = isZoomed && event.target === imageNodeRef.current; movedRef.current = false; dragRef.current = { id: event.pointerId, last: start, panning: panEligible, start }; if (!panEligible) return; capturePointer(event.target, event.pointerId); setPanning(true); }, [cancelPendingClose, isZoomed]); const onPointerMove = useCallback((event) => { const drag = dragRef.current; if (!drag || drag.id !== event.pointerId) return; const point = { x: event.clientX, y: event.clientY }; if (Math.hypot(point.x - drag.start.x, point.y - drag.start.y) > DRAG_THRESHOLD) movedRef.current = true; if (drag.panning) dragBy({ x: point.x - drag.last.x, y: point.y - drag.last.y }); drag.last = point; }, [dragBy]); const onPointerFinish = useCallback((event) => { const drag = dragRef.current; if (!drag || drag.id !== event.pointerId) return; dragRef.current = null; if (!drag.panning) return; setPanning(false); dragEnd(); }, [dragEnd]); const onSurfaceClick = useCallback(() => { if (movedRef.current) return; cancelPendingClose(); onClose(); }, [cancelPendingClose, onClose]); const onImageClick = useCallback((event) => { event.stopPropagation(); cancelPendingClose(); if (movedRef.current || !isCleanRef.current) return; pendingCloseRef.current = setTimeout(() => { pendingCloseRef.current = null; if (!isCleanRef.current) return; onClose(); }, DOUBLE_CLICK_WINDOW); }, [cancelPendingClose, onClose]); const onImageDoubleClick = useCallback((event) => { event.stopPropagation(); cancelPendingClose(); handleDoubleClick({ x: event.clientX, y: event.clientY }); }, [cancelPendingClose, handleDoubleClick]); return { cancelPendingClose, cursor: isZoomed ? panning ? "grabbing" : "grab" : "zoom-out", imageRef, onImageClick, onImageDoubleClick, onPointerDown, onPointerFinish, onPointerMove, onSurfaceClick, popupRef: setPopup }; }; //#endregion export { isTypingTarget, useViewerGestures }; //# sourceMappingURL=useViewerGestures.mjs.map