@kolosal-ai/rivet 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { parseAnchorHandleId, ANCHOR_SIDES, anchorDotHandleId, strayPlacementPct, anchorHandleId, anchorAutoHandleId, parseAnchorAutoHandleId, normalizeAnchorConnection, computeAnchorGeometry, anchorGeometryEqual, DEFAULT_ANCHOR_OPTIONS } from './chunk-AXT35ZJV.js';
2
- import { createPresenceRegistry, createLockRegistry, DEFAULT_PRESENCE_OPTIONS, peerColor } from './chunk-RG5ALD3X.js';
3
- export { LOCK_DEFAULT_REFUSED, useNodeLock, useNodeLockAllows } from './chunk-RG5ALD3X.js';
4
- import { clamp, clampNodeToLanes, swimlaneChromeAt, SWIMLANE_LABEL_WIDTH, resolveSwimlanes, flattenLanes, resolveMargin, requiredLaneHeight, withAlpha } from './chunk-6XRQSAQT.js';
1
+ import { findSelectableAnchor, parseAnchorHandleId, ANCHOR_SIDES, anchorDotHandleId, strayPlacementPct, anchorHandleId, anchorAutoHandleId, parseAnchorAutoHandleId, normalizeAnchorConnection, computeAnchorGeometry, anchorGeometryEqual, DEFAULT_ANCHOR_OPTIONS, anchorRect } from './chunk-W5HBJ4VF.js';
2
+ import { snapToGrid, boundingRect, clampChildToParent, anchorTargetKey, createPresenceRegistry, createLockRegistry, nodeControlledEqual, buildChildIndex, collectDescendants, worldPosition, nodeDepth, descendantIds, hasAncestorIn, edgeTargetKey, DEFAULT_PRESENCE_OPTIONS, collectPeerOutlines, peerOutlinesEqual, usePeerIdentities, serializeGraph, presenceTargetKey, peerColor, isNodeTarget, presenceTargetEdgeId } from './chunk-TT2N7KWU.js';
3
+ export { LOCK_DEFAULT_REFUSED, boundingRect, clampChildToParent, serializeGraph, snapToGrid, useNodeLock, useNodeLockAllows } from './chunk-TT2N7KWU.js';
5
4
  import { useRivetContext, RivetNodeContext, useRivetNodeContext, useRivetControls, RivetContext, useRivetViewport } from './chunk-VQYN27OK.js';
6
5
  export { useRivetContext, useRivetControls, useRivetFocusedNode, useRivetHistory, useRivetViewport } from './chunk-VQYN27OK.js';
6
+ import { clamp, clampNodeToLanes, swimlaneChromeAt, SWIMLANE_LABEL_WIDTH, resolveSwimlanes, flattenLanes, resolveMargin, requiredLaneHeight, withAlpha } from './chunk-6XRQSAQT.js';
7
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
8
  import { memo, useRef, useState, useCallback, useSyncExternalStore, useMemo, useEffect, Fragment as Fragment$1, useId, useLayoutEffect } from 'react';
9
9
 
@@ -282,6 +282,7 @@ var DEFAULT_NODE_SIZE = { width: 160, height: 44 };
282
282
  function handleKey(nodeId, handleId) {
283
283
  return `${nodeId} ${handleId}`;
284
284
  }
