@kolosal-ai/rivet 0.1.0 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -1,7 +1,9 @@
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';
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 ChangeOrigin, h as EdgeId, i as HandleRecord, j as ChangeMeta, k as Viewport, l as NodeGestureEvent, m as NodeLockConflictEvent, n as RivetSelection, o as ReconnectDelegate, C as Connection, P as PendingConnection, p as EdgeTypes, D as DefaultEdgeOptions, q as EdgeRendererFactory, r as SwimlaneGroup, s as SwimlaneMargin, t as SwimlaneHeaderProps, u as SwimlaneLabelProps, v as SwimlaneSizeChange, L as LaneChange, w as RivetControls, x as RivetSnapshot, y as EdgeRenderer, z as EdgeRendererOptions, B as EdgeDrawExtras, F as EdgeEnd, G as EdgePathFn } from './types-CQqnm4yW.js';
2
+ export { I as CenterNodeOptions, J as EdgeMarker, K as EdgePath, M as EdgePathParams, O as EdgeStyle, Q as FitViewOptions, T as NodeComponent } from './types-CQqnm4yW.js';
3
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';
4
+ import { d as AnchorRecord, f as AnchorRegistrationOptions, b as AnchorOptions, R as ResolvedAnchorOptions, e as AnchorRegistration } from './registry-t5ldbviG.js';
5
+ import { h as PresenceRegistry, d as LockRegistry, f as Peer, a as LocalPresence, g as PresenceOptions, C as CanInteractWithLocked } from './use-node-lock-Csg9pUeC.js';
6
+ export { L as LOCK_DEFAULT_REFUSED, b as LockIntent, c as LockInteraction, N as NodeLock, u as useNodeLock, i as useNodeLockAllows } from './use-node-lock-Csg9pUeC.js';
5
7
  import { ResolvedLane, ViewportElements, ResolvedSwimlaneGroup, ResolvedMargin } from './swimlane/index.js';
6
8
 
7
9
  /**
@@ -153,7 +155,7 @@ type NodeResizerProps = {
153
155
  * top/left handles) via {@link RivetStore.resizeNode} — so it works with
154
156
  * controlled mode (emits dimension changes) and nested nodes alike.
155
157
  */
156
- declare function NodeResizer({ minWidth, minHeight, maxWidth, maxHeight, keepAspectRatio, color, onResize, onResizeEnd, }: NodeResizerProps): React.ReactElement;
158
+ declare function NodeResizer({ minWidth, minHeight, maxWidth, maxHeight, keepAspectRatio, color, onResize, onResizeEnd, }: NodeResizerProps): React.ReactElement | null;
157
159
 
158
160
  /**
159
161
  * Edge alignment — the library-level policy for when an edge's endpoints
@@ -201,248 +203,25 @@ declare const DEFAULT_ALIGNMENT_HYSTERESIS = 1.2;
201
203
  * absorbs).
202
204
  */
203
205
  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
206
  /**
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}).
207
+ * {@link resolveFacingSide} restricted to the sides an end can actually reach.
208
+ *
209
+ * A node only offers connection points where its handles are. Letting an auto
210
+ * or live-aligned end resolve freely puts the edge on a bare side — a
211
+ * left/right node gets its edge stuck to the top midpoint, where the consumer
212
+ * put nothing. Passing the sides that carry a compatible handle keeps the edge
213
+ * on a real connection point.
214
+ *
215
+ * An empty `allowed` means "unconstrained" — a node with no registered handles
216
+ * attaches at side midpoints, so every side is equally valid, and anchors place
217
+ * themselves along whichever side faces the peer.
218
+ *
219
+ * When the freely-resolved side isn't reachable, a still-reachable `previous`
220
+ * wins over re-picking: with the peer straight above a left/right node the raw
221
+ * choice hangs on the sign of a near-zero `dx`, which would flicker frame to
222
+ * frame.
444
223
  */
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;
224
+ declare function resolveFacingSideAmong(node: Rect, peer: Rect, allowed: readonly HandlePosition[], previous?: HandlePosition, hysteresis?: number): HandlePosition;
446
225
 
447
226
  /** Composite key for the handle/anchor registries. */
448
227
  declare function handleKey(nodeId: NodeId, handleId: string): string;
@@ -458,8 +237,22 @@ type ViewportClamp = {
458
237
  maxY?: number;
459
238
  };
