@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
@@ -0,0 +1,634 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, SVGAttributes } from 'react';
3
+ import { Node, Edge, ReactFlowProps, NodeProps, EdgeProps, Position, NodeChange, MiniMapProps, PanelPosition } from '@xyflow/react';
4
+ export { Background, Connection, Controls, Edge, EdgeProps, MiniMap, Node, NodeProps, Panel, Position, ReactFlow, ReactFlowProvider, addEdge, useEdgesState, useNodesState, useReactFlow } from '@xyflow/react';
5
+
6
+ interface CanvasShellProps<NodeType extends Node = Node, EdgeType extends Edge = Edge> extends ReactFlowProps<NodeType, EdgeType> {
7
+ /** Render a branded dotted background grid. Defaults to true. */
8
+ background?: boolean;
9
+ /**
10
+ * Enable alignment guides + snapping while a single node is dragged. Off by
11
+ * default; when false, CanvasShell behaves exactly as before. When true,
12
+ * CanvasShell wires `useHelperLines` around your `onNodesChange` and renders
13
+ * the `<HelperLines>` overlay internally — pass `nodes` + `onNodesChange`
14
+ * (the standard controlled setup, e.g. via `useNodesState`). For full control
15
+ * use the exported `useHelperLines` + `<HelperLines>` directly.
16
+ */
17
+ helperLines?: boolean;
18
+ /** Overlays rendered inside the flow (ZoomControls, Legend, Panels). */
19
+ children?: ReactNode;
20
+ className?: string;
21
+ }
22
+ /**
23
+ * Branded React Flow canvas. Sets a token-driven background + sensible
24
+ * defaults; pass nodes/edges/handlers through as normal React Flow props.
25
+ *
26
+ * Consumers MUST import the React Flow stylesheet once at the app root:
27
+ * import "@xyflow/react/dist/style.css";
28
+ *
29
+ * Children render inside the flow context, so <ZoomControls /> and React Flow
30
+ * <Panel>s work when placed here.
31
+ */
32
+ declare function CanvasShell<NodeType extends Node = Node, EdgeType extends Edge = Edge>({ helperLines, ...props }: CanvasShellProps<NodeType, EdgeType>): react.JSX.Element;
33
+
34
+ /** A node side that can carry a handle. Doubles as the handle's stable id. */
35
+ type FlowHandleSide = "top" | "right" | "bottom" | "left";
36
+ /**
37
+ * Which sides of a node expose source and/or target handles. When omitted,
38
+ * `FlowNode` keeps its default single top **target** + bottom **source**. When
39
+ * set, a `<Handle>` is rendered on each listed side with a **stable id equal to
40
+ * the side name** (`"top" | "right" | "bottom" | "left"`), addressable per
41
+ * handle type — so edges (e.g. `FlowSmartEdge`) can pick a specific anchor.
42
+ */
43
+ interface FlowNodeHandles {
44
+ /** Sides exposing a source handle (handle `id` === side). */
45
+ source?: FlowHandleSide[];
46
+ /** Sides exposing a target handle (handle `id` === side). */
47
+ target?: FlowHandleSide[];
48
+ }
49
+ /**
50
+ * A `FlowNodeHandles` config exposing source **and** target anchors on all four
51
+ * sides. The recommended setup for `FlowSmartEdge` / `FlowFloatingEdge`: with
52
+ * anchors on every side, an edge connects on whichever side **faces** the other
53
+ * node (left/right when side-by-side, top/bottom when stacked) instead of always
54
+ * top/bottom. Spread onto a node's `data.handles`.
55
+ */
56
+ declare const FLOW_ALL_SIDE_HANDLES: FlowNodeHandles;
57
+ interface FlowNodeData extends Record<string, unknown> {
58
+ title: string;
59
+ subtitle?: string;
60
+ /** Short type label shown as an eyebrow, e.g. "Source", "Transform". */
61
+ kind?: string;
62
+ icon?: ReactNode;
63
+ tone?: "default" | "accent" | "success" | "warning" | "destructive";
64
+ /**
65
+ * Optional multi-side handle configuration. Absent → default top-target /
66
+ * bottom-source (unchanged, backward-compatible).
67
+ */
68
+ handles?: FlowNodeHandles;
69
+ }
70
+ type BrandFlowNode = Node<FlowNodeData, "brand">;
71
+ /**
72
+ * Branded custom node. Register it in `nodeTypes={{ brand: FlowNode }}` and
73
+ * create nodes with `type: "brand"` and `data: FlowNodeData`.
74
+ */
75
+ declare function FlowNode({ data, selected, sourcePosition, targetPosition, }: NodeProps<BrandFlowNode>): react.JSX.Element;
76
+
77
+ /**
78
+ * Branded bezier edge using the `--flow-edge` token. Register it in
79
+ * `edgeTypes={{ brand: FlowEdge }}` and create edges with `type: "brand"`.
80
+ */
81
+ declare function FlowEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, style, }: EdgeProps): react.JSX.Element;
82
+
83
+ /**
84
+ * Branded edge that picks the closest source/target handle pair (from a node's
85
+ * multi-side `FlowNodeHandles` config) and routes a bezier between them. Anchors
86
+ * are recomputed every render, so they flip as nodes are dragged. Register it in
87
+ * `edgeTypes={{ smart: FlowSmartEdge }}` and give the connected nodes a
88
+ * `data.handles` config. Uses the `--flow-edge` token, matching `FlowEdge`.
89
+ */
90
+ declare function FlowSmartEdge({ id, source, target, markerEnd, style }: EdgeProps): react.JSX.Element | null;
91
+
92
+ /** Axis-aligned node rectangle in absolute flow coordinates. */
93
+ interface NodeRect {
94
+ x: number;
95
+ y: number;
96
+ width: number;
97
+ height: number;
98
+ }
99
+ /** All four candidate handle sides, in a stable order. */
100
+ declare const HANDLE_SIDES: FlowHandleSide[];
101
+ /** Maps a handle side to the React Flow `Position` used for bezier control. */
102
+ declare const sideToPosition: Record<FlowHandleSide, Position>;
103
+ /** Absolute coordinate of a handle on the given side (the side's midpoint). */
104
+ declare function handlePoint(rect: NodeRect, side: FlowHandleSide): {
105
+ x: number;
106
+ y: number;
107
+ };
108
+ /** The chosen source/target handle pair plus their absolute anchor points. */
109
+ interface ClosestHandles {
110
+ sourceSide: FlowHandleSide;
111
+ targetSide: FlowHandleSide;
112
+ sx: number;
113
+ sy: number;
114
+ tx: number;
115
+ ty: number;
116
+ }
117
+ /**
118
+ * Picks the source/target handle pair (one handle per node) with the shortest
119
+ * straight-line distance between their anchor points. Empty side lists fall
120
+ * back to all four sides so an edge always resolves to a pair.
121
+ */
122
+ declare function pickClosestHandles(source: NodeRect, sourceSides: FlowHandleSide[], target: NodeRect, targetSides: FlowHandleSide[]): ClosestHandles;
123
+
124
+ /** Optional per-edge `data` for {@link FlowFloatingEdge}. */
125
+ interface FloatingEdgeData {
126
+ /**
127
+ * Show a small anchor dot at each border connection point. On by default so the
128
+ * edge visibly terminates on the node's closest side (rather than a bare line
129
+ * touching the border with no anchor). Set `false` to hide.
130
+ */
131
+ anchors?: boolean;
132
+ [key: string]: unknown;
133
+ }
134
+ /**
135
+ * Branded floating edge: it attaches to the node **border** at the point facing
136
+ * the other node (no fixed handle), recomputed as nodes drag. Register it in
137
+ * `edgeTypes={{ floating: FlowFloatingEdge }}`; the connected nodes need no
138
+ * handle config. Uses the `--flow-edge` token, matching `FlowEdge`.
139
+ *
140
+ * A small **anchor dot** is drawn at each connection point (on by default) so the
141
+ * line clearly terminates on the node's closest border side — not at a bare,
142
+ * unanchored spot. Toggle per edge with `data.anchors: false`.
143
+ */
144
+ declare function FlowFloatingEdge({ id, source, target, markerEnd, style, data }: EdgeProps): react.JSX.Element | null;
145
+
146
+ /**
147
+ * The minimal live node geometry `getEdgeParams` needs. A React Flow
148
+ * `InternalNode` satisfies this structurally (`internals.positionAbsolute` +
149
+ * `measured`), so you can pass one directly; tests can pass a plain fake.
150
+ */
151
+ interface FloatingNodeGeometry {
152
+ internals: {
153
+ positionAbsolute: {
154
+ x: number;
155
+ y: number;
156
+ };
157
+ };
158
+ measured: {
159
+ width?: number;
160
+ height?: number;
161
+ };
162
+ }
163
+ /** Border anchor points + `Position`s for a floating edge between two nodes. */
164
+ interface EdgeParams {
165
+ sx: number;
166
+ sy: number;
167
+ tx: number;
168
+ ty: number;
169
+ sourcePos: Position;
170
+ targetPos: Position;
171
+ }
172
+ /**
173
+ * Computes where a floating edge attaches to each node's border — the
174
+ * intersection of the center-to-center line with each rectangle — plus the
175
+ * border `Position`. Recompute per render so anchors track dragging nodes.
176
+ */
177
+ declare function getEdgeParams(source: FloatingNodeGeometry, target: FloatingNodeGeometry): EdgeParams;
178
+
179
+ interface FlowPlaceholderNodeData extends Record<string, unknown> {
180
+ /** Label rendered inside the placeholder. @default "Add node" */
181
+ label?: string;
182
+ /** Called when the placeholder is activated (click, Enter, or Space). */
183
+ onActivate?: () => void;
184
+ }
185
+ type BrandFlowPlaceholderNode = Node<FlowPlaceholderNodeData, "placeholder">;
186
+ /**
187
+ * Dashed, muted "add here" node — a clickable affordance that grows the
188
+ * graph. Register it in `nodeTypes={{ placeholder: FlowPlaceholderNode }}`
189
+ * and create nodes with `type: "placeholder"` and `data: FlowPlaceholderNodeData`.
190
+ *
191
+ * Renders a real `<button>` (keyboard-activatable via native Enter/Space) with
192
+ * an `aria-label`, and a single **target** `<Handle>` (top) so an existing
193
+ * edge can point at it. Fires `data.onActivate?.()` on activation — the
194
+ * typical handler converts the placeholder into a real `FlowNode` and grows a
195
+ * fresh placeholder beneath it (see the "Placeholder tail" story).
196
+ */
197
+ declare function FlowPlaceholderNode({ data }: NodeProps<BrandFlowPlaceholderNode>): react.JSX.Element;
198
+
199
+ interface FlowButtonEdgeData extends Record<string, unknown> {
200
+ /** aria-label for the insert button. @default "Insert node on edge" */
201
+ label?: string;
202
+ /** Called when the edge's "+" button is activated (click, Enter, or Space). */
203
+ onInsert?: () => void;
204
+ }
205
+ type BrandFlowButtonEdge = Edge<FlowButtonEdgeData, "button">;
206
+ /**
207
+ * Branded bezier edge (matching `FlowEdge`'s `--flow-edge` token) with a
208
+ * centered "+" button rendered via `EdgeLabelRenderer` at the edge midpoint.
209
+ * Register it in `edgeTypes={{ button: FlowButtonEdge }}` and create edges
210
+ * with `type: "button"` and `data: FlowButtonEdgeData`.
211
+ *
212
+ * The button is a real `<button>` with an `aria-label`, keyboard-activatable
213
+ * via native Enter/Space. Fires `data.onInsert?.()` on activation — the
214
+ * typical handler splits the edge: insert a new node between source and
215
+ * target and rewire the two edges (see the "Insert between" story).
216
+ */
217
+ declare function FlowButtonEdge({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, markerEnd, style, data, }: EdgeProps<BrandFlowButtonEdge>): react.JSX.Element;
218
+
219
+ /** Direction dagre lays the graph out in — top-to-bottom, left-to-right, etc. */
220
+ type FlowLayoutDirection = "TB" | "LR" | "BT" | "RL";
221
+ interface FlowLayoutOptions {
222
+ /** Layout direction. @default "TB" */
223
+ direction?: FlowLayoutDirection;
224
+ /** Gap between nodes in the same rank (dagre `nodesep`). @default 48 */
225
+ nodeSpacing?: number;
226
+ /** Gap between ranks (dagre `ranksep`). @default 72 */
227
+ rankSpacing?: number;
228
+ }
229
+ interface FlowLayoutResult<NodeType extends Node = Node, EdgeType extends Edge = Edge> {
230
+ nodes: NodeType[];
231
+ edges: EdgeType[];
232
+ }
233
+ /**
234
+ * Pure dagre-powered auto layout. Computes new `position`s for `nodes` given
235
+ * `edges` — no React, no side effects, safe to call anywhere (including
236
+ * outside a component or in a test).
237
+ *
238
+ * Respects each node's **measured** size (`node.measured` — how React Flow
239
+ * v12 reports actual rendered dimensions), falling back to `node.width`/
240
+ * `node.height`, then a sensible default. Node identity and `data` are left
241
+ * untouched — only `position` changes.
242
+ *
243
+ * Pair with `useFlowLayout` to apply the result to a live canvas.
244
+ */
245
+ declare function layoutFlow<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], options?: FlowLayoutOptions): FlowLayoutResult<NodeType, EdgeType>;
246
+
247
+ interface UseFlowLayoutResult {
248
+ /**
249
+ * Auto-lay-out the current nodes/edges with dagre and fit the view. Safe to
250
+ * call repeatedly (e.g. from a toolbar button on every click). No-ops until
251
+ * React Flow has measured every node.
252
+ */
253
+ layout: (direction?: FlowLayoutDirection, options?: Omit<FlowLayoutOptions, "direction">) => void;
254
+ /** Whether nodes are measured and ready to be laid out. */
255
+ ready: boolean;
256
+ /** Whether a layout pass is currently running (true only for the duration of `layout()`). */
257
+ layouting: boolean;
258
+ }
259
+ /**
260
+ * Hook form of `layoutFlow`. Reads the live nodes/edges from `useReactFlow()`,
261
+ * waits for `useNodesInitialized()` so real (measured) node sizes are used —
262
+ * never lays out against 0×0 unmeasured nodes — applies the computed
263
+ * positions via `setNodes`, then calls `fitView()`.
264
+ *
265
+ * Must be called from a component rendered inside a `<CanvasShell>` /
266
+ * `<ReactFlow>` (React Flow context).
267
+ */
268
+ declare function useFlowLayout<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): UseFlowLayoutResult;
269
+
270
+ /** Which generic graph-geometry algorithm `layoutGraph` should run. */
271
+ type LayoutAlgorithm = "concentric" | "force" | "layered-lr" | "grid";
272
+ /** Horizontal/vertical gap used by the `"grid"` and `"layered-lr"` algorithms. */
273
+ interface LayoutSpacing {
274
+ x: number;
275
+ y: number;
276
+ }
277
+ interface LayoutOptions {
278
+ /** Which layout algorithm to run. */
279
+ algorithm: LayoutAlgorithm;
280
+ /**
281
+ * `"concentric"` only — the focal node placed at the origin. Defaults to the
282
+ * highest-degree node when omitted (or when the id doesn't match a node).
283
+ */
284
+ centerId?: string;
285
+ /** `"concentric"` only — radius of ring 1; ring N sits at `N × ringRadius`. @default 180 */
286
+ ringRadius?: number;
287
+ /** `"force"` only — number of synchronous simulation ticks to run. @default 300 */
288
+ iterations?: number;
289
+ /** `"grid"` / `"layered-lr"` — gap between nodes. @default {x:200,y:120} for grid. */
290
+ spacing?: LayoutSpacing;
291
+ /** Resolve a node's size by id. Falls back to `measured`/`width`/`height`/a sensible default. */
292
+ nodeSize?: (id: string) => {
293
+ width: number;
294
+ height: number;
295
+ };
296
+ }
297
+ /**
298
+ * Pure graph-geometry auto layout. Computes new `position`s for `nodes` given
299
+ * `edges` and a `LayoutAlgorithm` — no React, no DOM, no side effects, safe to
300
+ * call anywhere (including outside a component or in a test). Deterministic:
301
+ * identical `nodes`/`edges`/`options` always produce identical positions.
302
+ *
303
+ * Node identity and `data` are left untouched — only `position` changes.
304
+ * Pair with `useAutoLayout` for a memoized hook form.
305
+ *
306
+ * - `"layered-lr"` delegates to the dagre-powered `layoutFlow` (direction `"LR"`).
307
+ * - `"concentric"` places `centerId` (or the highest-degree node) at the
308
+ * origin, with BFS shells at `ring × ringRadius`; disconnected nodes land in
309
+ * one extra outer ring so positions are never `NaN`.
310
+ * - `"force"` runs `d3-force` synchronously for a fixed number of `iterations`,
311
+ * seeded on a circle by index (no randomness) so runs are reproducible.
312
+ * - `"grid"` lays nodes out row-major using `spacing`.
313
+ */
314
+ declare function layoutGraph<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], options: LayoutOptions): NodeType[];
315
+
316
+ /**
317
+ * Memoized `layoutGraph` — pure graph-geometry layout (no live canvas access,
318
+ * no side effects). Recomputes only when `nodes`, `edges`, or `options`
319
+ * change; pass stable references (e.g. from `useNodesState`/`useEdgesState`
320
+ * and a memoized `options` object) to avoid recomputing every render.
321
+ *
322
+ * Use this when you want laid-out nodes to render with (e.g. feeding
323
+ * `useNodesState`'s initial value, or a derived/read-only view). To apply a
324
+ * layout to a *live* React Flow instance (read current nodes, set them back,
325
+ * fit the view), call `layoutGraph` directly from an event handler instead —
326
+ * see the `LayoutGraph` story.
327
+ */
328
+ declare function useAutoLayout<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], options: LayoutOptions): NodeType[];
329
+
330
+ /**
331
+ * Pure alignment-guide geometry for the helper-lines feature.
332
+ *
333
+ * Implemented fresh from the documented "helper lines" behaviour (the React Flow
334
+ * Pro example is paid — only the concept is reused): while a single node drags,
335
+ * compare its left / horizontal-center / right against every other node's
336
+ * left / center / right (and top / middle / bottom on the vertical axis). When a
337
+ * pair falls within `threshold` flow-coordinate pixels, record the guide's
338
+ * absolute coordinate and the snapped top-left position for the dragged node.
339
+ *
340
+ * Everything here is a pure function of plain rectangles so it is unit-testable
341
+ * without a live React Flow instance.
342
+ */
343
+ /** A node's bounding box in absolute flow coordinates (top-left origin). */
344
+ interface HelperLineRect {
345
+ /** Left edge (top-left x) in flow coordinates. */
346
+ x: number;
347
+ /** Top edge (top-left y) in flow coordinates. */
348
+ y: number;
349
+ width: number;
350
+ height: number;
351
+ }
352
+ interface HelperLinesResult {
353
+ /** New top-left x for the dragged node once snapped to the vertical guide. */
354
+ snapX?: number;
355
+ /** New top-left y for the dragged node once snapped to the horizontal guide. */
356
+ snapY?: number;
357
+ /** Absolute flow-x of the active vertical guide line, if any. */
358
+ vertical?: number;
359
+ /** Absolute flow-y of the active horizontal guide line, if any. */
360
+ horizontal?: number;
361
+ }
362
+ /**
363
+ * Compute the closest vertical + horizontal alignment guides (and the snapped
364
+ * position) for a dragged rectangle against the other rectangles.
365
+ *
366
+ * @param dragged the dragged node's proposed bounding box (flow coordinates)
367
+ * @param others every other node's bounding box
368
+ * @param threshold max distance (flow px) at which alignment engages; a pair at
369
+ * exactly `threshold` does NOT match (strict boundary)
370
+ */
371
+ declare function getHelperLines(dragged: HelperLineRect, others: Iterable<HelperLineRect>, threshold?: number): HelperLinesResult;
372
+
373
+ interface UseHelperLinesOptions {
374
+ /** Alignment threshold in flow coordinates. Defaults to 5. */
375
+ threshold?: number;
376
+ }
377
+ interface UseHelperLinesResult<NodeType extends Node = Node> {
378
+ /**
379
+ * Drop-in replacement for the consumer's `onNodesChange`: it snaps a dragged
380
+ * node onto any near-aligned edge/center, then forwards the (mutated) changes
381
+ * to the wrapped handler.
382
+ */
383
+ onNodesChange: (changes: NodeChange<NodeType>[]) => void;
384
+ /** Absolute flow-y of the active horizontal guide, or `undefined`. */
385
+ helperLineHorizontal: number | undefined;
386
+ /** Absolute flow-x of the active vertical guide, or `undefined`. */
387
+ helperLineVertical: number | undefined;
388
+ }
389
+ /**
390
+ * Decorate a consumer's `onNodesChange` with alignment guides + snapping.
391
+ *
392
+ * While a **single** node is dragged, the returned handler compares its
393
+ * left/center/right and top/middle/bottom against every other node; when a pair
394
+ * is within `threshold` flow px it (a) snaps the dragged node's position change
395
+ * onto the guide and (b) exposes the guide coordinates so `<HelperLines>` can
396
+ * draw them. Guides clear when the drag ends or nothing aligns.
397
+ *
398
+ * Must be called inside a React Flow context (a `<ReactFlowProvider>` or a
399
+ * component rendered under `<ReactFlow>`), since it reads the live node store.
400
+ *
401
+ * @example
402
+ * const [nodes, , onNodesChange] = useNodesState(initialNodes);
403
+ * const { onNodesChange: handle, helperLineHorizontal, helperLineVertical } =
404
+ * useHelperLines(onNodesChange);
405
+ * // <ReactFlow onNodesChange={handle}>
406
+ * // <HelperLines horizontal={helperLineHorizontal} vertical={helperLineVertical} />
407
+ */
408
+ declare function useHelperLines<NodeType extends Node = Node>(onNodesChange?: (changes: NodeChange<NodeType>[]) => void, options?: UseHelperLinesOptions): UseHelperLinesResult<NodeType>;
409
+
410
+ interface HelperLinesProps extends SVGAttributes<SVGSVGElement> {
411
+ /** Absolute flow-y of the horizontal guide (from `useHelperLines`). */
412
+ horizontal?: number;
413
+ /** Absolute flow-x of the vertical guide (from `useHelperLines`). */
414
+ vertical?: number;
415
+ }
416
+ /**
417
+ * Decorative alignment-guide overlay. Draws a full-width horizontal line at
418
+ * `horizontal` and/or a full-height vertical line at `vertical`, coloured with
419
+ * the `--flow-helper-line` token. It reads the live viewport transform
420
+ * (zoom + pan) so the guides sit on the real alignment position when the canvas
421
+ * is zoomed or panned.
422
+ *
423
+ * Render as a child of `<CanvasShell>` / `<ReactFlow>` (it must be inside the
424
+ * flow context to read the viewport). Purely decorative — hidden from AT.
425
+ */
426
+ declare const HelperLines: react.ForwardRefExoticComponent<HelperLinesProps & react.RefAttributes<SVGSVGElement>>;
427
+
428
+ type FlowMiniMapProps<NodeType extends Node = Node> = MiniMapProps<NodeType>;
429
+ /**
430
+ * Branded preset over React Flow's `<MiniMap>` — sources node/mask colors from
431
+ * the `--flow-minimap-*` tokens so the overview stays theme-aware (the raw
432
+ * `MiniMap` re-export defaults to library colors and renders theme-blind,
433
+ * especially under a low-chroma theme). Every prop can still be overridden; render
434
+ * inside a `<CanvasShell>` (it needs the React Flow context via `useStore`).
435
+ */
436
+ declare function FlowMiniMap<NodeType extends Node = Node>({ className, nodeColor, nodeStrokeColor, maskColor, ...props }: FlowMiniMapProps<NodeType>): react.JSX.Element;
437
+
438
+ interface InspectorPanelProps {
439
+ title?: ReactNode;
440
+ children: ReactNode;
441
+ onClose?: () => void;
442
+ /** Empty-state shown when nothing is selected. */
443
+ emptyMessage?: ReactNode;
444
+ /** When false, render the empty state instead of children. */
445
+ hasSelection?: boolean;
446
+ /**
447
+ * Stable identity of the current selection (e.g. the selected node's id).
448
+ * When it changes, the body replays a quick fade so it reads as "details for
449
+ * THIS node". Pass the selected entity's id (or any stable key derived from
450
+ * the selected data).
451
+ */
452
+ selectionKey?: string | number;
453
+ /**
454
+ * Collapse (research 09 step 3 — converged on the shared `@elabs-ai/components-ui`
455
+ * `useCollapsiblePanel` base; the panel stays single-view, drill-in is
456
+ * ContextPanel-only). Controlled `open` / uncontrolled `defaultOpen`.
457
+ */
458
+ open?: boolean;
459
+ defaultOpen?: boolean;
460
+ onOpenChange?: (open: boolean) => void;
461
+ /** Which edge the panel sits on (collapse slide direction). Default `"right"`. */
462
+ side?: "left" | "right";
463
+ /** Expanded width (any CSS length). Default `"18rem"` (the original `w-72`). */
464
+ width?: string;
465
+ className?: string;
466
+ }
467
+ /**
468
+ * Properties panel for the selected node/edge. Reusable beside a canvas (e.g.
469
+ * inside a <SplitPanel>) or as a React Flow <Panel>. Always mounted; collapses
470
+ * via the canonical `useCollapsiblePanel` width tween (CSS-gated, reduced-
471
+ * motion safe) when `open`/`defaultOpen` drive it.
472
+ */
473
+ declare function InspectorPanel({ title, children, onClose, emptyMessage, hasSelection, selectionKey, open, defaultOpen, onOpenChange, side, width, className, }: InspectorPanelProps): react.JSX.Element;
474
+
475
+ interface LegendItem {
476
+ label: string;
477
+ /** Any CSS color or token reference, e.g. "var(--chart-1)". */
478
+ color: string;
479
+ }
480
+ interface LegendProps {
481
+ items: LegendItem[];
482
+ title?: string;
483
+ className?: string;
484
+ }
485
+ /** Small legend mapping colors/types to labels for a canvas or chart. */
486
+ declare function Legend({ items, title, className }: LegendProps): react.JSX.Element;
487
+
488
+ interface ZoomControlsProps {
489
+ position?: PanelPosition;
490
+ className?: string;
491
+ }
492
+ /** Branded zoom in / out / fit controls. Render inside <CanvasShell>. */
493
+ declare function ZoomControls({ position, className }: ZoomControlsProps): react.JSX.Element;
494
+
495
+ /** Visual accent for a group container. */
496
+ type FlowGroupTone = "default" | "accent" | "success" | "warning" | "destructive";
497
+ interface FlowGroupNodeData extends Record<string, unknown> {
498
+ /** Header label for the group. */
499
+ title: string;
500
+ /** Optional leading glyph (a Lucide icon element, etc.). */
501
+ icon?: ReactNode;
502
+ /**
503
+ * Whether the group is collapsed to an overview chip. Managed by
504
+ * `useFlowGroups().collapseGroup` / `expandGroup`; the header toggle flips it.
505
+ */
506
+ collapsed?: boolean;
507
+ /**
508
+ * Fallback count shown next to the title. When omitted, the node counts its
509
+ * live direct children from the store, so the badge stays accurate as nodes
510
+ * are added or removed.
511
+ */
512
+ childCount?: number;
513
+ /** Accent tone for the header rule + count badge. */
514
+ tone?: FlowGroupTone;
515
+ }
516
+ type BrandFlowGroupNode = Node<FlowGroupNodeData, "group">;
517
+ /**
518
+ * Branded group container node. Register it as `nodeTypes={{ group: FlowGroupNode }}`
519
+ * and create nodes with `type: "group"` + typed `data: FlowGroupNodeData`. Children
520
+ * are re-parented onto it (`parentId`, `extent: "parent"`) — see `useFlowGroups`.
521
+ *
522
+ * The header carries the title, a live child-count badge and a collapse/expand
523
+ * toggle (a real `<button>` with `aria-expanded`) that drives
524
+ * `useFlowGroups().collapseGroup` / `expandGroup`. When selected (and expanded)
525
+ * a `<NodeResizer>` lets the group be resized.
526
+ *
527
+ * The surface uses the FL-01 tokens `--flow-group` (fill) + `--flow-group-border`
528
+ * (border). Under a theme whose `--flow-group` is near-transparent, the
529
+ * drawn border does the work.
530
+ */
531
+ declare function FlowGroupNode({ id, data, selected }: NodeProps<BrandFlowGroupNode>): react.JSX.Element;
532
+
533
+ /**
534
+ * Pure, framework-agnostic grouping operations over `nodes`/`edges` arrays.
535
+ *
536
+ * Every function is a **pure transform** — it never mutates its inputs and
537
+ * returns a fresh `{ nodes, edges }`. This is the testable core; `useFlowGroups`
538
+ * is a thin hook that wires these to a live canvas via `useReactFlow()`.
539
+ */
540
+ /** Node type used for the group container (matches `nodeTypes={{ group: FlowGroupNode }}`). */
541
+ declare const FLOW_GROUP_NODE_TYPE = "group";
542
+ interface GroupNodesOptions {
543
+ /** Id for the new group node. Required so the operation stays deterministic/testable. */
544
+ groupId: string;
545
+ /** Header title for the group. @default "Group" */
546
+ title?: string;
547
+ /** Accent tone for the group header. */
548
+ tone?: FlowGroupTone;
549
+ /** Padding (px) around the child bounds. @default 28 */
550
+ padding?: number;
551
+ }
552
+ interface FlowGroupOperationResult<NodeType extends Node = Node, EdgeType extends Edge = Edge> {
553
+ nodes: NodeType[];
554
+ edges: EdgeType[];
555
+ }
556
+ /** Marker + metadata written onto a proxy edge's `data`. */
557
+ interface FlowGroupProxyEdgeData extends Record<string, unknown> {
558
+ __flowGroupProxy: true;
559
+ /** Id of the collapsed group this proxy edge stands in for. */
560
+ groupId: string;
561
+ }
562
+ /** True if `edge` is a proxy edge synthesized for a collapsed group. */
563
+ declare function isFlowGroupProxyEdge(edge: Edge): boolean;
564
+ /**
565
+ * Wrap `nodeIds` (assumed top-level) in a new `FlowGroupNode` parent.
566
+ *
567
+ * - Computes the child bounds with `getNodesBounds`.
568
+ * - Inserts the group node **before** its children in the array (React Flow
569
+ * requires a parent to precede its children).
570
+ * - Re-parents each child (`parentId`, `extent: "parent"`) and rewrites its
571
+ * position to be **relative to the group origin**.
572
+ */
573
+ declare function groupNodes<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], nodeIds: string[], options: GroupNodesOptions): FlowGroupOperationResult<NodeType, EdgeType>;
574
+ /**
575
+ * Dissolve a group: remove the group node and restore its **direct** children to
576
+ * absolute positions (dropping `parentId`/`extent`). If the group is currently
577
+ * collapsed it is expanded first, so ungrouping a collapsed group is safe.
578
+ */
579
+ declare function ungroup<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], groupId: string): FlowGroupOperationResult<NodeType, EdgeType>;
580
+ /**
581
+ * Collapse a group to an overview chip.
582
+ *
583
+ * - Hides every descendant (including nested groups and their subtrees).
584
+ * - Shrinks the group to a fixed overview size and marks `data.collapsed`.
585
+ * - Re-routes every edge that **crosses the collapse boundary** (exactly one
586
+ * endpoint inside the collapsed subtree) to a **proxy edge** targeting the
587
+ * group node; the original edge is hidden and stashed for restoration.
588
+ * - Edges fully inside or fully outside the subtree are left untouched.
589
+ *
590
+ * The full pre-collapse graph is stashed on the group's `data` so `expandGroup`
591
+ * is an exact inverse. Nested collapse is handled by snapshotting each
592
+ * descendant's exact prior state (a subgroup that was already collapsed stays
593
+ * collapsed on expand — no double-unhide).
594
+ */
595
+ declare function collapseGroup<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], groupId: string): FlowGroupOperationResult<NodeType, EdgeType>;
596
+ /**
597
+ * Expand a collapsed group — the exact inverse of `collapseGroup`. Restores the
598
+ * group node, un-hides the descendant subtree (to each node's exact prior
599
+ * state), removes this group's proxy edges and restores the original edges.
600
+ * No-op if the group isn't collapsed.
601
+ */
602
+ declare function expandGroup<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], groupId: string): FlowGroupOperationResult<NodeType, EdgeType>;
603
+ /** Collapse if expanded, expand if collapsed. */
604
+ declare function toggleGroupCollapsed<NodeType extends Node = Node, EdgeType extends Edge = Edge>(nodes: NodeType[], edges: EdgeType[], groupId: string): FlowGroupOperationResult<NodeType, EdgeType>;
605
+
606
+ /** Options for `groupNodes`/`groupSelection` with an optional generated id. */
607
+ type GroupOptions = Partial<Pick<GroupNodesOptions, "groupId">> & Omit<GroupNodesOptions, "groupId">;
608
+ interface UseFlowGroupsResult {
609
+ /**
610
+ * Wrap the given (top-level) node ids in a new `FlowGroupNode`. Returns the new
611
+ * group id, or `undefined` if nothing was grouped. Pass `options.groupId` for a
612
+ * deterministic id; otherwise one is generated.
613
+ */
614
+ groupNodes: (nodeIds: string[], options?: GroupOptions) => string | undefined;
615
+ /** Group the currently-selected nodes. Returns the new group id (or `undefined`). */
616
+ groupSelection: (options?: GroupOptions) => string | undefined;
617
+ /** Dissolve a group, restoring its children to absolute positions. */
618
+ ungroup: (groupId: string) => void;
619
+ /** Collapse a group to an overview chip (hides subtree, reroutes boundary edges). */
620
+ collapseGroup: (groupId: string) => void;
621
+ /** Expand a collapsed group (exact inverse of collapse). */
622
+ expandGroup: (groupId: string) => void;
623
+ /** Collapse if expanded, expand if collapsed. */
624
+ toggleCollapse: (groupId: string) => void;
625
+ }
626
+ /**
627
+ * Thin hook over the pure grouping operations in `group-operations.ts`. Reads and
628
+ * writes the live graph via `useReactFlow()`; all the logic lives in the pure,
629
+ * unit-tested core. Must be called inside a React Flow context (e.g. from a
630
+ * `<Panel>` child of `<CanvasShell>` or from within a custom node).
631
+ */
632
+ declare function useFlowGroups<NodeType extends Node = Node, EdgeType extends Edge = Edge>(): UseFlowGroupsResult;
633
+
634
+ export { type BrandFlowButtonEdge, type BrandFlowGroupNode, type BrandFlowNode, type BrandFlowPlaceholderNode, CanvasShell, type CanvasShellProps, type ClosestHandles, type EdgeParams, FLOW_ALL_SIDE_HANDLES, FLOW_GROUP_NODE_TYPE, type FloatingEdgeData, type FloatingNodeGeometry, FlowButtonEdge, type FlowButtonEdgeData, FlowEdge, FlowFloatingEdge, FlowGroupNode, type FlowGroupNodeData, type FlowGroupOperationResult, type FlowGroupProxyEdgeData, type FlowGroupTone, type FlowHandleSide, type FlowLayoutDirection, type FlowLayoutOptions, type FlowLayoutResult, FlowMiniMap, type FlowMiniMapProps, FlowNode, type FlowNodeData, type FlowNodeHandles, FlowPlaceholderNode, type FlowPlaceholderNodeData, FlowSmartEdge, type GroupNodesOptions, type GroupOptions, HANDLE_SIDES, type HelperLineRect, HelperLines, type HelperLinesProps, type HelperLinesResult, InspectorPanel, type InspectorPanelProps, type LayoutAlgorithm, type LayoutOptions, type LayoutSpacing, Legend, type LegendItem, type LegendProps, type NodeRect, type UseFlowGroupsResult, type UseFlowLayoutResult, type UseHelperLinesOptions, type UseHelperLinesResult, ZoomControls, type ZoomControlsProps, collapseGroup, expandGroup, getEdgeParams, getHelperLines, groupNodes, handlePoint, isFlowGroupProxyEdge, layoutFlow, layoutGraph, pickClosestHandles, sideToPosition, toggleGroupCollapsed, ungroup, useAutoLayout, useFlowGroups, useFlowLayout, useHelperLines };