@pixerate/editor 0.17.0 → 0.18.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.
@@ -148,9 +148,19 @@ function createBezierTrajectory(getControlPoint) {
148
148
  };
149
149
  };
150
150
  }
151
+ function withStyleOpacity(style, opacity, emptyFormat = "string") {
152
+ if (style !== null && typeof style === "object") {
153
+ return { ...style, opacity };
154
+ }
155
+ if (typeof style === "string" && style.trim() !== "") {
156
+ const rest = style.replace(/(^|;)\s*opacity\s*:\s*[^;]*;?/gi, "$1").trim().replace(/;+\s*$/, "").trim();
157
+ return `${rest ? rest + "; " : ""}opacity: ${opacity};`;
158
+ }
159
+ return emptyFormat === "object" ? { opacity } : `opacity: ${opacity};`;
160
+ }
151
161
  function runMultiNodeTransition(transitions, options = {}) {
152
162
  if (transitions.length === 0) {
153
- _optionalChain([options, 'access', _ => _.onComplete, 'optionalCall', _2 => _2()]);
163
+ _optionalChain([options, 'access', _ => _.onComplete, 'optionalCall', _2 => _2([])]);
154
164
  return () => {
155
165
  };
156
166
  }
@@ -158,6 +168,7 @@ function runMultiNodeTransition(transitions, options = {}) {
158
168
  defaultDuration = 400,
159
169
  defaultEasing: easingFunc = defaultEasing,
160
170
  clock = realClock,
171
+ styleFormat = "string",
161
172
  onUpdate,
162
173
  onComplete
163
174
  } = options;
@@ -172,6 +183,7 @@ function runMultiNodeTransition(transitions, options = {}) {
172
183
  delay: _nullishCoalesce(tr.delay, () => ( 0)),
173
184
  trajectory: _nullishCoalesce(tr.trajectory, () => ( linearTrajectory))
174
185
  }));
186
+ const current = items.map((item) => item.node);
175
187
  function frame() {
176
188
  if (isCancelled) return;
177
189
  const now = clock.now();
@@ -186,29 +198,28 @@ function runMultiNodeTransition(transitions, options = {}) {
186
198
  const rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);
187
199
  const progress = item.duration <= 0 ? 1 : item.easing(rawProgress);
188
200
  const point = item.trajectory(item.from, item.to, progress, item.index, item.total);
189
- item.node.position = { ...point.position };
201
+ const next = { ...item.node, position: { ...point.position } };
190
202
  if (point.scale !== void 0 || point.opacity !== void 0) {
191
- if (!item.node.data) {
192
- item.node.data = {};
193
- }
203
+ const data = { ..._nullishCoalesce(item.node.data, () => ( {})) };
194
204
  if (point.scale !== void 0) {
195
- item.node.data.scale = point.scale;
205
+ data.scale = point.scale;
196
206
  }
197
207
  if (point.opacity !== void 0) {
198
- item.node.data.opacity = point.opacity;
199
- const currentStyle = (item.node.style || "").replace(/opacity:\s*[^;]+;?/g, "").trim();
200
- item.node.style = `${currentStyle ? currentStyle + "; " : ""}opacity: ${point.opacity};`;
208
+ data.opacity = point.opacity;
209
+ next.style = withStyleOpacity(item.node.style, point.opacity, styleFormat);
201
210
  }
211
+ next.data = data;
202
212
  }
213
+ current[i] = next;
203
214
  if (rawProgress < 1) {
204
215
  allCompleted = false;
205
216
  }
206
217
  }
207
- _optionalChain([onUpdate, 'optionalCall', _3 => _3()]);
218
+ _optionalChain([onUpdate, 'optionalCall', _3 => _3(current.slice())]);
208
219
  if (!allCompleted) {
209
220
  clock.frame().then(frame);
210
221
  } else {
211
- _optionalChain([onComplete, 'optionalCall', _4 => _4()]);
222
+ _optionalChain([onComplete, 'optionalCall', _4 => _4(current.slice())]);
212
223
  }
213
224
  }
214
225
  frame();
@@ -417,7 +428,16 @@ function isEventFromTextInput(event) {
417
428
  return false;
418
429
  }
419
430
  function isValidCanvasNode(obj) {
420
- return typeof obj === "object" && obj !== null && "id" in obj && typeof obj.id === "string" && "position" in obj && typeof obj.position === "object" && obj.position !== null && "x" in obj.position && "y" in obj.position;
431
+ if (typeof obj !== "object" || obj === null) return false;
432
+ const candidate = obj;
433
+ if (typeof candidate.id !== "string") return false;
434
+ const position = candidate.position;
435
+ return typeof position === "object" && position !== null && typeof position.x === "number" && Number.isFinite(position.x) && typeof position.y === "number" && Number.isFinite(position.y);
436
+ }
437
+ function isValidCanvasEdge(obj) {
438
+ if (typeof obj !== "object" || obj === null) return false;
439
+ const candidate = obj;
440
+ return typeof candidate.id === "string" && typeof candidate.source === "string" && typeof candidate.target === "string";
421
441
  }
