@tsdraw/react 0.8.2 → 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 +152 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +153 -67
- package/dist/index.js.map +1 -1
- package/dist/tsdraw.css +46 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -292,12 +292,11 @@ function getCanvasCursor(currentTool, state) {
|
|
|
292
292
|
if (state.isRotatingSelection) return "grabbing";
|
|
293
293
|
if (state.isResizingSelection) return "nwse-resize";
|
|
294
294
|
if (state.isMovingSelection) return "grabbing";
|
|
295
|
+
if (state.isHoveringSelectionBounds) return "move";
|
|
295
296
|
return "default";
|
|
296
297
|
}
|
|
297
298
|
return state.showToolOverlay ? "none" : "crosshair";
|
|
298
299
|
}
|
|
299
|
-
|
|
300
|
-
// src/canvas/touchInteractions.ts
|
|
301
300
|
var TAP_MAX_DURATION_MS = 100;
|
|
302
301
|
var DOUBLE_TAP_INTERVAL_MS = 100;
|
|
303
302
|
var TAP_MOVE_TOLERANCE = 14;
|
|
@@ -319,17 +318,28 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
319
318
|
mode: "not-sure",
|
|
320
319
|
previousCenter: { x: 0, y: 0 },
|
|
321
320
|
initialCenter: { x: 0, y: 0 },
|
|
322
|
-
previousDistance: 1,
|
|
323
321
|
initialDistance: 1,
|
|
324
|
-
|
|
322
|
+
initialZoom: 1
|
|
325
323
|
};
|
|
324
|
+
let fingerPanPointerId = null;
|
|
325
|
+
let fingerPanSession = null;
|
|
326
|
+
let fingerPanSlide = null;
|
|
326
327
|
const isTouchPointer = (event) => event.pointerType === "touch";
|
|
328
|
+
const stopFingerPanSlide = () => {
|
|
329
|
+
if (fingerPanSlide) {
|
|
330
|
+
fingerPanSlide.stop();
|
|
331
|
+
fingerPanSlide = null;
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
const endFingerPan = () => {
|
|
335
|
+
fingerPanPointerId = null;
|
|
336
|
+
fingerPanSession = null;
|
|
337
|
+
};
|
|
327
338
|
const endTouchCameraGesture = () => {
|
|
328
339
|
touchCameraState.active = false;
|
|
329
340
|
touchCameraState.mode = "not-sure";
|
|
330
|
-
touchCameraState.previousDistance = 1;
|
|
331
341
|
touchCameraState.initialDistance = 1;
|
|
332
|
-
touchCameraState.
|
|
342
|
+
touchCameraState.initialZoom = 1;
|
|
333
343
|
};
|
|
334
344
|
const maybeHandleTouchTapGesture = () => {
|
|
335
345
|
if (activeTouchPoints.size > 0) return;
|
|
@@ -360,14 +370,12 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
360
370
|
const second = points[1];
|
|
361
371
|
const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };
|
|
362
372
|
const distance = Math.hypot(second.x - first.x, second.y - first.y);
|
|
363
|
-
const angle = Math.atan2(second.y - first.y, second.x - first.x);
|
|
364
373
|
touchCameraState.active = true;
|
|
365
374
|
touchCameraState.mode = "not-sure";
|
|
366
375
|
touchCameraState.previousCenter = center;
|
|
367
376
|
touchCameraState.initialCenter = center;
|
|
368
|
-
touchCameraState.previousDistance = Math.max(1, distance);
|
|
369
377
|
touchCameraState.initialDistance = Math.max(1, distance);
|
|
370
|
-
touchCameraState.
|
|
378
|
+
touchCameraState.initialZoom = editor.getZoomLevel();
|
|
371
379
|
};
|
|
372
380
|
const updateTouchCameraGesture = () => {
|
|
373
381
|
if (!touchCameraState.active) return false;
|
|
@@ -380,7 +388,6 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
380
388
|
const second = points[1];
|
|
381
389
|
const center = { x: (first.x + second.x) / 2, y: (first.y + second.y) / 2 };
|
|
382
390
|
const distance = Math.max(1, Math.hypot(second.x - first.x, second.y - first.y));
|
|
383
|
-
const angle = Math.atan2(second.y - first.y, second.x - first.x);
|
|
384
391
|
const centerDx = center.x - touchCameraState.previousCenter.x;
|
|
385
392
|
const centerDy = center.y - touchCameraState.previousCenter.y;
|
|
386
393
|
const touchDistance = Math.abs(distance - touchCameraState.initialDistance);
|
|
@@ -392,20 +399,27 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
392
399
|
const canvasRect = canvas.getBoundingClientRect();
|
|
393
400
|
const centerOnCanvasX = center.x - canvasRect.left;
|
|
394
401
|
const centerOnCanvasY = center.y - canvasRect.top;
|
|
395
|
-
editor.panBy(centerDx, centerDy);
|
|
396
402
|
if (touchCameraState.mode === "zooming") {
|
|
397
|
-
const
|
|
398
|
-
editor.
|
|
399
|
-
|
|
403
|
+
const targetZoom = Math.max(0.1, Math.min(4, touchCameraState.initialZoom * (distance / touchCameraState.initialDistance)));
|
|
404
|
+
const pannedX = editor.viewport.x + centerDx;
|
|
405
|
+
const pannedY = editor.viewport.y + centerDy;
|
|
406
|
+
const pageAtCenterX = (centerOnCanvasX - pannedX) / editor.viewport.zoom;
|
|
407
|
+
const pageAtCenterY = (centerOnCanvasY - pannedY) / editor.viewport.zoom;
|
|
408
|
+
editor.setViewport({
|
|
409
|
+
x: centerOnCanvasX - pageAtCenterX * targetZoom,
|
|
410
|
+
y: centerOnCanvasY - pageAtCenterY * targetZoom,
|
|
411
|
+
zoom: targetZoom
|
|
412
|
+
});
|
|
413
|
+
} else {
|
|
414
|
+
editor.panBy(centerDx, centerDy);
|
|
400
415
|
}
|
|
401
416
|
touchCameraState.previousCenter = center;
|
|
402
|
-
touchCameraState.previousDistance = distance;
|
|
403
|
-
touchCameraState.previousAngle = angle;
|
|
404
417
|
handlers.refreshView();
|
|
405
418
|
return true;
|
|
406
419
|
};
|
|
407
420
|
const handlePointerDown = (event) => {
|
|
408
421
|
if (!isTouchPointer(event)) return false;
|
|
422
|
+
stopFingerPanSlide();
|
|
409
423
|
activeTouchPoints.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
410
424
|
if (!touchTapState.active) {
|
|
411
425
|
touchTapState.active = true;
|
|
@@ -418,9 +432,16 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
418
432
|
}
|
|
419
433
|
touchTapState.startPoints.set(event.pointerId, { x: event.clientX, y: event.clientY });
|
|
420
434
|
if (activeTouchPoints.size === 2) {
|
|
435
|
+
endFingerPan();
|
|
421
436
|
beginTouchCameraGesture();
|
|
422
437
|
return true;
|
|
423
438
|
}
|
|
439
|
+
if (handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
440
|
+
handlers.cancelActivePointerInteraction();
|
|
441
|
+
fingerPanPointerId = event.pointerId;
|
|
442
|
+
fingerPanSession = core.beginCameraPan(editor.viewport, event.clientX, event.clientY);
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
424
445
|
return false;
|
|
425
446
|
};
|
|
426
447
|
const handlePointerMove = (event) => {
|
|
@@ -431,16 +452,34 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
431
452
|
const moved = Math.hypot(event.clientX - tapStart.x, event.clientY - tapStart.y);
|
|
432
453
|
if (moved > TAP_MOVE_TOLERANCE) touchTapState.moved = true;
|
|
433
454
|
}
|
|
455
|
+
if (fingerPanPointerId === event.pointerId && fingerPanSession) {
|
|
456
|
+
const target = core.moveCameraPan(fingerPanSession, event.clientX, event.clientY);
|
|
457
|
+
editor.setViewport({ x: target.x, y: target.y });
|
|
458
|
+
handlers.refreshView();
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
434
461
|
return updateTouchCameraGesture();
|
|
435
462
|
};
|
|
436
463
|
const handlePointerUpOrCancel = (event) => {
|
|
437
464
|
if (!isTouchPointer(event)) return false;
|
|
438
465
|
const wasCameraGestureActive = touchCameraState.active;
|
|
466
|
+
const wasFingerPan = fingerPanPointerId === event.pointerId;
|
|
467
|
+
const releasedPanSession = wasFingerPan ? fingerPanSession : null;
|
|
439
468
|
activeTouchPoints.delete(event.pointerId);
|
|
440
469
|
touchTapState.startPoints.delete(event.pointerId);
|
|
441
470
|
if (activeTouchPoints.size < 2) endTouchCameraGesture();
|
|
471
|
+
if (wasFingerPan) {
|
|
472
|
+
endFingerPan();
|
|
473
|
+
if (releasedPanSession) {
|
|
474
|
+
fingerPanSlide = core.startCameraSlide(
|
|
475
|
+
releasedPanSession,
|
|
476
|
+
(dx, dy) => editor.panBy(dx, dy),
|
|
477
|
+
() => handlers.refreshView()
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
442
481
|
maybeHandleTouchTapGesture();
|
|
443
|
-
return wasCameraGestureActive;
|
|
482
|
+
return wasCameraGestureActive || wasFingerPan;
|
|
444
483
|
};
|
|
445
484
|
let gestureLastScale = 1;
|
|
446
485
|
let gestureActive = false;
|
|
@@ -474,6 +513,8 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
474
513
|
touchTapState.active = false;
|
|
475
514
|
touchTapState.startPoints.clear();
|
|
476
515
|
endTouchCameraGesture();
|
|
516
|
+
endFingerPan();
|
|
517
|
+
stopFingerPanSlide();
|
|
477
518
|
};
|
|
478
519
|
return {
|
|
479
520
|
handlePointerDown,
|
|
@@ -482,6 +523,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
482
523
|
handleGestureEvent,
|
|
483
524
|
reset,
|
|
484
525
|
isCameraGestureActive: () => touchCameraState.active,
|
|
526
|
+
isFingerPanActive: () => fingerPanPointerId !== null,
|
|
485
527
|
isTrackpadZoomActive: () => gestureActive
|
|
486
528
|
};
|
|
487
529
|
}
|
|
@@ -681,6 +723,18 @@ function toScreenRect(editor, bounds) {
|
|
|
681
723
|
function resolveDrawColor(colorStyle, theme) {
|
|
682
724
|
return core.resolveThemeColor(colorStyle, theme);
|
|
683
725
|
}
|
|
726
|
+
function getHandlePagePoint(bounds, handle) {
|
|
727
|
+
switch (handle) {
|
|
728
|
+
case "nw":
|
|
729
|
+
return { x: bounds.minX, y: bounds.minY };
|
|
730
|
+
case "ne":
|
|
731
|
+
return { x: bounds.maxX, y: bounds.minY };
|
|
732
|
+
case "sw":
|
|
733
|
+
return { x: bounds.minX, y: bounds.maxY };
|
|
734
|
+
case "se":
|
|
735
|
+
return { x: bounds.maxX, y: bounds.maxY };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
684
738
|
var ZOOM_WHEEL_CAP = 10;
|
|
685
739
|
function useTsdrawCanvasController(options = {}) {
|
|
686
740
|
const onMountRef = react.useRef(options.onMount);
|
|
@@ -698,11 +752,13 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
698
752
|
const schedulePersistRef = react.useRef(null);
|
|
699
753
|
const isPointerActiveRef = react.useRef(false);
|
|
700
754
|
const pendingRemoteDocumentRef = react.useRef(null);
|
|
755
|
+
const activeCameraSlideRef = react.useRef(null);
|
|
701
756
|
const selectionRotationRef = react.useRef(0);
|
|
702
757
|
const resizeRef = react.useRef({
|
|
703
758
|
handle: null,
|
|
704
759
|
startBounds: null,
|
|
705
|
-
startShapes: /* @__PURE__ */ new Map()
|
|
760
|
+
startShapes: /* @__PURE__ */ new Map(),
|
|
761
|
+
cursorHandleOffset: { x: 0, y: 0 }
|
|
706
762
|
});
|
|
707
763
|
const rotateRef = react.useRef({
|
|
708
764
|
center: null,
|
|
@@ -789,33 +845,38 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
789
845
|
setIsResizingSelection(false);
|
|
790
846
|
setIsRotatingSelection(false);
|
|
791
847
|
selectDragRef.current.mode = "none";
|
|
792
|
-
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map() };
|
|
793
|
-
rotateRef.current = {
|
|
794
|
-
center: null,
|
|
795
|
-
startAngle: 0,
|
|
796
|
-
startSelectionRotationDeg: selectionRotationRef.current,
|
|
797
|
-
startShapes: /* @__PURE__ */ new Map()
|
|
798
|
-
};
|
|
848
|
+
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
|
|
849
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
799
850
|
}, []);
|
|
800
851
|
const handleResizePointerDown = react.useCallback(
|
|
801
852
|
(e, handle) => {
|
|
802
853
|
e.preventDefault();
|
|
803
854
|
e.stopPropagation();
|
|
804
855
|
const editor = editorRef.current;
|
|
805
|
-
|
|
856
|
+
const canvas = canvasRef.current;
|
|
857
|
+
if (!editor || !canvas || selectedShapeIdsRef.current.length === 0) return;
|
|
806
858
|
const bounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
|
|
807
859
|
if (!bounds) return;
|
|
860
|
+
const handlePagePoint = getHandlePagePoint(bounds, handle);
|
|
861
|
+
const pointerPage = getPagePointFromClient(editor, e.clientX, e.clientY);
|
|
862
|
+
const cursorOffset = {
|
|
863
|
+
x: pointerPage.x - handlePagePoint.x,
|
|
864
|
+
y: pointerPage.y - handlePagePoint.y
|
|
865
|
+
};
|
|
808
866
|
resizeRef.current = {
|
|
809
867
|
handle,
|
|
810
868
|
startBounds: bounds,
|
|
811
|
-
startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current)
|
|
869
|
+
startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current),
|
|
870
|
+
cursorHandleOffset: cursorOffset
|
|
812
871
|
};
|
|
872
|
+
isPointerActiveRef.current = true;
|
|
873
|
+
activePointerIdsRef.current.add(e.pointerId);
|
|
874
|
+
canvas.setPointerCapture(e.pointerId);
|
|
813
875
|
editor.beginHistoryEntry();
|
|
814
876
|
selectDragRef.current.mode = "resize";
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
selectDragRef.current.
|
|
818
|
-
selectDragRef.current.currentPage = p;
|
|
877
|
+
editor.input.pointerDown(handlePagePoint.x, handlePagePoint.y, 0.5, false);
|
|
878
|
+
selectDragRef.current.startPage = handlePagePoint;
|
|
879
|
+
selectDragRef.current.currentPage = handlePagePoint;
|
|
819
880
|
lastPointerClientRef.current = { x: e.clientX, y: e.clientY };
|
|
820
881
|
setIsResizingSelection(true);
|
|
821
882
|
},
|
|
@@ -826,7 +887,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
826
887
|
e.preventDefault();
|
|
827
888
|
e.stopPropagation();
|
|
828
889
|
const editor = editorRef.current;
|
|
829
|
-
|
|
890
|
+
const canvas = canvasRef.current;
|
|
891
|
+
if (!editor || !canvas || selectedShapeIdsRef.current.length === 0) return;
|
|
830
892
|
const bounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
|
|
831
893
|
if (!bounds) return;
|
|
832
894
|
const center = {
|
|
@@ -840,6 +902,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
840
902
|
startSelectionRotationDeg: selectionRotationRef.current,
|
|
841
903
|
startShapes: core.buildTransformSnapshots(editor, selectedShapeIdsRef.current)
|
|
842
904
|
};
|
|
905
|
+
isPointerActiveRef.current = true;
|
|
906
|
+
activePointerIdsRef.current.add(e.pointerId);
|
|
907
|
+
canvas.setPointerCapture(e.pointerId);
|
|
843
908
|
editor.beginHistoryEntry();
|
|
844
909
|
selectDragRef.current.mode = "rotate";
|
|
845
910
|
editor.input.pointerDown(p.x, p.y, 0.5, false);
|
|
@@ -969,7 +1034,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
969
1034
|
setDrawDash(nextDrawStyle.dash);
|
|
970
1035
|
setDrawFill(nextDrawStyle.fill);
|
|
971
1036
|
setDrawSize(nextDrawStyle.size);
|
|
972
|
-
setSelectionRotationDeg(0);
|
|
973
1037
|
render();
|
|
974
1038
|
refreshSelectionBounds(editor, nextSelectionIds);
|
|
975
1039
|
ignorePersistenceChanges = false;
|
|
@@ -985,7 +1049,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
985
1049
|
const applyDocumentChangeResult = (changed) => {
|
|
986
1050
|
if (!changed) return false;
|
|
987
1051
|
reconcileSelectionAfterDocumentLoad();
|
|
988
|
-
setSelectionRotationDeg(0);
|
|
989
1052
|
render();
|
|
990
1053
|
syncHistoryState();
|
|
991
1054
|
return true;
|
|
@@ -1012,8 +1075,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1012
1075
|
setSelectedShapeIds([]);
|
|
1013
1076
|
selectedShapeIdsRef.current = [];
|
|
1014
1077
|
setSelectionBounds(null);
|
|
1015
|
-
|
|
1016
|
-
setSelectionRotationDeg(0);
|
|
1078
|
+
resetSelectUi();
|
|
1017
1079
|
render();
|
|
1018
1080
|
syncHistoryState();
|
|
1019
1081
|
return true;
|
|
@@ -1024,12 +1086,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1024
1086
|
lastPointerClientRef.current = null;
|
|
1025
1087
|
editor.input.pointerUp();
|
|
1026
1088
|
if (currentToolRef.current === "select") {
|
|
1027
|
-
|
|
1028
|
-
if (dragMode === "rotate") setIsRotatingSelection(false);
|
|
1029
|
-
if (dragMode === "resize") setIsResizingSelection(false);
|
|
1030
|
-
if (dragMode === "move") setIsMovingSelection(false);
|
|
1031
|
-
if (dragMode === "marquee") setSelectionBrush(null);
|
|
1032
|
-
selectDragRef.current.mode = "none";
|
|
1089
|
+
resetSelectUi();
|
|
1033
1090
|
} else {
|
|
1034
1091
|
editor.tools.pointerUp();
|
|
1035
1092
|
}
|
|
@@ -1044,12 +1101,19 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1044
1101
|
refreshSelectionBounds(editor);
|
|
1045
1102
|
},
|
|
1046
1103
|
runUndo: () => applyDocumentChangeResult(editor.undo()),
|
|
1047
|
-
runRedo: () => applyDocumentChangeResult(editor.redo())
|
|
1104
|
+
runRedo: () => applyDocumentChangeResult(editor.redo()),
|
|
1105
|
+
isPenModeActive: () => penModeRef.current
|
|
1048
1106
|
});
|
|
1049
|
-
const isDrawingTool = (tool) => tool !== "select" && tool !== "hand";
|
|
1050
1107
|
const hasRealPressure = (pressure) => pressure != null && pressure > 0 && pressure !== 0.5;
|
|
1108
|
+
const stopActiveSlide = () => {
|
|
1109
|
+
if (activeCameraSlideRef.current) {
|
|
1110
|
+
activeCameraSlideRef.current.stop();
|
|
1111
|
+
activeCameraSlideRef.current = null;
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1051
1114
|
const handlePointerDown = (e) => {
|
|
1052
1115
|
if (!canvas.contains(e.target)) return;
|
|
1116
|
+
stopActiveSlide();
|
|
1053
1117
|
if (!penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1054
1118
|
penDetectedRef.current = true;
|
|
1055
1119
|
penModeRef.current = true;
|
|
@@ -1057,15 +1121,15 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1057
1121
|
lastPointerDownWithRef.current = e.pointerType;
|
|
1058
1122
|
activePointerIdsRef.current.add(e.pointerId);
|
|
1059
1123
|
const startedCameraGesture = touchInteractions.handlePointerDown(e);
|
|
1060
|
-
if (startedCameraGesture || touchInteractions.isCameraGestureActive()) {
|
|
1124
|
+
if (startedCameraGesture || touchInteractions.isCameraGestureActive() || touchInteractions.isFingerPanActive()) {
|
|
1061
1125
|
e.preventDefault();
|
|
1062
1126
|
if (!canvas.hasPointerCapture(e.pointerId)) {
|
|
1063
1127
|
canvas.setPointerCapture(e.pointerId);
|
|
1064
1128
|
}
|
|
1065
1129
|
return;
|
|
1066
1130
|
}
|
|
1067
|
-
const
|
|
1068
|
-
if (
|
|
1131
|
+
const isTouchBlockedByPenMode = penModeRef.current && e.pointerType === "touch";
|
|
1132
|
+
if (isTouchBlockedByPenMode) {
|
|
1069
1133
|
return;
|
|
1070
1134
|
}
|
|
1071
1135
|
if (activePointerIdsRef.current.size > 1) {
|
|
@@ -1083,7 +1147,13 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1083
1147
|
if (currentToolRef.current === "select") {
|
|
1084
1148
|
const hit = core.getTopShapeAtPoint(editor, { x, y });
|
|
1085
1149
|
const isHitSelected = !!(hit && selectedShapeIdsRef.current.includes(hit.id));
|
|
1086
|
-
|
|
1150
|
+
const isInsideSelectionBounds = (() => {
|
|
1151
|
+
if (selectedShapeIdsRef.current.length === 0) return false;
|
|
1152
|
+
const pageBounds = core.getSelectionBoundsPage(editor, selectedShapeIdsRef.current);
|
|
1153
|
+
if (!pageBounds) return false;
|
|
1154
|
+
return x >= pageBounds.minX && x <= pageBounds.maxX && y >= pageBounds.minY && y <= pageBounds.maxY;
|
|
1155
|
+
})();
|
|
1156
|
+
if (isHitSelected || isInsideSelectionBounds) {
|
|
1087
1157
|
selectDragRef.current = {
|
|
1088
1158
|
mode: "move",
|
|
1089
1159
|
startPage: { x, y },
|
|
@@ -1114,7 +1184,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1114
1184
|
}
|
|
1115
1185
|
editor.input.pointerDown(x, y, pressure, isPen);
|
|
1116
1186
|
editor.input.setModifiers(first.shiftKey, first.ctrlKey, first.metaKey);
|
|
1117
|
-
editor.tools.pointerDown({ point: { x, y, z: pressure } });
|
|
1187
|
+
editor.tools.pointerDown({ point: { x, y, z: pressure }, screenX: e.clientX, screenY: e.clientY });
|
|
1118
1188
|
render();
|
|
1119
1189
|
refreshSelectionBounds(editor);
|
|
1120
1190
|
};
|
|
@@ -1127,7 +1197,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1127
1197
|
e.preventDefault();
|
|
1128
1198
|
return;
|
|
1129
1199
|
}
|
|
1130
|
-
if (penModeRef.current && e.pointerType === "touch" &&
|
|
1200
|
+
if (penModeRef.current && e.pointerType === "touch" && !isPointerActiveRef.current) return;
|
|
1131
1201
|
if (activePointerIdsRef.current.size > 1) return;
|
|
1132
1202
|
updatePointerPreview(e.clientX, e.clientY);
|
|
1133
1203
|
const prevClient = lastPointerClientRef.current;
|
|
@@ -1154,9 +1224,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1154
1224
|
return;
|
|
1155
1225
|
}
|
|
1156
1226
|
if (mode === "resize") {
|
|
1157
|
-
const { handle, startBounds, startShapes } = resizeRef.current;
|
|
1227
|
+
const { handle, startBounds, startShapes, cursorHandleOffset } = resizeRef.current;
|
|
1158
1228
|
if (!handle || !startBounds) return;
|
|
1159
|
-
core.applyResize(editor, handle, startBounds, startShapes, { x: px, y: py }, e.shiftKey);
|
|
1229
|
+
core.applyResize(editor, handle, startBounds, startShapes, { x: px - cursorHandleOffset.x, y: py - cursorHandleOffset.y }, e.shiftKey);
|
|
1160
1230
|
render();
|
|
1161
1231
|
refreshSelectionBounds(editor);
|
|
1162
1232
|
return;
|
|
@@ -1176,13 +1246,12 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1176
1246
|
const nextIds = selectDragRef.current.additive ? Array.from(/* @__PURE__ */ new Set([...selectDragRef.current.initialSelection, ...ids])) : ids;
|
|
1177
1247
|
setSelectedShapeIds(nextIds);
|
|
1178
1248
|
selectedShapeIdsRef.current = nextIds;
|
|
1179
|
-
setSelectionRotationDeg(0);
|
|
1180
1249
|
refreshSelectionBounds(editor, nextIds);
|
|
1181
1250
|
return;
|
|
1182
1251
|
}
|
|
1183
1252
|
}
|
|
1184
1253
|
editor.input.setModifiers(e.shiftKey, e.ctrlKey, e.metaKey);
|
|
1185
|
-
editor.tools.pointerMove({ screenDeltaX: dx, screenDeltaY: dy });
|
|
1254
|
+
editor.tools.pointerMove({ screenDeltaX: dx, screenDeltaY: dy, screenX: e.clientX, screenY: e.clientY });
|
|
1186
1255
|
render();
|
|
1187
1256
|
refreshSelectionBounds(editor);
|
|
1188
1257
|
};
|
|
@@ -1206,12 +1275,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1206
1275
|
setIsRotatingSelection(false);
|
|
1207
1276
|
selectDragRef.current.mode = "none";
|
|
1208
1277
|
setSelectionRotationDeg(0);
|
|
1209
|
-
rotateRef.current = {
|
|
1210
|
-
center: null,
|
|
1211
|
-
startAngle: 0,
|
|
1212
|
-
startSelectionRotationDeg: selectionRotationRef.current,
|
|
1213
|
-
startShapes: /* @__PURE__ */ new Map()
|
|
1214
|
-
};
|
|
1278
|
+
rotateRef.current = { center: null, startAngle: 0, startSelectionRotationDeg: 0, startShapes: /* @__PURE__ */ new Map() };
|
|
1215
1279
|
render();
|
|
1216
1280
|
refreshSelectionBounds(editor);
|
|
1217
1281
|
editor.endHistoryEntry();
|
|
@@ -1220,7 +1284,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1220
1284
|
if (drag.mode === "resize") {
|
|
1221
1285
|
setIsResizingSelection(false);
|
|
1222
1286
|
selectDragRef.current.mode = "none";
|
|
1223
|
-
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map() };
|
|
1287
|
+
resizeRef.current = { handle: null, startBounds: null, startShapes: /* @__PURE__ */ new Map(), cursorHandleOffset: { x: 0, y: 0 } };
|
|
1224
1288
|
render();
|
|
1225
1289
|
refreshSelectionBounds(editor);
|
|
1226
1290
|
editor.endHistoryEntry();
|
|
@@ -1253,7 +1317,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1253
1317
|
}
|
|
1254
1318
|
setSelectedShapeIds(ids);
|
|
1255
1319
|
selectedShapeIdsRef.current = ids;
|
|
1256
|
-
setSelectionRotationDeg(0);
|
|
1257
1320
|
setSelectionBrush(null);
|
|
1258
1321
|
selectDragRef.current.mode = "none";
|
|
1259
1322
|
render();
|
|
@@ -1267,9 +1330,26 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1267
1330
|
return;
|
|
1268
1331
|
}
|
|
1269
1332
|
}
|
|
1333
|
+
let handPanSession = null;
|
|
1334
|
+
if (currentToolRef.current === "hand") {
|
|
1335
|
+
const currentState = editor.tools.getCurrentState();
|
|
1336
|
+
if (currentState instanceof core.HandDraggingState) {
|
|
1337
|
+
handPanSession = currentState.getPanSession();
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1270
1340
|
editor.tools.pointerUp();
|
|
1271
1341
|
render();
|
|
1272
1342
|
refreshSelectionBounds(editor);
|
|
1343
|
+
if (handPanSession) {
|
|
1344
|
+
activeCameraSlideRef.current = core.startCameraSlide(
|
|
1345
|
+
handPanSession,
|
|
1346
|
+
(slideDx, slideDy) => editor.panBy(slideDx, slideDy),
|
|
1347
|
+
() => {
|
|
1348
|
+
render();
|
|
1349
|
+
refreshSelectionBounds(editor);
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1352
|
+
}
|
|
1273
1353
|
if (pendingRemoteDocumentRef.current) {
|
|
1274
1354
|
const pendingRemoteDocument = pendingRemoteDocumentRef.current;
|
|
1275
1355
|
pendingRemoteDocumentRef.current = null;
|
|
@@ -1436,11 +1516,17 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1436
1516
|
resize();
|
|
1437
1517
|
const ro = new ResizeObserver(resize);
|
|
1438
1518
|
ro.observe(container);
|
|
1519
|
+
const handlePointerLeaveViewport = (e) => {
|
|
1520
|
+
if (e.relatedTarget === null) {
|
|
1521
|
+
setIsPointerInsideCanvas(false);
|
|
1522
|
+
}
|
|
1523
|
+
};
|
|
1439
1524
|
canvas.addEventListener("pointerdown", handlePointerDown);
|
|
1440
1525
|
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
1441
1526
|
document.addEventListener("gesturestart", handleGestureEvent);
|
|
1442
1527
|
document.addEventListener("gesturechange", handleGestureEvent);
|
|
1443
1528
|
document.addEventListener("gestureend", handleGestureEvent);
|
|
1529
|
+
document.documentElement.addEventListener("pointerleave", handlePointerLeaveViewport);
|
|
1444
1530
|
window.addEventListener("pointermove", handlePointerMove);
|
|
1445
1531
|
window.addEventListener("pointerup", handlePointerUp);
|
|
1446
1532
|
window.addEventListener("pointercancel", handlePointerCancel);
|
|
@@ -1464,7 +1550,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1464
1550
|
const changed = editor.undo();
|
|
1465
1551
|
if (!changed) return false;
|
|
1466
1552
|
reconcileSelectionAfterDocumentLoad();
|
|
1467
|
-
setSelectionRotationDeg(0);
|
|
1468
1553
|
render();
|
|
1469
1554
|
syncHistoryState();
|
|
1470
1555
|
return true;
|
|
@@ -1473,7 +1558,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1473
1558
|
const changed = editor.redo();
|
|
1474
1559
|
if (!changed) return false;
|
|
1475
1560
|
reconcileSelectionAfterDocumentLoad();
|
|
1476
|
-
setSelectionRotationDeg(0);
|
|
1477
1561
|
render();
|
|
1478
1562
|
syncHistoryState();
|
|
1479
1563
|
return true;
|
|
@@ -1501,6 +1585,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1501
1585
|
document.removeEventListener("gesturestart", handleGestureEvent);
|
|
1502
1586
|
document.removeEventListener("gesturechange", handleGestureEvent);
|
|
1503
1587
|
document.removeEventListener("gestureend", handleGestureEvent);
|
|
1588
|
+
document.documentElement.removeEventListener("pointerleave", handlePointerLeaveViewport);
|
|
1504
1589
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
1505
1590
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
1506
1591
|
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
@@ -1509,6 +1594,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1509
1594
|
isPointerActiveRef.current = false;
|
|
1510
1595
|
activePointerIdsRef.current.clear();
|
|
1511
1596
|
pendingRemoteDocumentRef.current = null;
|
|
1597
|
+
stopActiveSlide();
|
|
1512
1598
|
touchInteractions.reset();
|
|
1513
1599
|
persistenceChannel?.close();
|
|
1514
1600
|
void persistenceDb?.close();
|
|
@@ -1565,7 +1651,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1565
1651
|
selectedShapeIdsRef.current = nextSelectedShapeIds;
|
|
1566
1652
|
setSelectedShapeIds(nextSelectedShapeIds);
|
|
1567
1653
|
}
|
|
1568
|
-
setSelectionRotationDeg(0);
|
|
1569
1654
|
render();
|
|
1570
1655
|
setCanUndo(editor.canUndo());
|
|
1571
1656
|
setCanRedo(editor.canRedo());
|
|
@@ -1581,17 +1666,18 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1581
1666
|
selectedShapeIdsRef.current = nextSelectedShapeIds;
|
|
1582
1667
|
setSelectedShapeIds(nextSelectedShapeIds);
|
|
1583
1668
|
}
|
|
1584
|
-
setSelectionRotationDeg(0);
|
|
1585
1669
|
render();
|
|
1586
1670
|
setCanUndo(editor.canUndo());
|
|
1587
1671
|
setCanRedo(editor.canRedo());
|
|
1588
1672
|
return true;
|
|
1589
1673
|
}, [render]);
|
|
1674
|
+
const isHoveringSelectionBounds = isPointerInsideCanvas && currentTool === "select" && selectedShapeIds.length > 0 && selectionBounds != null && pointerScreenPoint.x >= selectionBounds.left && pointerScreenPoint.x <= selectionBounds.left + selectionBounds.width && pointerScreenPoint.y >= selectionBounds.top && pointerScreenPoint.y <= selectionBounds.top + selectionBounds.height;
|
|
1590
1675
|
const showToolOverlay = isPointerInsideCanvas && (currentTool === "pen" || currentTool === "eraser");
|
|
1591
1676
|
const canvasCursor = getCanvasCursor(currentTool, {
|
|
1592
1677
|
isMovingSelection,
|
|
1593
1678
|
isResizingSelection,
|
|
1594
1679
|
isRotatingSelection,
|
|
1680
|
+
isHoveringSelectionBounds,
|
|
1595
1681
|
showToolOverlay
|
|
1596
1682
|
});
|
|
1597
1683
|
const cursorContext = {
|