@pixerate/editor-react 0.80.0 → 0.82.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.
@@ -1,3 +1,10 @@
1
+ import {
2
+ EffectParameters,
3
+ EffectsBrowser,
4
+ useEffectDescription
5
+ } from "../chunk-WOQ3H7KA.js";
6
+ import "../chunk-ZQZAKUUM.js";
7
+
1
8
  // src/canvas/AgentPresenceLayer.tsx
2
9
  import { jsx, jsxs } from "react/jsx-runtime";
3
10
  function AgentPresenceLayer({ presences, className = "" }) {
@@ -832,7 +839,8 @@ function useCanvasArrangeShortcuts(options) {
832
839
  import { useMemo as useMemo2, useRef as useRef6, useSyncExternalStore } from "react";
833
840
  import {
834
841
  CanvasScenesStore,
835
- fitViewportToNodes
842
+ fitViewportToNodes,
843
+ transitionCanvasView
836
844
  } from "@pixerate/editor/canvas";
837
845
  function useCanvasScenes(options = {}) {
838
846
  const optionsRef = useRef6(options);
@@ -847,9 +855,13 @@ function useCanvasScenes(options = {}) {
847
855
  );
848
856
  const actions = useMemo2(() => {
849
857
  const show = (scene) => {
850
- if (scene) {
851
- optionsRef.current.setViewport?.(scene.viewport, { duration: optionsRef.current.duration ?? 300 });
852
- }
858
+ if (!scene) return scene;
859
+ const o = optionsRef.current;
860
+ const provider = o.transitions?.provider;
861
+ const element = o.transitions?.element();
862
+ if (scene.transition && provider && element) {
863
+ void transitionCanvasView({ provider, transition: scene.transition, element, jump: () => o.setViewport?.(scene.viewport, { duration: 0 }) });
864
+ } else o.setViewport?.(scene.viewport, { duration: o.duration ?? 300 });
853
865
  return scene;
854
866
  };
855
867
  return {
@@ -1338,7 +1350,7 @@ import {
1338
1350
  } from "@pixerate/editor/canvas";
1339
1351
 
1340
1352
  // src/canvas/ShapeNode.tsx
1341
- import { getShapeRenderData, isShapeDescription } from "@pixerate/editor/canvas";
1353
+ import { getShapeRenderData, isShapeDescription as isShapeDescription2 } from "@pixerate/editor/canvas";
1342
1354
 
1343
1355
  // src/canvas/useShapeBrushstroke.tsx
1344
1356
  import { createContext, useContext, useEffect as useEffect7, useMemo as useMemo6, useState as useState6 } from "react";
@@ -1397,14 +1409,159 @@ function useCanvasBrushes() {
1397
1409
  return useMemo6(() => state, [state]);
1398
1410
  }
1399
1411
 
1412
+ // src/canvas/CanvasEffects.tsx
1413
+ import { createContext as createContext2, useContext as useContext2, useEffect as useEffect8, useMemo as useMemo7, useRef as useRef11, useState as useState7 } from "react";
1414
+ import {
1415
+ createCanvasEffectsController,
1416
+ createCanvasEffectsToolProvider,
1417
+ isShapeDescription,
1418
+ runCanvasNodeEffect
1419
+ } from "@pixerate/editor/canvas";
1420
+ import { Fragment, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1421
+ var ProviderContext = createContext2(null);
1422
+ function CanvasEffectsProvider({ provider, children }) {
1423
+ return /* @__PURE__ */ jsx5(ProviderContext.Provider, { value: provider ?? null, children });
1424
+ }
1425
+ var useCanvasEffectsProviderContext = () => useContext2(ProviderContext);
1426
+ function CanvasNodeEffectLayer({ effect, width, height, clipPath, clipTransform }) {
1427
+ const provider = useCanvasEffectsProviderContext();
1428
+ const ref = useRef11(null);
1429
+ const [failed, setFailed] = useState7(false);
1430
+ const dpr = typeof window !== "undefined" ? Math.min(2, window.devicePixelRatio || 1) : 1;
1431
+ const pw = Math.max(1, Math.round(width * dpr));
1432
+ const ph = Math.max(1, Math.round(height * dpr));
1433
+ const key = JSON.stringify(effect);
1434
+ useEffect8(() => {
1435
+ const canvas2 = ref.current;
1436
+ if (!provider || !canvas2) return;
1437
+ setFailed(false);
1438
+ const run = runCanvasNodeEffect({ provider, effect, target: canvas2, onError: () => setFailed(true) });
1439
+ return () => run.stop();
1440
+ }, [provider, key, pw, ph]);
1441
+ const clipId = useMemo7(() => `pixerate-effect-clip-${Math.random().toString(36).slice(2)}`, []);
1442
+ if (!provider || failed || !(width > 0 && height > 0)) return null;
1443
+ const style = { position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none", opacity: effect.opacity ?? 1 };
1444
+ const canvas = /* @__PURE__ */ jsx5("canvas", { ref, width: pw, height: ph, style: { width: "100%", height: "100%", display: "block" }, "data-canvas-effect": effect.effect });
1445
+ if (effect.mode === "overlay" || !clipPath) return /* @__PURE__ */ jsx5("div", { style, children: canvas });
1446
+ return /* @__PURE__ */ jsxs4("svg", { style, viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", "aria-hidden": "true", children: [
1447
+ /* @__PURE__ */ jsx5("defs", { children: /* @__PURE__ */ jsx5("clipPath", { id: clipId, children: /* @__PURE__ */ jsx5("path", { d: clipPath, transform: clipTransform }) }) }),
1448
+ /* @__PURE__ */ jsx5("foreignObject", { x: 0, y: 0, width, height, clipPath: `url(#${clipId})`, children: canvas })
1449
+ ] });
1450
+ }
1451
+ function useCanvasEffects(options) {
1452
+ const opts = useRef11(options);
1453
+ opts.current = options;
1454
+ const controller = useMemo7(
1455
+ () => createCanvasEffectsController({
1456
+ getNodes: () => opts.current.getNodes(),
1457
+ setNodes: (n) => opts.current.setNodes(n),
1458
+ history: { execute: (c) => opts.current.history ? opts.current.history.execute(c) : c.execute() },
1459
+ get scenes() {
1460
+ return opts.current.scenes;
1461
+ }
1462
+ }),
1463
+ []
1464
+ );
1465
+ useEffect8(() => {
1466
+ if (!options.effects) return;
1467
+ return controller.useEffects(options.effects);
1468
+ }, [controller, options.effects]);
1469
+ return controller;
1470
+ }
1471
+ function useCanvasEffectsTools(registry, controller, options = {}) {
1472
+ const { id, namespace, readOnly, subscribe } = options;
1473
+ useEffect8(() => {
1474
+ if (!registry) return;
1475
+ return registry.register(createCanvasEffectsToolProvider(controller, { id, namespace, readOnly, subscribe }));
1476
+ }, [registry, controller, id, namespace, readOnly, subscribe]);
1477
+ }
1478
+ var btn = "rounded border border-border px-1.5 py-0.5 text-[11px] disabled:opacity-40";
1479
+ function Controls({ controller, nodeId, effect }) {
1480
+ const { description, error } = useEffectDescription(controller.effectsProvider, effect.effect);
1481
+ const [message, setMessage] = useState7(null);
1482
+ const run = (work) => void work.then(() => setMessage(null), (e) => setMessage(e instanceof Error ? e.message : String(e)));
1483
+ if (error) return /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-amber-500", children: error });
1484
+ if (!description) return /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-muted-foreground", children: "Loading\u2026" });
1485
+ return /* @__PURE__ */ jsxs4(Fragment, { children: [
1486
+ /* @__PURE__ */ jsx5(
1487
+ EffectParameters,
1488
+ {
1489
+ description,
1490
+ values: effect.values,
1491
+ onChange: (k, v) => run(controller.updateNodeEffect(nodeId, { values: { [k]: v } })),
1492
+ onReset: (k) => run(controller.updateNodeEffect(nodeId, { values: { [k]: null } })),
1493
+ onPreset: (v) => run(controller.updateNodeEffect(nodeId, { values: v }))
1494
+ }
1495
+ ),
1496
+ message && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
1497
+ ] });
1498
+ }
1499
+ function CanvasEffectsPanel({ controller, nodeId, shape, sceneId, className = "" }) {
1500
+ const [, setTick] = useState7(0);
1501
+ useEffect8(() => controller.effects.subscribe(() => setTick((n) => n + 1)), [controller]);
1502
+ const provider = controller.effectsProvider;
1503
+ const [browse, setBrowse] = useState7(null);
1504
+ const [message, setMessage] = useState7(null);
1505
+ const run = (work) => {
1506
+ setMessage(null);
1507
+ Promise.resolve().then(work).then(() => setTick((n) => n + 1), (e) => setMessage(e instanceof Error ? e.message : String(e)));
1508
+ };
1509
+ const effect = isShapeDescription(shape) ? shape.effect : void 0;
1510
+ const transition = sceneId ? controller.sceneTransition(sceneId) : void 0;
1511
+ return /* @__PURE__ */ jsxs4("section", { className: `flex flex-col gap-2 text-xs ${className}`, "aria-label": "Canvas effects", "data-canvas-effects": "", children: [
1512
+ /* @__PURE__ */ jsxs4("div", { className: "flex flex-wrap items-center gap-1", children: [
1513
+ /* @__PURE__ */ jsx5("span", { className: "flex-1 font-semibold", children: "Effects" }),
1514
+ /* @__PURE__ */ jsx5("button", { type: "button", className: btn, disabled: !provider || !nodeId || !shape, "aria-pressed": browse === "fill", onClick: () => setBrowse(browse === "fill" ? null : "fill"), children: "Fill\u2026" }),
1515
+ /* @__PURE__ */ jsx5("button", { type: "button", className: btn, disabled: !provider || !nodeId || !shape, "aria-pressed": browse === "overlay", onClick: () => setBrowse(browse === "overlay" ? null : "overlay"), children: "Overlay\u2026" }),
1516
+ /* @__PURE__ */ jsx5("button", { type: "button", className: btn, disabled: !provider || !sceneId, "aria-pressed": browse === "transition", onClick: () => setBrowse(browse === "transition" ? null : "transition"), children: "Scene transition\u2026" })
1517
+ ] }),
1518
+ !provider && /* @__PURE__ */ jsx5("p", { className: "text-[11px] text-muted-foreground", children: "No effects provider is registered. Effects on the canvas are kept." }),
1519
+ browse === "fill" && nodeId && /* @__PURE__ */ jsx5(EffectsBrowser, { provider, uses: ["generator", "interactive"], title: "Fills", onPick: (e) => (setBrowse(null), run(() => controller.applyNodeEffect(nodeId, e.id, { mode: "fill" }))) }),
1520
+ browse === "overlay" && nodeId && /* @__PURE__ */ jsx5(EffectsBrowser, { provider, uses: ["overlay", "title", "interactive", "generator"], title: "Overlays", onPick: (e) => (setBrowse(null), run(() => controller.applyNodeEffect(nodeId, e.id, { mode: "overlay" }))) }),
1521
+ browse === "transition" && sceneId && /* @__PURE__ */ jsx5(EffectsBrowser, { provider, uses: ["transition"], title: "Transitions", onPick: (e) => (setBrowse(null), run(() => controller.setSceneTransition(sceneId, e.id))) }),
1522
+ effect && nodeId && /* @__PURE__ */ jsxs4("div", { className: "space-y-1 rounded border border-border p-1", "data-canvas-node-effect": nodeId, children: [
1523
+ /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", children: [
1524
+ /* @__PURE__ */ jsxs4("span", { className: "min-w-0 flex-1 truncate font-medium", children: [
1525
+ effect.name ?? effect.effect,
1526
+ " ",
1527
+ /* @__PURE__ */ jsxs4("span", { className: "text-muted-foreground", children: [
1528
+ "\xB7 ",
1529
+ effect.mode
1530
+ ] })
1531
+ ] }),
1532
+ /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-1", children: [
1533
+ /* @__PURE__ */ jsx5("input", { type: "checkbox", checked: effect.playing !== false, "aria-label": "Play", onChange: (e) => {
1534
+ const playing = e.currentTarget.checked;
1535
+ run(() => controller.updateNodeEffect(nodeId, { playing }));
1536
+ } }),
1537
+ " Play"
1538
+ ] }),
1539
+ /* @__PURE__ */ jsx5("button", { type: "button", className: btn, "aria-label": "Remove effect", onClick: () => run(() => controller.removeNodeEffect(nodeId)), children: "\u2715" })
1540
+ ] }),
1541
+ provider && /* @__PURE__ */ jsx5(Controls, { controller, nodeId, effect })
1542
+ ] }),
1543
+ sceneId && transition && /* @__PURE__ */ jsxs4("div", { className: "flex items-center gap-1", "data-canvas-scene-transition": sceneId, children: [
1544
+ /* @__PURE__ */ jsxs4("span", { className: "min-w-0 flex-1 truncate", children: [
1545
+ "Into scene: ",
1546
+ transition.name ?? transition.effect,
1547
+ " (",
1548
+ transition.duration ?? 600,
1549
+ " ms)"
1550
+ ] }),
1551
+ /* @__PURE__ */ jsx5("button", { type: "button", className: btn, "aria-label": "Remove transition", onClick: () => run(() => controller.setSceneTransition(sceneId, null)), children: "\u2715" })
1552
+ ] }),
1553
+ message && /* @__PURE__ */ jsx5("p", { role: "alert", className: "text-[11px] text-destructive", children: message })
1554
+ ] });
1555
+ }
1556
+
1400
1557
  // src/canvas/ShapeNode.tsx
1401
- import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
1558
+ import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1402
1559
  function ShapeNode({ id, data, width, height, selected, className = "", children }) {
1403
1560
  const shape = data?.shape;
1404
- const brushImage = useShapeBrushstroke(isShapeDescription(shape) ? shape : null, width, height);
1405
- if (!isShapeDescription(shape)) return null;
1561
+ const brushImage = useShapeBrushstroke(isShapeDescription2(shape) ? shape : null, width, height);
1562
+ if (!isShapeDescription2(shape)) return null;
1406
1563
  const render = getShapeRenderData(shape, { width, height }, { id: id || data?.id });
1407
- return /* @__PURE__ */ jsxs4(
1564
+ return /* @__PURE__ */ jsxs5(
1408
1565
  "div",
1409
1566
  {
1410
1567
  className: `pixerate-shape-node ${className}`.trim(),
@@ -1412,7 +1569,7 @@ function ShapeNode({ id, data, width, height, selected, className = "", children
1412
1569
  "data-selected": selected ? "true" : void 0,
1413
1570
  style: { position: "relative", width: "100%", height: "100%" },
1414
1571
  children: [
1415
- /* @__PURE__ */ jsxs4(
1572
+ /* @__PURE__ */ jsxs5(
1416
1573
  "svg",
1417
1574
  {
1418
1575
  viewBox: render.viewBox,
@@ -1422,13 +1579,13 @@ function ShapeNode({ id, data, width, height, selected, className = "", children
1422
1579
  style: { display: "block", overflow: "visible" },
1423
1580
  "aria-hidden": "true",
1424
1581
  children: [
1425
- (render.fillGradient || render.strokeGradient) && /* @__PURE__ */ jsxs4("defs", { children: [
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)) })
1582
+ (render.fillGradient || render.strokeGradient) && /* @__PURE__ */ jsxs5("defs", { children: [
1583
+ render.fillGradient && render.fillGradient.type === "linear" && /* @__PURE__ */ jsx6("linearGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx6("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1584
+ render.fillGradient && render.fillGradient.type === "radial" && /* @__PURE__ */ jsx6("radialGradient", { id: render.fillGradient.id, ...render.fillGradient.attrs, children: render.fillGradient.stops.map((stop, i) => /* @__PURE__ */ jsx6("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1585
+ render.strokeGradient && render.strokeGradient.type === "linear" && /* @__PURE__ */ jsx6("linearGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx6("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) }),
1586
+ render.strokeGradient && render.strokeGradient.type === "radial" && /* @__PURE__ */ jsx6("radialGradient", { id: render.strokeGradient.id, ...render.strokeGradient.attrs, children: render.strokeGradient.stops.map((stop, i) => /* @__PURE__ */ jsx6("stop", { offset: stop.offset, stopColor: stop.stopColor, stopOpacity: stop.stopOpacity }, i)) })
1430
1587
  ] }),
1431
- brushImage ? /* @__PURE__ */ jsx5("g", { transform: render.transform, children: /* @__PURE__ */ jsx5(
1588
+ brushImage ? /* @__PURE__ */ jsx6("g", { transform: render.transform, children: /* @__PURE__ */ jsx6(
1432
1589
  "image",
1433
1590
  {
1434
1591
  className: "pixerate-shape-brushstroke",
@@ -1440,7 +1597,7 @@ function ShapeNode({ id, data, width, height, selected, className = "", children
1440
1597
  preserveAspectRatio: "none",
1441
1598
  opacity: render.opacity
1442
1599
  }
1443
- ) }) : /* @__PURE__ */ jsx5(
1600
+ ) }) : /* @__PURE__ */ jsx6(
1444
1601
  "path",
1445
1602
  {
1446
1603
  d: render.d,
@@ -1458,6 +1615,7 @@ function ShapeNode({ id, data, width, height, selected, className = "", children
1458
1615
  ]
1459
1616
  }
1460
1617
  ),
1618
+ shape.effect && width && height ? /* @__PURE__ */ jsx6(CanvasNodeEffectLayer, { effect: shape.effect, width, height, clipPath: render.d, clipTransform: render.transform }) : null,
1461
1619
  children
1462
1620
  ]
1463
1621
  }
@@ -1477,7 +1635,7 @@ import {
1477
1635
  SHAPE_NODE_TYPE,
1478
1636
  SHAPE_OPERATIONS,
1479
1637
  SHAPE_KINDS,
1480
- isShapeDescription as isShapeDescription2,
1638
+ isShapeDescription as isShapeDescription3,
1481
1639
  shapeToPolygon,
1482
1640
  getShapeSvg,
1483
1641
  getShapeRenderData as getShapeRenderData2,
@@ -1591,17 +1749,17 @@ import {
1591
1749
  } from "@pixerate/editor/canvas";
1592
1750
 
1593
1751
  // src/canvas/useCanvasAutoLayout.ts
1594
- import { useLayoutEffect, useMemo as useMemo7, useRef as useRef11, useState as useState7 } from "react";
1752
+ import { useLayoutEffect, useMemo as useMemo8, useRef as useRef12, useState as useState8 } from "react";
1595
1753
  import {
1596
1754
  createCanvasAutoLayoutController
1597
1755
  } from "@pixerate/editor/canvas";
1598
1756
  function useCanvasAutoLayout(options) {
1599
- const [guides, setGuides] = useState7([]);
1600
- const nodesRef = useRef11(options.nodes);
1757
+ const [guides, setGuides] = useState8([]);
1758
+ const nodesRef = useRef12(options.nodes);
1601
1759
  nodesRef.current = options.nodes;
1602
- const optionsRef = useRef11(options);
1760
+ const optionsRef = useRef12(options);
1603
1761
  optionsRef.current = options;
1604
- const controller = useMemo7(
1762
+ const controller = useMemo8(
1605
1763
  () => createCanvasAutoLayoutController({
1606
1764
  getNodes: () => nodesRef.current,
1607
1765
  setNodes: (next) => {
@@ -1630,7 +1788,7 @@ function useCanvasAutoLayout(options) {
1630
1788
  useLayoutEffect(() => {
1631
1789
  if (controller.hasPending) controller.flush();
1632
1790
  }, [options.nodes, controller]);
1633
- return useMemo7(
1791
+ return useMemo8(
1634
1792
  () => ({
1635
1793
  ...controller,
1636
1794
  /** Drop marker of the active reorder drag (flow coordinates). */
@@ -1644,7 +1802,7 @@ function useCanvasAutoLayout(options) {
1644
1802
  }
1645
1803
 
1646
1804
  // src/canvas/useCanvasTokens.ts
1647
- import { useCallback as useCallback9, useEffect as useEffect8, useRef as useRef12, useSyncExternalStore as useSyncExternalStore2 } from "react";
1805
+ import { useCallback as useCallback9, useEffect as useEffect9, useRef as useRef13, useSyncExternalStore as useSyncExternalStore2 } from "react";
1648
1806
  import {
1649
1807
  bindCanvasToken,
1650
1808
  detachTokenBindings,
@@ -1655,10 +1813,10 @@ import {
1655
1813
  function useCanvasTokens(options) {
1656
1814
  const { store, nodes, setNodes, modes, properties } = options;
1657
1815
  const state = useSyncExternalStore2(store.subscribe, store.getState, store.getState);
1658
- const latest = useRef12({ nodes, setNodes, modes, properties });
1816
+ const latest = useRef13({ nodes, setNodes, modes, properties });
1659
1817
  latest.current = { nodes, setNodes, modes, properties };
1660
1818
  const opts = () => ({ modes: latest.current.modes, properties: latest.current.properties });
1661
- useEffect8(
1819
+ useEffect9(
1662
1820
  () => store.subscribe((event) => {
1663
1821
  if (event.deletedVariableIds.length === 0) return;
1664
1822
  latest.current.setNodes((prev) => detachTokenBindings(prev, event.previous, event.deletedVariableIds, opts()));
@@ -1667,7 +1825,7 @@ function useCanvasTokens(options) {
1667
1825
  [store]
1668
1826
  );
1669
1827
  const modesKey = JSON.stringify(modes ?? {});
1670
- useEffect8(() => {
1828
+ useEffect9(() => {
1671
1829
  const next = resolveCanvasTokens(nodes, state, opts());
1672
1830
  if (next !== nodes) setNodes(next);
1673
1831
  }, [nodes, state, modesKey, properties]);
@@ -1709,7 +1867,7 @@ import {
1709
1867
  getTokensForProperty
1710
1868
  } from "@pixerate/editor/canvas";
1711
1869
  import { getCurrentMode, resolveToken } from "@pixerate/editor/tokens";
1712
- import { jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1870
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
1713
1871
  function TokenBindingPicker({
1714
1872
  state,
1715
1873
  nodes,
@@ -1726,15 +1884,15 @@ function TokenBindingPicker({
1726
1884
  let rows = getBindableTokenProperties(first, options);
1727
1885
  if (properties) rows = properties.map((key) => rows.find((p) => p.key === key)).filter((p) => !!p);
1728
1886
  const modes = getEffectiveTokenModes2(first, nodes, state, options);
1729
- return /* @__PURE__ */ jsxs5("div", { "data-token-binding-picker": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
1887
+ return /* @__PURE__ */ jsxs6("div", { "data-token-binding-picker": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
1730
1888
  rows.map((property) => {
1731
1889
  const ids = new Set(selected.map((n) => getNodeTokenBindings(n)[property.key] ?? ""));
1732
1890
  const value = ids.size === 1 ? [...ids][0] : "__mixed";
1733
1891
  const candidates = getTokensForProperty(state, property, first);
1734
1892
  const resolved = value && value !== "__mixed" ? resolveToken(state, value, modes) : null;
1735
- return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-property": property.key, children: [
1736
- /* @__PURE__ */ jsx6("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1737
- /* @__PURE__ */ jsxs5(
1893
+ return /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2", "data-token-property": property.key, children: [
1894
+ /* @__PURE__ */ jsx7("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1895
+ /* @__PURE__ */ jsxs6(
1738
1896
  "select",
1739
1897
  {
1740
1898
  "aria-label": `${property.label} variable`,
@@ -1749,11 +1907,11 @@ function TokenBindingPicker({
1749
1907
  }
1750
1908
  },
1751
1909
  children: [
1752
- value === "__mixed" ? /* @__PURE__ */ jsx6("option", { value: "__mixed", children: "Mixed" }) : null,
1753
- /* @__PURE__ */ jsx6("option", { value: "", children: "None" }),
1910
+ value === "__mixed" ? /* @__PURE__ */ jsx7("option", { value: "__mixed", children: "Mixed" }) : null,
1911
+ /* @__PURE__ */ jsx7("option", { value: "", children: "None" }),
1754
1912
  candidates.map((v) => {
1755
1913
  const collection = state.collections.find((c) => c.id === v.collectionId);
1756
- return /* @__PURE__ */ jsxs5("option", { value: v.id, children: [
1914
+ return /* @__PURE__ */ jsxs6("option", { value: v.id, children: [
1757
1915
  v.name,
1758
1916
  collection && state.collections.length > 1 ? ` (${collection.name})` : ""
1759
1917
  ] }, v.id);
@@ -1761,17 +1919,17 @@ function TokenBindingPicker({
1761
1919
  ]
1762
1920
  }
1763
1921
  ),
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(
1922
+ /* @__PURE__ */ jsx7("span", { className: "w-16 shrink-0 truncate text-right text-muted-foreground", "data-token-resolved": "", children: resolved?.ok ? property.type === "color" ? /* @__PURE__ */ jsx7(
1765
1923
  "span",
1766
1924
  {
1767
1925
  className: "inline-block h-3 w-3 rounded border border-border align-middle",
1768
1926
  style: { background: String(resolved.value) },
1769
1927
  title: String(resolved.value)
1770
1928
  }
1771
- ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx6("span", { title: resolved.message, children: "\u26A0" }) : null })
1929
+ ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx7("span", { title: resolved.message, children: "\u26A0" }) : null })
1772
1930
  ] }, property.key);
1773
1931
  }),
1774
- state.collections.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1932
+ state.collections.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1775
1933
  ] });
1776
1934
  }
1777
1935
  function TokenModeSwitcher({
@@ -1789,12 +1947,12 @@ function TokenModeSwitcher({
1789
1947
  const parent = first.parentId ? nodes.find((n) => n.id === first.parentId) : void 0;
1790
1948
  const inherited = parent ? getEffectiveTokenModes2(parent, nodes, state, options) : { ...options.modes };
1791
1949
  const collections = state.collections.filter((c) => showSingleMode || c.modes.length > 1);
1792
- return /* @__PURE__ */ jsx6("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1950
+ return /* @__PURE__ */ jsx7("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1793
1951
  const auto = getCurrentMode(collection, inherited);
1794
1952
  const explicit = collection.modes.some((m) => m.id === own[collection.id]) ? own[collection.id] : "";
1795
- return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-collection": collection.id, children: [
1796
- /* @__PURE__ */ jsx6("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1797
- /* @__PURE__ */ jsxs5(
1953
+ return /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2", "data-token-collection": collection.id, children: [
1954
+ /* @__PURE__ */ jsx7("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1955
+ /* @__PURE__ */ jsxs6(
1798
1956
  "select",
1799
1957
  {
1800
1958
  "aria-label": `${collection.name} mode`,
@@ -1802,12 +1960,12 @@ function TokenModeSwitcher({
1802
1960
  value: explicit,
1803
1961
  onChange: (e) => onChange(setCanvasTokenMode2(nodes, nodeIds, collection.id, e.currentTarget.value || null, state, options)),
1804
1962
  children: [
1805
- /* @__PURE__ */ jsxs5("option", { value: "", children: [
1963
+ /* @__PURE__ */ jsxs6("option", { value: "", children: [
1806
1964
  "Auto (",
1807
1965
  auto.name,
1808
1966
  ")"
1809
1967
  ] }),
1810
- collection.modes.map((m) => /* @__PURE__ */ jsx6("option", { value: m.id, children: m.name }, m.id))
1968
+ collection.modes.map((m) => /* @__PURE__ */ jsx7("option", { value: m.id, children: m.name }, m.id))
1811
1969
  ]
1812
1970
  }
1813
1971
  )
@@ -1838,16 +1996,16 @@ import {
1838
1996
  } from "@pixerate/editor/canvas";
1839
1997
 
1840
1998
  // src/canvas/useCanvasAnimation.ts
1841
- import { useEffect as useEffect9, useMemo as useMemo8, useRef as useRef13, useSyncExternalStore as useSyncExternalStore3 } from "react";
1999
+ import { useEffect as useEffect10, useMemo as useMemo9, useRef as useRef14, useSyncExternalStore as useSyncExternalStore3 } from "react";
1842
2000
  import {
1843
2001
  createCanvasAnimationController
1844
2002
  } from "@pixerate/editor/canvas";
1845
2003
  function useCanvasAnimation(options) {
1846
- const nodesRef = useRef13(options.nodes);
2004
+ const nodesRef = useRef14(options.nodes);
1847
2005
  nodesRef.current = options.nodes;
1848
- const optionsRef = useRef13(options);
2006
+ const optionsRef = useRef14(options);
1849
2007
  optionsRef.current = options;
1850
- const controller = useMemo8(
2008
+ const controller = useMemo9(
1851
2009
  () => createCanvasAnimationController({
1852
2010
  getNodes: () => nodesRef.current,
1853
2011
  setNodes: (next) => {
@@ -1874,14 +2032,14 @@ function useCanvasAnimation(options) {
1874
2032
  }),
1875
2033
  []
1876
2034
  );
1877
- useEffect9(() => () => controller.destroy(), [controller]);
1878
- useEffect9(() => {
2035
+ useEffect10(() => () => controller.destroy(), [controller]);
2036
+ useEffect10(() => {
1879
2037
  if (options.loop !== void 0) controller.setLoop(options.loop);
1880
2038
  }, [controller, options.loop]);
1881
- useEffect9(() => {
2039
+ useEffect10(() => {
1882
2040
  if (options.playbackRate !== void 0) controller.setPlaybackRate(options.playbackRate);
1883
2041
  }, [controller, options.playbackRate]);
1884
- useEffect9(() => {
2042
+ useEffect10(() => {
1885
2043
  if (options.recording !== void 0) controller.setRecording(options.recording);
1886
2044
  }, [controller, options.recording]);
1887
2045
  const state = useSyncExternalStore3(
@@ -1889,11 +2047,11 @@ function useCanvasAnimation(options) {
1889
2047
  () => controller.getState(),
1890
2048
  () => controller.getState()
1891
2049
  );
1892
- useEffect9(() => {
2050
+ useEffect10(() => {
1893
2051
  controller.refresh();
1894
2052
  }, [controller, options.nodes]);
1895
- const displayNodes = useMemo8(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
1896
- return useMemo8(
2053
+ const displayNodes = useMemo9(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
2054
+ return useMemo9(
1897
2055
  () => ({
1898
2056
  ...controller,
1899
2057
  state,
@@ -1914,7 +2072,7 @@ function useCanvasAnimation(options) {
1914
2072
  }
1915
2073
 
1916
2074
  // src/canvas/AnimationTimeline.tsx
1917
- import { useEffect as useEffect10, useRef as useRef14 } from "react";
2075
+ import { useEffect as useEffect11, useRef as useRef15 } from "react";
1918
2076
  import {
1919
2077
  ANIMATION_EASING_LABELS,
1920
2078
  ANIMATION_EASING_PRESETS,
@@ -1923,7 +2081,7 @@ import {
1923
2081
  timeToX,
1924
2082
  xToTime
1925
2083
  } from "@pixerate/editor/canvas";
1926
- import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
2084
+ import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
1927
2085
  var LOOP_LABELS = { none: "Once", loop: "Loop", "ping-pong": "Ping-pong" };
1928
2086
  var easingValue = (easing) => typeof easing === "string" ? easing : easing ? "custom" : "linear";
1929
2087
  function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8, labelOf, className = "" }) {
@@ -1937,8 +2095,8 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1937
2095
  for (const row of rows) for (const k of row.keyframes) if (k.selected && k.ref.property === first.property && k.ref.nodeId === first.nodeId) return easingValue(k.easing);
1938
2096
  return "";
1939
2097
  })();
1940
- const dragRef = useRef14(null);
1941
- useEffect10(() => {
2098
+ const dragRef = useRef15(null);
2099
+ useEffect11(() => {
1942
2100
  const onKey = (event) => {
1943
2101
  if (event.key === "Escape" && dragRef.current) {
1944
2102
  dragRef.current = null;
@@ -1991,17 +2149,17 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1991
2149
  animation.deleteKeyframes();
1992
2150
  }
1993
2151
  };
1994
- const btn = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-50";
2152
+ const btn2 = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-50";
1995
2153
  const playheadX = timeToX(state.time, duration, width);
1996
2154
  const labelWidth = 120;
1997
- return /* @__PURE__ */ jsxs6("div", { "data-animation-timeline": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, tabIndex: 0, onKeyDown: onTimelineKey, children: [
1998
- /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2", role: "toolbar", "aria-label": "Animation", children: [
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(
2155
+ return /* @__PURE__ */ jsxs7("div", { "data-animation-timeline": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, tabIndex: 0, onKeyDown: onTimelineKey, children: [
2156
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-2", role: "toolbar", "aria-label": "Animation", children: [
2157
+ /* @__PURE__ */ jsx8("button", { type: "button", className: btn2, "data-action": "toggle-play", "aria-label": state.playing ? "Pause" : "Play", onClick: () => animation.toggle(), children: state.playing ? "\u275A\u275A" : "\u25B6" }),
2158
+ /* @__PURE__ */ jsx8(
2001
2159
  "button",
2002
2160
  {
2003
2161
  type: "button",
2004
- className: `${btn} ${state.recording ? "text-red-500" : ""}`,
2162
+ className: `${btn2} ${state.recording ? "text-red-500" : ""}`,
2005
2163
  "data-action": "record",
2006
2164
  "aria-pressed": state.recording,
2007
2165
  "aria-label": "Record keyframes",
@@ -2009,13 +2167,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
2009
2167
  children: "\u25CF Rec"
2010
2168
  }
2011
2169
  ),
2012
- /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
2013
- /* @__PURE__ */ jsxs6("span", { "data-time": "", className: "tabular-nums text-muted-foreground", children: [
2170
+ /* @__PURE__ */ jsx8("button", { type: "button", className: btn2, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
2171
+ /* @__PURE__ */ jsxs7("span", { "data-time": "", className: "tabular-nums text-muted-foreground", children: [
2014
2172
  formatAnimationTime(state.time),
2015
2173
  " / ",
2016
2174
  formatAnimationTime(duration)
2017
2175
  ] }),
2018
- /* @__PURE__ */ jsxs6(
2176
+ /* @__PURE__ */ jsxs7(
2019
2177
  "select",
2020
2178
  {
2021
2179
  "aria-label": "Easing",
@@ -2025,13 +2183,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
2025
2183
  disabled: state.selection.length === 0,
2026
2184
  onChange: (e) => e.target.value && e.target.value !== "custom" && animation.setEasing(e.target.value),
2027
2185
  children: [
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
2186
+ /* @__PURE__ */ jsx8("option", { value: "", children: "Easing\u2026" }),
2187
+ ANIMATION_EASING_PRESETS.map((preset) => /* @__PURE__ */ jsx8("option", { value: preset, children: ANIMATION_EASING_LABELS[preset] }, preset)),
2188
+ selectedEasing === "custom" ? /* @__PURE__ */ jsx8("option", { value: "custom", children: "Custom" }) : null
2031
2189
  ]
2032
2190
  }
2033
2191
  ),
2034
- /* @__PURE__ */ jsx7(
2192
+ /* @__PURE__ */ jsx8(
2035
2193
  "select",
2036
2194
  {
2037
2195
  "aria-label": "Loop",
@@ -2039,13 +2197,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
2039
2197
  className: "rounded border border-border bg-background px-1 py-0.5",
2040
2198
  value: state.loop,
2041
2199
  onChange: (e) => animation.setLoop(e.target.value),
2042
- children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx7("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
2200
+ children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx8("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
2043
2201
  }
2044
2202
  )
2045
2203
  ] }),
2046
- /* @__PURE__ */ jsxs6("div", { className: "flex", children: [
2047
- /* @__PURE__ */ jsx7("div", { style: { width: labelWidth }, className: "shrink-0" }),
2048
- /* @__PURE__ */ jsxs6(
2204
+ /* @__PURE__ */ jsxs7("div", { className: "flex", children: [
2205
+ /* @__PURE__ */ jsx8("div", { style: { width: labelWidth }, className: "shrink-0" }),
2206
+ /* @__PURE__ */ jsxs7(
2049
2207
  "div",
2050
2208
  {
2051
2209
  "data-timeline-ruler": "",
@@ -2053,15 +2211,15 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
2053
2211
  style: { width },
2054
2212
  onPointerDown: onRulerDown,
2055
2213
  children: [
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 } })
2214
+ ticks.map((tick) => /* @__PURE__ */ jsx8("span", { className: "absolute top-0 -translate-x-1/2 text-[10px] text-muted-foreground", style: { left: tick.x }, children: tick.label }, tick.time)),
2215
+ /* @__PURE__ */ jsx8("div", { "data-playhead": "", className: "absolute top-0 h-5 w-px bg-red-500", style: { left: playheadX } })
2058
2216
  ]
2059
2217
  }
2060
2218
  )
2061
2219
  ] }),
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: [
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: [
2064
- /* @__PURE__ */ jsx7(
2220
+ rows.length === 0 ? /* @__PURE__ */ jsx8("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__ */ jsxs7("div", { className: "flex items-center", "data-track": row.id, children: [
2221
+ /* @__PURE__ */ jsxs7("label", { className: "flex shrink-0 items-center gap-1 truncate pr-2", style: { width: labelWidth }, title: `${row.nodeLabel} \xB7 ${row.label}`, children: [
2222
+ /* @__PURE__ */ jsx8(
2065
2223
  "input",
2066
2224
  {
2067
2225
  type: "checkbox",
@@ -2070,15 +2228,15 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
2070
2228
  onChange: (e) => animation.setTrackEnabled(row.nodeId, row.property, e.target.checked)
2071
2229
  }
2072
2230
  ),
2073
- /* @__PURE__ */ jsxs6("span", { className: "truncate", children: [
2231
+ /* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
2074
2232
  rows.filter((r) => r.nodeId === row.nodeId).length < rows.length ? `${row.nodeLabel} \xB7 ` : "",
2075
2233
  row.label
2076
2234
  ] })
2077
2235
  ] }),
2078
- /* @__PURE__ */ jsxs6("div", { "data-lane": "", className: `relative h-5 rounded bg-muted/40 ${row.enabled ? "" : "opacity-50"}`, style: { width }, children: [
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(
2236
+ /* @__PURE__ */ jsxs7("div", { "data-lane": "", className: `relative h-5 rounded bg-muted/40 ${row.enabled ? "" : "opacity-50"}`, style: { width }, children: [
2237
+ /* @__PURE__ */ jsx8("div", { className: "pointer-events-none absolute top-0 h-5 w-px bg-red-500/60", style: { left: playheadX } }),
2238
+ state.drag?.target != null ? /* @__PURE__ */ jsx8("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,
2239
+ row.keyframes.map((k) => /* @__PURE__ */ jsx8(
2082
2240
  "button",
2083
2241
  {
2084
2242
  type: "button",
@@ -2176,16 +2334,16 @@ import {
2176
2334
  } from "@pixerate/editor/canvas";
2177
2335
 
2178
2336
  // src/canvas/useCanvasComponents.ts
2179
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo9, useRef as useRef15 } from "react";
2337
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo10, useRef as useRef16 } from "react";
2180
2338
  import {
2181
2339
  createCanvasComponentsController
2182
2340
  } from "@pixerate/editor/canvas";
2183
2341
  function useCanvasComponents(options) {
2184
- const nodesRef = useRef15(options.nodes);
2342
+ const nodesRef = useRef16(options.nodes);
2185
2343
  nodesRef.current = options.nodes;
2186
- const optionsRef = useRef15(options);
2344
+ const optionsRef = useRef16(options);
2187
2345
  optionsRef.current = options;
2188
- const controller = useMemo9(
2346
+ const controller = useMemo10(
2189
2347
  () => createCanvasComponentsController({
2190
2348
  getNodes: () => nodesRef.current,
2191
2349
  setNodes: (next) => {
@@ -2221,15 +2379,15 @@ function useCanvasComponents(options) {
2221
2379
  }
2222
2380
 
2223
2381
  // src/canvas/CanvasComponentsPanel.tsx
2224
- import { useState as useState8 } from "react";
2382
+ import { useState as useState9 } from "react";
2225
2383
  import {
2226
2384
  CANVAS_COMPONENT_DRAG_TYPE,
2227
2385
  inspectCanvasInstance,
2228
2386
  listCanvasComponents
2229
2387
  } from "@pixerate/editor/canvas";
2230
- import { Fragment, jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
2388
+ import { Fragment as Fragment2, jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2231
2389
  function InlineField({ value, label, onCommit }) {
2232
- return /* @__PURE__ */ jsx8(
2390
+ return /* @__PURE__ */ jsx9(
2233
2391
  "input",
2234
2392
  {
2235
2393
  type: "text",
@@ -2260,10 +2418,10 @@ function CanvasComponentsPanel({
2260
2418
  search = true,
2261
2419
  className = ""
2262
2420
  }) {
2263
- const [query, setQuery] = useState8("");
2264
- const [propsOpen, setPropsOpen] = useState8(null);
2265
- const [menuFor, setMenuFor] = useState8(null);
2266
- const [editing, setEditing] = useState8(null);
2421
+ const [query, setQuery] = useState9("");
2422
+ const [propsOpen, setPropsOpen] = useState9(null);
2423
+ const [menuFor, setMenuFor] = useState9(null);
2424
+ const [editing, setEditing] = useState9(null);
2267
2425
  const { components: all, sets } = listCanvasComponents(nodes);
2268
2426
  const q = query.trim().toLowerCase();
2269
2427
  const match = (name) => !q || name.toLowerCase().includes(q);
@@ -2275,7 +2433,7 @@ function CanvasComponentsPanel({
2275
2433
  };
2276
2434
  const small = "shrink-0 rounded px-1 text-[10px] text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40";
2277
2435
  const menuItem = "rounded px-2 py-0.5 text-left hover:bg-muted disabled:opacity-40";
2278
- const item = (id, name, count, extra = {}, onContextMenu) => /* @__PURE__ */ jsxs7(
2436
+ const item = (id, name, count, extra = {}, onContextMenu) => /* @__PURE__ */ jsxs8(
2279
2437
  "button",
2280
2438
  {
2281
2439
  type: "button",
@@ -2292,17 +2450,17 @@ function CanvasComponentsPanel({
2292
2450
  e.dataTransfer.effectAllowed = "copy";
2293
2451
  },
2294
2452
  children: [
2295
- /* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
2296
- /* @__PURE__ */ jsx8("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
2453
+ /* @__PURE__ */ jsxs8("span", { className: "truncate", children: [
2454
+ /* @__PURE__ */ jsx9("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
2297
2455
  name
2298
2456
  ] }),
2299
- /* @__PURE__ */ jsx8("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
2457
+ /* @__PURE__ */ jsx9("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
2300
2458
  ]
2301
2459
  },
2302
2460
  id
2303
2461
  );
2304
- return /* @__PURE__ */ jsxs7("div", { "data-components-panel": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
2305
- search ? /* @__PURE__ */ jsx8(
2462
+ return /* @__PURE__ */ jsxs8("div", { "data-components-panel": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
2463
+ search ? /* @__PURE__ */ jsx9(
2306
2464
  "input",
2307
2465
  {
2308
2466
  type: "search",
@@ -2312,11 +2470,11 @@ function CanvasComponentsPanel({
2312
2470
  onInput: (e) => setQuery(e.target.value)
2313
2471
  }
2314
2472
  ) : null,
2315
- shownSets.map((set) => /* @__PURE__ */ jsxs7("div", { "data-component-set": set.id, className: "flex flex-col", children: [
2316
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
2473
+ shownSets.map((set) => /* @__PURE__ */ jsxs8("div", { "data-component-set": set.id, className: "flex flex-col", children: [
2474
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
2317
2475
  item(set.id, set.name, set.variants.reduce((sum, v) => sum + v.instanceCount, 0), { "data-kind": "set" }),
2318
- components ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2319
- /* @__PURE__ */ jsx8(
2476
+ components ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
2477
+ /* @__PURE__ */ jsx9(
2320
2478
  "button",
2321
2479
  {
2322
2480
  type: "button",
@@ -2328,7 +2486,7 @@ function CanvasComponentsPanel({
2328
2486
  children: "+\xA0Variant"
2329
2487
  }
2330
2488
  ),
2331
- /* @__PURE__ */ jsx8(
2489
+ /* @__PURE__ */ jsx9(
2332
2490
  "button",
2333
2491
  {
2334
2492
  type: "button",
@@ -2343,8 +2501,8 @@ function CanvasComponentsPanel({
2343
2501
  )
2344
2502
  ] }) : null
2345
2503
  ] }),
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(
2504
+ components && propsOpen === set.id ? /* @__PURE__ */ jsx9("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__ */ jsxs8("div", { "data-variant-option": option.name, className: "flex flex-col gap-0.5", children: [
2505
+ /* @__PURE__ */ jsx9(
2348
2506
  InlineField,
2349
2507
  {
2350
2508
  value: option.name,
@@ -2352,7 +2510,7 @@ function CanvasComponentsPanel({
2352
2510
  onCommit: (next) => report(components.renameVariantProperty(option.name, next, set.id))
2353
2511
  }
2354
2512
  ),
2355
- /* @__PURE__ */ jsx8("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx8(
2513
+ /* @__PURE__ */ jsx9("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx9(
2356
2514
  InlineField,
2357
2515
  {
2358
2516
  value,
@@ -2362,8 +2520,8 @@ function CanvasComponentsPanel({
2362
2520
  value
2363
2521
  )) })
2364
2522
  ] }, option.name)) }) : null,
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: [
2366
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
2523
+ /* @__PURE__ */ jsx9("div", { className: "ml-3 flex flex-col border-l border-border pl-1", children: set.variants.map((v) => /* @__PURE__ */ jsxs8("div", { "data-variant-row": v.id, className: "flex flex-col", children: [
2524
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1", children: [
2367
2525
  item(
2368
2526
  v.id,
2369
2527
  v.name,
@@ -2374,7 +2532,7 @@ function CanvasComponentsPanel({
2374
2532
  setMenuFor(v.id);
2375
2533
  } : void 0
2376
2534
  ),
2377
- components ? /* @__PURE__ */ jsx8(
2535
+ components ? /* @__PURE__ */ jsx9(
2378
2536
  "button",
2379
2537
  {
2380
2538
  type: "button",
@@ -2388,8 +2546,8 @@ function CanvasComponentsPanel({
2388
2546
  }
2389
2547
  ) : null
2390
2548
  ] }),
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: [
2392
- /* @__PURE__ */ jsx8(
2549
+ components && menuFor === v.id ? /* @__PURE__ */ jsxs8("div", { role: "menu", "data-variant-menu": v.id, className: "ml-2 flex flex-col rounded border border-border bg-background p-1 shadow", children: [
2550
+ /* @__PURE__ */ jsx9(
2393
2551
  "button",
2394
2552
  {
2395
2553
  type: "button",
@@ -2403,7 +2561,7 @@ function CanvasComponentsPanel({
2403
2561
  children: "Edit values\u2026"
2404
2562
  }
2405
2563
  ),
2406
- /* @__PURE__ */ jsx8(
2564
+ /* @__PURE__ */ jsx9(
2407
2565
  "button",
2408
2566
  {
2409
2567
  type: "button",
@@ -2417,7 +2575,7 @@ function CanvasComponentsPanel({
2417
2575
  children: "Duplicate"
2418
2576
  }
2419
2577
  ),
2420
- /* @__PURE__ */ jsx8(
2578
+ /* @__PURE__ */ jsx9(
2421
2579
  "button",
2422
2580
  {
2423
2581
  type: "button",
@@ -2434,7 +2592,7 @@ function CanvasComponentsPanel({
2434
2592
  }
2435
2593
  )
2436
2594
  ] }) : null,
2437
- components && editing === v.id ? /* @__PURE__ */ jsxs7(
2595
+ components && editing === v.id ? /* @__PURE__ */ jsxs8(
2438
2596
  "form",
2439
2597
  {
2440
2598
  "data-variant-editor": v.id,
@@ -2448,9 +2606,9 @@ function CanvasComponentsPanel({
2448
2606
  if (report(components.setVariantProperties(values, v.id))) setEditing(null);
2449
2607
  },
2450
2608
  children: [
2451
- set.options.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", children: [
2452
- /* @__PURE__ */ jsx8("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2453
- /* @__PURE__ */ jsx8(
2609
+ set.options.map((option) => /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2", children: [
2610
+ /* @__PURE__ */ jsx9("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2611
+ /* @__PURE__ */ jsx9(
2454
2612
  "input",
2455
2613
  {
2456
2614
  type: "text",
@@ -2461,9 +2619,9 @@ function CanvasComponentsPanel({
2461
2619
  }
2462
2620
  )
2463
2621
  ] }, option.name)),
2464
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
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" })
2622
+ /* @__PURE__ */ jsxs8("div", { className: "flex gap-1", children: [
2623
+ /* @__PURE__ */ jsx9("button", { type: "submit", className: "rounded border border-border px-2 py-0.5 hover:bg-muted", "data-action": "save-variant", children: "Save" }),
2624
+ /* @__PURE__ */ jsx9("button", { type: "button", className: "rounded px-2 py-0.5 hover:bg-muted", "data-action": "cancel-variant", onClick: () => setEditing(null), children: "Cancel" })
2467
2625
  ] })
2468
2626
  ]
2469
2627
  }
@@ -2471,7 +2629,7 @@ function CanvasComponentsPanel({
2471
2629
  ] }, v.id)) })
2472
2630
  ] }, set.id)),
2473
2631
  lone.map((c) => item(c.id, c.name, c.instanceCount, { "data-kind": "component" })),
2474
- all.length === 0 ? /* @__PURE__ */ jsx8("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2632
+ all.length === 0 ? /* @__PURE__ */ jsx9("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2475
2633
  ] });
2476
2634
  }
2477
2635
  function CanvasInstanceInspector({
@@ -2489,28 +2647,28 @@ function CanvasInstanceInspector({
2489
2647
  const setProp = (name, value) => report(components.setInstanceProperties({ [name]: value }, [info.id]));
2490
2648
  const allComponents = listCanvasComponents(nodes).components;
2491
2649
  const linked = info.status === "linked";
2492
- const btn = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-40";
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: [
2494
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-2", children: [
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
2650
+ const btn2 = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-40";
2651
+ return /* @__PURE__ */ jsxs8("div", { "data-instance-inspector": "", "data-instance-id": info.id, className: `flex flex-col gap-2 text-xs text-foreground ${className}`, children: [
2652
+ /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between gap-2", children: [
2653
+ /* @__PURE__ */ jsx9("span", { className: "truncate font-medium", "data-instance-main": "", children: linked ? info.componentName : "Main component missing" }),
2654
+ info.nested ? /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground", children: "Nested" }) : null
2497
2655
  ] }),
2498
- info.variantOptions.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-variant-property": option.name, children: [
2499
- /* @__PURE__ */ jsx8("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2500
- /* @__PURE__ */ jsx8(
2656
+ info.variantOptions.map((option) => /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2", "data-variant-property": option.name, children: [
2657
+ /* @__PURE__ */ jsx9("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2658
+ /* @__PURE__ */ jsx9(
2501
2659
  "select",
2502
2660
  {
2503
2661
  "aria-label": `${option.name} variant`,
2504
2662
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2505
2663
  value: info.variant[option.name] ?? "",
2506
2664
  onChange: (e) => setProp(option.name, e.currentTarget.value),
2507
- children: option.values.map((v) => /* @__PURE__ */ jsx8("option", { value: v, children: v }, v))
2665
+ children: option.values.map((v) => /* @__PURE__ */ jsx9("option", { value: v, children: v }, v))
2508
2666
  }
2509
2667
  )
2510
2668
  ] }, option.name)),
2511
- info.properties.map((p) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-component-property": p.name, children: [
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(
2669
+ info.properties.map((p) => /* @__PURE__ */ jsxs8("label", { className: "flex items-center gap-2", "data-component-property": p.name, children: [
2670
+ /* @__PURE__ */ jsx9("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: p.name }),
2671
+ p.type === "boolean" ? /* @__PURE__ */ jsx9("input", { type: "checkbox", "aria-label": p.name, checked: p.value === true, onChange: (e) => setProp(p.name, e.currentTarget.checked) }) : p.type === "text" ? /* @__PURE__ */ jsx9(
2514
2672
  "input",
2515
2673
  {
2516
2674
  type: "text",
@@ -2525,22 +2683,22 @@ function CanvasInstanceInspector({
2525
2683
  }
2526
2684
  },
2527
2685
  String(p.value)
2528
- ) : /* @__PURE__ */ jsx8(
2686
+ ) : /* @__PURE__ */ jsx9(
2529
2687
  "select",
2530
2688
  {
2531
2689
  "aria-label": p.name,
2532
2690
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2533
2691
  value: String(p.value),
2534
2692
  onChange: (e) => setProp(p.name, e.currentTarget.value),
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))
2693
+ children: allComponents.filter((c) => !p.preferred?.length || p.preferred.includes(c.id) || c.id === p.value).map((c) => /* @__PURE__ */ jsx9("option", { value: c.id, children: c.name }, c.id))
2536
2694
  }
2537
2695
  )
2538
2696
  ] }, p.name)),
2539
- info.overrides.length ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", "data-instance-overrides": "", children: [
2540
- /* @__PURE__ */ jsx8("span", { className: "text-muted-foreground", children: "Overrides" }),
2541
- info.overrides.map((o) => /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", "data-override-layer": o.id, children: [
2542
- /* @__PURE__ */ jsx8("span", { className: "mr-1 truncate", children: o.name }),
2543
- o.groups.map((g) => /* @__PURE__ */ jsxs7(
2697
+ info.overrides.length ? /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-1", "data-instance-overrides": "", children: [
2698
+ /* @__PURE__ */ jsx9("span", { className: "text-muted-foreground", children: "Overrides" }),
2699
+ info.overrides.map((o) => /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap items-center gap-1", "data-override-layer": o.id, children: [
2700
+ /* @__PURE__ */ jsx9("span", { className: "mr-1 truncate", children: o.name }),
2701
+ o.groups.map((g) => /* @__PURE__ */ jsxs8(
2544
2702
  "button",
2545
2703
  {
2546
2704
  type: "button",
@@ -2557,27 +2715,27 @@ function CanvasInstanceInspector({
2557
2715
  ))
2558
2716
  ] }, o.id))
2559
2717
  ] }) : null,
2560
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1", children: [
2561
- linked ? /* @__PURE__ */ jsxs7(Fragment, { children: [
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(
2718
+ /* @__PURE__ */ jsxs8("div", { className: "flex flex-wrap gap-1", children: [
2719
+ linked ? /* @__PURE__ */ jsxs8(Fragment2, { children: [
2720
+ /* @__PURE__ */ jsx9("button", { type: "button", className: btn2, "data-action": "reset", onClick: () => report(components.resetOverrides(info.id)), children: "Reset all" }),
2721
+ /* @__PURE__ */ jsx9("button", { type: "button", className: btn2, "data-action": "go-to-main", onClick: () => components.goToMainComponent(info.id), children: "Go to main" }),
2722
+ /* @__PURE__ */ jsx9(
2565
2723
  "button",
2566
2724
  {
2567
2725
  type: "button",
2568
- className: btn,
2726
+ className: btn2,
2569
2727
  "data-action": "push",
2570
2728
  disabled: info.overrides.length === 0,
2571
2729
  onClick: () => report(components.pushOverrides(info.rootId)),
2572
2730
  children: "Push changes"
2573
2731
  }
2574
2732
  )
2575
- ] }) : /* @__PURE__ */ jsx8("button", { type: "button", className: btn, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2576
- /* @__PURE__ */ jsx8(
2733
+ ] }) : /* @__PURE__ */ jsx9("button", { type: "button", className: btn2, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2734
+ /* @__PURE__ */ jsx9(
2577
2735
  "button",
2578
2736
  {
2579
2737
  type: "button",
2580
- className: btn,
2738
+ className: btn2,
2581
2739
  "data-action": "detach",
2582
2740
  disabled: info.nested,
2583
2741
  title: info.nested ? "Detach the outer instance first" : void 0,
@@ -2635,6 +2793,7 @@ import {
2635
2793
  isBrushedShape as isBrushedShape2,
2636
2794
  listCanvasBrushes as listCanvasBrushes2,
2637
2795
  validateCanvasBrush,
2796
+ validateCanvasBrushPaint,
2638
2797
  renderShapeBrushstroke as renderShapeBrushstroke2,
2639
2798
  getBrushedShapeSvg,
2640
2799
  loadCanvasBrushstrokeRenderer,
@@ -2652,7 +2811,7 @@ import {
2652
2811
  // src/canvas/MermaidFamilyScene.tsx
2653
2812
  import { useId } from "react";
2654
2813
  import { diagramTextBaseline } from "@pixerate/editor/canvas";
2655
- import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
2814
+ import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2656
2815
  function MermaidFamilyScene({
2657
2816
  scene,
2658
2817
  padding = 16,
@@ -2683,7 +2842,7 @@ function MermaidFamilyScene({
2683
2842
  cursor: "pointer"
2684
2843
  } : { pointerEvents: "none" };
2685
2844
  const stroke = (mark) => mark.selected ? { stroke: selectionColor, strokeWidth: Math.max(2, mark.strokeWidth ?? 0) } : { stroke: mark.stroke, strokeWidth: mark.strokeWidth };
2686
- return /* @__PURE__ */ jsxs8(
2845
+ return /* @__PURE__ */ jsxs9(
2687
2846
  "svg",
2688
2847
  {
2689
2848
  className: `pixerate-diagram-scene ${className}`.trim(),
@@ -2696,15 +2855,15 @@ function MermaidFamilyScene({
2696
2855
  style: { display: "block", maxWidth: "100%", height: "auto", overflow: "visible", ...style },
2697
2856
  onClick: interactive ? (event) => onSelect?.(null, event) : void 0,
2698
2857
  children: [
2699
- /* @__PURE__ */ jsxs8("defs", { children: [
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 } }) })
2858
+ /* @__PURE__ */ jsxs9("defs", { children: [
2859
+ /* @__PURE__ */ jsx10("marker", { id: `${uid}-arrowclosed`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx10("path", { d: "M0,0 L10,5 L0,10 Z", style: { fill: markerColor } }) }),
2860
+ /* @__PURE__ */ jsx10("marker", { id: `${uid}-arrow`, viewBox: "0 0 10 10", refX: "9", refY: "5", markerWidth: "8", markerHeight: "8", orient: "auto-start-reverse", children: /* @__PURE__ */ jsx10("path", { d: "M0,0 L10,5 L0,10", style: { fill: "none", stroke: markerColor, strokeWidth: 1.5 } }) })
2702
2861
  ] }),
2703
2862
  scene.marks.map((mark) => {
2704
2863
  const common = { "data-role": mark.role, "data-target": mark.target, "data-selected": mark.selected ? "" : void 0, opacity: mark.opacity, ...handlers(mark) };
2705
2864
  switch (mark.kind) {
2706
2865
  case "rect":
2707
- return /* @__PURE__ */ jsx9(
2866
+ return /* @__PURE__ */ jsx10(
2708
2867
  "rect",
2709
2868
  {
2710
2869
  ...common,
@@ -2719,9 +2878,9 @@ function MermaidFamilyScene({
2719
2878
  mark.key
2720
2879
  );
2721
2880
  case "circle":
2722
- return /* @__PURE__ */ jsx9("circle", { ...common, cx: mark.cx, cy: mark.cy, r: mark.r, style: { fill: mark.fill, ...stroke(mark) } }, mark.key);
2881
+ return /* @__PURE__ */ jsx10("circle", { ...common, cx: mark.cx, cy: mark.cy, r: mark.r, style: { fill: mark.fill, ...stroke(mark) } }, mark.key);
2723
2882
  case "line":
2724
- return /* @__PURE__ */ jsx9(
2883
+ return /* @__PURE__ */ jsx10(
2725
2884
  "line",
2726
2885
  {
2727
2886
  ...common,
@@ -2737,7 +2896,7 @@ function MermaidFamilyScene({
2737
2896
  mark.key
2738
2897
  );
2739
2898
  case "path":
2740
- return /* @__PURE__ */ jsx9(
2899
+ return /* @__PURE__ */ jsx10(
2741
2900
  "path",
2742
2901
  {
2743
2902
  ...common,
@@ -2751,7 +2910,7 @@ function MermaidFamilyScene({
2751
2910
  mark.key
2752
2911
  );
2753
2912
  case "text":
2754
- return /* @__PURE__ */ jsx9(
2913
+ return /* @__PURE__ */ jsx10(
2755
2914
  "text",
2756
2915
  {
2757
2916
  ...common,
@@ -2779,7 +2938,7 @@ function MermaidFamilyScene({
2779
2938
  }
2780
2939
 
2781
2940
  // src/canvas/useMermaidDiagram.ts
2782
- import { useEffect as useEffect11, useMemo as useMemo10, useRef as useRef16, useState as useState9, useSyncExternalStore as useSyncExternalStore4 } from "react";
2941
+ import { useEffect as useEffect12, useMemo as useMemo11, useRef as useRef17, useState as useState10, useSyncExternalStore as useSyncExternalStore4 } from "react";
2783
2942
  import {
2784
2943
  DiagramController
2785
2944
  } from "@pixerate/editor/diagram";
@@ -2797,7 +2956,7 @@ function useDiagramState(controller) {
2797
2956
  return useSyncExternalStore4(controller.subscribe, controller.getState, controller.getState);
2798
2957
  }
2799
2958
  function useMermaidDiagram(controllerOrOptions = {}, view = {}) {
2800
- const own = useRef16(null);
2959
+ const own = useRef17(null);
2801
2960
  let controller;
2802
2961
  if (controllerOrOptions instanceof DiagramController) controller = controllerOrOptions;
2803
2962
  else {
@@ -2807,19 +2966,19 @@ function useMermaidDiagram(controllerOrOptions = {}, view = {}) {
2807
2966
  const layoutOptions = (controllerOrOptions instanceof DiagramController ? void 0 : controllerOrOptions.layoutOptions) ?? view.layoutOptions;
2808
2967
  const sceneTheme = (controllerOrOptions instanceof DiagramController ? void 0 : controllerOrOptions.sceneTheme) ?? view.sceneTheme;
2809
2968
  const state = useDiagramState(controller);
2810
- const canvas = useMemo10(
2969
+ const canvas = useMemo11(
2811
2970
  () => mermaidToCanvas(state.document, { ...layoutOptions, editable: state.editable }),
2812
2971
  // eslint-disable-next-line react-hooks/exhaustive-deps
2813
2972
  [state.document, state.editable]
2814
2973
  );
2815
- const scene = useMemo10(
2974
+ const scene = useMemo11(
2816
2975
  () => state.document.tier === "editable" ? familySceneFromLayout(state.document.family, state.document.graph, canvas.layout, { theme: { ...CSS_VARIABLE_DIAGRAM_SCENE_THEME, ...sceneTheme }, selection: state.selection }) : null,
2817
2976
  // eslint-disable-next-line react-hooks/exhaustive-deps
2818
2977
  [state.document, canvas.layout, state.selection]
2819
2978
  );
2820
- const layoutRef = useRef16(canvas.layout);
2979
+ const layoutRef = useRef17(canvas.layout);
2821
2980
  layoutRef.current = canvas.layout;
2822
- const actions = useMemo10(
2981
+ const actions = useMemo11(
2823
2982
  () => ({
2824
2983
  apply: (ops, label) => controller.apply(ops, label),
2825
2984
  load: (source, options) => controller.load(source, options),
@@ -2856,13 +3015,13 @@ function useMermaidDiagram(controllerOrOptions = {}, view = {}) {
2856
3015
  }
2857
3016
  function useMermaidStream(source, options = {}) {
2858
3017
  const { complete = false, duration = 300, ...layoutOptions } = options;
2859
- const stream = useRef16(null);
3018
+ const stream = useRef17(null);
2860
3019
  if (!stream.current) stream.current = new DiagramStream(layoutOptions);
2861
- const result = useMemo10(() => stream.current.update(source, complete), [source, complete]);
2862
- const [shown, setShown] = useState9({ layout: result.layout, settled: true });
2863
- const shownRef = useRef16(shown.layout);
3020
+ const result = useMemo11(() => stream.current.update(source, complete), [source, complete]);
3021
+ const [shown, setShown] = useState10({ layout: result.layout, settled: true });
3022
+ const shownRef = useRef17(shown.layout);
2864
3023
  shownRef.current = shown.layout;
2865
- useEffect11(() => {
3024
+ useEffect12(() => {
2866
3025
  const target = result.layout;
2867
3026
  if (!target) return setShown({ layout: null, settled: true });
2868
3027
  const from = shownRef.current;
@@ -2879,7 +3038,7 @@ function useMermaidStream(source, options = {}) {
2879
3038
  return () => cancelAnimationFrame(frame);
2880
3039
  }, [result.layout, duration]);
2881
3040
  const doc = result.document.tier === "editable" ? result.document : null;
2882
- const canvas = useMemo10(() => {
3041
+ const canvas = useMemo11(() => {
2883
3042
  if (!doc || !shown.layout) return { nodes: [], edges: [] };
2884
3043
  const placed = new Set(Object.keys(shown.layout.positions));
2885
3044
  const graph = {
@@ -2895,7 +3054,7 @@ function useMermaidStream(source, options = {}) {
2895
3054
 
2896
3055
  // src/canvas/MermaidDiagramNode.tsx
2897
3056
  import { MERMAID_SHAPE_PAINT, mermaidLabelLines, mermaidNodeMembers, mermaidNodePaint, mermaidShapePaths } from "@pixerate/editor/canvas";
2898
- import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
3057
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2899
3058
  function mermaidNodeStyle(styles = {}, selected = false) {
2900
3059
  const paint = mermaidNodePaint(styles, selected);
2901
3060
  const { color, "stroke-width": strokeWidth, "stroke-dasharray": strokeDasharray, ...vars } = paint;
@@ -2905,7 +3064,7 @@ function MermaidDiagramNode({ data, width = 160, height = 56, selected, classNam
2905
3064
  const node = data.node;
2906
3065
  if (!node) return null;
2907
3066
  const members = mermaidNodeMembers(node.data);
2908
- return /* @__PURE__ */ jsxs9(
3067
+ return /* @__PURE__ */ jsxs10(
2909
3068
  "div",
2910
3069
  {
2911
3070
  className: `pixerate-diagram-node ${className}`.trim(),
@@ -2913,14 +3072,14 @@ function MermaidDiagramNode({ data, width = 160, height = 56, selected, classNam
2913
3072
  "data-selected": selected ? "true" : void 0,
2914
3073
  style: { position: "relative", width: "100%", height: "100%", ...mermaidNodeStyle(data.resolvedStyles, selected) },
2915
3074
  children: [
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)) }),
2917
- /* @__PURE__ */ jsxs9(
3075
+ /* @__PURE__ */ jsx11("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__ */ jsx11("path", { d: path.d, style: { ...MERMAID_SHAPE_PAINT[path.paint], vectorEffect: "non-scaling-stroke" } }, i)) }),
3076
+ /* @__PURE__ */ jsxs10(
2918
3077
  "div",
2919
3078
  {
2920
3079
  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" },
2921
3080
  children: [
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)) })
3081
+ mermaidLabelLines(node.label).map((line, i) => /* @__PURE__ */ jsx11("span", { style: { fontWeight: members.length ? 600 : void 0 }, children: line }, i)),
3082
+ members.length > 0 && /* @__PURE__ */ jsx11("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__ */ jsx11("div", { children: m }, i)) })
2924
3083
  ]
2925
3084
  }
2926
3085
  ),
@@ -2932,7 +3091,7 @@ function MermaidDiagramNode({ data, width = 160, height = 56, selected, classNam
2932
3091
  function MermaidDiagramGroup({ data, selected, className = "", children }) {
2933
3092
  const group = data.group;
2934
3093
  if (!group) return null;
2935
- return /* @__PURE__ */ jsxs9(
3094
+ return /* @__PURE__ */ jsxs10(
2936
3095
  "div",
2937
3096
  {
2938
3097
  className: `pixerate-diagram-group ${className}`.trim(),
@@ -2947,7 +3106,7 @@ function MermaidDiagramGroup({ data, selected, className = "", children }) {
2947
3106
  color: data.resolvedStyles?.color ?? "var(--muted-foreground, #475569)"
2948
3107
  },
2949
3108
  children: [
2950
- /* @__PURE__ */ jsx10("div", { style: { padding: "6px 10px", fontSize: 12, fontWeight: 600 }, children: mermaidLabelLines(group.label).join(" ") }),
3109
+ /* @__PURE__ */ jsx11("div", { style: { padding: "6px 10px", fontSize: 12, fontWeight: 600 }, children: mermaidLabelLines(group.label).join(" ") }),
2951
3110
  children
2952
3111
  ]
2953
3112
  }
@@ -2955,29 +3114,29 @@ function MermaidDiagramGroup({ data, selected, className = "", children }) {
2955
3114
  }
2956
3115
 
2957
3116
  // src/canvas/MermaidThumbnail.tsx
2958
- import { useMemo as useMemo11 } from "react";
3117
+ import { useMemo as useMemo12 } from "react";
2959
3118
  import { parseMermaid } from "@pixerate/editor/diagram";
2960
3119
  import { mermaidThumbnailSvg } from "@pixerate/editor/canvas";
2961
- import { Fragment as Fragment2, jsx as jsx11 } from "react/jsx-runtime";
3120
+ import { Fragment as Fragment3, jsx as jsx12 } from "react/jsx-runtime";
2962
3121
  function MermaidThumbnail({ source, className = "", fallback = null, ...options }) {
2963
- const doc = useMemo11(() => typeof source === "string" ? parseMermaid(source) : source, [source]);
3122
+ const doc = useMemo12(() => typeof source === "string" ? parseMermaid(source) : source, [source]);
2964
3123
  const key = JSON.stringify(options);
2965
- const drawn = useMemo11(
3124
+ const drawn = useMemo12(
2966
3125
  () => doc.tier === "editable" && doc.graph.nodes.length ? mermaidThumbnailSvg(doc, options) : null,
2967
3126
  // eslint-disable-next-line react-hooks/exhaustive-deps
2968
3127
  [doc, key]
2969
3128
  );
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 } });
3129
+ if (!drawn) return /* @__PURE__ */ jsx12(Fragment3, { children: fallback });
3130
+ return /* @__PURE__ */ jsx12("span", { className: `pixerate-diagram-thumbnail ${className}`.trim(), style: { display: "inline-block", lineHeight: 0 }, dangerouslySetInnerHTML: { __html: drawn.svg } });
2972
3131
  }
2973
3132
 
2974
3133
  // src/canvas/useDiagramDirector.ts
2975
- import { useRef as useRef17, useSyncExternalStore as useSyncExternalStore5 } from "react";
3134
+ import { useRef as useRef18, useSyncExternalStore as useSyncExternalStore5 } from "react";
2976
3135
  import { DiagramDirector, PresenceStore } from "@pixerate/editor/diagram";
2977
3136
  function useDiagramDirector(controller, options) {
2978
- const placement = useRef17(options.placement);
3137
+ const placement = useRef18(options.placement);
2979
3138
  placement.current = options.placement;
2980
- const ref = useRef17(null);
3139
+ const ref = useRef18(null);
2981
3140
  if (!ref.current) {
2982
3141
  const presence = new PresenceStore();
2983
3142
  ref.current = {
@@ -2992,13 +3151,13 @@ function usePresenceState(presence) {
2992
3151
  }
2993
3152
 
2994
3153
  // src/canvas/DiagramPresenceLayer.tsx
2995
- import { jsx as jsx12, jsxs as jsxs10 } from "react/jsx-runtime";
3154
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2996
3155
  function DiagramPresenceLayer({ presence, viewport, className = "" }) {
2997
3156
  const { actors } = usePresenceState(presence);
2998
3157
  if (!actors.length) return null;
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 })
3158
+ return /* @__PURE__ */ jsx13("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__ */ jsxs11("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: [
3159
+ /* @__PURE__ */ jsx13("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__ */ jsx13("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 } }) }),
3160
+ /* @__PURE__ */ jsx13("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 })
3002
3161
  ] }, a.id)) });
3003
3162
  }
3004
3163
  export {
@@ -3021,8 +3180,11 @@ export {
3021
3180
  CUBIC_BEZIER_PRESETS,
3022
3181
  CanvasComponentsPanel,
3023
3182
  CanvasDrawLayer,
3183
+ CanvasEffectsPanel,
3184
+ CanvasEffectsProvider,
3024
3185
  CanvasGuidesLayer,
3025
3186
  CanvasInstanceInspector,
3187
+ CanvasNodeEffectLayer,
3026
3188
  CanvasScenesStore2 as CanvasScenesStore,
3027
3189
  DEFAULT_ARRANGE_SHORTCUTS,
3028
3190
  DiagramPresenceLayer,
@@ -3196,7 +3358,7 @@ export {
3196
3358
  isLocked,
3197
3359
  isMeasureModifierHeld2 as isMeasureModifierHeld,
3198
3360
  isNodeAnimated,
3199
- isShapeDescription2 as isShapeDescription,
3361
+ isShapeDescription3 as isShapeDescription,
3200
3362
  isShapeNode,
3201
3363
  isStraightCubic,
3202
3364
  isValidCanvasNode,
@@ -3337,6 +3499,9 @@ export {
3337
3499
  useCanvasComponents,
3338
3500
  useCanvasDocking,
3339
3501
  useCanvasDraw,
3502
+ useCanvasEffects,
3503
+ useCanvasEffectsProviderContext,
3504
+ useCanvasEffectsTools,
3340
3505
  useCanvasExplosion,
3341
3506
  useCanvasGraph,
3342
3507
  useCanvasLayout,
@@ -3352,6 +3517,7 @@ export {
3352
3517
  useShapeBrushstroke,
3353
3518
  useStreamingCanvas,
3354
3519
  validateCanvasBrush,
3520
+ validateCanvasBrushPaint,
3355
3521
  withNodeAnimation,
3356
3522
  withStyleSize,
3357
3523
  wouldCreateComponentCycle,