460
239
  type ChangeHandlers = {
461
- nodes?: (changes: NodeChange[]) => void;
462
- edges?: (changes: EdgeChange[]) => void;
240
+ nodes?: (changes: NodeChange[], meta: ChangeMeta) => void;
241
+ edges?: (changes: EdgeChange[], meta: ChangeMeta) => void;
242
+ };
243
+ /** Gesture lifecycle sinks — see {@link RivetStore.setGestureHandlers}. */
244
+ type GestureHandlers = {
245
+ start?: (event: NodeGestureEvent) => void;
246
+ end?: (event: NodeGestureEvent) => void;
247
+ /** A peer's lock landed on a node this client is already holding. */
248
+ conflict?: (event: NodeLockConflictEvent) => void;
249
+ };
250
+ /** Changes to hand to {@link RivetStore.applyRemote}. */
251
+ type RemoteChanges = {
252
+ nodes?: NodeChange[];
253
+ edges?: EdgeChange[];
254
+ /** Defaults to `"remote"`. Pass a peer or transaction id to route on it. */
255
+ origin?: ChangeOrigin;
463
256
  };
464
257
  /**
465
258
  * The single source of truth for a graph instance — the public contract. It's an
@@ -497,6 +290,18 @@ type RivetStore = {
497
290
  * nodes don't re-render.
498
291
  */
499
292
  reconcile: (nodes: RivetNode[], edges: RivetEdge[]) => void;
293
+ /**
294
+ * Apply a change batch that happened somewhere else — a peer's edit arriving
295
+ * over your transport. The graph updates and repaints immediately, then the
296
+ * batch is re-emitted to the change handlers tagged `"remote"` so a controlled
297
+ * owner applies it through the same path as everything else.
298
+ *
299
+ * Remote batches never create an undo step: undoing a peer's edit isn't undo,
300
+ * and in a shared document it would silently revert work you don't own. The
301
+ * history baseline is refreshed instead, so your next local step still diffs
302
+ * against reality.
303
+ */
304
+ applyRemote: (changes: RemoteChanges) => void;
500
305
  /** Add a node (imperative CRUD). Ignored if the id already exists. */
501
306
  addNode: (node: RivetNode) => void;
502
307
  /** Replace a node wholesale by id (imperative CRUD). Ignored if absent. */
