@kolosal-ai/rivet 0.1.1 → 0.3.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, b as RivetEdge, a 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-C9Vn1rzy.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-C9Vn1rzy.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-CCBgd7FN.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-DH0dB1yA.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-DH0dB1yA.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
@@ -221,248 +223,6 @@ declare function resolveFacingSide(node: Rect, peer: Rect, previous?: HandlePosi
221
223
  */
222
224
  declare function resolveFacingSideAmong(node: Rect, peer: Rect, allowed: readonly HandlePosition[], previous?: HandlePosition, hysteresis?: number): HandlePosition;
223
225
 
224
- /** Resolved graph context for a pane right-click, passed to `onPaneContextMenu`. */
225
- type PaneContextMenuContext = {
226
- /** The click position in world coordinates (already unprojected). */
227
- world: Vec2;
228
- /** The swimlane lane whose body sits under the cursor, or `null`. */
229
- lane: ResolvedLane | null;
230
- };
231
- type RivetProps = {
232
- /**
233
- * Nodes to seed the graph with (uncontrolled). Read once on mount to initialize
234
- * the internal store — later changes to this array are ignored. Use {@link nodes}
235
- * + {@link onNodesChange} for controlled mode instead. Defaults to `[]`.
236
- */
237
- defaultNodes?: RivetNode[];
238
- /**
239
- * Edges to seed the graph with (uncontrolled). See {@link defaultNodes}. An edge
240
- * whose `source` or `target` node is absent is skipped by the renderer. Defaults to `[]`.
241
- */
242
- defaultEdges?: RivetEdge[];
243
- /**
244
- * Controlled nodes. When provided, the graph mirrors this array — hold the nodes
245
- * in your own state and feed back the batches from {@link onNodesChange} (apply
246
- * them with `applyNodeChanges`). Node drags are suppressed from reconciling
247
- * mid-drag so the graph stays smooth. Takes precedence over {@link defaultNodes}.
248
- */
249
- nodes?: RivetNode[];
250
- /** Controlled edges. See {@link nodes}. Takes precedence over {@link defaultEdges}. */
251
- edges?: RivetEdge[];
252
- /** Change batches the graph wants applied to your nodes (controlled mode). */
253
- onNodesChange?: (changes: NodeChange[]) => void;
254
- /** Change batches the graph wants applied to your edges (controlled mode). */
255
- onEdgesChange?: (changes: EdgeChange[]) => void;
256
- /**
257
- * Initial pan/zoom. `x`/`y` are the screen-space offset in pixels and `zoom`
258
- * is the scale factor. Defaults to `{ x: 0, y: 0, zoom: 1 }` (origin, 1:1).
259
- */
260
- defaultViewport?: Viewport;
261
- /**
262
- * Maps a node's `type` to the React component that renders it. A node whose
263
- * `type` has no entry here (or no `type` at all) falls back to
264
- * {@link DefaultNode}. Defaults to `{}`.
265
- */
266
- nodeTypes?: NodeTypes;
267
- /**
268
- * Registry of custom edge shapes, keyed by `edge.type`. Each is a pure geometry
269
- * function (see {@link EdgePathFn}) so edges stay canvas/GPU-renderable. Merged
270
- * over the built-ins `bezier` (default), `smoothstep`, `step`, and `straight`.
271
- */
272
- edgeTypes?: EdgeTypes;
273
- /** Defaults merged into every edge that doesn't set the field (type, style, markers…). */
274
- defaultEdgeOptions?: DefaultEdgeOptions;
275
- /** Accessible name for the graph surface (`aria-label` on the pane). Defaults to "Node graph". */
276
- ariaLabel?: string;
277
- /**
278
- * Backend factory for the edge layers — swap Canvas2D for another
279
- * {@link EdgeRenderer}. Defaults to {@link canvas2DEdgeRenderer}. Pass a stable/memoized
280
- * reference; a new identity re-creates the renderers.
281
- */
282
- renderer?: EdgeRendererFactory;
283
- /**
284
- * Lower bound on `viewport.zoom`. All zooming (wheel, controls, `fitView`) is
285
- * clamped to this. Defaults to `0.2` (zoomed out to 20%).
286
- */
287
- minZoom?: number;
288
- /**
289
- * Upper bound on `viewport.zoom`. All zooming is clamped to this. Defaults to
290
- * `2.5` (zoomed in to 250%).
291
- */
292
- maxZoom?: number;
293
- /**
294
- * Spacing in world units between background grid dots. The grid scales with
295
- * zoom, so this is measured at `zoom === 1`. Defaults to `24`.
296
- */
297
- gridGap?: number;
298
- /**
299
- * Snap dragged nodes to a `[x, y]` world-unit grid. A `0` axis is left free.
300
- * Omit (default) for free positioning. Independent of {@link gridGap} (the
301
- * visual dot grid) — pass the same value to snap to the dots you see.
302
- */
303
- snapGrid?: [number, number];
304
- /**
305
- * While dragging a single node, show alignment guides and snap its edges/center
306
- * to nearby nodes' edges/centers. Defaults to `false`.
307
- */
308
- alignmentGuides?: boolean;
309
- /**
310
- * Placement policy for anchor chrome — the grab dots and stray perimeter
311
- * handles rivet renders for registered anchors (see {@link Anchor}). Omitted
312
- * fields fall back to the defaults.
313
- */
314
- anchorOptions?: AnchorOptions;
315
- /**
316
- * Which mouse buttons pan when dragging the pane: `true` = left only, `false` =
317
- * none, or a list of `MouseEvent.button` codes (`0` left, `1` middle, `2` right).
318
- * e.g. `[1, 2]` pans with middle/right and frees left for selection. Defaults to
319
- * `true`. Pass a stable reference; a new array identity re-binds the listeners.
320
- */
321
- panOnDrag?: boolean | number[];
322
- /**
323
- * Draw a selection marquee on a plain left-drag instead of panning. Pair with
324
- * `panOnDrag={[1, 2]}` so left selects and middle/right pan. A marquee that
325
- * ends over nodes leaves a persistent selection box around them — drag it to
326
- * move the whole selection; a click elsewhere or Escape dismisses it.
327
- * Defaults to `false`.
328
- */
329
- selectionOnDrag?: boolean;
330
- /**
331
- * Key(s) that force a marquee on a left-drag when held (e.g. `"Shift"`, `"Meta"`,
332
- * or `["Shift", "Meta"]`). `null` disables. Defaults to `"Shift"`.
333
- */
334
- selectionKeyCode?: string | string[] | null;
335
- /**
336
- * Key(s) that make a click or marquee **add to** the selection instead of
337
- * replacing it. Defaults to `["Meta", "Control", "Shift"]`. Use modifier keys
338
- * (`Shift`/`Meta`/`Control`/`Alt`) — other keys only resolve during a pane drag.
339
- */
340
- multiSelectionKeyCode?: string | string[];
341
- /**
342
- * Pan the viewport on wheel / two-finger trackpad scroll instead of zooming.
343
- * A pinch gesture (reported by the browser as ctrl+wheel) still zooms. When
344
- * `false` (default), scrolling zooms toward the cursor.
345
- */
346
- scrollToPan?: boolean;
347
- /**
348
- * Wheel/pinch zoom sensitivity — larger zooms faster per scroll delta.
349
- * Defaults to `0.0015`. Try `~0.003` for faster trackpad pinch zoom.
350
- */
351
- zoomSpeed?: number;
352
- /**
353
- * Swimlanes drawn behind the graph — a decorative background of labeled lane
354
- * groups (like planout's swimlanes). Not nodes or edges; just layout regions.
355
- * Omit for no swimlanes.
356
- */
357
- swimlane?: SwimlaneGroup[];
358
- /**
359
- * Bind node dragging to the swimlanes: on drop each node snaps fully inside
360
- * one lane (honoring {@link swimlaneMargin}) and is reassigned to the lane it
361
- * lands in via {@link RivetNode.laneId}. Defaults to `true` when
362
- * {@link swimlane} is provided, `false` otherwise. Ignored with no swimlanes.
363
- */
364
- clampToSwimlane?: boolean;
365
- /**
366
- * Inner clearance kept between a clamped node and its lane's edges. A number
367
- * applies to all sides; per-side values override it. Defaults to `16`.
368
- */
369
- swimlaneMargin?: number | SwimlaneMargin;
370
- /** Screen height of the sticky group header band, in px. Defaults to `32`. */
371
- swimlaneHeaderHeight?: number;
372
- /**
373
- * Show a resize handle on each lane's bottom edge to change its height.
374
- * Defaults to `true` when {@link swimlane} is provided.
375
- */
376
- swimlaneResizable?: boolean;
377
- /**
378
- * Fraction of the last lane's height that must stay visible when scrolled to
379
- * the bottom — the down-scroll stop. `0.5` (default) keeps half of it in view.
380
- * Only applies when {@link clampToSwimlane} is on.
381
- */
382
- swimlaneBottomReveal?: number;
383
- /** Render custom content for a group's sticky header (replaces the default). */
384
- renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode;
385
- /** Render custom content for a lane's sticky label (replaces the default). */
386
- renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode;
387
- /**
388
- * Called when a lane's height changes — from a resize drag or from auto-fit
389
- * growing it to contain a node that grew. The graph owns lane heights, so this
390
- * is a notification (persist it if you like), not a request to update state.
391
- */
392
- onSwimlaneSizeChange?: (change: SwimlaneSizeChange) => void;
393
- /**
394
- * Right-click on empty canvas (not over a node or swimlane chrome). The second
395
- * argument carries the resolved graph context — the world position and the lane
396
- * under the cursor — so you don't have to re-derive them.
397
- */
398
- onPaneContextMenu?: (event: MouseEvent, context: PaneContextMenuContext) => void;
399
- /** Right-click on a lane's sticky label. */
400
- onSwimlaneHeaderContextMenu?: (laneId: string, event: MouseEvent) => void;
401
- /** Right-click on a group's sticky header. */
402
- onSwimlaneGroupHeaderContextMenu?: (groupId: string, event: MouseEvent) => void;
403
- /** Called when a dragged node is reassigned to a different lane. */
404
- onLaneChange?: (change: LaneChange) => void;
405
- /** Veto a proposed connection; return false to reject it. Applies to reconnection too. */
406
- isValidConnection?: (connection: Connection) => boolean;
407
- /**
408
- * Rewrite a validated connection before it commits — normalize handle ids
409
- * (e.g. redirect an in-content grab handle to its canonical perimeter
410
- * handle), or force direction conventions. Returning `null` cancels the
411
- * drop. Applies to new connections and reconnections; `isValidConnection`
412
- * sees the raw connection, `onConnect`/`onReconnect` see the mapped one.
413
- */
414
- mapConnection?: (connection: Connection) => Connection | null;
415
- /** Called after a valid connection is made (the edge is already added). */
416
- onConnect?: (connection: Connection) => void;
417
- /**
418
- * Allow reconnecting edges by dragging an endpoint onto a new handle. Per-edge
419
- * `reconnectable` overrides this. Defaults to `false`.
420
- */
421
- edgesReconnectable?: boolean;
422
- /**
423
- * When edge endpoints re-side to face the peer node. `"manual"` (default)
424
- * never rewrites a chosen handle; `"on-move"` re-sides both ends of a moved
425
- * node's edges when the move commits — in the same batch as the move, so one
426
- * undo restores position and handles together. `"live"` additionally renders
427
- * a dragging node's edges as if their ends were auto — re-siding per frame
428
- * to face each other, with hysteresis so near-diagonal positions don't
429
- * flicker — and commits the displayed sides on drop, inside the move's
430
- * history step; nothing is persisted mid-gesture. Only committed position
431
- * changes trigger a rewrite: data replaces and dimension changes never
432
- * re-side, and a freshly created or reconnected edge keeps exactly the
433
- * handles the user grabbed or dropped on. Anchor and bare-side ends rewrite
434
- * their side directly; plain handles re-side to a registered handle of a
435
- * compatible type on the facing side, or stay pinned when the node has none
436
- * there. Independent of the policy, an end with *no* side — a missing
437
- * handle, or an `anchorAutoHandleId` anchor end — always floats: the
438
- * renderer resolves its facing side per frame and never writes it back.
439
- */
440
- edgeAlignment?: EdgeAlignment;
441
- /** Called when an endpoint drag re-points an edge (the edge is already updated). */
442
- onReconnect?: (edge: RivetEdge, connection: Connection) => void;
443
- /** Called when an endpoint reconnection drag starts. */
444
- onReconnectStart?: (edge: RivetEdge) => void;
445
- /** Called when an endpoint reconnection drag ends (committed or cancelled). */
446
- onReconnectEnd?: (edge: RivetEdge) => void;
447
- /** Called whenever the selected nodes and/or edges change. */
448
- onSelectionChange?: (selection: RivetSelection) => void;
449
- /**
450
- * Called when keyboard focus moves to a node (or leaves the graph — `null`).
451
- * The same state is available inside `<Rivet>` via {@link useRivetFocusedNode}.
452
- * Rivet draws no focus ring; use this (or the wrapper's `:focus-visible`) to
453
- * render your own indicator.
454
- */
455
- onFocusChange?: (id: NodeId | null) => void;
456
- className?: string;
457
- style?: CSSProperties;
458
- children?: ReactNode;
459
- };
460
- /**
461
- * The graph surface. Stacks a background grid canvas, an edge canvas, and a DOM
462
- * node layer — all driven by one shared viewport (see {@link useRivetRuntime}).
463
- */
464
- 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;
465
-
466
226
  /** Composite key for the handle/anchor registries. */
467
227
  declare function handleKey(nodeId: NodeId, handleId: string): string;
468
228
 
@@ -477,8 +237,22 @@ type ViewportClamp = {
477
237
  maxY?: number;
478
238
  };
479
239
  type ChangeHandlers = {
480
- nodes?: (changes: NodeChange[]) => void;
481
- 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;
482
256
  };
483
257
  /**
484
258
  * The single source of truth for a graph instance — the public contract. It's an
@@ -516,6 +290,18 @@ type RivetStore = {
516
290
  * nodes don't re-render.
517
291
  */
518
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;
519
305
  /** Add a node (imperative CRUD). Ignored if the id already exists. */
520
306
  addNode: (node: RivetNode) => void;
521
307
  /** Replace a node wholesale by id (imperative CRUD). Ignored if absent. */
@@ -593,13 +379,54 @@ type RivetStore = {
593
379
  /** True while a node drag is in progress (pauses swimlane auto-fit). */
594
380
  isNodeDragging: () => boolean;
595
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;
596
426
  /**
597
427
  * True while a resize gesture is in progress — set by {@link resizeNode}'s live
598
- * frames and cleared by its commit. Guards controlled-mode reconciliation the
599
- * same way {@link isNodeDragging} does: mid-gesture the consumer's props lag
600
- * the store (live frames may be dropped, and measurement echoes carry `size`
601
- * but not the explicit `width`/`height`), so reconciling them would stomp the
602
- * 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.
603
430
  */
604
431
  isNodeResizing: () => boolean;
605
432
  /**
@@ -684,6 +511,20 @@ type RivetStore = {
684
511
  setAlignmentGuides: (guides: AlignmentGuide[]) => void;
685
512
  registerHandle: (record: HandleRecord) => void;
686
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;
687
528
  readonly anchors: Map<string, AnchorRecord>;
688
529
  /**
689
530
  * Register (or re-bind) an anchor. Idempotent on the `(nodeId, anchorId)`
@@ -718,11 +559,351 @@ type RivetStore = {
718
559
  /** Ask the runtime to schedule a canvas frame. */
719
560
  requestRender: () => void;
720
561
  bindRenderRequester: (fn: () => void) => void;
562
+ /**
563
+ * Ask the runtime to repaint the peer-cursor layer only — everything else on
564
+ * screen keeps last frame's pixels. Degrades to {@link requestRender} while
565
+ * no runtime has claimed the layer, so a caller never has to check.
566
+ */
567
+ requestCursorRender: () => void;
568
+ bindCursorRenderRequester: (fn: (() => void) | null) => void;
721
569
  };
722
570
  /** Create a graph store. The public entry point; the class is an implementation detail. */
723
571
  declare function createRivetStore(init: RivetStoreInit): RivetStore;
724
572
 
725
- type RivetContextValue = {
573
+ /** Resolved graph context for a pane right-click, passed to `onPaneContextMenu`. */
574
+ type PaneContextMenuContext = {
575
+ /** The click position in world coordinates (already unprojected). */
576
+ world: Vec2;
577
+ /** The swimlane lane whose body sits under the cursor, or `null`. */
578
+ lane: ResolvedLane | null;
579
+ };
580
+ type RivetProps = {
581
+ /**
582
+ * Nodes to seed the graph with (uncontrolled). Read once on mount to initialize
583
+ * the internal store — later changes to this array are ignored. Use {@link nodes}
584
+ * + {@link onNodesChange} for controlled mode instead. Defaults to `[]`.
585
+ */
586
+ defaultNodes?: RivetNode[];
587
+ /**
588
+ * Edges to seed the graph with (uncontrolled). See {@link defaultNodes}. An edge
589
+ * whose `source` or `target` node is absent is skipped by the renderer. Defaults to `[]`.
590
+ */
591
+ defaultEdges?: RivetEdge[];
592
+ /**
593
+ * Controlled nodes. When provided, the graph mirrors this array — hold the nodes
594
+ * in your own state and feed back the batches from {@link onNodesChange} (apply
595
+ * them with `applyNodeChanges`). A node under a live gesture keeps its own
596
+ * geometry rather than the array's, so a drag stays smooth while the props
597
+ * lag behind it. Takes precedence over {@link defaultNodes}.
598
+ */
599
+ nodes?: RivetNode[];
600
+ /** Controlled edges. See {@link nodes}. Takes precedence over {@link defaultEdges}. */
601
+ edges?: RivetEdge[];
602
+ /**
603
+ * Change batches the graph wants applied to your nodes (controlled mode).
604
+ *
605
+ * `meta.origin` says where the batch came from: `"local"` for anything this
606
+ * client did, `"remote"` for a batch that arrived through
607
+ * {@link RivetInstance.applyRemote}. Multiplayer consumers apply both and send
608
+ * only the local ones onward — an untagged echo is indistinguishable from a
609
+ * peer's edit and loops straight back out.
610
+ */
611
+ onNodesChange?: (changes: NodeChange[], meta: ChangeMeta) => void;
612
+ /** Change batches the graph wants applied to your edges (controlled mode). See {@link onNodesChange}. */
613
+ onEdgesChange?: (changes: EdgeChange[], meta: ChangeMeta) => void;
614
+ /**
615
+ * Initial pan/zoom. `x`/`y` are the screen-space offset in pixels and `zoom`
616
+ * is the scale factor. Defaults to `{ x: 0, y: 0, zoom: 1 }` (origin, 1:1).
617
+ */
618
+ defaultViewport?: Viewport;
619
+ /**
620
+ * Maps a node's `type` to the React component that renders it. A node whose
621
+ * `type` has no entry here (or no `type` at all) falls back to
622
+ * {@link DefaultNode}. Defaults to `{}`.
623
+ */
624
+ nodeTypes?: NodeTypes;
625
+ /**
626
+ * Registry of custom edge shapes, keyed by `edge.type`. Each is a pure geometry
627
+ * function (see {@link EdgePathFn}) so edges stay canvas/GPU-renderable. Merged
628
+ * over the built-ins `bezier` (default), `smoothstep`, `step`, and `straight`.
629
+ */
630
+ edgeTypes?: EdgeTypes;
631
+ /** Defaults merged into every edge that doesn't set the field (type, style, markers…). */
632
+ defaultEdgeOptions?: DefaultEdgeOptions;
633
+ /** Accessible name for the graph surface (`aria-label` on the pane). Defaults to "Node graph". */
634
+ ariaLabel?: string;
635
+ /**
636
+ * Backend factory for the edge layers — swap Canvas2D for another
637
+ * {@link EdgeRenderer}. Defaults to {@link canvas2DEdgeRenderer}. Pass a stable/memoized
638
+ * reference; a new identity re-creates the renderers.
639
+ */
640
+ renderer?: EdgeRendererFactory;
641
+ /**
642
+ * Lower bound on `viewport.zoom`. All zooming (wheel, controls, `fitView`) is
643
+ * clamped to this. Defaults to `0.2` (zoomed out to 20%).
644
+ */
645
+ minZoom?: number;
646
+ /**
647
+ * Upper bound on `viewport.zoom`. All zooming is clamped to this. Defaults to
648
+ * `2.5` (zoomed in to 250%).
649
+ */
650
+ maxZoom?: number;
651
+ /**
652
+ * Spacing in world units between background grid dots. The grid scales with
653
+ * zoom, so this is measured at `zoom === 1`. Defaults to `24`.
654
+ */
655
+ gridGap?: number;
656
+ /**
657
+ * Snap dragged nodes to a `[x, y]` world-unit grid. A `0` axis is left free.
658
+ * Omit (default) for free positioning. Independent of {@link gridGap} (the
659
+ * visual dot grid) — pass the same value to snap to the dots you see.
660
+ */
661
+ snapGrid?: [number, number];
662
+ /**
663
+ * While dragging a single node, show alignment guides and snap its edges/center
664
+ * to nearby nodes' edges/centers. Defaults to `false`.
665
+ */
666
+ alignmentGuides?: boolean;
667
+ /**
668
+ * Placement policy for anchor chrome — the grab dots and stray perimeter
669
+ * handles rivet renders for registered anchors (see {@link Anchor}). Omitted
670
+ * fields fall back to the defaults.
671
+ */
672
+ anchorOptions?: AnchorOptions;
673
+ /**
674
+ * The other people in this document — the presence roster. Identity (name,
675
+ * colour) plus the slow fields: what each peer has selected, and what they're
676
+ * holding under a gesture. Rivet paints them: a cursor per peer, an outline
677
+ * around what they've selected, and the nodes they're moving drawn where they
678
+ * currently have them.
679
+ *
680
+ * Peer cursors move far faster than React should re-render, so the roster
681
+ * carries only a seed for them — feed live cursors and drag boxes through
682
+ * {@link RivetInstance.setPeerCursor} / `setPeerNodeTransform` instead.
683
+ *
684
+ * Presence is ephemeral: none of it reaches the graph, the change stream, or
685
+ * history. Dropping a peer from the array is how they leave.
686
+ */
687
+ peers?: Peer[];
688
+ /**
689
+ * Called with this client's presence — cursor (in world coordinates),
690
+ * selection, and held nodes — as a whole snapshot, ready to broadcast.
691
+ *
692
+ * Snapshots are idempotent, so a dropped or duplicated one costs nothing and
693
+ * a reconnecting peer is instantly correct. Cursor movement is coalesced to
694
+ * `presenceOptions.throttleMs`; selection and gesture changes are published
695
+ * immediately.
696
+ */
697
+ onLocalPresence?: (presence: LocalPresence) => void;
698
+ /** Presence policy — outbound throttle, and whether rivet paints peer cursors. */
699
+ presenceOptions?: PresenceOptions;
700
+ /**
701
+ * Nodes a peer has claimed, as `nodeId -> peerId`. Locks are policy where
702
+ * {@link peers} is description: rivet paints a locked node in its holder's
703
+ * colour *and* refuses to act on it.
704
+ *
705
+ * By default it refuses selection, dragging, resizing and deletion, and
706
+ * allows connections — a lock claims the node, not the graph around it.
707
+ * {@link canInteractWithLocked} replaces that policy.
708
+ *
709
+ * Lifetime is yours: rivet never takes or releases a lock, it only reads the
710
+ * table. Take one in {@link onNodeDragStart} and drop it in
711
+ * {@link onNodeDragEnd} for drag-scoped locks, or hold through deselection
712
+ * for something stickier. Like presence, none of this reaches the graph, the
713
+ * change stream, or history.
714
+ */
715
+ lockedNodes?: Record<NodeId, string>;
716
+ /**
717
+ * Decide what this client may do to a locked node, per attempt. Return `true`
718
+ * to allow the intent, `false` to refuse it.
719
+ *
720
+ * A handler owns the whole policy — rivet's defaults are replaced, not
721
+ * consulted — so answer every intent you care about:
722
+ *
723
+ * ```tsx
724
+ * // locked nodes stay inspectable, but nothing may change them
725
+ * canInteractWithLocked={({ intent }) => intent === "select" || intent === "connect"}
726
+ * ```
727
+ *
728
+ * Called on the input path (every marquee frame, every mover), so keep it
729
+ * cheap and free of side effects.
730
+ */
731
+ canInteractWithLocked?: CanInteractWithLocked;
732
+ /**
733
+ * Called when a lock for a peer arrives on a node this client is already
734
+ * holding — two clients started before either lock reached the other.
735
+ *
736
+ * rivet reports it and keeps going: the running gesture stays authoritative
737
+ * over its geometry, because only your transport knows which client actually
738
+ * lost the race. To yield, call {@link RivetInstance.releaseNodeGesture} —
739
+ * the gesture stops writing and tears down without committing.
740
+ */
741
+ onNodeLockConflict?: (event: NodeLockConflictEvent) => void;
742
+ /**
743
+ * Which mouse buttons pan when dragging the pane: `true` = left only, `false` =
744
+ * none, or a list of `MouseEvent.button` codes (`0` left, `1` middle, `2` right).
745
+ * e.g. `[1, 2]` pans with middle/right and frees left for selection. Defaults to
746
+ * `true`. Pass a stable reference; a new array identity re-binds the listeners.
747
+ */
748
+ panOnDrag?: boolean | number[];
749
+ /**
750
+ * Draw a selection marquee on a plain left-drag instead of panning. Pair with
751
+ * `panOnDrag={[1, 2]}` so left selects and middle/right pan. A marquee that
752
+ * ends over nodes leaves a persistent selection box around them — drag it to
753
+ * move the whole selection; a click elsewhere or Escape dismisses it.
754
+ * Defaults to `false`.
755
+ */
756
+ selectionOnDrag?: boolean;
757
+ /**
758
+ * Key(s) that force a marquee on a left-drag when held (e.g. `"Shift"`, `"Meta"`,
759
+ * or `["Shift", "Meta"]`). `null` disables. Defaults to `"Shift"`.
760
+ */
761
+ selectionKeyCode?: string | string[] | null;
762
+ /**
763
+ * Key(s) that make a click or marquee **add to** the selection instead of
764
+ * replacing it. Defaults to `["Meta", "Control", "Shift"]`. Use modifier keys
765
+ * (`Shift`/`Meta`/`Control`/`Alt`) — other keys only resolve during a pane drag.
766
+ */
767
+ multiSelectionKeyCode?: string | string[];
768
+ /**
769
+ * Pan the viewport on wheel / two-finger trackpad scroll instead of zooming.
770
+ * A pinch gesture (reported by the browser as ctrl+wheel) still zooms. When
771
+ * `false` (default), scrolling zooms toward the cursor.
772
+ */
773
+ scrollToPan?: boolean;
774
+ /**
775
+ * Wheel/pinch zoom sensitivity — larger zooms faster per scroll delta.
776
+ * Defaults to `0.0015`. Try `~0.003` for faster trackpad pinch zoom.
777
+ */
778
+ zoomSpeed?: number;
779
+ /**
780
+ * Swimlanes drawn behind the graph — a decorative background of labeled lane
781
+ * groups (like planout's swimlanes). Not nodes or edges; just layout regions.
782
+ * Omit for no swimlanes.
783
+ */
784
+ swimlane?: SwimlaneGroup[];
785
+ /**
786
+ * Bind node dragging to the swimlanes: on drop each node snaps fully inside
787
+ * one lane (honoring {@link swimlaneMargin}) and is reassigned to the lane it
788
+ * lands in via {@link RivetNode.laneId}. Defaults to `true` when
789
+ * {@link swimlane} is provided, `false` otherwise. Ignored with no swimlanes.
790
+ */
791
+ clampToSwimlane?: boolean;
792
+ /**
793
+ * Inner clearance kept between a clamped node and its lane's edges. A number
794
+ * applies to all sides; per-side values override it. Defaults to `16`.
795
+ */
796
+ swimlaneMargin?: number | SwimlaneMargin;
797
+ /** Screen height of the sticky group header band, in px. Defaults to `32`. */
798
+ swimlaneHeaderHeight?: number;
799
+ /**
800
+ * Show a resize handle on each lane's bottom edge to change its height.
801
+ * Defaults to `true` when {@link swimlane} is provided.
802
+ */
803
+ swimlaneResizable?: boolean;
804
+ /**
805
+ * Fraction of the last lane's height that must stay visible when scrolled to
806
+ * the bottom — the down-scroll stop. `0.5` (default) keeps half of it in view.
807
+ * Only applies when {@link clampToSwimlane} is on.
808
+ */
809
+ swimlaneBottomReveal?: number;
810
+ /** Render custom content for a group's sticky header (replaces the default). */
811
+ renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode;
812
+ /** Render custom content for a lane's sticky label (replaces the default). */
813
+ renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode;
814
+ /**
815
+ * Called when a lane's height changes — from a resize drag or from auto-fit
816
+ * growing it to contain a node that grew. The graph owns lane heights, so this
817
+ * is a notification (persist it if you like), not a request to update state.
818
+ */
819
+ onSwimlaneSizeChange?: (change: SwimlaneSizeChange) => void;
820
+ /**
821
+ * Right-click on empty canvas (not over a node or swimlane chrome). The second
822
+ * argument carries the resolved graph context — the world position and the lane
823
+ * under the cursor — so you don't have to re-derive them.
824
+ */
825
+ onPaneContextMenu?: (event: MouseEvent, context: PaneContextMenuContext) => void;
826
+ /** Right-click on a lane's sticky label. */
827
+ onSwimlaneHeaderContextMenu?: (laneId: string, event: MouseEvent) => void;
828
+ /** Right-click on a group's sticky header. */
829
+ onSwimlaneGroupHeaderContextMenu?: (groupId: string, event: MouseEvent) => void;
830
+ /** Called when a dragged node is reassigned to a different lane. */
831
+ onLaneChange?: (change: LaneChange) => void;
832
+ /** Veto a proposed connection; return false to reject it. Applies to reconnection too. */
833
+ isValidConnection?: (connection: Connection) => boolean;
834
+ /**
835
+ * Rewrite a validated connection before it commits — normalize handle ids
836
+ * (e.g. redirect an in-content grab handle to its canonical perimeter
837
+ * handle), or force direction conventions. Returning `null` cancels the
838
+ * drop. Applies to new connections and reconnections; `isValidConnection`
839
+ * sees the raw connection, `onConnect`/`onReconnect` see the mapped one.
840
+ */
841
+ mapConnection?: (connection: Connection) => Connection | null;
842
+ /** Called after a valid connection is made (the edge is already added). */
843
+ onConnect?: (connection: Connection) => void;
844
+ /**
845
+ * Allow reconnecting edges by dragging an endpoint onto a new handle. Per-edge
846
+ * `reconnectable` overrides this. Defaults to `false`.
847
+ */
848
+ edgesReconnectable?: boolean;
849
+ /**
850
+ * When edge endpoints re-side to face the peer node. `"manual"` (default)
851
+ * never rewrites a chosen handle; `"on-move"` re-sides both ends of a moved
852
+ * node's edges when the move commits — in the same batch as the move, so one
853
+ * undo restores position and handles together. `"live"` additionally renders
854
+ * a dragging node's edges as if their ends were auto — re-siding per frame
855
+ * to face each other, with hysteresis so near-diagonal positions don't
856
+ * flicker — and commits the displayed sides on drop, inside the move's
857
+ * history step; nothing is persisted mid-gesture. Only committed position
858
+ * changes trigger a rewrite: data replaces and dimension changes never
859
+ * re-side, and a freshly created or reconnected edge keeps exactly the
860
+ * handles the user grabbed or dropped on. Anchor and bare-side ends rewrite
861
+ * their side directly; plain handles re-side to a registered handle of a
862
+ * compatible type on the facing side, or stay pinned when the node has none
863
+ * there. Independent of the policy, an end with *no* side — a missing
864
+ * handle, or an `anchorAutoHandleId` anchor end — always floats: the
865
+ * renderer resolves its facing side per frame and never writes it back.
866
+ */
867
+ edgeAlignment?: EdgeAlignment;
868
+ /** Called when an endpoint drag re-points an edge (the edge is already updated). */
869
+ onReconnect?: (edge: RivetEdge, connection: Connection) => void;
870
+ /** Called when an endpoint reconnection drag starts. */
871
+ onReconnectStart?: (edge: RivetEdge) => void;
872
+ /** Called when an endpoint reconnection drag ends (committed or cancelled). */
873
+ onReconnectEnd?: (edge: RivetEdge) => void;
874
+ /**
875
+ * Called when a local gesture takes hold of one or more nodes — a pointer
876
+ * drag, a resize, or a keyboard grab. While it's open, those nodes' geometry
877
+ * belongs to this client: remote `position`/`dimensions` writes for them are
878
+ * dropped, and controlled props can't stomp the live box.
879
+ *
880
+ * In a shared document this is where you acquire a lock. It's paired with
881
+ * {@link onNodeDragEnd} — every start gets exactly one end, including a
882
+ * cancelled keyboard grab.
883
+ */
884
+ onNodeDragStart?: (event: NodeGestureEvent) => void;
885
+ /** Called when a gesture releases its nodes. See {@link onNodeDragStart}. */
886
+ onNodeDragEnd?: (event: NodeGestureEvent) => void;
887
+ /** Called whenever the selected nodes and/or edges change. */
888
+ onSelectionChange?: (selection: RivetSelection) => void;
889
+ /**
890
+ * Called when keyboard focus moves to a node (or leaves the graph — `null`).
891
+ * The same state is available inside `<Rivet>` via {@link useRivetFocusedNode}.
892
+ * Rivet draws no focus ring; use this (or the wrapper's `:focus-visible`) to
893
+ * render your own indicator.
894
+ */
895
+ onFocusChange?: (id: NodeId | null) => void;
896
+ className?: string;
897
+ style?: CSSProperties;
898
+ children?: ReactNode;
899
+ };
900
+ /**
901
+ * The graph surface. Stacks a background grid canvas, an edge canvas, and a DOM
902
+ * node layer — all driven by one shared viewport (see {@link useRivetRuntime}).
903
+ */
904
+ 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;
905
+
906
+ type RivetContextValue = {
726
907
  store: RivetStore;
727
908
  nodeTypes: NodeTypes;
728
909
  paneRef: RefObject<HTMLDivElement | null>;
@@ -892,6 +1073,20 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
892
1073
  toObject: () => RivetSnapshot<TNodeData, TEdgeData>;
893
1074
  getNodes: () => RivetNode<TNodeData>[];
894
1075
  getNode: (id: NodeId) => RivetNode<TNodeData> | undefined;
1076
+ /**
1077
+ * A node's box in **world** coordinates — its `parentId` chain resolved, and
1078
+ * its explicit or measured size applied. `null` for an id that isn't in the
1079
+ * graph.
1080
+ *
1081
+ * This is where the node currently *is on screen*, not what the document
1082
+ * says: a node a peer is dragging or resizing reports the box they last sent
1083
+ * (see {@link setPeerNodeTransform}), which is what anything painting over
1084
+ * the graph wants. Reach for {@link getNode} instead when you're computing a
1085
+ * mutation — that position is the one the document will agree with.
1086
+ *
1087
+ * Reads once; never subscribes. Call it inside your own frame loop.
1088
+ */
1089
+ getNodeRect: (id: NodeId) => Rect | null;
895
1090
  setNodes: (nodes: Updater<RivetNode<TNodeData>[]>) => void;
896
1091
  addNodes: (nodes: RivetNode<TNodeData> | RivetNode<TNodeData>[]) => void;
897
1092
  updateNode: (id: NodeId, patch: Updater<RivetNode<TNodeData>>) => void;
@@ -905,6 +1100,25 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
905
1100
  nodes?: NodeId[];
906
1101
  edges?: EdgeId[];
907
1102
  }) => void;
1103
+ /**
1104
+ * Apply a change batch that already happened elsewhere — a peer's edit off
1105
+ * your transport. The graph updates and repaints, then re-emits the batch to
1106
+ * `onNodesChange`/`onEdgesChange` tagged `"remote"`, so a controlled owner
1107
+ * keeps its state in step through the one path it already has.
1108
+ *
1109
+ * Remote batches never create an undo step; in a shared document, undoing a
1110
+ * peer's edit would silently revert work you don't own.
1111
+ *
1112
+ * ```ts
1113
+ * connection.onNodeMoved((row) => {
1114
+ * if (row.caller === me) return // your own echo — already applied
1115
+ * rivet.applyRemote({
1116
+ * nodes: [{ type: "position", id: row.id, position: row.position }],
1117
+ * })
1118
+ * })
1119
+ * ```
1120
+ */
1121
+ applyRemote: (changes: RemoteChanges) => void;
908
1122
  /**
909
1123
  * Bind an anchor — a connectable point — to a DOM element inside a node.
910
1124
  * The imperative core behind `<Anchor>`, for consumers whose content DOM is
@@ -926,6 +1140,40 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
926
1140
  * load) — rivet can't observe those.
927
1141
  */
928
1142
  remeasureAnchors: (nodeId: NodeId) => void;
1143
+ /**
1144
+ * Move a peer's cursor, in **world** coordinates (peers pan and zoom
1145
+ * independently, so a screen point means nothing to them). `null` parks it.
1146
+ *
1147
+ * The presence hot path: it paints on the next frame without going through
1148
+ * React, so call it as fast as your channel delivers. A peer the `peers`
1149
+ * roster hasn't mentioned yet still gets a cursor — a frame that beats the
1150
+ * join event shouldn't blink.
1151
+ */
1152
+ setPeerCursor: (peerId: string, point: Vec2 | null) => void;
1153
+ /**
1154
+ * Show a node where a peer currently has it, in world space — their drag,
1155
+ * mid-flight. The node renders, hit-tests and wires at that box until their
1156
+ * change lands through {@link applyRemote}; the graph itself is untouched.
1157
+ * Pass `null` on their drop.
1158
+ */
1159
+ setPeerNodeTransform: (peerId: string, nodeId: NodeId, rect: Rect | null) => void;
1160
+ /** Forget a peer entirely — cursor, held nodes and all. For a disconnect. */
1161
+ removePeer: (peerId: string) => void;
1162
+ /**
1163
+ * Give up a gesture's hold on its nodes without committing — the yield lever
1164
+ * for `onNodeLockConflict`, when your transport says this client lost the
1165
+ * race for a node it's already dragging.
1166
+ *
1167
+ * The running gesture notices on its next frame, stops writing, and tears
1168
+ * itself down; the nodes stay where they are and the holder's positions land
1169
+ * normally from then on. `onNodeDragEnd` fires as it would on a drop, so a
1170
+ * consumer's own lock release runs unchanged. Pass the conflict event's
1171
+ * `ids` to release a whole group.
1172
+ *
1173
+ * Doing nothing is also a valid answer: without this call the gesture keeps
1174
+ * its nodes until the pointer lifts.
1175
+ */
1176
+ releaseNodeGesture: (id: NodeId, ids?: NodeId[]) => void;
929
1177
  /** Copy nodes (default: the current selection) to the in-memory clipboard. */
930
1178
  copy: (ids?: NodeId[]) => void;
931
1179
  /** Copy then delete nodes (default: the current selection). */
@@ -1028,4 +1276,4 @@ declare function rectsIntersect(a: Rect, b: Rect): boolean;
1028
1276
 
1029
1277
  declare const VERSION = "0.0.0";
1030
1278
 
1031
- 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, resolveFacingSideAmong, screenToWorld, serializeGraph, snapToGrid, useRivet, useRivetContext, useRivetControls, useRivetFocusedNode, useRivetHistory, useRivetViewport, viewportToCss, visibleWorldRect, worldToScreen, zoomAt };
1279
+ 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 };