@pixerate/editor-react 0.20.0 → 0.21.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.
@@ -976,8 +976,210 @@ function CanvasGuidesLayer({
976
976
  );
977
977
  }
978
978
 
979
+ // src/canvas/useCanvasDraw.ts
980
+ import { useEffect as useEffect4, useMemo as useMemo3, useRef as useRef7, useState as useState3 } from "react";
981
+ import {
982
+ createCanvasDrawController
983
+ } from "@pixerate/editor/canvas";
984
+ function useCanvasDraw(options) {
985
+ const nodesRef = useRef7(options.nodes);
986
+ nodesRef.current = options.nodes;
987
+ const optionsRef = useRef7(options);
988
+ optionsRef.current = options;
989
+ const [tool, setToolState] = useState3(options.initialTool ?? "select");
990
+ const [activeStroke, setActiveStroke] = useState3(null);
991
+ const controller = useMemo3(
992
+ () => createCanvasDrawController({
993
+ getNodes: () => nodesRef.current,
994
+ setNodes: (next) => {
995
+ nodesRef.current = next;
996
+ optionsRef.current.setNodes(next);
997
+ },
998
+ get history() {
999
+ return optionsRef.current.history;
1000
+ },
1001
+ tool: options.initialTool ?? "select",
1002
+ style: options.initialStyle,
1003
+ drawOptions: options.drawOptions,
1004
+ generateNodeId: options.generateNodeId,
1005
+ onToolChange: setToolState,
1006
+ onStyleChange: (s) => setStyleState(s),
1007
+ onActiveStrokeChange: setActiveStroke,
1008
+ onStrokeComplete: (n) => optionsRef.current.onStrokeComplete?.(n),
1009
+ onNodesErase: (erased) => optionsRef.current.onNodesErase?.(erased)
1010
+ }),
1011
+ []
1012
+ );
1013
+ const [style, setStyleState] = useState3(controller.style);
1014
+ useEffect4(() => {
1015
+ if (options.initialTool && options.initialTool !== controller.tool) {
1016
+ controller.setTool(options.initialTool);
1017
+ }
1018
+ }, [options.initialTool, controller]);
1019
+ return useMemo3(
1020
+ () => ({
1021
+ tool,
1022
+ style,
1023
+ activeStroke,
1024
+ isDrawing: activeStroke !== null,
1025
+ setTool: controller.setTool,
1026
+ setStyle: controller.setStyle,
1027
+ startStroke: controller.startStroke,
1028
+ continueStroke: controller.continueStroke,
1029
+ endStroke: controller.endStroke,
1030
+ cancelStroke: controller.cancelStroke,
1031
+ eraseAt: controller.eraseAt
1032
+ }),
1033
+ [tool, style, activeStroke, controller]
1034
+ );
1035
+ }
1036
+
1037
+ // src/canvas/CanvasDrawLayer.tsx
1038
+ import { useCallback as useCallback7, useEffect as useEffect5, useRef as useRef8, useState as useState4 } from "react";
1039
+ import {
1040
+ strokePointsToPathData
1041
+ } from "@pixerate/editor/canvas";
1042
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1043
+ var DEFAULT_VIEWPORT2 = { x: 0, y: 0, zoom: 1 };
1044
+ function CanvasDrawLayer({
1045
+ draw,
1046
+ viewport = DEFAULT_VIEWPORT2,
1047
+ className = "",
1048
+ style,
1049
+ cursor,
1050
+ eraserRadius = 12
1051
+ }) {
1052
+ const svgRef = useRef8(null);
1053
+ const [spaceHeld, setSpaceHeld] = useState4(false);
1054
+ const [eraserPos, setEraserPos] = useState4(null);
1055
+ useEffect5(() => {
1056
+ function onKeyDown(e) {
1057
+ if (e.code === "Space" && !e.repeat) {
1058
+ const tag = e.target?.tagName;
1059
+ if (tag === "INPUT" || tag === "TEXTAREA" || e.target?.isContentEditable) {
1060
+ return;
1061
+ }
1062
+ setSpaceHeld(true);
1063
+ }
1064
+ }
1065
+ function onKeyUp(e) {
1066
+ if (e.code === "Space") {
1067
+ setSpaceHeld(false);
1068
+ }
1069
+ }
1070
+ window.addEventListener("keydown", onKeyDown);
1071
+ window.addEventListener("keyup", onKeyUp);
1072
+ return () => {
1073
+ window.removeEventListener("keydown", onKeyDown);
1074
+ window.removeEventListener("keyup", onKeyUp);
1075
+ };
1076
+ }, []);
1077
+ const clientToFlow = useCallback7(
1078
+ (clientX, clientY) => {
1079
+ if (!svgRef.current) return [0, 0];
1080
+ const rect = svgRef.current.getBoundingClientRect();
1081
+ const screenX = clientX - rect.left;
1082
+ const screenY = clientY - rect.top;
1083
+ const flowX = (screenX - viewport.x) / viewport.zoom;
1084
+ const flowY = (screenY - viewport.y) / viewport.zoom;
1085
+ return [flowX, flowY];
1086
+ },
1087
+ [viewport.x, viewport.y, viewport.zoom]
1088
+ );
1089
+ const isDrawingMode = draw.tool !== "select";
1090
+ const interactive = isDrawingMode && !spaceHeld;
1091
+ const handlePointerDown = (e) => {
1092
+ if (!interactive || e.button !== 0) return;
1093
+ e.preventDefault();
1094
+ e.stopPropagation();
1095
+ e.target.setPointerCapture?.(e.pointerId);
1096
+ const flowPt = clientToFlow(e.clientX, e.clientY);
1097
+ draw.startStroke(flowPt);
1098
+ if (draw.tool === "eraser") {
1099
+ setEraserPos([e.clientX, e.clientY]);
1100
+ }
1101
+ };
1102
+ const handlePointerMove = (e) => {
1103
+ if (draw.tool === "eraser" && !spaceHeld) {
1104
+ setEraserPos([e.clientX, e.clientY]);
1105
+ }
1106
+ if (!interactive) return;
1107
+ if (e.buttons === 1) {
1108
+ e.preventDefault();
1109
+ e.stopPropagation();
1110
+ const flowPt = clientToFlow(e.clientX, e.clientY);
1111
+ draw.continueStroke(flowPt);
1112
+ }
1113
+ };
1114
+ const handlePointerUp = (e) => {
1115
+ if (!interactive || e.button !== 0) return;
1116
+ e.preventDefault();
1117
+ e.stopPropagation();
1118
+ try {
1119
+ e.target.releasePointerCapture?.(e.pointerId);
1120
+ } catch {
1121
+ }
1122
+ draw.endStroke();
1123
+ };
1124
+ const handlePointerLeave = () => {
1125
+ setEraserPos(null);
1126
+ };
1127
+ const resolvedCursor = cursor ? cursor : spaceHeld ? "grab" : draw.tool === "eraser" ? "crosshair" : draw.tool === "pen" || draw.tool === "highlighter" ? "crosshair" : "default";
1128
+ const strokePath = draw.activeStroke ? strokePointsToPathData(draw.activeStroke.points) : null;
1129
+ return /* @__PURE__ */ jsxs3(
1130
+ "svg",
1131
+ {
1132
+ ref: svgRef,
1133
+ className: `pixerate-canvas-draw-layer ${className}`,
1134
+ style: {
1135
+ position: "absolute",
1136
+ inset: 0,
1137
+ width: "100%",
1138
+ height: "100%",
1139
+ pointerEvents: interactive ? "all" : "none",
1140
+ cursor: interactive ? resolvedCursor : void 0,
1141
+ overflow: "visible",
1142
+ zIndex: 20,
1143
+ touchAction: "none",
1144
+ ...style
1145
+ },
1146
+ onPointerDown: handlePointerDown,
1147
+ onPointerMove: handlePointerMove,
1148
+ onPointerUp: handlePointerUp,
1149
+ onPointerLeave: handlePointerLeave,
1150
+ children: [
1151
+ /* @__PURE__ */ jsx3("g", { transform: `translate(${viewport.x}, ${viewport.y}) scale(${viewport.zoom})`, children: draw.activeStroke && strokePath && /* @__PURE__ */ jsx3(
1152
+ "path",
1153
+ {
1154
+ d: strokePath,
1155
+ fill: "none",
1156
+ stroke: draw.activeStroke.style.color,
1157
+ strokeWidth: draw.activeStroke.style.width,
1158
+ opacity: draw.activeStroke.style.opacity,
1159
+ strokeLinecap: draw.activeStroke.style.cap ?? "round",
1160
+ strokeLinejoin: draw.activeStroke.style.join ?? "round"
1161
+ }
1162
+ ) }),
1163
+ draw.tool === "eraser" && eraserPos && svgRef.current && /* @__PURE__ */ jsx3(
1164
+ "circle",
1165
+ {
1166
+ cx: eraserPos[0] - svgRef.current.getBoundingClientRect().left,
1167
+ cy: eraserPos[1] - svgRef.current.getBoundingClientRect().top,
1168
+ r: eraserRadius,
1169
+ fill: "rgba(239, 68, 68, 0.15)",
1170
+ stroke: "rgba(239, 68, 68, 0.8)",
1171
+ strokeWidth: 1.5,
1172
+ strokeDasharray: "3 3",
1173
+ pointerEvents: "none"
1174
+ }
1175
+ )
1176
+ ]
1177
+ }
1178
+ );
1179
+ }
1180
+
979
1181
  // src/canvas/useCanvasMeasure.ts
