@lobehub/ui
Version:
Lobe UI is an open-source UI component library for building AIGC web apps
144 lines (143 loc) • 4.32 kB
JavaScript
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