@elabs-ai/components-flow 4.2.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 (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 +236 -1
  217. package/dist/index.js +167 -2311
  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 +718 -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 +13 -0
  263. package/src/templates-flow-workspace.stories.tsx +7 -2
@@ -0,0 +1,167 @@
1
+ "use client";
2
+ import {
3
+ backEdgeDetour
4
+ } from "./chunk-ZXZTLQIX.js";
5
+ import {
6
+ EdgeLabelPill
7
+ } from "./chunk-I53W3CFN.js";
8
+ import {
9
+ DEFAULT_EDGE_WIDTH_RANGE,
10
+ computeEdgeWeightScale
11
+ } from "./chunk-3BTU2VWZ.js";
12
+ import {
13
+ FlowEdgeTokens
14
+ } from "./chunk-ROJEIIA4.js";
15
+ import {
16
+ FlowEdgePath
17
+ } from "./chunk-SXXJV6YJ.js";
18
+
19
+ // src/flow-weighted-edge/flow-weighted-edge.tsx
20
+ import { useCallback, useMemo } from "react";
21
+ import {
22
+ Position,
23
+ getBezierPath,
24
+ getSmoothStepPath,
25
+ useEdges,
26
+ useStore
27
+ } from "@xyflow/react";
28
+ import { resolveTokenColor } from "@elabs-ai/components-tokens";
29
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
30
+ var NO_NODES = [];
31
+ var FALLBACK_WEAK = "#6085a1";
32
+ var FALLBACK_STRONG = "#496d89";
33
+ var BACK_EDGE_DASHARRAY = "6 4";
34
+ var BACK_EDGE_OPACITY = 0.7;
35
+ var BACK_EDGE_CLEARANCE = 40;
36
+ var DEFAULT_BACK_EDGE_LABEL = "Back edge \u2014 runs against the process direction";
37
+ function clamp01(t) {
38
+ return Math.min(1, Math.max(0, t));
39
+ }
40
+ function hexToRgb(hex) {
41
+ const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(hex);
42
+ if (!m) return [0, 0, 0];
43
+ return [parseInt(m[1], 16), parseInt(m[2], 16), parseInt(m[3], 16)];
44
+ }
45
+ function toHexByte(n) {
46
+ return Math.round(Math.min(255, Math.max(0, n))).toString(16).padStart(2, "0");
47
+ }
48
+ function mixHex(a, b, t) {
49
+ const [ar, ag, ab] = hexToRgb(a);
50
+ const [br, bg, bb] = hexToRgb(b);
51
+ const r = ar + (br - ar) * t;
52
+ const g = ag + (bg - ag) * t;
53
+ const bl = ab + (bb - ab) * t;
54
+ return `#${toHexByte(r)}${toHexByte(g)}${toHexByte(bl)}`;
55
+ }
56
+ function resolveValueStrokeColor(value, domain) {
57
+ if (value === void 0 || !domain) return void 0;
58
+ const [lo, hi] = domain;
59
+ const t = hi === lo ? 1 : clamp01((value - lo) / (hi - lo));
60
+ const weak = resolveTokenColor("--flow-edge-weak", { fallback: FALLBACK_WEAK });
61
+ const strong = resolveTokenColor("--flow-edge-strong", { fallback: FALLBACK_STRONG });
62
+ return mixHex(weak, strong, t);
63
+ }
64
+ function FlowWeightedEdge({
65
+ id,
66
+ sourceX,
67
+ sourceY,
68
+ targetX,
69
+ targetY,
70
+ sourcePosition,
71
+ targetPosition,
72
+ markerEnd,
73
+ style,
74
+ selected,
75
+ data
76
+ }) {
77
+ const edges = useEdges();
78
+ const widthByEdgeId = useMemo(
79
+ () => computeEdgeWeightScale(edges),
80
+ [edges]
81
+ );
82
+ const variant = data?.variant ?? "forward";
83
+ const isBack = variant === "back";
84
+ const nodes = useStore(
85
+ useCallback((s) => isBack ? s.nodes : NO_NODES, [isBack])
86
+ );
87
+ const pathType = data?.path ?? "bezier";
88
+ const axis = targetPosition === Position.Top || targetPosition === Position.Bottom ? "vertical" : "horizontal";
89
+ const rects = useMemo(() => {
90
+ if (!isBack) return [];
91
+ const out = [];
92
+ for (const node of nodes) {
93
+ if (node.parentId) continue;
94
+ const width = node.measured?.width ?? node.width;
95
+ const height = node.measured?.height ?? node.height;
96
+ if (!width || !height) continue;
97
+ out.push({ x: node.position.x, y: node.position.y, width, height });
98
+ }
99
+ return out;
100
+ }, [isBack, nodes]);
101
+ const detour = isBack ? backEdgeDetour(
102
+ rects,
103
+ axis,
104
+ axis === "vertical" ? [sourceY, targetY] : [sourceX, targetX],
105
+ BACK_EDGE_CLEARANCE
106
+ ) : null;
107
+ const [edgePath, labelX, labelY] = isBack ? getSmoothStepPath({
108
+ sourceX,
109
+ sourceY,
110
+ sourcePosition,
111
+ targetX,
112
+ targetY,
113
+ targetPosition,
114
+ offset: BACK_EDGE_CLEARANCE,
115
+ ...detour === null ? {} : axis === "vertical" ? { centerX: detour } : { centerY: detour }
116
+ }) : pathType === "smoothstep" ? getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition }) : getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });
117
+ const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];
118
+ const valueColor = useMemo(
119
+ () => resolveValueStrokeColor(data?.value, data?.valueDomain),
120
+ [data?.value, data?.valueDomain]
121
+ );
122
+ const stroke = selected ? "var(--ring)" : valueColor ?? "var(--flow-edge)";
123
+ const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;
124
+ const edge = /* @__PURE__ */ jsx(
125
+ FlowEdgePath,
126
+ {
127
+ id,
128
+ path: edgePath,
129
+ markerEnd,
130
+ "data-slot": "flow-weighted-edge",
131
+ "data-variant": variant,
132
+ "data-weight": data?.weight,
133
+ "data-value": data?.value,
134
+ stroke,
135
+ strokeWidth,
136
+ strokeDasharray: isBack ? BACK_EDGE_DASHARRAY : void 0,
137
+ strokeOpacity: isBack ? BACK_EDGE_OPACITY : void 0,
138
+ style
139
+ }
140
+ );
141
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
142
+ isBack ? (
143
+ // A `data-variant` is invisible to assistive technology, so the back
144
+ // edge's meaning is also published as a named graphic. Only the back
145
+ // variant is wrapped — a forward edge's DOM is unchanged from before
146
+ // this prop existed.
147
+ /* @__PURE__ */ jsx("g", { role: "img", "aria-label": data?.variantLabel ?? DEFAULT_BACK_EDGE_LABEL, children: edge })
148
+ ) : edge,
149
+ data?.tokens?.length ? /* @__PURE__ */ jsx(FlowEdgeTokens, { path: edgePath, tokens: data.tokens }) : null,
150
+ /* @__PURE__ */ jsx(
151
+ EdgeLabelPill,
152
+ {
153
+ label: data?.label,
154
+ secondaryLabel: data?.secondaryLabel,
155
+ x: labelX,
156
+ y: labelY,
157
+ selected,
158
+ ...data?.labelProps
159
+ }
160
+ )
161
+ ] });
162
+ }
163
+
164
+ export {
165
+ FlowWeightedEdge
166
+ };
167
+ //# sourceMappingURL=chunk-K5RPSVO6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-weighted-edge/flow-weighted-edge.tsx"],"sourcesContent":["import { useCallback, useMemo } from \"react\";\nimport {\n Position,\n getBezierPath,\n getSmoothStepPath,\n useEdges,\n useStore,\n type Edge,\n type EdgeProps,\n type ReactFlowState,\n} from \"@xyflow/react\";\nimport { resolveTokenColor } from \"@elabs-ai/components-tokens\";\nimport { FlowEdgePath } from \"../flow-edge-path\";\nimport { FlowEdgeTokens, type FlowEdgeToken } from \"../flow-edge-tokens\";\nimport { EdgeLabelPill, type EdgeLabelPillProps } from \"./edge-label-pill\";\nimport { backEdgeDetour, type BackEdgeNodeRect } from \"./back-edge-geometry\";\nimport {\n computeEdgeWeightScale,\n DEFAULT_EDGE_WIDTH_RANGE,\n type WeightedEdgeLike,\n} from \"./weight-scale\";\n\n/** Stable empty array for the forward branch of the node subscription below. */\nconst NO_NODES: ReactFlowState[\"nodes\"] = [];\n\nexport interface FlowWeightedEdgeData extends Record<string, unknown> {\n /** Frequency/volume this edge carries. Scaled into stroke width — see `computeEdgeWeightScale`. */\n weight?: number;\n /** Edges sharing a `scaleGroup` share one min-max width domain. @default all edges in the flow */\n scaleGroup?: string;\n /** A second, continuous measure (e.g. average duration). Colours the stroke — needs `valueDomain` too. */\n value?: number;\n /** `[min, max]` domain `value` is interpolated across, from `--flow-edge-weak` to `--flow-edge-strong`. */\n valueDomain?: [number, number];\n /** Primary edge-label-pill text, e.g. a frequency count. */\n label?: string;\n /** Secondary edge-label-pill text, e.g. a duration. */\n secondaryLabel?: string;\n /** Path geometry. Ignored when `variant` is `\"back\"`, which always routes smoothstep. @default \"bezier\" */\n path?: \"bezier\" | \"smoothstep\";\n /**\n * Whether this edge advances the process (`\"forward\"`) or runs against the\n * layout direction (`\"back\"` — a rework/retry edge, as reported by\n * `layoutFlow`'s `backEdges`).\n *\n * `\"back\"` is distinguished by SHAPE, not colour: a dashed stroke, and a\n * smoothstep route pushed clear of the forward edge between the same pair of\n * nodes so the two never overlap. It also carries a real accessible name, so\n * the direction reaches assistive technology as text rather than only as a\n * `data-variant` attribute.\n *\n * @default \"forward\"\n */\n variant?: \"forward\" | \"back\";\n /**\n * Overrides the accessible name given to a `\"back\"` edge's graphic. Defaults\n * to \"Back edge — runs against the process direction\".\n */\n variantLabel?: string;\n /**\n * Passed straight through to the rendered `EdgeLabelPill`'s `className`/`...props`\n * (see `EdgeLabelPillProps`) — the seam a composing package (e.g.\n * `@elabs-ai/components-process`'s `ProcessTransitionEdge`) uses to reach the pill's\n * own root button from outside this component, without a new semantic prop here.\n */\n labelProps?: Omit<EdgeLabelPillProps, \"label\" | \"secondaryLabel\" | \"x\" | \"y\" | \"selected\">;\n /**\n * Markers travelling along this edge's computed path (see `FlowEdgeTokens`). The parent\n * drives each token's `progress`; omitted or empty renders exactly the edge without them.\n */\n tokens?: readonly FlowEdgeToken[];\n}\n\nexport type BrandFlowWeightedEdge = Edge<FlowWeightedEdgeData, \"weighted\">;\n\n// Approximate hex fallbacks for `--flow-edge-weak`/`--flow-edge-strong`, used\n// only when the CSS custom property can't be read (SSR, or the tokens\n// stylesheet isn't loaded yet) — resolveTokenColor() reads the live theme\n// value whenever a `document` is available.\nconst FALLBACK_WEAK = \"#6085a1\";\nconst FALLBACK_STRONG = \"#496d89\";\n\n/**\n * Shape channel for `variant=\"back\"`: a dash pattern plus a reduced stroke\n * opacity on the same `--flow-edge` token. Dashes are readable in greyscale\n * and under any theme, so the back edge never depends on hue (WCAG 1.4.1).\n */\nconst BACK_EDGE_DASHARRAY = \"6 4\";\nconst BACK_EDGE_OPACITY = 0.7;\n/**\n * Clearance, in px, between a back edge and everything it must stay clear OF.\n *\n * Two uses. As `getSmoothStepPath`'s `offset` it is how far the path runs straight out of\n * a handle before it turns, which is what keeps it off the forward edge joining the same\n * two nodes. As the gap in {@link backEdgeDetour} it is how far past the outermost card\n * the return leg sits — see that function for why a card-relative placement, rather than\n * a nudge off the midpoint, is the only one that stays visible.\n */\nconst BACK_EDGE_CLEARANCE = 40;\n\nconst DEFAULT_BACK_EDGE_LABEL = \"Back edge — runs against the process direction\";\n\nfunction clamp01(t: number): number {\n return Math.min(1, Math.max(0, t));\n}\n\nfunction hexToRgb(hex: string): [number, number, number] {\n const m = /^#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})/i.exec(hex);\n if (!m) return [0, 0, 0];\n return [parseInt(m[1]!, 16), parseInt(m[2]!, 16), parseInt(m[3]!, 16)];\n}\n\nfunction toHexByte(n: number): string {\n return Math.round(Math.min(255, Math.max(0, n)))\n .toString(16)\n .padStart(2, \"0\");\n}\n\n/** Linear RGB interpolation between two hex colors — pure, no DOM. */\nfunction mixHex(a: string, b: string, t: number): string {\n const [ar, ag, ab] = hexToRgb(a);\n const [br, bg, bb] = hexToRgb(b);\n const r = ar + (br - ar) * t;\n const g = ag + (bg - ag) * t;\n const bl = ab + (bb - ab) * t;\n return `#${toHexByte(r)}${toHexByte(g)}${toHexByte(bl)}`;\n}\n\n/**\n * Resolve `data.value`/`data.valueDomain` into a stroke color interpolated\n * between the `--flow-edge-weak`/`--flow-edge-strong` tokens. Returns\n * `undefined` when either input is missing, so the caller can fall back to\n * the plain `--flow-edge` token (unweighted-looking) edge color.\n */\nfunction resolveValueStrokeColor(\n value: number | undefined,\n domain: [number, number] | undefined,\n): string | undefined {\n if (value === undefined || !domain) return undefined;\n const [lo, hi] = domain;\n const t = hi === lo ? 1 : clamp01((value - lo) / (hi - lo));\n const weak = resolveTokenColor(\"--flow-edge-weak\", { fallback: FALLBACK_WEAK });\n const strong = resolveTokenColor(\"--flow-edge-strong\", { fallback: FALLBACK_STRONG });\n return mixHex(weak, strong, t);\n}\n\n/**\n * Branded weighted edge: `data.weight` scales stroke width (min-maxed per\n * `data.scaleGroup` across every edge in the flow — see `computeEdgeWeightScale`,\n * exported so a sibling like a continuous `Legend` renders the same ramp);\n * `data.value` + `data.valueDomain` interpolate stroke colour between\n * `--flow-edge-weak` and `--flow-edge-strong`; `data.label`/`data.secondaryLabel`\n * render as an `EdgeLabelPill`. An edge with none of this data renders exactly\n * like `FlowEdge` (fixed 1.5px, `--flow-edge` token) — fully backward-compatible.\n * Register it in `edgeTypes={{ weighted: FlowWeightedEdge }}` and create edges\n * with `type: \"weighted\"` and `data: FlowWeightedEdgeData`.\n *\n * `weight`/`value` are VISUAL ONLY — stroke width and colour reach no screen\n * reader. This component cannot set its own accessible name: React Flow's\n * `EdgeWrapper` sources it from `edge.ariaLabel` on the edge OBJECT, one level\n * above the component it renders as a child (issue #285). Run your `edges`\n * through `withWeightedEdgeAria` (from `./edge-aria`, or set `edge.ariaLabel`\n * yourself) before handing them to `<CanvasShell>`/`<ReactFlow>` — otherwise\n * the measure this component exists to show reaches no assistive technology,\n * and the edge announces only React Flow's generic \"Edge from n1 to n2\".\n * `withWeightedEdgeAria` returns a new array each call — memoize it\n * (`useMemo(() => withWeightedEdgeAria(edges), [edges])`) if you call it\n * inline in render.\n *\n * `data-weight`/`data-value` are also stamped onto the rendered `<path>` (raw\n * `data.weight`/`data.value`, not the scaled stroke width) — a stable\n * selector for tests/consumers, independent of the naming seam above.\n *\n * `data.variant: \"back\"` marks an edge that runs against the process direction\n * (dagre's reversed edges — see `layoutFlow`'s `backEdges`). It is dashed and\n * routed clear of the forward edge between the same two nodes, and carries a\n * real accessible name; the default `\"forward\"` renders exactly as before.\n *\n * Selected state uses `--ring` (matching the `ring-ring` treatment `FlowNode`/\n * `FlowGroupNode` use), overriding weight/value-derived width and colour so a\n * selected edge always reads clearly. No stroke-dasharray animation — reduced\n * motion is respected because there is no motion to reduce.\n *\n * KEYBOARD FOCUS is a separate state, drawn by `FlowEdgePath` (#286): selection\n * needs a consumer's `onEdgesChange` to ever become true, so it can never be the\n * indicator a tab stop owes its user.\n */\nexport function FlowWeightedEdge({\n id,\n sourceX,\n sourceY,\n targetX,\n targetY,\n sourcePosition,\n targetPosition,\n markerEnd,\n style,\n selected,\n data,\n}: EdgeProps<BrandFlowWeightedEdge>) {\n const edges = useEdges();\n const widthByEdgeId = useMemo(\n () => computeEdgeWeightScale(edges as unknown as WeightedEdgeLike[]),\n [edges],\n );\n\n const variant = data?.variant ?? \"forward\";\n const isBack = variant === \"back\";\n // Only a BACK edge needs the node rects (to route its return leg around the cards it\n // crosses). `useNodes()` would subscribe every edge to the whole nodes array, which the\n // store replaces on each drag frame, selection and resize — so every forward edge on\n // the canvas re-rendered on any node change anywhere. The forward branch hands back one\n // frozen constant, which `useStore`'s default `Object.is` compares equal every time.\n const nodes = useStore(\n useCallback((s: ReactFlowState) => (isBack ? s.nodes : NO_NODES), [isBack]),\n );\n const pathType = data?.path ?? \"bezier\";\n\n // Ranks advance vertically when the handles are on the top/bottom faces. Read from the\n // TARGET side: `sourcePosition` on a self-connecting or hand-placed edge can disagree,\n // and it is the incoming face that decides which way the last leg must approach from.\n const axis =\n targetPosition === Position.Top || targetPosition === Position.Bottom\n ? \"vertical\"\n : \"horizontal\";\n const rects = useMemo<BackEdgeNodeRect[]>(() => {\n if (!isBack) return [];\n const out: BackEdgeNodeRect[] = [];\n for (const node of nodes) {\n // A child node's `position` is parent-relative, so it is not comparable with the\n // absolute handle coordinates this edge is placed against. Skipping one only costs\n // a little clearance; mixing coordinate spaces would move the leg somewhere wrong.\n if (node.parentId) continue;\n const width = node.measured?.width ?? node.width;\n const height = node.measured?.height ?? node.height;\n if (!width || !height) continue;\n out.push({ x: node.position.x, y: node.position.y, width, height });\n }\n return out;\n }, [isBack, nodes]);\n const detour = isBack\n ? backEdgeDetour(\n rects,\n axis,\n axis === \"vertical\" ? [sourceY, targetY] : [sourceX, targetX],\n BACK_EDGE_CLEARANCE,\n )\n : null;\n\n const [edgePath, labelX, labelY] = isBack\n ? getSmoothStepPath({\n sourceX,\n sourceY,\n sourcePosition,\n targetX,\n targetY,\n targetPosition,\n offset: BACK_EDGE_CLEARANCE,\n ...(detour === null ? {} : axis === \"vertical\" ? { centerX: detour } : { centerY: detour }),\n })\n : pathType === \"smoothstep\"\n ? getSmoothStepPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition })\n : getBezierPath({ sourceX, sourceY, sourcePosition, targetX, targetY, targetPosition });\n\n const scaledWidth = widthByEdgeId.get(id) ?? DEFAULT_EDGE_WIDTH_RANGE[0];\n const valueColor = useMemo(\n () => resolveValueStrokeColor(data?.value, data?.valueDomain),\n [data?.value, data?.valueDomain],\n );\n\n const stroke = selected ? \"var(--ring)\" : (valueColor ?? \"var(--flow-edge)\");\n const strokeWidth = selected ? scaledWidth + 1.5 : scaledWidth;\n\n const edge = (\n <FlowEdgePath\n id={id}\n path={edgePath}\n markerEnd={markerEnd}\n data-slot=\"flow-weighted-edge\"\n data-variant={variant}\n data-weight={data?.weight}\n data-value={data?.value}\n stroke={stroke}\n strokeWidth={strokeWidth}\n strokeDasharray={isBack ? BACK_EDGE_DASHARRAY : undefined}\n strokeOpacity={isBack ? BACK_EDGE_OPACITY : undefined}\n style={style}\n />\n );\n\n return (\n <>\n {isBack ? (\n // A `data-variant` is invisible to assistive technology, so the back\n // edge's meaning is also published as a named graphic. Only the back\n // variant is wrapped — a forward edge's DOM is unchanged from before\n // this prop existed.\n <g role=\"img\" aria-label={data?.variantLabel ?? DEFAULT_BACK_EDGE_LABEL}>\n {edge}\n </g>\n ) : (\n edge\n )}\n {data?.tokens?.length ? <FlowEdgeTokens path={edgePath} tokens={data.tokens} /> : null}\n <EdgeLabelPill\n label={data?.label}\n secondaryLabel={data?.secondaryLabel}\n x={labelX}\n y={labelY}\n selected={selected}\n {...data?.labelProps}\n />\n </>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,SAAS,aAAa,eAAe;AACrC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,yBAAyB;AAuQ9B,SAiBA,UAjBA,KAiBA,YAjBA;AA3PJ,IAAM,WAAoC,CAAC;AAwD3C,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;AAOxB,IAAM,sBAAsB;AAC5B,IAAM,oBAAoB;AAU1B,IAAM,sBAAsB;AAE5B,IAAM,0BAA0B;AAEhC,SAAS,QAAQ,GAAmB;AAClC,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC;AACnC;AAEA,SAAS,SAAS,KAAuC;AACvD,QAAM,IAAI,8CAA8C,KAAK,GAAG;AAChE,MAAI,CAAC,EAAG,QAAO,CAAC,GAAG,GAAG,CAAC;AACvB,SAAO,CAAC,SAAS,EAAE,CAAC,GAAI,EAAE,GAAG,SAAS,EAAE,CAAC,GAAI,EAAE,GAAG,SAAS,EAAE,CAAC,GAAI,EAAE,CAAC;AACvE;AAEA,SAAS,UAAU,GAAmB;AACpC,SAAO,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAC5C,SAAS,EAAE,EACX,SAAS,GAAG,GAAG;AACpB;AAGA,SAAS,OAAO,GAAW,GAAW,GAAmB;AACvD,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,CAAC;AAC/B,QAAM,CAAC,IAAI,IAAI,EAAE,IAAI,SAAS,CAAC;AAC/B,QAAM,IAAI,MAAM,KAAK,MAAM;AAC3B,QAAM,IAAI,MAAM,KAAK,MAAM;AAC3B,QAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,SAAO,IAAI,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,UAAU,EAAE,CAAC;AACxD;AAQA,SAAS,wBACP,OACA,QACoB;AACpB,MAAI,UAAU,UAAa,CAAC,OAAQ,QAAO;AAC3C,QAAM,CAAC,IAAI,EAAE,IAAI;AACjB,QAAM,IAAI,OAAO,KAAK,IAAI,SAAS,QAAQ,OAAO,KAAK,GAAG;AAC1D,QAAM,OAAO,kBAAkB,oBAAoB,EAAE,UAAU,cAAc,CAAC;AAC9E,QAAM,SAAS,kBAAkB,sBAAsB,EAAE,UAAU,gBAAgB,CAAC;AACpF,SAAO,OAAO,MAAM,QAAQ,CAAC;AAC/B;AA2CO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAqC;AACnC,QAAM,QAAQ,SAAS;AACvB,QAAM,gBAAgB;AAAA,IACpB,MAAM,uBAAuB,KAAsC;AAAA,IACnE,CAAC,KAAK;AAAA,EACR;AAEA,QAAM,UAAU,MAAM,WAAW;AACjC,QAAM,SAAS,YAAY;AAM3B,QAAM,QAAQ;AAAA,IACZ,YAAY,CAAC,MAAuB,SAAS,EAAE,QAAQ,UAAW,CAAC,MAAM,CAAC;AAAA,EAC5E;AACA,QAAM,WAAW,MAAM,QAAQ;AAK/B,QAAM,OACJ,mBAAmB,SAAS,OAAO,mBAAmB,SAAS,SAC3D,aACA;AACN,QAAM,QAAQ,QAA4B,MAAM;AAC9C,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,UAAM,MAA0B,CAAC;AACjC,eAAW,QAAQ,OAAO;AAIxB,UAAI,KAAK,SAAU;AACnB,YAAM,QAAQ,KAAK,UAAU,SAAS,KAAK;AAC3C,YAAM,SAAS,KAAK,UAAU,UAAU,KAAK;AAC7C,UAAI,CAAC,SAAS,CAAC,OAAQ;AACvB,UAAI,KAAK,EAAE,GAAG,KAAK,SAAS,GAAG,GAAG,KAAK,SAAS,GAAG,OAAO,OAAO,CAAC;AAAA,IACpE;AACA,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,KAAK,CAAC;AAClB,QAAM,SAAS,SACX;AAAA,IACE;AAAA,IACA;AAAA,IACA,SAAS,aAAa,CAAC,SAAS,OAAO,IAAI,CAAC,SAAS,OAAO;AAAA,IAC5D;AAAA,EACF,IACA;AAEJ,QAAM,CAAC,UAAU,QAAQ,MAAM,IAAI,SAC/B,kBAAkB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,GAAI,WAAW,OAAO,CAAC,IAAI,SAAS,aAAa,EAAE,SAAS,OAAO,IAAI,EAAE,SAAS,OAAO;AAAA,EAC3F,CAAC,IACD,aAAa,eACX,kBAAkB,EAAE,SAAS,SAAS,gBAAgB,SAAS,SAAS,eAAe,CAAC,IACxF,cAAc,EAAE,SAAS,SAAS,gBAAgB,SAAS,SAAS,eAAe,CAAC;AAE1F,QAAM,cAAc,cAAc,IAAI,EAAE,KAAK,yBAAyB,CAAC;AACvE,QAAM,aAAa;AAAA,IACjB,MAAM,wBAAwB,MAAM,OAAO,MAAM,WAAW;AAAA,IAC5D,CAAC,MAAM,OAAO,MAAM,WAAW;AAAA,EACjC;AAEA,QAAM,SAAS,WAAW,gBAAiB,cAAc;AACzD,QAAM,cAAc,WAAW,cAAc,MAAM;AAEnD,QAAM,OACJ;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,aAAU;AAAA,MACV,gBAAc;AAAA,MACd,eAAa,MAAM;AAAA,MACnB,cAAY,MAAM;AAAA,MAClB;AAAA,MACA;AAAA,MACA,iBAAiB,SAAS,sBAAsB;AAAA,MAChD,eAAe,SAAS,oBAAoB;AAAA,MAC5C;AAAA;AAAA,EACF;AAGF,SACE,iCACG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAKC,oBAAC,OAAE,MAAK,OAAM,cAAY,MAAM,gBAAgB,yBAC7C,gBACH;AAAA,QAEA;AAAA,IAED,MAAM,QAAQ,SAAS,oBAAC,kBAAe,MAAM,UAAU,QAAQ,KAAK,QAAQ,IAAK;AAAA,IAClF;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,MAAM;AAAA,QACb,gBAAgB,MAAM;AAAA,QACtB,GAAG;AAAA,QACH,GAAG;AAAA,QACH;AAAA,QACC,GAAG,MAAM;AAAA;AAAA,IACZ;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,78 @@
1
+ "use client";
2
+ import {
3
+ getEdgeParams
4
+ } from "./chunk-A7AFM3LI.js";
5
+ import {
6
+ FlowEdgePath
7
+ } from "./chunk-SXXJV6YJ.js";
8
+
9
+ // src/flow-floating-edge/flow-floating-edge.tsx
10
+ import { getBezierPath, useInternalNode } from "@xyflow/react";
11
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
12
+ var ANCHOR_RADIUS = 4;
13
+ function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
14
+ const sourceNode = useInternalNode(source);
15
+ const targetNode = useInternalNode(target);
16
+ if (!sourceNode || !targetNode) return null;
17
+ const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
18
+ const [edgePath] = getBezierPath({
19
+ sourceX: sx,
20
+ sourceY: sy,
21
+ sourcePosition: sourcePos,
22
+ targetX: tx,
23
+ targetY: ty,
24
+ targetPosition: targetPos
25
+ });
26
+ const showAnchors = data?.anchors !== false;
27
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
28
+ /* @__PURE__ */ jsx(
29
+ FlowEdgePath,
30
+ {
31
+ id,
32
+ path: edgePath,
33
+ markerEnd,
34
+ "data-slot": "flow-floating-edge",
35
+ stroke: "var(--flow-edge)",
36
+ strokeWidth: 1.5,
37
+ style
38
+ }
39
+ ),
40
+ showAnchors ? /* @__PURE__ */ jsxs(
41
+ "g",
42
+ {
43
+ className: "brand-floating-edge__anchors",
44
+ fill: "var(--flow-node)",
45
+ stroke: "var(--flow-edge)",
46
+ children: [
47
+ /* @__PURE__ */ jsx(
48
+ "circle",
49
+ {
50
+ cx: sx,
51
+ cy: sy,
52
+ r: ANCHOR_RADIUS,
53
+ strokeWidth: 2,
54
+ style: { pointerEvents: "none" },
55
+ "aria-hidden": "true"
56
+ }
57
+ ),
58
+ /* @__PURE__ */ jsx(
59
+ "circle",
60
+ {
61
+ cx: tx,
62
+ cy: ty,
63
+ r: ANCHOR_RADIUS,
64
+ strokeWidth: 2,
65
+ style: { pointerEvents: "none" },
66
+ "aria-hidden": "true"
67
+ }
68
+ )
69
+ ]
70
+ }
71
+ ) : null
72
+ ] });
73
+ }
74
+
75
+ export {
76
+ FlowFloatingEdge
77
+ };
78
+ //# sourceMappingURL=chunk-K6U27YJ6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/flow-floating-edge/flow-floating-edge.tsx"],"sourcesContent":["import { getBezierPath, useInternalNode, type EdgeProps } from \"@xyflow/react\";\nimport { FlowEdgePath } from \"../flow-edge-path\";\nimport { getEdgeParams } from \"./floating-edge-geometry\";\n\n/** Optional per-edge `data` for {@link FlowFloatingEdge}. */\nexport interface FloatingEdgeData {\n /**\n * Show a small anchor dot at each border connection point. On by default so the\n * edge visibly terminates on the node's closest side (rather than a bare line\n * touching the border with no anchor). Set `false` to hide.\n */\n anchors?: boolean;\n [key: string]: unknown;\n}\n\n/** Radius of the connection anchor dot (matches the `FlowNode` handle size). */\nconst ANCHOR_RADIUS = 4;\n\n/**\n * Branded floating edge: it attaches to the node **border** at the point facing\n * the other node (no fixed handle), recomputed as nodes drag. Register it in\n * `edgeTypes={{ floating: FlowFloatingEdge }}`; the connected nodes need no\n * handle config. Uses the `--flow-edge` token, matching `FlowEdge`.\n *\n * A small **anchor dot** is drawn at each connection point (on by default) so the\n * line clearly terminates on the node's closest border side — not at a bare,\n * unanchored spot. Toggle per edge with `data.anchors: false`.\n */\nexport function FlowFloatingEdge({ id, source, target, markerEnd, style, data }: EdgeProps) {\n const sourceNode = useInternalNode(source);\n const targetNode = useInternalNode(target);\n if (!sourceNode || !targetNode) return null;\n\n const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);\n\n const [edgePath] = getBezierPath({\n sourceX: sx,\n sourceY: sy,\n sourcePosition: sourcePos,\n targetX: tx,\n targetY: ty,\n targetPosition: targetPos,\n });\n\n const showAnchors = (data as FloatingEdgeData | undefined)?.anchors !== false;\n\n return (\n <>\n <FlowEdgePath\n id={id}\n path={edgePath}\n markerEnd={markerEnd}\n data-slot=\"flow-floating-edge\"\n stroke=\"var(--flow-edge)\"\n strokeWidth={1.5}\n style={style}\n />\n {showAnchors ? (\n <g\n className=\"brand-floating-edge__anchors\"\n fill=\"var(--flow-node)\"\n stroke=\"var(--flow-edge)\"\n >\n {/* Decorative rings matching the FlowNode handle look; never intercept\n pointer events so nodes stay draggable through them. */}\n <circle\n cx={sx}\n cy={sy}\n r={ANCHOR_RADIUS}\n strokeWidth={2}\n style={{ pointerEvents: \"none\" }}\n aria-hidden=\"true\"\n />\n <circle\n cx={tx}\n cy={ty}\n r={ANCHOR_RADIUS}\n strokeWidth={2}\n style={{ pointerEvents: \"none\" }}\n aria-hidden=\"true\"\n />\n </g>\n ) : null}\n </>\n );\n}\n"],"mappings":";;;;;;;;;AAAA,SAAS,eAAe,uBAAuC;AA+C3D,mBACE,KAUE,YAXJ;AA/BJ,IAAM,gBAAgB;AAYf,SAAS,iBAAiB,EAAE,IAAI,QAAQ,QAAQ,WAAW,OAAO,KAAK,GAAc;AAC1F,QAAM,aAAa,gBAAgB,MAAM;AACzC,QAAM,aAAa,gBAAgB,MAAM;AACzC,MAAI,CAAC,cAAc,CAAC,WAAY,QAAO;AAEvC,QAAM,EAAE,IAAI,IAAI,IAAI,IAAI,WAAW,UAAU,IAAI,cAAc,YAAY,UAAU;AAErF,QAAM,CAAC,QAAQ,IAAI,cAAc;AAAA,IAC/B,SAAS;AAAA,IACT,SAAS;AAAA,IACT,gBAAgB;AAAA,IAChB,SAAS;AAAA,IACT,SAAS;AAAA,IACT,gBAAgB;AAAA,EAClB,CAAC;AAED,QAAM,cAAe,MAAuC,YAAY;AAExE,SACE,iCACE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,aAAU;AAAA,QACV,QAAO;AAAA,QACP,aAAa;AAAA,QACb;AAAA;AAAA,IACF;AAAA,IACC,cACC;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,QAAO;AAAA,QAIP;AAAA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI;AAAA,cACJ,IAAI;AAAA,cACJ,GAAG;AAAA,cACH,aAAa;AAAA,cACb,OAAO,EAAE,eAAe,OAAO;AAAA,cAC/B,eAAY;AAAA;AAAA,UACd;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,IAAI;AAAA,cACJ,IAAI;AAAA,cACJ,GAAG;AAAA,cACH,aAAa;AAAA,cACb,OAAO,EAAE,eAAe,OAAO;AAAA,cAC/B,eAAY;AAAA;AAAA,UACd;AAAA;AAAA;AAAA,IACF,IACE;AAAA,KACN;AAEJ;","names":[]}
@@ -0,0 +1,2 @@
1
+ "use client";
2
+ //# sourceMappingURL=chunk-KDVMGMAK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import {
3
+ collapseGroup,
4
+ expandGroup,
5
+ groupNodes,
6
+ toggleGroupCollapsed,
7
+ ungroup
8
+ } from "./chunk-TGWPM2CG.js";
9
+
10
+ // src/use-flow-groups/use-flow-groups.ts
11
+ import { useCallback } from "react";
12
+ import { useReactFlow } from "@xyflow/react";
13
+ function generateGroupId() {
14
+ return `flow-group-${Math.random().toString(36).slice(2, 9)}`;
15
+ }
16
+ function useFlowGroups() {
17
+ const { getNodes, getEdges, setNodes, setEdges } = useReactFlow();
18
+ const apply = useCallback(
19
+ (result) => {
20
+ setNodes(result.nodes);
21
+ setEdges(result.edges);
22
+ },
23
+ [setNodes, setEdges]
24
+ );
25
+ const groupNodes2 = useCallback(
26
+ (nodeIds, options) => {
27
+ if (nodeIds.length === 0) return void 0;
28
+ const groupId = options?.groupId ?? generateGroupId();
29
+ const result = groupNodes(getNodes(), getEdges(), nodeIds, {
30
+ ...options,
31
+ groupId
32
+ });
33
+ apply(result);
34
+ return groupId;
35
+ },
36
+ [getNodes, getEdges, apply]
37
+ );
38
+ const groupSelection = useCallback(
39
+ (options) => {
40
+ const selectedIds = getNodes().filter((n) => n.selected && !n.parentId).map((n) => n.id);
41
+ return groupNodes2(selectedIds, options);
42
+ },
43
+ [getNodes, groupNodes2]
44
+ );
45
+ const ungroup2 = useCallback(
46
+ (groupId) => apply(ungroup(getNodes(), getEdges(), groupId)),
47
+ [getNodes, getEdges, apply]
48
+ );
49
+ const collapseGroup2 = useCallback(
50
+ (groupId) => apply(collapseGroup(getNodes(), getEdges(), groupId)),
51
+ [getNodes, getEdges, apply]
52
+ );
53
+ const expandGroup2 = useCallback(
54
+ (groupId) => apply(expandGroup(getNodes(), getEdges(), groupId)),
55
+ [getNodes, getEdges, apply]
56
+ );
57
+ const toggleCollapse = useCallback(
58
+ (groupId) => apply(toggleGroupCollapsed(getNodes(), getEdges(), groupId)),
59
+ [getNodes, getEdges, apply]
60
+ );
61
+ return {
62
+ groupNodes: groupNodes2,
63
+ groupSelection,
64
+ ungroup: ungroup2,
65
+ collapseGroup: collapseGroup2,
66
+ expandGroup: expandGroup2,
67
+ toggleCollapse
68
+ };
69
+ }
70
+
71
+ export {
72
+ useFlowGroups
73
+ };
74
+ //# sourceMappingURL=chunk-LQOZCOK3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/use-flow-groups/use-flow-groups.ts"],"sourcesContent":["import { useCallback } from \"react\";\nimport { useReactFlow, type Edge, type Node } from \"@xyflow/react\";\nimport {\n collapseGroup as collapseGroupOp,\n expandGroup as expandGroupOp,\n groupNodes as groupNodesOp,\n toggleGroupCollapsed as toggleGroupCollapsedOp,\n ungroup as ungroupOp,\n type GroupNodesOptions,\n} from \"./group-operations\";\n\n/** Options for `groupNodes`/`groupSelection` with an optional generated id. */\nexport type GroupOptions = Partial<Pick<GroupNodesOptions, \"groupId\">> &\n Omit<GroupNodesOptions, \"groupId\">;\n\nexport interface UseFlowGroupsResult {\n /**\n * Wrap the given (top-level) node ids in a new `FlowGroupNode`. Returns the new\n * group id, or `undefined` if nothing was grouped. Pass `options.groupId` for a\n * deterministic id; otherwise one is generated.\n */\n groupNodes: (nodeIds: string[], options?: GroupOptions) => string | undefined;\n /** Group the currently-selected nodes. Returns the new group id (or `undefined`). */\n groupSelection: (options?: GroupOptions) => string | undefined;\n /** Dissolve a group, restoring its children to absolute positions. */\n ungroup: (groupId: string) => void;\n /** Collapse a group to an overview chip (hides subtree, reroutes boundary edges). */\n collapseGroup: (groupId: string) => void;\n /** Expand a collapsed group (exact inverse of collapse). */\n expandGroup: (groupId: string) => void;\n /** Collapse if expanded, expand if collapsed. */\n toggleCollapse: (groupId: string) => void;\n}\n\nfunction generateGroupId(): string {\n return `flow-group-${Math.random().toString(36).slice(2, 9)}`;\n}\n\n/**\n * Thin hook over the pure grouping operations in `group-operations.ts`. Reads and\n * writes the live graph via `useReactFlow()`; all the logic lives in the pure,\n * unit-tested core. Must be called inside a React Flow context (e.g. from a\n * `<Panel>` child of `<CanvasShell>` or from within a custom node).\n */\nexport function useFlowGroups<\n NodeType extends Node = Node,\n EdgeType extends Edge = Edge,\n>(): UseFlowGroupsResult {\n const { getNodes, getEdges, setNodes, setEdges } = useReactFlow<NodeType, EdgeType>();\n\n const apply = useCallback(\n (result: { nodes: NodeType[]; edges: EdgeType[] }) => {\n setNodes(result.nodes);\n setEdges(result.edges);\n },\n [setNodes, setEdges],\n );\n\n const groupNodes = useCallback(\n (nodeIds: string[], options?: GroupOptions): string | undefined => {\n if (nodeIds.length === 0) return undefined;\n const groupId = options?.groupId ?? generateGroupId();\n const result = groupNodesOp<NodeType, EdgeType>(getNodes(), getEdges(), nodeIds, {\n ...options,\n groupId,\n });\n apply(result);\n return groupId;\n },\n [getNodes, getEdges, apply],\n );\n\n const groupSelection = useCallback(\n (options?: GroupOptions): string | undefined => {\n const selectedIds = getNodes()\n .filter((n) => n.selected && !n.parentId)\n .map((n) => n.id);\n return groupNodes(selectedIds, options);\n },\n [getNodes, groupNodes],\n );\n\n const ungroup = useCallback(\n (groupId: string) => apply(ungroupOp<NodeType, EdgeType>(getNodes(), getEdges(), groupId)),\n [getNodes, getEdges, apply],\n );\n\n const collapseGroup = useCallback(\n (groupId: string) =>\n apply(collapseGroupOp<NodeType, EdgeType>(getNodes(), getEdges(), groupId)),\n [getNodes, getEdges, apply],\n );\n\n const expandGroup = useCallback(\n (groupId: string) => apply(expandGroupOp<NodeType, EdgeType>(getNodes(), getEdges(), groupId)),\n [getNodes, getEdges, apply],\n );\n\n const toggleCollapse = useCallback(\n (groupId: string) =>\n apply(toggleGroupCollapsedOp<NodeType, EdgeType>(getNodes(), getEdges(), groupId)),\n [getNodes, getEdges, apply],\n );\n\n return {\n groupNodes,\n groupSelection,\n ungroup,\n collapseGroup,\n expandGroup,\n toggleCollapse,\n };\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,mBAAmB;AAC5B,SAAS,oBAA0C;AAiCnD,SAAS,kBAA0B;AACjC,SAAO,cAAc,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AAC7D;AAQO,SAAS,gBAGS;AACvB,QAAM,EAAE,UAAU,UAAU,UAAU,SAAS,IAAI,aAAiC;AAEpF,QAAM,QAAQ;AAAA,IACZ,CAAC,WAAqD;AACpD,eAAS,OAAO,KAAK;AACrB,eAAS,OAAO,KAAK;AAAA,IACvB;AAAA,IACA,CAAC,UAAU,QAAQ;AAAA,EACrB;AAEA,QAAMA,cAAa;AAAA,IACjB,CAAC,SAAmB,YAA+C;AACjE,UAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,YAAM,UAAU,SAAS,WAAW,gBAAgB;AACpD,YAAM,SAAS,WAAiC,SAAS,GAAG,SAAS,GAAG,SAAS;AAAA,QAC/E,GAAG;AAAA,QACH;AAAA,MACF,CAAC;AACD,YAAM,MAAM;AACZ,aAAO;AAAA,IACT;AAAA,IACA,CAAC,UAAU,UAAU,KAAK;AAAA,EAC5B;AAEA,QAAM,iBAAiB;AAAA,IACrB,CAAC,YAA+C;AAC9C,YAAM,cAAc,SAAS,EAC1B,OAAO,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,QAAQ,EACvC,IAAI,CAAC,MAAM,EAAE,EAAE;AAClB,aAAOA,YAAW,aAAa,OAAO;AAAA,IACxC;AAAA,IACA,CAAC,UAAUA,WAAU;AAAA,EACvB;AAEA,QAAMC,WAAU;AAAA,IACd,CAAC,YAAoB,MAAM,QAA8B,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAAA,IACzF,CAAC,UAAU,UAAU,KAAK;AAAA,EAC5B;AAEA,QAAMC,iBAAgB;AAAA,IACpB,CAAC,YACC,MAAM,cAAoC,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAAA,IAC5E,CAAC,UAAU,UAAU,KAAK;AAAA,EAC5B;AAEA,QAAMC,eAAc;AAAA,IAClB,CAAC,YAAoB,MAAM,YAAkC,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAAA,IAC7F,CAAC,UAAU,UAAU,KAAK;AAAA,EAC5B;AAEA,QAAM,iBAAiB;AAAA,IACrB,CAAC,YACC,MAAM,qBAA2C,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAAA,IACnF,CAAC,UAAU,UAAU,KAAK;AAAA,EAC5B;AAEA,SAAO;AAAA,IACL,YAAAH;AAAA,IACA;AAAA,IACA,SAAAC;AAAA,IACA,eAAAC;AAAA,IACA,aAAAC;AAAA,IACA;AAAA,EACF;AACF;","names":["groupNodes","ungroup","collapseGroup","expandGroup"]}
@@ -0,0 +1,102 @@
1
+ "use client";
2
+
3
+ // src/inspector-panel/inspector-panel.tsx
4
+ import "react";
5
+ import { Reveal, useCollapsiblePanel, useLocale } from "@elabs-ai/components-ui";
6
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var INSPECTOR_PANEL_WIDTH = "18rem";
9
+ function InspectorPanel({
10
+ title,
11
+ children,
12
+ onClose,
13
+ emptyMessage,
14
+ hasSelection = true,
15
+ selectionKey,
16
+ open,
17
+ defaultOpen = true,
18
+ onOpenChange,
19
+ side = "right",
20
+ width = INSPECTOR_PANEL_WIDTH,
21
+ className
22
+ }) {
23
+ const { t } = useLocale();
24
+ const resolvedTitle = title ?? t("flow.inspectorPanel.title");
25
+ const resolvedEmptyMessage = emptyMessage ?? t("flow.inspectorPanel.emptyMessage");
26
+ const panel = useCollapsiblePanel({
27
+ side,
28
+ open,
29
+ defaultOpen,
30
+ onOpenChange,
31
+ widthClassName: "w-(--inspector-panel-width)",
32
+ spacerCollapsedClassName: "group-data-[state=collapsed]:w-0",
33
+ containerSlideClassNames: {
34
+ left: "left-0 group-data-[state=collapsed]:left-[calc(var(--inspector-panel-width)*-1)]",
35
+ right: "right-0 group-data-[state=collapsed]:right-[calc(var(--inspector-panel-width)*-1)]"
36
+ }
37
+ });
38
+ return /* @__PURE__ */ jsxs(
39
+ "div",
40
+ {
41
+ className: cn("group relative h-full overflow-hidden", className),
42
+ style: { "--inspector-panel-width": width },
43
+ inert: panel.open ? void 0 : true,
44
+ ...panel.attrs,
45
+ children: [
46
+ /* @__PURE__ */ jsx("div", { "data-slot": "inspector-panel-gap", className: panel.spacerClassName }),
47
+ /* @__PURE__ */ jsx(
48
+ "div",
49
+ {
50
+ "data-slot": "inspector-panel-container",
51
+ className: cn(panel.containerClassName, "absolute inset-y-0 flex h-full border-s"),
52
+ children: /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col bg-card", children: [
53
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
54
+ /* @__PURE__ */ jsx("h3", { className: "text-body font-semibold text-foreground", children: resolvedTitle }),
55
+ onClose ? /* @__PURE__ */ jsx(
56
+ "button",
57
+ {
58
+ type: "button",
59
+ "aria-label": t("flow.inspectorPanel.close"),
60
+ onClick: onClose,
61
+ className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-ring",
62
+ children: /* @__PURE__ */ jsx(
63
+ "svg",
64
+ {
65
+ width: "14",
66
+ height: "14",
67
+ viewBox: "0 0 24 24",
68
+ fill: "none",
69
+ stroke: "currentColor",
70
+ strokeWidth: "2",
71
+ strokeLinecap: "round",
72
+ strokeLinejoin: "round",
73
+ "aria-hidden": "true",
74
+ children: /* @__PURE__ */ jsx("path", { d: "M18 6 6 18M6 6l12 12" })
75
+ }
76
+ )
77
+ }
78
+ ) : null
79
+ ] }),
80
+ hasSelection ? /* @__PURE__ */ jsx(
81
+ Reveal,
82
+ {
83
+ appear: "fade",
84
+ speed: "fast",
85
+ tabIndex: 0,
86
+ className: "focus-ring flex-1 overflow-y-auto p-4 text-body",
87
+ children
88
+ },
89
+ selectionKey
90
+ ) : /* @__PURE__ */ jsx("div", { tabIndex: 0, className: "focus-ring flex-1 overflow-y-auto p-4 text-body", children: /* @__PURE__ */ jsx("p", { className: "text-muted-foreground", children: resolvedEmptyMessage }) })
91
+ ] })
92
+ }
93
+ )
94
+ ]
95
+ }
96
+ );
97
+ }
98
+
99
+ export {
100
+ InspectorPanel
101
+ };
102
+ //# sourceMappingURL=chunk-NAQWNX2A.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/inspector-panel/inspector-panel.tsx"],"sourcesContent":["import { type CSSProperties, type ReactNode } from \"react\";\nimport { Reveal, useCollapsiblePanel, useLocale } from \"@elabs-ai/components-ui\";\nimport { cn } from \"@elabs-ai/components-ui/lib/cn\";\n\nconst INSPECTOR_PANEL_WIDTH = \"18rem\";\n\nexport interface InspectorPanelProps {\n title?: ReactNode;\n children: ReactNode;\n onClose?: () => void;\n /** Empty-state shown when nothing is selected. */\n emptyMessage?: ReactNode;\n /** When false, render the empty state instead of children. */\n hasSelection?: boolean;\n /**\n * Stable identity of the current selection (e.g. the selected node's id).\n * When it changes, the body replays a quick fade so it reads as \"details for\n * THIS node\". Pass the selected entity's id (or any stable key derived from\n * the selected data).\n */\n selectionKey?: string | number;\n /**\n * Collapse (research 09 step 3 — converged on the shared `@elabs-ai/components-ui`\n * `useCollapsiblePanel` base; the panel stays single-view, drill-in is\n * ContextPanel-only). Controlled `open` / uncontrolled `defaultOpen`.\n */\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n /** Which edge the panel sits on (collapse slide direction). Default `\"right\"`. */\n side?: \"left\" | \"right\";\n /** Expanded width (any CSS length). Default `\"18rem\"` (the original `w-72`). */\n width?: string;\n className?: string;\n}\n\n/**\n * Properties panel for the selected node/edge. Reusable beside a canvas (e.g.\n * inside a <SplitPanel>) or as a React Flow <Panel>. Always mounted; collapses\n * via the canonical `useCollapsiblePanel` width tween (CSS-gated, reduced-\n * motion safe) when `open`/`defaultOpen` drive it.\n */\nexport function InspectorPanel({\n title,\n children,\n onClose,\n emptyMessage,\n hasSelection = true,\n selectionKey,\n open,\n defaultOpen = true,\n onOpenChange,\n side = \"right\",\n width = INSPECTOR_PANEL_WIDTH,\n className,\n}: InspectorPanelProps) {\n const { t } = useLocale();\n const resolvedTitle = title ?? t(\"flow.inspectorPanel.title\");\n const resolvedEmptyMessage = emptyMessage ?? t(\"flow.inspectorPanel.emptyMessage\");\n const panel = useCollapsiblePanel({\n side,\n open,\n defaultOpen,\n onOpenChange,\n widthClassName: \"w-(--inspector-panel-width)\",\n spacerCollapsedClassName: \"group-data-[state=collapsed]:w-0\",\n containerSlideClassNames: {\n left: \"left-0 group-data-[state=collapsed]:left-[calc(var(--inspector-panel-width)*-1)]\",\n right: \"right-0 group-data-[state=collapsed]:right-[calc(var(--inspector-panel-width)*-1)]\",\n },\n });\n\n return (\n <div\n className={cn(\"group relative h-full overflow-hidden\", className)}\n style={{ \"--inspector-panel-width\": width } as CSSProperties}\n inert={panel.open ? undefined : true}\n {...panel.attrs}\n >\n <div data-slot=\"inspector-panel-gap\" className={panel.spacerClassName} />\n <div\n data-slot=\"inspector-panel-container\"\n // Bounded to the host (a SplitPanel / canvas edge), not the viewport:\n // absolute within the relative group, full height (research 09 §B.2).\n className={cn(panel.containerClassName, \"absolute inset-y-0 flex h-full border-s\")}\n >\n {/* Detail-tier surface: raised `card` — robust across themes (#193). */}\n <div className=\"flex h-full w-full flex-col bg-card\">\n <div className=\"flex items-center justify-between border-b px-4 py-3\">\n <h3 className=\"text-body font-semibold text-foreground\">{resolvedTitle}</h3>\n {onClose ? (\n <button\n type=\"button\"\n aria-label={t(\"flow.inspectorPanel.close\")}\n onClick={onClose}\n className=\"rounded-sm p-1 text-muted-foreground hover:text-foreground focus-ring\"\n >\n <svg\n width=\"14\"\n height=\"14\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n >\n <path d=\"M18 6 6 18M6 6l12 12\" />\n </svg>\n </button>\n ) : null}\n </div>\n {/*\n `tabIndex={0}` on a scroll container, in both branches: a region that scrolls\n but contains nothing focusable is unreachable by keyboard — a mouse user can\n read the overflow and a keyboard user cannot (axe\n `scrollable-region-focusable`). An inspector body is exactly that shape\n whenever its content is plain text, a definition list or a chart, which is\n most of the time. It carries `focus-ring` because anything that can take\n focus has to show it.\n */}\n {hasSelection ? (\n <Reveal\n key={selectionKey}\n appear=\"fade\"\n speed=\"fast\"\n tabIndex={0}\n className=\"focus-ring flex-1 overflow-y-auto p-4 text-body\"\n >\n {children}\n </Reveal>\n ) : (\n <div tabIndex={0} className=\"focus-ring flex-1 overflow-y-auto p-4 text-body\">\n <p className=\"text-muted-foreground\">{resolvedEmptyMessage}</p>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;AAAA,OAAmD;AACnD,SAAS,QAAQ,qBAAqB,iBAAiB;AACvD,SAAS,UAAU;AA6Eb,cASI,YATJ;AA3EN,IAAM,wBAAwB;AAsCvB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,eAAe;AAAA,EACf;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd;AAAA,EACA,OAAO;AAAA,EACP,QAAQ;AAAA,EACR;AACF,GAAwB;AACtB,QAAM,EAAE,EAAE,IAAI,UAAU;AACxB,QAAM,gBAAgB,SAAS,EAAE,2BAA2B;AAC5D,QAAM,uBAAuB,gBAAgB,EAAE,kCAAkC;AACjF,QAAM,QAAQ,oBAAoB;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,0BAA0B;AAAA,IAC1B,0BAA0B;AAAA,MACxB,MAAM;AAAA,MACN,OAAO;AAAA,IACT;AAAA,EACF,CAAC;AAED,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW,GAAG,yCAAyC,SAAS;AAAA,MAChE,OAAO,EAAE,2BAA2B,MAAM;AAAA,MAC1C,OAAO,MAAM,OAAO,SAAY;AAAA,MAC/B,GAAG,MAAM;AAAA,MAEV;AAAA,4BAAC,SAAI,aAAU,uBAAsB,WAAW,MAAM,iBAAiB;AAAA,QACvE;AAAA,UAAC;AAAA;AAAA,YACC,aAAU;AAAA,YAGV,WAAW,GAAG,MAAM,oBAAoB,yCAAyC;AAAA,YAGjF,+BAAC,SAAI,WAAU,uCACb;AAAA,mCAAC,SAAI,WAAU,wDACb;AAAA,oCAAC,QAAG,WAAU,2CAA2C,yBAAc;AAAA,gBACtE,UACC;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,cAAY,EAAE,2BAA2B;AAAA,oBACzC,SAAS;AAAA,oBACT,WAAU;AAAA,oBAEV;AAAA,sBAAC;AAAA;AAAA,wBACC,OAAM;AAAA,wBACN,QAAO;AAAA,wBACP,SAAQ;AAAA,wBACR,MAAK;AAAA,wBACL,QAAO;AAAA,wBACP,aAAY;AAAA,wBACZ,eAAc;AAAA,wBACd,gBAAe;AAAA,wBACf,eAAY;AAAA,wBAEZ,8BAAC,UAAK,GAAE,wBAAuB;AAAA;AAAA,oBACjC;AAAA;AAAA,gBACF,IACE;AAAA,iBACN;AAAA,cAUC,eACC;AAAA,gBAAC;AAAA;AAAA,kBAEC,QAAO;AAAA,kBACP,OAAM;AAAA,kBACN,UAAU;AAAA,kBACV,WAAU;AAAA,kBAET;AAAA;AAAA,gBANI;AAAA,cAOP,IAEA,oBAAC,SAAI,UAAU,GAAG,WAAU,mDAC1B,8BAAC,OAAE,WAAU,yBAAyB,gCAAqB,GAC7D;AAAA,eAEJ;AAAA;AAAA,QACF;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,152 @@
1
+ "use client";
2
+ import {
3
+ computeEdgeWeightScale
4
+ } from "./chunk-3BTU2VWZ.js";
5
+
6
+ // src/legend/legend.tsx
7
+ import { useLocale } from "@elabs-ai/components-ui";
8
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
9
+ import { jsx, jsxs } from "react/jsx-runtime";
10
+ function Legend(props) {
11
+ if (props.variant === "scale") {
12
+ return /* @__PURE__ */ jsx(LegendScale, { ...props });
13
+ }
14
+ return /* @__PURE__ */ jsx(LegendCategorical, { ...props });
15
+ }
16
+ function LegendCategorical({ items, title, className }) {
17
+ return /* @__PURE__ */ jsxs(
18
+ "div",
19
+ {
20
+ className: cn(
21
+ "rounded-lg bg-surface-elevated/90 p-3 text-xs shadow-ring-sm backdrop-blur",
22
+ className
23
+ ),
24
+ children: [
25
+ title ? /* @__PURE__ */ jsx("div", { className: "mb-1.5 font-medium text-foreground", children: title }) : null,
26
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-2 text-muted-foreground", children: [
27
+ /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ "aria-hidden": "true",
31
+ className: "size-2.5 shrink-0 rounded-full",
32
+ style: { backgroundColor: item.color }
33
+ }
34
+ ),
35
+ item.label
36
+ ] }, item.label)) })
37
+ ]
38
+ }
39
+ );
40
+ }
41
+ function computeWidthSamples(domain, ticks) {
42
+ const [min, max] = domain;
43
+ const values = ticks === "minmedmax" ? [min, (min + max) / 2, max] : [min, max];
44
+ const likeEdges = values.map((weight, index) => ({
45
+ id: String(index),
46
+ data: { weight }
47
+ }));
48
+ const widths = computeEdgeWeightScale(likeEdges);
49
+ return values.map((value, index) => ({ value, width: widths.get(String(index)) ?? 0 }));
50
+ }
51
+ var COLOR_STOP_FRACTIONS = [0, 0.25, 0.5, 0.75, 1];
52
+ function scaleKindLabel(kind) {
53
+ return kind === "width" ? "width" : "color";
54
+ }
55
+ function LegendScale({
56
+ kind,
57
+ domain,
58
+ format,
59
+ ticks = "minmax",
60
+ title,
61
+ className
62
+ }) {
63
+ const { formatNumber } = useLocale();
64
+ const format_ = format ?? formatNumber;
65
+ const [min, max] = domain;
66
+ const ariaLabel = `Edge ${scaleKindLabel(kind)} scale, ${format_(min)} to ${format_(max)}, minimum to maximum`;
67
+ return /* @__PURE__ */ jsxs(
68
+ "div",
69
+ {
70
+ "aria-label": ariaLabel,
71
+ className: cn(
72
+ "flex flex-col gap-2 rounded-lg bg-surface-elevated/90 p-3 shadow-ring-sm backdrop-blur",
73
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
74
+ className
75
+ ),
76
+ role: "group",
77
+ tabIndex: 0,
78
+ children: [
79
+ title ? /* @__PURE__ */ jsx("div", { className: "text-body font-medium text-foreground", children: title }) : null,
80
+ kind === "width" ? /* @__PURE__ */ jsx(LegendScaleWidth, { domain, format: format_, ticks }) : /* @__PURE__ */ jsx(LegendScaleColor, { domain, format: format_ })
81
+ ]
82
+ }
83
+ );
84
+ }
85
+ function LegendScaleWidth({
86
+ domain,
87
+ format,
88
+ ticks
89
+ }) {
90
+ const samples = computeWidthSamples(domain, ticks);
91
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: samples.map((sample, index) => (
92
+ // Index disambiguates a zero-width domain, where every sample shares
93
+ // the same value (and therefore the same width).
94
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
95
+ /* @__PURE__ */ jsx("svg", { "aria-hidden": "true", className: "shrink-0", height: 16, overflow: "visible", width: 40, children: /* @__PURE__ */ jsx(
96
+ "line",
97
+ {
98
+ stroke: "var(--flow-edge)",
99
+ strokeLinecap: "round",
100
+ strokeWidth: sample.width,
101
+ x1: 2,
102
+ x2: 38,
103
+ y1: 8,
104
+ y2: 8
105
+ }
106
+ ) }),
107
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 truncate text-meta tabular-nums text-muted-foreground", children: format(sample.value) })
108
+ ] }, `${index}-${sample.value}`)
109
+ )) });
110
+ }
111
+ function LegendScaleColor({
112
+ domain,
113
+ format
114
+ }) {
115
+ const [min, max] = domain;
116
+ const span = max - min;
117
+ const stops = COLOR_STOP_FRACTIONS.map((fraction) => ({
118
+ fraction,
119
+ value: min + fraction * span
120
+ }));
121
+ const gradient = `linear-gradient(to right, ${stops.map(({ fraction }) => {
122
+ const percent = Math.round(fraction * 100);
123
+ return `color-mix(in oklch, var(--flow-edge-strong) ${percent}%, var(--flow-edge-weak)) ${percent}%`;
124
+ }).join(", ")})`;
125
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", children: [
126
+ /* @__PURE__ */ jsx(
127
+ "div",
128
+ {
129
+ "aria-hidden": "true",
130
+ className: "h-3 w-full rounded-full",
131
+ style: { backgroundImage: gradient }
132
+ }
133
+ ),
134
+ /* @__PURE__ */ jsx("div", { className: "flex justify-between gap-1", children: stops.map((stop, index) => /* @__PURE__ */ jsx(
135
+ "span",
136
+ {
137
+ className: cn(
138
+ "min-w-0 truncate text-meta tabular-nums text-muted-foreground",
139
+ index === 0 && "text-start",
140
+ index === stops.length - 1 && "text-end"
141
+ ),
142
+ children: format(stop.value)
143
+ },
144
+ `${index}-${stop.value}`
145
+ )) })
146
+ ] });
147
+ }
148
+
149
+ export {
150
+ Legend
151
+ };
152
+ //# sourceMappingURL=chunk-NVJVWGUS.js.map