system-canvas-standalone 0.1.2 → 0.2.1

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.
@@ -12759,12 +12759,12 @@ var SystemCanvas = (() => {
12759
12759
  render: () => render,
12760
12760
  themes: () => themes
12761
12761
  });
12762
- var import_react21 = __toESM(require_react(), 1);
12762
+ var import_react22 = __toESM(require_react(), 1);
12763
12763
  var import_client = __toESM(require_client(), 1);
12764
12764
 
12765
12765
  // ../react/dist/components/SystemCanvas.js
12766
- var import_jsx_runtime27 = __toESM(require_jsx_runtime(), 1);
12767
- var import_react20 = __toESM(require_react(), 1);
12766
+ var import_jsx_runtime28 = __toESM(require_jsx_runtime(), 1);
12767
+ var import_react21 = __toESM(require_react(), 1);
12768
12768
 
12769
12769
  // ../core/dist/themes/dark.js
12770
12770
  var darkTheme = {
@@ -12827,6 +12827,25 @@ var SystemCanvas = (() => {
12827
12827
  fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', monospace",
12828
12828
  fontSize: 12
12829
12829
  },
12830
+ contextMenu: {
12831
+ background: "rgba(15, 23, 42, 0.96)",
12832
+ // slate-900 near-opaque
12833
+ borderColor: "rgba(71, 85, 105, 0.6)",
12834
+ // slate-600
12835
+ borderRadius: 8,
12836
+ itemColor: "#e2e8f0",
12837
+ // slate-200
12838
+ itemHoverBackground: "rgba(71, 85, 105, 0.35)",
12839
+ destructiveItemColor: "#fb7185",
12840
+ // rose-400
12841
+ fontFamily: "'JetBrains Mono', 'Fira Code', 'SF Mono', monospace",
12842
+ fontSize: 12,
12843
+ paddingY: 4,
12844
+ paddingX: 4,
12845
+ itemPaddingY: 5,
12846
+ itemPaddingX: 10,
12847
+ shadow: "0 8px 24px rgba(0,0,0,0.35)"
12848
+ },
12830
12849
  lanes: {
12831
12850
  bandFillEven: "rgba(30, 41, 59, 0.18)",
12832
12851
  // slate-800 soft
@@ -12912,6 +12931,21 @@ var SystemCanvas = (() => {
12912
12931
  fontFamily: "'JetBrains Mono', monospace",
12913
12932
  fontSize: 11
12914
12933
  },
12934
+ contextMenu: {
12935
+ background: "rgba(0, 0, 0, 0.96)",
12936
+ borderColor: "rgba(55, 65, 81, 0.7)",
12937
+ borderRadius: 6,
12938
+ itemColor: "#d1d5db",
12939
+ itemHoverBackground: "rgba(0, 255, 136, 0.12)",
12940
+ destructiveItemColor: "#f87171",
12941
+ fontFamily: "'JetBrains Mono', monospace",
12942
+ fontSize: 11,
12943
+ paddingY: 3,
12944
+ paddingX: 3,
12945
+ itemPaddingY: 5,
12946
+ itemPaddingX: 10,
12947
+ shadow: "0 8px 24px rgba(0,255,136,0.08)"
12948
+ },
12915
12949
  lanes: {
12916
12950
  bandFillEven: "rgba(17, 17, 17, 0.7)",
12917
12951
  bandFillOdd: "rgba(0, 0, 0, 0)",
@@ -12998,6 +13032,21 @@ var SystemCanvas = (() => {
12998
13032
  fontFamily: "'Inter', 'Helvetica Neue', sans-serif",
12999
13033
  fontSize: 12
13000
13034
  },
13035
+ contextMenu: {
13036
+ background: "rgba(255, 255, 255, 0.98)",
13037
+ borderColor: "rgba(203, 213, 225, 0.9)",
13038
+ borderRadius: 8,
13039
+ itemColor: "#1e293b",
13040
+ itemHoverBackground: "rgba(148, 163, 184, 0.18)",
13041
+ destructiveItemColor: "#dc2626",
13042
+ fontFamily: "'Inter', 'Helvetica Neue', sans-serif",
13043
+ fontSize: 12,
13044
+ paddingY: 4,
13045
+ paddingX: 4,
13046
+ itemPaddingY: 5,
13047
+ itemPaddingX: 10,
13048
+ shadow: "0 8px 24px rgba(15,23,42,0.12)"
13049
+ },
13001
13050
  lanes: {
13002
13051
  bandFillEven: "rgba(148, 163, 184, 0.10)",
13003
13052
  // slate-400 soft
@@ -13077,6 +13126,21 @@ var SystemCanvas = (() => {
13077
13126
  fontFamily: "'JetBrains Mono', monospace",
13078
13127
  fontSize: 11
13079
13128
  },
13129
+ contextMenu: {
13130
+ background: "rgba(10, 22, 40, 0.97)",
13131
+ borderColor: "rgba(74, 144, 217, 0.45)",
13132
+ borderRadius: 6,
13133
+ itemColor: "#d4e5f7",
13134
+ itemHoverBackground: "rgba(74, 144, 217, 0.18)",
13135
+ destructiveItemColor: "#fb7185",
13136
+ fontFamily: "'JetBrains Mono', monospace",
13137
+ fontSize: 11,
13138
+ paddingY: 3,
13139
+ paddingX: 3,
13140
+ itemPaddingY: 5,
13141
+ itemPaddingX: 10,
13142
+ shadow: "0 8px 24px rgba(10,22,40,0.5)"
13143
+ },
13080
13144
  lanes: {
13081
13145
  bandFillEven: "rgba(19, 34, 68, 0.45)",
13082
13146
  bandFillOdd: "rgba(19, 34, 68, 0.15)",
@@ -13154,6 +13218,21 @@ var SystemCanvas = (() => {
13154
13218
  fontFamily: "'IBM Plex Mono', monospace",
13155
13219
  fontSize: 12
13156
13220
  },
13221
+ contextMenu: {
13222
+ background: "rgba(26, 23, 20, 0.97)",
13223
+ borderColor: "rgba(107, 94, 74, 0.6)",
13224
+ borderRadius: 8,
13225
+ itemColor: "#e8ddd0",
13226
+ itemHoverBackground: "rgba(107, 94, 74, 0.3)",
13227
+ destructiveItemColor: "#fb7185",
13228
+ fontFamily: "'IBM Plex Mono', monospace",
13229
+ fontSize: 12,
13230
+ paddingY: 4,
13231
+ paddingX: 4,
13232
+ itemPaddingY: 5,
13233
+ itemPaddingX: 10,
13234
+ shadow: "0 8px 24px rgba(0,0,0,0.4)"
13235
+ },
13157
13236
  lanes: {
13158
13237
  bandFillEven: "rgba(42, 37, 32, 0.55)",
13159
13238
  bandFillOdd: "rgba(42, 37, 32, 0.2)",
@@ -13232,6 +13311,21 @@ var SystemCanvas = (() => {
13232
13311
  fontFamily: "'Inter', 'Helvetica Neue', sans-serif",
13233
13312
  fontSize: 12
13234
13313
  },
13314
+ contextMenu: {
13315
+ background: "rgba(15, 17, 21, 0.96)",
13316
+ borderColor: "rgba(63, 69, 83, 0.8)",
13317
+ borderRadius: 8,
13318
+ itemColor: "#e8ebf2",
13319
+ itemHoverBackground: "rgba(90, 100, 120, 0.3)",
13320
+ destructiveItemColor: "#fb7185",
13321
+ fontFamily: "'Inter', 'Helvetica Neue', sans-serif",
13322
+ fontSize: 12,
13323
+ paddingY: 4,
13324
+ paddingX: 4,
13325
+ itemPaddingY: 5,
13326
+ itemPaddingX: 10,
13327
+ shadow: "0 8px 24px rgba(0,0,0,0.4)"
13328
+ },
13235
13329
  // Slightly stronger lane fills than the other themes — roadmap columns
13236
13330
  // should read as the primary structure of the canvas.
13237
13331
  lanes: {
@@ -13458,10 +13552,16 @@ var SystemCanvas = (() => {
13458
13552
  group: { ...base.group, ...partial.group },
13459
13553
  breadcrumbs: { ...base.breadcrumbs, ...partial.breadcrumbs },
13460
13554
  lanes: { ...base.lanes, ...partial.lanes },
13555
+ // contextMenu is optional on CanvasTheme but darkTheme always defines it,
13556
+ // so the resolved theme is guaranteed to have a populated block as long
13557
+ // as `base` is a complete theme (which is the contract).
13558
+ contextMenu: partial.contextMenu ? { ...base.contextMenu, ...partial.contextMenu } : base.contextMenu,
13461
13559
  presetColors: { ...base.presetColors, ...partial.presetColors },
13462
13560
  categories: { ...base.categories, ...partial.categories },
13463
13561
  icons: partial.icons ? { ...base.icons ?? {}, ...partial.icons } : base.icons,
13464
- nodeActions: partial.nodeActions ?? base.nodeActions
13562
+ nodeActions: partial.nodeActions ?? base.nodeActions,
13563
+ showToolbarDelete: partial.showToolbarDelete ?? base.showToolbarDelete,
13564
+ toolbarAlign: partial.toolbarAlign ?? base.toolbarAlign
13465
13565
  };
13466
13566
  }
13467
13567
  function resolveColor(color2, theme) {
@@ -13809,6 +13909,29 @@ var SystemCanvas = (() => {
13809
13909
  return getNodeActions(theme);
13810
13910
  }
13811
13911
 
13912
+ // ../core/dist/contextMenu.js
13913
+ function matchesContextMenuItem(item, node, ctx) {
13914
+ const m = item.match;
13915
+ if (!m)
13916
+ return true;
13917
+ if (m.categories) {
13918
+ if (!node.category)
13919
+ return false;
13920
+ if (!m.categories.includes(node.category))
13921
+ return false;
13922
+ }
13923
+ if (m.types) {
13924
+ if (!m.types.includes(node.type))
13925
+ return false;
13926
+ }
13927
+ if (m.when && !m.when(node, ctx))
13928
+ return false;
13929
+ return true;
13930
+ }
13931
+ function filterContextMenuItems(items, node, ctx) {
13932
+ return items.filter((item) => matchesContextMenuItem(item, node, ctx));
13933
+ }
13934
+
13812
13935
  // ../core/dist/rollup.js
13813
13936
  function rollupNodes(canvas, predicate) {
13814
13937
  const nodes = canvas?.nodes ?? [];
@@ -13839,6 +13962,23 @@ var SystemCanvas = (() => {
13839
13962
  var TAB_BADGE_EM = 1.4;
13840
13963
  var TAB_BADGE_LIFT = 10;
13841
13964
  var TAB_BADGE_OVERHANG = 8;
13965
+ function cornerSlotClearance(spec, fontSize) {
13966
+ if (!spec)
13967
+ return 0;
13968
+ if (spec.kind !== "dot" && spec.kind !== "icon")
13969
+ return 0;
13970
+ const corner = CORNER_EM * fontSize;
13971
+ let renderedWidth = corner;
13972
+ if (spec.kind === "icon") {
13973
+ const explicitSize = spec.size;
13974
+ if (typeof explicitSize === "number" && explicitSize > corner) {
13975
+ renderedWidth = explicitSize;
13976
+ }
13977
+ }
13978
+ const gap = Math.max(8, Math.round(fontSize * 0.75));
13979
+ const overhang = Math.max(0, (renderedWidth - corner) / 2);
13980
+ return overhang + corner + gap;
13981
+ }
13842
13982
  function computeCategorySlotRegions(node, theme, slots) {
13843
13983
  const { x, y, width, height } = node;
13844
13984
  const fs = theme.node.fontSize;
@@ -13906,12 +14046,16 @@ var SystemCanvas = (() => {
13906
14046
  width: tab,
13907
14047
  height: tab
13908
14048
  },
13909
- topLeft: {
13910
- x: x + CORNER_INSET,
13911
- y: y + CORNER_INSET,
13912
- width: corner,
13913
- height: corner
13914
- },
14049
+ topLeft: (() => {
14050
+ const usesInlineRow = slots?.topLeft !== void 0 && (slots.topLeft.kind === "dot" || slots.topLeft.kind === "icon") && slots.header === void 0;
14051
+ const slotY = usesInlineRow ? y + (height - corner) / 2 : y + CORNER_INSET;
14052
+ return {
14053
+ x: x + CORNER_INSET,
14054
+ y: slotY,
14055
+ width: corner,
14056
+ height: corner
14057
+ };
14058
+ })(),
13915
14059
  topRight: {
13916
14060
  x: x + width - CORNER_INSET - corner,
13917
14061
  y: y + CORNER_INSET,
@@ -13930,18 +14074,49 @@ var SystemCanvas = (() => {
13930
14074
  width: corner,
13931
14075
  height: corner
13932
14076
  },
13933
- header: {
13934
- x: x + HEADER_INSET_X,
13935
- y: y + HEADER_INSET_Y,
13936
- width: Math.max(0, width - HEADER_INSET_X * 2),
13937
- height: header
13938
- },
13939
- footer: {
13940
- x: x + HEADER_INSET_X,
13941
- y: y + height - FOOTER_INSET_Y - footer,
13942
- width: Math.max(0, width - HEADER_INSET_X * 2),
13943
- height: footer
13944
- },
14077
+ header: (() => {
14078
+ let hx = x + HEADER_INSET_X;
14079
+ let hw = Math.max(0, width - HEADER_INSET_X * 2);
14080
+ const leftClear = cornerSlotClearance(slots?.topLeft, fs);
14081
+ if (leftClear > 0) {
14082
+ const leftEdge = x + CORNER_INSET + leftClear;
14083
+ const shift = Math.max(0, leftEdge - hx);
14084
+ hx += shift;
14085
+ hw = Math.max(0, hw - shift);
14086
+ }
14087
+ const rightClear = cornerSlotClearance(slots?.topRight, fs);
14088
+ if (rightClear > 0) {
14089
+ const rightEdge = x + width - CORNER_INSET - rightClear;
14090
+ const currentRight = hx + hw;
14091
+ const shrink = Math.max(0, currentRight - rightEdge);
14092
+ hw = Math.max(0, hw - shrink);
14093
+ }
14094
+ return { x: hx, y: y + HEADER_INSET_Y, width: hw, height: header };
14095
+ })(),
14096
+ footer: (() => {
14097
+ let fx = x + HEADER_INSET_X;
14098
+ let fw = Math.max(0, width - HEADER_INSET_X * 2);
14099
+ const leftClear = cornerSlotClearance(slots?.bottomLeft, fs);
14100
+ if (leftClear > 0) {
14101
+ const leftEdge = x + CORNER_INSET + leftClear;
14102
+ const shift = Math.max(0, leftEdge - fx);
14103
+ fx += shift;
14104
+ fw = Math.max(0, fw - shift);
14105
+ }
14106
+ const rightClear = cornerSlotClearance(slots?.bottomRight, fs);
14107
+ if (rightClear > 0) {
14108
+ const rightEdge = x + width - CORNER_INSET - rightClear;
14109
+ const currentRight = fx + fw;
14110
+ const shrink = Math.max(0, currentRight - rightEdge);
14111
+ fw = Math.max(0, fw - shrink);
14112
+ }
14113
+ return {
14114
+ x: fx,
14115
+ y: y + height - FOOTER_INSET_Y - footer,
14116
+ width: fw,
14117
+ height: footer
14118
+ };
14119
+ })(),
13945
14120
  body: {
13946
14121
  x: bodyX,
13947
14122
  y: bodyY,
@@ -14064,13 +14239,26 @@ var SystemCanvas = (() => {
14064
14239
  left = regions.leftEdge.width + 4;
14065
14240
  if (slots.rightEdge)
14066
14241
  right = regions.rightEdge.width + 4;
14067
- if (slots.topLeft && slots.topLeft.kind === "dot") {
14068
- left = Math.max(left, regions.topLeft.x + regions.topLeft.width - node.x + 6);
14242
+ if (slots.topLeft && (slots.topLeft.kind === "dot" || slots.topLeft.kind === "icon")) {
14243
+ const region = regions.topLeft;
14244
+ let renderedWidth = region.width;
14245
+ if (slots.topLeft.kind === "icon") {
14246
+ const explicitSize = slots.topLeft.size;
14247
+ if (typeof explicitSize === "number" && explicitSize > region.width) {
14248
+ renderedWidth = explicitSize;
14249
+ }
14250
+ }
14251
+ const iconLeftFromNode = region.x - node.x + (region.width - renderedWidth) / 2;
14252
+ const gap = Math.max(8, Math.round(theme.node.fontSize * 0.75));
14253
+ left = Math.max(left, iconLeftFromNode + renderedWidth + gap);
14069
14254
  }
14070
- const isDashboard = slots.header !== void 0 || slots.topRight !== void 0 || slots.bodyTop !== void 0 || slots.footer !== void 0 || slots.topLeft !== void 0 && slots.topLeft.kind === "dot";
14255
+ const hasTopRowSignal = slots.header !== void 0 || slots.topRight !== void 0 || slots.bodyTop !== void 0 || slots.footer !== void 0;
14256
+ const hasInlineLeftMarker = slots.topLeft !== void 0 && (slots.topLeft.kind === "dot" || slots.topLeft.kind === "icon");
14257
+ const isDashboard = hasTopRowSignal || hasInlineLeftMarker;
14071
14258
  if (isDashboard) {
14072
- if (!slots.header)
14259
+ if (hasTopRowSignal && !slots.header) {
14073
14260
  top = Math.max(top, HEADER_INSET_Y);
14261
+ }
14074
14262
  left = Math.max(left, HEADER_INSET_X);
14075
14263
  right = Math.max(right, HEADER_INSET_X);
14076
14264
  }
@@ -14168,6 +14356,29 @@ var SystemCanvas = (() => {
14168
14356
  }
14169
14357
  return out;
14170
14358
  }
14359
+ function truncateToWidth(text, maxWidth, fontSize) {
14360
+ if (!text)
14361
+ return text;
14362
+ if (maxWidth <= 0)
14363
+ return text;
14364
+ if (measureTextWidth(text, fontSize) <= maxWidth)
14365
+ return text;
14366
+ const ellipsis = "\u2026";
14367
+ let lo = 0;
14368
+ let hi = text.length;
14369
+ let best = 0;
14370
+ while (lo <= hi) {
14371
+ const mid = lo + hi >> 1;
14372
+ const candidate = text.slice(0, mid) + ellipsis;
14373
+ if (measureTextWidth(candidate, fontSize) <= maxWidth) {
14374
+ best = mid;
14375
+ lo = mid + 1;
14376
+ } else {
14377
+ hi = mid - 1;
14378
+ }
14379
+ }
14380
+ return text.slice(0, best) + ellipsis;
14381
+ }
14171
14382
  function ellipsize(line, maxWidth, fontSize) {
14172
14383
  const ellipsis = "\u2026";
14173
14384
  if (measureTextWidth(line + ellipsis, fontSize) <= maxWidth) {
@@ -14180,7 +14391,7 @@ var SystemCanvas = (() => {
14180
14391
  if (measureTextWidth(candidate, fontSize) <= maxWidth)
14181
14392
  return candidate;
14182
14393
  }
14183
- return line + ellipsis;
14394
+ return truncateToWidth(line, maxWidth, fontSize);
14184
14395
  }
14185
14396
 
14186
14397
  // ../core/dist/canvas.js
@@ -14484,7 +14695,12 @@ var SystemCanvas = (() => {
14484
14695
  onContextMenu({
14485
14696
  type,
14486
14697
  target,
14487
- position: canvasPos
14698
+ position: canvasPos,
14699
+ // Raw viewport coords from the original MouseEvent. Consumers
14700
+ // rendering a `position: fixed` floating menu need these — the
14701
+ // canvas-space `position` would be wrong because it walks
14702
+ // with the user's pan/zoom.
14703
+ screenPosition: { x: event.clientX, y: event.clientY }
14488
14704
  });
14489
14705
  };
14490
14706
  }, [onContextMenu, viewport]);
@@ -14520,11 +14736,43 @@ var SystemCanvas = (() => {
14520
14736
  var import_react3 = __toESM(require_react(), 1);
14521
14737
  var DRAG_THRESHOLD = 3;
14522
14738
  function useNodeDrag(options) {
14523
- const { viewport, nodesRef, onCommit } = options;
14739
+ const { viewport, nodesRef, onCommit, svgRef, canDropNodeOn, onNodeDrop } = options;
14524
14740
  const [dragOverrides, setDragOverrides] = (0, import_react3.useState)(() => /* @__PURE__ */ new Map());
14525
14741
  const [isDragging, setIsDragging] = (0, import_react3.useState)(false);
14742
+ const [dropTargetId, setDropTargetId] = (0, import_react3.useState)(null);
14526
14743
  const stateRef = (0, import_react3.useRef)(null);
14527
14744
  const movedRef = (0, import_react3.useRef)(false);
14745
+ const canDropNodeOnRef = (0, import_react3.useRef)(canDropNodeOn);
14746
+ canDropNodeOnRef.current = canDropNodeOn;
14747
+ const onNodeDropRef = (0, import_react3.useRef)(onNodeDrop);
14748
+ onNodeDropRef.current = onNodeDrop;
14749
+ const svgRefRef = (0, import_react3.useRef)(svgRef);
14750
+ svgRefRef.current = svgRef;
14751
+ const dropTargetIdRef = (0, import_react3.useRef)(null);
14752
+ const computeDropTarget = (0, import_react3.useCallback)((clientX, clientY) => {
14753
+ const cb = canDropNodeOnRef.current;
14754
+ const st = stateRef.current;
14755
+ const nodes = nodesRef.current;
14756
+ const svg = svgRefRef.current?.current;
14757
+ if (!cb || !st || !nodes || !svg)
14758
+ return null;
14759
+ const rect = svg.getBoundingClientRect();
14760
+ const vp = viewport.current ?? { x: 0, y: 0, zoom: 1 };
14761
+ const { x, y } = screenToCanvas(clientX - rect.left, clientY - rect.top, vp);
14762
+ for (let i = nodes.length - 1; i >= 0; i--) {
14763
+ const n = nodes[i];
14764
+ if (st.moving.has(n.id))
14765
+ continue;
14766
+ if (x < n.x || x > n.x + n.width)
14767
+ continue;
14768
+ if (y < n.y || y > n.y + n.height)
14769
+ continue;
14770
+ if (cb([st.source], n))
14771
+ return n.id;
14772
+ return null;
14773
+ }
14774
+ return null;
14775
+ }, [nodesRef, viewport]);
14528
14776
  const onPointerMove = (0, import_react3.useCallback)((event) => {
14529
14777
  const st = stateRef.current;
14530
14778
  if (!st || event.pointerId !== st.pointerId)
@@ -14546,7 +14794,12 @@ var SystemCanvas = (() => {
14546
14794
  next.set(id2, { x: start2.startX + dx, y: start2.startY + dy });
14547
14795
  }
14548
14796
  setDragOverrides(next);
14549
- }, [viewport]);
14797
+ const nextTarget = computeDropTarget(event.clientX, event.clientY);
14798
+ if (nextTarget !== dropTargetIdRef.current) {
14799
+ dropTargetIdRef.current = nextTarget;
14800
+ setDropTargetId(nextTarget);
14801
+ }
14802
+ }, [viewport, computeDropTarget]);
14550
14803
  const finishDrag = (0, import_react3.useCallback)((commit) => {
14551
14804
  const st = stateRef.current;
14552
14805
  if (!st)
@@ -14559,17 +14812,38 @@ var SystemCanvas = (() => {
14559
14812
  st.captureTarget.releasePointerCapture?.(st.pointerId);
14560
14813
  } catch {
14561
14814
  }
14815
+ const dropTarget = dropTargetIdRef.current;
14816
+ if (commit && movedRef.current && dropTarget) {
14817
+ const nodes = nodesRef.current;
14818
+ const target = nodes?.find((n) => n.id === dropTarget) ?? null;
14819
+ if (target && onNodeDropRef.current) {
14820
+ onNodeDropRef.current([st.source], target);
14821
+ stateRef.current = null;
14822
+ movedRef.current = false;
14823
+ dropTargetIdRef.current = null;
14824
+ setIsDragging(false);
14825
+ setDragOverrides(/* @__PURE__ */ new Map());
14826
+ setDropTargetId(null);
14827
+ return;
14828
+ }
14829
+ }
14562
14830
  if (commit && movedRef.current) {
14563
14831
  const overrides = Array.from(dragOverridesRef.current.entries());
14564
- for (const [id2, pos] of overrides) {
14565
- onCommit(id2, { x: Math.round(pos.x), y: Math.round(pos.y) });
14832
+ if (overrides.length > 0) {
14833
+ const updates = overrides.map(([id2, pos]) => ({
14834
+ id: id2,
14835
+ patch: { x: Math.round(pos.x), y: Math.round(pos.y) }
14836
+ }));
14837
+ onCommit(updates);
14566
14838
  }
14567
14839
  }
14568
14840
  stateRef.current = null;
14569
14841
  movedRef.current = false;
14842
+ dropTargetIdRef.current = null;
14570
14843
  setIsDragging(false);
14571
14844
  setDragOverrides(/* @__PURE__ */ new Map());
14572
- }, [onPointerMove, onCommit]);
14845
+ setDropTargetId(null);
14846
+ }, [onPointerMove, onCommit, nodesRef]);
14573
14847
  const dragOverridesRef = (0, import_react3.useRef)(dragOverrides);
14574
14848
  dragOverridesRef.current = dragOverrides;
14575
14849
  const onPointerUpRef = (0, import_react3.useRef)(null);
@@ -14609,9 +14883,11 @@ var SystemCanvas = (() => {
14609
14883
  startClientY: event.clientY,
14610
14884
  captureTarget: event.currentTarget,
14611
14885
  pointerId: event.pointerId,
14612
- moving
14886
+ moving,
14887
+ source: node
14613
14888
  };
14614
14889
  movedRef.current = false;
14890
+ dropTargetIdRef.current = null;
14615
14891
  try {
14616
14892
  ;
14617
14893
  event.currentTarget.setPointerCapture?.(event.pointerId);
@@ -14621,7 +14897,7 @@ var SystemCanvas = (() => {
14621
14897
  window.addEventListener("pointerup", onPointerUp);
14622
14898
  window.addEventListener("pointercancel", onPointerCancel);
14623
14899
  }, [nodesRef, onPointerMove, onPointerUp, onPointerCancel]);
14624
- return { dragOverrides, onPointerDown, isDragging };
14900
+ return { dragOverrides, dropTargetId, onPointerDown, isDragging };
14625
14901
  }
14626
14902
 
14627
14903
  // ../react/dist/hooks/useNodeResize.js
@@ -14896,11 +15172,15 @@ var SystemCanvas = (() => {
14896
15172
  return { x, y, zoom };
14897
15173
  }
14898
15174
  function useZoomNavigation(options) {
14899
- const { enabled, config, nodes, currentCanvas, parentFrame, canvases, onResolveCanvas, onSeedCanvas, theme, getViewportSize, onEnter, onExit } = options;
15175
+ const { enabled, config, nodes, currentCanvas, parentFrame, canvases, onResolveCanvas, onSeedCanvas, theme, getViewportSize, getCursorScreenPos, onEnter, onExit } = options;
14900
15176
  const committingRef = (0, import_react6.useRef)(false);
14901
15177
  (0, import_react6.useEffect)(() => {
14902
15178
  committingRef.current = false;
14903
15179
  }, [currentCanvas]);
15180
+ const exitArmedRef = (0, import_react6.useRef)(false);
15181
+ (0, import_react6.useEffect)(() => {
15182
+ exitArmedRef.current = false;
15183
+ }, [currentCanvas, parentFrame]);
14904
15184
  const prefetchRef = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
14905
15185
  const prefetch = (0, import_react6.useCallback)((ref) => {
14906
15186
  if (!onResolveCanvas)
@@ -14928,36 +15208,87 @@ var SystemCanvas = (() => {
14928
15208
  const size = getViewportSize();
14929
15209
  if (!size)
14930
15210
  return;
14931
- for (const n of nodes) {
14932
- if (!n.ref)
14933
- continue;
14934
- const screen = canvasRectToScreenRect({ x: n.x, y: n.y, width: n.width, height: n.height }, vp);
14935
- const centerX = screen.x + screen.width / 2;
14936
- const centerY = screen.y + screen.height / 2;
14937
- const centerOnScreen = centerX >= 0 && centerX <= size.width && centerY >= 0 && centerY <= size.height;
14938
- if (!centerOnScreen)
14939
- continue;
15211
+ const cursor = getCursorScreenPos();
15212
+ const viewportCenterX = size.width / 2;
15213
+ const viewportCenterY = size.height / 2;
15214
+ let cursorNode = null;
15215
+ if (cursor) {
15216
+ for (const n of nodes) {
15217
+ if (!n.ref)
15218
+ continue;
15219
+ const screen = canvasRectToScreenRect({ x: n.x, y: n.y, width: n.width, height: n.height }, vp);
15220
+ if (cursor.x < screen.x || cursor.x > screen.x + screen.width || cursor.y < screen.y || cursor.y > screen.y + screen.height) {
15221
+ continue;
15222
+ }
15223
+ const area = screen.width * screen.height;
15224
+ if (!cursorNode || area < cursorNode.area) {
15225
+ cursorNode = { node: n, screen, area };
15226
+ }
15227
+ }
15228
+ }
15229
+ let bestCandidate = null;
15230
+ if (cursorNode) {
15231
+ const screen = cursorNode.screen;
14940
15232
  const fillFraction = Math.max(screen.width / size.width, screen.height / size.height);
14941
- if (fillFraction >= config.prefetchThreshold && fillFraction < config.enterThreshold) {
14942
- prefetch(n.ref);
15233
+ if (fillFraction >= config.prefetchThreshold && cursorNode.node.ref) {
15234
+ prefetch(cursorNode.node.ref);
14943
15235
  }
14944
15236
  if (fillFraction >= config.enterThreshold) {
14945
- const childData = canvases?.[n.ref] ?? prefetchRef.current.get(n.ref)?.data;
14946
- if (!childData) {
15237
+ bestCandidate = { node: cursorNode.node, screen, distSq: 0 };
15238
+ }
15239
+ for (const n of nodes) {
15240
+ if (!n.ref)
15241
+ continue;
15242
+ if (n === cursorNode.node)
15243
+ continue;
15244
+ const s = canvasRectToScreenRect({ x: n.x, y: n.y, width: n.width, height: n.height }, vp);
15245
+ const ff = Math.max(s.width / size.width, s.height / size.height);
15246
+ if (ff >= config.prefetchThreshold)
14947
15247
  prefetch(n.ref);
15248
+ }
15249
+ } else {
15250
+ for (const n of nodes) {
15251
+ if (!n.ref)
15252
+ continue;
15253
+ const screen = canvasRectToScreenRect({ x: n.x, y: n.y, width: n.width, height: n.height }, vp);
15254
+ const centerX = screen.x + screen.width / 2;
15255
+ const centerY = screen.y + screen.height / 2;
15256
+ const centerOnScreen = centerX >= 0 && centerX <= size.width && centerY >= 0 && centerY <= size.height;
15257
+ if (!centerOnScreen)
14948
15258
  continue;
15259
+ const fillFraction = Math.max(screen.width / size.width, screen.height / size.height);
15260
+ if (fillFraction >= config.prefetchThreshold && fillFraction < config.enterThreshold) {
15261
+ prefetch(n.ref);
14949
15262
  }
15263
+ if (fillFraction >= config.enterThreshold) {
15264
+ const dx = centerX - viewportCenterX;
15265
+ const dy = centerY - viewportCenterY;
15266
+ const distSq = dx * dx + dy * dy;
15267
+ if (!bestCandidate || distSq < bestCandidate.distSq) {
15268
+ bestCandidate = { node: n, screen, distSq };
15269
+ }
15270
+ }
15271
+ }
15272
+ }
15273
+ if (bestCandidate) {
15274
+ const n = bestCandidate.node;
15275
+ const screen = bestCandidate.screen;
15276
+ const ref = n.ref;
15277
+ const childData = canvases?.[ref] ?? prefetchRef.current.get(ref)?.data;
15278
+ if (!childData) {
15279
+ prefetch(ref);
15280
+ } else {
14950
15281
  const resolved = resolveCanvas(childData, theme);
14951
- if (resolved.nodes.length === 0)
14952
- continue;
14953
- const bounds = computeBoundingBox(resolved.nodes);
14954
- const targetRect = expandRect(screen, config.landingScale);
14955
- const landingPad = Math.min(targetRect.width, targetRect.height) * config.landingPadding;
14956
- let targetTransform = fitBoundsIntoRect(bounds, targetRect, landingPad);
14957
- targetTransform = clampTransformToViewport(targetTransform, bounds, size, 16);
14958
- committingRef.current = true;
14959
- onEnter(n, targetTransform);
14960
- return;
15282
+ if (resolved.nodes.length > 0) {
15283
+ const bounds = computeBoundingBox(resolved.nodes);
15284
+ const targetRect = expandRect(screen, config.landingScale);
15285
+ const landingPad = Math.min(targetRect.width, targetRect.height) * config.landingPadding;
15286
+ let targetTransform = fitBoundsIntoRect(bounds, targetRect, landingPad);
15287
+ targetTransform = clampTransformToViewport(targetTransform, bounds, size, 16);
15288
+ committingRef.current = true;
15289
+ onEnter(n, targetTransform);
15290
+ return;
15291
+ }
14961
15292
  }
14962
15293
  }
14963
15294
  if (parentFrame && nodes.length > 0) {
@@ -14971,6 +15302,12 @@ var SystemCanvas = (() => {
14971
15302
  height: bounds.height
14972
15303
  }, vp);
14973
15304
  const fillFraction = Math.max(screen.width / size.width, screen.height / size.height);
15305
+ if (!exitArmedRef.current) {
15306
+ if (fillFraction > config.exitThreshold) {
15307
+ exitArmedRef.current = true;
15308
+ }
15309
+ return;
15310
+ }
14974
15311
  if (fillFraction <= config.exitThreshold) {
14975
15312
  const targetTransform = fitBoundsIntoRect({
14976
15313
  minX: parentFrame.parentNodeRect.x,
@@ -14997,6 +15334,7 @@ var SystemCanvas = (() => {
14997
15334
  theme,
14998
15335
  config,
14999
15336
  getViewportSize,
15337
+ getCursorScreenPos,
15000
15338
  prefetch,
15001
15339
  onEnter,
15002
15340
  onExit
@@ -17872,17 +18210,67 @@ var SystemCanvas = (() => {
17872
18210
 
17873
18211
  // ../react/dist/components/NodeIcon.js
17874
18212
  var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
17875
- function NodeIcon({ icon, x, y, size = 14, color: color2, opacity = 0.7, customIcons }) {
18213
+ function NodeIcon({ icon, x, y, size = 14, color: color2, opacity = 0.7, customIcons, mode = "stroke", viewBox = 16 }) {
17876
18214
  const pathData = customIcons?.[icon] ?? iconPaths[icon];
17877
18215
  if (!pathData)
17878
18216
  return null;
17879
- return (0, import_jsx_runtime.jsx)("g", { transform: `translate(${x}, ${y})`, pointerEvents: "none", opacity, children: pathData.map((d, i) => (0, import_jsx_runtime.jsx)("path", { d: scalePathData(d, size), fill: "none", stroke: color2, strokeWidth: 1.2, strokeLinecap: "round", strokeLinejoin: "round" }, i)) });
18217
+ const defaultStrokeWidth = viewBox * 0.075;
18218
+ return (0, import_jsx_runtime.jsx)("g", { transform: `translate(${x}, ${y})`, pointerEvents: "none", opacity, children: pathData.map((entry, i) => {
18219
+ const spec = typeof entry === "string" ? { d: entry } : entry;
18220
+ const pathMode = spec.mode ?? mode;
18221
+ const rawStrokeWidth = spec.strokeWidth ?? defaultStrokeWidth;
18222
+ const renderedStrokeWidth = rawStrokeWidth * size / viewBox;
18223
+ return (0, import_jsx_runtime.jsx)("path", {
18224
+ d: scalePathData(spec.d, size, viewBox),
18225
+ fill: pathMode === "fill" ? color2 : "none",
18226
+ stroke: pathMode === "stroke" ? color2 : "none",
18227
+ strokeWidth: pathMode === "stroke" ? renderedStrokeWidth : 0,
18228
+ strokeLinecap: pathMode === "stroke" ? "round" : void 0,
18229
+ strokeLinejoin: pathMode === "stroke" ? "round" : void 0,
18230
+ // Brand silhouettes are typically authored as a single path
18231
+ // whose holes are expressed with subpath winding. `evenodd`
18232
+ // is the safe choice; nonzero would fill some holes solid.
18233
+ fillRule: pathMode === "fill" ? "evenodd" : void 0
18234
+ }, i);
18235
+ }) });
17880
18236
  }
17881
- function scalePathData(d, size) {
17882
- const scale = size / 16;
17883
- return d.replace(/(-?\d+\.?\d*)/g, (match) => {
17884
- return String(parseFloat(match) * scale);
17885
- });
18237
+ function scalePathData(d, size, source) {
18238
+ const scale = size / source;
18239
+ const out = [];
18240
+ const numberAtStart = /^-?(?:\d+\.?\d*|\.\d+)/;
18241
+ let isArc = false;
18242
+ let arcIndex = 0;
18243
+ let i = 0;
18244
+ while (i < d.length) {
18245
+ const ch = d[i];
18246
+ if (ch === " " || ch === "," || ch === " " || ch === "\n" || ch === "\r") {
18247
+ i++;
18248
+ continue;
18249
+ }
18250
+ if (/[a-zA-Z]/.test(ch)) {
18251
+ out.push(ch);
18252
+ isArc = ch === "a" || ch === "A";
18253
+ arcIndex = 0;
18254
+ i++;
18255
+ continue;
18256
+ }
18257
+ if (isArc && (arcIndex === 3 || arcIndex === 4)) {
18258
+ out.push(ch === "0" ? "0" : "1");
18259
+ arcIndex = (arcIndex + 1) % 7;
18260
+ i++;
18261
+ continue;
18262
+ }
18263
+ const m = numberAtStart.exec(d.substring(i));
18264
+ if (!m) {
18265
+ i++;
18266
+ continue;
18267
+ }
18268
+ out.push(String(parseFloat(m[0]) * scale));
18269
+ if (isArc)
18270
+ arcIndex = (arcIndex + 1) % 7;
18271
+ i += m[0].length;
18272
+ }
18273
+ return out.join(" ");
17886
18274
  }
17887
18275
  var iconPaths = {
17888
18276
  // Database: cylinder shape
@@ -17975,11 +18363,12 @@ var SystemCanvas = (() => {
17975
18363
  // ../react/dist/components/RefIndicator.js
17976
18364
  var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
17977
18365
  var import_react8 = __toESM(require_react(), 1);
17978
- function RefIndicator({ node, theme, nodeX, nodeY, nodeWidth, nodeHeight, strokeColor, strokeWidth, corner = "bottom-right", size = 18, onNavigate }) {
18366
+ function RefIndicator({ node, theme, nodeX, nodeY, nodeWidth, nodeHeight, strokeColor, strokeWidth, corner = "bottom-right", size: sizeProp, onNavigate }) {
17979
18367
  const [hover, setHover] = (0, import_react8.useState)(false);
17980
18368
  const iconKind = theme.node.refIndicator.icon;
17981
18369
  if (iconKind === "none")
17982
18370
  return null;
18371
+ const size = sizeProp ?? theme.node.refIndicator.size ?? 18;
17983
18372
  const stopAll = (e) => e.stopPropagation();
17984
18373
  const right = nodeX + nodeWidth;
17985
18374
  const bottom = nodeY + nodeHeight;
@@ -18025,19 +18414,22 @@ var SystemCanvas = (() => {
18025
18414
  return (0, import_jsx_runtime2.jsxs)("g", { className: "system-canvas-ref-indicator", style: { cursor: "pointer" }, onClick: (e) => {
18026
18415
  e.stopPropagation();
18027
18416
  onNavigate(node, e);
18028
- }, onDoubleClick: stopAll, onPointerDown: stopAll, onMouseDown: stopAll, onPointerEnter: () => setHover(true), onPointerLeave: () => setHover(false), children: [hover && (0, import_jsx_runtime2.jsx)("path", { d: hoverPath, fill: hoverFill, pointerEvents: "none" }), (0, import_jsx_runtime2.jsx)("path", { d: carvePath, fill: "none", stroke: strokeColor, strokeWidth: sw, strokeLinecap: "butt", strokeLinejoin: "round", pointerEvents: "none" }), (0, import_jsx_runtime2.jsx)("rect", { x: squareX, y: squareY, width: size, height: size, fill: "transparent" }), (0, import_jsx_runtime2.jsx)(Glyph, { kind: iconKind, cx, cy, color: hover ? hoverGlyph : restGlyph })] });
18417
+ }, onDoubleClick: stopAll, onPointerDown: stopAll, onMouseDown: stopAll, onPointerEnter: () => setHover(true), onPointerLeave: () => setHover(false), children: [hover && (0, import_jsx_runtime2.jsx)("path", { d: hoverPath, fill: hoverFill, pointerEvents: "none" }), (0, import_jsx_runtime2.jsx)("path", { d: carvePath, fill: "none", stroke: strokeColor, strokeWidth: sw, strokeLinecap: "butt", strokeLinejoin: "round", pointerEvents: "none" }), (0, import_jsx_runtime2.jsx)("rect", { x: squareX, y: squareY, width: size, height: size, fill: "transparent" }), (0, import_jsx_runtime2.jsx)(Glyph, { kind: iconKind, cx, cy, color: hover ? hoverGlyph : restGlyph, size })] });
18029
18418
  }
18030
- function Glyph({ kind, cx, cy, color: color2 }) {
18031
- const s = 3;
18419
+ function Glyph({ kind, cx, cy, color: color2, size }) {
18420
+ const s = size / 6;
18421
+ const sw = Math.max(1, size / 9);
18032
18422
  switch (kind) {
18033
18423
  case "chevron":
18034
- return (0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s / 2} ${cy - s} L ${cx + s / 2} ${cy} L ${cx - s / 2} ${cy + s}`, fill: "none", stroke: color2, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", pointerEvents: "none" });
18424
+ return (0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s / 2} ${cy - s} L ${cx + s / 2} ${cy} L ${cx - s / 2} ${cy + s}`, fill: "none", stroke: color2, strokeWidth: sw, strokeLinecap: "round", strokeLinejoin: "round", pointerEvents: "none" });
18035
18425
  case "arrow": {
18036
18426
  const h = s * 0.9;
18037
- return (0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s} ${cy} L ${cx + s} ${cy} M ${cx + s - h} ${cy - h} L ${cx + s} ${cy} L ${cx + s - h} ${cy + h}`, fill: "none", stroke: color2, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", pointerEvents: "none" });
18427
+ return (0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s} ${cy} L ${cx + s} ${cy} M ${cx + s - h} ${cy - h} L ${cx + s} ${cy} L ${cx + s - h} ${cy + h}`, fill: "none", stroke: color2, strokeWidth: sw, strokeLinecap: "round", strokeLinejoin: "round", pointerEvents: "none" });
18428
+ }
18429
+ case "expand": {
18430
+ const sw2 = Math.max(1, size / 12);
18431
+ return (0, import_jsx_runtime2.jsxs)("g", { pointerEvents: "none", children: [(0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s} ${cy - s} L ${cx + s} ${cy - s} L ${cx + s} ${cy + s} L ${cx - s} ${cy + s} Z`, fill: "none", stroke: color2, strokeWidth: sw2, strokeLinejoin: "round" }), (0, import_jsx_runtime2.jsx)("line", { x1: cx, y1: cy - s + 1, x2: cx, y2: cy + s - 1, stroke: color2, strokeWidth: sw2, strokeLinecap: "round" }), (0, import_jsx_runtime2.jsx)("line", { x1: cx - s + 1, y1: cy, x2: cx + s - 1, y2: cy, stroke: color2, strokeWidth: sw2, strokeLinecap: "round" })] });
18038
18432
  }
18039
- case "expand":
18040
- return (0, import_jsx_runtime2.jsxs)("g", { pointerEvents: "none", children: [(0, import_jsx_runtime2.jsx)("path", { d: `M ${cx - s} ${cy - s} L ${cx + s} ${cy - s} L ${cx + s} ${cy + s} L ${cx - s} ${cy + s} Z`, fill: "none", stroke: color2, strokeWidth: 1.5, strokeLinejoin: "round" }), (0, import_jsx_runtime2.jsx)("line", { x1: cx, y1: cy - s + 1, x2: cx, y2: cy + s - 1, stroke: color2, strokeWidth: 1.5, strokeLinecap: "round" }), (0, import_jsx_runtime2.jsx)("line", { x1: cx - s + 1, y1: cy, x2: cx + s - 1, y2: cy, stroke: color2, strokeWidth: 1.5, strokeLinecap: "round" })] });
18041
18433
  }
18042
18434
  }
18043
18435
 
@@ -18164,7 +18556,7 @@ var SystemCanvas = (() => {
18164
18556
  // ../react/dist/primitives/NodeText.js
18165
18557
  var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
18166
18558
  var import_react9 = __toESM(require_react(), 1);
18167
- function NodeText({ region, value, theme, color: color2, fill, align = "start", fontWeight = 500, uppercase = false, useLabelFont = false, fontFamily, fontSize: fontSizeProp, wrap = false, maxLines, lineHeight: lineHeightProp }) {
18559
+ function NodeText({ region, value, theme, color: color2, fill, align = "start", fontWeight = 500, uppercase = false, useLabelFont = false, fontFamily, fontSize: fontSizeProp, wrap = false, maxLines, lineHeight: lineHeightProp, verticalAlign = "top" }) {
18168
18560
  const reactId = (0, import_react9.useId)();
18169
18561
  const safeId = reactId.replace(/:/g, "");
18170
18562
  if (!value)
@@ -18178,13 +18570,16 @@ var SystemCanvas = (() => {
18178
18570
  const fillAttr = gradId ? `url(#${gradId})` : color2 ?? theme.node.sublabelColor;
18179
18571
  const displayValue = uppercase ? value.toUpperCase() : value;
18180
18572
  if (!wrap) {
18573
+ const rendered = truncateToWidth(displayValue, region.width, fontSize);
18181
18574
  const y = region.y + region.height / 2 + fontSize * 0.36;
18182
- return (0, import_jsx_runtime7.jsxs)("g", { pointerEvents: "none", children: [fill && (0, import_jsx_runtime7.jsx)(GradientDef, { id: gradId, fill }), (0, import_jsx_runtime7.jsx)("text", { x, y, fill: fillAttr, fontSize, fontWeight, fontFamily: font, textAnchor: anchor, letterSpacing: uppercase ? 0.8 : 0.2, pointerEvents: "none", children: displayValue })] });
18575
+ return (0, import_jsx_runtime7.jsxs)("g", { pointerEvents: "none", children: [fill && (0, import_jsx_runtime7.jsx)(GradientDef, { id: gradId, fill }), (0, import_jsx_runtime7.jsx)("text", { x, y, fill: fillAttr, fontSize, fontWeight, fontFamily: font, textAnchor: anchor, letterSpacing: uppercase ? 0.8 : 0.2, pointerEvents: "none", children: rendered })] });
18183
18576
  }
18184
18577
  const lines = wrapTextWithBreaks(displayValue, region.width, fontSize, maxLines);
18185
18578
  if (lines.length === 0)
18186
18579
  return null;
18187
- const baseY = region.y + fontSize;
18580
+ const blockHeight = fontSize + (lines.length - 1) * lineHeight;
18581
+ const topY = region.y + fontSize;
18582
+ const baseY = verticalAlign === "center" ? region.y + (region.height - blockHeight) / 2 + fontSize : verticalAlign === "bottom" ? region.y + region.height - blockHeight + fontSize : topY;
18188
18583
  const clipId = `sc-text-clip-${safeId}`;
18189
18584
  return (0, import_jsx_runtime7.jsxs)("g", { pointerEvents: "none", children: [(0, import_jsx_runtime7.jsxs)("defs", { children: [fill && (0, import_jsx_runtime7.jsx)(GradientDef, { id: gradId, fill }), (0, import_jsx_runtime7.jsx)("clipPath", { id: clipId, children: (0, import_jsx_runtime7.jsx)("rect", { x: region.x, y: region.y, width: region.width, height: region.height }) })] }), (0, import_jsx_runtime7.jsx)("g", { clipPath: `url(#${clipId})`, pointerEvents: "none", children: (0, import_jsx_runtime7.jsx)("text", { x, y: baseY, fill: fillAttr, fontSize, fontWeight, fontFamily: font, textAnchor: anchor, letterSpacing: uppercase ? 0.8 : 0.2, pointerEvents: "none", children: lines.map((line, i) => (0, import_jsx_runtime7.jsx)("tspan", { x, dy: i === 0 ? 0 : lineHeight, children: line || " " }, i)) }) })] });
18190
18585
  }
@@ -18336,6 +18731,20 @@ var SystemCanvas = (() => {
18336
18731
  const color2 = resolveAccessorOr(spec.color, nodeColor, ctx);
18337
18732
  return (0, import_jsx_runtime9.jsx)(NodeDot, { region, color: color2 });
18338
18733
  }
18734
+ case "icon": {
18735
+ const name = resolveAccessor(spec.name, ctx);
18736
+ if (!name)
18737
+ return null;
18738
+ const color2 = resolveAccessorOr(spec.color, nodeColor, ctx);
18739
+ const opacity = resolveAccessorOr(spec.opacity, 1, ctx);
18740
+ const mode = resolveAccessorOr(spec.mode, "stroke", ctx);
18741
+ const viewBox = resolveAccessorOr(spec.viewBox, 16, ctx);
18742
+ const auto = Math.max(8, Math.min(region.width, region.height) - 2);
18743
+ const size = resolveAccessorOr(spec.size, auto, ctx);
18744
+ const x = region.x + (region.width - size) / 2;
18745
+ const y = region.y + (region.height - size) / 2;
18746
+ return (0, import_jsx_runtime9.jsx)(NodeIcon, { icon: name, x, y, size, color: color2, opacity, customIcons: theme.icons, mode, viewBox });
18747
+ }
18339
18748
  case "custom": {
18340
18749
  return spec.render(ctx);
18341
18750
  }
@@ -18363,20 +18772,21 @@ var SystemCanvas = (() => {
18363
18772
  const contentY = y + reservedTop;
18364
18773
  const contentWidth = Math.max(0, width - reservedLeft - reservedRight);
18365
18774
  const contentHeight = Math.max(0, height - reservedTop - reservedBottom);
18366
- const text = node.text ?? "";
18367
- const lines = text.split("\n").filter(Boolean);
18368
- const mainLabel = lines[0] ?? node.id;
18369
- const sublabel = lines[1];
18775
+ const LABEL_PAD_X = 10;
18776
+ const LABEL_PAD_Y = 6;
18777
+ const text = node.text ?? node.id;
18370
18778
  const hasBodySlot = slots?.body !== void 0;
18371
18779
  const hasHeader = reservedTop > 0;
18372
- const labelFont = theme.node.labelFont ?? theme.node.fontFamily;
18780
+ const hasInlineLeftMarker = slots?.topLeft !== void 0 && (slots.topLeft.kind === "dot" || slots.topLeft.kind === "icon") && !hasHeader;
18781
+ const isLeftAligned = hasHeader || hasInlineLeftMarker;
18373
18782
  const labelFontSize = theme.node.fontSize + (hasHeader ? 1 : 0);
18374
- const lineHeight = labelFontSize + 4;
18375
- const totalTextHeight = sublabel ? lineHeight + theme.node.sublabelFontSize + 4 : lineHeight;
18376
- const labelAnchor = hasHeader ? "start" : "middle";
18377
- const labelX = hasHeader ? contentX : contentX + contentWidth / 2;
18378
- const textStartY = hasHeader ? contentY + labelFontSize + 2 : contentY + (contentHeight - totalTextHeight) / 2 + labelFontSize;
18379
- return (0, import_jsx_runtime10.jsxs)("g", { className: "system-canvas-node system-canvas-node--text", style: { cursor: onPointerDown ? "move" : node.isNavigable ? "pointer" : "default" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime10.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: theme.background }), (0, import_jsx_runtime10.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: node.resolvedFill, stroke: node.resolvedStroke, strokeWidth: theme.node.strokeWidth }), !isEditing && !hasBodySlot && (0, import_jsx_runtime10.jsx)("text", { x: labelX, y: textStartY, fill: theme.node.labelColor, fontSize: labelFontSize, fontWeight: 600, fontFamily: labelFont, textAnchor: labelAnchor, pointerEvents: "none", children: mainLabel }), !isEditing && !hasBodySlot && sublabel && (0, import_jsx_runtime10.jsx)("text", { x: labelX, y: textStartY + lineHeight, fill: theme.node.sublabelColor, fontSize: theme.node.sublabelFontSize, fontFamily: theme.node.fontFamily, textAnchor: labelAnchor, pointerEvents: "none", children: sublabel }), node.resolvedIcon && (0, import_jsx_runtime10.jsx)(NodeIcon, { icon: node.resolvedIcon, x: x + 8 + reservedLeft, y: contentY + contentHeight / 2 - 7, size: 14, color: node.resolvedStroke, opacity: 0.7, customIcons: theme.icons }), slots && (0, import_jsx_runtime10.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime10.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor: node.resolvedStroke, strokeWidth: theme.node.strokeWidth, corner: toKebabCorner(refCorner), onNavigate })] });
18783
+ const labelAlign = isLeftAligned ? "start" : "center";
18784
+ return (0, import_jsx_runtime10.jsxs)("g", { className: "system-canvas-node system-canvas-node--text", style: { cursor: onPointerDown ? "move" : node.isNavigable ? "pointer" : "default" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime10.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: theme.background }), (0, import_jsx_runtime10.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: node.resolvedFill, stroke: node.resolvedStroke, strokeWidth: theme.node.strokeWidth }), !isEditing && !hasBodySlot && (0, import_jsx_runtime10.jsx)(NodeText, { region: {
18785
+ x: contentX + LABEL_PAD_X,
18786
+ y: contentY + LABEL_PAD_Y,
18787
+ width: Math.max(0, contentWidth - LABEL_PAD_X * 2),
18788
+ height: Math.max(0, contentHeight - LABEL_PAD_Y * 2)
18789
+ }, value: text, theme, color: theme.node.labelColor, align: labelAlign, fontWeight: 600, fontSize: labelFontSize, useLabelFont: true, wrap: true, verticalAlign: hasHeader ? "top" : "center" }), node.resolvedIcon && (0, import_jsx_runtime10.jsx)(NodeIcon, { icon: node.resolvedIcon, x: x + 8 + reservedLeft, y: contentY + contentHeight / 2 - 7, size: 14, color: node.resolvedStroke, opacity: 0.7, customIcons: theme.icons }), slots && (0, import_jsx_runtime10.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime10.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor: node.resolvedStroke, strokeWidth: theme.node.strokeWidth, corner: toKebabCorner(refCorner), onNavigate })] });
18380
18790
  }
18381
18791
 
18382
18792
  // ../react/dist/components/FileNode.js
@@ -18390,6 +18800,7 @@ var SystemCanvas = (() => {
18390
18800
  const fold = 10;
18391
18801
  const textPadding = 10 + reservedLeft;
18392
18802
  const maxTextWidth = width - textPadding - reservedRight - fold;
18803
+ const LABEL_PAD_Y = 6;
18393
18804
  const contentY = y + reservedTop;
18394
18805
  const contentHeight = Math.max(0, height - reservedTop - reservedBottom);
18395
18806
  const shapePath = [
@@ -18408,7 +18819,12 @@ var SystemCanvas = (() => {
18408
18819
  const strokeColor = node.resolvedStroke;
18409
18820
  const thinStroke = 0.75;
18410
18821
  const clipId = `file-clip-${node.id}`;
18411
- return (0, import_jsx_runtime11.jsxs)("g", { className: "system-canvas-node system-canvas-node--file", style: { cursor: onPointerDown ? "move" : node.isNavigable ? "pointer" : "default" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime11.jsx)("defs", { children: (0, import_jsx_runtime11.jsx)("clipPath", { id: clipId, children: (0, import_jsx_runtime11.jsx)("rect", { x: x + textPadding, y: contentY, width: maxTextWidth, height: contentHeight }) }) }), (0, import_jsx_runtime11.jsx)("path", { d: shapePath, fill: theme.background }), (0, import_jsx_runtime11.jsx)("path", { d: shapePath, fill: node.resolvedFill, stroke: strokeColor, strokeWidth: thinStroke }), (0, import_jsx_runtime11.jsx)("path", { d: foldPath, fill: "none", stroke: strokeColor, strokeWidth: thinStroke, opacity: 0.5 }), !isEditing && !slots?.body && (0, import_jsx_runtime11.jsxs)("g", { clipPath: `url(#${clipId})`, children: [dirPath && (0, import_jsx_runtime11.jsxs)("text", { x: x + textPadding, y: contentY + 14, fill: theme.node.sublabelColor, fontSize: theme.node.sublabelFontSize - 1, fontFamily: theme.node.fontFamily, pointerEvents: "none", opacity: 0.6, children: [dirPath, "/"] }), (0, import_jsx_runtime11.jsx)("text", { x: x + textPadding, y: contentY + (dirPath ? 28 : contentHeight / 2 + (subpath ? -2 : 4)), fill: theme.node.labelColor, fontSize: theme.node.fontSize - 1, fontWeight: 500, fontFamily: theme.node.fontFamily, pointerEvents: "none", children: fileName }), subpath && (0, import_jsx_runtime11.jsx)("text", { x: x + textPadding, y: contentY + (dirPath ? 40 : contentHeight / 2 + theme.node.fontSize + 2), fill: theme.node.sublabelColor, fontSize: theme.node.sublabelFontSize, fontFamily: theme.node.fontFamily, pointerEvents: "none", children: subpath })] }), slots && (0, import_jsx_runtime11.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime11.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor, strokeWidth: thinStroke, corner: toKebabCorner(refCorner), onNavigate })] });
18822
+ return (0, import_jsx_runtime11.jsxs)("g", { className: "system-canvas-node system-canvas-node--file", style: { cursor: onPointerDown ? "move" : node.isNavigable ? "pointer" : "default" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime11.jsx)("defs", { children: (0, import_jsx_runtime11.jsx)("clipPath", { id: clipId, children: (0, import_jsx_runtime11.jsx)("rect", { x: x + textPadding, y: contentY, width: maxTextWidth, height: contentHeight }) }) }), (0, import_jsx_runtime11.jsx)("path", { d: shapePath, fill: theme.background }), (0, import_jsx_runtime11.jsx)("path", { d: shapePath, fill: node.resolvedFill, stroke: strokeColor, strokeWidth: thinStroke }), (0, import_jsx_runtime11.jsx)("path", { d: foldPath, fill: "none", stroke: strokeColor, strokeWidth: thinStroke, opacity: 0.5 }), !isEditing && !slots?.body && (0, import_jsx_runtime11.jsxs)("g", { clipPath: `url(#${clipId})`, children: [dirPath && (0, import_jsx_runtime11.jsxs)("text", { x: x + textPadding, y: contentY + 14, fill: theme.node.sublabelColor, fontSize: theme.node.sublabelFontSize - 1, fontFamily: theme.node.fontFamily, pointerEvents: "none", opacity: 0.6, children: [dirPath, "/"] }), (0, import_jsx_runtime11.jsx)(NodeText, { region: {
18823
+ x: x + textPadding,
18824
+ y: dirPath ? contentY + 18 : contentY + (subpath ? 8 : LABEL_PAD_Y),
18825
+ width: maxTextWidth,
18826
+ height: dirPath ? Math.max(0, contentHeight - 18 - (subpath ? 16 : 0)) : Math.max(0, contentHeight - (subpath ? 16 : 0) - LABEL_PAD_Y * 2)
18827
+ }, value: fileName, theme, color: theme.node.labelColor, align: "start", fontWeight: 500, fontSize: theme.node.fontSize - 1, wrap: true, verticalAlign: dirPath ? "top" : "center" }), subpath && (0, import_jsx_runtime11.jsx)("text", { x: x + textPadding, y: contentY + (dirPath ? 40 : contentHeight / 2 + theme.node.fontSize + 2), fill: theme.node.sublabelColor, fontSize: theme.node.sublabelFontSize, fontFamily: theme.node.fontFamily, pointerEvents: "none", children: subpath })] }), slots && (0, import_jsx_runtime11.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime11.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor, strokeWidth: thinStroke, corner: toKebabCorner(refCorner), onNavigate })] });
18412
18828
  }
18413
18829
 
18414
18830
  // ../react/dist/components/LinkNode.js
@@ -18419,14 +18835,21 @@ var SystemCanvas = (() => {
18419
18835
  const contentY = y + reservedTop;
18420
18836
  const contentWidth = Math.max(0, width - reservedLeft - reservedRight);
18421
18837
  const contentHeight = Math.max(0, height - reservedTop - reservedBottom);
18422
- const cx = contentX + contentWidth / 2;
18838
+ const glyphReserve = 20;
18839
+ const LABEL_PAD_X = 10;
18840
+ const LABEL_PAD_Y = 6;
18423
18841
  let displayUrl = node.url ?? "";
18424
18842
  try {
18425
18843
  const url = new URL(displayUrl);
18426
18844
  displayUrl = url.hostname;
18427
18845
  } catch {
18428
18846
  }
18429
- return (0, import_jsx_runtime12.jsxs)("g", { className: "system-canvas-node system-canvas-node--link", style: { cursor: onPointerDown ? "move" : "pointer" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime12.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: theme.background }), (0, import_jsx_runtime12.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: node.resolvedFill, stroke: node.resolvedStroke, strokeWidth: theme.node.strokeWidth }), !isEditing && !slots?.body && (0, import_jsx_runtime12.jsx)("text", { x: contentX + 12, y: contentY + contentHeight / 2 + 4, fill: node.resolvedStroke, fontSize: 12, fontFamily: theme.node.fontFamily, pointerEvents: "none", opacity: 0.6, children: "\u29C9" }), !isEditing && !slots?.body && (0, import_jsx_runtime12.jsx)("text", { x: cx, y: contentY + contentHeight / 2 + 4, fill: theme.node.labelColor, fontSize: theme.node.fontSize, fontWeight: 600, fontFamily: theme.node.fontFamily, textAnchor: "middle", pointerEvents: "none", textDecoration: "underline", children: displayUrl }), slots && (0, import_jsx_runtime12.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime12.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor: node.resolvedStroke, strokeWidth: theme.node.strokeWidth, corner: toKebabCorner(refCorner), onNavigate })] });
18847
+ return (0, import_jsx_runtime12.jsxs)("g", { className: "system-canvas-node system-canvas-node--link", style: { cursor: onPointerDown ? "move" : "pointer" }, onClick: (e) => onClick(node, e), onDoubleClick: (e) => onDoubleClick(node, e), onContextMenu: (e) => onContextMenu(node, e), onPointerDown: onPointerDown ? (e) => onPointerDown(node, e) : void 0, children: [(0, import_jsx_runtime12.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: theme.background }), (0, import_jsx_runtime12.jsx)("rect", { x, y, width, height, rx: node.resolvedCornerRadius, fill: node.resolvedFill, stroke: node.resolvedStroke, strokeWidth: theme.node.strokeWidth }), !isEditing && !slots?.body && (0, import_jsx_runtime12.jsx)("text", { x: contentX + 12, y: contentY + contentHeight / 2 + 4, fill: node.resolvedStroke, fontSize: 12, fontFamily: theme.node.fontFamily, pointerEvents: "none", opacity: 0.6, children: "\u29C9" }), !isEditing && !slots?.body && (0, import_jsx_runtime12.jsx)(NodeText, { region: {
18848
+ x: contentX + glyphReserve,
18849
+ y: contentY + LABEL_PAD_Y,
18850
+ width: Math.max(0, contentWidth - glyphReserve - LABEL_PAD_X),
18851
+ height: Math.max(0, contentHeight - LABEL_PAD_Y * 2)
18852
+ }, value: displayUrl, theme, color: theme.node.labelColor, align: "center", fontWeight: 600, fontSize: theme.node.fontSize, wrap: true, verticalAlign: "center" }), slots && (0, import_jsx_runtime12.jsx)(CategorySlotsLayer, { node, theme, canvases, slots }), node.isNavigable && (0, import_jsx_runtime12.jsx)(RefIndicator, { node, theme, nodeX: x, nodeY: y, nodeWidth: width, nodeHeight: height, strokeColor: node.resolvedStroke, strokeWidth: theme.node.strokeWidth, corner: toKebabCorner(refCorner), onNavigate })] });
18430
18853
  }
18431
18854
 
18432
18855
  // ../react/dist/components/GroupNode.js
@@ -18552,7 +18975,8 @@ var SystemCanvas = (() => {
18552
18975
  const isEditing = editingId === edge.id;
18553
18976
  const baseColor = edge.color ? resolveColor(edge.color, theme).stroke : theme.edge.stroke;
18554
18977
  const edgeColor = isSelected ? theme.node.labelColor : baseColor;
18555
- const strokeWidth = isSelected ? theme.edge.strokeWidth * 1.75 : theme.edge.strokeWidth;
18978
+ const baseStrokeWidth = edge.strokeWidth ?? theme.edge.strokeWidth;
18979
+ const strokeWidth = isSelected ? baseStrokeWidth * 1.75 : baseStrokeWidth;
18556
18980
  const toEnd = edge.toEnd ?? "arrow";
18557
18981
  const fromEnd = edge.fromEnd ?? "none";
18558
18982
  const arrowId = "system-canvas-arrowhead";
@@ -18615,6 +19039,10 @@ var SystemCanvas = (() => {
18615
19039
  const fontFamily = theme.node.fontFamily;
18616
19040
  const fontSize = theme.node.fontSize;
18617
19041
  const padding = 8;
19042
+ const slots = getCategorySlots(node, theme);
19043
+ const reservations = computeReflowReservations(node, theme, slots);
19044
+ const hasHeader = reservations.top > 0;
19045
+ const textAlign = node.type === "text" && !hasHeader ? "center" : "left";
18618
19046
  const commonFieldStyle = {
18619
19047
  width: "100%",
18620
19048
  height: "100%",
@@ -18628,7 +19056,7 @@ var SystemCanvas = (() => {
18628
19056
  borderRadius: node.resolvedCornerRadius,
18629
19057
  outline: "none",
18630
19058
  resize: "none",
18631
- textAlign: node.type === "text" ? "center" : "left"
19059
+ textAlign
18632
19060
  };
18633
19061
  return (0, import_jsx_runtime17.jsx)("foreignObject", { x: node.x, y: node.y, width: node.width, height: node.height, onPointerDown: stopPointer, onMouseDown: stopPointer, onClick: stopPointer, onDoubleClick: stopPointer, children: node.type === "text" ? (0, import_jsx_runtime17.jsx)("textarea", { ref: textareaRef, value, onChange: (e) => setValue(e.target.value), onBlur: commit, onKeyDown: (e) => {
18634
19062
  if (e.key === "Enter" && !e.shiftKey) {
@@ -18998,7 +19426,7 @@ var SystemCanvas = (() => {
18998
19426
  // ../react/dist/components/Viewport.js
18999
19427
  var HOVER_PADDING = 10;
19000
19428
  var EDGE_PROXIMITY = 16;
19001
- var Viewport = (0, import_react15.forwardRef)(function Viewport2({ nodes, edges, nodeMap, theme, edgeStyle, columns, rows, canvases, minZoom, maxZoom, defaultViewport, onViewportChange, onNodeClick, onNodeDoubleClick, onNodeNavigate, onEdgeClick, onEdgeDoubleClick, onCanvasClick, onCanvasContextMenu, onNodeContextMenu, onEdgeContextMenu, onNodePointerDown, selectedId, editingId, selectedEdgeId, editingEdgeId, dragOverrides, resizeOverrides, onResizeHandlePointerDown, onEditorCommit, onEditorCancel, onEdgeEditorCommit, onEdgeEditorCancel, pendingEdge, onConnectionHandlePointerDown, edgeCreateEnabled, autoFit = "canvas-change", canvasRef, handoffTransform, onHandoffApplied, handoffFadeMs = 0 }, ref) {
19429
+ var Viewport = (0, import_react15.forwardRef)(function Viewport2({ nodes, edges, nodeMap, theme, edgeStyle, columns, rows, canvases, minZoom, maxZoom, defaultViewport, onViewportChange, onNodeClick, onNodeDoubleClick, onNodeNavigate, onEdgeClick, onEdgeDoubleClick, onCanvasClick, onCanvasContextMenu, onNodeContextMenu, onEdgeContextMenu, onNodePointerDown, selectedId, editingId, selectedEdgeId, editingEdgeId, dragOverrides, dropTargetId, resizeOverrides, onResizeHandlePointerDown, onEditorCommit, onEditorCancel, onEdgeEditorCommit, onEdgeEditorCancel, pendingEdge, onConnectionHandlePointerDown, edgeCreateEnabled, autoFit = "canvas-change", canvasRef, handoffTransform, onHandoffApplied, handoffFadeMs = 0 }, ref) {
19002
19430
  const { svgRef, groupRef, viewport, fitToContent, zoomToNode, setTransform } = useViewport({
19003
19431
  minZoom,
19004
19432
  maxZoom,
@@ -19041,6 +19469,7 @@ var SystemCanvas = (() => {
19041
19469
  }, []);
19042
19470
  const [hoveredNodeId, setHoveredNodeId] = (0, import_react15.useState)(null);
19043
19471
  const [hoveredSide, setHoveredSide] = (0, import_react15.useState)(null);
19472
+ const cursorPosRef = (0, import_react15.useRef)(null);
19044
19473
  (0, import_react15.useImperativeHandle)(ref, () => ({
19045
19474
  zoomToNode: (node, onComplete, options) => {
19046
19475
  navigatingRef.current = true;
@@ -19049,7 +19478,8 @@ var SystemCanvas = (() => {
19049
19478
  fitToContent,
19050
19479
  setTransform,
19051
19480
  getSvgElement: () => svgRef.current,
19052
- getViewport: () => viewport.current ?? { x: 0, y: 0, zoom: 1 }
19481
+ getViewport: () => viewport.current ?? { x: 0, y: 0, zoom: 1 },
19482
+ getCursorScreenPos: () => cursorPosRef.current
19053
19483
  }));
19054
19484
  const renderNodes = (0, import_react15.useMemo)(() => {
19055
19485
  const hasDrag = dragOverrides && dragOverrides.size > 0;
@@ -19132,14 +19562,17 @@ var SystemCanvas = (() => {
19132
19562
  ]);
19133
19563
  const editingNode = editingId ? renderNodes.find((n) => n.id === editingId) ?? null : null;
19134
19564
  const handleSvgPointerMove = (0, import_react15.useCallback)((event) => {
19135
- if (!edgeCreateEnabled)
19136
- return;
19137
19565
  const svg = svgRef.current;
19138
19566
  if (!svg)
19139
19567
  return;
19140
19568
  const rect = svg.getBoundingClientRect();
19569
+ const cursorScreenX = event.clientX - rect.left;
19570
+ const cursorScreenY = event.clientY - rect.top;
19571
+ cursorPosRef.current = { x: cursorScreenX, y: cursorScreenY };
19572
+ if (!edgeCreateEnabled)
19573
+ return;
19141
19574
  const vp = viewport.current ?? { x: 0, y: 0, zoom: 1 };
19142
- const { x, y } = screenToCanvas(event.clientX - rect.left, event.clientY - rect.top, vp);
19575
+ const { x, y } = screenToCanvas(cursorScreenX, cursorScreenY, vp);
19143
19576
  const pad = HOVER_PADDING;
19144
19577
  let hit = null;
19145
19578
  for (let i = renderNodes.length - 1; i >= 0; i--) {
@@ -19183,11 +19616,13 @@ var SystemCanvas = (() => {
19183
19616
  const handleSvgPointerLeave = (0, import_react15.useCallback)(() => {
19184
19617
  setHoveredNodeId(null);
19185
19618
  setHoveredSide(null);
19619
+ cursorPosRef.current = null;
19186
19620
  }, []);
19187
19621
  const handlesNodeId = pendingEdge?.sourceId ?? hoveredNodeId;
19188
19622
  const handlesNode = edgeCreateEnabled && handlesNodeId ? renderNodeMap.get(handlesNodeId) ?? null : null;
19189
19623
  const pendingSourceNode = pendingEdge ? renderNodeMap.get(pendingEdge.sourceId) ?? null : null;
19190
19624
  const pendingTargetNode = pendingEdge?.hoveredTargetId && pendingEdge.hoveredTargetId !== pendingEdge.sourceId ? renderNodeMap.get(pendingEdge.hoveredTargetId) ?? null : null;
19625
+ const dropTargetNode = dropTargetId ? renderNodeMap.get(dropTargetId) ?? null : null;
19191
19626
  const editingEdge = editingEdgeId ? edges.find((e) => e.id === editingEdgeId) ?? null : null;
19192
19627
  const editingEdgeMidpoint = (() => {
19193
19628
  if (!editingEdge)
@@ -19207,7 +19642,7 @@ var SystemCanvas = (() => {
19207
19642
  WebkitUserSelect: "none",
19208
19643
  MozUserSelect: "none",
19209
19644
  msUserSelect: "none"
19210
- }, onClick: onCanvasClick, onContextMenu: onCanvasContextMenu, onPointerMove: handleSvgPointerMove, onPointerLeave: handleSvgPointerLeave, children: [(0, import_jsx_runtime22.jsx)("defs", { children: (0, import_jsx_runtime22.jsx)("pattern", { id: "system-canvas-grid", width: theme.grid.size, height: theme.grid.size, patternUnits: "userSpaceOnUse", children: (0, import_jsx_runtime22.jsx)("path", { d: `M ${theme.grid.size} 0 L 0 0 0 ${theme.grid.size}`, fill: "none", stroke: theme.grid.color, strokeWidth: theme.grid.strokeWidth }) }) }), (0, import_jsx_runtime22.jsx)("rect", { x: "-50000", y: "-50000", width: "100000", height: "100000", fill: "url(#system-canvas-grid)" }), (0, import_jsx_runtime22.jsxs)("g", { ref: groupRef, children: [(0, import_jsx_runtime22.jsx)(LanesBackground, { columns, rows, theme }), (0, import_jsx_runtime22.jsx)(NodeRenderer, { nodes: renderNodes, theme, onClick: onNodeClick, onDoubleClick: onNodeDoubleClick, onContextMenu: onNodeContextMenu, onNavigate: onNodeNavigate, onPointerDown: onNodePointerDown, selectedId, editingId, canvases, only: "groups" }), (0, import_jsx_runtime22.jsx)(EdgeRenderer, { edges, nodeMap: renderNodeMap, theme, defaultEdgeStyle: edgeStyle, onClick: onEdgeClick, onDoubleClick: onEdgeDoubleClick, onContextMenu: onEdgeContextMenu, selectedId: selectedEdgeId, editingId: editingEdgeId }), (0, import_jsx_runtime22.jsx)(NodeRenderer, { nodes: renderNodes, theme, onClick: onNodeClick, onDoubleClick: onNodeDoubleClick, onContextMenu: onNodeContextMenu, onNavigate: onNodeNavigate, onPointerDown: onNodePointerDown, selectedId, editingId, onResizeHandlePointerDown, canvases, only: "non-groups" }), pendingTargetNode && (0, import_jsx_runtime22.jsx)("rect", { className: "system-canvas-drop-target", x: pendingTargetNode.x - 4, y: pendingTargetNode.y - 4, width: pendingTargetNode.width + 8, height: pendingTargetNode.height + 8, rx: pendingTargetNode.resolvedCornerRadius + 4, fill: "none", stroke: theme.node.labelColor, strokeWidth: 2, opacity: 0.85, pointerEvents: "none" }), pendingEdge && pendingSourceNode && (0, import_jsx_runtime22.jsx)(PendingEdgeRenderer, { sourceNode: pendingSourceNode, sourceSide: pendingEdge.sourceSide, cursor: pendingEdge.cursor, targetNode: pendingTargetNode, theme, defaultEdgeStyle: edgeStyle }), handlesNode && onConnectionHandlePointerDown && (0, import_jsx_runtime22.jsx)(ConnectionHandles, { node: handlesNode, theme, onHandlePointerDown: onConnectionHandlePointerDown, immediate: !!pendingEdge, activeSide: hoveredSide }), editingNode && onEditorCommit && onEditorCancel && (0, import_jsx_runtime22.jsx)(NodeEditor, { node: editingNode, theme, onCommit: onEditorCommit, onCancel: onEditorCancel }), editingEdge && editingEdgeMidpoint && onEdgeEditorCommit && onEdgeEditorCancel && (0, import_jsx_runtime22.jsx)(EdgeLabelEditor, { initialLabel: editingEdge.label ?? "", midpoint: editingEdgeMidpoint, theme, onCommit: onEdgeEditorCommit, onCancel: onEdgeEditorCancel })] })] });
19645
+ }, onClick: onCanvasClick, onContextMenu: onCanvasContextMenu, onPointerMove: handleSvgPointerMove, onPointerLeave: handleSvgPointerLeave, children: [(0, import_jsx_runtime22.jsx)("defs", { children: (0, import_jsx_runtime22.jsx)("pattern", { id: "system-canvas-grid", width: theme.grid.size, height: theme.grid.size, patternUnits: "userSpaceOnUse", children: (0, import_jsx_runtime22.jsx)("path", { d: `M ${theme.grid.size} 0 L 0 0 0 ${theme.grid.size}`, fill: "none", stroke: theme.grid.color, strokeWidth: theme.grid.strokeWidth }) }) }), (0, import_jsx_runtime22.jsx)("rect", { x: "-50000", y: "-50000", width: "100000", height: "100000", fill: "url(#system-canvas-grid)" }), (0, import_jsx_runtime22.jsxs)("g", { ref: groupRef, children: [(0, import_jsx_runtime22.jsx)(LanesBackground, { columns, rows, theme }), (0, import_jsx_runtime22.jsx)(NodeRenderer, { nodes: renderNodes, theme, onClick: onNodeClick, onDoubleClick: onNodeDoubleClick, onContextMenu: onNodeContextMenu, onNavigate: onNodeNavigate, onPointerDown: onNodePointerDown, selectedId, editingId, canvases, only: "groups" }), (0, import_jsx_runtime22.jsx)(EdgeRenderer, { edges, nodeMap: renderNodeMap, theme, defaultEdgeStyle: edgeStyle, onClick: onEdgeClick, onDoubleClick: onEdgeDoubleClick, onContextMenu: onEdgeContextMenu, selectedId: selectedEdgeId, editingId: editingEdgeId }), (0, import_jsx_runtime22.jsx)(NodeRenderer, { nodes: renderNodes, theme, onClick: onNodeClick, onDoubleClick: onNodeDoubleClick, onContextMenu: onNodeContextMenu, onNavigate: onNodeNavigate, onPointerDown: onNodePointerDown, selectedId, editingId, onResizeHandlePointerDown, canvases, only: "non-groups" }), pendingTargetNode && (0, import_jsx_runtime22.jsx)("rect", { className: "system-canvas-drop-target", x: pendingTargetNode.x - 4, y: pendingTargetNode.y - 4, width: pendingTargetNode.width + 8, height: pendingTargetNode.height + 8, rx: pendingTargetNode.resolvedCornerRadius + 4, fill: "none", stroke: theme.node.labelColor, strokeWidth: 2, opacity: 0.85, pointerEvents: "none" }), dropTargetNode && (0, import_jsx_runtime22.jsx)("rect", { className: "system-canvas-drop-target system-canvas-node-drop-target", x: dropTargetNode.x - 4, y: dropTargetNode.y - 4, width: dropTargetNode.width + 8, height: dropTargetNode.height + 8, rx: dropTargetNode.resolvedCornerRadius + 4, fill: "none", stroke: theme.node.labelColor, strokeWidth: 2, strokeDasharray: "6 4", opacity: 0.9, pointerEvents: "none" }), pendingEdge && pendingSourceNode && (0, import_jsx_runtime22.jsx)(PendingEdgeRenderer, { sourceNode: pendingSourceNode, sourceSide: pendingEdge.sourceSide, cursor: pendingEdge.cursor, targetNode: pendingTargetNode, theme, defaultEdgeStyle: edgeStyle }), handlesNode && onConnectionHandlePointerDown && (0, import_jsx_runtime22.jsx)(ConnectionHandles, { node: handlesNode, theme, onHandlePointerDown: onConnectionHandlePointerDown, immediate: !!pendingEdge, activeSide: hoveredSide }), editingNode && onEditorCommit && onEditorCancel && (0, import_jsx_runtime22.jsx)(NodeEditor, { node: editingNode, theme, onCommit: onEditorCommit, onCancel: onEditorCancel }), editingEdge && editingEdgeMidpoint && onEdgeEditorCommit && onEdgeEditorCancel && (0, import_jsx_runtime22.jsx)(EdgeLabelEditor, { initialLabel: editingEdge.label ?? "", midpoint: editingEdgeMidpoint, theme, onCommit: onEdgeEditorCommit, onCancel: onEdgeEditorCancel })] })] });
19211
19646
  });
19212
19647
 
19213
19648
  // ../react/dist/components/Breadcrumbs.js
@@ -19436,7 +19871,7 @@ var SystemCanvas = (() => {
19436
19871
  const cx = visibleLeft + visibleW / 2;
19437
19872
  if (endScreen <= colsOffsetLeft || startScreen >= width)
19438
19873
  return null;
19439
- return (0, import_jsx_runtime25.jsxs)("g", { children: [(0, import_jsx_runtime25.jsx)("line", { x1: startScreen, y1: pinned ? headerSize : y + headerSize, x2: startScreen, y2: pinned ? 0 : y, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth }), (0, import_jsx_runtime25.jsx)("text", { x: cx, y: (pinned ? 0 : y) + headerSize / 2, textAnchor: "middle", dominantBaseline: "middle", fill: lanesTheme.headerTextColor, fontFamily: lanesTheme.headerFontFamily, fontSize: lanesTheme.headerFontSize, style: { userSelect: "none" }, children: truncateToWidth(col.label, visibleW - pad * 2, lanesTheme.headerFontSize) })] }, `colh-${col.id}`);
19874
+ return (0, import_jsx_runtime25.jsxs)("g", { children: [(0, import_jsx_runtime25.jsx)("line", { x1: startScreen, y1: pinned ? headerSize : y + headerSize, x2: startScreen, y2: pinned ? 0 : y, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth }), (0, import_jsx_runtime25.jsx)("text", { x: cx, y: (pinned ? 0 : y) + headerSize / 2, textAnchor: "middle", dominantBaseline: "middle", fill: lanesTheme.headerTextColor, fontFamily: lanesTheme.headerFontFamily, fontSize: lanesTheme.headerFontSize, style: { userSelect: "none" }, children: truncateToWidth2(col.label, visibleW - pad * 2, lanesTheme.headerFontSize) })] }, `colh-${col.id}`);
19440
19875
  }), pinned && (0, import_jsx_runtime25.jsx)("line", { x1: colsOffsetLeft, y1: headerSize, x2: width, y2: headerSize, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth })] }), hasRows && (0, import_jsx_runtime25.jsxs)("g", { children: [pinned && (0, import_jsx_runtime25.jsx)("rect", { x: 0, y: rowsOffsetTop, width: headerSize, height: height - rowsOffsetTop, fill: lanesTheme.headerBackground }), rows.map((row) => {
19441
19876
  const startScreen = canvasToScreen(0, row.start, viewport).y;
19442
19877
  const endScreen = canvasToScreen(0, row.start + row.size, viewport).y;
@@ -19448,10 +19883,10 @@ var SystemCanvas = (() => {
19448
19883
  const cy = visibleTop + visibleH / 2;
19449
19884
  if (endScreen <= rowsOffsetTop || startScreen >= height)
19450
19885
  return null;
19451
- return (0, import_jsx_runtime25.jsxs)("g", { children: [(0, import_jsx_runtime25.jsx)("line", { x1: pinned ? 0 : x, y1: startScreen, x2: pinned ? headerSize : x + headerSize, y2: startScreen, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth }), (0, import_jsx_runtime25.jsx)("text", { x: (pinned ? 0 : x) + headerSize / 2, y: cy, textAnchor: "middle", dominantBaseline: "middle", fill: lanesTheme.headerTextColor, fontFamily: lanesTheme.headerFontFamily, fontSize: lanesTheme.headerFontSize, transform: `rotate(-90 ${(pinned ? 0 : x) + headerSize / 2} ${cy})`, style: { userSelect: "none" }, children: truncateToWidth(row.label, visibleH - pad * 2, lanesTheme.headerFontSize) })] }, `rowh-${row.id}`);
19886
+ return (0, import_jsx_runtime25.jsxs)("g", { children: [(0, import_jsx_runtime25.jsx)("line", { x1: pinned ? 0 : x, y1: startScreen, x2: pinned ? headerSize : x + headerSize, y2: startScreen, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth }), (0, import_jsx_runtime25.jsx)("text", { x: (pinned ? 0 : x) + headerSize / 2, y: cy, textAnchor: "middle", dominantBaseline: "middle", fill: lanesTheme.headerTextColor, fontFamily: lanesTheme.headerFontFamily, fontSize: lanesTheme.headerFontSize, transform: `rotate(-90 ${(pinned ? 0 : x) + headerSize / 2} ${cy})`, style: { userSelect: "none" }, children: truncateToWidth2(row.label, visibleH - pad * 2, lanesTheme.headerFontSize) })] }, `rowh-${row.id}`);
19452
19887
  }), pinned && (0, import_jsx_runtime25.jsx)("line", { x1: headerSize, y1: rowsOffsetTop, x2: headerSize, y2: height, stroke: lanesTheme.dividerColor, strokeWidth: lanesTheme.dividerWidth })] }), hasColumns && hasRows && pinned && (0, import_jsx_runtime25.jsx)("rect", { x: 0, y: 0, width: headerSize, height: headerSize, fill: lanesTheme.headerBackground })] });
19453
19888
  }
19454
- function truncateToWidth(label, availablePx, fontSize) {
19889
+ function truncateToWidth2(label, availablePx, fontSize) {
19455
19890
  if (availablePx <= 0)
19456
19891
  return "";
19457
19892
  const charPx = fontSize * 0.6;
@@ -19493,8 +19928,10 @@ var SystemCanvas = (() => {
19493
19928
  raf = requestAnimationFrame(tick);
19494
19929
  return () => cancelAnimationFrame(raf);
19495
19930
  }, [getViewport]);
19496
- const topCenter = canvasToScreen(node.x + node.width / 2, node.y, viewport);
19497
- const bottomCenter = canvasToScreen(node.x + node.width / 2, node.y + node.height, viewport);
19931
+ const align = theme.toolbarAlign ?? "center";
19932
+ const anchorCanvasX = align === "left" ? node.x : align === "right" ? node.x + node.width : node.x + node.width / 2;
19933
+ const topAnchor = canvasToScreen(anchorCanvasX, node.y, viewport);
19934
+ const bottomAnchor = canvasToScreen(anchorCanvasX, node.y + node.height, viewport);
19498
19935
  const toolbarRef = (0, import_react19.useRef)(null);
19499
19936
  const [size, setSize] = (0, import_react19.useState)({ width: 0, height: 0 });
19500
19937
  (0, import_react19.useEffect)(() => {
@@ -19503,17 +19940,17 @@ var SystemCanvas = (() => {
19503
19940
  return;
19504
19941
  const update = () => {
19505
19942
  const r = el.getBoundingClientRect();
19506
- setSize({ width: r.width, height: r.height });
19943
+ setSize((prev) => prev.width === r.width && prev.height === r.height ? prev : { width: r.width, height: r.height });
19507
19944
  };
19508
19945
  update();
19509
19946
  const ro = new ResizeObserver(update);
19510
19947
  ro.observe(el);
19511
19948
  return () => ro.disconnect();
19512
- });
19513
- let left = topCenter.x - size.width / 2;
19514
- let top = topCenter.y - size.height - NODE_GAP;
19949
+ }, []);
19950
+ let left = align === "left" ? topAnchor.x : align === "right" ? topAnchor.x - size.width : topAnchor.x - size.width / 2;
19951
+ let top = topAnchor.y - size.height - NODE_GAP;
19515
19952
  if (top < FLIP_MARGIN) {
19516
- top = bottomCenter.y + NODE_GAP;
19953
+ top = bottomAnchor.y + NODE_GAP;
19517
19954
  }
19518
19955
  left = Math.max(FLIP_MARGIN, Math.min(left, containerWidth - size.width - FLIP_MARGIN));
19519
19956
  const patch = (update) => onPatch(update);
@@ -19552,7 +19989,7 @@ var SystemCanvas = (() => {
19552
19989
  }
19553
19990
  function DefaultToolbarContent({ node, theme, onPatch, onDelete }) {
19554
19991
  const groups = (0, import_react19.useMemo)(() => getNodeActionsForNode(node, theme), [node, theme]);
19555
- const showDelete = !theme.hideToolbarDelete;
19992
+ const showDelete = theme.showToolbarDelete === true;
19556
19993
  return (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [groups.map((group, i) => (0, import_jsx_runtime26.jsxs)(import_react19.default.Fragment, { children: [i > 0 && (0, import_jsx_runtime26.jsx)(Divider2, { theme }), (0, import_jsx_runtime26.jsx)(ActionGroupView, { group, node, theme, onPatch })] }, group.id)), showDelete && (0, import_jsx_runtime26.jsxs)(import_jsx_runtime26.Fragment, { children: [(0, import_jsx_runtime26.jsx)(Divider2, { theme }), (0, import_jsx_runtime26.jsx)(DeleteButton, { theme, onDelete })] })] });
19557
19994
  }
19558
19995
  function Divider2({ theme }) {
@@ -19707,16 +20144,134 @@ var SystemCanvas = (() => {
19707
20144
  }, children: (0, import_jsx_runtime26.jsx)("svg", { width: DELETE_SIZE, height: DELETE_SIZE, viewBox: "0 0 16 16", children: (0, import_jsx_runtime26.jsx)("path", { d: "M 3 5 L 13 5 M 6 5 L 6 3 L 10 3 L 10 5 M 5 5 L 5.5 14 L 10.5 14 L 11 5 M 7 7 L 7 12 M 9 7 L 9 12", fill: "none", stroke: "currentColor", strokeWidth: 1.2, strokeLinecap: "round", strokeLinejoin: "round" }) }) });
19708
20145
  }
19709
20146
 
20147
+ // ../react/dist/components/NodeContextMenuOverlay.js
20148
+ var import_jsx_runtime27 = __toESM(require_jsx_runtime(), 1);
20149
+ var import_react20 = __toESM(require_react(), 1);
20150
+ var ESTIMATED_MENU_WIDTH = 200;
20151
+ var MIN_MENU_WIDTH = 160;
20152
+ var VIEWPORT_MARGIN = 8;
20153
+ function NodeContextMenuOverlay({ state, config, theme, onClose }) {
20154
+ const rootRef = (0, import_react20.useRef)(null);
20155
+ const [hoveredId, setHoveredId] = (0, import_react20.useState)(null);
20156
+ (0, import_react20.useEffect)(() => {
20157
+ if (state)
20158
+ setHoveredId(null);
20159
+ }, [state]);
20160
+ (0, import_react20.useEffect)(() => {
20161
+ if (!state)
20162
+ return;
20163
+ function onDown(e) {
20164
+ const root2 = rootRef.current;
20165
+ if (!root2)
20166
+ return;
20167
+ if (root2.contains(e.target))
20168
+ return;
20169
+ onClose();
20170
+ }
20171
+ function onKey(e) {
20172
+ if (e.key === "Escape") {
20173
+ e.stopPropagation();
20174
+ onClose();
20175
+ }
20176
+ }
20177
+ function onScroll() {
20178
+ onClose();
20179
+ }
20180
+ window.addEventListener("mousedown", onDown);
20181
+ window.addEventListener("keydown", onKey);
20182
+ window.addEventListener("scroll", onScroll, true);
20183
+ window.addEventListener("blur", onClose);
20184
+ return () => {
20185
+ window.removeEventListener("mousedown", onDown);
20186
+ window.removeEventListener("keydown", onKey);
20187
+ window.removeEventListener("scroll", onScroll, true);
20188
+ window.removeEventListener("blur", onClose);
20189
+ };
20190
+ }, [state, onClose]);
20191
+ if (!state)
20192
+ return null;
20193
+ const cm = theme.contextMenu;
20194
+ if (!cm)
20195
+ return null;
20196
+ const vw = typeof window !== "undefined" ? window.innerWidth : 0;
20197
+ const vh = typeof window !== "undefined" ? window.innerHeight : 0;
20198
+ const itemHeight = cm.itemPaddingY * 2 + cm.fontSize + 4;
20199
+ const estimatedHeight = state.items.length * itemHeight + cm.paddingY * 2;
20200
+ const left = vw ? Math.min(state.screenPosition.x, vw - ESTIMATED_MENU_WIDTH - VIEWPORT_MARGIN) : state.screenPosition.x;
20201
+ const top = vh ? Math.min(state.screenPosition.y, vh - estimatedHeight - VIEWPORT_MARGIN) : state.screenPosition.y;
20202
+ const matchCtx = { canvasRef: state.canvasRef };
20203
+ const anyIcon = state.items.some((item) => !!item.icon);
20204
+ return (0, import_jsx_runtime27.jsx)("div", {
20205
+ ref: rootRef,
20206
+ role: "menu",
20207
+ // Stop right-clicks inside the menu from bubbling into the document
20208
+ // listener and re-opening the canvas-level menu.
20209
+ onContextMenu: (e) => {
20210
+ e.preventDefault();
20211
+ e.stopPropagation();
20212
+ },
20213
+ style: {
20214
+ position: "fixed",
20215
+ left,
20216
+ top,
20217
+ zIndex: 1e3,
20218
+ minWidth: MIN_MENU_WIDTH,
20219
+ padding: `${cm.paddingY}px ${cm.paddingX}px`,
20220
+ background: cm.background,
20221
+ color: cm.itemColor,
20222
+ border: `1px solid ${cm.borderColor}`,
20223
+ borderRadius: cm.borderRadius,
20224
+ boxShadow: cm.shadow,
20225
+ fontFamily: cm.fontFamily,
20226
+ fontSize: cm.fontSize,
20227
+ backdropFilter: "blur(10px)",
20228
+ userSelect: "none",
20229
+ // `pointer-events: auto` so the menu still receives clicks even
20230
+ // when a parent canvas overlay disables them.
20231
+ pointerEvents: "auto"
20232
+ },
20233
+ children: state.items.map((item) => {
20234
+ const isDisabled = item.disabled?.(state.node, matchCtx) ?? false;
20235
+ const isHovered = !isDisabled && hoveredId === item.id;
20236
+ const color2 = item.destructive ? cm.destructiveItemColor : cm.itemColor;
20237
+ return (0, import_jsx_runtime27.jsxs)("div", { role: "menuitem", "aria-disabled": isDisabled, onMouseEnter: () => !isDisabled && setHoveredId(item.id), onMouseLeave: () => setHoveredId((id2) => id2 === item.id ? null : id2), onClick: () => {
20238
+ if (isDisabled)
20239
+ return;
20240
+ config.onSelect(item.id, state.node, {
20241
+ canvasRef: state.canvasRef,
20242
+ screenPosition: state.screenPosition
20243
+ });
20244
+ onClose();
20245
+ }, style: {
20246
+ display: "flex",
20247
+ alignItems: "center",
20248
+ gap: 10,
20249
+ padding: `${cm.itemPaddingY}px ${cm.itemPaddingX}px`,
20250
+ borderRadius: Math.max(0, cm.borderRadius - 4),
20251
+ cursor: isDisabled ? "not-allowed" : "pointer",
20252
+ opacity: isDisabled ? 0.45 : 1,
20253
+ background: isHovered ? cm.itemHoverBackground : "transparent",
20254
+ color: color2
20255
+ }, children: [item.icon ? (0, import_jsx_runtime27.jsx)("svg", { width: 14, height: 14, viewBox: "0 0 16 16", style: { flexShrink: 0, overflow: "visible" }, children: (0, import_jsx_runtime27.jsx)(NodeIcon, { icon: item.icon, x: 0, y: 0, size: 14, color: color2, opacity: 1, customIcons: theme.icons }) }) : anyIcon ? (
20256
+ // Only reserve a spacer when other items in the same menu
20257
+ // do have icons — keeps labels vertically aligned in a
20258
+ // mixed menu without padding lone-item menus.
20259
+ (0, import_jsx_runtime27.jsx)("span", { style: { width: 14, flexShrink: 0 }, "aria-hidden": true })
20260
+ ) : null, (0, import_jsx_runtime27.jsx)("span", { children: item.label })] }, item.id);
20261
+ })
20262
+ });
20263
+ }
20264
+
19710
20265
  // ../react/dist/components/SystemCanvas.js
19711
20266
  var CASCADE_WINDOW_MS = 1500;
19712
20267
  var CASCADE_OFFSET = 20;
19713
- var SystemCanvas = (0, import_react20.forwardRef)(function SystemCanvas2({ canvas, onResolveCanvas, canvases, rootLabel = "Home", onNavigate, onBreadcrumbClick, onNodeClick, onNodeDoubleClick, onEdgeClick, onEdgeDoubleClick, onContextMenu, editable = false, onNodeAdd, onNodeUpdate, onNodeDelete, onEdgeUpdate, onEdgeDelete, onEdgeAdd, renderAddNodeButton, showNodeToolbar = true, renderNodeToolbar, theme: themeProp, themes: customThemes, edgeStyle = "bezier", defaultViewport, minZoom: minZoomProp, maxZoom, onViewportChange, autoFit = "canvas-change", laneHeaders = "pinned", snapToLanes = false, zoomNavigation = false, className, style }, forwardedRef) {
19714
- const zoomNavConfig = (0, import_react20.useMemo)(() => {
20268
+ var SystemCanvas = (0, import_react21.forwardRef)(function SystemCanvas2({ canvas, onResolveCanvas, canvases, rootLabel = "Home", onNavigate, onBreadcrumbClick, onBreadcrumbsChange, onNodeClick, onNodeDoubleClick, onEdgeClick, onEdgeDoubleClick, onContextMenu, onSelectionChange, nodeContextMenu, editable = false, onNodeAdd, onNodeUpdate, onNodesUpdate, onNodeDelete, onEdgeUpdate, onEdgeDelete, onEdgeAdd, canDropNodeOn, onNodeDrop, renderAddNodeButton, showNodeToolbar = true, renderNodeToolbar, theme: themeProp, themes: customThemes, edgeStyle = "bezier", defaultViewport, minZoom: minZoomProp, maxZoom, onViewportChange, autoFit = "canvas-change", laneHeaders = "pinned", snapToLanes = false, zoomNavigation = false, className, style }, forwardedRef) {
20269
+ const zoomNavConfig = (0, import_react21.useMemo)(() => {
19715
20270
  const defaults = {
19716
20271
  enterThreshold: 0.66,
19717
20272
  exitThreshold: 0.33,
19718
20273
  prefetchThreshold: 0.4,
19719
- landingScale: 1.2,
20274
+ landingScale: 1.3,
19720
20275
  landingPadding: 0.08,
19721
20276
  fadeDuration: 216
19722
20277
  };
@@ -19736,16 +20291,16 @@ var SystemCanvas = (() => {
19736
20291
  }, [zoomNavigation]);
19737
20292
  const effectiveMaxZoom = maxZoom ?? (zoomNavConfig.enabled ? 16 : 4);
19738
20293
  const effectiveMinZoom = minZoomProp ?? (zoomNavConfig.enabled ? 0.01 : 0.1);
19739
- (0, import_react20.useEffect)(() => {
20294
+ (0, import_react21.useEffect)(() => {
19740
20295
  const env = globalThis.process?.env?.NODE_ENV;
19741
20296
  if (editable && !canvases && env !== "production") {
19742
20297
  console.warn("[system-canvas] `editable` is enabled but `canvases` prop is missing. Edits to sub-canvases will not be reflected without a synchronous ref \u2192 CanvasData map.");
19743
20298
  }
19744
20299
  }, [editable, canvases]);
19745
- const [parentFrames, setParentFrames] = (0, import_react20.useState)([]);
19746
- const [pendingHandoff, setPendingHandoff] = (0, import_react20.useState)(null);
19747
- const suppressNextHandoffClearRef = (0, import_react20.useRef)(false);
19748
- const handleBreadcrumbClick = (0, import_react20.useCallback)((index) => {
20300
+ const [parentFrames, setParentFrames] = (0, import_react21.useState)([]);
20301
+ const [pendingHandoff, setPendingHandoff] = (0, import_react21.useState)(null);
20302
+ const suppressNextHandoffClearRef = (0, import_react21.useRef)(false);
20303
+ const handleBreadcrumbClick = (0, import_react21.useCallback)((index) => {
19749
20304
  setParentFrames((prev) => prev.slice(0, index));
19750
20305
  if (suppressNextHandoffClearRef.current) {
19751
20306
  suppressNextHandoffClearRef.current = false;
@@ -19762,7 +20317,10 @@ var SystemCanvas = (() => {
19762
20317
  onNavigate,
19763
20318
  onBreadcrumbClick: handleBreadcrumbClick
19764
20319
  });
19765
- const theme = (0, import_react20.useMemo)(() => {
20320
+ (0, import_react21.useEffect)(() => {
20321
+ onBreadcrumbsChange?.(breadcrumbs);
20322
+ }, [breadcrumbs, onBreadcrumbsChange]);
20323
+ const theme = (0, import_react21.useMemo)(() => {
19766
20324
  const registry = { ...themes, ...customThemes };
19767
20325
  const resolveByName = (name) => name && registry[name] ? registry[name] : null;
19768
20326
  if (themeProp) {
@@ -19773,22 +20331,22 @@ var SystemCanvas = (() => {
19773
20331
  }
19774
20332
  return resolveByName(currentCanvas.theme?.base) ?? resolveByName(canvas.theme?.base) ?? darkTheme;
19775
20333
  }, [themeProp, customThemes, currentCanvas.theme?.base, canvas.theme?.base]);
19776
- const { nodes, edges, nodeMap } = (0, import_react20.useMemo)(() => {
20334
+ const { nodes, edges, nodeMap } = (0, import_react21.useMemo)(() => {
19777
20335
  const resolved = resolveCanvas(currentCanvas, theme);
19778
20336
  const map = buildNodeMap(resolved.nodes);
19779
20337
  return { nodes: resolved.nodes, edges: resolved.edges, nodeMap: map };
19780
20338
  }, [currentCanvas, theme]);
19781
- const nodesRef = (0, import_react20.useRef)(nodes);
20339
+ const nodesRef = (0, import_react21.useRef)(nodes);
19782
20340
  nodesRef.current = nodes;
19783
- const viewportStateRef = (0, import_react20.useRef)(defaultViewport ?? { x: 0, y: 0, zoom: 1 });
19784
- const viewportHandleRef = (0, import_react20.useRef)(null);
19785
- const navigateToRefRef = (0, import_react20.useRef)(navigateToRef);
20341
+ const viewportStateRef = (0, import_react21.useRef)(defaultViewport ?? { x: 0, y: 0, zoom: 1 });
20342
+ const viewportHandleRef = (0, import_react21.useRef)(null);
20343
+ const navigateToRefRef = (0, import_react21.useRef)(navigateToRef);
19786
20344
  navigateToRefRef.current = navigateToRef;
19787
- const navigateToBreadcrumbRef = (0, import_react20.useRef)(navigateToBreadcrumb);
20345
+ const navigateToBreadcrumbRef = (0, import_react21.useRef)(navigateToBreadcrumb);
19788
20346
  navigateToBreadcrumbRef.current = navigateToBreadcrumb;
19789
- const breadcrumbsRef = (0, import_react20.useRef)(breadcrumbs);
20347
+ const breadcrumbsRef = (0, import_react21.useRef)(breadcrumbs);
19790
20348
  breadcrumbsRef.current = breadcrumbs;
19791
- (0, import_react20.useImperativeHandle)(forwardedRef, () => ({
20349
+ (0, import_react21.useImperativeHandle)(forwardedRef, () => ({
19792
20350
  zoomIntoNode: (nodeId, options) => {
19793
20351
  return new Promise((resolve) => {
19794
20352
  const node = nodesRef.current.find((n) => n.id === nodeId);
@@ -19820,19 +20378,51 @@ var SystemCanvas = (() => {
19820
20378
  navigateToBreadcrumbRef.current(0);
19821
20379
  }
19822
20380
  }), [forwardedRef]);
19823
- const [selectedId, setSelectedId] = (0, import_react20.useState)(null);
19824
- const [editingId, setEditingId] = (0, import_react20.useState)(null);
19825
- const [selectedEdgeId, setSelectedEdgeId] = (0, import_react20.useState)(null);
19826
- const [editingEdgeId, setEditingEdgeId] = (0, import_react20.useState)(null);
19827
- (0, import_react20.useEffect)(() => {
20381
+ const [selectedId, setSelectedId] = (0, import_react21.useState)(null);
20382
+ const [editingId, setEditingId] = (0, import_react21.useState)(null);
20383
+ const [selectedEdgeId, setSelectedEdgeId] = (0, import_react21.useState)(null);
20384
+ const [editingEdgeId, setEditingEdgeId] = (0, import_react21.useState)(null);
20385
+ (0, import_react21.useEffect)(() => {
19828
20386
  setSelectedId(null);
19829
20387
  setEditingId(null);
19830
20388
  setSelectedEdgeId(null);
19831
20389
  setEditingEdgeId(null);
19832
20390
  }, [currentCanvasRef]);
19833
- const containerRef = (0, import_react20.useRef)(null);
19834
- const [containerSize, setContainerSize] = (0, import_react20.useState)({ width: 0, height: 0 });
19835
- (0, import_react20.useEffect)(() => {
20391
+ const onSelectionChangeRef = (0, import_react21.useRef)(onSelectionChange);
20392
+ (0, import_react21.useEffect)(() => {
20393
+ onSelectionChangeRef.current = onSelectionChange;
20394
+ }, [onSelectionChange]);
20395
+ const lastEmittedSelectionRef = (0, import_react21.useRef)({ kind: null, id: null, canvasRef: void 0 });
20396
+ (0, import_react21.useEffect)(() => {
20397
+ const cb = onSelectionChangeRef.current;
20398
+ let next = null;
20399
+ if (selectedId) {
20400
+ const node = nodeMap.get(selectedId);
20401
+ if (node)
20402
+ next = { kind: "node", node, canvasRef: currentCanvasRef };
20403
+ }
20404
+ if (!next && selectedEdgeId) {
20405
+ const edge = edges.find((e) => e.id === selectedEdgeId);
20406
+ if (edge)
20407
+ next = { kind: "edge", edge, canvasRef: currentCanvasRef };
20408
+ }
20409
+ const last = lastEmittedSelectionRef.current;
20410
+ const nextKind = next?.kind ?? null;
20411
+ const nextId = next ? next.kind === "node" ? next.node.id : next.edge.id : null;
20412
+ const nextRef = next?.canvasRef;
20413
+ if (last.kind === nextKind && last.id === nextId && last.canvasRef === nextRef) {
20414
+ return;
20415
+ }
20416
+ lastEmittedSelectionRef.current = {
20417
+ kind: nextKind,
20418
+ id: nextId,
20419
+ canvasRef: nextRef
20420
+ };
20421
+ cb?.(next);
20422
+ }, [selectedId, selectedEdgeId, currentCanvasRef, nodeMap, edges]);
20423
+ const containerRef = (0, import_react21.useRef)(null);
20424
+ const [containerSize, setContainerSize] = (0, import_react21.useState)({ width: 0, height: 0 });
20425
+ (0, import_react21.useEffect)(() => {
19836
20426
  const el = containerRef.current;
19837
20427
  if (!el)
19838
20428
  return;
@@ -19847,42 +20437,67 @@ var SystemCanvas = (() => {
19847
20437
  }, []);
19848
20438
  const hasLanes = currentCanvas.columns && currentCanvas.columns.length > 0 || currentCanvas.rows && currentCanvas.rows.length > 0;
19849
20439
  const showLaneHeaders = hasLanes && laneHeaders !== "none";
19850
- const getViewportState = (0, import_react20.useCallback)(() => viewportStateRef.current ?? { x: 0, y: 0, zoom: 1 }, []);
19851
- const commitDrag = (0, import_react20.useCallback)((id2, patch) => {
20440
+ const getViewportState = (0, import_react21.useCallback)(() => viewportStateRef.current ?? { x: 0, y: 0, zoom: 1 }, []);
20441
+ const applyLaneSnap = (0, import_react21.useCallback)((id2, patch) => {
20442
+ if (!snapToLanes)
20443
+ return patch;
20444
+ const cols = currentCanvas.columns;
20445
+ const rows = currentCanvas.rows;
20446
+ const node = nodesRef.current.find((n) => n.id === id2);
19852
20447
  let final = patch;
19853
- if (snapToLanes) {
19854
- const cols = currentCanvas.columns;
19855
- const rows = currentCanvas.rows;
19856
- const node = nodesRef.current.find((n) => n.id === id2);
19857
- const nx = patch.x;
19858
- const ny = patch.y;
19859
- if (cols && cols.length > 0 && nx != null) {
19860
- const snapped = snapToLane(nx, cols, {
19861
- edge: "center",
19862
- size: node?.width ?? 0
19863
- });
19864
- final = { ...final, x: Math.round(snapped) };
19865
- }
19866
- if (rows && rows.length > 0 && ny != null) {
19867
- const snapped = snapToLane(ny, rows, {
19868
- edge: "center",
19869
- size: node?.height ?? 0
19870
- });
19871
- final = { ...final, y: Math.round(snapped) };
19872
- }
20448
+ const nx = patch.x;
20449
+ const ny = patch.y;
20450
+ if (cols && cols.length > 0 && nx != null) {
20451
+ const snapped = snapToLane(nx, cols, {
20452
+ edge: "center",
20453
+ size: node?.width ?? 0
20454
+ });
20455
+ final = { ...final, x: Math.round(snapped) };
20456
+ }
20457
+ if (rows && rows.length > 0 && ny != null) {
20458
+ const snapped = snapToLane(ny, rows, {
20459
+ edge: "center",
20460
+ size: node?.height ?? 0
20461
+ });
20462
+ final = { ...final, y: Math.round(snapped) };
20463
+ }
20464
+ return final;
20465
+ }, [snapToLanes, currentCanvas.columns, currentCanvas.rows]);
20466
+ const commitResize = (0, import_react21.useCallback)((id2, patch) => {
20467
+ onNodeUpdate?.(id2, applyLaneSnap(id2, patch), currentCanvasRef);
20468
+ }, [onNodeUpdate, currentCanvasRef, applyLaneSnap]);
20469
+ const commitDragBatch = (0, import_react21.useCallback)((updates) => {
20470
+ const final = updates.map((u) => ({
20471
+ id: u.id,
20472
+ patch: applyLaneSnap(u.id, u.patch)
20473
+ }));
20474
+ if (onNodesUpdate) {
20475
+ onNodesUpdate(final, currentCanvasRef);
20476
+ return;
19873
20477
  }
19874
- onNodeUpdate?.(id2, final, currentCanvasRef);
19875
- }, [onNodeUpdate, currentCanvasRef, snapToLanes, currentCanvas.columns, currentCanvas.rows]);
19876
- const { dragOverrides, onPointerDown: onNodePointerDown } = useNodeDrag({
20478
+ if (!onNodeUpdate)
20479
+ return;
20480
+ for (const { id: id2, patch } of final) {
20481
+ onNodeUpdate(id2, patch, currentCanvasRef);
20482
+ }
20483
+ }, [onNodeUpdate, onNodesUpdate, currentCanvasRef, applyLaneSnap]);
20484
+ const svgProxyRef = (0, import_react21.useRef)(null);
20485
+ const handleNodeDrop = (0, import_react21.useCallback)((sources, target) => {
20486
+ onNodeDrop?.(sources, target, { canvasRef: currentCanvasRef });
20487
+ }, [onNodeDrop, currentCanvasRef]);
20488
+ const { dragOverrides, dropTargetId, onPointerDown: onNodePointerDown } = useNodeDrag({
19877
20489
  viewport: viewportStateRef,
19878
20490
  nodesRef,
19879
- onCommit: commitDrag
20491
+ onCommit: commitDragBatch,
20492
+ svgRef: svgProxyRef,
20493
+ canDropNodeOn,
20494
+ onNodeDrop: handleNodeDrop
19880
20495
  });
19881
20496
  const { resizeOverrides, onHandlePointerDown: onResizeHandlePointerDown } = useNodeResize({
19882
20497
  viewport: viewportStateRef,
19883
- onCommit: commitDrag
20498
+ onCommit: commitResize
19884
20499
  });
19885
- const selectedResolvedNode = (0, import_react20.useMemo)(() => {
20500
+ const selectedResolvedNode = (0, import_react21.useMemo)(() => {
19886
20501
  if (!selectedId)
19887
20502
  return null;
19888
20503
  const base = nodeMap.get(selectedId);
@@ -19898,9 +20513,8 @@ var SystemCanvas = (() => {
19898
20513
  }
19899
20514
  return base;
19900
20515
  }, [selectedId, nodeMap, dragOverrides, resizeOverrides]);
19901
- const svgProxyRef = (0, import_react20.useRef)(null);
19902
20516
  svgProxyRef.current = viewportHandleRef.current?.getSvgElement() ?? null;
19903
- const handleEdgeCreated = (0, import_react20.useCallback)((edge) => {
20517
+ const handleEdgeCreated = (0, import_react21.useCallback)((edge) => {
19904
20518
  onEdgeAdd?.(edge, currentCanvasRef);
19905
20519
  }, [onEdgeAdd, currentCanvasRef]);
19906
20520
  const { pending: pendingEdge, onHandlePointerDown: onConnectionHandlePointerDown } = useEdgeCreate({
@@ -19909,7 +20523,7 @@ var SystemCanvas = (() => {
19909
20523
  nodesRef,
19910
20524
  onCreate: handleEdgeCreated
19911
20525
  });
19912
- const handleNavigableNodeClick = (0, import_react20.useCallback)((node) => {
20526
+ const handleNavigableNodeClick = (0, import_react21.useCallback)((node) => {
19913
20527
  const frame2 = {
19914
20528
  parentCanvasRef: currentCanvasRef,
19915
20529
  parentNodeRect: {
@@ -19936,7 +20550,7 @@ var SystemCanvas = (() => {
19936
20550
  navigateToRef(node);
19937
20551
  }
19938
20552
  }, [navigateToRef, currentCanvasRef, zoomNavConfig.enabled]);
19939
- const handleZoomEnter = (0, import_react20.useCallback)((node, targetTransform) => {
20553
+ const handleZoomEnter = (0, import_react21.useCallback)((node, targetTransform) => {
19940
20554
  const frame2 = {
19941
20555
  parentCanvasRef: currentCanvasRef,
19942
20556
  parentNodeRect: {
@@ -19950,7 +20564,7 @@ var SystemCanvas = (() => {
19950
20564
  setPendingHandoff(targetTransform);
19951
20565
  navigateToRef(node);
19952
20566
  }, [currentCanvasRef, navigateToRef]);
19953
- const handleZoomExit = (0, import_react20.useCallback)((targetTransform) => {
20567
+ const handleZoomExit = (0, import_react21.useCallback)((targetTransform) => {
19954
20568
  setPendingHandoff(targetTransform);
19955
20569
  suppressNextHandoffClearRef.current = true;
19956
20570
  navigateToBreadcrumb(breadcrumbs.length - 2);
@@ -19973,30 +20587,58 @@ var SystemCanvas = (() => {
19973
20587
  const rect = svg.getBoundingClientRect();
19974
20588
  return { width: rect.width, height: rect.height };
19975
20589
  },
20590
+ getCursorScreenPos: () => viewportHandleRef.current?.getCursorScreenPos() ?? null,
19976
20591
  onEnter: handleZoomEnter,
19977
20592
  onExit: handleZoomExit
19978
20593
  });
19979
- const handleViewportChange = (0, import_react20.useCallback)((vp) => {
20594
+ const handleViewportChange = (0, import_react21.useCallback)((vp) => {
19980
20595
  viewportStateRef.current = vp;
19981
20596
  handleZoomNavViewportChange(vp);
19982
20597
  onViewportChange?.(vp);
19983
20598
  }, [handleZoomNavViewportChange, onViewportChange]);
19984
- const handleHandoffApplied = (0, import_react20.useCallback)(() => {
20599
+ const handleHandoffApplied = (0, import_react21.useCallback)(() => {
19985
20600
  setPendingHandoff(null);
19986
20601
  clearZoomNavCommitting();
19987
20602
  }, [clearZoomNavCommitting]);
19988
- const handleBeginEdit = (0, import_react20.useCallback)((node) => {
20603
+ const handleBeginEdit = (0, import_react21.useCallback)((node) => {
19989
20604
  setEditingId(node.id);
19990
20605
  }, []);
19991
- const handleBeginEditEdge = (0, import_react20.useCallback)((edge) => {
20606
+ const handleBeginEditEdge = (0, import_react21.useCallback)((edge) => {
19992
20607
  setEditingEdgeId(edge.id);
19993
20608
  }, []);
20609
+ const [contextMenuState, setContextMenuState] = (0, import_react21.useState)(null);
20610
+ (0, import_react21.useEffect)(() => {
20611
+ setContextMenuState(null);
20612
+ }, [currentCanvasRef]);
20613
+ const handleContextMenu = (0, import_react21.useCallback)((event) => {
20614
+ onContextMenu?.(event);
20615
+ if (!nodeContextMenu)
20616
+ return;
20617
+ if (event.type !== "node")
20618
+ return;
20619
+ const node = event.target;
20620
+ if (!node)
20621
+ return;
20622
+ const matched = filterContextMenuItems(nodeContextMenu.items, node, {
20623
+ canvasRef: currentCanvasRef ?? null
20624
+ });
20625
+ if (matched.length === 0) {
20626
+ setContextMenuState(null);
20627
+ return;
20628
+ }
20629
+ setContextMenuState({
20630
+ items: matched,
20631
+ node,
20632
+ screenPosition: event.screenPosition,
20633
+ canvasRef: currentCanvasRef ?? null
20634
+ });
20635
+ }, [onContextMenu, nodeContextMenu, currentCanvasRef]);
19994
20636
  const { handleNodeClick, handleNodeDoubleClick, handleNodeNavigate, handleEdgeClick, handleEdgeDoubleClick, handleCanvasClick, handleCanvasContextMenu, handleNodeContextMenu, handleEdgeContextMenu } = useCanvasInteraction({
19995
20637
  onNodeClick,
19996
20638
  onNodeDoubleClick,
19997
20639
  onEdgeClick,
19998
20640
  onEdgeDoubleClick,
19999
- onContextMenu,
20641
+ onContextMenu: handleContextMenu,
20000
20642
  onNavigableNodeClick: handleNavigableNodeClick,
20001
20643
  viewport: viewportStateRef,
20002
20644
  editable,
@@ -20005,27 +20647,27 @@ var SystemCanvas = (() => {
20005
20647
  onSelectEdge: setSelectedEdgeId,
20006
20648
  onBeginEditEdge: handleBeginEditEdge
20007
20649
  });
20008
- const handleEditorCommit = (0, import_react20.useCallback)((patch) => {
20650
+ const handleEditorCommit = (0, import_react21.useCallback)((patch) => {
20009
20651
  if (editingId) {
20010
20652
  onNodeUpdate?.(editingId, patch, currentCanvasRef);
20011
20653
  }
20012
20654
  setEditingId(null);
20013
20655
  }, [editingId, onNodeUpdate, currentCanvasRef]);
20014
- const handleEditorCancel = (0, import_react20.useCallback)(() => {
20656
+ const handleEditorCancel = (0, import_react21.useCallback)(() => {
20015
20657
  setEditingId(null);
20016
20658
  }, []);
20017
- const handleEdgeEditorCommit = (0, import_react20.useCallback)((patch) => {
20659
+ const handleEdgeEditorCommit = (0, import_react21.useCallback)((patch) => {
20018
20660
  if (editingEdgeId) {
20019
20661
  onEdgeUpdate?.(editingEdgeId, patch, currentCanvasRef);
20020
20662
  }
20021
20663
  setEditingEdgeId(null);
20022
20664
  }, [editingEdgeId, onEdgeUpdate, currentCanvasRef]);
20023
- const handleEdgeEditorCancel = (0, import_react20.useCallback)(() => {
20665
+ const handleEdgeEditorCancel = (0, import_react21.useCallback)(() => {
20024
20666
  setEditingEdgeId(null);
20025
20667
  }, []);
20026
- const lastAddRef = (0, import_react20.useRef)(null);
20027
- const menuOptions = (0, import_react20.useMemo)(() => getNodeMenuOptions(currentCanvas, theme), [currentCanvas, theme]);
20028
- const addNode2 = (0, import_react20.useCallback)((option, position) => {
20668
+ const lastAddRef = (0, import_react21.useRef)(null);
20669
+ const menuOptions = (0, import_react21.useMemo)(() => getNodeMenuOptions(currentCanvas, theme), [currentCanvas, theme]);
20670
+ const addNode2 = (0, import_react21.useCallback)((option, position) => {
20029
20671
  let x, y;
20030
20672
  if (position) {
20031
20673
  x = position.x;
@@ -20057,7 +20699,7 @@ var SystemCanvas = (() => {
20057
20699
  const node = createNodeFromOption(option, Math.round(x), Math.round(y), void 0, theme);
20058
20700
  onNodeAdd?.(node, currentCanvasRef);
20059
20701
  }, [onNodeAdd, currentCanvasRef, theme]);
20060
- const handleKeyDown = (0, import_react20.useCallback)((e) => {
20702
+ const handleKeyDown = (0, import_react21.useCallback)((e) => {
20061
20703
  if (!editable)
20062
20704
  return;
20063
20705
  if (e.key === "Escape") {
@@ -20091,14 +20733,14 @@ var SystemCanvas = (() => {
20091
20733
  currentCanvasRef
20092
20734
  ]);
20093
20735
  const renderProps = { options: menuOptions, addNode: addNode2, theme };
20094
- return (0, import_jsx_runtime27.jsxs)("div", { ref: containerRef, className: `system-canvas ${className ?? ""}`, tabIndex: editable ? 0 : -1, onKeyDown: handleKeyDown, style: {
20736
+ return (0, import_jsx_runtime28.jsxs)("div", { ref: containerRef, className: `system-canvas ${className ?? ""}`, tabIndex: editable ? 0 : -1, onKeyDown: handleKeyDown, style: {
20095
20737
  position: "relative",
20096
20738
  width: "100%",
20097
20739
  height: "100%",
20098
20740
  overflow: "hidden",
20099
20741
  outline: "none",
20100
20742
  ...style
20101
- }, children: [(0, import_jsx_runtime27.jsx)(Breadcrumbs, { breadcrumbs, theme: theme.breadcrumbs, onNavigate: navigateToBreadcrumb }), isLoading && (0, import_jsx_runtime27.jsx)("div", { className: "system-canvas-loading", style: {
20743
+ }, children: [(0, import_jsx_runtime28.jsx)(Breadcrumbs, { breadcrumbs, theme: theme.breadcrumbs, onNavigate: navigateToBreadcrumb }), isLoading && (0, import_jsx_runtime28.jsx)("div", { className: "system-canvas-loading", style: {
20102
20744
  position: "absolute",
20103
20745
  top: 12,
20104
20746
  right: 12,
@@ -20110,12 +20752,12 @@ var SystemCanvas = (() => {
20110
20752
  fontFamily: theme.node.fontFamily,
20111
20753
  fontSize: 12,
20112
20754
  backdropFilter: "blur(8px)"
20113
- }, children: "Loading..." }), (0, import_jsx_runtime27.jsx)(Viewport, { ref: viewportHandleRef, nodes, edges, nodeMap, theme, edgeStyle, columns: currentCanvas.columns, rows: currentCanvas.rows, canvases, minZoom: effectiveMinZoom, maxZoom: effectiveMaxZoom, defaultViewport, autoFit, canvasRef: currentCanvasRef, handoffTransform: pendingHandoff, onHandoffApplied: handleHandoffApplied, handoffFadeMs: zoomNavConfig.fadeDuration, onViewportChange: handleViewportChange, onNodeClick: handleNodeClick, onNodeDoubleClick: handleNodeDoubleClick, onNodeNavigate: handleNodeNavigate, onEdgeClick: handleEdgeClick, onEdgeDoubleClick: handleEdgeDoubleClick, onCanvasClick: editable ? handleCanvasClick : void 0, onCanvasContextMenu: handleCanvasContextMenu, onNodeContextMenu: handleNodeContextMenu, onEdgeContextMenu: handleEdgeContextMenu, onNodePointerDown: editable ? onNodePointerDown : void 0, selectedId: editable ? selectedId : null, editingId: editable ? editingId : null, selectedEdgeId: editable ? selectedEdgeId : null, editingEdgeId: editable ? editingEdgeId : null, dragOverrides, resizeOverrides, onResizeHandlePointerDown: editable ? onResizeHandlePointerDown : void 0, onEditorCommit: handleEditorCommit, onEditorCancel: handleEditorCancel, onEdgeEditorCommit: handleEdgeEditorCommit, onEdgeEditorCancel: handleEdgeEditorCancel, pendingEdge: editable ? pendingEdge : null, onConnectionHandlePointerDown: editable ? onConnectionHandlePointerDown : void 0, edgeCreateEnabled: editable }), showLaneHeaders && (0, import_jsx_runtime27.jsx)(LaneHeaders, { columns: currentCanvas.columns, rows: currentCanvas.rows, theme, getViewport: getViewportState, width: containerSize.width, height: containerSize.height, pinned: laneHeaders === "pinned" }), editable && showNodeToolbar && selectedResolvedNode && !editingId && (0, import_jsx_runtime27.jsx)(NodeToolbar, { node: selectedResolvedNode, theme, onPatch: (update) => {
20755
+ }, children: "Loading..." }), (0, import_jsx_runtime28.jsx)(Viewport, { ref: viewportHandleRef, nodes, edges, nodeMap, theme, edgeStyle, columns: currentCanvas.columns, rows: currentCanvas.rows, canvases, minZoom: effectiveMinZoom, maxZoom: effectiveMaxZoom, defaultViewport, autoFit, canvasRef: currentCanvasRef, handoffTransform: pendingHandoff, onHandoffApplied: handleHandoffApplied, handoffFadeMs: zoomNavConfig.fadeDuration, onViewportChange: handleViewportChange, onNodeClick: handleNodeClick, onNodeDoubleClick: handleNodeDoubleClick, onNodeNavigate: handleNodeNavigate, onEdgeClick: handleEdgeClick, onEdgeDoubleClick: handleEdgeDoubleClick, onCanvasClick: editable ? handleCanvasClick : void 0, onCanvasContextMenu: handleCanvasContextMenu, onNodeContextMenu: handleNodeContextMenu, onEdgeContextMenu: handleEdgeContextMenu, onNodePointerDown: editable ? onNodePointerDown : void 0, selectedId: editable ? selectedId : null, editingId: editable ? editingId : null, selectedEdgeId: editable ? selectedEdgeId : null, editingEdgeId: editable ? editingEdgeId : null, dragOverrides, dropTargetId, resizeOverrides, onResizeHandlePointerDown: editable ? onResizeHandlePointerDown : void 0, onEditorCommit: handleEditorCommit, onEditorCancel: handleEditorCancel, onEdgeEditorCommit: handleEdgeEditorCommit, onEdgeEditorCancel: handleEdgeEditorCancel, pendingEdge: editable ? pendingEdge : null, onConnectionHandlePointerDown: editable ? onConnectionHandlePointerDown : void 0, edgeCreateEnabled: editable }), showLaneHeaders && (0, import_jsx_runtime28.jsx)(LaneHeaders, { columns: currentCanvas.columns, rows: currentCanvas.rows, theme, getViewport: getViewportState, width: containerSize.width, height: containerSize.height, pinned: laneHeaders === "pinned" }), editable && showNodeToolbar && selectedResolvedNode && !editingId && (0, import_jsx_runtime28.jsx)(NodeToolbar, { node: selectedResolvedNode, theme, onPatch: (update) => {
20114
20756
  onNodeUpdate?.(selectedResolvedNode.id, update, currentCanvasRef);
20115
20757
  }, onDelete: () => {
20116
20758
  onNodeDelete?.(selectedResolvedNode.id, currentCanvasRef);
20117
20759
  setSelectedId(null);
20118
- }, getViewport: getViewportState, containerWidth: containerSize.width, containerHeight: containerSize.height, render: renderNodeToolbar }), editable && (renderAddNodeButton ? renderAddNodeButton(renderProps) : (0, import_jsx_runtime27.jsx)(AddNodeButton, { ...renderProps }))] });
20760
+ }, getViewport: getViewportState, containerWidth: containerSize.width, containerHeight: containerSize.height, render: renderNodeToolbar }), editable && (renderAddNodeButton ? renderAddNodeButton(renderProps) : (0, import_jsx_runtime28.jsx)(AddNodeButton, { ...renderProps })), nodeContextMenu && (0, import_jsx_runtime28.jsx)(NodeContextMenuOverlay, { state: contextMenuState, config: nodeContextMenu, theme, onClose: () => setContextMenuState(null) })] });
20119
20761
  });
20120
20762
 
20121
20763
  // src/index.tsx
@@ -20226,7 +20868,7 @@ var SystemCanvas = (() => {
20226
20868
  onEdgeUpdate: handleEdgeUpdate,
20227
20869
  onEdgeDelete: handleEdgeDelete
20228
20870
  };
20229
- root2.render(import_react21.default.createElement(SystemCanvas, props));
20871
+ root2.render(import_react22.default.createElement(SystemCanvas, props));
20230
20872
  };
20231
20873
  doRender();
20232
20874
  return {