@kolosal-ai/rivet 0.6.0 → 0.7.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.
Files changed (38) hide show
  1. package/dist/anchor/index.d.ts +3 -3
  2. package/dist/anchor/index.js +1 -36
  3. package/dist/anchor/index.js.map +1 -1
  4. package/dist/chunk-B3YKB2QN.js +2 -0
  5. package/dist/chunk-B3YKB2QN.js.map +1 -0
  6. package/dist/chunk-CVSEMPYO.js +2 -0
  7. package/dist/chunk-CVSEMPYO.js.map +1 -0
  8. package/dist/chunk-NLE6BOPW.js +2 -0
  9. package/dist/chunk-NLE6BOPW.js.map +1 -0
  10. package/dist/chunk-RDSWIH3F.js +2 -0
  11. package/dist/chunk-RDSWIH3F.js.map +1 -0
  12. package/dist/chunk-X4VW3W4W.js +2 -0
  13. package/dist/chunk-X4VW3W4W.js.map +1 -0
  14. package/dist/chunk-XTQGGEUS.js +2 -0
  15. package/dist/chunk-XTQGGEUS.js.map +1 -0
  16. package/dist/index.d.ts +80 -37
  17. package/dist/index.js +1 -6213
  18. package/dist/index.js.map +1 -1
  19. package/dist/presence/index.d.ts +3 -3
  20. package/dist/presence/index.js +1 -37
  21. package/dist/presence/index.js.map +1 -1
  22. package/dist/{registry-DPfWNdkC.d.ts → registry-Vc6r10MJ.d.ts} +1 -1
  23. package/dist/svg/index.d.ts +2 -2
  24. package/dist/svg/index.js +2 -128
  25. package/dist/svg/index.js.map +1 -1
  26. package/dist/swimlane/index.d.ts +4 -4
  27. package/dist/swimlane/index.js +1 -2
  28. package/dist/{types-tkMbskgC.d.ts → types-BPtqcAVr.d.ts} +45 -1
  29. package/dist/{use-node-lock-CKtOwBrI.d.ts → use-node-lock-BFQQAr8M.d.ts} +1 -1
  30. package/package.json +1 -1
  31. package/dist/chunk-6XRQSAQT.js +0 -148
  32. package/dist/chunk-6XRQSAQT.js.map +0 -1
  33. package/dist/chunk-76GHEXDP.js +0 -152
  34. package/dist/chunk-76GHEXDP.js.map +0 -1
  35. package/dist/chunk-TT2N7KWU.js +0 -676
  36. package/dist/chunk-TT2N7KWU.js.map +0 -1
  37. package/dist/chunk-VQYN27OK.js +0 -36
  38. package/dist/chunk-VQYN27OK.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { CSSProperties, ReactNode } from 'react';
2
- import { A as AnchorRef } from '../registry-DPfWNdkC.js';
3
- export { a as ANCHOR_SIDES, b as AnchorGeometry, c as AnchorOptions, d as AnchorPoint, e as AnchorRecord, f as AnchorRegistration, g as AnchorRegistrationOptions, h as AnchorStrayPolicy, D as DEFAULT_ANCHOR_OPTIONS, R as ResolvedAnchorOptions, i as anchorAutoHandleId, j as anchorDotHandleId, k as anchorHandleId, l as anchorRect, m as computeAnchorGeometry, n as findSelectableAnchor, o as normalizeAnchorConnection, p as orderedSelectableAnchors, q as parseAnchorAutoHandleId, r as parseAnchorDotHandleId, s as parseAnchorHandleId, t as strayPlacementPct, u as strayPoint } from '../registry-DPfWNdkC.js';
4
- import '../types-tkMbskgC.js';
2
+ import { A as AnchorRef } from '../registry-Vc6r10MJ.js';
3
+ export { a as ANCHOR_SIDES, b as AnchorGeometry, c as AnchorOptions, d as AnchorPoint, e as AnchorRecord, f as AnchorRegistration, g as AnchorRegistrationOptions, h as AnchorStrayPolicy, D as DEFAULT_ANCHOR_OPTIONS, R as ResolvedAnchorOptions, i as anchorAutoHandleId, j as anchorDotHandleId, k as anchorHandleId, l as anchorRect, m as computeAnchorGeometry, n as findSelectableAnchor, o as normalizeAnchorConnection, p as orderedSelectableAnchors, q as parseAnchorAutoHandleId, r as parseAnchorDotHandleId, s as parseAnchorHandleId, t as strayPlacementPct, u as strayPoint } from '../registry-Vc6r10MJ.js';
4
+ import '../types-BPtqcAVr.js';
5
5
 