@@ -574,13 +379,54 @@ type RivetStore = {
574
379
  /** True while a node drag is in progress (pauses swimlane auto-fit). */
575
380
  isNodeDragging: () => boolean;
576
381
  setNodeDragging: (dragging: boolean) => void;
382
+ /**
383
+ * Open a gesture on `ids` — a pointer drag, a resize, or a keyboard grab.
384
+ * While it's open those nodes are *held*: this client's geometry for them wins
385
+ * over anything arriving from elsewhere (see {@link isNodeHeld}), and
386
+ * {@link GestureHandlers.start} fires so a consumer can take a lock.
387
+ *
388
+ * `id` is the node the gesture began on; `ids` is everything moving with it
389
+ * (a group drag holds the whole selection). Re-opening an already-held node
390
+ * is a no-op, so a gesture may safely widen.
391
+ */
392
+ beginNodeGesture: (id: NodeId, ids?: NodeId[]) => void;
393
+ /**
394
+ * Close a gesture opened by {@link beginNodeGesture}, releasing its nodes.
395
+ *
396
+ * Normally the gesture calls this itself on drop. Calling it from outside is
397
+ * how a consumer yields a contested node mid-drag: the running gesture sees
398
+ * that it no longer holds its nodes, stops writing to them, and tears itself
399
+ * down without committing.
400
+ */
401
+ endNodeGesture: (id: NodeId, ids?: NodeId[]) => void;
402
+ /**
403
+ * Publish the lock table (the `lockedNodes` prop), and report any node it
404
+ * claims that a local gesture is already holding to
405
+ * {@link GestureHandlers.conflict}. The gesture itself is left running — see
406
+ * {@link NodeLockConflictEvent} for why rivet doesn't pick the loser.
407
+ */
408
+ setLockedNodes: (locks: Record<NodeId, string> | undefined) => void;
409
+ /**
410
+ * True while a local gesture holds this node. Held geometry is exclusively
411
+ * this client's: {@link applyRemote} drops incoming `position`/`dimensions`
412
+ * for it, and {@link reconcile} keeps the live box rather than the prop's.
413
+ */
414
+ isNodeHeld: (id: NodeId) => boolean;
415
+ /** Every node currently under a local gesture. */
416
+ getHeldNodeIds: () => ReadonlySet<NodeId>;
417
+ /**
418
+ * Subscribe to the held set opening or closing. Unlike
419
+ * {@link setGestureHandlers} — a single sink the `onNodeDragStart` /
420
+ * `onNodeDragEnd` props own — this is a broadcast channel, so presence can
421
+ * watch the same gesture the consumer's lock handler is acting on.
422
+ */
423
+ subscribeHeldNodes: (listener: () => void) => () => void;
424
+ /** Register the gesture lifecycle handlers (the lock acquire/release signals). */
425
+ setGestureHandlers: (handlers: GestureHandlers) => void;
577
426
  /**
578
427
  * 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.
428
+ * frames and cleared by its commit. Stomp protection is per-node and lives in
429
+ * {@link isNodeHeld}; this stays a coarse "is a resize happening at all" flag.
584
430
  */
585
431
  isNodeResizing: () => boolean;
586
432
  /**
@@ -665,6 +511,20 @@ type RivetStore = {
665
511
  setAlignmentGuides: (guides: AlignmentGuide[]) => void;
666
512
  registerHandle: (record: HandleRecord) => void;
667
513
  unregisterHandle: (nodeId: NodeId, handleId: string) => void;
514
+ /**
515
+ * Ephemeral peer presence — cursors, selections, and the boxes peers are
516
+ * dragging right now. Never part of the graph, the change stream, or
517
+ * history: presence describes people, not the document. Painted from the
518
+ * render loop; see `rivet/presence`.
519
+ */
520
+ readonly presence: PresenceRegistry;
521
+ /**
522
+ * Nodes peers have claimed, and the policy for what this client may still do
523
+ * to them. Published whole by {@link setLockedNodes}; read by every input
524
+ * path before it acts, and by the render loop to outline the holder. Like
525
+ * presence, never part of the graph, the change stream, or history.
526
+ */
527
+ readonly locks: LockRegistry;
668
528
  readonly anchors: Map<string, AnchorRecord>;
669
529
  /**
670
530
  * Register (or re-bind) an anchor. Idempotent on the `(nodeId, anchorId)`
@@ -703,6 +563,339 @@ type RivetStore = {
703
563
  /** Create a graph store. The public entry point; the class is an implementation detail. */
704
564
  declare function createRivetStore(init: RivetStoreInit): RivetStore;
705
565
 
566
+ /** Resolved graph context for a pane right-click, passed to `onPaneContextMenu`. */
567
+ type PaneContextMenuContext = {
568
+ /** The click position in world coordinates (already unprojected). */
569
+ world: Vec2;
570
+ /** The swimlane lane whose body sits under the cursor, or `null`. */
571
+ lane: ResolvedLane | null;
572
+ };
573
+ type RivetProps = {
574
+ /**
575
+ * Nodes to seed the graph with (uncontrolled). Read once on mount to initialize
576
+ * the internal store — later changes to this array are ignored. Use {@link nodes}
577
+ * + {@link onNodesChange} for controlled mode instead. Defaults to `[]`.
578
+ */
579
+ defaultNodes?: RivetNode[];
580
+ /**
581
+ * Edges to seed the graph with (uncontrolled). See {@link defaultNodes}. An edge
582
+ * whose `source` or `target` node is absent is skipped by the renderer. Defaults to `[]`.
583
+ */
584
+ defaultEdges?: RivetEdge[];
585
+ /**
586
+ * Controlled nodes. When provided, the graph mirrors this array — hold the nodes
587
+ * in your own state and feed back the batches from {@link onNodesChange} (apply
588
+ * them with `applyNodeChanges`). A node under a live gesture keeps its own
589
+ * geometry rather than the array's, so a drag stays smooth while the props
590
+ * lag behind it. Takes precedence over {@link defaultNodes}.
591
+ */
592
+ nodes?: RivetNode[];
593
+ /** Controlled edges. See {@link nodes}. Takes precedence over {@link defaultEdges}. */
594
+ edges?: RivetEdge[];
595
+ /**
596
+ * Change batches the graph wants applied to your nodes (controlled mode).
597
+ *
598
+ * `meta.origin` says where the batch came from: `"local"` for anything this
599
+ * client did, `"remote"` for a batch that arrived through
600
+ * {@link RivetInstance.applyRemote}. Multiplayer consumers apply both and send
601
+ * only the local ones onward — an untagged echo is indistinguishable from a
602
+ * peer's edit and loops straight back out.
603
+ */
604
+ onNodesChange?: (changes: NodeChange[], meta: ChangeMeta) => void;
605
+ /** Change batches the graph wants applied to your edges (controlled mode). See {@link onNodesChange}. */
606
+ onEdgesChange?: (changes: EdgeChange[], meta: ChangeMeta) => void;
607
+ /**
608
+ * Initial pan/zoom. `x`/`y` are the screen-space offset in pixels and `zoom`
609
+ * is the scale factor. Defaults to `{ x: 0, y: 0, zoom: 1 }` (origin, 1:1).
610
+ */
611
+ defaultViewport?: Viewport;
612
+ /**
613
+ * Maps a node's `type` to the React component that renders it. A node whose
614
+ * `type` has no entry here (or no `type` at all) falls back to
615
+ * {@link DefaultNode}. Defaults to `{}`.
616
+ */
617
+ nodeTypes?: NodeTypes;
618
+ /**
619
+ * Registry of custom edge shapes, keyed by `edge.type`. Each is a pure geometry
620
+ * function (see {@link EdgePathFn}) so edges stay canvas/GPU-renderable. Merged
621
+ * over the built-ins `bezier` (default), `smoothstep`, `step`, and `straight`.
622
+ */
623
+ edgeTypes?: EdgeTypes;
624
+ /** Defaults merged into every edge that doesn't set the field (type, style, markers…). */
625
+ defaultEdgeOptions?: DefaultEdgeOptions;
626
+ /** Accessible name for the graph surface (`aria-label` on the pane). Defaults to "Node graph". */
627
+ ariaLabel?: string;
628
+ /**
629
+ * Backend factory for the edge layers — swap Canvas2D for another
630
+ * {@link EdgeRenderer}. Defaults to {@link canvas2DEdgeRenderer}. Pass a stable/memoized
631
+ * reference; a new identity re-creates the renderers.
632
+ */
633
+ renderer?: EdgeRendererFactory;
634
+ /**
635
+ * Lower bound on `viewport.zoom`. All zooming (wheel, controls, `fitView`) is
636
+ * clamped to this. Defaults to `0.2` (zoomed out to 20%).
637
+ */
638
+ minZoom?: number;
639
+ /**
640
+ * Upper bound on `viewport.zoom`. All zooming is clamped to this. Defaults to
641
+ * `2.5` (zoomed in to 250%).
642
+ */
643
+ maxZoom?: number;
644
+ /**
645
+ * Spacing in world units between background grid dots. The grid scales with
646
+ * zoom, so this is measured at `zoom === 1`. Defaults to `24`.
647
+ */
648
+ gridGap?: number;
649
+ /**
650
+ * Snap dragged nodes to a `[x, y]` world-unit grid. A `0` axis is left free.
651
+ * Omit (default) for free positioning. Independent of {@link gridGap} (the
652
+ * visual dot grid) — pass the same value to snap to the dots you see.
653
+ */
654
+ snapGrid?: [number, number];
655
+ /**
656
+ * While dragging a single node, show alignment guides and snap its edges/center
657
+ * to nearby nodes' edges/centers. Defaults to `false`.
658
+ */
659
+ alignmentGuides?: boolean;
660
+ /**
661
+ * Placement policy for anchor chrome — the grab dots and stray perimeter
662
+ * handles rivet renders for registered anchors (see {@link Anchor}). Omitted
663
+ * fields fall back to the defaults.
664
+ */
665
+ anchorOptions?: AnchorOptions;
666
+ /**
667
+ * The other people in this document — the presence roster. Identity (name,
668
+ * colour) plus the slow fields: what each peer has selected, and what they're
669
+ * holding under a gesture. Rivet paints them: a cursor per peer, an outline
670
+ * around what they've selected, and the nodes they're moving drawn where they
671
+ * currently have them.
672
+ *
673
+ * Peer cursors move far faster than React should re-render, so the roster
674
+ * carries only a seed for them — feed live cursors and drag boxes through
675
+ * {@link RivetInstance.setPeerCursor} / `setPeerNodeTransform` instead.
676
+ *
677
+ * Presence is ephemeral: none of it reaches the graph, the change stream, or
678
+ * history. Dropping a peer from the array is how they leave.
679
+ */
680
+ peers?: Peer[];
681
+ /**
682
+ * Called with this client's presence — cursor (in world coordinates),
683
+ * selection, and held nodes — as a whole snapshot, ready to broadcast.
684
+ *
685
+ * Snapshots are idempotent, so a dropped or duplicated one costs nothing and
686
+ * a reconnecting peer is instantly correct. Cursor movement is coalesced to
687
+ * `presenceOptions.throttleMs`; selection and gesture changes are published
688
+ * immediately.
689
+ */
690
+ onLocalPresence?: (presence: LocalPresence) => void;
691
+ /** Presence policy — outbound throttle, and whether rivet paints peer cursors. */
692
+ presenceOptions?: PresenceOptions;
693
+ /**
694
+ * Nodes a peer has claimed, as `nodeId -> peerId`. Locks are policy where
695
+ * {@link peers} is description: rivet paints a locked node in its holder's
696
+ * colour *and* refuses to act on it.
697
+ *
698
+ * By default it refuses selection, dragging, resizing and deletion, and
699
+ * allows connections — a lock claims the node, not the graph around it.
700
+ * {@link canInteractWithLocked} replaces that policy.
701
+ *
702
+ * Lifetime is yours: rivet never takes or releases a lock, it only reads the
703
+ * table. Take one in {@link onNodeDragStart} and drop it in
704
+ * {@link onNodeDragEnd} for drag-scoped locks, or hold through deselection
705
+ * for something stickier. Like presence, none of this reaches the graph, the
706
+ * change stream, or history.
707
+ */
708
+ lockedNodes?: Record<NodeId, string>;
709
+ /**
710
+ * Decide what this client may do to a locked node, per attempt. Return `true`
711
+ * to allow the intent, `false` to refuse it.
712
+ *
713
+ * A handler owns the whole policy — rivet's defaults are replaced, not
714
+ * consulted — so answer every intent you care about:
715
+ *
716
+ * ```tsx
717
+ * // locked nodes stay inspectable, but nothing may change them
718
+ * canInteractWithLocked={({ intent }) => intent === "select" || intent === "connect"}
719
+ * ```
720
+ *
721
+ * Called on the input path (every marquee frame, every mover), so keep it
722
+ * cheap and free of side effects.
723
+ */
724
+ canInteractWithLocked?: CanInteractWithLocked;
725
+ /**
726
+ * Called when a lock for a peer arrives on a node this client is already
727
+ * holding — two clients started before either lock reached the other.
728
+ *
729
+ * rivet reports it and keeps going: the running gesture stays authoritative
730
+ * over its geometry, because only your transport knows which client actually
731
+ * lost the race. To yield, call {@link RivetInstance.releaseNodeGesture} —
732
+ * the gesture stops writing and tears down without committing.
733
+ */
734
+ onNodeLockConflict?: (event: NodeLockConflictEvent) => void;
735
+ /**
736
+ * Which mouse buttons pan when dragging the pane: `true` = left only, `false` =
737
+ * none, or a list of `MouseEvent.button` codes (`0` left, `1` middle, `2` right).
738
+ * e.g. `[1, 2]` pans with middle/right and frees left for selection. Defaults to
739
+ * `true`. Pass a stable reference; a new array identity re-binds the listeners.
740
+ */
741
+ panOnDrag?: boolean | number[];
742
+ /**
743
+ * Draw a selection marquee on a plain left-drag instead of panning. Pair with
744
+ * `panOnDrag={[1, 2]}` so left selects and middle/right pan. A marquee that
745
+ * ends over nodes leaves a persistent selection box around them — drag it to
746
+ * move the whole selection; a click elsewhere or Escape dismisses it.
747
+ * Defaults to `false`.
748
+ */
749
+ selectionOnDrag?: boolean;
750
+ /**
751
+ * Key(s) that force a marquee on a left-drag when held (e.g. `"Shift"`, `"Meta"`,
752
+ * or `["Shift", "Meta"]`). `null` disables. Defaults to `"Shift"`.
753
+ */
754
+ selectionKeyCode?: string | string[] | null;
755
+ /**
756
+ * Key(s) that make a click or marquee **add to** the selection instead of
757
+ * replacing it. Defaults to `["Meta", "Control", "Shift"]`. Use modifier keys
758
+ * (`Shift`/`Meta`/`Control`/`Alt`) — other keys only resolve during a pane drag.
759
+ */
760
+ multiSelectionKeyCode?: string | string[];
761
+ /**
762
+ * Pan the viewport on wheel / two-finger trackpad scroll instead of zooming.
763
+ * A pinch gesture (reported by the browser as ctrl+wheel) still zooms. When
764
+ * `false` (default), scrolling zooms toward the cursor.
765
+ */
766
+ scrollToPan?: boolean;
767
+ /**
768
+ * Wheel/pinch zoom sensitivity — larger zooms faster per scroll delta.
769
+ * Defaults to `0.0015`. Try `~0.003` for faster trackpad pinch zoom.
770
+ */
771
+ zoomSpeed?: number;
772
+ /**
773
+ * Swimlanes drawn behind the graph — a decorative background of labeled lane
774
+ * groups (like planout's swimlanes). Not nodes or edges; just layout regions.
775
+ * Omit for no swimlanes.
776
+ */
777
+ swimlane?: SwimlaneGroup[];
778
+ /**
779
+ * Bind node dragging to the swimlanes: on drop each node snaps fully inside
780
+ * one lane (honoring {@link swimlaneMargin}) and is reassigned to the lane it
781
+ * lands in via {@link RivetNode.laneId}. Defaults to `true` when
782
+ * {@link swimlane} is provided, `false` otherwise. Ignored with no swimlanes.
783
+ */
784
+ clampToSwimlane?: boolean;
785
+ /**
786
+ * Inner clearance kept between a clamped node and its lane's edges. A number
787
+ * applies to all sides; per-side values override it. Defaults to `16`.
788
+ */
789
+ swimlaneMargin?: number | SwimlaneMargin;
790
+ /** Screen height of the sticky group header band, in px. Defaults to `32`. */
791
+ swimlaneHeaderHeight?: number;
792
+ /**
793
+ * Show a resize handle on each lane's bottom edge to change its height.
794
+ * Defaults to `true` when {@link swimlane} is provided.
795
+ */
796
+ swimlaneResizable?: boolean;
797
+ /**
798
+ * Fraction of the last lane's height that must stay visible when scrolled to
799
+ * the bottom — the down-scroll stop. `0.5` (default) keeps half of it in view.
800
+ * Only applies when {@link clampToSwimlane} is on.
801
+ */
802
+ swimlaneBottomReveal?: number;
803
+ /** Render custom content for a group's sticky header (replaces the default). */
804
+ renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode;
805
+ /** Render custom content for a lane's sticky label (replaces the default). */
806
+ renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode;
807
+ /**
808
+ * Called when a lane's height changes — from a resize drag or from auto-fit
809
+ * growing it to contain a node that grew. The graph owns lane heights, so this
810
+ * is a notification (persist it if you like), not a request to update state.
811
+ */
812
+ onSwimlaneSizeChange?: (change: SwimlaneSizeChange) => void;
813
+ /**
814
+ * Right-click on empty canvas (not over a node or swimlane chrome). The second
815
+ * argument carries the resolved graph context — the world position and the lane
816
+ * under the cursor — so you don't have to re-derive them.
817
+ */
818
+ onPaneContextMenu?: (event: MouseEvent, context: PaneContextMenuContext) => void;
819
+ /** Right-click on a lane's sticky label. */
820
+ onSwimlaneHeaderContextMenu?: (laneId: string, event: MouseEvent) => void;
821
+ /** Right-click on a group's sticky header. */
822
+ onSwimlaneGroupHeaderContextMenu?: (groupId: string, event: MouseEvent) => void;
823
+ /** Called when a dragged node is reassigned to a different lane. */
824
+ onLaneChange?: (change: LaneChange) => void;
825
+ /** Veto a proposed connection; return false to reject it. Applies to reconnection too. */
826
+ isValidConnection?: (connection: Connection) => boolean;
827
+ /**
828
+ * Rewrite a validated connection before it commits — normalize handle ids
829
+ * (e.g. redirect an in-content grab handle to its canonical perimeter
830
+ * handle), or force direction conventions. Returning `null` cancels the
831
+ * drop. Applies to new connections and reconnections; `isValidConnection`
832
+ * sees the raw connection, `onConnect`/`onReconnect` see the mapped one.
833
+ */
834
+ mapConnection?: (connection: Connection) => Connection | null;
835
+ /** Called after a valid connection is made (the edge is already added). */
836
+ onConnect?: (connection: Connection) => void;
837
+ /**
838
+ * Allow reconnecting edges by dragging an endpoint onto a new handle. Per-edge
839
+ * `reconnectable` overrides this. Defaults to `false`.
840
+ */
841
+ edgesReconnectable?: boolean;
842
+ /**
843
+ * When edge endpoints re-side to face the peer node. `"manual"` (default)
844
+ * never rewrites a chosen handle; `"on-move"` re-sides both ends of a moved
845
+ * node's edges when the move commits — in the same batch as the move, so one
846
+ * undo restores position and handles together. `"live"` additionally renders
847
+ * a dragging node's edges as if their ends were auto — re-siding per frame
848
+ * to face each other, with hysteresis so near-diagonal positions don't
849
+ * flicker — and commits the displayed sides on drop, inside the move's
850
+ * history step; nothing is persisted mid-gesture. Only committed position
851
+ * changes trigger a rewrite: data replaces and dimension changes never
852
+ * re-side, and a freshly created or reconnected edge keeps exactly the
853
+ * handles the user grabbed or dropped on. Anchor and bare-side ends rewrite
854
+ * their side directly; plain handles re-side to a registered handle of a
855
+ * compatible type on the facing side, or stay pinned when the node has none
856
+ * there. Independent of the policy, an end with *no* side — a missing
857
+ * handle, or an `anchorAutoHandleId` anchor end — always floats: the
858
+ * renderer resolves its facing side per frame and never writes it back.
859
+ */
860
+ edgeAlignment?: EdgeAlignment;
861
+ /** Called when an endpoint drag re-points an edge (the edge is already updated). */
862
+ onReconnect?: (edge: RivetEdge, connection: Connection) => void;
863
+ /** Called when an endpoint reconnection drag starts. */
864
+ onReconnectStart?: (edge: RivetEdge) => void;
865
+ /** Called when an endpoint reconnection drag ends (committed or cancelled). */
866
+ onReconnectEnd?: (edge: RivetEdge) => void;
867
+ /**
868
+ * Called when a local gesture takes hold of one or more nodes — a pointer
869
+ * drag, a resize, or a keyboard grab. While it's open, those nodes' geometry
870
+ * belongs to this client: remote `position`/`dimensions` writes for them are
871
+ * dropped, and controlled props can't stomp the live box.
872
+ *
873
+ * In a shared document this is where you acquire a lock. It's paired with
874
+ * {@link onNodeDragEnd} — every start gets exactly one end, including a
875
+ * cancelled keyboard grab.
876
+ */
877
+ onNodeDragStart?: (event: NodeGestureEvent) => void;
878
+ /** Called when a gesture releases its nodes. See {@link onNodeDragStart}. */
879
+ onNodeDragEnd?: (event: NodeGestureEvent) => void;
880
+ /** Called whenever the selected nodes and/or edges change. */
881
+ onSelectionChange?: (selection: RivetSelection) => void;
882
+ /**
883
+ * Called when keyboard focus moves to a node (or leaves the graph — `null`).
884
+ * The same state is available inside `<Rivet>` via {@link useRivetFocusedNode}.
885
+ * Rivet draws no focus ring; use this (or the wrapper's `:focus-visible`) to
886
+ * render your own indicator.
887
+ */
888
+ onFocusChange?: (id: NodeId | null) => void;
889
+ className?: string;
890
+ style?: CSSProperties;
891
+ children?: ReactNode;
892
+ };
893
+ /**
894
+ * The graph surface. Stacks a background grid canvas, an edge canvas, and a DOM
895
+ * node layer — all driven by one shared viewport (see {@link useRivetRuntime}).
896
+ */
897
+ declare function Rivet({ defaultNodes, defaultEdges, nodes: controlledNodes, edges: controlledEdges, onNodesChange, onEdgesChange, defaultViewport, nodeTypes, edgeTypes, defaultEdgeOptions, ariaLabel, renderer, minZoom, maxZoom, gridGap, snapGrid, alignmentGuides, anchorOptions, peers, onLocalPresence, presenceOptions, lockedNodes, canInteractWithLocked, onNodeLockConflict, 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, onNodeDragStart, onNodeDragEnd, onSelectionChange, onFocusChange, className, style, children, }: RivetProps): React.ReactElement;
898
+
706
899
  type RivetContextValue = {
707
900
  store: RivetStore;
708
901
  nodeTypes: NodeTypes;
@@ -886,6 +1079,25 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
886
1079
  nodes?: NodeId[];
887
1080
  edges?: EdgeId[];
888
1081
  }) => void;
