@kolosal-ai/rivet 0.2.0 → 0.4.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,9 +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 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';
1
+ import { R as Rect, V as Vec2, A as AlignmentGuide, c as EdgeChange, d as RivetEdge, a as NodeChange, e as RivetNode, N as NodeId, f as NodeProps, g as NodeTypes, b as HandleType, H as HandlePosition, S as Size, h as ChangeOrigin, E 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-D3aldmsO.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-D3aldmsO.js';
3
3
  import { ReactNode, CSSProperties, MouseEvent, RefObject } from 'react';
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';
4
+ import { e as AnchorRecord, g as AnchorRegistrationOptions, A as AnchorRef, c as AnchorOptions, R as ResolvedAnchorOptions, f as AnchorRegistration } from './registry-Z_oiPyjm.js';
5
+ import { b as PresenceTargetKey, p as PresenceRegistry, h as LockRegistry, k as Peer, e as LocalPresence, o as PresenceOptions, C as CanInteractWithLocked, l as PeerConnection } from './use-node-lock-CbS59CRE.js';
6
+ export { L as LOCK_DEFAULT_REFUSED, f as LockIntent, g as LockInteraction, i as LockTarget, N as NodeLock, y as useNodeLock, z as useNodeLockAllows } from './use-node-lock-CbS59CRE.js';
7
7
  import { ResolvedLane, ViewportElements, ResolvedSwimlaneGroup, ResolvedMargin } from './swimlane/index.js';
8
8
 
9
9
  /**
@@ -319,6 +319,19 @@ type RivetStore = {
319
319
  setViewportClamp: (clamp: ViewportClamp | null) => void;
320
320
  subscribeFrame: (listener: () => void) => () => void;
321
321
  notifyFrame: () => void;
322
+ /**
323
+ * Post-frame channel for peer cursors alone. Fires after every frame that
324
+ * repainted them — the cursor-only frames a pointer move schedules, *and*
325
+ * full frames, which own every layer.
326
+ *
327
+ * Its own channel because cursor frames are the ones that arrive at pointer
328
+ * rate for every peer: chrome that follows a cursor needs all of them, while
329
+ * everything else on {@link subscribeFrame} would be woken up for nothing.
330
+ * Read positions off `presence.getPeers()` — they're the interpolated ones,
331
+ * already stepped for this frame.
332
+ */
333
+ subscribeCursorFrame: (listener: () => void) => () => void;
334
+ notifyCursorFrame: () => void;
322
335
  /** Move a node. `commit` re-renders just that node; skip it during drag. */
323
336
  moveNode: (id: NodeId, position: Vec2, commit: boolean) => void;
324
337
  setNodeSize: (id: NodeId, size: Size) => void;
@@ -400,12 +413,17 @@ type RivetStore = {
400
413
  */
401
414
  endNodeGesture: (id: NodeId, ids?: NodeId[]) => void;
402
415
  /**
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.
416
+ * Publish the lock table (the `lockedNodes` prop), keyed by
417
+ * {@link PresenceTargetKey}, and report any node it claims that a local
418
+ * gesture is already holding to {@link GestureHandlers.conflict}. The gesture
419
+ * itself is left running — see {@link NodeLockConflictEvent} for why rivet
420
+ * doesn't pick the loser.
421
+ *
422
+ * Only node gestures are scanned, because only nodes are held: an edge or an
423
+ * anchor has no gesture to conflict with, so a lock on either is simply in
424
+ * force from the moment it arrives.
407
425
  */
408
- setLockedNodes: (locks: Record<NodeId, string> | undefined) => void;
426
+ setLockedNodes: (locks: Record<PresenceTargetKey, string> | undefined) => void;
409
427
  /**
410
428
  * True while a local gesture holds this node. Held geometry is exclusively
411
429
  * this client's: {@link applyRemote} drops incoming `position`/`dimensions`
@@ -440,6 +458,11 @@ type RivetStore = {
440
458
  getSelectedNodes: () => NodeId[];
441
459
  /** True if the node is part of the current selection. */
442
460
  isNodeSelected: (id: NodeId) => boolean;
461
+ /**
462
+ * The selected edge, if any — edge selection is single. The id alone, for
463
+ * callers that would otherwise build {@link getSelection}'s records to read it.
464
+ */
465
+ getSelectedEdgeId: () => EdgeId | null;
443
466
  /** The current selection (nodes + edges) as full records. */
444
467
  getSelection: () => RivetSelection;
445
468
  /** Subscribe to selection changes (nodes and/or edges). */
@@ -506,6 +529,14 @@ type RivetStore = {
506
529
  subscribeHistory: (listener: () => void) => () => void;
507
530
  getEdgeLabelAnchors: () => Map<EdgeId, Vec2>;
508
531
  setEdgeLabelAnchors: (anchors: Map<EdgeId, Vec2>) => void;
532
+ /**
533
+ * Screen-space anchor points for the edges peers have claimed, from the same
534
+ * renderer pass — a point on each drawn path, which is the one thing a DOM
535
+ * outline layer can't work out for itself. Empty unless something is claimed
536
+ * and DOM outline chrome is mounted.
537
+ */
538
+ getPeerEdgeAnchors: () => ReadonlyMap<EdgeId, Vec2>;
539
+ setPeerEdgeAnchors: (anchors: ReadonlyMap<EdgeId, Vec2>) => void;
509
540
  /** Alignment/snap guides to draw for the current drag (world space). Empty when idle. */
510
541
  getAlignmentGuides: () => AlignmentGuide[];
511
542
  setAlignmentGuides: (guides: AlignmentGuide[]) => void;
@@ -550,15 +581,40 @@ type RivetStore = {
550
581
  getNodeAnchors: (nodeId: NodeId) => AnchorRecord[];
551
582
  getNodeAnchorsVersion: (nodeId: NodeId) => number;
552
583
  subscribeNodeAnchors: (nodeId: NodeId, listener: () => void) => () => void;
584
+ /** The selected anchors. A cached snapshot — identity changes with the data. */
585
+ getSelectedAnchors: () => readonly AnchorRef[];
586
+ /**
587
+ * Replace the anchor selection. Selecting any anchor clears the node and edge
588
+ * selection: one selection concept, so delete and the chrome never describe
589
+ * two claims at once.
590
+ */
591
+ setSelectedAnchors: (refs: readonly AnchorRef[]) => void;
592
+ isAnchorSelected: (nodeId: NodeId, anchorId: string) => boolean;
593
+ subscribeAnchorSelection: (listener: () => void) => () => void;
553
594
  getEdgesVersion: () => number;
554
595
  subscribeEdges: (listener: () => void) => () => void;
555
596
  getPending: () => PendingConnection | null;
556
597
  beginConnection: (pending: PendingConnection) => void;
557
598
  updateConnection: (to: Vec2, toPosition?: HandlePosition) => void;
558
599
  endConnection: () => void;
600
+ /**
601
+ * The local pending connection changing — started, moved, dropped. Its own
602
+ * channel because presence has to publish it: the drag lives entirely in the
603
+ * canvas otherwise, and nothing else would tell an outbound tracker that a
604
+ * wire was let go of (a drop that moves the pointer no further would leave a
605
+ * peer's copy hanging there).
606
+ */
607
+ subscribeConnection: (listener: () => void) => () => void;
559
608
  /** Ask the runtime to schedule a canvas frame. */
560
609
  requestRender: () => void;
561
610
  bindRenderRequester: (fn: () => void) => void;
611
+ /**
612
+ * Ask the runtime to repaint the peer-cursor layer only — everything else on
613
+ * screen keeps last frame's pixels. Degrades to {@link requestRender} while
614
+ * no runtime has claimed the layer, so a caller never has to check.
615
+ */
616
+ requestCursorRender: () => void;
617
+ bindCursorRenderRequester: (fn: (() => void) | null) => void;
562
618
  };
563
619
  /** Create a graph store. The public entry point; the class is an implementation detail. */
564
620
  declare function createRivetStore(init: RivetStoreInit): RivetStore;
@@ -665,10 +721,14 @@ type RivetProps = {
665
721
  anchorOptions?: AnchorOptions;
666
722
  /**
667
723
  * 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.
724
+ * colour, `data`) plus the slow fields: what each peer has selected, and what
725
+ * they're holding under a gesture. Rivet paints them: a cursor per peer, an
726
+ * outline around what they've selected, and the nodes they're moving drawn
727
+ * where they currently have them.
728
+ *
729
+ * Claims are node ids, or `PresenceTarget`s for an edge or an anchor. Only
730
+ * nodes are painted today — the rest travels and is readable through
731
+ * {@link usePeers}, which is what a consumer rendering its own chrome needs.
672
732
  *
673
733
  * Peer cursors move far faster than React should re-render, so the roster
674
734
  * carries only a seed for them — feed live cursors and drag boxes through
@@ -688,16 +748,40 @@ type RivetProps = {
688
748
  * immediately.
689
749
  */
690
750
  onLocalPresence?: (presence: LocalPresence) => void;
691
- /** Presence policy — outbound throttle, and whether rivet paints peer cursors. */
751
+ /**
752
+ * Presence policy — the outbound throttle, and how peer cursors are drawn:
753
+ * rivet's canvas arrow, your own component, or nothing.
754
+ *
755
+ * Compared shallowly, so `cursorComponent` must be a stable reference —
756
+ * declared outside the render or memoized. An inline component would be a new
757
+ * type on every render, and React remounts every cursor when the type changes.
758
+ */
692
759
  presenceOptions?: PresenceOptions;
693
760
  /**
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.
761
+ * What a peer has claimed, as `target -> peerId`. Locks are policy where
762
+ * {@link peers} is description: rivet paints a claimed element in its
763
+ * holder's colour *and* refuses to act on it.
697
764
  *
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.
765
+ * Keys are {@link PresenceTargetKey}s, and a bare node id is its own key — so
766
+ * `{ n1: "ada" }` means exactly what it always did, and edges and anchors
767
+ * join on the same table:
768
+ *
769
+ * ```tsx
770
+ * <Rivet
771
+ * lockedNodes={{
772
+ * n1: "ada", // a node, and everything inside it
773
+ * "edge:e7": "kai", // an edge — refuses reconnection
774
+ * "anchor:n2:door": "wren", // one anchor inside n2
775
+ * }}
776
+ * />
777
+ * ```
778
+ *
779
+ * By default it refuses selection, dragging, resizing, deletion and
780
+ * reconnection, and allows connections — a lock claims the element, not the
781
+ * graph around it. {@link canInteractWithLocked} replaces that policy.
782
+ *
783
+ * A node's lock reaches its contents and their anchors; an edge's reaches
784
+ * nothing, since an edge belongs to no container.
701
785
  *
702
786
  * Lifetime is yours: rivet never takes or releases a lock, it only reads the
703
787
  * table. Take one in {@link onNodeDragStart} and drop it in
@@ -705,10 +789,10 @@ type RivetProps = {
705
789
  * for something stickier. Like presence, none of this reaches the graph, the
706
790
  * change stream, or history.
707
791
  */
708
- lockedNodes?: Record<NodeId, string>;
792
+ lockedNodes?: Record<PresenceTargetKey, string>;
709
793
  /**
710
- * Decide what this client may do to a locked node, per attempt. Return `true`
711
- * to allow the intent, `false` to refuse it.
794
+ * Decide what this client may do to a locked element, per attempt. Return
795
+ * `true` to allow the intent, `false` to refuse it.
712
796
  *
713
797
  * A handler owns the whole policy — rivet's defaults are replaced, not
714
798
  * consulted — so answer every intent you care about:
@@ -718,6 +802,16 @@ type RivetProps = {
718
802
  * canInteractWithLocked={({ intent }) => intent === "select" || intent === "connect"}
719
803
  * ```
720
804
  *
805
+ * `target` is what was touched and `lockedTarget` is what carries the lock —
806
+ * they differ when a lock is inherited, which is how a locked container can
807
+ * hold itself while leaving its contents free:
808
+ *
809
+ * ```tsx
810
+ * canInteractWithLocked={({ target, lockedTarget }) =>
811
+ * target.kind === "node" && lockedTarget.kind === "node" && target.id !== lockedTarget.id
812
+ * }
813
+ * ```
814
+ *
721
815
  * Called on the input path (every marquee frame, every mover), so keep it
722
816
  * cheap and free of side effects.
723
817
  */
@@ -1066,6 +1160,20 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
1066
1160
  toObject: () => RivetSnapshot<TNodeData, TEdgeData>;
1067
1161
  getNodes: () => RivetNode<TNodeData>[];
1068
1162
  getNode: (id: NodeId) => RivetNode<TNodeData> | undefined;
1163
+ /**
1164
+ * A node's box in **world** coordinates — its `parentId` chain resolved, and
1165
+ * its explicit or measured size applied. `null` for an id that isn't in the
1166
+ * graph.
1167
+ *
1168
+ * This is where the node currently *is on screen*, not what the document
1169
+ * says: a node a peer is dragging or resizing reports the box they last sent
1170
+ * (see {@link setPeerNodeTransform}), which is what anything painting over
1171
+ * the graph wants. Reach for {@link getNode} instead when you're computing a
1172
+ * mutation — that position is the one the document will agree with.
1173
+ *
1174
+ * Reads once; never subscribes. Call it inside your own frame loop.
1175
+ */
1176
+ getNodeRect: (id: NodeId) => Rect | null;
1069
1177
  setNodes: (nodes: Updater<RivetNode<TNodeData>[]>) => void;
1070
1178
  addNodes: (nodes: RivetNode<TNodeData> | RivetNode<TNodeData>[]) => void;
1071
1179
  updateNode: (id: NodeId, patch: Updater<RivetNode<TNodeData>>) => void;
@@ -1119,6 +1227,19 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
1119
1227
  * load) — rivet can't observe those.
1120
1228
  */
1121
1229
  remeasureAnchors: (nodeId: NodeId) => void;
1230
+ /**
1231
+ * One anchor's record — geometry, colour, and whatever `data` was registered
1232
+ * with it — or `null` when the node has no such anchor.
1233
+ */
1234
+ getAnchor: (nodeId: NodeId, anchorId: string) => AnchorRecord | null;
1235
+ /**
1236
+ * Replace the anchor selection. Selecting an anchor clears the node and edge
1237
+ * selection. View state: no change is emitted and nothing is recorded to
1238
+ * history, so this is safe to call from a render-driven effect.
1239
+ */
1240
+ setSelectedAnchors: (refs: readonly AnchorRef[]) => void;
1241
+ /** The selected anchors. Reactive through `useSelectedAnchors()`. */
1242
+ getSelectedAnchors: () => readonly AnchorRef[];
1122
1243
  /**
1123
1244
  * Move a peer's cursor, in **world** coordinates (peers pan and zoom
1124
1245
  * independently, so a screen point means nothing to them). `null` parks it.
@@ -1136,6 +1257,16 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
1136
1257
  * Pass `null` on their drop.
1137
1258
  */
1138
1259
  setPeerNodeTransform: (peerId: string, nodeId: NodeId, rect: Rect | null) => void;
1260
+ /**
1261
+ * Draw the wire a peer is pulling out of a handle — their `LocalPresence.pending`,
1262
+ * fed straight back. Both ends are in **world** coordinates. Pass `null` when
1263
+ * they let go, which their next snapshot says either way.
1264
+ *
1265
+ * Painted on the foreground in their colour, with a dot on the loose end. It
1266
+ * changes nothing about the graph: a connection exists when their change
1267
+ * lands, not while they're dragging toward it.
1268
+ */
1269
+ setPeerConnection: (peerId: string, connection: PeerConnection | null) => void;
1139
1270
  /** Forget a peer entirely — cursor, held nodes and all. For a disconnect. */
1140
1271
  removePeer: (peerId: string) => void;
1141
1272
  /**
@@ -1203,10 +1334,14 @@ declare class Canvas2DEdgeRenderer implements EdgeRenderer {
1203
1334
  private readonly endpoints;
1204
1335
  /** Screen-space label anchor per edge that has a label. */
1205
1336
  private readonly labels;
1337
+ /** Screen-space midpoint per edge somebody has claimed, for DOM claim chrome. */
1338
+ private readonly peerAnchors;
1206
1339
  constructor(canvas: HTMLCanvasElement, options?: EdgeRendererOptions);
1207
1340
  resize(width: number, height: number, dpr: number): void;
1208
1341
  /** Screen-space label anchors for the edges drawn last frame. */
1209
1342
  getLabels(): Map<EdgeId, Vec2>;
1343
+ /** Screen-space midpoints for the edges a peer had claimed last frame. */
1344
+ getPeerEdgeAnchors(): ReadonlyMap<EdgeId, Vec2>;
1210
1345
  /** Screen-space endpoints per edge from the last frame (reconnect affordance). */
1211
1346
  getEndpoints(): ReadonlyMap<EdgeId, {
1212
1347
  source: Vec2;