@elabs-ai/components-flow 4.1.0 → 5.0.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 (268) 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 +241 -2
  217. package/dist/index.js +167 -2306
  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 +14 -7
  238. package/src/__contract__/inspector-panel.contract.test.tsx +49 -0
  239. package/src/__contract__/legend.contract.test.tsx +49 -0
  240. package/src/canvas-shell/canvas-shell.stories.tsx +1 -0
  241. package/src/canvas-shell/canvas-shell.tsx +51 -4
  242. package/src/canvas-shell/clamped-fit-offset.test.ts +25 -0
  243. package/src/canvas-shell/clamped-fit-offset.ts +30 -0
  244. package/src/custom-nodes/custom-nodes.stories.tsx +718 -0
  245. package/src/flow-edge-tokens/edge-path-sampler.test.ts +108 -0
  246. package/src/flow-edge-tokens/edge-path-sampler.ts +362 -0
  247. package/src/flow-edge-tokens/flow-edge-tokens.stories.tsx +312 -0
  248. package/src/flow-edge-tokens/flow-edge-tokens.test.tsx +115 -0
  249. package/src/flow-edge-tokens/flow-edge-tokens.tsx +94 -0
  250. package/src/flow-edge-tokens/index.ts +12 -0
  251. package/src/flow-layout/backbone.test.ts +88 -0
  252. package/src/flow-layout/backbone.ts +58 -0
  253. package/src/flow-layout/layout-flow-elk.stories.tsx +154 -0
  254. package/src/flow-layout/layout-flow-elk.test.ts +152 -0
  255. package/src/flow-layout/layout-flow-elk.ts +399 -0
  256. package/src/flow-mini-map/flow-mini-map.stories.tsx +20 -0
  257. package/src/flow-node/flow-node.stories.tsx +40 -1
  258. package/src/flow-self-loop-edge/flow-self-loop-edge.test.tsx +38 -0
  259. package/src/flow-self-loop-edge/flow-self-loop-edge.tsx +7 -0
  260. package/src/flow-weighted-edge/edge-label-pill.test.tsx +20 -0
  261. package/src/flow-weighted-edge/flow-weighted-edge.stories.tsx +1 -0
  262. package/src/flow-weighted-edge/flow-weighted-edge.test.tsx +40 -0
  263. package/src/flow-weighted-edge/flow-weighted-edge.tsx +7 -0
  264. package/src/index.ts +13 -0
  265. package/src/inspector-panel/inspector-panel.tsx +9 -6
  266. package/src/legend/legend.test.tsx +11 -0
  267. package/src/legend/legend.tsx +15 -7
  268. package/src/templates-flow-workspace.stories.tsx +7 -2