422
442
  function isValidUrl(url) {
423
443
  try {
@@ -439,28 +459,46 @@ function parseCanvasClipboardData(raw) {
439
459
  try {
440
460
  const parsed = JSON.parse(raw);
441
461
  if (typeof parsed === "object" && parsed !== null && Array.isArray(parsed.nodes) && parsed.nodes.length > 0 && parsed.nodes.every(isValidCanvasNode)) {
442
- return parsed;
462
+ const edges = Array.isArray(parsed.edges) ? parsed.edges.filter(isValidCanvasEdge) : void 0;
463
+ return { ...parsed, edges };
443
464
  }
444
465
  } catch (e3) {
445
466
  }
446
467
  return null;
447
468
  }
448
469
  function remapPastedNodes(nodes, edges = [], offset = { x: 30, y: 30 }, idGenerator = () => `node-${Math.random().toString(36).substring(2, 9)}`) {
470
+ const validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode);
471
+ const validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge);
472
+ const dx = Number.isFinite(_optionalChain([offset, 'optionalAccess', _29 => _29.x])) ? offset.x : 0;
473
+ const dy = Number.isFinite(_optionalChain([offset, 'optionalAccess', _30 => _30.y])) ? offset.y : 0;
449
474
  const idMap = /* @__PURE__ */ new Map();
450
- const newNodes = nodes.map((node) => {
451
- const newId = idGenerator();
452
- idMap.set(node.id, newId);
453
- return {
475
+ for (const node of validNodes) {
476
+ idMap.set(node.id, idGenerator());
477
+ }
478
+ const newNodes = validNodes.map((node) => {
479
+ const next = {
454
480
  ...node,
455
- id: newId,
481
+ id: idMap.get(node.id),
456
482
  position: {
457
- x: node.position.x + offset.x,
458
- y: node.position.y + offset.y
483
+ x: node.position.x + dx,
484
+ y: node.position.y + dy
459
485
  },
460
486
  selected: true
461
487
  };
488
+ let parentRemapped = false;
489
+ for (const key of ["parentId", "parentNode"]) {
490
+ const parent = node[key];
491
+ if (typeof parent === "string" && idMap.has(parent)) {
492
+ next[key] = idMap.get(parent);
493
+ parentRemapped = true;
494
+ }
495
+ }
496
+ if (parentRemapped) {
497
+ next.position = { x: node.position.x, y: node.position.y };
498
+ }
499
+ return next;
462
500
  });
463
- const newEdges = edges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((edge) => ({
501
+ const newEdges = validEdges.filter((e) => idMap.has(e.source) && idMap.has(e.target)).map((edge) => ({
464
502
  ...edge,
465
503
  id: `edge-${Math.random().toString(36).substring(2, 9)}`,
466
504
  source: idMap.get(edge.source),
@@ -509,7 +547,7 @@ function layoutBounds(nodes) {
509
547
  return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
510
548
  }
511
549
  function canvasHeight(nodes, options) {
512
- return clampHeight(_nullishCoalesce(_optionalChain([layoutBounds, 'call', _29 => _29(nodes), 'optionalAccess', _30 => _30.height]), () => ( 0)), options);
550
+ return clampHeight(_nullishCoalesce(_optionalChain([layoutBounds, 'call', _31 => _31(nodes), 'optionalAccess', _32 => _32.height]), () => ( 0)), options);
513
551
  }
514
552
  var DEFAULT_RANK_HEIGHT = 80 + 72;
515
553
  var NODES_PER_RANK = 2;
@@ -557,9 +595,11 @@ var StreamingLayoutManager = (_class2 = class {
557
595
 
558
596
 
559
597
  __init5() {this.pendingTimer = null}
560
- __init6() {this.activeTransitionCancel = null}
561
- __init7() {this.targetPositions = /* @__PURE__ */ new Map()}
562
- constructor(options = {}) {;_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);
598
+ __init6() {this.pendingArgs = null}
599
+ __init7() {this.activeTransitionCancel = null}
600
+ __init8() {this.targetPositions = /* @__PURE__ */ new Map()}
601
+ __init9() {this.livePositions = /* @__PURE__ */ new Map()}
602
+ constructor(options = {}) {;_class2.prototype.__init5.call(this);_class2.prototype.__init6.call(this);_class2.prototype.__init7.call(this);_class2.prototype.__init8.call(this);_class2.prototype.__init9.call(this);
563
603
  this.throttleMs = _nullishCoalesce(options.throttleMs, () => ( 200));
564
604
  this.glideDurationMs = _nullishCoalesce(options.glideDurationMs, () => ( 300));
565
605
  this.connectedOnly = _nullishCoalesce(options.connectedOnly, () => ( false));
@@ -569,52 +609,70 @@ var StreamingLayoutManager = (_class2 = class {
569
609
  }
570
610
  /**
571
611
  * Feed incoming nodes and edges into the streaming manager.
572
- * Coalesces rapid streaming bursts and triggers smooth layout gliding.
612
+ * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.
573
613
  */
574
614
  pushStreamUpdate(nodes, edges, currentPositions) {
575
- if (this.pendingTimer) {
576
- clearTimeout(this.pendingTimer);
615
+ if (this.pendingTimer !== null) {
616
+ this.pendingArgs = [nodes, edges, currentPositions];
617
+ return;
577
618
  }
619
+ this.recalculateAndGlide(nodes, edges, currentPositions);
620
+ this.startWindow();
621
+ }
622
+ startWindow() {
578
623
  this.pendingTimer = setTimeout(() => {
579
624
  this.pendingTimer = null;
580
- this.recalculateAndGlide(nodes, edges, currentPositions);
581
- }, this.throttleMs);
625
+ const args = this.pendingArgs;
626
+ this.pendingArgs = null;
627
+ if (args) {
628
+ this.recalculateAndGlide(...args);
629
+ this.startWindow();
630
+ }
631
+ }, Math.max(0, this.throttleMs));
582
632
  }
583
633
  /**
584
634
  * Forces immediate calculation and cancels pending throttled timers.
585
635
  */
586
636
  flush(nodes, edges, currentPositions) {
587
- if (this.pendingTimer) {
637
+ this.clearPending();
638
+ this.recalculateAndGlide(nodes, edges, currentPositions);
639
+ }
640
+ /**
641
+ * Latest computed layout targets (the positions nodes are gliding towards).
642
+ */
643
+ getTargetPositions() {
644
+ return new Map(this.targetPositions);
645
+ }
646
+ clearPending() {
647
+ if (this.pendingTimer !== null) {
588
648
  clearTimeout(this.pendingTimer);
589
649
  this.pendingTimer = null;
590
650
  }
591
- this.recalculateAndGlide(nodes, edges, currentPositions);
651
+ this.pendingArgs = null;
592
652
  }
593
653
  recalculateAndGlide(nodes, edges, currentPositions) {
594
654
  const targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;
595
655
  if (targetNodes.length === 0) return;
656
+ const startPositions = new Map(currentPositions);
596
657
  if (this.activeTransitionCancel) {
658
+ for (const [id, pos] of this.livePositions) {
659
+ startPositions.set(id, pos);
660
+ }
597
661
  this.activeTransitionCancel();
598
662
  this.activeTransitionCancel = null;
599
663
  }
600
- const stored = {};
601
- for (const [id, pos] of currentPositions.entries()) {
602
- stored[id] = pos;
603
- }
604
- const layouted = _chunkWADCJ7A7cjs.ensureLayout.call(void 0, targetNodes, edges, {
605
- ...this.layoutOptions,
606
- storedPositions: stored
607
- });
664
+ const layouted = _chunkWADCJ7A7cjs.ensureLayout.call(void 0, targetNodes, edges, this.layoutOptions);
608
665
  const nextPositions = /* @__PURE__ */ new Map();
609
666
  const transitions = [];
610
667
  for (const node of layouted) {
611
- const target = node.position;
668
+ const target = { ...node.position };
612
669
  nextPositions.set(node.id, target);
613
- const current = _nullishCoalesce(currentPositions.get(node.id), () => ( target));
670
+ const current = startPositions.get(node.id);
671
+ if (!current) continue;
614
672
  const distance = Math.hypot(target.x - current.x, target.y - current.y);
615
673
  if (distance > 2) {
616
674
  transitions.push({
617
- node,
675
+ node: { ...node, position: { ...current } },
618
676
  from: current,
619
677
  to: target,
620
678
  trajectory: linearPositionTrajectory,
@@ -623,30 +681,38 @@ var StreamingLayoutManager = (_class2 = class {
623
681
  }
624
682
  }
625
683
  this.targetPositions = nextPositions;
626
- if (transitions.length > 0) {
627
- this.activeTransitionCancel = runMultiNodeTransition(transitions, {
628
- clock: this.clock,
629
- onUpdate: () => {
630
- const liveMap = new Map(currentPositions);
631
- for (const trans of transitions) {
632
- liveMap.set(trans.node.id, { ...trans.node.position });
633
- }
634
- _optionalChain([this, 'access', _31 => _31.onLayoutUpdated, 'optionalCall', _32 => _32(liveMap)]);
635
- },
636
- onComplete: () => {
637
- this.activeTransitionCancel = null;
638
- _optionalChain([this, 'access', _33 => _33.onLayoutUpdated, 'optionalCall', _34 => _34(nextPositions)]);
639
- }
640
- });
641
- } else {
642
- _optionalChain([this, 'access', _35 => _35.onLayoutUpdated, 'optionalCall', _36 => _36(nextPositions)]);
684
+ if (transitions.length === 0) {
685
+ this.livePositions = new Map(nextPositions);
686
+ _optionalChain([this, 'access', _33 => _33.onLayoutUpdated, 'optionalCall', _34 => _34(nextPositions)]);
687
+ return;
643
688
  }
689
+ const live = new Map(nextPositions);
690
+ for (const trans of transitions) {
691
+ live.set(trans.node.id, { ...trans.from });
692
+ }
693
+ this.livePositions = live;
694
+ let finished = false;
695
+ const cancel = runMultiNodeTransition(transitions, {
696
+ clock: this.clock,
697
+ onUpdate: (updated) => {
698
+ const frame = new Map(this.livePositions);
699
+ for (const node of updated) {
700
+ frame.set(node.id, { ...node.position });
701
+ }
702
+ this.livePositions = frame;
703
+ _optionalChain([this, 'access', _35 => _35.onLayoutUpdated, 'optionalCall', _36 => _36(frame)]);
704
+ },
705
+ onComplete: () => {
706
+ finished = true;
707
+ this.activeTransitionCancel = null;
708
+ this.livePositions = new Map(nextPositions);
709
+ _optionalChain([this, 'access', _37 => _37.onLayoutUpdated, 'optionalCall', _38 => _38(nextPositions)]);
710
+ }
711
+ });
712
+ this.activeTransitionCancel = finished ? null : cancel;
644
713
  }
645
714
  destroy() {
646
- if (this.pendingTimer) {
647
- clearTimeout(this.pendingTimer);
648
- this.pendingTimer = null;
649
- }
715
+ this.clearPending();
650
716
  if (this.activeTransitionCancel) {
651
717
  this.activeTransitionCancel();
652
718
  this.activeTransitionCancel = null;
@@ -692,5 +758,7 @@ var StreamingLayoutManager = (_class2 = class {
692
758
 
693
759
 
694
760
 
695
- exports.DEFAULT_NODE_HEIGHT = _chunkWADCJ7A7cjs.DEFAULT_NODE_HEIGHT; exports.DEFAULT_NODE_WIDTH = _chunkWADCJ7A7cjs.DEFAULT_NODE_WIDTH; exports.DEFAULT_PLACEMENT_GAP = _chunkWADCJ7A7cjs.DEFAULT_PLACEMENT_GAP; exports.ManualClock = ManualClock; exports.StreamingLayoutManager = StreamingLayoutManager; exports.blendLayout = blendLayout; exports.boundsOf = _chunkWADCJ7A7cjs.boundsOf; exports.calculateDagreReflowDisplacement = calculateDagreReflowDisplacement; exports.calculateDirectionalDisplacement = calculateDirectionalDisplacement; exports.canvasHeight = canvasHeight; exports.centerNodes = _chunkWADCJ7A7cjs.centerNodes; exports.centre = _chunkWADCJ7A7cjs.centre; exports.computeNodesBoundingBox = computeNodesBoundingBox; exports.connectedOnly = connectedOnly; exports.createBezierTrajectory = createBezierTrajectory; exports.createFanOutTrajectory = createFanOutTrajectory; exports.defaultEasing = defaultEasing; exports.ensureLayout = _chunkWADCJ7A7cjs.ensureLayout; exports.estimateCanvasHeight = estimateCanvasHeight; exports.estimateNodeSize = _chunkWADCJ7A7cjs.estimateNodeSize; exports.flowSide = _chunkWADCJ7A7cjs.flowSide; exports.getCenteredNodePosition = _chunkWADCJ7A7cjs.getCenteredNodePosition; exports.getLayoutedNodes = _chunkWADCJ7A7cjs.getLayoutedNodes; exports.isEventFromTextInput = isEventFromTextInput; exports.isValidCanvasNode = isValidCanvasNode; exports.isValidUrl = isValidUrl; exports.layoutBounds = layoutBounds; exports.linearPositionTrajectory = linearPositionTrajectory; exports.linearTrajectory = linearTrajectory; exports.overlaps = _chunkWADCJ7A7cjs.overlaps; exports.pageHidden = pageHidden; exports.parseCanvasClipboardData = parseCanvasClipboardData; exports.placeBeside = _chunkWADCJ7A7cjs.placeBeside; exports.realClock = realClock; exports.remapPastedNodes = remapPastedNodes; exports.repositionNodesTo = repositionNodesTo; exports.runMultiNodeTransition = runMultiNodeTransition; exports.serializeCanvasNodes = serializeCanvasNodes;
761
+
762
+
763
+ exports.DEFAULT_NODE_HEIGHT = _chunkWADCJ7A7cjs.DEFAULT_NODE_HEIGHT; exports.DEFAULT_NODE_WIDTH = _chunkWADCJ7A7cjs.DEFAULT_NODE_WIDTH; exports.DEFAULT_PLACEMENT_GAP = _chunkWADCJ7A7cjs.DEFAULT_PLACEMENT_GAP; exports.ManualClock = ManualClock; exports.StreamingLayoutManager = StreamingLayoutManager; exports.blendLayout = blendLayout; exports.boundsOf = _chunkWADCJ7A7cjs.boundsOf; exports.calculateDagreReflowDisplacement = calculateDagreReflowDisplacement; exports.calculateDirectionalDisplacement = calculateDirectionalDisplacement; exports.canvasHeight = canvasHeight; exports.centerNodes = _chunkWADCJ7A7cjs.centerNodes; exports.centre = _chunkWADCJ7A7cjs.centre; exports.computeNodesBoundingBox = computeNodesBoundingBox; exports.connectedOnly = connectedOnly; exports.createBezierTrajectory = createBezierTrajectory; exports.createFanOutTrajectory = createFanOutTrajectory; exports.defaultEasing = defaultEasing; exports.ensureLayout = _chunkWADCJ7A7cjs.ensureLayout; exports.estimateCanvasHeight = estimateCanvasHeight; exports.estimateNodeSize = _chunkWADCJ7A7cjs.estimateNodeSize; exports.flowSide = _chunkWADCJ7A7cjs.flowSide; exports.getCenteredNodePosition = _chunkWADCJ7A7cjs.getCenteredNodePosition; exports.getLayoutedNodes = _chunkWADCJ7A7cjs.getLayoutedNodes; exports.isEventFromTextInput = isEventFromTextInput; exports.isValidCanvasEdge = isValidCanvasEdge; exports.isValidCanvasNode = isValidCanvasNode; exports.isValidUrl = isValidUrl; exports.layoutBounds = layoutBounds; exports.linearPositionTrajectory = linearPositionTrajectory; exports.linearTrajectory = linearTrajectory; exports.overlaps = _chunkWADCJ7A7cjs.overlaps; exports.pageHidden = pageHidden; exports.parseCanvasClipboardData = parseCanvasClipboardData; exports.placeBeside = _chunkWADCJ7A7cjs.placeBeside; exports.realClock = realClock; exports.remapPastedNodes = remapPastedNodes; exports.repositionNodesTo = repositionNodesTo; exports.runMultiNodeTransition = runMultiNodeTransition; exports.serializeCanvasNodes = serializeCanvasNodes; exports.withStyleOpacity = withStyleOpacity;
696
764
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts"],"names":[],"mappings":"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AAGP,IAAA;AACH,MAAA;AACA,MAAA;AACN,IAAA;AAG4C,IAAA;AACpB,IAAA;AACV,MAAA;AACd,IAAA;AAEiB,IAAA;AACR,MAAA;AACS,MAAA;AACjB,IAAA;AAEqB,IAAA;AACwB,IAAA;AAE3B,IAAA;AACE,MAAA;AACG,MAAA;AAEP,MAAA;AACM,MAAA;AAEJ,MAAA;AACA,QAAA;AAChB,UAAA;AACM,UAAA;AACF,UAAA;AACQ,UAAA;AACG,UAAA;AACf,QAAA;AACF,MAAA;AACD,IAAA;AAEuB,IAAA;AAEE,IAAA;AACnB,MAAA;AACQ,QAAA;AACI,QAAA;AAEC,UAAA;AACL,UAAA;AACE,YAAA;AACb,UAAA;AACK,0BAAA;AACN,QAAA;AACkB,QAAA;AACZ,UAAA;AACA,0BAAA;AACN,QAAA;AACA,MAAA;AACK,IAAA;AACiB,sBAAA;AACxB,IAAA;AACD,EAAA;AAEuB,EAAA;AACC,IAAA;AACJ,MAAA;AACE,MAAA;AACrB,IAAA;AACS,IAAA;AACH,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACD;ANmf6B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"/**\n * The time source that canvas animations and agent performances play on.\n *\n * Defaults to `realClock` (requestAnimationFrame and setTimeout).\n * A `ManualClock` moves only when told to via `advance(ms)` or `tick(fps)`,\n * enabling deterministic frame-by-frame stepping for testing, video generation,\n * or headless server environments.\n */\n\nexport interface Clock {\n\t/** Current time in milliseconds. */\n\tnow(): number;\n\t/** Resolves on the next animation frame. */\n\tframe(): Promise<void>;\n\t/** Resolves after `ms` milliseconds of this clock's time. */\n\tsleep(ms: number): Promise<void>;\n\t/**\n\t * Whether the clock follows wall time. Live clocks stop or degrade when the\n\t * tab is in the background (browsers pause animation frames), so transitions\n\t * can commit immediately rather than leaving operations hanging.\n\t */\n\treadonly live: boolean;\n}\n\n/** Whether the browser tab is hidden (where RAF pauses). */\nexport const pageHidden = (): boolean =>\n\ttypeof document !== 'undefined' && document.hidden;\n\n/** Real-time clock using requestAnimationFrame and setTimeout. */\nexport const realClock: Clock = {\n\tlive: true,\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\tif (typeof requestAnimationFrame === 'function' && !pageHidden()) {\n\t\t\t\trequestAnimationFrame(() => resolve());\n\t\t\t} else {\n\t\t\t\tsetTimeout(resolve, 16);\n\t\t\t}\n\t\t}),\n\tsleep: (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, ms)))\n};\n\nconst settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * A deterministic clock that only moves forward when `advance` or `tick` is called.\n * Allows step-by-step frame testing and video rendering without waiting for real time.\n */\nexport class ManualClock implements Clock {\n\treadonly live = false;\n\tprivate time: number;\n\tprivate frames: (() => void)[] = [];\n\tprivate sleepers: { at: number; seq: number; resolve: () => void }[] = [];\n\tprivate seq = 0;\n\n\tconstructor(start = 0) {\n\t\tthis.time = start;\n\t}\n\n\tpublic now(): number {\n\t\treturn this.time;\n\t}\n\n\tpublic frame(): Promise<void> {\n\t\treturn new Promise<void>((resolve) => this.frames.push(resolve));\n\t}\n\n\tpublic sleep(ms: number): Promise<void> {\n\t\treturn new Promise<void>((resolve) =>\n\t\t\tthis.sleepers.push({\n\t\t\t\tat: this.time + Math.max(0, ms),\n\t\t\t\tseq: this.seq++,\n\t\t\t\tresolve\n\t\t\t})\n\t\t);\n\t}\n\n\t/** Whether any frames or sleep timers are currently pending. */\n\tpublic get pending(): boolean {\n\t\treturn this.frames.length > 0 || this.sleepers.length > 0;\n\t}\n\n\t/** Advances time by `ms`, resolving due sleep timers in order, followed by queued frames. */\n\tpublic async advance(ms: number): Promise<void> {\n\t\tconst end = this.time + Math.max(0, ms);\n\t\tawait settle();\n\n\t\twhile (true) {\n\t\t\tlet next: (typeof this.sleepers)[number] | undefined;\n\t\t\tfor (const s of this.sleepers) {\n\t\t\t\tif (s.at <= end && (!next || s.at < next.at || (s.at === next.at && s.seq < next.seq))) {\n\t\t\t\t\tnext = s;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!next) break;\n\n\t\t\tthis.sleepers.splice(this.sleepers.indexOf(next), 1);\n\t\t\tthis.time = Math.max(this.time, next.at);\n\t\t\tnext.resolve();\n\t\t\tawait settle();\n\t\t}\n\n\t\tthis.time = end;\n\t\tconst dueFrames = this.frames.splice(0, this.frames.length);\n\t\tfor (const resolveFrame of dueFrames) {\n\t\t\tresolveFrame();\n\t\t}\n\t\tawait settle();\n\t}\n\n\t/** Convenience method to advance by 1 frame at a given fps (default: 60fps = 16.67ms). */\n\tpublic async tick(fps = 60): Promise<void> {\n\t\tawait this.advance(Math.round(1000 / fps));\n\t}\n}\n","import type {\n\tCanvasNode,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Coordinates and animates multiple node transitions simultaneously with zero per-frame array allocations.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: {\n\t\tdefaultDuration?: number;\n\t\tdefaultEasing?: (t: number) => number;\n\t\tclock?: Clock;\n\t\tonUpdate?: () => void;\n\t\tonComplete?: () => void;\n\t} = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.();\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\titem.node.position = { ...point.position };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tif (!item.node.data) {\n\t\t\t\t\titem.node.data = {} as any;\n\t\t\t\t}\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\t(item.node.data as any).scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\t(item.node.data as any).opacity = point.opacity;\n\t\t\t\t\tconst currentStyle = (item.node.style || '').replace(/opacity:\\s*[^;]+;?/g, '').trim();\n\t\t\t\t\titem.node.style = `${currentStyle ? currentStyle + '; ' : ''}opacity: ${point.opacity};`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.();\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.();\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\treturn (\n\t\ttypeof obj === 'object' &&\n\t\tobj !== null &&\n\t\t'id' in obj &&\n\t\ttypeof (obj as any).id === 'string' &&\n\t\t'position' in obj &&\n\t\ttypeof (obj as any).position === 'object' &&\n\t\t(obj as any).position !== null &&\n\t\t'x' in (obj as any).position &&\n\t\t'y' in (obj as any).position\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\treturn parsed as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst idMap = new Map<string, string>();\n\n\tconst newNodes = nodes.map((node) => {\n\t\tconst newId = idGenerator();\n\t\tidMap.set(node.id, newId);\n\t\treturn {\n\t\t\t...node,\n\t\t\tid: newId,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + offset.x,\n\t\t\t\ty: node.position.y + offset.y\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\t});\n\n\tconst newEdges = edges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: any = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Coalesces rapid streaming bursts and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t}\n\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\t}, this.throttleMs);\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Cancel any currently running RAF glide\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Incremental layout preserves existing nodes and aligns new nodes\n\t\tconst stored: Record<string, XYPosition> = {};\n\t\tfor (const [id, pos] of currentPositions.entries()) {\n\t\t\tstored[id] = pos;\n\t\t}\n\n\t\tconst layouted = ensureLayout(targetNodes, edges, {\n\t\t\t...this.layoutOptions,\n\t\t\tstoredPositions: stored\n\t\t});\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = node.position;\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = currentPositions.get(node.id) ?? target;\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode,\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length > 0) {\n\t\t\tthis.activeTransitionCancel = runMultiNodeTransition(transitions, {\n\t\t\t\tclock: this.clock,\n\t\t\t\tonUpdate: () => {\n\t\t\t\t\t// Broadcast updated positions during RAF\n\t\t\t\t\tconst liveMap = new Map<string, XYPosition>(currentPositions);\n\t\t\t\t\tfor (const trans of transitions) {\n\t\t\t\t\t\tliveMap.set(trans.node.id, { ...trans.node.position });\n\t\t\t\t\t}\n\t\t\t\t\tthis.onLayoutUpdated?.(liveMap);\n\t\t\t\t},\n\t\t\t\tonComplete: () => {\n\t\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t\t}\n\t\t\t});\n\t\t} else {\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t}\n\t}\n\n\tpublic destroy(): void {\n\t\tif (this.pendingTimer) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts"],"names":[],"mappings":"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A;AAMC,EAAA;AAIK,IAAA;AACA,IAAA;AACN,EAAA;AAAA;AAAA;AAAA;AAKO,EAAA;AACC,IAAA;AACR,EAAA;AAEQ,EAAA;AACH,IAAA;AACH,MAAA;AACK,MAAA;AACN,IAAA;AACK,IAAA;AACN,EAAA;AAEQ,EAAA;AAKD,IAAA;AACF,IAAA;AAIE,IAAA;AACF,IAAA;AACH,MAAA;AACC,QAAA;AACD,MAAA;AACK,MAAA;AACA,MAAA;AACN,IAAA;AAIM,IAAA;AAEA,IAAA;AACA,IAAA;AAEN,IAAA;AACO,MAAA;AACN,MAAA;AAEM,MAAA;AACD,MAAA;AAEC,MAAA;AACF,MAAA;AACH,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,QAAA;AACF,MAAA;AACD,IAAA;AAEK,IAAA;AAED,IAAA;AACE,MAAA;AACA,sBAAA;AACL,MAAA;AACD,IAAA;AAEM,IAAA;AACN,IAAA;AACM,MAAA;AACN,IAAA;AACK,IAAA;AAED,IAAA;AACE,IAAA;AACL,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACC,UAAA;AACD,QAAA;AACA,QAAA;AACA,wBAAA;AACD,MAAA;AACA,MAAA;AACC,QAAA;AACA,QAAA;AACA,QAAA;AACA,wBAAA;AACD,MAAA;AACA,IAAA;AAEI,IAAA;AACN,EAAA;AAEuB,EAAA;AACjB,IAAA;AACD,IAAA;AACE,MAAA;AACA,MAAA;AACN,IAAA;AACD,EAAA;AACD;ANugBY;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/editor/editor/packages/core/dist/canvas/index.cjs","sourcesContent":[null,"/**\n * The time source that canvas animations and agent performances play on.\n *\n * Defaults to `realClock` (requestAnimationFrame and setTimeout).\n * A `ManualClock` moves only when told to via `advance(ms)` or `tick(fps)`,\n * enabling deterministic frame-by-frame stepping for testing, video generation,\n * or headless server environments.\n */\n\nexport interface Clock {\n\t/** Current time in milliseconds. */\n\tnow(): number;\n\t/** Resolves on the next animation frame. */\n\tframe(): Promise<void>;\n\t/** Resolves after `ms` milliseconds of this clock's time. */\n\tsleep(ms: number): Promise<void>;\n\t/**\n\t * Whether the clock follows wall time. Live clocks stop or degrade when the\n\t * tab is in the background (browsers pause animation frames), so transitions\n\t * can commit immediately rather than leaving operations hanging.\n\t */\n\treadonly live: boolean;\n}\n\n/** Whether the browser tab is hidden (where RAF pauses). */\nexport const pageHidden = (): boolean =>\n\ttypeof document !== 'undefined' && document.hidden;\n\n/** Real-time clock using requestAnimationFrame and setTimeout. */\nexport const realClock: Clock = {\n\tlive: true,\n\tnow: () => (typeof performance !== 'undefined' ? performance.now() : Date.now()),\n\tframe: () =>\n\t\tnew Promise<void>((resolve) => {\n\t\t\tif (typeof requestAnimationFrame === 'function' && !pageHidden()) {\n\t\t\t\trequestAnimationFrame(() => resolve());\n\t\t\t} else {\n\t\t\t\tsetTimeout(resolve, 16);\n\t\t\t}\n\t\t}),\n\tsleep: (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, ms)))\n};\n\nconst settle = () => new Promise<void>((resolve) => setTimeout(resolve, 0));\n\n/**\n * A deterministic clock that only moves forward when `advance` or `tick` is called.\n * Allows step-by-step frame testing and video rendering without waiting for real time.\n */\nexport class ManualClock implements Clock {\n\treadonly live = false;\n\tprivate time: number;\n\tprivate frames: (() => void)[] = [];\n\tprivate sleepers: { at: number; seq: number; resolve: () => void }[] = [];\n\tprivate seq = 0;\n\n\tconstructor(start = 0) {\n\t\tthis.time = start;\n\t}\n\n\tpublic now(): number {\n\t\treturn this.time;\n\t}\n\n\tpublic frame(): Promise<void> {\n\t\treturn new Promise<void>((resolve) => this.frames.push(resolve));\n\t}\n\n\tpublic sleep(ms: number): Promise<void> {\n\t\treturn new Promise<void>((resolve) =>\n\t\t\tthis.sleepers.push({\n\t\t\t\tat: this.time + Math.max(0, ms),\n\t\t\t\tseq: this.seq++,\n\t\t\t\tresolve\n\t\t\t})\n\t\t);\n\t}\n\n\t/** Whether any frames or sleep timers are currently pending. */\n\tpublic get pending(): boolean {\n\t\treturn this.frames.length > 0 || this.sleepers.length > 0;\n\t}\n\n\t/** Advances time by `ms`, resolving due sleep timers in order, followed by queued frames. */\n\tpublic async advance(ms: number): Promise<void> {\n\t\tconst end = this.time + Math.max(0, ms);\n\t\tawait settle();\n\n\t\twhile (true) {\n\t\t\tlet next: (typeof this.sleepers)[number] | undefined;\n\t\t\tfor (const s of this.sleepers) {\n\t\t\t\tif (s.at <= end && (!next || s.at < next.at || (s.at === next.at && s.seq < next.seq))) {\n\t\t\t\t\tnext = s;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!next) break;\n\n\t\t\tthis.sleepers.splice(this.sleepers.indexOf(next), 1);\n\t\t\tthis.time = Math.max(this.time, next.at);\n\t\t\tnext.resolve();\n\t\t\tawait settle();\n\t\t}\n\n\t\tthis.time = end;\n\t\tconst dueFrames = this.frames.splice(0, this.frames.length);\n\t\tfor (const resolveFrame of dueFrames) {\n\t\t\tresolveFrame();\n\t\t}\n\t\tawait settle();\n\t}\n\n\t/** Convenience method to advance by 1 frame at a given fps (default: 60fps = 16.67ms). */\n\tpublic async tick(fps = 60): Promise<void> {\n\t\tawait this.advance(Math.round(1000 / fps));\n\t}\n}\n","import type {\n\tCanvasNode,\n\tCanvasNodeStyle,\n\tFanOutTrajectoryOptions,\n\tNodeTransition,\n\tTrajectoryFunction,\n\tTrajectoryPoint,\n\tXYPosition\n} from './types.js';\n\n/**\n * Standard cubic ease-out function (pure math, framework-agnostic).\n */\nexport const defaultEasing = (t: number): number => --t * t * t + 1;\n\n/**\n * Standard linear trajectory: straight line interpolation with linear scale & opacity progression.\n */\nexport const linearTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t},\n\t\tscale: progress,\n\t\topacity: Math.min(1, progress * 1.5)\n\t};\n};\n\n/**\n * Linear position trajectory: pure position interpolation without altering scale or opacity.\n */\nexport const linearPositionTrajectory: TrajectoryFunction = (\n\torigin: XYPosition,\n\ttarget: XYPosition,\n\tprogress: number\n): TrajectoryPoint => {\n\treturn {\n\t\tposition: {\n\t\t\tx: origin.x + (target.x - origin.x) * progress,\n\t\t\ty: origin.y + (target.y - origin.y) * progress\n\t\t}\n\t};\n};\n\n/**\n * Creates a fan-out trajectory where nodes arc outward from origin to target.\n */\nexport function createFanOutTrajectory(options: FanOutTrajectoryOptions = {}): TrajectoryFunction {\n\tconst {\n\t\tcurvature = 0.4,\n\t\tstartScale = 0,\n\t\tendScale = 1,\n\t\tstartOpacity = 0,\n\t\tendOpacity = 1\n\t} = options;\n\n\treturn (\n\t\torigin: XYPosition,\n\t\ttarget: XYPosition,\n\t\tprogress: number,\n\t\tindex: number,\n\t\ttotal: number\n\t): TrajectoryPoint => {\n\t\tconst dx = target.x - origin.x;\n\t\tconst dy = target.y - origin.y;\n\n\t\t// Normalized index from -1 (top) to +1 (bottom)\n\t\tconst normIndex = total > 1 ? (index - (total - 1) / 2) / ((total - 1) / 2) : 0;\n\n\t\t// Perpendicular control point offset to create smooth fan arc\n\t\tconst midX = origin.x + dx * 0.4;\n\t\tconst midY = origin.y + dy * 0.1 - normIndex * Math.abs(dx) * 0.15 * curvature;\n\n\t\t// Quadratic Bezier interpolation: B(t) = (1-t)^2 * P0 + 2(1-t)t * P1 + t^2 * P2\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * midX + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * midY + t * t * target.y;\n\n\t\tconst scale = startScale + (endScale - startScale) * progress;\n\t\tconst opacity = startOpacity + (endOpacity - startOpacity) * progress;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale,\n\t\t\topacity\n\t\t};\n\t};\n}\n\n/**\n * Creates a custom quadratic Bezier trajectory with a specified control point resolver.\n */\nexport function createBezierTrajectory(\n\tgetControlPoint: (origin: XYPosition, target: XYPosition) => XYPosition\n): TrajectoryFunction {\n\treturn (origin: XYPosition, target: XYPosition, progress: number): TrajectoryPoint => {\n\t\tconst cp = getControlPoint(origin, target);\n\t\tconst t = progress;\n\t\tconst oneMinusT = 1 - t;\n\n\t\tconst x = oneMinusT * oneMinusT * origin.x + 2 * oneMinusT * t * cp.x + t * t * target.x;\n\t\tconst y = oneMinusT * oneMinusT * origin.y + 2 * oneMinusT * t * cp.y + t * t * target.y;\n\n\t\treturn {\n\t\t\tposition: { x, y },\n\t\t\tscale: progress,\n\t\t\topacity: progress\n\t\t};\n\t};\n}\n\nimport { realClock, type Clock } from './clock.js';\n\n/**\n * Returns a copy of `style` with its opacity set to `opacity`.\n *\n * Supports both CSS strings (`\"color: red; opacity: 0.5;\"`, used by SvelteFlow)\n * and style objects (`{ color: 'red', opacity: 0.5 }`, used by React Flow / xyflow).\n * When `style` is empty, `emptyFormat` decides which representation is produced.\n * Never mutates the input.\n */\nexport function withStyleOpacity(\n\tstyle: CanvasNodeStyle | undefined | null,\n\topacity: number,\n\temptyFormat: 'string' | 'object' = 'string'\n): CanvasNodeStyle {\n\tif (style !== null && typeof style === 'object') {\n\t\treturn { ...style, opacity };\n\t}\n\tif (typeof style === 'string' && style.trim() !== '') {\n\t\tconst rest = style\n\t\t\t.replace(/(^|;)\\s*opacity\\s*:\\s*[^;]*;?/gi, '$1')\n\t\t\t.trim()\n\t\t\t.replace(/;+\\s*$/, '')\n\t\t\t.trim();\n\t\treturn `${rest ? rest + '; ' : ''}opacity: ${opacity};`;\n\t}\n\treturn emptyFormat === 'object' ? { opacity } : `opacity: ${opacity};`;\n}\n\nexport interface MultiNodeTransitionOptions<TNode extends CanvasNode = CanvasNode> {\n\tdefaultDuration?: number;\n\tdefaultEasing?: (t: number) => number;\n\tclock?: Clock;\n\t/**\n\t * Style representation to create for nodes that have no `style` yet when an opacity\n\t * is applied. Defaults to `'string'` (SvelteFlow); pass `'object'` for React Flow.\n\t * Existing styles always keep their representation.\n\t */\n\tstyleFormat?: 'string' | 'object';\n\t/**\n\t * Called on every frame with updated copies of the transitioning nodes, in the same\n\t * order as the `transitions` array. Input nodes are never mutated.\n\t */\n\tonUpdate?: (nodes: TNode[]) => void;\n\t/** Called once all transitions finished, with the final node copies. */\n\tonComplete?: (nodes: TNode[]) => void;\n}\n\n/**\n * Coordinates and animates multiple node transitions simultaneously.\n *\n * The input nodes are treated as immutable: every frame produces updated copies\n * (position, `data.scale` / `data.opacity`, and `style` opacity) which are handed to\n * `onUpdate` and finally `onComplete`. Merge them into your node state by `id`.\n */\nexport function runMultiNodeTransition<TNode extends CanvasNode = CanvasNode>(\n\ttransitions: NodeTransition<TNode>[],\n\toptions: MultiNodeTransitionOptions<TNode> = {}\n): () => void {\n\tif (transitions.length === 0) {\n\t\toptions.onComplete?.([]);\n\t\treturn () => {};\n\t}\n\n\tconst {\n\t\tdefaultDuration = 400,\n\t\tdefaultEasing: easingFunc = defaultEasing,\n\t\tclock = realClock,\n\t\tstyleFormat = 'string',\n\t\tonUpdate,\n\t\tonComplete\n\t} = options;\n\n\tlet isCancelled = false;\n\tconst startTime = clock.now();\n\n\tconst items = transitions.map((tr, i) => ({\n\t\t...tr,\n\t\tindex: tr.index ?? i,\n\t\ttotal: tr.total ?? transitions.length,\n\t\tduration: tr.duration ?? defaultDuration,\n\t\teasing: tr.easing ?? easingFunc,\n\t\tdelay: tr.delay ?? 0,\n\t\ttrajectory: tr.trajectory ?? linearTrajectory\n\t}));\n\n\t// Latest copy of every node; starts out as the untouched inputs.\n\tconst current: TNode[] = items.map((item) => item.node);\n\n\tfunction frame() {\n\t\tif (isCancelled) return;\n\n\t\tconst now = clock.now();\n\t\tlet allCompleted = true;\n\n\t\tfor (let i = 0; i < items.length; i++) {\n\t\t\tconst item = items[i];\n\t\t\tconst elapsed = now - (startTime + item.delay);\n\n\t\t\tif (elapsed < 0) {\n\t\t\t\tallCompleted = false;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Guard zero duration and division by zero (referenced in GOTCHAS.md)\n\t\t\tconst rawProgress = item.duration <= 0 ? 1 : Math.min(elapsed / item.duration, 1);\n\t\t\tconst progress = item.duration <= 0 ? 1 : item.easing(rawProgress);\n\n\t\t\tconst point = item.trajectory(item.from, item.to, progress, item.index, item.total);\n\n\t\t\tconst next: TNode = { ...item.node, position: { ...point.position } };\n\n\t\t\tif (point.scale !== undefined || point.opacity !== undefined) {\n\t\t\t\tconst data: Record<string, any> = { ...(item.node.data ?? {}) };\n\t\t\t\tif (point.scale !== undefined) {\n\t\t\t\t\tdata.scale = point.scale;\n\t\t\t\t}\n\t\t\t\tif (point.opacity !== undefined) {\n\t\t\t\t\tdata.opacity = point.opacity;\n\t\t\t\t\t(next as CanvasNode).style = withStyleOpacity(item.node.style, point.opacity, styleFormat);\n\t\t\t\t}\n\t\t\t\t(next as CanvasNode).data = data;\n\t\t\t}\n\n\t\t\tcurrent[i] = next;\n\n\t\t\tif (rawProgress < 1) {\n\t\t\t\tallCompleted = false;\n\t\t\t}\n\t\t}\n\n\t\tonUpdate?.(current.slice());\n\n\t\tif (!allCompleted) {\n\t\t\tclock.frame().then(frame);\n\t\t} else {\n\t\t\tonComplete?.(current.slice());\n\t\t}\n\t}\n\n\tframe();\n\n\treturn () => {\n\t\tisCancelled = true;\n\t};\n}\n","import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasNode,\n\tDirectionalDisplacementOptions,\n\tRect,\n\tReflowDisplacementOptions,\n\tXYPosition\n} from './types.js';\n\n/**\n * Calculates a bounding rectangle for a set of nodes if not provided by getNodesBounds.\n */\nexport function computeNodesBoundingBox<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tdefaultWidth = 200,\n\tdefaultHeight = 60\n): Rect {\n\tif (nodes.length === 0) {\n\t\treturn { x: 0, y: 0, width: 0, height: 0 };\n\t}\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst width = node.measured?.width ?? (node as any).width ?? defaultWidth;\n\t\tconst height = node.measured?.height ?? (node as any).height ?? defaultHeight;\n\n\t\tminX = Math.min(minX, node.position.x);\n\t\tminY = Math.min(minY, node.position.y);\n\t\tmaxX = Math.max(maxX, node.position.x + width);\n\t\tmaxY = Math.max(maxY, node.position.y + height);\n\t}\n\n\treturn {\n\t\tx: minX,\n\t\ty: minY,\n\t\twidth: maxX - minX,\n\t\theight: maxY - minY\n\t};\n}\n\n/**\n * Pure directional displacement calculation.\n * Shifts downstream or surrounding nodes along an axis to clear space for newly inserted child nodes.\n */\nexport function calculateDirectionalDisplacement<TNode extends CanvasNode = CanvasNode>(params: {\n\texistingNodes: TNode[];\n\toriginNode: TNode;\n\tchildNodes: TNode[];\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n\toptions?: DirectionalDisplacementOptions;\n}): Map<string, XYPosition> {\n\tconst {\n\t\texistingNodes,\n\t\toriginNode,\n\t\tchildNodes,\n\t\tgetNodesBounds,\n\t\toptions = {}\n\t} = params;\n\n\tconst {\n\t\tdirection = 'right',\n\t\tgap = 40,\n\t\tspreadSiblingLanes = false,\n\t\tsiblingTolerance = 50,\n\t\tminY\n\t} = options;\n\n\tconst displacedMap = new Map<string, XYPosition>();\n\tif (childNodes.length === 0 || existingNodes.length === 0) {\n\t\treturn displacedMap;\n\t}\n\n\tconst childBounds = getNodesBounds\n\t\t? getNodesBounds(childNodes)\n\t\t: computeNodesBoundingBox(childNodes);\n\n\tconst originWidth = originNode.measured?.width ?? (originNode as any).width ?? 200;\n\tconst originHeight = originNode.measured?.height ?? (originNode as any).height ?? 60;\n\n\t// The delta space required along primary axis\n\tconst deltaPrimary =\n\t\tdirection === 'right' || direction === 'left'\n\t\t\t? childBounds.width + gap\n\t\t\t: childBounds.height + gap;\n\n\t// Vertical displacement parameters: upward and downward row clearance\n\tconst deltaUp = Math.max(0, originNode.position.y - childBounds.y + gap);\n\tconst deltaDown = Math.max(\n\t\t0,\n\t\tchildBounds.y + childBounds.height - (originNode.position.y + originHeight) + gap\n\t);\n\n\t// Both above and below displace equally so the row expands symmetrically\n\tconst equalDelta = Math.max(deltaUp, deltaDown);\n\n\tfor (const node of existingNodes) {\n\t\tif (node.id === originNode.id) continue;\n\n\t\tconst isSiblingRow =\n\t\t\tMath.abs(node.position.y - originNode.position.y) < siblingTolerance;\n\n\t\tswitch (direction) {\n\t\t\tcase 'right': {\n\t\t\t\tif (node.position.x > originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x + deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'left': {\n\t\t\t\tif (node.position.x < originNode.position.x) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x - deltaPrimary,\n\t\t\t\t\t\ty: node.position.y\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'down': {\n\t\t\t\tif (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'up': {\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - deltaPrimary\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'vertical': {\n\t\t\t\tif (isSiblingRow) break;\n\n\t\t\t\tif (node.position.y < originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y - equalDelta\n\t\t\t\t\t});\n\t\t\t\t} else if (node.position.y > originNode.position.y) {\n\t\t\t\t\tdisplacedMap.set(node.id, {\n\t\t\t\t\t\tx: node.position.x,\n\t\t\t\t\t\ty: node.position.y + equalDelta\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Sibling lane spreading to avoid overlap among child nodes\n\tif (spreadSiblingLanes && (direction === 'right' || direction === 'left')) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tconst node = existingNodes.find((n) => n.id === nodeId);\n\t\t\tif (node && Math.abs(node.position.y - originNode.position.y) < siblingTolerance) {\n\t\t\t\tconst isAbove = node.position.y < originNode.position.y;\n\t\t\t\tdisplacedMap.set(nodeId, {\n\t\t\t\t\tx: pos.x,\n\t\t\t\t\ty: pos.y + (isAbove ? -deltaUp : deltaDown)\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t// Floor constraint\n\tif (minY !== undefined) {\n\t\tfor (const [nodeId, pos] of displacedMap.entries()) {\n\t\t\tif (pos.y < minY) {\n\t\t\t\tdisplacedMap.set(nodeId, { x: pos.x, y: minY });\n\t\t\t}\n\t\t}\n\t}\n\n\treturn displacedMap;\n}\n\n/**\n * Calculates Dagre reflow displacement preserving the position of an anchor node.\n */\nexport function calculateDagreReflowDisplacement<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(params: {\n\tcurrentNodes: TNode[];\n\tcurrentEdges: TEdge[];\n\tfutureNodes: TNode[];\n\tfutureEdges: TEdge[];\n\toptions: ReflowDisplacementOptions;\n\tgetNodesBounds?: (nodes: TNode[]) => Rect;\n}): Map<string, XYPosition> {\n\tconst {\n\t\tcurrentNodes,\n\t\tfutureNodes,\n\t\tfutureEdges,\n\t\toptions\n\t} = params;\n\n\tconst {\n\t\tanchorNodeId,\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = 200,\n\t\tdefaultNodeHeight = 60\n\t} = options;\n\n\tconst anchorNode = currentNodes.find((n) => n.id === anchorNodeId);\n\tif (!anchorNode) return new Map();\n\n\tconst g = new dagre.graphlib.Graph();\n\tg.setDefaultEdgeLabel(() => ({}));\n\tg.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(futureNodes.map((n) => n.id));\n\n\tfutureNodes.forEach((node) => {\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\t\tg.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tfutureEdges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tg.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(g);\n\n\tconst anchorLayoutPos = g.node(anchorNodeId);\n\tif (!anchorLayoutPos) return new Map();\n\n\tconst anchorWidth = anchorNode.measured?.width ?? (anchorNode as any).width ?? defaultNodeWidth;\n\tconst anchorHeight = anchorNode.measured?.height ?? (anchorNode as any).height ?? defaultNodeHeight;\n\n\tconst anchorLayoutUpperLeft = {\n\t\tx: anchorLayoutPos.x - anchorWidth / 2,\n\t\ty: anchorLayoutPos.y - anchorHeight / 2\n\t};\n\n\tconst deltaX = anchorNode.position.x - anchorLayoutUpperLeft.x;\n\tconst deltaY = anchorNode.position.y - anchorLayoutUpperLeft.y;\n\n\tconst displacementMap = new Map<string, XYPosition>();\n\n\tcurrentNodes.forEach((node) => {\n\t\tif (node.id === anchorNodeId) return;\n\n\t\tconst layoutNode = g.node(node.id);\n\t\tif (!layoutNode) return;\n\n\t\tconst nodeWidth = node.measured?.width ?? (node as any).width ?? defaultNodeWidth;\n\t\tconst nodeHeight = node.measured?.height ?? (node as any).height ?? defaultNodeHeight;\n\n\t\tconst upperLeftX = layoutNode.x - nodeWidth / 2 + deltaX;\n\t\tconst upperLeftY = layoutNode.y - nodeHeight / 2 + deltaY;\n\n\t\tif (\n\t\t\tMath.abs(upperLeftX - node.position.x) > 1 ||\n\t\t\tMath.abs(upperLeftY - node.position.y) > 1\n\t\t) {\n\t\t\tdisplacementMap.set(node.id, { x: upperLeftX, y: upperLeftY });\n\t\t}\n\t});\n\n\treturn displacementMap;\n}\n","import type {\n\tCanvasClipboardPayload,\n\tCanvasEdge,\n\tCanvasNode,\n\tXYPosition\n} from './types.js';\n\n/**\n * Checks if an event originated from a text input, textarea, contenteditable element, or rich editor.\n */\nexport function isEventFromTextInput(event: Event | { target: EventTarget | null }): boolean {\n\tconst target = (event as any).target as HTMLElement | null;\n\tif (!target) return false;\n\tif (\n\t\ttarget.tagName === 'INPUT' ||\n\t\ttarget.tagName === 'TEXTAREA' ||\n\t\ttarget.tagName === 'SELECT' ||\n\t\ttarget.isContentEditable ||\n\t\ttarget.closest?.('[contenteditable=\"true\"]') ||\n\t\ttarget.closest?.('.ProseMirror')\n\t) {\n\t\treturn true;\n\t}\n\treturn false;\n}\n\n/**\n * Validates that an object conforms to minimal CanvasNode shape:\n * a string `id` and a `position` whose `x` / `y` are finite numbers.\n */\nexport function isValidCanvasNode(obj: unknown): obj is CanvasNode {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; position?: unknown };\n\tif (typeof candidate.id !== 'string') return false;\n\tconst position = candidate.position as { x?: unknown; y?: unknown } | null | undefined;\n\treturn (\n\t\ttypeof position === 'object' &&\n\t\tposition !== null &&\n\t\ttypeof position.x === 'number' &&\n\t\tNumber.isFinite(position.x) &&\n\t\ttypeof position.y === 'number' &&\n\t\tNumber.isFinite(position.y)\n\t);\n}\n\n/**\n * Validates that an object conforms to minimal CanvasEdge shape (string `id`, `source`, `target`).\n */\nexport function isValidCanvasEdge(obj: unknown): obj is CanvasEdge {\n\tif (typeof obj !== 'object' || obj === null) return false;\n\tconst candidate = obj as { id?: unknown; source?: unknown; target?: unknown };\n\treturn (\n\t\ttypeof candidate.id === 'string' &&\n\t\ttypeof candidate.source === 'string' &&\n\t\ttypeof candidate.target === 'string'\n\t);\n}\n\n/**\n * Checks if a string is a valid HTTP/HTTPS URL.\n */\nexport function isValidUrl(url: string): boolean {\n\ttry {\n\t\tconst parsed = new URL(url);\n\t\treturn parsed.protocol === 'http:' || parsed.protocol === 'https:';\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Serializes nodes and optional edges into clipboard JSON payload.\n */\nexport function serializeCanvasNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(nodes: TNode[], edges?: TEdge[]): string {\n\tconst payload: CanvasClipboardPayload<TNode, TEdge> = {\n\t\tversion: 1,\n\t\tnodes,\n\t\tedges\n\t};\n\treturn JSON.stringify(payload);\n}\n\n/**\n * Parses raw JSON string into a CanvasClipboardPayload if valid.\n */\nexport function parseCanvasClipboardData<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(raw: string): CanvasClipboardPayload<TNode, TEdge> | null {\n\ttry {\n\t\tconst parsed = JSON.parse(raw);\n\t\tif (\n\t\t\ttypeof parsed === 'object' &&\n\t\t\tparsed !== null &&\n\t\t\tArray.isArray(parsed.nodes) &&\n\t\t\tparsed.nodes.length > 0 &&\n\t\t\tparsed.nodes.every(isValidCanvasNode)\n\t\t) {\n\t\t\t// Untrusted input: drop malformed edges instead of crashing downstream consumers.\n\t\t\tconst edges = Array.isArray(parsed.edges)\n\t\t\t\t? (parsed.edges as unknown[]).filter(isValidCanvasEdge)\n\t\t\t\t: undefined;\n\t\t\treturn { ...parsed, edges } as CanvasClipboardPayload<TNode, TEdge>;\n\t\t}\n\t} catch {\n\t\t// Not JSON or invalid payload\n\t}\n\treturn null;\n}\n\n/**\n * Generates unique new IDs for pasted nodes and updates edge references accordingly.\n *\n * - Nodes without a valid id / finite numeric position are skipped.\n * - `parentId` (and legacy `parentNode`) references to other pasted nodes are remapped to\n * the new IDs; such children keep their parent-relative position (no extra offset).\n * References to parents outside the pasted set are left unchanged.\n * - Edges are kept only when both endpoints were pasted; a non-array `edges` is ignored.\n */\nexport function remapPastedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[] | null | undefined = [],\n\toffset: XYPosition = { x: 30, y: 30 },\n\tidGenerator: () => string = () => `node-${Math.random().toString(36).substring(2, 9)}`\n): { nodes: TNode[]; edges: TEdge[] } {\n\tconst validNodes = (Array.isArray(nodes) ? nodes : []).filter(isValidCanvasNode) as TNode[];\n\tconst validEdges = (Array.isArray(edges) ? edges : []).filter(isValidCanvasEdge) as TEdge[];\n\n\tconst dx = Number.isFinite(offset?.x) ? offset.x : 0;\n\tconst dy = Number.isFinite(offset?.y) ? offset.y : 0;\n\n\tconst idMap = new Map<string, string>();\n\tfor (const node of validNodes) {\n\t\tidMap.set(node.id, idGenerator());\n\t}\n\n\tconst newNodes = validNodes.map((node) => {\n\t\tconst next: CanvasNode = {\n\t\t\t...node,\n\t\t\tid: idMap.get(node.id)!,\n\t\t\tposition: {\n\t\t\t\tx: node.position.x + dx,\n\t\t\t\ty: node.position.y + dy\n\t\t\t},\n\t\t\tselected: true\n\t\t};\n\n\t\tlet parentRemapped = false;\n\t\tfor (const key of ['parentId', 'parentNode'] as const) {\n\t\t\tconst parent = (node as CanvasNode)[key];\n\t\t\tif (typeof parent === 'string' && idMap.has(parent)) {\n\t\t\t\tnext[key] = idMap.get(parent)!;\n\t\t\t\tparentRemapped = true;\n\t\t\t}\n\t\t}\n\t\tif (parentRemapped) {\n\t\t\t// Position is relative to the (also pasted, also offset) parent.\n\t\t\tnext.position = { x: node.position.x, y: node.position.y };\n\t\t}\n\n\t\treturn next as TNode;\n\t});\n\n\tconst newEdges = validEdges\n\t\t.filter((e) => idMap.has(e.source) && idMap.has(e.target))\n\t\t.map((edge) => ({\n\t\t\t...edge,\n\t\t\tid: `edge-${Math.random().toString(36).substring(2, 9)}`,\n\t\t\tsource: idMap.get(edge.source)!,\n\t\t\ttarget: idMap.get(edge.target)!,\n\t\t\tselected: false\n\t\t}));\n\n\treturn { nodes: newNodes, edges: newEdges };\n}\n\n/**\n * Repositions nodes so their bounding box origin matches the given target position.\n */\nexport function repositionNodesTo<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\ttargetOrigin: XYPosition\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\tconst minX = Math.min(...nodes.map((n) => n.position.x));\n\tconst minY = Math.min(...nodes.map((n) => n.position.y));\n\tconst deltaX = targetOrigin.x - minX;\n\tconst deltaY = targetOrigin.y - minY;\n\n\treturn nodes.map((n) => ({\n\t\t...n,\n\t\tposition: {\n\t\t\tx: n.position.x + deltaX,\n\t\t\ty: n.position.y + deltaY\n\t\t}\n\t}));\n}\n","import type { CanvasEdge, CanvasNode, Rect } from './types.js';\n\nexport interface CanvasHeightOptions {\n\t/** Smallest height returned (default 320). */\n\tmin?: number;\n\t/** Largest height returned; taller content is scaled/zoomed (default 560). */\n\tmax?: number;\n\t/** Extra padding around the content (default 48). */\n\tpadding?: number;\n}\n\nfunction clampHeight(contentHeight: number, options: CanvasHeightOptions = {}): number {\n\tconst { min = 320, max = 560, padding = 48 } = options;\n\treturn Math.round(Math.min(max, Math.max(min, contentHeight + padding)));\n}\n\n/**\n * Calculates the bounding box extent of all positioned nodes, or `null` for an empty graph.\n */\nexport function layoutBounds<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[]\n): Rect | null {\n\tif (nodes.length === 0) return null;\n\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\n\tfor (const node of nodes) {\n\t\tconst x = node.position.x;\n\t\tconst y = node.position.y;\n\t\tconst width = node.width ?? 180;\n\t\tconst height = node.height ?? 80;\n\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x + width);\n\t\tmaxY = Math.max(maxY, y + height);\n\t}\n\n\tif (minX === Infinity) return null;\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Returns a clamped container height that fits the layout at 1:1 scale without overflow.\n */\nexport function canvasHeight<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\toptions?: CanvasHeightOptions\n): number {\n\treturn clampHeight(layoutBounds(nodes)?.height ?? 0, options);\n}\n\nconst DEFAULT_RANK_HEIGHT = 80 + 72; // Average node height + default rank separation\nconst NODES_PER_RANK = 2;\n\n/**\n * Estimates the container height to reserve before layout calculation finishes,\n * based on node and edge counts alone. Prevents layout shift (CLS).\n */\nexport function estimateCanvasHeight<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions?: CanvasHeightOptions\n): number {\n\tconst ranks = Math.max(1, Math.ceil(nodes.length / NODES_PER_RANK));\n\treturn clampHeight(ranks * DEFAULT_RANK_HEIGHT, options);\n}\n\nfunction mix(a: number, b: number, t: number): number {\n\treturn a + (b - a) * t;\n}\n\n/**\n * Interpolates node positions and sizes between two layouts partway at progress `t` (0 = from, 1 = to).\n * Nodes present in both interpolate smoothly; nodes newly added in `to` snap to target.\n */\nexport function blendLayout<TNode extends CanvasNode = CanvasNode>(\n\tfrom: TNode[],\n\tto: TNode[],\n\tt: number\n): TNode[] {\n\tif (t >= 1) return to;\n\n\tconst fromMap = new Map<string, TNode>(from.map((n) => [n.id, n]));\n\n\treturn to.map((target) => {\n\t\tconst origin = fromMap.get(target.id);\n\t\tif (!origin) return target;\n\n\t\treturn {\n\t\t\t...target,\n\t\t\tposition: {\n\t\t\t\tx: mix(origin.position.x, target.position.x, t),\n\t\t\t\ty: mix(origin.position.y, target.position.y, t)\n\t\t\t},\n\t\t\twidth:\n\t\t\t\torigin.width !== undefined && target.width !== undefined\n\t\t\t\t\t? mix(origin.width, target.width, t)\n\t\t\t\t\t: target.width,\n\t\t\theight:\n\t\t\t\torigin.height !== undefined && target.height !== undefined\n\t\t\t\t\t? mix(origin.height, target.height, t)\n\t\t\t\t\t: target.height\n\t\t};\n\t});\n}\n\n/**\n * Filters out nodes that are not touched by any edge.\n *\n * During LLM streaming, nodes often arrive a chunk or token before the edge\n * that connects them. Displaying them immediately causes them to be placed as root\n * nodes only to jump into their correct position once the edge arrives.\n * Hiding orphan nodes during active streaming keeps the layout calm and jitter-free.\n */\nexport function connectedOnly<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[]\n): TNode[] {\n\tif (edges.length === 0) return nodes;\n\n\tconst linkedNodeIds = new Set<string>();\n\tfor (const edge of edges) {\n\t\tlinkedNodeIds.add(edge.source);\n\t\tlinkedNodeIds.add(edge.target);\n\t}\n\n\tconst filtered = nodes.filter((node) => linkedNodeIds.has(node.id));\n\t// If filtering would remove everything, return the original nodes\n\treturn filtered.length > 0 ? filtered : nodes;\n}\n","import { realClock, type Clock } from './clock.js';\nimport { ensureLayout } from './layout.js';\nimport { linearPositionTrajectory, runMultiNodeTransition } from './trajectory.js';\nimport type { CanvasEdge, CanvasNode, IncrementalLayoutOptions, NodeTransition, XYPosition } from './types.js';\nimport { connectedOnly } from './view.js';\n\nexport interface StreamingLayoutManagerOptions {\n\tthrottleMs?: number;\n\tglideDurationMs?: number;\n\tconnectedOnly?: boolean;\n\tclock?: Clock;\n\tlayoutOptions?: IncrementalLayoutOptions;\n\tonLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n}\n\n/**\n * Manages throttled incremental layout recalculation and smooth tweened node gliding\n * during active streaming (e.g. LLM streaming or graph generation).\n *\n * - `pushStreamUpdate` is a true throttle (leading + trailing edge): the first update\n * lays out immediately, further updates within `throttleMs` are coalesced and the\n * latest one is laid out when the window closes. Layouts therefore keep running at\n * most once per `throttleMs` while a stream is active, instead of being postponed\n * until the stream goes quiet.\n * - Every layout is computed from the graph (respecting `layoutOptions.storedPositions`\n * as pinned/manual coordinates) and each known node glides from its *current*\n * position (the `currentPositions` argument) to its newly computed position.\n */\nexport class StreamingLayoutManager<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n> {\n\tprivate throttleMs: number;\n\tprivate glideDurationMs: number;\n\tprivate connectedOnly: boolean;\n\tprivate clock: Clock;\n\tprivate layoutOptions: IncrementalLayoutOptions;\n\tprivate onLayoutUpdated?: (positions: Map<string, XYPosition>) => void;\n\n\tprivate pendingTimer: ReturnType<typeof setTimeout> | null = null;\n\tprivate pendingArgs: [TNode[], TEdge[], Map<string, XYPosition>] | null = null;\n\tprivate activeTransitionCancel: (() => void) | null = null;\n\tprivate targetPositions = new Map<string, XYPosition>();\n\tprivate livePositions = new Map<string, XYPosition>();\n\n\tconstructor(options: StreamingLayoutManagerOptions = {}) {\n\t\tthis.throttleMs = options.throttleMs ?? 200;\n\t\tthis.glideDurationMs = options.glideDurationMs ?? 300;\n\t\tthis.connectedOnly = options.connectedOnly ?? false;\n\t\tthis.clock = options.clock ?? realClock;\n\t\tthis.layoutOptions = options.layoutOptions ?? { direction: 'LR' };\n\t\tthis.onLayoutUpdated = options.onLayoutUpdated;\n\t}\n\n\t/**\n\t * Feed incoming nodes and edges into the streaming manager.\n\t * Throttles rapid streaming bursts (leading + trailing edge) and triggers smooth layout gliding.\n\t */\n\tpublic pushStreamUpdate(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\t// Inside the throttle window: remember the latest update for the trailing edge.\n\t\t\tthis.pendingArgs = [nodes, edges, currentPositions];\n\t\t\treturn;\n\t\t}\n\n\t\t// Leading edge: lay out immediately and open a throttle window.\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t\tthis.startWindow();\n\t}\n\n\tprivate startWindow(): void {\n\t\tthis.pendingTimer = setTimeout(() => {\n\t\t\tthis.pendingTimer = null;\n\t\t\tconst args = this.pendingArgs;\n\t\t\tthis.pendingArgs = null;\n\t\t\tif (args) {\n\t\t\t\t// Trailing edge: lay out the latest coalesced update and keep throttling.\n\t\t\t\tthis.recalculateAndGlide(...args);\n\t\t\t\tthis.startWindow();\n\t\t\t}\n\t\t}, Math.max(0, this.throttleMs));\n\t}\n\n\t/**\n\t * Forces immediate calculation and cancels pending throttled timers.\n\t */\n\tpublic flush(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tthis.clearPending();\n\t\tthis.recalculateAndGlide(nodes, edges, currentPositions);\n\t}\n\n\t/**\n\t * Latest computed layout targets (the positions nodes are gliding towards).\n\t */\n\tpublic getTargetPositions(): Map<string, XYPosition> {\n\t\treturn new Map(this.targetPositions);\n\t}\n\n\tprivate clearPending(): void {\n\t\tif (this.pendingTimer !== null) {\n\t\t\tclearTimeout(this.pendingTimer);\n\t\t\tthis.pendingTimer = null;\n\t\t}\n\t\tthis.pendingArgs = null;\n\t}\n\n\tprivate recalculateAndGlide(\n\t\tnodes: TNode[],\n\t\tedges: TEdge[],\n\t\tcurrentPositions: Map<string, XYPosition>\n\t): void {\n\t\tconst targetNodes = this.connectedOnly ? connectedOnly(nodes, edges) : nodes;\n\t\tif (targetNodes.length === 0) return;\n\n\t\t// Where nodes are right now: the caller's view, overridden by any glide still in\n\t\t// flight (the caller may not have applied the latest animated frame yet).\n\t\tconst startPositions = new Map<string, XYPosition>(currentPositions);\n\t\tif (this.activeTransitionCancel) {\n\t\t\tfor (const [id, pos] of this.livePositions) {\n\t\t\t\tstartPositions.set(id, pos);\n\t\t\t}\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\n\t\t// Compute fresh layout targets. Only explicitly stored (pinned/manual) positions\n\t\t// are preserved; current positions are the glide *origin*, not the destination.\n\t\tconst layouted = ensureLayout(targetNodes, edges, this.layoutOptions);\n\n\t\tconst nextPositions = new Map<string, XYPosition>();\n\t\tconst transitions: NodeTransition<TNode>[] = [];\n\n\t\tfor (const node of layouted) {\n\t\t\tconst target = { ...node.position };\n\t\t\tnextPositions.set(node.id, target);\n\n\t\t\tconst current = startPositions.get(node.id);\n\t\t\tif (!current) continue; // brand-new node: appears directly at its target\n\n\t\t\tconst distance = Math.hypot(target.x - current.x, target.y - current.y);\n\t\t\tif (distance > 2) {\n\t\t\t\ttransitions.push({\n\t\t\t\t\tnode: { ...node, position: { ...current } },\n\t\t\t\t\tfrom: current,\n\t\t\t\t\tto: target,\n\t\t\t\t\ttrajectory: linearPositionTrajectory,\n\t\t\t\t\tduration: this.glideDurationMs\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tthis.targetPositions = nextPositions;\n\n\t\tif (transitions.length === 0) {\n\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\treturn;\n\t\t}\n\n\t\tconst live = new Map<string, XYPosition>(nextPositions);\n\t\tfor (const trans of transitions) {\n\t\t\tlive.set(trans.node.id, { ...trans.from });\n\t\t}\n\t\tthis.livePositions = live;\n\n\t\tlet finished = false;\n\t\tconst cancel = runMultiNodeTransition(transitions, {\n\t\t\tclock: this.clock,\n\t\t\tonUpdate: (updated) => {\n\t\t\t\tconst frame = new Map<string, XYPosition>(this.livePositions);\n\t\t\t\tfor (const node of updated) {\n\t\t\t\t\tframe.set(node.id, { ...node.position });\n\t\t\t\t}\n\t\t\t\tthis.livePositions = frame;\n\t\t\t\tthis.onLayoutUpdated?.(frame);\n\t\t\t},\n\t\t\tonComplete: () => {\n\t\t\t\tfinished = true;\n\t\t\t\tthis.activeTransitionCancel = null;\n\t\t\t\tthis.livePositions = new Map(nextPositions);\n\t\t\t\tthis.onLayoutUpdated?.(nextPositions);\n\t\t\t}\n\t\t});\n\t\t// A zero-duration glide completes synchronously; don't keep a stale canceller.\n\t\tthis.activeTransitionCancel = finished ? null : cancel;\n\t}\n\n\tpublic destroy(): void {\n\t\tthis.clearPending();\n\t\tif (this.activeTransitionCancel) {\n\t\t\tthis.activeTransitionCancel();\n\t\t\tthis.activeTransitionCancel = null;\n\t\t}\n\t}\n}\n"]}