@kolosal-ai/rivet 0.4.0 → 0.6.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.
@@ -1,6 +1,6 @@
1
- import { P as PeerIdentity, a as PeerRecord, b as PresenceTargetKey, c as PresenceTarget, d as PeerClaim } from '../use-node-lock-CbS59CRE.js';
2
- export { C as CanInteractWithLocked, D as DEFAULT_PRESENCE_OPTIONS, L as LOCK_DEFAULT_REFUSED, e as LocalPresence, f as LockIntent, g as LockInteraction, h as LockRegistry, i as LockTarget, N as NodeLock, j as PEER_COLORS, k as Peer, l as PeerConnection, m as PeerCursorProps, n as PeerOutlineProps, o as PresenceOptions, p as PresenceRegistry, R as ResolvedPresenceOptions, q as anchorTargetKey, r as edgeTargetKey, s as isNodeTarget, t as parsePresenceTarget, u as peerColor, v as presenceTargetEdgeId, w as presenceTargetKey, x as presenceTargetNodeId, y as useNodeLock, z as useNodeLockAllows } from '../use-node-lock-CbS59CRE.js';
3
- import { a as NodeChange } from '../types-D3aldmsO.js';
1
+ import { P as PeerIdentity, a as PeerRecord, b as PresenceTargetKey, c as PresenceTarget, d as PeerClaim } from '../use-node-lock-CKtOwBrI.js';
2
+ export { C as CanInteractWithLocked, D as DEFAULT_PRESENCE_OPTIONS, L as LOCK_DEFAULT_REFUSED, e as LocalPresence, f as LockIntent, g as LockInteraction, h as LockRegistry, i as LockTarget, N as NodeLock, j as PEER_COLORS, k as Peer, l as PeerConnection, m as PeerCursorProps, n as PeerOutlineProps, o as PresenceOptions, p as PresenceRegistry, R as ResolvedPresenceOptions, q as anchorTargetKey, r as edgeTargetKey, s as isNodeTarget, t as parsePresenceTarget, u as peerColor, v as presenceTargetEdgeId, w as presenceTargetKey, x as presenceTargetNodeId, y as useNodeLock, z as useNodeLockAllows } from '../use-node-lock-CKtOwBrI.js';
3
+ import { a as NodeChange } from '../types-tkMbskgC.js';
4
4
  import 'react';
5
5
 
6
6
  /**
@@ -1,4 +1,4 @@
1
- import { H as HandlePosition, N as NodeId, R as Rect, C as Connection } from './types-D3aldmsO.js';
1
+ import { H as HandlePosition, N as NodeId, R as Rect, C as Connection } from './types-tkMbskgC.js';
2
2
 
3
3
  /**
4
4
  * Anchor — a connectable point bound to a DOM element rendered inside a node.
@@ -50,10 +50,16 @@ type AnchorGeometry = {
50
50
  */
51
51
  type AnchorStrayPolicy = "dynamic" | "none";
52
52
  /**
53
- * A registered anchor. `element` is `null` while detached — the consumer
54
- * unmounted the display (or an editor replaced the DOM node) without
55
- * unregistering, so the last-known `geometry` is retained and edges stay put
56
- * (stale retention). Unregistering is the explicit removal signal.
53
+ * A registered anchor. Empty `elements` means detached — the consumer unmounted
54
+ * the display (or an editor replaced the DOM node) without unregistering, so
55
+ * the last-known `geometry` is retained and edges stay put (stale retention).
56
+ * Unregistering is the explicit removal signal.
57
+ *
58
+ * An anchor can be bound to **several** elements, because one thing on screen
59
+ * isn't always one element: a rich-text mark interrupted by another mark
60
+ * renders as two spans, and an imported graphic can be a group of paths. They
61
+ * are treated as fragments of a single run — first line of the first, last line
62
+ * of the last — which is what {@link AnchorGeometry} already describes.
57
63
  */