1082
+ /**
1083
+ * Apply a change batch that already happened elsewhere — a peer's edit off
1084
+ * your transport. The graph updates and repaints, then re-emits the batch to
1085
+ * `onNodesChange`/`onEdgesChange` tagged `"remote"`, so a controlled owner
1086
+ * keeps its state in step through the one path it already has.
1087
+ *
1088
+ * Remote batches never create an undo step; in a shared document, undoing a
1089
+ * peer's edit would silently revert work you don't own.
1090
+ *
1091
+ * ```ts
1092
+ * connection.onNodeMoved((row) => {
1093
+ * if (row.caller === me) return // your own echo — already applied
1094
+ * rivet.applyRemote({
1095
+ * nodes: [{ type: "position", id: row.id, position: row.position }],
1096
+ * })
1097
+ * })
1098
+ * ```
1099
+ */
1100
+ applyRemote: (changes: RemoteChanges) => void;
889
1101
  /**
890
1102
  * Bind an anchor — a connectable point — to a DOM element inside a node.
891
1103
  * The imperative core behind `<Anchor>`, for consumers whose content DOM is
@@ -907,6 +1119,40 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
907
1119
  * load) — rivet can't observe those.
908
1120
  */
909
1121
  remeasureAnchors: (nodeId: NodeId) => void;
