@tsdraw/react 0.8.4 → 0.8.5
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 +141 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -2
- package/dist/index.d.ts +37 -2
- package/dist/index.js +141 -31
- 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,17 @@ 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 readOnlyRef = react.useRef(options.readOnly ?? false);
|
|
741
764
|
const containerRef = react.useRef(null);
|
|
742
765
|
const canvasRef = react.useRef(null);
|
|
743
766
|
const editorRef = react.useRef(null);
|
|
@@ -797,6 +820,30 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
797
820
|
react.useEffect(() => {
|
|
798
821
|
onMountRef.current = options.onMount;
|
|
799
822
|
}, [options.onMount]);
|
|
823
|
+
react.useEffect(() => {
|
|
824
|
+
onChangeRef.current = options.onChange;
|
|
825
|
+
}, [options.onChange]);
|
|
826
|
+
react.useEffect(() => {
|
|
827
|
+
onCameraChangeRef.current = options.onCameraChange;
|
|
828
|
+
}, [options.onCameraChange]);
|
|
829
|
+
react.useEffect(() => {
|
|
830
|
+
onToolChangeRef.current = options.onToolChange;
|
|
831
|
+
}, [options.onToolChange]);
|
|
832
|
+
react.useEffect(() => {
|
|
833
|
+
cameraOptionsRef.current = options.cameraOptions;
|
|
834
|
+
}, [options.cameraOptions]);
|
|
835
|
+
react.useEffect(() => {
|
|
836
|
+
touchOptionsRef.current = options.touchOptions;
|
|
837
|
+
}, [options.touchOptions]);
|
|
838
|
+
react.useEffect(() => {
|
|
839
|
+
keyboardShortcutsRef.current = options.keyboardShortcuts;
|
|
840
|
+
}, [options.keyboardShortcuts]);
|
|
841
|
+
react.useEffect(() => {
|
|
842
|
+
penOptionsRef.current = options.penOptions;
|
|
843
|
+
}, [options.penOptions]);
|
|
844
|
+
react.useEffect(() => {
|
|
845
|
+
readOnlyRef.current = options.readOnly ?? false;
|
|
846
|
+
}, [options.readOnly]);
|
|
800
847
|
react.useEffect(() => {
|
|
801
848
|
selectedShapeIdsRef.current = selectedShapeIds;
|
|
802
849
|
}, [selectedShapeIds]);
|
|
@@ -920,14 +967,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
920
967
|
const canvas = canvasRef.current;
|
|
921
968
|
if (!container || !canvas) return;
|
|
922
969
|
const initialTool = options.initialTool ?? "pen";
|
|
970
|
+
const cameraOpts = cameraOptionsRef.current;
|
|
971
|
+
const touchOpts = touchOptionsRef.current;
|
|
972
|
+
const toolShortcutMap = resolveToolShortcuts(keyboardShortcutsRef.current);
|
|
923
973
|
const editor = new core.Editor({
|
|
924
974
|
toolDefinitions: options.toolDefinitions,
|
|
925
|
-
initialToolId: initialTool
|
|
975
|
+
initialToolId: initialTool,
|
|
976
|
+
zoomRange: cameraOpts?.zoomRange
|
|
926
977
|
});
|
|
927
978
|
editor.renderer.setTheme(options.theme ?? "light");
|
|
928
979
|
if (!editor.tools.hasTool(initialTool)) {
|
|
929
980
|
editor.setCurrentTool("pen");
|
|
930
981
|
}
|
|
982
|
+
if (options.snapshot) {
|
|
983
|
+
editor.loadPersistenceSnapshot(options.snapshot);
|
|
984
|
+
}
|
|
931
985
|
let disposed = false;
|
|
932
986
|
let ignorePersistenceChanges = false;
|
|
933
987
|
let disposeMount;
|
|
@@ -1094,16 +1148,24 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1094
1148
|
render();
|
|
1095
1149
|
refreshSelectionBounds(editor);
|
|
1096
1150
|
};
|
|
1151
|
+
const emitCameraChange = () => {
|
|
1152
|
+
onCameraChangeRef.current?.({ ...editor.viewport });
|
|
1153
|
+
};
|
|
1097
1154
|
const touchInteractions = createTouchInteractionController(editor, canvas, {
|
|
1098
1155
|
cancelActivePointerInteraction,
|
|
1099
1156
|
refreshView: () => {
|
|
1100
1157
|
render();
|
|
1101
1158
|
refreshSelectionBounds(editor);
|
|
1159
|
+
emitCameraChange();
|
|
1102
1160
|
},
|
|
1103
1161
|
runUndo: () => applyDocumentChangeResult(editor.undo()),
|
|
1104
1162
|
runRedo: () => applyDocumentChangeResult(editor.redo()),
|
|
1105
|
-
isPenModeActive: () => penModeRef.current
|
|
1106
|
-
|
|
1163
|
+
isPenModeActive: () => penModeRef.current,
|
|
1164
|
+
getSlideOptions: () => ({
|
|
1165
|
+
enabled: cameraOptionsRef.current?.slideEnabled !== false,
|
|
1166
|
+
slideOptions: { friction: cameraOptionsRef.current?.slideFriction }
|
|
1167
|
+
})
|
|
1168
|
+
}, touchOpts);
|
|
1107
1169
|
const hasRealPressure = (pressure) => pressure != null && pressure > 0 && pressure !== 0.5;
|
|
1108
1170
|
const stopActiveSlide = () => {
|
|
1109
1171
|
if (activeCameraSlideRef.current) {
|
|
@@ -1113,8 +1175,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1113
1175
|
};
|
|
1114
1176
|
const handlePointerDown = (e) => {
|
|
1115
1177
|
if (!canvas.contains(e.target)) return;
|
|
1178
|
+
if (cameraOptionsRef.current?.locked && e.pointerType !== "pen") return;
|
|
1116
1179
|
stopActiveSlide();
|
|
1117
|
-
|
|
1180
|
+
const penAutoDetect = penOptionsRef.current?.autoDetect !== false;
|
|
1181
|
+
if (penAutoDetect && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1118
1182
|
penDetectedRef.current = true;
|
|
1119
1183
|
penModeRef.current = true;
|
|
1120
1184
|
}
|
|
@@ -1135,6 +1199,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1135
1199
|
if (activePointerIdsRef.current.size > 1) {
|
|
1136
1200
|
return;
|
|
1137
1201
|
}
|
|
1202
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(currentToolRef.current)) {
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1138
1205
|
isPointerActiveRef.current = true;
|
|
1139
1206
|
editor.beginHistoryEntry();
|
|
1140
1207
|
canvas.setPointerCapture(e.pointerId);
|
|
@@ -1142,7 +1209,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1142
1209
|
updatePointerPreview(e.clientX, e.clientY);
|
|
1143
1210
|
const first = sampleEvents(e)[0];
|
|
1144
1211
|
const { x, y } = getPagePoint(first);
|
|
1145
|
-
const
|
|
1212
|
+
const pressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1213
|
+
const pressure = (first.pressure ?? 0.5) * pressureSensitivity;
|
|
1146
1214
|
const isPen = first.pointerType === "pen" || hasRealPressure(first.pressure);
|
|
1147
1215
|
if (currentToolRef.current === "select") {
|
|
1148
1216
|
const hit = core.getTopShapeAtPoint(editor, { x, y });
|
|
@@ -1189,7 +1257,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1189
1257
|
refreshSelectionBounds(editor);
|
|
1190
1258
|
};
|
|
1191
1259
|
const handlePointerMove = (e) => {
|
|
1192
|
-
|
|
1260
|
+
const penAutoDetectOnMove = penOptionsRef.current?.autoDetect !== false;
|
|
1261
|
+
if (penAutoDetectOnMove && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1193
1262
|
penDetectedRef.current = true;
|
|
1194
1263
|
penModeRef.current = true;
|
|
1195
1264
|
}
|
|
@@ -1204,9 +1273,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1204
1273
|
const dx = prevClient ? e.clientX - prevClient.x : 0;
|
|
1205
1274
|
const dy = prevClient ? e.clientY - prevClient.y : 0;
|
|
1206
1275
|
lastPointerClientRef.current = { x: e.clientX, y: e.clientY };
|
|
1276
|
+
const movePressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1207
1277
|
for (const sample of sampleEvents(e)) {
|
|
1208
1278
|
const { x, y } = getPagePoint(sample);
|
|
1209
|
-
const pressure = sample.pressure ?? 0.5;
|
|
1279
|
+
const pressure = (sample.pressure ?? 0.5) * movePressureSensitivity;
|
|
1210
1280
|
const isPen = sample.pointerType === "pen" || hasRealPressure(sample.pressure);
|
|
1211
1281
|
editor.input.pointerMove(x, y, pressure, isPen);
|
|
1212
1282
|
}
|
|
@@ -1340,14 +1410,18 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1340
1410
|
editor.tools.pointerUp();
|
|
1341
1411
|
render();
|
|
1342
1412
|
refreshSelectionBounds(editor);
|
|
1343
|
-
if (handPanSession) {
|
|
1413
|
+
if (handPanSession && cameraOptionsRef.current?.slideEnabled !== false) {
|
|
1344
1414
|
activeCameraSlideRef.current = core.startCameraSlide(
|
|
1345
1415
|
handPanSession,
|
|
1346
|
-
(slideDx, slideDy) =>
|
|
1416
|
+
(slideDx, slideDy) => {
|
|
1417
|
+
editor.panBy(slideDx, slideDy);
|
|
1418
|
+
emitCameraChange();
|
|
1419
|
+
},
|
|
1347
1420
|
() => {
|
|
1348
1421
|
render();
|
|
1349
1422
|
refreshSelectionBounds(editor);
|
|
1350
|
-
}
|
|
1423
|
+
},
|
|
1424
|
+
{ friction: cameraOptionsRef.current?.slideFriction }
|
|
1351
1425
|
);
|
|
1352
1426
|
}
|
|
1353
1427
|
if (pendingRemoteDocumentRef.current) {
|
|
@@ -1392,41 +1466,59 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1392
1466
|
const handleWheel = (e) => {
|
|
1393
1467
|
if (!container.contains(e.target)) return;
|
|
1394
1468
|
e.preventDefault();
|
|
1469
|
+
const camOpts = cameraOptionsRef.current;
|
|
1470
|
+
if (camOpts?.locked) return;
|
|
1471
|
+
if (camOpts?.wheelBehavior === "none") return;
|
|
1395
1472
|
if (touchInteractions.isTrackpadZoomActive()) return;
|
|
1396
1473
|
const delta = normalizeWheelDelta(e);
|
|
1474
|
+
const panMultiplier = camOpts?.panSpeed ?? 1;
|
|
1475
|
+
const zoomMultiplier = camOpts?.zoomSpeed ?? 1;
|
|
1397
1476
|
if (delta.z !== 0) {
|
|
1398
1477
|
const rect = canvas.getBoundingClientRect();
|
|
1399
1478
|
const pointX = e.clientX - rect.left;
|
|
1400
1479
|
const pointY = e.clientY - rect.top;
|
|
1401
|
-
editor.zoomAt(Math.exp(delta.z), pointX, pointY);
|
|
1480
|
+
editor.zoomAt(Math.exp(delta.z * zoomMultiplier), pointX, pointY);
|
|
1402
1481
|
} else {
|
|
1403
|
-
editor.panBy(delta.x, delta.y);
|
|
1482
|
+
editor.panBy(delta.x * panMultiplier, delta.y * panMultiplier);
|
|
1404
1483
|
}
|
|
1405
1484
|
render();
|
|
1406
1485
|
refreshSelectionBounds(editor);
|
|
1486
|
+
emitCameraChange();
|
|
1407
1487
|
};
|
|
1408
1488
|
const handleGestureEvent = (e) => {
|
|
1409
1489
|
touchInteractions.handleGestureEvent(e, container);
|
|
1410
1490
|
};
|
|
1411
1491
|
const handleKeyDown = (e) => {
|
|
1492
|
+
if (keyboardShortcutsRef.current?.enabled === false) return;
|
|
1493
|
+
const isReadOnly = readOnlyRef.current;
|
|
1412
1494
|
handleKeyboardShortcutKeyDown(e, {
|
|
1413
|
-
isToolAvailable: (tool) =>
|
|
1495
|
+
isToolAvailable: (tool) => {
|
|
1496
|
+
if (isReadOnly && !VIEW_ONLY_TOOLS.has(tool)) return false;
|
|
1497
|
+
return editor.tools.hasTool(tool);
|
|
1498
|
+
},
|
|
1414
1499
|
setToolFromShortcut: (tool) => {
|
|
1415
1500
|
editor.setCurrentTool(tool);
|
|
1416
1501
|
setCurrentToolState(tool);
|
|
1417
1502
|
currentToolRef.current = tool;
|
|
1418
1503
|
if (tool !== "select") resetSelectUi();
|
|
1419
1504
|
render();
|
|
1505
|
+
onToolChangeRef.current?.(tool);
|
|
1506
|
+
},
|
|
1507
|
+
runHistoryShortcut: (shouldRedo) => {
|
|
1508
|
+
if (isReadOnly) return false;
|
|
1509
|
+
return applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo());
|
|
1510
|
+
},
|
|
1511
|
+
deleteSelection: () => {
|
|
1512
|
+
if (isReadOnly) return false;
|
|
1513
|
+
return currentToolRef.current === "select" ? deleteCurrentSelection() : false;
|
|
1420
1514
|
},
|
|
1421
|
-
runHistoryShortcut: (shouldRedo) => applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo()),
|
|
1422
|
-
deleteSelection: () => currentToolRef.current === "select" ? deleteCurrentSelection() : false,
|
|
1423
1515
|
dispatchKeyDown: (event) => {
|
|
1424
1516
|
editor.input.setModifiers(event.shiftKey, event.ctrlKey, event.metaKey);
|
|
1425
1517
|
editor.tools.keyDown({ key: event.key });
|
|
1426
1518
|
render();
|
|
1427
1519
|
},
|
|
1428
1520
|
dispatchKeyUp: () => void 0
|
|
1429
|
-
});
|
|
1521
|
+
}, toolShortcutMap);
|
|
1430
1522
|
};
|
|
1431
1523
|
const handleKeyUp = (e) => {
|
|
1432
1524
|
handleKeyboardShortcutKeyUp(e, {
|
|
@@ -1507,6 +1599,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1507
1599
|
const cleanupEditorListener = editor.listen(() => {
|
|
1508
1600
|
if (ignorePersistenceChanges) return;
|
|
1509
1601
|
schedulePersist();
|
|
1602
|
+
onChangeRef.current?.(editor.getDocumentSnapshot());
|
|
1510
1603
|
});
|
|
1511
1604
|
const cleanupHistoryListener = editor.listenHistory(() => {
|
|
1512
1605
|
syncHistoryState();
|
|
@@ -1573,6 +1666,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1573
1666
|
render();
|
|
1574
1667
|
}
|
|
1575
1668
|
});
|
|
1669
|
+
if (options.autoFocus !== false) {
|
|
1670
|
+
container.focus({ preventScroll: true });
|
|
1671
|
+
}
|
|
1576
1672
|
return () => {
|
|
1577
1673
|
disposed = true;
|
|
1578
1674
|
schedulePersistRef.current = null;
|
|
@@ -1621,10 +1717,12 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1621
1717
|
const editor = editorRef.current;
|
|
1622
1718
|
if (!editor) return;
|
|
1623
1719
|
if (!editor.tools.hasTool(tool)) return;
|
|
1720
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(tool)) return;
|
|
1624
1721
|
editor.setCurrentTool(tool);
|
|
1625
1722
|
setCurrentToolState(tool);
|
|
1626
1723
|
currentToolRef.current = tool;
|
|
1627
1724
|
if (tool !== "select") resetSelectUi();
|
|
1725
|
+
onToolChangeRef.current?.(tool);
|
|
1628
1726
|
},
|
|
1629
1727
|
[resetSelectUi]
|
|
1630
1728
|
);
|
|
@@ -1860,12 +1958,22 @@ function Tsdraw(props) {
|
|
|
1860
1958
|
initialTool,
|
|
1861
1959
|
theme: resolvedTheme,
|
|
1862
1960
|
persistenceKey: props.persistenceKey,
|
|
1863
|
-
onMount: props.onMount
|
|
1961
|
+
onMount: props.onMount,
|
|
1962
|
+
cameraOptions: props.cameraOptions,
|
|
1963
|
+
touchOptions: props.touchOptions,
|
|
1964
|
+
keyboardShortcuts: props.keyboardShortcuts,
|
|
1965
|
+
penOptions: props.penOptions,
|
|
1966
|
+
readOnly: props.readOnly,
|
|
1967
|
+
autoFocus: props.autoFocus,
|
|
1968
|
+
snapshot: props.snapshot,
|
|
1969
|
+
onChange: props.onChange,
|
|
1970
|
+
onCameraChange: props.onCameraChange,
|
|
1971
|
+
onToolChange: props.onToolChange
|
|
1864
1972
|
});
|
|
1865
1973
|
const toolbarPlacementStyle = resolvePlacementStyle(props.uiOptions?.toolbar?.placement, "bottom-center", 0, 14);
|
|
1866
1974
|
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8, 8);
|
|
1867
1975
|
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
1868
|
-
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true;
|
|
1976
|
+
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true || props.readOnly === true;
|
|
1869
1977
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1870
1978
|
const defaultToolOverlay = /* @__PURE__ */ jsxRuntime.jsx(
|
|
1871
1979
|
ToolOverlay,
|
|
@@ -1954,12 +2062,14 @@ function Tsdraw(props) {
|
|
|
1954
2062
|
"div",
|
|
1955
2063
|
{
|
|
1956
2064
|
ref: containerRef,
|
|
2065
|
+
tabIndex: 0,
|
|
1957
2066
|
className: `tsdraw tsdraw-${resolvedTheme}mode ${props.className ?? ""}`,
|
|
1958
2067
|
style: {
|
|
1959
2068
|
width: props.width ?? "100%",
|
|
1960
2069
|
height: props.height ?? "100%",
|
|
1961
2070
|
position: "relative",
|
|
1962
2071
|
overflow: "hidden",
|
|
2072
|
+
outline: "none",
|
|
1963
2073
|
...props.style
|
|
1964
2074
|
},
|
|
1965
2075
|
children: [
|