@elabs-ai/components-flow 4.2.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +2 -1
  2. package/dist/canvas-shell/canvas-shell.js +13 -0
  3. package/dist/canvas-shell/canvas-shell.js.map +1 -0
  4. package/dist/canvas-shell/clamped-fit-offset.js +8 -0
  5. package/dist/canvas-shell/clamped-fit-offset.js.map +1 -0
  6. package/dist/canvas-shell/index.js +14 -0
  7. package/dist/canvas-shell/index.js.map +1 -0
  8. package/dist/canvas-shell/use-measured-nodes.js +8 -0
  9. package/dist/canvas-shell/use-measured-nodes.js.map +1 -0
  10. package/dist/chunk-3BTU2VWZ.js +42 -0
  11. package/dist/chunk-3BTU2VWZ.js.map +1 -0
  12. package/dist/chunk-3LK6OOXE.js +2 -0
  13. package/dist/chunk-3LK6OOXE.js.map +1 -0
  14. package/dist/chunk-3M3FQAD4.js +105 -0
  15. package/dist/chunk-3M3FQAD4.js.map +1 -0
  16. package/dist/chunk-3Y5TCGC3.js +2 -0
  17. package/dist/chunk-3Y5TCGC3.js.map +1 -0
  18. package/dist/chunk-4VYHZM3S.js +108 -0
  19. package/dist/chunk-4VYHZM3S.js.map +1 -0
  20. package/dist/chunk-5TVVM4GR.js +45 -0
  21. package/dist/chunk-5TVVM4GR.js.map +1 -0
  22. package/dist/chunk-5XT73MQQ.js +2 -0
  23. package/dist/chunk-5XT73MQQ.js.map +1 -0
  24. package/dist/chunk-6LQD5Y5R.js +24 -0
  25. package/dist/chunk-6LQD5Y5R.js.map +1 -0
  26. package/dist/chunk-6SR26QY2.js +18 -0
  27. package/dist/chunk-6SR26QY2.js.map +1 -0
  28. package/dist/chunk-75JJK6BH.js +157 -0
  29. package/dist/chunk-75JJK6BH.js.map +1 -0
  30. package/dist/chunk-A66JBW4X.js +2 -0
  31. package/dist/chunk-A66JBW4X.js.map +1 -0
  32. package/dist/chunk-A7AFM3LI.js +72 -0
  33. package/dist/chunk-A7AFM3LI.js.map +1 -0
  34. package/dist/chunk-ADHLQF56.js +2 -0
  35. package/dist/chunk-ADHLQF56.js.map +1 -0
  36. package/dist/chunk-ATO4LXJF.js +75 -0
  37. package/dist/chunk-ATO4LXJF.js.map +1 -0
  38. package/dist/chunk-ATXVXTMZ.js +227 -0
  39. package/dist/chunk-ATXVXTMZ.js.map +1 -0
  40. package/dist/chunk-B6IUPZU7.js +2 -0
  41. package/dist/chunk-B6IUPZU7.js.map +1 -0
  42. package/dist/chunk-BJ7UMCD3.js +53 -0
  43. package/dist/chunk-BJ7UMCD3.js.map +1 -0
  44. package/dist/chunk-BWGYV4JZ.js +2 -0
  45. package/dist/chunk-BWGYV4JZ.js.map +1 -0
  46. package/dist/chunk-CWNO63LQ.js +109 -0
  47. package/dist/chunk-CWNO63LQ.js.map +1 -0
  48. package/dist/chunk-DHPUXKCZ.js +76 -0
  49. package/dist/chunk-DHPUXKCZ.js.map +1 -0
  50. package/dist/chunk-DNX7G3KA.js +185 -0
  51. package/dist/chunk-DNX7G3KA.js.map +1 -0
  52. package/dist/chunk-DNZRAJAQ.js +41 -0
  53. package/dist/chunk-DNZRAJAQ.js.map +1 -0
  54. package/dist/chunk-ELCLRVD4.js +2 -0
  55. package/dist/chunk-ELCLRVD4.js.map +1 -0
  56. package/dist/chunk-ELOADZSX.js +2 -0
  57. package/dist/chunk-ELOADZSX.js.map +1 -0
  58. package/dist/chunk-EUNV2MR5.js +21 -0
  59. package/dist/chunk-EUNV2MR5.js.map +1 -0
  60. package/dist/chunk-FAFQ5KLB.js +43 -0
  61. package/dist/chunk-FAFQ5KLB.js.map +1 -0
  62. package/dist/chunk-FIRO6Y3R.js +128 -0
  63. package/dist/chunk-FIRO6Y3R.js.map +1 -0
  64. package/dist/chunk-GE73WQUK.js +2 -0
  65. package/dist/chunk-GE73WQUK.js.map +1 -0
  66. package/dist/chunk-GIARAOU6.js +39 -0
  67. package/dist/chunk-GIARAOU6.js.map +1 -0
  68. package/dist/chunk-HTLTCCFA.js +73 -0
  69. package/dist/chunk-HTLTCCFA.js.map +1 -0
  70. package/dist/chunk-I53W3CFN.js +56 -0
  71. package/dist/chunk-I53W3CFN.js.map +1 -0
  72. package/dist/chunk-IJVFPUXO.js +2 -0
  73. package/dist/chunk-IJVFPUXO.js.map +1 -0
  74. package/dist/chunk-JUPMOISE.js +51 -0
  75. package/dist/chunk-JUPMOISE.js.map +1 -0
  76. package/dist/chunk-K5RPSVO6.js +167 -0
  77. package/dist/chunk-K5RPSVO6.js.map +1 -0
  78. package/dist/chunk-K6U27YJ6.js +78 -0
  79. package/dist/chunk-K6U27YJ6.js.map +1 -0
  80. package/dist/chunk-KDVMGMAK.js +2 -0
  81. package/dist/chunk-KDVMGMAK.js.map +1 -0
  82. package/dist/chunk-LQOZCOK3.js +74 -0
  83. package/dist/chunk-LQOZCOK3.js.map +1 -0
  84. package/dist/chunk-NAQWNX2A.js +102 -0
  85. package/dist/chunk-NAQWNX2A.js.map +1 -0
  86. package/dist/chunk-NVJVWGUS.js +152 -0
  87. package/dist/chunk-NVJVWGUS.js.map +1 -0
  88. package/dist/chunk-O2F3R3PV.js +2 -0
  89. package/dist/chunk-O2F3R3PV.js.map +1 -0
  90. package/dist/chunk-OMFQHMA6.js +2 -0
  91. package/dist/chunk-OMFQHMA6.js.map +1 -0
  92. package/dist/chunk-PE6JXVOX.js +2 -0
  93. package/dist/chunk-PE6JXVOX.js.map +1 -0
  94. package/dist/chunk-ROJEIIA4.js +52 -0
  95. package/dist/chunk-ROJEIIA4.js.map +1 -0
  96. package/dist/chunk-ROYAQKJA.js +9 -0
  97. package/dist/chunk-ROYAQKJA.js.map +1 -0
  98. package/dist/chunk-SHCZK4DO.js +45 -0
  99. package/dist/chunk-SHCZK4DO.js.map +1 -0
  100. package/dist/chunk-SXXJV6YJ.js +79 -0
  101. package/dist/chunk-SXXJV6YJ.js.map +1 -0
  102. package/dist/chunk-T7S7NJFD.js +287 -0
  103. package/dist/chunk-T7S7NJFD.js.map +1 -0
  104. package/dist/chunk-TEZ3OXAY.js +63 -0
  105. package/dist/chunk-TEZ3OXAY.js.map +1 -0
  106. package/dist/chunk-TGWPM2CG.js +199 -0
  107. package/dist/chunk-TGWPM2CG.js.map +1 -0
  108. package/dist/chunk-TSB6RE7L.js +2 -0
  109. package/dist/chunk-TSB6RE7L.js.map +1 -0
  110. package/dist/chunk-UPV2MT4O.js +2 -0
  111. package/dist/chunk-UPV2MT4O.js.map +1 -0
  112. package/dist/chunk-UZJZY4A5.js +2 -0
  113. package/dist/chunk-UZJZY4A5.js.map +1 -0
  114. package/dist/chunk-W3TQMUGX.js +2 -0
  115. package/dist/chunk-W3TQMUGX.js.map +1 -0
  116. package/dist/chunk-WN46RIOC.js +29 -0
  117. package/dist/chunk-WN46RIOC.js.map +1 -0
  118. package/dist/chunk-XZFE2YYH.js +70 -0
  119. package/dist/chunk-XZFE2YYH.js.map +1 -0
  120. package/dist/chunk-YG3KXTZW.js +2 -0
  121. package/dist/chunk-YG3KXTZW.js.map +1 -0
  122. package/dist/chunk-ZATVF42E.js +65 -0
  123. package/dist/chunk-ZATVF42E.js.map +1 -0
  124. package/dist/chunk-ZXZTLQIX.js +19 -0
  125. package/dist/chunk-ZXZTLQIX.js.map +1 -0
  126. package/dist/flow-button-edge/flow-button-edge.js +10 -0
  127. package/dist/flow-button-edge/flow-button-edge.js.map +1 -0
  128. package/dist/flow-button-edge/index.js +11 -0
  129. package/dist/flow-button-edge/index.js.map +1 -0
  130. package/dist/flow-edge/flow-edge.js +10 -0
  131. package/dist/flow-edge/flow-edge.js.map +1 -0
  132. package/dist/flow-edge/index.js +11 -0
  133. package/dist/flow-edge/index.js.map +1 -0
  134. package/dist/flow-edge-path/flow-edge-path.js +12 -0
  135. package/dist/flow-edge-path/flow-edge-path.js.map +1 -0
  136. package/dist/flow-edge-path/index.js +13 -0
  137. package/dist/flow-edge-path/index.js.map +1 -0
  138. package/dist/flow-edge-tokens/edge-path-sampler.js +10 -0
  139. package/dist/flow-edge-tokens/edge-path-sampler.js.map +1 -0
  140. package/dist/flow-edge-tokens/flow-edge-tokens.js +12 -0
  141. package/dist/flow-edge-tokens/flow-edge-tokens.js.map +1 -0
  142. package/dist/flow-edge-tokens/index.js +17 -0
  143. package/dist/flow-edge-tokens/index.js.map +1 -0
  144. package/dist/flow-floating-edge/floating-edge-geometry.js +8 -0
  145. package/dist/flow-floating-edge/floating-edge-geometry.js.map +1 -0
  146. package/dist/flow-floating-edge/flow-floating-edge.js +11 -0
  147. package/dist/flow-floating-edge/flow-floating-edge.js.map +1 -0
  148. package/dist/flow-floating-edge/index.js +15 -0
  149. package/dist/flow-floating-edge/index.js.map +1 -0
  150. package/dist/flow-group-node/flow-group-node.js +12 -0
  151. package/dist/flow-group-node/flow-group-node.js.map +1 -0
  152. package/dist/flow-group-node/index.js +13 -0
  153. package/dist/flow-group-node/index.js.map +1 -0
  154. package/dist/flow-handle/flow-handle-anchor.js +8 -0
  155. package/dist/flow-handle/flow-handle-anchor.js.map +1 -0
  156. package/dist/flow-handle/index.js +9 -0
  157. package/dist/flow-handle/index.js.map +1 -0
  158. package/dist/flow-layout/backbone.js +8 -0
  159. package/dist/flow-layout/backbone.js.map +1 -0
  160. package/dist/flow-layout/flow-layout.js +10 -0
  161. package/dist/flow-layout/flow-layout.js.map +1 -0
  162. package/dist/flow-layout/index.js +21 -0
  163. package/dist/flow-layout/index.js.map +1 -0
  164. package/dist/flow-layout/layout-flow-elk.js +9 -0
  165. package/dist/flow-layout/layout-flow-elk.js.map +1 -0
  166. package/dist/flow-layout/layout-graph.js +9 -0
  167. package/dist/flow-layout/layout-graph.js.map +1 -0
  168. package/dist/flow-layout/use-auto-layout.js +10 -0
  169. package/dist/flow-layout/use-auto-layout.js.map +1 -0
  170. package/dist/flow-layout/use-flow-layout.js +9 -0
  171. package/dist/flow-layout/use-flow-layout.js.map +1 -0
  172. package/dist/flow-mini-map/flow-mini-map.js +8 -0
  173. package/dist/flow-mini-map/flow-mini-map.js.map +1 -0
  174. package/dist/flow-mini-map/index.js +9 -0
  175. package/dist/flow-mini-map/index.js.map +1 -0
  176. package/dist/flow-node/flow-node.js +11 -0
  177. package/dist/flow-node/flow-node.js.map +1 -0
  178. package/dist/flow-node/index.js +12 -0
  179. package/dist/flow-node/index.js.map +1 -0
  180. package/dist/flow-placeholder-node/flow-placeholder-node.js +9 -0
  181. package/dist/flow-placeholder-node/flow-placeholder-node.js.map +1 -0
  182. package/dist/flow-placeholder-node/index.js +10 -0
  183. package/dist/flow-placeholder-node/index.js.map +1 -0
  184. package/dist/flow-self-loop-edge/flow-self-loop-edge.js +20 -0
  185. package/dist/flow-self-loop-edge/flow-self-loop-edge.js.map +1 -0
  186. package/dist/flow-self-loop-edge/index.js +28 -0
  187. package/dist/flow-self-loop-edge/index.js.map +1 -0
  188. package/dist/flow-self-loop-edge/self-loop-geometry.js +12 -0
  189. package/dist/flow-self-loop-edge/self-loop-geometry.js.map +1 -0
  190. package/dist/flow-smart-edge/flow-smart-edge.js +11 -0
  191. package/dist/flow-smart-edge/flow-smart-edge.js.map +1 -0
  192. package/dist/flow-smart-edge/index.js +25 -0
  193. package/dist/flow-smart-edge/index.js.map +1 -0
  194. package/dist/flow-smart-edge/smart-edge-geometry.js +18 -0
  195. package/dist/flow-smart-edge/smart-edge-geometry.js.map +1 -0
  196. package/dist/flow-weighted-edge/back-edge-geometry.js +8 -0
  197. package/dist/flow-weighted-edge/back-edge-geometry.js.map +1 -0
  198. package/dist/flow-weighted-edge/edge-aria.js +10 -0
  199. package/dist/flow-weighted-edge/edge-aria.js.map +1 -0
  200. package/dist/flow-weighted-edge/edge-label-pill.js +8 -0
  201. package/dist/flow-weighted-edge/edge-label-pill.js.map +1 -0
  202. package/dist/flow-weighted-edge/flow-weighted-edge.js +16 -0
  203. package/dist/flow-weighted-edge/flow-weighted-edge.js.map +1 -0
  204. package/dist/flow-weighted-edge/index.js +34 -0
  205. package/dist/flow-weighted-edge/index.js.map +1 -0
  206. package/dist/flow-weighted-edge/weight-scale.js +10 -0
  207. package/dist/flow-weighted-edge/weight-scale.js.map +1 -0
  208. package/dist/helper-lines/get-helper-lines.js +8 -0
  209. package/dist/helper-lines/get-helper-lines.js.map +1 -0
  210. package/dist/helper-lines/helper-lines.js +9 -0
  211. package/dist/helper-lines/helper-lines.js.map +1 -0
  212. package/dist/helper-lines/index.js +17 -0
  213. package/dist/helper-lines/index.js.map +1 -0
  214. package/dist/helper-lines/use-helper-lines.js +10 -0
  215. package/dist/helper-lines/use-helper-lines.js.map +1 -0
  216. package/dist/index.d.ts +237 -2
  217. package/dist/index.js +186 -2308
  218. package/dist/index.js.map +1 -1
  219. package/dist/inspector-panel/index.js +9 -0
  220. package/dist/inspector-panel/index.js.map +1 -0
  221. package/dist/inspector-panel/inspector-panel.js +8 -0
  222. package/dist/inspector-panel/inspector-panel.js.map +1 -0
  223. package/dist/legend/index.js +10 -0
  224. package/dist/legend/index.js.map +1 -0
  225. package/dist/legend/legend.js +9 -0
  226. package/dist/legend/legend.js.map +1 -0
  227. package/dist/use-flow-groups/group-operations.js +20 -0
  228. package/dist/use-flow-groups/group-operations.js.map +1 -0
  229. package/dist/use-flow-groups/index.js +25 -0
  230. package/dist/use-flow-groups/index.js.map +1 -0
  231. package/dist/use-flow-groups/use-flow-groups.js +9 -0
  232. package/dist/use-flow-groups/use-flow-groups.js.map +1 -0
  233. package/dist/zoom-controls/index.js +9 -0
  234. package/dist/zoom-controls/index.js.map +1 -0
  235. package/dist/zoom-controls/zoom-controls.js +8 -0
  236. package/dist/zoom-controls/zoom-controls.js.map +1 -0
  237. package/package.json +13 -6
  238. package/src/canvas-shell/canvas-shell.stories.tsx +1 -0
  239. package/src/canvas-shell/canvas-shell.tsx +51 -4
  240. package/src/canvas-shell/clamped-fit-offset.test.ts +25 -0
  241. package/src/canvas-shell/clamped-fit-offset.ts +30 -0
  242. package/src/custom-nodes/custom-nodes.stories.tsx +719 -0
  243. package/src/flow-edge-tokens/edge-path-sampler.test.ts +108 -0
  244. package/src/flow-edge-tokens/edge-path-sampler.ts +362 -0
  245. package/src/flow-edge-tokens/flow-edge-tokens.stories.tsx +312 -0
  246. package/src/flow-edge-tokens/flow-edge-tokens.test.tsx +115 -0
  247. package/src/flow-edge-tokens/flow-edge-tokens.tsx +94 -0
  248. package/src/flow-edge-tokens/index.ts +12 -0
  249. package/src/flow-layout/backbone.test.ts +88 -0
  250. package/src/flow-layout/backbone.ts +58 -0
  251. package/src/flow-layout/layout-flow-elk.stories.tsx +154 -0
  252. package/src/flow-layout/layout-flow-elk.test.ts +152 -0
  253. package/src/flow-layout/layout-flow-elk.ts +399 -0
  254. package/src/flow-mini-map/flow-mini-map.stories.tsx +10 -7
  255. package/src/flow-node/flow-node.stories.tsx +40 -1
  256. package/src/flow-self-loop-edge/flow-self-loop-edge.test.tsx +38 -0
  257. package/src/flow-self-loop-edge/flow-self-loop-edge.tsx +7 -0
  258. package/src/flow-weighted-edge/edge-label-pill.test.tsx +20 -0
  259. package/src/flow-weighted-edge/flow-weighted-edge.stories.tsx +1 -0
  260. package/src/flow-weighted-edge/flow-weighted-edge.test.tsx +40 -0
  261. package/src/flow-weighted-edge/flow-weighted-edge.tsx +7 -0
  262. package/src/index.ts +31 -0
  263. package/src/templates-flow-workspace.stories.tsx +7 -2
