@elabs-ai/components-flow 4.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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +79 -0
  3. package/dist/index.d.ts +634 -0
  4. package/dist/index.js +1577 -0
  5. package/dist/index.js.map +1 -0
  6. package/package.json +68 -0
  7. package/src/canvas-shell/canvas-shell.stories.tsx +77 -0
  8. package/src/canvas-shell/canvas-shell.test.tsx +106 -0
  9. package/src/canvas-shell/canvas-shell.tsx +128 -0
  10. package/src/canvas-shell/index.ts +1 -0
  11. package/src/flow-button-edge/flow-button-edge.stories.tsx +151 -0
  12. package/src/flow-button-edge/flow-button-edge.test.tsx +99 -0
  13. package/src/flow-button-edge/flow-button-edge.tsx +80 -0
  14. package/src/flow-button-edge/index.ts +5 -0
  15. package/src/flow-edge/flow-edge.stories.tsx +79 -0
  16. package/src/flow-edge/flow-edge.test.tsx +76 -0
  17. package/src/flow-edge/flow-edge.tsx +34 -0
  18. package/src/flow-edge/index.ts +1 -0
  19. package/src/flow-floating-edge/floating-edge-geometry.test.ts +100 -0
  20. package/src/flow-floating-edge/floating-edge-geometry.ts +128 -0
  21. package/src/flow-floating-edge/flow-floating-edge.stories.tsx +81 -0
  22. package/src/flow-floating-edge/flow-floating-edge.tsx +82 -0
  23. package/src/flow-floating-edge/index.ts +6 -0
  24. package/src/flow-group-node/flow-group-node.stories.tsx +283 -0
  25. package/src/flow-group-node/flow-group-node.test.tsx +100 -0
  26. package/src/flow-group-node/flow-group-node.tsx +134 -0
  27. package/src/flow-group-node/index.ts +6 -0
  28. package/src/flow-layout/flow-layout.stories.tsx +215 -0
  29. package/src/flow-layout/flow-layout.test.tsx +133 -0
  30. package/src/flow-layout/flow-layout.ts +104 -0
  31. package/src/flow-layout/index.ts +14 -0
  32. package/src/flow-layout/layout-graph.test.ts +257 -0
  33. package/src/flow-layout/layout-graph.ts +302 -0
  34. package/src/flow-layout/use-auto-layout.ts +26 -0
  35. package/src/flow-layout/use-flow-layout.ts +60 -0
  36. package/src/flow-mini-map/flow-mini-map.stories.tsx +74 -0
  37. package/src/flow-mini-map/flow-mini-map.test.tsx +56 -0
  38. package/src/flow-mini-map/flow-mini-map.tsx +29 -0
  39. package/src/flow-mini-map/index.ts +1 -0
  40. package/src/flow-node/flow-node.stories.tsx +143 -0
  41. package/src/flow-node/flow-node.test.tsx +156 -0
  42. package/src/flow-node/flow-node.tsx +190 -0
  43. package/src/flow-node/index.ts +8 -0
  44. package/src/flow-placeholder-node/flow-placeholder-node.stories.tsx +260 -0
  45. package/src/flow-placeholder-node/flow-placeholder-node.test.tsx +101 -0
  46. package/src/flow-placeholder-node/flow-placeholder-node.tsx +49 -0
  47. package/src/flow-placeholder-node/index.ts +5 -0
  48. package/src/flow-smart-edge/flow-smart-edge.stories.tsx +75 -0
  49. package/src/flow-smart-edge/flow-smart-edge.tsx +75 -0
  50. package/src/flow-smart-edge/index.ts +9 -0
  51. package/src/flow-smart-edge/smart-edge-geometry.test.ts +120 -0
  52. package/src/flow-smart-edge/smart-edge-geometry.ts +116 -0
  53. package/src/helper-lines/get-helper-lines.ts +98 -0
  54. package/src/helper-lines/helper-lines.stories.tsx +107 -0
  55. package/src/helper-lines/helper-lines.test.tsx +78 -0
  56. package/src/helper-lines/helper-lines.tsx +67 -0
  57. package/src/helper-lines/index.ts +7 -0
  58. package/src/helper-lines/use-helper-lines.ts +109 -0
  59. package/src/index.ts +37 -0
  60. package/src/inspector-panel/index.ts +1 -0
  61. package/src/inspector-panel/inspector-panel.stories.tsx +161 -0
  62. package/src/inspector-panel/inspector-panel.test.tsx +82 -0
  63. package/src/inspector-panel/inspector-panel.tsx +129 -0
  64. package/src/legend/index.ts +1 -0
  65. package/src/legend/legend.stories.tsx +56 -0
  66. package/src/legend/legend.test.tsx +46 -0
  67. package/src/legend/legend.tsx +39 -0
  68. package/src/templates-flow-workspace.stories.tsx +169 -0
  69. package/src/use-flow-groups/group-operations.test.ts +318 -0
  70. package/src/use-flow-groups/group-operations.ts +349 -0
  71. package/src/use-flow-groups/index.ts +13 -0
  72. package/src/use-flow-groups/use-flow-groups.ts +113 -0
  73. package/src/zoom-controls/index.ts +1 -0
  74. package/src/zoom-controls/zoom-controls.stories.tsx +68 -0
  75. package/src/zoom-controls/zoom-controls.test.tsx +80 -0
  76. package/src/zoom-controls/zoom-controls.tsx +78 -0
