@tsdraw/react 0.8.3 → 0.8.4
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/index.cjs +93 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
-
import { DEFAULT_COLORS, getSelectionBoundsPage, buildTransformSnapshots, Editor, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor, pageToScreen, getTopShapeAtPoint, buildStartPositions, applyRotation, applyResize, applyMove, normalizeSelectionBounds, getShapesInBounds, isSelectTool } from '@tsdraw/core';
|
|
3
|
+
import { DEFAULT_COLORS, getSelectionBoundsPage, buildTransformSnapshots, Editor, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor, pageToScreen, getTopShapeAtPoint, buildStartPositions, applyRotation, applyResize, applyMove, normalizeSelectionBounds, getShapesInBounds, HandDraggingState, startCameraSlide, isSelectTool, beginCameraPan, moveCameraPan } from '@tsdraw/core';
|
|
4
4
|
import { IconPointer, IconPencil, IconSquare, IconCircle, IconEraser, IconHandStop, IconArrowBackUp, IconArrowForwardUp } from '@tabler/icons-react';
|
|
5
5
|
|
|
6
6
|
// src/components/TsdrawCanvas.tsx
|
|
@@ -295,8 +295,6 @@ function getCanvasCursor(currentTool, state) {
|
|
|
295
295
|
}
|
|
296
296
|
return state.showToolOverlay ? "none" : "crosshair";
|
|
297
297
|
}
|
|
298
|
-
|
|
299
|
-
// src/canvas/touchInteractions.ts
|
|
300
298
|
var TAP_MAX_DURATION_MS = 100;
|
|
301
299
|
var DOUBLE_TAP_INTERVAL_MS = 100;
|
|
302
300
|
var TAP_MOVE_TOLERANCE = 14;
|
|
@@ -318,15 +316,28 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
318
316
|
mode: "not-sure",
|
|
319
317
|
previousCenter: { x: 0, y: 0 },
|
|
320
318
|
initialCenter: { x: 0, y: 0 },
|
|
321
|
-
|
|
322
|
-
|
|
319
|
+
initialDistance: 1,
|
|
320
|
+
initialZoom: 1
|
|
323
321
|
};
|
|
322
|
+
let fingerPanPointerId = null;
|
|
323
|
+
let fingerPanSession = null;
|
|
324
|
+
let fingerPanSlide = null;
|
|
324
325
|
const isTouchPointer = (event) => event.pointerType === "touch";
|
|
326
|
+
const stopFingerPanSlide = () => {
|
|
327
|
+
if (fingerPanSlide) {
|
|
328
|
+
fingerPanSlide.stop();
|
|
329
|
+
fingerPanSlide = null;
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
const endFingerPan = () => {
|
|
333
|
+
fingerPanPointerId = null;
|
|
334
|
+
fingerPanSession = null;
|
|
335
|
+
};
|
|
325
336
|
const endTouchCameraGesture = () => {
|
|
326
337
|
touchCameraState.active = false;
|
|
327
338
|
touchCameraState.mode = "not-sure";
|
|
328
|
-
touchCameraState.previousDistance = 1;
|
|
329
339
|
touchCameraState.initialDistance = 1;
|
|
340
|
+
touchCameraState.initialZoom = 1;
|
|
330
341
|
};
|
|
331
342
|
const maybeHandleTouchTapGesture = () => {
|
|
332
343
|
if (activeTouchPoints.size > 0) return;
|
|
@@ -361,8 +372,8 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
361
372
|
touchCameraState.mode = "not-sure";
|
|
362
373
|
touchCameraState.previousCenter = center;
|
|
363
374
|
touchCameraState.initialCenter = center;
|
|
364
|
-
touchCameraState.previousDistance = Math.max(1, distance);
|
|
365
375
|
touchCameraState.initialDistance = Math.max(1, distance);
|
|
376
|
+
touchCameraState.initialZoom = editor.getZoomLevel();
|
|
366
377
|
};
|
|
367
378
|
const updateTouchCameraGesture = () => {
|
|
368
379
|
if (!touchCameraState.active) return false;
|
|
@@ -386,18 +397,27 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
386
397
|
const canvasRect = canvas.getBoundingClientRect();
|
|
387
398
|
const centerOnCanvasX = center.x - canvasRect.left;
|
|
388
399
|
const centerOnCanvasY = center.y - canvasRect.top;
|
|
389
|
-
editor.panBy(centerDx, centerDy);
|
|
390
400
|
if (touchCameraState.mode === "zooming") {
|
|
391
|
-
const
|
|
392
|
-
editor.
|
|
401
|
+
const targetZoom = Math.max(0.1, Math.min(4, touchCameraState.initialZoom * (distance / touchCameraState.initialDistance)));
|
|
402
|
+
const pannedX = editor.viewport.x + centerDx;
|
|
403
|
+
const pannedY = editor.viewport.y + centerDy;
|
|
404
|
+
const pageAtCenterX = (centerOnCanvasX - pannedX) / editor.viewport.zoom;
|
|
405
|
+
const pageAtCenterY = (centerOnCanvasY - pannedY) / editor.viewport.zoom;
|
|
406
|
+
editor.setViewport({
|
|
407
|
+
x: centerOnCanvasX - pageAtCenterX * targetZoom,
|
|
408
|
+
y: centerOnCanvasY - pageAtCenterY * targetZoom,
|
|
409
|
+
zoom: targetZoom
|
|
410
|
+
});
|
|
411
|
+
} else {
|
|
412
|
+
editor.panBy(centerDx, centerDy);
|
|
393
413
|
}
|
|
394
414
|
touchCameraState.previousCenter = center;
|
|
395
|
-
touchCameraState.previousDistance = distance;
|
|
396
415
|
handlers.refreshView();
|
|
397
416
|
return true;
|
|
398
417
|
};
|
|
399
418
|
const handlePointerDown = (event) => {
|
|
400
419
|
if (!isTouchPointer(event)) return false;
|
|
420
|
+
stopFingerPanSlide();
|
|
401
421
|
activeTouchPoints.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
402
422
|
if (!touchTapState.active) {
|
|
403
423
|
touchTapState.active = true;
|
|
@@ -410,9 +430,16 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
410
430
|
}
|
|
411
431
|
touchTapState.startPoints.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
412
432
|
if (activeTouchPoints.size === 2) {
|
|
433
|
+
endFingerPan();
|
|
413
434
|
beginTouchCameraGesture();
|
|
414
435
|
return true;
|
|
415
436
|
}
|
|
437
|
+
if (handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
438
|
+
handlers.cancelActivePointerInteraction();
|
|
439
|
+
fingerPanPointerId = event.pointerId;
|
|
440
|
+
fingerPanSession = beginCameraPan(editor.viewport, event.clientX, event.clientY);
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
416
443
|
return false;
|
|
417
444
|
};
|
|
418
445
|
const handlePointerMove = (event) => {
|
|
@@ -423,16 +450,34 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
423
450
|
const moved = Math.hypot(event.clientX - tapStart.x, event.clientY - tapStart.y);
|
|
424
451
|
if (moved > TAP_MOVE_TOLERANCE) touchTapState.moved = true;
|
|
425
452
|
}
|
|
453
|
+
if (fingerPanPointerId === event.pointerId && fingerPanSession) {
|
|
454
|
+
const target = moveCameraPan(fingerPanSession, event.clientX, event.clientY);
|
|
455
|
+
editor.setViewport({ x: target.x, y: target.y });
|
|
456
|
+
handlers.refreshView();
|
|
457
|
+
return true;
|
|
458
|
+
}
|
|
426
459
|
return updateTouchCameraGesture();
|
|
427
460
|
};
|
|
428
461
|
const handlePointerUpOrCancel = (event) => {
|
|
429
462
|
if (!isTouchPointer(event)) return false;
|
|
430
463
|
const wasCameraGestureActive = touchCameraState.active;
|
|
464
|
+
const wasFingerPan = fingerPanPointerId === event.pointerId;
|
|
465
|
+
const releasedPanSession = wasFingerPan ? fingerPanSession : null;
|
|
431
466
|
activeTouchPoints.delete(event.pointerId);
|
|
432
467
|
touchTapState.startPoints.delete(event.pointerId);
|
|
433
468
|
if (activeTouchPoints.size < 2) endTouchCameraGesture();
|
|
469
|
+
if (wasFingerPan) {
|
|
470
|
+
endFingerPan();
|
|
471
|
+
if (releasedPanSession) {
|
|
472
|
+
fingerPanSlide = startCameraSlide(
|
|
473
|
+
releasedPanSession,
|
|
474
|
+
(dx, dy) => editor.panBy(dx, dy),
|
|
475
|
+
() => handlers.refreshView()
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
434
479
|
maybeHandleTouchTapGesture();
|
|
435
|
-
return wasCameraGestureActive;
|
|
480
|
+
return wasCameraGestureActive || wasFingerPan;
|
|
436
481
|
};
|
|
437
482
|
let gestureLastScale = 1;
|
|
438
483
|
let gestureActive = false;
|
|
@@ -466,6 +511,8 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
466
511
|
touchTapState.active = false;
|
|
467
512
|
touchTapState.startPoints.clear();
|
|
468
513
|
endTouchCameraGesture();
|
|
514
|
+
endFingerPan();
|
|
515
|
+
stopFingerPanSlide();
|
|
469
516
|
};
|
|
470
517
|
return {
|
|
471
518
|
handlePointerDown,
|
|
@@ -474,6 +521,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
474
521
|
handleGestureEvent,
|
|
475
522
|
reset,
|
|
476
523
|
isCameraGestureActive: () => touchCameraState.active,
|
|
524
|
+
isFingerPanActive: () => fingerPanPointerId !== null,
|
|
477
525
|
isTrackpadZoomActive: () => gestureActive
|
|
478
526
|
};
|
|
479
527
|
}
|
|
@@ -702,6 +750,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
702
750
|
const schedulePersistRef = useRef(null);
|
|
703
751
|
const isPointerActiveRef = useRef(false);
|
|
704
752
|
const pendingRemoteDocumentRef = useRef(null);
|
|
753
|
+
const activeCameraSlideRef = useRef(null);
|
|
705
754
|
const selectionRotationRef = useRef(0);
|
|
706
755
|
const resizeRef = useRef({
|
|
707
756
|
handle: null,
|
|
@@ -1050,12 +1099,19 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1050
1099
|
refreshSelectionBounds(editor);
|
|
1051
1100
|
},
|
|
1052
1101
|
runUndo: () => applyDocumentChangeResult(editor.undo()),
|
|
1053
|
-
runRedo: () => applyDocumentChangeResult(editor.redo())
|
|
1102
|
+
runRedo: () => applyDocumentChangeResult(editor.redo()),
|
|
1103
|
+
isPenModeActive: () => penModeRef.current
|
|
1054
1104
|
});
|
|
1055
|
-
const isDrawingTool = (tool) => tool !== "select" && tool !== "hand";
|
|
1056
1105
|
const hasRealPressure = (pressure) => pressure != null && pressure > 0 && pressure !== 0.5;
|
|
1106
|
+
const stopActiveSlide = () => {
|
|
1107
|
+
if (activeCameraSlideRef.current) {
|
|
1108
|
+
activeCameraSlideRef.current.stop();
|
|
1109
|
+
activeCameraSlideRef.current = null;
|
|
1110
|
+
}
|
|
1111
|
+
};
|
|
1057
1112
|
const handlePointerDown = (e) => {
|
|
1058
1113
|
if (!canvas.contains(e.target)) return;
|
|
1114
|
+
stopActiveSlide();
|
|
1059
1115
|
if (!penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1060
1116
|
penDetectedRef.current = true;
|
|
1061
1117
|
penModeRef.current = true;
|
|
@@ -1063,15 +1119,15 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1063
1119
|
lastPointerDownWithRef.current = e.pointerType;
|
|
1064
1120
|
activePointerIdsRef.current.add(e.pointerId);
|
|
1065
1121
|
const startedCameraGesture = touchInteractions.handlePointerDown(e);
|
|
1066
|
-
if (startedCameraGesture || touchInteractions.isCameraGestureActive()) {
|
|
1122
|
+
if (startedCameraGesture || touchInteractions.isCameraGestureActive() || touchInteractions.isFingerPanActive()) {
|
|
1067
1123
|
e.preventDefault();
|
|
1068
1124
|
if (!canvas.hasPointerCapture(e.pointerId)) {
|
|
1069
1125
|
canvas.setPointerCapture(e.pointerId);
|
|
1070
1126
|
}
|
|
1071
1127
|
return;
|
|
1072
1128
|
}
|
|
1073
|
-
const
|
|
1074
|
-
if (
|
|
1129
|
+
const isTouchBlockedByPenMode = penModeRef.current && e.pointerType === "touch";
|
|
1130
|
+
if (isTouchBlockedByPenMode) {
|
|
1075
1131
|
return;
|
|
1076
1132
|
}
|
|
1077
1133
|
if (activePointerIdsRef.current.size > 1) {
|
|
@@ -1126,7 +1182,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1126
1182
|
}
|
|
1127
1183
|
editor.input.pointerDown(x, y, pressure, isPen);
|
|
1128
1184
|
editor.input.setModifiers(first.shiftKey, first.ctrlKey, first.metaKey);
|
|
1129
|
-
editor.tools.pointerDown({ point: { x, y, z: pressure } });
|
|
1185
|
+
editor.tools.pointerDown({ point: { x, y, z: pressure }, screenX: e.clientX, screenY: e.clientY });
|
|
1130
1186
|
render();
|
|
1131
1187
|
refreshSelectionBounds(editor);
|
|
1132
1188
|
};
|
|
@@ -1139,7 +1195,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1139
1195
|
e.preventDefault();
|
|
1140
1196
|
return;
|
|
1141
1197
|
}
|
|
1142
|
-
if (penModeRef.current && e.pointerType === "touch" &&
|
|
1198
|
+
if (penModeRef.current && e.pointerType === "touch" && !isPointerActiveRef.current) return;
|
|
1143
1199
|
if (activePointerIdsRef.current.size > 1) return;
|
|
1144
1200
|
updatePointerPreview(e.clientX, e.clientY);
|
|
1145
1201
|
const prevClient = lastPointerClientRef.current;
|
|
@@ -1193,7 +1249,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1193
1249
|
}
|
|
1194
1250
|
}
|
|
1195
1251
|
editor.input.setModifiers(e.shiftKey, e.ctrlKey, e.metaKey);
|
|
1196
|
-
editor.tools.pointerMove({ screenDeltaX: dx, screenDeltaY: dy });
|
|
1252
|
+
editor.tools.pointerMove({ screenDeltaX: dx, screenDeltaY: dy, screenX: e.clientX, screenY: e.clientY });
|
|
1197
1253
|
render();
|
|
1198
1254
|
refreshSelectionBounds(editor);
|
|
1199
1255
|
};
|
|
@@ -1272,9 +1328,26 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1272
1328
|
return;
|
|
1273
1329
|
}
|
|
1274
1330
|
}
|
|
1331
|
+
let handPanSession = null;
|
|
1332
|
+
if (currentToolRef.current === "hand") {
|
|
1333
|
+
const currentState = editor.tools.getCurrentState();
|
|
1334
|
+
if (currentState instanceof HandDraggingState) {
|
|
1335
|
+
handPanSession = currentState.getPanSession();
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1275
1338
|
editor.tools.pointerUp();
|
|
1276
1339
|
render();
|
|
1277
1340
|
refreshSelectionBounds(editor);
|
|
1341
|
+
if (handPanSession) {
|
|
1342
|
+
activeCameraSlideRef.current = startCameraSlide(
|
|
1343
|
+
handPanSession,
|
|
1344
|
+
(slideDx, slideDy) => editor.panBy(slideDx, slideDy),
|
|
1345
|
+
() => {
|
|
1346
|
+
render();
|
|
1347
|
+
refreshSelectionBounds(editor);
|
|
1348
|
+
}
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1278
1351
|
if (pendingRemoteDocumentRef.current) {
|
|
1279
1352
|
const pendingRemoteDocument = pendingRemoteDocumentRef.current;
|
|
1280
1353
|
pendingRemoteDocumentRef.current = null;
|
|
@@ -1519,6 +1592,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1519
1592
|
isPointerActiveRef.current = false;
|
|
1520
1593
|
activePointerIdsRef.current.clear();
|
|
1521
1594
|
pendingRemoteDocumentRef.current = null;
|
|
1595
|
+
stopActiveSlide();
|
|
1522
1596
|
touchInteractions.reset();
|
|
1523
1597
|
persistenceChannel?.close();
|
|
1524
1598
|
void persistenceDb?.close();
|