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