UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

218 lines (217 loc) • 7.31 kB
import { createUseGesture, pinchAction, wheelAction } from "@use-gesture/react"; import * as React from "react"; import { Vec } from "../primitives/Vec.mjs"; import { preventDefault, stopEventPropagation } from "../utils/dom.mjs"; import { isAccelKey } from "../utils/keyboard.mjs"; import { normalizeWheel } from "../utils/normalizeWheel.mjs"; import { useEditor } from "./useEditor.mjs"; const useGesture = createUseGesture([wheelAction, pinchAction]); let lastWheelTime = void 0; const isWheelEndEvent = (time) => { if (lastWheelTime === void 0) { lastWheelTime = time; return false; } if (time - lastWheelTime > 120 && time - lastWheelTime < 160) { lastWheelTime = time; return true; } lastWheelTime = time; return false; }; function useGestureEvents(ref) { const editor = useEditor(); const events = React.useMemo(() => { let pinchState = "not sure"; const onWheel = ({ event }) => { if (!editor.getInstanceState().isFocused) { return; } pinchState = "not sure"; if (isWheelEndEvent(Date.now())) { return; } const editingShapeId = editor.getEditingShapeId(); if (editingShapeId) { const shape = editor.getShape(editingShapeId); if (shape) { const util = editor.getShapeUtil(shape); if (util.canScroll(shape)) { const bounds = editor.getShapePageBounds(editingShapeId); if (bounds?.containsPoint(editor.inputs.currentPagePoint)) { return; } } } } preventDefault(event); stopEventPropagation(event); const delta = normalizeWheel(event); if (delta.x === 0 && delta.y === 0) return; const info = { type: "wheel", name: "wheel", delta, point: new Vec(event.clientX, event.clientY), shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: isAccelKey(event) }; editor.dispatch(info); }; let initDistanceBetweenFingers = 1; let initZoom = 1; let currDistanceBetweenFingers = 0; const initPointBetweenFingers = new Vec(); const prevPointBetweenFingers = new Vec(); const onPinchStart = (gesture) => { const elm = ref.current; pinchState = "not sure"; const { event, origin, da } = gesture; if (event instanceof WheelEvent) return; if (!(event.target === elm || elm?.contains(event.target))) return; prevPointBetweenFingers.x = origin[0]; prevPointBetweenFingers.y = origin[1]; initPointBetweenFingers.x = origin[0]; initPointBetweenFingers.y = origin[1]; initDistanceBetweenFingers = da[0]; initZoom = editor.getZoomLevel(); editor.dispatch({ type: "pinch", name: "pinch_start", point: { x: origin[0], y: origin[1], z: editor.getZoomLevel() }, delta: { x: 0, y: 0 }, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: isAccelKey(event) }); }; const updatePinchState = (isSafariTrackpadPinch) => { if (isSafariTrackpadPinch) { pinchState = "zooming"; } if (pinchState === "zooming") { return; } const touchDistance = Math.abs(currDistanceBetweenFingers - initDistanceBetweenFingers); const originDistance = Vec.Dist(initPointBetweenFingers, prevPointBetweenFingers); switch (pinchState) { case "not sure": { if (touchDistance > 24) { pinchState = "zooming"; } else if (originDistance > 16) { pinchState = "panning"; } break; } case "panning": { if (touchDistance > 64) { pinchState = "zooming"; } break; } } }; const onPinch = (gesture) => { const elm = ref.current; const { event, origin, offset, da } = gesture; if (event instanceof WheelEvent) return; if (!(event.target === elm || elm?.contains(event.target))) return; const isSafariTrackpadPinch = gesture.type === "gesturechange" || gesture.type === "gestureend"; currDistanceBetweenFingers = da[0]; const dx = origin[0] - prevPointBetweenFingers.x; const dy = origin[1] - prevPointBetweenFingers.y; prevPointBetweenFingers.x = origin[0]; prevPointBetweenFingers.y = origin[1]; updatePinchState(isSafariTrackpadPinch); switch (pinchState) { case "zooming": { const currZoom = offset[0] ** editor.getCameraOptions().zoomSpeed; editor.dispatch({ type: "pinch", name: "pinch", point: { x: origin[0], y: origin[1], z: currZoom }, delta: { x: dx, y: dy }, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: isAccelKey(event) }); break; } case "panning": { editor.dispatch({ type: "pinch", name: "pinch", point: { x: origin[0], y: origin[1], z: initZoom }, delta: { x: dx, y: dy }, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: isAccelKey(event) }); break; } } }; const onPinchEnd = (gesture) => { const elm = ref.current; const { event, origin, offset } = gesture; if (event instanceof WheelEvent) return; if (!(event.target === elm || elm?.contains(event.target))) return; const scale = offset[0] ** editor.getCameraOptions().zoomSpeed; pinchState = "not sure"; editor.timers.requestAnimationFrame(() => { editor.dispatch({ type: "pinch", name: "pinch_end", point: { x: origin[0], y: origin[1], z: scale }, delta: { x: origin[0], y: origin[1] }, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: isAccelKey(event) }); }); }; return { onWheel, onPinchStart, onPinchEnd, onPinch }; }, [editor, ref]); useGesture(events, { target: ref, eventOptions: { passive: false }, pinch: { from: () => { const { zoomSpeed } = editor.getCameraOptions(); const level = editor.getZoomLevel() ** (1 / zoomSpeed); return [level, 0]; }, // Return the camera z to use when pinch starts scaleBounds: () => { const baseZoom = editor.getBaseZoom(); const { zoomSteps, zoomSpeed } = editor.getCameraOptions(); const zoomMin = zoomSteps[0] * baseZoom; const zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom; return { max: zoomMax ** (1 / zoomSpeed), min: zoomMin ** (1 / zoomSpeed) }; } } }); } export { useGestureEvents }; //# sourceMappingURL=useGestureEvents.mjs.map