@tsdraw/react 0.6.1 → 0.7.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 +227 -102
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +27 -4
- package/dist/index.d.ts +27 -4
- package/dist/index.js +229 -104
- package/dist/index.js.map +1 -1
- package/dist/tsdraw.css +26 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { useState, useMemo, useEffect,
|
|
1
|
+
import { useState, useMemo, useEffect, useCallback, useRef } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { DEFAULT_COLORS, getSelectionBoundsPage, buildTransformSnapshots, Editor, STROKE_WIDTHS, ERASER_MARGIN, resolveThemeColor, getTopShapeAtPoint, buildStartPositions, applyRotation, applyResize, applyMove, normalizeSelectionBounds, getShapesInBounds, isSelectTool } from '@tsdraw/core';
|
|
4
|
-
import { IconPointer, IconPencil, IconEraser, IconHandStop, IconArrowBackUp, IconArrowForwardUp } from '@tabler/icons-react';
|
|
4
|
+
import { IconPointer, IconPencil, IconSquare, IconCircle, IconEraser, IconHandStop, IconArrowBackUp, IconArrowForwardUp } from '@tabler/icons-react';
|
|
5
5
|
|
|
6
6
|
// src/components/TsdrawCanvas.tsx
|
|
7
7
|
function SelectionOverlay({
|
|
@@ -98,66 +98,106 @@ function SelectionOverlay({
|
|
|
98
98
|
}
|
|
99
99
|
var STYLE_COLORS = Object.entries(DEFAULT_COLORS).filter(([key]) => key !== "white").map(([value]) => ({ value }));
|
|
100
100
|
var STYLE_DASHES = ["draw", "solid", "dashed", "dotted"];
|
|
101
|
+
var STYLE_FILLS = ["none", "blank", "semi", "solid"];
|
|
101
102
|
var STYLE_SIZES = ["s", "m", "l", "xl"];
|
|
102
103
|
function StylePanel({
|
|
103
104
|
visible,
|
|
105
|
+
parts,
|
|
106
|
+
customParts,
|
|
104
107
|
style,
|
|
105
108
|
theme,
|
|
106
109
|
drawColor,
|
|
107
110
|
drawDash,
|
|
111
|
+
drawFill,
|
|
108
112
|
drawSize,
|
|
109
113
|
onColorSelect,
|
|
110
114
|
onDashSelect,
|
|
115
|
+
onFillSelect,
|
|
111
116
|
onSizeSelect
|
|
112
117
|
}) {
|
|
113
|
-
if (!visible) return null;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
"
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
118
|
+
if (!visible || parts.length === 0) return null;
|
|
119
|
+
const context = {
|
|
120
|
+
drawColor,
|
|
121
|
+
drawDash,
|
|
122
|
+
drawFill,
|
|
123
|
+
drawSize,
|
|
124
|
+
onColorSelect,
|
|
125
|
+
onDashSelect,
|
|
126
|
+
onFillSelect,
|
|
127
|
+
onSizeSelect
|
|
128
|
+
};
|
|
129
|
+
const customPartMap = new Map((customParts ?? []).map((customPart) => [customPart.id, customPart]));
|
|
130
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-panel", style, "aria-label": "Draw style panel", children: parts.map((part) => {
|
|
131
|
+
if (part === "colors") {
|
|
132
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-colors", children: STYLE_COLORS.map((item) => /* @__PURE__ */ jsx(
|
|
133
|
+
"button",
|
|
134
|
+
{
|
|
135
|
+
type: "button",
|
|
136
|
+
className: "tsdraw-style-color",
|
|
137
|
+
"data-active": drawColor === item.value ? "true" : void 0,
|
|
138
|
+
"aria-label": `Color ${item.value}`,
|
|
139
|
+
title: item.value,
|
|
140
|
+
onClick: () => onColorSelect(item.value),
|
|
141
|
+
children: /* @__PURE__ */ jsx(
|
|
142
|
+
"span",
|
|
143
|
+
{
|
|
144
|
+
className: "tsdraw-style-color-dot",
|
|
145
|
+
style: { background: resolveThemeColor(item.value, theme) }
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
},
|
|
149
|
+
item.value
|
|
150
|
+
)) }, part);
|
|
151
|
+
}
|
|
152
|
+
if (part === "dashes") {
|
|
153
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section", children: STYLE_DASHES.map((dash) => /* @__PURE__ */ jsx(
|
|
154
|
+
"button",
|
|
155
|
+
{
|
|
156
|
+
type: "button",
|
|
157
|
+
className: "tsdraw-style-row",
|
|
158
|
+
"data-active": drawDash === dash ? "true" : void 0,
|
|
159
|
+
"aria-label": `Stroke ${dash}`,
|
|
160
|
+
title: dash,
|
|
161
|
+
onClick: () => onDashSelect(dash),
|
|
162
|
+
children: /* @__PURE__ */ jsx("span", { className: "tsdraw-style-preview", children: /* @__PURE__ */ jsx("span", { className: `tsdraw-style-preview-line tsdraw-style-preview-line--${dash}` }) })
|
|
163
|
+
},
|
|
164
|
+
dash
|
|
165
|
+
)) }, part);
|
|
166
|
+
}
|
|
167
|
+
if (part === "fills") {
|
|
168
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section", children: STYLE_FILLS.map((fill) => /* @__PURE__ */ jsx(
|
|
169
|
+
"button",
|
|
170
|
+
{
|
|
171
|
+
type: "button",
|
|
172
|
+
className: "tsdraw-style-row",
|
|
173
|
+
"data-active": drawFill === fill ? "true" : void 0,
|
|
174
|
+
"aria-label": `Fill ${fill}`,
|
|
175
|
+
title: fill,
|
|
176
|
+
onClick: () => onFillSelect(fill),
|
|
177
|
+
children: /* @__PURE__ */ jsx("span", { className: "tsdraw-style-preview", children: /* @__PURE__ */ jsx("span", { className: `tsdraw-style-fill tsdraw-style-fill--${fill}` }) })
|
|
178
|
+
},
|
|
179
|
+
fill
|
|
180
|
+
)) }, part);
|
|
181
|
+
}
|
|
182
|
+
if (part === "sizes") {
|
|
183
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section", children: STYLE_SIZES.map((size) => /* @__PURE__ */ jsx(
|
|
184
|
+
"button",
|
|
185
|
+
{
|
|
186
|
+
type: "button",
|
|
187
|
+
className: "tsdraw-style-row",
|
|
188
|
+
"data-active": drawSize === size ? "true" : void 0,
|
|
189
|
+
"aria-label": `Thickness ${size}`,
|
|
190
|
+
title: size,
|
|
191
|
+
onClick: () => onSizeSelect(size),
|
|
192
|
+
children: /* @__PURE__ */ jsx("span", { className: "tsdraw-style-preview", children: /* @__PURE__ */ jsx("span", { className: `tsdraw-style-size tsdraw-style-size--${size}` }) })
|
|
193
|
+
},
|
|
194
|
+
size
|
|
195
|
+
)) }, part);
|
|
196
|
+
}
|
|
197
|
+
const customPart = customPartMap.get(part);
|
|
198
|
+
if (!customPart) return null;
|
|
199
|
+
return /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section tsdraw-style-section--custom", children: customPart.render(context) }, part);
|
|
200
|
+
}) });
|
|
161
201
|
}
|
|
162
202
|
function ToolOverlay({
|
|
163
203
|
visible,
|
|
@@ -197,6 +237,8 @@ function ToolOverlay({
|
|
|
197
237
|
function getDefaultToolbarIcon(toolId, isActive) {
|
|
198
238
|
if (toolId === "select") return /* @__PURE__ */ jsx(IconPointer, { size: 16, stroke: 1.8, fill: isActive ? "currentColor" : "none" });
|
|
199
239
|
if (toolId === "pen") return /* @__PURE__ */ jsx(IconPencil, { size: 16, stroke: 1.8, fill: isActive ? "currentColor" : "none" });
|
|
240
|
+
if (toolId === "square") return /* @__PURE__ */ jsx(IconSquare, { size: 16, stroke: 1.8, fill: isActive ? "currentColor" : "none" });
|
|
241
|
+
if (toolId === "circle") return /* @__PURE__ */ jsx(IconCircle, { size: 16, stroke: 1.8, fill: isActive ? "currentColor" : "none" });
|
|
200
242
|
if (toolId === "eraser") return /* @__PURE__ */ jsx(IconEraser, { size: 16, stroke: 1.8, fill: isActive ? "currentColor" : "none" });
|
|
201
243
|
if (toolId === "hand") return /* @__PURE__ */ jsx(IconHandStop, { size: 16, stroke: isActive ? 1 : 1.8, fill: isActive ? "currentColor" : "none", style: isActive ? { stroke: "#000000" } : void 0 });
|
|
202
244
|
return null;
|
|
@@ -363,11 +405,15 @@ function createSessionId() {
|
|
|
363
405
|
}
|
|
364
406
|
function getOrCreateSessionId() {
|
|
365
407
|
if (typeof window === "undefined") return createSessionId();
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
408
|
+
try {
|
|
409
|
+
const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
|
|
410
|
+
if (existing) return existing;
|
|
411
|
+
const newId = createSessionId();
|
|
412
|
+
window.sessionStorage.setItem(SESSION_STORAGE_KEY, newId);
|
|
413
|
+
return newId;
|
|
414
|
+
} catch {
|
|
415
|
+
return createSessionId();
|
|
416
|
+
}
|
|
371
417
|
}
|
|
372
418
|
|
|
373
419
|
// src/canvas/useTsdrawCanvasController.ts
|
|
@@ -384,8 +430,7 @@ function resolveDrawColor(colorStyle, theme) {
|
|
|
384
430
|
return resolveThemeColor(colorStyle, theme);
|
|
385
431
|
}
|
|
386
432
|
function useTsdrawCanvasController(options = {}) {
|
|
387
|
-
const
|
|
388
|
-
const stylePanelToolIdsRef = useRef(stylePanelToolIds);
|
|
433
|
+
const onMountRef = useRef(options.onMount);
|
|
389
434
|
const containerRef = useRef(null);
|
|
390
435
|
const canvasRef = useRef(null);
|
|
391
436
|
const editorRef = useRef(null);
|
|
@@ -419,6 +464,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
419
464
|
const [currentTool, setCurrentToolState] = useState(options.initialTool ?? "pen");
|
|
420
465
|
const [drawColor, setDrawColor] = useState("black");
|
|
421
466
|
const [drawDash, setDrawDash] = useState("draw");
|
|
467
|
+
const [drawFill, setDrawFill] = useState("none");
|
|
422
468
|
const [drawSize, setDrawSize] = useState("m");
|
|
423
469
|
const [selectedShapeIds, setSelectedShapeIds] = useState([]);
|
|
424
470
|
const [selectionBrush, setSelectionBrush] = useState(null);
|
|
@@ -436,8 +482,8 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
436
482
|
currentToolRef.current = currentTool;
|
|
437
483
|
}, [currentTool]);
|
|
438
484
|
useEffect(() => {
|
|
439
|
-
|
|
440
|
-
}, [
|
|
485
|
+
onMountRef.current = options.onMount;
|
|
486
|
+
}, [options.onMount]);
|
|
441
487
|
useEffect(() => {
|
|
442
488
|
selectedShapeIdsRef.current = selectedShapeIds;
|
|
443
489
|
}, [selectedShapeIds]);
|
|
@@ -446,7 +492,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
446
492
|
}, [selectionRotationDeg]);
|
|
447
493
|
useEffect(() => {
|
|
448
494
|
schedulePersistRef.current?.();
|
|
449
|
-
}, [selectedShapeIds, currentTool, drawColor, drawDash, drawSize]);
|
|
495
|
+
}, [selectedShapeIds, currentTool, drawColor, drawDash, drawFill, drawSize]);
|
|
450
496
|
const render = useCallback(() => {
|
|
451
497
|
const canvas = canvasRef.current;
|
|
452
498
|
const editor = editorRef.current;
|
|
@@ -582,6 +628,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
582
628
|
const initialStyle = editor.getCurrentDrawStyle();
|
|
583
629
|
setDrawColor(initialStyle.color);
|
|
584
630
|
setDrawDash(initialStyle.dash);
|
|
631
|
+
setDrawFill(initialStyle.fill);
|
|
585
632
|
setDrawSize(initialStyle.size);
|
|
586
633
|
const resize = () => {
|
|
587
634
|
const dpr = window.devicePixelRatio ?? 1;
|
|
@@ -663,6 +710,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
663
710
|
const nextDrawStyle = editor.getCurrentDrawStyle();
|
|
664
711
|
setDrawColor(nextDrawStyle.color);
|
|
665
712
|
setDrawDash(nextDrawStyle.dash);
|
|
713
|
+
setDrawFill(nextDrawStyle.fill);
|
|
666
714
|
setDrawSize(nextDrawStyle.size);
|
|
667
715
|
setSelectionRotationDeg(0);
|
|
668
716
|
render();
|
|
@@ -711,7 +759,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
711
759
|
additive: first.shiftKey,
|
|
712
760
|
initialSelection: [...selectedShapeIdsRef.current]
|
|
713
761
|
};
|
|
714
|
-
setSelectionBrush({
|
|
762
|
+
setSelectionBrush(toScreenRect(editor, { minX: x, minY: y, maxX: x, maxY: y }));
|
|
715
763
|
if (!e.shiftKey) {
|
|
716
764
|
setSelectedShapeIds([]);
|
|
717
765
|
selectedShapeIdsRef.current = [];
|
|
@@ -868,6 +916,35 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
868
916
|
}
|
|
869
917
|
editor.endHistoryEntry();
|
|
870
918
|
};
|
|
919
|
+
const handlePointerCancel = () => {
|
|
920
|
+
if (!isPointerActiveRef.current) return;
|
|
921
|
+
isPointerActiveRef.current = false;
|
|
922
|
+
lastPointerClientRef.current = null;
|
|
923
|
+
editor.input.pointerUp();
|
|
924
|
+
if (currentToolRef.current === "select") {
|
|
925
|
+
const drag = selectDragRef.current;
|
|
926
|
+
if (drag.mode === "rotate") setIsRotatingSelection(false);
|
|
927
|
+
if (drag.mode === "resize") setIsResizingSelection(false);
|
|
928
|
+
if (drag.mode === "move") setIsMovingSelection(false);
|
|
929
|
+
if (drag.mode === "marquee") setSelectionBrush(null);
|
|
930
|
+
if (drag.mode !== "none") {
|
|
931
|
+
selectDragRef.current.mode = "none";
|
|
932
|
+
render();
|
|
933
|
+
refreshSelectionBounds(editor);
|
|
934
|
+
}
|
|
935
|
+
editor.endHistoryEntry();
|
|
936
|
+
} else {
|
|
937
|
+
editor.tools.pointerUp();
|
|
938
|
+
render();
|
|
939
|
+
refreshSelectionBounds(editor);
|
|
940
|
+
editor.endHistoryEntry();
|
|
941
|
+
}
|
|
942
|
+
if (pendingRemoteDocumentRef.current) {
|
|
943
|
+
const pending = pendingRemoteDocumentRef.current;
|
|
944
|
+
pendingRemoteDocumentRef.current = null;
|
|
945
|
+
applyRemoteDocumentSnapshot(pending);
|
|
946
|
+
}
|
|
947
|
+
};
|
|
871
948
|
const handleKeyDown = (e) => {
|
|
872
949
|
const isMetaPressed = e.metaKey || e.ctrlKey;
|
|
873
950
|
const loweredKey = e.key.toLowerCase();
|
|
@@ -914,22 +991,42 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
914
991
|
}
|
|
915
992
|
editor.loadHistorySnapshot(loaded.history);
|
|
916
993
|
syncHistoryState();
|
|
994
|
+
if (disposed) return;
|
|
917
995
|
persistenceActive = true;
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (isPointerActiveRef.current) {
|
|
927
|
-
pendingRemoteDocumentRef.current = nextDocument;
|
|
996
|
+
if (typeof BroadcastChannel !== "undefined") {
|
|
997
|
+
persistenceChannel = new BroadcastChannel(`tsdraw:persistence:${persistenceKey}`);
|
|
998
|
+
let isLoadingRemote = false;
|
|
999
|
+
let pendingRemoteLoad = false;
|
|
1000
|
+
persistenceChannel.onmessage = () => {
|
|
1001
|
+
if (disposed) return;
|
|
1002
|
+
if (isLoadingRemote) {
|
|
1003
|
+
pendingRemoteLoad = true;
|
|
928
1004
|
return;
|
|
929
1005
|
}
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
1006
|
+
isLoadingRemote = true;
|
|
1007
|
+
const processLoad = async () => {
|
|
1008
|
+
try {
|
|
1009
|
+
do {
|
|
1010
|
+
pendingRemoteLoad = false;
|
|
1011
|
+
if (!persistenceDb || disposed) return;
|
|
1012
|
+
const nextLoaded = await persistenceDb.load(sessionId);
|
|
1013
|
+
if (disposed) return;
|
|
1014
|
+
if (nextLoaded.records.length > 0) {
|
|
1015
|
+
const nextDocument = { records: nextLoaded.records };
|
|
1016
|
+
if (isPointerActiveRef.current) {
|
|
1017
|
+
pendingRemoteDocumentRef.current = nextDocument;
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
applyRemoteDocumentSnapshot(nextDocument);
|
|
1021
|
+
}
|
|
1022
|
+
} while (pendingRemoteLoad && !disposed);
|
|
1023
|
+
} finally {
|
|
1024
|
+
isLoadingRemote = false;
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
void processLoad();
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
933
1030
|
} finally {
|
|
934
1031
|
if (!disposed) {
|
|
935
1032
|
setIsPersistenceReady(true);
|
|
@@ -951,12 +1048,13 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
951
1048
|
canvas.addEventListener("pointerdown", handlePointerDown);
|
|
952
1049
|
window.addEventListener("pointermove", handlePointerMove);
|
|
953
1050
|
window.addEventListener("pointerup", handlePointerUp);
|
|
1051
|
+
window.addEventListener("pointercancel", handlePointerCancel);
|
|
954
1052
|
window.addEventListener("keydown", handleKeyDown);
|
|
955
1053
|
window.addEventListener("keyup", handleKeyUp);
|
|
956
1054
|
void initializePersistence().catch((error) => {
|
|
957
1055
|
console.error("failed to initialize tsdraw persistence", error);
|
|
958
1056
|
});
|
|
959
|
-
disposeMount =
|
|
1057
|
+
disposeMount = onMountRef.current?.({
|
|
960
1058
|
editor,
|
|
961
1059
|
container,
|
|
962
1060
|
canvas,
|
|
@@ -991,6 +1089,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
991
1089
|
editor.setCurrentDrawStyle(partial);
|
|
992
1090
|
if (partial.color) setDrawColor(partial.color);
|
|
993
1091
|
if (partial.dash) setDrawDash(partial.dash);
|
|
1092
|
+
if (partial.fill) setDrawFill(partial.fill);
|
|
994
1093
|
if (partial.size) setDrawSize(partial.size);
|
|
995
1094
|
render();
|
|
996
1095
|
}
|
|
@@ -1005,6 +1104,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1005
1104
|
canvas.removeEventListener("pointerdown", handlePointerDown);
|
|
1006
1105
|
window.removeEventListener("pointermove", handlePointerMove);
|
|
1007
1106
|
window.removeEventListener("pointerup", handlePointerUp);
|
|
1107
|
+
window.removeEventListener("pointercancel", handlePointerCancel);
|
|
1008
1108
|
window.removeEventListener("keydown", handleKeyDown);
|
|
1009
1109
|
window.removeEventListener("keyup", handleKeyUp);
|
|
1010
1110
|
isPointerActiveRef.current = false;
|
|
@@ -1016,7 +1116,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1016
1116
|
}, [
|
|
1017
1117
|
getPagePointFromClient,
|
|
1018
1118
|
options.initialTool,
|
|
1019
|
-
options.onMount,
|
|
1020
1119
|
options.persistenceKey,
|
|
1021
1120
|
options.toolDefinitions,
|
|
1022
1121
|
refreshSelectionBounds,
|
|
@@ -1048,6 +1147,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1048
1147
|
editor.setCurrentDrawStyle(partial);
|
|
1049
1148
|
if (partial.color) setDrawColor(partial.color);
|
|
1050
1149
|
if (partial.dash) setDrawDash(partial.dash);
|
|
1150
|
+
if (partial.fill) setDrawFill(partial.fill);
|
|
1051
1151
|
if (partial.size) setDrawSize(partial.size);
|
|
1052
1152
|
render();
|
|
1053
1153
|
},
|
|
@@ -1115,6 +1215,7 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1115
1215
|
currentTool,
|
|
1116
1216
|
drawColor,
|
|
1117
1217
|
drawDash,
|
|
1218
|
+
drawFill,
|
|
1118
1219
|
drawSize,
|
|
1119
1220
|
selectedShapeIds,
|
|
1120
1221
|
selectionBrush,
|
|
@@ -1124,7 +1225,6 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1124
1225
|
cursorContext,
|
|
1125
1226
|
toolOverlay,
|
|
1126
1227
|
isPersistenceReady,
|
|
1127
|
-
showStylePanel: stylePanelToolIdsRef.current.includes(currentTool),
|
|
1128
1228
|
canUndo,
|
|
1129
1229
|
canRedo,
|
|
1130
1230
|
undo,
|
|
@@ -1135,10 +1235,21 @@ function useTsdrawCanvasController(options = {}) {
|
|
|
1135
1235
|
handleRotatePointerDown
|
|
1136
1236
|
};
|
|
1137
1237
|
}
|
|
1138
|
-
var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser"]];
|
|
1238
|
+
var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser", "square", "circle"]];
|
|
1239
|
+
var EMPTY_CUSTOM_TOOLS = [];
|
|
1240
|
+
var EMPTY_CUSTOM_ELEMENTS = [];
|
|
1241
|
+
var EMPTY_STYLE_PANEL_PARTS = [];
|
|
1242
|
+
var EMPTY_STYLE_PANEL_CUSTOM_PARTS = [];
|
|
1243
|
+
var DEFAULT_STYLE_PANEL_PARTS_BY_TOOL = {
|
|
1244
|
+
pen: ["colors", "dashes", "sizes"],
|
|
1245
|
+
square: ["colors", "dashes", "fills", "sizes"],
|
|
1246
|
+
circle: ["colors", "dashes", "fills", "sizes"]
|
|
1247
|
+
};
|
|
1139
1248
|
var DEFAULT_TOOL_LABELS = {
|
|
1140
1249
|
select: "Select",
|
|
1141
1250
|
pen: "Pen",
|
|
1251
|
+
square: "Rectangle",
|
|
1252
|
+
circle: "Ellipse",
|
|
1142
1253
|
eraser: "Eraser",
|
|
1143
1254
|
hand: "Hand"
|
|
1144
1255
|
};
|
|
@@ -1181,14 +1292,14 @@ function resolvePlacementStyle(placement, fallbackAnchor, fallbackOffsetX, fallb
|
|
|
1181
1292
|
if (offsetY) transforms.push(`translateY(${offsetY}px)`);
|
|
1182
1293
|
}
|
|
1183
1294
|
if (transforms.length > 0) result.transform = transforms.join(" ");
|
|
1184
|
-
return { ...result, ...placement
|
|
1295
|
+
return placement?.style ? { ...result, ...placement.style } : result;
|
|
1185
1296
|
}
|
|
1186
1297
|
function Tsdraw(props) {
|
|
1187
1298
|
const [systemTheme, setSystemTheme] = useState(() => {
|
|
1188
1299
|
if (typeof window === "undefined") return "light";
|
|
1189
1300
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
|
1190
1301
|
});
|
|
1191
|
-
const customTools = props.customTools ??
|
|
1302
|
+
const customTools = props.customTools ?? EMPTY_CUSTOM_TOOLS;
|
|
1192
1303
|
const toolbarPartIds = props.uiOptions?.toolbar?.parts ?? DEFAULT_TOOLBAR_PARTS;
|
|
1193
1304
|
const customToolMap = useMemo(
|
|
1194
1305
|
() => new Map(customTools.map((customTool) => [customTool.id, customTool])),
|
|
@@ -1210,21 +1321,6 @@ function Tsdraw(props) {
|
|
|
1210
1321
|
() => customTools.filter((customTool) => toolbarToolIds.has(customTool.id)).map((customTool) => customTool.definition),
|
|
1211
1322
|
[customTools, toolbarToolIds]
|
|
1212
1323
|
);
|
|
1213
|
-
const stylePanelToolIds = useMemo(
|
|
1214
|
-
() => {
|
|
1215
|
-
const nextToolIds = /* @__PURE__ */ new Set();
|
|
1216
|
-
if (toolbarToolIds.has("pen")) {
|
|
1217
|
-
nextToolIds.add("pen");
|
|
1218
|
-
}
|
|
1219
|
-
for (const customTool of customTools) {
|
|
1220
|
-
if ((customTool.showStylePanel ?? false) && toolbarToolIds.has(customTool.id)) {
|
|
1221
|
-
nextToolIds.add(customTool.id);
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
return [...nextToolIds];
|
|
1225
|
-
},
|
|
1226
|
-
[customTools, toolbarToolIds]
|
|
1227
|
-
);
|
|
1228
1324
|
const firstToolbarTool = useMemo(() => {
|
|
1229
1325
|
for (const toolbarPart of toolbarPartIds) {
|
|
1230
1326
|
for (const item of toolbarPart) {
|
|
@@ -1253,6 +1349,7 @@ function Tsdraw(props) {
|
|
|
1253
1349
|
currentTool,
|
|
1254
1350
|
drawColor,
|
|
1255
1351
|
drawDash,
|
|
1352
|
+
drawFill,
|
|
1256
1353
|
drawSize,
|
|
1257
1354
|
selectedShapeIds,
|
|
1258
1355
|
selectionBrush,
|
|
@@ -1262,7 +1359,6 @@ function Tsdraw(props) {
|
|
|
1262
1359
|
cursorContext,
|
|
1263
1360
|
toolOverlay,
|
|
1264
1361
|
isPersistenceReady,
|
|
1265
|
-
showStylePanel,
|
|
1266
1362
|
canUndo,
|
|
1267
1363
|
canRedo,
|
|
1268
1364
|
undo,
|
|
@@ -1276,11 +1372,12 @@ function Tsdraw(props) {
|
|
|
1276
1372
|
initialTool,
|
|
1277
1373
|
theme: resolvedTheme,
|
|
1278
1374
|
persistenceKey: props.persistenceKey,
|
|
1279
|
-
stylePanelToolIds,
|
|
1280
1375
|
onMount: props.onMount
|
|
1281
1376
|
});
|
|
1282
1377
|
const toolbarPlacementStyle = resolvePlacementStyle(props.uiOptions?.toolbar?.placement, "bottom-center", 0, 14);
|
|
1283
1378
|
const stylePanelPlacementStyle = resolvePlacementStyle(props.uiOptions?.stylePanel?.placement, "top-right", 8, 8);
|
|
1379
|
+
const isToolbarHidden = props.uiOptions?.toolbar?.hide === true;
|
|
1380
|
+
const isStylePanelHidden = props.uiOptions?.stylePanel?.hide === true;
|
|
1284
1381
|
const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
|
|
1285
1382
|
const defaultToolOverlay = /* @__PURE__ */ jsx(
|
|
1286
1383
|
ToolOverlay,
|
|
@@ -1295,7 +1392,31 @@ function Tsdraw(props) {
|
|
|
1295
1392
|
}
|
|
1296
1393
|
);
|
|
1297
1394
|
const overlayNode = props.uiOptions?.overlays?.renderToolOverlay?.({ defaultOverlay: defaultToolOverlay, overlayState: toolOverlay, currentTool }) ?? defaultToolOverlay;
|
|
1298
|
-
const customElements = props.uiOptions?.customElements ??
|
|
1395
|
+
const customElements = props.uiOptions?.customElements ?? EMPTY_CUSTOM_ELEMENTS;
|
|
1396
|
+
const onColorSelect = useCallback((color) => {
|
|
1397
|
+
applyDrawStyle({ color });
|
|
1398
|
+
}, [applyDrawStyle]);
|
|
1399
|
+
const onDashSelect = useCallback((dash) => {
|
|
1400
|
+
applyDrawStyle({ dash });
|
|
1401
|
+
}, [applyDrawStyle]);
|
|
1402
|
+
const onFillSelect = useCallback((fill) => {
|
|
1403
|
+
applyDrawStyle({ fill });
|
|
1404
|
+
}, [applyDrawStyle]);
|
|
1405
|
+
const onSizeSelect = useCallback((size) => {
|
|
1406
|
+
applyDrawStyle({ size });
|
|
1407
|
+
}, [applyDrawStyle]);
|
|
1408
|
+
const activeCustomTool = customToolMap.get(currentTool);
|
|
1409
|
+
const stylePanelParts = useMemo(
|
|
1410
|
+
() => {
|
|
1411
|
+
const fromCustomTool = activeCustomTool?.stylePanel?.parts;
|
|
1412
|
+
if (fromCustomTool && fromCustomTool.length > 0) return fromCustomTool;
|
|
1413
|
+
if (activeCustomTool?.stylePanel?.customParts && activeCustomTool.stylePanel.customParts.length > 0) return activeCustomTool.stylePanel.customParts.map((customPart) => customPart.id);
|
|
1414
|
+
if (currentTool in DEFAULT_STYLE_PANEL_PARTS_BY_TOOL) return DEFAULT_STYLE_PANEL_PARTS_BY_TOOL[currentTool] ?? EMPTY_STYLE_PANEL_PARTS;
|
|
1415
|
+
return EMPTY_STYLE_PANEL_PARTS;
|
|
1416
|
+
},
|
|
1417
|
+
[activeCustomTool, currentTool]
|
|
1418
|
+
);
|
|
1419
|
+
const stylePanelCustomParts = activeCustomTool?.stylePanel?.customParts ?? EMPTY_STYLE_PANEL_CUSTOM_PARTS;
|
|
1299
1420
|
const toolbarParts = useMemo(
|
|
1300
1421
|
() => toolbarPartIds.map((toolbarPart, partIndex) => {
|
|
1301
1422
|
const items = toolbarPart.map((item) => {
|
|
@@ -1384,15 +1505,19 @@ function Tsdraw(props) {
|
|
|
1384
1505
|
/* @__PURE__ */ jsx(
|
|
1385
1506
|
StylePanel,
|
|
1386
1507
|
{
|
|
1387
|
-
visible: isPersistenceReady &&
|
|
1508
|
+
visible: !isStylePanelHidden && isPersistenceReady && stylePanelParts.length > 0,
|
|
1509
|
+
parts: stylePanelParts,
|
|
1510
|
+
customParts: stylePanelCustomParts,
|
|
1388
1511
|
style: stylePanelPlacementStyle,
|
|
1389
1512
|
theme: resolvedTheme,
|
|
1390
1513
|
drawColor,
|
|
1391
1514
|
drawDash,
|
|
1515
|
+
drawFill,
|
|
1392
1516
|
drawSize,
|
|
1393
|
-
onColorSelect
|
|
1394
|
-
onDashSelect
|
|
1395
|
-
|
|
1517
|
+
onColorSelect,
|
|
1518
|
+
onDashSelect,
|
|
1519
|
+
onFillSelect,
|
|
1520
|
+
onSizeSelect
|
|
1396
1521
|
}
|
|
1397
1522
|
),
|
|
1398
1523
|
customElements.map((customElement) => /* @__PURE__ */ jsx(
|
|
@@ -1408,7 +1533,7 @@ function Tsdraw(props) {
|
|
|
1408
1533
|
},
|
|
1409
1534
|
customElement.id
|
|
1410
1535
|
)),
|
|
1411
|
-
/* @__PURE__ */ jsx(
|
|
1536
|
+
!isToolbarHidden ? /* @__PURE__ */ jsx(
|
|
1412
1537
|
Toolbar,
|
|
1413
1538
|
{
|
|
1414
1539
|
parts: toolbarParts,
|
|
@@ -1417,7 +1542,7 @@ function Tsdraw(props) {
|
|
|
1417
1542
|
onToolChange: setTool,
|
|
1418
1543
|
disabled: !isPersistenceReady
|
|
1419
1544
|
}
|
|
1420
|
-
)
|
|
1545
|
+
) : null
|
|
1421
1546
|
]
|
|
1422
1547
|
}
|
|
1423
1548
|
);
|