980
- import { useCallback as useCallback7, useEffect as useEffect4, useMemo as useMemo3, useRef as useRef7, useState as useState3 } from "react";
1182
+ import { useCallback as useCallback8, useEffect as useEffect6, useMemo as useMemo4, useRef as useRef9, useState as useState5 } from "react";
981
1183
  import {
982
1184
  isMeasureModifierHeld,
983
1185
  measureSelection
@@ -985,11 +1187,11 @@ import {
985
1187
  var EMPTY = { guides: [], target: null, selection: null };
986
1188
  function useCanvasMeasure(options) {
987
1189
  const { nodes, enabled = true, requireModifier = true, targetElement, rectOptions } = options;
988
- const [hoveredId, setHoveredId] = useState3(null);
989
- const [modifier, setModifier] = useState3(false);
990
- const optionsRef = useRef7(options);
1190
+ const [hoveredId, setHoveredId] = useState5(null);
1191
+ const [modifier, setModifier] = useState5(false);
1192
+ const optionsRef = useRef9(options);
991
1193
  optionsRef.current = options;
992
- useEffect4(() => {
1194
+ useEffect6(() => {
993
1195
  if (!enabled) return;
994
1196
  const target = targetElement ?? (typeof window !== "undefined" ? window : null);
995
1197
  if (!target) return;
@@ -1006,16 +1208,16 @@ function useCanvasMeasure(options) {
1006
1208
  };
1007
1209
  }, [enabled, targetElement]);
1008
1210
  const active = enabled && (!requireModifier || modifier);
1009
- const result = useMemo3(() => {
1211
+ const result = useMemo4(() => {
1010
1212
  if (!active) return EMPTY;
1011
1213
  const ids = optionsRef.current.getSelectedIds?.() ?? nodes.filter((n) => n.selected).map((n) => n.id);
1012
1214
  return measureSelection(nodes, ids, hoveredId, rectOptions);
1013
1215
  }, [active, nodes, hoveredId, rectOptions]);
1014
- const onNodeMouseEnter = useCallback7((event, node) => {
1216
+ const onNodeMouseEnter = useCallback8((event, node) => {
1015
1217
  setHoveredId(node.id);
1016
1218
  if (event && typeof event.altKey === "boolean") setModifier(event.altKey);
1017
1219
  }, []);
1018
- const onNodeMouseLeave = useCallback7((_event, node) => {
1220
+ const onNodeMouseLeave = useCallback8((_event, node) => {
1019
1221
  setHoveredId((current) => current === node.id ? null : current);
1020
1222
  }, []);
1021
1223
  return {
@@ -1094,12 +1296,12 @@ import {
1094
1296
 
1095
1297
  // src/canvas/ShapeNode.tsx
1096
1298
  import { getShapeRenderData, isShapeDescription } from "@pixerate/editor/canvas";
1097
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
1299
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1098
1300
  function ShapeNode({ data, width, height, selected, className = "", children }) {
1099
1301
  const shape = data?.shape;
1100
1302
  if (!isShapeDescription(shape)) return null;
1101
1303
  const render = getShapeRenderData(shape, { width, height });
1102
- return /* @__PURE__ */ jsxs3(
1304
+ return /* @__PURE__ */ jsxs4(
1103
1305
  "div",
1104
1306
  {
1105
1307
  className: `pixerate-shape-node ${className}`.trim(),
@@ -1107,7 +1309,7 @@ function ShapeNode({ data, width, height, selected, className = "", children })
1107
1309
  "data-selected": selected ? "true" : void 0,
1108
1310
  style: { position: "relative", width: "100%", height: "100%" },
1109
1311
  children: [
1110
- /* @__PURE__ */ jsx3(
1312
+ /* @__PURE__ */ jsx4(
1111
1313
  "svg",
1112
1314
  {
1113
1315
  viewBox: render.viewBox,
@@ -1116,7 +1318,7 @@ function ShapeNode({ data, width, height, selected, className = "", children })
1116
1318
  height: "100%",
1117
1319
  style: { display: "block", overflow: "visible" },
1118
1320
  "aria-hidden": "true",
1119
- children: /* @__PURE__ */ jsx3(
1321
+ children: /* @__PURE__ */ jsx4(
1120
1322
  "path",
1121
1323
  {
1122
1324
  d: render.d,
@@ -1264,17 +1466,17 @@ import {
1264
1466
  } from "@pixerate/editor/canvas";
1265
1467
 
1266
1468
  // src/canvas/useCanvasAutoLayout.ts
1267
- import { useLayoutEffect, useMemo as useMemo4, useRef as useRef8, useState as useState4 } from "react";
1469
+ import { useLayoutEffect, useMemo as useMemo5, useRef as useRef10, useState as useState6 } from "react";
1268
1470
  import {
1269
1471
  createCanvasAutoLayoutController
1270
1472
  } from "@pixerate/editor/canvas";
1271
1473
  function useCanvasAutoLayout(options) {
1272
- const [guides, setGuides] = useState4([]);
1273
- const nodesRef = useRef8(options.nodes);
1474
+ const [guides, setGuides] = useState6([]);
1475
+ const nodesRef = useRef10(options.nodes);
1274
1476
  nodesRef.current = options.nodes;
1275
- const optionsRef = useRef8(options);
1477
+ const optionsRef = useRef10(options);
1276
1478
  optionsRef.current = options;
1277
- const controller = useMemo4(
1479
+ const controller = useMemo5(
1278
1480
  () => createCanvasAutoLayoutController({
1279
1481
  getNodes: () => nodesRef.current,
1280
1482
  setNodes: (next) => {
@@ -1303,7 +1505,7 @@ function useCanvasAutoLayout(options) {
1303
1505
  useLayoutEffect(() => {
1304
1506
  if (controller.hasPending) controller.flush();
1305
1507
  }, [options.nodes, controller]);
1306
- return useMemo4(
1508
+ return useMemo5(
1307
1509
  () => ({
1308
1510
  ...controller,
1309
1511
  /** Drop marker of the active reorder drag (flow coordinates). */
@@ -1317,7 +1519,7 @@ function useCanvasAutoLayout(options) {
1317
1519
  }
1318
1520
 
1319
1521
  // src/canvas/useCanvasTokens.ts
1320
- import { useCallback as useCallback8, useEffect as useEffect5, useRef as useRef9, useSyncExternalStore as useSyncExternalStore2 } from "react";
1522
+ import { useCallback as useCallback9, useEffect as useEffect7, useRef as useRef11, useSyncExternalStore as useSyncExternalStore2 } from "react";
1321
1523
  import {
1322
1524
  bindCanvasToken,
1323
1525
  detachTokenBindings,
@@ -1328,10 +1530,10 @@ import {
1328
1530
  function useCanvasTokens(options) {
1329
1531
  const { store, nodes, setNodes, modes, properties } = options;
1330
1532
  const state = useSyncExternalStore2(store.subscribe, store.getState, store.getState);
1331
- const latest = useRef9({ nodes, setNodes, modes, properties });
1533
+ const latest = useRef11({ nodes, setNodes, modes, properties });
1332
1534
  latest.current = { nodes, setNodes, modes, properties };
1333
1535
  const opts = () => ({ modes: latest.current.modes, properties: latest.current.properties });
1334
- useEffect5(
1536
+ useEffect7(
1335
1537
  () => store.subscribe((event) => {
1336
1538
  if (event.deletedVariableIds.length === 0) return;
1337
1539
  latest.current.setNodes((prev) => detachTokenBindings(prev, event.previous, event.deletedVariableIds, opts()));
@@ -1340,11 +1542,11 @@ function useCanvasTokens(options) {
1340
1542
  [store]
1341
1543
  );
1342
1544
  const modesKey = JSON.stringify(modes ?? {});
1343
- useEffect5(() => {
1545
+ useEffect7(() => {
1344
1546
  const next = resolveCanvasTokens(nodes, state, opts());
1345
1547
  if (next !== nodes) setNodes(next);
1346
1548
  }, [nodes, state, modesKey, properties]);
1347
- const bind = useCallback8(
1549
+ const bind = useCallback9(
1348
1550
  (ids, propertyKey, variableId) => {
1349
1551
  const next = bindCanvasToken(latest.current.nodes, ids, propertyKey, variableId, store.getState(), opts());
1350
1552
  if (next !== latest.current.nodes) latest.current.setNodes(next);
@@ -1352,7 +1554,7 @@ function useCanvasTokens(options) {
1352
1554
  // eslint-disable-next-line react-hooks/exhaustive-deps
1353
1555
  [store]
1354
1556
  );
1355
- const setMode = useCallback8(
1557
+ const setMode = useCallback9(
1356
1558
  (ids, collectionId, modeId) => {
1357
1559
  const next = setCanvasTokenMode(latest.current.nodes, ids, collectionId, modeId, store.getState(), opts());
1358
1560
  if (next !== latest.current.nodes) latest.current.setNodes(next);
@@ -1360,7 +1562,7 @@ function useCanvasTokens(options) {
1360
1562
  // eslint-disable-next-line react-hooks/exhaustive-deps
1361
1563
  [store]
1362
1564
  );
1363
- const modesOf = useCallback8(
1565
+ const modesOf = useCallback9(
1364
1566
  (nodeId) => {
1365
1567
  const node = latest.current.nodes.find((n) => n.id === nodeId);
1366
1568
  return node ? getEffectiveTokenModes(node, latest.current.nodes, store.getState(), opts()) : {};
@@ -1382,7 +1584,7 @@ import {
1382
1584
  getTokensForProperty
1383
1585
  } from "@pixerate/editor/canvas";
1384
1586
  import { getCurrentMode, resolveToken } from "@pixerate/editor/tokens";
1385
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1587
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1386
1588
  function TokenBindingPicker({
1387
1589
  state,
1388
1590
  nodes,
@@ -1399,15 +1601,15 @@ function TokenBindingPicker({
1399
1601
  let rows = getBindableTokenProperties(first, options);
1400
1602
  if (properties) rows = properties.map((key) => rows.find((p) => p.key === key)).filter((p) => !!p);
1401
1603
  const modes = getEffectiveTokenModes2(first, nodes, state, options);
1402
- return /* @__PURE__ */ jsxs4("div", { "data-token-binding-picker": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
1604
+ return /* @__PURE__ */ jsxs5("div", { "data-token-binding-picker": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
1403
1605
  rows.map((property) => {
1404
1606
  const ids = new Set(selected.map((n) => getNodeTokenBindings(n)[property.key] ?? ""));
1405
1607
  const value = ids.size === 1 ? [...ids][0] : "__mixed";
1406
1608
  const candidates = getTokensForProperty(state, property, first);
1407
1609
  const resolved = value && value !== "__mixed" ? resolveToken(state, value, modes) : null;
1408
- return /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2", "data-token-property": property.key, children: [
1409
- /* @__PURE__ */ jsx4("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1410
- /* @__PURE__ */ jsxs4(
1610
+ return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-property": property.key, children: [
1611
+ /* @__PURE__ */ jsx5("span", { className: "w-28 shrink-0 text-muted-foreground", children: property.label }),
1612
+ /* @__PURE__ */ jsxs5(
1411
1613
  "select",
1412
1614
  {
1413
1615
  "aria-label": `${property.label} variable`,
@@ -1422,11 +1624,11 @@ function TokenBindingPicker({
1422
1624
  }
1423
1625
  },
1424
1626
  children: [
1425
- value === "__mixed" ? /* @__PURE__ */ jsx4("option", { value: "__mixed", children: "Mixed" }) : null,
1426
- /* @__PURE__ */ jsx4("option", { value: "", children: "None" }),
1627
+ value === "__mixed" ? /* @__PURE__ */ jsx5("option", { value: "__mixed", children: "Mixed" }) : null,
1628
+ /* @__PURE__ */ jsx5("option", { value: "", children: "None" }),
1427
1629
  candidates.map((v) => {
1428
1630
  const collection = state.collections.find((c) => c.id === v.collectionId);
1429
- return /* @__PURE__ */ jsxs4("option", { value: v.id, children: [
1631
+ return /* @__PURE__ */ jsxs5("option", { value: v.id, children: [
1430
1632
  v.name,
1431
1633
  collection && state.collections.length > 1 ? ` (${collection.name})` : ""
1432
1634
  ] }, v.id);
@@ -1434,17 +1636,17 @@ function TokenBindingPicker({
1434
1636
  ]
1435
1637
  }
1436
1638
  ),
1437
- /* @__PURE__ */ jsx4("span", { className: "w-16 shrink-0 truncate text-right text-muted-foreground", "data-token-resolved": "", children: resolved?.ok ? property.type === "color" ? /* @__PURE__ */ jsx4(
1639
+ /* @__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(
1438
1640
  "span",
1439
1641
  {
1440
1642
  className: "inline-block h-3 w-3 rounded border border-border align-middle",
1441
1643
  style: { background: String(resolved.value) },
1442
1644
  title: String(resolved.value)
1443
1645
  }
1444
- ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx4("span", { title: resolved.message, children: "\u26A0" }) : null })
1646
+ ) : String(resolved.value) : resolved ? /* @__PURE__ */ jsx5("span", { title: resolved.message, children: "\u26A0" }) : null })
1445
1647
  ] }, property.key);
1446
1648
  }),
1447
- state.collections.length === 0 ? /* @__PURE__ */ jsx4("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1649
+ state.collections.length === 0 ? /* @__PURE__ */ jsx5("p", { className: "text-muted-foreground", children: "No variables yet." }) : null
1448
1650
  ] });
1449
1651
  }
1450
1652
  function TokenModeSwitcher({
@@ -1462,12 +1664,12 @@ function TokenModeSwitcher({
1462
1664
  const parent = first.parentId ? nodes.find((n) => n.id === first.parentId) : void 0;
1463
1665
  const inherited = parent ? getEffectiveTokenModes2(parent, nodes, state, options) : { ...options.modes };
1464
1666
  const collections = state.collections.filter((c) => showSingleMode || c.modes.length > 1);
1465
- return /* @__PURE__ */ jsx4("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1667
+ return /* @__PURE__ */ jsx5("div", { "data-token-mode-switcher": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: collections.map((collection) => {
1466
1668
  const auto = getCurrentMode(collection, inherited);
1467
1669
  const explicit = collection.modes.some((m) => m.id === own[collection.id]) ? own[collection.id] : "";
1468
- return /* @__PURE__ */ jsxs4("label", { className: "flex items-center gap-2", "data-token-collection": collection.id, children: [
1469
- /* @__PURE__ */ jsx4("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1470
- /* @__PURE__ */ jsxs4(
1670
+ return /* @__PURE__ */ jsxs5("label", { className: "flex items-center gap-2", "data-token-collection": collection.id, children: [
1671
+ /* @__PURE__ */ jsx5("span", { className: "w-28 shrink-0 truncate text-muted-foreground", children: collection.name }),
1672
+ /* @__PURE__ */ jsxs5(
1471
1673
  "select",
1472
1674
  {
1473
1675
  "aria-label": `${collection.name} mode`,
@@ -1475,12 +1677,12 @@ function TokenModeSwitcher({
1475
1677
  value: explicit,
1476
1678
  onChange: (e) => onChange(setCanvasTokenMode2(nodes, nodeIds, collection.id, e.currentTarget.value || null, state, options)),
1477
1679
  children: [
1478
- /* @__PURE__ */ jsxs4("option", { value: "", children: [
1680
+ /* @__PURE__ */ jsxs5("option", { value: "", children: [
1479
1681
  "Auto (",
1480
1682
  auto.name,
1481
1683
  ")"
1482
1684
  ] }),
1483
- collection.modes.map((m) => /* @__PURE__ */ jsx4("option", { value: m.id, children: m.name }, m.id))
1685
+ collection.modes.map((m) => /* @__PURE__ */ jsx5("option", { value: m.id, children: m.name }, m.id))
1484
1686
  ]
1485
1687
  }
1486
1688
  )
@@ -1511,16 +1713,16 @@ import {
1511
1713
  } from "@pixerate/editor/canvas";
1512
1714
 
1513
1715
  // src/canvas/useCanvasAnimation.ts
1514
- import { useEffect as useEffect6, useMemo as useMemo5, useRef as useRef10, useSyncExternalStore as useSyncExternalStore3 } from "react";
1716
+ import { useEffect as useEffect8, useMemo as useMemo6, useRef as useRef12, useSyncExternalStore as useSyncExternalStore3 } from "react";
1515
1717
  import {
1516
1718
  createCanvasAnimationController
1517
1719
  } from "@pixerate/editor/canvas";
1518
1720
  function useCanvasAnimation(options) {
1519
- const nodesRef = useRef10(options.nodes);
1721
+ const nodesRef = useRef12(options.nodes);
1520
1722
  nodesRef.current = options.nodes;
1521
- const optionsRef = useRef10(options);
1723
+ const optionsRef = useRef12(options);
1522
1724
  optionsRef.current = options;
1523
- const controller = useMemo5(
1725
+ const controller = useMemo6(
1524
1726
  () => createCanvasAnimationController({
1525
1727
  getNodes: () => nodesRef.current,
1526
1728
  setNodes: (next) => {
@@ -1547,14 +1749,14 @@ function useCanvasAnimation(options) {
1547
1749
  }),
1548
1750
  []
1549
1751
  );
1550
- useEffect6(() => () => controller.destroy(), [controller]);
1551
- useEffect6(() => {
1752
+ useEffect8(() => () => controller.destroy(), [controller]);
1753
+ useEffect8(() => {
1552
1754
  if (options.loop !== void 0) controller.setLoop(options.loop);
1553
1755
  }, [controller, options.loop]);
1554
- useEffect6(() => {
1756
+ useEffect8(() => {
1555
1757
  if (options.playbackRate !== void 0) controller.setPlaybackRate(options.playbackRate);
1556
1758
  }, [controller, options.playbackRate]);
1557
- useEffect6(() => {
1759
+ useEffect8(() => {
1558
1760
  if (options.recording !== void 0) controller.setRecording(options.recording);
1559
1761
  }, [controller, options.recording]);
1560
1762
  const state = useSyncExternalStore3(
@@ -1562,11 +1764,11 @@ function useCanvasAnimation(options) {
1562
1764
  () => controller.getState(),
1563
1765
  () => controller.getState()
1564
1766
  );
1565
- useEffect6(() => {
1767
+ useEffect8(() => {
1566
1768
  controller.refresh();
1567
1769
  }, [controller, options.nodes]);
1568
- const displayNodes = useMemo5(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
1569
- return useMemo5(
1770
+ const displayNodes = useMemo6(() => controller.display(options.nodes), [controller, options.nodes, state.time]);
1771
+ return useMemo6(
1570
1772
  () => ({
1571
1773
  ...controller,
1572
1774
  state,
@@ -1587,7 +1789,7 @@ function useCanvasAnimation(options) {
1587
1789
  }
1588
1790
 
1589
1791
  // src/canvas/AnimationTimeline.tsx
1590
- import { useEffect as useEffect7, useRef as useRef11 } from "react";
1792
+ import { useEffect as useEffect9, useRef as useRef13 } from "react";
1591
1793
  import {
1592
1794
  ANIMATION_EASING_LABELS,
1593
1795
  ANIMATION_EASING_PRESETS,
@@ -1596,7 +1798,7 @@ import {
1596
1798
  timeToX,
1597
1799
  xToTime
1598
1800
  } from "@pixerate/editor/canvas";
1599
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
1801
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
1600
1802
  var LOOP_LABELS = { none: "Once", loop: "Loop", "ping-pong": "Ping-pong" };
1601
1803
  var easingValue = (easing) => typeof easing === "string" ? easing : easing ? "custom" : "linear";
1602
1804
  function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8, labelOf, className = "" }) {
@@ -1610,8 +1812,8 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1610
1812
  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);
1611
1813
  return "";
1612
1814
  })();
1613
- const dragRef = useRef11(null);
1614
- useEffect7(() => {
1815
+ const dragRef = useRef13(null);
1816
+ useEffect9(() => {
1615
1817
  const onKey = (event) => {
1616
1818
  if (event.key === "Escape" && dragRef.current) {
1617
1819
  dragRef.current = null;
@@ -1667,10 +1869,10 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1667
1869
  const btn = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-50";
1668
1870
  const playheadX = timeToX(state.time, duration, width);
1669
1871
  const labelWidth = 120;
1670
- return /* @__PURE__ */ jsxs5("div", { "data-animation-timeline": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, tabIndex: 0, onKeyDown: onTimelineKey, children: [
1671
- /* @__PURE__ */ jsxs5("div", { className: "flex flex-wrap items-center gap-2", role: "toolbar", "aria-label": "Animation", children: [
1672
- /* @__PURE__ */ jsx5("button", { type: "button", className: btn, "data-action": "toggle-play", "aria-label": state.playing ? "Pause" : "Play", onClick: () => animation.toggle(), children: state.playing ? "\u275A\u275A" : "\u25B6" }),
1673
- /* @__PURE__ */ jsx5(
1872
+ return /* @__PURE__ */ jsxs6("div", { "data-animation-timeline": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, tabIndex: 0, onKeyDown: onTimelineKey, children: [
1873
+ /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-2", role: "toolbar", "aria-label": "Animation", children: [
1874
+ /* @__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" }),
1875
+ /* @__PURE__ */ jsx6(
1674
1876
  "button",
1675
1877
  {
1676
1878
  type: "button",
@@ -1682,13 +1884,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1682
1884
  children: "\u25CF Rec"
1683
1885
  }
1684
1886
  ),
1685
- /* @__PURE__ */ jsx5("button", { type: "button", className: btn, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
1686
- /* @__PURE__ */ jsxs5("span", { "data-time": "", className: "tabular-nums text-muted-foreground", children: [
1887
+ /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "add-keyframe", onClick: () => animation.addKeyframe(), children: "\u25C6 Key" }),
1888
+ /* @__PURE__ */ jsxs6("span", { "data-time": "", className: "tabular-nums text-muted-foreground", children: [
1687
1889
  formatAnimationTime(state.time),
1688
1890
  " / ",
1689
1891
  formatAnimationTime(duration)
1690
1892
  ] }),
1691
- /* @__PURE__ */ jsxs5(
1893
+ /* @__PURE__ */ jsxs6(
1692
1894
  "select",
1693
1895
  {
1694
1896
  "aria-label": "Easing",
@@ -1698,13 +1900,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1698
1900
  disabled: state.selection.length === 0,
1699
1901
  onChange: (e) => e.target.value && e.target.value !== "custom" && animation.setEasing(e.target.value),
1700
1902
  children: [
1701
- /* @__PURE__ */ jsx5("option", { value: "", children: "Easing\u2026" }),
1702
- ANIMATION_EASING_PRESETS.map((preset) => /* @__PURE__ */ jsx5("option", { value: preset, children: ANIMATION_EASING_LABELS[preset] }, preset)),
1703
- selectedEasing === "custom" ? /* @__PURE__ */ jsx5("option", { value: "custom", children: "Custom" }) : null
1903
+ /* @__PURE__ */ jsx6("option", { value: "", children: "Easing\u2026" }),
1904
+ ANIMATION_EASING_PRESETS.map((preset) => /* @__PURE__ */ jsx6("option", { value: preset, children: ANIMATION_EASING_LABELS[preset] }, preset)),
1905
+ selectedEasing === "custom" ? /* @__PURE__ */ jsx6("option", { value: "custom", children: "Custom" }) : null
1704
1906
  ]
1705
1907
  }
1706
1908
  ),
1707
- /* @__PURE__ */ jsx5(
1909
+ /* @__PURE__ */ jsx6(
1708
1910
  "select",
1709
1911
  {
1710
1912
  "aria-label": "Loop",
@@ -1712,13 +1914,13 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1712
1914
  className: "rounded border border-border bg-background px-1 py-0.5",
1713
1915
  value: state.loop,
1714
1916
  onChange: (e) => animation.setLoop(e.target.value),
1715
- children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx5("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
1917
+ children: Object.keys(LOOP_LABELS).map((mode) => /* @__PURE__ */ jsx6("option", { value: mode, children: LOOP_LABELS[mode] }, mode))
1716
1918
  }
1717
1919
  )
1718
1920
  ] }),
1719
- /* @__PURE__ */ jsxs5("div", { className: "flex", children: [
1720
- /* @__PURE__ */ jsx5("div", { style: { width: labelWidth }, className: "shrink-0" }),
1721
- /* @__PURE__ */ jsxs5(
1921
+ /* @__PURE__ */ jsxs6("div", { className: "flex", children: [
1922
+ /* @__PURE__ */ jsx6("div", { style: { width: labelWidth }, className: "shrink-0" }),
1923
+ /* @__PURE__ */ jsxs6(
1722
1924
  "div",
1723
1925
  {
1724
1926
  "data-timeline-ruler": "",
@@ -1726,15 +1928,15 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1726
1928
  style: { width },
1727
1929
  onPointerDown: onRulerDown,
1728
1930
  children: [
1729
- ticks.map((tick) => /* @__PURE__ */ jsx5("span", { className: "absolute top-0 -translate-x-1/2 text-[10px] text-muted-foreground", style: { left: tick.x }, children: tick.label }, tick.time)),
1730
- /* @__PURE__ */ jsx5("div", { "data-playhead": "", className: "absolute top-0 h-5 w-px bg-red-500", style: { left: playheadX } })
1931
+ 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)),
1932
+ /* @__PURE__ */ jsx6("div", { "data-playhead": "", className: "absolute top-0 h-5 w-px bg-red-500", style: { left: playheadX } })
1731
1933
  ]
1732
1934
  }
1733
1935
  )
1734
1936
  ] }),
1735
- rows.length === 0 ? /* @__PURE__ */ jsx5("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__ */ jsxs5("div", { className: "flex items-center", "data-track": row.id, children: [
1736
- /* @__PURE__ */ jsxs5("label", { className: "flex shrink-0 items-center gap-1 truncate pr-2", style: { width: labelWidth }, title: `${row.nodeLabel} \xB7 ${row.label}`, children: [
1737
- /* @__PURE__ */ jsx5(
1937
+ 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: [
1938
+ /* @__PURE__ */ jsxs6("label", { className: "flex shrink-0 items-center gap-1 truncate pr-2", style: { width: labelWidth }, title: `${row.nodeLabel} \xB7 ${row.label}`, children: [
1939
+ /* @__PURE__ */ jsx6(
1738
1940
  "input",
1739
1941
  {
1740
1942
  type: "checkbox",
@@ -1743,15 +1945,15 @@ function AnimationTimeline({ animation, nodeIds, width = 360, snapThreshold = 8,
1743
1945
  onChange: (e) => animation.setTrackEnabled(row.nodeId, row.property, e.target.checked)
1744
1946
  }
1745
1947
  ),
1746
- /* @__PURE__ */ jsxs5("span", { className: "truncate", children: [
1948
+ /* @__PURE__ */ jsxs6("span", { className: "truncate", children: [
1747
1949
  rows.filter((r) => r.nodeId === row.nodeId).length < rows.length ? `${row.nodeLabel} \xB7 ` : "",
1748
1950
  row.label
1749
1951
  ] })
1750
1952
  ] }),
1751
- /* @__PURE__ */ jsxs5("div", { "data-lane": "", className: `relative h-5 rounded bg-muted/40 ${row.enabled ? "" : "opacity-50"}`, style: { width }, children: [
1752
- /* @__PURE__ */ jsx5("div", { className: "pointer-events-none absolute top-0 h-5 w-px bg-red-500/60", style: { left: playheadX } }),
1753
- state.drag?.target != null ? /* @__PURE__ */ jsx5("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,
1754
- row.keyframes.map((k) => /* @__PURE__ */ jsx5(
1953
+ /* @__PURE__ */ jsxs6("div", { "data-lane": "", className: `relative h-5 rounded bg-muted/40 ${row.enabled ? "" : "opacity-50"}`, style: { width }, children: [
1954
+ /* @__PURE__ */ jsx6("div", { className: "pointer-events-none absolute top-0 h-5 w-px bg-red-500/60", style: { left: playheadX } }),
1955
+ 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,
1956
+ row.keyframes.map((k) => /* @__PURE__ */ jsx6(
1755
1957
  "button",
1756
1958
  {
1757
1959
  type: "button",
@@ -1849,16 +2051,16 @@ import {
1849
2051
  } from "@pixerate/editor/canvas";
1850
2052
 
1851
2053
  // src/canvas/useCanvasComponents.ts
1852
- import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo6, useRef as useRef12 } from "react";
2054
+ import { useLayoutEffect as useLayoutEffect2, useMemo as useMemo7, useRef as useRef14 } from "react";
1853
2055
  import {
1854
2056
  createCanvasComponentsController
1855
2057
  } from "@pixerate/editor/canvas";
1856
2058
  function useCanvasComponents(options) {
1857
- const nodesRef = useRef12(options.nodes);
2059
+ const nodesRef = useRef14(options.nodes);
1858
2060
  nodesRef.current = options.nodes;
1859
- const optionsRef = useRef12(options);
2061
+ const optionsRef = useRef14(options);
1860
2062
  optionsRef.current = options;
1861
- const controller = useMemo6(
2063
+ const controller = useMemo7(
1862
2064
  () => createCanvasComponentsController({
1863
2065
  getNodes: () => nodesRef.current,
1864
2066
  setNodes: (next) => {
@@ -1894,15 +2096,15 @@ function useCanvasComponents(options) {
1894
2096
  }
1895
2097
 
1896
2098
  // src/canvas/CanvasComponentsPanel.tsx
1897
- import { useState as useState5 } from "react";
2099
+ import { useState as useState7 } from "react";
1898
2100
  import {
1899
2101
  CANVAS_COMPONENT_DRAG_TYPE,
1900
2102
  inspectCanvasInstance,
1901
2103
  listCanvasComponents
1902
2104
  } from "@pixerate/editor/canvas";
1903
- import { Fragment, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
2105
+ import { Fragment, jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1904
2106
  function InlineField({ value, label, onCommit }) {
1905
- return /* @__PURE__ */ jsx6(
2107
+ return /* @__PURE__ */ jsx7(
1906
2108
  "input",
1907
2109
  {
1908
2110
  type: "text",
@@ -1933,10 +2135,10 @@ function CanvasComponentsPanel({
1933
2135
  search = true,
1934
2136
  className = ""
1935
2137
  }) {
1936
- const [query, setQuery] = useState5("");
1937
- const [propsOpen, setPropsOpen] = useState5(null);
1938
- const [menuFor, setMenuFor] = useState5(null);
1939
- const [editing, setEditing] = useState5(null);
2138
+ const [query, setQuery] = useState7("");
2139
+ const [propsOpen, setPropsOpen] = useState7(null);
2140
+ const [menuFor, setMenuFor] = useState7(null);
2141
+ const [editing, setEditing] = useState7(null);
1940
2142
  const { components: all, sets } = listCanvasComponents(nodes);
1941
2143
  const q = query.trim().toLowerCase();
1942
2144
  const match = (name) => !q || name.toLowerCase().includes(q);
@@ -1948,7 +2150,7 @@ function CanvasComponentsPanel({
1948
2150
  };
1949
2151
  const small = "shrink-0 rounded px-1 text-[10px] text-muted-foreground hover:bg-muted hover:text-foreground disabled:opacity-40";
1950
2152
  const menuItem = "rounded px-2 py-0.5 text-left hover:bg-muted disabled:opacity-40";
1951
- const item = (id, name, count, extra = {}, onContextMenu) => /* @__PURE__ */ jsxs6(
2153
+ const item = (id, name, count, extra = {}, onContextMenu) => /* @__PURE__ */ jsxs7(
1952
2154
  "button",
1953
2155
  {
1954
2156
  type: "button",
@@ -1965,17 +2167,17 @@ function CanvasComponentsPanel({
1965
2167
  e.dataTransfer.effectAllowed = "copy";
1966
2168
  },
1967
2169
  children: [
1968
- /* @__PURE__ */ jsxs6("span", { className: "truncate", children: [
1969
- /* @__PURE__ */ jsx6("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
2170
+ /* @__PURE__ */ jsxs7("span", { className: "truncate", children: [
2171
+ /* @__PURE__ */ jsx7("span", { "aria-hidden": true, className: "mr-1 text-muted-foreground", children: "\u25C7" }),
1970
2172
  name
1971
2173
  ] }),
1972
- /* @__PURE__ */ jsx6("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
2174
+ /* @__PURE__ */ jsx7("span", { className: "text-[10px] text-muted-foreground", "data-instance-count": "", children: count })
1973
2175
  ]
1974
2176
  },
1975
2177
  id
1976
2178
  );
1977
- return /* @__PURE__ */ jsxs6("div", { "data-components-panel": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
1978
- search ? /* @__PURE__ */ jsx6(
2179
+ return /* @__PURE__ */ jsxs7("div", { "data-components-panel": "", className: `flex flex-col gap-1 text-xs text-foreground ${className}`, children: [
2180
+ search ? /* @__PURE__ */ jsx7(
1979
2181
  "input",
1980
2182
  {
1981
2183
  type: "search",
@@ -1985,11 +2187,11 @@ function CanvasComponentsPanel({
1985
2187
  onInput: (e) => setQuery(e.target.value)
1986
2188
  }
1987
2189
  ) : null,
1988
- shownSets.map((set) => /* @__PURE__ */ jsxs6("div", { "data-component-set": set.id, className: "flex flex-col", children: [
1989
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
2190
+ shownSets.map((set) => /* @__PURE__ */ jsxs7("div", { "data-component-set": set.id, className: "flex flex-col", children: [
2191
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
1990
2192
  item(set.id, set.name, set.variants.reduce((sum, v) => sum + v.instanceCount, 0), { "data-kind": "set" }),
1991
- components ? /* @__PURE__ */ jsxs6(Fragment, { children: [
1992
- /* @__PURE__ */ jsx6(
2193
+ components ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2194
+ /* @__PURE__ */ jsx7(
1993
2195
  "button",
1994
2196
  {
1995
2197
  type: "button",
@@ -2001,7 +2203,7 @@ function CanvasComponentsPanel({
2001
2203
  children: "+\xA0Variant"
2002
2204
  }
2003
2205
  ),
2004
- /* @__PURE__ */ jsx6(
2206
+ /* @__PURE__ */ jsx7(
2005
2207
  "button",
2006
2208
  {
2007
2209
  type: "button",
@@ -2016,8 +2218,8 @@ function CanvasComponentsPanel({
2016
2218
  )
2017
2219
  ] }) : null
2018
2220
  ] }),
2019
- components && propsOpen === set.id ? /* @__PURE__ */ jsx6("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__ */ jsxs6("div", { "data-variant-option": option.name, className: "flex flex-col gap-0.5", children: [
2020
- /* @__PURE__ */ jsx6(
2221
+ 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: [
2222
+ /* @__PURE__ */ jsx7(
2021
2223
  InlineField,
2022
2224
  {
2023
2225
  value: option.name,
@@ -2025,7 +2227,7 @@ function CanvasComponentsPanel({
2025
2227
  onCommit: (next) => report(components.renameVariantProperty(option.name, next, set.id))
2026
2228
  }
2027
2229
  ),
2028
- /* @__PURE__ */ jsx6("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx6(
2230
+ /* @__PURE__ */ jsx7("div", { className: "ml-2 flex flex-wrap gap-1", children: option.values.map((value) => /* @__PURE__ */ jsx7(
2029
2231
  InlineField,
2030
2232
  {
2031
2233
  value,
@@ -2035,8 +2237,8 @@ function CanvasComponentsPanel({
2035
2237
  value
2036
2238
  )) })
2037
2239
  ] }, option.name)) }) : null,
2038
- /* @__PURE__ */ jsx6("div", { className: "ml-3 flex flex-col border-l border-border pl-1", children: set.variants.map((v) => /* @__PURE__ */ jsxs6("div", { "data-variant-row": v.id, className: "flex flex-col", children: [
2039
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-1", children: [
2240
+ /* @__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: [
2241
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1", children: [
2040
2242
  item(
2041
2243
  v.id,
2042
2244
  v.name,
@@ -2047,7 +2249,7 @@ function CanvasComponentsPanel({
2047
2249
  setMenuFor(v.id);
2048
2250
  } : void 0
2049
2251
  ),
2050
- components ? /* @__PURE__ */ jsx6(
2252
+ components ? /* @__PURE__ */ jsx7(
2051
2253
  "button",
2052
2254
  {
2053
2255
  type: "button",
@@ -2061,8 +2263,8 @@ function CanvasComponentsPanel({
2061
2263
  }
2062
2264
  ) : null
2063
2265
  ] }),
2064
- components && menuFor === v.id ? /* @__PURE__ */ jsxs6("div", { role: "menu", "data-variant-menu": v.id, className: "ml-2 flex flex-col rounded border border-border bg-background p-1 shadow", children: [
2065
- /* @__PURE__ */ jsx6(
2266
+ 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: [
2267
+ /* @__PURE__ */ jsx7(
2066
2268
  "button",
2067
2269
  {
2068
2270
  type: "button",
@@ -2076,7 +2278,7 @@ function CanvasComponentsPanel({
2076
2278
  children: "Edit values\u2026"
2077
2279
  }
2078
2280
  ),
2079
- /* @__PURE__ */ jsx6(
2281
+ /* @__PURE__ */ jsx7(
2080
2282
  "button",
2081
2283
  {
2082
2284
  type: "button",
@@ -2090,7 +2292,7 @@ function CanvasComponentsPanel({
2090
2292
  children: "Duplicate"
2091
2293
  }
2092
2294
  ),
2093
- /* @__PURE__ */ jsx6(
2295
+ /* @__PURE__ */ jsx7(
2094
2296
  "button",
2095
2297
  {
2096
2298
  type: "button",
@@ -2107,7 +2309,7 @@ function CanvasComponentsPanel({
2107
2309
  }
2108
2310
  )
2109
2311
  ] }) : null,
2110
- components && editing === v.id ? /* @__PURE__ */ jsxs6(
2312
+ components && editing === v.id ? /* @__PURE__ */ jsxs7(
2111
2313
  "form",
2112
2314
  {
2113
2315
  "data-variant-editor": v.id,
@@ -2121,9 +2323,9 @@ function CanvasComponentsPanel({
2121
2323
  if (report(components.setVariantProperties(values, v.id))) setEditing(null);
2122
2324
  },
2123
2325
  children: [
2124
- set.options.map((option) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2", children: [
2125
- /* @__PURE__ */ jsx6("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2126
- /* @__PURE__ */ jsx6(
2326
+ set.options.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", children: [
2327
+ /* @__PURE__ */ jsx7("span", { className: "w-16 shrink-0 truncate text-muted-foreground", children: option.name }),
2328
+ /* @__PURE__ */ jsx7(
2127
2329
  "input",
2128
2330
  {
2129
2331
  type: "text",
@@ -2134,9 +2336,9 @@ function CanvasComponentsPanel({
2134
2336
  }
2135
2337
  )
2136
2338
  ] }, option.name)),
2137
- /* @__PURE__ */ jsxs6("div", { className: "flex gap-1", children: [
2138
- /* @__PURE__ */ jsx6("button", { type: "submit", className: "rounded border border-border px-2 py-0.5 hover:bg-muted", "data-action": "save-variant", children: "Save" }),
2139
- /* @__PURE__ */ jsx6("button", { type: "button", className: "rounded px-2 py-0.5 hover:bg-muted", "data-action": "cancel-variant", onClick: () => setEditing(null), children: "Cancel" })
2339
+ /* @__PURE__ */ jsxs7("div", { className: "flex gap-1", children: [
2340
+ /* @__PURE__ */ jsx7("button", { type: "submit", className: "rounded border border-border px-2 py-0.5 hover:bg-muted", "data-action": "save-variant", children: "Save" }),
2341
+ /* @__PURE__ */ jsx7("button", { type: "button", className: "rounded px-2 py-0.5 hover:bg-muted", "data-action": "cancel-variant", onClick: () => setEditing(null), children: "Cancel" })
2140
2342
  ] })
2141
2343
  ]
2142
2344
  }
@@ -2144,7 +2346,7 @@ function CanvasComponentsPanel({
2144
2346
  ] }, v.id)) })
2145
2347
  ] }, set.id)),
2146
2348
  lone.map((c) => item(c.id, c.name, c.instanceCount, { "data-kind": "component" })),
2147
- all.length === 0 ? /* @__PURE__ */ jsx6("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2349
+ all.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-muted-foreground", children: "No components yet. Select a frame and choose Create Component." }) : null
2148
2350
  ] });
2149
2351
  }
2150
2352
  function CanvasInstanceInspector({
@@ -2163,27 +2365,27 @@ function CanvasInstanceInspector({
2163
2365
  const allComponents = listCanvasComponents(nodes).components;
2164
2366
  const linked = info.status === "linked";
2165
2367
  const btn = "rounded border border-border px-2 py-0.5 text-xs hover:bg-muted disabled:opacity-40";
2166
- return /* @__PURE__ */ jsxs6("div", { "data-instance-inspector": "", "data-instance-id": info.id, className: `flex flex-col gap-2 text-xs text-foreground ${className}`, children: [
2167
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between gap-2", children: [
2168
- /* @__PURE__ */ jsx6("span", { className: "truncate font-medium", "data-instance-main": "", children: linked ? info.componentName : "Main component missing" }),
2169
- info.nested ? /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "Nested" }) : null
2368
+ return /* @__PURE__ */ jsxs7("div", { "data-instance-inspector": "", "data-instance-id": info.id, className: `flex flex-col gap-2 text-xs text-foreground ${className}`, children: [
2369
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between gap-2", children: [
2370
+ /* @__PURE__ */ jsx7("span", { className: "truncate font-medium", "data-instance-main": "", children: linked ? info.componentName : "Main component missing" }),
2371
+ info.nested ? /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "Nested" }) : null
2170
2372
  ] }),
2171
- info.variantOptions.map((option) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2", "data-variant-property": option.name, children: [
2172
- /* @__PURE__ */ jsx6("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2173
- /* @__PURE__ */ jsx6(
2373
+ info.variantOptions.map((option) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-variant-property": option.name, children: [
2374
+ /* @__PURE__ */ jsx7("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: option.name }),
2375
+ /* @__PURE__ */ jsx7(
2174
2376
  "select",
2175
2377
  {
2176
2378
  "aria-label": `${option.name} variant`,
2177
2379
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2178
2380
  value: info.variant[option.name] ?? "",
2179
2381
  onChange: (e) => setProp(option.name, e.currentTarget.value),
2180
- children: option.values.map((v) => /* @__PURE__ */ jsx6("option", { value: v, children: v }, v))
2382
+ children: option.values.map((v) => /* @__PURE__ */ jsx7("option", { value: v, children: v }, v))
2181
2383
  }
2182
2384
  )
2183
2385
  ] }, option.name)),
2184
- info.properties.map((p) => /* @__PURE__ */ jsxs6("label", { className: "flex items-center gap-2", "data-component-property": p.name, children: [
2185
- /* @__PURE__ */ jsx6("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: p.name }),
2186
- p.type === "boolean" ? /* @__PURE__ */ jsx6("input", { type: "checkbox", "aria-label": p.name, checked: p.value === true, onChange: (e) => setProp(p.name, e.currentTarget.checked) }) : p.type === "text" ? /* @__PURE__ */ jsx6(
2386
+ info.properties.map((p) => /* @__PURE__ */ jsxs7("label", { className: "flex items-center gap-2", "data-component-property": p.name, children: [
2387
+ /* @__PURE__ */ jsx7("span", { className: "w-24 shrink-0 truncate text-muted-foreground", children: p.name }),
2388
+ 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(
2187
2389
  "input",
2188
2390
  {
2189
2391
  type: "text",
@@ -2198,22 +2400,22 @@ function CanvasInstanceInspector({
2198
2400
  }
2199
2401
  },
2200
2402
  String(p.value)
2201
- ) : /* @__PURE__ */ jsx6(
2403
+ ) : /* @__PURE__ */ jsx7(
2202
2404
  "select",
2203
2405
  {
2204
2406
  "aria-label": p.name,
2205
2407
  className: "min-w-0 flex-1 rounded border border-border bg-background px-1 py-0.5 text-xs",
2206
2408
  value: String(p.value),
2207
2409
  onChange: (e) => setProp(p.name, e.currentTarget.value),
2208
- children: allComponents.filter((c) => !p.preferred?.length || p.preferred.includes(c.id) || c.id === p.value).map((c) => /* @__PURE__ */ jsx6("option", { value: c.id, children: c.name }, c.id))
2410
+ 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))
2209
2411
  }
2210
2412
  )
2211
2413
  ] }, p.name)),
2212
- info.overrides.length ? /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-1", "data-instance-overrides": "", children: [
2213
- /* @__PURE__ */ jsx6("span", { className: "text-muted-foreground", children: "Overrides" }),
2214
- info.overrides.map((o) => /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap items-center gap-1", "data-override-layer": o.id, children: [
2215
- /* @__PURE__ */ jsx6("span", { className: "mr-1 truncate", children: o.name }),
2216
- o.groups.map((g) => /* @__PURE__ */ jsxs6(
2414
+ info.overrides.length ? /* @__PURE__ */ jsxs7("div", { className: "flex flex-col gap-1", "data-instance-overrides": "", children: [
2415
+ /* @__PURE__ */ jsx7("span", { className: "text-muted-foreground", children: "Overrides" }),
2416
+ info.overrides.map((o) => /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap items-center gap-1", "data-override-layer": o.id, children: [
2417
+ /* @__PURE__ */ jsx7("span", { className: "mr-1 truncate", children: o.name }),
2418
+ o.groups.map((g) => /* @__PURE__ */ jsxs7(
2217
2419
  "button",
2218
2420
  {
2219
2421
  type: "button",
@@ -2230,11 +2432,11 @@ function CanvasInstanceInspector({
2230
2432
  ))
2231
2433
  ] }, o.id))
2232
2434
  ] }) : null,
2233
- /* @__PURE__ */ jsxs6("div", { className: "flex flex-wrap gap-1", children: [
2234
- linked ? /* @__PURE__ */ jsxs6(Fragment, { children: [
2235
- /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "reset", onClick: () => report(components.resetOverrides(info.id)), children: "Reset all" }),
2236
- /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "go-to-main", onClick: () => components.goToMainComponent(info.id), children: "Go to main" }),
2237
- /* @__PURE__ */ jsx6(
2435
+ /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-1", children: [
2436
+ linked ? /* @__PURE__ */ jsxs7(Fragment, { children: [
2437
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "reset", onClick: () => report(components.resetOverrides(info.id)), children: "Reset all" }),
2438
+ /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "go-to-main", onClick: () => components.goToMainComponent(info.id), children: "Go to main" }),
2439
+ /* @__PURE__ */ jsx7(
2238
2440
  "button",
2239
2441
  {
2240
2442
  type: "button",
@@ -2245,8 +2447,8 @@ function CanvasInstanceInspector({
2245
2447
  children: "Push changes"
2246
2448
  }
2247
2449
  )
2248
- ] }) : /* @__PURE__ */ jsx6("button", { type: "button", className: btn, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2249
- /* @__PURE__ */ jsx6(
2450
+ ] }) : /* @__PURE__ */ jsx7("button", { type: "button", className: btn, "data-action": "restore", onClick: () => report(components.restoreMainComponent(info.rootId)), children: "Restore main component" }),
2451
+ /* @__PURE__ */ jsx7(
2250
2452
  "button",
2251
2453
  {
2252
2454
  type: "button",
@@ -2296,7 +2498,13 @@ import {
2296
2498
  isCanvasComponentSet,
2297
2499
  isCanvasInstance,
2298
2500
  wouldCreateComponentCycle,
2299
- CANVAS_COMPONENT_DRAG_TYPE as CANVAS_COMPONENT_DRAG_TYPE2
2501
+ CANVAS_COMPONENT_DRAG_TYPE as CANVAS_COMPONENT_DRAG_TYPE2,
2502
+ createCanvasDrawController as createCanvasDrawController2,
2503
+ createDefaultStrokeStyle,
2504
+ findStrokesAtPoint,
2505
+ streamlinePoints,
2506
+ strokePointsToPathData as strokePointsToPathData2,
2507
+ strokeToShapeNode
2300
2508
  } from "@pixerate/editor/canvas";
2301
2509
  export {
2302
2510
  ANIMATABLE_PROPERTIES,
@@ -2316,6 +2524,7 @@ export {
2316
2524
  CSS_ANIMATION_PROPERTIES,
2317
2525
  CUBIC_BEZIER_PRESETS,
2318
2526
  CanvasComponentsPanel,
2527
+ CanvasDrawLayer,
2319
2528
  CanvasGuidesLayer,
2320
2529
  CanvasInstanceInspector,
2321
2530
  CanvasScenesStore2 as CanvasScenesStore,
@@ -2375,8 +2584,10 @@ export {
2375
2584
  createCanvasAutoLayoutController2 as createCanvasAutoLayoutController,
2376
2585
  createCanvasComponent,
2377
2586
  createCanvasComponentsController2 as createCanvasComponentsController,
2587
+ createCanvasDrawController2 as createCanvasDrawController,
2378
2588
  createCanvasInstance,
2379
2589
  createCanvasScenesStore,
2590
+ createDefaultStrokeStyle,
2380
2591
  createFanOutTrajectory2 as createFanOutTrajectory,
2381
2592
  createGuideIndex,
2382
2593
  createNodeGuideIndex,
@@ -2404,6 +2615,7 @@ export {
2404
2615
  excludePaths,
2405
2616
  exportCssAnimation,
2406
2617
  findConnectorSnap,
2618
+ findStrokesAtPoint,
2407
2619
  fitCubicRun,
2408
2620
  fitCubics,
2409
2621
  fitFreehandPath,
@@ -2577,7 +2789,10 @@ export {
2577
2789
  snapTimeToFrame,
2578
2790
  snapToGrid,
2579
2791
  springAt,
2792
+ streamlinePoints,
2580
2793
  strokeOutlineRings,
2794
+ strokePointsToPathData2 as strokePointsToPathData,
2795
+ strokeToShapeNode,
2581
2796
  subtractPaths,
2582
2797
  subtractPolygons,
2583
2798
  swapCanvasInstance,
@@ -2600,6 +2815,7 @@ export {
2600
2815
  useCanvasClipboard,
2601
2816
  useCanvasComponents,
2602
2817
  useCanvasDocking,
2818
+ useCanvasDraw,
2603
2819
  useCanvasExplosion,
2604
2820
  useCanvasGraph,
2605
2821
  useCanvasLayout,