6
6
  type AnchorProps = {
7
7
  /**
@@ -1,37 +1,2 @@
1
- export { ANCHOR_SIDES, DEFAULT_ANCHOR_OPTIONS, anchorAutoHandleId, anchorDotHandleId, anchorHandleId, anchorRect, computeAnchorGeometry, findSelectableAnchor, normalizeAnchorConnection, orderedSelectableAnchors, parseAnchorAutoHandleId, parseAnchorDotHandleId, parseAnchorHandleId, strayPlacementPct, strayPoint } from '../chunk-76GHEXDP.js';
2
- import { useRivetContext, useRivetNodeContext } from '../chunk-VQYN27OK.js';
3
- import { useRef, useLayoutEffect, useSyncExternalStore } from 'react';
4
- import { jsx } from 'react/jsx-runtime';
5
-
6
- function Anchor({
7
- id,
8
- color,
9
- selectable,
10
- data,
11
- className,
12
- style,
13
- children
14
- }) {
15
- const { store } = useRivetContext();
16
- const { nodeId } = useRivetNodeContext();
17
- const ref = useRef(null);
18
- useLayoutEffect(() => {
19
- const el = ref.current;
20
- if (!el) return;
21
- store.registerAnchor(nodeId, id, el, { color, selectable, data });
22
- return () => store.detachAnchor(nodeId, id);
23
- }, [store, nodeId, id, color, selectable, data]);
24
- return /* @__PURE__ */ jsx("span", { ref, "data-rivet-anchor": id, className, style, children });
25
- }
26
- function useSelectedAnchors() {
27
- const { store } = useRivetContext();
28
- return useSyncExternalStore(
29
- store.subscribeAnchorSelection,
30
- store.getSelectedAnchors,
31
- store.getSelectedAnchors
32
- );
33
- }
34
-
35
- export { Anchor, useSelectedAnchors };
36
- //# sourceMappingURL=index.js.map
1
+ export{a as ANCHOR_SIDES,c as DEFAULT_ANCHOR_OPTIONS,f as anchorAutoHandleId,h as anchorDotHandleId,d as anchorHandleId,m as anchorRect,k as computeAnchorGeometry,b as findSelectableAnchor,j as normalizeAnchorConnection,l as orderedSelectableAnchors,g as parseAnchorAutoHandleId,i as parseAnchorDotHandleId,e as parseAnchorHandleId,o as strayPlacementPct,n as strayPoint}from'../chunk-X4VW3W4W.js';import'../chunk-B3YKB2QN.js';import {c,k}from'../chunk-NLE6BOPW.js';import {useRef,useLayoutEffect,useSyncExternalStore}from'react';import {jsx}from'react/jsx-runtime';function v({id:e,color:n,selectable:c$1,data:s,className:A,style:i,children:l}){let{store:r}=c(),{nodeId:t}=k(),a=useRef(null);return useLayoutEffect(()=>{let p=a.current;if(p)return r.registerAnchor(t,e,p,{color:n,selectable:c$1,data:s}),()=>r.detachAnchor(t,e)},[r,t,e,n,c$1,s]),jsx("span",{ref:a,"data-rivet-anchor":e,className:A,style:i,children:l})}function T(){let{store:e}=c();return useSyncExternalStore(e.subscribeAnchorSelection,e.getSelectedAnchors,e.getSelectedAnchors)}export{v as Anchor,T as useSelectedAnchors};//# sourceMappingURL=index.js.map
37
2
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/anchor.tsx","../../src/hooks/use-selected-anchors.ts"],"names":[],"mappings":";;;;;AAqCO,SAAS,MAAA,CAAO;AAAA,EACrB,EAAA;AAAA,EACA,KAAA;AAAA,EACA,UAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA,EACA,KAAA;AAAA,EACA;AACF,CAAA,EAAoC;AAClC,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,eAAA,EAAgB;AAClC,EAAA,MAAM,EAAE,MAAA,EAAO,GAAI,mBAAA,EAAoB;AACvC,EAAA,MAAM,GAAA,GAAM,OAAwB,IAAI,CAAA;AAExC,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,MAAM,KAAK,GAAA,CAAI,OAAA;AACf,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,KAAA,CAAM,cAAA,CAAe,QAAQ,EAAA,EAAI,EAAA,EAAI,EAAE,KAAA,EAAO,UAAA,EAAY,MAAM,CAAA;AAChE,IAAA,OAAO,MAAM,KAAA,CAAM,YAAA,CAAa,MAAA,EAAQ,EAAE,CAAA;AAAA,EAC5C,CAAA,EAAG,CAAC,KAAA,EAAO,MAAA,EAAQ,IAAI,KAAA,EAAO,UAAA,EAAY,IAAI,CAAC,CAAA;AAE/C,EAAA,2BACG,MAAA,EAAA,EAAK,GAAA,EAAU,qBAAmB,EAAA,EAAI,SAAA,EAAsB,OAC1D,QAAA,EACH,CAAA;AAEJ;AC1CO,SAAS,kBAAA,GAA2C;AACzD,EAAA,MAAM,EAAE,KAAA,EAAM,GAAI,eAAA,EAAgB;AAClC,EAAA,OAAO,oBAAA;AAAA,IACL,KAAA,CAAM,wBAAA;AAAA,IACN,KAAA,CAAM,kBAAA;AAAA,IACN,KAAA,CAAM;AAAA,GACR;AACF","file":"index.js","sourcesContent":["import { type CSSProperties, type ReactNode, useLayoutEffect, useRef } from \"react\"\nimport { useRivetContext, useRivetNodeContext } from \"../context\"\n\nexport type AnchorProps = {\n /**\n * Identifies the anchor within its node. Persisted edge handle ids derive\n * from it, so keep it stable across renders and sessions.\n */\n id: string\n /** Accent color for the anchor's grab dots and stray handles. */\n color?: string\n /**\n * Let a click on the wrapped content select the anchor. Off by default:\n * inline anchors usually sit in editable text, where a press has to reach the\n * caret rather than be claimed as a selection.\n */\n selectable?: boolean\n /** Opaque payload kept on the anchor record and handed back with it. */\n data?: unknown\n className?: string\n style?: CSSProperties\n children: ReactNode\n}\n\n/**\n * Declarative anchor registration — wraps any inline content rendered inside a\n * node and binds it as a connectable point. Sugar over the imperative registry\n * (`RivetInstance.registerAnchor`) for consumers whose React tree owns the\n * DOM; the wrapper is a plain unstyled `<span>`, so it doesn't affect inline\n * layout. Rivet measures the span's line boxes and renders grab dots and stray\n * perimeter handles as overlay chrome — nothing is injected here.\n *\n * Unmounting **detaches** the anchor (stale retention: geometry and edges stay\n * put, for display/editor swaps) rather than unregistering it. Removal is an\n * explicit consumer action via `RivetInstance.unregisterAnchor` — e.g. when\n * the content the anchor referred to is edited away for good.\n */\nexport function Anchor({\n id,\n color,\n selectable,\n data,\n className,\n style,\n children,\n}: AnchorProps): React.ReactElement {\n const { store } = useRivetContext()\n const { nodeId } = useRivetNodeContext()\n const ref = useRef<HTMLSpanElement>(null)\n\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n store.registerAnchor(nodeId, id, el, { color, selectable, data })\n return () => store.detachAnchor(nodeId, id)\n }, [store, nodeId, id, color, selectable, data])\n\n return (\n <span ref={ref} data-rivet-anchor={id} className={className} style={style}>\n {children}\n </span>\n )\n}\n","import { useSyncExternalStore } from \"react\"\nimport type { AnchorRef } from \"../anchor/registry\"\nimport { useRivetContext } from \"../context\"\n\n/**\n * The anchors this client has selected — a click on a shape inside an\n * `<SvgArtwork>`, or on any `<Anchor selectable>` content.\n *\n * Anchor selection is view state, like hover and focus. An anchor isn't in the\n * graph — it's read off rendered DOM — so selecting one emits no change,\n * records nothing to history, and never appears in `RivetSelection`. That also\n * means it can't be driven from props: this reports what the pointer did, and\n * `setSelectedAnchors` on `useRivet()` is how you set it yourself.\n *\n * ```tsx\n * const [anchor] = useSelectedAnchors()\n * const record = anchor && rivet.getAnchor(anchor.nodeId, anchor.anchorId)\n * return <Inspector shape={record?.data} />\n * ```\n */\nexport function useSelectedAnchors(): readonly AnchorRef[] {\n const { store } = useRivetContext()\n return useSyncExternalStore(\n store.subscribeAnchorSelection,\n store.getSelectedAnchors,\n store.getSelectedAnchors,\n )\n}\n"]}
1
+ {"version":3,"sources":["../../src/components/anchor.tsx","../../src/hooks/use-selected-anchors.ts"],"names":["Anchor","id","color","selectable","data","className","style","children","store","useRivetContext","nodeId","useRivetNodeContext","ref","useRef","useLayoutEffect","el","jsx","useSelectedAnchors","useSyncExternalStore"],"mappings":"sjBAqCO,SAASA,CAAAA,CAAO,CACrB,EAAA,CAAAC,CAAAA,CACA,KAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,GAAAA,CACA,IAAA,CAAAC,CAAAA,CACA,SAAA,CAAAC,CAAAA,CACA,KAAA,CAAAC,CAAAA,CACA,QAAA,CAAAC,CACF,CAAA,CAAoC,CAClC,GAAM,CAAE,MAAAC,CAAM,CAAA,CAAIC,CAAAA,EAAgB,CAC5B,CAAE,MAAA,CAAAC,CAAO,CAAA,CAAIC,CAAAA,EAAoB,CACjCC,CAAAA,CAAMC,MAAAA,CAAwB,IAAI,CAAA,CAExC,OAAAC,eAAAA,CAAgB,IAAM,CACpB,IAAMC,CAAAA,CAAKH,CAAAA,CAAI,OAAA,CACf,GAAKG,CAAAA,CACL,OAAAP,CAAAA,CAAM,cAAA,CAAeE,CAAAA,CAAQT,CAAAA,CAAIc,CAAAA,CAAI,CAAE,KAAA,CAAAb,CAAAA,CAAO,UAAA,CAAAC,GAAAA,CAAY,IAAA,CAAAC,CAAK,CAAC,CAAA,CACzD,IAAMI,CAAAA,CAAM,YAAA,CAAaE,CAAAA,CAAQT,CAAE,CAC5C,CAAA,CAAG,CAACO,CAAAA,CAAOE,CAAAA,CAAQT,CAAAA,CAAIC,CAAAA,CAAOC,GAAAA,CAAYC,CAAI,CAAC,CAAA,CAG7CY,GAAAA,CAAC,MAAA,CAAA,CAAK,GAAA,CAAKJ,CAAAA,CAAK,mBAAA,CAAmBX,CAAAA,CAAI,SAAA,CAAWI,CAAAA,CAAW,KAAA,CAAOC,CAAAA,CACjE,QAAA,CAAAC,CAAAA,CACH,CAEJ,CC1CO,SAASU,CAAAA,EAA2C,CACzD,GAAM,CAAE,KAAA,CAAAT,CAAM,CAAA,CAAIC,CAAAA,EAAgB,CAClC,OAAOS,oBAAAA,CACLV,CAAAA,CAAM,wBAAA,CACNA,CAAAA,CAAM,kBAAA,CACNA,CAAAA,CAAM,kBACR,CACF","file":"index.js","sourcesContent":["import { type CSSProperties, type ReactNode, useLayoutEffect, useRef } from \"react\"\nimport { useRivetContext, useRivetNodeContext } from \"../context\"\n\nexport type AnchorProps = {\n /**\n * Identifies the anchor within its node. Persisted edge handle ids derive\n * from it, so keep it stable across renders and sessions.\n */\n id: string\n /** Accent color for the anchor's grab dots and stray handles. */\n color?: string\n /**\n * Let a click on the wrapped content select the anchor. Off by default:\n * inline anchors usually sit in editable text, where a press has to reach the\n * caret rather than be claimed as a selection.\n */\n selectable?: boolean\n /** Opaque payload kept on the anchor record and handed back with it. */\n data?: unknown\n className?: string\n style?: CSSProperties\n children: ReactNode\n}\n\n/**\n * Declarative anchor registration — wraps any inline content rendered inside a\n * node and binds it as a connectable point. Sugar over the imperative registry\n * (`RivetInstance.registerAnchor`) for consumers whose React tree owns the\n * DOM; the wrapper is a plain unstyled `<span>`, so it doesn't affect inline\n * layout. Rivet measures the span's line boxes and renders grab dots and stray\n * perimeter handles as overlay chrome — nothing is injected here.\n *\n * Unmounting **detaches** the anchor (stale retention: geometry and edges stay\n * put, for display/editor swaps) rather than unregistering it. Removal is an\n * explicit consumer action via `RivetInstance.unregisterAnchor` — e.g. when\n * the content the anchor referred to is edited away for good.\n */\nexport function Anchor({\n id,\n color,\n selectable,\n data,\n className,\n style,\n children,\n}: AnchorProps): React.ReactElement {\n const { store } = useRivetContext()\n const { nodeId } = useRivetNodeContext()\n const ref = useRef<HTMLSpanElement>(null)\n\n useLayoutEffect(() => {\n const el = ref.current\n if (!el) return\n store.registerAnchor(nodeId, id, el, { color, selectable, data })\n return () => store.detachAnchor(nodeId, id)\n }, [store, nodeId, id, color, selectable, data])\n\n return (\n <span ref={ref} data-rivet-anchor={id} className={className} style={style}>\n {children}\n </span>\n )\n}\n","import { useSyncExternalStore } from \"react\"\nimport type { AnchorRef } from \"../anchor/registry\"\nimport { useRivetContext } from \"../context\"\n\n/**\n * The anchors this client has selected — a click on a shape inside an\n * `<SvgArtwork>`, or on any `<Anchor selectable>` content.\n *\n * Anchor selection is view state, like hover and focus. An anchor isn't in the\n * graph — it's read off rendered DOM — so selecting one emits no change,\n * records nothing to history, and never appears in `RivetSelection`. That also\n * means it can't be driven from props: this reports what the pointer did, and\n * `setSelectedAnchors` on `useRivet()` is how you set it yourself.\n *\n * ```tsx\n * const [anchor] = useSelectedAnchors()\n * const record = anchor && rivet.getAnchor(anchor.nodeId, anchor.anchorId)\n * return <Inspector shape={record?.data} />\n * ```\n */\nexport function useSelectedAnchors(): readonly AnchorRef[] {\n const { store } = useRivetContext()\n return useSyncExternalStore(\n store.subscribeAnchorSelection,\n store.getSelectedAnchors,\n store.getSelectedAnchors,\n )\n}\n"]}
@@ -0,0 +1,2 @@
1
+ var a={width:160,height:44};function n(e,t){return `${e} ${t}`}var d=4,o={node:"[data-rivet-node]",handle:"[data-rivet-handle]",noPan:"[data-nopan]",noDrag:"[data-nodrag]",noSelect:"[data-noselect]",swimlaneHandle:"[data-rivet-swimlane-handle]",nodesSelection:"[data-rivet-nodes-selection]",paneGestureExempt:"[data-rivet-node], [data-rivet-swimlane-handle], [data-nopan], [data-rivet-nodes-selection]"};export{a,n as b,d as c,o as d};//# sourceMappingURL=chunk-B3YKB2QN.js.map
2
+ //# sourceMappingURL=chunk-B3YKB2QN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts"],"names":["DEFAULT_NODE_SIZE","handleKey","nodeId","handleId","DRAG_THRESHOLD","SELECTOR"],"mappings":"AAGO,IAAMA,EAA0B,CAAE,KAAA,CAAO,GAAA,CAAK,MAAA,CAAQ,EAAG,EAGzD,SAASC,CAAAA,CAAUC,CAAAA,CAAgBC,EAA0B,CAClE,OAAO,GAAGD,CAAM,CAAA,CAAA,EAAIC,CAAQ,CAAA,CAC9B,CAYO,IAAMC,CAAAA,CAAiB,EAEjBC,CAAAA,CAAW,CACtB,KAAM,mBAAA,CACN,MAAA,CAAQ,sBACR,KAAA,CAAO,cAAA,CAEP,MAAA,CAAQ,eAAA,CAER,SAAU,iBAAA,CACV,cAAA,CAAgB,+BAEhB,cAAA,CAAgB,8BAAA,CAEhB,kBACE,6FACJ","file":"chunk-B3YKB2QN.js","sourcesContent":["import type { NodeId, Size } from \"./types\"\n\n/** Assumed node size before the DOM node has mounted and reported its own. */\nexport const DEFAULT_NODE_SIZE: Size = { width: 160, height: 44 }\n\n/** Composite key for the handle/anchor registries. */\nexport function handleKey(nodeId: NodeId, handleId: string): string {\n return `${nodeId} ${handleId}`\n}\n\n/**\n * DOM markers the library and its consumers key interactions off. `data-nopan` is\n * the consumer-facing opt-out (stamp it on toolbars/inputs so a press there doesn't\n * start a node drag or pane pan); the rest are internal hit-test hooks.\n */\n/**\n * How far the pointer may travel before a press stops being a click and becomes\n * a drag. Shared so the two gestures that race over one press — moving the node\n * and selecting the anchor under the pointer — agree on where the line is.\n */\nexport const DRAG_THRESHOLD = 4\n\nexport const SELECTOR = {\n node: \"[data-rivet-node]\",\n handle: \"[data-rivet-handle]\",\n noPan: \"[data-nopan]\",\n /** Press here starts no node drag (but the node can still be selected/clicked). */\n noDrag: \"[data-nodrag]\",\n /** Press here never changes the selection. */\n noSelect: \"[data-noselect]\",\n swimlaneHandle: \"[data-rivet-swimlane-handle]\",\n /** The draggable box a marquee leaves over its selection (owns its own drag). */\n nodesSelection: \"[data-rivet-nodes-selection]\",\n /** Elements that own their own pointer — a pane pointerdown here starts no gesture. */\n paneGestureExempt:\n \"[data-rivet-node], [data-rivet-swimlane-handle], [data-nopan], [data-rivet-nodes-selection]\",\n} as const\n"]}
@@ -0,0 +1,2 @@
1
+ import {a}from'./chunk-B3YKB2QN.js';import {c as c$1}from'./chunk-NLE6BOPW.js';import {c,b as b$1}from'./chunk-RDSWIH3F.js';import {useSyncExternalStore,useCallback}from'react';var g="edge:",h="anchor:";function m(t){return typeof t=="string"?t:t.kind==="edge"?`${g}${t.id}`:t.kind==="anchor"?`${h}${t.nodeId}:${t.anchorId}`:t.id}function Z(t){return `${g}${t}`}function J(t,e){return `${h}${t}:${e}`}function C(t){let e=t.indexOf(":");return e>0&&e<t.length-1?e:-1}function M(t){return t.startsWith(g)?false:t.startsWith(h)?C(t.slice(h.length))<0:true}function f(t){if(t.startsWith(g))return {kind:"edge",id:t.slice(g.length)};if(t.startsWith(h)){let e=t.slice(h.length),r=C(e);if(r>=0)return {kind:"anchor",nodeId:e.slice(0,r),anchorId:e.slice(r+1)}}return {kind:"node",id:t}}function k(t){let e=typeof t=="string"?f(t):t;return e.kind==="node"?e.id:e.kind==="anchor"?e.nodeId:null}function w(t){return typeof t=="string"?t.startsWith(g)?t.slice(g.length):null:t.kind==="edge"?t.id:null}var re={throttleMs:50,renderCursors:true,renderOutlines:true},I=["#6366f1","#ec4899","#f59e0b","#10b981","#3b82f6","#8b5cf6","#ef4444","#14b8a6"];function S(t){let e=0;for(let n=0;n<t.length;n+=1)e=e*31+t.charCodeAt(n)|0;let r=(e%I.length+I.length)%I.length;return I[r]??I[0]}var A=45,H=16,F=.05;function p(t,e,r){let n=b$1(t,e,r);return Math.abs(e-n)<F?e:n}var T=[];function b(t){return !t||t.length===0?T:t.map(m)}var y=(t,e)=>t===e||t!=null&&e!=null&&t.x===e.x&&t.y===e.y,V=(t,e)=>t===e?true:!t||!e?false:t.source===e.source&&t.sourceHandle===e.sourceHandle&&t.sourceType===e.sourceType&&t.toPosition===e.toPosition&&y(t.from,e.from)&&y(t.to,e.to),K=(t,e)=>t!==void 0&&t.x===e.x&&t.y===e.y&&t.width===e.width&&t.height===e.height,O=(t,e)=>{let r=t??T,n=e??T;return r.length===n.length&&r.every((o,s)=>o===n[s])};function $(t,e){return t===e?true:!t||!e?false:t.name===e.name&&t.color===e.color&&t.data===e.data&&y(t.cursor,e.cursor)&&V(t.pending,e.pending)}var R=class{constructor(e){this.requests=e;}requests;peers=new Map;listeners=new Set;identityListeners=new Set;claimsListeners=new Set;snapshot=[];stale=false;identitySnapshot=[];identityStale=false;transforms=new Map;transformsStale=false;version=0;lastStep=-1;touch(){this.stale=true,this.requests.requestRender();}touchCursor(){this.stale=true,this.requests.requestCursorRender();}touchRoster(e,r){if(!(!e&&!r)){if(this.touch(),e){this.identityStale=true;for(let n of this.identityListeners)n();}if(r)for(let n of this.claimsListeners)n();for(let n of this.listeners)n();}}peer(e){let r=this.peers.get(e);return r||(r={id:e,declared:null,selection:T,holding:T,live:void 0,renderCursor:null,transforms:new Map,renderTransforms:new Map,livePending:void 0,renderPending:null},this.peers.set(e,r),this.identityStale=true),r}prune(e){e.declared||e.live||e.transforms.size>0||e.livePending||(this.peers.delete(e.id),this.identityStale=true,this.invalidateTransforms());}invalidateTransforms(){this.transformsStale=true,this.version+=1;}setPeers=e=>{let r=false,n=false,o=new Set;for(let s of e){o.add(s.id);let i=this.peers.has(s.id),d=this.peer(s.id);(!i||!$(d.declared,s))&&(r=true);let l=b(s.selection),c=b(s.holding);(!O(d.selection,l)||!O(d.holding,c))&&(d.selection=l,d.holding=c,n=true),d.declared=s;}for(let s of this.peers.values())o.has(s.id)||s.declared&&(this.peers.delete(s.id),r=true,(s.selection.length>0||s.holding.length>0)&&(n=true));this.touchRoster(r,n);};setPeerCursor=(e,r)=>{let n=this.peer(e);n.live!==void 0&&y(n.live,r)||(n.live=r,r===null&&this.prune(n),this.touchCursor());};setPeerNodeTransform=(e,r,n)=>{let o=this.peer(e);if(n===null){let s=o.transforms.delete(r);if(o.renderTransforms.delete(r),this.prune(o),!s)return;this.invalidateTransforms(),this.touch();return}K(o.transforms.get(r),n)||(o.transforms.set(r,n),o.renderTransforms.has(r)||(o.renderTransforms.set(r,n),this.invalidateTransforms()),this.touch());};setPeerConnection=(e,r)=>{let n=this.peer(e);n.livePending!==void 0&&V(n.livePending,r)||(n.livePending=r,r===null&&this.prune(n),this.touch());};removePeer=e=>{let r=this.peers.get(e);r&&(this.peers.delete(e),this.invalidateTransforms(),this.touchRoster(true,r.selection.length>0||r.holding.length>0));};getNodeTransforms=()=>{if(this.transformsStale){this.transformsStale=false,this.transforms=new Map;for(let e of this.peers.values())for(let[r,n]of e.renderTransforms)this.transforms.set(r,n);}return this.transforms};getTransformVersion=()=>this.version;step=e=>{let r=this.lastStep<0?H:Math.max(0,e-this.lastStep);this.lastStep=e;let n=1-Math.exp(-r/A),o=false,s=false;for(let i of this.peers.values()){let d=i.live!==void 0?i.live:i.declared?.cursor??null;d===null?i.renderCursor=null:i.renderCursor?y(i.renderCursor,d)||(i.renderCursor={x:p(i.renderCursor.x,d.x,n),y:p(i.renderCursor.y,d.y,n)},o=true):i.renderCursor=d;let l=i.livePending!==void 0?i.livePending:i.declared?.pending??null;l?!i.renderPending||i.renderPending.source!==l.source||i.renderPending.sourceHandle!==l.sourceHandle?i.renderPending=l:y(i.renderPending.to,l.to)||(i.renderPending={...l,to:{x:p(i.renderPending.to.x,l.to.x,n),y:p(i.renderPending.to.y,l.to.y,n)}},o=true):i.renderPending=null;for(let[c,a]of i.transforms){let u=i.renderTransforms.get(c);if(!u){i.renderTransforms.set(c,a),s=true;continue}K(u,a)||(i.renderTransforms.set(c,{x:p(u.x,a.x,n),y:p(u.y,a.y,n),width:p(u.width,a.width,n),height:p(u.height,a.height,n)}),s=true,o=true);}}return s&&this.invalidateTransforms(),o&&(this.stale=true),o};getPeers=()=>(this.stale&&(this.stale=false,this.snapshot=Array.from(this.peers.values(),e=>{let r=e.declared,n=(e.live!==void 0?e.live:r?.cursor)??null;return {id:e.id,name:r?.name,color:r?.color??S(e.id),data:r?.data,cursor:n===null?null:e.renderCursor??n,selection:e.selection,holding:e.holding,transforms:e.renderTransforms,pending:e.renderPending??(e.livePending!==void 0?e.livePending:r?.pending??null)}})),this.snapshot);getPeerIdentities=()=>(this.identityStale&&(this.identityStale=false,this.identitySnapshot=Array.from(this.peers.values(),e=>({id:e.id,name:e.declared?.name,color:e.declared?.color??S(e.id),data:e.declared?.data}))),this.identitySnapshot);isEmpty=()=>this.peers.size===0;subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e);});subscribeIdentity=e=>(this.identityListeners.add(e),()=>{this.identityListeners.delete(e);});subscribeClaims=e=>(this.claimsListeners.add(e),()=>{this.claimsListeners.delete(e);})};function ne(t){return new R(t)}function de(t,e,r){let n=Math.max(0,r.width-e.width),o=Math.max(0,r.height-e.height);return {x:Math.min(n,Math.max(0,t.x)),y:Math.min(o,Math.max(0,t.y))}}function le(t,e){let[r,n]=e;return {x:c(t.x,r),y:c(t.y,n)}}function ce(t){if(t.length===0)return null;let e=Number.POSITIVE_INFINITY,r=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,o=Number.NEGATIVE_INFINITY;for(let s of t)e=Math.min(e,s.x),r=Math.min(r,s.y),n=Math.max(n,s.x+s.width),o=Math.max(o,s.y+s.height);return {x:e,y:r,width:n-e,height:o-r}}var v=50;function ae(t,e,r=v){let n=t.get(e);if(!n)return {x:0,y:0};let o=n.position.x,s=n.position.y,i=n.parentId,d=0;for(;i&&d++<r;){let l=t.get(i);if(!l)break;o+=l.position.x,s+=l.position.y,i=l.parentId;}return {x:o,y:s}}function ue(t,e,r=v){let n=0,o=t.get(e)?.parentId;for(;o&&n<r;)n++,o=t.get(o)?.parentId;return n}function pe(t,e,r,n=v){let o=t.get(e)?.parentId,s=0;for(;o&&s++<n;){if(r.has(o))return true;o=t.get(o)?.parentId;}return false}function W(t){let e=new Map;for(let r of t.values()){if(!r.parentId)continue;let n=e.get(r.parentId);n?n.push(r.id):e.set(r.parentId,[r.id]);}return e}function _(t,e,r){for(let n of e.get(t)??[])r.push(n),_(n,e,r);}function ge(t,e){let r=[];return _(e,W(t),r),r}function he(t,e,r){let n=[];for(let o of t){let s={...o,position:{...o.position}};o.size&&(s.size={...o.size}),s.hovered=void 0,s.dragging=void 0,n.push(s);}return {nodes:n,edges:[...e].map(o=>({...o})),viewport:{...r}}}function fe(t,e){return t.position.x===e.position.x&&t.position.y===e.position.y&&!!t.selected==!!e.selected&&t.data===e.data&&t.type===e.type&&t.laneId===e.laneId&&t.ariaLabel===e.ariaLabel&&t.parentId===e.parentId&&t.extent===e.extent&&t.width===e.width&&t.height===e.height&&!!t.dragging==!!e.dragging&&t.size?.width===e.size?.width&&t.size?.height===e.size?.height}function ye(t,e){return {x:e.x,y:e.y,width:t?.width??t?.size?.width??a.width,height:t?.height??t?.size?.height??a.height}}var Y=new Set(["select","drag","resize","delete","reconnect"]);function G(t,e){let r=Object.keys(e);return r.length!==t.size?false:r.every(n=>t.get(n)===e[n])}var z=new Map;function X(t){let e=null;for(let[r,n]of t){let o=w(r);o!==null&&(e??=new Map,e.set(o,n));}return e??z}var E=class{constructor(e,r=()=>{}){this.requestRender=e;this.getParentId=r;}requestRender;getParentId;locks=new Map;edgeLocks=z;policy=null;version=0;listeners=new Set;setLocks=e=>{let r=e??{};if(G(this.locks,r))return [];let n=[];for(let[o,s]of Object.entries(r))this.locks.get(o)!==s&&n.push(o);this.locks=new Map(Object.entries(r)),this.edgeLocks=X(this.locks),this.version+=1,this.requestRender();for(let o of this.listeners)o();return n};setPolicy=e=>{this.policy=e;};isLocked=e=>this.getLockSource(e)!==null;getHolder=e=>{let r=this.getLockSource(e);return r===null?null:this.locks.get(r)??null};getLockSource=e=>{if(this.locks.size===0)return null;let r=typeof e=="string"?e:m(e);if(this.locks.has(r))return r;if(M(r))return this.ancestorLock(r);let n=k(r);return n===null?null:this.locks.has(n)?n:this.ancestorLock(n)};ancestorLock(e){let r=this.getParentId(e),n=0;for(;r&&n++<v;){if(this.locks.has(r))return r;r=this.getParentId(r);}return null}getLocks=()=>this.locks;getLockedEdges=()=>this.edgeLocks;size=()=>this.locks.size;allows=(e,r)=>{if(this.locks.size===0)return true;let n=this.getLockSource(e);if(n===null)return true;let o=this.locks.get(n);if(o===void 0)return true;if(this.policy){let s=typeof e=="string"?e:m(e),i=typeof e=="string"?f(s):e,d=f(n);return this.policy({target:i,lockedTarget:d,nodeId:k(i),lockedNodeId:k(d),holderId:o,intent:r})}return !Y.has(r)};getVersion=()=>this.version;subscribe=e=>(this.listeners.add(e),()=>{this.listeners.delete(e);})};function Te(t,e){return new E(t,e)}function xe(){let{store:t}=c$1();return useSyncExternalStore(t.presence.subscribeIdentity,t.presence.getPeerIdentities,t.presence.getPeerIdentities)}var U=[];function Le(t,e,r){if(t.length===0&&r.size===0)return U;let n=new Map(e.map(d=>[d.id,d])),o=new Map,s=d=>{let l=o.get(d);return l||(l={key:d,target:f(d),claims:[],lock:null},o.set(d,l)),l},i=(d,l,c)=>{let a=s(d);a.claims.some(u=>u.peer.id===l.id)||a.claims.push({peer:l,kind:c});};for(let d of t){let l=n.get(d.id)??d;for(let c of d.holding)i(c,l,"held");for(let c of d.transforms.keys())i(c,l,"held");for(let c of d.selection)i(c,l,"selected");}for(let[d,l]of r)s(d).lock={holderId:l,peer:n.get(l)};return [...o.values()]}function Ce(t,e){return t===e?true:t.length!==e.length?false:t.every((r,n)=>{let o=e[n];return !o||r.key!==o.key||r.lock?.holderId!==o.lock?.holderId||r.claims.length!==o.claims.length?false:r.claims.every((s,i)=>{let d=o.claims[i];return d?.peer===s.peer&&d?.kind===s.kind})})}var j={locked:false};function be(t){let{store:e}=c$1(),r=useCallback(()=>e.locks.getHolder(t),[e,t]),n=useSyncExternalStore(e.locks.subscribe,r,r);return n===null?j:{locked:true,holderId:n}}function Ke(t,e){let{store:r}=c$1(),n=useCallback(()=>r.locks.allows(t,e),[r,t,e]);return useSyncExternalStore(r.locks.subscribe,n,n)}export{Le as A,Ce as B,be as C,Ke as D,m as a,Z as b,J as c,M as d,f as e,k as f,w as g,re as h,I as i,S as j,ne as k,de as l,le as m,ce as n,ae as o,ue as p,pe as q,W as r,_ as s,ge as t,he as u,fe as v,ye as w,Y as x,Te as y,xe as z};//# sourceMappingURL=chunk-CVSEMPYO.js.map
2
+ //# sourceMappingURL=chunk-CVSEMPYO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/presence/targets.ts","../src/presence/registry.ts","../src/graph.ts","../src/locks/registry.ts","../src/hooks/use-peer-identities.ts","../src/presence/outlines.ts","../src/hooks/use-node-lock.ts"],"names":["EDGE_PREFIX","ANCHOR_PREFIX","presenceTargetKey","target","edgeTargetKey","id","anchorTargetKey","nodeId","anchorId","anchorSplit","rest","split","isNodeTarget","key","parsePresenceTarget","presenceTargetNodeId","resolved","presenceTargetEdgeId","DEFAULT_PRESENCE_OPTIONS","PEER_COLORS","peerColor","hash","i","index","SMOOTHING_MS","NOMINAL_FRAME_MS","SETTLED_EPSILON","approach","from","to","t","next","lerp","EMPTY_IDS","normalizeTargets","targets","samePoint","a","b","sameConnection","sameRect","sameIds","left","right","identityEqual","Registry","requests","identity","claims","listener","state","peers","identityChanged","claimsChanged","incoming","peer","known","selection","holding","peerId","point","rect","had","connection","now","elapsed","moving","transformsMoved","wire","current","declared","createPresenceRegistry","clampChildToParent","position","childSize","parentSize","maxX","maxY","snapToGrid","grid","gx","gy","snap","boundingRect","rects","minX","minY","r","MAX_PARENT_DEPTH","worldPosition","nodes","maxDepth","node","x","y","parentId","guard","parent","nodeDepth","depth","hasAncestorIn","set","buildChildIndex","childrenOf","list","collectDescendants","childIndex","out","child","descendantIds","serializeGraph","edges","viewport","cleanNodes","copy","edge","nodeControlledEqual","nodeBox","origin","DEFAULT_NODE_SIZE","LOCK_DEFAULT_REFUSED","locksEqual","keys","NO_EDGE_LOCKS","indexEdgeLocks","locks","holderId","edgeId","requestRender","getParentId","opened","policy","source","owner","intent","lockedKey","lockedTarget","createLockRegistry","usePeerIdentities","store","useRivetContext","useSyncExternalStore","EMPTY_ENTRIES","collectPeerOutlines","identities","byId","entries","entry","found","claim","kind","existing","peerOutlinesEqual","other","UNLOCKED","useNodeLock","getHolder","useCallback","useNodeLockAllows","allows"],"mappings":"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","file":"chunk-CVSEMPYO.js","sourcesContent":["/**\n * Presence targets — the things a peer can have a claim on.\n *\n * Presence started node-shaped: `Peer.selection` and `Peer.holding` were\n * `NodeId[]`. The graph addresses more than nodes, though, and the rest of it\n * is just as selectable — an edge is a row with its own id, and an anchor is an\n * addressable element inside a node that can be clicked and wired. A target\n * says *which kind of thing* an id refers to, so presence can describe any of\n * them without three parallel fields.\n *\n * Strings stay the wire format, the same arrangement {@link EdgeEndpoint} uses\n * for edge ends: a bare string is a node id, which is exactly what a 0.2.0\n * transport already sends, so nothing has to be migrated to adopt the rest of\n * this. The struct sits on top as a codec.\n *\n * | structured | key |\n * |-------------------------------------|----------------------------|\n * | `{ kind: \"node\", id }` | `<nodeId>` |\n * | `{ kind: \"edge\", id }` | `edge:<edgeId>` |\n * | `{ kind: \"anchor\", nodeId, anchorId }` | `anchor:<nodeId>:<anchorId>` |\n *\n * `edge:` and `anchor:` are therefore reserved prefixes on a node id, and a\n * node id inside an anchor key can't contain `:` (the anchor id may — the split\n * takes the first separator). Anything that doesn't parse reads as a node id,\n * which is the safe direction: an unknown node is skipped everywhere, where an\n * unknown *kind* would need a decision at every call site.\n */\n\nimport type { EdgeId, NodeId } from \"../types\"\n\n/** Something a peer can select, hold or lock. */\nexport type PresenceTarget =\n | { kind: \"node\"; id: NodeId }\n | { kind: \"edge\"; id: EdgeId }\n /** An {@link Anchor} inside a node — addressed by both, since anchor ids are per-node. */\n | { kind: \"anchor\"; nodeId: NodeId; anchorId: string }\n\n/**\n * A target in wire form — what travels, and what `PeerRecord.selection` and\n * `PeerRecord.holding` hold. A plain node id is its own key.\n */\nexport type PresenceTargetKey = string\n\nconst EDGE_PREFIX = \"edge:\"\nconst ANCHOR_PREFIX = \"anchor:\"\n\n/** The wire key for a target. A bare node id passes through unchanged. */\nexport function presenceTargetKey(target: PresenceTarget | NodeId): PresenceTargetKey {\n if (typeof target === \"string\") return target\n if (target.kind === \"edge\") return `${EDGE_PREFIX}${target.id}`\n if (target.kind === \"anchor\") return `${ANCHOR_PREFIX}${target.nodeId}:${target.anchorId}`\n return target.id\n}\n\n/**\n * The key for an edge, without building the struct first. Sugar over\n * {@link presenceTargetKey} for the call sites that ask a key-shaped question\n * every frame — a lock check on the hovered edge, say — where the intermediate\n * object is the only allocation in the path.\n */\nexport function edgeTargetKey(id: EdgeId): PresenceTargetKey {\n return `${EDGE_PREFIX}${id}`\n}\n\n/** The key for an anchor, without building the struct first. */\nexport function anchorTargetKey(nodeId: NodeId, anchorId: string): PresenceTargetKey {\n return `${ANCHOR_PREFIX}${nodeId}:${anchorId}`\n}\n\n/**\n * Where the node id ends inside an `anchor:` key's payload, or `-1` when the\n * payload names no anchor anyone could resolve (`anchor:`, `anchor:n1`,\n * `anchor::door`). Both halves have to be there.\n */\nfunction anchorSplit(rest: string): number {\n const split = rest.indexOf(\":\")\n return split > 0 && split < rest.length - 1 ? split : -1\n}\n\n/**\n * Whether a key addresses a node itself — in which case the key *is* the node\n * id, and no parse is needed. The render loop's question, asked once per\n * claimed target per peer per frame, so it allocates nothing.\n */\nexport function isNodeTarget(key: PresenceTargetKey): boolean {\n if (key.startsWith(EDGE_PREFIX)) return false\n if (key.startsWith(ANCHOR_PREFIX)) return anchorSplit(key.slice(ANCHOR_PREFIX.length)) < 0\n return true\n}\n\n/** Read a wire key back into a target. Anything malformed comes back as a node. */\nexport function parsePresenceTarget(key: PresenceTargetKey): PresenceTarget {\n if (key.startsWith(EDGE_PREFIX)) return { kind: \"edge\", id: key.slice(EDGE_PREFIX.length) }\n if (key.startsWith(ANCHOR_PREFIX)) {\n const rest = key.slice(ANCHOR_PREFIX.length)\n const split = anchorSplit(rest)\n if (split >= 0) {\n return { kind: \"anchor\", nodeId: rest.slice(0, split), anchorId: rest.slice(split + 1) }\n }\n }\n return { kind: \"node\", id: key }\n}\n\n/**\n * The node a target belongs to: itself for a node, its owner for an anchor,\n * `null` for an edge (which has two, and rivet holds neither as the edge's own).\n * How a node-keyed question — geometry, a parent chain, a lock — is asked of a\n * target that isn't a node.\n */\nexport function presenceTargetNodeId(target: PresenceTarget | PresenceTargetKey): NodeId | null {\n const resolved = typeof target === \"string\" ? parsePresenceTarget(target) : target\n if (resolved.kind === \"node\") return resolved.id\n if (resolved.kind === \"anchor\") return resolved.nodeId\n return null\n}\n\n/**\n * The edge a target names, or `null` for anything else. The mirror of\n * {@link presenceTargetNodeId}, and deliberately parse-free: an `edge:` key is\n * recognisable by its prefix alone, so the frame loop that asks this of every\n * claim allocates nothing on the answers it discards.\n */\nexport function presenceTargetEdgeId(target: PresenceTarget | PresenceTargetKey): EdgeId | null {\n if (typeof target === \"string\") {\n return target.startsWith(EDGE_PREFIX) ? target.slice(EDGE_PREFIX.length) : null\n }\n return target.kind === \"edge\" ? target.id : null\n}\n","import { lerp } from \"@rivet/utils\"\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, `data`) and the\n * slow fields (selection, holding). React-rate, and notified as two\n * channels: people change far less often than what they're pointing at.\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 { ComponentType } from \"react\"\nimport type { HandlePosition, HandleType, NodeId, Rect, Vec2 } from \"../types\"\nimport { type PresenceTarget, type PresenceTargetKey, presenceTargetKey } from \"./targets\"\n\n/**\n * A wire somebody is pulling out of a handle, right now — the peer-side view of\n * a `PendingConnection`.\n *\n * Both ends are in **world** coordinates for the reason cursors are: peers pan\n * and zoom independently. `from` travels rather than being recomputed from the\n * handle, because the node it belongs to may be culled on this client, and a\n * wire that vanishes when you scroll away from its source is worse than one\n * drawn from a point.\n *\n * Deliberately not `PendingConnection` itself: that type carries `reconnecting`,\n * the local drag's note that an edge is hidden while its preview stands in for\n * it. A peer's uncommitted gesture must not hide an edge from this client — the\n * edge is still in the document until their change lands — so the field stops\n * at the publishing boundary.\n */\nexport type PeerConnection = {\n source: NodeId\n sourceHandle: string\n sourceType: HandleType\n /** The anchored end, at the handle it was pulled from. */\n from: Vec2\n /** The loose end, following their pointer. Interpolated, like a cursor. */\n to: Vec2\n /** Side the loose end enters from while they're snapped onto a handle. */\n toPosition?: HandlePosition\n}\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 /**\n * Anything else your app knows about them — an avatar URL, a role, what\n * they're doing. rivet never reads it; it rides along to {@link PeerRecord}\n * so participant chrome doesn't need a side map keyed by peer id.\n */\n data?: unknown\n /**\n * What they have selected. Bare strings are node ids — the 0.2.0 shape, still\n * exactly right — and a {@link PresenceTarget} names an edge or an anchor.\n */\n selection?: readonly (NodeId | PresenceTarget)[]\n /**\n * What's under one of their live gestures (their `onNodeDragStart`). Nodes in\n * practice: an edge or an anchor is selected, never dragged.\n */\n holding?: readonly (NodeId | PresenceTarget)[]\n /**\n * A wire they're pulling out of a handle, or `null` when they aren't. Like\n * {@link Peer.cursor} this is only a seed: once\n * {@link PresenceRegistry.setPeerConnection} has been called for a peer, the\n * imperative value owns it.\n */\n pending?: PeerConnection | null\n}\n\n/**\n * Who a peer is, with nothing about what they're doing.\n *\n * Its own type because it's its own subscription: identity changes when someone\n * joins, leaves, is renamed or recoloured — human-rate events — while claims\n * change every time anybody clicks anything. Chrome that only shows *who is\n * here* subscribes to this and sits out the rest (see `usePeerIdentities`).\n */\nexport type PeerIdentity = {\n id: string\n name?: string\n /** Always set — {@link peerColor} fills in for a peer that declared none. */\n color: string\n /** Whatever the roster attached to them, untouched. */\n data?: unknown\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 = PeerIdentity & {\n cursor: Vec2 | null\n /**\n * Their claims, in wire form: a bare node id, or an `edge:`/`anchor:` key.\n * {@link isNodeTarget} is the cheap question, `parsePresenceTarget` the full\n * one.\n */\n selection: readonly PresenceTargetKey[]\n holding: readonly PresenceTargetKey[]\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 * The wire they're pulling right now, `null` when they aren't. The loose end\n * is the interpolated one — what {@link step} has eased to, as with cursors.\n */\n pending: PeerConnection | null\n}\n\n/**\n * What a {@link PresenceOptions.cursorComponent} is handed: who the cursor\n * belongs to, and nothing about where it is.\n *\n * Position is deliberately absent. A cursor moves at pointer rate for every\n * peer in the room, and passing it as a prop would re-render the component that\n * often — the cost the cursor layer exists to avoid. rivet writes the position\n * onto the wrapper element around your component instead, as a `transform`, so\n * your component re-renders only when the *person* changes.\n *\n * {@link PeerIdentity} rather than {@link PeerRecord} for the same reason: a\n * record's `cursor`, `selection` and `transforms` all move faster than this\n * component is re-rendered, so they would be stale in your hands. Anything else\n * you want to show — an avatar, a role, what they're doing — rides on\n * {@link PeerIdentity.data}.\n */\nexport type PeerCursorProps = {\n peer: PeerIdentity\n}\n\n/** One peer's claim on a target, as {@link PeerOutlineProps} reports it. */\nexport type PeerClaim = {\n peer: PeerIdentity\n /**\n * `\"held\"` when the target is under one of their live gestures, `\"selected\"`\n * when they merely have it selected. A peer who is doing both appears once,\n * as `\"held\"` — the more current fact, and the one the canvas paint picks too.\n */\n kind: \"selected\" | \"held\"\n}\n\n/**\n * What a {@link PresenceOptions.outlineComponent} is handed: one claimed\n * target, everybody who has claimed it, and whether it's locked.\n *\n * One wrapper per **target**, not per peer-per-target. A node can be in three\n * peers' selections while a fourth holds it and a lock sits on top; the canvas\n * paint resolves that itself, because two strokes on one box read as a heavier\n * border rather than as two facts. A component can do better than a stroke —\n * stack avatars, badge a count, truncate at three — so it gets the whole list\n * instead of rivet's answer to a question it didn't ask.\n *\n * Like {@link PeerCursorProps}, peers arrive as {@link PeerIdentity}: this\n * re-renders when the *claims* change, so a record's `cursor` and `transforms`\n * would be stale in your hands. Geometry isn't a prop for the same reason —\n * rivet sizes and positions the wrapper element imperatively every frame.\n */\nexport type PeerOutlineProps = {\n target: PresenceTarget\n claims: readonly PeerClaim[]\n /**\n * The lock on this target, or `null` when it isn't locked. `peer` is absent\n * when the holder isn't in the roster — a lock outlives the tab that took it,\n * which is the point of taking one.\n */\n lock: { holderId: string; peer?: PeerIdentity } | null\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 * Draw peer cursors. Turn off to render them entirely yourself off\n * `subscribeCursorFrame`; neither layer below is created. Node outlines and\n * peers' live boxes are unaffected — they live with the geometry they\n * annotate. Default `true`.\n */\n renderCursors?: boolean\n /**\n * Render each peer's cursor as your own React component instead of the\n * built-in canvas arrow: an avatar, a follow button, a status chip — anything\n * a canvas glyph can't be.\n *\n * One wrapper per peer, mounted when they join and unmounted when they leave.\n * rivet positions the wrapper imperatively every cursor frame, so nothing\n * re-renders at pointer rate; see {@link PeerCursorProps}.\n *\n * Supplying this means the cursor *canvas* is never created. Setting\n * `renderCursors: false` beats it: the boolean is \"no cursors from rivet at\n * all\", which is a different statement from \"cursors, drawn my way\".\n */\n cursorComponent?: ComponentType<PeerCursorProps>\n /**\n * Outline what peers have claimed — their selections, whatever they're\n * holding, and locked targets — in the claimant's colour. Turn off to show\n * claims entirely yourself, or not at all: no outlines are stroked around\n * nodes and anchors, and no halo is drawn under a claimed edge. Cursors are\n * unaffected. Default `true`.\n */\n renderOutlines?: boolean\n /**\n * Render each claimed target as your own React component instead of the\n * built-in outline: an avatar stack, a \"3 others\" badge, a name chip — again,\n * anything a stroked box can't be.\n *\n * One wrapper per claimed target, mounted when the first claim on it arrives\n * and unmounted with the last. rivet positions and sizes the wrapper\n * imperatively every frame, so nothing re-renders as peers drag things\n * around; see {@link PeerOutlineProps}.\n *\n * Supplying this replaces the outlines rivet strokes around **nodes and\n * anchors**. The halo under a claimed *edge* stays, because a DOM overlay\n * cannot follow an edge's path — your component still gets `edge` targets,\n * positioned at the midpoint of the drawn path, for chrome that sits on one.\n * `renderOutlines: false` is the switch that turns off all of it.\n */\n outlineComponent?: ComponentType<PeerOutlineProps>\n}\n\n/**\n * Presence options with every default filled in. The two components stay\n * optional — there is no default component, and \"no component\" is the state\n * that selects the canvas.\n */\nexport type ResolvedPresenceOptions = Required<\n Omit<PresenceOptions, \"cursorComponent\" | \"outlineComponent\">\n> &\n Pick<PresenceOptions, \"cursorComponent\" | \"outlineComponent\">\n\nexport const DEFAULT_PRESENCE_OPTIONS: ResolvedPresenceOptions = {\n throttleMs: 50,\n renderCursors: true,\n renderOutlines: 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 * The wire a peer is pulling out of a handle, or `null` when they let go.\n *\n * Hot path like the cursor, but it takes a **full** frame rather than the\n * cursor layer's narrow one: the wire is painted on the shared foreground,\n * which is also where the local pending connection, the alignment guides and\n * the peer outlines live. Unlike a pointer, which is always moving, this only\n * arrives while somebody is actually dragging a connection.\n */\n setPeerConnection: (peerId: string, connection: PeerConnection | 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 /**\n * Every known peer, identity only — no cursor, no claims. Its own cached\n * snapshot, so it keeps its identity across every frame and every click that\n * leaves the roster's *people* unchanged.\n */\n getPeerIdentities: () => readonly PeerIdentity[]\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 * The half of {@link subscribe} that's about *people*: joins, leaves, names,\n * colours and `data`. Silent when a peer only changes what they have\n * selected or held.\n *\n * The two halves move at completely different rates. At 15–100 participants,\n * somebody is always clicking something, so a roster subscription that counts\n * claims re-renders continuously — while the set of people in the room barely\n * moves. An avatar stack wants this one.\n */\n subscribeIdentity: (listener: () => void) => () => void\n /**\n * The other half: what peers have selected or are holding. Also fires when a\n * peer joins or leaves carrying claims, since that changes who claims what.\n */\n subscribeClaims: (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 /**\n * The roster's claims, normalized to wire keys. Held apart from\n * {@link declared} because they're compared, notified and snapshotted on\n * their own schedule — and because the prop may state them either way round.\n */\n selection: readonly PresenceTargetKey[]\n holding: readonly PresenceTargetKey[]\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 /** `undefined` until an imperative write; after that it owns the wire. */\n livePending: PeerConnection | null | undefined\n /** The wire as drawn, its loose end easing toward {@link livePending}. */\n renderPending: PeerConnection | null\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 = lerp(from, to, t)\n return Math.abs(to - next) < SETTLED_EPSILON ? to : next\n}\n\nconst EMPTY_IDS: readonly PresenceTargetKey[] = []\n\n/** The roster's claims as wire keys. Already-bare node ids pass through free. */\nfunction normalizeTargets(\n targets: readonly (NodeId | PresenceTarget)[] | undefined,\n): readonly PresenceTargetKey[] {\n if (!targets || targets.length === 0) return EMPTY_IDS\n return targets.map(presenceTargetKey)\n}\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\n/** Whether two wires are the same wire in the same place. */\nconst sameConnection = (\n a: PeerConnection | null | undefined,\n b: PeerConnection | null | undefined,\n): boolean => {\n if (a === b) return true\n if (!a || !b) return false\n return (\n a.source === b.source &&\n a.sourceHandle === b.sourceHandle &&\n a.sourceType === b.sourceType &&\n a.toPosition === b.toPosition &&\n samePoint(a.from, b.from) &&\n samePoint(a.to, b.to)\n )\n}\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 = (\n a: readonly PresenceTargetKey[] | undefined,\n b: readonly PresenceTargetKey[] | undefined,\n): 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/**\n * Whether two roster entries describe the same *person*: name, colour, `data`,\n * and the two hot-path seeds. Claims are compared separately, off the\n * normalized keys — they're the half that changes constantly, and the two\n * notify apart.\n *\n * The seeds are in here rather than nowhere: a consumer driving cursors or\n * pending wires through the `peers` prop alone would otherwise change them\n * without anything repainting. Transports that use the imperative writers never\n * restate them, so they pay nothing for it.\n */\nfunction identityEqual(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 a.data === b.data &&\n samePoint(a.cursor, b.cursor) &&\n sameConnection(a.pending, b.pending)\n )\n}\n\nclass Registry implements PresenceRegistry {\n private readonly peers = new Map<string, PeerState>()\n private readonly listeners = new Set<() => void>()\n private readonly identityListeners = new Set<() => void>()\n private readonly claimsListeners = new Set<() => void>()\n private snapshot: readonly PeerRecord[] = []\n private stale = false\n private identitySnapshot: readonly PeerIdentity[] = []\n private identityStale = 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 /**\n * A change React needs to know about, as well as the canvas — routed to the\n * half (or halves) it belongs to. The union channel fires once either way: a\n * peer who joins with a selection is one roster change, not two.\n */\n private touchRoster(identity: boolean, claims: boolean): void {\n if (!identity && !claims) return\n this.touch()\n if (identity) {\n this.identityStale = true\n for (const listener of this.identityListeners) listener()\n }\n if (claims) for (const listener of this.claimsListeners) listener()\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 selection: EMPTY_IDS,\n holding: EMPTY_IDS,\n live: undefined,\n renderCursor: null,\n transforms: new Map(),\n renderTransforms: new Map(),\n livePending: undefined,\n renderPending: null,\n }\n this.peers.set(id, state)\n // A peer can also arrive on the hot path, ahead of any roster entry. That\n // doesn't notify — cursor frames never do — but the identity snapshot has\n // to include them the next time anything reads it.\n this.identityStale = true\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 || state.livePending) return\n this.peers.delete(state.id)\n this.identityStale = true\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 identityChanged = false\n let claimsChanged = false\n const incoming = new Set<string>()\n for (const peer of peers) {\n incoming.add(peer.id)\n const known = this.peers.has(peer.id)\n const state = this.peer(peer.id)\n // A peer arriving is an identity change even when they declare nothing:\n // an avatar stack has someone new to draw.\n if (!known || !identityEqual(state.declared, peer)) identityChanged = true\n const selection = normalizeTargets(peer.selection)\n const holding = normalizeTargets(peer.holding)\n if (!sameIds(state.selection, selection) || !sameIds(state.holding, holding)) {\n state.selection = selection\n state.holding = holding\n claimsChanged = true\n }\n state.declared = peer\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 identityChanged = true\n // Their claims left with them, so the claims half only hears about it if\n // they had any.\n if (state.selection.length > 0 || state.holding.length > 0) claimsChanged = true\n }\n this.touchRoster(identityChanged, claimsChanged)\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 setPeerConnection = (peerId: string, connection: PeerConnection | null): void => {\n const state = this.peer(peerId)\n if (state.livePending !== undefined && sameConnection(state.livePending, connection)) return\n state.livePending = connection\n if (connection === null) this.prune(state)\n this.touch()\n }\n\n removePeer = (peerId: string): void => {\n const state = this.peers.get(peerId)\n if (!state) return\n this.peers.delete(peerId)\n this.invalidateTransforms()\n this.touchRoster(true, state.selection.length > 0 || state.holding.length > 0)\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 // The loose end of a wire eases exactly like a cursor — it *is* their\n // pointer, drawn as a line. Everything else about the wire (the anchored\n // end, the side it snapped to) is a state change and lands at once.\n const wire =\n state.livePending !== undefined ? state.livePending : (state.declared?.pending ?? null)\n if (!wire) {\n state.renderPending = null\n } else if (\n !state.renderPending ||\n // A different wire, not the same one moved: placed outright, or the\n // loose end would sweep across the canvas between two drags.\n state.renderPending.source !== wire.source ||\n state.renderPending.sourceHandle !== wire.sourceHandle\n ) {\n state.renderPending = wire\n } else if (!samePoint(state.renderPending.to, wire.to)) {\n state.renderPending = {\n ...wire,\n to: {\n x: approach(state.renderPending.to.x, wire.to.x, t),\n y: approach(state.renderPending.to.y, wire.to.y, t),\n },\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 = Array.from(this.peers.values(), (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 data: declared?.data,\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: state.selection,\n holding: state.holding,\n transforms: state.renderTransforms,\n // Same fallback as the cursor: a registry nobody steps still reports\n // the wire that arrived rather than nothing.\n pending:\n state.renderPending ??\n (state.livePending !== undefined ? state.livePending : (declared?.pending ?? null)),\n }\n })\n }\n return this.snapshot\n }\n\n getPeerIdentities = (): readonly PeerIdentity[] => {\n if (this.identityStale) {\n this.identityStale = false\n this.identitySnapshot = Array.from(this.peers.values(), (state) => ({\n id: state.id,\n name: state.declared?.name,\n color: state.declared?.color ?? peerColor(state.id),\n data: state.declared?.data,\n }))\n }\n return this.identitySnapshot\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 subscribeIdentity = (listener: () => void): (() => void) => {\n this.identityListeners.add(listener)\n return () => {\n this.identityListeners.delete(listener)\n }\n }\n\n subscribeClaims = (listener: () => void): (() => void) => {\n this.claimsListeners.add(listener)\n return () => {\n this.claimsListeners.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 { snap } from \"@rivet/utils\"\nimport { DEFAULT_NODE_SIZE } from \"./constants\"\nimport type {\n NodeId,\n Rect,\n RivetEdge,\n RivetNode,\n RivetSnapshot,\n Size,\n Vec2,\n Viewport,\n} from \"./types\"\n\n/**\n * Confine a child's parent-relative position so its box stays fully inside the\n * parent (the `extent: \"parent\"` sub-flow constraint). Pure geometry — the\n * caller supplies both measured sizes.\n */\nexport function clampChildToParent(position: Vec2, childSize: Size, parentSize: Size): Vec2 {\n const maxX = Math.max(0, parentSize.width - childSize.width)\n const maxY = Math.max(0, parentSize.height - childSize.height)\n return {\n x: Math.min(maxX, Math.max(0, position.x)),\n y: Math.min(maxY, Math.max(0, position.y)),\n }\n}\n\n/** Snap a position to the nearest multiple of `[gx, gy]`. A `0` axis is left free. */\nexport function snapToGrid(position: Vec2, grid: readonly [number, number]): Vec2 {\n const [gx, gy] = grid\n // `snap` is a no-op for a non-positive step, which is what a free axis is.\n return { x: snap(position.x, gx), y: snap(position.y, gy) }\n}\n\n/** The tight bounding rect over a set of rects, or `null` when empty. */\nexport function boundingRect(rects: readonly Rect[]): Rect | null {\n if (rects.length === 0) return null\n let minX = Number.POSITIVE_INFINITY\n let minY = Number.POSITIVE_INFINITY\n let maxX = Number.NEGATIVE_INFINITY\n let maxY = Number.NEGATIVE_INFINITY\n for (const r of rects) {\n minX = Math.min(minX, r.x)\n minY = Math.min(minY, r.y)\n maxX = Math.max(maxX, r.x + r.width)\n maxY = Math.max(maxY, r.y + r.height)\n }\n return { x: minX, y: minY, width: maxX - minX, height: maxY - minY }\n}\n\n/**\n * Pure, reusable graph algorithms over a node map. These take the node collection\n * explicitly (no hidden state), so they're trivially testable and shared by the\n * store without duplicating parent-chain / subtree walks.\n */\n\n/** Longest parent chain any walk will follow — a guard against accidental cycles. */\nexport const MAX_PARENT_DEPTH = 50\n\ntype Nodes = ReadonlyMap<NodeId, RivetNode>\n\n/** Absolute world position of a node, summing its parent chain's positions. */\nexport function worldPosition(nodes: Nodes, id: NodeId, maxDepth = MAX_PARENT_DEPTH): Vec2 {\n const node = nodes.get(id)\n if (!node) return { x: 0, y: 0 }\n let x = node.position.x\n let y = node.position.y\n let parentId = node.parentId\n let guard = 0\n while (parentId && guard++ < maxDepth) {\n const parent = nodes.get(parentId)\n if (!parent) break\n x += parent.position.x\n y += parent.position.y\n parentId = parent.parentId\n }\n return { x, y }\n}\n\n/** How many ancestors a node has (0 for a top-level node). */\nexport function nodeDepth(nodes: Nodes, id: NodeId, maxDepth = MAX_PARENT_DEPTH): number {\n let depth = 0\n let parentId = nodes.get(id)?.parentId\n while (parentId && depth < maxDepth) {\n depth++\n parentId = nodes.get(parentId)?.parentId\n }\n return depth\n}\n\n/** True if any ancestor of `id` is in `set`. */\nexport function hasAncestorIn(\n nodes: Nodes,\n id: NodeId,\n set: ReadonlySet<NodeId>,\n maxDepth = MAX_PARENT_DEPTH,\n): boolean {\n let parentId = nodes.get(id)?.parentId\n let guard = 0\n while (parentId && guard++ < maxDepth) {\n if (set.has(parentId)) return true\n parentId = nodes.get(parentId)?.parentId\n }\n return false\n}\n\n/** Parent id → its direct children, computed in one pass over all nodes. */\nexport function buildChildIndex(nodes: Nodes): Map<NodeId, NodeId[]> {\n const childrenOf = new Map<NodeId, NodeId[]>()\n for (const node of nodes.values()) {\n if (!node.parentId) continue\n const list = childrenOf.get(node.parentId)\n if (list) list.push(node.id)\n else childrenOf.set(node.parentId, [node.id])\n }\n return childrenOf\n}\n\n/** Append every descendant of `id` (children, grandchildren, …) to `out`. */\nexport function collectDescendants(\n id: NodeId,\n childIndex: ReadonlyMap<NodeId, NodeId[]>,\n out: NodeId[],\n): void {\n for (const child of childIndex.get(id) ?? []) {\n out.push(child)\n collectDescendants(child, childIndex, out)\n }\n}\n\n/** Every descendant of `id`, building the child index internally. */\nexport function descendantIds(nodes: Nodes, id: NodeId): NodeId[] {\n const out: NodeId[] = []\n collectDescendants(id, buildChildIndex(nodes), out)\n return out\n}\n\n/**\n * True when nothing the consumer controls has changed between two versions of a\n * node — so reconcile can keep the existing one (preserving store-owned transient\n * state like `hovered`). Compares only fields a controlled consumer owns.\n */\n/**\n * A serializable snapshot of the graph. Store-owned geometry (`position`, `size`)\n * is cloned so mutating the snapshot can't reach back into the store, and purely\n * transient node flags (`hovered`, `dragging`) are dropped — you never want to\n * persist \"was mid-drag\". `data` is copied by reference, matching the\n * controlled-mode contract (the consumer owns it).\n */\nexport function serializeGraph<TNodeData = unknown, TEdgeData = unknown>(\n nodes: Iterable<RivetNode<TNodeData>>,\n edges: Iterable<RivetEdge<TEdgeData>>,\n viewport: Viewport,\n): RivetSnapshot<TNodeData, TEdgeData> {\n const cleanNodes: RivetNode<TNodeData>[] = []\n for (const node of nodes) {\n const copy = { ...node, position: { ...node.position } }\n if (node.size) copy.size = { ...node.size }\n copy.hovered = undefined\n copy.dragging = undefined\n cleanNodes.push(copy)\n }\n return {\n nodes: cleanNodes,\n edges: [...edges].map((edge) => ({ ...edge })),\n viewport: { ...viewport },\n }\n}\n\nexport function nodeControlledEqual(a: RivetNode, b: RivetNode): boolean {\n return (\n a.position.x === b.position.x &&\n a.position.y === b.position.y &&\n Boolean(a.selected) === Boolean(b.selected) &&\n a.data === b.data &&\n a.type === b.type &&\n a.laneId === b.laneId &&\n a.ariaLabel === b.ariaLabel &&\n a.parentId === b.parentId &&\n a.extent === b.extent &&\n a.width === b.width &&\n a.height === b.height &&\n Boolean(a.dragging) === Boolean(b.dragging) &&\n a.size?.width === b.size?.width &&\n a.size?.height === b.size?.height\n )\n}\n\n/**\n * A node's box from an already-resolved world origin.\n *\n * Explicit `width`/`height` outrank the measured `size`, the same order the\n * node element itself renders in — a controlled node arrives sized but\n * unmeasured, and reporting the default box for it would put edges, the\n * minimap and marquee hit-testing on a box nothing is drawn at. One definition,\n * so the store and the renderer cannot disagree about how big a node is.\n */\nexport function nodeBox(node: RivetNode | undefined, origin: Vec2): Rect {\n return {\n x: origin.x,\n y: origin.y,\n width: node?.width ?? node?.size?.width ?? DEFAULT_NODE_SIZE.width,\n height: node?.height ?? node?.size?.height ?? DEFAULT_NODE_SIZE.height,\n }\n}\n","/**\n * Locks — what a peer has claimed, and what this client may still do to it.\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 * A lock reaches down the parent chain: locking a container makes everything\n * inside it read-only, because a claim on a box that leaves its contents free\n * isn't a claim on anything. The table itself stays exactly as declared — the\n * cascade is resolved per question, not stored — so paint and policy can differ,\n * and they should: one outline on the container says what a stroke around every\n * descendant would only repeat.\n *\n * The table is keyed by {@link PresenceTargetKey}, so an edge and an anchor are\n * lockable on the same terms a node is. A bare node id is its own key, which is\n * what every 0.2.0 table already contains — widening the key space cost nothing\n * and migrates nothing. What each kind inherits differs, because containment\n * does:\n *\n * - a **node** inherits from its ancestors;\n * - an **anchor** inherits from the node that owns it, and so from that node's\n * ancestors — a locked container reaches its contents' anchors too;\n * - an **edge** inherits from nothing. Locking a node claims the node, not the\n * graph around it (the same reasoning that keeps `connect` out of\n * {@link LOCK_DEFAULT_REFUSED}), so an edge is governed by its own entry\n * alone even when both its endpoints are locked.\n */\n\nimport { MAX_PARENT_DEPTH } from \"../graph\"\nimport {\n isNodeTarget,\n type PresenceTarget,\n type PresenceTargetKey,\n parsePresenceTarget,\n presenceTargetEdgeId,\n presenceTargetKey,\n presenceTargetNodeId,\n} from \"../presence/targets\"\nimport type { EdgeId, 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 * Drag one end of an existing edge onto a different handle. Asked of the\n * *edge*, never of the nodes it runs between — reconnection is the one thing\n * a lock on an edge is for, and asking its endpoints instead would make\n * `connect`'s deliberate permissiveness meaningless.\n */\n | \"reconnect\"\n\n/** What {@link CanInteractWithLocked} is asked about. */\nexport type LockInteraction = {\n /** The thing this client is trying to act on. */\n target: PresenceTarget\n /**\n * The target actually carrying the lock: {@link target} itself for a direct\n * lock, an ancestor node when it's inherited from a container, the owning\n * node when an anchor inherits from it. Compare the two to treat a locked\n * container's contents differently from the container.\n */\n lockedTarget: PresenceTarget\n /** The peer holding the lock — the `lockedNodes` value for {@link lockedTarget}. */\n holderId: string\n /**\n * The node {@link target} belongs to: itself for a node, its owner for an\n * anchor, and `null` for an edge — an edge runs between two nodes and rivet\n * holds neither as its own. The node-shaped view of {@link target}, kept\n * because every policy written before targets existed reads it.\n */\n nodeId: NodeId | null\n /** The node-shaped view of {@link lockedTarget}, `null` for an edge lock. */\n lockedNodeId: NodeId | null\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 *\n * `reconnect` is present for the same reason `connect` isn't. It's asked of the\n * edge being re-pointed, so refusing it withholds exactly the edge somebody has\n * claimed and leaves every other wire on those nodes free.\n */\nexport const LOCK_DEFAULT_REFUSED: ReadonlySet<LockIntent> = new Set<LockIntent>([\n \"select\",\n \"drag\",\n \"resize\",\n \"delete\",\n \"reconnect\",\n])\n\n/**\n * What the registry can be asked about — a target struct, or its wire key.\n * A node's key *is* its id, so every call site written against the node-only\n * registry still passes a valid target.\n */\nexport type LockTarget = PresenceTarget | PresenceTargetKey\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 targets 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<PresenceTargetKey, string> | undefined) => PresenceTargetKey[]\n /** Install the consumer's policy, or `null` to fall back to the defaults. */\n setPolicy: (policy: CanInteractWithLocked | null) => void\n /** Whether a lock governs this target — its own, or one it inherits. */\n isLocked: (target: LockTarget) => boolean\n /**\n * Who holds the lock governing this target, or `null` when none does. Reports\n * the inherited holder for a node inside a locked container, so consumer\n * chrome inside the contents reads the same as chrome on the container.\n */\n getHolder: (target: LockTarget) => string | null\n /**\n * The target whose lock governs `target` — itself when directly locked, the\n * nearest locked ancestor (or owning node, for an anchor) when inherited,\n * `null` when nothing governs it.\n */\n getLockSource: (target: LockTarget) => PresenceTargetKey | null\n /**\n * The declared table, exactly as the prop gave it — containers only, never\n * the contents they govern. The renderer walks it once a frame and outlines\n * what it finds; expanding it to every descendant would stroke a box around\n * each child of a locked container, which reads as many claims rather than\n * one.\n *\n * Keyed by {@link PresenceTargetKey}, so a walk that only wants nodes has to\n * say so — `isNodeTarget` is the cheap filter.\n */\n getLocks: () => ReadonlyMap<PresenceTargetKey, string>\n /**\n * Just the edge locks, `edgeId -> holderId`, indexed when the table is set.\n * The edge renderer wants them keyed by edge id and nothing else, once a\n * frame; deriving that from {@link getLocks} each time would allocate a map\n * per frame to answer a question that changes at human rate.\n */\n getLockedEdges: () => ReadonlyMap<EdgeId, string>\n /** How many targets are directly locked — the render loop's early-out. */\n size: () => number\n /**\n * Whether this client may act on a target. Always `true` for a target no lock\n * governs, so callers can ask unconditionally.\n */\n allows: (target: LockTarget, 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 target→holder pairs. */\nfunction locksEqual(\n current: ReadonlyMap<PresenceTargetKey, string>,\n next: Record<PresenceTargetKey, string>,\n): boolean {\n const keys = Object.keys(next)\n if (keys.length !== current.size) return false\n return keys.every((key) => current.get(key) === next[key])\n}\n\n/** Shared empty map, so an edge-free table allocates nothing per frame. */\nconst NO_EDGE_LOCKS: ReadonlyMap<EdgeId, string> = new Map()\n\n/** Pull the `edge:` entries out of a table, keyed by edge id. */\nfunction indexEdgeLocks(\n locks: ReadonlyMap<PresenceTargetKey, string>,\n): ReadonlyMap<EdgeId, string> {\n let edges: Map<EdgeId, string> | null = null\n for (const [key, holderId] of locks) {\n const edgeId = presenceTargetEdgeId(key)\n if (edgeId === null) continue\n edges ??= new Map()\n edges.set(edgeId, holderId)\n }\n return edges ?? NO_EDGE_LOCKS\n}\n\nclass Registry implements LockRegistry {\n private locks = new Map<PresenceTargetKey, string>()\n /** `edge:` entries of {@link locks}, re-indexed on every table change. */\n private edgeLocks: ReadonlyMap<EdgeId, string> = NO_EDGE_LOCKS\n private policy: CanInteractWithLocked | null = null\n private version = 0\n private readonly listeners = new Set<() => void>()\n\n /**\n * `requestRender` marks the canvas dirty — a lock is paint as well as policy.\n * `getParentId` is how the cascade walks upward; a registry built without one\n * (no graph to ask) governs each node by its own entry alone.\n */\n constructor(\n private readonly requestRender: () => void,\n private readonly getParentId: (id: NodeId) => NodeId | undefined = () => undefined,\n ) {}\n\n setLocks = (locks: Record<PresenceTargetKey, string> | undefined): PresenceTargetKey[] => {\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: PresenceTargetKey[] = []\n for (const [key, holderId] of Object.entries(next)) {\n if (this.locks.get(key) !== holderId) opened.push(key)\n }\n this.locks = new Map(Object.entries(next))\n this.edgeLocks = indexEdgeLocks(this.locks)\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 = (target: LockTarget): boolean => this.getLockSource(target) !== null\n\n getHolder = (target: LockTarget): string | null => {\n const source = this.getLockSource(target)\n return source === null ? null : (this.locks.get(source) ?? null)\n }\n\n getLockSource = (target: LockTarget): PresenceTargetKey | null => {\n // Fast path: nothing locked at all, which is every single-player frame.\n if (this.locks.size === 0) return null\n const key = typeof target === \"string\" ? target : presenceTargetKey(target)\n // A direct entry answers for any kind, including an anchor somebody locked\n // on its own rather than through the node holding it.\n if (this.locks.has(key)) return key\n // `isNodeTarget` is the parse-free branch: true means the key *is* the node\n // id, which is the overwhelmingly common shape and the one asked per frame.\n if (isNodeTarget(key)) return this.ancestorLock(key)\n // An anchor falls back to the node that owns it (and so to that node's\n // ancestors); an edge has no owner and stops here.\n const owner = presenceTargetNodeId(key)\n if (owner === null) return null\n return this.locks.has(owner) ? owner : this.ancestorLock(owner)\n }\n\n /**\n * The nearest locked ancestor of a node, ignoring the node itself. Locking a\n * container claims what's inside it: a lock that stopped at the container\n * would leave its contents draggable out of it, deletable, and rewireable —\n * the box protected and everything it holds is not.\n */\n private ancestorLock(nodeId: NodeId): NodeId | null {\n let parentId = this.getParentId(nodeId)\n let guard = 0\n while (parentId && guard++ < MAX_PARENT_DEPTH) {\n if (this.locks.has(parentId)) return parentId\n parentId = this.getParentId(parentId)\n }\n return null\n }\n\n getLocks = (): ReadonlyMap<PresenceTargetKey, string> => this.locks\n\n getLockedEdges = (): ReadonlyMap<EdgeId, string> => this.edgeLocks\n\n size = (): number => this.locks.size\n\n allows = (target: LockTarget, intent: LockIntent): boolean => {\n if (this.locks.size === 0) return true\n const lockedKey = this.getLockSource(target)\n if (lockedKey === null) return true\n const holderId = this.locks.get(lockedKey)\n if (holderId === undefined) return true\n if (this.policy) {\n // Only now is a struct worth building: the target is locked *and* a\n // policy is installed, so this allocates on the rare answer rather than\n // on every question the marquee asks.\n const key = typeof target === \"string\" ? target : presenceTargetKey(target)\n const resolved = typeof target === \"string\" ? parsePresenceTarget(key) : target\n const lockedTarget = parsePresenceTarget(lockedKey)\n return this.policy({\n target: resolved,\n lockedTarget,\n nodeId: presenceTargetNodeId(resolved),\n lockedNodeId: presenceTargetNodeId(lockedTarget),\n holderId,\n intent,\n })\n }\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/**\n * Create a lock registry. `requestRender` repaints the holder outlines, and\n * `getParentId` lets a container's lock reach the nodes it holds.\n */\nexport function createLockRegistry(\n requestRender: () => void,\n getParentId?: (id: NodeId) => NodeId | undefined,\n): LockRegistry {\n return new Registry(requestRender, getParentId)\n}\n","import { useSyncExternalStore } from \"react\"\nimport { useRivetContext } from \"../context\"\nimport type { PeerIdentity } from \"../presence/registry\"\n\n/**\n * Who else is in the document — ids, names, colours and `data`, and nothing\n * about what anyone is doing.\n *\n * The same roster {@link usePeers} reports, on the narrower channel: this\n * re-renders when someone joins, leaves, is renamed or recoloured, and stays\n * silent while peers select and drag things. With a room of any size that's the\n * difference between an avatar stack re-rendering on human events and\n * re-rendering continuously, since at 15+ participants somebody is always\n * clicking something.\n *\n * Reach for {@link usePeers} when the chrome actually shows claims (a \"3 people\n * on this node\" badge); reach for this when it shows people.\n */\nexport function usePeerIdentities(): readonly PeerIdentity[] {\n const { store } = useRivetContext()\n return useSyncExternalStore(\n store.presence.subscribeIdentity,\n store.presence.getPeerIdentities,\n store.presence.getPeerIdentities,\n )\n}\n","/**\n * Claims, merged per target — what a {@link PresenceOptions.outlineComponent}\n * mounts a wrapper for.\n *\n * The canvas paint answers \"whose is this?\" with one stroke and moves on: it\n * walks peers and locks in a fixed order, skips whatever it has already\n * outlined, and never builds a list. A component can say more than a stroke,\n * so this collects the same facts the other way round — keyed by target, with\n * every claimant on it — and leaves the editorial decisions (stack, badge,\n * truncate) to the consumer.\n *\n * Pure and allocation-free on the empty case, because it runs on the frame\n * channel: claims arrive on a React-safe channel, but a peer's live gesture\n * doesn't (transform frames never notify), and their frames can beat their\n * roster entry. Deriving the set per frame is the only way to be right about\n * both, and it's the same union the renderer takes.\n */\n\nimport type { PeerClaim, PeerIdentity, PeerRecord } from \"./registry\"\nimport { type PresenceTarget, type PresenceTargetKey, parsePresenceTarget } from \"./targets\"\n\n/**\n * Which layer paints peer claims: rivet's outlines on the foreground canvas,\n * the consumer's component in a DOM overlay, or nothing at all.\n *\n * The halo under a claimed edge follows `\"none\"` alone — it belongs to the edge\n * renderer, which is the only thing that knows the path, so a DOM component\n * replaces the boxes and leaves the strokes.\n */\nexport type PeerOutlineMode = \"canvas\" | \"dom\" | \"none\"\n\n/** One claimed target: what it is, who claims it, and whether it's locked. */\nexport type PeerOutlineEntry = {\n /** The wire key — stable across frames, so it keys the wrapper element. */\n key: PresenceTargetKey\n target: PresenceTarget\n claims: readonly PeerClaim[]\n lock: { holderId: string; peer?: PeerIdentity } | null\n}\n\nconst EMPTY_ENTRIES: readonly PeerOutlineEntry[] = []\n\ntype MutableEntry = {\n key: PresenceTargetKey\n target: PresenceTarget\n claims: PeerClaim[]\n lock: { holderId: string; peer?: PeerIdentity } | null\n}\n\n/**\n * Every target somebody else has a claim on, in the order a reader would rank\n * them: each peer's held targets before their selected ones, peers in roster\n * order, locks last.\n *\n * `identities` is the registry's cached identity snapshot rather than the\n * records themselves — it keeps its object identity across claim changes, so a\n * component that re-renders when claims move still sees the same `peer` prop it\n * saw last time. A holder no identity covers (a lock outliving its tab) is\n * synthesized from the id, which is where its colour comes from anyway.\n */\nexport function collectPeerOutlines(\n peers: readonly PeerRecord[],\n identities: readonly PeerIdentity[],\n locks: ReadonlyMap<PresenceTargetKey, string>,\n): readonly PeerOutlineEntry[] {\n if (peers.length === 0 && locks.size === 0) return EMPTY_ENTRIES\n const byId = new Map(identities.map((identity) => [identity.id, identity]))\n const entries = new Map<PresenceTargetKey, MutableEntry>()\n\n const entry = (key: PresenceTargetKey): MutableEntry => {\n let found = entries.get(key)\n if (!found) {\n found = { key, target: parsePresenceTarget(key), claims: [], lock: null }\n entries.set(key, found)\n }\n return found\n }\n\n const claim = (key: PresenceTargetKey, peer: PeerIdentity, kind: PeerClaim[\"kind\"]): void => {\n const target = entry(key)\n // One claim per peer per target: somebody dragging their own selection\n // holds and selects the same node, and listing them twice would make a\n // three-peer stack look like four.\n if (target.claims.some((existing) => existing.peer.id === peer.id)) return\n target.claims.push({ peer, kind })\n }\n\n for (const peer of peers) {\n // The record is itself a superset of an identity, so it stands in if the\n // snapshots ever disagree about who exists. Never a fresh object, either:\n // {@link peerOutlinesEqual} compares peers by reference, and something\n // allocated per frame would re-render the component on every one of them.\n const identity: PeerIdentity = byId.get(peer.id) ?? peer\n // Held first, so it wins the per-peer dedupe above.\n for (const key of peer.holding) claim(key, identity, \"held\")\n // A live gesture is the more current fact than the roster entry describing\n // it — the frames are already moving the node, so the claim exists whether\n // or not their `holding` has caught up.\n for (const nodeId of peer.transforms.keys()) claim(nodeId, identity, \"held\")\n for (const key of peer.selection) claim(key, identity, \"selected\")\n }\n\n for (const [key, holderId] of locks) {\n entry(key).lock = { holderId, peer: byId.get(holderId) }\n }\n\n return [...entries.values()]\n}\n\n/**\n * Whether two collections say the same thing — the test that keeps a frame from\n * re-rendering React. Compares what a component is handed (which targets, who\n * claims them and how, who holds the lock) and nothing else: geometry moves\n * every frame and is written to the wrapper element instead.\n */\nexport function peerOutlinesEqual(\n a: readonly PeerOutlineEntry[],\n b: readonly PeerOutlineEntry[],\n): boolean {\n if (a === b) return true\n if (a.length !== b.length) return false\n return a.every((left, index) => {\n const right = b[index]\n if (!right || left.key !== right.key) return false\n if (left.lock?.holderId !== right.lock?.holderId) return false\n if (left.claims.length !== right.claims.length) return false\n return left.claims.every((claim, i) => {\n const other = right.claims[i]\n return other?.peer === claim.peer && other?.kind === claim.kind\n })\n })\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 * Answers for the lock that *governs* the node: a node inside a locked container\n * reports the container's holder, so a disabled input inside the contents reads\n * the same as one on the container itself.\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"]}
@@ -0,0 +1,2 @@
1
+ import {createContext,useContext,useSyncExternalStore}from'react';var l=createContext(null),a=createContext(null);function o(){let e=useContext(l);if(!e)throw new Error("useRivetContext must be used inside <Rivet>");return e}function u(){let e=useContext(a);if(!e)throw new Error("useRivetSwimlaneContext must be used inside <Rivet>");return e}function p(){return {...o(),...u()}}function m(){return o().controls}function R(){let{store:e}=o();return useSyncExternalStore(e.subscribeViewport,e.getViewport,e.getViewport)}function v(){let{store:e}=o(),r=useSyncExternalStore(e.subscribeHistory,e.canUndo,e.canUndo),s=useSyncExternalStore(e.subscribeHistory,e.canRedo,e.canRedo);return {canUndo:r,canRedo:s,undo:e.undo,redo:e.redo,clearHistory:e.clearHistory}}function w(){let{store:e}=o();return useSyncExternalStore(e.subscribeFocus,e.getFocusedNode,e.getFocusedNode)}var d=createContext(null);function C(){let e=useContext(d);if(!e)throw new Error("<Handle> must be rendered inside a rivet node component");return e}export{l as a,a as b,o as c,u as d,p as e,m as f,R as g,v as h,w as i,d as j,C as k};//# sourceMappingURL=chunk-NLE6BOPW.js.map
2
+ //# sourceMappingURL=chunk-NLE6BOPW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/context.ts"],"names":["RivetContext","createContext","RivetSwimlaneContext","useRivetContext","value","useContext","useRivetSwimlaneContext","useFullRivetContext","useRivetControls","useRivetViewport","store","useSyncExternalStore","useRivetHistory","canUndo","canRedo","useRivetFocusedNode","RivetNodeContext","useRivetNodeContext","ctx"],"mappings":"kEAkGO,IAAMA,CAAAA,CAAeC,cAA4C,IAAI,CAAA,CAC/DC,EAAuBD,aAAAA,CAAgD,IAAI,EAMjF,SAASE,CAAAA,EAAyC,CACvD,IAAMC,CAAAA,CAAQC,UAAAA,CAAWL,CAAY,EACrC,GAAI,CAACI,EAAO,MAAM,IAAI,MAAM,6CAA6C,CAAA,CACzE,OAAOA,CACT,CAGO,SAASE,CAAAA,EAAqD,CACnE,IAAMF,CAAAA,CAAQC,UAAAA,CAAWH,CAAoB,CAAA,CAC7C,GAAI,CAACE,CAAAA,CAAO,MAAM,IAAI,KAAA,CAAM,qDAAqD,CAAA,CACjF,OAAOA,CACT,CAOO,SAASG,GAAyC,CACvD,OAAO,CAAE,GAAGJ,CAAAA,GAAmB,GAAGG,CAAAA,EAA0B,CAC9D,CAGO,SAASE,CAAAA,EAAkC,CAChD,OAAOL,CAAAA,EAAgB,CAAE,QAC3B,CAGO,SAASM,GAA6B,CAC3C,GAAM,CAAE,KAAA,CAAAC,CAAM,EAAIP,CAAAA,EAAgB,CAClC,OAAOQ,oBAAAA,CAAqBD,CAAAA,CAAM,kBAAmBA,CAAAA,CAAM,WAAA,CAAaA,CAAAA,CAAM,WAAW,CAC3F,CAGO,SAASE,GAMd,CACA,GAAM,CAAE,KAAA,CAAAF,CAAM,EAAIP,CAAAA,EAAgB,CAC5BU,EAAUF,oBAAAA,CAAqBD,CAAAA,CAAM,iBAAkBA,CAAAA,CAAM,OAAA,CAASA,EAAM,OAAO,CAAA,CACnFI,CAAAA,CAAUH,oBAAAA,CAAqBD,EAAM,gBAAA,CAAkBA,CAAAA,CAAM,QAASA,CAAAA,CAAM,OAAO,EACzF,OAAO,CAAE,QAAAG,CAAAA,CAAS,OAAA,CAAAC,EAAS,IAAA,CAAMJ,CAAAA,CAAM,KAAM,IAAA,CAAMA,CAAAA,CAAM,KAAM,YAAA,CAAcA,CAAAA,CAAM,YAAa,CAClG,CAOO,SAASK,CAAAA,EAAqC,CACnD,GAAM,CAAE,MAAAL,CAAM,CAAA,CAAIP,GAAgB,CAClC,OAAOQ,qBAAqBD,CAAAA,CAAM,cAAA,CAAgBA,EAAM,cAAA,CAAgBA,CAAAA,CAAM,cAAc,CAC9F,KAQaM,CAAAA,CAAmBf,aAAAA,CAA4C,IAAI,EAEzE,SAASgB,GAA6C,CAC3D,IAAMC,EAAMb,UAAAA,CAAWW,CAAgB,EACvC,GAAI,CAACE,EAAK,MAAM,IAAI,MAAM,yDAAyD,CAAA,CACnF,OAAOA,CACT","file":"chunk-NLE6BOPW.js","sourcesContent":["import {\n createContext,\n type ReactNode,\n type RefObject,\n useContext,\n useSyncExternalStore,\n} from \"react\"\nimport type { ResolvedAnchorOptions } from \"./anchor/registry\"\nimport type { RivetStore } from \"./store\"\nimport type {\n ResolvedLane,\n ResolvedMargin,\n ResolvedSwimlaneGroup,\n ViewportElements,\n} from \"./swimlane/lanes\"\nimport type {\n Connection,\n DefaultEdgeOptions,\n NodeId,\n NodeTypes,\n RivetControls,\n SwimlaneHeaderProps,\n SwimlaneLabelProps,\n SwimlaneSizeChange,\n Viewport,\n} from \"./types\"\n\n/**\n * Swimlane layout — the volatile half. Lane heights change on a resize drag and\n * on auto-fit, and a context re-renders every consumer when its value changes,\n * whichever fields they actually read. Kept apart so a lane resize re-renders\n * the overlay rather than every node.\n */\nexport type RivetSwimlaneContextValue = {\n /** Resolved swimlane groups (header + lanes, world space). Empty when unused. */\n swimlaneGroups: ResolvedSwimlaneGroup[]\n /** Every group's lanes, flattened — the clamp targets. */\n swimlaneLanes: ResolvedLane[]\n /** Whether node drags are bounded to a lane and reassign lane membership. */\n clampToSwimlane: boolean\n /** Inner clearance kept between a clamped node and its lane edges. */\n swimlaneMargin: ResolvedMargin\n /** Screen width of the sticky lane-label strip, in px. */\n swimlaneLabelWidth: number\n /** Screen height of the sticky group header band, in px. */\n swimlaneHeaderSize: number\n /** Whether lanes show a bottom resize handle. */\n swimlaneResizable: boolean\n /**\n * Resize a lane. `commit` fires the consumer's `onSwimlaneSizeChange` with the\n * new height; `reason` tags it (`\"resize\"` for a drag, `\"autofit\"` for a\n * fit-to-content snap). Defaults to `\"resize\"`.\n */\n resizeSwimlane: (\n laneId: string,\n height: number,\n commit: boolean,\n reason?: SwimlaneSizeChange[\"reason\"],\n ) => void\n /** Consumer override for the sticky group header content. */\n renderSwimlaneHeader?: (props: SwimlaneHeaderProps) => ReactNode\n /** Consumer override for the sticky lane label content. */\n renderSwimlaneLabel?: (props: SwimlaneLabelProps) => ReactNode\n}\n\n/** Everything that isn't swimlane layout — stable across an interaction. */\nexport type RivetCoreContextValue = {\n store: RivetStore\n nodeTypes: NodeTypes\n paneRef: RefObject<HTMLDivElement | null>\n /** Imperative viewport controls (zoom, fit-view). */\n controls: RivetControls\n /** Keys that make a click add to (toggle in) the selection. */\n multiSelectionKeys: string[]\n /**\n * Publish a message to the pane's visually-hidden live region, so screen\n * readers narrate keyboard interactions (grab/move/drop, connect).\n */\n announce: (message: string) => void\n /** Id of the pane's hidden keyboard-instructions element (`aria-describedby` target). */\n nodeDescriptionId: string\n /** Defaults merged into every edge — the label layer reads `labelStyle`. */\n defaultEdgeOptions?: DefaultEdgeOptions\n /** Resolved placement policy for anchor chrome (dots + stray handles). */\n anchorOptions: ResolvedAnchorOptions\n /** Snapshot the nodes and lanes currently on screen. Imperative — never subscribes. */\n getViewportElements: () => ViewportElements\n /** Veto a proposed connection; return false to reject. */\n isValidConnection?: (connection: Connection) => boolean\n /** Rewrite a validated connection before commit; `null` cancels the drop. */\n mapConnection?: (connection: Connection) => Connection | null\n /** Called after a valid connection is made. */\n onConnect?: (connection: Connection) => void\n}\n\n/** The full context, as {@link useRivetContext} returns it. */\nexport type RivetContextValue = RivetCoreContextValue & RivetSwimlaneContextValue\n\nexport const RivetContext = createContext<RivetCoreContextValue | null>(null)\nexport const RivetSwimlaneContext = createContext<RivetSwimlaneContextValue | null>(null)\n\n/**\n * The core half — what internal components read. A swimlane resize must not\n * re-render every node just because lane geometry lives beside the store.\n */\nexport function useRivetContext(): RivetCoreContextValue {\n const value = useContext(RivetContext)\n if (!value) throw new Error(\"useRivetContext must be used inside <Rivet>\")\n return value\n}\n\n/** Swimlane layout. Read by the overlay, and by consumers laying out against lanes. */\nexport function useRivetSwimlaneContext(): RivetSwimlaneContextValue {\n const value = useContext(RivetSwimlaneContext)\n if (!value) throw new Error(\"useRivetSwimlaneContext must be used inside <Rivet>\")\n return value\n}\n\n/**\n * Both halves as one object — the shape `useRivetContext` has always had for\n * consumers, and what the package exports under that name. Re-renders on\n * either half, which is what asking for everything means.\n */\nexport function useFullRivetContext(): RivetContextValue {\n return { ...useRivetContext(), ...useRivetSwimlaneContext() }\n}\n\n/** Access the imperative viewport controls from inside `<Rivet>`. */\nexport function useRivetControls(): RivetControls {\n return useRivetContext().controls\n}\n\n/** Subscribe to the live viewport — re-renders on every pan/zoom. */\nexport function useRivetViewport(): Viewport {\n const { store } = useRivetContext()\n return useSyncExternalStore(store.subscribeViewport, store.getViewport, store.getViewport)\n}\n\n/** Reactive undo/redo state + actions — re-renders when `canUndo`/`canRedo` flip. */\nexport function useRivetHistory(): {\n canUndo: boolean\n canRedo: boolean\n undo: () => boolean\n redo: () => boolean\n clearHistory: () => void\n} {\n const { store } = useRivetContext()\n const canUndo = useSyncExternalStore(store.subscribeHistory, store.canUndo, store.canUndo)\n const canRedo = useSyncExternalStore(store.subscribeHistory, store.canRedo, store.canRedo)\n return { canUndo, canRedo, undo: store.undo, redo: store.redo, clearHistory: store.clearHistory }\n}\n\n/**\n * The node holding keyboard focus, or `null` — re-renders as focus roves.\n * Rendering a focus indicator is the consumer's job (rivet is headless); this\n * is the state to drive it from, along with the wrapper's native `:focus-visible`.\n */\nexport function useRivetFocusedNode(): NodeId | null {\n const { store } = useRivetContext()\n return useSyncExternalStore(store.subscribeFocus, store.getFocusedNode, store.getFocusedNode)\n}\n\n/** Per-node context so `<Handle>` can find its node and measure against it. */\nexport type RivetNodeContextValue = {\n nodeId: NodeId\n wrapperRef: RefObject<HTMLDivElement | null>\n}\n\nexport const RivetNodeContext = createContext<RivetNodeContextValue | null>(null)\n\nexport function useRivetNodeContext(): RivetNodeContextValue {\n const ctx = useContext(RivetNodeContext)\n if (!ctx) throw new Error(\"<Handle> must be rendered inside a rivet node component\")\n return ctx\n}\n"]}
@@ -0,0 +1,2 @@
1
+ function e(n,r,u){return Math.min(Math.max(n,r),u)}function m(n,r,u){return n+(r-n)*u}function b(n,r){return r<=0?n:Math.round(n/r)*r}export{e as a,m as b,b as c};//# sourceMappingURL=chunk-RDSWIH3F.js.map
2
+ //# sourceMappingURL=chunk-RDSWIH3F.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../utils/src/math.ts"],"names":["clamp","value","min","max","lerp","a","b","t","snap","step"],"mappings":"AACO,SAASA,EAAMC,CAAAA,CAAeC,CAAAA,CAAaC,EAAqB,CACrE,OAAO,KAAK,GAAA,CAAI,IAAA,CAAK,IAAIF,CAAAA,CAAOC,CAAG,EAAGC,CAAG,CAC3C,CAGO,SAASC,CAAAA,CAAKC,EAAWC,CAAAA,CAAWC,CAAAA,CAAmB,CAC5D,OAAOF,GAAKC,CAAAA,CAAID,CAAAA,EAAKE,CACvB,CAGO,SAASC,EAAKP,CAAAA,CAAeQ,CAAAA,CAAsB,CACxD,OAAIA,CAAAA,EAAQ,EAAUR,CAAAA,CACf,IAAA,CAAK,MAAMA,CAAAA,CAAQQ,CAAI,EAAIA,CACpC","file":"chunk-RDSWIH3F.js","sourcesContent":["/** Clamp `value` into the inclusive range `[min, max]`. */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max)\n}\n\n/** Linear interpolation between `a` and `b` by `t` (unclamped). */\nexport function lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t\n}\n\n/** Round `value` to `step`'s grid (used for snap-to-grid). */\nexport function snap(value: number, step: number): number {\n if (step <= 0) return value\n return Math.round(value / step) * step\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import {b as b$1}from'./chunk-B3YKB2QN.js';var P=["top","right","bottom","left"];function T(r,e){let t=null,n=null;for(let o of r){if(!o.selectable)continue;let s=o.elements.find(c=>c.contains(e));s&&(n&&s.contains(n)||(t=o,n=s));}return t}var Y={defaultSide:"top",centerKeepoutPct:14,cornerMarginPct:8,dotSize:7,strayHandleSize:8},l="inode-",d="inode-text-";function b(r){return P.includes(r)}function A(r,e){return `${l}${r}-${e}`}function C(r){if(!r?.startsWith(l)||r.startsWith(d))return null;let e=r.lastIndexOf("-"),t=r.slice(e+1);if(!b(t))return null;let n=r.slice(l.length,e);return n?{anchorId:n,side:t}:null}var R="auto";function G(r){return `${l}${r}-${R}`}function _(r){if(!r?.startsWith(l)||r.startsWith(d))return null;let e=r.lastIndexOf("-");return r.slice(e+1)!==R?null:r.slice(l.length,e)||null}function k(r,e){return `${d}${r}-${e}`}function g(r){if(!r?.startsWith(d))return null;let e=r.lastIndexOf("-"),t=r.slice(e+1);if(!b(t))return null;let n=r.slice(d.length,e);return n?{anchorId:n,side:t}:null}function D(r){let e=g(r.sourceHandle),t=g(r.targetHandle);return !e&&!t?r:{...r,sourceHandle:e?A(e.anchorId,e.side):r.sourceHandle,targetHandle:t?A(t.anchorId,t.side):r.targetHandle}}function N(r,e,t,n){let o=(s,c)=>({xPct:(s-r.left)/r.width*100,yPct:(c-r.top)/r.height*100});return {firstLineYPct:(e.top+e.height/2-r.top)/r.height*100,lastLineYPct:(t.top+t.height/2-r.top)/r.height*100,centerXPct:(n.left+n.width/2-r.left)/r.width*100,dots:{top:o(e.left+e.width/2,e.top),bottom:o(t.left+t.width/2,t.top+t.height),left:o(e.left,e.top+e.height/2),right:o(t.left+t.width,t.top+t.height/2)}}}function z(r){return r.filter(e=>e.selectable).map((e,t)=>({record:e,index:t})).sort((e,t)=>{let n=e.record.geometry,o=t.record.geometry;if(!n||!o)return n?-1:o?1:e.index-t.index;let s=n.firstLineYPct-o.firstLineYPct;return Math.abs(s)>v?s:n.centerXPct-o.centerXPct}).map(({record:e})=>e)}var v=2;function V(r,e){let{dots:t}=e,n=Math.min(t.left.xPct,t.right.xPct),o=Math.max(t.left.xPct,t.right.xPct),s=Math.min(t.top.yPct,t.bottom.yPct),c=Math.max(t.top.yPct,t.bottom.yPct);return {x:r.x+n/100*r.width,y:r.y+s/100*r.height,width:(o-n)/100*r.width,height:(c-s)/100*r.height}}var a=.1;function E(r,e){return Math.abs(r.xPct-e.xPct)<a&&Math.abs(r.yPct-e.yPct)<a}function L(r,e){return r===e?true:!r||!e?false:Math.abs(r.firstLineYPct-e.firstLineYPct)<a&&Math.abs(r.lastLineYPct-e.lastLineYPct)<a&&Math.abs(r.centerXPct-e.centerXPct)<a&&P.every(t=>E(r.dots[t],e.dots[t]))}function M(r,e){let t=Math.min(Math.max(r,e.cornerMarginPct),100-e.cornerMarginPct);return Math.abs(t-50)>=e.centerKeepoutPct?t:t<50?50-e.centerKeepoutPct:50+e.centerKeepoutPct}function $(r,e,t){let n=w(r,e,t);switch(e){case "left":return {xPct:0,yPct:n};case "right":return {xPct:100,yPct:n};case "top":return {xPct:n,yPct:0};case "bottom":return {xPct:n,yPct:100}}}function w(r,e,t){let n=e==="left"?r.firstLineYPct:e==="right"?r.lastLineYPct:r.centerXPct;return M(n,t)}var y=class{constructor(e){this.deps=e;}deps;anchors=new Map;anchorVersions=new Map;anchorListeners=new Map;selectedAnchors=new Map;selectedAnchorSnapshot=[];selectedAnchorsStale=false;anchorSelectionListeners=new Set;bumpAnchors(e){this.anchorVersions.set(e,(this.anchorVersions.get(e)??0)+1);let t=this.anchorListeners.get(e);if(t)for(let n of t)n();}dropNode(e){this.anchorVersions.delete(e);let t=`${e} `;for(let n of this.anchors.keys())n.startsWith(t)&&this.anchors.delete(n);this.dropSelected(n=>n.nodeId===e);}measureAnchor(e){let t=this.anchors.get(e);if(!t)return false;let n=t.elements.filter(f=>f.isConnected),o=n[0],s=n[n.length-1];if(!o||!s)return false;let c=this.deps.getNodeElement(t.nodeId);if(!c)return false;let i=c.getBoundingClientRect(),u=H(n.map(f=>f.getBoundingClientRect()));if(i.width<=0||i.height<=0||u.width<=0&&u.height<=0)return false;let x=o.getClientRects(),p=s.getClientRects(),S=x.item(0)??o.getBoundingClientRect(),I=p.item(p.length-1)??s.getBoundingClientRect(),m=N(i,S,I,u);return L(t.geometry,m)?false:(this.anchors.set(e,{...t,geometry:m}),true)}registerAnchor=(e,t,n,o)=>{let s=b$1(e,t),c=this.anchors.get(s),i=Array.isArray(n)?[...n]:[n];this.anchors.set(s,{nodeId:e,anchorId:t,color:o?.color??c?.color,strays:o?.strays??c?.strays,selectable:o?.selectable??c?.selectable,data:o?.data??c?.data,elements:i,element:i[0]??null,geometry:c?.geometry??null}),this.measureAnchor(s),this.bumpAnchors(e),this.deps.requestRender();};detachAnchor=(e,t)=>{let n=b$1(e,t),o=this.anchors.get(n);!o||o.elements.length===0||(this.anchors.set(n,{...o,elements:[],element:null}),this.bumpAnchors(e));};unregisterAnchor=(e,t)=>{this.anchors.delete(b$1(e,t))&&(this.dropSelected(n=>n.nodeId===e&&n.anchorId===t),this.bumpAnchors(e),this.deps.requestRender());};remeasureAnchors=e=>{let t=false;for(let[n,o]of this.anchors)o.nodeId===e&&this.measureAnchor(n)&&(t=true);t&&(this.bumpAnchors(e),this.deps.requestRender());};getNodeAnchors=e=>{let t=[];for(let n of this.anchors.values())n.nodeId===e&&t.push(n);return t};getNodeAnchorsVersion=e=>this.anchorVersions.get(e)??0;getSelectedAnchors=()=>(this.selectedAnchorsStale&&(this.selectedAnchorsStale=false,this.selectedAnchorSnapshot=[...this.selectedAnchors.values()]),this.selectedAnchorSnapshot);setSelectedAnchors=e=>{let t=new Map;for(let n of e)t.set(b$1(n.nodeId,n.anchorId),n);if(!(t.size===this.selectedAnchors.size&&[...t.keys()].every(n=>this.selectedAnchors.has(n)))){t.size>0&&this.deps.hasGraphSelection()&&this.deps.clearGraphSelection(),this.selectedAnchors=t,this.selectedAnchorsStale=true;for(let n of this.anchorSelectionListeners)n();this.deps.requestRender();}};isAnchorSelected=(e,t)=>this.selectedAnchors.has(b$1(e,t));subscribeAnchorSelection=e=>(this.anchorSelectionListeners.add(e),()=>{this.anchorSelectionListeners.delete(e);});dropSelected(e){if(this.selectedAnchors.size===0)return;let t=false;for(let[n,o]of this.selectedAnchors)e(o)&&(this.selectedAnchors.delete(n),t=true);if(t){this.selectedAnchorsStale=true;for(let n of this.anchorSelectionListeners)n();this.deps.requestRender();}}subscribeNodeAnchors=(e,t)=>{let n=this.anchorListeners.get(e);return n||(n=new Set,this.anchorListeners.set(e,n)),n.add(t),()=>{n.delete(t),n.size===0&&this.anchorListeners.delete(e);}}};function H(r){let e=Number.POSITIVE_INFINITY,t=Number.POSITIVE_INFINITY,n=Number.NEGATIVE_INFINITY,o=Number.NEGATIVE_INFINITY;for(let s of r)e=Math.min(e,s.left),t=Math.min(t,s.top),n=Math.max(n,s.right),o=Math.max(o,s.bottom);return Number.isFinite(e)?{left:e,top:t,width:n-e,height:o-t}:{left:0,top:0,width:0,height:0}}export{P as a,T as b,Y as c,A as d,C as e,G as f,_ as g,k as h,g as i,D as j,N as k,z as l,V as m,$ as n,w as o,y as p};//# sourceMappingURL=chunk-X4VW3W4W.js.map
2
+ //# sourceMappingURL=chunk-X4VW3W4W.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/anchor/registry.ts"],"names":["ANCHOR_SIDES","findSelectableAnchor","records","target","found","foundElement","record","hit","element","DEFAULT_ANCHOR_OPTIONS","STRAY_PREFIX","DOT_PREFIX","isSide","value","anchorHandleId","anchorId","side","parseAnchorHandleId","handleId","lastDash","AUTO_SIDE","anchorAutoHandleId","parseAnchorAutoHandleId","anchorDotHandleId","parseAnchorDotHandleId","normalizeAnchorConnection","connection","source","computeAnchorGeometry","root","first","last","box","point","x","y","orderedSelectableAnchors","index","a","b","left","right","rowDelta","SAME_ROW_TOLERANCE_PCT","anchorRect","node","geometry","dots","top","bottom","GEOMETRY_TOLERANCE_PCT","pointsEqual","anchorGeometryEqual","placeAlongSide","rawPct","options","clamped","strayPoint","along","strayPlacementPct","raw","AnchorRegistry","deps","id","listeners","listener","nodeId","prefix","key","ref","live","firstElement","lastElement","nodeEl","unionRect","firstRects","lastRects","handleKey","prev","elements","changed","refs","next","matches","set","rects","rect"],"mappings":"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","file":"chunk-X4VW3W4W.js","sourcesContent":["/**\n * Anchor — a connectable point bound to a DOM element rendered inside a node.\n *\n * Rivet knows nothing about what the element means (an inline text span, an\n * icon, a table row — that's the consumer's business); it owns the geometry:\n * measuring the element's line boxes within the node, rendering grab dots and\n * stray perimeter handles from those measurements, and rewriting dot handle\n * ids to the persisted stray ids when a connection commits.\n *\n * Placement derives from the element's *first* and *last* client line rects —\n * never from CSS positioning inside the inline element, which Chrome anchors\n * to the first line box only — so a line-wrapped span places each dot where it\n * visually belongs. All values are percentages of the node box: client rects\n * scale together under the viewport transform, so the ratio cancels zoom.\n */\n\nimport { handleKey } from \"../constants\"\nimport type { Connection, HandlePosition, NodeId, Rect } from \"../types\"\n\n/** The four sides an anchor can expose handles on. */\nexport const ANCHOR_SIDES = [\"top\", \"right\", \"bottom\", \"left\"] as const\n\n/** A measured point within a node's box, as percentages (zoom-independent). */\nexport type AnchorPoint = { xPct: number; yPct: number }\n\n/** Where an anchor's element sits within its node box, from measured line rects. */\nexport type AnchorGeometry = {\n /** Vertical center of the element's first line box, as % of the node height. */\n firstLineYPct: number\n /** Vertical center of the element's last line box, as % of the node height. */\n lastLineYPct: number\n /** Horizontal center of the element's bounding box, as % of the node width. */\n centerXPct: number\n /**\n * Grab dot positions: left/top on the first line box, right/bottom on the\n * last, so a wrapped element gets each dot on the line it belongs to.\n */\n dots: Record<HandlePosition, AnchorPoint>\n}\n\n/**\n * Stray-handle chrome policy, chosen at registration:\n *\n * - `\"dynamic\"` — one perimeter handle per connected side, a single\n * default-side handle while unconnected. The inline-text default.\n * - `\"none\"` — no stray chrome at all; the anchor connects through its grab\n * dots only. For anchors whose element boundary *is* the visual connection\n * point (an SVG shape), where a floating perimeter handle reads as clutter.\n * Purely visual: edges bound to stray ids still resolve at the measured\n * placement — the renderer derives endpoints from geometry, not chrome.\n */\nexport type AnchorStrayPolicy = \"dynamic\" | \"none\"\n\n/**\n * A registered anchor. Empty `elements` means detached — the consumer unmounted\n * the display (or an editor replaced the DOM node) without unregistering, so\n * the last-known `geometry` is retained and edges stay put (stale retention).\n * Unregistering is the explicit removal signal.\n *\n * An anchor can be bound to **several** elements, because one thing on screen\n * isn't always one element: a rich-text mark interrupted by another mark\n * renders as two spans, and an imported graphic can be a group of paths. They\n * are treated as fragments of a single run — first line of the first, last line\n * of the last — which is what {@link AnchorGeometry} already describes.\n */\nexport type AnchorRecord = {\n nodeId: NodeId\n anchorId: string\n /** Accent color for the anchor's dots and stray handles. */\n color?: string\n /** Stray chrome policy. Absent = `\"dynamic\"`. */\n strays?: AnchorStrayPolicy\n /** Whether the anchor is a selection target at all — by press or key. Absent = `false`. */\n selectable?: boolean\n /**\n * Whatever the consumer attached at registration — a shape's label, the\n * bounds it was imported at, the layer it came from. Rivet stores and returns\n * it, and never reads it.\n */\n data?: unknown\n /**\n * Every element this anchor is bound to, in document order. Empty while\n * detached.\n */\n elements: readonly Element[]\n /**\n * The first of {@link elements}, or `null` when detached — the answer for the\n * overwhelmingly common one-element anchor, kept so call sites that only need\n * \"is this anchor live, and where\" don't have to index.\n */\n element: Element | null\n /** Null until the first successful measurement. */\n geometry: AnchorGeometry | null\n}\n\nexport type AnchorRegistrationOptions = {\n color?: string\n /** Stray-handle chrome policy for this anchor. Default `\"dynamic\"`. */\n strays?: AnchorStrayPolicy\n /**\n * Make the anchor a selection target: the element becomes pointer-interactive\n * so a click selects it, and it joins the node's keyboard stepping order\n * (Alt+Left/Right on the focused node).\n *\n * Default `false`: an anchor around inline text usually sits in editable\n * content, where swallowing a press would stop the caret from landing. Shapes\n * are the opposite case — the element *is* the thing you mean to click. One\n * flag for both routes, so an anchor is never selectable one way and not the\n * other.\n */\n selectable?: boolean\n /** Opaque consumer payload, returned on the record untouched. */\n data?: unknown\n}\n\n/** Identifies one anchor: which node holds it, and which anchor on that node. */\nexport type AnchorRef = {\n nodeId: NodeId\n anchorId: string\n}\n\n/**\n * The selectable anchor a press landed in, or `null` for a press anywhere else\n * on the node. Innermost wins: anchors can nest (a shape inside a group that is\n * itself an anchor), and the deepest one is the one under the pointer.\n */\nexport function findSelectableAnchor(\n records: readonly AnchorRecord[],\n target: Element,\n): AnchorRecord | null {\n let found: AnchorRecord | null = null\n let foundElement: Element | null = null\n for (const record of records) {\n if (!record.selectable) continue\n const hit = record.elements.find((element) => element.contains(target))\n if (!hit) continue\n // Innermost wins across fragments too: the containing element of the\n // candidate is compared, not the record's first.\n if (foundElement && hit.contains(foundElement)) continue\n found = record\n foundElement = hit\n }\n return found\n}\n\n/**\n * Imperative registration handle for headless consumers (rich-text editors and\n * other renderers that own their DOM). Returned by `RivetInstance.registerAnchor`.\n */\nexport type AnchorRegistration = {\n /**\n * Re-attach the anchor after the renderer replaced its DOM node — one element\n * or, for content that renders as several, all of its fragments. Passing\n * `null` detaches instead: geometry is retained and edges stay put until the\n * element (or a replacement) is rebound or the anchor is unregistered.\n */\n rebind: (element: Element | readonly Element[] | null) => void\n /** Explicit removal — drops the anchor and the chrome rendered for it. */\n unregister: () => void\n}\n\n/** Placement policy for anchor chrome, set via the `anchorOptions` prop. */\nexport type AnchorOptions = {\n /** Side an unconnected anchor shows its single stray handle on. Default `\"top\"`. */\n defaultSide?: HandlePosition\n /**\n * Minimum distance (in % of the side) a stray handle keeps from the side's\n * center, where a node's own side-center handles usually sit. Default `14`.\n */\n centerKeepoutPct?: number\n /** Minimum distance (in % of the side) a stray handle keeps from the corners. Default `8`. */\n cornerMarginPct?: number\n /** Grab dot diameter in px. Default `7`. */\n dotSize?: number\n /** Stray handle diameter in px. Default `8`. */\n strayHandleSize?: number\n}\n\nexport type ResolvedAnchorOptions = Required<AnchorOptions>\n\nexport const DEFAULT_ANCHOR_OPTIONS: ResolvedAnchorOptions = {\n defaultSide: \"top\",\n centerKeepoutPct: 14,\n cornerMarginPct: 8,\n dotSize: 7,\n strayHandleSize: 8,\n}\n\n// ============================================================================\n// Handle-id grammar\n// ============================================================================\n//\n// The string forms are the persisted wire format for anchor connectivity:\n// `inode-<anchorId>-<side>` for the stray (perimeter) handles that edges\n// reference, `inode-text-<anchorId>-<side>` for the in-content grab dots that\n// only ever *start* a drag. The `inode-` prefix is the historical grammar from\n// the pre-primitive consumer implementation, kept verbatim so existing graphs\n// load without migration; the structured edge-end model replaces these strings\n// later without changing what's stored.\n\nconst STRAY_PREFIX = \"inode-\"\nconst DOT_PREFIX = \"inode-text-\"\n\nfunction isSide(value: string): value is HandlePosition {\n return (ANCHOR_SIDES as readonly string[]).includes(value)\n}\n\n/** Build the persisted handle id for an anchor's stray handle on `side`. */\nexport function anchorHandleId(anchorId: string, side: HandlePosition): string {\n return `${STRAY_PREFIX}${anchorId}-${side}`\n}\n\n/** Parse a stray anchor handle id back into `{ anchorId, side }`, or null. */\nexport function parseAnchorHandleId(\n handleId: string | null | undefined,\n): { anchorId: string; side: HandlePosition } | null {\n // A dot handle id shares the stray prefix; it is a local-only affordance,\n // never a persisted edge handle, so it must not be read as a stray here.\n if (!handleId?.startsWith(STRAY_PREFIX) || handleId.startsWith(DOT_PREFIX)) return null\n const lastDash = handleId.lastIndexOf(\"-\")\n const side = handleId.slice(lastDash + 1)\n if (!isSide(side)) return null\n const anchorId = handleId.slice(STRAY_PREFIX.length, lastDash)\n if (!anchorId) return null\n return { anchorId, side }\n}\n\n/**\n * The auto-side marker in the anchor id grammar. `inode-<anchorId>-auto` is an\n * anchor end with *no pinned side*: the renderer resolves the side per frame to\n * face the peer node (a floating end). Not a side, so it never collides with\n * the stray forms — but an `anchorId` should not itself end in `-auto` or a\n * `-<side>` suffix, which the grammar cannot distinguish.\n */\nconst AUTO_SIDE = \"auto\"\n\n/** Build the handle id for an anchor end with an auto (render-resolved) side. */\nexport function anchorAutoHandleId(anchorId: string): string {\n return `${STRAY_PREFIX}${anchorId}-${AUTO_SIDE}`\n}\n\n/** Parse an auto anchor handle id back into its `anchorId`, or null. */\nexport function parseAnchorAutoHandleId(handleId: string | null | undefined): string | null {\n if (!handleId?.startsWith(STRAY_PREFIX) || handleId.startsWith(DOT_PREFIX)) return null\n const lastDash = handleId.lastIndexOf(\"-\")\n if (handleId.slice(lastDash + 1) !== AUTO_SIDE) return null\n const anchorId = handleId.slice(STRAY_PREFIX.length, lastDash)\n return anchorId || null\n}\n\n/** Build the handle id for an anchor's in-content grab dot on `side`. */\nexport function anchorDotHandleId(anchorId: string, side: HandlePosition): string {\n return `${DOT_PREFIX}${anchorId}-${side}`\n}\n\n/** Parse a grab dot handle id back into `{ anchorId, side }`, or null. */\nexport function parseAnchorDotHandleId(\n handleId: string | null | undefined,\n): { anchorId: string; side: HandlePosition } | null {\n if (!handleId?.startsWith(DOT_PREFIX)) return null\n const lastDash = handleId.lastIndexOf(\"-\")\n const side = handleId.slice(lastDash + 1)\n if (!isSide(side)) return null\n const anchorId = handleId.slice(DOT_PREFIX.length, lastDash)\n if (!anchorId) return null\n return { anchorId, side }\n}\n\n/**\n * Rewrite grab dot ends to the persisted stray ids — same anchor, same side —\n * before a connection commits. Intent-preserving: the side comes from the dot\n * the user actually grabbed or dropped on. Runs before any consumer\n * `mapConnection`; connections not touching a dot pass through unchanged.\n */\nexport function normalizeAnchorConnection(connection: Connection): Connection {\n const source = parseAnchorDotHandleId(connection.sourceHandle)\n const target = parseAnchorDotHandleId(connection.targetHandle)\n if (!source && !target) return connection\n return {\n ...connection,\n sourceHandle: source ? anchorHandleId(source.anchorId, source.side) : connection.sourceHandle,\n targetHandle: target ? anchorHandleId(target.anchorId, target.side) : connection.targetHandle,\n }\n}\n\n// ============================================================================\n// Measurement\n// ============================================================================\n\n/** The rect fields measurement reads — satisfied by DOMRect. */\nexport type AnchorRectLike = {\n left: number\n top: number\n width: number\n height: number\n}\n\n/**\n * Derive an anchor's geometry from its node box (`root`), its first and last\n * client line rects, and its bounding box. Pure — callers supply the rects.\n */\nexport function computeAnchorGeometry(\n root: AnchorRectLike,\n first: AnchorRectLike,\n last: AnchorRectLike,\n box: AnchorRectLike,\n): AnchorGeometry {\n const point = (x: number, y: number): AnchorPoint => ({\n xPct: ((x - root.left) / root.width) * 100,\n yPct: ((y - root.top) / root.height) * 100,\n })\n return {\n firstLineYPct: ((first.top + first.height / 2 - root.top) / root.height) * 100,\n lastLineYPct: ((last.top + last.height / 2 - root.top) / root.height) * 100,\n centerXPct: ((box.left + box.width / 2 - root.left) / root.width) * 100,\n dots: {\n top: point(first.left + first.width / 2, first.top),\n bottom: point(last.left + last.width / 2, last.top + last.height),\n left: point(first.left, first.top + first.height / 2),\n right: point(last.left + last.width, last.top + last.height / 2),\n },\n }\n}\n\n/**\n * The box an anchor's element occupies inside `node`, in the same space that\n * rect is given in — how chrome that annotates a whole anchor (a peer's claim\n * on it, say) gets a rect out of measurements taken as percentages.\n *\n * Derived from the grab dots, which sit on the element's edges: left and top on\n * its first line box, right and bottom on its last. For anything that fits on\n * one line that *is* the element's box. A line-wrapped element instead reports\n * the span from where it starts to where it ends — the union of its line boxes\n * would be the node's whole text column, which describes the paragraph rather\n * than the anchor.\n */\n/**\n * A node's selectable anchors in **reading order** — first line box down the\n * node, then left to right within a line.\n *\n * The order keyboard stepping moves in, and it has to be reading order rather\n * than registration order: a consumer's JSX order is usually the same thing,\n * but an editor that re-registers anchors as its document changes has no reason\n * to keep them in sync, and stepping through a paragraph in the order its spans\n * happened to mount would be unusable.\n *\n * Unmeasured anchors sort last, keeping their relative order: they have no\n * position to compare, and putting them first would make the first press of a\n * key land somewhere arbitrary.\n */\nexport function orderedSelectableAnchors(\n records: readonly AnchorRecord[],\n): readonly AnchorRecord[] {\n return records\n .filter((record) => record.selectable)\n .map((record, index) => ({ record, index }))\n .sort((a, b) => {\n const left = a.record.geometry\n const right = b.record.geometry\n if (!left || !right) {\n if (left) return -1\n if (right) return 1\n return a.index - b.index\n }\n // A line's own height is the tolerance: two anchors on the same line\n // differ in `firstLineYPct` by measurement noise, not by a line.\n const rowDelta = left.firstLineYPct - right.firstLineYPct\n if (Math.abs(rowDelta) > SAME_ROW_TOLERANCE_PCT) return rowDelta\n return left.centerXPct - right.centerXPct\n })\n .map(({ record }) => record)\n}\n\n/** Vertical distance (% of node height) within which two anchors count as one row. */\nconst SAME_ROW_TOLERANCE_PCT = 2\n\nexport function anchorRect(node: Rect, geometry: AnchorGeometry): Rect {\n const { dots } = geometry\n // Sorted rather than assumed: a wrapped element can start further right than\n // it ends, which would otherwise come out as a negative width.\n const left = Math.min(dots.left.xPct, dots.right.xPct)\n const right = Math.max(dots.left.xPct, dots.right.xPct)\n const top = Math.min(dots.top.yPct, dots.bottom.yPct)\n const bottom = Math.max(dots.top.yPct, dots.bottom.yPct)\n return {\n x: node.x + (left / 100) * node.width,\n y: node.y + (top / 100) * node.height,\n width: ((right - left) / 100) * node.width,\n height: ((bottom - top) / 100) * node.height,\n }\n}\n\nconst GEOMETRY_TOLERANCE_PCT = 0.1\n\nfunction pointsEqual(a: AnchorPoint, b: AnchorPoint): boolean {\n return (\n Math.abs(a.xPct - b.xPct) < GEOMETRY_TOLERANCE_PCT &&\n Math.abs(a.yPct - b.yPct) < GEOMETRY_TOLERANCE_PCT\n )\n}\n\n/** Sub-tolerance equality, so measurement echoes don't churn subscribers. */\nexport function anchorGeometryEqual(a: AnchorGeometry | null, b: AnchorGeometry | null): boolean {\n if (a === b) return true\n if (!a || !b) return false\n return (\n Math.abs(a.firstLineYPct - b.firstLineYPct) < GEOMETRY_TOLERANCE_PCT &&\n Math.abs(a.lastLineYPct - b.lastLineYPct) < GEOMETRY_TOLERANCE_PCT &&\n Math.abs(a.centerXPct - b.centerXPct) < GEOMETRY_TOLERANCE_PCT &&\n ANCHOR_SIDES.every((side) => pointsEqual(a.dots[side], b.dots[side]))\n )\n}\n\n// ============================================================================\n// Stray-handle placement along a node side\n// ============================================================================\n\n/**\n * Nudge a raw position (in % of the side) out of the side-center keep-out band\n * — where the node's own handles usually sit — and off the corners.\n */\nexport function placeAlongSide(rawPct: number, options: ResolvedAnchorOptions): number {\n const clamped = Math.min(Math.max(rawPct, options.cornerMarginPct), 100 - options.cornerMarginPct)\n if (Math.abs(clamped - 50) >= options.centerKeepoutPct) return clamped\n return clamped < 50 ? 50 - options.centerKeepoutPct : 50 + options.centerKeepoutPct\n}\n\n/**\n * Where an anchor's stray handle sits along `side`, as % of that side — the\n * measured row (left/right) or column (top/bottom) nearest the anchor, nudged\n * by {@link placeAlongSide}. Shared by the stray-handle chrome and by the edge\n * renderer when it places an auto anchor end on a render-resolved side.\n */\n/**\n * A stray handle's position on the node box, as percentages of it — the same\n * placement {@link strayPlacementPct} gives, resolved into both axes.\n *\n * Both axes, for every side, so one pair of numbers can put a stray anywhere on\n * the perimeter. That's what lets the chrome follow an end floating from one\n * side to another without re-rendering: the element's position is two style\n * writes rather than a different set of anchoring properties.\n */\nexport function strayPoint(\n geometry: AnchorGeometry,\n side: HandlePosition,\n options: ResolvedAnchorOptions,\n): AnchorPoint {\n const along = strayPlacementPct(geometry, side, options)\n switch (side) {\n case \"left\":\n return { xPct: 0, yPct: along }\n case \"right\":\n return { xPct: 100, yPct: along }\n case \"top\":\n return { xPct: along, yPct: 0 }\n case \"bottom\":\n return { xPct: along, yPct: 100 }\n }\n}\n\nexport function strayPlacementPct(\n geometry: AnchorGeometry,\n side: HandlePosition,\n options: ResolvedAnchorOptions,\n): number {\n const raw =\n side === \"left\"\n ? geometry.firstLineYPct\n : side === \"right\"\n ? geometry.lastLineYPct\n : geometry.centerXPct\n return placeAlongSide(raw, options)\n}\n\n/**\n * What the anchor registry needs from the graph around it. Five callbacks, so\n * the registry can be built and driven without a store.\n */\nexport type AnchorRegistryDeps = {\n /** The mounted element for a node, for measuring anchors against its box. */\n getNodeElement: (nodeId: NodeId) => HTMLElement | undefined\n requestRender: () => void\n /** Whether any node or edge is selected — anchor selection is exclusive with it. */\n hasGraphSelection: () => boolean\n /** Drop the node/edge/box selection, without cascading back into anchors. */\n clearGraphSelection: () => void\n}\n\n/**\n * Anchors registered against nodes: their measured geometry, the per-node\n * change channel their chrome subscribes to, and which of them are selected.\n *\n * A class with injected effects rather than part of the store, so it can be\n * driven directly — the store is one caller, a test is another. The same shape\n * {@link HistoryManager} uses, and the reason this file is called `registry`.\n */\nexport class AnchorRegistry {\n /** Registered anchors, keyed by `handleKey(nodeId, anchorId)`. */\n readonly anchors = new Map<string, AnchorRecord>()\n private readonly anchorVersions = new Map<NodeId, number>()\n private readonly anchorListeners = new Map<NodeId, Set<() => void>>()\n private selectedAnchors = new Map<string, AnchorRef>()\n private selectedAnchorSnapshot: readonly AnchorRef[] = []\n private selectedAnchorsStale = false\n private readonly anchorSelectionListeners = new Set<() => void>()\n\n constructor(private readonly deps: AnchorRegistryDeps) {}\n\n private bumpAnchors(id: NodeId): void {\n this.anchorVersions.set(id, (this.anchorVersions.get(id) ?? 0) + 1)\n const listeners = this.anchorListeners.get(id)\n if (listeners) for (const listener of listeners) listener()\n }\n\n /** Forget everything registered against a node that is going away. */\n dropNode(nodeId: NodeId): void {\n this.anchorVersions.delete(nodeId)\n const prefix = `${nodeId} `\n for (const key of this.anchors.keys()) {\n if (key.startsWith(prefix)) this.anchors.delete(key)\n }\n this.dropSelected((ref) => ref.nodeId === nodeId)\n }\n\n /**\n * Measure one anchor against its node element. Returns true when geometry\n * changed beyond tolerance. Bails — keeping the last-known geometry — while\n * the element or node box can't produce a meaningful measurement (detached,\n * display:none, node not yet laid out).\n */\n private measureAnchor(key: string): boolean {\n const record = this.anchors.get(key)\n if (!record) return false\n const live = record.elements.filter((element) => element.isConnected)\n const firstElement = live[0]\n const lastElement = live[live.length - 1]\n if (!firstElement || !lastElement) return false\n const nodeEl = this.deps.getNodeElement(record.nodeId)\n if (!nodeEl) return false\n const root = nodeEl.getBoundingClientRect()\n // The union across fragments: an anchor rendered as several elements is one\n // run of content, and its box is the box of the whole run.\n const box = unionRect(live.map((element) => element.getBoundingClientRect()))\n if (root.width <= 0 || root.height <= 0 || (box.width <= 0 && box.height <= 0)) return false\n // First line of the first fragment, last line of the last — exactly what\n // the geometry means for a wrapped single element, extended across the\n // fragments that make up one anchor.\n const firstRects = firstElement.getClientRects()\n const lastRects = lastElement.getClientRects()\n const first = firstRects.item(0) ?? firstElement.getBoundingClientRect()\n const last = lastRects.item(lastRects.length - 1) ?? lastElement.getBoundingClientRect()\n const geometry = computeAnchorGeometry(root, first, last, box)\n if (anchorGeometryEqual(record.geometry, geometry)) return false\n this.anchors.set(key, { ...record, geometry })\n return true\n }\n\n registerAnchor = (\n nodeId: NodeId,\n anchorId: string,\n element: Element | readonly Element[],\n options?: AnchorRegistrationOptions,\n ): void => {\n const key = handleKey(nodeId, anchorId)\n const prev = this.anchors.get(key)\n const elements = Array.isArray(element) ? [...element] : [element as Element]\n this.anchors.set(key, {\n nodeId,\n anchorId,\n color: options?.color ?? prev?.color,\n strays: options?.strays ?? prev?.strays,\n selectable: options?.selectable ?? prev?.selectable,\n data: options?.data ?? prev?.data,\n elements,\n element: elements[0] ?? null,\n // Keep the last-known geometry until the next successful measurement, so\n // a re-bind (display remount, editor DOM swap) never blanks the chrome.\n geometry: prev?.geometry ?? null,\n })\n this.measureAnchor(key)\n this.bumpAnchors(nodeId)\n this.deps.requestRender()\n }\n\n detachAnchor = (nodeId: NodeId, anchorId: string): void => {\n const key = handleKey(nodeId, anchorId)\n const record = this.anchors.get(key)\n if (!record || record.elements.length === 0) return\n this.anchors.set(key, { ...record, elements: [], element: null })\n this.bumpAnchors(nodeId)\n }\n\n unregisterAnchor = (nodeId: NodeId, anchorId: string): void => {\n if (!this.anchors.delete(handleKey(nodeId, anchorId))) return\n // Selection is view state over the registry, so it can't outlive the record\n // it points at. Detaching deliberately doesn't clear it — the anchor still\n // exists, its display is just gone for the moment.\n this.dropSelected((ref) => ref.nodeId === nodeId && ref.anchorId === anchorId)\n this.bumpAnchors(nodeId)\n this.deps.requestRender()\n }\n\n remeasureAnchors = (nodeId: NodeId): void => {\n let changed = false\n for (const [key, record] of this.anchors) {\n if (record.nodeId === nodeId && this.measureAnchor(key)) changed = true\n }\n if (changed) {\n this.bumpAnchors(nodeId)\n this.deps.requestRender()\n }\n }\n\n getNodeAnchors = (nodeId: NodeId): AnchorRecord[] => {\n const records: AnchorRecord[] = []\n for (const record of this.anchors.values()) {\n if (record.nodeId === nodeId) records.push(record)\n }\n return records\n }\n\n getNodeAnchorsVersion = (nodeId: NodeId): number => this.anchorVersions.get(nodeId) ?? 0\n\n getSelectedAnchors = (): readonly AnchorRef[] => {\n if (this.selectedAnchorsStale) {\n this.selectedAnchorsStale = false\n this.selectedAnchorSnapshot = [...this.selectedAnchors.values()]\n }\n return this.selectedAnchorSnapshot\n }\n\n setSelectedAnchors = (refs: readonly AnchorRef[]): void => {\n const next = new Map<string, AnchorRef>()\n for (const ref of refs) next.set(handleKey(ref.nodeId, ref.anchorId), ref)\n if (\n next.size === this.selectedAnchors.size &&\n [...next.keys()].every((key) => this.selectedAnchors.has(key))\n ) {\n return\n }\n // Selecting an anchor is selecting *something*, so the graph selection goes.\n // Cleared through the primitives rather than `selectNode`/`selectEdge`:\n // those clear anchors in turn, which would erase what is being set here.\n if (next.size > 0 && this.deps.hasGraphSelection()) {\n this.deps.clearGraphSelection()\n }\n this.selectedAnchors = next\n this.selectedAnchorsStale = true\n for (const listener of this.anchorSelectionListeners) listener()\n this.deps.requestRender()\n }\n\n isAnchorSelected = (nodeId: NodeId, anchorId: string): boolean =>\n this.selectedAnchors.has(handleKey(nodeId, anchorId))\n\n subscribeAnchorSelection = (listener: () => void): (() => void) => {\n this.anchorSelectionListeners.add(listener)\n return () => {\n this.anchorSelectionListeners.delete(listener)\n }\n }\n\n /** Drop every selected anchor the predicate claims. No-op when none match. */\n /** Drop every selected anchor matching `matches`. */\n dropSelected(matches: (ref: AnchorRef) => boolean): void {\n if (this.selectedAnchors.size === 0) return\n let changed = false\n for (const [key, ref] of this.selectedAnchors) {\n if (!matches(ref)) continue\n this.selectedAnchors.delete(key)\n changed = true\n }\n if (!changed) return\n this.selectedAnchorsStale = true\n for (const listener of this.anchorSelectionListeners) listener()\n this.deps.requestRender()\n }\n\n subscribeNodeAnchors = (nodeId: NodeId, listener: () => void): (() => void) => {\n let set = this.anchorListeners.get(nodeId)\n if (!set) {\n set = new Set()\n this.anchorListeners.set(nodeId, set)\n }\n set.add(listener)\n return () => {\n set.delete(listener)\n if (set.size === 0) this.anchorListeners.delete(nodeId)\n }\n }\n}\n\n/** The smallest rect containing all of `rects`. Empty input is a zero box. */\nfunction unionRect(rects: readonly DOMRect[]): {\n left: number\n top: number\n width: number\n height: number\n} {\n let left = Number.POSITIVE_INFINITY\n let top = Number.POSITIVE_INFINITY\n let right = Number.NEGATIVE_INFINITY\n let bottom = Number.NEGATIVE_INFINITY\n for (const rect of rects) {\n left = Math.min(left, rect.left)\n top = Math.min(top, rect.top)\n right = Math.max(right, rect.right)\n bottom = Math.max(bottom, rect.bottom)\n }\n if (!Number.isFinite(left)) return { left: 0, top: 0, width: 0, height: 0 }\n return { left, top, width: right - left, height: bottom - top }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import {a}from'./chunk-RDSWIH3F.js';var x=32,L=24,d=80;function S(e){return typeof e=="number"?{top:e,right:e,bottom:e,left:e}:{top:e?.top??16,right:e?.right??16,bottom:e?.bottom??16,left:e?.left??16}}var R={x:0,y:0};function N(e,s){let n=e.trim();if(!n.startsWith("#"))return e;let o=n.slice(1),i=o.length===3?o.split("").map(r=>r+r).join(""):o;if(i.length!==6)return e;let t=Number.parseInt(i,16);return Number.isNaN(t)?e:`rgba(${t>>16&255}, ${t>>8&255}, ${t&255}, ${s})`}function w(e,s=x){let n=e.position??R,o=[],i=n.y+s;for(let t of e.lanes)o.push({id:t.id,groupId:e.id,label:t.label,color:t.color,top:i,height:t.size,x:n.x,width:e.length}),i+=t.size;return {id:e.id,label:e.label,color:e.color,x:n.x,width:e.length,top:n.y,headerSize:s,bottom:i,lanes:o}}function G(e,s=x){return e.map(n=>w(n,s))}function E(e){return e.flatMap(s=>s.lanes)}function M(e,s,n,o){if(e.length===0)return null;let i=s+n,t=null,r=-1;for(let u of e){let a=Math.max(0,Math.min(i,u.top+u.height)-Math.max(s,u.top));(a>r||a===r&&u.id===o)&&(r=a,t=u);}if(r>0)return t;let l=s+n/2,c=null,m=Number.POSITIVE_INFINITY;for(let u of e){let a=Math.abs(u.top+u.height/2-l);a<m&&(m=a,c=u);}return c}function V(e,s,n){let o=new Map;for(let t of s){if(!t.laneId||!t.size)continue;let r=t.position.y+t.size.height+n,l=o.get(t.laneId);(l===void 0||r>l)&&o.set(t.laneId,r);}let i=new Map;for(let t of e){let r=o.get(t.id),l=r===void 0?d:r-t.top;i.set(t.id,l>d?l:d);}return i}function T(e,s,n){let o=d;for(let i of s){if(i.laneId!==e.id||!i.size)continue;let t=i.position.y+i.size.height+n-e.top;t>o&&(o=t);}return o}function z(e,s,n,o,i){let{x:t,y:r,zoom:l}=s,c=new Map;for(let m of e){let u=m.top*l+r,a=m.bottom*l+r,p=Math.max(0,Math.min(u,a-o));if(c.set(m.id,p+o),a>0&&n.y>=p&&n.y<=p+o)return {type:"group",id:m.id}}for(let m of e){let u=c.get(m.id)??0;for(let a of m.lanes){let p=a.x*l+t,b=a.top*l+r,g=b+a.height*l,v=p+a.width*l,f=Math.max(0,Math.min(p,v-i)),y=Math.max(u,b);if(n.x>=f&&n.x<=f+i&&n.y>=y&&n.y<=g)return {type:"lane",id:a.id}}}return null}function A(e,s,n,o,i,t){let r=M(n,e.y,s.height,t);if(!r)return {position:e,laneId:t??null};let l=r.top+o.top,c=Math.max(l,r.top+r.height-s.height-o.bottom),m=r.x+i+o.left;return {position:{x:Math.max(m,e.x),y:a(e.y,l,c)},laneId:r.id}}export{x as a,L as b,d as c,S as d,N as e,w as f,G as g,E as h,M as i,V as j,T as k,z as l,A as m};//# sourceMappingURL=chunk-XTQGGEUS.js.map
2
+ //# sourceMappingURL=chunk-XTQGGEUS.js.map