@pixerate/editor 0.16.2 → 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.
Files changed (42) hide show
  1. package/dist/agent/index.cjs +32 -29
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.d.cts +9 -3
  4. package/dist/agent/index.d.ts +9 -3
  5. package/dist/agent/index.js +31 -28
  6. package/dist/agent/index.js.map +1 -1
  7. package/dist/canvas/index.cjs +133 -65
  8. package/dist/canvas/index.cjs.map +1 -1
  9. package/dist/canvas/index.d.cts +64 -11
  10. package/dist/canvas/index.d.ts +64 -11
  11. package/dist/canvas/index.js +128 -60
  12. package/dist/canvas/index.js.map +1 -1
  13. package/dist/{chunk-33UVEDOS.cjs → chunk-2TWGLC5K.cjs} +58 -29
  14. package/dist/chunk-2TWGLC5K.cjs.map +1 -0
  15. package/dist/{chunk-3A7KVDEA.js → chunk-AIZUKWTL.js} +527 -114
  16. package/dist/chunk-AIZUKWTL.js.map +1 -0
  17. package/dist/{chunk-GWHJVZPI.js → chunk-KAADNAPF.js} +58 -29
  18. package/dist/chunk-KAADNAPF.js.map +1 -0
  19. package/dist/{chunk-TYG7K6WK.cjs → chunk-YQCI4CCN.cjs} +562 -149
  20. package/dist/chunk-YQCI4CCN.cjs.map +1 -0
  21. package/dist/history/index.cjs +2 -2
  22. package/dist/history/index.d.cts +5 -0
  23. package/dist/history/index.d.ts +5 -0
  24. package/dist/history/index.js +1 -1
  25. package/dist/index.cjs +13 -3
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +12 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/spreadsheet/index.cjs +12 -2
  32. package/dist/spreadsheet/index.cjs.map +1 -1
  33. package/dist/spreadsheet/index.d.cts +71 -2
  34. package/dist/spreadsheet/index.d.ts +71 -2
  35. package/dist/spreadsheet/index.js +11 -1
  36. package/dist/{types-DC8b2TsA.d.cts → types-JV3S8VeR.d.cts} +6 -2
  37. package/dist/{types-DC8b2TsA.d.ts → types-JV3S8VeR.d.ts} +6 -2
  38. package/package.json +1 -1
  39. package/dist/chunk-33UVEDOS.cjs.map +0 -1
  40. package/dist/chunk-3A7KVDEA.js.map +0 -1
  41. package/dist/chunk-GWHJVZPI.js.map +0 -1
  42. package/dist/chunk-TYG7K6WK.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/canvas/clock.ts","../../src/canvas/trajectory.ts","../../src/canvas/displacement.ts","../../src/canvas/clipboard.ts","../../src/canvas/view.ts","../../src/canvas/streaming.ts"],"sourcesContent":["/**\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"],"mappings":"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","names":[]}
@@ -118,37 +118,64 @@ var HistoryManager = (_class = class {
118
118
  this.currentBatch = [];
119
119
  try {
120
120
  fn();
121
- } finally {
121
+ } catch (error) {
122
+ const executed = _nullishCoalesce(this.currentBatch, () => ( []));
122
123
  this.batchDepth--;
123
- const batchCommands = this.currentBatch;
124
124
  this.currentBatch = null;
125
- if (batchCommands && batchCommands.length > 0) {
126
- const compoundCommand = {
127
- name,
128
- timestamp: Date.now(),
129
- execute: () => {
130
- for (let i = 0; i < batchCommands.length; i++) {
131
- (_nullishCoalesce(batchCommands[i].redo, () => ( batchCommands[i].execute)))();
132
- }
133
- },
134
- undo: () => {
135
- for (let i = batchCommands.length - 1; i >= 0; i--) {
136
- batchCommands[i].undo();
137
- }
138
- },
139
- redo: () => {
140
- for (let i = 0; i < batchCommands.length; i++) {
141
- (_nullishCoalesce(batchCommands[i].redo, () => ( batchCommands[i].execute)))();
142
- }
125
+ this.rollback(executed);
126
+ throw error;
127
+ }
128
+ this.batchDepth--;
129
+ const batchCommands = _nullishCoalesce(this.currentBatch, () => ( []));
130
+ this.currentBatch = null;
131
+ if (batchCommands.length > 0) {
132
+ const compoundCommand = {
133
+ name,
134
+ timestamp: Date.now(),
135
+ execute: () => {
136
+ for (let i = 0; i < batchCommands.length; i++) {
137
+ (_nullishCoalesce(batchCommands[i].redo, () => ( batchCommands[i].execute)))();
138
+ }
139
+ },
140
+ undo: () => {
141
+ for (let i = batchCommands.length - 1; i >= 0; i--) {
142
+ batchCommands[i].undo();
143
+ }
144
+ },
145
+ redo: () => {
146
+ for (let i = 0; i < batchCommands.length; i++) {
147
+ (_nullishCoalesce(batchCommands[i].redo, () => ( batchCommands[i].execute)))();
143
148
  }
144
- };
145
- this.undoStack.push(compoundCommand);
146
- if (this.undoStack.length > this.maxDepth) {
147
- this.undoStack.shift();
148
149
  }
149
- this.redoStack = [];
150
- this.notify();
150
+ };
151
+ this.undoStack.push(compoundCommand);
152
+ if (this.undoStack.length > this.maxDepth) {
153
+ this.undoStack.shift();
151
154
  }
155
+ this.redoStack = [];
156
+ this.notify();
157
+ }
158
+ }
159
+ /**
160
+ * Reverts already-executed commands of an aborted batch, newest first.
161
+ * Commands executed by the undo closures themselves are not recorded.
162
+ */
163
+ rollback(commands) {
164
+ const wasApplying = this.isApplying;
165
+ this.isApplying = true;
166
+ try {
167
+ for (let i = commands.length - 1; i >= 0; i--) {
168
+ try {
169
+ commands[i].undo();
170
+ } catch (err) {
171
+ console.error(
172
+ `[HistoryManager] Error rolling back "${commands[i].name}" of a failed batch:`,
173
+ err
174
+ );
175
+ }
176
+ }
177
+ } finally {
178
+ this.isApplying = wasApplying;
152
179
  }
153
180
  }