package/dist/index.js ADDED
@@ -0,0 +1,1577 @@
1
+ "use client";
2
+
3
+ // src/canvas-shell/canvas-shell.tsx
4
+ import "react";
5
+ import {
6
+ Background,
7
+ ReactFlow,
8
+ ReactFlowProvider
9
+ } from "@xyflow/react";
10
+ import { cn as cn2 } from "@elabs-ai/components-ui/lib/cn";
11
+
12
+ // src/helper-lines/helper-lines.tsx
13
+ import { forwardRef } from "react";
14
+ import { useViewport } from "@xyflow/react";
15
+ import { cn } from "@elabs-ai/components-ui/lib/cn";
16
+ import { jsx, jsxs } from "react/jsx-runtime";
17
+ var HelperLines = forwardRef(function HelperLines2({ horizontal, vertical, className, ...props }, ref) {
18
+ const { x, y, zoom } = useViewport();
19
+ if (horizontal == null && vertical == null) return null;
20
+ const screenY = horizontal == null ? null : y + horizontal * zoom;
21
+ const screenX = vertical == null ? null : x + vertical * zoom;
22
+ return /* @__PURE__ */ jsxs(
23
+ "svg",
24
+ {
25
+ ref,
26
+ "aria-hidden": "true",
27
+ width: "100%",
28
+ height: "100%",
29
+ className: cn("pointer-events-none absolute inset-0 size-full", className),
30
+ ...props,
31
+ children: [
32
+ screenY != null ? /* @__PURE__ */ jsx(
33
+ "line",
34
+ {
35
+ x1: 0,
36
+ x2: "100%",
37
+ y1: screenY,
38
+ y2: screenY,
39
+ stroke: "var(--flow-helper-line, var(--primary))",
40
+ strokeWidth: 1
41
+ }
42
+ ) : null,
43
+ screenX != null ? /* @__PURE__ */ jsx(
44
+ "line",
45
+ {
46
+ x1: screenX,
47
+ x2: screenX,
48
+ y1: 0,
49
+ y2: "100%",
50
+ stroke: "var(--flow-helper-line, var(--primary))",
51
+ strokeWidth: 1
52
+ }
53
+ ) : null
54
+ ]
55
+ }
56
+ );
57
+ });
58
+
59
+ // src/helper-lines/use-helper-lines.ts
60
+ import { useCallback, useState } from "react";
61
+ import { useReactFlow } from "@xyflow/react";
62
+
63
+ // src/helper-lines/get-helper-lines.ts
64
+ var ANCHORS = [0, 0.5, 1];
65
+ function getHelperLines(dragged, others, threshold = 5) {
66
+ const result = {};
67
+ let bestVerticalDist = threshold;
68
+ let bestHorizontalDist = threshold;
69
+ const draggedX = ANCHORS.map((a) => ({ a, pos: dragged.x + dragged.width * a }));
70
+ const draggedY = ANCHORS.map((a) => ({ a, pos: dragged.y + dragged.height * a }));
71
+ for (const other of others) {
72
+ const otherX = ANCHORS.map((a) => other.x + other.width * a);
73
+ const otherY = ANCHORS.map((a) => other.y + other.height * a);
74
+ for (const d of draggedX) {
75
+ for (const oPos of otherX) {
76
+ const dx = Math.abs(d.pos - oPos);
77
+ if (dx < bestVerticalDist) {
78
+ bestVerticalDist = dx;
79
+ result.vertical = oPos;
80
+ result.snapX = oPos - dragged.width * d.a;
81
+ }
82
+ }
83
+ }
84
+ for (const d of draggedY) {
85
+ for (const oPos of otherY) {
86
+ const dy = Math.abs(d.pos - oPos);
87
+ if (dy < bestHorizontalDist) {
88
+ bestHorizontalDist = dy;
89
+ result.horizontal = oPos;
90
+ result.snapY = oPos - dragged.height * d.a;
91
+ }
92
+ }
93
+ }
94
+ }
95
+ return result;
96
+ }
97
+
98
+ // src/helper-lines/use-helper-lines.ts
99
+ function nodeRect(node) {
100
+ return {
101
+ x: node.position.x,
102
+ y: node.position.y,
103
+ width: node.measured?.width ?? node.width ?? 0,
104
+ height: node.measured?.height ?? node.height ?? 0
105
+ };
106
+ }
107
+ function useHelperLines(onNodesChange, options = {}) {
108
+ const { threshold = 5 } = options;
109
+ const { getNodes } = useReactFlow();
110
+ const [helperLineHorizontal, setHelperLineHorizontal] = useState();
111
+ const [helperLineVertical, setHelperLineVertical] = useState();
112
+ const handleNodesChange = useCallback(
113
+ (changes) => {
114
+ let horizontal;
115
+ let vertical;
116
+ const dragChanges = changes.filter(
117
+ (c) => c.type === "position" && c.dragging === true && c.position != null
118
+ );
119
+ if (dragChanges.length === 1) {
120
+ const change = dragChanges[0];
121
+ const nodes = getNodes();
122
+ const dragged = change ? nodes.find((n) => n.id === change.id) : void 0;
123
+ if (change && change.position && dragged) {
124
+ const draggedRect = {
125
+ x: change.position.x,
126
+ y: change.position.y,
127
+ width: dragged.measured?.width ?? dragged.width ?? 0,
128
+ height: dragged.measured?.height ?? dragged.height ?? 0
129
+ };
130
+ const others = nodes.filter((n) => n.id !== change.id).map(nodeRect);
131
+ const {
132
+ snapX,
133
+ snapY,
134
+ vertical: v,
135
+ horizontal: h
136
+ } = getHelperLines(draggedRect, others, threshold);
137
+ if (snapX !== void 0) change.position.x = snapX;
138
+ if (snapY !== void 0) change.position.y = snapY;
139
+ vertical = v;
140
+ horizontal = h;
141
+ }
142
+ }
143
+ setHelperLineHorizontal(horizontal);
144
+ setHelperLineVertical(vertical);
145
+ onNodesChange?.(changes);
146
+ },
147
+ [getNodes, onNodesChange, threshold]
148
+ );
149
+ return { onNodesChange: handleNodesChange, helperLineHorizontal, helperLineVertical };
150
+ }
151
+
152
+ // src/canvas-shell/canvas-shell.tsx
153
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
154
+ var DEFAULT_ARIA_LABEL_CONFIG = {
155
+ "node.a11yDescription.default": "Press Enter or Space to select this node. Press Delete to remove it, Escape to cancel.",
156
+ "node.a11yDescription.keyboardDisabled": "Press Enter or Space to select this node. Then use the arrow keys to move it. Press Delete to remove it, Escape to cancel.",
157
+ "edge.a11yDescription.default": "Press Enter or Space to select this edge. Press Delete to remove it, Escape to cancel.",
158
+ "controls.ariaLabel": "Canvas controls",
159
+ "controls.zoomIn.ariaLabel": "Zoom in",
160
+ "controls.zoomOut.ariaLabel": "Zoom out",
161
+ "controls.fitView.ariaLabel": "Fit view",
162
+ "controls.interactive.ariaLabel": "Toggle interactivity",
163
+ "minimap.ariaLabel": "Minimap",
164
+ "handle.ariaLabel": "Connection handle"
165
+ };
166
+ function CanvasShell({
167
+ helperLines = false,
168
+ ...props
169
+ }) {
170
+ if (helperLines) {
171
+ return /* @__PURE__ */ jsx2(ReactFlowProvider, { children: /* @__PURE__ */ jsx2(CanvasShellWithHelperLines, { ...props }) });
172
+ }
173
+ return /* @__PURE__ */ jsx2(CanvasShellBase, { ...props });
174
+ }
175
+ function CanvasShellBase({
176
+ background = true,
177
+ children,
178
+ className,
179
+ ariaLabelConfig,
180
+ ...props
181
+ }) {
182
+ return /* @__PURE__ */ jsx2("div", { className: cn2("h-full w-full bg-canvas", className), children: /* @__PURE__ */ jsxs2(
183
+ ReactFlow,
184
+ {
185
+ fitView: true,
186
+ proOptions: { hideAttribution: true },
187
+ ariaLabelConfig: { ...DEFAULT_ARIA_LABEL_CONFIG, ...ariaLabelConfig },
188
+ ...props,
189
+ children: [
190
+ background ? /* @__PURE__ */ jsx2(Background, { gap: 20, size: 1, color: "var(--canvas-grid)" }) : null,
191
+ children
192
+ ]
193
+ }
194
+ ) });
195
+ }
196
+ function CanvasShellWithHelperLines({
197
+ onNodesChange,
198
+ children,
199
+ ...props
200
+ }) {
201
+ const {
202
+ onNodesChange: wrappedOnNodesChange,
203
+ helperLineHorizontal,
204
+ helperLineVertical
205
+ } = useHelperLines(onNodesChange);
206
+ return /* @__PURE__ */ jsxs2(CanvasShellBase, { ...props, onNodesChange: wrappedOnNodesChange, children: [
207
+ children,
208
+ /* @__PURE__ */ jsx2(HelperLines, { horizontal: helperLineHorizontal, vertical: helperLineVertical })
209
+ ] });
210
+ }
211
+
212
+ // src/flow-node/flow-node.tsx
213
+ import "react";
214
+ import { Handle, Position } from "@xyflow/react";
215
+ import { Star } from "lucide-react";
216
+ import { STATUS_TONE_ICONS } from "@elabs-ai/components-ui";
217
+ import { cn as cn3 } from "@elabs-ai/components-ui/lib/cn";
218
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
219
+ var FLOW_ALL_SIDE_HANDLES = {
220
+ source: ["top", "right", "bottom", "left"],
221
+ target: ["top", "right", "bottom", "left"]
222
+ };
223
+ var toneRing = {
224
+ default: "border-border",
225
+ accent: "border-primary",
226
+ success: "border-success",
227
+ warning: "border-warning",
228
+ destructive: "border-destructive"
229
+ };
230
+ var toneIcon = {
231
+ accent: Star,
232
+ success: STATUS_TONE_ICONS.success,
233
+ warning: STATUS_TONE_ICONS.warning,
234
+ destructive: STATUS_TONE_ICONS.destructive
235
+ };
236
+ var toneIconColor = {
237
+ accent: "text-primary",
238
+ success: "text-success-text",
239
+ warning: "text-warning-text",
240
+ destructive: "text-destructive-text"
241
+ };
242
+ var toneLabel = {
243
+ accent: "Highlighted",
244
+ success: "Success",
245
+ warning: "Warning",
246
+ destructive: "Destructive"
247
+ };
248
+ var sidePosition = {
249
+ top: Position.Top,
250
+ right: Position.Right,
251
+ bottom: Position.Bottom,
252
+ left: Position.Left
253
+ };
254
+ var handleClassName = "!size-2 !border-2 !border-flow-edge !bg-flow-node";
255
+ function FlowNode({
256
+ data,
257
+ selected,
258
+ sourcePosition,
259
+ targetPosition
260
+ }) {
261
+ const tone = data.tone ?? "default";
262
+ const ToneIcon = toneIcon[tone];
263
+ return /* @__PURE__ */ jsxs3(
264
+ "div",
265
+ {
266
+ "data-tone": tone,
267
+ className: cn3(
268
+ "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",
269
+ toneRing[tone],
270
+ selected && "ring-2 ring-ring"
271
+ ),
272
+ children: [
273
+ data.handles ? /* @__PURE__ */ jsxs3(Fragment, { children: [
274
+ (data.handles.target ?? []).map((side) => /* @__PURE__ */ jsx3(
275
+ Handle,
276
+ {
277
+ id: side,
278
+ type: "target",
279
+ position: sidePosition[side],
280
+ className: handleClassName
281
+ },
282
+ `target-${side}`
283
+ )),
284
+ (data.handles.source ?? []).map((side) => /* @__PURE__ */ jsx3(
285
+ Handle,
286
+ {
287
+ id: side,
288
+ type: "source",
289
+ position: sidePosition[side],
290
+ className: handleClassName
291
+ },
292
+ `source-${side}`
293
+ ))
294
+ ] }) : /* @__PURE__ */ jsxs3(Fragment, { children: [
295
+ /* @__PURE__ */ jsx3(
296
+ Handle,
297
+ {
298
+ type: "target",
299
+ position: targetPosition ?? Position.Top,
300
+ className: handleClassName
301
+ }
302
+ ),
303
+ /* @__PURE__ */ jsx3(
304
+ Handle,
305
+ {
306
+ type: "source",
307
+ position: sourcePosition ?? Position.Bottom,
308
+ className: handleClassName
309
+ }
310
+ )
311
+ ] }),
312
+ /* @__PURE__ */ jsxs3("div", { className: "flex items-center gap-2", children: [
313
+ data.icon ? /* @__PURE__ */ jsx3("span", { className: "[&_svg]:size-4 text-muted-foreground", children: data.icon }) : null,
314
+ /* @__PURE__ */ jsxs3("div", { className: "min-w-0 flex-1", children: [
315
+ data.kind ? /* @__PURE__ */ jsx3("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: data.kind }) : null,
316
+ /* @__PURE__ */ jsx3("div", { className: "truncate text-sm font-medium", children: data.title }),
317
+ data.subtitle ? /* @__PURE__ */ jsx3("div", { className: "truncate text-xs text-muted-foreground", children: data.subtitle }) : null
318
+ ] }),
319
+ ToneIcon ? /* @__PURE__ */ jsx3(ToneIcon, { "aria-hidden": "true", className: cn3("size-3.5 shrink-0", toneIconColor[tone]) }) : null
320
+ ] }),
321
+ toneLabel[tone] ? /* @__PURE__ */ jsx3("span", { className: "sr-only", children: toneLabel[tone] }) : null
322
+ ]
323
+ }
324
+ );
325
+ }
326
+
327
+ // src/flow-edge/flow-edge.tsx
328
+ import { BaseEdge, getBezierPath } from "@xyflow/react";
329
+ import { jsx as jsx4 } from "react/jsx-runtime";
330
+ function FlowEdge({
331
+ id,
332
+ sourceX,
333
+ sourceY,
334
+ targetX,
335
+ targetY,
336
+ sourcePosition,
337
+ targetPosition,
338
+ markerEnd,
339
+ style
340
+ }) {
341
+ const [edgePath] = getBezierPath({
342
+ sourceX,
343
+ sourceY,
344
+ targetX,
345
+ targetY,
346
+ sourcePosition,
347
+ targetPosition
348
+ });
349
+ return /* @__PURE__ */ jsx4(
350
+ BaseEdge,
351
+ {
352
+ id,
353
+ path: edgePath,
354
+ markerEnd,
355
+ style: { stroke: "var(--flow-edge)", strokeWidth: 1.5, ...style }
356
+ }
357
+ );
358
+ }
359
+
360
+ // src/flow-smart-edge/flow-smart-edge.tsx
361
+ import {
362
+ BaseEdge as BaseEdge2,
363
+ getBezierPath as getBezierPath2,
364
+ useInternalNode
365
+ } from "@xyflow/react";
366
+
367
+ // src/flow-smart-edge/smart-edge-geometry.ts
368
+ import { Position as Position2 } from "@xyflow/react";
369
+ var HANDLE_SIDES = ["top", "right", "bottom", "left"];
370
+ var sideToPosition = {
371
+ top: Position2.Top,
372
+ right: Position2.Right,
373
+ bottom: Position2.Bottom,
374
+ left: Position2.Left
375
+ };
376
+ function rectCenter(rect) {
377
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 };
378
+ }
379
+ function slideAnchor(rect, side, toward, inset = 12) {
380
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
381
+ const ix = Math.min(inset, rect.width / 2);
382
+ const iy = Math.min(inset, rect.height / 2);
383
+ switch (side) {
384
+ case "top":
385
+ return { x: clamp(toward.x, rect.x + ix, rect.x + rect.width - ix), y: rect.y };
386
+ case "bottom":
387
+ return { x: clamp(toward.x, rect.x + ix, rect.x + rect.width - ix), y: rect.y + rect.height };
388
+ case "left":
389
+ return { x: rect.x, y: clamp(toward.y, rect.y + iy, rect.y + rect.height - iy) };
390
+ case "right":
391
+ return { x: rect.x + rect.width, y: clamp(toward.y, rect.y + iy, rect.y + rect.height - iy) };
392
+ }
393
+ }
394
+ function handlePoint(rect, side) {
395
+ switch (side) {
396
+ case "top":
397
+ return { x: rect.x + rect.width / 2, y: rect.y };
398
+ case "bottom":
399
+ return { x: rect.x + rect.width / 2, y: rect.y + rect.height };
400
+ case "left":
401
+ return { x: rect.x, y: rect.y + rect.height / 2 };
402
+ case "right":
403
+ return { x: rect.x + rect.width, y: rect.y + rect.height / 2 };
404
+ }
405
+ }
406
+ function pickClosestHandles(source, sourceSides, target, targetSides) {
407
+ const srcSides = sourceSides.length ? sourceSides : HANDLE_SIDES;
408
+ const tgtSides = targetSides.length ? targetSides : HANDLE_SIDES;
409
+ let best;
410
+ let bestDist = Infinity;
411
+ for (const s of srcSides) {
412
+ const sp = handlePoint(source, s);
413
+ for (const t of tgtSides) {
414
+ const tp = handlePoint(target, t);
415
+ const dx = tp.x - sp.x;
416
+ const dy = tp.y - sp.y;
417
+ const dist = dx * dx + dy * dy;
418
+ if (dist < bestDist) {
419
+ bestDist = dist;
420
+ best = { sourceSide: s, targetSide: t, sx: sp.x, sy: sp.y, tx: tp.x, ty: tp.y };
421
+ }
422
+ }
423
+ }
424
+ return best;
425
+ }
426
+
427
+ // src/flow-smart-edge/flow-smart-edge.tsx
428
+ import { jsx as jsx5 } from "react/jsx-runtime";
429
+ function toRect(node) {
430
+ return {
431
+ x: node.internals.positionAbsolute.x,
432
+ y: node.internals.positionAbsolute.y,
433
+ width: node.measured.width ?? 0,
434
+ height: node.measured.height ?? 0
435
+ };
436
+ }
437
+ function FlowSmartEdge({ id, source, target, markerEnd, style }) {
438
+ const sourceNode = useInternalNode(source);
439
+ const targetNode = useInternalNode(target);
440
+ if (!sourceNode || !targetNode) return null;
441
+ const sourceData = sourceNode.internals.userNode.data;
442
+ const targetData = targetNode.internals.userNode.data;
443
+ const sourceRect = toRect(sourceNode);
444
+ const targetRect = toRect(targetNode);
445
+ const { sourceSide, targetSide } = pickClosestHandles(
446
+ sourceRect,
447
+ sourceData?.handles?.source ?? [],
448
+ targetRect,
449
+ targetData?.handles?.target ?? []
450
+ );
451
+ const sourceAnchor = slideAnchor(sourceRect, sourceSide, rectCenter(targetRect));
452
+ const targetAnchor = slideAnchor(targetRect, targetSide, rectCenter(sourceRect));
453
+ const [edgePath] = getBezierPath2({
454
+ sourceX: sourceAnchor.x,
455
+ sourceY: sourceAnchor.y,
456
+ sourcePosition: sideToPosition[sourceSide],
457
+ targetX: targetAnchor.x,
458
+ targetY: targetAnchor.y,
459
+ targetPosition: sideToPosition[targetSide]
460
+ });
461
+ return /* @__PURE__ */ jsx5(
462
+ BaseEdge2,
463
+ {
464
+ id,
465
+ path: edgePath,
466
+ markerEnd,
467
+ style: { stroke: "var(--flow-edge)", strokeWidth: 1.5, ...style }
468
+ }
469
+ );
470
+ }
471
+
472
+ // src/flow-floating-edge/flow-floating-edge.tsx
473
+ import { BaseEdge as BaseEdge3, getBezierPath as getBezierPath3, useInternalNode as useInternalNode2 } from "@xyflow/react";
474
+
475
+ // src/flow-floating-edge/floating-edge-geometry.ts
476
+ import { Position as Position3 } from "@xyflow/react";
477
+ function getNodeIntersection(intersectionNode, targetNode) {
478
+ const w = (intersectionNode.measured.width ?? 0) / 2;
479
+ const h = (intersectionNode.measured.height ?? 0) / 2;
480
+ const pos = intersectionNode.internals.positionAbsolute;
481
+ const targetPos = targetNode.internals.positionAbsolute;
482
+ const x2 = pos.x + w;
483
+ const y2 = pos.y + h;
484
+ const x1 = targetPos.x + (targetNode.measured.width ?? 0) / 2;
485
+ const y1 = targetPos.y + (targetNode.measured.height ?? 0) / 2;
486
+ const safeW = w || 1;
487
+ const safeH = h || 1;
488
+ const xx1 = (x1 - x2) / (2 * safeW) - (y1 - y2) / (2 * safeH);
489
+ const yy1 = (x1 - x2) / (2 * safeW) + (y1 - y2) / (2 * safeH);
490
+ const a = 1 / (Math.abs(xx1) + Math.abs(yy1) || 1);
491
+ const xx3 = a * xx1;
492
+ const yy3 = a * yy1;
493
+ const x = safeW * (xx3 + yy3) + x2;
494
+ const y = safeH * (-xx3 + yy3) + y2;
495
+ return { x, y };
496
+ }
497
+ function clampToBorder(point, node) {
498
+ const x0 = node.internals.positionAbsolute.x;
499
+ const y0 = node.internals.positionAbsolute.y;
500
+ const w = node.measured.width ?? 0;
501
+ const h = node.measured.height ?? 0;
502
+ if (w <= 0 || h <= 0) return point;
503
+ const cx = Math.min(Math.max(point.x, x0), x0 + w);
504
+ const cy = Math.min(Math.max(point.y, y0), y0 + h);
505
+ const dLeft = cx - x0;
506
+ const dRight = x0 + w - cx;
507
+ const dTop = cy - y0;
508
+ const dBottom = y0 + h - cy;
509
+ const nearest = Math.min(dLeft, dRight, dTop, dBottom);
510
+ if (nearest === dLeft) return { x: x0, y: cy };
511
+ if (nearest === dRight) return { x: x0 + w, y: cy };
512
+ if (nearest === dTop) return { x: cx, y: y0 };
513
+ return { x: cx, y: y0 + h };
514
+ }
515
+ function getEdgeBorderPosition(node, point) {
516
+ const nx = Math.round(node.internals.positionAbsolute.x);
517
+ const ny = Math.round(node.internals.positionAbsolute.y);
518
+ const px = Math.round(point.x);
519
+ const py = Math.round(point.y);
520
+ const w = node.measured.width ?? 0;
521
+ const h = node.measured.height ?? 0;
522
+ if (px <= nx + 1) return Position3.Left;
523
+ if (px >= nx + w - 1) return Position3.Right;
524
+ if (py <= ny + 1) return Position3.Top;
525
+ if (py >= ny + h - 1) return Position3.Bottom;
526
+ return Position3.Top;
527
+ }
528
+ function getEdgeParams(source, target) {
529
+ const sourceIntersection = clampToBorder(getNodeIntersection(source, target), source);
530
+ const targetIntersection = clampToBorder(getNodeIntersection(target, source), target);
531
+ return {
532
+ sx: sourceIntersection.x,
533
+ sy: sourceIntersection.y,
534
+ tx: targetIntersection.x,
535
+ ty: targetIntersection.y,
536
+ sourcePos: getEdgeBorderPosition(source, sourceIntersection),
537
+ targetPos: getEdgeBorderPosition(target, targetIntersection)
538
+ };
539
+ }
540
+
541
+ // src/flow-floating-edge/flow-floating-edge.tsx
542
+ import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
543
+ var ANCHOR_RADIUS = 4;
544
+ function FlowFloatingEdge({ id, source, target, markerEnd, style, data }) {
545
+ const sourceNode = useInternalNode2(source);
546
+ const targetNode = useInternalNode2(target);
547
+ if (!sourceNode || !targetNode) return null;
548
+ const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
549
+ const [edgePath] = getBezierPath3({
550
+ sourceX: sx,
551
+ sourceY: sy,
552
+ sourcePosition: sourcePos,
553
+ targetX: tx,
554
+ targetY: ty,
555
+ targetPosition: targetPos
556
+ });
557
+ const showAnchors = data?.anchors !== false;
558
+ return /* @__PURE__ */ jsxs4(Fragment2, { children: [
559
+ /* @__PURE__ */ jsx6(
560
+ BaseEdge3,
561
+ {
562
+ id,
563
+ path: edgePath,
564
+ markerEnd,
565
+ style: { stroke: "var(--flow-edge)", strokeWidth: 1.5, ...style }
566
+ }
567
+ ),
568
+ showAnchors ? /* @__PURE__ */ jsxs4(
569
+ "g",
570
+ {
571
+ className: "brand-floating-edge__anchors",
572
+ fill: "var(--flow-node)",
573
+ stroke: "var(--flow-edge)",
574
+ children: [
575
+ /* @__PURE__ */ jsx6(
576
+ "circle",
577
+ {
578
+ cx: sx,
579
+ cy: sy,
580
+ r: ANCHOR_RADIUS,
581
+ strokeWidth: 2,
582
+ style: { pointerEvents: "none" },
583
+ "aria-hidden": "true"
584
+ }
585
+ ),
586
+ /* @__PURE__ */ jsx6(
587
+ "circle",
588
+ {
589
+ cx: tx,
590
+ cy: ty,
591
+ r: ANCHOR_RADIUS,
592
+ strokeWidth: 2,
593
+ style: { pointerEvents: "none" },
594
+ "aria-hidden": "true"
595
+ }
596
+ )
597
+ ]
598
+ }
599
+ ) : null
600
+ ] });
601
+ }
602
+
603
+ // src/flow-placeholder-node/flow-placeholder-node.tsx
604
+ import { Handle as Handle2, Position as Position4 } from "@xyflow/react";
605
+ import { Plus } from "lucide-react";
606
+ import { cn as cn4 } from "@elabs-ai/components-ui/lib/cn";
607
+ import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
608
+ function FlowPlaceholderNode({ data }) {
609
+ const label = data.label ?? "Add node";
610
+ return /* @__PURE__ */ jsxs5("div", { className: "relative", children: [
611
+ /* @__PURE__ */ jsx7(
612
+ Handle2,
613
+ {
614
+ type: "target",
615
+ position: Position4.Top,
616
+ className: "!size-2 !border-2 !border-flow-edge !bg-flow-node"
617
+ }
618
+ ),
619
+ /* @__PURE__ */ jsxs5(
620
+ "button",
621
+ {
622
+ type: "button",
623
+ "aria-label": label,
624
+ onClick: () => data.onActivate?.(),
625
+ className: cn4(
626
+ "flex min-w-44 items-center justify-center gap-1.5 rounded-lg border border-dashed border-flow-group-border bg-flow-group px-3 py-2 text-muted-foreground",
627
+ "transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground",
628
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
629
+ ),
630
+ children: [
631
+ /* @__PURE__ */ jsx7(Plus, { className: "size-4", "aria-hidden": "true" }),
632
+ /* @__PURE__ */ jsx7("span", { className: "text-body", children: label })
633
+ ]
634
+ }
635
+ )
636
+ ] });
637
+ }
638
+
639
+ // src/flow-button-edge/flow-button-edge.tsx
640
+ import {
641
+ BaseEdge as BaseEdge4,
642
+ EdgeLabelRenderer,
643
+ getBezierPath as getBezierPath4
644
+ } from "@xyflow/react";
645
+ import { Plus as Plus2 } from "lucide-react";
646
+ import { Fragment as Fragment3, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
647
+ function FlowButtonEdge({
648
+ id,
649
+ sourceX,
650
+ sourceY,
651
+ targetX,
652
+ targetY,
653
+ sourcePosition,
654
+ targetPosition,
655
+ markerEnd,
656
+ style,
657
+ data
658
+ }) {
659
+ const [edgePath, labelX, labelY] = getBezierPath4({
660
+ sourceX,
661
+ sourceY,
662
+ sourcePosition,
663
+ targetX,
664
+ targetY,
665
+ targetPosition
666
+ });
667
+ const label = data?.label ?? "Insert node on edge";
668
+ return /* @__PURE__ */ jsxs6(Fragment3, { children: [
669
+ /* @__PURE__ */ jsx8(
670
+ BaseEdge4,
671
+ {
672
+ id,
673
+ path: edgePath,
674
+ markerEnd,
675
+ style: { stroke: "var(--flow-edge)", strokeWidth: 1.5, ...style }
676
+ }
677
+ ),
678
+ /* @__PURE__ */ jsx8(EdgeLabelRenderer, { children: /* @__PURE__ */ jsx8(
679
+ "div",
680
+ {
681
+ style: {
682
+ position: "absolute",
683
+ transform: `translate(-50%, -50%) translate(${labelX}px, ${labelY}px)`
684
+ },
685
+ className: "nodrag nopan pointer-events-auto",
686
+ children: /* @__PURE__ */ jsx8(
687
+ "button",
688
+ {
689
+ type: "button",
690
+ "aria-label": label,
691
+ onClick: () => data?.onInsert?.(),
692
+ className: "flex size-5 items-center justify-center rounded-full border border-flow-group-border bg-flow-node text-flow-node-foreground shadow-sm transition-colors duration-fast ease-standard hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
693
+ children: /* @__PURE__ */ jsx8(Plus2, { className: "size-3", "aria-hidden": "true" })
694
+ }
695
+ )
696
+ }
697
+ ) })
698
+ ] });
699
+ }
700
+
701
+ // src/flow-layout/flow-layout.ts
702
+ import dagre from "@dagrejs/dagre";
703
+ import { Position as Position5 } from "@xyflow/react";
704
+ var HANDLE_BY_DIRECTION = {
705
+ TB: { source: Position5.Bottom, target: Position5.Top },
706
+ BT: { source: Position5.Top, target: Position5.Bottom },
707
+ LR: { source: Position5.Right, target: Position5.Left },
708
+ RL: { source: Position5.Left, target: Position5.Right }
709
+ };
710
+ var DEFAULT_NODE_WIDTH = 172;
711
+ var DEFAULT_NODE_HEIGHT = 40;
712
+ function nodeSize(node) {
713
+ return {
714
+ width: node.measured?.width ?? node.width ?? DEFAULT_NODE_WIDTH,
715
+ height: node.measured?.height ?? node.height ?? DEFAULT_NODE_HEIGHT
716
+ };
717
+ }
718
+ function layoutFlow(nodes, edges, options = {}) {
719
+ const { direction = "TB", nodeSpacing = 48, rankSpacing = 72 } = options;
720
+ const graph = new dagre.graphlib.Graph();
721
+ graph.setDefaultEdgeLabel(() => ({}));
722
+ graph.setGraph({ rankdir: direction, nodesep: nodeSpacing, ranksep: rankSpacing });
723
+ for (const node of nodes) {
724
+ const { width, height } = nodeSize(node);
725
+ graph.setNode(node.id, { width, height });
726
+ }
727
+ for (const edge of edges) {
728
+ graph.setEdge(edge.source, edge.target);
729
+ }
730
+ dagre.layout(graph);
731
+ const handles = HANDLE_BY_DIRECTION[direction];
732
+ const layoutedNodes = nodes.map((node) => {
733
+ const dagreNode = graph.node(node.id);
734
+ const { width, height } = nodeSize(node);
735
+ return {
736
+ ...node,
737
+ // Move the anchors with the layout direction so `FlowNode` puts its
738
+ // handles on the sides the edges actually leave/enter.
739
+ sourcePosition: handles.source,
740
+ targetPosition: handles.target,
741
+ position: {
742
+ x: dagreNode.x - width / 2,
743
+ y: dagreNode.y - height / 2
744
+ }
745
+ };
746
+ });
747
+ return { nodes: layoutedNodes, edges };
748
+ }
749
+
750
+ // src/flow-layout/use-flow-layout.ts
751
+ import { useCallback as useCallback2, useRef, useState as useState2 } from "react";
752
+ import { useNodesInitialized, useReactFlow as useReactFlow2 } from "@xyflow/react";
753
+ function useFlowLayout() {
754
+ const { getNodes, getEdges, setNodes, fitView } = useReactFlow2();
755
+ const nodesInitialized = useNodesInitialized();
756
+ const [layouting, setLayouting] = useState2(false);
757
+ const runningRef = useRef(false);
758
+ const layout = useCallback2(
759
+ (direction, options) => {
760
+ if (!nodesInitialized || runningRef.current) return;
761
+ runningRef.current = true;
762
+ setLayouting(true);
763
+ try {
764
+ const { nodes: layoutedNodes } = layoutFlow(getNodes(), getEdges(), {
765
+ ...options,
766
+ direction
767
+ });
768
+ setNodes(layoutedNodes);
769
+ requestAnimationFrame(() => fitView());
770
+ } finally {
771
+ runningRef.current = false;
772
+ setLayouting(false);
773
+ }
774
+ },
775
+ [nodesInitialized, getNodes, getEdges, setNodes, fitView]
776
+ );
777
+ return { layout, ready: nodesInitialized, layouting };
778
+ }
779
+
780
+ // src/flow-layout/layout-graph.ts
781
+ import {
782
+ forceCenter,
783
+ forceCollide,
784
+ forceLink,
785
+ forceManyBody,
786
+ forceSimulation
787
+ } from "d3-force";
788
+ var DEFAULT_NODE_WIDTH2 = 172;
789
+ var DEFAULT_NODE_HEIGHT2 = 40;
790
+ var DEFAULT_RING_RADIUS = 180;
791
+ var DEFAULT_FORCE_ITERATIONS = 300;
792
+ var DEFAULT_GRID_SPACING = { x: 200, y: 120 };
793
+ var FORCE_CHARGE_STRENGTH = -300;
794
+ var FORCE_LINK_DISTANCE = 150;
795
+ function resolveNodeSize(node, nodeSize2) {
796
+ if (nodeSize2) return nodeSize2(node.id);
797
+ return {
798
+ width: node.measured?.width ?? node.width ?? DEFAULT_NODE_WIDTH2,
799
+ height: node.measured?.height ?? node.height ?? DEFAULT_NODE_HEIGHT2
800
+ };
801
+ }
802
+ function layoutGraph(nodes, edges, options) {
803
+ if (nodes.length === 0) return [];
804
+ switch (options.algorithm) {
805
+ case "layered-lr":
806
+ return layoutLayeredLr(nodes, edges, options);
807
+ case "concentric":
808
+ return layoutConcentric(nodes, edges, options);
809
+ case "force":
810
+ return layoutForce(nodes, edges, options);
811
+ case "grid":
812
+ return layoutGrid(nodes, options);
813
+ default: {
814
+ const exhaustiveCheck = options.algorithm;
815
+ throw new Error(`layoutGraph: unknown algorithm "${String(exhaustiveCheck)}"`);
816
+ }
817
+ }
818
+ }
819
+ function layoutLayeredLr(nodes, edges, options) {
820
+ const { spacing, nodeSize: nodeSize2 } = options;
821
+ const sizedNodes = nodeSize2 ? nodes.map((node) => {
822
+ const { width, height } = nodeSize2(node.id);
823
+ return { ...node, width, height, measured: { width, height } };
824
+ }) : nodes;
825
+ const { nodes: laidOut } = layoutFlow(sizedNodes, edges, {
826
+ direction: "LR",
827
+ // For an LR flow, ranks progress horizontally (x) and nodes within a rank
828
+ // stack vertically (y) — map spacing accordingly onto dagre's rank/node sep.
829
+ nodeSpacing: spacing?.y,
830
+ rankSpacing: spacing?.x
831
+ });
832
+ return nodes.map((node, i) => ({
833
+ ...node,
834
+ sourcePosition: laidOut[i].sourcePosition,
835
+ targetPosition: laidOut[i].targetPosition,
836
+ position: laidOut[i].position
837
+ }));
838
+ }
839
+ function resolveCenterId(centerId, nodeIds, adjacency) {
840
+ if (centerId && nodeIds.includes(centerId)) return centerId;
841
+ let best = nodeIds[0];
842
+ let bestDegree = -1;
843
+ for (const id of nodeIds) {
844
+ const degree = adjacency.get(id)?.size ?? 0;
845
+ if (degree > bestDegree) {
846
+ bestDegree = degree;
847
+ best = id;
848
+ }
849
+ }
850
+ return best;
851
+ }
852
+ function layoutConcentric(nodes, edges, options) {
853
+ const ringRadius = options.ringRadius ?? DEFAULT_RING_RADIUS;
854
+ const nodeIds = nodes.map((node) => node.id);
855
+ const idSet = new Set(nodeIds);
856
+ const adjacency = /* @__PURE__ */ new Map();
857
+ for (const id of nodeIds) adjacency.set(id, /* @__PURE__ */ new Set());
858
+ for (const edge of edges) {
859
+ if (!idSet.has(edge.source) || !idSet.has(edge.target)) continue;
860
+ adjacency.get(edge.source).add(edge.target);
861
+ adjacency.get(edge.target).add(edge.source);
862
+ }
863
+ const centerId = resolveCenterId(options.centerId, nodeIds, adjacency);
864
+ const hopOf = /* @__PURE__ */ new Map();
865
+ hopOf.set(centerId, 0);
866
+ const queue = [centerId];
867
+ let maxHop = 0;
868
+ while (queue.length > 0) {
869
+ const current = queue.shift();
870
+ const currentHop = hopOf.get(current);
871
+ for (const neighbor of adjacency.get(current) ?? []) {
872
+ if (hopOf.has(neighbor)) continue;
873
+ const hop = currentHop + 1;
874
+ hopOf.set(neighbor, hop);
875
+ maxHop = Math.max(maxHop, hop);
876
+ queue.push(neighbor);
877
+ }
878
+ }
879
+ const leftoverHop = maxHop + 1;
880
+ for (const id of nodeIds) {
881
+ if (!hopOf.has(id)) hopOf.set(id, leftoverHop);
882
+ }
883
+ const rings = /* @__PURE__ */ new Map();
884
+ for (const id of nodeIds) {
885
+ const hop = hopOf.get(id);
886
+ if (hop === 0) continue;
887
+ if (!rings.has(hop)) rings.set(hop, []);
888
+ rings.get(hop).push(id);
889
+ }
890
+ const positionById = /* @__PURE__ */ new Map();
891
+ positionById.set(centerId, { x: 0, y: 0 });
892
+ for (const [hop, ids] of rings) {
893
+ const count = ids.length;
894
+ const angleStep = 2 * Math.PI / count;
895
+ const ringOffset = (hop - 1) * (angleStep / 2);
896
+ const radius = hop * ringRadius;
897
+ ids.forEach((id, i) => {
898
+ const angle = ringOffset + i * angleStep;
899
+ positionById.set(id, { x: radius * Math.cos(angle), y: radius * Math.sin(angle) });
900
+ });
901
+ }
902
+ return nodes.map((node) => ({
903
+ ...node,
904
+ position: positionById.get(node.id) ?? { x: 0, y: 0 }
905
+ }));
906
+ }
907
+ function layoutForce(nodes, edges, options) {
908
+ const iterations = options.iterations ?? DEFAULT_FORCE_ITERATIONS;
909
+ const nodeIds = new Set(nodes.map((node) => node.id));
910
+ const seedRadius = Math.max(nodes.length * 20, 100);
911
+ const simNodes = nodes.map((node, i) => {
912
+ const angle = 2 * Math.PI * i / Math.max(nodes.length, 1);
913
+ return { id: node.id, x: seedRadius * Math.cos(angle), y: seedRadius * Math.sin(angle) };
914
+ });
915
+ const simLinks = edges.filter((edge) => nodeIds.has(edge.source) && nodeIds.has(edge.target)).map((edge) => ({ source: edge.source, target: edge.target }));
916
+ const collisionById = new Map(
917
+ nodes.map((node) => {
918
+ const { width, height } = resolveNodeSize(node, options.nodeSize);
919
+ return [node.id, Math.max(width, height) / 2];
920
+ })
921
+ );
922
+ const simulation = forceSimulation(simNodes).force("charge", forceManyBody().strength(FORCE_CHARGE_STRENGTH)).force(
923
+ "link",
924
+ forceLink(simLinks).id((d) => d.id).distance(FORCE_LINK_DISTANCE)
925
+ ).force("center", forceCenter(0, 0)).force(
926
+ "collide",
927
+ forceCollide((d) => collisionById.get(d.id) ?? DEFAULT_NODE_WIDTH2 / 2)
928
+ ).stop();
929
+ for (let i = 0; i < iterations; i++) {
930
+ simulation.tick();
931
+ }
932
+ const positionById = new Map(simNodes.map((n) => [n.id, { x: n.x ?? 0, y: n.y ?? 0 }]));
933
+ return nodes.map((node) => ({
934
+ ...node,
935
+ position: positionById.get(node.id) ?? { x: 0, y: 0 }
936
+ }));
937
+ }
938
+ function layoutGrid(nodes, options) {
939
+ const spacing = options.spacing ?? DEFAULT_GRID_SPACING;
940
+ const cols = Math.max(1, Math.ceil(Math.sqrt(nodes.length)));
941
+ const handles = HANDLE_BY_DIRECTION.TB;
942
+ return nodes.map((node, i) => {
943
+ const row = Math.floor(i / cols);
944
+ const col = i % cols;
945
+ return {
946
+ ...node,
947
+ sourcePosition: handles.source,
948
+ targetPosition: handles.target,
949
+ position: { x: col * spacing.x, y: row * spacing.y }
950
+ };
951
+ });
952
+ }
953
+
954
+ // src/flow-layout/use-auto-layout.ts
955
+ import { useMemo } from "react";
956
+ function useAutoLayout(nodes, edges, options) {
957
+ return useMemo(
958
+ () => layoutGraph(nodes, edges, options),
959
+ [nodes, edges, options]
960
+ );
961
+ }
962
+
963
+ // src/flow-mini-map/flow-mini-map.tsx
964
+ import { MiniMap } from "@xyflow/react";
965
+ import { cn as cn5 } from "@elabs-ai/components-ui/lib/cn";
966
+ import { jsx as jsx9 } from "react/jsx-runtime";
967
+ function FlowMiniMap({
968
+ className,
969
+ nodeColor = "var(--flow-minimap-node)",
970
+ nodeStrokeColor = "var(--flow-minimap-node)",
971
+ maskColor = "var(--flow-minimap-mask)",
972
+ ...props
973
+ }) {
974
+ return /* @__PURE__ */ jsx9(
975
+ MiniMap,
976
+ {
977
+ className: cn5("!rounded-lg !bg-surface-elevated !shadow-ring-sm", className),
978
+ nodeColor,
979
+ nodeStrokeColor,
980
+ maskColor,
981
+ ...props
982
+ }
983
+ );
984
+ }
985
+
986
+ // src/inspector-panel/inspector-panel.tsx
987
+ import "react";
988
+ import { Reveal, useCollapsiblePanel } from "@elabs-ai/components-ui";
989
+ import { cn as cn6 } from "@elabs-ai/components-ui/lib/cn";
990
+ import { jsx as jsx10, jsxs as jsxs7 } from "react/jsx-runtime";
991
+ var INSPECTOR_PANEL_WIDTH = "18rem";
992
+ function InspectorPanel({
993
+ title = "Inspector",
994
+ children,
995
+ onClose,
996
+ emptyMessage = "Select a node to see its details.",
997
+ hasSelection = true,
998
+ selectionKey,
999
+ open,
1000
+ defaultOpen = true,
1001
+ onOpenChange,
1002
+ side = "right",
1003
+ width = INSPECTOR_PANEL_WIDTH,
1004
+ className
1005
+ }) {
1006
+ const panel = useCollapsiblePanel({
1007
+ side,
1008
+ open,
1009
+ defaultOpen,
1010
+ onOpenChange,
1011
+ widthClassName: "w-(--inspector-panel-width)",
1012
+ spacerCollapsedClassName: "group-data-[state=collapsed]:w-0",
1013
+ containerSlideClassNames: {
1014
+ left: "left-0 group-data-[state=collapsed]:left-[calc(var(--inspector-panel-width)*-1)]",
1015
+ right: "right-0 group-data-[state=collapsed]:right-[calc(var(--inspector-panel-width)*-1)]"
1016
+ }
1017
+ });
1018
+ return /* @__PURE__ */ jsxs7(
1019
+ "div",
1020
+ {
1021
+ className: cn6("group relative h-full overflow-hidden", className),
1022
+ style: { "--inspector-panel-width": width },
1023
+ inert: panel.open ? void 0 : true,
1024
+ ...panel.attrs,
1025
+ children: [
1026
+ /* @__PURE__ */ jsx10("div", { "data-slot": "inspector-panel-gap", className: panel.spacerClassName }),
1027
+ /* @__PURE__ */ jsx10(
1028
+ "div",
1029
+ {
1030
+ "data-slot": "inspector-panel-container",
1031
+ className: cn6(panel.containerClassName, "absolute inset-y-0 flex h-full border-s"),
1032
+ children: /* @__PURE__ */ jsxs7("div", { className: "flex h-full w-full flex-col bg-card", children: [
1033
+ /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between border-b px-4 py-3", children: [
1034
+ /* @__PURE__ */ jsx10("h3", { className: "text-body font-semibold text-foreground", children: title }),
1035
+ onClose ? /* @__PURE__ */ jsx10(
1036
+ "button",
1037
+ {
1038
+ type: "button",
1039
+ "aria-label": "Close inspector",
1040
+ onClick: onClose,
1041
+ className: "rounded-sm p-1 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
1042
+ children: /* @__PURE__ */ jsx10(
1043
+ "svg",
1044
+ {
1045
+ width: "14",
1046
+ height: "14",
1047
+ viewBox: "0 0 24 24",
1048
+ fill: "none",
1049
+ stroke: "currentColor",
1050
+ strokeWidth: "2",
1051
+ strokeLinecap: "round",
1052
+ strokeLinejoin: "round",
1053
+ "aria-hidden": "true",
1054
+ children: /* @__PURE__ */ jsx10("path", { d: "M18 6 6 18M6 6l12 12" })
1055
+ }
1056
+ )
1057
+ }
1058
+ ) : null
1059
+ ] }),
1060
+ hasSelection ? /* @__PURE__ */ jsx10(
1061
+ Reveal,
1062
+ {
1063
+ appear: "fade",
1064
+ speed: "fast",
1065
+ className: "flex-1 overflow-y-auto p-4 text-body",
1066
+ children
1067
+ },
1068
+ selectionKey
1069
+ ) : /* @__PURE__ */ jsx10("div", { className: "flex-1 overflow-y-auto p-4 text-body", children: /* @__PURE__ */ jsx10("p", { className: "text-muted-foreground", children: emptyMessage }) })
1070
+ ] })
1071
+ }
1072
+ )
1073
+ ]
1074
+ }
1075
+ );
1076
+ }
1077
+
1078
+ // src/legend/legend.tsx
1079
+ import { cn as cn7 } from "@elabs-ai/components-ui/lib/cn";
1080
+ import { jsx as jsx11, jsxs as jsxs8 } from "react/jsx-runtime";
1081
+ function Legend({ items, title, className }) {
1082
+ return /* @__PURE__ */ jsxs8(
1083
+ "div",
1084
+ {
1085
+ className: cn7(
1086
+ "rounded-lg bg-surface-elevated/90 p-3 text-xs shadow-ring-sm backdrop-blur",
1087
+ className
1088
+ ),
1089
+ children: [
1090
+ title ? /* @__PURE__ */ jsx11("div", { className: "mb-1.5 font-medium text-foreground", children: title }) : null,
1091
+ /* @__PURE__ */ jsx11("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs8("li", { className: "flex items-center gap-2 text-muted-foreground", children: [
1092
+ /* @__PURE__ */ jsx11(
1093
+ "span",
1094
+ {
1095
+ "aria-hidden": "true",
1096
+ className: "size-2.5 shrink-0 rounded-full",
1097
+ style: { backgroundColor: item.color }
1098
+ }
1099
+ ),
1100
+ item.label
1101
+ ] }, item.label)) })
1102
+ ]
1103
+ }
1104
+ );
1105
+ }
1106
+
1107
+ // src/zoom-controls/zoom-controls.tsx
1108
+ import { Panel, useReactFlow as useReactFlow3 } from "@xyflow/react";
1109
+ import { cn as cn8 } from "@elabs-ai/components-ui/lib/cn";
1110
+ import { jsx as jsx12, jsxs as jsxs9 } from "react/jsx-runtime";
1111
+ function ControlButton({
1112
+ label,
1113
+ onClick,
1114
+ children
1115
+ }) {
1116
+ return /* @__PURE__ */ jsx12(
1117
+ "button",
1118
+ {
1119
+ type: "button",
1120
+ "aria-label": label,
1121
+ onClick,
1122
+ className: "flex size-8 items-center justify-center text-foreground transition-colors duration-fast hover:bg-surface-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring [&_svg]:size-4",
1123
+ children
1124
+ }
1125
+ );
1126
+ }
1127
+ function ZoomControls({ position = "bottom-right", className }) {
1128
+ const { zoomIn, zoomOut, fitView } = useReactFlow3();
1129
+ return /* @__PURE__ */ jsx12(Panel, { position, children: /* @__PURE__ */ jsxs9(
1130
+ "div",
1131
+ {
1132
+ className: cn8(
1133
+ "flex divide-x overflow-hidden rounded-lg bg-surface-elevated shadow-ring-sm",
1134
+ className
1135
+ ),
1136
+ children: [
1137
+ /* @__PURE__ */ jsx12(ControlButton, { label: "Zoom in", onClick: () => zoomIn(), children: /* @__PURE__ */ jsx12(
1138
+ "svg",
1139
+ {
1140
+ viewBox: "0 0 24 24",
1141
+ fill: "none",
1142
+ stroke: "currentColor",
1143
+ strokeWidth: "2",
1144
+ strokeLinecap: "round",
1145
+ children: /* @__PURE__ */ jsx12("path", { d: "M12 5v14M5 12h14" })
1146
+ }
1147
+ ) }),
1148
+ /* @__PURE__ */ jsx12(ControlButton, { label: "Zoom out", onClick: () => zoomOut(), children: /* @__PURE__ */ jsx12(
1149
+ "svg",
1150
+ {
1151
+ viewBox: "0 0 24 24",
1152
+ fill: "none",
1153
+ stroke: "currentColor",
1154
+ strokeWidth: "2",
1155
+ strokeLinecap: "round",
1156
+ children: /* @__PURE__ */ jsx12("path", { d: "M5 12h14" })
1157
+ }
1158
+ ) }),
1159
+ /* @__PURE__ */ jsx12(ControlButton, { label: "Fit view", onClick: () => fitView(), children: /* @__PURE__ */ jsx12(
1160
+ "svg",
1161
+ {
1162
+ viewBox: "0 0 24 24",
1163
+ fill: "none",
1164
+ stroke: "currentColor",
1165
+ strokeWidth: "2",
1166
+ strokeLinecap: "round",
1167
+ strokeLinejoin: "round",
1168
+ children: /* @__PURE__ */ jsx12("path", { d: "M8 3H5a2 2 0 0 0-2 2v3M21 8V5a2 2 0 0 0-2-2h-3M16 21h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" })
1169
+ }
1170
+ ) })
1171
+ ]
1172
+ }
1173
+ ) });
1174
+ }
1175
+
1176
+ // src/flow-group-node/flow-group-node.tsx
1177
+ import "react";
1178
+ import { Handle as Handle3, NodeResizer, Position as Position6, useNodes } from "@xyflow/react";
1179
+ import { ChevronDown, ChevronRight } from "lucide-react";
1180
+ import { cn as cn9 } from "@elabs-ai/components-ui/lib/cn";
1181
+
1182
+ // src/use-flow-groups/use-flow-groups.ts
1183
+ import { useCallback as useCallback3 } from "react";
1184
+ import { useReactFlow as useReactFlow4 } from "@xyflow/react";
1185
+
1186
+ // src/use-flow-groups/group-operations.ts
1187
+ import { getNodesBounds } from "@xyflow/react";
1188
+ var FLOW_GROUP_NODE_TYPE = "group";
1189
+ var DEFAULT_GROUP_PADDING = 28;
1190
+ var GROUP_HEADER_OFFSET = 44;
1191
+ var OVERVIEW_WIDTH = 220;
1192
+ var OVERVIEW_HEIGHT = 48;
1193
+ var COLLAPSED_STATE_KEY = "__flowGroupCollapsedState";
1194
+ function isFlowGroupProxyEdge(edge) {
1195
+ return Boolean(edge.data?.__flowGroupProxy);
1196
+ }
1197
+ function proxyEdgeId(groupId, originalEdgeId) {
1198
+ return `flow-group-proxy__${groupId}__${originalEdgeId}`;
1199
+ }
1200
+ function readCollapsedState(node) {
1201
+ return node.data?.[COLLAPSED_STATE_KEY];
1202
+ }
1203
+ function collectDescendantIds(nodes, groupId) {
1204
+ const childrenByParent = /* @__PURE__ */ new Map();
1205
+ for (const n of nodes) {
1206
+ if (n.parentId) {
1207
+ const arr = childrenByParent.get(n.parentId);
1208
+ if (arr) arr.push(n);
1209
+ else childrenByParent.set(n.parentId, [n]);
1210
+ }
1211
+ }
1212
+ const out = /* @__PURE__ */ new Set();
1213
+ const stack = [...childrenByParent.get(groupId) ?? []];
1214
+ while (stack.length > 0) {
1215
+ const n = stack.pop();
1216
+ if (out.has(n.id)) continue;
1217
+ out.add(n.id);
1218
+ const kids = childrenByParent.get(n.id);
1219
+ if (kids) stack.push(...kids);
1220
+ }
1221
+ return out;
1222
+ }
1223
+ function groupNodes(nodes, edges, nodeIds, options) {
1224
+ const idSet = new Set(nodeIds);
1225
+ const children = nodes.filter((n) => idSet.has(n.id));
1226
+ if (children.length === 0) return { nodes, edges };
1227
+ const bounds = getNodesBounds(children);
1228
+ const padding = options.padding ?? DEFAULT_GROUP_PADDING;
1229
+ const originX = bounds.x - padding;
1230
+ const originY = bounds.y - padding - GROUP_HEADER_OFFSET;
1231
+ const data = {
1232
+ title: options.title ?? "Group",
1233
+ childCount: children.length,
1234
+ ...options.tone ? { tone: options.tone } : {}
1235
+ };
1236
+ const groupNode = {
1237
+ id: options.groupId,
1238
+ type: FLOW_GROUP_NODE_TYPE,
1239
+ position: { x: originX, y: originY },
1240
+ width: bounds.width + padding * 2,
1241
+ height: bounds.height + padding * 2 + GROUP_HEADER_OFFSET,
1242
+ data
1243
+ };
1244
+ const reparented = /* @__PURE__ */ new Map();
1245
+ for (const child of children) {
1246
+ reparented.set(child.id, {
1247
+ ...child,
1248
+ parentId: options.groupId,
1249
+ extent: "parent",
1250
+ position: { x: child.position.x - originX, y: child.position.y - originY }
1251
+ });
1252
+ }
1253
+ let earliest = nodes.length;
1254
+ for (let i = 0; i < nodes.length; i++) {
1255
+ if (idSet.has(nodes[i].id)) {
1256
+ earliest = i;
1257
+ break;
1258
+ }
1259
+ }
1260
+ const mapped = nodes.map((n) => reparented.get(n.id) ?? n);
1261
+ const resultNodes = [...mapped.slice(0, earliest), groupNode, ...mapped.slice(earliest)];
1262
+ return { nodes: resultNodes, edges };
1263
+ }
1264
+ function ungroup(nodes, edges, groupId) {
1265
+ const group = nodes.find((n) => n.id === groupId);
1266
+ if (!group) return { nodes, edges };
1267
+ const base = group.data?.collapsed ? expandGroup(nodes, edges, groupId) : { nodes, edges };
1268
+ const liveGroup = base.nodes.find((n) => n.id === groupId);
1269
+ const gx = liveGroup.position.x;
1270
+ const gy = liveGroup.position.y;
1271
+ const resultNodes = base.nodes.filter((n) => n.id !== groupId).map((n) => {
1272
+ if (n.parentId === groupId) {
1273
+ const {
1274
+ parentId: _parentId,
1275
+ extent: _extent,
1276
+ ...rest
1277
+ } = n;
1278
+ return {
1279
+ ...rest,
1280
+ position: { x: n.position.x + gx, y: n.position.y + gy }
1281
+ };
1282
+ }
1283
+ return n;
1284
+ });
1285
+ return { nodes: resultNodes, edges: base.edges };
1286
+ }
1287
+ function collapseGroup(nodes, edges, groupId) {
1288
+ const group = nodes.find((n) => n.id === groupId);
1289
+ if (!group) return { nodes, edges };
1290
+ if (group.data?.collapsed) return { nodes, edges };
1291
+ const descendantIds = collectDescendantIds(nodes, groupId);
1292
+ const descendantSnapshots = nodes.filter((n) => descendantIds.has(n.id));
1293
+ const directChildCount = nodes.filter((n) => n.parentId === groupId).length;
1294
+ const isInside = (id) => descendantIds.has(id);
1295
+ const reroutedEdges = [];
1296
+ const proxyEdges = [];
1297
+ for (const edge of edges) {
1298
+ const sourceInside = isInside(edge.source);
1299
+ const targetInside = isInside(edge.target);
1300
+ if (sourceInside === targetInside) continue;
1301
+ reroutedEdges.push(edge);
1302
+ const proxyData = {
1303
+ ...edge.data ?? {},
1304
+ __flowGroupProxy: true,
1305
+ groupId
1306
+ };
1307
+ proxyEdges.push({
1308
+ ...edge,
1309
+ id: proxyEdgeId(groupId, edge.id),
1310
+ source: sourceInside ? groupId : edge.source,
1311
+ target: targetInside ? groupId : edge.target,
1312
+ sourceHandle: void 0,
1313
+ targetHandle: void 0,
1314
+ data: proxyData
1315
+ });
1316
+ }
1317
+ const collapsedState = {
1318
+ groupSnapshot: group,
1319
+ descendantSnapshots,
1320
+ reroutedEdges
1321
+ };
1322
+ const resultNodes = nodes.map((n) => {
1323
+ if (n.id === groupId) {
1324
+ return {
1325
+ ...n,
1326
+ width: OVERVIEW_WIDTH,
1327
+ height: OVERVIEW_HEIGHT,
1328
+ data: {
1329
+ ...n.data,
1330
+ collapsed: true,
1331
+ childCount: directChildCount,
1332
+ [COLLAPSED_STATE_KEY]: collapsedState
1333
+ }
1334
+ };
1335
+ }
1336
+ if (descendantIds.has(n.id)) {
1337
+ return { ...n, hidden: true };
1338
+ }
1339
+ return n;
1340
+ });
1341
+ const reroutedIds = new Set(reroutedEdges.map((e) => e.id));
1342
+ const resultEdges = edges.map(
1343
+ (e) => reroutedIds.has(e.id) ? { ...e, hidden: true } : e
1344
+ );
1345
+ resultEdges.push(...proxyEdges);
1346
+ return { nodes: resultNodes, edges: resultEdges };
1347
+ }
1348
+ function expandGroup(nodes, edges, groupId) {
1349
+ const group = nodes.find((n) => n.id === groupId);
1350
+ const state = group ? readCollapsedState(group) : void 0;
1351
+ if (!group || !state) return { nodes, edges };
1352
+ const snapshotById = new Map(state.descendantSnapshots.map((n) => [n.id, n]));
1353
+ const reroutedById = new Map(state.reroutedEdges.map((e) => [e.id, e]));
1354
+ const resultNodes = nodes.map((n) => {
1355
+ if (n.id === groupId) return state.groupSnapshot;
1356
+ const snap = snapshotById.get(n.id);
1357
+ return snap ? snap : n;
1358
+ });
1359
+ const resultEdges = edges.filter(
1360
+ (e) => !(isFlowGroupProxyEdge(e) && e.data?.groupId === groupId)
1361
+ ).map((e) => {
1362
+ const original = reroutedById.get(e.id);
1363
+ return original ? original : e;
1364
+ });
1365
+ return { nodes: resultNodes, edges: resultEdges };
1366
+ }
1367
+ function toggleGroupCollapsed(nodes, edges, groupId) {
1368
+ const group = nodes.find((n) => n.id === groupId);
1369
+ if (!group) return { nodes, edges };
1370
+ return group.data?.collapsed ? expandGroup(nodes, edges, groupId) : collapseGroup(nodes, edges, groupId);
1371
+ }
1372
+
1373
+ // src/use-flow-groups/use-flow-groups.ts
1374
+ function generateGroupId() {
1375
+ return `flow-group-${Math.random().toString(36).slice(2, 9)}`;
1376
+ }
1377
+ function useFlowGroups() {
1378
+ const { getNodes, getEdges, setNodes, setEdges } = useReactFlow4();
1379
+ const apply = useCallback3(
1380
+ (result) => {
1381
+ setNodes(result.nodes);
1382
+ setEdges(result.edges);
1383
+ },
1384
+ [setNodes, setEdges]
1385
+ );
1386
+ const groupNodes2 = useCallback3(
1387
+ (nodeIds, options) => {
1388
+ if (nodeIds.length === 0) return void 0;
1389
+ const groupId = options?.groupId ?? generateGroupId();
1390
+ const result = groupNodes(getNodes(), getEdges(), nodeIds, {
1391
+ ...options,
1392
+ groupId
1393
+ });
1394
+ apply(result);
1395
+ return groupId;
1396
+ },
1397
+ [getNodes, getEdges, apply]
1398
+ );
1399
+ const groupSelection = useCallback3(
1400
+ (options) => {
1401
+ const selectedIds = getNodes().filter((n) => n.selected && !n.parentId).map((n) => n.id);
1402
+ return groupNodes2(selectedIds, options);
1403
+ },
1404
+ [getNodes, groupNodes2]
1405
+ );
1406
+ const ungroup2 = useCallback3(
1407
+ (groupId) => apply(ungroup(getNodes(), getEdges(), groupId)),
1408
+ [getNodes, getEdges, apply]
1409
+ );
1410
+ const collapseGroup2 = useCallback3(
1411
+ (groupId) => apply(collapseGroup(getNodes(), getEdges(), groupId)),
1412
+ [getNodes, getEdges, apply]
1413
+ );
1414
+ const expandGroup2 = useCallback3(
1415
+ (groupId) => apply(expandGroup(getNodes(), getEdges(), groupId)),
1416
+ [getNodes, getEdges, apply]
1417
+ );
1418
+ const toggleCollapse = useCallback3(
1419
+ (groupId) => apply(toggleGroupCollapsed(getNodes(), getEdges(), groupId)),
1420
+ [getNodes, getEdges, apply]
1421
+ );
1422
+ return {
1423
+ groupNodes: groupNodes2,
1424
+ groupSelection,
1425
+ ungroup: ungroup2,
1426
+ collapseGroup: collapseGroup2,
1427
+ expandGroup: expandGroup2,
1428
+ toggleCollapse
1429
+ };
1430
+ }
1431
+
1432
+ // src/flow-group-node/flow-group-node.tsx
1433
+ import { jsx as jsx13, jsxs as jsxs10 } from "react/jsx-runtime";
1434
+ var toneAccent = {
1435
+ default: "text-muted-foreground",
1436
+ // #399 — the group LABEL is text on the canvas, so it takes the on-surface
1437
+ // `-text` rung. (The status rows below stay on their fill rungs; those are a
1438
+ // separate, still-open question, not part of #399's enumerated call sites.)
1439
+ accent: "text-primary-text",
1440
+ success: "text-success",
1441
+ warning: "text-warning",
1442
+ destructive: "text-destructive"
1443
+ };
1444
+ var handleClassName2 = "!size-2 !border-2 !border-flow-group-border !bg-flow-group";
1445
+ function FlowGroupNode({ id, data, selected }) {
1446
+ const { toggleCollapse } = useFlowGroups();
1447
+ const nodes = useNodes();
1448
+ const collapsed = data.collapsed ?? false;
1449
+ const tone = data.tone ?? "default";
1450
+ const liveChildCount = nodes.filter((n) => n.parentId === id).length;
1451
+ const childCount = liveChildCount || data.childCount || 0;
1452
+ return /* @__PURE__ */ jsxs10(
1453
+ "div",
1454
+ {
1455
+ className: cn9(
1456
+ "flex h-full w-full flex-col rounded-lg border bg-flow-group/60 text-foreground",
1457
+ "border-flow-group-border transition-[box-shadow,border-color] duration-fast ease-standard motion-reduce:transition-none",
1458
+ collapsed ? "shadow-sm" : "shadow-none",
1459
+ selected && "ring-2 ring-ring"
1460
+ ),
1461
+ children: [
1462
+ !collapsed && selected ? /* @__PURE__ */ jsx13(
1463
+ NodeResizer,
1464
+ {
1465
+ minWidth: 160,
1466
+ minHeight: 96,
1467
+ lineClassName: "!border-flow-group-border",
1468
+ handleClassName: handleClassName2
1469
+ }
1470
+ ) : null,
1471
+ /* @__PURE__ */ jsx13(Handle3, { type: "target", position: Position6.Top, className: handleClassName2 }),
1472
+ /* @__PURE__ */ jsx13(Handle3, { type: "source", position: Position6.Bottom, className: handleClassName2 }),
1473
+ /* @__PURE__ */ jsxs10(
1474
+ "div",
1475
+ {
1476
+ className: cn9(
1477
+ "flex items-center gap-2 px-3 py-2",
1478
+ !collapsed && "border-b border-flow-group-border"
1479
+ ),
1480
+ children: [
1481
+ /* @__PURE__ */ jsx13(
1482
+ "button",
1483
+ {
1484
+ type: "button",
1485
+ onClick: () => toggleCollapse(id),
1486
+ "aria-label": collapsed ? `Expand group ${data.title}` : `Collapse group ${data.title}`,
1487
+ "aria-expanded": !collapsed,
1488
+ className: cn9(
1489
+ "-ml-1 grid size-5 shrink-0 place-items-center rounded",
1490
+ "text-muted-foreground hover:bg-muted hover:text-foreground",
1491
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1492
+ ),
1493
+ children: collapsed ? /* @__PURE__ */ jsx13(ChevronRight, { "aria-hidden": "true", className: "size-4" }) : /* @__PURE__ */ jsx13(ChevronDown, { "aria-hidden": "true", className: "size-4" })
1494
+ }
1495
+ ),
1496
+ data.icon ? /* @__PURE__ */ jsx13("span", { className: cn9("[&_svg]:size-4", toneAccent[tone]), children: data.icon }) : null,
1497
+ /* @__PURE__ */ jsx13("span", { className: "min-w-0 flex-1 truncate text-body font-medium", children: data.title }),
1498
+ /* @__PURE__ */ jsx13(
1499
+ "span",
1500
+ {
1501
+ className: cn9(
1502
+ "shrink-0 rounded-full bg-muted px-1.5 py-0.5 text-meta font-medium tabular-nums",
1503
+ toneAccent[tone]
1504
+ ),
1505
+ "aria-label": `${childCount} ${childCount === 1 ? "node" : "nodes"}`,
1506
+ children: childCount
1507
+ }
1508
+ )
1509
+ ]
1510
+ }
1511
+ )
1512
+ ]
1513
+ }
1514
+ );
1515
+ }
1516
+
1517
+ // src/index.ts
1518
+ import {
1519
+ Background as Background2,
1520
+ Controls,
1521
+ MiniMap as MiniMap2,
1522
+ Panel as Panel2,
1523
+ Position as Position7,
1524
+ ReactFlow as ReactFlow2,
1525
+ ReactFlowProvider as ReactFlowProvider2,
1526
+ useReactFlow as useReactFlow5,
1527
+ useNodesState,
1528
+ useEdgesState,
1529
+ addEdge
1530
+ } from "@xyflow/react";
1531
+ export {
1532
+ Background2 as Background,
1533
+ CanvasShell,
1534
+ Controls,
1535
+ FLOW_ALL_SIDE_HANDLES,
1536
+ FLOW_GROUP_NODE_TYPE,
1537
+ FlowButtonEdge,
1538
+ FlowEdge,
1539
+ FlowFloatingEdge,
1540
+ FlowGroupNode,
1541
+ FlowMiniMap,
1542
+ FlowNode,
1543
+ FlowPlaceholderNode,
1544
+ FlowSmartEdge,
1545
+ HANDLE_SIDES,
1546
+ HelperLines,
1547
+ InspectorPanel,
1548
+ Legend,
1549
+ MiniMap2 as MiniMap,
1550
+ Panel2 as Panel,
1551
+ Position7 as Position,
1552
+ ReactFlow2 as ReactFlow,
1553
+ ReactFlowProvider2 as ReactFlowProvider,
1554
+ ZoomControls,
1555
+ addEdge,
1556
+ collapseGroup,
1557
+ expandGroup,
1558
+ getEdgeParams,
1559
+ getHelperLines,
1560
+ groupNodes,
1561
+ handlePoint,
1562
+ isFlowGroupProxyEdge,
1563
+ layoutFlow,
1564
+ layoutGraph,
1565
+ pickClosestHandles,
1566
+ sideToPosition,
1567
+ toggleGroupCollapsed,
1568
+ ungroup,
1569
+ useAutoLayout,
1570
+ useEdgesState,
1571
+ useFlowGroups,
1572
+ useFlowLayout,
1573
+ useHelperLines,
1574
+ useNodesState,
1575
+ useReactFlow5 as useReactFlow
1576
+ };
1577
+ //# sourceMappingURL=index.js.map