@tldraw/editor 4.6.0-canary.668d5a2d75dd → 4.6.0-canary.68eadaa8c321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.js +1 -1
- package/dist-cjs/lib/hooks/useGestureEvents.js +171 -127
- package/dist-cjs/lib/hooks/useGestureEvents.js.map +3 -3
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.mjs +1 -1
- package/dist-esm/lib/hooks/useGestureEvents.mjs +171 -127
- package/dist-esm/lib/hooks/useGestureEvents.mjs.map +3 -3
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/package.json +7 -8
- package/src/lib/hooks/useGestureEvents.ts +240 -168
- package/src/version.ts +3 -3
package/dist-cjs/index.js
CHANGED
|
@@ -376,7 +376,7 @@ var import_LocalIndexedDb = require("./lib/utils/sync/LocalIndexedDb");
|
|
|
376
376
|
var import_uniq = require("./lib/utils/uniq");
|
|
377
377
|
(0, import_utils.registerTldrawLibraryVersion)(
|
|
378
378
|
"@tldraw/editor",
|
|
379
|
-
"4.6.0-canary.
|
|
379
|
+
"4.6.0-canary.68eadaa8c321",
|
|
380
380
|
"cjs"
|
|
381
381
|
);
|
|
382
382
|
//# sourceMappingURL=index.js.map
|
|
@@ -31,39 +31,24 @@ __export(useGestureEvents_exports, {
|
|
|
31
31
|
useGestureEvents: () => useGestureEvents
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(useGestureEvents_exports);
|
|
34
|
-
var import_react = require("@use-gesture/react");
|
|
35
34
|
var React = __toESM(require("react"), 1);
|
|
35
|
+
var import_environment = require("../globals/environment");
|
|
36
36
|
var import_Vec = require("../primitives/Vec");
|
|
37
37
|
var import_dom = require("../utils/dom");
|
|
38
38
|
var import_keyboard = require("../utils/keyboard");
|
|
39
39
|
var import_normalizeWheel = require("../utils/normalizeWheel");
|
|
40
40
|
var import_useEditor = require("./useEditor");
|
|
41
|
-
const useGesture = (0, import_react.createUseGesture)([import_react.wheelAction, import_react.pinchAction]);
|
|
42
|
-
let lastWheelTime = void 0;
|
|
43
|
-
const isWheelEndEvent = (time) => {
|
|
44
|
-
if (lastWheelTime === void 0) {
|
|
45
|
-
lastWheelTime = time;
|
|
46
|
-
return false;
|
|
47
|
-
}
|
|
48
|
-
if (time - lastWheelTime > 120 && time - lastWheelTime < 160) {
|
|
49
|
-
lastWheelTime = time;
|
|
50
|
-
return true;
|
|
51
|
-
}
|
|
52
|
-
lastWheelTime = time;
|
|
53
|
-
return false;
|
|
54
|
-
};
|
|
55
41
|
function useGestureEvents(ref) {
|
|
56
42
|
const editor = (0, import_useEditor.useEditor)();
|
|
57
|
-
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
const elm = ref.current;
|
|
45
|
+
if (!elm) return;
|
|
58
46
|
let pinchState = "not sure";
|
|
59
|
-
|
|
47
|
+
function onWheel(event) {
|
|
60
48
|
if (!editor.getInstanceState().isFocused) {
|
|
61
49
|
return;
|
|
62
50
|
}
|
|
63
51
|
pinchState = "not sure";
|
|
64
|
-
if (isWheelEndEvent(Date.now())) {
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
52
|
const editingShapeId = editor.getEditingShapeId();
|
|
68
53
|
if (editingShapeId) {
|
|
69
54
|
const shape = editor.getShape(editingShapeId);
|
|
@@ -93,37 +78,30 @@ function useGestureEvents(ref) {
|
|
|
93
78
|
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
94
79
|
};
|
|
95
80
|
editor.dispatch(info);
|
|
96
|
-
}
|
|
81
|
+
}
|
|
97
82
|
let initDistanceBetweenFingers = 1;
|
|
98
83
|
let initZoom = 1;
|
|
99
84
|
let currDistanceBetweenFingers = 0;
|
|
100
85
|
const initPointBetweenFingers = new import_Vec.Vec();
|
|
101
86
|
const prevPointBetweenFingers = new import_Vec.Vec();
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
editor.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
altKey: event.altKey,
|
|
121
|
-
ctrlKey: event.metaKey || event.ctrlKey,
|
|
122
|
-
metaKey: event.metaKey,
|
|
123
|
-
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
124
|
-
});
|
|
125
|
-
};
|
|
126
|
-
const updatePinchState = (isSafariTrackpadPinch) => {
|
|
87
|
+
let activeTouches = [];
|
|
88
|
+
function getScaleBounds() {
|
|
89
|
+
const baseZoom = editor.getBaseZoom();
|
|
90
|
+
const { zoomSteps, zoomSpeed } = editor.getCameraOptions();
|
|
91
|
+
const zoomMin = zoomSteps[0] * baseZoom;
|
|
92
|
+
const zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom;
|
|
93
|
+
return {
|
|
94
|
+
min: zoomMin ** (1 / zoomSpeed),
|
|
95
|
+
max: zoomMax ** (1 / zoomSpeed)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function getScaleFrom() {
|
|
99
|
+
const { zoomSpeed } = editor.getCameraOptions();
|
|
100
|
+
return editor.getZoomLevel() ** (1 / zoomSpeed);
|
|
101
|
+
}
|
|
102
|
+
let scaleOffset = 1;
|
|
103
|
+
let initScaleFrom = 1;
|
|
104
|
+
function updatePinchState(isSafariTrackpadPinch) {
|
|
127
105
|
if (isSafariTrackpadPinch) {
|
|
128
106
|
pinchState = "zooming";
|
|
129
107
|
}
|
|
@@ -148,100 +126,166 @@ function useGestureEvents(ref) {
|
|
|
148
126
|
break;
|
|
149
127
|
}
|
|
150
128
|
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
129
|
+
}
|
|
130
|
+
function dispatchPinchEvent(name, origin, delta, zoom, event) {
|
|
131
|
+
editor.dispatch({
|
|
132
|
+
type: "pinch",
|
|
133
|
+
name,
|
|
134
|
+
point: { x: origin.x, y: origin.y, z: zoom },
|
|
135
|
+
delta,
|
|
136
|
+
shiftKey: event.shiftKey,
|
|
137
|
+
altKey: event.altKey,
|
|
138
|
+
ctrlKey: event.metaKey || event.ctrlKey,
|
|
139
|
+
metaKey: event.metaKey,
|
|
140
|
+
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function getOriginAndDistance(t0, t1) {
|
|
144
|
+
const origin = {
|
|
145
|
+
x: (t0.clientX + t1.clientX) / 2,
|
|
146
|
+
y: (t0.clientY + t1.clientY) / 2
|
|
147
|
+
};
|
|
148
|
+
const distance = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY);
|
|
149
|
+
return { origin, distance };
|
|
150
|
+
}
|
|
151
|
+
function onTouchStart(event) {
|
|
156
152
|
if (!(event.target === elm || elm?.contains(event.target))) return;
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
153
|
+
activeTouches = Array.from(event.touches);
|
|
154
|
+
if (activeTouches.length === 2) {
|
|
155
|
+
pinchState = "not sure";
|
|
156
|
+
const { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1]);
|
|
157
|
+
prevPointBetweenFingers.x = origin.x;
|
|
158
|
+
prevPointBetweenFingers.y = origin.y;
|
|
159
|
+
initPointBetweenFingers.x = origin.x;
|
|
160
|
+
initPointBetweenFingers.y = origin.y;
|
|
161
|
+
initDistanceBetweenFingers = Math.max(distance, 1);
|
|
162
|
+
currDistanceBetweenFingers = distance;
|
|
163
|
+
initZoom = editor.getZoomLevel();
|
|
164
|
+
initScaleFrom = getScaleFrom();
|
|
165
|
+
scaleOffset = initScaleFrom;
|
|
166
|
+
dispatchPinchEvent("pinch_start", origin, { x: 0, y: 0 }, editor.getZoomLevel(), event);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function onTouchMove(event) {
|
|
170
|
+
activeTouches = Array.from(event.touches);
|
|
171
|
+
if (activeTouches.length < 2) return;
|
|
172
|
+
const { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1]);
|
|
173
|
+
currDistanceBetweenFingers = distance;
|
|
174
|
+
const dx = origin.x - prevPointBetweenFingers.x;
|
|
175
|
+
const dy = origin.y - prevPointBetweenFingers.y;
|
|
176
|
+
prevPointBetweenFingers.x = origin.x;
|
|
177
|
+
prevPointBetweenFingers.y = origin.y;
|
|
178
|
+
updatePinchState(false);
|
|
179
|
+
const bounds = getScaleBounds();
|
|
180
|
+
const rawScale = initScaleFrom * (distance / initDistanceBetweenFingers);
|
|
181
|
+
scaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale));
|
|
164
182
|
switch (pinchState) {
|
|
165
183
|
case "zooming": {
|
|
166
|
-
const currZoom =
|
|
167
|
-
|
|
168
|
-
type: "pinch",
|
|
169
|
-
name: "pinch",
|
|
170
|
-
point: { x: origin[0], y: origin[1], z: currZoom },
|
|
171
|
-
delta: { x: dx, y: dy },
|
|
172
|
-
shiftKey: event.shiftKey,
|
|
173
|
-
altKey: event.altKey,
|
|
174
|
-
ctrlKey: event.metaKey || event.ctrlKey,
|
|
175
|
-
metaKey: event.metaKey,
|
|
176
|
-
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
177
|
-
});
|
|
184
|
+
const currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed;
|
|
185
|
+
dispatchPinchEvent("pinch", origin, { x: dx, y: dy }, currZoom, event);
|
|
178
186
|
break;
|
|
179
187
|
}
|
|
180
188
|
case "panning": {
|
|
181
|
-
|
|
182
|
-
type: "pinch",
|
|
183
|
-
name: "pinch",
|
|
184
|
-
point: { x: origin[0], y: origin[1], z: initZoom },
|
|
185
|
-
delta: { x: dx, y: dy },
|
|
186
|
-
shiftKey: event.shiftKey,
|
|
187
|
-
altKey: event.altKey,
|
|
188
|
-
ctrlKey: event.metaKey || event.ctrlKey,
|
|
189
|
-
metaKey: event.metaKey,
|
|
190
|
-
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
191
|
-
});
|
|
189
|
+
dispatchPinchEvent("pinch", origin, { x: dx, y: dy }, initZoom, event);
|
|
192
190
|
break;
|
|
193
191
|
}
|
|
194
192
|
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
if (
|
|
200
|
-
|
|
201
|
-
|
|
193
|
+
}
|
|
194
|
+
function onTouchEnd(event) {
|
|
195
|
+
const wasPinching = activeTouches.length >= 2;
|
|
196
|
+
activeTouches = Array.from(event.touches);
|
|
197
|
+
if (wasPinching && activeTouches.length < 2) {
|
|
198
|
+
const scale = scaleOffset ** editor.getCameraOptions().zoomSpeed;
|
|
199
|
+
const origin = { ...prevPointBetweenFingers };
|
|
200
|
+
pinchState = "not sure";
|
|
201
|
+
editor.timers.requestAnimationFrame(() => {
|
|
202
|
+
dispatchPinchEvent("pinch_end", origin, { x: origin.x, y: origin.y }, scale, event);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
let safariGestureInitialScale = 1;
|
|
207
|
+
function onGestureStart(event) {
|
|
208
|
+
const e = event;
|
|
209
|
+
if (!(e.target === elm || elm?.contains(e.target))) return;
|
|
210
|
+
(0, import_dom.preventDefault)(e);
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
pinchState = "not sure";
|
|
213
|
+
safariGestureInitialScale = getScaleFrom();
|
|
214
|
+
scaleOffset = safariGestureInitialScale;
|
|
215
|
+
initZoom = editor.getZoomLevel();
|
|
216
|
+
prevPointBetweenFingers.x = e.clientX;
|
|
217
|
+
prevPointBetweenFingers.y = e.clientY;
|
|
218
|
+
initPointBetweenFingers.x = e.clientX;
|
|
219
|
+
initPointBetweenFingers.y = e.clientY;
|
|
220
|
+
initDistanceBetweenFingers = 1;
|
|
221
|
+
currDistanceBetweenFingers = 1;
|
|
222
|
+
dispatchPinchEvent(
|
|
223
|
+
"pinch_start",
|
|
224
|
+
{ x: e.clientX, y: e.clientY },
|
|
225
|
+
{ x: 0, y: 0 },
|
|
226
|
+
editor.getZoomLevel(),
|
|
227
|
+
e
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
function onGestureChange(event) {
|
|
231
|
+
const e = event;
|
|
232
|
+
if (!(e.target === elm || elm?.contains(e.target))) return;
|
|
233
|
+
(0, import_dom.preventDefault)(e);
|
|
234
|
+
e.stopPropagation();
|
|
235
|
+
const dx = e.clientX - prevPointBetweenFingers.x;
|
|
236
|
+
const dy = e.clientY - prevPointBetweenFingers.y;
|
|
237
|
+
prevPointBetweenFingers.x = e.clientX;
|
|
238
|
+
prevPointBetweenFingers.y = e.clientY;
|
|
239
|
+
const bounds = getScaleBounds();
|
|
240
|
+
const rawScale = safariGestureInitialScale * e.scale;
|
|
241
|
+
scaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale));
|
|
242
|
+
currDistanceBetweenFingers = e.scale * initDistanceBetweenFingers;
|
|
243
|
+
updatePinchState(true);
|
|
244
|
+
const currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed;
|
|
245
|
+
dispatchPinchEvent("pinch", { x: e.clientX, y: e.clientY }, { x: dx, y: dy }, currZoom, e);
|
|
246
|
+
}
|
|
247
|
+
function onGestureEnd(event) {
|
|
248
|
+
const e = event;
|
|
249
|
+
if (!(e.target === elm || elm?.contains(e.target))) return;
|
|
250
|
+
(0, import_dom.preventDefault)(e);
|
|
251
|
+
e.stopPropagation();
|
|
252
|
+
const scale = scaleOffset ** editor.getCameraOptions().zoomSpeed;
|
|
202
253
|
pinchState = "not sure";
|
|
203
254
|
editor.timers.requestAnimationFrame(() => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
ctrlKey: event.metaKey || event.ctrlKey,
|
|
212
|
-
metaKey: event.metaKey,
|
|
213
|
-
accelKey: (0, import_keyboard.isAccelKey)(event)
|
|
214
|
-
});
|
|
255
|
+
dispatchPinchEvent(
|
|
256
|
+
"pinch_end",
|
|
257
|
+
{ x: e.clientX, y: e.clientY },
|
|
258
|
+
{ x: e.clientX, y: e.clientY },
|
|
259
|
+
scale,
|
|
260
|
+
e
|
|
261
|
+
);
|
|
215
262
|
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
263
|
+
}
|
|
264
|
+
elm.addEventListener("wheel", onWheel, { passive: false });
|
|
265
|
+
const useGestureEvents2 = !import_environment.tlenv.isIos && "GestureEvent" in window;
|
|
266
|
+
if (useGestureEvents2) {
|
|
267
|
+
elm.addEventListener("gesturestart", onGestureStart);
|
|
268
|
+
elm.addEventListener("gesturechange", onGestureChange);
|
|
269
|
+
elm.addEventListener("gestureend", onGestureEnd);
|
|
270
|
+
} else {
|
|
271
|
+
elm.addEventListener("touchstart", onTouchStart);
|
|
272
|
+
elm.addEventListener("touchmove", onTouchMove);
|
|
273
|
+
elm.addEventListener("touchend", onTouchEnd);
|
|
274
|
+
elm.addEventListener("touchcancel", onTouchEnd);
|
|
275
|
+
}
|
|
276
|
+
return () => {
|
|
277
|
+
elm.removeEventListener("wheel", onWheel);
|
|
278
|
+
if (useGestureEvents2) {
|
|
279
|
+
elm.removeEventListener("gesturestart", onGestureStart);
|
|
280
|
+
elm.removeEventListener("gesturechange", onGestureChange);
|
|
281
|
+
elm.removeEventListener("gestureend", onGestureEnd);
|
|
282
|
+
} else {
|
|
283
|
+
elm.removeEventListener("touchstart", onTouchStart);
|
|
284
|
+
elm.removeEventListener("touchmove", onTouchMove);
|
|
285
|
+
elm.removeEventListener("touchend", onTouchEnd);
|
|
286
|
+
elm.removeEventListener("touchcancel", onTouchEnd);
|
|
287
|
+
}
|
|
222
288
|
};
|
|
223
289
|
}, [editor, ref]);
|
|
224
|
-
useGesture(events, {
|
|
225
|
-
target: ref,
|
|
226
|
-
eventOptions: { passive: false },
|
|
227
|
-
pinch: {
|
|
228
|
-
from: () => {
|
|
229
|
-
const { zoomSpeed } = editor.getCameraOptions();
|
|
230
|
-
const level = editor.getZoomLevel() ** (1 / zoomSpeed);
|
|
231
|
-
return [level, 0];
|
|
232
|
-
},
|
|
233
|
-
// Return the camera z to use when pinch starts
|
|
234
|
-
scaleBounds: () => {
|
|
235
|
-
const baseZoom = editor.getBaseZoom();
|
|
236
|
-
const { zoomSteps, zoomSpeed } = editor.getCameraOptions();
|
|
237
|
-
const zoomMin = zoomSteps[0] * baseZoom;
|
|
238
|
-
const zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom;
|
|
239
|
-
return {
|
|
240
|
-
max: zoomMax ** (1 / zoomSpeed),
|
|
241
|
-
min: zoomMin ** (1 / zoomSpeed)
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
});
|
|
246
290
|
}
|
|
247
291
|
//# sourceMappingURL=useGestureEvents.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/lib/hooks/useGestureEvents.ts"],
|
|
4
|
-
"sourcesContent": ["import type { AnyHandlerEventTypes, EventTypes, GestureKey, Handler } from '@use-gesture/core/types'\nimport { createUseGesture, pinchAction, wheelAction } from '@use-gesture/react'\nimport * as React from 'react'\nimport { TLWheelEventInfo } from '../editor/types/event-types'\nimport { Vec } from '../primitives/Vec'\nimport { preventDefault } from '../utils/dom'\nimport { isAccelKey } from '../utils/keyboard'\nimport { normalizeWheel } from '../utils/normalizeWheel'\nimport { useEditor } from './useEditor'\n\n/*\n\n# How does pinching work?\n\nThe pinching handler is fired under two circumstances: \n- when a user is on a MacBook trackpad and is ZOOMING with a two-finger pinch\n- when a user is on a touch device and is ZOOMING with a two-finger pinch\n- when a user is on a touch device and is PANNING with two fingers\n\nZooming is much more expensive than panning (because it causes shapes to render), \nso we want to be sure that we don't zoom while two-finger panning. \n\nIn order to do this, we keep track of a \"pinchState\", which is either:\n- \"zooming\"\n- \"panning\"\n- \"not sure\"\n\nIf a user is on a trackpad, the pinchState will be set to \"zooming\". \n\nIf the user is on a touch screen, then we start in the \"not sure\" state and switch back and forth\nbetween \"zooming\", \"panning\", and \"not sure\" based on what the user is doing with their fingers.\n\nIn the \"not sure\" state, we examine whether the user has moved the center of the gesture far enough\nto suggest that they're panning; or else that they've moved their fingers further apart or closer\ntogether enough to suggest that they're zooming. \n\nIn the \"panning\" state, we check whether the user's fingers have moved far enough apart to suggest\nthat they're zooming. If they have, we switch to the \"zooming\" state.\n\nIn the \"zooming\" state, we just stay zooming\u2014it's not YET possible to switch back to panning.\n\ntodo: compare velocities of change in order to determine whether the user has switched back to panning\n*/\n\ntype check<T extends AnyHandlerEventTypes, Key extends GestureKey> = undefined extends T[Key]\n\t? EventTypes[Key]\n\t: T[Key]\ntype PinchHandler = Handler<'pinch', check<EventTypes, 'pinch'>>\n\nconst useGesture = createUseGesture([wheelAction, pinchAction])\n\n/**\n * GOTCHA\n *\n * UseGesture fires a wheel event 140ms after the gesture actually ends, with a momentum-adjusted\n * delta. This creates a messed up interaction where after you stop scrolling suddenly the dang page\n * jumps a tick. why do they do this? you are asking the wrong person. it seems intentional though.\n * anyway we want to ignore that last event, but there's no way to directly detect it so we need to\n * keep track of timestamps. Yes this is awful, I am sorry.\n */\nlet lastWheelTime = undefined as undefined | number\n\nconst isWheelEndEvent = (time: number) => {\n\tif (lastWheelTime === undefined) {\n\t\tlastWheelTime = time\n\t\treturn false\n\t}\n\n\tif (time - lastWheelTime > 120 && time - lastWheelTime < 160) {\n\t\tlastWheelTime = time\n\t\treturn true\n\t}\n\n\tlastWheelTime = time\n\treturn false\n}\n\nexport function useGestureEvents(ref: React.RefObject<HTMLDivElement | null>) {\n\tconst editor = useEditor()\n\n\tconst events = React.useMemo(() => {\n\t\tlet pinchState = 'not sure' as 'not sure' | 'zooming' | 'panning'\n\n\t\tconst onWheel: Handler<'wheel', WheelEvent> = ({ event }) => {\n\t\t\tif (!editor.getInstanceState().isFocused) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpinchState = 'not sure'\n\n\t\t\tif (isWheelEndEvent(Date.now())) {\n\t\t\t\t// ignore wheelEnd events\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Awful tht we need to put this logic here, but basically\n\t\t\t// we don't want to handle the the wheel event (or call prevent\n\t\t\t// default on the evnet) if the user is wheeling over an a shape\n\t\t\t// that is scrollable which they're currently editing.\n\n\t\t\tconst editingShapeId = editor.getEditingShapeId()\n\t\t\tif (editingShapeId) {\n\t\t\t\tconst shape = editor.getShape(editingShapeId)\n\t\t\t\tif (shape) {\n\t\t\t\t\tconst util = editor.getShapeUtil(shape)\n\t\t\t\t\tif (util.canScroll(shape)) {\n\t\t\t\t\t\tconst bounds = editor.getShapePageBounds(editingShapeId)\n\t\t\t\t\t\tif (bounds?.containsPoint(editor.inputs.getCurrentPagePoint())) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpreventDefault(event)\n\t\t\tevent.stopPropagation()\n\t\t\tconst delta = normalizeWheel(event)\n\n\t\t\tif (delta.x === 0 && delta.y === 0) return\n\n\t\t\tconst info: TLWheelEventInfo = {\n\t\t\t\ttype: 'wheel',\n\t\t\t\tname: 'wheel',\n\t\t\t\tdelta,\n\t\t\t\tpoint: new Vec(event.clientX, event.clientY),\n\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\taltKey: event.altKey,\n\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t}\n\n\t\t\teditor.dispatch(info)\n\t\t}\n\n\t\tlet initDistanceBetweenFingers = 1 // the distance between the two fingers when the pinch starts\n\t\tlet initZoom = 1 // the browser's zoom level when the pinch starts\n\t\tlet currDistanceBetweenFingers = 0\n\t\tconst initPointBetweenFingers = new Vec()\n\t\tconst prevPointBetweenFingers = new Vec()\n\n\t\tconst onPinchStart: PinchHandler = (gesture) => {\n\t\t\tconst elm = ref.current\n\t\t\tpinchState = 'not sure'\n\n\t\t\tconst { event, origin, da } = gesture\n\n\t\t\tif (event instanceof WheelEvent) return\n\t\t\tif (!(event.target === elm || elm?.contains(event.target as Node))) return\n\n\t\t\tprevPointBetweenFingers.x = origin[0]\n\t\t\tprevPointBetweenFingers.y = origin[1]\n\t\t\tinitPointBetweenFingers.x = origin[0]\n\t\t\tinitPointBetweenFingers.y = origin[1]\n\t\t\tinitDistanceBetweenFingers = da[0]\n\t\t\tinitZoom = editor.getZoomLevel()\n\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pinch',\n\t\t\t\tname: 'pinch_start',\n\t\t\t\tpoint: { x: origin[0], y: origin[1], z: editor.getZoomLevel() },\n\t\t\t\tdelta: { x: 0, y: 0 },\n\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\taltKey: event.altKey,\n\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t})\n\t\t}\n\n\t\t// let timeout: any\n\t\tconst updatePinchState = (isSafariTrackpadPinch: boolean) => {\n\t\t\tif (isSafariTrackpadPinch) {\n\t\t\t\tpinchState = 'zooming'\n\t\t\t}\n\n\t\t\tif (pinchState === 'zooming') {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Initial: [touch]-------origin-------[touch]\n\t\t\t// Current: [touch]-----------origin----------[touch]\n\t\t\t// |----| |------------|\n\t\t\t// originDistance ^ ^ touchDistance\n\n\t\t\t// How far have the two touch points moved towards or away from eachother?\n\t\t\tconst touchDistance = Math.abs(currDistanceBetweenFingers - initDistanceBetweenFingers)\n\t\t\t// How far has the point between the touches moved?\n\t\t\tconst originDistance = Vec.Dist(initPointBetweenFingers, prevPointBetweenFingers)\n\n\t\t\tswitch (pinchState) {\n\t\t\t\tcase 'not sure': {\n\t\t\t\t\tif (touchDistance > 24) {\n\t\t\t\t\t\tpinchState = 'zooming'\n\t\t\t\t\t} else if (originDistance > 16) {\n\t\t\t\t\t\tpinchState = 'panning'\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'panning': {\n\t\t\t\t\t// Slightly more touch distance needed to go from panning to zooming\n\t\t\t\t\tif (touchDistance > 64) {\n\t\t\t\t\t\tpinchState = 'zooming'\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst onPinch: PinchHandler = (gesture) => {\n\t\t\tconst elm = ref.current\n\t\t\tconst { event, origin, offset, da } = gesture\n\n\t\t\tif (event instanceof WheelEvent) return\n\t\t\tif (!(event.target === elm || elm?.contains(event.target as Node))) return\n\n\t\t\t// In (desktop) Safari, a two finger trackpad pinch will be a \"gesturechange\" event\n\t\t\t// and will have 0 touches; on iOS, a two-finger pinch will be a \"pointermove\" event\n\t\t\t// with two touches.\n\t\t\tconst isSafariTrackpadPinch =\n\t\t\t\tgesture.type === 'gesturechange' || gesture.type === 'gestureend'\n\n\t\t\t// The distance between the two touch points\n\t\t\tcurrDistanceBetweenFingers = da[0]\n\n\t\t\t// Only update the zoom if the pointers are far enough apart;\n\t\t\t// a very small touchDistance means that the user has probably\n\t\t\t// pinched out and their fingers are touching; this produces\n\t\t\t// very unstable zooming behavior.\n\n\t\t\tconst dx = origin[0] - prevPointBetweenFingers.x\n\t\t\tconst dy = origin[1] - prevPointBetweenFingers.y\n\n\t\t\tprevPointBetweenFingers.x = origin[0]\n\t\t\tprevPointBetweenFingers.y = origin[1]\n\n\t\t\tupdatePinchState(isSafariTrackpadPinch)\n\n\t\t\tswitch (pinchState) {\n\t\t\t\tcase 'zooming': {\n\t\t\t\t\tconst currZoom = offset[0] ** editor.getCameraOptions().zoomSpeed\n\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pinch',\n\t\t\t\t\t\tname: 'pinch',\n\t\t\t\t\t\tpoint: { x: origin[0], y: origin[1], z: currZoom },\n\t\t\t\t\t\tdelta: { x: dx, y: dy },\n\t\t\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\t\t\taltKey: event.altKey,\n\t\t\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t\t\t})\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'panning': {\n\t\t\t\t\teditor.dispatch({\n\t\t\t\t\t\ttype: 'pinch',\n\t\t\t\t\t\tname: 'pinch',\n\t\t\t\t\t\tpoint: { x: origin[0], y: origin[1], z: initZoom },\n\t\t\t\t\t\tdelta: { x: dx, y: dy },\n\t\t\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\t\t\taltKey: event.altKey,\n\t\t\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t\t\t})\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst onPinchEnd: PinchHandler = (gesture) => {\n\t\t\tconst elm = ref.current\n\t\t\tconst { event, origin, offset } = gesture\n\n\t\t\tif (event instanceof WheelEvent) return\n\t\t\tif (!(event.target === elm || elm?.contains(event.target as Node))) return\n\n\t\t\tconst scale = offset[0] ** editor.getCameraOptions().zoomSpeed\n\n\t\t\tpinchState = 'not sure'\n\n\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\teditor.dispatch({\n\t\t\t\t\ttype: 'pinch',\n\t\t\t\t\tname: 'pinch_end',\n\t\t\t\t\tpoint: { x: origin[0], y: origin[1], z: scale },\n\t\t\t\t\tdelta: { x: origin[0], y: origin[1] },\n\t\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\t\taltKey: event.altKey,\n\t\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t\t})\n\t\t\t})\n\t\t}\n\n\t\treturn {\n\t\t\tonWheel,\n\t\t\tonPinchStart,\n\t\t\tonPinchEnd,\n\t\t\tonPinch,\n\t\t}\n\t}, [editor, ref])\n\n\tuseGesture(events, {\n\t\ttarget: ref,\n\t\teventOptions: { passive: false },\n\t\tpinch: {\n\t\t\tfrom: () => {\n\t\t\t\tconst { zoomSpeed } = editor.getCameraOptions()\n\t\t\t\tconst level = editor.getZoomLevel() ** (1 / zoomSpeed)\n\t\t\t\treturn [level, 0]\n\t\t\t}, // Return the camera z to use when pinch starts\n\t\t\tscaleBounds: () => {\n\t\t\t\tconst baseZoom = editor.getBaseZoom()\n\t\t\t\tconst { zoomSteps, zoomSpeed } = editor.getCameraOptions()\n\t\t\t\tconst zoomMin = zoomSteps[0] * baseZoom\n\t\t\t\tconst zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom\n\n\t\t\t\treturn {\n\t\t\t\t\tmax: zoomMax ** (1 / zoomSpeed),\n\t\t\t\t\tmin: zoomMin ** (1 / zoomSpeed),\n\t\t\t\t}\n\t\t\t},\n\t\t},\n\t})\n}\n"],
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import * as React from 'react'\nimport { TLWheelEventInfo } from '../editor/types/event-types'\nimport { tlenv } from '../globals/environment'\nimport { Vec } from '../primitives/Vec'\nimport { preventDefault } from '../utils/dom'\nimport { isAccelKey } from '../utils/keyboard'\nimport { normalizeWheel } from '../utils/normalizeWheel'\nimport { useEditor } from './useEditor'\n\n/*\n\n# How does pinching work?\n\nThe pinching handler is fired under two circumstances:\n- when a user is on a MacBook trackpad and is ZOOMING with a two-finger pinch\n- when a user is on a touch device and is ZOOMING with a two-finger pinch\n- when a user is on a touch device and is PANNING with two fingers\n\nZooming is much more expensive than panning (because it causes shapes to render),\nso we want to be sure that we don't zoom while two-finger panning.\n\nIn order to do this, we keep track of a \"pinchState\", which is either:\n- \"zooming\"\n- \"panning\"\n- \"not sure\"\n\nIf a user is on a trackpad, the pinchState will be set to \"zooming\".\n\nIf the user is on a touch screen, then we start in the \"not sure\" state and switch back and forth\nbetween \"zooming\", \"panning\", and \"not sure\" based on what the user is doing with their fingers.\n\nIn the \"not sure\" state, we examine whether the user has moved the center of the gesture far enough\nto suggest that they're panning; or else that they've moved their fingers further apart or closer\ntogether enough to suggest that they're zooming.\n\nIn the \"panning\" state, we check whether the user's fingers have moved far enough apart to suggest\nthat they're zooming. If they have, we switch to the \"zooming\" state.\n\nIn the \"zooming\" state, we just stay zooming\u2014it's not YET possible to switch back to panning.\n\ntodo: compare velocities of change in order to determine whether the user has switched back to panning\n*/\n\n/** Safari's non-standard GestureEvent */\ninterface GestureEvent extends Event {\n\tscale: number\n\trotation: number\n\tclientX: number\n\tclientY: number\n\tshiftKey: boolean\n\taltKey: boolean\n\tmetaKey: boolean\n\tctrlKey: boolean\n}\n\nexport function useGestureEvents(ref: React.RefObject<HTMLDivElement | null>) {\n\tconst editor = useEditor()\n\n\tReact.useEffect(() => {\n\t\tconst elm = ref.current\n\t\tif (!elm) return\n\n\t\tlet pinchState = 'not sure' as 'not sure' | 'zooming' | 'panning'\n\n\t\t// --- Wheel handling ---\n\n\t\tfunction onWheel(event: WheelEvent) {\n\t\t\tif (!editor.getInstanceState().isFocused) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tpinchState = 'not sure'\n\n\t\t\t// Don't handle wheel events over a scrollable editing shape\n\t\t\tconst editingShapeId = editor.getEditingShapeId()\n\t\t\tif (editingShapeId) {\n\t\t\t\tconst shape = editor.getShape(editingShapeId)\n\t\t\t\tif (shape) {\n\t\t\t\t\tconst util = editor.getShapeUtil(shape)\n\t\t\t\t\tif (util.canScroll(shape)) {\n\t\t\t\t\t\tconst bounds = editor.getShapePageBounds(editingShapeId)\n\t\t\t\t\t\tif (bounds?.containsPoint(editor.inputs.getCurrentPagePoint())) {\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpreventDefault(event)\n\t\t\tevent.stopPropagation()\n\t\t\tconst delta = normalizeWheel(event)\n\n\t\t\tif (delta.x === 0 && delta.y === 0) return\n\n\t\t\tconst info: TLWheelEventInfo = {\n\t\t\t\ttype: 'wheel',\n\t\t\t\tname: 'wheel',\n\t\t\t\tdelta,\n\t\t\t\tpoint: new Vec(event.clientX, event.clientY),\n\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\taltKey: event.altKey,\n\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t}\n\n\t\t\teditor.dispatch(info)\n\t\t}\n\n\t\t// --- Touch pinch handling ---\n\n\t\tlet initDistanceBetweenFingers = 1 // the distance between the two fingers when the pinch starts\n\t\tlet initZoom = 1 // the zoom level when the pinch starts\n\t\tlet currDistanceBetweenFingers = 0\n\t\tconst initPointBetweenFingers = new Vec()\n\t\tconst prevPointBetweenFingers = new Vec()\n\n\t\t// Track active touches\n\t\tlet activeTouches: Touch[] = []\n\n\t\tfunction getScaleBounds() {\n\t\t\tconst baseZoom = editor.getBaseZoom()\n\t\t\tconst { zoomSteps, zoomSpeed } = editor.getCameraOptions()\n\t\t\tconst zoomMin = zoomSteps[0] * baseZoom\n\t\t\tconst zoomMax = zoomSteps[zoomSteps.length - 1] * baseZoom\n\t\t\treturn {\n\t\t\t\tmin: zoomMin ** (1 / zoomSpeed),\n\t\t\t\tmax: zoomMax ** (1 / zoomSpeed),\n\t\t\t}\n\t\t}\n\n\t\tfunction getScaleFrom() {\n\t\t\tconst { zoomSpeed } = editor.getCameraOptions()\n\t\t\treturn editor.getZoomLevel() ** (1 / zoomSpeed)\n\t\t}\n\n\t\t// Accumulated scale offset, clamped to bounds \u2014 replaces @use-gesture's offset[0]\n\t\tlet scaleOffset = 1\n\t\tlet initScaleFrom = 1 // the scale-space zoom level when the pinch started\n\n\t\tfunction updatePinchState(isSafariTrackpadPinch: boolean) {\n\t\t\tif (isSafariTrackpadPinch) {\n\t\t\t\tpinchState = 'zooming'\n\t\t\t}\n\n\t\t\tif (pinchState === 'zooming') {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// Initial: [touch]-------origin-------[touch]\n\t\t\t// Current: [touch]-----------origin----------[touch]\n\t\t\t// |----| |------------|\n\t\t\t// originDistance ^ ^ touchDistance\n\n\t\t\t// How far have the two touch points moved towards or away from each other?\n\t\t\tconst touchDistance = Math.abs(currDistanceBetweenFingers - initDistanceBetweenFingers)\n\t\t\t// How far has the point between the touches moved?\n\t\t\tconst originDistance = Vec.Dist(initPointBetweenFingers, prevPointBetweenFingers)\n\n\t\t\tswitch (pinchState) {\n\t\t\t\tcase 'not sure': {\n\t\t\t\t\tif (touchDistance > 24) {\n\t\t\t\t\t\tpinchState = 'zooming'\n\t\t\t\t\t} else if (originDistance > 16) {\n\t\t\t\t\t\tpinchState = 'panning'\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'panning': {\n\t\t\t\t\t// Slightly more touch distance needed to go from panning to zooming\n\t\t\t\t\tif (touchDistance > 64) {\n\t\t\t\t\t\tpinchState = 'zooming'\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction dispatchPinchEvent(\n\t\t\tname: 'pinch_start' | 'pinch' | 'pinch_end',\n\t\t\torigin: { x: number; y: number },\n\t\t\tdelta: { x: number; y: number },\n\t\t\tzoom: number,\n\t\t\tevent: TouchEvent | GestureEvent\n\t\t) {\n\t\t\teditor.dispatch({\n\t\t\t\ttype: 'pinch',\n\t\t\t\tname,\n\t\t\t\tpoint: { x: origin.x, y: origin.y, z: zoom },\n\t\t\t\tdelta,\n\t\t\t\tshiftKey: event.shiftKey,\n\t\t\t\taltKey: event.altKey,\n\t\t\t\tctrlKey: event.metaKey || event.ctrlKey,\n\t\t\t\tmetaKey: event.metaKey,\n\t\t\t\taccelKey: isAccelKey(event),\n\t\t\t})\n\t\t}\n\n\t\tfunction getOriginAndDistance(t0: Touch, t1: Touch) {\n\t\t\tconst origin = {\n\t\t\t\tx: (t0.clientX + t1.clientX) / 2,\n\t\t\t\ty: (t0.clientY + t1.clientY) / 2,\n\t\t\t}\n\t\t\tconst distance = Math.hypot(t1.clientX - t0.clientX, t1.clientY - t0.clientY)\n\t\t\treturn { origin, distance }\n\t\t}\n\n\t\tfunction onTouchStart(event: TouchEvent) {\n\t\t\tif (!(event.target === elm || elm?.contains(event.target as Node))) return\n\n\t\t\tactiveTouches = Array.from(event.touches)\n\n\t\t\tif (activeTouches.length === 2) {\n\t\t\t\t// Two fingers down \u2014 start pinch\n\t\t\t\tpinchState = 'not sure'\n\t\t\t\tconst { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1])\n\n\t\t\t\tprevPointBetweenFingers.x = origin.x\n\t\t\t\tprevPointBetweenFingers.y = origin.y\n\t\t\t\tinitPointBetweenFingers.x = origin.x\n\t\t\t\tinitPointBetweenFingers.y = origin.y\n\t\t\t\tinitDistanceBetweenFingers = Math.max(distance, 1)\n\t\t\t\tcurrDistanceBetweenFingers = distance\n\t\t\t\tinitZoom = editor.getZoomLevel()\n\t\t\t\tinitScaleFrom = getScaleFrom()\n\t\t\t\tscaleOffset = initScaleFrom\n\n\t\t\t\tdispatchPinchEvent('pinch_start', origin, { x: 0, y: 0 }, editor.getZoomLevel(), event)\n\t\t\t}\n\t\t}\n\n\t\tfunction onTouchMove(event: TouchEvent) {\n\t\t\tactiveTouches = Array.from(event.touches)\n\n\t\t\tif (activeTouches.length < 2) return\n\n\t\t\tconst { origin, distance } = getOriginAndDistance(activeTouches[0], activeTouches[1])\n\t\t\tcurrDistanceBetweenFingers = distance\n\n\t\t\tconst dx = origin.x - prevPointBetweenFingers.x\n\t\t\tconst dy = origin.y - prevPointBetweenFingers.y\n\n\t\t\tprevPointBetweenFingers.x = origin.x\n\t\t\tprevPointBetweenFingers.y = origin.y\n\n\t\t\tupdatePinchState(false)\n\n\t\t\t// Only update the zoom if the pointers are far enough apart;\n\t\t\t// a very small touchDistance means that the user has probably\n\t\t\t// pinched out and their fingers are touching; this produces\n\t\t\t// very unstable zooming behavior.\n\t\t\tconst bounds = getScaleBounds()\n\t\t\tconst rawScale = initScaleFrom * (distance / initDistanceBetweenFingers)\n\t\t\tscaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale))\n\n\t\t\tswitch (pinchState) {\n\t\t\t\tcase 'zooming': {\n\t\t\t\t\tconst currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed\n\t\t\t\t\tdispatchPinchEvent('pinch', origin, { x: dx, y: dy }, currZoom, event)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\tcase 'panning': {\n\t\t\t\t\tdispatchPinchEvent('pinch', origin, { x: dx, y: dy }, initZoom, event)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tfunction onTouchEnd(event: TouchEvent) {\n\t\t\tconst wasPinching = activeTouches.length >= 2\n\t\t\tactiveTouches = Array.from(event.touches)\n\n\t\t\tif (wasPinching && activeTouches.length < 2) {\n\t\t\t\t// Pinch ended\n\t\t\t\tconst scale = scaleOffset ** editor.getCameraOptions().zoomSpeed\n\t\t\t\tconst origin = { ...prevPointBetweenFingers }\n\t\t\t\tpinchState = 'not sure'\n\n\t\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\t\tdispatchPinchEvent('pinch_end', origin, { x: origin.x, y: origin.y }, scale, event)\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\n\t\t// --- Safari trackpad pinch (GestureEvent) ---\n\n\t\tlet safariGestureInitialScale = 1\n\n\t\tfunction onGestureStart(event: Event) {\n\t\t\tconst e = event as GestureEvent\n\t\t\tif (!(e.target === elm || elm?.contains(e.target as Node))) return\n\n\t\t\tpreventDefault(e)\n\t\t\te.stopPropagation()\n\n\t\t\tpinchState = 'not sure'\n\t\t\tsafariGestureInitialScale = getScaleFrom()\n\t\t\tscaleOffset = safariGestureInitialScale\n\t\t\tinitZoom = editor.getZoomLevel()\n\n\t\t\tprevPointBetweenFingers.x = e.clientX\n\t\t\tprevPointBetweenFingers.y = e.clientY\n\t\t\tinitPointBetweenFingers.x = e.clientX\n\t\t\tinitPointBetweenFingers.y = e.clientY\n\t\t\tinitDistanceBetweenFingers = 1\n\t\t\tcurrDistanceBetweenFingers = 1\n\n\t\t\tdispatchPinchEvent(\n\t\t\t\t'pinch_start',\n\t\t\t\t{ x: e.clientX, y: e.clientY },\n\t\t\t\t{ x: 0, y: 0 },\n\t\t\t\teditor.getZoomLevel(),\n\t\t\t\te\n\t\t\t)\n\t\t}\n\n\t\tfunction onGestureChange(event: Event) {\n\t\t\tconst e = event as GestureEvent\n\t\t\tif (!(e.target === elm || elm?.contains(e.target as Node))) return\n\n\t\t\tpreventDefault(e)\n\t\t\te.stopPropagation()\n\n\t\t\tconst dx = e.clientX - prevPointBetweenFingers.x\n\t\t\tconst dy = e.clientY - prevPointBetweenFingers.y\n\n\t\t\tprevPointBetweenFingers.x = e.clientX\n\t\t\tprevPointBetweenFingers.y = e.clientY\n\n\t\t\t// Safari GestureEvent.scale is a multiplier relative to gesture start\n\t\t\tconst bounds = getScaleBounds()\n\t\t\tconst rawScale = safariGestureInitialScale * e.scale\n\t\t\tscaleOffset = Math.min(bounds.max, Math.max(bounds.min, rawScale))\n\n\t\t\t// Update distance tracking for pinch state (treat scale change as distance change)\n\t\t\tcurrDistanceBetweenFingers = e.scale * initDistanceBetweenFingers\n\n\t\t\tupdatePinchState(true)\n\n\t\t\tconst currZoom = scaleOffset ** editor.getCameraOptions().zoomSpeed\n\n\t\t\tdispatchPinchEvent('pinch', { x: e.clientX, y: e.clientY }, { x: dx, y: dy }, currZoom, e)\n\t\t}\n\n\t\tfunction onGestureEnd(event: Event) {\n\t\t\tconst e = event as GestureEvent\n\t\t\tif (!(e.target === elm || elm?.contains(e.target as Node))) return\n\n\t\t\tpreventDefault(e)\n\t\t\te.stopPropagation()\n\n\t\t\tconst scale = scaleOffset ** editor.getCameraOptions().zoomSpeed\n\t\t\tpinchState = 'not sure'\n\n\t\t\teditor.timers.requestAnimationFrame(() => {\n\t\t\t\tdispatchPinchEvent(\n\t\t\t\t\t'pinch_end',\n\t\t\t\t\t{ x: e.clientX, y: e.clientY },\n\t\t\t\t\t{ x: e.clientX, y: e.clientY },\n\t\t\t\t\tscale,\n\t\t\t\t\te\n\t\t\t\t)\n\t\t\t})\n\t\t}\n\n\t\t// --- Attach event listeners ---\n\n\t\telm.addEventListener('wheel', onWheel, { passive: false })\n\n\t\t// On touch devices (iOS), use pointer events for pinch.\n\t\t// On non-touch Safari (macOS trackpad), use GestureEvent.\n\t\t// Never use both simultaneously \u2014 on iOS Safari, both event types fire\n\t\t// for the same pinch gesture, causing conflicting state updates.\n\t\tconst useGestureEvents = !tlenv.isIos && 'GestureEvent' in window\n\n\t\tif (useGestureEvents) {\n\t\t\telm.addEventListener('gesturestart', onGestureStart)\n\t\t\telm.addEventListener('gesturechange', onGestureChange)\n\t\t\telm.addEventListener('gestureend', onGestureEnd)\n\t\t} else {\n\t\t\telm.addEventListener('touchstart', onTouchStart)\n\t\t\telm.addEventListener('touchmove', onTouchMove)\n\t\t\telm.addEventListener('touchend', onTouchEnd)\n\t\t\telm.addEventListener('touchcancel', onTouchEnd)\n\t\t}\n\n\t\treturn () => {\n\t\t\telm.removeEventListener('wheel', onWheel)\n\t\t\tif (useGestureEvents) {\n\t\t\t\telm.removeEventListener('gesturestart', onGestureStart)\n\t\t\t\telm.removeEventListener('gesturechange', onGestureChange)\n\t\t\t\telm.removeEventListener('gestureend', onGestureEnd)\n\t\t\t} else {\n\t\t\t\telm.removeEventListener('touchstart', onTouchStart)\n\t\t\t\telm.removeEventListener('touchmove', onTouchMove)\n\t\t\t\telm.removeEventListener('touchend', onTouchEnd)\n\t\t\t\telm.removeEventListener('touchcancel', onTouchEnd)\n\t\t\t}\n\t\t}\n\t}, [editor, ref])\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["useGestureEvents"]
|
|
7
7
|
}
|
package/dist-cjs/version.js
CHANGED
|
@@ -22,10 +22,10 @@ __export(version_exports, {
|
|
|
22
22
|
version: () => version
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(version_exports);
|
|
25
|
-
const version = "4.6.0-canary.
|
|
25
|
+
const version = "4.6.0-canary.68eadaa8c321";
|
|
26
26
|
const publishDates = {
|
|
27
27
|
major: "2025-09-18T14:39:22.803Z",
|
|
28
|
-
minor: "2026-04-
|
|
29
|
-
patch: "2026-04-
|
|
28
|
+
minor: "2026-04-02T08:55:13.811Z",
|
|
29
|
+
patch: "2026-04-02T08:55:13.811Z"
|
|
30
30
|
};
|
|
31
31
|
//# sourceMappingURL=version.js.map
|
package/dist-cjs/version.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/version.ts"],
|
|
4
|
-
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.
|
|
4
|
+
"sourcesContent": ["// This file is automatically generated by internal/scripts/refresh-assets.ts.\n// Do not edit manually. Or do, I'm a comment, not a cop.\n\nexport const version = '4.6.0-canary.68eadaa8c321'\nexport const publishDates = {\n\tmajor: '2025-09-18T14:39:22.803Z',\n\tminor: '2026-04-02T08:55:13.811Z',\n\tpatch: '2026-04-02T08:55:13.811Z',\n}\n"],
|
|
5
5
|
"mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAGO,MAAM,UAAU;AAChB,MAAM,eAAe;AAAA,EAC3B,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACR;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist-esm/index.mjs
CHANGED
|
@@ -313,7 +313,7 @@ import { LocalIndexedDb, Table } from "./lib/utils/sync/LocalIndexedDb.mjs";
|
|
|
313
313
|
import { uniq } from "./lib/utils/uniq.mjs";
|
|
314
314
|
registerTldrawLibraryVersion(
|
|
315
315
|
"@tldraw/editor",
|
|
316
|
-
"4.6.0-canary.
|
|
316
|
+
"4.6.0-canary.68eadaa8c321",
|
|
317
317
|
"esm"
|
|
318
318
|
);
|
|
319
319
|
export {
|