154
181
  /**
@@ -161,8 +188,9 @@ var HistoryManager = (_class = class {
161
188
  }
162
189
  this.isApplying = true;
163
190
  try {
164
- const command = this.undoStack.pop();
191
+ const command = this.undoStack[this.undoStack.length - 1];
165
192
  command.undo();
193
+ this.undoStack.pop();
166
194
  this.redoStack.push(command);
167
195
  return true;
168
196
  } finally {
@@ -180,8 +208,9 @@ var HistoryManager = (_class = class {
180
208
  }
181
209
  this.isApplying = true;
182
210
  try {
183
- const command = this.redoStack.pop();
211
+ const command = this.redoStack[this.redoStack.length - 1];
184
212
  (_nullishCoalesce(command.redo, () => ( command.execute)))();
213
+ this.redoStack.pop();
185
214
  this.undoStack.push(command);
186
215
  return true;
187
216
  } finally {
@@ -230,4 +259,4 @@ function createHistoryManager(options = {}) {
230
259
 
231
260
 
232
261
  exports.HistoryManager = HistoryManager; exports.createHistoryManager = createHistoryManager;
233
- //# sourceMappingURL=chunk-33UVEDOS.cjs.map
262
+ //# sourceMappingURL=chunk-2TWGLC5K.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-2TWGLC5K.cjs","../src/history/HistoryManager.ts"],"names":[],"mappings":"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","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-2TWGLC5K.cjs","sourcesContent":[null,"import {\n Command,\n HistoryManagerOptions,\n HistoryState,\n MergedExecutionOptions,\n SnapshotCommandOptions,\n} from \"./types\";\n\n/**\n * UI-agnostic History and Undo/Redo manager supporting command closures,\n * snapshot diffs, transaction batching, and high-frequency event coalescing.\n */\nexport class HistoryManager {\n private undoStack: Command[] = [];\n private redoStack: Command[] = [];\n private readonly maxDepth: number;\n private isApplying = false;\n private batchDepth = 0;\n private currentBatch: Command[] | null = null;\n private listeners: Set<(state: HistoryState) => void> = new Set();\n private onStateChange?: (state: HistoryState) => void;\n private cachedState: HistoryState;\n\n constructor(options: HistoryManagerOptions = {}) {\n this.maxDepth = options.maxDepth ?? 50;\n this.onStateChange = options.onStateChange;\n this.cachedState = this.computeState();\n }\n\n private computeState(): HistoryState {\n const canUndo = this.undoStack.length > 0;\n const canRedo = this.redoStack.length > 0;\n return {\n canUndo,\n canRedo,\n undoName: canUndo ? this.undoStack[this.undoStack.length - 1].name : \"\",\n redoName: canRedo ? this.redoStack[this.redoStack.length - 1].name : \"\",\n undoCount: this.undoStack.length,\n redoCount: this.redoStack.length,\n };\n }\n\n /**\n * Returns a snapshot of the current history state.\n * Cached to maintain referential equality across renders for useSyncExternalStore.\n */\n public getState(): HistoryState {\n return this.cachedState;\n }\n\n private notify(): void {\n this.cachedState = this.computeState();\n const state = this.cachedState;\n this.onStateChange?.(state);\n this.listeners.forEach((listener) => {\n try {\n listener(state);\n } catch (err) {\n console.error(\"[HistoryManager] Error in state listener:\", err);\n }\n });\n }\n\n /**\n * Executes a command and pushes it onto the undo stack.\n * Clears the redo stack.\n */\n public execute(command: Command): void {\n if (this.isApplying) {\n command.execute();\n return;\n }\n\n if (this.batchDepth > 0 && this.currentBatch) {\n command.execute();\n this.currentBatch.push({\n ...command,\n timestamp: command.timestamp ?? Date.now(),\n });\n return;\n }\n\n command.execute();\n\n this.undoStack.push({\n ...command,\n timestamp: command.timestamp ?? Date.now(),\n });\n\n if (this.undoStack.length > this.maxDepth) {\n this.undoStack.shift();\n }\n\n this.redoStack = [];\n this.notify();\n }\n\n /**\n * Executes a command, coalescing it with the previous command if they share\n * the same `tag` and occurred within `windowMs` (default: 300ms).\n * Useful for high-frequency interactions like slider scrubs or node drags.\n */\n public executeMerged(\n command: Command,\n options: MergedExecutionOptions = {},\n ): void {\n const windowMs = options.windowMs ?? 300;\n const now = Date.now();\n\n if (\n command.tag &&\n this.undoStack.length > 0 &&\n this.batchDepth === 0 &&\n !this.isApplying\n ) {\n const top = this.undoStack[this.undoStack.length - 1];\n const elapsed = top.timestamp ? now - top.timestamp : Infinity;\n\n if (top.tag === command.tag && elapsed <= windowMs) {\n command.execute();\n // Replace top with merged command keeping the original initial undo\n const initialUndo = top.undo;\n this.undoStack[this.undoStack.length - 1] = {\n name: command.name,\n tag: command.tag,\n timestamp: now,\n execute: command.execute,\n undo: initialUndo,\n redo: command.redo ?? command.execute,\n };\n this.redoStack = [];\n this.notify();\n return;\n }\n }\n\n this.execute({ ...command, timestamp: now });\n }\n\n /**\n * Batches multiple commands executed within `fn` into a single atomic undo entry.\n */\n public batch(name: string, fn: () => void): void {\n if (this.isApplying) {\n fn();\n return;\n }\n\n if (this.batchDepth > 0) {\n // Already inside a batch; execute directly\n fn();\n return;\n }\n\n this.batchDepth++;\n this.currentBatch = [];\n\n try {\n fn();\n } catch (error) {\n const executed = this.currentBatch ?? [];\n this.batchDepth--;\n this.currentBatch = null;\n this.rollback(executed);\n throw error;\n }\n\n this.batchDepth--;\n const batchCommands: Command[] = this.currentBatch ?? [];\n this.currentBatch = null;\n\n if (batchCommands.length > 0) {\n const compoundCommand: Command = {\n name,\n timestamp: Date.now(),\n execute: () => {\n for (let i = 0; i < batchCommands.length; i++) {\n (batchCommands[i].redo ?? batchCommands[i].execute)();\n }\n },\n undo: () => {\n for (let i = batchCommands.length - 1; i >= 0; i--) {\n batchCommands[i].undo();\n }\n },\n redo: () => {\n for (let i = 0; i < batchCommands.length; i++) {\n (batchCommands[i].redo ?? batchCommands[i].execute)();\n }\n },\n };\n\n this.undoStack.push(compoundCommand);\n if (this.undoStack.length > this.maxDepth) {\n this.undoStack.shift();\n }\n this.redoStack = [];\n this.notify();\n }\n }\n\n /**\n * Reverts already-executed commands of an aborted batch, newest first.\n * Commands executed by the undo closures themselves are not recorded.\n */\n private rollback(commands: Command[]): void {\n const wasApplying = this.isApplying;\n this.isApplying = true;\n try {\n for (let i = commands.length - 1; i >= 0; i--) {\n try {\n commands[i].undo();\n } catch (err) {\n console.error(\n `[HistoryManager] Error rolling back \"${commands[i].name}\" of a failed batch:`,\n err,\n );\n }\n }\n } finally {\n this.isApplying = wasApplying;\n }\n }\n\n /**\n * Reverts the most recent command on the undo stack.\n * Returns true if a command was undone, false otherwise.\n */\n public undo(): boolean {\n if (this.undoStack.length === 0 || this.isApplying || this.batchDepth > 0) {\n return false;\n }\n\n this.isApplying = true;\n try {\n const command = this.undoStack[this.undoStack.length - 1];\n // If undo throws, the command stays on the undo stack and the error propagates.\n command.undo();\n this.undoStack.pop();\n this.redoStack.push(command);\n return true;\n } finally {\n this.isApplying = false;\n this.notify();\n }\n }\n\n /**\n * Re-applies the most recent undone command on the redo stack.\n * Returns true if a command was redone, false otherwise.\n */\n public redo(): boolean {\n if (this.redoStack.length === 0 || this.isApplying || this.batchDepth > 0) {\n return false;\n }\n\n this.isApplying = true;\n try {\n const command = this.redoStack[this.redoStack.length - 1];\n // If redo throws, the command stays on the redo stack and the error propagates.\n (command.redo ?? command.execute)();\n this.redoStack.pop();\n this.undoStack.push(command);\n return true;\n } finally {\n this.isApplying = false;\n this.notify();\n }\n }\n\n /**\n * Clears both undo and redo stacks.\n */\n public clear(): void {\n this.undoStack = [];\n this.redoStack = [];\n this.notify();\n }\n\n /**\n * Subscribes to history state changes.\n * The listener is immediately invoked with the current state.\n * Returns an unsubscribe function.\n */\n public subscribe(listener: (state: HistoryState) => void): () => void {\n this.listeners.add(listener);\n listener(this.getState());\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * Utility helper to create a Command from before/after state snapshots.\n */\n public static createSnapshotCommand<T>(\n options: SnapshotCommandOptions<T>,\n ): Command {\n return {\n name: options.name,\n tag: options.tag,\n execute: () => options.apply(options.after),\n undo: () => options.apply(options.before),\n redo: () => options.apply(options.after),\n };\n }\n}\n\n/**\n * Creates and initializes a new HistoryManager instance.\n */\nexport function createHistoryManager(\n options: HistoryManagerOptions = {},\n): HistoryManager {\n return new HistoryManager(options);\n}\n"]}