@pixerate/editor-react 0.76.0 → 0.77.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.
@@ -1056,7 +1056,9 @@ function useCanvasDraw(options) {
1056
1056
  continueStroke: controller.continueStroke,
1057
1057
  endStroke: controller.endStroke,
1058
1058
  cancelStroke: controller.cancelStroke,
1059
- eraseAt: controller.eraseAt
1059
+ eraseAt: controller.eraseAt,
1060
+ drawStroke: controller.drawStroke,
1061
+ listBrushes: controller.listBrushes
1060
1062
  }),
1061
1063
  [tool, style, activeStroke, controller]
1062
1064
  );
@@ -1065,6 +1067,7 @@ function useCanvasDraw(options) {
1065
1067
  // src/canvas/CanvasDrawLayer.tsx
1066
1068
  import { useCallback as useCallback7, useEffect as useEffect5, useRef as useRef9, useState as useState4 } from "react";
1067
1069
  import {
1070
+ pointerPenSample,
1068
1071
  strokePointsToPathData,
1069
1072
  resolveCanvasGradient,
1070
1073
  gradientToSvgDef
@@ -1124,7 +1127,7 @@ function CanvasDrawLayer({
1124
1127
  e.stopPropagation();
1125
1128
  e.target.setPointerCapture?.(e.pointerId);
1126
1129
  const flowPt = clientToFlow(e.clientX, e.clientY);
1127
- draw.startStroke(flowPt);
1130
+ draw.startStroke(flowPt, pointerPenSample(e));
1128
1131
  if (draw.tool === "eraser") {
1129
1132
  setEraserPos([e.clientX, e.clientY]);
1130
1133
  }
@@ -1137,8 +1140,10 @@ function CanvasDrawLayer({
1137
1140
  if (e.buttons === 1) {
1138
1141
  e.preventDefault();
1139
1142
  e.stopPropagation();
1140
- const flowPt = clientToFlow(e.clientX, e.clientY);
1141
- draw.continueStroke(flowPt);
1143
+ const events = draw.style.brush !== void 0 ? e.nativeEvent.getCoalescedEvents?.() ?? [] : [];
1144
+ for (const ev of events.length ? events : [e.nativeEvent]) {
1145
+ draw.continueStroke(clientToFlow(ev.clientX, ev.clientY), pointerPenSample(ev));
1146
+ }
1142
1147
  }
1143
1148
  };
1144
1149
  const handlePointerUp = (e) => {
@@ -1334,9 +1339,69 @@ import {
1334
1339
 
1335
1340
  // src/canvas/ShapeNode.tsx
1336
1341
  import { getShapeRenderData, isShapeDescription } from "@pixerate/editor/canvas";
1337
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1342
+
1343
+ // src/canvas/useShapeBrushstroke.tsx
1344
+ import { createContext, useContext, useEffect as useEffect7, useMemo as useMemo6, useState as useState6 } from "react";
1345
+ import {
1346
+ brushstrokeScaleBucket,
1347
+ isBrushedShape,
1348
+ listCanvasBrushes,
1349
+ renderShapeBrushstroke
1350
+ } from "@pixerate/editor/canvas";
1351
+ import { jsx as jsx4 } from "react/jsx-runtime";
1352
+ var ShapeNodeZoomContext = createContext(1);
1353
+ function ShapeNodeZoomProvider({ zoom, children }) {
1354
+ const bucket = brushstrokeScaleBucket(zoom);
1355
+ return /* @__PURE__ */ jsx4(ShapeNodeZoomContext.Provider, { value: bucket, children });
1356
+ }
1357
+ function useShapeBrushstroke(shape, width, height) {
1358
+ const zoom = useContext(ShapeNodeZoomContext);
1359
+ const brushed = isBrushedShape(shape) ? shape : null;
1360
+ const vb = brushed?.viewBox;
1361
+ const nodeScale = vb && width && height && vb[2] > 0 && vb[3] > 0 ? Math.max(width / vb[2], height / vb[3]) : 1;
1362
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
1363
+ const scale = brushstrokeScaleBucket(zoom * nodeScale, dpr);
1364
+ const [image, setImage] = useState6(null);
1365
+ useEffect7(() => {
1366
+ if (!brushed) {
1367
+ setImage(null);
1368
+ return;
1369
+ }
1370
+ let live = true;
1371
+ renderShapeBrushstroke(brushed, { scale }).then(
1372
+ (img) => live && setImage(img),
1373
+ (error) => {
1374
+ if (!live) return;
1375
+ setImage(null);
1376
+ console.warn("[pixerate] brushed stroke not rendered:", error instanceof Error ? error.message : error);
1377
+ }
1378
+ );
1379
+ return () => {
1380
+ live = false;
1381
+ };
1382
+ }, [brushed, scale]);
1383
+ return image;
1384
+ }
1385
+ function useCanvasBrushes() {
1386
+ const [state, setState] = useState6({ brushes: [], loading: true, error: null });
1387
+ useEffect7(() => {
1388
+ let live = true;
1389
+ listCanvasBrushes().then(
1390
+ (brushes) => live && setState({ brushes, loading: false, error: null }),
1391
+ (error) => live && setState({ brushes: [], loading: false, error: error instanceof Error ? error.message : String(error) })
1392
+ );
1393
+ return () => {
1394
+ live = false;
1395
+ };
1396
+ }, []);
1397
+ return useMemo6(() => state, [state]);
1398
+ }
1399
+
1400
+ // src/canvas/ShapeNode.tsx
1401
+ import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1338
1402
  function ShapeNode({ id, data, width, height, selected, className = "", children }) {
1339
1403
  const shape = data?.shape;
1404
+ const brushImage = useShapeBrushstroke(isShapeDescription(shape) ? shape : null, width, height);
1340
1405
  if (!isShapeDescription(shape)) return null;
1341
1406
  const render = getShapeRenderData(shape, { width, height }, { id: id || data?.id });
1342
1407
  return /* @__PURE__ */ jsxs4(
@@ -1358,12 +1423,24 @@ function ShapeNode({ id, data, width, height, selected, className = "", children
1358
1423
  "aria-hidden": "true",
1359
1424
  children: [
1360
1425
  (render.fillGradient || render.strokeGradient) && /* @__PURE__ */ jsxs4("defs", { children: [
1361
- render.fillGradient && render.fillGradient.type === "linear" && /* @__PURE__ */ jsx4("linearGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx4("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1362
- render.fillGradient && render.fillGradient.type === "radial" && /* @__PURE__ */ jsx4("radialGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx4("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1363
- render.strokeGradient && render.strokeGradient.type === "linear" && /* @__PURE__ */ jsx4("linearGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx4("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1364
- render.strokeGradient && render.strokeGradient.type === "radial" && /* @__PURE__ */ jsx4("radialGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx4("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) })
1426
+ render.fillGradient && render.fillGradient.type === "linear" && /* @__PURE__ */ jsx5("linearGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx5("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1427
+ render.fillGradient && render.fillGradient.type === "radial" && /* @__PURE__ */ jsx5("radialGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx5("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1428
+ render.strokeGradient && render.strokeGradient.type === "linear" && /* @__PURE__ */ jsx5("linearGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx5("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1429
+ render.strokeGradient && render.strokeGradient.type === "radial" && /* @__PURE__ */ jsx5("radialGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx5("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) })
1365
1430
  ] }),
1366
- /* @__PURE__ */ jsx4(
1431
+ brushImage ? /* @__PURE__ */ jsx5("g", { transform: render.transform, children: /* @__PURE__ */ jsx5(
1432
+ "image",
1433
+ {
1434
+ className: "pixerate-shape-brushstroke",
1435
+ href: brushImage.href,
1436
+ x: brushImage.x,
1437
+ y: brushImage.y,
1438
+ width: brushImage.width,
1439
+ height: brushImage.height,
1440
+ preserveAspectRatio: "none",
1441
+ opacity: render.opacity
1442
+ }
1443
+ ) }) : /* @__PURE__ */ jsx5(
1367
1444
  "path",
1368
1445
  {
1369
1446
  d: render.d,
@@ -1514,17 +1591,17 @@ import {
1514
1591
  } from "@pixerate/editor/canvas";
1515
1592
 
1516
1593
  // src/canvas/useCanvasAutoLayout.ts
1517
- import { useLayoutEffect, useMemo as useMemo6, useRef as useRef11, useState as useState6 } from "react";
1594
+ import { useLayoutEffect, useMemo as useMemo7, useRef as useRef11, useState as useState7 } from "react";
1518
1595
  import {
1519
1596
  createCanvasAutoLayoutController
1520
1597
  } from "@pixerate/editor/canvas";
1521
1598
  function useCanvasAutoLayout(options) {
1522
- const [guides, setGuides] = useState6([]);
1599
+ const [guides, setGuides] = useState7([]);
1523
1600
  const nodesRef = useRef11(options.nodes);
1524
1601
  nodesRef.current = options.nodes;
1525
1602
  const optionsRef = useRef11(options);
1526
1603
  optionsRef.current = options;
1527
- const controller = useMemo6(
1604
+ const controller = useMemo7(
1528
1605
  () => createCanvasAutoLayoutController({
1529
1606
  getNodes: () => nodesRef.current,
1530
1607
  setNodes: (next) => {
@@ -1553,7 +1630,7 @@ function useCanvasAutoLayout(options) {
1553
1630
  useLayoutEffect(() => {
1554
1631
  if (controller.hasPending) controller.flush();
1555
1632
  }, [options.nodes, controller]);
1556
- return useMemo6(
1633
+ return useMemo7(
1557
1634
  () => ({
1558
1635
  ...controller,
1559
1636
  /** Drop marker of the active reorder drag (flow coordinates). */
@@ -1567,7 +1644,7 @@ function useCanvasAutoLayout(options) {
1567
1644
  }
1568
1645
 
1569
1646
  // src/canvas/useCanvasTokens.ts
1570
- import { useCallback as useCallback9, useEffect as useEffect7, useRef as useRef12, useSyncExternalStore as useSyncExternalStore2 } from "react";
1647
+ import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef12, useSyncExternalStore as useSyncExternalStore2 } from "react";
1571
1648
  import {
1572
1649
  bindCanvasToken,
1573
1650
  detachTokenBindings,
@@ -1581,7 +1658,7 @@ function useCanvasTokens(options) {
1581
1658
  const latest = useRef12({ nodes, setNodes, modes, properties });
1582
1659
  latest.current = { nodes, setNodes, modes, properties };
1583
1660
  const opts = () => ({ modes: latest.current.modes, properties: latest.current.properties });
1584
- useEffect7(
1661
+ useEffect8(
1585
1662
  () => store.subscribe((event) => {
1586
1663
  if (event.deletedVariableIds.length === 0) return;
1587
1664
  latest.current.setNodes((prev) => detachTokenBindings(prev, event.previous, event.deletedVariableIds, opts()));
@@ -1590,7 +1667,7 @@ function useCanvasTokens(options) {
1590
1667
  [store]
1591
1668
  );
1592
1669
  const modesKey = JSON.stringify(modes ?? {});
1593
- useEffect7(() => {
1670
+ useEffect8(() => {
1594
1671
  const next = resolveCanvasTokens(nodes, state, opts());
1595
1672
  if (next !== nodes) setNodes(next);
1596
1673
  }, [nodes, state, modesKey, properties]);
@@ -1632,7 +1709,7 @@ import {
1632
1709
  getTokensForProperty
1633
1710
  } from "@pixerate/editor/canvas";
1634
1711
  import { getCurrentMode, resolveToken } from "@pixerate/editor/tokens";
1635
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1712
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1636
1713
  function TokenBindingPicker({
1637
1714
  state,
1638
1715
  nodes,
@@ -1656,7 +1733,7 @@ function TokenBindingPicker({
1656
1733
  const candidates = getTokensForProperty(state, property, first);
1657
1734
  const resolved = value && value !== "__mixed" ? resolveToken(state, value, modes) : null;
1658
1735
  return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-property": property.key, children: [
1659
- /* @__PURE__ */ jsx5("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1736
+ /* @__PURE__ */ jsx6("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1660
1737
  /* @__PURE__ */ jsxs5(
1661
1738
  "select",
1662
1739
  {
@@ -1672,8 +1749,8 @@ function TokenBindingPicker({
1672
1749
  }
1673
1750
  },
1674
1751
  children: [
1675
- value === "__mixed" ? /* @__PURE__ */ jsx5("option", { value: "__mixed", children: "Mixed" }) : null,
1676
- /* @__PURE__ */ jsx5("option", { value: "", children: "None" }),
1752
+ value === "__mixed" ? /* @__PURE__ */ jsx6("option", { value: "__mixed", children: "Mixed" }) : null,
1753
+ /* @__PURE__ */ jsx6("option", { value: "", children: "None" }),
1677
1754
  candidates.map((v) => {
1678
1755
  const collection = state.collections.find((c) => c.id === v.collectionId);
1679
1756
  return /* @__PURE__ */ jsxs5("option", { value: v.id, children: [
@@ -1684,17 +1761,17 @@ function TokenBindingPicker({
1684
1761
  ]
1685
1762
  }
1686
1763
  ),
1687
- /* @__PURE__ */ jsx5("span", { className: "w-16 shrink-0 truncate text-right text-muted-foreground", "data-token-resolved": "", children: resolved?.ok ? property.type === "color" ? /* @__PURE__ */ jsx5(
1764
+ /* @__PURE__ */ jsx6("span", { className: "w-16 shrink-0 truncate text-right text-muted-foreground", "data-token-resolved": "", children: resolved?.ok ? property.type === "color" ? /* @__PURE__ */ jsx6(
1688
1765
  "span",
1689
1766
  {
1690
1767
  className: "inline-block h-3 w-3 rounded border border-border align-middle",
1691
1768
  style: { background: String(resolved.value) },
1692
1769
  title: String(resolved.value)
1693
1770
  }
1694
- ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx5("span", { title: resolved.message, children: "\u26A0" }) : null })
1771
+ ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx6("span", { title: resolved.message, children: "\u26A0" }) : null })
1695
1772
  ] }, property.key);
1696
1773
  }),
1697
- state.collections.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1774
+ state.collections.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1698
1775
  ] });
1699
1776
  }
1700
1777
  function TokenModeSwitcher({
@@ -1712,11 +1789,11 @@ function TokenModeSwitcher({
1712
1789
  const parent = first.parentId ? nodes.find((n) => n.id === first.parentId) : void 0;
1713
1790
  const inherited = parent ? getEffectiveTokenModes2(parent, nodes, state, options) : { ...options.modes };
1714
1791
  const collections = state.collections.filter((c) => showSingleMode || c.modes.length > 1);
1715
- return /* @__PURE__ */ jsx5("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1792
+ return /* @__PURE__ */ jsx6("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1716
1793
  const auto = getCurrentMode(collection, inherited);
1717
1794
  const explicit = collection.modes.some((m) => m.id === own[collection.id]) ? own[collection.id] : "";
1718
1795
  return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-collection": collection.id, children: [
1719
- /* @__PURE__ */ jsx5("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1796
+ /* @__PURE__ */ jsx6("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1720
1797
  /* @__PURE__ */ jsxs5(
1721
1798
  "select",
1722
1799
  {
@@ -1730,7 +1807,7 @@ function TokenModeSwitcher({
1730
1807
  auto.name,
1731
1808
  ")"
1732
1809
  ] }),
1733
- collection.modes.map((m) => /* @__PURE__ */ jsx5("option", { value: m.id, children: m.name }, m.id))
1810
+ collection.modes.map((m) => /* @__PURE__ */ jsx6("option", { value: m.id, children: m.name }, m.id))
1734
1811
  ]
1735
1812
  }
1736
1813
  )
@@ -1761,7 +1838,7 @@ import {
1761
1838
  } from "@pixerate/editor/canvas";
1762
1839
 
1763
1840
  // src/canvas/useCanvasAnimation.ts
1764
- import { useEffect as useEffect8, useMemo as useMemo7, useRef as useRef13, useSyncExternalStore as useSyncExternalStore3 } from "react";
1841
+ import { useEffect as useEffect9, useMemo as useMemo8, useRef as useRef13, useSyncExternalStore as useSyncExternalStore3 } from "react";
1765
1842
  import {
1766
1843
  createCanvasAnimationController
1767
1844
  } from "@pixerate/editor/canvas";
@@ -1770,7 +1847,7 @@ function useCanvasAnimation(options) {
1770
1847
  nodesRef.current = options.nodes;
1771
1848
  const optionsRef = useRef13(options);
1772
1849
  optionsRef.current = options;
1773
- const controller = useMemo7(
1850
+ const controller = useMemo8(
1774
1851
  () => createCanvasAnimationController({
1775
1852
  getNodes: () => nodesRef.current,
1776
1853
  setNodes: (next) => {
@@ -1797,14 +1874,14 @@ function useCanvasAnimation(options) {
1797
1874
  }),
1798
1875
  []
1799
1876
  );
1800
- useEffect8(() => () => controller.destroy(), [controller]);
1801
- useEffect8(() => {
1877
+ useEffect9(() => () => controller.destroy(), [controller]);
1878
+ useEffect9(() => {
1802
1879
  if (options.loop !== void 0) controller.setLoop(options.loop);
1803
1880
  }, [controller, options.loop]);
1804
- useEffect8(() => {
1881
+ useEffect9(() => {
1805
1882
  if (options.playbackRate !== void 0) controller.setPlaybackRate(options.playbackRate);
1806
1883
  }, [controller, options.playbackRate]);
1807
- useEffect8(() => {
1884
+ useEffect9(() => {
1808
1885
  if (options.recording !== void 0) controller.setRecording(options.recording);
1809
1886
  }, [controller, options.recording]);
1810
1887
  const state = useSyncExternalStore3(
@@ -1812,11 +1889,11 @@ function useCanvasAnimation(options) {
1812
1889
  () => controller.getState(),
1813
1890
  () => controller.getState()
1814
1891
  );
1815
- useEffect8(() => {
1892
+ useEffect9(() => {
1816
1893
  controller.refresh();
1817
1894
  }, [controller, options.nodes]);
1818
- const displayNodes = useMemo7(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
1819
- return useMemo7(
1895
+ const displayNodes = useMemo8(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
1896
+ return useMemo8(
1820
1897
  () => ({
1821
1898
  ...controller,
1822
1899
  state,
@@ -1837,7 +1914,7 @@ function useCanvasAnimation(options) {
1837
1914
  }
1838
1915
 
1839
1916
  // src/canvas/AnimationTimeline.tsx
1840
- import { useEffect as useEffect9, useRef as useRef14 } from "react";
1917
+ import { useEffect as useEffect10, useRef as useRef14 } from "react";
1841
1918
  import {
1842
1919
  ANIMATION_EASING_LABELS,
1843
1920
  ANIMATION_EASING_PRESETS,
@@ -1846,7 +1923,7 @@ import {
1846
1923
  timeToX,
1847
1924
  xToTime
1848
1925
  } from "@pixerate/editor/canvas";
1849
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1926
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1850
1927
  var LOOP_LABELS = { none: "Once", loop: "Loop", "ping-pong": "Ping-pong" };
1851
1928
  var easingValue = (easing) => typeof easing === "string" ? easing : easing ? "custom" : "linear";
1852
1929
  function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8, labelOf, className = "" }) {
@@ -1861,7 +1938,7 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1861
1938
  return "";
1862
1939
  })();
1863
1940
  const dragRef = useRef14(null);
1864
- useEffect9(() => {
1941
+ useEffect10(() => {
1865
1942
  const onKey = (event) => {
1866
1943
  if (event.key === "Escape" && dragRef.current) {
1867
1944
  dragRef.current = null;
@@ -1919,8 +1996,8 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1919
1996
  const labelWidth = 120;
1920
1997
  return /* @__PURE__ */ jsxs6("div", { "data-animation-timeline": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, tabIndex: 0, onKeyDown: onTimelineKey, children: [
1921
1998
  /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2", role: "toolbar", "aria-label": "Animation", children: [
1922
- /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "toggle-play", "aria-label": state.playing ? "Pause" : "Play", onClick: () => animation.toggle(), children: state.playing ? "\u275A\u275A" : "\u25B6" }),
1923
- /* @__PURE__ */ jsx6(
1999
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "toggle-play", "aria-label": state.playing ? "Pause" : "Play", onClick: () => animation.toggle(), children: state.playing ? "\u275A\u275A" : "\u25B6" }),
2000
+ /* @__PURE__ */ jsx7(
1924
2001
  "button",
1925
2002
  {
1926
2003
  type: "button",
@@ -1932,7 +2009,7 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1932
2009
  children: "\u25CF Rec"
1933
2010
  }
1934
2011
  ),
1935
- /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
2012
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
1936
2013
  /* @__PURE__ */ jsxs6("span", { "data-time": "", className: "tabular-nums text-muted-foreground", children: [
1937
2014
  formatAnimationTime(state.time),
1938
2015
  " / ",
@@ -1948,13 +2025,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1948
2025
  disabled: state.selection.length === 0,
1949
2026
  onChange: (e) => e.target.value && e.target.value !== "custom" && animation.setEasing(e.target.value),
1950
2027
  children: [
1951
- /* @__PURE__ */ jsx6("option", { value: "", children: "Easing\u2026" }),
1952
- ANIMATION_EASING_PRESETS.map((preset) => /* @__PURE__ */ jsx6("option", { value: preset, children: ANIMATION_EASING_LABELS[preset] }, preset)),
1953
- selectedEasing === "custom" ? /* @__PURE__ */ jsx6("option", { value: "custom", children: "Custom" }) : null
2028
+ /* @__PURE__ */ jsx7("option", { value: "", children: "Easing\u2026" }),
2029
+ ANIMATION_EASING_PRESETS.map((preset) => /* @__PURE__ */ jsx7("option", { value: preset, children: ANIMATION_EASING_LABELS[preset] }, preset)),
2030
+ selectedEasing === "custom" ? /* @__PURE__ */ jsx7("option", { value: "custom", children: "Custom" }) : null
1954
2031
  ]
1955
2032
  }
1956
2033
  ),
1957
- /* @__PURE__ */ jsx6(
2034
+ /* @__PURE__ */ jsx7(
1958
2035
  "select",
1959
2036
  {
1960
2037
  "aria-label": "Loop",
@@ -1962,12 +2039,12 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1962
2039
  className: "rounded border border-border bg-background px-1 py-0.5",
1963
2040
  value: state.loop,
1964
2041
  onChange: (e) => animation.setLoop(e.target.value),
1965
- children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx6("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
2042
+ children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx7("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
1966
2043
  }
1967
2044
  )
1968
2045
  ] }),
1969
2046
  /* @__PURE__ */ jsxs6("div", { className: "flex", children: [
1970
- /* @__PURE__ */ jsx6("div", { style: { width: labelWidth }, className: "shrink-0" }),
2047
+ /* @__PURE__ */ jsx7("div", { style: { width: labelWidth }, className: "shrink-0" }),
1971
2048
  /* @__PURE__ */ jsxs6(
1972
2049
  "div",
1973
2050
  {
@@ -1976,15 +2053,15 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1976
2053
  style: { width },
1977
2054
  onPointerDown: onRulerDown,
1978
2055
  children: [
1979
- ticks.map((tick) => /* @__PURE__ */ jsx6("span", { className: "absolute top-0 -translate-x-1/2 text-[10px] text-muted-foreground", style: { left: tick.x }, children: tick.label }, tick.time)),
1980
- /* @__PURE__ */ jsx6("div", { "data-playhead": "", className: "absolute top-0 h-5 w-px bg-red-500", style: { left: playheadX } })
2056
+ ticks.map((tick) => /* @__PURE__ */ jsx7("span", { className: "absolute top-0 -translate-x-1/2 text-[10px] text-muted-foreground", style: { left: tick.x }, children: tick.label }, tick.time)),
2057
+ /* @__PURE__ */ jsx7("div", { "data-playhead": "", className: "absolute top-0 h-5 w-px bg-red-500", style: { left: playheadX } })
1981
2058
  ]
1982
2059
  }
1983
2060
  )
1984
2061
  ] }),
1985
- rows.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", "data-timeline-empty": "", children: "Select an animated node, or press \u25C6 Key to add a keyframe." }) : rows.map((row) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center", "data-track": row.id, children: [
2062
+ rows.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground", "data-timeline-empty": "", children: "Select an animated node, or press \u25C6 Key to add a keyframe." }) : rows.map((row) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center", "data-track": row.id, children: [
1986
2063
  /* @__PURE__ */ jsxs6("label", { className: "flex shrink-0 items-center gap-1 truncate pr-2", style: { width: labelWidth }, title: `${row.nodeLabel} \xB7 ${row.label}`, children: [
1987
- /* @__PURE__ */ jsx6(
2064
+ /* @__PURE__ */ jsx7(
1988
2065
  "input",
1989
2066
  {
1990
2067
  type: "checkbox",
@@ -1999,9 +2076,9 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1999
2076
  ] })
2000
2077
  ] }),
2001
2078
  /* @__PURE__ */ jsxs6("div", { "data-lane": "", className: `relative h-5 rounded bg-muted/40 ${row.enabled ? "" : "opacity-50"}`, style: { width }, children: [
2002
- /* @__PURE__ */ jsx6("div", { className: "pointer-events-none absolute top-0 h-5 w-px bg-red-500/60", style: { left: playheadX } }),
2003
- state.drag?.target != null ? /* @__PURE__ */ jsx6("div", { "data-snap-target": "", className: "pointer-events-none absolute top-0 h-5 w-px bg-sky-400", style: { left: timeToX(state.drag.target, duration, width) } }) : null,
2004
- row.keyframes.map((k) => /* @__PURE__ */ jsx6(
2079
+ /* @__PURE__ */ jsx7("div", { className: "pointer-events-none absolute top-0 h-5 w-px bg-red-500/60", style: { left: playheadX } }),
2080
+ state.drag?.target != null ? /* @__PURE__ */ jsx7("div", { "data-snap-target": "", className: "pointer-events-none absolute top-0 h-5 w-px bg-sky-400", style: { left: timeToX(state.drag.target, duration, width) } }) : null,
2081
+ row.keyframes.map((k) => /* @__PURE__ */ jsx7(
2005
2082
  "button",
2006
2083
  {
2007
2084
  type: "button",
@@ -2099,7 +2176,7 @@ import {
2099
2176
  } from "@pixerate/editor/canvas";
2100
2177
 
2101
2178
  // src/canvas/useCanvasComponents.ts
2102
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo8, useRef as useRef15 } from "react";
2179
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo9, useRef as useRef15 } from "react";
2103
2180
  import {
2104
2181
  createCanvasComponentsController
2105
2182
  } from "@pixerate/editor/canvas";
@@ -2108,7 +2185,7 @@ function useCanvasComponents(options) {
2108
2185
  nodesRef.current = options.nodes;
2109
2186
  const optionsRef = useRef15(options);
2110
2187
  optionsRef.current = options;
2111
- const controller = useMemo8(
2188
+ const controller = useMemo9(
2112
2189
  () => createCanvasComponentsController({
2113
2190
  getNodes: () => nodesRef.current,
2114
2191
  setNodes: (next) => {
@@ -2144,15 +2221,15 @@ function useCanvasComponents(options) {
2144
2221
  }
2145
2222
 
2146
2223
  // src/canvas/CanvasComponentsPanel.tsx
2147
- import { useState as useState7 } from "react";
2224
+ import { useState as useState8 } from "react";
2148
2225
  import {
2149
2226
  CANVAS_COMPONENT_DRAG_TYPE,
2150
2227
  inspectCanvasInstance,
2151
2228
  listCanvasComponents
2152
2229
  } from "@pixerate/editor/canvas";
2153
- import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
2230
+ import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2154
2231
  function InlineField({ value, label, onCommit }) {
2155
- return /* @__PURE__ */ jsx7(
2232
+ return /* @__PURE__ */ jsx8(
2156
2233
  "input",
2157
2234
  {
2158
2235
  type: "text",
@@ -2183,10 +2260,10 @@ function CanvasComponentsPanel({
2183
2260
  search = true,
2184
2261
  className = ""
2185
2262
  }) {
2186
- const [query, setQuery] = useState7("");
2187
- const [propsOpen, setPropsOpen] = useState7(null);
2188
- const [menuFor, setMenuFor] = useState7(null);
2189
- const [editing, setEditing] = useState7(null);
2263
+ const [query, setQuery] = useState8("");
2264
+ const [propsOpen, setPropsOpen] = useState8(null);
2265
+ const [menuFor, setMenuFor] = useState8(null);
2266
+ const [editing, setEditing] = useState8(null);
2190
2267
  const { components: all, sets } = listCanvasComponents(nodes);
2191
2268
  const q = query.trim().toLowerCase();
2192
2269
  const match = (name) => !q || name.toLowerCase().includes(q);
@@ -2216,16 +2293,16 @@ function CanvasComponentsPanel({
2216
2293
  },
2217
2294
  children: [
2218
2295
  /* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
2219
- /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
2296
+ /* @__PURE__ */ jsx8("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
2220
2297
  name
2221
2298
  ] }),
2222
- /* @__PURE__ */ jsx7("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
2299
+ /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
2223
2300
  ]
2224
2301
  },
2225
2302
  id
2226
2303
  );
2227
2304
  return /* @__PURE__ */ jsxs7("div", { "data-components-panel": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
2228
- search ? /* @__PURE__ */ jsx7(
2305
+ search ? /* @__PURE__ */ jsx8(
2229
2306
  "input",
2230
2307
  {
2231
2308
  type: "search",
@@ -2239,7 +2316,7 @@ function CanvasComponentsPanel({
2239
2316
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
2240
2317
  item(set.id, set.name, set.variants.reduce((sum, v) => sum + v.instanceCount, 0), { "data-kind": "set" }),
2241
2318
  components ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2242
- /* @__PURE__ */ jsx7(
2319
+ /* @__PURE__ */ jsx8(
2243
2320
  "button",
2244
2321
  {
2245
2322
  type: "button",
@@ -2251,7 +2328,7 @@ function CanvasComponentsPanel({
2251
2328
  children: "+\xA0Variant"
2252
2329
  }
2253
2330
  ),
2254
- /* @__PURE__ */ jsx7(
2331
+ /* @__PURE__ */ jsx8(
2255
2332
  "button",
2256
2333
  {
2257
2334
  type: "button",
@@ -2266,8 +2343,8 @@ function CanvasComponentsPanel({
2266
2343
  )
2267
2344
  ] }) : null
2268
2345
  ] }),
2269
- components && propsOpen === set.id ? /* @__PURE__ */ jsx7("div", { "data-variant-properties": set.id, className: "ml-3 flex flex-col gap-1 border-l border-border py-1 pl-2", children: set.options.map((option) => /* @__PURE__ */ jsxs7("div", { "data-variant-option": option.name, className: "flex flex-col gap-0.5", children: [
2270
- /* @__PURE__ */ jsx7(
2346
+ components && propsOpen === set.id ? /* @__PURE__ */ jsx8("div", { "data-variant-properties": set.id, className: "ml-3 flex flex-col gap-1 border-l border-border py-1 pl-2", children: set.options.map((option) => /* @__PURE__ */ jsxs7("div", { "data-variant-option": option.name, className: "flex flex-col gap-0.5", children: [
2347
+ /* @__PURE__ */ jsx8(
2271
2348
  InlineField,
2272
2349
  {
2273
2350
  value: option.name,
@@ -2275,7 +2352,7 @@ function CanvasComponentsPanel({
2275
2352
  onCommit: (next) => report(components.renameVariantProperty(option.name, next, set.id))
2276
2353
  }
2277
2354
  ),
2278
- /* @__PURE__ */ jsx7("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx7(
2355
+ /* @__PURE__ */ jsx8("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx8(
2279
2356
  InlineField,
2280
2357
  {
2281
2358
  value,
@@ -2285,7 +2362,7 @@ function CanvasComponentsPanel({
2285
2362
  value
2286
2363
  )) })
2287
2364
  ] }, option.name)) }) : null,
2288
- /* @__PURE__ */ jsx7("div", { className: "ml-3 flex flex-col border-l border-border pl-1", children: set.variants.map((v) => /* @__PURE__ */ jsxs7("div", { "data-variant-row": v.id, className: "flex flex-col", children: [
2365
+ /* @__PURE__ */ jsx8("div", { className: "ml-3 flex flex-col border-l border-border pl-1", children: set.variants.map((v) => /* @__PURE__ */ jsxs7("div", { "data-variant-row": v.id, className: "flex flex-col", children: [
2289
2366
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
2290
2367
  item(
2291
2368
  v.id,
@@ -2297,7 +2374,7 @@ function CanvasComponentsPanel({
2297
2374
  setMenuFor(v.id);
2298
2375
  } : void 0
2299
2376
  ),
2300
- components ? /* @__PURE__ */ jsx7(
2377
+ components ? /* @__PURE__ */ jsx8(
2301
2378
  "button",
2302
2379
  {
2303
2380
  type: "button",
@@ -2312,7 +2389,7 @@ function CanvasComponentsPanel({
2312
2389
  ) : null
2313
2390
  ] }),
2314
2391
  components && menuFor === v.id ? /* @__PURE__ */ jsxs7("div", { role: "menu", "data-variant-menu": v.id, className: "ml-2 flex flex-col rounded border border-border bg-background p-1 shadow", children: [
2315
- /* @__PURE__ */ jsx7(
2392
+ /* @__PURE__ */ jsx8(
2316
2393
  "button",
2317
2394
  {
2318
2395
  type: "button",
@@ -2326,7 +2403,7 @@ function CanvasComponentsPanel({
2326
2403
  children: "Edit values\u2026"
2327
2404
  }
2328
2405
  ),
2329
- /* @__PURE__ */ jsx7(
2406
+ /* @__PURE__ */ jsx8(
2330
2407
  "button",
2331
2408
  {
2332
2409
  type: "button",
@@ -2340,7 +2417,7 @@ function CanvasComponentsPanel({
2340
2417
  children: "Duplicate"
2341
2418
  }
2342
2419
  ),
2343
- /* @__PURE__ */ jsx7(
2420
+ /* @__PURE__ */ jsx8(
2344
2421
  "button",
2345
2422
  {
2346
2423
  type: "button",
@@ -2372,8 +2449,8 @@ function CanvasComponentsPanel({
2372
2449
  },
2373
2450
  children: [
2374
2451
  set.options.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", children: [
2375
- /* @__PURE__ */ jsx7("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2376
- /* @__PURE__ */ jsx7(
2452
+ /* @__PURE__ */ jsx8("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2453
+ /* @__PURE__ */ jsx8(
2377
2454
  "input",
2378
2455
  {
2379
2456
  type: "text",
@@ -2385,8 +2462,8 @@ function CanvasComponentsPanel({
2385
2462
  )
2386
2463
  ] }, option.name)),
2387
2464
  /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
2388
- /* @__PURE__ */ jsx7("button", { type: "submit", className: "rounded border border-border px-2 py-0.5 hover:bg-muted", "data-action": "save-variant", children: "Save" }),
2389
- /* @__PURE__ */ jsx7("button", { type: "button", className: "rounded px-2 py-0.5 hover:bg-muted", "data-action": "cancel-variant", onClick: () => setEditing(null), children: "Cancel" })
2465
+ /* @__PURE__ */ jsx8("button", { type: "submit", className: "rounded border border-border px-2 py-0.5 hover:bg-muted", "data-action": "save-variant", children: "Save" }),
2466
+ /* @__PURE__ */ jsx8("button", { type: "button", className: "rounded px-2 py-0.5 hover:bg-muted", "data-action": "cancel-variant", onClick: () => setEditing(null), children: "Cancel" })
2390
2467
  ] })
2391
2468
  ]
2392
2469
  }
@@ -2394,7 +2471,7 @@ function CanvasComponentsPanel({
2394
2471
  ] }, v.id)) })
2395
2472
  ] }, set.id)),
2396
2473
  lone.map((c) => item(c.id, c.name, c.instanceCount, { "data-kind": "component" })),
2397
- all.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2474
+ all.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2398
2475
  ] });
2399
2476
  }
2400
2477
  function CanvasInstanceInspector({
@@ -2415,25 +2492,25 @@ function CanvasInstanceInspector({
2415
2492
  const btn = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-40";
2416
2493
  return /* @__PURE__ */ jsxs7("div", { "data-instance-inspector": "", "data-instance-id": info.id, className: `flex flex-col gap-2 text-xs text-foreground ${className}`, children: [
2417
2494
  /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-2", children: [
2418
- /* @__PURE__ */ jsx7("span", { className: "truncate font-medium", "data-instance-main": "", children: linked ? info.componentName : "Main component missing" }),
2419
- info.nested ? /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "Nested" }) : null
2495
+ /* @__PURE__ */ jsx8("span", { className: "truncate font-medium", "data-instance-main": "", children: linked ? info.componentName : "Main component missing" }),
2496
+ info.nested ? /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground", children: "Nested" }) : null
2420
2497
  ] }),
2421
2498
  info.variantOptions.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-variant-property": option.name, children: [
2422
- /* @__PURE__ */ jsx7("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2423
- /* @__PURE__ */ jsx7(
2499
+ /* @__PURE__ */ jsx8("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2500
+ /* @__PURE__ */ jsx8(
2424
2501
  "select",
2425
2502
  {
2426
2503
  "aria-label": `${option.name} variant`,
2427
2504
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2428
2505
  value: info.variant[option.name] ?? "",
2429
2506
  onChange: (e) => setProp(option.name, e.currentTarget.value),
2430
- children: option.values.map((v) => /* @__PURE__ */ jsx7("option", { value: v, children: v }, v))
2507
+ children: option.values.map((v) => /* @__PURE__ */ jsx8("option", { value: v, children: v }, v))
2431
2508
  }
2432
2509
  )
2433
2510
  ] }, option.name)),
2434
2511
  info.properties.map((p) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-component-property": p.name, children: [
2435
- /* @__PURE__ */ jsx7("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: p.name }),
2436
- p.type === "boolean" ? /* @__PURE__ */ jsx7("input", { type: "checkbox", "aria-label": p.name, checked: p.value === true, onChange: (e) => setProp(p.name, e.currentTarget.checked) }) : p.type === "text" ? /* @__PURE__ */ jsx7(
2512
+ /* @__PURE__ */ jsx8("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: p.name }),
2513
+ p.type === "boolean" ? /* @__PURE__ */ jsx8("input", { type: "checkbox", "aria-label": p.name, checked: p.value === true, onChange: (e) => setProp(p.name, e.currentTarget.checked) }) : p.type === "text" ? /* @__PURE__ */ jsx8(
2437
2514
  "input",
2438
2515
  {
2439
2516
  type: "text",
@@ -2448,21 +2525,21 @@ function CanvasInstanceInspector({
2448
2525
  }
2449
2526
  },
2450
2527
  String(p.value)
2451
- ) : /* @__PURE__ */ jsx7(
2528
+ ) : /* @__PURE__ */ jsx8(
2452
2529
  "select",
2453
2530
  {
2454
2531
  "aria-label": p.name,
2455
2532
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2456
2533
  value: String(p.value),
2457
2534
  onChange: (e) => setProp(p.name, e.currentTarget.value),
2458
- children: allComponents.filter((c) => !p.preferred?.length || p.preferred.includes(c.id) || c.id === p.value).map((c) => /* @__PURE__ */ jsx7("option", { value: c.id, children: c.name }, c.id))
2535
+ children: allComponents.filter((c) => !p.preferred?.length || p.preferred.includes(c.id) || c.id === p.value).map((c) => /* @__PURE__ */ jsx8("option", { value: c.id, children: c.name }, c.id))
2459
2536
  }
2460
2537
  )
2461
2538
  ] }, p.name)),
2462
2539
  info.overrides.length ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", "data-instance-overrides": "", children: [
2463
- /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "Overrides" }),
2540
+ /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground", children: "Overrides" }),
2464
2541
  info.overrides.map((o) => /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", "data-override-layer": o.id, children: [
2465
- /* @__PURE__ */ jsx7("span", { className: "mr-1 truncate", children: o.name }),
2542
+ /* @__PURE__ */ jsx8("span", { className: "mr-1 truncate", children: o.name }),
2466
2543
  o.groups.map((g) => /* @__PURE__ */ jsxs7(
2467
2544
  "button",
2468
2545
  {
@@ -2482,9 +2559,9 @@ function CanvasInstanceInspector({
2482
2559
  ] }) : null,
2483
2560
  /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1", children: [
2484
2561
  linked ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2485
- /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "reset", onClick: () => report(components.resetOverrides(info.id)), children: "Reset all" }),
2486
- /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "go-to-main", onClick: () => components.goToMainComponent(info.id), children: "Go to main" }),
2487
- /* @__PURE__ */ jsx7(
2562
+ /* @__PURE__ */ jsx8("button", { type: "button", className: btn, "data-action": "reset", onClick: () => report(components.resetOverrides(info.id)), children: "Reset all" }),
2563
+ /* @__PURE__ */ jsx8("button", { type: "button", className: btn, "data-action": "go-to-main", onClick: () => components.goToMainComponent(info.id), children: "Go to main" }),
2564
+ /* @__PURE__ */ jsx8(
2488
2565
  "button",
2489
2566
  {
2490
2567
  type: "button",
@@ -2495,8 +2572,8 @@ function CanvasInstanceInspector({
2495
2572
  children: "Push changes"
2496
2573
  }
2497
2574
  )
2498
- ] }) : /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2499
- /* @__PURE__ */ jsx7(
2575
+ ] }) : /* @__PURE__ */ jsx8("button", { type: "button", className: btn, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2576
+ /* @__PURE__ */ jsx8(
2500
2577
  "button",
2501
2578
  {
2502
2579
  type: "button",
@@ -2553,6 +2630,17 @@ import {
2553
2630
  streamlinePoints,
2554
2631
  strokePointsToPathData as strokePointsToPathData2,
2555
2632
  strokeToShapeNode,
2633
+ pointerPenSample as pointerPenSample2,
2634
+ freehandBrushstroke,
2635
+ isBrushedShape as isBrushedShape2,
2636
+ listCanvasBrushes as listCanvasBrushes2,
2637
+ validateCanvasBrush,
2638
+ renderShapeBrushstroke as renderShapeBrushstroke2,
2639
+ getBrushedShapeSvg,
2640
+ loadCanvasBrushstrokeRenderer,
2641
+ brushstrokeScaleBucket as brushstrokeScaleBucket2,
2642
+ pathDataToPolyline,
2643
+ profileAlongPolyline,
2556
2644
  CANVAS_GRADIENT_PRESETS,
2557
2645
  isCanvasGradient,
2558
2646
  parseCssGradient,
@@ -2564,7 +2652,7 @@ import {
2564
2652
  // src/canvas/MermaidFamilyScene.tsx
2565
2653
  import { useId } from "react";
2566
2654
  import { diagramTextBaseline } from "@pixerate/editor/canvas";
2567
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
2655
+ import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2568
2656
  function MermaidFamilyScene({
2569
2657
  scene,
2570
2658
  padding = 16,
@@ -2609,14 +2697,14 @@ function MermaidFamilyScene({
2609
2697
  onClick: interactive ? (event) => onSelect?.(null, event) : void 0,
2610
2698
  children: [
2611
2699
  /* @__PURE__ */ jsxs8("defs", { children: [
2612
- /* @__PURE__ */ jsx8("marker", { id: `${uid}-arrowclosed`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx8("path", { d: "M0,0 L10,5 L0,10 Z", style: { fill: markerColor } }) }),
2613
- /* @__PURE__ */ jsx8("marker", { id: `${uid}-arrow`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx8("path", { d: "M0,0 L10,5 L0,10", style: { fill: "none", stroke: markerColor, strokeWidth: 1.5 } }) })
2700
+ /* @__PURE__ */ jsx9("marker", { id: `${uid}-arrowclosed`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx9("path", { d: "M0,0 L10,5 L0,10 Z", style: { fill: markerColor } }) }),
2701
+ /* @__PURE__ */ jsx9("marker", { id: `${uid}-arrow`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx9("path", { d: "M0,0 L10,5 L0,10", style: { fill: "none", stroke: markerColor, strokeWidth: 1.5 } }) })
2614
2702
  ] }),
2615
2703
  scene.marks.map((mark) => {
2616
2704
  const common = { "data-role": mark.role, "data-target": mark.target, "data-selected": mark.selected ? "" : void 0, opacity: mark.opacity, ...handlers(mark) };
2617
2705
  switch (mark.kind) {
2618
2706
  case "rect":
2619
- return /* @__PURE__ */ jsx8(
2707
+ return /* @__PURE__ */ jsx9(
2620
2708
  "rect",
2621
2709
  {
2622
2710
  ...common,
@@ -2631,9 +2719,9 @@ function MermaidFamilyScene({
2631
2719
  mark.key
2632
2720
  );
2633
2721
  case "circle":
2634
- return /* @__PURE__ */ jsx8("circle", { ...common, cx: mark.cx, cy: mark.cy, r: mark.r, style: { fill: mark.fill, ...stroke(mark) } }, mark.key);
2722
+ return /* @__PURE__ */ jsx9("circle", { ...common, cx: mark.cx, cy: mark.cy, r: mark.r, style: { fill: mark.fill, ...stroke(mark) } }, mark.key);
2635
2723
  case "line":
2636
- return /* @__PURE__ */ jsx8(
2724
+ return /* @__PURE__ */ jsx9(
2637
2725
  "line",
2638
2726
  {
2639
2727
  ...common,
@@ -2649,7 +2737,7 @@ function MermaidFamilyScene({
2649
2737
  mark.key
2650
2738
  );
2651
2739
  case "path":
2652
- return /* @__PURE__ */ jsx8(
2740
+ return /* @__PURE__ */ jsx9(
2653
2741
  "path",
2654
2742
  {
2655
2743
  ...common,
@@ -2663,7 +2751,7 @@ function MermaidFamilyScene({
2663
2751
  mark.key
2664
2752
  );
2665
2753
  case "text":
2666
- return /* @__PURE__ */ jsx8(
2754
+ return /* @__PURE__ */ jsx9(
2667
2755
  "text",
2668
2756
  {
2669
2757
  ...common,
@@ -2691,7 +2779,7 @@ function MermaidFamilyScene({
2691
2779
  }
2692
2780
 
2693
2781
  // src/canvas/useMermaidDiagram.ts
2694
- import { useEffect as useEffect10, useMemo as useMemo9, useRef as useRef16, useState as useState8, useSyncExternalStore as useSyncExternalStore4 } from "react";
2782
+ import { useEffect as useEffect11, useMemo as useMemo10, useRef as useRef16, useState as useState9, useSyncExternalStore as useSyncExternalStore4 } from "react";
2695
2783
  import {
2696
2784
  DiagramController
2697
2785
  } from "@pixerate/editor/diagram";
@@ -2719,19 +2807,19 @@ function useMermaidDiagram(controllerOrOptions = {}, view = {}) {
2719
2807
  const layoutOptions = (controllerOrOptions instanceof DiagramController ? void 0 : controllerOrOptions.layoutOptions) ?? view.layoutOptions;
2720
2808
  const sceneTheme = (controllerOrOptions instanceof DiagramController ? void 0 : controllerOrOptions.sceneTheme) ?? view.sceneTheme;
2721
2809
  const state = useDiagramState(controller);
2722
- const canvas = useMemo9(
2810
+ const canvas = useMemo10(
2723
2811
  () => mermaidToCanvas(state.document, { ...layoutOptions, editable: state.editable }),
2724
2812
  // eslint-disable-next-line react-hooks/exhaustive-deps
2725
2813
  [state.document, state.editable]
2726
2814
  );
2727
- const scene = useMemo9(
2815
+ const scene = useMemo10(
2728
2816
  () => state.document.tier === "editable" ? familySceneFromLayout(state.document.family, state.document.graph, canvas.layout, { theme: { ...CSS_VARIABLE_DIAGRAM_SCENE_THEME, ...sceneTheme }, selection: state.selection }) : null,
2729
2817
  // eslint-disable-next-line react-hooks/exhaustive-deps
2730
2818
  [state.document, canvas.layout, state.selection]
2731
2819
  );
2732
2820
  const layoutRef = useRef16(canvas.layout);
2733
2821
  layoutRef.current = canvas.layout;
2734
- const actions = useMemo9(
2822
+ const actions = useMemo10(
2735
2823
  () => ({
2736
2824
  apply: (ops, label) => controller.apply(ops, label),
2737
2825
  load: (source, options) => controller.load(source, options),
@@ -2770,11 +2858,11 @@ function useMermaidStream(source, options = {}) {
2770
2858
  const { complete = false, duration = 300, ...layoutOptions } = options;
2771
2859
  const stream = useRef16(null);
2772
2860
  if (!stream.current) stream.current = new DiagramStream(layoutOptions);
2773
- const result = useMemo9(() => stream.current.update(source, complete), [source, complete]);
2774
- const [shown, setShown] = useState8({ layout: result.layout, settled: true });
2861
+ const result = useMemo10(() => stream.current.update(source, complete), [source, complete]);
2862
+ const [shown, setShown] = useState9({ layout: result.layout, settled: true });
2775
2863
  const shownRef = useRef16(shown.layout);
2776
2864
  shownRef.current = shown.layout;
2777
- useEffect10(() => {
2865
+ useEffect11(() => {
2778
2866
  const target = result.layout;
2779
2867
  if (!target) return setShown({ layout: null, settled: true });
2780
2868
  const from = shownRef.current;
@@ -2791,7 +2879,7 @@ function useMermaidStream(source, options = {}) {
2791
2879
  return () => cancelAnimationFrame(frame);
2792
2880
  }, [result.layout, duration]);
2793
2881
  const doc = result.document.tier === "editable" ? result.document : null;
2794
- const canvas = useMemo9(() => {
2882
+ const canvas = useMemo10(() => {
2795
2883
  if (!doc || !shown.layout) return { nodes: [], edges: [] };
2796
2884
  const placed = new Set(Object.keys(shown.layout.positions));
2797
2885
  const graph = {
@@ -2807,7 +2895,7 @@ function useMermaidStream(source, options = {}) {
2807
2895
 
2808
2896
  // src/canvas/MermaidDiagramNode.tsx
2809
2897
  import { MERMAID_SHAPE_PAINT, mermaidLabelLines, mermaidNodeMembers, mermaidNodePaint, mermaidShapePaths } from "@pixerate/editor/canvas";
2810
- import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
2898
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2811
2899
  function mermaidNodeStyle(styles = {}, selected = false) {
2812
2900
  const paint = mermaidNodePaint(styles, selected);
2813
2901
  const { color, "stroke-width": strokeWidth, "stroke-dasharray": strokeDasharray, ...vars } = paint;
@@ -2825,14 +2913,14 @@ function MermaidDiagramNode({ data, width = 160, height = 56, selected, classNam
2825
2913
  "data-selected": selected ? "true" : void 0,
2826
2914
  style: { position: "relative", width: "100%", height: "100%", ...mermaidNodeStyle(data.resolvedStyles, selected) },
2827
2915
  children: [
2828
- /* @__PURE__ */ jsx9("svg", { viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", width: "100%", height: "100%", style: { position: "absolute", inset: 0, overflow: "visible" }, "aria-hidden": "true", children: mermaidShapePaths(node.shape, width, height).map((path, i) => /* @__PURE__ */ jsx9("path", { d: path.d, style: { ...MERMAID_SHAPE_PAINT[path.paint], vectorEffect: "non-scaling-stroke" } }, i)) }),
2916
+ /* @__PURE__ */ jsx10("svg", { viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", width: "100%", height: "100%", style: { position: "absolute", inset: 0, overflow: "visible" }, "aria-hidden": "true", children: mermaidShapePaths(node.shape, width, height).map((path, i) => /* @__PURE__ */ jsx10("path", { d: path.d, style: { ...MERMAID_SHAPE_PAINT[path.paint], vectorEffect: "non-scaling-stroke" } }, i)) }),
2829
2917
  /* @__PURE__ */ jsxs9(
2830
2918
  "div",
2831
2919
  {
2832
2920
  style: { position: "relative", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: members.length ? "flex-start" : "center", height: "100%", padding: "6px 10px", boxSizing: "border-box", fontSize: 13, lineHeight: 1.3, textAlign: "center", pointerEvents: "none" },
2833
2921
  children: [
2834
- mermaidLabelLines(node.label).map((line, i) => /* @__PURE__ */ jsx9("span", { style: { fontWeight: members.length ? 600 : void 0 }, children: line }, i)),
2835
- members.length > 0 && /* @__PURE__ */ jsx9("div", { style: { alignSelf: "stretch", marginTop: 4, paddingTop: 4, borderTop: "1px solid var(--pixerate-diagram-node-border)", textAlign: "left", fontSize: 12 }, children: members.map((m, i) => /* @__PURE__ */ jsx9("div", { children: m }, i)) })
2922
+ mermaidLabelLines(node.label).map((line, i) => /* @__PURE__ */ jsx10("span", { style: { fontWeight: members.length ? 600 : void 0 }, children: line }, i)),
2923
+ members.length > 0 && /* @__PURE__ */ jsx10("div", { style: { alignSelf: "stretch", marginTop: 4, paddingTop: 4, borderTop: "1px solid var(--pixerate-diagram-node-border)", textAlign: "left", fontSize: 12 }, children: members.map((m, i) => /* @__PURE__ */ jsx10("div", { children: m }, i)) })
2836
2924
  ]
2837
2925
  }
2838
2926
  ),
@@ -2859,7 +2947,7 @@ function MermaidDiagramGroup({ data, selected, className = "", children }) {
2859
2947
  color: data.resolvedStyles?.color ?? "var(--muted-foreground, #475569)"
2860
2948
  },
2861
2949
  children: [
2862
- /* @__PURE__ */ jsx9("div", { style: { padding: "6px 10px", fontSize: 12, fontWeight: 600 }, children: mermaidLabelLines(group.label).join(" ") }),
2950
+ /* @__PURE__ */ jsx10("div", { style: { padding: "6px 10px", fontSize: 12, fontWeight: 600 }, children: mermaidLabelLines(group.label).join(" ") }),
2863
2951
  children
2864
2952
  ]
2865
2953
  }
@@ -2867,20 +2955,20 @@ function MermaidDiagramGroup({ data, selected, className = "", children }) {
2867
2955
  }
2868
2956
 
2869
2957
  // src/canvas/MermaidThumbnail.tsx
2870
- import { useMemo as useMemo10 } from "react";
2958
+ import { useMemo as useMemo11 } from "react";
2871
2959
  import { parseMermaid } from "@pixerate/editor/diagram";
2872
2960
  import { mermaidThumbnailSvg } from "@pixerate/editor/canvas";
2873
- import { Fragment as Fragment2, jsx as jsx10 } from "react/jsx-runtime";
2961
+ import { Fragment as Fragment2, jsx as jsx11 } from "react/jsx-runtime";
2874
2962
  function MermaidThumbnail({ source, className = "", fallback = null, ...options }) {
2875
- const doc = useMemo10(() => typeof source === "string" ? parseMermaid(source) : source, [source]);
2963
+ const doc = useMemo11(() => typeof source === "string" ? parseMermaid(source) : source, [source]);
2876
2964
  const key = JSON.stringify(options);
2877
- const drawn = useMemo10(
2965
+ const drawn = useMemo11(
2878
2966
  () => doc.tier === "editable" && doc.graph.nodes.length ? mermaidThumbnailSvg(doc, options) : null,
2879
2967
  // eslint-disable-next-line react-hooks/exhaustive-deps
2880
2968
  [doc, key]
2881
2969
  );
2882
- if (!drawn) return /* @__PURE__ */ jsx10(Fragment2, { children: fallback });
2883
- return /* @__PURE__ */ jsx10("span", { className: `pixerate-diagram-thumbnail ${className}`.trim(), style: { display: "inline-block", lineHeight: 0 }, dangerouslySetInnerHTML: { __html: drawn.svg } });
2970
+ if (!drawn) return /* @__PURE__ */ jsx11(Fragment2, { children: fallback });
2971
+ return /* @__PURE__ */ jsx11("span", { className: `pixerate-diagram-thumbnail ${className}`.trim(), style: { display: "inline-block", lineHeight: 0 }, dangerouslySetInnerHTML: { __html: drawn.svg } });
2884
2972
  }
2885
2973
 
2886
2974
  // src/canvas/useDiagramDirector.ts
@@ -2904,13 +2992,13 @@ function usePresenceState(presence) {
2904
2992
  }
2905
2993
 
2906
2994
  // src/canvas/DiagramPresenceLayer.tsx
2907
- import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2995
+ import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
2908
2996
  function DiagramPresenceLayer({ presence, viewport, className = "" }) {
2909
2997
  const { actors } = usePresenceState(presence);
2910
2998
  if (!actors.length) return null;
2911
- return /* @__PURE__ */ jsx11("div", { className: `pixerate-diagram-presence ${className}`.trim(), "aria-hidden": "true", style: { position: "absolute", inset: 0, pointerEvents: "none", overflow: "hidden", zIndex: 50 }, children: actors.map((a) => /* @__PURE__ */ jsxs10("div", { "data-actor": a.id, style: { position: "absolute", left: 0, top: 0, transform: `translate(${a.x * viewport.zoom + viewport.x}px, ${a.y * viewport.zoom + viewport.y}px)` }, children: [
2912
- /* @__PURE__ */ jsx11("svg", { width: "18", height: "20", viewBox: "0 0 12 18", style: { display: "block", transform: a.pressed ? "scale(0.85)" : void 0, transformOrigin: "0 0" }, children: /* @__PURE__ */ jsx11("path", { d: "M0,0 L0,14 L4,10.5 L7,17 L9.5,16 L6.5,9.5 L11.5,9.5 Z", style: { fill: a.color, stroke: "#ffffff", strokeWidth: 1 } }) }),
2913
- /* @__PURE__ */ jsx11("span", { style: { position: "absolute", left: 14, top: 16, whiteSpace: "nowrap", borderRadius: 4, padding: "1px 6px", fontSize: 11, fontWeight: 600, color: "#ffffff", background: a.color }, children: a.message ?? a.name })
2999
+ return /* @__PURE__ */ jsx12("div", { className: `pixerate-diagram-presence ${className}`.trim(), "aria-hidden": "true", style: { position: "absolute", inset: 0, pointerEvents: "none", overflow: "hidden", zIndex: 50 }, children: actors.map((a) => /* @__PURE__ */ jsxs10("div", { "data-actor": a.id, style: { position: "absolute", left: 0, top: 0, transform: `translate(${a.x * viewport.zoom + viewport.x}px, ${a.y * viewport.zoom + viewport.y}px)` }, children: [
3000
+ /* @__PURE__ */ jsx12("svg", { width: "18", height: "20", viewBox: "0 0 12 18", style: { display: "block", transform: a.pressed ? "scale(0.85)" : void 0, transformOrigin: "0 0" }, children: /* @__PURE__ */ jsx12("path", { d: "M0,0 L0,14 L4,10.5 L7,17 L9.5,16 L6.5,9.5 L11.5,9.5 Z", style: { fill: a.color, stroke: "#ffffff", strokeWidth: 1 } }) }),
3001
+ /* @__PURE__ */ jsx12("span", { style: { position: "absolute", left: 14, top: 16, whiteSpace: "nowrap", borderRadius: 4, padding: "1px 6px", fontSize: 11, fontWeight: 600, color: "#ffffff", background: a.color }, children: a.message ?? a.name })
2914
3002
  ] }, a.id)) });
2915
3003
  }
2916
3004
  export {
@@ -2950,6 +3038,8 @@ export {
2950
3038
  SHAPE_NODE_TYPE,
2951
3039
  SHAPE_OPERATIONS,
2952
3040
  ShapeNode,
3041
+ ShapeNodeZoomContext,
3042
+ ShapeNodeZoomProvider,
2953
3043
  TOKEN_BINDINGS_DATA_KEY,
2954
3044
  TOKEN_MODES_DATA_KEY,
2955
3045
  TokenBindingPicker,
@@ -2975,6 +3065,7 @@ export {
2975
3065
  bindCanvasToken3 as bindCanvasToken,
2976
3066
  blendLayout,
2977
3067
  boundsOf,
3068
+ brushstrokeScaleBucket2 as brushstrokeScaleBucket,
2978
3069
  calculateDagreReflowDisplacement,
2979
3070
  calculateDirectionalDisplacement2 as calculateDirectionalDisplacement,
2980
3071
  canCombineShapeNodes,
@@ -3040,6 +3131,7 @@ export {
3040
3131
  flowSide,
3041
3132
  formatAnimationTime2 as formatAnimationTime,
3042
3133
  formatOklab,
3134
+ freehandBrushstroke,
3043
3135
  getAbsolutePosition,
3044
3136
  getAnimatableProperty,
3045
3137
  getAnimatedProperties,
@@ -3050,6 +3142,7 @@ export {
3050
3142
  getAutoLayoutGrid,
3051
3143
  getAutoLayoutInsertTarget,
3052
3144
  getBindableTokenProperties2 as getBindableTokenProperties,
3145
+ getBrushedShapeSvg,
3053
3146
  getCanvasComponentDragId,
3054
3147
  getCanvasTokenProperty,
3055
3148
  getCenteredNodePosition2 as getCenteredNodePosition,
@@ -3092,6 +3185,7 @@ export {
3092
3185
  isAnimationEasing,
3093
3186
  isAnimationKeyframe,
3094
3187
  isAutoLayoutFrame,
3188
+ isBrushedShape2 as isBrushedShape,
3095
3189
  isCanvasComponent,
3096
3190
  isCanvasComponentSet,
3097
3191
  isCanvasGradient,
@@ -3114,7 +3208,9 @@ export {
3114
3208
  layoutUpMany,
3115
3209
  linearPositionTrajectory2 as linearPositionTrajectory,
3116
3210
  linearTrajectory,
3211
+ listCanvasBrushes2 as listCanvasBrushes,
3117
3212
  listCanvasComponents2 as listCanvasComponents,
3213
+ loadCanvasBrushstrokeRenderer,
3118
3214
  lockNodes,
3119
3215
  makeSizingCoherent,
3120
3216
  matchArrangeShortcut2 as matchArrangeShortcut,
@@ -3149,15 +3245,18 @@ export {
3149
3245
  parseSvgPath,
3150
3246
  pasteKeyframes,
3151
3247
  pathBoolean,
3248
+ pathDataToPolyline,
3152
3249
  placeBeside2 as placeBeside,
3153
3250
  playWebAnimations,
3154
3251
  pointInBezierPath,
3155
3252
  pointInMultiPolygon,
3156
3253
  pointInRings,
3254
+ pointerPenSample2 as pointerPenSample,
3157
3255
  polygonArea,
3158
3256
  polygonBoolean,
3159
3257
  polygonToSvgPath,
3160
3258
  positionToSide,
3259
+ profileAlongPolyline,
3161
3260
  pruneCanvasTokenModes,
3162
3261
  pushCanvasInstanceOverrides,
3163
3262
  readAnimatableValue,
@@ -3171,6 +3270,7 @@ export {
3171
3270
  removeTrack,
3172
3271
  renameCanvasVariantProperty,
3173
3272
  renameCanvasVariantValue,
3273
+ renderShapeBrushstroke2 as renderShapeBrushstroke,
3174
3274
  reorderNodes,
3175
3275
  repositionNodesTo,
3176
3276
  resetCanvasInstanceOverrides,
@@ -3231,6 +3331,7 @@ export {
3231
3331
  useCanvasArrange,
3232
3332
  useCanvasArrangeShortcuts,
3233
3333
  useCanvasAutoLayout,
3334
+ useCanvasBrushes,
3234
3335
  useCanvasCapture,
3235
3336
  useCanvasClipboard,
3236
3337
  useCanvasComponents,
@@ -3248,7 +3349,9 @@ export {
3248
3349
  useMermaidDiagram,
3249
3350
  useMermaidStream,
3250
3351
  usePresenceState,
3352
+ useShapeBrushstroke,
3251
3353
  useStreamingCanvas,
3354
+ validateCanvasBrush,
3252
3355
  withNodeAnimation,
3253
3356
  withStyleSize,
3254
3357
  wouldCreateComponentCycle,