@tsdraw/react 0.6.2 → 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;
@@ -390,8 +432,6 @@ function resolveDrawColor(colorStyle, theme) {
390
432
  return core.resolveThemeColor(colorStyle, theme);
391
433
  }
392
434
  function useTsdrawCanvasController(options = {}) {
393
- const stylePanelToolIds = options.stylePanelToolIds ?? ["pen"];
394
- const stylePanelToolIdsRef = react.useRef(stylePanelToolIds);
395
435
  const onMountRef = react.useRef(options.onMount);
396
436
  const containerRef = react.useRef(null);
397
437
  const canvasRef = react.useRef(null);
@@ -426,6 +466,7 @@ function useTsdrawCanvasController(options = {}) {
426
466
  const [currentTool, setCurrentToolState] = react.useState(options.initialTool ?? "pen");
427
467
  const [drawColor, setDrawColor] = react.useState("black");
428
468
  const [drawDash, setDrawDash] = react.useState("draw");
469
+ const [drawFill, setDrawFill] = react.useState("none");
429
470
  const [drawSize, setDrawSize] = react.useState("m");
430
471
  const [selectedShapeIds, setSelectedShapeIds] = react.useState([]);
431
472
  const [selectionBrush, setSelectionBrush] = react.useState(null);
@@ -442,9 +483,6 @@ function useTsdrawCanvasController(options = {}) {
442
483
  react.useEffect(() => {
443
484
  currentToolRef.current = currentTool;
444
485
  }, [currentTool]);
445
- react.useEffect(() => {
446
- stylePanelToolIdsRef.current = stylePanelToolIds;
447
- }, [stylePanelToolIds]);
448
486
  react.useEffect(() => {
449
487
  onMountRef.current = options.onMount;
450
488
  }, [options.onMount]);
@@ -456,7 +494,7 @@ function useTsdrawCanvasController(options = {}) {
456
494
  }, [selectionRotationDeg]);
457
495
  react.useEffect(() => {
458
496
  schedulePersistRef.current?.();
459
- }, [selectedShapeIds, currentTool, drawColor, drawDash, drawSize]);
497
+ }, [selectedShapeIds, currentTool, drawColor, drawDash, drawFill, drawSize]);
460
498
  const render = react.useCallback(() => {
461
499
  const canvas = canvasRef.current;
462
500
  const editor = editorRef.current;
@@ -592,6 +630,7 @@ function useTsdrawCanvasController(options = {}) {
592
630
  const initialStyle = editor.getCurrentDrawStyle();
593
631
  setDrawColor(initialStyle.color);
594
632
  setDrawDash(initialStyle.dash);
633
+ setDrawFill(initialStyle.fill);
595
634
  setDrawSize(initialStyle.size);
596
635
  const resize = () => {
597
636
  const dpr = window.devicePixelRatio ?? 1;
@@ -673,6 +712,7 @@ function useTsdrawCanvasController(options = {}) {
673
712
  const nextDrawStyle = editor.getCurrentDrawStyle();
674
713
  setDrawColor(nextDrawStyle.color);
675
714
  setDrawDash(nextDrawStyle.dash);
715
+ setDrawFill(nextDrawStyle.fill);
676
716
  setDrawSize(nextDrawStyle.size);
677
717
  setSelectionRotationDeg(0);
678
718
  render();
@@ -1051,6 +1091,7 @@ function useTsdrawCanvasController(options = {}) {
1051
1091
  editor.setCurrentDrawStyle(partial);
1052
1092
  if (partial.color) setDrawColor(partial.color);
1053
1093
  if (partial.dash) setDrawDash(partial.dash);
1094
+ if (partial.fill) setDrawFill(partial.fill);
1054
1095
  if (partial.size) setDrawSize(partial.size);
1055
1096
  render();
1056
1097
  }
@@ -1108,6 +1149,7 @@ function useTsdrawCanvasController(options = {}) {
1108
1149
  editor.setCurrentDrawStyle(partial);
1109
1150
  if (partial.color) setDrawColor(partial.color);
1110
1151
  if (partial.dash) setDrawDash(partial.dash);
1152
+ if (partial.fill) setDrawFill(partial.fill);
1111
1153
  if (partial.size) setDrawSize(partial.size);
1112
1154
  render();
1113
1155
  },
@@ -1175,6 +1217,7 @@ function useTsdrawCanvasController(options = {}) {
1175
1217
  currentTool,
1176
1218
  drawColor,
1177
1219
  drawDash,
1220
+ drawFill,
1178
1221
  drawSize,
1179
1222
  selectedShapeIds,
1180
1223
  selectionBrush,
@@ -1184,7 +1227,6 @@ function useTsdrawCanvasController(options = {}) {
1184
1227
  cursorContext,
1185
1228
  toolOverlay,
1186
1229
  isPersistenceReady,
1187
- showStylePanel: stylePanelToolIdsRef.current.includes(currentTool),
1188
1230
  canUndo,
1189
1231
  canRedo,
1190
1232
  undo,
@@ -1195,12 +1237,21 @@ function useTsdrawCanvasController(options = {}) {
1195
1237
  handleRotatePointerDown
1196
1238
  };
1197
1239
  }
1198
- var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser"]];
1240
+ var DEFAULT_TOOLBAR_PARTS = [["undo", "redo"], ["select", "hand", "pen", "eraser", "square", "circle"]];
1199
1241
  var EMPTY_CUSTOM_TOOLS = [];
1200
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
+ };
1201
1250
  var DEFAULT_TOOL_LABELS = {
1202
1251
  select: "Select",
1203
1252
  pen: "Pen",
1253
+ square: "Rectangle",
1254
+ circle: "Ellipse",
1204
1255
  eraser: "Eraser",
1205
1256
  hand: "Hand"
1206
1257
  };
@@ -1272,21 +1323,6 @@ function Tsdraw(props) {
1272
1323
  () => customTools.filter((customTool) => toolbarToolIds.has(customTool.id)).map((customTool) => customTool.definition),
1273
1324
  [customTools, toolbarToolIds]
1274
1325
  );
1275
- const stylePanelToolIds = react.useMemo(
1276
- () => {
1277
- const nextToolIds = /* @__PURE__ */ new Set();
1278
- if (toolbarToolIds.has("pen")) {
1279
- nextToolIds.add("pen");
1280
- }
1281
- for (const customTool of customTools) {
1282
- if ((customTool.showStylePanel ?? false) && toolbarToolIds.has(customTool.id)) {
1283
- nextToolIds.add(customTool.id);
1284
- }
1285
- }
1286
- return [...nextToolIds];
1287
- },
1288
- [customTools, toolbarToolIds]
1289
- );
1290
1326
  const firstToolbarTool = react.useMemo(() => {
1291
1327
  for (const toolbarPart of toolbarPartIds) {
1292
1328
  for (const item of toolbarPart) {
@@ -1315,6 +1351,7 @@ function Tsdraw(props) {
1315
1351
  currentTool,
1316
1352
  drawColor,
1317
1353
  drawDash,
1354
+ drawFill,
1318
1355
  drawSize,
1319
1356
  selectedShapeIds,
1320
1357
  selectionBrush,
@@ -1324,7 +1361,6 @@ function Tsdraw(props) {
1324
1361
  cursorContext,
1325
1362
  toolOverlay,
1326
1363
  isPersistenceReady,
1327
- showStylePanel,
1328
1364
  canUndo,
1329
1365
  canRedo,
1330
1366
  undo,
@@ -1338,11 +1374,12 @@ function Tsdraw(props) {
1338
1374
  initialTool,
1339
1375
  theme: resolvedTheme,
1340
1376
  persistenceKey: props.persistenceKey,
1341
- stylePanelToolIds,
1342
1377
  onMount: props.onMount
1343
1378
  });
1344
1379
  const toolbarPlacementStyle = resolvePlacementStyle(props.uiOptions?.toolbar?.placement, "bottom-center", 0, 14);
1345
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;
1346
1383
  const canvasCursor = props.uiOptions?.cursor?.getCursor?.(cursorContext) ?? defaultCanvasCursor;
1347
1384
  const defaultToolOverlay = /* @__PURE__ */ jsxRuntime.jsx(
1348
1385
  ToolOverlay,
@@ -1364,9 +1401,24 @@ function Tsdraw(props) {
1364
1401
  const onDashSelect = react.useCallback((dash) => {
1365
1402
  applyDrawStyle({ dash });
1366
1403
  }, [applyDrawStyle]);
1404
+ const onFillSelect = react.useCallback((fill) => {
1405
+ applyDrawStyle({ fill });
1406
+ }, [applyDrawStyle]);
1367
1407
  const onSizeSelect = react.useCallback((size) => {
1368
1408
  applyDrawStyle({ size });
1369
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;
1370
1422
  const toolbarParts = react.useMemo(
1371
1423
  () => toolbarPartIds.map((toolbarPart, partIndex) => {
1372
1424
  const items = toolbarPart.map((item) => {
@@ -1455,14 +1507,18 @@ function Tsdraw(props) {
1455
1507
  /* @__PURE__ */ jsxRuntime.jsx(
1456
1508
  StylePanel,
1457
1509
  {
1458
- visible: isPersistenceReady && showStylePanel,
1510
+ visible: !isStylePanelHidden && isPersistenceReady && stylePanelParts.length > 0,
1511
+ parts: stylePanelParts,
1512
+ customParts: stylePanelCustomParts,
1459
1513
  style: stylePanelPlacementStyle,
1460
1514
  theme: resolvedTheme,
1461
1515
  drawColor,
1462
1516
  drawDash,
1517
+ drawFill,
1463
1518
  drawSize,
1464
1519
  onColorSelect,
1465
1520
  onDashSelect,
1521
+ onFillSelect,
1466
1522
  onSizeSelect
1467
1523
  }
1468
1524
  ),
@@ -1479,7 +1535,7 @@ function Tsdraw(props) {
1479
1535
  },
1480
1536
  customElement.id
1481
1537
  )),
1482
- /* @__PURE__ */ jsxRuntime.jsx(
1538
+ !isToolbarHidden ? /* @__PURE__ */ jsxRuntime.jsx(
1483
1539
  Toolbar,
1484
1540
  {
1485
1541
  parts: toolbarParts,
@@ -1488,7 +1544,7 @@ function Tsdraw(props) {
1488
1544
  onToolChange: setTool,
1489
1545
  disabled: !isPersistenceReady
1490
1546
  }
1491
- )
1547
+ ) : null
1492
1548
  ]
1493
1549
  }
1494
1550
  );