@@ -0,0 +1,43 @@
1
+ "use client";
2
+
3
+ // src/flow-weighted-edge/edge-aria.ts
4
+ var DEFAULT_WEIGHT_LABEL = "weight";
5
+ var DEFAULT_VALUE_LABEL = "value";
6
+ function defaultNameOf(nodeId) {
7
+ return nodeId;
8
+ }
9
+ function defaultFormatNumber(n) {
10
+ return String(n);
11
+ }
12
+ function buildWeightedEdgeAriaLabel(edge, opts = {}) {
13
+ if (edge.ariaLabel) return edge.ariaLabel;
14
+ const { weight, value, label, secondaryLabel } = edge.data ?? {};
15
+ if (weight === void 0 && value === void 0 && !label && !secondaryLabel) {
16
+ return void 0;
17
+ }
18
+ const {
19
+ weightLabel = DEFAULT_WEIGHT_LABEL,
20
+ valueLabel = DEFAULT_VALUE_LABEL,
21
+ nameOf = defaultNameOf,
22
+ formatNumber = defaultFormatNumber
23
+ } = opts;
24
+ const parts = [`Edge from ${nameOf(edge.source)} to ${nameOf(edge.target)}`];
25
+ if (weight !== void 0) parts.push(`${weightLabel} ${formatNumber(weight)}`);
26
+ if (value !== void 0) parts.push(`${valueLabel} ${formatNumber(value)}`);
27
+ const pillText = [label, secondaryLabel].filter(Boolean).join(" ");
28
+ if (pillText) parts.push(pillText);
29
+ return parts.join(", ");
30
+ }
31
+ function withWeightedEdgeAria(edges, opts) {
32
+ return edges.map((edge) => {
33
+ if (edge.ariaLabel) return edge;
34
+ const ariaLabel = buildWeightedEdgeAriaLabel(edge, opts);
35
+ return ariaLabel === void 0 ? edge : { ...edge, ariaLabel };
36
+ });
37
+ }
38
+
39
+ export {
40
+ buildWeightedEdgeAriaLabel,
41
+ withWeightedEdgeAria
42
+ };
43
+ //# sourceMappingURL=chunk-FAFQ5KLB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-weighted-edge/edge-aria.ts"],"sourcesContent":["/**\n * edge-aria — pure, framework-free \"edge data → accessible name\" naming seam\n * for `FlowWeightedEdge`, mirroring `weight-scale.ts`'s shape (no React, no\n * DOM, trivially unit-testable).\n *\n * `FlowWeightedEdge` encodes `data.weight` as stroke width and, optionally,\n * `data.value` as stroke colour — both purely visual. React Flow's own\n * `EdgeWrapper` sources an edge's accessible name from `edge.ariaLabel`\n * (`edge` object, not the edge COMPONENT — see issue #285), so the component\n * has no channel to set it. This module is the seam a caller runs their\n * `edges` array through instead.\n *\n * Naming contract (a decision, not an accident):\n * 1. An explicit `edge.ariaLabel` always wins — never overwritten.\n * 2. Otherwise compose `\"Edge from <source> to <target>, weight <n>\"`,\n * appending `\", <valueLabel> <n>\"` when `data.value` is set and the pill\n * text (`data.label`/`data.secondaryLabel`, space-joined) when either is\n * present.\n * 3. When an edge carries none of `weight`/`value`/`label`/`secondaryLabel`,\n * return `undefined` so React Flow's own default\n * (\"Edge from <source> to <target>\") survives untouched. That quoted\n * string is `@xyflow/react`'s output, not ours — the `e-bare` edge in\n * `flow-weighted-edge.stories.tsx`'s `NamingContractEdges` story (#327)\n * is the browser lock for it: it renders a measure-less edge against a\n * real `CanvasShell` and asserts the default name via\n * `toHaveAccessibleName`. If that story goes red after a dependency\n * bump, read it as a signal that React Flow's default changed, not as a\n * flake to silence.\n */\n\nimport type { BrandFlowWeightedEdge } from \"./flow-weighted-edge\";\n\nexport interface WeightedEdgeAriaOptions {\n /** Word used for the width measure in the composed name. @default \"weight\" */\n weightLabel?: string;\n /** Word used for the colour measure in the composed name. @default \"value\" */\n valueLabel?: string;\n /**\n * Map a node id to its display name, so the composed name says\n * \"Edge from Order placed to Picked\" rather than \"Edge from n1 to n2\". The\n * caller owns node display names (this module never reaches into a `nodes`\n * array), so it is a hook, not an automatic lookup.\n */\n nameOf?: (nodeId: string) => string;\n /** Format a numeric measure for display. @default String(n) */\n formatNumber?: (n: number) => string;\n}\n\nconst DEFAULT_WEIGHT_LABEL = \"weight\";\nconst DEFAULT_VALUE_LABEL = \"value\";\n\nfunction defaultNameOf(nodeId: string): string {\n return nodeId;\n}\n\nfunction defaultFormatNumber(n: number): string {\n return String(n);\n}\n\n/**\n * Derive one edge's accessible name from its weight/value/labels. Pure — no\n * React, no DOM. Returns:\n * - the edge's own `ariaLabel`, unchanged, when the caller already set one;\n * - a composed string when the edge carries `weight`, `value`, `label` or\n * `secondaryLabel`;\n * - `undefined` when there is nothing to add (so the default announcement\n * from React Flow's `EdgeWrapper` survives untouched).\n */\nexport function buildWeightedEdgeAriaLabel(\n edge: BrandFlowWeightedEdge,\n opts: WeightedEdgeAriaOptions = {},\n): string | undefined {\n if (edge.ariaLabel) return edge.ariaLabel;\n\n const { weight, value, label, secondaryLabel } = edge.data ?? {};\n if (weight === undefined && value === undefined && !label && !secondaryLabel) {\n return undefined;\n }\n\n const {\n weightLabel = DEFAULT_WEIGHT_LABEL,\n valueLabel = DEFAULT_VALUE_LABEL,\n nameOf = defaultNameOf,\n formatNumber = defaultFormatNumber,\n } = opts;\n\n const parts = [`Edge from ${nameOf(edge.source)} to ${nameOf(edge.target)}`];\n if (weight !== undefined) parts.push(`${weightLabel} ${formatNumber(weight)}`);\n if (value !== undefined) parts.push(`${valueLabel} ${formatNumber(value)}`);\n\n const pillText = [label, secondaryLabel].filter(Boolean).join(\" \");\n if (pillText) parts.push(pillText);\n\n return parts.join(\", \");\n}\n\n/**\n * Stamp `ariaLabel` onto every weighted edge that has none, per\n * `buildWeightedEdgeAriaLabel`'s naming contract. An edge that already\n * carries an `ariaLabel` is returned unchanged (never overwritten); an edge\n * with nothing to add is also returned unchanged, so an unrelated edge type\n * sharing this array keeps its identity.\n *\n * Returns a NEW array — memoize the call when used inline in render (e.g.\n * `useMemo(() => withWeightedEdgeAria(edges), [edges])`), or a consumer hands\n * React Flow a new `edges` identity every frame.\n */\nexport function withWeightedEdgeAria<E extends BrandFlowWeightedEdge>(\n edges: E[],\n opts?: WeightedEdgeAriaOptions,\n): E[] {\n return edges.map((edge) => {\n if (edge.ariaLabel) return edge;\n const ariaLabel = buildWeightedEdgeAriaLabel(edge, opts);\n return ariaLabel === undefined ? edge : { ...edge, ariaLabel };\n });\n}\n"],"mappings":";;;AAgDA,IAAM,uBAAuB;AAC7B,IAAM,sBAAsB;AAE5B,SAAS,cAAc,QAAwB;AAC7C,SAAO;AACT;AAEA,SAAS,oBAAoB,GAAmB;AAC9C,SAAO,OAAO,CAAC;AACjB;AAWO,SAAS,2BACd,MACA,OAAgC,CAAC,GACb;AACpB,MAAI,KAAK,UAAW,QAAO,KAAK;AAEhC,QAAM,EAAE,QAAQ,OAAO,OAAO,eAAe,IAAI,KAAK,QAAQ,CAAC;AAC/D,MAAI,WAAW,UAAa,UAAU,UAAa,CAAC,SAAS,CAAC,gBAAgB;AAC5E,WAAO;AAAA,EACT;AAEA,QAAM;AAAA,IACJ,cAAc;AAAA,IACd,aAAa;AAAA,IACb,SAAS;AAAA,IACT,eAAe;AAAA,EACjB,IAAI;AAEJ,QAAM,QAAQ,CAAC,aAAa,OAAO,KAAK,MAAM,CAAC,OAAO,OAAO,KAAK,MAAM,CAAC,EAAE;AAC3E,MAAI,WAAW,OAAW,OAAM,KAAK,GAAG,WAAW,IAAI,aAAa,MAAM,CAAC,EAAE;AAC7E,MAAI,UAAU,OAAW,OAAM,KAAK,GAAG,UAAU,IAAI,aAAa,KAAK,CAAC,EAAE;AAE1E,QAAM,WAAW,CAAC,OAAO,cAAc,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AACjE,MAAI,SAAU,OAAM,KAAK,QAAQ;AAEjC,SAAO,MAAM,KAAK,IAAI;AACxB;AAaO,SAAS,qBACd,OACA,MACK;AACL,SAAO,MAAM,IAAI,CAAC,SAAS;AACzB,QAAI,KAAK,UAAW,QAAO;AAC3B,UAAM,YAAY,2BAA2B,MAAM,IAAI;AACvD,WAAO,cAAc,SAAY,OAAO,EAAE,GAAG,MAAM,UAAU;AAAA,EAC/D,CAAC;AACH;","names":[]}
@@ -0,0 +1,128 @@
1
+ "use client";
2
+ import {
3
+ FLOW_HANDLE_ANCHOR_CLASS
4
+ } from "./chunk-ROYAQKJA.js";
5
+
6
+ // src/flow-node/flow-node.tsx
7
+ import "react";
8
+ import { Handle, Position } from "@xyflow/react";
9
+ import { Star } from "lucide-react";
10
+ import { STATUS_TONE_ICONS } from "@elabs-ai/components-ui";
11
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ var FLOW_ALL_SIDE_HANDLES = {
14
+ source: ["top", "right", "bottom", "left"],
15
+ target: ["top", "right", "bottom", "left"]
16
+ };
17
+ var toneRing = {
18
+ default: "border-border",
19
+ accent: "border-primary",
20
+ success: "border-success",
21
+ warning: "border-warning",
22
+ destructive: "border-destructive"
23
+ };
24
+ var toneIcon = {
25
+ accent: Star,
26
+ success: STATUS_TONE_ICONS.success,
27
+ warning: STATUS_TONE_ICONS.warning,
28
+ destructive: STATUS_TONE_ICONS.destructive
29
+ };
30
+ var toneIconColor = {
31
+ accent: "text-primary",
32
+ success: "text-success-text",
33
+ warning: "text-warning-text",
34
+ destructive: "text-destructive-text"
35
+ };
36
+ var toneLabel = {
37
+ accent: "Highlighted",
38
+ success: "Success",
39
+ warning: "Warning",
40
+ destructive: "Destructive"
41
+ };
42
+ var sidePosition = {
43
+ top: Position.Top,
44
+ right: Position.Right,
45
+ bottom: Position.Bottom,
46
+ left: Position.Left
47
+ };
48
+ var handleClassName = `!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`;
49
+ function FlowNode({
50
+ data,
51
+ selected,
52
+ sourcePosition,
53
+ targetPosition
54
+ }) {
55
+ const tone = data.tone ?? "default";
56
+ const ToneIcon = toneIcon[tone];
57
+ return /* @__PURE__ */ jsxs(
58
+ "div",
59
+ {
60
+ "data-slot": "flow-node",
61
+ "data-tone": tone,
62
+ className: cn(
63
+ "min-w-44 rounded-lg border bg-flow-node px-3 py-2 text-flow-node-foreground shadow-sm transition-[box-shadow,border-color] duration-fast ease-standard",
64
+ toneRing[tone],
65
+ selected && "ring-2 ring-ring",
66
+ "[[data-id]:focus-visible_&]:focus-ring-static"
67
+ ),
68
+ children: [
69
+ data.handles ? /* @__PURE__ */ jsxs(Fragment, { children: [
70
+ (data.handles.target ?? []).map((side) => /* @__PURE__ */ jsx(
71
+ Handle,
72
+ {
73
+ id: side,
74
+ type: "target",
75
+ position: sidePosition[side],
76
+ className: handleClassName
77
+ },
78
+ `target-${side}`
79
+ )),
80
+ (data.handles.source ?? []).map((side) => /* @__PURE__ */ jsx(
81
+ Handle,
82
+ {
83
+ id: side,
84
+ type: "source",
85
+ position: sidePosition[side],
86
+ className: handleClassName
87
+ },
88
+ `source-${side}`
89
+ ))
90
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
91
+ /* @__PURE__ */ jsx(
92
+ Handle,
93
+ {
94
+ type: "target",
95
+ position: targetPosition ?? Position.Top,
96
+ className: handleClassName
97
+ }
98
+ ),
99
+ /* @__PURE__ */ jsx(
100
+ Handle,
101
+ {
102
+ type: "source",
103
+ position: sourcePosition ?? Position.Bottom,
104
+ className: handleClassName
105
+ }
106
+ )
107
+ ] }),
108
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
109
+ data.icon ? /* @__PURE__ */ jsx("span", { className: "[&_svg]:size-4 text-muted-foreground", children: data.icon }) : null,
110
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
111
+ data.kind ? /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: data.kind }) : null,
112
+ /* @__PURE__ */ jsx("div", { className: "truncate text-sm font-medium", children: data.title }),
113
+ data.subtitle ? /* @__PURE__ */ jsx("div", { className: "truncate text-xs text-muted-foreground", children: data.subtitle }) : null
114
+ ] }),
115
+ ToneIcon ? /* @__PURE__ */ jsx(ToneIcon, { "aria-hidden": "true", className: cn("size-3.5 shrink-0", toneIconColor[tone]) }) : null
116
+ ] }),
117
+ data.footer ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: data.footer }) : null,
118
+ toneLabel[tone] ? /* @__PURE__ */ jsx("span", { className: "sr-only", children: toneLabel[tone] }) : null
119
+ ]
120
+ }
121
+ );
122
+ }
123
+
124
+ export {
125
+ FLOW_ALL_SIDE_HANDLES,
126
+ FlowNode
127
+ };
128
+ //# sourceMappingURL=chunk-FIRO6Y3R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-node/flow-node.tsx"],"sourcesContent":["import { type ReactNode } from \"react\";\nimport { Handle, Position, type Node, type NodeProps } from \"@xyflow/react\";\nimport { Star, type LucideIcon } from \"lucide-react\";\nimport { STATUS_TONE_ICONS } from \"@elabs-ai/components-ui\";\nimport { FLOW_HANDLE_ANCHOR_CLASS } from \"../flow-handle/flow-handle-anchor\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\n/** A node side that can carry a handle. Doubles as the handle's stable id. */\nexport type FlowHandleSide = \"top\" | \"right\" | \"bottom\" | \"left\";\n\n/**\n * Which sides of a node expose source and/or target handles. When omitted,\n * `FlowNode` keeps its default single top **target** + bottom **source**. When\n * set, a `<Handle>` is rendered on each listed side with a **stable id equal to\n * the side name** (`\"top\" | \"right\" | \"bottom\" | \"left\"`), addressable per\n * handle type — so edges (e.g. `FlowSmartEdge`) can pick a specific anchor.\n */\nexport interface FlowNodeHandles {\n /** Sides exposing a source handle (handle `id` === side). */\n source?: FlowHandleSide[];\n /** Sides exposing a target handle (handle `id` === side). */\n target?: FlowHandleSide[];\n}\n\n/**\n * A `FlowNodeHandles` config exposing source **and** target anchors on all four\n * sides. The recommended setup for `FlowSmartEdge` / `FlowFloatingEdge`: with\n * anchors on every side, an edge connects on whichever side **faces** the other\n * node (left/right when side-by-side, top/bottom when stacked) instead of always\n * top/bottom. Spread onto a node's `data.handles`.\n */\nexport const FLOW_ALL_SIDE_HANDLES: FlowNodeHandles = {\n source: [\"top\", \"right\", \"bottom\", \"left\"],\n target: [\"top\", \"right\", \"bottom\", \"left\"],\n};\n\nexport interface FlowNodeData extends Record<string, unknown> {\n title: string;\n subtitle?: string;\n /** Short type label shown as an eyebrow, e.g. \"Source\", \"Transform\". */\n kind?: string;\n icon?: ReactNode;\n tone?: \"default\" | \"accent\" | \"success\" | \"warning\" | \"destructive\";\n /**\n * Optional multi-side handle configuration. Absent → default top-target /\n * bottom-source (unchanged, backward-compatible).\n */\n handles?: FlowNodeHandles;\n /**\n * An extra row rendered INSIDE the card, below the text block — a meter bar, a\n * sparkline, a chip row.\n *\n * It exists because content a composing package renders BESIDE `FlowNode` (as a sibling\n * inside React Flow's node element) silently breaks the canvas's geometry: React Flow\n * positions every `<Handle>` against the nearest positioned ancestor and measures the\n * node box from its own wrapper, so a sibling row makes the node box taller than the\n * visible card and the handles drift off the card's border by exactly that difference.\n * Measured on the process map's activity node, whose 6px meter and 4px gap put every\n * bottom dot 10px below the card it was supposed to sit on, and every left/right dot\n * 5px below the card's own mid-line.\n *\n * Put the row here instead and the card IS the node box again, so the dots land on the\n * card edge for free. Nothing is rendered when it is absent — existing nodes are\n * byte-identical.\n */\n footer?: ReactNode;\n}\n\nexport type BrandFlowNode = Node<FlowNodeData, \"brand\">;\n\nconst toneRing: Record<NonNullable<FlowNodeData[\"tone\"]>, string> = {\n default: \"border-border\",\n accent: \"border-primary\",\n success: \"border-success\",\n warning: \"border-warning\",\n destructive: \"border-destructive\",\n};\n\n/**\n * `tone` used to be encoded in colour ALONE — a 1px border and nothing else\n * (WCAG 1.4.1, #387). Every non-default tone now also gets a leading Lucide\n * glyph (`aria-hidden`, decorative — the sr-only text below carries the\n * meaning) and a distinct accessible name. `success`/`warning`/`destructive`\n * reuse `@elabs-ai/components-ui`'s `STATUS_TONE_ICONS` (the SAME glyph\n * `StatusBadge`/`StatusIcon` already pair with that tone) rather than\n * inventing a second icon vocabulary; `accent` has no canonical `StatusTone`\n * counterpart to reuse (the closed `StatusTone` enum is deliberately\n * CALM-ONLY and excludes a \"primary/highlighted\" bucket — see\n * `status-badge.tsx`'s docblock), so it gets the one net-new pairing.\n * `default` keeps neither — reaching for a glyph/label on every ordinary node\n * would be visual and assistive-tech noise on the common case.\n */\nconst toneIcon: Partial<Record<NonNullable<FlowNodeData[\"tone\"]>, LucideIcon>> = {\n accent: Star,\n success: STATUS_TONE_ICONS.success,\n warning: STATUS_TONE_ICONS.warning,\n destructive: STATUS_TONE_ICONS.destructive,\n};\n\n/** Ink for the tone glyph — the coloured-text rung (`-text`), not the fill rung. */\nconst toneIconColor: Partial<Record<NonNullable<FlowNodeData[\"tone\"]>, string>> = {\n accent: \"text-primary\",\n success: \"text-success-text\",\n warning: \"text-warning-text\",\n destructive: \"text-destructive-text\",\n};\n\n/** The accessible name the tone glyph stands in for (`sr-only`, #387). */\nconst toneLabel: Partial<Record<NonNullable<FlowNodeData[\"tone\"]>, string>> = {\n accent: \"Highlighted\",\n success: \"Success\",\n warning: \"Warning\",\n destructive: \"Destructive\",\n};\n\nconst sidePosition: Record<FlowHandleSide, Position> = {\n top: Position.Top,\n right: Position.Right,\n bottom: Position.Bottom,\n left: Position.Left,\n};\n\n// `FLOW_HANDLE_ANCHOR_CLASS` last: a connector dot must never be in flight when React\n// Flow measures it. See `flow-handle/flow-handle-anchor.ts`.\nconst handleClassName = `!size-2 !border-2 !border-flow-edge !bg-flow-node ${FLOW_HANDLE_ANCHOR_CLASS}`;\n\n/**\n * Branded custom node. Register it in `nodeTypes={{ brand: FlowNode }}` and\n * create nodes with `type: \"brand\"` and `data: FlowNodeData`.\n *\n * ## Focus vs selection (#312)\n *\n * `selected && \"ring-2 ring-ring\"` below is a SELECTION marker, not a focus\n * indicator — it is React Flow's own click-driven `selected` state and is the\n * genuine-selection carve-out `.claude/rules/theming.md` names explicitly.\n * Keyboard focus is a separate, independent signal this component used to omit\n * entirely (issue #312): React Flow puts `tabIndex`/`role=\"group\"` and the real\n * `:focus-visible` state on **its own wrapper** (`.react-flow__node`, which\n * also always carries a `data-id` attribute), one level ABOVE the `<div>` this\n * component returns — so neither `focus-ring` (`:focus-visible` on self) nor\n * `focus-ring-within` (`:focus-within`, a focused descendant) can ever fire\n * here; focus is PROXIED to an ancestor this component doesn't render.\n * `focus-ring-static` is the flavour built for that exact shape (ADR 0027),\n * gated by an ancestor-selector arbitrary variant — the same idiom\n * `FlowEdgePath` uses for `.react-flow__edge:focus-visible`, keyed on\n * `[data-id]` here (rather than the escaped `.react-flow\\_\\_node` class) so\n * the selector needs no backslash escaping inside a plain JS string — a\n * literal `\\_` in a `cn()` argument is a real JS string escape and would be\n * silently stripped at runtime (unlike in a bare, unbraced JSX attribute,\n * where backslashes are never processed — the reason `FlowEdgePath` can use\n * the class form safely and this component, composing through `cn()`, cannot).\n * The two signals compose without merging into one ring: `selected` alone\n * paints the ring layer only, while a focused node additionally gets the\n * `--ring-contour` outline drawn outside it, so \"selected AND focused\" reads\n * as two visible layers, not the single ring \"selected alone\" paints.\n */\nexport function FlowNode({\n data,\n selected,\n sourcePosition,\n targetPosition,\n}: NodeProps<BrandFlowNode>) {\n const tone = data.tone ?? \"default\";\n const ToneIcon = toneIcon[tone];\n return (\n <div\n // The PAINTED card, and the box every handle dot must sit on the border of.\n // `.react-flow__node` (the wrapper React Flow positions) can legitimately be\n // taller than this — a composing package may render a badge or a meter beside\n // the card — so a test that wants \"is the connector on the card?\" measures\n // against this slot, never against the wrapper. See `testing/canvas-framing`.\n data-slot=\"flow-node\"\n data-tone={tone}\n className={cn(\n \"min-w-44 rounded-lg border bg-flow-node px-3 py-2 text-flow-node-foreground shadow-sm transition-[box-shadow,border-color] duration-fast ease-standard\",\n toneRing[tone],\n selected && \"ring-2 ring-ring\",\n \"[[data-id]:focus-visible_&]:focus-ring-static\",\n )}\n >\n {data.handles ? (\n <>\n {(data.handles.target ?? []).map((side) => (\n <Handle\n key={`target-${side}`}\n id={side}\n type=\"target\"\n position={sidePosition[side]}\n className={handleClassName}\n />\n ))}\n {(data.handles.source ?? []).map((side) => (\n <Handle\n key={`source-${side}`}\n id={side}\n type=\"source\"\n position={sidePosition[side]}\n className={handleClassName}\n />\n ))}\n </>\n ) : (\n <>\n {/* Default single target + source. Positions follow the layout\n direction: React Flow passes `sourcePosition`/`targetPosition`\n (which `layoutFlow` sets per direction), so an LR layout puts the\n target on the LEFT and the source on the RIGHT. Falls back to\n top-in / bottom-out when unset. Handle placement is by `position`,\n not DOM order. */}\n <Handle\n type=\"target\"\n position={targetPosition ?? Position.Top}\n className={handleClassName}\n />\n <Handle\n type=\"source\"\n position={sourcePosition ?? Position.Bottom}\n className={handleClassName}\n />\n </>\n )}\n <div className=\"flex items-center gap-2\">\n {data.icon ? (\n <span className=\"[&_svg]:size-4 text-muted-foreground\">{data.icon}</span>\n ) : null}\n <div className=\"min-w-0 flex-1\">\n {data.kind ? (\n <div className=\"text-[10px] font-medium uppercase tracking-wide text-muted-foreground\">\n {data.kind}\n </div>\n ) : null}\n <div className=\"truncate text-sm font-medium\">{data.title}</div>\n {data.subtitle ? (\n <div className=\"truncate text-xs text-muted-foreground\">{data.subtitle}</div>\n ) : null}\n </div>\n {ToneIcon ? (\n <ToneIcon aria-hidden=\"true\" className={cn(\"size-3.5 shrink-0\", toneIconColor[tone])} />\n ) : null}\n </div>\n {data.footer ? <div className=\"mt-2\">{data.footer}</div> : null}\n {toneLabel[tone] ? <span className=\"sr-only\">{toneLabel[tone]}</span> : null}\n </div>\n );\n}\n"],"mappings":";;;;;;AAAA,OAA+B;AAC/B,SAAS,QAAQ,gBAA2C;AAC5D,SAAS,YAA6B;AACtC,SAAS,yBAAyB;AAElC,SAAS,UAAU;AAgLX,mBAEI,KAFJ;AAtJD,IAAM,wBAAyC;AAAA,EACpD,QAAQ,CAAC,OAAO,SAAS,UAAU,MAAM;AAAA,EACzC,QAAQ,CAAC,OAAO,SAAS,UAAU,MAAM;AAC3C;AAoCA,IAAM,WAA8D;AAAA,EAClE,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAgBA,IAAM,WAA2E;AAAA,EAC/E,QAAQ;AAAA,EACR,SAAS,kBAAkB;AAAA,EAC3B,SAAS,kBAAkB;AAAA,EAC3B,aAAa,kBAAkB;AACjC;AAGA,IAAM,gBAA4E;AAAA,EAChF,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAGA,IAAM,YAAwE;AAAA,EAC5E,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAEA,IAAM,eAAiD;AAAA,EACrD,KAAK,SAAS;AAAA,EACd,OAAO,SAAS;AAAA,EAChB,QAAQ,SAAS;AAAA,EACjB,MAAM,SAAS;AACjB;AAIA,IAAM,kBAAkB,qDAAqD,wBAAwB;AAgC9F,SAAS,SAAS;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,OAAO,KAAK,QAAQ;AAC1B,QAAM,WAAW,SAAS,IAAI;AAC9B,SACE;AAAA,IAAC;AAAA;AAAA,MAMC,aAAU;AAAA,MACV,aAAW;AAAA,MACX,WAAW;AAAA,QACT;AAAA,QACA,SAAS,IAAI;AAAA,QACb,YAAY;AAAA,QACZ;AAAA,MACF;AAAA,MAEC;AAAA,aAAK,UACJ,iCACI;AAAA,gBAAK,QAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,SAChC;AAAA,YAAC;AAAA;AAAA,cAEC,IAAI;AAAA,cACJ,MAAK;AAAA,cACL,UAAU,aAAa,IAAI;AAAA,cAC3B,WAAW;AAAA;AAAA,YAJN,UAAU,IAAI;AAAA,UAKrB,CACD;AAAA,WACC,KAAK,QAAQ,UAAU,CAAC,GAAG,IAAI,CAAC,SAChC;AAAA,YAAC;AAAA;AAAA,cAEC,IAAI;AAAA,cACJ,MAAK;AAAA,cACL,UAAU,aAAa,IAAI;AAAA,cAC3B,WAAW;AAAA;AAAA,YAJN,UAAU,IAAI;AAAA,UAKrB,CACD;AAAA,WACH,IAEA,iCAOE;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,UAAU,kBAAkB,SAAS;AAAA,cACrC,WAAW;AAAA;AAAA,UACb;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,UAAU,kBAAkB,SAAS;AAAA,cACrC,WAAW;AAAA;AAAA,UACb;AAAA,WACF;AAAA,QAEF,qBAAC,SAAI,WAAU,2BACZ;AAAA,eAAK,OACJ,oBAAC,UAAK,WAAU,wCAAwC,eAAK,MAAK,IAChE;AAAA,UACJ,qBAAC,SAAI,WAAU,kBACZ;AAAA,iBAAK,OACJ,oBAAC,SAAI,WAAU,yEACZ,eAAK,MACR,IACE;AAAA,YACJ,oBAAC,SAAI,WAAU,gCAAgC,eAAK,OAAM;AAAA,YACzD,KAAK,WACJ,oBAAC,SAAI,WAAU,0CAA0C,eAAK,UAAS,IACrE;AAAA,aACN;AAAA,UACC,WACC,oBAAC,YAAS,eAAY,QAAO,WAAW,GAAG,qBAAqB,cAAc,IAAI,CAAC,GAAG,IACpF;AAAA,WACN;AAAA,QACC,KAAK,SAAS,oBAAC,SAAI,WAAU,QAAQ,eAAK,QAAO,IAAS;AAAA,QAC1D,UAAU,IAAI,IAAI,oBAAC,UAAK,WAAU,WAAW,oBAAU,IAAI,GAAE,IAAU;AAAA;AAAA;AAAA,EAC1E;AAEJ;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-GE73WQUK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,39 @@
1
+ "use client";
2
+ import {
3
+ layoutFlow
4
+ } from "./chunk-HTLTCCFA.js";
5
+
6
+ // src/flow-layout/use-flow-layout.ts
7
+ import { useCallback, useRef, useState } from "react";
8
+ import { useNodesInitialized, useReactFlow } from "@xyflow/react";
9
+ function useFlowLayout() {
10
+ const { getNodes, getEdges, setNodes, fitView } = useReactFlow();
11
+ const nodesInitialized = useNodesInitialized();
12
+ const [layouting, setLayouting] = useState(false);
13
+ const runningRef = useRef(false);
14
+ const layout = useCallback(
15
+ (direction, options) => {
16
+ if (!nodesInitialized || runningRef.current) return;
17
+ runningRef.current = true;
18
+ setLayouting(true);
19
+ try {
20
+ const { nodes: layoutedNodes } = layoutFlow(getNodes(), getEdges(), {
21
+ ...options,
22
+ direction
23
+ });
24
+ setNodes(layoutedNodes);
25
+ requestAnimationFrame(() => fitView());
26
+ } finally {
27
+ runningRef.current = false;
28
+ setLayouting(false);
29
+ }
30
+ },
31
+ [nodesInitialized, getNodes, getEdges, setNodes, fitView]
32
+ );
33
+ return { layout, ready: nodesInitialized, layouting };
34
+ }
35
+
36
+ export {
37
+ useFlowLayout
38
+ };
39
+ //# sourceMappingURL=chunk-GIARAOU6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-layout/use-flow-layout.ts"],"sourcesContent":["import { useCallback, useRef, useState } from \"react\";\nimport { useNodesInitialized, useReactFlow, type Edge, type Node } from \"@xyflow/react\";\nimport { layoutFlow, type FlowLayoutDirection, type FlowLayoutOptions } from \"./flow-layout\";\n\nexport interface UseFlowLayoutResult {\n /**\n * Auto-lay-out the current nodes/edges with dagre and fit the view. Safe to\n * call repeatedly (e.g. from a toolbar button on every click). No-ops until\n * React Flow has measured every node.\n */\n layout: (direction?: FlowLayoutDirection, options?: Omit<FlowLayoutOptions, \"direction\">) => void;\n /** Whether nodes are measured and ready to be laid out. */\n ready: boolean;\n /** Whether a layout pass is currently running (true only for the duration of `layout()`). */\n layouting: boolean;\n}\n\n/**\n * Hook form of `layoutFlow`. Reads the live nodes/edges from `useReactFlow()`,\n * waits for `useNodesInitialized()` so real (measured) node sizes are used —\n * never lays out against 0×0 unmeasured nodes — applies the computed\n * positions via `setNodes`, then calls `fitView()`.\n *\n * Must be called from a component rendered inside a `<CanvasShell>` /\n * `<ReactFlow>` (React Flow context).\n */\nexport function useFlowLayout<\n NodeType extends Node = Node,\n EdgeType extends Edge = Edge,\n>(): UseFlowLayoutResult {\n const { getNodes, getEdges, setNodes, fitView } = useReactFlow<NodeType, EdgeType>();\n const nodesInitialized = useNodesInitialized();\n const [layouting, setLayouting] = useState(false);\n // Avoid overlapping layout passes if `layout()` is invoked in quick succession.\n const runningRef = useRef(false);\n\n const layout = useCallback(\n (direction?: FlowLayoutDirection, options?: Omit<FlowLayoutOptions, \"direction\">) => {\n if (!nodesInitialized || runningRef.current) return;\n\n runningRef.current = true;\n setLayouting(true);\n try {\n const { nodes: layoutedNodes } = layoutFlow<NodeType, EdgeType>(getNodes(), getEdges(), {\n ...options,\n direction,\n });\n setNodes(layoutedNodes);\n // Defer fitView one frame so React Flow has committed the new positions first.\n requestAnimationFrame(() => fitView());\n } finally {\n runningRef.current = false;\n setLayouting(false);\n }\n },\n [nodesInitialized, getNodes, getEdges, setNodes, fitView],\n );\n\n return { layout, ready: nodesInitialized, layouting };\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa,QAAQ,gBAAgB;AAC9C,SAAS,qBAAqB,oBAA0C;AAyBjE,SAAS,gBAGS;AACvB,QAAM,EAAE,UAAU,UAAU,UAAU,QAAQ,IAAI,aAAiC;AACnF,QAAM,mBAAmB,oBAAoB;AAC7C,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,KAAK;AAEhD,QAAM,aAAa,OAAO,KAAK;AAE/B,QAAM,SAAS;AAAA,IACb,CAAC,WAAiC,YAAmD;AACnF,UAAI,CAAC,oBAAoB,WAAW,QAAS;AAE7C,iBAAW,UAAU;AACrB,mBAAa,IAAI;AACjB,UAAI;AACF,cAAM,EAAE,OAAO,cAAc,IAAI,WAA+B,SAAS,GAAG,SAAS,GAAG;AAAA,UACtF,GAAG;AAAA,UACH;AAAA,QACF,CAAC;AACD,iBAAS,aAAa;AAEtB,8BAAsB,MAAM,QAAQ,CAAC;AAAA,MACvC,UAAE;AACA,mBAAW,UAAU;AACrB,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,kBAAkB,UAAU,UAAU,UAAU,OAAO;AAAA,EAC1D;AAEA,SAAO,EAAE,QAAQ,OAAO,kBAAkB,UAAU;AACtD;","names":[]}
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ // src/flow-layout/flow-layout.ts
4
+ import dagre from "@dagrejs/dagre";
5
+ import { Position } from "@xyflow/react";
6
+ var HANDLE_BY_DIRECTION = {
7
+ TB: { source: Position.Bottom, target: Position.Top },
8
+ BT: { source: Position.Top, target: Position.Bottom },
9
+ LR: { source: Position.Right, target: Position.Left },
10
+ RL: { source: Position.Left, target: Position.Right }
11
+ };
12
+ var DEFAULT_NODE_WIDTH = 172;
13
+ var DEFAULT_NODE_HEIGHT = 40;
14
+ function nodeSize(node) {
15
+ return {
16
+ width: node.measured?.width ?? node.width ?? DEFAULT_NODE_WIDTH,
17
+ height: node.measured?.height ?? node.height ?? DEFAULT_NODE_HEIGHT
18
+ };
19
+ }
20
+ function layoutFlow(nodes, edges, options = {}) {
21
+ const { direction = "TB", nodeSpacing = 48, rankSpacing = 72 } = options;
22
+ const graph = new dagre.graphlib.Graph();
23
+ graph.setDefaultEdgeLabel(() => ({}));
24
+ graph.setGraph({ rankdir: direction, nodesep: nodeSpacing, ranksep: rankSpacing });
25
+ for (const node of nodes) {
26
+ const { width, height } = nodeSize(node);
27
+ graph.setNode(node.id, { width, height });
28
+ }
29
+ const selfLoops = [];
30
+ for (const edge of edges) {
31
+ if (edge.source === edge.target) {
32
+ selfLoops.push(edge.id);
33
+ continue;
34
+ }
35
+ graph.setEdge(edge.source, edge.target);
36
+ }
37
+ dagre.layout(graph);
38
+ const backEdges = collectBackEdges(graph, edges);
39
+ const handles = HANDLE_BY_DIRECTION[direction];
40
+ const layoutedNodes = nodes.map((node) => {
41
+ const dagreNode = graph.node(node.id);
42
+ const { width, height } = nodeSize(node);
43
+ return {
44
+ ...node,
45
+ // Move the anchors with the layout direction so `FlowNode` puts its
46
+ // handles on the sides the edges actually leave/enter.
47
+ sourcePosition: handles.source,
48
+ targetPosition: handles.target,
49
+ position: {
50
+ x: dagreNode.x - width / 2,
51
+ y: dagreNode.y - height / 2
52
+ }
53
+ };
54
+ });
55
+ return { nodes: layoutedNodes, edges, backEdges, selfLoops };
56
+ }
57
+ function collectBackEdges(graph, edges) {
58
+ const backEdges = [];
59
+ for (const edge of edges) {
60
+ if (edge.source === edge.target) continue;
61
+ const sourceRank = graph.node(edge.source)?.rank;
62
+ const targetRank = graph.node(edge.target)?.rank;
63
+ if (typeof sourceRank !== "number" || typeof targetRank !== "number") continue;
64
+ if (sourceRank >= targetRank) backEdges.push(edge.id);
65
+ }
66
+ return backEdges;
67
+ }
68
+
69
+ export {
70
+ HANDLE_BY_DIRECTION,
71
+ layoutFlow
72
+ };
73
+ //# sourceMappingURL=chunk-HTLTCCFA.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-layout/flow-layout.ts"],"sourcesContent":["import dagre from \"@dagrejs/dagre\";\nimport { Position } from \"@xyflow/react\";\nimport type { Edge, Node } from \"@xyflow/react\";\n\n/** The graphlib graph `dagre.graphlib.Graph` produces — dagre exports no standalone type for it. */\ntype DagreGraph = InstanceType<typeof dagre.graphlib.Graph>;\n\n/**\n * The fields dagre writes back onto a node label during `layout()`. `rank` is\n * absent from dagre's own `.d.ts` (it is documented output, not declared\n * output), so it is narrowed here rather than asserted at the call site.\n */\ninterface DagreNodeLabel {\n x: number;\n y: number;\n rank?: number;\n}\n\n/** Direction dagre lays the graph out in — top-to-bottom, left-to-right, etc. */\nexport type FlowLayoutDirection = \"TB\" | \"LR\" | \"BT\" | \"RL\";\n\n/**\n * The handle sides an edge should attach to for each layout direction. A\n * directional layout must move the anchors with it: a left-to-right flow exits\n * the source's RIGHT and enters the target's LEFT; top-to-bottom uses\n * bottom/top. `layoutFlow` stamps these onto every node so `FlowNode` renders\n * its default handles on the correct sides (and `FlowEdge` routes accordingly)\n * — otherwise every layout is stuck with top/bottom handles.\n */\nexport const HANDLE_BY_DIRECTION: Record<\n FlowLayoutDirection,\n { source: Position; target: Position }\n> = {\n TB: { source: Position.Bottom, target: Position.Top },\n BT: { source: Position.Top, target: Position.Bottom },\n LR: { source: Position.Right, target: Position.Left },\n RL: { source: Position.Left, target: Position.Right },\n};\n\nexport interface FlowLayoutOptions {\n /** Layout direction. @default \"TB\" */\n direction?: FlowLayoutDirection;\n /** Gap between nodes in the same rank (dagre `nodesep`). @default 48 */\n nodeSpacing?: number;\n /** Gap between ranks (dagre `ranksep`). @default 72 */\n rankSpacing?: number;\n}\n\nexport interface FlowLayoutResult<NodeType extends Node = Node, EdgeType extends Edge = Edge> {\n nodes: NodeType[];\n edges: EdgeType[];\n /**\n * Ids of edges that run **against** the layout direction — a rework / retry\n * loop in a process graph. dagre breaks cycles by reversing such edges\n * internally and never surfaces which ones it reversed, so this is derived\n * from the ranks dagre stamps on the laid-out graph: an edge whose source\n * ranks at or after its target went backwards. Render these with\n * `FlowWeightedEdge`'s `variant=\"back\"`.\n */\n backEdges: string[];\n /**\n * Ids of edges whose `source === target`. dagre does not lay out self-loops,\n * so they are withheld from the graph entirely (never `setEdge`-ed) and are\n * returned unchanged in `edges` — they take part in no rank computation and\n * cannot distort the layout. Render these with `FlowSelfLoopEdge`.\n */\n selfLoops: string[];\n}\n\n/** Fallback size used when a node hasn't been measured yet (React Flow's own default node width). */\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/**\n * Pure dagre-powered auto layout. Computes new `position`s for `nodes` given\n * `edges` — no React, no side effects, safe to call anywhere (including\n * outside a component or in a test).\n *\n * Respects each node's **measured** size (`node.measured` — how React Flow\n * v12 reports actual rendered dimensions), falling back to `node.width`/\n * `node.height`, then a sensible default. Node identity and `data` are left\n * untouched — only `position` changes.\n *\n * Also reports the graph's two structural signals — `backEdges` (edges that run\n * against the layout direction) and `selfLoops` (`source === target`). Both are\n * additive fields on the result; a caller that only destructures\n * `{ nodes, edges }` is unaffected.\n *\n * Pair with `useFlowLayout` to apply the result to a live canvas.\n */\nexport function layoutFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(\n nodes: NodeType[],\n edges: EdgeType[],\n options: FlowLayoutOptions = {},\n): FlowLayoutResult<NodeType, EdgeType> {\n const { direction = \"TB\", nodeSpacing = 48, rankSpacing = 72 } = options;\n\n const graph = new dagre.graphlib.Graph();\n graph.setDefaultEdgeLabel(() => ({}));\n graph.setGraph({ rankdir: direction, nodesep: nodeSpacing, ranksep: rankSpacing });\n\n for (const node of nodes) {\n const { width, height } = nodeSize(node);\n graph.setNode(node.id, { width, height });\n }\n\n // Self-loops are withheld from dagre entirely: dagre does not lay them out,\n // and feeding them in only perturbs the ranks of a graph they say nothing\n // about. They are re-attached untouched in the returned `edges`.\n const selfLoops: string[] = [];\n for (const edge of edges) {\n if (edge.source === edge.target) {\n selfLoops.push(edge.id);\n continue;\n }\n graph.setEdge(edge.source, edge.target);\n }\n\n dagre.layout(graph);\n\n const backEdges = collectBackEdges(graph, edges);\n\n const handles = HANDLE_BY_DIRECTION[direction];\n const layoutedNodes = nodes.map((node) => {\n const dagreNode = graph.node(node.id);\n const { width, height } = nodeSize(node);\n // dagre positions nodes by their center; React Flow positions by top-left.\n return {\n ...node,\n // Move the anchors with the layout direction so `FlowNode` puts its\n // handles on the sides the edges actually leave/enter.\n sourcePosition: handles.source,\n targetPosition: handles.target,\n position: {\n x: dagreNode.x - width / 2,\n y: dagreNode.y - height / 2,\n },\n };\n });\n\n return { nodes: layoutedNodes, edges, backEdges, selfLoops };\n}\n\n/**\n * Which edges dagre had to run backwards, derived from the ranks it stamps on\n * the laid-out graph (`graph.node(id).rank`).\n *\n * The obvious alternative — reading `graph.edge(e).reversed` after dagre's\n * `acyclic.run` — is **not usable on the pinned `@dagrejs/dagre` 3.0.0**:\n * cycle breaking happens on an internal copy of the graph, and the caller's\n * graph carries no `reversed` flag once `dagre.layout()` returns (verified\n * against the installed version — every edge label is bare `{ points }`).\n * `rank` *is* on the public graph, and is direction-independent: it counts up\n * along the flow for every `rankdir`, so `rank(source) >= rank(target)` means\n * \"this edge does not advance the process\" in TB, BT, LR and RL alike.\n *\n * `>=` rather than `>` on purpose: a same-rank edge between two siblings is\n * not forward progress either, and dagre would have had to reverse or flatten\n * it. Self-loops never reach here — they are filtered out before layout.\n * An edge naming a node that isn't in the graph has no ranks to compare and is\n * left out rather than guessed at.\n */\nfunction collectBackEdges(graph: DagreGraph, edges: Edge[]): string[] {\n const backEdges: string[] = [];\n for (const edge of edges) {\n if (edge.source === edge.target) continue;\n const sourceRank = (graph.node(edge.source) as DagreNodeLabel | undefined)?.rank;\n const targetRank = (graph.node(edge.target) as DagreNodeLabel | undefined)?.rank;\n if (typeof sourceRank !== \"number\" || typeof targetRank !== \"number\") continue;\n if (sourceRank >= targetRank) backEdges.push(edge.id);\n }\n return backEdges;\n}\n"],"mappings":";;;AAAA,OAAO,WAAW;AAClB,SAAS,gBAAgB;AA4BlB,IAAM,sBAGT;AAAA,EACF,IAAI,EAAE,QAAQ,SAAS,QAAQ,QAAQ,SAAS,IAAI;AAAA,EACpD,IAAI,EAAE,QAAQ,SAAS,KAAK,QAAQ,SAAS,OAAO;AAAA,EACpD,IAAI,EAAE,QAAQ,SAAS,OAAO,QAAQ,SAAS,KAAK;AAAA,EACpD,IAAI,EAAE,QAAQ,SAAS,MAAM,QAAQ,SAAS,MAAM;AACtD;AAiCA,IAAM,qBAAqB;AAC3B,IAAM,sBAAsB;AAE5B,SAAS,SAAS,MAA+C;AAC/D,SAAO;AAAA,IACL,OAAO,KAAK,UAAU,SAAS,KAAK,SAAS;AAAA,IAC7C,QAAQ,KAAK,UAAU,UAAU,KAAK,UAAU;AAAA,EAClD;AACF;AAmBO,SAAS,WACd,OACA,OACA,UAA6B,CAAC,GACQ;AACtC,QAAM,EAAE,YAAY,MAAM,cAAc,IAAI,cAAc,GAAG,IAAI;AAEjE,QAAM,QAAQ,IAAI,MAAM,SAAS,MAAM;AACvC,QAAM,oBAAoB,OAAO,CAAC,EAAE;AACpC,QAAM,SAAS,EAAE,SAAS,WAAW,SAAS,aAAa,SAAS,YAAY,CAAC;AAEjF,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,OAAO,OAAO,IAAI,SAAS,IAAI;AACvC,UAAM,QAAQ,KAAK,IAAI,EAAE,OAAO,OAAO,CAAC;AAAA,EAC1C;AAKA,QAAM,YAAsB,CAAC;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,KAAK,QAAQ;AAC/B,gBAAU,KAAK,KAAK,EAAE;AACtB;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,QAAQ,KAAK,MAAM;AAAA,EACxC;AAEA,QAAM,OAAO,KAAK;AAElB,QAAM,YAAY,iBAAiB,OAAO,KAAK;AAE/C,QAAM,UAAU,oBAAoB,SAAS;AAC7C,QAAM,gBAAgB,MAAM,IAAI,CAAC,SAAS;AACxC,UAAM,YAAY,MAAM,KAAK,KAAK,EAAE;AACpC,UAAM,EAAE,OAAO,OAAO,IAAI,SAAS,IAAI;AAEvC,WAAO;AAAA,MACL,GAAG;AAAA;AAAA;AAAA,MAGH,gBAAgB,QAAQ;AAAA,MACxB,gBAAgB,QAAQ;AAAA,MACxB,UAAU;AAAA,QACR,GAAG,UAAU,IAAI,QAAQ;AAAA,QACzB,GAAG,UAAU,IAAI,SAAS;AAAA,MAC5B;AAAA,IACF;AAAA,EACF,CAAC;AAED,SAAO,EAAE,OAAO,eAAe,OAAO,WAAW,UAAU;AAC7D;AAqBA,SAAS,iBAAiB,OAAmB,OAAyB;AACpE,QAAM,YAAsB,CAAC;AAC7B,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,KAAK,OAAQ;AACjC,UAAM,aAAc,MAAM,KAAK,KAAK,MAAM,GAAkC;AAC5E,UAAM,aAAc,MAAM,KAAK,KAAK,MAAM,GAAkC;AAC5E,QAAI,OAAO,eAAe,YAAY,OAAO,eAAe,SAAU;AACtE,QAAI,cAAc,WAAY,WAAU,KAAK,KAAK,EAAE;AAAA,EACtD;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,56 @@
1
+ "use client";
2
+
3
+ // src/flow-weighted-edge/edge-label-pill.tsx
4
+ import { EdgeLabelRenderer } from "@xyflow/react";
5
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
6
+ import { jsx, jsxs } from "react/jsx-runtime";
7
+ function EdgeLabelPill({
8
+ label,
9
+ secondaryLabel,
10
+ x,
11
+ y,
12
+ selected,
13
+ className,
14
+ ...props
15
+ }) {
16
+ if (!label && !secondaryLabel) return null;
17
+ const accessibleName = [label, secondaryLabel].filter(Boolean).join(" \xB7 ");
18
+ return /* @__PURE__ */ jsx(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx(
19
+ "div",
20
+ {
21
+ style: {
22
+ position: "absolute",
23
+ transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,
24
+ pointerEvents: "none"
25
+ },
26
+ className: "nodrag nopan",
27
+ "data-slot": "edge-label-pill-anchor",
28
+ children: /* @__PURE__ */ jsxs(
29
+ "button",
30
+ {
31
+ type: "button",
32
+ "aria-label": accessibleName,
33
+ "data-slot": "edge-label-pill",
34
+ ...props,
35
+ className: cn(
36
+ "pointer-events-auto flex items-center gap-1 rounded-full border bg-flow-node px-2 py-0.5",
37
+ "text-meta font-medium text-flow-node-foreground shadow-sm",
38
+ "transition-colors duration-fast ease-standard",
39
+ "focus-ring",
40
+ selected ? "border-ring" : "border-flow-group-border",
41
+ className
42
+ ),
43
+ children: [
44
+ label ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: label }) : null,
45
+ secondaryLabel ? /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: "text-flow-node-foreground/70 tabular-nums", children: secondaryLabel }) : null
46
+ ]
47
+ }
48
+ )
49
+ }
50
+ ) });
51
+ }
52
+
53
+ export {
54
+ EdgeLabelPill
55
+ };
56
+ //# sourceMappingURL=chunk-I53W3CFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-weighted-edge/edge-label-pill.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef } from \"react\";\nimport { EdgeLabelRenderer } from \"@xyflow/react\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nexport interface EdgeLabelPillProps extends ComponentPropsWithoutRef<\"button\"> {\n /** Primary label, e.g. a frequency count (\"128×\"). */\n label?: string;\n /** Secondary label rendered alongside the primary, e.g. a duration (\"3.4d avg\"). */\n secondaryLabel?: string;\n /** Label anchor, from `getBezierPath`/`getSmoothStepPath`'s `labelX`/`labelY`. */\n x: number;\n y: number;\n /** Matches the parent edge's `selected` state. */\n selected?: boolean;\n}\n\n/**\n * A small HTML pill (via `EdgeLabelRenderer`, not SVG `<text>`) anchored at an\n * edge's label point — so it can theme, wrap, and carry two values, unlike a\n * bare SVG text node. Renders nothing when neither label is set. Real\n * `<button>` so it is a genuine keyboard tab stop with a visible focus ring;\n * `pointer-events: auto` on an otherwise `nodrag nopan` wrapper so it doesn't\n * drag/pan the canvas, and doesn't block hovering the edge underneath it (the\n * wrapper is sized to the pill itself, not the whole edge).\n *\n * `className`/`...props` spread onto the root `<button>` (`data-slot=\"edge-label-pill\"`)\n * so a consumer that composes this pill from outside `@elabs-ai/components-flow` —\n * `@elabs-ai/components-process`'s `ProcessTransitionEdge` is the reference caller —\n * can reach it directly (a dashed frame, a `data-selection` attribute) without a new\n * semantic prop on this component. `className` merges LAST via `cn()`, so a caller can\n * override any of the pill's own utility classes; omitting both leaves every existing\n * caller's rendered markup unchanged.\n */\nexport function EdgeLabelPill({\n label,\n secondaryLabel,\n x,\n y,\n selected,\n className,\n ...props\n}: EdgeLabelPillProps) {\n if (!label && !secondaryLabel) return null;\n\n const accessibleName = [label, secondaryLabel].filter(Boolean).join(\" · \");\n\n return (\n <EdgeLabelRenderer>\n <div\n style={{\n position: \"absolute\",\n transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,\n pointerEvents: \"none\",\n }}\n className=\"nodrag nopan\"\n data-slot=\"edge-label-pill-anchor\"\n >\n <button\n type=\"button\"\n aria-label={accessibleName}\n data-slot=\"edge-label-pill\"\n {...props}\n className={cn(\n \"pointer-events-auto flex items-center gap-1 rounded-full border bg-flow-node px-2 py-0.5\",\n \"text-meta font-medium text-flow-node-foreground shadow-sm\",\n \"transition-colors duration-fast ease-standard\",\n \"focus-ring\",\n selected ? \"border-ring\" : \"border-flow-group-border\",\n className,\n )}\n >\n {label ? <span aria-hidden=\"true\">{label}</span> : null}\n {secondaryLabel ? (\n <span aria-hidden=\"true\" className=\"text-flow-node-foreground/70 tabular-nums\">\n {secondaryLabel}\n </span>\n ) : null}\n </button>\n </div>\n </EdgeLabelRenderer>\n );\n}\n"],"mappings":";;;AACA,SAAS,yBAAyB;AAClC,SAAS,UAAU;AAuDX,SAcW,KAdX;AAxBD,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAuB;AACrB,MAAI,CAAC,SAAS,CAAC,eAAgB,QAAO;AAEtC,QAAM,iBAAiB,CAAC,OAAO,cAAc,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK;AAEzE,SACE,oBAAC,qBACC;AAAA,IAAC;AAAA;AAAA,MACC,OAAO;AAAA,QACL,UAAU;AAAA,QACV,WAAW,mCAAmC,CAAC,OAAO,CAAC;AAAA,QACvD,eAAe;AAAA,MACjB;AAAA,MACA,WAAU;AAAA,MACV,aAAU;AAAA,MAEV;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAY;AAAA,UACZ,aAAU;AAAA,UACT,GAAG;AAAA,UACJ,WAAW;AAAA,YACT;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,YACA,WAAW,gBAAgB;AAAA,YAC3B;AAAA,UACF;AAAA,UAEC;AAAA,oBAAQ,oBAAC,UAAK,eAAY,QAAQ,iBAAM,IAAU;AAAA,YAClD,iBACC,oBAAC,UAAK,eAAY,QAAO,WAAU,6CAChC,0BACH,IACE;AAAA;AAAA;AAAA,MACN;AAAA;AAAA,EACF,GACF;AAEJ;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-IJVFPUXO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ // src/canvas-shell/use-measured-nodes.ts
4
+ import { useCallback, useMemo, useRef, useState } from "react";
5
+ function useMeasuredNodes(nodes, onNodesChange) {
6
+ const measured = useRef(/* @__PURE__ */ new Map());
7
+ const [version, setVersion] = useState(0);
8
+ const handleNodesChange = useCallback(
9
+ (changes) => {
10
+ let touched = false;
11
+ for (const change of changes) {
12
+ if (change.type !== "dimensions" || !change.dimensions) continue;
13
+ const previous = measured.current.get(change.id);
14
+ if (previous?.width === change.dimensions.width && previous.height === change.dimensions.height) {
15
+ continue;
16
+ }
17
+ measured.current.set(change.id, { ...change.dimensions });
18
+ touched = true;
19
+ }
20
+ onNodesChange?.(changes);
21
+ if (touched) setVersion((n) => n + 1);
22
+ },
23
+ [onNodesChange]
24
+ );
25
+ const mergedNodes = useMemo(() => {
26
+ if (!nodes) return nodes;
27
+ void version;
28
+ let changed = false;
29
+ const next = nodes.map((node) => {
30
+ const dimensions = measured.current.get(node.id);
31
+ if (!dimensions) return node;
32
+ if (node.measured?.width === dimensions.width && node.measured?.height === dimensions.height) {
33
+ return node;
34
+ }
35
+ changed = true;
36
+ return { ...node, measured: dimensions };
37
+ });
38
+ return changed ? next : nodes;
39
+ }, [nodes, version]);
40
+ return {
41
+ nodes: mergedNodes,
42
+ // Only take the handler over when there is something to measure INTO. An uncontrolled
43
+ // canvas is left exactly as it was.
44
+ onNodesChange: nodes ? handleNodesChange : onNodesChange
45
+ };
46
+ }
47
+
48
+ export {
49
+ useMeasuredNodes
50
+ };
51
+ //# sourceMappingURL=chunk-JUPMOISE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas-shell/use-measured-nodes.ts"],"sourcesContent":["import { useCallback, useMemo, useRef, useState } from \"react\";\nimport type { Node, NodeChange, OnNodesChange } from \"@xyflow/react\";\n\n/**\n * Feed React Flow's own measurements back onto the node objects a CONTROLLED canvas\n * renders.\n *\n * React Flow measures every node in the DOM and keeps the result on its INTERNAL node\n * record; the objects the consumer passed in `nodes` are never touched. That is fine for\n * the renderer — edges and the viewport read the internal record — but three shipped\n * surfaces read `node.measured` off the USER object instead, and each of them silently\n * degrades when it is absent:\n *\n * - **`<MiniMap>`** bails out per node in `nodeHasDimensions(internals.userNode)` and\n * renders NOTHING. Measured: a `CanvasShell` with 11 nodes drew 0 minimap rects while\n * the minimap's own `viewBox` (computed from the internal records) was correct — a\n * blank white panel, in the flow package's own `FlowMiniMap` story as much as in a\n * composing package's.\n * - **`layoutFlow`** falls back to `DEFAULT_NODE_WIDTH`/`DEFAULT_NODE_HEIGHT`\n * (172×40) and hands dagre a node half the height of the real card, so ranks are laid\n * out too close together and edge labels collide with the nodes below them.\n * - Any consumer arithmetic over `node.measured` — the same trap, one layer out.\n *\n * React Flow's own examples never hit this because they drive the canvas with\n * `useNodesState`, whose `onNodesChange` applies the `dimensions` change back into the\n * array. A canvas driven from derived data (a discovered process graph, a layout hook)\n * legitimately has no such setter, and gets a half-working canvas with no error. This\n * hook closes that gap centrally so no consumer has to know about it: it remembers the\n * dimensions React Flow reports and merges them into the nodes on the way past.\n *\n * It is deliberately a NO-OP in the two cases where React Flow already does the right\n * thing: an UNCONTROLLED canvas (`defaultNodes`, where `hasDefaultNodes` makes React Flow\n * apply the changes itself) never reaches here because `nodes` is undefined, and a\n * controlled canvas whose consumer already applies dimension changes finds `measured`\n * equal to the cached value and returns the very same array identity.\n */\nexport interface MeasuredNodesResult<NodeType extends Node> {\n /** `nodes`, with React Flow's measured dimensions merged in. Same identity when nothing changed. */\n nodes: NodeType[] | undefined;\n /** The handler to give React Flow — records dimensions, then calls the consumer's own. */\n onNodesChange: OnNodesChange<NodeType> | undefined;\n}\n\nexport function useMeasuredNodes<NodeType extends Node>(\n nodes: NodeType[] | undefined,\n onNodesChange: OnNodesChange<NodeType> | undefined,\n): MeasuredNodesResult<NodeType> {\n const measured = useRef(new Map<string, { width: number; height: number }>());\n // A version counter, not the map itself: the map is a ref so the change handler stays\n // stable, and this is what tells the memo below that its contents moved.\n const [version, setVersion] = useState(0);\n\n const handleNodesChange = useCallback<OnNodesChange<NodeType>>(\n (changes: NodeChange<NodeType>[]) => {\n let touched = false;\n for (const change of changes) {\n if (change.type !== \"dimensions\" || !change.dimensions) continue;\n const previous = measured.current.get(change.id);\n if (\n previous?.width === change.dimensions.width &&\n previous.height === change.dimensions.height\n ) {\n continue;\n }\n measured.current.set(change.id, { ...change.dimensions });\n touched = true;\n }\n onNodesChange?.(changes);\n if (touched) setVersion((n) => n + 1);\n },\n [onNodesChange],\n );\n\n const mergedNodes = useMemo(() => {\n if (!nodes) return nodes;\n // `version` is read so this recomputes when the ref's contents move; the map itself\n // is intentionally not a dependency (a ref never changes identity).\n void version;\n let changed = false;\n const next = nodes.map((node) => {\n const dimensions = measured.current.get(node.id);\n if (!dimensions) return node;\n if (\n node.measured?.width === dimensions.width &&\n node.measured?.height === dimensions.height\n ) {\n return node;\n }\n changed = true;\n return { ...node, measured: dimensions };\n });\n return changed ? next : nodes;\n }, [nodes, version]);\n\n return {\n nodes: mergedNodes,\n // Only take the handler over when there is something to measure INTO. An uncontrolled\n // canvas is left exactly as it was.\n onNodesChange: nodes ? handleNodesChange : onNodesChange,\n };\n}\n"],"mappings":";;;AAAA,SAAS,aAAa,SAAS,QAAQ,gBAAgB;AA2ChD,SAAS,iBACd,OACA,eAC+B;AAC/B,QAAM,WAAW,OAAO,oBAAI,IAA+C,CAAC;AAG5E,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,CAAC;AAExC,QAAM,oBAAoB;AAAA,IACxB,CAAC,YAAoC;AACnC,UAAI,UAAU;AACd,iBAAW,UAAU,SAAS;AAC5B,YAAI,OAAO,SAAS,gBAAgB,CAAC,OAAO,WAAY;AACxD,cAAM,WAAW,SAAS,QAAQ,IAAI,OAAO,EAAE;AAC/C,YACE,UAAU,UAAU,OAAO,WAAW,SACtC,SAAS,WAAW,OAAO,WAAW,QACtC;AACA;AAAA,QACF;AACA,iBAAS,QAAQ,IAAI,OAAO,IAAI,EAAE,GAAG,OAAO,WAAW,CAAC;AACxD,kBAAU;AAAA,MACZ;AACA,sBAAgB,OAAO;AACvB,UAAI,QAAS,YAAW,CAAC,MAAM,IAAI,CAAC;AAAA,IACtC;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AAEA,QAAM,cAAc,QAAQ,MAAM;AAChC,QAAI,CAAC,MAAO,QAAO;AAGnB,SAAK;AACL,QAAI,UAAU;AACd,UAAM,OAAO,MAAM,IAAI,CAAC,SAAS;AAC/B,YAAM,aAAa,SAAS,QAAQ,IAAI,KAAK,EAAE;AAC/C,UAAI,CAAC,WAAY,QAAO;AACxB,UACE,KAAK,UAAU,UAAU,WAAW,SACpC,KAAK,UAAU,WAAW,WAAW,QACrC;AACA,eAAO;AAAA,MACT;AACA,gBAAU;AACV,aAAO,EAAE,GAAG,MAAM,UAAU,WAAW;AAAA,IACzC,CAAC;AACD,WAAO,UAAU,OAAO;AAAA,EAC1B,GAAG,CAAC,OAAO,OAAO,CAAC;AAEnB,SAAO;AAAA,IACL,OAAO;AAAA;AAAA;AAAA,IAGP,eAAe,QAAQ,oBAAoB;AAAA,EAC7C;AACF;","names":[]}