@kolosal-ai/rivet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1012 @@
1
+ import { R as Rect, V as Vec2, A as AlignmentGuide, E as EdgeChange, a as RivetEdge, b as NodeChange, c as RivetNode, N as NodeId, d as NodeProps, e as NodeTypes, f as HandleType, H as HandlePosition, S as Size, g as Viewport, h as EdgeTypes, D as DefaultEdgeOptions, i as EdgeRendererFactory, j as SwimlaneGroup, k as SwimlaneMargin, l as SwimlaneHeaderProps, m as SwimlaneLabelProps, n as SwimlaneSizeChange, L as LaneChange, C as Connection, o as RivetSelection, p as EdgeId, q as HandleRecord, r as ReconnectDelegate, P as PendingConnection, s as RivetControls, t as RivetSnapshot, u as EdgeRenderer, v as EdgeRendererOptions, w as EdgeDrawExtras, x as EdgeEnd, y as EdgePathFn } from './types-B8AAJ60T.js';
2
+ export { z as CenterNodeOptions, B as EdgeMarker, F as EdgePath, G as EdgePathParams, I as EdgeStyle, J as FitViewOptions, K as NodeComponent } from './types-B8AAJ60T.js';
3
+ import { ReactNode, CSSProperties, MouseEvent, RefObject } from 'react';
4
+ import { b as AnchorOptions, d as AnchorRecord, f as AnchorRegistrationOptions, R as ResolvedAnchorOptions, e as AnchorRegistration } from './registry-Dkk4ZKt-.js';
5
+ import { ResolvedLane, ViewportElements, ResolvedSwimlaneGroup, ResolvedMargin } from './swimlane/index.js';
6
+
7
+ /**
8
+ * Find the closest edge alignment between the moving rect and any static rect,
9
+ * within `threshold` (world units), independently per axis. Returns the offset
10
+ * to apply to `moving` so its aligned edge lands exactly on the neighbor's, plus
11
+ * the guide line(s) to draw. Pure — the drag loop supplies the rects.
12
+ */
13
+ declare function alignRect(moving: Rect, statics: readonly Rect[], threshold: number): {
14
+ offset: Vec2;
15
+ guides: AlignmentGuide[];
16
+ };
17
+
18
+ /**
19
+ * Apply a batch of {@link NodeChange}s to a nodes array, returning a new array
20
+ * (the input is not mutated). Order is preserved; added nodes are appended.
21
+ *
22
+ * Pair this with the graph's `onNodesChange` to run controlled mode:
23
+ * ```ts
24
+ * const [nodes, setNodes] = useState(initial)
25
+ * <Rivet nodes={nodes} onNodesChange={(c) => setNodes((ns) => applyNodeChanges(c, ns))} />
26
+ * ```
27
+ */
28
+ declare function applyNodeChanges<TData = unknown>(changes: NodeChange<TData>[], nodes: RivetNode<TData>[]): RivetNode<TData>[];
29
+ /**
30
+ * Apply a batch of {@link EdgeChange}s to an edges array, returning a new array.
31
+ * Order is preserved; added edges are appended. See {@link applyNodeChanges}.
32
+ */
33
+ declare function applyEdgeChanges<TData = unknown>(changes: EdgeChange<TData>[], edges: RivetEdge<TData>[]): RivetEdge<TData>[];
34
+
35
+ /** Options for {@link cloneElements}. */
36
+ type CloneOptions = {
37
+ /**
38
+ * World-space offset applied to every top-level clone's position. Children of
39
+ * a copied parent keep their parent-relative position and ride along.
40
+ * Default `{0,0}`.
41
+ */
42
+ offset?: Vec2;
43
+ /** Id generator for the clones. Default `crypto.randomUUID`. */
44
+ makeId?: () => NodeId;
45
+ };
46
+ /** The result of {@link cloneElements}: fresh nodes and their internal edges. */
47
+ type ClonedElements<TNodeData = unknown, TEdgeData = unknown> = {
48
+ nodes: RivetNode<TNodeData>[];
49
+ edges: RivetEdge<TEdgeData>[];
50
+ };
51
+ /**
52
+ * Clone a set of nodes (and the edges wholly between them) with fresh ids —
53
+ * the core of copy/paste and duplicate. Edges are kept only when **both**
54
+ * endpoints are in the set (dangling connections to un-copied nodes are
55
+ * dropped). `parentId` is remapped when the parent is also copied, else cleared
56
+ * so the clone isn't orphaned to a missing parent. Transient flags are stripped;
57
+ * selecting the clones is the caller's job.
58
+ */
59
+ declare function cloneElements<TNodeData = unknown, TEdgeData = unknown>(nodes: RivetNode<TNodeData>[], edges: RivetEdge<TEdgeData>[], options?: CloneOptions): ClonedElements<TNodeData, TEdgeData>;
60
+
61
+ type ControlsProps = {
62
+ /** Show the zoom-in / zoom-out buttons (default true). */
63
+ showZoom?: boolean;
64
+ /** Show the fit-view button (default true). */
65
+ showFitView?: boolean;
66
+ /** Extra buttons appended after the defaults. */
67
+ children?: ReactNode;
68
+ className?: string;
69
+ style?: CSSProperties;
70
+ };
71
+ /** A small zoom / fit-view button panel. Must be a child of `<Rivet>`. */
72
+ declare function Controls({ showZoom, showFitView, children, className, style, }: ControlsProps): React.ReactElement;
73
+
74
+ /** Fallback node used when a node's `type` has no entry in `nodeTypes`. */
75
+ declare function DefaultNode({ data, selected, hovered }: NodeProps): React.ReactElement;
76
+
77
+ /**
78
+ * Built-in container node (`type: "group"`) — the visual basis for sub-flows.
79
+ * A styled region with no content or handles of its own; children point at it
80
+ * via `parentId` (and `extent: "parent"` to stay confined). Give it explicit
81
+ * `width`/`height` — a group has no intrinsic content to measure. Children
82
+ * render above it automatically (z-index follows nesting depth).
83
+ */
84
+ declare function GroupNode({ data, selected, hovered }: NodeProps): React.ReactElement;
85
+ /** The built-in node components, keyed by `type`. Consumer `nodeTypes` win. */
86
+ declare const BUILTIN_NODE_TYPES: NodeTypes;
87
+
88
+ type HandleProps = {
89
+ /**
90
+ * Whether this handle is a connection `source` or `target`. Optional — an
91
+ * untyped handle resolves to `"either"` and can start or receive a
92
+ * connection; a pair is valid when one end can source and the other can
93
+ * target. Set an explicit type to constrain direction.
94
+ */
95
+ type?: HandleType;
96
+ /**
97
+ * Distinguishes multiple handles on a node. Defaults to the resolved
98
+ * `position`, since each side of a node is unique. Set an explicit `id` when a
99
+ * node has several handles sharing one side.
100
+ */
101
+ id?: string;
102
+ position?: HandlePosition;
103
+ className?: string;
104
+ style?: CSSProperties;
105
+ /**
106
+ * Accessible name for the handle. Defaults to naming the handle and its node
107
+ * (e.g. `"right handle of node a"`) so a keyboard user tabbing across the
108
+ * graph can tell same-sided handles on different nodes apart.
109
+ */
110
+ ariaLabel?: string;
111
+ };
112
+ /**
113
+ * A connection point placed inside a node component. It measures its own
114
+ * position within the node (so edges anchor precisely) and starts a
115
+ * drag-to-connect gesture.
116
+ */
117
+ declare function Handle({ type, id, position, className, style, ariaLabel, }: HandleProps): React.ReactElement;
118
+
119
+ type MiniMapProps = {
120
+ width?: number;
121
+ height?: number;
122
+ nodeColor?: string;
123
+ selectedColor?: string;
124
+ /** Fill for the area outside the current viewport. */
125
+ maskColor?: string;
126
+ className?: string;
127
+ style?: CSSProperties;
128
+ };
129
+ /**
130
+ * An overview of the whole graph with a viewport indicator. Redraws on every
131
+ * canvas frame (via the store's frame channel) and recenters on click/drag.
132
+ * Must be a child of `<Rivet>`.
133
+ */
134
+ declare function MiniMap({ width, height, nodeColor, selectedColor, maskColor, className, style, }: MiniMapProps): React.ReactElement;
135
+
136
+ type NodeResizerProps = {
137
+ minWidth?: number;
138
+ minHeight?: number;
139
+ maxWidth?: number;
140
+ maxHeight?: number;
141
+ /** Lock the width/height ratio while resizing from a corner. */
142
+ keepAspectRatio?: boolean;
143
+ /** Handle color. */
144
+ color?: string;
145
+ /** Called on every resize step (live). */
146
+ onResize?: (size: Size, position: Vec2) => void;
147
+ /** Called once when the resize gesture ends. */
148
+ onResizeEnd?: (size: Size, position: Vec2) => void;
149
+ };
150
+ /**
151
+ * Drop inside a node component to make it resizable. Renders eight drag handles
152
+ * that write the node's explicit `width`/`height` (and move its origin for
153
+ * top/left handles) via {@link RivetStore.resizeNode} — so it works with
154
+ * controlled mode (emits dimension changes) and nested nodes alike.
155
+ */
156
+ declare function NodeResizer({ minWidth, minHeight, maxWidth, maxHeight, keepAspectRatio, color, onResize, onResizeEnd, }: NodeResizerProps): React.ReactElement;
157
+
158
+ /**
159
+ * Edge alignment — the library-level policy for when an edge's endpoints
160
+ * re-side to face the peer node.
161
+ *
162
+ * A handle chosen by the user (grabbed or dropped on) is *pinned*: rivet
163
+ * renders the edge exactly there. The alignment policy decides when geometry
164
+ * may override that pin. Under `"on-move"`, a committed position change of an
165
+ * endpoint node re-sides both ends of its edges toward each other — the user
166
+ * visibly changed the layout, so the pin yields. Nothing else triggers it:
167
+ * data replaces (description edits, history restores) and dimension echoes
168
+ * must never move an edge the user didn't touch or rewrite handles an undo
169
+ * just faithfully restored.
170
+ *
171
+ * The store performs the move and the re-side in one internal batch, so
172
+ * "move + realign = one undo step" holds by construction.
173
+ */
174
+
175
+ /**
176
+ * When pinned edge endpoints re-side to face the peer node.
177
+ *
178
+ * - `"manual"` — never; a chosen handle is kept until the user reconnects.
179
+ * - `"on-move"` — on a committed position change of an endpoint node.
180
+ * - `"live"` — as `"on-move"`, plus: while an endpoint node is dragging, the
181
+ * edge's pinned ends *render* as if auto — re-siding per frame to face each
182
+ * other, with hysteresis — and the displayed sides commit on drop, inside
183
+ * the move's history step. Nothing is persisted mid-gesture.
184
+ */
185
+ type EdgeAlignment = "manual" | "on-move" | "live";
186
+ /** The side of `node` that faces `peer`, by dominant center-to-center axis. */
187
+ declare function facingSide(node: Rect, peer: Rect): HandlePosition;
188
+ /**
189
+ * How much the losing axis must dominate before a per-frame resolved side
190
+ * switches axes: with a current horizontal side, the edge flips to top/bottom
191
+ * only once `|dy| > |dx| * ratio` (and symmetrically). `1` disables the band.
192
+ */
193
+ declare const DEFAULT_ALIGNMENT_HYSTERESIS = 1.2;
194
+ /**
195
+ * Per-frame side resolution with hysteresis. Without a `previous` side this is
196
+ * exactly {@link facingSide}; with one, the side keeps its current axis until
197
+ * the other axis dominates by `hysteresis`, so near-diagonal positions don't
198
+ * flicker between sides frame to frame. Within the kept axis the sign always
199
+ * tracks (a left↔right swap at a mostly-horizontal layout is a real crossing,
200
+ * not jitter — jitter near the diagonal is an axis change, which the band
201
+ * absorbs).
202
+ */
203
+ declare function resolveFacingSide(node: Rect, peer: Rect, previous?: HandlePosition, hysteresis?: number): HandlePosition;
204
+
205
+ /** Resolved graph context for a pane right-click, passed to `onPaneContextMenu`. */
206
+ type PaneContextMenuContext = {
207
+ /** The click position in world coordinates (already unprojected). */
208
+ world: Vec2;
209
+ /** The swimlane lane whose body sits under the cursor, or `null`. */
210
+ lane: ResolvedLane | null;
211
+ };
212
+ type RivetProps = {
213
+ /**
214
+ * Nodes to seed the graph with (uncontrolled). Read once on mount to initialize
215
+ * the internal store — later changes to this array are ignored. Use {@link nodes}
216
+ * + {@link onNodesChange} for controlled mode instead. Defaults to `[]`.
217
+ */
218
+ defaultNodes?: RivetNode[];
219
+ /**
220
+ * Edges to seed the graph with (uncontrolled). See {@link defaultNodes}. An edge
221
+ * whose `source` or `target` node is absent is skipped by the renderer. Defaults to `[]`.
222
+ */
223
+ defaultEdges?: RivetEdge[];
224
+ /**
225
+ * Controlled nodes. When provided, the graph mirrors this array — hold the nodes
226
+ * in your own state and feed back the batches from {@link onNodesChange} (apply
227
+ * them with `applyNodeChanges`). Node drags are suppressed from reconciling
228
+ * mid-drag so the graph stays smooth. Takes precedence over {@link defaultNodes}.
229
+ */
230
+ nodes?: RivetNode[];
231
+ /** Controlled edges. See {@link nodes}. Takes precedence over {@link defaultEdges}. */
232
+ edges?: RivetEdge[];
233
+ /** Change batches the graph wants applied to your nodes (controlled mode). */
234
+ onNodesChange?: (changes: NodeChange[]) => void;
235
+ /** Change batches the graph wants applied to your edges (controlled mode). */
236
+ onEdgesChange?: (changes: EdgeChange[]) => void;
237
+ /**
238
+ * Initial pan/zoom. `x`/`y` are the screen-space offset in pixels and `zoom`
239
+ * is the scale factor. Defaults to `{ x: 0, y: 0, zoom: 1 }` (origin, 1:1).
240
+ */
241
+ defaultViewport?: Viewport;
242
+ /**
243
+ * Maps a node's `type` to the React component that renders it. A node whose
244
+ * `type` has no entry here (or no `type` at all) falls back to
245
+ * {@link DefaultNode}. Defaults to `{}`.
246
+ */
247
+ nodeTypes?: NodeTypes;
248
+ /**
249
+ * Registry of custom edge shapes, keyed by `edge.type`. Each is a pure geometry
250
+ * function (see {@link EdgePathFn}) so edges stay canvas/GPU-renderable. Merged
251
+ * over the built-ins `bezier` (default), `smoothstep`, `step`, and `straight`.
252
+ */
253
+ edgeTypes?: EdgeTypes;
254
+ /** Defaults merged into every edge that doesn't set the field (type, style, markers…). */
255
+ defaultEdgeOptions?: DefaultEdgeOptions;
256
+ /** Accessible name for the graph surface (`aria-label` on the pane). Defaults to "Node graph". */
257
+ ariaLabel?: string;
258
+ /**
259
+ * Backend factory for the edge layers — swap Canvas2D for another
260
+ * {@link EdgeRenderer}. Defaults to {@link canvas2DEdgeRenderer}. Pass a stable/memoized
261
+ * reference; a new identity re-creates the renderers.
262
+ */
263
+ renderer?: EdgeRendererFactory;
264
+ /**
265
+ * Lower bound on `viewport.zoom`. All zooming (wheel, controls, `fitView`) is
266
+ * clamped to this. Defaults to `0.2` (zoomed out to 20%).
267
+ */
268
+ minZoom?: number;
269
+ /**
270
+ * Upper bound on `viewport.zoom`. All zooming is clamped to this. Defaults to
271
+ * `2.5` (zoomed in to 250%).
272
+ */
273
+ maxZoom?: number;
274
+ /**
275
+ * Spacing in world units between background grid dots. The grid scales with
276
+ * zoom, so this is measured at `zoom === 1`. Defaults to `24`.
277
+ */
278
+ gridGap?: number;
279
+ /**
280
+ * Snap dragged nodes to a `[x, y]` world-unit grid. A `0` axis is left free.
281
+ * Omit (default) for free positioning. Independent of {@link gridGap} (the
282
+ * visual dot grid) — pass the same value to snap to the dots you see.
283
+ */
284
+ snapGrid?: [number, number];
285
+ /**
286
+ * While dragging a single node, show alignment guides and snap its edges/center
287
+ * to nearby nodes' edges/centers. Defaults to `false`.
288
+ */
289
+ alignmentGuides?: boolean;
290
+ /**
291
+ * Placement policy for anchor chrome — the grab dots and stray perimeter
292
+ * handles rivet renders for registered anchors (see {@link Anchor}). Omitted
293
+ * fields fall back to the defaults.
294
+ */
295
+ anchorOptions?: AnchorOptions;
296
+ /**
297
+ * Which mouse buttons pan when dragging the pane: `true` = left only, `false` =
298
+ * none, or a list of `MouseEvent.button` codes (`0` left, `1` middle, `2` right).
299
+ * e.g. `[1, 2]` pans with middle/right and frees left for selection. Defaults to
300
+ * `true`. Pass a stable reference; a new array identity re-binds the listeners.
301
+ */
302
+ panOnDrag?: boolean | number[];
303
+ /**
304
+ * Draw a selection marquee on a plain left-drag instead of panning. Pair with
305
+ * `panOnDrag={[1, 2]}` so left selects and middle/right pan. A marquee that
306
+ * ends over nodes leaves a persistent selection box around them — drag it to
307
+ * move the whole selection; a click elsewhere or Escape dismisses it.
308
+ * Defaults to `false`.
309
+ */
310
+ selectionOnDrag?: boolean;
311
+ /**
312
+ * Key(s) that force a marquee on a left-drag when held (e.g. `"Shift"`, `"Meta"`,
313
+ * or `["Shift", "Meta"]`). `null` disables. Defaults to `"Shift"`.
314
+ */
315
+ selectionKeyCode?: string | string[] | null;
316
+ /**
317
+ * Key(s) that make a click or marquee **add to** the selection instead of
318
+ * replacing it. Defaults to `["Meta", "Control", "Shift"]`. Use modifier keys
319
+ * (`Shift`/`Meta`/`Control`/`Alt`) — other keys only resolve during a pane drag.
320
+ */
321
+ multiSelectionKeyCode?: string | string[];
322
+ /**
323
+ * Pan the viewport on wheel / two-finger trackpad scroll instead of zooming.
324
+ * A pinch gesture (reported by the browser as ctrl+wheel) still zooms. When
325
+ * `false` (default), scrolling zooms toward the cursor.
326
+ */
327
+ scrollToPan?: boolean;
328
+ /**
329
+ * Wheel/pinch zoom sensitivity — larger zooms faster per scroll delta.
330
+ * Defaults to `0.0015`. Try `~0.003` for faster trackpad pinch zoom.
331
+ */
332
+ zoomSpeed?: number;
333
+ /**
334
+ * Swimlanes drawn behind the graph — a decorative background of labeled lane
335
+ * groups (like planout's swimlanes). Not nodes or edges; just layout regions.
336
+ * Omit for no swimlanes.
337
+ */
338
+ swimlane?: SwimlaneGroup[];
339
+ /**
340
+ * Bind node dragging to the swimlanes: on drop each node snaps fully inside
341
+ * one lane (honoring {@link swimlaneMargin}) and is reassigned to the lane it
342
+ * lands in via {@link RivetNode.laneId}. Defaults to `true` when
343
+ * {@link swimlane} is provided, `false` otherwise. Ignored with no swimlanes.
344
+ */
345
+ clampToSwimlane?: boolean;
346
+ /**
347
+ * Inner clearance kept between a clamped node and its lane's edges. A number
348
+ * applies to all sides; per-side values override it. Defaults to `16`.
349
+ */
350
+ swimlaneMargin?: number | SwimlaneMargin;
351
+ /** Screen height of the sticky group header band, in px. Defaults to `32`. */
352
+ swimlaneHeaderHeight?: number;
353
+ /**
354
+ * Show a resize handle on each lane's bottom edge to change its height.
355
+ * Defaults to `true` when {@link swimlane} is provided.
356
+ */
357
+ swimlaneResizable?: boolean;
358
+ /**
359
+ * Fraction of the last lane's height that must stay visible when scrolled to
360
+ * the bottom — the down-scroll stop. `0.5` (default) keeps half of it in view.
361
+ * Only applies when {@link clampToSwimlane} is on.
362
+ */
363
+ swimlaneBottomReveal?: number;
364
+ /** Render custom content for a group's sticky header (replaces the default). */
365
+ renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode;
366
+ /** Render custom content for a lane's sticky label (replaces the default). */
367
+ renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode;
368
+ /**
369
+ * Called when a lane's height changes — from a resize drag or from auto-fit
370
+ * growing it to contain a node that grew. The graph owns lane heights, so this
371
+ * is a notification (persist it if you like), not a request to update state.
372
+ */
373
+ onSwimlaneSizeChange?: (change: SwimlaneSizeChange) => void;
374
+ /**
375
+ * Right-click on empty canvas (not over a node or swimlane chrome). The second
376
+ * argument carries the resolved graph context — the world position and the lane
377
+ * under the cursor — so you don't have to re-derive them.
378
+ */
379
+ onPaneContextMenu?: (event: MouseEvent, context: PaneContextMenuContext) => void;
380
+ /** Right-click on a lane's sticky label. */
381
+ onSwimlaneHeaderContextMenu?: (laneId: string, event: MouseEvent) => void;
382
+ /** Right-click on a group's sticky header. */
383
+ onSwimlaneGroupHeaderContextMenu?: (groupId: string, event: MouseEvent) => void;
384
+ /** Called when a dragged node is reassigned to a different lane. */
385
+ onLaneChange?: (change: LaneChange) => void;
386
+ /** Veto a proposed connection; return false to reject it. Applies to reconnection too. */
387
+ isValidConnection?: (connection: Connection) => boolean;
388
+ /**
389
+ * Rewrite a validated connection before it commits — normalize handle ids
390
+ * (e.g. redirect an in-content grab handle to its canonical perimeter
391
+ * handle), or force direction conventions. Returning `null` cancels the
392
+ * drop. Applies to new connections and reconnections; `isValidConnection`
393
+ * sees the raw connection, `onConnect`/`onReconnect` see the mapped one.
394
+ */
395
+ mapConnection?: (connection: Connection) => Connection | null;
396
+ /** Called after a valid connection is made (the edge is already added). */
397
+ onConnect?: (connection: Connection) => void;
398
+ /**
399
+ * Allow reconnecting edges by dragging an endpoint onto a new handle. Per-edge
400
+ * `reconnectable` overrides this. Defaults to `false`.
401
+ */
402
+ edgesReconnectable?: boolean;
403
+ /**
404
+ * When edge endpoints re-side to face the peer node. `"manual"` (default)
405
+ * never rewrites a chosen handle; `"on-move"` re-sides both ends of a moved
406
+ * node's edges when the move commits — in the same batch as the move, so one
407
+ * undo restores position and handles together. `"live"` additionally renders
408
+ * a dragging node's edges as if their ends were auto — re-siding per frame
409
+ * to face each other, with hysteresis so near-diagonal positions don't
410
+ * flicker — and commits the displayed sides on drop, inside the move's
411
+ * history step; nothing is persisted mid-gesture. Only committed position
412
+ * changes trigger a rewrite: data replaces and dimension changes never
413
+ * re-side, and a freshly created or reconnected edge keeps exactly the
414
+ * handles the user grabbed or dropped on. Anchor and bare-side ends rewrite
415
+ * their side directly; plain handles re-side to a registered handle of a
416
+ * compatible type on the facing side, or stay pinned when the node has none
417
+ * there. Independent of the policy, an end with *no* side — a missing
418
+ * handle, or an `anchorAutoHandleId` anchor end — always floats: the
419
+ * renderer resolves its facing side per frame and never writes it back.
420
+ */
421
+ edgeAlignment?: EdgeAlignment;
422
+ /** Called when an endpoint drag re-points an edge (the edge is already updated). */
423
+ onReconnect?: (edge: RivetEdge, connection: Connection) => void;
424
+ /** Called when an endpoint reconnection drag starts. */
425
+ onReconnectStart?: (edge: RivetEdge) => void;
426
+ /** Called when an endpoint reconnection drag ends (committed or cancelled). */
427
+ onReconnectEnd?: (edge: RivetEdge) => void;
428
+ /** Called whenever the selected nodes and/or edges change. */
429
+ onSelectionChange?: (selection: RivetSelection) => void;
430
+ /**
431
+ * Called when keyboard focus moves to a node (or leaves the graph — `null`).
432
+ * The same state is available inside `<Rivet>` via {@link useRivetFocusedNode}.
433
+ * Rivet draws no focus ring; use this (or the wrapper's `:focus-visible`) to
434
+ * render your own indicator.
435
+ */
436
+ onFocusChange?: (id: NodeId | null) => void;
437
+ className?: string;
438
+ style?: CSSProperties;
439
+ children?: ReactNode;
440
+ };
441
+ /**
442
+ * The graph surface. Stacks a background grid canvas, an edge canvas, and a DOM
443
+ * node layer — all driven by one shared viewport (see {@link useRivetRuntime}).
444
+ */
445
+ declare function Rivet({ defaultNodes, defaultEdges, nodes: controlledNodes, edges: controlledEdges, onNodesChange, onEdgesChange, defaultViewport, nodeTypes, edgeTypes, defaultEdgeOptions, ariaLabel, renderer, minZoom, maxZoom, gridGap, snapGrid, alignmentGuides, anchorOptions, panOnDrag, selectionOnDrag, selectionKeyCode, multiSelectionKeyCode, scrollToPan, zoomSpeed, swimlane, clampToSwimlane, swimlaneMargin, swimlaneHeaderHeight, swimlaneResizable, swimlaneBottomReveal, renderSwimlaneHeader, renderSwimlaneLabel, onSwimlaneSizeChange, onPaneContextMenu, onSwimlaneHeaderContextMenu, onSwimlaneGroupHeaderContextMenu, onLaneChange, isValidConnection, mapConnection, onConnect, edgesReconnectable, edgeAlignment, onReconnect, onReconnectStart, onReconnectEnd, onSelectionChange, onFocusChange, className, style, children, }: RivetProps): React.ReactElement;
446
+
447
+ /** Composite key for the handle/anchor registries. */
448
+ declare function handleKey(nodeId: NodeId, handleId: string): string;
449
+
450
+ type RivetStoreInit = {
451
+ nodes: RivetNode[];
452
+ edges: RivetEdge[];
453
+ viewport: Viewport;
454
+ };
455
+ type ViewportClamp = {
456
+ minX: number;
457
+ minY: number;
458
+ maxY?: number;
459
+ };
460
+ type ChangeHandlers = {
461
+ nodes?: (changes: NodeChange[]) => void;
462
+ edges?: (changes: EdgeChange[]) => void;
463
+ };
464
+ /**
465
+ * The single source of truth for a graph instance — the public contract. It's an
466
+ * interface so consumers depend on the shape, not the implementation ({@link
467
+ * RivetGraphStore}); pure graph algorithms live in `graph.ts`.
468
+ *
469
+ * Subscriptions are deliberately granular so touching one node never re-renders
470
+ * the others:
471
+ * - **per-node** channel — a `NodeWrapper` subscribes to only its own node, so
472
+ * selecting/moving/measuring node A re-renders A alone.
473
+ * - the `NodeLayer`'s list is driven by the culled `visibleIds` prop, so it
474
+ * re-renders only when the visible set changes, never on node data.
475
+ * - **viewport** and **frame** channels drive the canvas (and the label
476
+ * layer's imperative positioning) without touching React at all.
477
+ */
478
+ type RivetStore = {
479
+ readonly nodes: Map<NodeId, RivetNode>;
480
+ readonly edges: Map<EdgeId, RivetEdge>;
481
+ readonly handles: Map<string, HandleRecord>;
482
+ getNodeVersion: (id: NodeId) => number;
483
+ subscribeNode: (id: NodeId, listener: () => void) => () => void;
484
+ registerNodeElement: (id: NodeId, el: HTMLElement) => void;
485
+ unregisterNodeElement: (id: NodeId, el: HTMLElement) => void;
486
+ getNodeElement: (id: NodeId) => HTMLElement | undefined;
487
+ /**
488
+ * Register the controlled-mode change sinks. When set, node/edge mutations emit
489
+ * {@link NodeChange}/{@link EdgeChange} batches so a consumer can own the state.
490
+ * Emission is suppressed while {@link reconcile} is applying incoming props, so
491
+ * feeding changes back in never loops.
492
+ */
493
+ setChangeHandlers: (handlers: ChangeHandlers) => void;
494
+ /**
495
+ * Sync the store to controlled `nodes`/`edges` props. Adds/removes/updates to
496
+ * match, without re-emitting changes. No-op writes are skipped so unrelated
497
+ * nodes don't re-render.
498
+ */
499
+ reconcile: (nodes: RivetNode[], edges: RivetEdge[]) => void;
500
+ /** Add a node (imperative CRUD). Ignored if the id already exists. */
501
+ addNode: (node: RivetNode) => void;
502
+ /** Replace a node wholesale by id (imperative CRUD). Ignored if absent. */
503
+ replaceNode: (id: NodeId, node: RivetNode) => void;
504
+ getViewport: () => Viewport;
505
+ setViewport: (viewport: Viewport) => void;
506
+ subscribeViewport: (listener: (viewport: Viewport) => void) => () => void;
507
+ /**
508
+ * Bound panning against the swimlane area. `minX`/`minY` pin the top-left: you
509
+ * can't scroll left of `minX` or up above `minY`. `maxY` (optional) pins the
510
+ * bottom: you can't scroll down far enough to push the world-y `maxY` above the
511
+ * pane top. Pass `null` to remove the bound. Applied to every viewport change,
512
+ * including zoom and programmatic sets.
513
+ */
514
+ setViewportClamp: (clamp: ViewportClamp | null) => void;
515
+ subscribeFrame: (listener: () => void) => () => void;
516
+ notifyFrame: () => void;
517
+ /** Move a node. `commit` re-renders just that node; skip it during drag. */
518
+ moveNode: (id: NodeId, position: Vec2, commit: boolean) => void;
519
+ setNodeSize: (id: NodeId, size: Size) => void;
520
+ /**
521
+ * Set a node's explicit `width`/`height` (from {@link NodeResizer}), optionally
522
+ * moving its origin (for top/left resize handles). Re-renders the node every
523
+ * frame so its box and geometry props update live; `commit` only marks the
524
+ * emitted change final (`resizing`/`dragging` false → recordable to history).
525
+ */
526
+ resizeNode: (id: NodeId, size: Size, position?: Vec2, commit?: boolean) => void;
527
+ /** Absolute world position of a node, resolving its {@link RivetNode.parentId} chain. */
528
+ getNodeWorldPosition: (id: NodeId) => Vec2;
529
+ /**
530
+ * World positions for every node (parent chains applied), cached and rebuilt only
531
+ * when node geometry changes — so pan/zoom frames, where nothing moved, reuse it.
532
+ */
533
+ getWorldPositions: () => ReadonlyMap<NodeId, Vec2>;
534
+ /** A node's world-space box (`getNodeWorldPosition` + measured/default size). */
535
+ getNodeRect: (id: NodeId) => Rect;
536
+ /** How many ancestors a node has (0 for a top-level node). Drives z-stacking. */
537
+ getNodeDepth: (id: NodeId) => number;
538
+ /** Ids of every descendant of a node (children, grandchildren, …). */
539
+ getDescendantIds: (id: NodeId) => NodeId[];
540
+ /**
541
+ * Of the given ids, the ones whose ancestor isn't also in the set — the nodes a
542
+ * group move should actually translate (a selected child of a selected parent
543
+ * rides along with the parent, so moving it too would double its travel).
544
+ */
545
+ getMovers: (ids: NodeId[]) => NodeId[];
546
+ /**
547
+ * Reassign a node's swimlane. Returns the previous lane id if it changed (so
548
+ * the caller can fire a lane-change callback), or `false` when unchanged.
549
+ */
550
+ setNodeLane: (id: NodeId, laneId: string | null) => string | null | false;
551
+ /**
552
+ * Set the edge-alignment policy (the `edgeAlignment` prop). Under
553
+ * `"on-move"`, a committed position change re-sides the moved node's edges
554
+ * to face their peers, inside the same emission batch as the move.
555
+ */
556
+ setEdgeAlignment: (mode: EdgeAlignment) => void;
557
+ /** The current edge-alignment policy (the runtime reads it per frame). */
558
+ getEdgeAlignment: () => EdgeAlignment;
559
+ /**
560
+ * Hysteresis memory for per-frame side resolution, keyed by
561
+ * `displayedSideKey(edgeId, end)`. The render loop hands this map to the
562
+ * renderer, which reads each auto-resolved end's last displayed side and
563
+ * writes the new one back; a `"live"` alignment commit re-sides pinned ends
564
+ * to exactly what was displayed, then drops those entries. Never persisted.
565
+ */
566
+ getDisplayedSides: () => Map<string, HandlePosition>;
567
+ /**
568
+ * Nodes currently mid-drag — populated by uncommitted {@link moveNode}
569
+ * frames, emptied on commit (and cleared as a backstop when
570
+ * {@link setNodeDragging} ends the gesture). Under `edgeAlignment: "live"`,
571
+ * edges touching these nodes render both ends as auto.
572
+ */
573
+ getDraggingNodeIds: () => ReadonlySet<NodeId>;
574
+ /** True while a node drag is in progress (pauses swimlane auto-fit). */
575
+ isNodeDragging: () => boolean;
576
+ setNodeDragging: (dragging: boolean) => void;
577
+ /**
578
+ * True while a resize gesture is in progress — set by {@link resizeNode}'s live
579
+ * frames and cleared by its commit. Guards controlled-mode reconciliation the
580
+ * same way {@link isNodeDragging} does: mid-gesture the consumer's props lag
581
+ * the store (live frames may be dropped, and measurement echoes carry `size`
582
+ * but not the explicit `width`/`height`), so reconciling them would stomp the
583
+ * live box back to its pre-gesture value every frame.
584
+ */
585
+ isNodeResizing: () => boolean;
586
+ /**
587
+ * Select node(s). `additive` toggles `id` in the current selection (Shift/Cmd
588
+ * click); otherwise the selection is replaced. Passing `null` clears it.
589
+ */
590
+ selectNode: (id: NodeId | null, additive?: boolean) => void;
591
+ /** Replace (or, if `additive`, extend) the node selection with `ids`. */
592
+ selectNodes: (ids: NodeId[], additive?: boolean) => void;
593
+ /** The currently selected node ids. */
594
+ getSelectedNodes: () => NodeId[];
595
+ /** True if the node is part of the current selection. */
596
+ isNodeSelected: (id: NodeId) => boolean;
597
+ /** The current selection (nodes + edges) as full records. */
598
+ getSelection: () => RivetSelection;
599
+ /** Subscribe to selection changes (nodes and/or edges). */
600
+ subscribeSelection: (listener: (selection: RivetSelection) => void) => () => void;
601
+ /**
602
+ * The persistent selection box left behind by a marquee — a draggable rect
603
+ * over the selected nodes' bounding box. Activated by the marquee's end;
604
+ * deactivated by any other selection change (a node/edge/pane click, a
605
+ * programmatic select, Escape, or deleting the selection).
606
+ */
607
+ setSelectionBoxActive: (active: boolean) => void;
608
+ isSelectionBoxActive: () => boolean;
609
+ /**
610
+ * Version channel for the selection box: bumped when the flag flips and, while
611
+ * active, when the boxed selection's membership or geometry changes — so the
612
+ * box component can re-render its bounding rect from a single subscription.
613
+ */
614
+ getSelectionBoxVersion: () => number;
615
+ subscribeSelectionBox: (listener: () => void) => () => void;
616
+ /** Set (or clear, with `null`) the hovered node. */
617
+ hoverNode: (id: NodeId | null) => void;
618
+ /**
619
+ * The node that currently holds keyboard focus, or `null`. View state, like
620
+ * hover — never recorded to history, never emitted as a change, and stripped
621
+ * from snapshots. Tracked from the node elements' focus/blur by the node
622
+ * layer; set it programmatically to move real DOM focus to a node (the
623
+ * runtime follows this channel, centering the viewport first when the node
624
+ * is culled out of the DOM).
625
+ */
626
+ getFocusedNode: () => NodeId | null;
627
+ /** Focus a node by id, or clear with `null`. Unknown ids are ignored. */
628
+ setFocusedNode: (id: NodeId | null) => void;
629
+ /** Subscribe to focused-node changes. */
630
+ subscribeFocus: (listener: (id: NodeId | null) => void) => () => void;
631
+ selectEdge: (id: EdgeId | null) => void;
632
+ /** The selected edge's id, or null. */
633
+ getSelectedEdge: () => EdgeId | null;
634
+ /**
635
+ * Runtime binding for the reconnect gateway. `<Handle>` consults it on
636
+ * pointer-down so a press that lands on a handle coinciding with a
637
+ * reconnectable edge's endpoint moves that edge instead of starting a new
638
+ * connection. Bound by the runtime (like {@link bindRenderRequester}).
639
+ */
640
+ bindReconnectDelegate: (delegate: ReconnectDelegate | null) => void;
641
+ getReconnectDelegate: () => ReconnectDelegate | null;
642
+ addEdge: (edge: RivetEdge) => void;
643
+ /** Replace an edge wholesale by id (imperative CRUD). Ignored if absent. */
644
+ replaceEdge: (id: EdgeId, edge: RivetEdge) => void;
645
+ /** Re-point an edge's source/target (from an endpoint reconnection drag). */
646
+ reconnectEdge: (id: EdgeId, connection: Connection) => void;
647
+ removeEdge: (id: EdgeId) => void;
648
+ removeNode: (id: NodeId) => void;
649
+ /** Delete the selected node (and its edges) and/or edge. Returns true if any. */
650
+ deleteSelection: () => boolean;
651
+ /** Revert the last recordable change. Returns false when there's nothing to undo. */
652
+ undo: () => boolean;
653
+ /** Re-apply the last undone change. Returns false when there's nothing to redo. */
654
+ redo: () => boolean;
655
+ canUndo: () => boolean;
656
+ canRedo: () => boolean;
657
+ /** Drop all history and reset the baseline to the current graph. */
658
+ clearHistory: () => void;
659
+ /** Subscribe to history changes (drives `canUndo`/`canRedo` reactivity). */
660
+ subscribeHistory: (listener: () => void) => () => void;
661
+ getEdgeLabelAnchors: () => Map<EdgeId, Vec2>;
662
+ setEdgeLabelAnchors: (anchors: Map<EdgeId, Vec2>) => void;
663
+ /** Alignment/snap guides to draw for the current drag (world space). Empty when idle. */
664
+ getAlignmentGuides: () => AlignmentGuide[];
665
+ setAlignmentGuides: (guides: AlignmentGuide[]) => void;
666
+ registerHandle: (record: HandleRecord) => void;
667
+ unregisterHandle: (nodeId: NodeId, handleId: string) => void;
668
+ readonly anchors: Map<string, AnchorRecord>;
669
+ /**
670
+ * Register (or re-bind) an anchor. Idempotent on the `(nodeId, anchorId)`
671
+ * key: calling again with a new element re-attaches it, keeping any
672
+ * last-known geometry until the next successful measurement.
673
+ */
674
+ registerAnchor: (nodeId: NodeId, anchorId: string, element: Element, options?: AnchorRegistrationOptions) => void;
675
+ /**
676
+ * Mark an anchor's element gone without removing the anchor — the stale
677
+ * retention signal. Geometry (and the stray handles rendered from it) is
678
+ * kept so edges stay put while the display is unmounted.
679
+ */
680
+ detachAnchor: (nodeId: NodeId, anchorId: string) => void;
681
+ /** Explicitly remove an anchor and the chrome rendered for it. */
682
+ unregisterAnchor: (nodeId: NodeId, anchorId: string) => void;
683
+ /**
684
+ * Re-measure a node's anchors. Called automatically when the node's box
685
+ * resizes; call it directly after content reflows *without* a resize (an
686
+ * editor transaction, a font load) — rivet can't observe those.
687
+ */
688
+ remeasureAnchors: (nodeId: NodeId) => void;
689
+ /** The node's anchors, in registration order. */
690
+ getNodeAnchors: (nodeId: NodeId) => AnchorRecord[];
691
+ getNodeAnchorsVersion: (nodeId: NodeId) => number;
692
+ subscribeNodeAnchors: (nodeId: NodeId, listener: () => void) => () => void;
693
+ getEdgesVersion: () => number;
694
+ subscribeEdges: (listener: () => void) => () => void;
695
+ getPending: () => PendingConnection | null;
696
+ beginConnection: (pending: PendingConnection) => void;
697
+ updateConnection: (to: Vec2, toPosition?: HandlePosition) => void;
698
+ endConnection: () => void;
699
+ /** Ask the runtime to schedule a canvas frame. */
700
+ requestRender: () => void;
701
+ bindRenderRequester: (fn: () => void) => void;
702
+ };
703
+ /** Create a graph store. The public entry point; the class is an implementation detail. */
704
+ declare function createRivetStore(init: RivetStoreInit): RivetStore;
705
+
706
+ type RivetContextValue = {
707
+ store: RivetStore;
708
+ nodeTypes: NodeTypes;
709
+ paneRef: RefObject<HTMLDivElement | null>;
710
+ /** Imperative viewport controls (zoom, fit-view). */
711
+ controls: RivetControls;
712
+ /** Keys that make a click add to (toggle in) the selection. */
713
+ multiSelectionKeys: string[];
714
+ /**
715
+ * Publish a message to the pane's visually-hidden live region, so screen
716
+ * readers narrate keyboard interactions (grab/move/drop, connect).
717
+ */
718
+ announce: (message: string) => void;
719
+ /** Id of the pane's hidden keyboard-instructions element (`aria-describedby` target). */
720
+ nodeDescriptionId: string;
721
+ /** Defaults merged into every edge — the label layer reads `labelStyle`. */
722
+ defaultEdgeOptions?: DefaultEdgeOptions;
723
+ /** Grid to snap dragged nodes to (`[x, y]` world units), or null for free movement. */
724
+ snapGrid: [number, number] | null;
725
+ /** Whether a single-node drag shows alignment guides and snaps to neighbors. */
726
+ alignmentGuides: boolean;
727
+ /** Resolved placement policy for anchor chrome (dots + stray handles). */
728
+ anchorOptions: ResolvedAnchorOptions;
729
+ /** Snapshot the nodes and lanes currently on screen. Imperative — never subscribes. */
730
+ getViewportElements: () => ViewportElements;
731
+ /** Resolved swimlane groups (header + lanes, world space). Empty when unused. */
732
+ swimlaneGroups: ResolvedSwimlaneGroup[];
733
+ /** Every group's lanes, flattened — the clamp targets. */
734
+ swimlaneLanes: ResolvedLane[];
735
+ /** Whether node drags are bounded to a lane and reassign lane membership. */
736
+ clampToSwimlane: boolean;
737
+ /** Inner clearance kept between a clamped node and its lane edges. */
738
+ swimlaneMargin: ResolvedMargin;
739
+ /** Screen width of the sticky lane-label strip, in px. */
740
+ swimlaneLabelWidth: number;
741
+ /** Screen height of the sticky group header band, in px. */
742
+ swimlaneHeaderSize: number;
743
+ /** Whether lanes show a bottom resize handle. */
744
+ swimlaneResizable: boolean;
745
+ /**
746
+ * Resize a lane. `commit` fires the consumer's `onSwimlaneSizeChange` with the
747
+ * new height; `reason` tags it (`"resize"` for a drag, `"autofit"` for a
748
+ * fit-to-content snap). Defaults to `"resize"`.
749
+ */
750
+ resizeSwimlane: (laneId: string, height: number, commit: boolean, reason?: SwimlaneSizeChange["reason"]) => void;
751
+ /** Consumer override for the sticky group header content. */
752
+ renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode;
753
+ /** Consumer override for the sticky lane label content. */
754
+ renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode;
755
+ /** Called when a dragged node crosses into a different lane. */
756
+ onLaneChange?: (change: LaneChange) => void;
757
+ /** Veto a proposed connection; return false to reject. */
758
+ isValidConnection?: (connection: Connection) => boolean;
759
+ /** Rewrite a validated connection before commit; `null` cancels the drop. */
760
+ mapConnection?: (connection: Connection) => Connection | null;
761
+ /** Called after a valid connection is made. */
762
+ onConnect?: (connection: Connection) => void;
763
+ };
764
+ declare function useRivetContext(): RivetContextValue;
765
+ /** Access the imperative viewport controls from inside `<Rivet>`. */
766
+ declare function useRivetControls(): RivetControls;
767
+ /** Subscribe to the live viewport — re-renders on every pan/zoom. */
768
+ declare function useRivetViewport(): Viewport;
769
+ /** Reactive undo/redo state + actions — re-renders when `canUndo`/`canRedo` flip. */
770
+ declare function useRivetHistory(): {
771
+ canUndo: boolean;
772
+ canRedo: boolean;
773
+ undo: () => boolean;
774
+ redo: () => boolean;
775
+ clearHistory: () => void;
776
+ };
777
+ /**
778
+ * The node holding keyboard focus, or `null` — re-renders as focus roves.
779
+ * Rendering a focus indicator is the consumer's job (rivet is headless); this
780
+ * is the state to drive it from, along with the wrapper's native `:focus-visible`.
781
+ */
782
+ declare function useRivetFocusedNode(): NodeId | null;
783
+
784
+ /**
785
+ * Structured edge ends — the semantic model over the string handle ids that
786
+ * `RivetEdge.sourceHandle`/`targetHandle` persist.
787
+ *
788
+ * An end is `{ nodeId, anchorId?, side? }`:
789
+ * - `anchorId` present — the end binds to a registered {@link Anchor}.
790
+ * - `side` present — the end is *pinned*: the user chose it (grabbed a dot,
791
+ * dropped on a handle) and rivet renders it exactly there until the
792
+ * alignment policy says otherwise.
793
+ * - `side` absent — the end is *auto* (floating): the renderer resolves the
794
+ * facing side per frame from the two node boxes. Nothing is written while a
795
+ * node drags, so live re-anchoring is free and cannot touch history.
796
+ *
797
+ * Strings remain the wire format, so persisted graphs and external schemas
798
+ * keep working; this module is the codec between the two forms:
799
+ *
800
+ * | structured | handle string |
801
+ * |---------------------------------|----------------------|
802
+ * | `{ anchorId, side }` | `inode-<id>-<side>` |
803
+ * | `{ anchorId }` (auto) | `inode-<id>-auto` |
804
+ * | `{ side }` (plain, pinned) | `"<side>"` |
805
+ * | `{}` (plain, auto) | `undefined` |
806
+ *
807
+ * A custom handle id (anything else) stays a plain pinned end whose side lives
808
+ * in the handle registry; `parseEdgeEndpoint` reads it from there when given
809
+ * the registry, and otherwise leaves `side` unset.
810
+ */
811
+
812
+ /** One end of an edge, structurally. See the module doc for the string mapping. */
813
+ type EdgeEndpoint = {
814
+ nodeId: NodeId;
815
+ /** Anchor this end binds to; absent for a plain node end. */
816
+ anchorId?: string;
817
+ /** Pinned side; absent = auto — the renderer resolves the facing side per frame. */
818
+ side?: HandlePosition;
819
+ };
820
+ /** Parse a bare-side handle id (`"left"`, `"top"`, …) — a side-pinned plain end. */
821
+ declare function parseSideHandleId(handleId: string | null | undefined): HandlePosition | null;
822
+ /**
823
+ * The handle string for a structured end — what `sourceHandle`/`targetHandle`
824
+ * stores. `undefined` (a plain auto end) means "no handle": the renderer
825
+ * resolves the side per frame. A plain pinned end that should attach to a
826
+ * specific registered handle keeps using that handle's own id directly; this
827
+ * codec covers the library-owned grammars.
828
+ */
829
+ declare function edgeEndpointHandle(end: EdgeEndpoint): string | undefined;
830
+ /**
831
+ * Parse one end of an edge into the structured form. Library grammars (anchor
832
+ * stray/auto ids, bare sides, absent handles) round-trip exactly; a custom
833
+ * handle id resolves its side through `handles` when provided (the store's
834
+ * registry), and otherwise comes back with `side` unset — which callers must
835
+ * not read as auto without that context.
836
+ */
837
+ declare function parseEdgeEndpoint(nodeId: NodeId, handleId: string | null | undefined, handles?: ReadonlyMap<string, HandleRecord>): EdgeEndpoint;
838
+
839
+ /**
840
+ * Confine a child's parent-relative position so its box stays fully inside the
841
+ * parent (the `extent: "parent"` sub-flow constraint). Pure geometry — the
842
+ * caller supplies both measured sizes.
843
+ */
844
+ declare function clampChildToParent(position: Vec2, childSize: Size, parentSize: Size): Vec2;
845
+ /** Snap a position to the nearest multiple of `[gx, gy]`. A `0` axis is left free. */
846
+ declare function snapToGrid(position: Vec2, grid: readonly [number, number]): Vec2;
847
+ /** The tight bounding rect over a set of rects, or `null` when empty. */
848
+ declare function boundingRect(rects: readonly Rect[]): Rect | null;
849
+ /**
850
+ * True when nothing the consumer controls has changed between two versions of a
851
+ * node — so reconcile can keep the existing one (preserving store-owned transient
852
+ * state like `hovered`). Compares only fields a controlled consumer owns.
853
+ */
854
+ /**
855
+ * A serializable snapshot of the graph. Store-owned geometry (`position`, `size`)
856
+ * is cloned so mutating the snapshot can't reach back into the store, and purely
857
+ * transient node flags (`hovered`, `dragging`) are dropped — you never want to
858
+ * persist "was mid-drag". `data` is copied by reference, matching the
859
+ * controlled-mode contract (the consumer owns it).
860
+ */
861
+ declare function serializeGraph<TNodeData = unknown, TEdgeData = unknown>(nodes: Iterable<RivetNode<TNodeData>>, edges: Iterable<RivetEdge<TEdgeData>>, viewport: Viewport): RivetSnapshot<TNodeData, TEdgeData>;
862
+
863
+ type Updater<T> = T | ((current: T) => T);
864
+ /**
865
+ * Imperative handle to the graph, returned by {@link useRivet}. Parameterize it
866
+ * with your node/edge `data` types — `useRivet<MyNodeData, MyEdgeData>()` — so
867
+ * `getNodes`, `toObject`, etc. hand those types back instead of `unknown`.
868
+ */
869
+ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
870
+ /** Snapshot the nodes and lanes currently on screen. Reads the viewport once — never subscribes. */
871
+ getViewportElements: () => ViewportElements<TNodeData>;
872
+ /** Serializable snapshot of nodes, edges, and viewport. Safe to `JSON.stringify`. */
873
+ toObject: () => RivetSnapshot<TNodeData, TEdgeData>;
874
+ getNodes: () => RivetNode<TNodeData>[];
875
+ getNode: (id: NodeId) => RivetNode<TNodeData> | undefined;
876
+ setNodes: (nodes: Updater<RivetNode<TNodeData>[]>) => void;
877
+ addNodes: (nodes: RivetNode<TNodeData> | RivetNode<TNodeData>[]) => void;
878
+ updateNode: (id: NodeId, patch: Updater<RivetNode<TNodeData>>) => void;
879
+ getEdges: () => RivetEdge<TEdgeData>[];
880
+ getEdge: (id: EdgeId) => RivetEdge<TEdgeData> | undefined;
881
+ setEdges: (edges: Updater<RivetEdge<TEdgeData>[]>) => void;
882
+ addEdges: (edges: RivetEdge<TEdgeData> | RivetEdge<TEdgeData>[]) => void;
883
+ updateEdge: (id: EdgeId, patch: Updater<RivetEdge<TEdgeData>>) => void;
884
+ /** Delete nodes (and their edges) and/or edges by id. */
885
+ deleteElements: (elements: {
886
+ nodes?: NodeId[];
887
+ edges?: EdgeId[];
888
+ }) => void;
889
+ /**
890
+ * Bind an anchor — a connectable point — to a DOM element inside a node.
891
+ * The imperative core behind `<Anchor>`, for consumers whose content DOM is
892
+ * owned by another renderer (a rich-text editor's contentEditable, say).
893
+ * Idempotent on `(nodeId, anchorId)`; the returned registration re-binds a
894
+ * replaced element or unregisters explicitly.
895
+ */
896
+ registerAnchor: (nodeId: NodeId, anchorId: string, element: Element, options?: AnchorRegistrationOptions) => AnchorRegistration;
897
+ /**
898
+ * Explicitly remove an anchor and its chrome. Unmounting `<Anchor>` (or
899
+ * rebinding `null`) only *detaches* — geometry and edges are retained — so
900
+ * call this when the anchored content is gone for good, alongside deleting
901
+ * its edges.
902
+ */
903
+ unregisterAnchor: (nodeId: NodeId, anchorId: string) => void;
904
+ /**
905
+ * Re-measure a node's anchors. Automatic on node resize; needed after a
906
+ * content reflow that keeps the node box (an editor transaction, a font
907
+ * load) — rivet can't observe those.
908
+ */
909
+ remeasureAnchors: (nodeId: NodeId) => void;
910
+ /** Copy nodes (default: the current selection) to the in-memory clipboard. */
911
+ copy: (ids?: NodeId[]) => void;
912
+ /** Copy then delete nodes (default: the current selection). */
913
+ cut: (ids?: NodeId[]) => void;
914
+ /** Paste the clipboard as fresh, selected nodes offset from the originals. No-op when empty. */
915
+ paste: (options?: {
916
+ offset?: Vec2;
917
+ }) => ClonedElements<TNodeData, TEdgeData> | undefined;
918
+ /** Clone nodes (default: the current selection) in place with fresh ids, and select them. */
919
+ duplicate: (ids?: NodeId[]) => ClonedElements<TNodeData, TEdgeData> | undefined;
920
+ /** Revert the last recordable change. Returns false when there's nothing to undo. */
921
+ undo: () => boolean;
922
+ /** Re-apply the last undone change. Returns false when there's nothing to redo. */
923
+ redo: () => boolean;
924
+ canUndo: () => boolean;
925
+ canRedo: () => boolean;
926
+ /** Drop all undo/redo history and reset the baseline to the current graph. */
927
+ clearHistory: () => void;
928
+ };
929
+ /**
930
+ * Imperative access to the graph from any component inside `<Rivet>` — CRUD on
931
+ * nodes/edges plus the viewport controls. Backed by the same store that drives
932
+ * rendering, so mutations are reflected immediately and (in controlled mode) emit
933
+ * changes to `onNodesChange`/`onEdgesChange`.
934
+ *
935
+ * Parameterize with your `data` types to keep them through the API:
936
+ * `const rivet = useRivet<MyNodeData, MyEdgeData>()`.
937
+ */
938
+ declare function useRivet<TNodeData = unknown, TEdgeData = unknown>(): RivetInstance<TNodeData, TEdgeData>;
939
+
940
+ /**
941
+ * The default {@link EdgeRenderer}. Strokes each edge's sampled polyline (built by
942
+ * its {@link EdgePathFn}) on a 2D canvas, with per-edge style, arrowheads, and an
943
+ * animated dash. The geometry it draws comes from {@link buildEdges}, which is
944
+ * backend-neutral.
945
+ */
946
+ declare class Canvas2DEdgeRenderer implements EdgeRenderer {
947
+ private readonly canvas;
948
+ private readonly ctx;
949
+ private dpr;
950
+ private width;
951
+ private height;
952
+ private readonly edgeTypes;
953
+ private readonly defaults;
954
+ /** Screen-space polylines from the last frame, keyed by edge id, for picking. */
955
+ private readonly geometry;
956
+ /** Screen-space endpoints per edge, for endpoint (reconnection) picking. */
957
+ private readonly endpoints;
958
+ /** Screen-space label anchor per edge that has a label. */
959
+ private readonly labels;
960
+ constructor(canvas: HTMLCanvasElement, options?: EdgeRendererOptions);
961
+ resize(width: number, height: number, dpr: number): void;
962
+ /** Screen-space label anchors for the edges drawn last frame. */
963
+ getLabels(): Map<EdgeId, Vec2>;
964
+ /** Screen-space endpoints per edge from the last frame (reconnect affordance). */
965
+ getEndpoints(): ReadonlyMap<EdgeId, {
966
+ source: Vec2;
967
+ target: Vec2;
968
+ }>;
969
+ draw(edges: RivetEdge[], nodes: Map<NodeId, RivetNode>, viewport: Viewport, extras?: EdgeDrawExtras): void;
970
+ pick(x: number, y: number, tolerance?: number): EdgeId | null;
971
+ pickEndpoint(x: number, y: number): {
972
+ edgeId: EdgeId;
973
+ end: EdgeEnd;
974
+ } | null;
975
+ dispose(): void;
976
+ }
977
+ /** The built-in {@link EdgeRendererFactory} — the default backend for `<Rivet>`. */
978
+ declare const canvas2DEdgeRenderer: EdgeRendererFactory;
979
+
980
+ /** A straight line between the two ends. */
981
+ declare const getStraightPath: EdgePathFn;
982
+ /**
983
+ * A cubic bezier that leaves each end perpendicular to its handle's side, so
984
+ * edges curve cleanly out of left/right/top/bottom handles. This is the default.
985
+ */
986
+ declare const getBezierPath: EdgePathFn;
987
+ /** An axis-aligned step path with rounded corners. */
988
+ declare const getSmoothStepPath: EdgePathFn;
989
+ /** An axis-aligned step path with sharp corners. */
990
+ declare const getStepPath: EdgePathFn;
991
+ /** The built-in edge shapes, keyed by `type`. `bezier` is the default. */
992
+ declare const BUILTIN_EDGE_TYPES: Record<string, EdgePathFn>;
993
+
994
+ /** Convert a world-space point to screen-space pixels. */
995
+ declare function worldToScreen(point: Vec2, viewport: Viewport): Vec2;
996
+ /** Convert a screen-space point (e.g. a pointer event) to world space. */
997
+ declare function screenToWorld(point: Vec2, viewport: Viewport): Vec2;
998
+ /**
999
+ * Zoom toward an anchor point (usually the cursor) so the world point under the
1000
+ * anchor stays put. Returns a new viewport; the input is not mutated.
1001
+ */
1002
+ declare function zoomAt(viewport: Viewport, anchor: Vec2, nextZoom: number, minZoom?: number, maxZoom?: number): Viewport;
1003
+ /** The CSS transform string applied to the DOM node layer. */
1004
+ declare function viewportToCss(viewport: Viewport): string;
1005
+ /** The visible world-space rectangle for a viewport of the given pixel size. */
1006
+ declare function visibleWorldRect(viewport: Viewport, width: number, height: number): Rect;
1007
+ /** True if two axis-aligned rectangles overlap (used for node culling). */
1008
+ declare function rectsIntersect(a: Rect, b: Rect): boolean;
1009
+
1010
+ declare const VERSION = "0.0.0";
1011
+
1012
+ export { AlignmentGuide, BUILTIN_EDGE_TYPES, BUILTIN_NODE_TYPES, Canvas2DEdgeRenderer, type CloneOptions, type ClonedElements, Connection, Controls, type ControlsProps, DEFAULT_ALIGNMENT_HYSTERESIS, DefaultEdgeOptions, DefaultNode, type EdgeAlignment, EdgeChange, EdgeDrawExtras, EdgeEnd, type EdgeEndpoint, EdgeId, EdgePathFn, EdgeRenderer, EdgeRendererFactory, EdgeRendererOptions, EdgeTypes, GroupNode, Handle, HandlePosition, type HandleProps, HandleRecord, HandleType, MiniMap, type MiniMapProps, NodeChange, NodeId, NodeProps, NodeResizer, type NodeResizerProps, NodeTypes, type PaneContextMenuContext, PendingConnection, ReconnectDelegate, Rect, Rivet, RivetControls, RivetEdge, type RivetInstance, RivetNode, type RivetProps, RivetSelection, RivetSnapshot, type RivetStore, Size, VERSION, Vec2, Viewport, alignRect, applyEdgeChanges, applyNodeChanges, boundingRect, canvas2DEdgeRenderer, clampChildToParent, cloneElements, createRivetStore, edgeEndpointHandle, facingSide, getBezierPath, getSmoothStepPath, getStepPath, getStraightPath, handleKey, parseEdgeEndpoint, parseSideHandleId, rectsIntersect, resolveFacingSide, screenToWorld, serializeGraph, snapToGrid, useRivet, useRivetContext, useRivetControls, useRivetFocusedNode, useRivetHistory, useRivetViewport, viewportToCss, visibleWorldRect, worldToScreen, zoomAt };