@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.js CHANGED
@@ -1,7 +1,7 @@
1
- import { useState, useMemo, useEffect, useRef, useCallback } from 'react';
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
- return /* @__PURE__ */ jsxs("div", { className: "tsdraw-style-panel", style, "aria-label": "Draw style panel", children: [
115
- /* @__PURE__ */ jsx("div", { className: "tsdraw-style-colors", children: STYLE_COLORS.map((item) => /* @__PURE__ */ jsx(
116
- "button",
117
- {
118
- type: "button",
119
- className: "tsdraw-style-color",
120
- "data-active": drawColor === item.value ? "true" : void 0,
121
- "aria-label": `Color ${item.value}`,
122
- title: item.value,
123
- onClick: () => onColorSelect(item.value),
124
- children: /* @__PURE__ */ jsx(
125
- "span",
126
- {
127
- className: "tsdraw-style-color-dot",
128
- style: { background: resolveThemeColor(item.value, theme) }
129
- }
130
- )
131
- },
132
- item.value
133
- )) }),
134
- /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section", children: STYLE_DASHES.map((dash) => /* @__PURE__ */ jsx(
135
- "button",
136
- {
137
- type: "button",
138
- className: "tsdraw-style-row",
139
- "data-active": drawDash === dash ? "true" : void 0,
140
- "aria-label": `Stroke ${dash}`,
141
- title: dash,
142
- onClick: () => onDashSelect(dash),
143
- children: /* @__PURE__ */ jsx("span", { className: "tsdraw-style-preview", children: /* @__PURE__ */ jsx("span", { className: `tsdraw-style-preview-line tsdraw-style-preview-line--${dash}` }) })
144
- },
145
- dash
146
- )) }),
147
- /* @__PURE__ */ jsx("div", { className: "tsdraw-style-section", children: STYLE_SIZES.map((size) => /* @__PURE__ */ jsx(
148
- "button",
149
- {
150
- type: "button",
151
- className: "tsdraw-style-row",
152
- "data-active": drawSize === size ? "true" : void 0,
153
- "aria-label": `Thickness ${size}`,
154
- title: size,
155
- onClick: () => onSizeSelect(size),
156
- children: /* @__PURE__ */ jsx("span", { className: "tsdraw-style-preview", children: /* @__PURE__ */ jsx("span", { className: `tsdraw-style-size tsdraw-style-size--${size}` }) })
157
- },
158
- size
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
- const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
367
- if (existing) return existing;
368
- const newId = createSessionId();
369
- window.sessionStorage.setItem(SESSION_STORAGE_KEY, newId);
370
- return newId;
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 stylePanelToolIds = options.stylePanelToolIds ?? ["pen"];
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
- stylePanelToolIdsRef.current = stylePanelToolIds;
440
- }, [stylePanelToolIds]);
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({ left: e.offsetX, top: e.offsetY, width: 0, height: 0 });
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
- persistenceChannel = new BroadcastChannel(`tsdraw:persistence:${persistenceKey}`);
919
- persistenceChannel.onmessage = async (event) => {
920
- const data = event.data;
921
- if (data?.type !== "tsdraw:persisted" || data.senderSessionId === sessionId) return;
922
- if (!persistenceDb || disposed) return;
923
- const nextLoaded = await persistenceDb.load(sessionId);
924
- if (nextLoaded.records.length > 0) {
925
- const nextDocument = { records: nextLoaded.records };
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
- applyRemoteDocumentSnapshot(nextDocument);
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 = options.onMount?.({
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?.style ?? {} };
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 && showStylePanel,
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: (color) => applyDrawStyle({ color }),
1394
- onDashSelect: (dash) => applyDrawStyle({ dash }),
1395
- onSizeSelect: (size) => applyDrawStyle({ size })
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
  );