58
64
  type AnchorRecord = {
59
65
  nodeId: NodeId;
@@ -62,7 +68,7 @@ type AnchorRecord = {
62
68
  color?: string;
63
69
  /** Stray chrome policy. Absent = `"dynamic"`. */
64
70
  strays?: AnchorStrayPolicy;
65
- /** Whether a press on the element selects the anchor. Absent = `false`. */
71
+ /** Whether the anchor is a selection target at all by press or key. Absent = `false`. */
66
72
  selectable?: boolean;
67
73
  /**
68
74
  * Whatever the consumer attached at registration — a shape's label, the
@@ -70,6 +76,16 @@ type AnchorRecord = {
70
76
  * it, and never reads it.
71
77
  */
72
78
  data?: unknown;
79
+ /**
80
+ * Every element this anchor is bound to, in document order. Empty while
81
+ * detached.
82
+ */
83
+ elements: readonly Element[];
84
+ /**
85
+ * The first of {@link elements}, or `null` when detached — the answer for the
86
+ * overwhelmingly common one-element anchor, kept so call sites that only need
87
+ * "is this anchor live, and where" don't have to index.
88
+ */
73
89
  element: Element | null;
74
90
  /** Null until the first successful measurement. */
75
91
  geometry: AnchorGeometry | null;
@@ -79,10 +95,15 @@ type AnchorRegistrationOptions = {
79
95
  /** Stray-handle chrome policy for this anchor. Default `"dynamic"`. */
80
96
  strays?: AnchorStrayPolicy;
81
97
  /**
82
- * Make the element pointer-interactive, so a click on it selects the anchor.
98
+ * Make the anchor a selection target: the element becomes pointer-interactive
99
+ * so a click selects it, and it joins the node's keyboard stepping order
100
+ * (Alt+Left/Right on the focused node).
101
+ *
83
102
  * Default `false`: an anchor around inline text usually sits in editable
84
103
  * content, where swallowing a press would stop the caret from landing. Shapes
85
- * are the opposite case — the element *is* the thing you mean to click.
104
+ * are the opposite case — the element *is* the thing you mean to click. One
105
+ * flag for both routes, so an anchor is never selectable one way and not the
106
+ * other.
86
107
  */
87
108
  selectable?: boolean;
88
109
  /** Opaque consumer payload, returned on the record untouched. */
@@ -105,11 +126,12 @@ declare function findSelectableAnchor(records: readonly AnchorRecord[], target:
105
126
  */
106
127
  type AnchorRegistration = {
107
128
  /**
108
- * Re-attach the anchor after the renderer replaced its DOM node. Passing
129
+ * Re-attach the anchor after the renderer replaced its DOM node — one element
130
+ * or, for content that renders as several, all of its fragments. Passing
109
131
  * `null` detaches instead: geometry is retained and edges stay put until the
110
132
  * element (or a replacement) is rebound or the anchor is unregistered.
111
133
  */
112
- rebind: (element: Element | null) => void;
134
+ rebind: (element: Element | readonly Element[] | null) => void;
113
135
  /** Explicit removal — drops the anchor and the chrome rendered for it. */
114
136
  unregister: () => void;
115
137
  };
@@ -180,6 +202,21 @@ declare function computeAnchorGeometry(root: AnchorRectLike, first: AnchorRectLi
180
202
  * would be the node's whole text column, which describes the paragraph rather
181
203
  * than the anchor.
182
204
  */
205
+ /**
206
+ * A node's selectable anchors in **reading order** — first line box down the
207
+ * node, then left to right within a line.
208
+ *
209
+ * The order keyboard stepping moves in, and it has to be reading order rather
210
+ * than registration order: a consumer's JSX order is usually the same thing,
211
+ * but an editor that re-registers anchors as its document changes has no reason
212
+ * to keep them in sync, and stepping through a paragraph in the order its spans
213
+ * happened to mount would be unusable.
214
+ *
215
+ * Unmeasured anchors sort last, keeping their relative order: they have no
216
+ * position to compare, and putting them first would make the first press of a
217
+ * key land somewhere arbitrary.
218
+ */
219
+ declare function orderedSelectableAnchors(records: readonly AnchorRecord[]): readonly AnchorRecord[];
183
220
  declare function anchorRect(node: Rect, geometry: AnchorGeometry): Rect;
184
221
  /**
185
222
  * Where an anchor's stray handle sits along `side`, as % of that side — the
@@ -187,6 +224,16 @@ declare function anchorRect(node: Rect, geometry: AnchorGeometry): Rect;
187
224
  * by {@link placeAlongSide}. Shared by the stray-handle chrome and by the edge
188
225
  * renderer when it places an auto anchor end on a render-resolved side.
189
226
  */
227
+ /**
228
+ * A stray handle's position on the node box, as percentages of it — the same
229
+ * placement {@link strayPlacementPct} gives, resolved into both axes.
230
+ *
231
+ * Both axes, for every side, so one pair of numbers can put a stray anywhere on
232
+ * the perimeter. That's what lets the chrome follow an end floating from one
233
+ * side to another without re-rendering: the element's position is two style
234
+ * writes rather than a different set of anchoring properties.
235
+ */
236
+ declare function strayPoint(geometry: AnchorGeometry, side: HandlePosition, options: ResolvedAnchorOptions): AnchorPoint;
190
237
  declare function strayPlacementPct(geometry: AnchorGeometry, side: HandlePosition, options: ResolvedAnchorOptions): number;
191
238
 
192
- export { type AnchorRef as A, DEFAULT_ANCHOR_OPTIONS as D, type ResolvedAnchorOptions as R, ANCHOR_SIDES as a, type AnchorGeometry as b, type AnchorOptions as c, type AnchorPoint as d, type AnchorRecord as e, type AnchorRegistration as f, type AnchorRegistrationOptions as g, type AnchorStrayPolicy as h, anchorAutoHandleId as i, anchorDotHandleId as j, anchorHandleId as k, anchorRect as l, computeAnchorGeometry as m, findSelectableAnchor as n, normalizeAnchorConnection as o, parseAnchorAutoHandleId as p, parseAnchorDotHandleId as q, parseAnchorHandleId as r, strayPlacementPct as s };
239
+ export { type AnchorRef as A, DEFAULT_ANCHOR_OPTIONS as D, type ResolvedAnchorOptions as R, ANCHOR_SIDES as a, type AnchorGeometry as b, type AnchorOptions as c, type AnchorPoint as d, type AnchorRecord as e, type AnchorRegistration as f, type AnchorRegistrationOptions as g, type AnchorStrayPolicy as h, anchorAutoHandleId as i, anchorDotHandleId as j, anchorHandleId as k, anchorRect as l, computeAnchorGeometry as m, findSelectableAnchor as n, normalizeAnchorConnection as o, orderedSelectableAnchors as p, parseAnchorAutoHandleId as q, parseAnchorDotHandleId as r, parseAnchorHandleId as s, strayPlacementPct as t, strayPoint as u };
@@ -1,7 +1,7 @@
1
1
  import * as react from 'react';
2
2
  import { CSSProperties } from 'react';
3
- import { h as AnchorStrayPolicy } from '../registry-Z_oiPyjm.js';
4
- import '../types-D3aldmsO.js';
3
+ import { h as AnchorStrayPolicy } from '../registry-DPfWNdkC.js';
4
+ import '../types-tkMbskgC.js';
5
5
 
6
6
  /**
7
7
  * The extract seam behind `<SvgArtwork>` — which elements inside a parsed
@@ -1,5 +1,5 @@
1
- import { R as Rect, e as RivetNode, V as Vec2, S as Size, s as SwimlaneMargin, r as SwimlaneGroup } from '../types-D3aldmsO.js';
2
- export { L as LaneChange, U as Swimlane, t as SwimlaneHeaderProps, u as SwimlaneLabelProps, v as SwimlaneSizeChange } from '../types-D3aldmsO.js';
1
+ import { R as Rect, e as RivetNode, V as Vec2, S as Size, s as SwimlaneMargin, r as SwimlaneGroup } from '../types-tkMbskgC.js';
2
+ export { L as LaneChange, U as Swimlane, t as SwimlaneHeaderProps, u as SwimlaneLabelProps, v as SwimlaneSizeChange } from '../types-tkMbskgC.js';
3
3
  import 'react';
4
4
 
5
5
  /** World height of a group's sticky header band. */
@@ -368,6 +368,13 @@ type EdgeDrawExtras = {
368
368
  * resolved sides commit on drop, not here.
369
369
  */
370
370
  liveAlignNodes?: ReadonlySet<NodeId>;
371
+ /**
372
+ * How far the losing axis must dominate before a resolved side switches axes
373
+ * (the `edgeAlignmentHysteresis` prop). Absent falls back to
374
+ * `DEFAULT_ALIGNMENT_HYSTERESIS` — a backend that ignores it resolves with
375
+ * the default band rather than none.
376
+ */
377
+ alignmentHysteresis?: number;
371
378
  /**
372
379
  * Places an anchor end along `side` of its node — the anchor's stray-handle
373
380
  * position as % of that side — or null when the anchor is unknown or
@@ -1,4 +1,4 @@
1
- import { N as NodeId, E as EdgeId, V as Vec2, b as HandleType, H as HandlePosition, R as Rect } from './types-D3aldmsO.js';
1
+ import { N as NodeId, E as EdgeId, V as Vec2, b as HandleType, H as HandlePosition, R as Rect } from './types-tkMbskgC.js';
2
2
  import { ComponentType } from 'react';
3
3
 
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolosal-ai/rivet",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Canvas-rendered node graph for React: DOM nodes, Canvas2D edges, swimlanes, anchors, undo/redo",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/anchor/registry.ts"],"names":[],"mappings":"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","file":"chunk-W5HBJ4VF.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 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. `element` is `null` while detached — the consumer\n * unmounted the display (or an editor replaced the DOM node) without\n * unregistering, so the last-known `geometry` is retained and edges stay put\n * (stale retention). Unregistering is the explicit removal signal.\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 a press on the element selects the anchor. 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 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 element pointer-interactive, so a click on it selects the anchor.\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.\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 for (const record of records) {\n if (!record.selectable || !record.element?.contains(target)) continue\n if (found?.element && record.element.contains(found.element)) continue\n found = record\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. 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 | 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 */\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 */\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"]}