@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.d.cts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { ColorStyle, DashStyle, FillStyle, SizeStyle,
|
|
3
|
+
import { ZoomRange, ToolId, ColorStyle, DashStyle, FillStyle, SizeStyle, Editor, ToolDefinition, TsdrawEditorSnapshot, TsdrawDocumentSnapshot, Viewport } from '@tsdraw/core';
|
|
4
|
+
|
|
5
|
+
interface TsdrawCameraOptions {
|
|
6
|
+
panSpeed?: number;
|
|
7
|
+
zoomSpeed?: number;
|
|
8
|
+
zoomRange?: ZoomRange;
|
|
9
|
+
wheelBehavior?: 'pan' | 'zoom' | 'none';
|
|
10
|
+
slideEnabled?: boolean;
|
|
11
|
+
slideFriction?: number;
|
|
12
|
+
locked?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface TsdrawTouchOptions {
|
|
15
|
+
pinchToZoom?: boolean;
|
|
16
|
+
fingerPanInPenMode?: boolean;
|
|
17
|
+
tapUndoRedo?: boolean;
|
|
18
|
+
trackpadGestures?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface TsdrawKeyboardShortcutOptions {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
toolShortcuts?: Record<string, ToolId>;
|
|
23
|
+
overrideDefaults?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface TsdrawPenOptions {
|
|
26
|
+
pressureSensitivity?: number;
|
|
27
|
+
autoDetect?: boolean;
|
|
28
|
+
}
|
|
4
29
|
|
|
5
30
|
type TsdrawStylePanelPartItem = 'colors' | 'dashes' | 'fills' | 'sizes' | (string & {});
|
|
6
31
|
interface TsdrawStylePanelRenderContext {
|
|
@@ -123,6 +148,16 @@ interface TsdrawProps {
|
|
|
123
148
|
initialToolId?: ToolId;
|
|
124
149
|
uiOptions?: TsdrawUiOptions;
|
|
125
150
|
onMount?: (api: TsdrawMountApi) => void | (() => void);
|
|
151
|
+
cameraOptions?: TsdrawCameraOptions;
|
|
152
|
+
touchOptions?: TsdrawTouchOptions;
|
|
153
|
+
keyboardShortcuts?: TsdrawKeyboardShortcutOptions;
|
|
154
|
+
penOptions?: TsdrawPenOptions;
|
|
155
|
+
readOnly?: boolean;
|
|
156
|
+
autoFocus?: boolean;
|
|
157
|
+
snapshot?: TsdrawEditorSnapshot;
|
|
158
|
+
onChange?: (snapshot: TsdrawDocumentSnapshot) => void;
|
|
159
|
+
onCameraChange?: (viewport: Viewport) => void;
|
|
160
|
+
onToolChange?: (toolId: ToolId) => void;
|
|
126
161
|
}
|
|
127
162
|
type TsdrawCanvasProps = TsdrawProps;
|
|
128
163
|
declare function Tsdraw(props: TsdrawProps): react_jsx_runtime.JSX.Element;
|
|
@@ -148,4 +183,4 @@ interface ToolbarPart {
|
|
|
148
183
|
}
|
|
149
184
|
declare function getDefaultToolbarIcon(toolId: ToolId, isActive: boolean): ReactNode;
|
|
150
185
|
|
|
151
|
-
export { type ToolbarActionItem, type ToolbarPart, type ToolbarPartItem, type ToolbarRenderItem, type ToolbarToolItem, Tsdraw, TsdrawCanvas, type TsdrawCanvasProps, type TsdrawCursorContext, type TsdrawCustomElement, type TsdrawCustomElementRenderArgs, type TsdrawCustomTool, type TsdrawMountApi, type TsdrawProps, type TsdrawStylePanelCustomPart, type TsdrawStylePanelPartItem, type TsdrawStylePanelRenderContext, type TsdrawToolOverlayState, type TsdrawToolbarBuiltInAction, type TsdrawUiOptions, type TsdrawUiPlacement, type UiAnchor, getDefaultToolbarIcon };
|
|
186
|
+
export { type ToolbarActionItem, type ToolbarPart, type ToolbarPartItem, type ToolbarRenderItem, type ToolbarToolItem, Tsdraw, type TsdrawCameraOptions, TsdrawCanvas, type TsdrawCanvasProps, type TsdrawCursorContext, type TsdrawCustomElement, type TsdrawCustomElementRenderArgs, type TsdrawCustomTool, type TsdrawKeyboardShortcutOptions, type TsdrawMountApi, type TsdrawPenOptions, type TsdrawProps, type TsdrawStylePanelCustomPart, type TsdrawStylePanelPartItem, type TsdrawStylePanelRenderContext, type TsdrawToolOverlayState, type TsdrawToolbarBuiltInAction, type TsdrawTouchOptions, type TsdrawUiOptions, type TsdrawUiPlacement, type UiAnchor, getDefaultToolbarIcon };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode, CSSProperties } from 'react';
|
|
3
|
-
import { ColorStyle, DashStyle, FillStyle, SizeStyle,
|
|
3
|
+
import { ZoomRange, ToolId, ColorStyle, DashStyle, FillStyle, SizeStyle, Editor, ToolDefinition, TsdrawEditorSnapshot, TsdrawDocumentSnapshot, Viewport } from '@tsdraw/core';
|
|
4
|
+
|
|
5
|
+
interface TsdrawCameraOptions {
|
|
6
|
+
panSpeed?: number;
|
|
7
|
+
zoomSpeed?: number;
|
|
8
|
+
zoomRange?: ZoomRange;
|
|
9
|
+
wheelBehavior?: 'pan' | 'zoom' | 'none';
|
|
10
|
+
slideEnabled?: boolean;
|
|
11
|
+
slideFriction?: number;
|
|
12
|
+
locked?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface TsdrawTouchOptions {
|
|
15
|
+
pinchToZoom?: boolean;
|
|
16
|
+
fingerPanInPenMode?: boolean;
|
|
17
|
+
tapUndoRedo?: boolean;
|
|
18
|
+
trackpadGestures?: boolean;
|
|
19
|
+
}
|
|
20
|
+
interface TsdrawKeyboardShortcutOptions {
|
|
21
|
+
enabled?: boolean;
|
|
22
|
+
toolShortcuts?: Record<string, ToolId>;
|
|
23
|
+
overrideDefaults?: boolean;
|
|
24
|
+
}
|
|
25
|
+
interface TsdrawPenOptions {
|
|
26
|
+
pressureSensitivity?: number;
|
|
27
|
+
autoDetect?: boolean;
|
|
28
|
+
}
|
|
4
29
|
|
|
5
30
|
type TsdrawStylePanelPartItem = 'colors' | 'dashes' | 'fills' | 'sizes' | (string & {});
|
|
6
31
|
interface TsdrawStylePanelRenderContext {
|
|
@@ -123,6 +148,16 @@ interface TsdrawProps {
|
|
|
123
148
|
initialToolId?: ToolId;
|
|
124
149
|
uiOptions?: TsdrawUiOptions;
|
|
125
150
|
onMount?: (api: TsdrawMountApi) => void | (() => void);
|
|
151
|
+
cameraOptions?: TsdrawCameraOptions;
|
|
152
|
+
touchOptions?: TsdrawTouchOptions;
|
|
153
|
+
keyboardShortcuts?: TsdrawKeyboardShortcutOptions;
|
|
154
|
+
penOptions?: TsdrawPenOptions;
|
|
155
|
+
readOnly?: boolean;
|
|
156
|
+
autoFocus?: boolean;
|
|
157
|
+
snapshot?: TsdrawEditorSnapshot;
|
|
158
|
+
onChange?: (snapshot: TsdrawDocumentSnapshot) => void;
|
|
159
|
+
onCameraChange?: (viewport: Viewport) => void;
|
|
160
|
+
onToolChange?: (toolId: ToolId) => void;
|
|
126
161
|
}
|
|
127
162
|
type TsdrawCanvasProps = TsdrawProps;
|
|
128
163
|
declare function Tsdraw(props: TsdrawProps): react_jsx_runtime.JSX.Element;
|
|
@@ -148,4 +183,4 @@ interface ToolbarPart {
|
|
|
148
183
|
}
|
|
149
184
|
declare function getDefaultToolbarIcon(toolId: ToolId, isActive: boolean): ReactNode;
|
|
150
185
|
|
|
151
|
-
export { type ToolbarActionItem, type ToolbarPart, type ToolbarPartItem, type ToolbarRenderItem, type ToolbarToolItem, Tsdraw, TsdrawCanvas, type TsdrawCanvasProps, type TsdrawCursorContext, type TsdrawCustomElement, type TsdrawCustomElementRenderArgs, type TsdrawCustomTool, type TsdrawMountApi, type TsdrawProps, type TsdrawStylePanelCustomPart, type TsdrawStylePanelPartItem, type TsdrawStylePanelRenderContext, type TsdrawToolOverlayState, type TsdrawToolbarBuiltInAction, type TsdrawUiOptions, type TsdrawUiPlacement, type UiAnchor, getDefaultToolbarIcon };
|
|
186
|
+
export { type ToolbarActionItem, type ToolbarPart, type ToolbarPartItem, type ToolbarRenderItem, type ToolbarToolItem, Tsdraw, type TsdrawCameraOptions, TsdrawCanvas, type TsdrawCanvasProps, type TsdrawCursorContext, type TsdrawCustomElement, type TsdrawCustomElementRenderArgs, type TsdrawCustomTool, type TsdrawKeyboardShortcutOptions, type TsdrawMountApi, type TsdrawPenOptions, type TsdrawProps, type TsdrawStylePanelCustomPart, type TsdrawStylePanelPartItem, type TsdrawStylePanelRenderContext, type TsdrawToolOverlayState, type TsdrawToolbarBuiltInAction, type TsdrawTouchOptions, type TsdrawUiOptions, type TsdrawUiPlacement, type UiAnchor, getDefaultToolbarIcon };
|
package/dist/index.js
CHANGED
|
@@ -301,7 +301,11 @@ var TAP_MOVE_TOLERANCE = 14;
|
|
|
301
301
|
var PINCH_MODE_ZOOM_DISTANCE = 24;
|
|
302
302
|
var PINCH_MODE_PAN_DISTANCE = 16;
|
|
303
303
|
var PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE = 64;
|
|
304
|
-
function createTouchInteractionController(editor, canvas, handlers) {
|
|
304
|
+
function createTouchInteractionController(editor, canvas, handlers, touchOptions) {
|
|
305
|
+
const allowPinchZoom = touchOptions?.pinchToZoom !== false;
|
|
306
|
+
const allowFingerPan = touchOptions?.fingerPanInPenMode !== false;
|
|
307
|
+
const allowTapUndoRedo = touchOptions?.tapUndoRedo !== false;
|
|
308
|
+
const allowTrackpadGestures = touchOptions?.trackpadGestures !== false;
|
|
305
309
|
const activeTouchPoints = /* @__PURE__ */ new Map();
|
|
306
310
|
const touchTapState = {
|
|
307
311
|
active: false,
|
|
@@ -343,7 +347,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
343
347
|
if (activeTouchPoints.size > 0) return;
|
|
344
348
|
if (!touchTapState.active) return;
|
|
345
349
|
const elapsed = performance.now() - touchTapState.startTime;
|
|
346
|
-
if (!touchTapState.moved && elapsed <= TAP_MAX_DURATION_MS && (touchTapState.maxTouchCount === 2 || touchTapState.maxTouchCount === 3)) {
|
|
350
|
+
if (allowTapUndoRedo && !touchTapState.moved && elapsed <= TAP_MAX_DURATION_MS && (touchTapState.maxTouchCount === 2 || touchTapState.maxTouchCount === 3)) {
|
|
347
351
|
const fingerCount = touchTapState.maxTouchCount;
|
|
348
352
|
const now = performance.now();
|
|
349
353
|
const previousTapTime = touchTapState.lastTapAtByCount[fingerCount] ?? 0;
|
|
@@ -391,9 +395,9 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
391
395
|
const touchDistance = Math.abs(distance - touchCameraState.initialDistance);
|
|
392
396
|
const originDistance = Math.hypot(center.x - touchCameraState.initialCenter.x, center.y - touchCameraState.initialCenter.y);
|
|
393
397
|
if (touchCameraState.mode === "not-sure") {
|
|
394
|
-
if (touchDistance > PINCH_MODE_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
398
|
+
if (allowPinchZoom && touchDistance > PINCH_MODE_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
395
399
|
else if (originDistance > PINCH_MODE_PAN_DISTANCE) touchCameraState.mode = "panning";
|
|
396
|
-
} else if (touchCameraState.mode === "panning" && touchDistance > PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
400
|
+
} else if (allowPinchZoom && touchCameraState.mode === "panning" && touchDistance > PINCH_MODE_SWITCH_TO_ZOOM_DISTANCE) touchCameraState.mode = "zooming";
|
|
397
401
|
const canvasRect = canvas.getBoundingClientRect();
|
|
398
402
|
const centerOnCanvasX = center.x - canvasRect.left;
|
|
399
403
|
const centerOnCanvasY = center.y - canvasRect.top;
|
|
@@ -434,7 +438,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
434
438
|
beginTouchCameraGesture();
|
|
435
439
|
return true;
|
|
436
440
|
}
|
|
437
|
-
if (handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
441
|
+
if (allowFingerPan && handlers.isPenModeActive() && activeTouchPoints.size === 1) {
|
|
438
442
|
handlers.cancelActivePointerInteraction();
|
|
439
443
|
fingerPanPointerId = event.pointerId;
|
|
440
444
|
fingerPanSession = beginCameraPan(editor.viewport, event.clientX, event.clientY);
|
|
@@ -469,11 +473,15 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
469
473
|
if (wasFingerPan) {
|
|
470
474
|
endFingerPan();
|
|
471
475
|
if (releasedPanSession) {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
476
|
+
const slideConfig = handlers.getSlideOptions();
|
|
477
|
+
if (slideConfig.enabled) {
|
|
478
|
+
fingerPanSlide = startCameraSlide(
|
|
479
|
+
releasedPanSession,
|
|
480
|
+
(dx, dy) => editor.panBy(dx, dy),
|
|
481
|
+
() => handlers.refreshView(),
|
|
482
|
+
slideConfig.slideOptions
|
|
483
|
+
);
|
|
484
|
+
}
|
|
477
485
|
}
|
|
478
486
|
}
|
|
479
487
|
maybeHandleTouchTapGesture();
|
|
@@ -484,6 +492,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
484
492
|
const handleGestureEvent = (event, container) => {
|
|
485
493
|
if (!container.contains(event.target)) return;
|
|
486
494
|
event.preventDefault();
|
|
495
|
+
if (!allowTrackpadGestures) return;
|
|
487
496
|
const gestureEvent = event;
|
|
488
497
|
if (gestureEvent.scale == null) return;
|
|
489
498
|
if (event.type === "gesturestart") {
|
|
@@ -527,7 +536,7 @@ function createTouchInteractionController(editor, canvas, handlers) {
|
|
|
527
536
|
}
|
|
528
537
|
|
|
529
538
|
// src/canvas/keyboardShortcuts.ts
|
|
530
|
-
var
|
|
539
|
+
var DEFAULT_TOOL_SHORTCUTS = {
|
|
531
540
|
v: "select",
|
|
532
541
|
h: "hand",
|
|
533
542
|
e: "eraser",
|
|
@@ -539,6 +548,11 @@ var TOOL_SHORTCUTS = {
|
|
|
539
548
|
o: "circle",
|
|
540
549
|
c: "circle"
|
|
541
550
|
};
|
|
551
|
+
function resolveToolShortcuts(shortcutOptions) {
|
|
552
|
+
if (!shortcutOptions?.toolShortcuts) return DEFAULT_TOOL_SHORTCUTS;
|
|
553
|
+
if (shortcutOptions.overrideDefaults) return { ...shortcutOptions.toolShortcuts };
|
|
554
|
+
return { ...DEFAULT_TOOL_SHORTCUTS, ...shortcutOptions.toolShortcuts };
|
|
555
|
+
}
|
|
542
556
|
function isEditableTarget(eventTarget) {
|
|
543
557
|
const element = eventTarget;
|
|
544
558
|
if (!element) return false;
|
|
@@ -546,7 +560,7 @@ function isEditableTarget(eventTarget) {
|
|
|
546
560
|
const tagName = element.tagName;
|
|
547
561
|
return tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT";
|
|
548
562
|
}
|
|
549
|
-
function handleKeyboardShortcutKeyDown(event, handlers) {
|
|
563
|
+
function handleKeyboardShortcutKeyDown(event, handlers, toolShortcutMap = DEFAULT_TOOL_SHORTCUTS) {
|
|
550
564
|
if (isEditableTarget(event.target)) return;
|
|
551
565
|
const loweredKey = event.key.toLowerCase();
|
|
552
566
|
const isMetaPressed = event.metaKey || event.ctrlKey;
|
|
@@ -559,7 +573,7 @@ function handleKeyboardShortcutKeyDown(event, handlers) {
|
|
|
559
573
|
}
|
|
560
574
|
}
|
|
561
575
|
if (!isMetaPressed && !event.altKey) {
|
|
562
|
-
const nextToolId =
|
|
576
|
+
const nextToolId = toolShortcutMap[loweredKey];
|
|
563
577
|
if (nextToolId && handlers.isToolAvailable(nextToolId)) {
|
|
564
578
|
handlers.setToolFromShortcut(nextToolId);
|
|
565
579
|
event.preventDefault();
|
|
@@ -734,8 +748,17 @@ function getHandlePagePoint(bounds, handle) {
|
|
|
734
748
|
}
|
|
735
749
|
}
|
|
736
750
|
var ZOOM_WHEEL_CAP = 10;
|
|
751
|
+
var VIEW_ONLY_TOOLS = /* @__PURE__ */ new Set(["select", "hand"]);
|
|
737
752
|
function useTsdrawCanvasController(options = {}) {
|
|
738
753
|
const onMountRef = useRef(options.onMount);
|
|
754
|
+
const onChangeRef = useRef(options.onChange);
|
|
755
|
+
const onCameraChangeRef = useRef(options.onCameraChange);
|
|
756
|
+
const onToolChangeRef = useRef(options.onToolChange);
|
|
757
|
+
const cameraOptionsRef = useRef(options.cameraOptions);
|
|
758
|
+
const touchOptionsRef = useRef(options.touchOptions);
|
|
759
|
+
const keyboardShortcutsRef = useRef(options.keyboardShortcuts);
|
|
760
|
+
const penOptionsRef = useRef(options.penOptions);
|
|
761
|
+
const readOnlyRef = useRef(options.readOnly ?? false);
|
|
739
762
|
const containerRef = useRef(null);
|
|
740
763
|
const canvasRef = useRef(null);
|
|
741
764
|
const editorRef = useRef(null);
|
|
@@ -795,6 +818,30 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
795
818
|
useEffect(() => {
|
|
796
819
|
onMountRef.current = options.onMount;
|
|
797
820
|
}, [options.onMount]);
|
|
821
|
+
useEffect(() => {
|
|
822
|
+
onChangeRef.current = options.onChange;
|
|
823
|
+
}, [options.onChange]);
|
|
824
|
+
useEffect(() => {
|
|
825
|
+
onCameraChangeRef.current = options.onCameraChange;
|
|
826
|
+
}, [options.onCameraChange]);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
onToolChangeRef.current = options.onToolChange;
|
|
829
|
+
}, [options.onToolChange]);
|
|
830
|
+
useEffect(() => {
|
|
831
|
+
cameraOptionsRef.current = options.cameraOptions;
|
|
832
|
+
}, [options.cameraOptions]);
|
|
833
|
+
useEffect(() => {
|
|
834
|
+
touchOptionsRef.current = options.touchOptions;
|
|
835
|
+
}, [options.touchOptions]);
|
|
836
|
+
useEffect(() => {
|
|
837
|
+
keyboardShortcutsRef.current = options.keyboardShortcuts;
|
|
838
|
+
}, [options.keyboardShortcuts]);
|
|
839
|
+
useEffect(() => {
|
|
840
|
+
penOptionsRef.current = options.penOptions;
|
|
841
|
+
}, [options.penOptions]);
|
|
842
|
+
useEffect(() => {
|
|
843
|
+
readOnlyRef.current = options.readOnly ?? false;
|
|
844
|
+
}, [options.readOnly]);
|
|
798
845
|
useEffect(() => {
|
|
799
846
|
selectedShapeIdsRef.current = selectedShapeIds;
|
|
800
847
|
}, [selectedShapeIds]);
|
|
@@ -918,14 +965,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
918
965
|
const canvas = canvasRef.current;
|
|
919
966
|
if (!container || !canvas) return;
|
|
920
967
|
const initialTool = options.initialTool ?? "pen";
|
|
968
|
+
const cameraOpts = cameraOptionsRef.current;
|
|
969
|
+
const touchOpts = touchOptionsRef.current;
|
|
970
|
+
const toolShortcutMap = resolveToolShortcuts(keyboardShortcutsRef.current);
|
|
921
971
|
const editor = new Editor({
|
|
922
972
|
toolDefinitions: options.toolDefinitions,
|
|
923
|
-
initialToolId: initialTool
|
|
973
|
+
initialToolId: initialTool,
|
|
974
|
+
zoomRange: cameraOpts?.zoomRange
|
|
924
975
|
});
|
|
925
976
|
editor.renderer.setTheme(options.theme ?? "light");
|
|
926
977
|
if (!editor.tools.hasTool(initialTool)) {
|
|
927
978
|
editor.setCurrentTool("pen");
|
|
928
979
|
}
|
|
980
|
+
if (options.snapshot) {
|
|
981
|
+
editor.loadPersistenceSnapshot(options.snapshot);
|
|
982
|
+
}
|
|
929
983
|
let disposed = false;
|
|
930
984
|
let ignorePersistenceChanges = false;
|
|
931
985
|
let disposeMount;
|
|
@@ -1092,16 +1146,24 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1092
1146
|
render();
|
|
1093
1147
|
refreshSelectionBounds(editor);
|
|
1094
1148
|
};
|
|
1149
|
+
const emitCameraChange = () => {
|
|
1150
|
+
onCameraChangeRef.current?.({ ...editor.viewport });
|
|
1151
|
+
};
|
|
1095
1152
|
const touchInteractions = createTouchInteractionController(editor, canvas, {
|
|
1096
1153
|
cancelActivePointerInteraction,
|
|
1097
1154
|
refreshView: () => {
|
|
1098
1155
|
render();
|
|
1099
1156
|
refreshSelectionBounds(editor);
|
|
1157
|
+
emitCameraChange();
|
|
1100
1158
|
},
|
|
1101
1159
|
runUndo: () => applyDocumentChangeResult(editor.undo()),
|
|
1102
1160
|
runRedo: () => applyDocumentChangeResult(editor.redo()),
|
|
1103
|
-
isPenModeActive: () => penModeRef.current
|
|
1104
|
-
|
|
1161
|
+
isPenModeActive: () => penModeRef.current,
|
|
1162
|
+
getSlideOptions: () => ({
|
|
1163
|
+
enabled: cameraOptionsRef.current?.slideEnabled !== false,
|
|
1164
|
+
slideOptions: { friction: cameraOptionsRef.current?.slideFriction }
|
|
1165
|
+
})
|
|
1166
|
+
}, touchOpts);
|
|
1105
1167
|
const hasRealPressure = (pressure) => pressure != null && pressure > 0 && pressure !== 0.5;
|
|
1106
1168
|
const stopActiveSlide = () => {
|
|
1107
1169
|
if (activeCameraSlideRef.current) {
|
|
@@ -1111,8 +1173,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1111
1173
|
};
|
|
1112
1174
|
const handlePointerDown = (e) => {
|
|
1113
1175
|
if (!canvas.contains(e.target)) return;
|
|
1176
|
+
if (cameraOptionsRef.current?.locked && e.pointerType !== "pen") return;
|
|
1114
1177
|
stopActiveSlide();
|
|
1115
|
-
|
|
1178
|
+
const penAutoDetect = penOptionsRef.current?.autoDetect !== false;
|
|
1179
|
+
if (penAutoDetect && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1116
1180
|
penDetectedRef.current = true;
|
|
1117
1181
|
penModeRef.current = true;
|
|
1118
1182
|
}
|
|
@@ -1133,6 +1197,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1133
1197
|
if (activePointerIdsRef.current.size > 1) {
|
|
1134
1198
|
return;
|
|
1135
1199
|
}
|
|
1200
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(currentToolRef.current)) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1136
1203
|
isPointerActiveRef.current = true;
|
|
1137
1204
|
editor.beginHistoryEntry();
|
|
1138
1205
|
canvas.setPointerCapture(e.pointerId);
|
|
@@ -1140,7 +1207,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1140
1207
|
updatePointerPreview(e.clientX, e.clientY);
|
|
1141
1208
|
const first = sampleEvents(e)[0];
|
|
1142
1209
|
const { x, y } = getPagePoint(first);
|
|
1143
|
-
const
|
|
1210
|
+
const pressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1211
|
+
const pressure = (first.pressure ?? 0.5) * pressureSensitivity;
|
|
1144
1212
|
const isPen = first.pointerType === "pen" || hasRealPressure(first.pressure);
|
|
1145
1213
|
if (currentToolRef.current === "select") {
|
|
1146
1214
|
const hit = getTopShapeAtPoint(editor, { x, y });
|
|
@@ -1187,7 +1255,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1187
1255
|
refreshSelectionBounds(editor);
|
|
1188
1256
|
};
|
|
1189
1257
|
const handlePointerMove = (e) => {
|
|
1190
|
-
|
|
1258
|
+
const penAutoDetectOnMove = penOptionsRef.current?.autoDetect !== false;
|
|
1259
|
+
if (penAutoDetectOnMove && !penDetectedRef.current && (e.pointerType === "pen" || hasRealPressure(e.pressure))) {
|
|
1191
1260
|
penDetectedRef.current = true;
|
|
1192
1261
|
penModeRef.current = true;
|
|
1193
1262
|
}
|
|
@@ -1202,9 +1271,10 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1202
1271
|
const dx = prevClient ? e.clientX - prevClient.x : 0;
|
|
1203
1272
|
const dy = prevClient ? e.clientY - prevClient.y : 0;
|
|
1204
1273
|
lastPointerClientRef.current = { x: e.clientX, y: e.clientY };
|
|
1274
|
+
const movePressureSensitivity = penOptionsRef.current?.pressureSensitivity ?? 1;
|
|
1205
1275
|
for (const sample of sampleEvents(e)) {
|
|
1206
1276
|
const { x, y } = getPagePoint(sample);
|
|
1207
|
-
const pressure = sample.pressure ?? 0.5;
|
|
1277
|
+
const pressure = (sample.pressure ?? 0.5) * movePressureSensitivity;
|
|
1208
1278
|
const isPen = sample.pointerType === "pen" || hasRealPressure(sample.pressure);
|
|
1209
1279
|
editor.input.pointerMove(x, y, pressure, isPen);
|
|
1210
1280
|
}
|
|
@@ -1338,14 +1408,18 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1338
1408
|
editor.tools.pointerUp();
|
|
1339
1409
|
render();
|
|
1340
1410
|
refreshSelectionBounds(editor);
|
|
1341
|
-
if (handPanSession) {
|
|
1411
|
+
if (handPanSession && cameraOptionsRef.current?.slideEnabled !== false) {
|
|
1342
1412
|
activeCameraSlideRef.current = startCameraSlide(
|
|
1343
1413
|
handPanSession,
|
|
1344
|
-
(slideDx, slideDy) =>
|
|
1414
|
+
(slideDx, slideDy) => {
|
|
1415
|
+
editor.panBy(slideDx, slideDy);
|
|
1416
|
+
emitCameraChange();
|
|
1417
|
+
},
|
|
1345
1418
|
() => {
|
|
1346
1419
|
render();
|
|
1347
1420
|
refreshSelectionBounds(editor);
|
|
1348
|
-
}
|
|
1421
|
+
},
|
|
1422
|
+
{ friction: cameraOptionsRef.current?.slideFriction }
|
|
1349
1423
|
);
|
|
1350
1424
|
}
|
|
1351
1425
|
if (pendingRemoteDocumentRef.current) {
|
|
@@ -1390,41 +1464,59 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1390
1464
|
const handleWheel = (e) => {
|
|
1391
1465
|
if (!container.contains(e.target)) return;
|
|
1392
1466
|
e.preventDefault();
|
|
1467
|
+
const camOpts = cameraOptionsRef.current;
|
|
1468
|
+
if (camOpts?.locked) return;
|
|
1469
|
+
if (camOpts?.wheelBehavior === "none") return;
|
|
1393
1470
|
if (touchInteractions.isTrackpadZoomActive()) return;
|
|
1394
1471
|
const delta = normalizeWheelDelta(e);
|
|
1472
|
+
const panMultiplier = camOpts?.panSpeed ?? 1;
|
|
1473
|
+
const zoomMultiplier = camOpts?.zoomSpeed ?? 1;
|
|
1395
1474
|
if (delta.z !== 0) {
|
|
1396
1475
|
const rect = canvas.getBoundingClientRect();
|
|
1397
1476
|
const pointX = e.clientX - rect.left;
|
|
1398
1477
|
const pointY = e.clientY - rect.top;
|
|
1399
|
-
editor.zoomAt(Math.exp(delta.z), pointX, pointY);
|
|
1478
|
+
editor.zoomAt(Math.exp(delta.z * zoomMultiplier), pointX, pointY);
|
|
1400
1479
|
} else {
|
|
1401
|
-
editor.panBy(delta.x, delta.y);
|
|
1480
|
+
editor.panBy(delta.x * panMultiplier, delta.y * panMultiplier);
|
|
1402
1481
|
}
|
|
1403
1482
|
render();
|
|
1404
1483
|
refreshSelectionBounds(editor);
|
|
1484
|
+
emitCameraChange();
|
|
1405
1485
|
};
|
|
1406
1486
|
const handleGestureEvent = (e) => {
|
|
1407
1487
|
touchInteractions.handleGestureEvent(e, container);
|
|
1408
1488
|
};
|
|
1409
1489
|
const handleKeyDown = (e) => {
|
|
1490
|
+
if (keyboardShortcutsRef.current?.enabled === false) return;
|
|
1491
|
+
const isReadOnly = readOnlyRef.current;
|
|
1410
1492
|
handleKeyboardShortcutKeyDown(e, {
|
|
1411
|
-
isToolAvailable: (tool) =>
|
|
1493
|
+
isToolAvailable: (tool) => {
|
|
1494
|
+
if (isReadOnly && !VIEW_ONLY_TOOLS.has(tool)) return false;
|
|
1495
|
+
return editor.tools.hasTool(tool);
|
|
1496
|
+
},
|
|
1412
1497
|
setToolFromShortcut: (tool) => {
|
|
1413
1498
|
editor.setCurrentTool(tool);
|
|
1414
1499
|
setCurrentToolState(tool);
|
|
1415
1500
|
currentToolRef.current = tool;
|
|
1416
1501
|
if (tool !== "select") resetSelectUi();
|
|
1417
1502
|
render();
|
|
1503
|
+
onToolChangeRef.current?.(tool);
|
|
1504
|
+
},
|
|
1505
|
+
runHistoryShortcut: (shouldRedo) => {
|
|
1506
|
+
if (isReadOnly) return false;
|
|
1507
|
+
return applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo());
|
|
1508
|
+
},
|
|
1509
|
+
deleteSelection: () => {
|
|
1510
|
+
if (isReadOnly) return false;
|
|
1511
|
+
return currentToolRef.current === "select" ? deleteCurrentSelection() : false;
|
|
1418
1512
|
},
|
|
1419
|
-
runHistoryShortcut: (shouldRedo) => applyDocumentChangeResult(shouldRedo ? editor.redo() : editor.undo()),
|
|
1420
|
-
deleteSelection: () => currentToolRef.current === "select" ? deleteCurrentSelection() : false,
|
|
1421
1513
|
dispatchKeyDown: (event) => {
|
|
1422
1514
|
editor.input.setModifiers(event.shiftKey, event.ctrlKey, event.metaKey);
|
|
1423
1515
|
editor.tools.keyDown({ key: event.key });
|
|
1424
1516
|
render();
|
|
1425
1517
|
},
|
|
1426
1518
|
dispatchKeyUp: () => void 0
|
|
1427
|
-
});
|
|
1519
|
+
}, toolShortcutMap);
|
|
1428
1520
|
};
|
|
1429
1521
|
const handleKeyUp = (e) => {
|
|
1430
1522
|
handleKeyboardShortcutKeyUp(e, {
|
|
@@ -1505,6 +1597,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1505
1597
|
const cleanupEditorListener = editor.listen(() => {
|
|
1506
1598
|
if (ignorePersistenceChanges) return;
|
|
1507
1599
|
schedulePersist();
|
|
1600
|
+
onChangeRef.current?.(editor.getDocumentSnapshot());
|
|
1508
1601
|
});
|
|
1509
1602
|
const cleanupHistoryListener = editor.listenHistory(() => {
|
|
1510
1603
|
syncHistoryState();
|
|
@@ -1571,6 +1664,9 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1571
1664
|
render();
|
|
1572
1665
|
}
|
|
1573
1666
|
});
|
|
1667
|
+
if (options.autoFocus !== false) {
|
|
1668
|
+
container.focus({ preventScroll: true });
|
|
1669
|
+
}
|
|
1574
1670
|
return () => {
|
|
1575
1671
|
disposed = true;
|
|
1576
1672
|
schedulePersistRef.current = null;
|
|
@@ -1619,10 +1715,12 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1619
1715
|
const editor = editorRef.current;
|
|
1620
1716
|
if (!editor) return;
|
|
1621
1717
|
if (!editor.tools.hasTool(tool)) return;
|
|
1718
|
+
if (readOnlyRef.current && !VIEW_ONLY_TOOLS.has(tool)) return;
|
|
1622
1719
|
editor.setCurrentTool(tool);
|
|
1623
1720
|
setCurrentToolState(tool);
|
|
1624
1721
|
currentToolRef.current = tool;
|
|
1625
1722
|
if (tool !== "select") resetSelectUi();
|
|
1723
|
+
onToolChangeRef.current?.(tool);
|
|
1626
1724
|
},
|
|
1627
1725
|
[resetSelectUi]
|
|
1628
1726
|
);
|
|
@@ -1858,12 +1956,22 @@ function Tsdraw(props) {
|
|
|
1858
1956
|
initialTool,
|
|
1859
1957
|
theme: resolvedTheme,
|
|
1860
1958
|
persistenceKey: props.persistenceKey,
|
|
1861
|
-
onMount: props.onMount
|
|
1959
|
+
onMount: props.onMount,
|
|
1960
|
+
cameraOptions: props.cameraOptions,
|
|
1961
|
+
touchOptions: props.touchOptions,
|
|
1962
|
+
keyboardShortcuts: props.keyboardShortcuts,
|
|
1963
|
+
penOptions: props.penOptions,
|
|
1964
|
+
readOnly: props.readOnly,
|
|
1965
|
+
autoFocus: props.autoFocus,
|
|
1966
|
+
snapshot: props.snapshot,
|
|
1967
|
+
onChange: props.onChange,
|
|
1968
|
+
onCameraChange: props.onCameraChange,
|
|
1969
|
+
onToolChange: props.onToolChange
|
|
1862
1970
|
});
|
|
1863
1971
|
const toolbarPlacementStyle = resolvePlacementStyle(props.uiOptions?.toolbar?.placement, "bottom-center", 0, 14);
|
|
1864
1972
|
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8, 8);
|
|
1865
1973
|
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
1866
|
-
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true;
|
|
1974
|
+
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true || props.readOnly === true;
|
|
1867
1975
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1868
1976
|
const defaultToolOverlay = /* @__PURE__ */ jsx(
|
|
1869
1977
|
ToolOverlay,
|
|
@@ -1952,12 +2060,14 @@ function Tsdraw(props) {
|
|
|
1952
2060
|
"div",
|
|
1953
2061
|
{
|
|
1954
2062
|
ref: containerRef,
|
|
2063
|
+
tabIndex: 0,
|
|
1955
2064
|
className: `tsdraw tsdraw-${resolvedTheme}mode ${props.className ?? ""}`,
|
|
1956
2065
|
style: {
|
|
1957
2066
|
width: props.width ?? "100%",
|
|
1958
2067
|
height: props.height ?? "100%",
|
|
1959
2068
|
position: "relative",
|
|
1960
2069
|
overflow: "hidden",
|
|
2070
|
+
outline: "none",
|
|
1961
2071
|
...props.style
|
|
1962
2072
|
},
|
|
1963
2073
|
children: [
|