@tsdraw/react 0.8.4 → 0.9.0
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 +154 -32
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +39 -2
- package/dist/index.d.ts +39 -2
- package/dist/index.js +155 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -303,7 +303,11 @@ var TAP_MOVE_TOLERANCE = 14;
|
|
|
303
303
|
var PINCH_MODE_ZOOM_DISTANCE = 24;
|
|
304
304
|
var PINCH_MODE_PAN_DISTANCE = 16;
|
|
305
305
|
var PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE = 64;
|
|
306
|
-
function createTouchInteractionController(editor, canvas, handlers) {
|
|
306
|
+
function createTouchInteractionController(editor, canvas, handlers, touchOptions) {
|
|
307
|
+
const allowPinchZoom = touchOptions?.pinchToZoom !== false;
|
|
308
|
+
const allowFingerPan = touchOptions?.fingerPanInPenMode !== false;
|
|
309
|
+
const allowTapUndoRedo = touchOptions?.tapUndoRedo !== false;
|
|
310
|
+
const allowTrackpadGestures = touchOptions?.trackpadGestures !== false;
|
|
307
311
|
const activeTouchPoints = /* @__PURE__ */ new Map();
|
|
308
312
|
const touchTapState = {
|
|
309
313
|
active: false,
|
|
@@ -345,7 +349,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
345
349
|
if (activeTouchPoints.size > 0) return;
|
|
346
350
|
if (!touchTapState.active) return;
|
|
347
351
|
const elapsed = performance.now() - touchTapState.startTime;
|
|
348
|
-
if (!touchTapState.moved && elapsed <= TAP_MAX_DURATION_MS && (touchTapState.maxTouchCount === 2 || touchTapState.maxTouchCount === 3)) {
|
|
352
|
+
if (allowTapUndoRedo && !touchTapState.moved && elapsed <= TAP_MAX_DURATION_MS && (touchTapState.maxTouchCount === 2 || touchTapState.maxTouchCount === 3)) {
|
|
349
353
|
const fingerCount = touchTapState.maxTouchCount;
|
|
350
354
|
const now = performance.now();
|
|
351
355
|
const previousTapTime = touchTapState.lastTapAtByCount[fingerCount] ?? 0;
|
|
@@ -393,9 +397,9 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
393
397
|
const touchDistance = Math.abs(distance - touchCameraState.initialDistance);
|
|
394
398
|
const originDistance = Math.hypot(center.x - touchCameraState.initialCenter.x, center.y - touchCameraState.initialCenter.y);
|
|
395
399
|
if (touchCameraState.mode === "not-sure") {
|
|
396
|
-
if (touchDistance > PINCH_MODE_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
400
|
+
if (allowPinchZoom && touchDistance > PINCH_MODE_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
397
401
|
else if (originDistance > PINCH_MODE_PAN_DISTANCE) touchCameraState.mode = "panning";
|
|
398
|
-
} else if (touchCameraState.mode === "panning" && touchDistance > PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
402
|
+
} else if (allowPinchZoom && touchCameraState.mode === "panning" && touchDistance > PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
399
403
|
const canvasRect = canvas.getBoundingClientRect();
|
|
400
404
|
const centerOnCanvasX = center.x - canvasRect.left;
|
|
401
405
|
const centerOnCanvasY = center.y - canvasRect.top;
|
|
@@ -436,7 +440,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
436
440
|
beginTouchCameraGesture();
|
|
437
441
|
return true;
|
|
438
442
|
}
|
|
439
|
-
if (handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
443
|
+
if (allowFingerPan && handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
440
444
|
handlers.cancelActivePointerInteraction();
|
|
441
445
|
fingerPanPointerId = event.pointerId;
|
|
442
446
|
fingerPanSession = core.beginCameraPan(editor.viewport, event.clientX, event.clientY);
|
|
@@ -471,11 +475,15 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
471
475
|
if (wasFingerPan) {
|
|
472
476
|
endFingerPan();
|
|
473
477
|
if (releasedPanSession) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
478
|
+
const slideConfig = handlers.getSlideOptions();
|
|
479
|
+
if (slideConfig.enabled) {
|
|
480
|
+
fingerPanSlide = core.startCameraSlide(
|
|
481
|
+
releasedPanSession,
|
|
482
|
+
(dx, dy) => editor.panBy(dx, dy),
|
|
483
|
+
() => handlers.refreshView(),
|
|
484
|
+
slideConfig.slideOptions
|
|
485
|
+
);
|
|
486
|
+
}
|
|
479
487
|
}
|
|
480
488
|
}
|
|
481
489
|
maybeHandleTouchTapGesture();
|
|
@@ -486,6 +494,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
486
494
|
const handleGestureEvent = (event, container) => {
|
|
487
495
|
if (!container.contains(event.target)) return;
|
|
488
496
|
event.preventDefault();
|
|
497
|
+
if (!allowTrackpadGestures) return;
|
|
489
498
|
const gestureEvent = event;
|
|
490
499
|
if (gestureEvent.scale == null) return;
|
|
491
500
|
if (event.type === "gesturestart") {
|
|
@@ -529,7 +538,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
529
538
|
}
|
|
530
539
|
|
|
531
540
|
// src/canvas/keyboardShortcuts.ts
|
|
532
|
-
var
|
|
541
|
+
var DEFAULT_TOOL_SHORTCUTS = {
|
|
533
542
|
v: "select",
|
|
534
543
|
h: "hand",
|
|
535
544
|
e: "eraser",
|
|
@@ -541,6 +550,11 @@ var TOOL_SHORTCUTS = {
|
|
|
541
550
|
o: "circle",
|
|
542
551
|
c: "circle"
|
|
543
552
|
};
|
|
553
|
+
function resolveToolShortcuts(shortcutOptions) {
|
|
554
|
+
if (!shortcutOptions?.toolShortcuts) return DEFAULT_TOOL_SHORTCUTS;
|
|
555
|
+
if (shortcutOptions.overrideDefaults) return { ...shortcutOptions.toolShortcuts };
|
|
556
|
+
return { ...DEFAULT_TOOL_SHORTCUTS, ...shortcutOptions.toolShortcuts };
|
|
557
|
+
}
|
|
544
558
|
function isEditableTarget(eventTarget) {
|
|
545
559
|
const element = eventTarget;
|
|
546
560
|
if (!element) return false;
|
|
@@ -548,7 +562,7 @@ function isEditableTarget(eventTarget) {
|
|
|
548
562
|
const tagName = element.tagName;
|
|
549
563
|
return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
|
|
550
564
|
}
|
|
551
|
-
function handleKeyboardShortcutKeyDown(event, handlers) {
|
|
565
|
+
function handleKeyboardShortcutKeyDown(event, handlers, toolShortcutMap = DEFAULT_TOOL_SHORTCUTS) {
|
|
552
566
|
if (isEditableTarget(event.target)) return;
|
|
553
567
|
const loweredKey = event.key.toLowerCase();
|
|
554
568
|
const isMetaPressed = event.metaKey || event.ctrlKey;
|
|
@@ -561,7 +575,7 @@ function handleKeyboardShortcutKeyDown(event, handlers) {
|
|
|
561
575
|
}
|
|
562
576
|
}
|
|
563
577
|
if (!isMetaPressed && !event.altKey) {
|
|
564
|
-
const nextToolId =
|
|
578
|
+
const nextToolId = toolShortcutMap[loweredKey];
|
|
565
579
|
if (nextToolId && handlers.isToolAvailable(nextToolId)) {
|
|
566
580
|
handlers.setToolFromShortcut(nextToolId);
|
|
567
581
|
event.preventDefault();
|
|
@@ -736,8 +750,18 @@ function getHandlePagePoint(bounds, handle) {
|
|
|
736
750
|
}
|
|
737
751
|
}
|
|
738
752
|
var ZOOM_WHEEL_CAP = 10;
|
|
753
|
+
var VIEW_ONLY_TOOLS = /* @__PURE__ */ new Set(["select", "hand"]);
|
|
739
754
|
function useTsdrawCanvasController(options = {}) {
|
|
740
755
|
const onMountRef = react.useRef(options.onMount);
|
|
756
|
+
const onChangeRef = react.useRef(options.onChange);
|
|
757
|
+
const onCameraChangeRef = react.useRef(options.onCameraChange);
|
|
758
|
+
const onToolChangeRef = react.useRef(options.onToolChange);
|
|
759
|
+
const cameraOptionsRef = react.useRef(options.cameraOptions);
|
|
760
|
+
const touchOptionsRef = react.useRef(options.touchOptions);
|
|
761
|
+
const keyboardShortcutsRef = react.useRef(options.keyboardShortcuts);
|
|
762
|
+
const penOptionsRef = react.useRef(options.penOptions);
|
|
763
|
+
const backgroundRef = react.useRef(options.background);
|
|
764
|
+
const readOnlyRef = react.useRef(options.readOnly ?? false);
|
|
741
765
|
const containerRef = react.useRef(null);
|
|
742
766
|
const canvasRef = react.useRef(null);
|
|
743
767
|
const editorRef = react.useRef(null);
|
|
@@ -797,6 +821,33 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
797
821
|
react.useEffect(() => {
|
|
798
822
|
onMountRef.current = options.onMount;
|
|
799
823
|
}, [options.onMount]);
|
|
824
|
+
react.useEffect(() => {
|
|
825
|
+
onChangeRef.current = options.onChange;
|
|
826
|
+
}, [options.onChange]);
|
|
827
|
+
react.useEffect(() => {
|
|
828
|
+
onCameraChangeRef.current = options.onCameraChange;
|
|
829
|
+
}, [options.onCameraChange]);
|
|
830
|
+
react.useEffect(() => {
|
|
831
|
+
onToolChangeRef.current = options.onToolChange;
|
|
832
|
+
}, [options.onToolChange]);
|
|
833
|
+
react.useEffect(() => {
|
|
834
|
+
cameraOptionsRef.current = options.cameraOptions;
|
|
835
|
+
}, [options.cameraOptions]);
|
|
836
|
+
react.useEffect(() => {
|
|
837
|
+
touchOptionsRef.current = options.touchOptions;
|
|
838
|
+
}, [options.touchOptions]);
|
|
839
|
+
react.useEffect(() => {
|
|
840
|
+
keyboardShortcutsRef.current = options.keyboardShortcuts;
|
|
841
|
+
}, [options.keyboardShortcuts]);
|
|
842
|
+
react.useEffect(() => {
|
|
843
|
+
penOptionsRef.current = options.penOptions;
|
|
844
|
+
}, [options.penOptions]);
|
|
845
|
+
react.useEffect(() => {
|
|
846
|
+
backgroundRef.current = options.background;
|
|
847
|
+
}, [options.background]);
|
|
848
|
+
react.useEffect(() => {
|
|
849
|
+
readOnlyRef.current = options.readOnly ?? false;
|
|
850
|
+
}, [options.readOnly]);
|
|
800
851
|
react.useEffect(() => {
|
|
801
852
|
selectedShapeIdsRef.current = selectedShapeIds;
|
|
802
853
|
}, [selectedShapeIds]);
|
|
@@ -813,8 +864,11 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
813
864
|
const ctx = canvas.getContext("2d");
|
|
814
865
|
if (!ctx) return;
|
|
815
866
|
const dpr = dprRef.current || 1;
|
|
867
|
+
const logicalWidth = canvas.width / dpr;
|
|
868
|
+
const logicalHeight = canvas.height / dpr;
|
|
816
869
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
817
|
-
ctx.clearRect(0, 0,
|
|
870
|
+
ctx.clearRect(0, 0, logicalWidth, logicalHeight);
|
|
871
|
+
core.renderCanvasBackground(ctx, editor.viewport, logicalWidth, logicalHeight, backgroundRef.current, editor.renderer.theme);
|
|
818
872
|
editor.render(ctx);
|
|
819
873
|
}, []);
|
|
820
874
|
const refreshSelectionBounds = react.useCallback((editor, ids = selectedShapeIdsRef.current) => {
|
|
@@ -920,14 +974,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
920
974
|
const canvas = canvasRef.current;
|
|
921
975
|
if (!container || !canvas) return;
|
|
922
976
|
const initialTool = options.initialTool ?? "pen";
|
|
977
|
+
const cameraOpts = cameraOptionsRef.current;
|
|
978
|
+
const touchOpts = touchOptionsRef.current;
|
|
979
|
+
const toolShortcutMap = resolveToolShortcuts(keyboardShortcutsRef.current);
|
|
923
980
|
const editor = new core.Editor({
|
|
924
981
|
toolDefinitions: options.toolDefinitions,
|
|
925
|
-
initialToolId: initialTool
|
|
982
|
+
initialToolId: initialTool,
|
|
983
|
+
zoomRange: cameraOpts?.zoomRange
|
|
926
984
|
});
|
|
927
985
|
editor.renderer.setTheme(options.theme ?? "light");
|
|
928
986
|
if (!editor.tools.hasTool(initialTool)) {
|
|
929
987
|
editor.setCurrentTool("pen");
|
|
930
988
|
}
|
|
989
|
+
if (options.snapshot) {
|
|
990
|
+
editor.loadPersistenceSnapshot(options.snapshot);
|
|
991
|
+
}
|
|
931
992
|
let disposed = false;
|
|
932
993
|
let ignorePersistenceChanges = false;
|
|
933
994
|
let disposeMount;
|
|
@@ -1094,16 +1155,24 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1094
1155
|
render();
|
|
1095
1156
|
refreshSelectionBounds(editor);
|
|
1096
1157
|
};
|
|
1158
|
+
const emitCameraChange = () => {
|
|
1159
|
+
onCameraChangeRef.current?.({ ...editor.viewport });
|
|
1160
|
+
};
|
|
1097
1161
|
const touchInteractions = createTouchInteractionController(editor, canvas, {
|
|
1098
1162
|
cancelActivePointerInteraction,
|
|
1099
1163
|
refreshView: () => {
|
|
1100
1164
|
render();
|
|
1101
1165
|
refreshSelectionBounds(editor);
|
|
1166
|
+
emitCameraChange();
|
|
1102
1167
|
},
|
|
1103
1168
|
runUndo: () => applyDocumentChangeResult(editor.undo()),
|
|
1104
1169
|
runRedo: () => applyDocumentChangeResult(editor.redo()),
|
|
1105
|
-
isPenModeActive: () => penModeRef.current
|
|
1106
|
-
|
|
1170
|
+
isPenModeActive: () => penModeRef.current,
|
|
1171
|
+
getSlideOptions: () => ({
|
|
1172
|
+
enabled: cameraOptionsRef.current?.slideEnabled !== false,
|
|
1173
|
+
slideOptions: { friction: cameraOptionsRef.current?.slideFriction }
|
|
1174
|
+
})
|
|
1175
|
+
}, touchOpts);
|
|
1107
1176
|
const hasRealPressure = (pressure) => pressure != null && pressure > 0 && pressure !== 0.5;
|
|
1108
1177
|
const stopActiveSlide = () => {
|
|
1109
1178
|
if (activeCameraSlideRef.current) {
|
|
@@ -1113,8 +1182,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1113
1182
|
};
|
|
1114
1183
|
const handlePointerDown = (e) => {
|
|
1115
1184
|
if (!canvas.contains(e.target)) return;
|
|
1185
|
+
if (cameraOptionsRef.current?.locked && e.pointerType !== "pen") return;
|
|
1116
1186
|
stopActiveSlide();
|
|
1117
|
-
|
|
1187
|
+
const penAutoDetect = penOptionsRef.current?.autoDetect !== false;
|
|
1188
|
+
if (penAutoDetect && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1118
1189
|
penDetectedRef.current = true;
|
|
1119
1190
|
penModeRef.current = true;
|
|
1120
1191
|
}
|
|
@@ -1135,6 +1206,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1135
1206
|
if (activePointerIdsRef.current.size > 1) {
|
|
1136
1207
|
return;
|
|
1137
1208
|
}
|
|
1209
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(currentToolRef.current)) {
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1138
1212
|
isPointerActiveRef.current = true;
|
|
1139
1213
|
editor.beginHistoryEntry();
|
|
1140
1214
|
canvas.setPointerCapture(e.pointerId);
|
|
@@ -1142,7 +1216,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1142
1216
|
updatePointerPreview(e.clientX, e.clientY);
|
|
1143
1217
|
const first = sampleEvents(e)[0];
|
|
1144
1218
|
const { x, y } = getPagePoint(first);
|
|
1145
|
-
const
|
|
1219
|
+
const pressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1220
|
+
const pressure = (first.pressure ?? 0.5) * pressureSensitivity;
|
|
1146
1221
|
const isPen = first.pointerType === "pen" || hasRealPressure(first.pressure);
|
|
1147
1222
|
if (currentToolRef.current === "select") {
|
|
1148
1223
|
const hit = core.getTopShapeAtPoint(editor, { x, y });
|
|
@@ -1189,7 +1264,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1189
1264
|
refreshSelectionBounds(editor);
|
|
1190
1265
|
};
|
|
1191
1266
|
const handlePointerMove = (e) => {
|
|
1192
|
-
|
|
1267
|
+
const penAutoDetectOnMove = penOptionsRef.current?.autoDetect !== false;
|
|
1268
|
+
if (penAutoDetectOnMove && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1193
1269
|
penDetectedRef.current = true;
|
|
1194
1270
|
penModeRef.current = true;
|
|
1195
1271
|
}
|
|
@@ -1204,9 +1280,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1204
1280
|
const dx = prevClient ? e.clientX - prevClient.x : 0;
|
|
1205
1281
|
const dy = prevClient ? e.clientY - prevClient.y : 0;
|
|
1206
1282
|
lastPointerClientRef.current = { x: e.clientX, y: e.clientY };
|
|
1283
|
+
const movePressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1207
1284
|
for (const sample of sampleEvents(e)) {
|
|
1208
1285
|
const { x, y } = getPagePoint(sample);
|
|
1209
|
-
const pressure = sample.pressure ?? 0.5;
|
|
1286
|
+
const pressure = (sample.pressure ?? 0.5) * movePressureSensitivity;
|
|
1210
1287
|
const isPen = sample.pointerType === "pen" || hasRealPressure(sample.pressure);
|
|
1211
1288
|
editor.input.pointerMove(x, y, pressure, isPen);
|
|
1212
1289
|
}
|
|
@@ -1340,14 +1417,18 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1340
1417
|
editor.tools.pointerUp();
|
|
1341
1418
|
render();
|
|
1342
1419
|
refreshSelectionBounds(editor);
|
|
1343
|
-
if (handPanSession) {
|
|
1420
|
+
if (handPanSession && cameraOptionsRef.current?.slideEnabled !== false) {
|
|
1344
1421
|
activeCameraSlideRef.current = core.startCameraSlide(
|
|
1345
1422
|
handPanSession,
|
|
1346
|
-
(slideDx, slideDy) =>
|
|
1423
|
+
(slideDx, slideDy) => {
|
|
1424
|
+
editor.panBy(slideDx, slideDy);
|
|
1425
|
+
emitCameraChange();
|
|
1426
|
+
},
|
|
1347
1427
|
() => {
|
|
1348
1428
|
render();
|
|
1349
1429
|
refreshSelectionBounds(editor);
|
|
1350
|
-
}
|
|
1430
|
+
},
|
|
1431
|
+
{ friction: cameraOptionsRef.current?.slideFriction }
|
|
1351
1432
|
);
|
|
1352
1433
|
}
|
|
1353
1434
|
if (pendingRemoteDocumentRef.current) {
|
|
@@ -1392,41 +1473,59 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1392
1473
|
const handleWheel = (e) => {
|
|
1393
1474
|
if (!container.contains(e.target)) return;
|
|
1394
1475
|
e.preventDefault();
|
|
1476
|
+
const camOpts = cameraOptionsRef.current;
|
|
1477
|
+
if (camOpts?.locked) return;
|
|
1478
|
+
if (camOpts?.wheelBehavior === "none") return;
|
|
1395
1479
|
if (touchInteractions.isTrackpadZoomActive()) return;
|
|
1396
1480
|
const delta = normalizeWheelDelta(e);
|
|
1481
|
+
const panMultiplier = camOpts?.panSpeed ?? 1;
|
|
1482
|
+
const zoomMultiplier = camOpts?.zoomSpeed ?? 1;
|
|
1397
1483
|
if (delta.z !== 0) {
|
|
1398
1484
|
const rect = canvas.getBoundingClientRect();
|
|
1399
1485
|
const pointX = e.clientX - rect.left;
|
|
1400
1486
|
const pointY = e.clientY - rect.top;
|
|
1401
|
-
editor.zoomAt(Math.exp(delta.z), pointX, pointY);
|
|
1487
|
+
editor.zoomAt(Math.exp(delta.z * zoomMultiplier), pointX, pointY);
|
|
1402
1488
|
} else {
|
|
1403
|
-
editor.panBy(delta.x, delta.y);
|
|
1489
|
+
editor.panBy(delta.x * panMultiplier, delta.y * panMultiplier);
|
|
1404
1490
|
}
|
|
1405
1491
|
render();
|
|
1406
1492
|
refreshSelectionBounds(editor);
|
|
1493
|
+
emitCameraChange();
|
|
1407
1494
|
};
|
|
1408
1495
|
const handleGestureEvent = (e) => {
|
|
1409
1496
|
touchInteractions.handleGestureEvent(e, container);
|
|
1410
1497
|
};
|
|
1411
1498
|
const handleKeyDown = (e) => {
|
|
1499
|
+
if (keyboardShortcutsRef.current?.enabled === false) return;
|
|
1500
|
+
const isReadOnly = readOnlyRef.current;
|
|
1412
1501
|
handleKeyboardShortcutKeyDown(e, {
|
|
1413
|
-
isToolAvailable: (tool) =>
|
|
1502
|
+
isToolAvailable: (tool) => {
|
|
1503
|
+
if (isReadOnly && !VIEW_ONLY_TOOLS.has(tool)) return false;
|
|
1504
|
+
return editor.tools.hasTool(tool);
|
|
1505
|
+
},
|
|
1414
1506
|
setToolFromShortcut: (tool) => {
|
|
1415
1507
|
editor.setCurrentTool(tool);
|
|
1416
1508
|
setCurrentToolState(tool);
|
|
1417
1509
|
currentToolRef.current = tool;
|
|
1418
1510
|
if (tool !== "select") resetSelectUi();
|
|
1419
1511
|
render();
|
|
1512
|
+
onToolChangeRef.current?.(tool);
|
|
1513
|
+
},
|
|
1514
|
+
runHistoryShortcut: (shouldRedo) => {
|
|
1515
|
+
if (isReadOnly) return false;
|
|
1516
|
+
return applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo());
|
|
1517
|
+
},
|
|
1518
|
+
deleteSelection: () => {
|
|
1519
|
+
if (isReadOnly) return false;
|
|
1520
|
+
return currentToolRef.current === "select" ? deleteCurrentSelection() : false;
|
|
1420
1521
|
},
|
|
1421
|
-
runHistoryShortcut: (shouldRedo) => applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo()),
|
|
1422
|
-
deleteSelection: () => currentToolRef.current === "select" ? deleteCurrentSelection() : false,
|
|
1423
1522
|
dispatchKeyDown: (event) => {
|
|
1424
1523
|
editor.input.setModifiers(event.shiftKey, event.ctrlKey, event.metaKey);
|
|
1425
1524
|
editor.tools.keyDown({ key: event.key });
|
|
1426
1525
|
render();
|
|
1427
1526
|
},
|
|
1428
1527
|
dispatchKeyUp: () => void 0
|
|
1429
|
-
});
|
|
1528
|
+
}, toolShortcutMap);
|
|
1430
1529
|
};
|
|
1431
1530
|
const handleKeyUp = (e) => {
|
|
1432
1531
|
handleKeyboardShortcutKeyUp(e, {
|
|
@@ -1507,6 +1606,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1507
1606
|
const cleanupEditorListener = editor.listen(() => {
|
|
1508
1607
|
if (ignorePersistenceChanges) return;
|
|
1509
1608
|
schedulePersist();
|
|
1609
|
+
onChangeRef.current?.(editor.getDocumentSnapshot());
|
|
1510
1610
|
});
|
|
1511
1611
|
const cleanupHistoryListener = editor.listenHistory(() => {
|
|
1512
1612
|
syncHistoryState();
|
|
@@ -1573,6 +1673,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1573
1673
|
render();
|
|
1574
1674
|
}
|
|
1575
1675
|
});
|
|
1676
|
+
if (options.autoFocus !== false) {
|
|
1677
|
+
container.focus({ preventScroll: true });
|
|
1678
|
+
}
|
|
1576
1679
|
return () => {
|
|
1577
1680
|
disposed = true;
|
|
1578
1681
|
schedulePersistRef.current = null;
|
|
@@ -1616,15 +1719,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1616
1719
|
editor.renderer.setTheme(options.theme ?? "light");
|
|
1617
1720
|
render();
|
|
1618
1721
|
}, [options.theme, render]);
|
|
1722
|
+
react.useEffect(() => {
|
|
1723
|
+
if (!editorRef.current) return;
|
|
1724
|
+
render();
|
|
1725
|
+
}, [options.background, render]);
|
|
1619
1726
|
const setTool = react.useCallback(
|
|
1620
1727
|
(tool) => {
|
|
1621
1728
|
const editor = editorRef.current;
|
|
1622
1729
|
if (!editor) return;
|
|
1623
1730
|
if (!editor.tools.hasTool(tool)) return;
|
|
1731
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(tool)) return;
|
|
1624
1732
|
editor.setCurrentTool(tool);
|
|
1625
1733
|
setCurrentToolState(tool);
|
|
1626
1734
|
currentToolRef.current = tool;
|
|
1627
1735
|
if (tool !== "select") resetSelectUi();
|
|
1736
|
+
onToolChangeRef.current?.(tool);
|
|
1628
1737
|
},
|
|
1629
1738
|
[resetSelectUi]
|
|
1630
1739
|
);
|
|
@@ -1860,12 +1969,23 @@ function Tsdraw(props) {
|
|
|
1860
1969
|
initialTool,
|
|
1861
1970
|
theme: resolvedTheme,
|
|
1862
1971
|
persistenceKey: props.persistenceKey,
|
|
1863
|
-
onMount: props.onMount
|
|
1972
|
+
onMount: props.onMount,
|
|
1973
|
+
cameraOptions: props.cameraOptions,
|
|
1974
|
+
touchOptions: props.touchOptions,
|
|
1975
|
+
keyboardShortcuts: props.keyboardShortcuts,
|
|
1976
|
+
penOptions: props.penOptions,
|
|
1977
|
+
background: props.background,
|
|
1978
|
+
readOnly: props.readOnly,
|
|
1979
|
+
autoFocus: props.autoFocus,
|
|
1980
|
+
snapshot: props.snapshot,
|
|
1981
|
+
onChange: props.onChange,
|
|
1982
|
+
onCameraChange: props.onCameraChange,
|
|
1983
|
+
onToolChange: props.onToolChange
|
|
1864
1984
|
});
|
|
1865
1985
|
const toolbarPlacementStyle = resolvePlacementStyle(props.uiOptions?.toolbar?.placement, "bottom-center", 0, 14);
|
|
1866
1986
|
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8, 8);
|
|
1867
1987
|
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
1868
|
-
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true;
|
|
1988
|
+
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true || props.readOnly === true;
|
|
1869
1989
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1870
1990
|
const defaultToolOverlay = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1871
1991
|
ToolOverlay,
|
|
@@ -1954,12 +2074,14 @@ function Tsdraw(props) {
|
|
|
1954
2074
|
"div",
|
|
1955
2075
|
{
|
|
1956
2076
|
ref: containerRef,
|
|
2077
|
+
tabIndex: 0,
|
|
1957
2078
|
className: `tsdraw tsdraw-${resolvedTheme}mode ${props.className ?? ""}`,
|
|
1958
2079
|
style: {
|
|
1959
2080
|
width: props.width ?? "100%",
|
|
1960
2081
|
height: props.height ?? "100%",
|
|
1961
2082
|
position: "relative",
|
|
1962
2083
|
overflow: "hidden",
|
|
2084
|
+
outline: "none",
|
|
1963
2085
|
...props.style
|
|
1964
2086
|
},
|
|
1965
2087
|
children: [
|