1122
+ /**
1123
+ * Move a peer's cursor, in **world** coordinates (peers pan and zoom
1124
+ * independently, so a screen point means nothing to them). `null` parks it.
1125
+ *
1126
+ * The presence hot path: it paints on the next frame without going through
1127
+ * React, so call it as fast as your channel delivers. A peer the `peers`
1128
+ * roster hasn't mentioned yet still gets a cursor — a frame that beats the
1129
+ * join event shouldn't blink.
1130
+ */
1131
+ setPeerCursor: (peerId: string, point: Vec2 | null) => void;
1132
+ /**
1133
+ * Show a node where a peer currently has it, in world space — their drag,
1134
+ * mid-flight. The node renders, hit-tests and wires at that box until their
1135
+ * change lands through {@link applyRemote}; the graph itself is untouched.
1136
+ * Pass `null` on their drop.
1137
+ */
1138
+ setPeerNodeTransform: (peerId: string, nodeId: NodeId, rect: Rect | null) => void;
1139
+ /** Forget a peer entirely — cursor, held nodes and all. For a disconnect. */
1140
+ removePeer: (peerId: string) => void;
1141
+ /**
1142
+ * Give up a gesture's hold on its nodes without committing — the yield lever
1143
+ * for `onNodeLockConflict`, when your transport says this client lost the
1144
+ * race for a node it's already dragging.
1145
+ *
1146
+ * The running gesture notices on its next frame, stops writing, and tears
1147
+ * itself down; the nodes stay where they are and the holder's positions land
1148
+ * normally from then on. `onNodeDragEnd` fires as it would on a drop, so a
1149
+ * consumer's own lock release runs unchanged. Pass the conflict event's
1150
+ * `ids` to release a whole group.
1151
+ *
1152
+ * Doing nothing is also a valid answer: without this call the gesture keeps
1153
+ * its nodes until the pointer lifts.
1154
+ */
1155
+ releaseNodeGesture: (id: NodeId, ids?: NodeId[]) => void;
910
1156
  /** Copy nodes (default: the current selection) to the in-memory clipboard. */
911
1157
  copy: (ids?: NodeId[]) => void;
912
1158
  /** Copy then delete nodes (default: the current selection). */
@@ -1009,4 +1255,4 @@ declare function rectsIntersect(a: Rect, b: Rect): boolean;
1009
1255
 
1010
1256
  declare const VERSION = "0.0.0";
1011
1257
 
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 };
1258
+ export { AlignmentGuide, BUILTIN_EDGE_TYPES, BUILTIN_NODE_TYPES, CanInteractWithLocked, Canvas2DEdgeRenderer, ChangeMeta, ChangeOrigin, 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, LockRegistry, MiniMap, type MiniMapProps, NodeChange, NodeGestureEvent, NodeId, NodeLockConflictEvent, NodeProps, NodeResizer, type NodeResizerProps, NodeTypes, type PaneContextMenuContext, PendingConnection, ReconnectDelegate, Rect, type RemoteChanges, 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, resolveFacingSideAmong, screenToWorld, serializeGraph, snapToGrid, useRivet, useRivetContext, useRivetControls, useRivetFocusedNode, useRivetHistory, useRivetViewport, viewportToCss, visibleWorldRect, worldToScreen, zoomAt };