@kolosal-ai/rivet 0.2.0 → 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/anchor/index.d.ts +2 -2
- package/dist/{chunk-RG5ALD3X.js → chunk-A52HZWLH.js} +18 -10
- package/dist/chunk-A52HZWLH.js.map +1 -0
- package/dist/index.d.ts +26 -5
- package/dist/index.js +105 -24
- package/dist/index.js.map +1 -1
- package/dist/presence/index.d.ts +93 -4
- package/dist/presence/index.js +24 -2
- package/dist/presence/index.js.map +1 -1
- package/dist/{registry-t5ldbviG.d.ts → registry-CCBgd7FN.d.ts} +1 -1
- package/dist/svg/index.d.ts +2 -2
- package/dist/swimlane/index.d.ts +2 -2
- package/dist/{types-CQqnm4yW.d.ts → types-C9Vn1rzy.d.ts} +1 -1
- package/dist/{use-node-lock-Csg9pUeC.d.ts → use-node-lock-DH0dB1yA.d.ts} +10 -6
- package/package.json +1 -1
- package/dist/chunk-RG5ALD3X.js.map +0 -1
package/dist/anchor/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
-
export { A as ANCHOR_SIDES, a as AnchorGeometry, b as AnchorOptions, c as AnchorPoint, d as AnchorRecord, e as AnchorRegistration, f as AnchorRegistrationOptions, g as AnchorStrayPolicy, D as DEFAULT_ANCHOR_OPTIONS, R as ResolvedAnchorOptions, h as anchorAutoHandleId, i as anchorDotHandleId, j as anchorHandleId, k as computeAnchorGeometry, n as normalizeAnchorConnection, p as parseAnchorAutoHandleId, l as parseAnchorDotHandleId, m as parseAnchorHandleId, s as strayPlacementPct } from '../registry-
|
|
3
|
-
import '../types-
|
|
2
|
+
export { A as ANCHOR_SIDES, a as AnchorGeometry, b as AnchorOptions, c as AnchorPoint, d as AnchorRecord, e as AnchorRegistration, f as AnchorRegistrationOptions, g as AnchorStrayPolicy, D as DEFAULT_ANCHOR_OPTIONS, R as ResolvedAnchorOptions, h as anchorAutoHandleId, i as anchorDotHandleId, j as anchorHandleId, k as computeAnchorGeometry, n as normalizeAnchorConnection, p as parseAnchorAutoHandleId, l as parseAnchorDotHandleId, m as parseAnchorHandleId, s as strayPlacementPct } from '../registry-CCBgd7FN.js';
|
|
3
|
+
import '../types-C9Vn1rzy.js';
|
|
4
4
|
|
|
5
5
|
type AnchorProps = {
|
|
6
6
|
/**
|
|
@@ -104,11 +104,11 @@ function declarationEqual(a, b) {
|
|
|
104
104
|
return a.name === b.name && a.color === b.color && samePoint(a.cursor, b.cursor) && sameIds(a.selection, b.selection) && sameIds(a.holding, b.holding);
|
|
105
105
|
}
|
|
106
106
|
var Registry2 = class {
|
|
107
|
-
/** Called on every change, roster or hot-path —
|
|
108
|
-
constructor(
|
|
109
|
-
this.
|
|
107
|
+
/** Called on every change, roster or hot-path — something has to repaint either way. */
|
|
108
|
+
constructor(requests) {
|
|
109
|
+
this.requests = requests;
|
|
110
110
|
}
|
|
111
|
-
|
|
111
|
+
requests;
|
|
112
112
|
peers = /* @__PURE__ */ new Map();
|
|
113
113
|
listeners = /* @__PURE__ */ new Set();
|
|
114
114
|
snapshot = [];
|
|
@@ -121,7 +121,15 @@ var Registry2 = class {
|
|
|
121
121
|
lastStep = -1;
|
|
122
122
|
touch() {
|
|
123
123
|
this.stale = true;
|
|
124
|
-
this.requestRender();
|
|
124
|
+
this.requests.requestRender();
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* A change only the cursor layer can see. Same snapshot invalidation, but the
|
|
128
|
+
* frame it asks for repaints one canvas instead of all of them.
|
|
129
|
+
*/
|
|
130
|
+
touchCursor() {
|
|
131
|
+
this.stale = true;
|
|
132
|
+
this.requests.requestCursorRender();
|
|
125
133
|
}
|
|
126
134
|
/** A change React needs to know about, as well as the canvas. */
|
|
127
135
|
touchRoster() {
|
|
@@ -176,7 +184,7 @@ var Registry2 = class {
|
|
|
176
184
|
if (state.live !== void 0 && samePoint(state.live, point)) return;
|
|
177
185
|
state.live = point;
|
|
178
186
|
if (point === null) this.prune(state);
|
|
179
|
-
this.
|
|
187
|
+
this.touchCursor();
|
|
180
188
|
};
|
|
181
189
|
setPeerNodeTransform = (peerId, nodeId, rect) => {
|
|
182
190
|
const state = this.peer(peerId);
|
|
@@ -285,8 +293,8 @@ var Registry2 = class {
|
|
|
285
293
|
};
|
|
286
294
|
};
|
|
287
295
|
};
|
|
288
|
-
function createPresenceRegistry(
|
|
289
|
-
return new Registry2(
|
|
296
|
+
function createPresenceRegistry(requests) {
|
|
297
|
+
return new Registry2(requests);
|
|
290
298
|
}
|
|
291
299
|
var UNLOCKED = { locked: false };
|
|
292
300
|
function useNodeLock(id) {
|
|
@@ -302,5 +310,5 @@ function useNodeLockAllows(id, intent) {
|
|
|
302
310
|
}
|
|
303
311
|
|
|
304
312
|
export { DEFAULT_PRESENCE_OPTIONS, LOCK_DEFAULT_REFUSED, PEER_COLORS, createLockRegistry, createPresenceRegistry, peerColor, useNodeLock, useNodeLockAllows };
|
|
305
|
-
//# sourceMappingURL=chunk-
|
|
306
|
-
//# sourceMappingURL=chunk-
|
|
313
|
+
//# sourceMappingURL=chunk-A52HZWLH.js.map
|
|
314
|
+
//# sourceMappingURL=chunk-A52HZWLH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/locks/registry.ts","../src/presence/registry.ts","../src/hooks/use-node-lock.ts"],"names":["Registry"],"mappings":"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","file":"chunk-A52HZWLH.js","sourcesContent":["/**\n * Locks — nodes a peer has claimed, and what this client may still do to them.\n *\n * A lock is policy where presence is description. `Peer.holding` says \"they\n * have their pointer on this\"; a lock says \"hands off\", and rivet acts on it.\n * The two render identically on purpose — to whoever is looking, they mean the\n * same thing — but only a lock refuses anything.\n *\n * Locks come in whole, as the `lockedNodes` prop, because the transport already\n * has to hold them: a lock outlives the tab that took it (that's the point of\n * taking one), so the server owns the lifetime and rivet only reads it. Nothing\n * here enters the graph, the change stream, or history — a lock is no more a\n * fact about the document than a cursor is.\n *\n * What a lock refuses is a default, not a rule. {@link LOCK_DEFAULT_REFUSED} is\n * rivet's opinion; `canInteractWithLocked` is the consumer's, and it wins on\n * every intent it's asked about.\n */\n\nimport type { NodeId } from \"../types\"\n\n/**\n * A thing this client might try to do to a locked node.\n *\n * Granular because the answer genuinely differs per app: a design tool wants a\n * locked node inspectable, a form builder wants it untouchable, and a pipeline\n * editor usually wants to keep wiring around one while a colleague renames it.\n */\nexport type LockIntent =\n /** Click it, or sweep it up in a marquee. */\n | \"select\"\n /** Move it — pointer drag, group drag, keyboard grab or arrow nudge. */\n | \"drag\"\n /** Change its box through {@link NodeResizer}. */\n | \"resize\"\n /** Remove it as part of deleting the selection. */\n | \"delete\"\n /** Start or land an edge on one of its handles. */\n | \"connect\"\n\n/** What {@link CanInteractWithLocked} is asked about. */\nexport type LockInteraction = {\n nodeId: NodeId\n /** The peer holding the lock — the `lockedNodes` value for this node. */\n holderId: string\n intent: LockIntent\n}\n\n/**\n * Consumer override for rivet's default refusals, consulted once per attempt.\n * Return `true` to allow the intent, `false` to refuse it — the default is\n * replaced, not consulted, so a handler owns the whole policy.\n */\nexport type CanInteractWithLocked = (event: LockInteraction) => boolean\n\n/** A node's lock as {@link useNodeLock} reports it. */\nexport type NodeLock = {\n locked: boolean\n /** The peer holding it, or `undefined` when the node isn't locked. */\n holderId?: string\n}\n\n/**\n * The intents rivet refuses on a locked node when no policy is given.\n *\n * `connect` is deliberately absent. A lock claims the node, not the graph\n * around it: an edge is its own row with its own id, and refusing to wire one\n * would make a locked node an island for as long as somebody holds it — a far\n * heavier lock than \"someone is editing this\" warrants. Consumers who want that\n * can say so through {@link CanInteractWithLocked}.\n */\nexport const LOCK_DEFAULT_REFUSED: ReadonlySet<LockIntent> = new Set<LockIntent>([\n \"select\",\n \"drag\",\n \"resize\",\n \"delete\",\n])\n\n/**\n * The lock registry — one per graph, reachable as `store.locks`.\n *\n * Reads are hot: every marquee frame and every mover loop asks\n * {@link LockRegistry.allows}, so it stays a map lookup plus at most one\n * consumer call, with no allocation on the common (nothing locked) path.\n */\nexport type LockRegistry = {\n /**\n * Replace the lock table (the `lockedNodes` prop). Returns the nodes that\n * weren't locked before and are now — the store scans those for conflicts\n * with a live local gesture.\n */\n setLocks: (locks: Record<NodeId, string> | undefined) => NodeId[]\n /** Install the consumer's policy, or `null` to fall back to the defaults. */\n setPolicy: (policy: CanInteractWithLocked | null) => void\n isLocked: (id: NodeId) => boolean\n /** Who holds this node's lock, or `null` when it isn't locked. */\n getHolder: (id: NodeId) => string | null\n /** The whole table. The renderer walks it once a frame. */\n getLocks: () => ReadonlyMap<NodeId, string>\n /** How many nodes are locked — the render loop's early-out. */\n size: () => number\n /**\n * Whether this client may act on a node. Always `true` for an unlocked node,\n * so callers can ask unconditionally.\n */\n allows: (id: NodeId, intent: LockIntent) => boolean\n /** Bumped on every table change, for `useSyncExternalStore`. */\n getVersion: () => number\n /** Subscribe to the table changing — locks move at human rate, so this is React-safe. */\n subscribe: (listener: () => void) => () => void\n}\n\n/** Whether two lock tables hold the same node→holder pairs. */\nfunction locksEqual(current: ReadonlyMap<NodeId, string>, next: Record<NodeId, string>): boolean {\n const keys = Object.keys(next)\n if (keys.length !== current.size) return false\n return keys.every((id) => current.get(id) === next[id])\n}\n\nclass Registry implements LockRegistry {\n private locks = new Map<NodeId, string>()\n private policy: CanInteractWithLocked | null = null\n private version = 0\n private readonly listeners = new Set<() => void>()\n\n /** Marks the canvas dirty — a lock is paint as well as policy. */\n constructor(private readonly requestRender: () => void) {}\n\n setLocks = (locks: Record<NodeId, string> | undefined): NodeId[] => {\n const next = locks ?? {}\n // An unchanged table is the common case (any prop change re-runs the\n // effect), and re-publishing it would re-fire every conflict.\n if (locksEqual(this.locks, next)) return []\n const opened: NodeId[] = []\n for (const [id, holderId] of Object.entries(next)) {\n if (this.locks.get(id) !== holderId) opened.push(id)\n }\n this.locks = new Map(Object.entries(next))\n this.version += 1\n this.requestRender()\n for (const listener of this.listeners) listener()\n return opened\n }\n\n setPolicy = (policy: CanInteractWithLocked | null): void => {\n this.policy = policy\n }\n\n isLocked = (id: NodeId): boolean => this.locks.has(id)\n\n getHolder = (id: NodeId): string | null => this.locks.get(id) ?? null\n\n getLocks = (): ReadonlyMap<NodeId, string> => this.locks\n\n size = (): number => this.locks.size\n\n allows = (id: NodeId, intent: LockIntent): boolean => {\n // Fast path: nothing locked at all, which is every single-player frame.\n if (this.locks.size === 0) return true\n const holderId = this.locks.get(id)\n if (holderId === undefined) return true\n if (this.policy) return this.policy({ nodeId: id, holderId, intent })\n return !LOCK_DEFAULT_REFUSED.has(intent)\n }\n\n getVersion = (): number => this.version\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n}\n\n/** Create a lock registry. `requestRender` repaints the holder outlines. */\nexport function createLockRegistry(requestRender: () => void): LockRegistry {\n return new Registry(requestRender)\n}\n","/**\n * Presence — who else is in the document, and where they are.\n *\n * Presence is ephemeral by construction. Nothing here enters the graph, the\n * change stream, or history: a peer's cursor is not a fact about the document,\n * it's a fact about a person, and it stops being true the moment they move. So\n * the registry is imperative, painted straight from the render loop, and thrown\n * away when the tab closes.\n *\n * Two doors write to it, at deliberately different speeds:\n *\n * - the `peers` prop — the roster. Identity (name, colour) and the slow\n * fields (selection, holding). React-rate.\n * - {@link PresenceRegistry.setPeerCursor} / `setPeerNodeTransform` — the hot\n * path off your ephemeral channel, called as fast as frames arrive. These\n * never touch React: they mark the canvas dirty and nothing else. A cursor\n * dirties only the cursor layer; a transform moves a real node, so it\n * dirties the frame.\n *\n * A peer the roster has never mentioned is still legitimate — a cursor frame\n * can beat the join event, and dropping it would blink the cursor. Such a peer\n * lives until its cursor clears (see {@link PresenceRegistry.setPeerCursor}),\n * and its colour is derived from its id, so the record can be rebuilt any\n * number of times without the colour ever flickering.\n */\n\nimport type { NodeId, Rect, Vec2 } from \"../types\"\n\n/**\n * A participant other than this client, as your transport describes them.\n *\n * Everything is optional but `id`: presence degrades field by field, and a peer\n * you know nothing about except that they exist is still worth an avatar.\n */\nexport type Peer = {\n id: string\n /** Display name, drawn beside their cursor. Unnamed peers get no label. */\n name?: string\n /** Any CSS colour. Defaults to a stable colour derived from {@link Peer.id}. */\n color?: string\n /**\n * Cursor in **world** coordinates — peers pan and zoom independently, so a\n * screen point means nothing to anyone else. `null` when their pointer is\n * off the canvas. Only a seed: once {@link PresenceRegistry.setPeerCursor}\n * has been called for a peer, the imperative value owns the cursor.\n */\n cursor?: Vec2 | null\n /** Nodes they have selected. Outlined in their colour. */\n selection?: NodeId[]\n /** Nodes under one of their live gestures (their `onNodeDragStart`). */\n holding?: NodeId[]\n}\n\n/**\n * A peer as the registry holds it: identity resolved, live overlays merged in.\n * What the renderer and {@link usePeers} both read.\n */\nexport type PeerRecord = {\n id: string\n name?: string\n /** Always set — {@link peerColor} fills in for a peer that declared none. */\n color: string\n cursor: Vec2 | null\n selection: readonly NodeId[]\n holding: readonly NodeId[]\n /**\n * Where this peer currently has each node they're dragging, in world space —\n * the frame-rate overlay from `setPeerNodeTransform`. The node renders there\n * until they let go; the graph keeps its own position until their commit\n * arrives as a real change.\n */\n transforms: ReadonlyMap<NodeId, Rect>\n}\n\n/** Presence policy, set via the `presenceOptions` prop. */\nexport type PresenceOptions = {\n /**\n * Smallest gap between outbound `onLocalPresence` snapshots, in ms. Cursor\n * movement is coalesced to this rate; selection and gesture changes ignore it\n * and go out at once (a lock signal that waits is a lock signal that races).\n * Default `50`.\n */\n throttleMs?: number\n /**\n * Give peer cursors their own canvas layer. Turn off to render your own from\n * {@link usePeers} — avatars, follow buttons, anything the canvas can't do —\n * and the layer is never created at all. Node outlines and peers' live boxes\n * are unaffected: they live with the geometry they annotate. Default `true`.\n */\n renderCursors?: boolean\n}\n\nexport type ResolvedPresenceOptions = Required<PresenceOptions>\n\nexport const DEFAULT_PRESENCE_OPTIONS: ResolvedPresenceOptions = {\n throttleMs: 50,\n renderCursors: true,\n}\n\n/**\n * Default peer colours. Spaced around the wheel and mid-toned, so they read on\n * a light or dark canvas and stay distinguishable side by side.\n */\nexport const PEER_COLORS = [\n \"#6366f1\",\n \"#ec4899\",\n \"#f59e0b\",\n \"#10b981\",\n \"#3b82f6\",\n \"#8b5cf6\",\n \"#ef4444\",\n \"#14b8a6\",\n] as const\n\n/**\n * A stable colour for a peer id. Deterministic, so the same person is the same\n * colour on every client and across a record being pruned and rebuilt — nobody\n * coordinates a palette, and nobody's cursor changes colour mid-session.\n */\nexport function peerColor(id: string): string {\n let hash = 0\n for (let i = 0; i < id.length; i += 1) hash = (hash * 31 + id.charCodeAt(i)) | 0\n // Modulo of a negative hash is negative; normalize before indexing.\n const index = ((hash % PEER_COLORS.length) + PEER_COLORS.length) % PEER_COLORS.length\n return PEER_COLORS[index] ?? PEER_COLORS[0]\n}\n\n/**\n * The presence registry — one per graph, reachable as `store.presence`.\n *\n * Reads are snapshot-based: {@link getPeers} returns a cached array rebuilt\n * only when something actually changed, so the render loop can call it every\n * frame and `useSyncExternalStore` can compare identities.\n */\nexport type PresenceRegistry = {\n /**\n * Replace the roster (the `peers` prop). Peers that were in the previous\n * roster and aren't in this one are dropped outright — leaving the roster is\n * how a peer leaves. Peers this registry only knows imperatively are kept.\n */\n setPeers: (peers: Peer[]) => void\n /**\n * Move a peer's cursor, in world coordinates. The hot path: call it as fast\n * as your channel delivers frames — it dirties the cursor layer and nothing\n * else, so the cost doesn't scale into the rest of the canvas.\n *\n * `null` parks the cursor. For a peer the roster never declared, that leaves\n * nothing to draw, so the record is dropped — the next frame recreates it\n * with the same colour.\n */\n setPeerCursor: (peerId: string, point: Vec2 | null) => void\n /**\n * Where a peer currently has a node, in world space — their drag, mid-flight.\n * The node itself moves there: it's painted, hit-tested and wired at the peer's\n * live box, while the graph underneath is untouched until their commit lands.\n * `null` releases it, which is what their drop should do just before their\n * real change arrives.\n */\n setPeerNodeTransform: (peerId: string, nodeId: NodeId, rect: Rect | null) => void\n /**\n * Every node a peer currently has under a gesture, at its *rendered* position\n * — the interpolated one, not the last frame that arrived. What the store\n * folds into world positions.\n */\n getNodeTransforms: () => ReadonlyMap<NodeId, Rect>\n /**\n * Bumped whenever a rendered transform moves. Lets the store cache world\n * positions across frames where peers are idle.\n */\n getTransformVersion: () => number\n /**\n * Advance the interpolation toward the values that arrived, and report\n * whether anything is still in motion (i.e. whether another frame is owed).\n *\n * Frames arrive at whatever rate a transport manages — 20Hz is normal, and\n * stepping straight to each one makes a cursor stutter and a dragged node\n * jump. Easing between them costs one lerp per peer and buys motion that\n * reads as somebody moving rather than as packets landing.\n */\n step: (now: number) => boolean\n /** Forget a peer entirely — cursor, held nodes and all. Use it when they disconnect. */\n removePeer: (peerId: string) => void\n /** Every known peer. A cached snapshot; identity only changes when the data does. */\n getPeers: () => readonly PeerRecord[]\n /** True when nobody else is here — the render loop's early-out. */\n isEmpty: () => boolean\n /**\n * Subscribe to **roster** changes: peers arriving or leaving, and their\n * identity, selection or holding changing. Cursor and transform frames\n * deliberately don't notify — they'd re-render React at pointer rate, and\n * they're already painted on the canvas. Read them from {@link getPeers} on\n * the frame channel if you need them in the DOM.\n */\n subscribe: (listener: () => void) => () => void\n}\n\n/** Internal peer state: what the roster declared, plus the imperative overlays. */\ntype PeerState = {\n id: string\n /** The roster's entry, or null for a peer only known through cursor frames. */\n declared: Peer | null\n /** `undefined` until an imperative write; after that it owns the cursor. */\n live: Vec2 | null | undefined\n /** Where the cursor is being drawn, easing toward {@link live}. */\n renderCursor: Vec2 | null\n /** Latest frame per node — the target the rendered box is easing toward. */\n transforms: Map<NodeId, Rect>\n /** Where each box is actually drawn this frame. */\n renderTransforms: Map<NodeId, Rect>\n}\n\n/**\n * Time constant for peer interpolation, in ms: the gap to the target closes by\n * ~63% every `SMOOTHING_MS`. Short enough that a cursor never trails visibly\n * behind the frames arriving, long enough to absorb a 20Hz channel.\n */\nconst SMOOTHING_MS = 45\n/** Assumed frame length for the very first step, which has no interval to measure. */\nconst NOMINAL_FRAME_MS = 16\n/** Below this (px, world or screen) the ease is over and the value snaps. */\nconst SETTLED_EPSILON = 0.05\n\n/** Ease `from` toward `to` by `t`, or snap when it's close enough to be done. */\nfunction approach(from: number, to: number, t: number): number {\n const next = from + (to - from) * t\n return Math.abs(to - next) < SETTLED_EPSILON ? to : next\n}\n\nconst EMPTY_IDS: readonly NodeId[] = []\n\nconst samePoint = (a: Vec2 | null | undefined, b: Vec2 | null | undefined): boolean =>\n a === b || (a != null && b != null && a.x === b.x && a.y === b.y)\n\nconst sameRect = (a: Rect | undefined, b: Rect): boolean =>\n a !== undefined && a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height\n\nconst sameIds = (a: readonly NodeId[] | undefined, b: readonly NodeId[] | undefined): boolean => {\n const left = a ?? EMPTY_IDS\n const right = b ?? EMPTY_IDS\n return left.length === right.length && left.every((id, i) => id === right[i])\n}\n\n/** Whether two roster entries carry the same identity and slow fields. */\nfunction declarationEqual(a: Peer | null, b: Peer | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return (\n a.name === b.name &&\n a.color === b.color &&\n samePoint(a.cursor, b.cursor) &&\n sameIds(a.selection, b.selection) &&\n sameIds(a.holding, b.holding)\n )\n}\n\nclass Registry implements PresenceRegistry {\n private readonly peers = new Map<string, PeerState>()\n private readonly listeners = new Set<() => void>()\n private snapshot: readonly PeerRecord[] = []\n private stale = false\n /** Merged rendered transforms across peers, rebuilt when {@link version} moves. */\n private transforms: Map<NodeId, Rect> = new Map()\n private transformsStale = false\n private version = 0\n /** Timestamp of the last {@link step}, so the ease is frame-rate independent. */\n private lastStep = -1\n\n /** Called on every change, roster or hot-path — something has to repaint either way. */\n constructor(private readonly requests: PresenceRenderRequests) {}\n\n private touch(): void {\n this.stale = true\n this.requests.requestRender()\n }\n\n /**\n * A change only the cursor layer can see. Same snapshot invalidation, but the\n * frame it asks for repaints one canvas instead of all of them.\n */\n private touchCursor(): void {\n this.stale = true\n this.requests.requestCursorRender()\n }\n\n /** A change React needs to know about, as well as the canvas. */\n private touchRoster(): void {\n this.touch()\n for (const listener of this.listeners) listener()\n }\n\n private peer(id: string): PeerState {\n let state = this.peers.get(id)\n if (!state) {\n state = {\n id,\n declared: null,\n live: undefined,\n renderCursor: null,\n transforms: new Map(),\n renderTransforms: new Map(),\n }\n this.peers.set(id, state)\n }\n return state\n }\n\n /** Drop a peer the roster doesn't claim and that has nothing left to draw. */\n private prune(state: PeerState): void {\n if (state.declared || state.live || state.transforms.size > 0) return\n this.peers.delete(state.id)\n this.invalidateTransforms()\n }\n\n private invalidateTransforms(): void {\n this.transformsStale = true\n this.version += 1\n }\n\n setPeers = (peers: Peer[]): void => {\n let changed = false\n const incoming = new Set<string>()\n for (const peer of peers) {\n incoming.add(peer.id)\n const state = this.peer(peer.id)\n if (declarationEqual(state.declared, peer)) continue\n state.declared = peer\n changed = true\n }\n for (const state of [...this.peers.values()]) {\n if (incoming.has(state.id)) continue\n // Declared before, absent now: they left. A peer the roster never\n // mentioned is none of its business and survives.\n if (!state.declared) continue\n this.peers.delete(state.id)\n changed = true\n }\n if (changed) this.touchRoster()\n }\n\n setPeerCursor = (peerId: string, point: Vec2 | null): void => {\n const state = this.peer(peerId)\n if (state.live !== undefined && samePoint(state.live, point)) return\n state.live = point\n if (point === null) this.prune(state)\n // Cursor-only: a peer this registry knows nothing else about is pruned here\n // too, but pruning one is only ever the erasure of a cursor — a record with\n // a roster entry or a live transform doesn't qualify (see `prune`).\n this.touchCursor()\n }\n\n setPeerNodeTransform = (peerId: string, nodeId: NodeId, rect: Rect | null): void => {\n const state = this.peer(peerId)\n if (rect === null) {\n const had = state.transforms.delete(nodeId)\n // The rendered box goes with the target: a released node snaps back to\n // wherever the graph says it is, which by then is where their commit put it.\n state.renderTransforms.delete(nodeId)\n this.prune(state)\n if (!had) return\n this.invalidateTransforms()\n this.touch()\n return\n }\n if (sameRect(state.transforms.get(nodeId), rect)) return\n state.transforms.set(nodeId, rect)\n // The first frame for a node places it outright — there's nothing to ease\n // from, and easing from the graph position would slide it across the canvas.\n if (!state.renderTransforms.has(nodeId)) {\n state.renderTransforms.set(nodeId, rect)\n this.invalidateTransforms()\n }\n this.touch()\n }\n\n removePeer = (peerId: string): void => {\n if (!this.peers.delete(peerId)) return\n this.invalidateTransforms()\n this.touchRoster()\n }\n\n getNodeTransforms = (): ReadonlyMap<NodeId, Rect> => {\n if (this.transformsStale) {\n this.transformsStale = false\n this.transforms = new Map()\n for (const state of this.peers.values()) {\n for (const [nodeId, rect] of state.renderTransforms) this.transforms.set(nodeId, rect)\n }\n }\n return this.transforms\n }\n\n getTransformVersion = (): number => this.version\n\n step = (now: number): boolean => {\n // Exponential ease, sampled at whatever interval this frame actually took,\n // so the motion looks the same on a 60Hz and a 120Hz display. The first\n // step has no interval to measure and assumes one ordinary frame.\n const elapsed = this.lastStep < 0 ? NOMINAL_FRAME_MS : Math.max(0, now - this.lastStep)\n this.lastStep = now\n const t = 1 - Math.exp(-elapsed / SMOOTHING_MS)\n let moving = false\n let transformsMoved = false\n\n for (const state of this.peers.values()) {\n const target = state.live !== undefined ? state.live : (state.declared?.cursor ?? null)\n if (target === null) {\n state.renderCursor = null\n } else if (!state.renderCursor) {\n state.renderCursor = target\n } else if (!samePoint(state.renderCursor, target)) {\n state.renderCursor = {\n x: approach(state.renderCursor.x, target.x, t),\n y: approach(state.renderCursor.y, target.y, t),\n }\n moving = true\n }\n\n for (const [nodeId, rect] of state.transforms) {\n const current = state.renderTransforms.get(nodeId)\n if (!current) {\n state.renderTransforms.set(nodeId, rect)\n transformsMoved = true\n continue\n }\n if (sameRect(current, rect)) continue\n state.renderTransforms.set(nodeId, {\n x: approach(current.x, rect.x, t),\n y: approach(current.y, rect.y, t),\n // Size comes from a resize rather than a drag and rarely differs\n // frame to frame, but easing it keeps a live resize as smooth.\n width: approach(current.width, rect.width, t),\n height: approach(current.height, rect.height, t),\n })\n transformsMoved = true\n moving = true\n }\n }\n\n if (transformsMoved) this.invalidateTransforms()\n if (moving) this.stale = true\n return moving\n }\n\n getPeers = (): readonly PeerRecord[] => {\n if (this.stale) {\n this.stale = false\n this.snapshot = [...this.peers.values()].map((state) => {\n const declared = state.declared\n // An imperative write, once made, owns the cursor; the roster's is a seed.\n const target = (state.live !== undefined ? state.live : declared?.cursor) ?? null\n return {\n id: state.id,\n name: declared?.name,\n color: declared?.color ?? peerColor(state.id),\n // Rendered, not raw: what {@link step} has eased to. Falls back to the\n // target so a registry nobody steps still paints something sensible.\n cursor: target === null ? null : (state.renderCursor ?? target),\n selection: declared?.selection ?? EMPTY_IDS,\n holding: declared?.holding ?? EMPTY_IDS,\n transforms: state.renderTransforms,\n }\n })\n }\n return this.snapshot\n }\n\n isEmpty = (): boolean => this.peers.size === 0\n\n subscribe = (listener: () => void): (() => void) => {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n}\n\n/**\n * How the registry asks for paint. Every write is a repaint — presence exists\n * only as paint — but they don't all cost the same canvas.\n */\nexport type PresenceRenderRequests = {\n /** Repaint the frame: roster changes and peers' live boxes move real geometry. */\n requestRender: () => void\n /** Repaint the cursor layer alone. */\n requestCursorRender: () => void\n}\n\n/** Create a presence registry. */\nexport function createPresenceRegistry(requests: PresenceRenderRequests): PresenceRegistry {\n return new Registry(requests)\n}\n","import { useCallback, useSyncExternalStore } from \"react\"\nimport { useRivetContext } from \"../context\"\nimport type { LockIntent, NodeLock } from \"../locks/registry\"\nimport type { NodeId } from \"../types\"\n\nconst UNLOCKED: NodeLock = { locked: false }\n\n/**\n * Whether a node is locked, and by whom — for the chrome rivet can't draw for\n * you: a padlock in the node's header, a \"Ada is editing\" line, a disabled\n * form inside the node body.\n *\n * rivet already refuses the interactions it owns (see the `lockedNodes` prop),\n * so this is for *your* controls. Re-renders only when this node changes hands,\n * not when any lock in the document moves.\n *\n * ```tsx\n * function MyNode({ id }: NodeProps) {\n * const { locked, holderId } = useNodeLock(id)\n * return <input disabled={locked} title={locked ? `${holderId} is editing` : \"\"} />\n * }\n * ```\n */\nexport function useNodeLock(id: NodeId): NodeLock {\n const { store } = useRivetContext()\n const getHolder = useCallback(() => store.locks.getHolder(id), [store, id])\n // Snapshot the holder id, not an object: `useSyncExternalStore` compares with\n // Object.is, so a fresh `{ locked, holderId }` every call would re-render on\n // any lock change anywhere. The record is built after the comparison.\n const holderId = useSyncExternalStore(store.locks.subscribe, getHolder, getHolder)\n return holderId === null ? UNLOCKED : { locked: true, holderId }\n}\n\n/**\n * Whether this client may do `intent` to a node right now — the same question\n * every rivet input path asks, answered through the same policy.\n *\n * Use it to keep your own affordances honest: hide a delete button on a node a\n * peer has locked, rather than letting it be pressed and refused. Always `true`\n * for an unlocked node.\n */\nexport function useNodeLockAllows(id: NodeId, intent: LockIntent): boolean {\n const { store } = useRivetContext()\n const allows = useCallback(() => store.locks.allows(id, intent), [store, id, intent])\n return useSyncExternalStore(store.locks.subscribe, allows, allows)\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { R as Rect, V as Vec2, A as AlignmentGuide, E as EdgeChange,
|
|
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-
|
|
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 { d as AnchorRecord, f as AnchorRegistrationOptions, b as AnchorOptions, R as ResolvedAnchorOptions, e as AnchorRegistration } from './registry-
|
|
5
|
-
import { h as PresenceRegistry, d as LockRegistry, f as Peer, a as LocalPresence, g as PresenceOptions, C as CanInteractWithLocked } from './use-node-lock-
|
|
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-
|
|
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';
|
|
7
7
|
import { ResolvedLane, ViewportElements, ResolvedSwimlaneGroup, ResolvedMargin } from './swimlane/index.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
@@ -559,6 +559,13 @@ type RivetStore = {
|
|
|
559
559
|
/** Ask the runtime to schedule a canvas frame. */
|
|
560
560
|
requestRender: () => void;
|
|
561
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;
|
|
562
569
|
};
|
|
563
570
|
/** Create a graph store. The public entry point; the class is an implementation detail. */
|
|
564
571
|
declare function createRivetStore(init: RivetStoreInit): RivetStore;
|
|
@@ -1066,6 +1073,20 @@ type RivetInstance<TNodeData = unknown, TEdgeData = unknown> = RivetControls & {
|
|
|
1066
1073
|
toObject: () => RivetSnapshot<TNodeData, TEdgeData>;
|
|
1067
1074
|
getNodes: () => RivetNode<TNodeData>[];
|
|
1068
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;
|
|
1069
1090
|
setNodes: (nodes: Updater<RivetNode<TNodeData>[]>) => void;
|
|
1070
1091
|
addNodes: (nodes: RivetNode<TNodeData> | RivetNode<TNodeData>[]) => void;
|
|
1071
1092
|
updateNode: (id: NodeId, patch: Updater<RivetNode<TNodeData>>) => void;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parseAnchorHandleId, ANCHOR_SIDES, anchorDotHandleId, strayPlacementPct, anchorHandleId, anchorAutoHandleId, parseAnchorAutoHandleId, normalizeAnchorConnection, computeAnchorGeometry, anchorGeometryEqual, DEFAULT_ANCHOR_OPTIONS } from './chunk-AXT35ZJV.js';
|
|
2
|
-
import { createPresenceRegistry, createLockRegistry, DEFAULT_PRESENCE_OPTIONS, peerColor } from './chunk-
|
|
3
|
-
export { LOCK_DEFAULT_REFUSED, useNodeLock, useNodeLockAllows } from './chunk-
|
|
4
|
-
import { clamp, clampNodeToLanes, swimlaneChromeAt, SWIMLANE_LABEL_WIDTH, resolveSwimlanes, flattenLanes, resolveMargin, requiredLaneHeight, withAlpha } from './chunk-6XRQSAQT.js';
|
|
2
|
+
import { createPresenceRegistry, createLockRegistry, DEFAULT_PRESENCE_OPTIONS, peerColor } from './chunk-A52HZWLH.js';
|
|
3
|
+
export { LOCK_DEFAULT_REFUSED, useNodeLock, useNodeLockAllows } from './chunk-A52HZWLH.js';
|
|
5
4
|
import { useRivetContext, RivetNodeContext, useRivetNodeContext, useRivetControls, RivetContext, useRivetViewport } from './chunk-VQYN27OK.js';
|
|
6
5
|
export { useRivetContext, useRivetControls, useRivetFocusedNode, useRivetHistory, useRivetViewport } from './chunk-VQYN27OK.js';
|
|
6
|
+
import { clamp, clampNodeToLanes, swimlaneChromeAt, SWIMLANE_LABEL_WIDTH, resolveSwimlanes, flattenLanes, resolveMargin, requiredLaneHeight, withAlpha } from './chunk-6XRQSAQT.js';
|
|
7
7
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
8
8
|
import { memo, useRef, useState, useCallback, useSyncExternalStore, useMemo, useEffect, Fragment as Fragment$1, useId, useLayoutEffect } from 'react';
|
|
9
9
|
|
|
@@ -1025,7 +1025,10 @@ var RivetGraphStore = class {
|
|
|
1025
1025
|
anchors = /* @__PURE__ */ new Map();
|
|
1026
1026
|
// Arrow-wrapped so the (later-initialized) requestRender field is looked up
|
|
1027
1027
|
// at call time, not while this field is being constructed.
|
|
1028
|
-
presence = createPresenceRegistry(
|
|
1028
|
+
presence = createPresenceRegistry({
|
|
1029
|
+
requestRender: () => this.requestRender(),
|
|
1030
|
+
requestCursorRender: () => this.requestCursorRender()
|
|
1031
|
+
});
|
|
1029
1032
|
locks = createLockRegistry(() => this.requestRender());
|
|
1030
1033
|
viewport;
|
|
1031
1034
|
viewportClamp = null;
|
|
@@ -1072,6 +1075,12 @@ var RivetGraphStore = class {
|
|
|
1072
1075
|
peerWorldVersion = -1;
|
|
1073
1076
|
renderRequester = () => {
|
|
1074
1077
|
};
|
|
1078
|
+
/**
|
|
1079
|
+
* Null until a runtime claims it, and then `requestCursorRender` narrows to
|
|
1080
|
+
* the cursor layer. Falling back to a full frame is the safe default: a
|
|
1081
|
+
* consumer without the layer mounted still gets its paint.
|
|
1082
|
+
*/
|
|
1083
|
+
cursorRenderRequester = null;
|
|
1075
1084
|
constructor(init) {
|
|
1076
1085
|
this.viewport = init.viewport;
|
|
1077
1086
|
for (const node of init.nodes) this.writeNode(node.id, node);
|
|
@@ -1419,8 +1428,14 @@ var RivetGraphStore = class {
|
|
|
1419
1428
|
getNodeRect = (id) => {
|
|
1420
1429
|
const origin = this.getNodeWorldPosition(id);
|
|
1421
1430
|
const live = this.heldNodeIds.has(id) ? void 0 : this.presence.getNodeTransforms().get(id);
|
|
1422
|
-
|
|
1423
|
-
|
|
1431
|
+
if (live) return { x: origin.x, y: origin.y, width: live.width, height: live.height };
|
|
1432
|
+
const node = this.nodes.get(id);
|
|
1433
|
+
return {
|
|
1434
|
+
x: origin.x,
|
|
1435
|
+
y: origin.y,
|
|
1436
|
+
width: node?.width ?? node?.size?.width ?? DEFAULT_NODE_SIZE.width,
|
|
1437
|
+
height: node?.height ?? node?.size?.height ?? DEFAULT_NODE_SIZE.height
|
|
1438
|
+
};
|
|
1424
1439
|
};
|
|
1425
1440
|
getNodeDepth = (id) => nodeDepth(this.nodes, id);
|
|
1426
1441
|
getDescendantIds = (id) => descendantIds(this.nodes, id);
|
|
@@ -2010,6 +2025,13 @@ var RivetGraphStore = class {
|
|
|
2010
2025
|
bindRenderRequester = (fn) => {
|
|
2011
2026
|
this.renderRequester = fn;
|
|
2012
2027
|
};
|
|
2028
|
+
requestCursorRender = () => {
|
|
2029
|
+
if (this.cursorRenderRequester) this.cursorRenderRequester();
|
|
2030
|
+
else this.renderRequester();
|
|
2031
|
+
};
|
|
2032
|
+
bindCursorRenderRequester = (fn) => {
|
|
2033
|
+
this.cursorRenderRequester = fn;
|
|
2034
|
+
};
|
|
2013
2035
|
// --- reconnect gateway (runtime-bound; consulted by <Handle>) ------------
|
|
2014
2036
|
reconnectDelegate = null;
|
|
2015
2037
|
getSelectedEdge = () => this.selectedEdgeId;
|
|
@@ -3043,7 +3065,7 @@ function createPanController(deps) {
|
|
|
3043
3065
|
|
|
3044
3066
|
// src/input/resize-controller.ts
|
|
3045
3067
|
function createResizeController(deps) {
|
|
3046
|
-
const { pane, bgCanvas, size, edgeRenderer, foregroundRenderer, schedule } = deps;
|
|
3068
|
+
const { pane, bgCanvas, size, edgeRenderer, foregroundRenderer, onResize, schedule } = deps;
|
|
3047
3069
|
const resize = () => {
|
|
3048
3070
|
const rect = pane.getBoundingClientRect();
|
|
3049
3071
|
size.width = rect.width;
|
|
@@ -3055,6 +3077,7 @@ function createResizeController(deps) {
|
|
|
3055
3077
|
bgCanvas.style.height = `${rect.height}px`;
|
|
3056
3078
|
edgeRenderer.resize(rect.width, rect.height, size.dpr);
|
|
3057
3079
|
foregroundRenderer.resize(rect.width, rect.height, size.dpr);
|
|
3080
|
+
onResize?.();
|
|
3058
3081
|
schedule();
|
|
3059
3082
|
};
|
|
3060
3083
|
const observer = new ResizeObserver(resize);
|
|
@@ -3199,7 +3222,7 @@ function drawCursor(ctx, point, color, name) {
|
|
|
3199
3222
|
ctx.fillStyle = "#ffffff";
|
|
3200
3223
|
ctx.fillText(name, x + LABEL_PADDING_X, y + LABEL_HEIGHT / 2);
|
|
3201
3224
|
}
|
|
3202
|
-
function
|
|
3225
|
+
function drawPeerOutlines(ctx, peers, { viewport, size, getNodeRect, locks }) {
|
|
3203
3226
|
if (peers.length === 0 && locks.size === 0) return;
|
|
3204
3227
|
ctx.save();
|
|
3205
3228
|
ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
|
|
@@ -3232,16 +3255,18 @@ function drawPresence(ctx, peers, { viewport, size, getNodeRect, renderCursors,
|
|
|
3232
3255
|
drawNodeOutline(ctx, screen, declared.get(holderId) ?? peerColor(holderId), "held");
|
|
3233
3256
|
}
|
|
3234
3257
|
}
|
|
3235
|
-
if (renderCursors) {
|
|
3236
|
-
for (const peer of peers) {
|
|
3237
|
-
if (!peer.cursor) continue;
|
|
3238
|
-
const point = worldToScreen(peer.cursor, viewport);
|
|
3239
|
-
if (!onScreen({ ...point, width: 0, height: 0 }, size)) continue;
|
|
3240
|
-
drawCursor(ctx, point, peer.color, peer.name);
|
|
3241
|
-
}
|
|
3242
|
-
}
|
|
3243
3258
|
ctx.restore();
|
|
3244
3259
|
}
|
|
3260
|
+
function drawPeerCursors(ctx, peers, { viewport, size }) {
|
|
3261
|
+
ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
|
|
3262
|
+
ctx.clearRect(0, 0, size.width, size.height);
|
|
3263
|
+
for (const peer of peers) {
|
|
3264
|
+
if (!peer.cursor) continue;
|
|
3265
|
+
const point = worldToScreen(peer.cursor, viewport);
|
|
3266
|
+
if (!onScreen({ ...point, width: 0, height: 0 }, size)) continue;
|
|
3267
|
+
drawCursor(ctx, point, peer.color, peer.name);
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3245
3270
|
|
|
3246
3271
|
// src/renderer/swimlane.ts
|
|
3247
3272
|
var DEFAULTS = {
|
|
@@ -3318,7 +3343,7 @@ function drawEndpointBubbles(ctx, ends, size) {
|
|
|
3318
3343
|
}
|
|
3319
3344
|
function useRivetRuntime(params) {
|
|
3320
3345
|
const { store, paneRef, nodeContainerRef } = params;
|
|
3321
|
-
const { backgroundCanvasRef, edgeCanvasRef, foregroundCanvasRef } = params;
|
|
3346
|
+
const { backgroundCanvasRef, edgeCanvasRef, foregroundCanvasRef, cursorCanvasRef } = params;
|
|
3322
3347
|
const { swimlaneLanes, scrollToPan, zoomSpeed } = params;
|
|
3323
3348
|
const { minZoom, maxZoom, gridGap, edgeTypes, defaultEdgeOptions, anchorOptions } = params;
|
|
3324
3349
|
const { edgeRenderer: createEdgeRenderer } = params;
|
|
@@ -3357,6 +3382,7 @@ function useRivetRuntime(params) {
|
|
|
3357
3382
|
const size = sizeRef.current;
|
|
3358
3383
|
let frame = 0;
|
|
3359
3384
|
let dirty = true;
|
|
3385
|
+
let cursorsDirty = true;
|
|
3360
3386
|
let hoveredEdgeId = null;
|
|
3361
3387
|
let peerPositioned = /* @__PURE__ */ new Set();
|
|
3362
3388
|
const positionPeerNodes = (worldPositions) => {
|
|
@@ -3383,6 +3409,39 @@ function useRivetRuntime(params) {
|
|
|
3383
3409
|
}
|
|
3384
3410
|
peerPositioned = current;
|
|
3385
3411
|
};
|
|
3412
|
+
let cursorCanvas = null;
|
|
3413
|
+
let cursorCtx = null;
|
|
3414
|
+
let cursorAllocated = false;
|
|
3415
|
+
const sizeCursorLayer = () => {
|
|
3416
|
+
if (!cursorCanvas || !cursorAllocated) return;
|
|
3417
|
+
cursorCanvas.width = Math.max(1, Math.round(size.width * size.dpr));
|
|
3418
|
+
cursorCanvas.height = Math.max(1, Math.round(size.height * size.dpr));
|
|
3419
|
+
cursorCanvas.style.width = `${size.width}px`;
|
|
3420
|
+
cursorCanvas.style.height = `${size.height}px`;
|
|
3421
|
+
};
|
|
3422
|
+
const drawCursorLayer = () => {
|
|
3423
|
+
const canvas = cursorCanvasRef.current;
|
|
3424
|
+
if (canvas !== cursorCanvas) {
|
|
3425
|
+
cursorCanvas = canvas;
|
|
3426
|
+
cursorCtx = canvas?.getContext("2d") ?? null;
|
|
3427
|
+
cursorAllocated = false;
|
|
3428
|
+
}
|
|
3429
|
+
if (!cursorCtx) return;
|
|
3430
|
+
const peers = store.presence.getPeers();
|
|
3431
|
+
const wanted = peers.some((peer) => peer.cursor);
|
|
3432
|
+
if (!wanted && !cursorAllocated) return;
|
|
3433
|
+
if (!cursorAllocated) {
|
|
3434
|
+
cursorAllocated = true;
|
|
3435
|
+
sizeCursorLayer();
|
|
3436
|
+
}
|
|
3437
|
+
drawPeerCursors(cursorCtx, peers, { viewport: store.getViewport(), size });
|
|
3438
|
+
};
|
|
3439
|
+
const stepPresence = () => {
|
|
3440
|
+
const version = store.presence.getTransformVersion();
|
|
3441
|
+
if (!store.presence.step(performance.now())) return;
|
|
3442
|
+
if (store.presence.getTransformVersion() !== version) schedule();
|
|
3443
|
+
else scheduleCursors();
|
|
3444
|
+
};
|
|
3386
3445
|
const anchorPlacement = (nodeId, anchorId, side) => {
|
|
3387
3446
|
const record = store.anchors.get(handleKey(nodeId, anchorId));
|
|
3388
3447
|
return record?.geometry ? strayPlacementPct(record.geometry, side, anchorOptions) : null;
|
|
@@ -3400,7 +3459,7 @@ function useRivetRuntime(params) {
|
|
|
3400
3459
|
});
|
|
3401
3460
|
const render = () => {
|
|
3402
3461
|
zoom.step();
|
|
3403
|
-
|
|
3462
|
+
stepPresence();
|
|
3404
3463
|
const viewport = store.getViewport();
|
|
3405
3464
|
layerHint.apply(viewportToCss(viewport));
|
|
3406
3465
|
bgCtx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
|
|
@@ -3438,14 +3497,14 @@ function useRivetRuntime(params) {
|
|
|
3438
3497
|
}
|
|
3439
3498
|
}
|
|
3440
3499
|
if (fgCtx && (!store.presence.isEmpty() || store.locks.size() > 0)) {
|
|
3441
|
-
|
|
3500
|
+
drawPeerOutlines(fgCtx, store.presence.getPeers(), {
|
|
3442
3501
|
viewport,
|
|
3443
3502
|
size,
|
|
3444
3503
|
getNodeRect: (id) => store.nodes.has(id) ? store.getNodeRect(id) : null,
|
|
3445
|
-
renderCursors: reconnectRef.current.presenceOptions.renderCursors,
|
|
3446
3504
|
locks: store.locks.getLocks()
|
|
3447
3505
|
});
|
|
3448
3506
|
}
|
|
3507
|
+
drawCursorLayer();
|
|
3449
3508
|
if (edgeList.some((edge) => edge.animated ?? defaultEdgeOptions?.animated)) schedule();
|
|
3450
3509
|
const rect = visibleWorldRect(viewport, size.width, size.height);
|
|
3451
3510
|
const margin = CULL_MARGIN_PX2 / viewport.zoom;
|
|
@@ -3477,16 +3536,29 @@ function useRivetRuntime(params) {
|
|
|
3477
3536
|
};
|
|
3478
3537
|
const tick = () => {
|
|
3479
3538
|
frame = 0;
|
|
3480
|
-
if (
|
|
3481
|
-
|
|
3482
|
-
|
|
3539
|
+
if (dirty) {
|
|
3540
|
+
dirty = false;
|
|
3541
|
+
cursorsDirty = false;
|
|
3542
|
+
render();
|
|
3543
|
+
return;
|
|
3544
|
+
}
|
|
3545
|
+
if (!cursorsDirty) return;
|
|
3546
|
+
cursorsDirty = false;
|
|
3547
|
+
stepPresence();
|
|
3548
|
+
drawCursorLayer();
|
|
3483
3549
|
};
|
|
3484
3550
|
const schedule = () => {
|
|
3485
3551
|
dirty = true;
|
|
3486
3552
|
if (frame) return;
|
|
3487
3553
|
frame = requestAnimationFrame(tick);
|
|
3488
3554
|
};
|
|
3555
|
+
const scheduleCursors = () => {
|
|
3556
|
+
cursorsDirty = true;
|
|
3557
|
+
if (frame) return;
|
|
3558
|
+
frame = requestAnimationFrame(tick);
|
|
3559
|
+
};
|
|
3489
3560
|
store.bindRenderRequester(schedule);
|
|
3561
|
+
store.bindCursorRenderRequester(scheduleCursors);
|
|
3490
3562
|
const setEdgeHover = (id) => {
|
|
3491
3563
|
if (hoveredEdgeId === id) return;
|
|
3492
3564
|
hoveredEdgeId = id;
|
|
@@ -3696,6 +3768,7 @@ function useRivetRuntime(params) {
|
|
|
3696
3768
|
size,
|
|
3697
3769
|
edgeRenderer,
|
|
3698
3770
|
foregroundRenderer,
|
|
3771
|
+
onResize: sizeCursorLayer,
|
|
3699
3772
|
schedule
|
|
3700
3773
|
});
|
|
3701
3774
|
pane.addEventListener("wheel", zoom.onWheel, { passive: false });
|
|
@@ -3712,6 +3785,7 @@ function useRivetRuntime(params) {
|
|
|
3712
3785
|
resizer.dispose();
|
|
3713
3786
|
store.bindRenderRequester(() => {
|
|
3714
3787
|
});
|
|
3788
|
+
store.bindCursorRenderRequester(null);
|
|
3715
3789
|
store.bindReconnectDelegate(null);
|
|
3716
3790
|
unsubscribeFocus();
|
|
3717
3791
|
if (focusRetryFrame) cancelAnimationFrame(focusRetryFrame);
|
|
@@ -3739,6 +3813,7 @@ function useRivetRuntime(params) {
|
|
|
3739
3813
|
backgroundCanvasRef,
|
|
3740
3814
|
edgeCanvasRef,
|
|
3741
3815
|
foregroundCanvasRef,
|
|
3816
|
+
cursorCanvasRef,
|
|
3742
3817
|
scrollToPan,
|
|
3743
3818
|
zoomSpeed,
|
|
3744
3819
|
minZoom,
|
|
@@ -5189,6 +5264,7 @@ function Rivet({
|
|
|
5189
5264
|
const backgroundCanvasRef = useRef(null);
|
|
5190
5265
|
const edgeCanvasRef = useRef(null);
|
|
5191
5266
|
const foregroundCanvasRef = useRef(null);
|
|
5267
|
+
const cursorCanvasRef = useRef(null);
|
|
5192
5268
|
useEffect(() => {
|
|
5193
5269
|
if (peers === void 0) return;
|
|
5194
5270
|
store.presence.setPeers(peers);
|
|
@@ -5213,11 +5289,11 @@ function Rivet({
|
|
|
5213
5289
|
backgroundCanvasRef,
|
|
5214
5290
|
edgeCanvasRef,
|
|
5215
5291
|
foregroundCanvasRef,
|
|
5292
|
+
cursorCanvasRef,
|
|
5216
5293
|
swimlaneLanes: swimlanes.lanes,
|
|
5217
5294
|
edgeTypes: cfg.edgeTypes,
|
|
5218
5295
|
defaultEdgeOptions: cfg.defaultEdgeOptions,
|
|
5219
5296
|
anchorOptions: cfg.anchorOptions,
|
|
5220
|
-
presenceOptions: cfg.presenceOptions,
|
|
5221
5297
|
edgeRenderer: renderer,
|
|
5222
5298
|
edgesReconnectable,
|
|
5223
5299
|
isValidConnection: cfg.isValidConnection,
|
|
@@ -5306,6 +5382,7 @@ function Rivet({
|
|
|
5306
5382
|
/* @__PURE__ */ jsx("canvas", { ref: edgeCanvasRef, style: canvasStyle }),
|
|
5307
5383
|
/* @__PURE__ */ jsx(NodeLayer, { containerRef: nodeContainerRef, visibleIds }),
|
|
5308
5384
|
/* @__PURE__ */ jsx("canvas", { ref: foregroundCanvasRef, style: canvasStyle }),
|
|
5385
|
+
cfg.presenceOptions.renderCursors && /* @__PURE__ */ jsx("canvas", { ref: cursorCanvasRef, style: canvasStyle }),
|
|
5309
5386
|
/* @__PURE__ */ jsx(EdgeLabelLayer, {}),
|
|
5310
5387
|
/* @__PURE__ */ jsx(SwimlaneOverlay, {}),
|
|
5311
5388
|
children,
|
|
@@ -5365,6 +5442,10 @@ function buildInstance(store, controls, getViewportElements) {
|
|
|
5365
5442
|
),
|
|
5366
5443
|
getNodes,
|
|
5367
5444
|
getNode: (id) => store.nodes.get(id),
|
|
5445
|
+
// The store's own `getNodeRect` answers for any id, falling back to the
|
|
5446
|
+
// origin and the default size; a public caller needs to tell "it's there,
|
|
5447
|
+
// at 0,0" from "it isn't there".
|
|
5448
|
+
getNodeRect: (id) => store.nodes.has(id) ? store.getNodeRect(id) : null,
|
|
5368
5449
|
setNodes,
|
|
5369
5450
|
addNodes: (nodes) => {
|
|
5370
5451
|
for (const node of Array.isArray(nodes) ? nodes : [nodes]) store.addNode(node);
|