285
+ var DRAG_THRESHOLD = 4;
285
286
  var SELECTOR = {
286
287
  node: "[data-rivet-node]",
287
288
  handle: "[data-rivet-handle]",
@@ -387,111 +388,6 @@ function guardNodeChanges(changes, isHeld, getLocal) {
387
388
  return guarded;
388
389
  }
389
390
 
390
- // src/graph.ts
391
- function clampChildToParent(position, childSize, parentSize) {
392
- const maxX = Math.max(0, parentSize.width - childSize.width);
393
- const maxY = Math.max(0, parentSize.height - childSize.height);
394
- return {
395
- x: Math.min(maxX, Math.max(0, position.x)),
396
- y: Math.min(maxY, Math.max(0, position.y))
397
- };
398
- }
399
- function snapToGrid(position, grid) {
400
- const [gx, gy] = grid;
401
- return {
402
- x: gx > 0 ? Math.round(position.x / gx) * gx : position.x,
403
- y: gy > 0 ? Math.round(position.y / gy) * gy : position.y
404
- };
405
- }
406
- function boundingRect(rects) {
407
- if (rects.length === 0) return null;
408
- let minX = Number.POSITIVE_INFINITY;
409
- let minY = Number.POSITIVE_INFINITY;
410
- let maxX = Number.NEGATIVE_INFINITY;
411
- let maxY = Number.NEGATIVE_INFINITY;
412
- for (const r of rects) {
413
- minX = Math.min(minX, r.x);
414
- minY = Math.min(minY, r.y);
415
- maxX = Math.max(maxX, r.x + r.width);
416
- maxY = Math.max(maxY, r.y + r.height);
417
- }
418
- return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
419
- }
420
- var MAX_PARENT_DEPTH = 50;
421
- function worldPosition(nodes, id, maxDepth = MAX_PARENT_DEPTH) {
422
- const node = nodes.get(id);
423
- if (!node) return { x: 0, y: 0 };
424
- let x = node.position.x;
425
- let y = node.position.y;
426
- let parentId = node.parentId;
427
- let guard = 0;
428
- while (parentId && guard++ < maxDepth) {
429
- const parent = nodes.get(parentId);
430
- if (!parent) break;
431
- x += parent.position.x;
432
- y += parent.position.y;
433
- parentId = parent.parentId;
434
- }
435
- return { x, y };
436
- }
437
- function nodeDepth(nodes, id, maxDepth = MAX_PARENT_DEPTH) {
438
- let depth = 0;
439
- let parentId = nodes.get(id)?.parentId;
440
- while (parentId && depth < maxDepth) {
441
- depth++;
442
- parentId = nodes.get(parentId)?.parentId;
443
- }
444
- return depth;
445
- }
446
- function hasAncestorIn(nodes, id, set, maxDepth = MAX_PARENT_DEPTH) {
447
- let parentId = nodes.get(id)?.parentId;
448
- let guard = 0;
449
- while (parentId && guard++ < maxDepth) {
450
- if (set.has(parentId)) return true;
451
- parentId = nodes.get(parentId)?.parentId;
452
- }
453
- return false;
454
- }
455
- function buildChildIndex(nodes) {
456
- const childrenOf = /* @__PURE__ */ new Map();
457
- for (const node of nodes.values()) {
458
- if (!node.parentId) continue;
459
- const list = childrenOf.get(node.parentId);
460
- if (list) list.push(node.id);
461
- else childrenOf.set(node.parentId, [node.id]);
462
- }
463
- return childrenOf;
464
- }
465
- function collectDescendants(id, childIndex, out) {
466
- for (const child of childIndex.get(id) ?? []) {
467
- out.push(child);
468
- collectDescendants(child, childIndex, out);
469
- }
470
- }
471
- function descendantIds(nodes, id) {
472
- const out = [];
473
- collectDescendants(id, buildChildIndex(nodes), out);
474
- return out;
475
- }
476
- function serializeGraph(nodes, edges, viewport) {
477
- const cleanNodes = [];
478
- for (const node of nodes) {
479
- const copy = { ...node, position: { ...node.position } };
480
- if (node.size) copy.size = { ...node.size };
481
- copy.hovered = void 0;
482
- copy.dragging = void 0;
483
- cleanNodes.push(copy);
484
- }
485
- return {
486
- nodes: cleanNodes,
487
- edges: [...edges].map((edge) => ({ ...edge })),
488
- viewport: { ...viewport }
489
- };
490
- }
491
- function nodeControlledEqual(a, b) {
492
- return a.position.x === b.position.x && a.position.y === b.position.y && Boolean(a.selected) === Boolean(b.selected) && a.data === b.data && a.type === b.type && a.laneId === b.laneId && a.ariaLabel === b.ariaLabel && a.parentId === b.parentId && a.extent === b.extent && a.width === b.width && a.height === b.height && Boolean(a.dragging) === Boolean(b.dragging) && a.size?.width === b.size?.width && a.size?.height === b.size?.height;
493
- }
494
-
495
391
  // src/history.ts
496
392
  function hasRecordableChange(changes) {
497
393
  return changes.some((change) => {
@@ -997,6 +893,30 @@ function resolveEnd(edge, role, node, peer, frame) {
997
893
  extras?.displayedSides?.set(key, side);
998
894
  return endAt(side);
999
895
  }
896
+ function polylineMidpoint(points) {
897
+ const first = points[0];
898
+ if (!first) return { x: 0, y: 0 };
899
+ let total = 0;
900
+ for (let i = 0; i < points.length - 1; i++) {
901
+ const a = points[i];
902
+ const b = points[i + 1];
903
+ if (a && b) total += Math.hypot(b.x - a.x, b.y - a.y);
904
+ }
905
+ let travelled = 0;
906
+ const half = total / 2;
907
+ for (let i = 0; i < points.length - 1; i++) {
908
+ const a = points[i];
909
+ const b = points[i + 1];
910
+ if (!a || !b) continue;
911
+ const length = Math.hypot(b.x - a.x, b.y - a.y);
912
+ if (travelled + length >= half) {
913
+ const t = length === 0 ? 0 : (half - travelled) / length;
914
+ return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t };
915
+ }
916
+ travelled += length;
917
+ }
918
+ return points[points.length - 1] ?? first;
919
+ }
1000
920
  function distanceToPolyline(point, points) {
1001
921
  let best = Number.POSITIVE_INFINITY;
1002
922
  for (let i = 0; i < points.length - 1; i++) {
@@ -1025,8 +945,14 @@ var RivetGraphStore = class {
1025
945
  anchors = /* @__PURE__ */ new Map();
1026
946
  // Arrow-wrapped so the (later-initialized) requestRender field is looked up
1027
947
  // at call time, not while this field is being constructed.
1028
- presence = createPresenceRegistry(() => this.requestRender());
1029
- locks = createLockRegistry(() => this.requestRender());
948
+ presence = createPresenceRegistry({
949
+ requestRender: () => this.requestRender(),
950
+ requestCursorRender: () => this.requestCursorRender()
951
+ });
952
+ locks = createLockRegistry(
953
+ () => this.requestRender(),
954
+ (id) => this.nodes.get(id)?.parentId
955
+ );
1030
956
  viewport;
1031
957
  viewportClamp = null;
1032
958
  pending = null;
@@ -1039,6 +965,8 @@ var RivetGraphStore = class {
1039
965
  nodeElements = /* @__PURE__ */ new Map();
1040
966
  viewportListeners = /* @__PURE__ */ new Set();
1041
967
  frameListeners = /* @__PURE__ */ new Set();
968
+ cursorFrameListeners = /* @__PURE__ */ new Set();
969
+ connectionListeners = /* @__PURE__ */ new Set();
1042
970
  selectionListeners = /* @__PURE__ */ new Set();
1043
971
  selectedNodeIds = /* @__PURE__ */ new Set();
1044
972
  selectionBoxActive = false;
@@ -1048,6 +976,11 @@ var RivetGraphStore = class {
1048
976
  focusedNodeId = null;
1049
977
  focusListeners = /* @__PURE__ */ new Set();
1050
978
  selectedEdgeId = null;
979
+ /** Selected anchors, keyed like the registry so lookups are one hash. */
980
+ selectedAnchors = /* @__PURE__ */ new Map();
981
+ selectedAnchorSnapshot = [];
982
+ selectedAnchorsStale = false;
983
+ anchorSelectionListeners = /* @__PURE__ */ new Set();
1051
984
  nodeDragging = false;
1052
985
  nodeResizing = false;
1053
986
  edgeAlignment = "manual";
@@ -1059,6 +992,8 @@ var RivetGraphStore = class {
1059
992
  lockConflictHandler = null;
1060
993
  displayedSides = /* @__PURE__ */ new Map();
1061
994
  edgeLabelAnchors = /* @__PURE__ */ new Map();
995
+ /** Screen-space points on claimed edges, published only while DOM outlines are on. */
996
+ peerEdgeAnchors = /* @__PURE__ */ new Map();
1062
997
  alignmentGuides = [];
1063
998
  nodeChangeHandler = null;
1064
999
  edgeChangeHandler = null;
@@ -1072,6 +1007,12 @@ var RivetGraphStore = class {
1072
1007
  peerWorldVersion = -1;
1073
1008
  renderRequester = () => {
1074
1009
  };
1010
+ /**
1011
+ * Null until a runtime claims it, and then `requestCursorRender` narrows to
1012
+ * the cursor layer. Falling back to a full frame is the safe default: a
1013
+ * consumer without the layer mounted still gets its paint.
1014
+ */
1015
+ cursorRenderRequester = null;
1075
1016
  constructor(init) {
1076
1017
  this.viewport = init.viewport;
1077
1018
  for (const node of init.nodes) this.writeNode(node.id, node);
@@ -1246,6 +1187,7 @@ var RivetGraphStore = class {
1246
1187
  for (const key of this.anchors.keys()) {
1247
1188
  if (key.startsWith(prefix)) this.anchors.delete(key);
1248
1189
  }
1190
+ this.dropSelectedAnchors((ref) => ref.nodeId === id);
1249
1191
  }
1250
1192
  removeNodeInternal(id, removedEdges) {
1251
1193
  if (!this.nodes.has(id)) return false;
@@ -1373,6 +1315,15 @@ var RivetGraphStore = class {
1373
1315
  notifyFrame = () => {
1374
1316
  for (const listener of this.frameListeners) listener();
1375
1317
  };
1318
+ subscribeCursorFrame = (listener) => {
1319
+ this.cursorFrameListeners.add(listener);
1320
+ return () => {
1321
+ this.cursorFrameListeners.delete(listener);
1322
+ };
1323
+ };
1324
+ notifyCursorFrame = () => {
1325
+ for (const listener of this.cursorFrameListeners) listener();
1326
+ };
1376
1327
  // --- node geometry -------------------------------------------------------
1377
1328
  getWorldPositions = () => {
1378
1329
  if (!this.worldCache) {
@@ -1419,8 +1370,14 @@ var RivetGraphStore = class {
1419
1370
  getNodeRect = (id) => {
1420
1371
  const origin = this.getNodeWorldPosition(id);
1421
1372
  const live = this.heldNodeIds.has(id) ? void 0 : this.presence.getNodeTransforms().get(id);
1422
- const size = live ?? this.nodes.get(id)?.size ?? DEFAULT_NODE_SIZE;
1423
- return { x: origin.x, y: origin.y, width: size.width, height: size.height };
1373
+ if (live) return { x: origin.x, y: origin.y, width: live.width, height: live.height };
1374
+ const node = this.nodes.get(id);
1375
+ return {
1376
+ x: origin.x,
1377
+ y: origin.y,
1378
+ width: node?.width ?? node?.size?.width ?? DEFAULT_NODE_SIZE.width,
1379
+ height: node?.height ?? node?.size?.height ?? DEFAULT_NODE_SIZE.height
1380
+ };
1424
1381
  };
1425
1382
  getNodeDepth = (id) => nodeDepth(this.nodes, id);
1426
1383
  getDescendantIds = (id) => descendantIds(this.nodes, id);
@@ -1618,8 +1575,10 @@ var RivetGraphStore = class {
1618
1575
  const opened = this.locks.setLocks(locks);
1619
1576
  if (opened.length === 0 || this.heldNodeIds.size === 0) return;
1620
1577
  const held = [...this.heldNodeIds];
1621
- for (const nodeId of opened) {
1622
- if (!this.heldNodeIds.has(nodeId)) continue;
1578
+ const openedSet = new Set(opened);
1579
+ for (const nodeId of held) {
1580
+ const source = this.locks.getLockSource(nodeId);
1581
+ if (source === null || !openedSet.has(source)) continue;
1623
1582
  const holderId = this.locks.getHolder(nodeId);
1624
1583
  if (holderId) this.lockConflictHandler?.({ nodeId, holderId, ids: held });
1625
1584
  }
@@ -1647,6 +1606,7 @@ var RivetGraphStore = class {
1647
1606
  };
1648
1607
  selectNode = (id, additive = false) => {
1649
1608
  this.setSelectionBoxActive(false);
1609
+ this.dropSelectedAnchors(() => true);
1650
1610
  if (id === null) {
1651
1611
  this.applyNodeSelection(/* @__PURE__ */ new Set());
1652
1612
  this.notifySelection();
@@ -1666,6 +1626,7 @@ var RivetGraphStore = class {
1666
1626
  };
1667
1627
  selectNodes = (ids, additive = false) => {
1668
1628
  this.setSelectionBoxActive(false);
1629
+ this.dropSelectedAnchors(() => true);
1669
1630
  const target = additive ? new Set(this.selectedNodeIds) : /* @__PURE__ */ new Set();
1670
1631
  for (const id of ids) target.add(id);
1671
1632
  this.applyNodeSelection(target);
@@ -1674,6 +1635,7 @@ var RivetGraphStore = class {
1674
1635
  };
1675
1636
  getSelectedNodes = () => [...this.selectedNodeIds];
1676
1637
  isNodeSelected = (id) => this.selectedNodeIds.has(id);
1638
+ getSelectedEdgeId = () => this.selectedEdgeId;
1677
1639
  getSelection = () => this.buildSelection();
1678
1640
  subscribeSelection = (listener) => {
1679
1641
  this.selectionListeners.add(listener);
@@ -1713,6 +1675,7 @@ var RivetGraphStore = class {
1713
1675
  // --- edges ---------------------------------------------------------------
1714
1676
  selectEdge = (id) => {
1715
1677
  this.setSelectionBoxActive(false);
1678
+ this.dropSelectedAnchors(() => true);
1716
1679
  this.setEdgeSelection(id);
1717
1680
  this.applyNodeSelection(/* @__PURE__ */ new Set());
1718
1681
  this.notifySelection();
@@ -1758,7 +1721,7 @@ var RivetGraphStore = class {
1758
1721
  const removedNodes = [];
1759
1722
  const removedEdges = [];
1760
1723
  const selectedEdge = this.selectedEdgeId;
1761
- if (selectedEdge && this.removeEdgeInternal(selectedEdge)) {
1724
+ if (selectedEdge && this.locks.allows(edgeTargetKey(selectedEdge), "delete") && this.removeEdgeInternal(selectedEdge)) {
1762
1725
  removedEdges.push(selectedEdge);
1763
1726
  }
1764
1727
  if (this.selectedNodeIds.size > 0) {
@@ -1883,6 +1846,10 @@ var RivetGraphStore = class {
1883
1846
  setEdgeLabelAnchors = (anchors) => {
1884
1847
  this.edgeLabelAnchors = anchors;
1885
1848
  };
1849
+ getPeerEdgeAnchors = () => this.peerEdgeAnchors;
1850
+ setPeerEdgeAnchors = (anchors) => {
1851
+ this.peerEdgeAnchors = anchors;
1852
+ };
1886
1853
  getAlignmentGuides = () => this.alignmentGuides;
1887
1854
  setAlignmentGuides = (guides) => {
1888
1855
  this.alignmentGuides = guides;
@@ -1929,6 +1896,8 @@ var RivetGraphStore = class {
1929
1896
  anchorId,
1930
1897
  color: options?.color ?? prev?.color,
1931
1898
  strays: options?.strays ?? prev?.strays,
1899
+ selectable: options?.selectable ?? prev?.selectable,
1900
+ data: options?.data ?? prev?.data,
1932
1901
  element,
1933
1902
  // Keep the last-known geometry until the next successful measurement, so
1934
1903
  // a re-bind (display remount, editor DOM swap) never blanks the chrome.
@@ -1947,6 +1916,7 @@ var RivetGraphStore = class {
1947
1916
  };
1948
1917
  unregisterAnchor = (nodeId, anchorId) => {
1949
1918
  if (!this.anchors.delete(handleKey(nodeId, anchorId))) return;
1919
+ this.dropSelectedAnchors((ref) => ref.nodeId === nodeId && ref.anchorId === anchorId);
1950
1920
  this.bumpAnchors(nodeId);
1951
1921
  this.requestRender();
1952
1922
  };
@@ -1968,6 +1938,51 @@ var RivetGraphStore = class {
1968
1938
  return records;
1969
1939
  };
1970
1940
  getNodeAnchorsVersion = (nodeId) => this.anchorVersions.get(nodeId) ?? 0;
1941
+ getSelectedAnchors = () => {
1942
+ if (this.selectedAnchorsStale) {
1943
+ this.selectedAnchorsStale = false;
1944
+ this.selectedAnchorSnapshot = [...this.selectedAnchors.values()];
1945
+ }
1946
+ return this.selectedAnchorSnapshot;
1947
+ };
1948
+ setSelectedAnchors = (refs) => {
1949
+ const next = /* @__PURE__ */ new Map();
1950
+ for (const ref of refs) next.set(handleKey(ref.nodeId, ref.anchorId), ref);
1951
+ if (next.size === this.selectedAnchors.size && [...next.keys()].every((key) => this.selectedAnchors.has(key))) {
1952
+ return;
1953
+ }
1954
+ const clearsGraph = next.size > 0 && (this.selectedNodeIds.size > 0 || this.selectedEdgeId !== null);
1955
+ this.selectedAnchors = /* @__PURE__ */ new Map();
1956
+ if (clearsGraph) {
1957
+ this.selectNode(null);
1958
+ this.selectEdge(null);
1959
+ }
1960
+ this.selectedAnchors = next;
1961
+ this.selectedAnchorsStale = true;
1962
+ for (const listener of this.anchorSelectionListeners) listener();
1963
+ this.requestRender();
1964
+ };
1965
+ isAnchorSelected = (nodeId, anchorId) => this.selectedAnchors.has(handleKey(nodeId, anchorId));
1966
+ subscribeAnchorSelection = (listener) => {
1967
+ this.anchorSelectionListeners.add(listener);
1968
+ return () => {
1969
+ this.anchorSelectionListeners.delete(listener);
1970
+ };
1971
+ };
1972
+ /** Drop every selected anchor the predicate claims. No-op when none match. */
1973
+ dropSelectedAnchors(matches) {
1974
+ if (this.selectedAnchors.size === 0) return;
1975
+ let changed = false;
1976
+ for (const [key, ref] of this.selectedAnchors) {
1977
+ if (!matches(ref)) continue;
1978
+ this.selectedAnchors.delete(key);
1979
+ changed = true;
1980
+ }
1981
+ if (!changed) return;
1982
+ this.selectedAnchorsStale = true;
1983
+ for (const listener of this.anchorSelectionListeners) listener();
1984
+ this.requestRender();
1985
+ }
1971
1986
  subscribeNodeAnchors = (nodeId, listener) => {
1972
1987
  let set = this.anchorListeners.get(nodeId);
1973
1988
  if (!set) {
@@ -1992,17 +2007,29 @@ var RivetGraphStore = class {
1992
2007
  beginConnection = (next) => {
1993
2008
  this.pending = next;
1994
2009
  this.requestRender();
2010
+ this.notifyConnection();
1995
2011
  };
1996
2012
  updateConnection = (to, toPosition) => {
1997
2013
  if (!this.pending) return;
1998
2014
  this.pending = { ...this.pending, to, toPosition };
1999
2015
  this.requestRender();
2016
+ this.notifyConnection();
2000
2017
  };
2001
2018
  endConnection = () => {
2002
2019
  if (!this.pending) return;
2003
2020
  this.pending = null;
2004
2021
  this.requestRender();
2022
+ this.notifyConnection();
2023
+ };
2024
+ subscribeConnection = (listener) => {
2025
+ this.connectionListeners.add(listener);
2026
+ return () => {
2027
+ this.connectionListeners.delete(listener);
2028
+ };
2005
2029
  };
2030
+ notifyConnection() {
2031
+ for (const listener of this.connectionListeners) listener();
2032
+ }
2006
2033
  // --- render loop hook ----------------------------------------------------
2007
2034
  requestRender = () => {
2008
2035
  this.renderRequester();
@@ -2010,6 +2037,13 @@ var RivetGraphStore = class {
2010
2037
  bindRenderRequester = (fn) => {
2011
2038
  this.renderRequester = fn;
2012
2039
  };
2040
+ requestCursorRender = () => {
2041
+ if (this.cursorRenderRequester) this.cursorRenderRequester();
2042
+ else this.renderRequester();
2043
+ };
2044
+ bindCursorRenderRequester = (fn) => {
2045
+ this.cursorRenderRequester = fn;
2046
+ };
2013
2047
  // --- reconnect gateway (runtime-bound; consulted by <Handle>) ------------
2014
2048
  reconnectDelegate = null;
2015
2049
  getSelectedEdge = () => this.selectedEdgeId;
@@ -2674,7 +2708,12 @@ function NodeResizer({
2674
2708
 
2675
2709
  // src/presence/local.ts
2676
2710
  var sameIds = (a, b) => a.length === b.length && a.every((id, i) => id === b[i]);
2677
- var samePresence = (a, b) => a.cursor?.x === b.cursor?.x && a.cursor?.y === b.cursor?.y && sameIds(a.selection, b.selection) && sameIds(a.holding, b.holding);
2711
+ var sameConnection = (a, b) => {
2712
+ if (a === b) return true;
2713
+ if (!a || !b) return false;
2714
+ return a.source === b.source && a.sourceHandle === b.sourceHandle && a.toPosition === b.toPosition && a.to.x === b.to.x && a.to.y === b.to.y;
2715
+ };
2716
+ var samePresence = (a, b) => a.cursor?.x === b.cursor?.x && a.cursor?.y === b.cursor?.y && sameIds(a.selection, b.selection) && sameIds(a.holding, b.holding) && sameConnection(a.pending, b.pending);
2678
2717
  function trackLocalPresence({
2679
2718
  store,
2680
2719
  pane,
@@ -2686,6 +2725,21 @@ function trackLocalPresence({
2686
2725
  let last = null;
2687
2726
  let timer = null;
2688
2727
  let lastEmit = Number.NEGATIVE_INFINITY;
2728
+ const selectionTargets = () => {
2729
+ const targets = store.getSelectedNodes();
2730
+ const edgeId = store.getSelectedEdgeId();
2731
+ if (edgeId) targets.push(presenceTargetKey({ kind: "edge", id: edgeId }));
2732
+ for (const ref of store.getSelectedAnchors()) {
2733
+ targets.push(presenceTargetKey({ kind: "anchor", ...ref }));
2734
+ }
2735
+ return targets;
2736
+ };
2737
+ const localConnection = () => {
2738
+ const pending = store.getPending();
2739
+ if (!pending) return null;
2740
+ const { source, sourceHandle, sourceType, from, to, toPosition } = pending;
2741
+ return toPosition === void 0 ? { source, sourceHandle, sourceType, from, to } : { source, sourceHandle, sourceType, from, to, toPosition };
2742
+ };
2689
2743
  const publish = () => {
2690
2744
  if (timer !== null) {
2691
2745
  clearTimeout(timer);
@@ -2694,8 +2748,9 @@ function trackLocalPresence({
2694
2748
  lastEmit = performance.now();
2695
2749
  const next = {
2696
2750
  cursor,
2697
- selection: store.getSelectedNodes(),
2698
- holding: [...store.getHeldNodeIds()]
2751
+ selection: selectionTargets(),
2752
+ holding: [...store.getHeldNodeIds()],
2753
+ pending: localConnection()
2699
2754
  };
2700
2755
  if (last && samePresence(last, next)) return;
2701
2756
  last = next;
@@ -2737,7 +2792,18 @@ function trackLocalPresence({
2737
2792
  schedule();
2738
2793
  });
2739
2794
  const unsubscribeSelection = store.subscribeSelection(() => publish());
2795
+ const unsubscribeAnchors = store.subscribeAnchorSelection(() => publish());
2740
2796
  const unsubscribeHeld = store.subscribeHeldNodes(() => publish());
2797
+ let hadPending = store.getPending() !== null;
2798
+ const unsubscribeConnection = store.subscribeConnection(() => {
2799
+ const has = store.getPending() !== null;
2800
+ if (has === hadPending) {
2801
+ schedule();
2802
+ return;
2803
+ }
2804
+ hadPending = has;
2805
+ publish();
2806
+ });
2741
2807
  pane.addEventListener("pointermove", onPointerMove);
2742
2808
  pane.addEventListener("pointerleave", onPointerLeave);
2743
2809
  return () => {
@@ -2746,7 +2812,9 @@ function trackLocalPresence({
2746
2812
  pane.removeEventListener("pointerleave", onPointerLeave);
2747
2813
  unsubscribeViewport();
2748
2814
  unsubscribeSelection();
2815
+ unsubscribeAnchors();
2749
2816
  unsubscribeHeld();
2817
+ unsubscribeConnection();
2750
2818
  };
2751
2819
  }
2752
2820
 
@@ -3043,7 +3111,7 @@ function createPanController(deps) {
3043
3111
 
3044
3112
  // src/input/resize-controller.ts
3045
3113
  function createResizeController(deps) {
3046
- const { pane, bgCanvas, size, edgeRenderer, foregroundRenderer, schedule } = deps;
3114
+ const { pane, bgCanvas, size, edgeRenderer, foregroundRenderer, onResize, schedule } = deps;
3047
3115
  const resize = () => {
3048
3116
  const rect = pane.getBoundingClientRect();
3049
3117
  size.width = rect.width;
@@ -3055,6 +3123,7 @@ function createResizeController(deps) {
3055
3123
  bgCanvas.style.height = `${rect.height}px`;
3056
3124
  edgeRenderer.resize(rect.width, rect.height, size.dpr);
3057
3125
  foregroundRenderer.resize(rect.width, rect.height, size.dpr);
3126
+ onResize?.();
3058
3127
  schedule();
3059
3128
  };
3060
3129
  const observer = new ResizeObserver(resize);
@@ -3147,6 +3216,9 @@ var LABEL_PADDING_X = 6;
3147
3216
  var LABEL_HEIGHT = 17;
3148
3217
  var LABEL_RADIUS = 4;
3149
3218
  var CULL_MARGIN_PX = 64;
3219
+ var PENDING_DASH = [6, 4];
3220
+ var PENDING_LINE_WIDTH = 2;
3221
+ var PENDING_TIP_RADIUS = 3.5;
3150
3222
  function traceRect(ctx, rect, radius) {
3151
3223
  ctx.beginPath();
3152
3224
  if (typeof ctx.roundRect === "function") {
@@ -3199,15 +3271,18 @@ function drawCursor(ctx, point, color, name) {
3199
3271
  ctx.fillStyle = "#ffffff";
3200
3272
  ctx.fillText(name, x + LABEL_PADDING_X, y + LABEL_HEIGHT / 2);
3201
3273
  }
3202
- function drawPresence(ctx, peers, { viewport, size, getNodeRect, renderCursors, locks }) {
3274
+ function drawPeerOutlines(ctx, peers, { viewport, size, getNodeRect, locks }) {
3203
3275
  if (peers.length === 0 && locks.size === 0) return;
3204
3276
  ctx.save();
3205
3277
  ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
3206
3278
  const painted = /* @__PURE__ */ new Set();
3207
3279
  for (const peer of peers) {
3208
- const held = /* @__PURE__ */ new Set([...peer.holding, ...peer.transforms.keys()]);
3280
+ const held = /* @__PURE__ */ new Set([
3281
+ ...peer.holding.filter((key) => isNodeTarget(key)),
3282
+ ...peer.transforms.keys()
3283
+ ]);
3209
3284
  for (const id of peer.selection) {
3210
- if (held.has(id)) continue;
3285
+ if (!isNodeTarget(id) || held.has(id)) continue;
3211
3286
  const rect = getNodeRect(id);
3212
3287
  if (!rect) continue;
3213
3288
  const screen = projectRect(rect, viewport);
@@ -3224,7 +3299,7 @@ function drawPresence(ctx, peers, { viewport, size, getNodeRect, renderCursors,
3224
3299
  if (locks.size > 0) {
3225
3300
  const declared = new Map(peers.map((peer) => [peer.id, peer.color]));
3226
3301
  for (const [id, holderId] of locks) {
3227
- if (painted.has(id)) continue;
3302
+ if (!isNodeTarget(id) || painted.has(id)) continue;
3228
3303
  const rect = getNodeRect(id);
3229
3304
  if (!rect) continue;
3230
3305
  const screen = projectRect(rect, viewport);
@@ -3232,15 +3307,80 @@ function drawPresence(ctx, peers, { viewport, size, getNodeRect, renderCursors,
3232
3307
  drawNodeOutline(ctx, screen, declared.get(holderId) ?? peerColor(holderId), "held");
3233
3308
  }
3234
3309
  }
3235
- if (renderCursors) {
3236
- for (const peer of peers) {
3237
- if (!peer.cursor) continue;
3238
- const point = worldToScreen(peer.cursor, viewport);
3239
- if (!onScreen({ ...point, width: 0, height: 0 }, size)) continue;
3240
- drawCursor(ctx, point, peer.color, peer.name);
3310
+ ctx.restore();
3311
+ }
3312
+ function collectPeerEdges(peers, lockedEdges) {
3313
+ if (peers.length === 0 && lockedEdges.size === 0) return void 0;
3314
+ let claimed = null;
3315
+ const claim = (key, color, override) => {
3316
+ const edgeId = presenceTargetEdgeId(key);
3317
+ if (edgeId === null) return;
3318
+ claimed ??= /* @__PURE__ */ new Map();
3319
+ if (override || !claimed.has(edgeId)) claimed.set(edgeId, color);
3320
+ };
3321
+ for (const peer of peers) {
3322
+ for (const key of peer.selection) claim(key, peer.color, false);
3323
+ }
3324
+ for (const peer of peers) {
3325
+ for (const key of peer.holding) claim(key, peer.color, true);
3326
+ }
3327
+ if (lockedEdges.size > 0) {
3328
+ const declared = new Map(peers.map((peer) => [peer.id, peer.color]));
3329
+ for (const [edgeId, holderId] of lockedEdges) {
3330
+ claimed ??= /* @__PURE__ */ new Map();
3331
+ if (!claimed.has(edgeId)) {
3332
+ claimed.set(edgeId, declared.get(holderId) ?? peerColor(holderId));
3333
+ }
3241
3334
  }
3242
3335
  }
3243
- ctx.restore();
3336
+ return claimed ?? void 0;
3337
+ }
3338
+ function drawPeerPending(ctx, peers, { viewport, size, handles }) {
3339
+ let saved = false;
3340
+ for (const peer of peers) {
3341
+ if (!peer.pending) continue;
3342
+ const from = worldToScreen(peer.pending.from, viewport);
3343
+ const to = worldToScreen(peer.pending.to, viewport);
3344
+ const bounds = {
3345
+ x: Math.min(from.x, to.x),
3346
+ y: Math.min(from.y, to.y),
3347
+ width: Math.abs(to.x - from.x),
3348
+ height: Math.abs(to.y - from.y)
3349
+ };
3350
+ if (!onScreen(bounds, size)) continue;
3351
+ if (!saved) {
3352
+ saved = true;
3353
+ ctx.save();
3354
+ ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
3355
+ ctx.lineWidth = PENDING_LINE_WIDTH;
3356
+ ctx.setLineDash(PENDING_DASH);
3357
+ }
3358
+ const points = buildPendingPath(peer.pending, handles, viewport);
3359
+ const [first, ...rest] = points;
3360
+ if (!first) continue;
3361
+ ctx.strokeStyle = peer.color;
3362
+ ctx.beginPath();
3363
+ ctx.moveTo(first.x, first.y);
3364
+ for (const point of rest) ctx.lineTo(point.x, point.y);
3365
+ ctx.stroke();
3366
+ ctx.setLineDash([]);
3367
+ ctx.fillStyle = peer.color;
3368
+ ctx.beginPath();
3369
+ ctx.arc(to.x, to.y, PENDING_TIP_RADIUS, 0, Math.PI * 2);
3370
+ ctx.fill();
3371
+ ctx.setLineDash(PENDING_DASH);
3372
+ }
3373
+ if (saved) ctx.restore();
3374
+ }
3375
+ function drawPeerCursors(ctx, peers, { viewport, size }) {
3376
+ ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
3377
+ ctx.clearRect(0, 0, size.width, size.height);
3378
+ for (const peer of peers) {
3379
+ if (!peer.cursor) continue;
3380
+ const point = worldToScreen(peer.cursor, viewport);
3381
+ if (!onScreen({ ...point, width: 0, height: 0 }, size)) continue;
3382
+ drawCursor(ctx, point, peer.color, peer.name);
3383
+ }
3244
3384
  }
3245
3385
 
3246
3386
  // src/renderer/swimlane.ts
@@ -3302,6 +3442,7 @@ function drawAlignmentGuides(ctx, guides, viewport, size) {
3302
3442
  }
3303
3443
  var ENDPOINT_BUBBLE_RADIUS = 6;
3304
3444
  var HOVER_KEEP_TOLERANCE = 16;
3445
+ var EMPTY_EDGE_ANCHORS = /* @__PURE__ */ new Map();
3305
3446
  function drawEndpointBubbles(ctx, ends, size) {
3306
3447
  ctx.save();
3307
3448
  ctx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
@@ -3318,7 +3459,7 @@ function drawEndpointBubbles(ctx, ends, size) {
3318
3459
  }
3319
3460
  function useRivetRuntime(params) {
3320
3461
  const { store, paneRef, nodeContainerRef } = params;
3321
- const { backgroundCanvasRef, edgeCanvasRef, foregroundCanvasRef } = params;
3462
+ const { backgroundCanvasRef, edgeCanvasRef, foregroundCanvasRef, cursorCanvasRef } = params;
3322
3463
  const { swimlaneLanes, scrollToPan, zoomSpeed } = params;
3323
3464
  const { minZoom, maxZoom, gridGap, edgeTypes, defaultEdgeOptions, anchorOptions } = params;
3324
3465
  const { edgeRenderer: createEdgeRenderer } = params;
@@ -3326,6 +3467,8 @@ function useRivetRuntime(params) {
3326
3467
  const [visibleIds, setVisibleIds] = useState([]);
3327
3468
  const reconnectRef = useRef(params);
3328
3469
  reconnectRef.current = params;
3470
+ const outlineModeRef = useRef(params.outlineMode);
3471
+ outlineModeRef.current = params.outlineMode;
3329
3472
  const sizeRef = useRef({ width: 0, height: 0, dpr: 1 });
3330
3473
  const visibleKeyRef = useRef("");
3331
3474
  const swimlaneLanesRef = useRef(swimlaneLanes);
@@ -3357,6 +3500,7 @@ function useRivetRuntime(params) {
3357
3500
  const size = sizeRef.current;
3358
3501
  let frame = 0;
3359
3502
  let dirty = true;
3503
+ let cursorsDirty = true;
3360
3504
  let hoveredEdgeId = null;
3361
3505
  let peerPositioned = /* @__PURE__ */ new Set();
3362
3506
  const positionPeerNodes = (worldPositions) => {
@@ -3383,6 +3527,39 @@ function useRivetRuntime(params) {
3383
3527
  }
3384
3528
  peerPositioned = current;
3385
3529
  };
3530
+ let cursorCanvas = null;
3531
+ let cursorCtx = null;
3532
+ let cursorAllocated = false;
3533
+ const sizeCursorLayer = () => {
3534
+ if (!cursorCanvas || !cursorAllocated) return;
3535
+ cursorCanvas.width = Math.max(1, Math.round(size.width * size.dpr));
3536
+ cursorCanvas.height = Math.max(1, Math.round(size.height * size.dpr));
3537
+ cursorCanvas.style.width = `${size.width}px`;
3538
+ cursorCanvas.style.height = `${size.height}px`;
3539
+ };
3540
+ const drawCursorLayer = () => {
3541
+ const canvas = cursorCanvasRef.current;
3542
+ if (canvas !== cursorCanvas) {
3543
+ cursorCanvas = canvas;
3544
+ cursorCtx = canvas?.getContext("2d") ?? null;
3545
+ cursorAllocated = false;
3546
+ }
3547
+ if (!cursorCtx) return;
3548
+ const peers = store.presence.getPeers();
3549
+ const wanted = peers.some((peer) => peer.cursor);
3550
+ if (!wanted && !cursorAllocated) return;
3551
+ if (!cursorAllocated) {
3552
+ cursorAllocated = true;
3553
+ sizeCursorLayer();
3554
+ }
3555
+ drawPeerCursors(cursorCtx, peers, { viewport: store.getViewport(), size });
3556
+ };
3557
+ const stepPresence = () => {
3558
+ const version = store.presence.getTransformVersion();
3559
+ if (!store.presence.step(performance.now())) return;
3560
+ if (store.presence.getTransformVersion() !== version) schedule();
3561
+ else scheduleCursors();
3562
+ };
3386
3563
  const anchorPlacement = (nodeId, anchorId, side) => {
3387
3564
  const record = store.anchors.get(handleKey(nodeId, anchorId));
3388
3565
  return record?.geometry ? strayPlacementPct(record.geometry, side, anchorOptions) : null;
@@ -3398,9 +3575,10 @@ function useRivetRuntime(params) {
3398
3575
  scrollToPan,
3399
3576
  schedule: () => schedule()
3400
3577
  });
3578
+ const canReconnect = (edgeId) => store.locks.size() === 0 || store.locks.allows(edgeTargetKey(edgeId), "reconnect");
3401
3579
  const render = () => {
3402
3580
  zoom.step();
3403
- if (store.presence.step(performance.now())) schedule();
3581
+ stepPresence();
3404
3582
  const viewport = store.getViewport();
3405
3583
  layerHint.apply(viewportToCss(viewport));
3406
3584
  bgCtx.setTransform(size.dpr, 0, 0, size.dpr, 0, 0);
@@ -3412,6 +3590,8 @@ function useRivetRuntime(params) {
3412
3590
  const edgeList = [...store.edges.values()];
3413
3591
  const pending = store.getPending();
3414
3592
  const liveAlignNodes = store.getEdgeAlignment() === "live" && store.isNodeDragging() ? store.getDraggingNodeIds() : void 0;
3593
+ const outlineMode = outlineModeRef.current;
3594
+ const peerEdges = outlineMode === "none" ? void 0 : collectPeerEdges(store.presence.getPeers(), store.locks.getLockedEdges());
3415
3595
  edgeRenderer.draw(edgeList, store.nodes, viewport, {
3416
3596
  handles: store.handles,
3417
3597
  pending: null,
@@ -3420,32 +3600,43 @@ function useRivetRuntime(params) {
3420
3600
  worldPositions,
3421
3601
  displayedSides: store.getDisplayedSides(),
3422
3602
  liveAlignNodes,
3423
- anchorPlacement
3603
+ anchorPlacement,
3604
+ peerEdges
3424
3605
  });
3425
3606
  store.setEdgeLabelAnchors(edgeRenderer.getLabels());
3607
+ if (outlineMode === "dom") {
3608
+ store.setPeerEdgeAnchors(edgeRenderer.getPeerEdgeAnchors?.() ?? EMPTY_EDGE_ANCHORS);
3609
+ }
3426
3610
  foregroundRenderer.draw([], store.nodes, viewport, {
3427
3611
  handles: store.handles,
3428
3612
  pending
3429
3613
  });
3614
+ if (fgCtx && !store.presence.isEmpty()) {
3615
+ drawPeerPending(fgCtx, store.presence.getPeers(), {
3616
+ viewport,
3617
+ size,
3618
+ handles: store.handles
3619
+ });
3620
+ }
3430
3621
  if (fgCtx) drawAlignmentGuides(fgCtx, store.getAlignmentGuides(), viewport, size);
3431
3622
  if (fgCtx) {
3432
3623
  const affordanceId = hoveredEdgeId ?? store.getSelectedEdge();
3433
3624
  const edge = affordanceId ? store.edges.get(affordanceId) : void 0;
3434
3625
  const reconnectable = edge ? edge.reconnectable ?? reconnectRef.current.edgesReconnectable : false;
3435
- if (edge && reconnectable && edge.id !== pending?.reconnecting) {
3626
+ if (edge && reconnectable && edge.id !== pending?.reconnecting && canReconnect(edge.id)) {
3436
3627
  const ends = edgeRenderer.getEndpoints?.().get(edge.id);
3437
3628
  if (ends) drawEndpointBubbles(fgCtx, ends, size);
3438
3629
  }
3439
3630
  }
3440
- if (fgCtx && (!store.presence.isEmpty() || store.locks.size() > 0)) {
3441
- drawPresence(fgCtx, store.presence.getPeers(), {
3631
+ if (fgCtx && outlineMode === "canvas" && (!store.presence.isEmpty() || store.locks.size() > 0)) {
3632
+ drawPeerOutlines(fgCtx, store.presence.getPeers(), {
3442
3633
  viewport,
3443
3634
  size,
3444
3635
  getNodeRect: (id) => store.nodes.has(id) ? store.getNodeRect(id) : null,
3445
- renderCursors: reconnectRef.current.presenceOptions.renderCursors,
3446
3636
  locks: store.locks.getLocks()
3447
3637
  });
3448
3638
  }
3639
+ drawCursorLayer();
3449
3640
  if (edgeList.some((edge) => edge.animated ?? defaultEdgeOptions?.animated)) schedule();
3450
3641
  const rect = visibleWorldRect(viewport, size.width, size.height);
3451
3642
  const margin = CULL_MARGIN_PX2 / viewport.zoom;
@@ -3474,19 +3665,34 @@ function useRivetRuntime(params) {
3474
3665
  setVisibleIds(ids);
3475
3666
  }
3476
3667
  store.notifyFrame();
3668
+ store.notifyCursorFrame();
3477
3669
  };
3478
3670
  const tick = () => {
3479
3671
  frame = 0;
3480
- if (!dirty) return;
3481
- dirty = false;
3482
- render();
3672
+ if (dirty) {
3673
+ dirty = false;
3674
+ cursorsDirty = false;
3675
+ render();
3676
+ return;
3677
+ }
3678
+ if (!cursorsDirty) return;
3679
+ cursorsDirty = false;
3680
+ stepPresence();
3681
+ drawCursorLayer();
3682
+ store.notifyCursorFrame();
3483
3683
  };
3484
3684
  const schedule = () => {
3485
3685
  dirty = true;
3486
3686
  if (frame) return;
3487
3687
  frame = requestAnimationFrame(tick);
3488
3688
  };
3689
+ const scheduleCursors = () => {
3690
+ cursorsDirty = true;
3691
+ if (frame) return;
3692
+ frame = requestAnimationFrame(tick);
3693
+ };
3489
3694
  store.bindRenderRequester(schedule);
3695
+ store.bindCursorRenderRequester(scheduleCursors);
3490
3696
  const setEdgeHover = (id) => {
3491
3697
  if (hoveredEdgeId === id) return;
3492
3698
  hoveredEdgeId = id;
@@ -3522,6 +3728,7 @@ function useRivetRuntime(params) {
3522
3728
  return { x, y: base.y + size2.height / 2 };
3523
3729
  };
3524
3730
  const beginReconnect = (edge, end) => {
3731
+ if (!canReconnect(edge.id)) return;
3525
3732
  zoom.cancel();
3526
3733
  const fixedIsSource = end === "target";
3527
3734
  const fixedNodeId = fixedIsSource ? edge.source : edge.target;
@@ -3578,7 +3785,7 @@ function useRivetRuntime(params) {
3578
3785
  if (hit.edgeId !== hoveredEdgeId && hit.edgeId !== store.getSelectedEdge()) return null;
3579
3786
  const edge = store.edges.get(hit.edgeId);
3580
3787
  const reconnectable = edge ? edge.reconnectable ?? reconnectRef.current.edgesReconnectable : false;
3581
- return reconnectable ? hit : null;
3788
+ return reconnectable && canReconnect(hit.edgeId) ? hit : null;
3582
3789
  },
3583
3790
  begin: (edgeId, end) => {
3584
3791
  const edge = store.edges.get(edgeId);
@@ -3617,7 +3824,7 @@ function useRivetRuntime(params) {
3617
3824
  }
3618
3825
  const hitEdge = edgeRenderer.pick(px, py);
3619
3826
  if (hitEdge && store.edges.get(hitEdge)?.selectable !== false) {
3620
- store.selectEdge(hitEdge);
3827
+ if (store.locks.allows(edgeTargetKey(hitEdge), "select")) store.selectEdge(hitEdge);
3621
3828
  return;
3622
3829
  }
3623
3830
  store.selectNode(null);
@@ -3696,6 +3903,7 @@ function useRivetRuntime(params) {
3696
3903
  size,
3697
3904
  edgeRenderer,
3698
3905
  foregroundRenderer,
3906
+ onResize: sizeCursorLayer,
3699
3907
  schedule
3700
3908
  });
3701
3909
  pane.addEventListener("wheel", zoom.onWheel, { passive: false });
@@ -3712,6 +3920,7 @@ function useRivetRuntime(params) {
3712
3920
  resizer.dispose();
3713
3921
  store.bindRenderRequester(() => {
3714
3922
  });
3923
+ store.bindCursorRenderRequester(null);
3715
3924
  store.bindReconnectDelegate(null);
3716
3925
  unsubscribeFocus();
3717
3926
  if (focusRetryFrame) cancelAnimationFrame(focusRetryFrame);
@@ -3739,6 +3948,7 @@ function useRivetRuntime(params) {
3739
3948
  backgroundCanvasRef,
3740
3949
  edgeCanvasRef,
3741
3950
  foregroundCanvasRef,
3951
+ cursorCanvasRef,
3742
3952
  scrollToPan,
3743
3953
  zoomSpeed,
3744
3954
  minZoom,
@@ -3958,6 +4168,8 @@ var PICK_TOLERANCE = 6;
3958
4168
  var ENDPOINT_RADIUS = 12;
3959
4169
  var ARROW_SIZE = 9;
3960
4170
  var DASH_SPEED = 40;
4171
+ var PEER_HALO_EXTRA = 6;
4172
+ var PEER_HALO_ALPHA = 0.45;
3961
4173
  var Canvas2DEdgeRenderer = class {
3962
4174
  constructor(canvas, options = {}) {
3963
4175
  this.canvas = canvas;
@@ -3980,6 +4192,8 @@ var Canvas2DEdgeRenderer = class {
3980
4192
  endpoints = /* @__PURE__ */ new Map();
3981
4193
  /** Screen-space label anchor per edge that has a label. */
3982
4194
  labels = /* @__PURE__ */ new Map();
4195
+ /** Screen-space midpoint per edge somebody has claimed, for DOM claim chrome. */
4196
+ peerAnchors = /* @__PURE__ */ new Map();
3983
4197
  resize(width, height, dpr) {
3984
4198
  this.width = width;
3985
4199
  this.height = height;
@@ -3993,6 +4207,10 @@ var Canvas2DEdgeRenderer = class {
3993
4207
  getLabels() {
3994
4208
  return this.labels;
3995
4209
  }
4210
+ /** Screen-space midpoints for the edges a peer had claimed last frame. */
4211
+ getPeerEdgeAnchors() {
4212
+ return this.peerAnchors;
4213
+ }
3996
4214
  /** Screen-space endpoints per edge from the last frame (reconnect affordance). */
3997
4215
  getEndpoints() {
3998
4216
  return this.endpoints;
@@ -4007,11 +4225,23 @@ var Canvas2DEdgeRenderer = class {
4007
4225
  this.geometry.clear();
4008
4226
  this.endpoints.clear();
4009
4227
  this.labels.clear();
4228
+ this.peerAnchors.clear();
4010
4229
  const resolved = buildEdges(edges, nodes, viewport, this.edgeTypes, this.defaults, extras);
4230
+ const peerEdges = extras?.peerEdges;
4011
4231
  for (const edge of resolved) {
4012
4232
  this.geometry.set(edge.id, edge.points);
4013
4233
  this.endpoints.set(edge.id, { source: edge.source, target: edge.target });
4014
4234
  if (edge.label) this.labels.set(edge.id, edge.label);
4235
+ const peerColor2 = peerEdges?.get(edge.id);
4236
+ if (peerColor2) {
4237
+ this.peerAnchors.set(edge.id, polylineMidpoint(edge.points));
4238
+ ctx.strokeStyle = peerColor2;
4239
+ ctx.lineWidth = edge.width + PEER_HALO_EXTRA * 2;
4240
+ ctx.globalAlpha = PEER_HALO_ALPHA;
4241
+ ctx.setLineDash([]);
4242
+ ctx.lineDashOffset = 0;
4243
+ strokePolyline(ctx, edge.points);
4244
+ }
4015
4245
  ctx.strokeStyle = edge.stroke;
4016
4246
  ctx.lineWidth = edge.width;
4017
4247
  ctx.globalAlpha = edge.opacity;
@@ -4072,6 +4302,7 @@ var Canvas2DEdgeRenderer = class {
4072
4302
  this.geometry.clear();
4073
4303
  this.endpoints.clear();
4074
4304
  this.labels.clear();
4305
+ this.peerAnchors.clear();
4075
4306
  }
4076
4307
  };
4077
4308
  var canvas2DEdgeRenderer = (canvas, options) => new Canvas2DEdgeRenderer(canvas, options);
@@ -4181,7 +4412,6 @@ function EdgeLabelLayer() {
4181
4412
  }
4182
4413
 
4183
4414
  // src/input/group-drag.ts
4184
- var DRAG_THRESHOLD = 4;
4185
4415
  var ALIGN_THRESHOLD = 6;
4186
4416
  function startGroupDrag(params) {
4187
4417
  const { store, el, snapGrid, alignmentGuides, onLaneChange, onFrame } = params;
@@ -4206,6 +4436,7 @@ function startGroupDrag(params) {
4206
4436
  onFrame?.();
4207
4437
  };
4208
4438
  const beginDrag = () => {
4439
+ params.selectOnDrag?.();
4209
4440
  const movers = store.getMovers(store.getSelectedNodes()).filter((moverId) => store.locks.allows(moverId, "drag"));
4210
4441
  if (movers.length === 0) return false;
4211
4442
  started = true;
@@ -4334,6 +4565,37 @@ function clampToParent(store, parentId, position, size) {
4334
4565
  };
4335
4566
  return clampChildToParent(position, size, parentSize);
4336
4567
  }
4568
+
4569
+ // src/anchor/select-gesture.ts
4570
+ function attachAnchorSelection(store, record) {
4571
+ const element = record.element;
4572
+ if (!record.selectable || !(element instanceof HTMLElement || element instanceof SVGElement)) {
4573
+ return null;
4574
+ }
4575
+ const { nodeId, anchorId } = record;
4576
+ const previous = element.style.pointerEvents;
4577
+ element.style.pointerEvents = "auto";
4578
+ const onPointerDown = (event) => {
4579
+ if (!(event instanceof MouseEvent)) return;
4580
+ if (event.button !== 0) return;
4581
+ const originX = event.clientX;
4582
+ const originY = event.clientY;
4583
+ const onUp = (upEvent) => {
4584
+ window.removeEventListener("pointerup", onUp);
4585
+ if (!(upEvent instanceof MouseEvent)) return;
4586
+ const moved = Math.hypot(upEvent.clientX - originX, upEvent.clientY - originY);
4587
+ if (moved >= DRAG_THRESHOLD) return;
4588
+ if (!store.locks.allows(anchorTargetKey(nodeId, anchorId), "select")) return;
4589
+ store.setSelectedAnchors([{ nodeId, anchorId }]);
4590
+ };
4591
+ window.addEventListener("pointerup", onUp);
4592
+ };
4593
+ element.addEventListener("pointerdown", onPointerDown);
4594
+ return () => {
4595
+ element.removeEventListener("pointerdown", onPointerDown);
4596
+ element.style.pointerEvents = previous;
4597
+ };
4598
+ }
4337
4599
  var DEFAULT_ANCHOR_COLOR = "#6366f1";
4338
4600
  function NodeAnchors({ nodeId }) {
4339
4601
  const { store, anchorOptions } = useRivetContext();
@@ -4342,8 +4604,27 @@ function NodeAnchors({ nodeId }) {
4342
4604
  [store, nodeId]
4343
4605
  );
4344
4606
  const getAnchorsVersion = useCallback(() => store.getNodeAnchorsVersion(nodeId), [store, nodeId]);
4345
- useSyncExternalStore(subscribeAnchors, getAnchorsVersion, getAnchorsVersion);
4607
+ const anchorsVersion = useSyncExternalStore(
4608
+ subscribeAnchors,
4609
+ getAnchorsVersion,
4610
+ getAnchorsVersion
4611
+ );
4346
4612
  useSyncExternalStore(store.subscribeEdges, store.getEdgesVersion, store.getEdgesVersion);
4613
+ useSyncExternalStore(
4614
+ store.subscribeAnchorSelection,
4615
+ store.getSelectedAnchors,
4616
+ store.getSelectedAnchors
4617
+ );
4618
+ useEffect(() => {
4619
+ const cleanups = [];
4620
+ for (const record of store.getNodeAnchors(nodeId)) {
4621
+ const cleanup = attachAnchorSelection(store, record);
4622
+ if (cleanup) cleanups.push(cleanup);
4623
+ }
4624
+ return () => {
4625
+ for (const cleanup of cleanups) cleanup();
4626
+ };
4627
+ }, [store, nodeId, anchorsVersion]);
4347
4628
  const records = store.getNodeAnchors(nodeId);
4348
4629
  if (records.length === 0) return null;
4349
4630
  const dotsVisible = Boolean(store.nodes.get(nodeId)?.hovered);
@@ -4361,6 +4642,7 @@ function NodeAnchors({ nodeId }) {
4361
4642
  }
4362
4643
  }
4363
4644
  return /* @__PURE__ */ jsx(Fragment, { children: records.map((record) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
4645
+ store.isAnchorSelected(nodeId, record.anchorId) && /* @__PURE__ */ jsx(AnchorSelectionOutline, { record }),
4364
4646
  /* @__PURE__ */ jsx(AnchorDots, { record, options: anchorOptions, visible: dotsVisible }),
4365
4647
  record.strays !== "none" && /* @__PURE__ */ jsx(
4366
4648
  AnchorStrayHandles,
@@ -4372,6 +4654,31 @@ function NodeAnchors({ nodeId }) {
4372
4654
  )
4373
4655
  ] }, record.anchorId)) });
4374
4656
  }
4657
+ function AnchorSelectionOutline({ record }) {
4658
+ const geometry = record.geometry;
4659
+ if (!geometry) return null;
4660
+ const { dots } = geometry;
4661
+ const left = dots.left.xPct;
4662
+ const top = dots.top.yPct;
4663
+ return /* @__PURE__ */ jsx(
4664
+ "div",
4665
+ {
4666
+ "data-rivet-anchor-selected": record.anchorId,
4667
+ style: {
4668
+ position: "absolute",
4669
+ left: `${left}%`,
4670
+ top: `${top}%`,
4671
+ width: `${dots.right.xPct - left}%`,
4672
+ height: `${dots.bottom.yPct - top}%`,
4673
+ border: `1px solid ${record.color ?? DEFAULT_ANCHOR_COLOR}`,
4674
+ borderRadius: 2,
4675
+ // The box is a readout, never a target: presses belong to the element
4676
+ // underneath it, which is what put the selection here.
4677
+ pointerEvents: "none"
4678
+ }
4679
+ }
4680
+ );
4681
+ }
4375
4682
  function AnchorDots(props) {
4376
4683
  const { record, options } = props;
4377
4684
  if (!record.geometry || !record.element?.isConnected) return null;
@@ -4590,7 +4897,11 @@ var NodeWrapper = memo(function NodeWrapper2({ id }) {
4590
4897
  if (!noSelect) store.selectNode(id, true);
4591
4898
  return;
4592
4899
  }
4593
- if (!noSelect && !store.isNodeSelected(id)) store.selectNode(id);
4900
+ const onAnchor = target !== null && findSelectableAnchor(store.getNodeAnchors(id), target) !== null;
4901
+ const selectNode = () => {
4902
+ if (!noSelect && !store.isNodeSelected(id)) store.selectNode(id);
4903
+ };
4904
+ if (!onAnchor) selectNode();
4594
4905
  if (noDrag) return;
4595
4906
  startGroupDrag({
4596
4907
  store,
@@ -4603,7 +4914,8 @@ var NodeWrapper = memo(function NodeWrapper2({ id }) {
4603
4914
  clampToSwimlane,
4604
4915
  swimlaneMargin,
4605
4916
  swimlaneLabelWidth,
4606
- onLaneChange
4917
+ onLaneChange,
4918
+ selectOnDrag: onAnchor ? selectNode : void 0
4607
4919
  });
4608
4920
  };
4609
4921
  const node = store.nodes.get(id);
@@ -4760,6 +5072,176 @@ function NodeLayer({ containerRef, visibleIds }) {
4760
5072
  /* @__PURE__ */ jsx(NodesSelection, {})
4761
5073
  ] });
4762
5074
  }
5075
+ var overlayStyle2 = {
5076
+ position: "absolute",
5077
+ inset: 0,
5078
+ // Cursor chrome belongs to the canvas: clipped to the pane rather than
5079
+ // escaping into whatever app UI surrounds it.
5080
+ overflow: "hidden",
5081
+ pointerEvents: "none"
5082
+ };
5083
+ var cursorStyle = {
5084
+ position: "absolute",
5085
+ top: 0,
5086
+ left: 0,
5087
+ // The wrapper is a point, not a box: it sits at the pointer tip and whatever
5088
+ // the consumer renders lays itself out from there.
5089
+ width: 0,
5090
+ height: 0,
5091
+ pointerEvents: "none"
5092
+ };
5093
+ function PeerCursorLayer({
5094
+ component: Cursor
5095
+ }) {
5096
+ const { store, paneRef } = useRivetContext();
5097
+ const peers = usePeerIdentities();
5098
+ const refs = useRef(/* @__PURE__ */ new Map());
5099
+ const zoomRef = useRef(null);
5100
+ const zoomWritten = useRef(/* @__PURE__ */ new WeakSet());
5101
+ const position = useCallback(() => {
5102
+ const pane = paneRef.current;
5103
+ if (!pane) return;
5104
+ const viewport = store.getViewport();
5105
+ const zoomChanged = zoomRef.current !== viewport.zoom;
5106
+ zoomRef.current = viewport.zoom;
5107
+ const { width, height } = pane.getBoundingClientRect();
5108
+ const live = new Map(store.presence.getPeers().map((peer) => [peer.id, peer.cursor]));
5109
+ for (const [id, el] of refs.current) {
5110
+ if (!el) continue;
5111
+ const cursor = live.get(id);
5112
+ if (!cursor) {
5113
+ el.style.display = "none";
5114
+ continue;
5115
+ }
5116
+ el.style.display = "";
5117
+ const point = worldToScreen(cursor, viewport);
5118
+ const x = Math.min(Math.max(point.x, 0), width);
5119
+ const y = Math.min(Math.max(point.y, 0), height);
5120
+ const offscreen = x !== point.x || y !== point.y;
5121
+ el.style.transform = `translate(${x}px, ${y}px)`;
5122
+ el.style.visibility = offscreen ? "hidden" : "";
5123
+ if (offscreen) el.setAttribute("data-rivet-offscreen", "");
5124
+ else el.removeAttribute("data-rivet-offscreen");
5125
+ if (zoomChanged || !zoomWritten.current.has(el)) {
5126
+ el.style.setProperty("--rivet-zoom", String(viewport.zoom));
5127
+ zoomWritten.current.add(el);
5128
+ }
5129
+ }
5130
+ }, [store, paneRef]);
5131
+ useEffect(() => {
5132
+ return store.subscribeCursorFrame(position);
5133
+ }, [store, position]);
5134
+ useLayoutEffect(position);
5135
+ return /* @__PURE__ */ jsx("div", { style: overlayStyle2, children: peers.map((peer) => /* @__PURE__ */ jsx(
5136
+ "div",
5137
+ {
5138
+ ref: (el) => {
5139
+ if (el) refs.current.set(peer.id, el);
5140
+ else refs.current.delete(peer.id);
5141
+ },
5142
+ style: { ...cursorStyle, display: "none" },
5143
+ "data-rivet-peer-cursor": peer.id,
5144
+ children: /* @__PURE__ */ jsx(Cursor, { peer })
5145
+ },
5146
+ peer.id
5147
+ )) });
5148
+ }
5149
+ var overlayStyle3 = {
5150
+ position: "absolute",
5151
+ inset: 0,
5152
+ // Claim chrome belongs to the canvas, so a box that has been panned off the
5153
+ // pane is clipped rather than left hanging over the app around it.
5154
+ overflow: "hidden",
5155
+ pointerEvents: "none"
5156
+ };
5157
+ var wrapperStyle2 = {
5158
+ position: "absolute",
5159
+ top: 0,
5160
+ left: 0,
5161
+ pointerEvents: "none"
5162
+ };
5163
+ function projectRect2(rect, viewport) {
5164
+ const origin = worldToScreen({ x: rect.x, y: rect.y }, viewport);
5165
+ return {
5166
+ x: origin.x,
5167
+ y: origin.y,
5168
+ width: rect.width * viewport.zoom,
5169
+ height: rect.height * viewport.zoom
5170
+ };
5171
+ }
5172
+ function PeerOutlineLayer({
5173
+ component: Outline
5174
+ }) {
5175
+ const { store } = useRivetContext();
5176
+ const [entries, setEntries] = useState([]);
5177
+ const refs = useRef(/* @__PURE__ */ new Map());
5178
+ const zoomRef = useRef(null);
5179
+ const zoomWritten = useRef(/* @__PURE__ */ new WeakSet());
5180
+ const rectOf = useCallback(
5181
+ (target, viewport) => {
5182
+ if (target.kind === "edge") {
5183
+ const point = store.getPeerEdgeAnchors().get(target.id);
5184
+ return point ? { x: point.x, y: point.y, width: 0, height: 0 } : null;
5185
+ }
5186
+ const nodeId = target.kind === "node" ? target.id : target.nodeId;
5187
+ if (!store.nodes.has(nodeId)) return null;
5188
+ const node = store.getNodeRect(nodeId);
5189
+ if (target.kind === "node") return projectRect2(node, viewport);
5190
+ const record = store.anchors.get(handleKey(nodeId, target.anchorId));
5191
+ if (!record?.geometry) return null;
5192
+ return projectRect2(anchorRect(node, record.geometry), viewport);
5193
+ },
5194
+ [store]
5195
+ );
5196
+ const position = useCallback(() => {
5197
+ const viewport = store.getViewport();
5198
+ const zoomChanged = zoomRef.current !== viewport.zoom;
5199
+ zoomRef.current = viewport.zoom;
5200
+ for (const entry of entries) {
5201
+ const el = refs.current.get(entry.key);
5202
+ if (!el) continue;
5203
+ const rect = rectOf(entry.target, viewport);
5204
+ if (!rect) {
5205
+ el.style.display = "none";
5206
+ continue;
5207
+ }
5208
+ el.style.display = "";
5209
+ el.style.transform = `translate(${rect.x}px, ${rect.y}px)`;
5210
+ el.style.width = `${rect.width}px`;
5211
+ el.style.height = `${rect.height}px`;
5212
+ if (zoomChanged || !zoomWritten.current.has(el)) {
5213
+ el.style.setProperty("--rivet-zoom", String(viewport.zoom));
5214
+ zoomWritten.current.add(el);
5215
+ }
5216
+ }
5217
+ }, [store, entries, rectOf]);
5218
+ useEffect(() => {
5219
+ return store.subscribeFrame(() => {
5220
+ const next = collectPeerOutlines(
5221
+ store.presence.getPeers(),
5222
+ store.presence.getPeerIdentities(),
5223
+ store.locks.getLocks()
5224
+ );
5225
+ setEntries((prev) => peerOutlinesEqual(prev, next) ? prev : next);
5226
+ position();
5227
+ });
5228
+ }, [store, position]);
5229
+ useLayoutEffect(position);
5230
+ return /* @__PURE__ */ jsx("div", { style: overlayStyle3, children: entries.map((entry) => /* @__PURE__ */ jsx(
5231
+ "div",
5232
+ {
5233
+ ref: (el) => {
5234
+ if (el) refs.current.set(entry.key, el);
5235
+ else refs.current.delete(entry.key);
5236
+ },
5237
+ style: { ...wrapperStyle2, display: "none" },
5238
+ "data-rivet-peer-outline": entry.key,
5239
+ "data-rivet-outline-kind": entry.target.kind,
5240
+ children: /* @__PURE__ */ jsx(Outline, { target: entry.target, claims: entry.claims, lock: entry.lock })
5241
+ },
5242
+ entry.key
5243
+ )) });
5244
+ }
4763
5245
  var containerStyle2 = {
4764
5246
  position: "absolute",
4765
5247
  inset: 0,
@@ -5189,6 +5671,7 @@ function Rivet({
5189
5671
  const backgroundCanvasRef = useRef(null);
5190
5672
  const edgeCanvasRef = useRef(null);
5191
5673
  const foregroundCanvasRef = useRef(null);
5674
+ const cursorCanvasRef = useRef(null);
5192
5675
  useEffect(() => {
5193
5676
  if (peers === void 0) return;
5194
5677
  store.presence.setPeers(peers);
@@ -5206,6 +5689,7 @@ function Rivet({
5206
5689
  store.setLockedNodes(lockedNodes);
5207
5690
  }, [store, lockedNodes]);
5208
5691
  useLocalPresence({ store, paneRef, onLocalPresence, throttleMs: cfg.presenceOptions.throttleMs });
5692
+ const outlineMode = !cfg.presenceOptions.renderOutlines ? "none" : cfg.presenceOptions.outlineComponent ? "dom" : "canvas";
5209
5693
  const { visibleIds, controls, getViewportElements } = useRivetRuntime({
5210
5694
  store,
5211
5695
  paneRef,
@@ -5213,11 +5697,12 @@ function Rivet({
5213
5697
  backgroundCanvasRef,
5214
5698
  edgeCanvasRef,
5215
5699
  foregroundCanvasRef,
5700
+ cursorCanvasRef,
5701
+ outlineMode,
5216
5702
  swimlaneLanes: swimlanes.lanes,
5217
5703
  edgeTypes: cfg.edgeTypes,
5218
5704
  defaultEdgeOptions: cfg.defaultEdgeOptions,
5219
5705
  anchorOptions: cfg.anchorOptions,
5220
- presenceOptions: cfg.presenceOptions,
5221
5706
  edgeRenderer: renderer,
5222
5707
  edgesReconnectable,
5223
5708
  isValidConnection: cfg.isValidConnection,
@@ -5306,6 +5791,8 @@ function Rivet({
5306
5791
  /* @__PURE__ */ jsx("canvas", { ref: edgeCanvasRef, style: canvasStyle }),
5307
5792
  /* @__PURE__ */ jsx(NodeLayer, { containerRef: nodeContainerRef, visibleIds }),
5308
5793
  /* @__PURE__ */ jsx("canvas", { ref: foregroundCanvasRef, style: canvasStyle }),
5794
+ outlineMode === "dom" && cfg.presenceOptions.outlineComponent && /* @__PURE__ */ jsx(PeerOutlineLayer, { component: cfg.presenceOptions.outlineComponent }),
5795
+ cfg.presenceOptions.renderCursors && (cfg.presenceOptions.cursorComponent ? /* @__PURE__ */ jsx(PeerCursorLayer, { component: cfg.presenceOptions.cursorComponent }) : /* @__PURE__ */ jsx("canvas", { ref: cursorCanvasRef, style: canvasStyle })),
5309
5796
  /* @__PURE__ */ jsx(EdgeLabelLayer, {}),
5310
5797
  /* @__PURE__ */ jsx(SwimlaneOverlay, {}),
5311
5798
  children,
@@ -5365,6 +5852,10 @@ function buildInstance(store, controls, getViewportElements) {
5365
5852
  ),
5366
5853
  getNodes,
5367
5854
  getNode: (id) => store.nodes.get(id),
5855
+ // The store's own `getNodeRect` answers for any id, falling back to the
5856
+ // origin and the default size; a public caller needs to tell "it's there,
5857
+ // at 0,0" from "it isn't there".
5858
+ getNodeRect: (id) => store.nodes.has(id) ? store.getNodeRect(id) : null,
5368
5859
  setNodes,
5369
5860
  addNodes: (nodes) => {
5370
5861
  for (const node of Array.isArray(nodes) ? nodes : [nodes]) store.addNode(node);
@@ -5399,8 +5890,12 @@ function buildInstance(store, controls, getViewportElements) {
5399
5890
  },
5400
5891
  unregisterAnchor: (nodeId, anchorId) => store.unregisterAnchor(nodeId, anchorId),
5401
5892
  remeasureAnchors: (nodeId) => store.remeasureAnchors(nodeId),
5893
+ getAnchor: (nodeId, anchorId) => store.getNodeAnchors(nodeId).find((record) => record.anchorId === anchorId) ?? null,
5894
+ setSelectedAnchors: (refs) => store.setSelectedAnchors(refs),
5895
+ getSelectedAnchors: () => store.getSelectedAnchors(),
5402
5896
  setPeerCursor: (peerId, point) => store.presence.setPeerCursor(peerId, point),
5403
5897
  setPeerNodeTransform: (peerId, nodeId, rect) => store.presence.setPeerNodeTransform(peerId, nodeId, rect),
5898
+ setPeerConnection: (peerId, connection) => store.presence.setPeerConnection(peerId, connection),
5404
5899
  removePeer: (peerId) => store.presence.removePeer(peerId),
5405
5900
  releaseNodeGesture: (id, ids) => store.endNodeGesture(id, ids),
5406
5901
  copy: (ids) => {
@@ -5442,6 +5937,6 @@ function useRivet() {
5442
5937
  // src/index.ts
5443
5938
  var VERSION = "0.0.0";
5444
5939
 
5445
- export { BUILTIN_EDGE_TYPES, BUILTIN_NODE_TYPES, Canvas2DEdgeRenderer, Controls, DEFAULT_ALIGNMENT_HYSTERESIS, DefaultNode, GroupNode, Handle, MiniMap, NodeResizer, Rivet, VERSION, alignRect, applyEdgeChanges, applyNodeChanges, boundingRect, canvas2DEdgeRenderer, clampChildToParent, cloneElements, createRivetStore, edgeEndpointHandle, facingSide, getBezierPath, getSmoothStepPath, getStepPath, getStraightPath, handleKey, parseEdgeEndpoint, parseSideHandleId, rectsIntersect, resolveFacingSide, resolveFacingSideAmong, screenToWorld, serializeGraph, snapToGrid, useRivet, viewportToCss, visibleWorldRect, worldToScreen, zoomAt };
5940
+ export { BUILTIN_EDGE_TYPES, BUILTIN_NODE_TYPES, Canvas2DEdgeRenderer, Controls, DEFAULT_ALIGNMENT_HYSTERESIS, DefaultNode, GroupNode, Handle, MiniMap, NodeResizer, Rivet, VERSION, alignRect, applyEdgeChanges, applyNodeChanges, canvas2DEdgeRenderer, cloneElements, createRivetStore, edgeEndpointHandle, facingSide, getBezierPath, getSmoothStepPath, getStepPath, getStraightPath, handleKey, parseEdgeEndpoint, parseSideHandleId, rectsIntersect, resolveFacingSide, resolveFacingSideAmong, screenToWorld, useRivet, viewportToCss, visibleWorldRect, worldToScreen, zoomAt };
5446
5941
  //# sourceMappingURL=index.js.map
5447
5942
  //# sourceMappingURL=index.js.map