UNPKG

@tldraw/editor

Version:

tldraw infinite canvas SDK (editor).

292 lines (291 loc) • 11.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var useGestureEvents_exports = {}; __export(useGestureEvents_exports, { useGestureEvents: () => useGestureEvents }); module.exports = __toCommonJS(useGestureEvents_exports); var React = __toESM(require("react"), 1); var import_environment = require("../globals/environment"); var import_Vec = require("../primitives/Vec"); var import_dom = require("../utils/dom"); var import_keyboard = require("../utils/keyboard"); var import_normalizeWheel = require("../utils/normalizeWheel"); var import_useEditor = require("./useEditor"); function useGestureEvents(ref) { const editor = (0, import_useEditor.useEditor)(); React.useEffect(() => { const elm = ref.current; if (!elm) return; let pinchState = "not sure"; function onWheel(event) { if (!editor.getInstanceState().isFocused) { return; } pinchState = "not sure"; 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.getCurrentPagePoint())) { return; } } } } (0, import_dom.preventDefault)(event); event.stopPropagation(); const delta = (0, import_normalizeWheel.normalizeWheel)(event); if (delta.x === 0 && delta.y === 0) return; const info = { type: "wheel", name: "wheel", delta, point: new import_Vec.Vec(event.clientX, event.clientY), shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: (0, import_keyboard.isAccelKey)(event) }; editor.dispatch(info); } let initDistanceBetweenFingers = 1; let initZoom = 1; let currDistanceBetweenFingers = 0; const initPointBetweenFingers = new import_Vec.Vec(); const prevPointBetweenFingers = new import_Vec.Vec(); let activeTouches = []; function getScaleBounds() { const baseZoom = editor.getBaseZoom(); const { zoomSteps, zoomSpeed } = editor.getCameraOptions(); const zoomMin = zoomSteps[0] * baseZoom; const zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom; return { min: zoomMin ** (1 / zoomSpeed), max: zoomMax ** (1 / zoomSpeed) }; } function getScaleFrom() { const { zoomSpeed } = editor.getCameraOptions(); return editor.getZoomLevel() ** (1 / zoomSpeed); } let scaleOffset = 1; let initScaleFrom = 1; function updatePinchState(isSafariTrackpadPinch) { if (isSafariTrackpadPinch) { pinchState = "zooming"; } if (pinchState === "zooming") { return; } const touchDistance = Math.abs(currDistanceBetweenFingers - initDistanceBetweenFingers); const originDistance = import_Vec.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; } } } function dispatchPinchEvent(name, origin, delta, zoom, event) { editor.dispatch({ type: "pinch", name, point: { x: origin.x, y: origin.y, z: zoom }, delta, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.metaKey || event.ctrlKey, metaKey: event.metaKey, accelKey: (0, import_keyboard.isAccelKey)(event) }); } function getOriginAndDistance(t0, t1) { const origin = { x: (t0.clientX + t1.clientX) / 2, y: (t0.clientY + t1.clientY) / 2 }; const distance = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY); return { origin, distance }; } function onTouchStart(event) { if (!(event.target === elm || elm?.contains(event.target))) return; activeTouches = Array.from(event.touches); if (activeTouches.length === 2) { pinchState = "not sure"; const { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1]); prevPointBetweenFingers.x = origin.x; prevPointBetweenFingers.y = origin.y; initPointBetweenFingers.x = origin.x; initPointBetweenFingers.y = origin.y; initDistanceBetweenFingers = Math.max(distance, 1); currDistanceBetweenFingers = distance; initZoom = editor.getZoomLevel(); initScaleFrom = getScaleFrom(); scaleOffset = initScaleFrom; dispatchPinchEvent("pinch_start", origin, { x: 0, y: 0 }, editor.getZoomLevel(), event); } } function onTouchMove(event) { activeTouches = Array.from(event.touches); if (activeTouches.length < 2) return; const { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1]); currDistanceBetweenFingers = distance; const dx = origin.x - prevPointBetweenFingers.x; const dy = origin.y - prevPointBetweenFingers.y; prevPointBetweenFingers.x = origin.x; prevPointBetweenFingers.y = origin.y; updatePinchState(false); const bounds = getScaleBounds(); const rawScale = initScaleFrom * (distance / initDistanceBetweenFingers); scaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale)); switch (pinchState) { case "zooming": { const currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed; dispatchPinchEvent("pinch", origin, { x: dx, y: dy }, currZoom, event); break; } case "panning": { dispatchPinchEvent("pinch", origin, { x: dx, y: dy }, initZoom, event); break; } } } function onTouchEnd(event) { const wasPinching = activeTouches.length >= 2; activeTouches = Array.from(event.touches); if (wasPinching && activeTouches.length < 2) { const scale = scaleOffset ** editor.getCameraOptions().zoomSpeed; const origin = { ...prevPointBetweenFingers }; pinchState = "not sure"; editor.timers.requestAnimationFrame(() => { dispatchPinchEvent("pinch_end", origin, { x: origin.x, y: origin.y }, scale, event); }); } } let safariGestureInitialScale = 1; function onGestureStart(event) { const e = event; if (!(e.target === elm || elm?.contains(e.target))) return; (0, import_dom.preventDefault)(e); e.stopPropagation(); pinchState = "not sure"; safariGestureInitialScale = getScaleFrom(); scaleOffset = safariGestureInitialScale; initZoom = editor.getZoomLevel(); prevPointBetweenFingers.x = e.clientX; prevPointBetweenFingers.y = e.clientY; initPointBetweenFingers.x = e.clientX; initPointBetweenFingers.y = e.clientY; initDistanceBetweenFingers = 1; currDistanceBetweenFingers = 1; dispatchPinchEvent( "pinch_start", { x: e.clientX, y: e.clientY }, { x: 0, y: 0 }, editor.getZoomLevel(), e ); } function onGestureChange(event) { const e = event; if (!(e.target === elm || elm?.contains(e.target))) return; (0, import_dom.preventDefault)(e); e.stopPropagation(); const dx = e.clientX - prevPointBetweenFingers.x; const dy = e.clientY - prevPointBetweenFingers.y; prevPointBetweenFingers.x = e.clientX; prevPointBetweenFingers.y = e.clientY; const bounds = getScaleBounds(); const rawScale = safariGestureInitialScale * e.scale; scaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale)); currDistanceBetweenFingers = e.scale * initDistanceBetweenFingers; updatePinchState(true); const currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed; dispatchPinchEvent("pinch", { x: e.clientX, y: e.clientY }, { x: dx, y: dy }, currZoom, e); } function onGestureEnd(event) { const e = event; if (!(e.target === elm || elm?.contains(e.target))) return; (0, import_dom.preventDefault)(e); e.stopPropagation(); const scale = scaleOffset ** editor.getCameraOptions().zoomSpeed; pinchState = "not sure"; editor.timers.requestAnimationFrame(() => { dispatchPinchEvent( "pinch_end", { x: e.clientX, y: e.clientY }, { x: e.clientX, y: e.clientY }, scale, e ); }); } elm.addEventListener("wheel", onWheel, { passive: false }); const useGestureEvents2 = !import_environment.tlenv.isIos && "GestureEvent" in window; if (useGestureEvents2) { elm.addEventListener("gesturestart", onGestureStart); elm.addEventListener("gesturechange", onGestureChange); elm.addEventListener("gestureend", onGestureEnd); } else { elm.addEventListener("touchstart", onTouchStart); elm.addEventListener("touchmove", onTouchMove); elm.addEventListener("touchend", onTouchEnd); elm.addEventListener("touchcancel", onTouchEnd); } return () => { elm.removeEventListener("wheel", onWheel); if (useGestureEvents2) { elm.removeEventListener("gesturestart", onGestureStart); elm.removeEventListener("gesturechange", onGestureChange); elm.removeEventListener("gestureend", onGestureEnd); } else { elm.removeEventListener("touchstart", onTouchStart); elm.removeEventListener("touchmove", onTouchMove); elm.removeEventListener("touchend", onTouchEnd); elm.removeEventListener("touchcancel", onTouchEnd); } }; }, [editor, ref]); } //# sourceMappingURL=useGestureEvents.js.map