@@ -0,0 +1,157 @@
1
+ "use client";
2
+ import {
3
+ useHelperLines
4
+ } from "./chunk-ZATVF42E.js";
5
+ import {
6
+ HelperLines
7
+ } from "./chunk-BJ7UMCD3.js";
8
+ import {
9
+ clampedFitOffset
10
+ } from "./chunk-EUNV2MR5.js";
11
+ import {
12
+ useMeasuredNodes
13
+ } from "./chunk-JUPMOISE.js";
14
+
15
+ // src/canvas-shell/canvas-shell.tsx
16
+ import { useEffect } from "react";
17
+ import {
18
+ Background,
19
+ ReactFlow,
20
+ ReactFlowProvider,
21
+ useNodesInitialized,
22
+ useReactFlow,
23
+ useStoreApi
24
+ } from "@xyflow/react";
25
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
26
+ import { jsx, jsxs } from "react/jsx-runtime";
27
+ var DEFAULT_ARIA_LABEL_CONFIG = {
28
+ "node.a11yDescription.default": "Press Enter or Space to select this node. Press Delete to remove it, Escape to cancel.",
29
+ "node.a11yDescription.keyboardDisabled": "Press Enter or Space to select this node. Then use the arrow keys to move it. Press Delete to remove it, Escape to cancel.",
30
+ "edge.a11yDescription.default": "Press Enter or Space to select this edge. Press Delete to remove it, Escape to cancel.",
31
+ "controls.ariaLabel": "Canvas controls",
32
+ "controls.zoomIn.ariaLabel": "Zoom in",
33
+ "controls.zoomOut.ariaLabel": "Zoom out",
34
+ "controls.fitView.ariaLabel": "Fit view",
35
+ "controls.interactive.ariaLabel": "Toggle interactivity",
36
+ "minimap.ariaLabel": "Minimap",
37
+ "handle.ariaLabel": "Connection handle"
38
+ };
39
+ function CanvasShell({
40
+ helperLines = false,
41
+ ...props
42
+ }) {
43
+ if (helperLines) {
44
+ return /* @__PURE__ */ jsx(ReactFlowProvider, { children: /* @__PURE__ */ jsx(CanvasShellWithHelperLines, { ...props }) });
45
+ }
46
+ return /* @__PURE__ */ jsx(CanvasShellBase, { ...props });
47
+ }
48
+ function CanvasShellBase({
49
+ background = true,
50
+ children,
51
+ className,
52
+ ariaLabelConfig,
53
+ nodes,
54
+ onNodesChange,
55
+ fitViewKey,
56
+ fitViewKeyOptions,
57
+ fitViewAnchorNodeIds,
58
+ ...props
59
+ }) {
60
+ const measured = useMeasuredNodes(nodes, onNodesChange);
61
+ return /* @__PURE__ */ jsx("div", { className: cn("h-full w-full bg-canvas", className), children: /* @__PURE__ */ jsxs(
62
+ ReactFlow,
63
+ {
64
+ fitView: true,
65
+ proOptions: { hideAttribution: true },
66
+ ariaLabelConfig: { ...DEFAULT_ARIA_LABEL_CONFIG, ...ariaLabelConfig },
67
+ nodes: measured.nodes,
68
+ onNodesChange: measured.onNodesChange,
69
+ ...props,
70
+ children: [
71
+ background ? /* @__PURE__ */ jsx(Background, { gap: 20, size: 1, color: "var(--canvas-grid)" }) : null,
72
+ fitViewKey === void 0 ? null : /* @__PURE__ */ jsx(
73
+ FitViewOnKey,
74
+ {
75
+ fitViewKey,
76
+ options: fitViewKeyOptions,
77
+ anchorNodeIds: fitViewAnchorNodeIds
78
+ }
79
+ ),
80
+ children
81
+ ]
82
+ }
83
+ ) });
84
+ }
85
+ function FitViewOnKey({
86
+ fitViewKey,
87
+ options,
88
+ anchorNodeIds
89
+ }) {
90
+ const { fitView, getNodes, getNodesBounds, setViewport } = useReactFlow();
91
+ const store = useStoreApi();
92
+ const nodesInitialized = useNodesInitialized();
93
+ useEffect(() => {
94
+ if (!nodesInitialized) return;
95
+ void fitView(options).then(
96
+ () => anchorToStartWhenClamped(
97
+ store,
98
+ setViewport,
99
+ getNodesBounds,
100
+ getNodes(),
101
+ options,
102
+ anchorNodeIds
103
+ )
104
+ );
105
+ }, [fitViewKey, nodesInitialized, fitView, getNodes, getNodesBounds, setViewport, store]);
106
+ return null;
107
+ }
108
+ function anchorToStartWhenClamped(store, setViewport, getNodesBounds, nodes, options, anchorNodeIds) {
109
+ if (nodes.length === 0) return;
110
+ const { width, height, transform, panZoom } = store.getState();
111
+ const [x, y, zoom] = transform;
112
+ if (!width || !height || !zoom) return;
113
+ const bounds = getNodesBounds(nodes);
114
+ const padding = typeof options?.padding === "number" ? options.padding : 0.1;
115
+ const padX = width * padding;
116
+ const padY = height * padding;
117
+ const overflowsX = bounds.width * zoom > width - padX * 2 + 1;
118
+ const overflowsY = bounds.height * zoom > height - padY * 2 + 1;
119
+ if (!overflowsX && !overflowsY) return;
120
+ const anchors = anchorNodeIds?.length ? nodes.filter((node) => anchorNodeIds.includes(node.id)) : [];
121
+ const anchor = anchors.length > 0 ? getNodesBounds(anchors) : null;
122
+ const along = (size, pad, start, extent, centre) => clampedFitOffset({
123
+ size,
124
+ pad,
125
+ start,
126
+ extent,
127
+ zoom,
128
+ anchorCentre: anchor === null ? void 0 : centre
129
+ });
130
+ const next = {
131
+ x: overflowsX ? along(width, padX, bounds.x, bounds.width, (anchor?.x ?? 0) + (anchor?.width ?? 0) / 2) : x,
132
+ y: overflowsY ? along(height, padY, bounds.y, bounds.height, (anchor?.y ?? 0) + (anchor?.height ?? 0) / 2) : y,
133
+ zoom
134
+ };
135
+ if (!panZoom) return;
136
+ void setViewport(next);
137
+ }
138
+ function CanvasShellWithHelperLines({
139
+ onNodesChange,
140
+ children,
141
+ ...props
142
+ }) {
143
+ const {
144
+ onNodesChange: wrappedOnNodesChange,
145
+ helperLineHorizontal,
146
+ helperLineVertical
147
+ } = useHelperLines(onNodesChange);
148
+ return /* @__PURE__ */ jsxs(CanvasShellBase, { ...props, onNodesChange: wrappedOnNodesChange, children: [
149
+ children,
150
+ /* @__PURE__ */ jsx(HelperLines, { horizontal: helperLineHorizontal, vertical: helperLineVertical })
151
+ ] });
152
+ }
153
+
154
+ export {
155
+ CanvasShell
156
+ };
157
+ //# sourceMappingURL=chunk-75JJK6BH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas-shell/canvas-shell.tsx"],"sourcesContent":["import { useEffect, type ReactNode } from \"react\";\nimport {\n Background,\n ReactFlow,\n ReactFlowProvider,\n useNodesInitialized,\n useReactFlow,\n useStoreApi,\n type AriaLabelConfig,\n type Edge,\n type FitViewOptions,\n type Node,\n type ReactFlowProps,\n} from \"@xyflow/react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\nimport { HelperLines } from \"../helper-lines/helper-lines\";\nimport { useHelperLines } from \"../helper-lines/use-helper-lines\";\nimport { clampedFitOffset } from \"./clamped-fit-offset\";\nimport { useMeasuredNodes } from \"./use-measured-nodes\";\n\nexport interface CanvasShellProps<\n NodeType extends Node = Node,\n EdgeType extends Edge = Edge,\n> extends ReactFlowProps<NodeType, EdgeType> {\n /** Render a branded dotted background grid. Defaults to true. */\n background?: boolean;\n /**\n * Enable alignment guides + snapping while a single node is dragged. Off by\n * default; when false, CanvasShell behaves exactly as before. When true,\n * CanvasShell wires `useHelperLines` around your `onNodesChange` and renders\n * the `<HelperLines>` overlay internally — pass `nodes` + `onNodesChange`\n * (the standard controlled setup, e.g. via `useNodesState`). For full control\n * use the exported `useHelperLines` + `<HelperLines>` directly.\n */\n helperLines?: boolean;\n /**\n * Re-fit the viewport whenever this value changes.\n *\n * React Flow's own `fitView` prop fits ONCE, as soon as the nodes are first measured,\n * and then never again. A canvas whose positions arrive AFTER that first paint —\n * anything laid out asynchronously, e.g. a debounced `layoutFlow` pass — therefore gets\n * fitted to the pre-layout positions (typically every node still stacked at the origin)\n * and stays there. Measured on the process map: the viewport pinned at React Flow's\n * `maxZoom` of 2 with 2 of 11 nodes on screen, while the layout underneath was correct\n * the whole time.\n *\n * Pass the key the layout is cached on (structure + direction), NOT something that\n * changes on every render and not a metric: a re-fit is a viewport jump, so it must\n * happen when the picture genuinely moved and never while the reader is reading. The\n * fit waits for `useNodesInitialized`, so it measures real node boxes rather than\n * fitting a degenerate bounding box a second time.\n */\n fitViewKey?: string | number;\n /** Options for the {@link fitViewKey} re-fit. Ignored without one. */\n fitViewKeyOptions?: FitViewOptions;\n /**\n * Which nodes a CLAMPED {@link fitViewKey} re-fit keeps in view — typically where the\n * diagram begins. A fit that cannot shrink far enough (a `minZoom` floor) is pinned to the\n * content's top-left corner by default, which is where a top-down layout starts; a\n * left-to-right layout starts at the left edge but at mid-height, so pinning to the top\n * opens on an empty corner. With anchors, each overflowing axis centres the anchor nodes\n * instead, without ever scrolling past the content's own edge. Unset: today's corner pin.\n */\n fitViewAnchorNodeIds?: readonly string[];\n /** Overlays rendered inside the flow (ZoomControls, Legend, Panels). */\n children?: ReactNode;\n className?: string;\n}\n\ntype CanvasShellInnerProps<NodeType extends Node, EdgeType extends Edge> = Omit<\n CanvasShellProps<NodeType, EdgeType>,\n \"helperLines\"\n>;\n\n/**\n * Branded defaults for React Flow's `ariaLabelConfig` (12.7+) — sentence-case\n * labels that match the rest of brand-ui's voice (see `ZoomControls`'\n * \"Zoom in\"/\"Zoom out\"/\"Fit view\"), instead of the library's Title Case\n * defaults. Callers can override any key; unset keys keep these defaults, and\n * React Flow itself fills in any key neither side sets.\n */\nconst DEFAULT_ARIA_LABEL_CONFIG: Partial<AriaLabelConfig> = {\n \"node.a11yDescription.default\":\n \"Press Enter or Space to select this node. Press Delete to remove it, Escape to cancel.\",\n \"node.a11yDescription.keyboardDisabled\":\n \"Press Enter or Space to select this node. Then use the arrow keys to move it. Press Delete to remove it, Escape to cancel.\",\n \"edge.a11yDescription.default\":\n \"Press Enter or Space to select this edge. Press Delete to remove it, Escape to cancel.\",\n \"controls.ariaLabel\": \"Canvas controls\",\n \"controls.zoomIn.ariaLabel\": \"Zoom in\",\n \"controls.zoomOut.ariaLabel\": \"Zoom out\",\n \"controls.fitView.ariaLabel\": \"Fit view\",\n \"controls.interactive.ariaLabel\": \"Toggle interactivity\",\n \"minimap.ariaLabel\": \"Minimap\",\n \"handle.ariaLabel\": \"Connection handle\",\n};\n\n/**\n * Branded React Flow canvas. Sets a token-driven background + sensible\n * defaults; pass nodes/edges/handlers through as normal React Flow props.\n *\n * Consumers MUST import the React Flow stylesheet once at the app root:\n * import \"@xyflow/react/dist/style.css\";\n *\n * Children render inside the flow context, so <ZoomControls /> and React Flow\n * <Panel>s work when placed here.\n */\nexport function CanvasShell<NodeType extends Node = Node, EdgeType extends Edge = Edge>({\n helperLines = false,\n ...props\n}: CanvasShellProps<NodeType, EdgeType>) {\n if (helperLines) {\n // A provider is needed so `useHelperLines` can read the live node store; the\n // ReactFlow rendered below connects to this same provider.\n return (\n <ReactFlowProvider>\n <CanvasShellWithHelperLines<NodeType, EdgeType> {...props} />\n </ReactFlowProvider>\n );\n }\n return <CanvasShellBase<NodeType, EdgeType> {...props} />;\n}\n\nfunction CanvasShellBase<NodeType extends Node, EdgeType extends Edge>({\n background = true,\n children,\n className,\n ariaLabelConfig,\n nodes,\n onNodesChange,\n fitViewKey,\n fitViewKeyOptions,\n fitViewAnchorNodeIds,\n ...props\n}: CanvasShellInnerProps<NodeType, EdgeType>) {\n const measured = useMeasuredNodes<NodeType>(nodes, onNodesChange);\n return (\n <div className={cn(\"h-full w-full bg-canvas\", className)}>\n <ReactFlow\n fitView\n proOptions={{ hideAttribution: true }}\n ariaLabelConfig={{ ...DEFAULT_ARIA_LABEL_CONFIG, ...ariaLabelConfig }}\n nodes={measured.nodes}\n onNodesChange={measured.onNodesChange}\n {...props}\n >\n {background ? <Background gap={20} size={1} color=\"var(--canvas-grid)\" /> : null}\n {fitViewKey === undefined ? null : (\n <FitViewOnKey\n fitViewKey={fitViewKey}\n options={fitViewKeyOptions}\n anchorNodeIds={fitViewAnchorNodeIds}\n />\n )}\n {children}\n </ReactFlow>\n </div>\n );\n}\n\n/**\n * Re-fits the viewport when `fitViewKey` changes and the nodes have been measured.\n *\n * Rendered as a CHILD of `<ReactFlow>` because `useReactFlow`/`useNodesInitialized` need\n * the flow context, which only exists below it. It renders nothing.\n */\nfunction FitViewOnKey({\n fitViewKey,\n options,\n anchorNodeIds,\n}: {\n fitViewKey: string | number;\n options?: FitViewOptions;\n anchorNodeIds?: readonly string[];\n}) {\n const { fitView, getNodes, getNodesBounds, setViewport } = useReactFlow();\n const store = useStoreApi();\n const nodesInitialized = useNodesInitialized();\n useEffect(() => {\n if (!nodesInitialized) return;\n void fitView(options).then(() =>\n anchorToStartWhenClamped(\n store,\n setViewport,\n getNodesBounds,\n getNodes(),\n options,\n anchorNodeIds,\n ),\n );\n // `options` is deliberately absent: an inline object literal would re-fit on every\n // render, which is a viewport jump under the reader's cursor. The key is the trigger.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fitViewKey, nodesInitialized, fitView, getNodes, getNodesBounds, setViewport, store]);\n return null;\n}\n\n/**\n * After a fit that could NOT show everything, show the BEGINNING rather than the middle.\n *\n * `fitView` always centres. That is right when the content fits — and wrong the moment a\n * `minZoom` floor stops it from shrinking far enough, because centring a graph that\n * overflows puts its first rank off the top of the pane and opens the canvas on the\n * middle of a process nobody has read the start of yet. Every layout this shell is used\n * with runs from an origin (top for `TB`, left for `LR`), so the content's own top-left\n * corner IS the beginning, in both.\n *\n * Applied PER AXIS, and only to an axis that actually overflows: a tall graph in a wide\n * pane is pinned to the top and stays horizontally centred, which is what a reader\n * expects. A canvas whose content fits is left exactly as `fitView` left it.\n */\nfunction anchorToStartWhenClamped(\n store: ReturnType<typeof useStoreApi>,\n setViewport: ReturnType<typeof useReactFlow>[\"setViewport\"],\n // The HOOK's `getNodesBounds`, never the top-level export: the standalone one warns in\n // development when it is handed no `nodeLookup` (\"Please use `getNodesBounds` from\n // `useReactFlow`…\") and measures a parent's own `position` instead of resolving a\n // nested node's absolute one, so a keyed re-fit on a canvas with sub-flows anchors to\n // the wrong corner.\n getNodesBounds: ReturnType<typeof useReactFlow>[\"getNodesBounds\"],\n nodes: Node[],\n options: FitViewOptions | undefined,\n anchorNodeIds?: readonly string[],\n): void {\n if (nodes.length === 0) return;\n const { width, height, transform, panZoom } = store.getState();\n const [x, y, zoom] = transform;\n if (!width || !height || !zoom) return;\n\n const bounds = getNodesBounds(nodes);\n // The same fraction-of-the-pane padding `fitView` itself applies, so the anchored\n // corner sits exactly where a fitted one would.\n const padding = typeof options?.padding === \"number\" ? options.padding : 0.1;\n const padX = width * padding;\n const padY = height * padding;\n\n const overflowsX = bounds.width * zoom > width - padX * 2 + 1;\n const overflowsY = bounds.height * zoom > height - padY * 2 + 1;\n if (!overflowsX && !overflowsY) return;\n\n // With anchors: centre them on an overflowing axis, but never scroll past the content's\n // own start or end. On the axis the diagram runs along, the anchors sit at the content's\n // start, so this lands exactly where the corner pin does; on the cross axis it keeps the\n // first rank in view instead of opening on whatever happens to be top-most.\n const anchors = anchorNodeIds?.length\n ? nodes.filter((node) => anchorNodeIds.includes(node.id))\n : [];\n const anchor = anchors.length > 0 ? getNodesBounds(anchors) : null;\n const along = (size: number, pad: number, start: number, extent: number, centre: number) =>\n clampedFitOffset({\n size,\n pad,\n start,\n extent,\n zoom,\n anchorCentre: anchor === null ? undefined : centre,\n });\n\n const next = {\n x: overflowsX\n ? along(width, padX, bounds.x, bounds.width, (anchor?.x ?? 0) + (anchor?.width ?? 0) / 2)\n : x,\n y: overflowsY\n ? along(height, padY, bounds.y, bounds.height, (anchor?.y ?? 0) + (anchor?.height ?? 0) / 2)\n : y,\n zoom,\n };\n // `setViewport` is a no-op before the pan/zoom instance exists (the very first commit).\n if (!panZoom) return;\n void setViewport(next);\n}\n\nfunction CanvasShellWithHelperLines<NodeType extends Node, EdgeType extends Edge>({\n onNodesChange,\n children,\n ...props\n}: CanvasShellInnerProps<NodeType, EdgeType>) {\n const {\n onNodesChange: wrappedOnNodesChange,\n helperLineHorizontal,\n helperLineVertical,\n } = useHelperLines<NodeType>(onNodesChange);\n\n return (\n <CanvasShellBase<NodeType, EdgeType> {...props} onNodesChange={wrappedOnNodesChange}>\n {children}\n <HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />\n </CanvasShellBase>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-A66JBW4X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ // src/flow-floating-edge/floating-edge-geometry.ts
4
+ import { Position } from "@xyflow/react";
5
+ function getNodeIntersection(intersectionNode, targetNode) {
6
+ const w = (intersectionNode.measured.width ?? 0) / 2;
7
+ const h = (intersectionNode.measured.height ?? 0) / 2;
8
+ const pos = intersectionNode.internals.positionAbsolute;
9
+ const targetPos = targetNode.internals.positionAbsolute;
10
+ const x2 = pos.x + w;
11
+ const y2 = pos.y + h;
12
+ const x1 = targetPos.x + (targetNode.measured.width ?? 0) / 2;
13
+ const y1 = targetPos.y + (targetNode.measured.height ?? 0) / 2;
14
+ const safeW = w || 1;
15
+ const safeH = h || 1;
16
+ const xx1 = (x1 - x2) / (2 * safeW) - (y1 - y2) / (2 * safeH);
17
+ const yy1 = (x1 - x2) / (2 * safeW) + (y1 - y2) / (2 * safeH);
18
+ const a = 1 / (Math.abs(xx1) + Math.abs(yy1) || 1);
19
+ const xx3 = a * xx1;
20
+ const yy3 = a * yy1;
21
+ const x = safeW * (xx3 + yy3) + x2;
22
+ const y = safeH * (-xx3 + yy3) + y2;
23
+ return { x, y };
24
+ }
25
+ function clampToBorder(point, node) {
26
+ const x0 = node.internals.positionAbsolute.x;
27
+ const y0 = node.internals.positionAbsolute.y;
28
+ const w = node.measured.width ?? 0;
29
+ const h = node.measured.height ?? 0;
30
+ if (w <= 0 || h <= 0) return point;
31
+ const cx = Math.min(Math.max(point.x, x0), x0 + w);
32
+ const cy = Math.min(Math.max(point.y, y0), y0 + h);
33
+ const dLeft = cx - x0;
34
+ const dRight = x0 + w - cx;
35
+ const dTop = cy - y0;
36
+ const dBottom = y0 + h - cy;
37
+ const nearest = Math.min(dLeft, dRight, dTop, dBottom);
38
+ if (nearest === dLeft) return { x: x0, y: cy };
39
+ if (nearest === dRight) return { x: x0 + w, y: cy };
40
+ if (nearest === dTop) return { x: cx, y: y0 };
41
+ return { x: cx, y: y0 + h };
42
+ }
43
+ function getEdgeBorderPosition(node, point) {
44
+ const nx = Math.round(node.internals.positionAbsolute.x);
45
+ const ny = Math.round(node.internals.positionAbsolute.y);
46
+ const px = Math.round(point.x);
47
+ const py = Math.round(point.y);
48
+ const w = node.measured.width ?? 0;
49
+ const h = node.measured.height ?? 0;
50
+ if (px <= nx + 1) return Position.Left;
51
+ if (px >= nx + w - 1) return Position.Right;
52
+ if (py <= ny + 1) return Position.Top;
53
+ if (py >= ny + h - 1) return Position.Bottom;
54
+ return Position.Top;
55
+ }
56
+ function getEdgeParams(source, target) {
57
+ const sourceIntersection = clampToBorder(getNodeIntersection(source, target), source);
58
+ const targetIntersection = clampToBorder(getNodeIntersection(target, source), target);
59
+ return {
60
+ sx: sourceIntersection.x,
61
+ sy: sourceIntersection.y,
62
+ tx: targetIntersection.x,
63
+ ty: targetIntersection.y,
64
+ sourcePos: getEdgeBorderPosition(source, sourceIntersection),
65
+ targetPos: getEdgeBorderPosition(target, targetIntersection)
66
+ };
67
+ }
68
+
69
+ export {
70
+ getEdgeParams
71
+ };
72
+ //# sourceMappingURL=chunk-A7AFM3LI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-floating-edge/floating-edge-geometry.ts"],"sourcesContent":["import { Position } from \"@xyflow/react\";\n\n/**\n * The minimal live node geometry `getEdgeParams` needs. A React Flow\n * `InternalNode` satisfies this structurally (`internals.positionAbsolute` +\n * `measured`), so you can pass one directly; tests can pass a plain fake.\n */\nexport interface FloatingNodeGeometry {\n internals: { positionAbsolute: { x: number; y: number } };\n measured: { width?: number; height?: number };\n}\n\ninterface Point {\n x: number;\n y: number;\n}\n\n/**\n * The point where the line from `intersectionNode`'s center to `targetNode`'s\n * center crosses `intersectionNode`'s rectangle border. Standard React Flow\n * floating-edge math (ellipse-normalized).\n */\nfunction getNodeIntersection(\n intersectionNode: FloatingNodeGeometry,\n targetNode: FloatingNodeGeometry,\n): Point {\n const w = (intersectionNode.measured.width ?? 0) / 2;\n const h = (intersectionNode.measured.height ?? 0) / 2;\n const pos = intersectionNode.internals.positionAbsolute;\n const targetPos = targetNode.internals.positionAbsolute;\n\n const x2 = pos.x + w;\n const y2 = pos.y + h;\n const x1 = targetPos.x + (targetNode.measured.width ?? 0) / 2;\n const y1 = targetPos.y + (targetNode.measured.height ?? 0) / 2;\n\n // A not-yet-measured node has w/h = 0; guard the divisions so we return the\n // node's position (finite) instead of NaN (which would break the edge path and\n // make the line appear to end at a random spot until measurement lands).\n const safeW = w || 1;\n const safeH = h || 1;\n\n const xx1 = (x1 - x2) / (2 * safeW) - (y1 - y2) / (2 * safeH);\n const yy1 = (x1 - x2) / (2 * safeW) + (y1 - y2) / (2 * safeH);\n const a = 1 / (Math.abs(xx1) + Math.abs(yy1) || 1);\n const xx3 = a * xx1;\n const yy3 = a * yy1;\n const x = safeW * (xx3 + yy3) + x2;\n const y = safeH * (-xx3 + yy3) + y2;\n\n return { x, y };\n}\n\n/**\n * Snap `point` exactly onto `node`'s nearest border side. The intersection math\n * already lands on the border for a measured node; this guarantees it (removing\n * any sub-pixel drift that would leave the anchor a hair inside the node) and\n * gives a sensible on-border fallback if a point ever lands inside. Returns the\n * point unchanged when the node has no measured size yet.\n */\nfunction clampToBorder(point: Point, node: FloatingNodeGeometry): Point {\n const x0 = node.internals.positionAbsolute.x;\n const y0 = node.internals.positionAbsolute.y;\n const w = node.measured.width ?? 0;\n const h = node.measured.height ?? 0;\n if (w <= 0 || h <= 0) return point;\n\n const cx = Math.min(Math.max(point.x, x0), x0 + w);\n const cy = Math.min(Math.max(point.y, y0), y0 + h);\n // Distance from the (clamped-inside) point to each side; snap to the nearest.\n const dLeft = cx - x0;\n const dRight = x0 + w - cx;\n const dTop = cy - y0;\n const dBottom = y0 + h - cy;\n const nearest = Math.min(dLeft, dRight, dTop, dBottom);\n if (nearest === dLeft) return { x: x0, y: cy };\n if (nearest === dRight) return { x: x0 + w, y: cy };\n if (nearest === dTop) return { x: cx, y: y0 };\n return { x: cx, y: y0 + h };\n}\n\n/** Which border of `node` the intersection `point` sits on. */\nfunction getEdgeBorderPosition(node: FloatingNodeGeometry, point: Point): Position {\n const nx = Math.round(node.internals.positionAbsolute.x);\n const ny = Math.round(node.internals.positionAbsolute.y);\n const px = Math.round(point.x);\n const py = Math.round(point.y);\n const w = node.measured.width ?? 0;\n const h = node.measured.height ?? 0;\n\n if (px <= nx + 1) return Position.Left;\n if (px >= nx + w - 1) return Position.Right;\n if (py <= ny + 1) return Position.Top;\n if (py >= ny + h - 1) return Position.Bottom;\n return Position.Top;\n}\n\n/** Border anchor points + `Position`s for a floating edge between two nodes. */\nexport interface EdgeParams {\n sx: number;\n sy: number;\n tx: number;\n ty: number;\n sourcePos: Position;\n targetPos: Position;\n}\n\n/**\n * Computes where a floating edge attaches to each node's border — the\n * intersection of the center-to-center line with each rectangle — plus the\n * border `Position`. Recompute per render so anchors track dragging nodes.\n */\nexport function getEdgeParams(\n source: FloatingNodeGeometry,\n target: FloatingNodeGeometry,\n): EdgeParams {\n const sourceIntersection = clampToBorder(getNodeIntersection(source, target), source);\n const targetIntersection = clampToBorder(getNodeIntersection(target, source), target);\n\n return {\n sx: sourceIntersection.x,\n sy: sourceIntersection.y,\n tx: targetIntersection.x,\n ty: targetIntersection.y,\n sourcePos: getEdgeBorderPosition(source, sourceIntersection),\n targetPos: getEdgeBorderPosition(target, targetIntersection),\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-ADHLQF56.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ // src/zoom-controls/zoom-controls.tsx
4
+ import { Panel, useReactFlow } from "@xyflow/react";
5
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function ControlButton({
8
+ label,
9
+ onClick,
10
+ children
11
+ }) {
12
+ return /* @__PURE__ */ jsx(
13
+ "button",
14
+ {
15
+ type: "button",
16
+ "aria-label": label,
17
+ onClick,
18
+ className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-ring [&_svg]:size-4",
19
+ children
20
+ }
21
+ );
22
+ }
23
+ function ZoomControls({ position = "bottom-right", className }) {
24
+ const { zoomIn, zoomOut, fitView } = useReactFlow();
25
+ return /* @__PURE__ */ jsx(Panel, { position, children: /* @__PURE__ */ jsxs(
26
+ "div",
27
+ {
28
+ className: cn(
29
+ "flex divide-x overflow-hidden rounded-lg bg-surface-elevated shadow-ring-sm",
30
+ className
31
+ ),
32
+ children: [
33
+ /* @__PURE__ */ jsx(ControlButton, { label: "Zoom in", onClick: () => zoomIn(), children: /* @__PURE__ */ jsx(
34
+ "svg",
35
+ {
36
+ viewBox: "0 0 24 24",
37
+ fill: "none",
38
+ stroke: "currentColor",
39
+ strokeWidth: "2",
40
+ strokeLinecap: "round",
41
+ children: /* @__PURE__ */ jsx("path", { d: "M12 5v14M5 12h14" })
42
+ }
43
+ ) }),
44
+ /* @__PURE__ */ jsx(ControlButton, { label: "Zoom out", onClick: () => zoomOut(), children: /* @__PURE__ */ jsx(
45
+ "svg",
46
+ {
47
+ viewBox: "0 0 24 24",
48
+ fill: "none",
49
+ stroke: "currentColor",
50
+ strokeWidth: "2",
51
+ strokeLinecap: "round",
52
+ children: /* @__PURE__ */ jsx("path", { d: "M5 12h14" })
53
+ }
54
+ ) }),
55
+ /* @__PURE__ */ jsx(ControlButton, { label: "Fit view", onClick: () => fitView(), children: /* @__PURE__ */ jsx(
56
+ "svg",
57
+ {
58
+ viewBox: "0 0 24 24",
59
+ fill: "none",
60
+ stroke: "currentColor",
61
+ strokeWidth: "2",
62
+ strokeLinecap: "round",
63
+ strokeLinejoin: "round",
64
+ children: /* @__PURE__ */ jsx("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M16 21h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" })
65
+ }
66
+ ) })
67
+ ]
68
+ }
69
+ ) });
70
+ }
71
+
72
+ export {
73
+ ZoomControls
74
+ };
75
+ //# sourceMappingURL=chunk-ATO4LXJF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/zoom-controls/zoom-controls.tsx"],"sourcesContent":["import { Panel, useReactFlow, type PanelPosition } from \"@xyflow/react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface ZoomControlsProps {\n position?: PanelPosition;\n className?: string;\n}\n\nfunction ControlButton({\n label,\n onClick,\n children,\n}: {\n label: string;\n onClick: () => void;\n children: React.ReactNode;\n}) {\n return (\n <button\n type=\"button\"\n aria-label={label}\n onClick={onClick}\n className=\"flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-ring [&_svg]:size-4\"\n >\n {children}\n </button>\n );\n}\n\n/** Branded zoom in / out / fit controls. Render inside <CanvasShell>. */\nexport function ZoomControls({ position = \"bottom-right\", className }: ZoomControlsProps) {\n const { zoomIn, zoomOut, fitView } = useReactFlow();\n return (\n <Panel position={position}>\n <div\n className={cn(\n \"flex divide-x overflow-hidden rounded-lg bg-surface-elevated shadow-ring-sm\",\n className,\n )}\n >\n <ControlButton label=\"Zoom in\" onClick={() => zoomIn()}>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n >\n <path d=\"M12 5v14M5 12h14\" />\n </svg>\n </ControlButton>\n <ControlButton label=\"Zoom out\" onClick={() => zoomOut()}>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n >\n <path d=\"M5 12h14\" />\n </svg>\n </ControlButton>\n <ControlButton label=\"Fit view\" onClick={() => fitView()}>\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M16 21h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3\" />\n </svg>\n </ControlButton>\n </div>\n </Panel>\n );\n}\n"],"mappings":";;;AAAA,SAAS,OAAO,oBAAwC;AACxD,SAAS,UAAU;AAiBf,cAgBE,YAhBF;AAVJ,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA,MAET;AAAA;AAAA,EACH;AAEJ;AAGO,SAAS,aAAa,EAAE,WAAW,gBAAgB,UAAU,GAAsB;AACxF,QAAM,EAAE,QAAQ,SAAS,QAAQ,IAAI,aAAa;AAClD,SACE,oBAAC,SAAM,UACL;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,iBAAc,OAAM,WAAU,SAAS,MAAM,OAAO,GACnD;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,eAAc;AAAA,YAEd,8BAAC,UAAK,GAAE,oBAAmB;AAAA;AAAA,QAC7B,GACF;AAAA,QACA,oBAAC,iBAAc,OAAM,YAAW,SAAS,MAAM,QAAQ,GACrD;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,eAAc;AAAA,YAEd,8BAAC,UAAK,GAAE,YAAW;AAAA;AAAA,QACrB,GACF;AAAA,QACA,oBAAC,iBAAc,OAAM,YAAW,SAAS,MAAM,QAAQ,GACrD;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,QAAO;AAAA,YACP,aAAY;AAAA,YACZ,eAAc;AAAA,YACd,gBAAe;AAAA,YAEf,8BAAC,UAAK,GAAE,kGAAiG;AAAA;AAAA,QAC3G,GACF;AAAA;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
@@ -0,0 +1,227 @@
1
+ "use client";
2
+ import {
3
+ HANDLE_BY_DIRECTION,
4
+ layoutFlow
5
+ } from "./chunk-HTLTCCFA.js";
6
+
7
+ // src/flow-layout/layout-flow-elk.ts
8
+ var ELK_DIRECTION = {
9
+ TB: "DOWN",
10
+ BT: "UP",
11
+ LR: "RIGHT",
12
+ RL: "LEFT"
13
+ };
14
+ var GROUP_PADDING = "[top=60,left=16,bottom=16,right=16]";
15
+ var BACKBONE_STRAIGHTNESS = "100";
16
+ var DEFAULT_NODE_WIDTH = 172;
17
+ var DEFAULT_NODE_HEIGHT = 40;
18
+ function nodeSize(node) {
19
+ return {
20
+ width: node.measured?.width ?? node.width ?? DEFAULT_NODE_WIDTH,
21
+ height: node.measured?.height ?? node.height ?? DEFAULT_NODE_HEIGHT
22
+ };
23
+ }
24
+ function interopDefault(mod) {
25
+ let value = mod;
26
+ while (value && typeof value === "object" && "default" in value && value.default) {
27
+ value = value.default;
28
+ }
29
+ return value;
30
+ }
31
+ async function loadDefaultEngine() {
32
+ const ELK = interopDefault(await import("elkjs/lib/elk-api.js"));
33
+ const mod = await import("elkjs/lib/elk-worker.min.js");
34
+ const FakeWorker = mod.Worker ?? (typeof mod.default === "function" ? mod.default : mod.default?.Worker);
35
+ if (FakeWorker === void 0) throw new Error("elkjs inline worker not found");
36
+ return new ELK({ workerFactory: (url) => new FakeWorker(url) });
37
+ }
38
+ function warnFallback(error) {
39
+ if (typeof process !== "undefined" && process.env.NODE_ENV === "production") return;
40
+ console.warn(
41
+ "[@elabs-ai/components-flow] layoutFlowElk could not run elkjs (is the optional peer `elkjs` installed?) \u2014 falling back to the dagre layout.",
42
+ error
43
+ );
44
+ }
45
+ function toElkGraph(nodes, edges, options) {
46
+ const {
47
+ direction = "TB",
48
+ edgeRouting = "splines",
49
+ feedbackEdges = true,
50
+ nodeSpacing = 48,
51
+ rankSpacing = 72,
52
+ groups = [],
53
+ backbone
54
+ } = options;
55
+ const ids = new Set(nodes.map((n) => n.id));
56
+ const straight = new Set(backbone?.edgeIds ?? []);
57
+ const parentOf = /* @__PURE__ */ new Map();
58
+ const groupIds = /* @__PURE__ */ new Set();
59
+ for (const group of groups) {
60
+ if (!ids.has(group.id)) continue;
61
+ groupIds.add(group.id);
62
+ for (const child of group.children) {
63
+ if (ids.has(child) && child !== group.id) parentOf.set(child, group.id);
64
+ }
65
+ }
66
+ const elkNodes = /* @__PURE__ */ new Map();
67
+ for (const node of nodes) {
68
+ const isGroup = groupIds.has(node.id);
69
+ elkNodes.set(node.id, {
70
+ id: node.id,
71
+ // A group is sized by ELK from its children; a leaf keeps its measured size.
72
+ ...isGroup ? { layoutOptions: { "elk.padding": GROUP_PADDING } } : nodeSize(node)
73
+ });
74
+ }
75
+ const root = {
76
+ id: "__flow_elk_root__",
77
+ layoutOptions: {
78
+ "elk.algorithm": "layered",
79
+ "elk.direction": ELK_DIRECTION[direction],
80
+ "elk.edgeRouting": edgeRouting === "orthogonal" ? "ORTHOGONAL" : "SPLINES",
81
+ "elk.spacing.nodeNode": String(nodeSpacing),
82
+ "elk.layered.spacing.nodeNodeBetweenLayers": String(rankSpacing),
83
+ "elk.layered.feedbackEdges": String(feedbackEdges),
84
+ // Break cycles by input order, as a reader would: the edge pointing back to an
85
+ // EARLIER node is the reversed one — the same edge dagre reports.
86
+ "elk.layered.cycleBreaking.strategy": "MODEL_ORDER",
87
+ "elk.hierarchyHandling": groupIds.size > 0 ? "INCLUDE_CHILDREN" : "INHERIT",
88
+ ...backbone && backbone.nodeIds.length > 0 ? { "elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX" } : {}
89
+ },
90
+ children: [],
91
+ edges: edges.filter((edge) => ids.has(edge.source) && ids.has(edge.target)).map((edge) => ({
92
+ id: edge.id,
93
+ sources: [edge.source],
94
+ targets: [edge.target],
95
+ ...straight.has(edge.id) ? { layoutOptions: { "elk.layered.priority.straightness": BACKBONE_STRAIGHTNESS } } : {}
96
+ }))
97
+ };
98
+ for (const node of nodes) {
99
+ const elkNode = elkNodes.get(node.id);
100
+ const parent = resolveParent(node.id, parentOf);
101
+ const container = parent === void 0 ? root : elkNodes.get(parent);
102
+ (container.children ??= []).push(elkNode);
103
+ }
104
+ return root;
105
+ }
106
+ function resolveParent(id, parentOf) {
107
+ const parent = parentOf.get(id);
108
+ if (parent === void 0) return void 0;
109
+ const seen = /* @__PURE__ */ new Set([id]);
110
+ let cursor = parent;
111
+ while (cursor !== void 0) {
112
+ if (seen.has(cursor)) return void 0;
113
+ seen.add(cursor);
114
+ cursor = parentOf.get(cursor);
115
+ }
116
+ return parent;
117
+ }
118
+ function collectPlacements(graph, origin, parentId, out) {
119
+ for (const child of graph.children ?? []) {
120
+ const relative = { x: child.x ?? 0, y: child.y ?? 0 };
121
+ const absolute = { x: origin.x + relative.x, y: origin.y + relative.y };
122
+ out.set(child.id, {
123
+ relative,
124
+ absolute,
125
+ width: child.width,
126
+ height: child.height,
127
+ ...parentId === void 0 ? {} : { parentId }
128
+ });
129
+ if (child.children && child.children.length > 0) {
130
+ collectPlacements(child, absolute, child.id, out);
131
+ }
132
+ }
133
+ }
134
+ function straightenBackbone(placements, nodeIds, direction) {
135
+ const along = direction === "LR" || direction === "RL" ? "x" : "y";
136
+ const cross = along === "x" ? "y" : "x";
137
+ const extent = (p, axis) => (axis === "x" ? p.width : p.height) ?? 0;
138
+ const centerOf = (p) => p.absolute[cross] + extent(p, cross) / 2;
139
+ const pinned = nodeIds.map((id) => placements.get(id)).filter((p) => p !== void 0 && p.parentId === void 0);
140
+ const first = pinned[0];
141
+ if (first === void 0) return;
142
+ const line = centerOf(first);
143
+ const topLevel = [...placements.values()].filter((p) => p.parentId === void 0);
144
+ const moved = /* @__PURE__ */ new Set();
145
+ for (const anchor of pinned) {
146
+ const delta = line - centerOf(anchor);
147
+ if (delta === 0) continue;
148
+ const start = anchor.absolute[along];
149
+ const end = start + extent(anchor, along);
150
+ for (const p of topLevel) {
151
+ if (moved.has(p)) continue;
152
+ const pStart = p.absolute[along];
153
+ if (pStart >= end || pStart + extent(p, along) <= start) continue;
154
+ p.absolute = { ...p.absolute, [cross]: p.absolute[cross] + delta };
155
+ p.relative = { ...p.relative, [cross]: p.relative[cross] + delta };
156
+ moved.add(p);
157
+ }
158
+ }
159
+ }
160
+ async function layoutFlowElk(nodes, edges, options = {}) {
161
+ const direction = options.direction ?? "TB";
162
+ let laidOut;
163
+ try {
164
+ const engine = await (options.loadEngine ?? loadDefaultEngine)();
165
+ laidOut = await engine.layout(toElkGraph(nodes, edges, options));
166
+ } catch (error) {
167
+ warnFallback(error);
168
+ return {
169
+ ...layoutFlow(nodes, edges, {
170
+ direction,
171
+ ...options.nodeSpacing === void 0 ? {} : { nodeSpacing: options.nodeSpacing },
172
+ ...options.rankSpacing === void 0 ? {} : { rankSpacing: options.rankSpacing }
173
+ }),
174
+ engine: "dagre"
175
+ };
176
+ }
177
+ const placements = /* @__PURE__ */ new Map();
178
+ collectPlacements(laidOut, { x: 0, y: 0 }, void 0, placements);
179
+ if (options.backbone) straightenBackbone(placements, options.backbone.nodeIds, direction);
180
+ const handles = HANDLE_BY_DIRECTION[direction];
181
+ const groupIds = new Set((options.groups ?? []).map((group) => group.id));
182
+ const layoutedNodes = nodes.map((node) => {
183
+ const placed = placements.get(node.id);
184
+ if (!placed) return node;
185
+ const next = {
186
+ ...node,
187
+ sourcePosition: handles.source,
188
+ targetPosition: handles.target,
189
+ position: placed.relative
190
+ };
191
+ if (placed.parentId !== void 0) {
192
+ next.parentId = placed.parentId;
193
+ next.extent = "parent";
194
+ }
195
+ if (groupIds.has(node.id) && placed.width !== void 0 && placed.height !== void 0) {
196
+ next.width = placed.width;
197
+ next.height = placed.height;
198
+ }
199
+ return next;
200
+ });
201
+ const selfLoops = [];
202
+ const backEdges = [];
203
+ const axis = direction === "LR" || direction === "RL" ? "x" : "y";
204
+ const sign = direction === "BT" || direction === "RL" ? -1 : 1;
205
+ const center = (id) => {
206
+ const placed = placements.get(id);
207
+ if (!placed) return void 0;
208
+ const size = axis === "x" ? placed.width ?? 0 : placed.height ?? 0;
209
+ return sign * (placed.absolute[axis] + size / 2);
210
+ };
211
+ for (const edge of edges) {
212
+ if (edge.source === edge.target) {
213
+ selfLoops.push(edge.id);
214
+ continue;
215
+ }
216
+ const source = center(edge.source);
217
+ const target = center(edge.target);
218
+ if (source === void 0 || target === void 0) continue;
219
+ if (source >= target) backEdges.push(edge.id);
220
+ }
221
+ return { nodes: layoutedNodes, edges, backEdges, selfLoops, engine: "elk" };
222
+ }
223
+
224
+ export {
225
+ layoutFlowElk
226
+ };
227
+ //# sourceMappingURL=chunk-ATXVXTMZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-layout/layout-flow-elk.ts"],"sourcesContent":["import type { Edge, Node, XYPosition } from \"@xyflow/react\";\nimport type { FlowElkBackbone } from \"./backbone\";\nimport {\n HANDLE_BY_DIRECTION,\n layoutFlow,\n type FlowLayoutDirection,\n type FlowLayoutResult,\n} from \"./flow-layout\";\n\n/**\n * elkjs layout adapter — RM-067.\n *\n * dagre has no self-loop layout, no edge routing and weak compound nodes. ELK (Eclipse\n * Layout Kernel, EPL-2.0) has all three, and it is large — so it is an OPTIONAL peer of this\n * package, reached ONLY through `import()` inside {@link layoutFlowElk}'s body. A consumer\n * that never calls it never downloads it, and a consumer that calls it without installing\n * elkjs gets `layoutFlow`'s dagre answer plus a development-only console warning, never a\n * crash.\n *\n * The default engine runs inline on the calling thread (elkjs's own in-thread worker\n * stand-in). The package deliberately ships NO worker entry: a `new Worker(new URL(…,\n * import.meta.url))` pointing at a file the tsup build does not emit breaks some consumer\n * bundlers at build time. A host that wants the layout off the main thread supplies\n * `loadEngine` with its own worker, e.g.\n *\n * ```ts\n * loadEngine: async () => {\n * const { default: ELK } = await import(\"elkjs/lib/elk-api.js\");\n * return new ELK({ workerUrl: new URL(\"elkjs/lib/elk-worker.min.js\", import.meta.url).href });\n * }\n * ```\n *\n * No remote origin is involved either way.\n */\n\n/** Options for {@link layoutFlowElk}. */\nexport interface FlowLayoutElkOptions {\n /** Layout direction. @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** How ELK routes edges. @default \"splines\" */\n edgeRouting?: \"orthogonal\" | \"splines\";\n /** Route back-edges as ELK feedback edges (around the graph, not through it). @default true */\n feedbackEdges?: boolean;\n /** Gap between nodes in the same layer. @default 48 */\n nodeSpacing?: number;\n /** Gap between layers. @default 72 */\n rankSpacing?: number;\n /**\n * Compound nodes. Each `id` names a node already in `nodes` (typically a `FlowGroupNode`);\n * its `children` are laid out inside it and come back with `parentId` set, positioned\n * relative to the group, and the group comes back sized to fit them.\n */\n groups?: { id: string; children: string[] }[];\n /** A pinned straight path — see `pinBackbone`. */\n backbone?: FlowElkBackbone;\n /**\n * Supply the engine yourself: a host with its own worker URL, or a test. Rejecting (for\n * example because elkjs is not installed) falls back to dagre exactly like the default\n * loader does.\n */\n loadEngine?: () => Promise<FlowElkEngine>;\n}\n\n/** A graph in ELK's JSON format — the minimal shape this adapter reads and writes. */\nexport interface FlowElkGraph {\n id: string;\n x?: number;\n y?: number;\n width?: number;\n height?: number;\n layoutOptions?: Record<string, string>;\n children?: FlowElkGraph[];\n edges?: {\n id: string;\n sources: string[];\n targets: string[];\n layoutOptions?: Record<string, string>;\n }[];\n}\n\n/** A running ELK engine. `new ELK()` from elkjs satisfies it. */\nexport interface FlowElkEngine {\n layout(graph: FlowElkGraph): Promise<FlowElkGraph>;\n}\n\n/** What {@link layoutFlowElk} answers — `layoutFlow`'s result plus which engine produced it. */\nexport interface FlowLayoutElkResult<\n NodeType extends Node = Node,\n EdgeType extends Edge = Edge,\n> extends FlowLayoutResult<NodeType, EdgeType> {\n /** `\"elk\"`, or `\"dagre\"` when elkjs could not be loaded or failed and dagre answered. */\n engine: \"elk\" | \"dagre\";\n}\n\nconst ELK_DIRECTION: Record<FlowLayoutDirection, string> = {\n TB: \"DOWN\",\n BT: \"UP\",\n LR: \"RIGHT\",\n RL: \"LEFT\",\n};\n\n/** Room above a group's children for its header, matching `groupNodes`' own offset. */\nconst GROUP_PADDING = \"[top=60,left=16,bottom=16,right=16]\";\n\n/**\n * Straightness priority for backbone edges, against the default `0` of every other edge —\n * enough for network-simplex placement to favour them in every tie it breaks.\n */\nconst BACKBONE_STRAIGHTNESS = \"100\";\n\nconst DEFAULT_NODE_WIDTH = 172;\nconst DEFAULT_NODE_HEIGHT = 40;\n\nfunction nodeSize(node: Node): { width: number; height: number } {\n return {\n width: node.measured?.width ?? node.width ?? DEFAULT_NODE_WIDTH,\n height: node.measured?.height ?? node.height ?? DEFAULT_NODE_HEIGHT,\n };\n}\n\n/** CommonJS interop: elkjs ships CJS, which bundlers and Node wrap in `default` differently. */\nfunction interopDefault<T>(mod: unknown): T {\n let value = mod as { default?: unknown };\n while (value && typeof value === \"object\" && \"default\" in value && value.default) {\n value = value.default as { default?: unknown };\n }\n return value as T;\n}\n\ntype ElkConstructor = new (args: { workerFactory: (url?: string) => unknown }) => FlowElkEngine;\n\ntype FakeWorkerConstructor = new (url?: string) => unknown;\n\n/** The default engine: elkjs run inline on the calling thread (see the module docblock). */\nasync function loadDefaultEngine(): Promise<FlowElkEngine> {\n const ELK = interopDefault<ElkConstructor>(await import(\"elkjs/lib/elk-api.js\"));\n // `elk-worker.min.js` exports `{ default: FakeWorker, Worker: FakeWorker }` outside a worker.\n // @ts-expect-error -- elkjs ships no declaration for the minified worker; the shape is narrowed below.\n const mod = (await import(\"elkjs/lib/elk-worker.min.js\")) as {\n Worker?: FakeWorkerConstructor;\n default?: FakeWorkerConstructor | { Worker?: FakeWorkerConstructor };\n };\n const FakeWorker =\n mod.Worker ?? (typeof mod.default === \"function\" ? mod.default : mod.default?.Worker);\n if (FakeWorker === undefined) throw new Error(\"elkjs inline worker not found\");\n return new ELK({ workerFactory: (url) => new FakeWorker(url) });\n}\n\nfunction warnFallback(error: unknown): void {\n if (typeof process !== \"undefined\" && process.env.NODE_ENV === \"production\") return;\n console.warn(\n \"[@elabs-ai/components-flow] layoutFlowElk could not run elkjs (is the optional peer \" +\n \"`elkjs` installed?) — falling back to the dagre layout.\",\n error,\n );\n}\n\n/** Build ELK's hierarchical graph, nesting grouped nodes under their group. */\nfunction toElkGraph(nodes: Node[], edges: Edge[], options: FlowLayoutElkOptions): FlowElkGraph {\n const {\n direction = \"TB\",\n edgeRouting = \"splines\",\n feedbackEdges = true,\n nodeSpacing = 48,\n rankSpacing = 72,\n groups = [],\n backbone,\n } = options;\n\n const ids = new Set(nodes.map((n) => n.id));\n const straight = new Set(backbone?.edgeIds ?? []);\n const parentOf = new Map<string, string>();\n const groupIds = new Set<string>();\n for (const group of groups) {\n if (!ids.has(group.id)) continue;\n groupIds.add(group.id);\n for (const child of group.children) {\n if (ids.has(child) && child !== group.id) parentOf.set(child, group.id);\n }\n }\n\n const elkNodes = new Map<string, FlowElkGraph>();\n for (const node of nodes) {\n const isGroup = groupIds.has(node.id);\n elkNodes.set(node.id, {\n id: node.id,\n // A group is sized by ELK from its children; a leaf keeps its measured size.\n ...(isGroup ? { layoutOptions: { \"elk.padding\": GROUP_PADDING } } : nodeSize(node)),\n });\n }\n\n const root: FlowElkGraph = {\n id: \"__flow_elk_root__\",\n layoutOptions: {\n \"elk.algorithm\": \"layered\",\n \"elk.direction\": ELK_DIRECTION[direction],\n \"elk.edgeRouting\": edgeRouting === \"orthogonal\" ? \"ORTHOGONAL\" : \"SPLINES\",\n \"elk.spacing.nodeNode\": String(nodeSpacing),\n \"elk.layered.spacing.nodeNodeBetweenLayers\": String(rankSpacing),\n \"elk.layered.feedbackEdges\": String(feedbackEdges),\n // Break cycles by input order, as a reader would: the edge pointing back to an\n // EARLIER node is the reversed one — the same edge dagre reports.\n \"elk.layered.cycleBreaking.strategy\": \"MODEL_ORDER\",\n \"elk.hierarchyHandling\": groupIds.size > 0 ? \"INCLUDE_CHILDREN\" : \"INHERIT\",\n ...(backbone && backbone.nodeIds.length > 0\n ? { \"elk.layered.nodePlacement.strategy\": \"NETWORK_SIMPLEX\" }\n : {}),\n },\n children: [],\n edges: edges\n .filter((edge) => ids.has(edge.source) && ids.has(edge.target))\n .map((edge) => ({\n id: edge.id,\n sources: [edge.source],\n targets: [edge.target],\n ...(straight.has(edge.id)\n ? { layoutOptions: { \"elk.layered.priority.straightness\": BACKBONE_STRAIGHTNESS } }\n : {}),\n })),\n };\n\n for (const node of nodes) {\n const elkNode = elkNodes.get(node.id)!;\n const parent = resolveParent(node.id, parentOf);\n const container = parent === undefined ? root : elkNodes.get(parent)!;\n (container.children ??= []).push(elkNode);\n }\n return root;\n}\n\n/** A node's group, ignoring any parent chain that would loop back onto itself. */\nfunction resolveParent(id: string, parentOf: Map<string, string>): string | undefined {\n const parent = parentOf.get(id);\n if (parent === undefined) return undefined;\n const seen = new Set([id]);\n let cursor: string | undefined = parent;\n while (cursor !== undefined) {\n if (seen.has(cursor)) return undefined;\n seen.add(cursor);\n cursor = parentOf.get(cursor);\n }\n return parent;\n}\n\ninterface PlacedNode {\n relative: XYPosition;\n absolute: XYPosition;\n width?: number;\n height?: number;\n parentId?: string;\n}\n\nfunction collectPlacements(\n graph: FlowElkGraph,\n origin: XYPosition,\n parentId: string | undefined,\n out: Map<string, PlacedNode>,\n): void {\n for (const child of graph.children ?? []) {\n const relative = { x: child.x ?? 0, y: child.y ?? 0 };\n const absolute = { x: origin.x + relative.x, y: origin.y + relative.y };\n out.set(child.id, {\n relative,\n absolute,\n width: child.width,\n height: child.height,\n ...(parentId === undefined ? {} : { parentId }),\n });\n if (child.children && child.children.length > 0) {\n collectPlacements(child, absolute, child.id, out);\n }\n }\n}\n\n/**\n * Put the backbone's nodes on one line by translating each of their LAYERS along the cross\n * axis. ELK's straightness priority gets close but does not guarantee a line; shifting a\n * whole layer does, and cannot overlap two nodes of that layer. Only top-level nodes are\n * moved — a node inside a group is positioned relative to it.\n */\nfunction straightenBackbone(\n placements: Map<string, PlacedNode>,\n nodeIds: readonly string[],\n direction: FlowLayoutDirection,\n): void {\n const along = direction === \"LR\" || direction === \"RL\" ? \"x\" : \"y\";\n const cross = along === \"x\" ? \"y\" : \"x\";\n const extent = (p: PlacedNode, axis: \"x\" | \"y\") => (axis === \"x\" ? p.width : p.height) ?? 0;\n const centerOf = (p: PlacedNode) => p.absolute[cross] + extent(p, cross) / 2;\n\n const pinned = nodeIds\n .map((id) => placements.get(id))\n .filter((p): p is PlacedNode => p !== undefined && p.parentId === undefined);\n const first = pinned[0];\n if (first === undefined) return;\n const line = centerOf(first);\n\n const topLevel = [...placements.values()].filter((p) => p.parentId === undefined);\n const moved = new Set<PlacedNode>();\n for (const anchor of pinned) {\n const delta = line - centerOf(anchor);\n if (delta === 0) continue;\n const start = anchor.absolute[along];\n const end = start + extent(anchor, along);\n for (const p of topLevel) {\n if (moved.has(p)) continue;\n const pStart = p.absolute[along];\n if (pStart >= end || pStart + extent(p, along) <= start) continue;\n p.absolute = { ...p.absolute, [cross]: p.absolute[cross] + delta };\n p.relative = { ...p.relative, [cross]: p.relative[cross] + delta };\n moved.add(p);\n }\n }\n}\n\n/**\n * Lay a graph out with ELK's layered algorithm — `layoutFlow`'s async sibling.\n *\n * Same result shape as `layoutFlow` (`nodes`, `edges`, `backEdges`, `selfLoops`), so a\n * caller can swap engines without a different consumption path; `engine` says which one\n * actually answered. Self-loops are laid out by ELK (it routes them around their node) and\n * still reported in `selfLoops`; `backEdges` are the edges whose source sits at or after\n * their target along the layout direction.\n *\n * elkjs is imported lazily inside this function. If it is not installed, or ELK throws,\n * the result is `layoutFlow`'s dagre layout with `engine: \"dagre\"`.\n */\nexport async function layoutFlowElk<NodeType extends Node = Node, EdgeType extends Edge = Edge>(\n nodes: NodeType[],\n edges: EdgeType[],\n options: FlowLayoutElkOptions = {},\n): Promise<FlowLayoutElkResult<NodeType, EdgeType>> {\n const direction = options.direction ?? \"TB\";\n\n let laidOut: FlowElkGraph;\n try {\n const engine = await (options.loadEngine ?? loadDefaultEngine)();\n laidOut = await engine.layout(toElkGraph(nodes, edges, options));\n } catch (error) {\n warnFallback(error);\n return {\n ...layoutFlow(nodes, edges, {\n direction,\n ...(options.nodeSpacing === undefined ? {} : { nodeSpacing: options.nodeSpacing }),\n ...(options.rankSpacing === undefined ? {} : { rankSpacing: options.rankSpacing }),\n }),\n engine: \"dagre\",\n };\n }\n\n const placements = new Map<string, PlacedNode>();\n collectPlacements(laidOut, { x: 0, y: 0 }, undefined, placements);\n if (options.backbone) straightenBackbone(placements, options.backbone.nodeIds, direction);\n\n const handles = HANDLE_BY_DIRECTION[direction];\n const groupIds = new Set((options.groups ?? []).map((group) => group.id));\n const layoutedNodes = nodes.map((node) => {\n const placed = placements.get(node.id);\n if (!placed) return node;\n const next: NodeType = {\n ...node,\n sourcePosition: handles.source,\n targetPosition: handles.target,\n position: placed.relative,\n };\n if (placed.parentId !== undefined) {\n next.parentId = placed.parentId;\n next.extent = \"parent\";\n }\n if (groupIds.has(node.id) && placed.width !== undefined && placed.height !== undefined) {\n next.width = placed.width;\n next.height = placed.height;\n }\n return next;\n });\n\n const selfLoops: string[] = [];\n const backEdges: string[] = [];\n const axis = direction === \"LR\" || direction === \"RL\" ? \"x\" : \"y\";\n const sign = direction === \"BT\" || direction === \"RL\" ? -1 : 1;\n const center = (id: string): number | undefined => {\n const placed = placements.get(id);\n if (!placed) return undefined;\n const size = axis === \"x\" ? (placed.width ?? 0) : (placed.height ?? 0);\n return sign * (placed.absolute[axis] + size / 2);\n };\n for (const edge of edges) {\n if (edge.source === edge.target) {\n selfLoops.push(edge.id);\n continue;\n }\n const source = center(edge.source);\n const target = center(edge.target);\n if (source === undefined || target === undefined) continue;\n if (source >= target) backEdges.push(edge.id);\n }\n\n return { nodes: layoutedNodes, edges, backEdges, selfLoops, engine: \"elk\" };\n}\n"],"mappings":"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","names":[]}