@pixerate/editor 0.21.0 → 0.22.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 (70) hide show
  1. package/README.md +20 -11
  2. package/dist/agent/index.cjs +8 -8
  3. package/dist/agent/index.d.cts +2 -2
  4. package/dist/agent/index.d.ts +2 -2
  5. package/dist/agent/index.js +2 -2
  6. package/dist/canvas/index.cjs +1269 -13
  7. package/dist/canvas/index.cjs.map +1 -1
  8. package/dist/canvas/index.d.cts +432 -6
  9. package/dist/canvas/index.d.ts +432 -6
  10. package/dist/canvas/index.js +1264 -8
  11. package/dist/canvas/index.js.map +1 -1
  12. package/dist/{chunk-U6NDQFOK.cjs → chunk-6M3BCTBD.cjs} +427 -138
  13. package/dist/chunk-6M3BCTBD.cjs.map +1 -0
  14. package/dist/{chunk-TVRC4KVT.cjs → chunk-AA4ZEVCC.cjs} +845 -90
  15. package/dist/chunk-AA4ZEVCC.cjs.map +1 -0
  16. package/dist/{chunk-LA7PEGU3.cjs → chunk-CL37JGFW.cjs} +4 -4
  17. package/dist/{chunk-LA7PEGU3.cjs.map → chunk-CL37JGFW.cjs.map} +1 -1
  18. package/dist/{chunk-A7DKIA72.js → chunk-DENSCKFC.js} +642 -69
  19. package/dist/chunk-DENSCKFC.js.map +1 -0
  20. package/dist/{chunk-4QPLEUL2.cjs → chunk-GVSMRPHB.cjs} +1046 -54
  21. package/dist/chunk-GVSMRPHB.cjs.map +1 -0
  22. package/dist/{chunk-KYRZDWM4.js → chunk-JJDLPEWL.js} +2 -2
  23. package/dist/{chunk-3TYMNULT.js → chunk-MJZBNWG7.js} +807 -52
  24. package/dist/chunk-MJZBNWG7.js.map +1 -0
  25. package/dist/{chunk-CYFJCKC4.js → chunk-QGHGJ56O.js} +997 -5
  26. package/dist/chunk-QGHGJ56O.js.map +1 -0
  27. package/dist/{chunk-GVW4P7BG.js → chunk-RU52I7QO.js} +305 -16
  28. package/dist/chunk-RU52I7QO.js.map +1 -0
  29. package/dist/{chunk-JYRIOFEN.cjs → chunk-SAZRENEZ.cjs} +647 -74
  30. package/dist/chunk-SAZRENEZ.cjs.map +1 -0
  31. package/dist/{controller-OUdxPY-X.d.ts → controller-B7GIybc4.d.cts} +139 -7
  32. package/dist/{controller-OUdxPY-X.d.cts → controller-B7GIybc4.d.ts} +139 -7
  33. package/dist/extensions/index.cjs +14 -4
  34. package/dist/extensions/index.cjs.map +1 -1
  35. package/dist/extensions/index.d.cts +3 -3
  36. package/dist/extensions/index.d.ts +3 -3
  37. package/dist/extensions/index.js +13 -3
  38. package/dist/{index-k3mFbPx_.d.cts → index-CCr_z9kF.d.cts} +3 -3
  39. package/dist/{index-Ng1pG7JE.d.ts → index-Ck6ajwXh.d.ts} +3 -3
  40. package/dist/index.cjs +250 -16
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +294 -8
  43. package/dist/index.d.ts +294 -8
  44. package/dist/index.js +241 -7
  45. package/dist/index.js.map +1 -1
  46. package/dist/{registry-iAHbqkYt.d.cts → registry-GJI4gToU.d.cts} +15 -4
  47. package/dist/{registry-iAHbqkYt.d.ts → registry-GJI4gToU.d.ts} +15 -4
  48. package/dist/serializers/index.cjs +3 -3
  49. package/dist/serializers/index.d.cts +1 -1
  50. package/dist/serializers/index.d.ts +1 -1
  51. package/dist/serializers/index.js +2 -2
  52. package/dist/spreadsheet/index.cjs +7 -3
  53. package/dist/spreadsheet/index.cjs.map +1 -1
  54. package/dist/spreadsheet/index.d.cts +33 -4
  55. package/dist/spreadsheet/index.d.ts +33 -4
  56. package/dist/spreadsheet/index.js +6 -2
  57. package/dist/{track-changes-G8JcWB8b.d.cts → track-changes-BSF58RK0.d.cts} +92 -9
  58. package/dist/{track-changes-G8JcWB8b.d.ts → track-changes-BSF58RK0.d.ts} +92 -9
  59. package/dist/{types-DMe0vFB1.d.cts → types-CJ4U-HHI.d.cts} +1 -1
  60. package/dist/{types-DMe0vFB1.d.ts → types-CJ4U-HHI.d.ts} +1 -1
  61. package/package.json +1 -1
  62. package/dist/chunk-3TYMNULT.js.map +0 -1
  63. package/dist/chunk-4QPLEUL2.cjs.map +0 -1
  64. package/dist/chunk-A7DKIA72.js.map +0 -1
  65. package/dist/chunk-CYFJCKC4.js.map +0 -1
  66. package/dist/chunk-GVW4P7BG.js.map +0 -1
  67. package/dist/chunk-JYRIOFEN.cjs.map +0 -1
  68. package/dist/chunk-TVRC4KVT.cjs.map +0 -1
  69. package/dist/chunk-U6NDQFOK.cjs.map +0 -1
  70. /package/dist/{chunk-KYRZDWM4.js.map → chunk-JJDLPEWL.js.map} +0 -0
@@ -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","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/arrangeShortcuts.ts","../../src/canvas/arrangeController.ts"],"names":["next","best"],"mappings":"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","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","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst dx = sx.delta ?? 0;\n\tconst dy = sy.delta ?? 0;\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","import { isEventFromTextInput } from './clipboard.js';\n\n/**\n * Canvas arrangement actions that have keyboard shortcuts.\n */\nexport type CanvasArrangeAction =\n\t| 'alignLeft'\n\t| 'alignCenter'\n\t| 'alignRight'\n\t| 'alignTop'\n\t| 'alignMiddle'\n\t| 'alignBottom'\n\t| 'distributeHorizontal'\n\t| 'distributeVertical'\n\t| 'lock'\n\t| 'unlock'\n\t| 'bringForward'\n\t| 'sendBackward'\n\t| 'bringToFront'\n\t| 'sendToBack'\n\t| 'nextScene'\n\t| 'previousScene';\n\nexport interface ArrangeShortcutBinding {\n\taction: CanvasArrangeAction;\n\t/** `KeyboardEvent.code` (layout- and Option-independent, e.g. `KeyA`, `BracketRight`). */\n\tcode: string;\n\tmod?: boolean;\n\talt?: boolean;\n\tshift?: boolean;\n\t/** Human readable label, e.g. for menus. `Mod` is ⌘ on macOS and Ctrl elsewhere. */\n\tlabel: string;\n}\n\n/**\n * Default bindings. Align/distribute follow Figma (Option/Alt + letter), layer order follows\n * the common `Mod+]` / `Mod+[` convention, lock follows Keynote/Freeform (`Mod+L`,\n * `Mod+Alt+L` to unlock) and scenes step with PageDown / PageUp.\n */\nexport const DEFAULT_ARRANGE_SHORTCUTS: readonly ArrangeShortcutBinding[] = [\n\t{ action: 'alignLeft', code: 'KeyA', alt: true, label: 'Alt+A' },\n\t{ action: 'alignCenter', code: 'KeyH', alt: true, label: 'Alt+H' },\n\t{ action: 'alignRight', code: 'KeyD', alt: true, label: 'Alt+D' },\n\t{ action: 'alignTop', code: 'KeyW', alt: true, label: 'Alt+W' },\n\t{ action: 'alignMiddle', code: 'KeyV', alt: true, label: 'Alt+V' },\n\t{ action: 'alignBottom', code: 'KeyS', alt: true, label: 'Alt+S' },\n\t{ action: 'distributeHorizontal', code: 'KeyH', alt: true, shift: true, label: 'Alt+Shift+H' },\n\t{ action: 'distributeVertical', code: 'KeyV', alt: true, shift: true, label: 'Alt+Shift+V' },\n\t{ action: 'lock', code: 'KeyL', mod: true, label: 'Mod+L' },\n\t{ action: 'unlock', code: 'KeyL', mod: true, alt: true, label: 'Mod+Alt+L' },\n\t{ action: 'bringForward', code: 'BracketRight', mod: true, label: 'Mod+]' },\n\t{ action: 'sendBackward', code: 'BracketLeft', mod: true, label: 'Mod+[' },\n\t{ action: 'bringToFront', code: 'BracketRight', mod: true, alt: true, label: 'Mod+Alt+]' },\n\t{ action: 'sendToBack', code: 'BracketLeft', mod: true, alt: true, label: 'Mod+Alt+[' },\n\t{ action: 'nextScene', code: 'PageDown', label: 'PageDown' },\n\t{ action: 'previousScene', code: 'PageUp', label: 'PageUp' }\n];\n\nconst KEY_TO_CODE: Record<string, string> = {\n\t']': 'BracketRight',\n\t'[': 'BracketLeft',\n\tPageDown: 'PageDown',\n\tPageUp: 'PageUp'\n};\n\nfunction eventCode(event: KeyboardEvent): string {\n\tif (event.code) return event.code;\n\t// Synthetic events (and some test environments) omit `code`; derive it from `key`.\n\tif (KEY_TO_CODE[event.key]) return KEY_TO_CODE[event.key];\n\tif (/^[a-z]$/i.test(event.key)) return `Key${event.key.toUpperCase()}`;\n\treturn event.key;\n}\n\n/**\n * Resolves a keydown event to an arrange action, or `null`. Events from text inputs,\n * textareas, selects, contenteditable and ProseMirror are ignored (see `isEventFromTextInput`).\n * `Mod` is ⌘ or Ctrl.\n */\nexport function matchArrangeShortcut(\n\tevent: KeyboardEvent,\n\tbindings: readonly ArrangeShortcutBinding[] = DEFAULT_ARRANGE_SHORTCUTS\n): CanvasArrangeAction | null {\n\tif (isEventFromTextInput(event)) return null;\n\tconst code = eventCode(event);\n\tconst mod = event.metaKey || event.ctrlKey;\n\tfor (const binding of bindings) {\n\t\tif (\n\t\t\tbinding.code === code &&\n\t\t\t!!binding.mod === mod &&\n\t\t\t!!binding.alt === event.altKey &&\n\t\t\t!!binding.shift === event.shiftKey\n\t\t) {\n\t\t\treturn binding.action;\n\t\t}\n\t}\n\treturn null;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/** A React Flow / SvelteFlow style node change; only `position` changes are inspected. */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter((c) => c.type === 'position' && c.position && c.dragging);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (changes.some((c) => c.type === 'position' && c.dragging === false)) controller.endDrag();\n\t\t\t\treturn changes;\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position!]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) =>\n\t\t\t\tc.type === 'position' && c.position && c.dragging && shift.has(c.id)\n\t\t\t\t\t? { ...c, position: { x: c.position.x + dx, y: c.position.y + dy } }\n\t\t\t\t\t: c\n\t\t\t);\n\t\t},\n\n\t\t/** Clears guides and the cached snap targets. */\n\t\tendDrag() {\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tsetGuides([]);\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\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","../../src/canvas/arrange.ts","../../src/canvas/guides.ts","../../src/canvas/connectors.ts","../../src/canvas/scenes.ts","../../src/canvas/arrangeShortcuts.ts","../../src/canvas/polygonBoolean.ts","../../src/canvas/shapeGeometry.ts","../../src/canvas/shapeNodes.ts","../../src/canvas/arrangeController.ts"],"names":["next","best","defaultId"],"mappings":"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;AAKP,EAAA;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACG,EAAA;AACV;AAKgB;AAMT,EAAA;AACA,EAAA;AACA,EAAA;AAGA,EAAA;AACC,EAAA;AACR;AViyCY;AACA;AWzlDC;AACF,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACX;AAEM;AACA,EAAA;AACA,EAAA;AACK,EAAA;AACF,EAAA;AACT;AAES;AACE,EAAA;AAEN,EAAA;AACA,EAAA;AACG,EAAA;AACR;AAOgB;AAIX,EAAA;AACE,EAAA;AACA,EAAA;AACN,EAAA;AAEE,IAAA;AAKA,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AX0kDY;AACA;AYjmDI;AACL,EAAA;AACA,EAAA;AACD,EAAA;AACD,IAAA;AACA,IAAA;AACA,IAAA;AACR,EAAA;AACO,EAAA;AACR;AAGgB;AACX,EAAA;AACA,EAAA;AACK,EAAA;AACF,EAAA;AACR;AAGgB;AACX,EAAA;AACJ,EAAA;AACO,EAAA;AACR;AAGgB;AAGX,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACE,EAAA;AACL,IAAA;AACK,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACL,IAAA;AACD,EAAA;AACA,EAAA;AACK,IAAA;AACH,MAAA;AACM,IAAA;AACA,MAAA;AACP,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAMgB;AAKR,EAAA;AACH,EAAA;AACJ,EAAA;AACO,IAAA;AACN,IAAA;AACO,MAAA;AACA,MAAA;AACF,MAAA;AACC,QAAA;AACL,MAAA;AACK,QAAA;AACL,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAGgB;AACR,EAAA;AACR;AAMS;AACF,EAAA;AACN,EAAA;AACM,IAAA;AACC,IAAA;AACF,IAAA;AACA,IAAA;AACL,EAAA;AACO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A,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,MAAA;AACJ,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACK,MAAA;AACJ,QAAA;AACC,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;AAMS;AACF,EAAA;AACC,EAAA;AACR;AAES;AAaF,EAAA;AACA,IAAA;AACA,IAAA;AACN,EAAA;AACU,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACD;AAES;AAUF,EAAA;AACI,EAAA;AACD,EAAA;AACF,IAAA;AACA,IAAA;AACF,IAAA;AACL,EAAA;AACD;AAES;AACJ,EAAA;AACE,EAAA;AACC,EAAA;AACR;AAGS;AAOD,EAAA;AACG,EAAA;AACF,EAAA;AACJ,EAAA;AACA,EAAA;AACC,IAAA;AACJ,IAAA;AACD,EAAA;AACM,EAAA;AACA,EAAA;AAC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K,IAAA;AACA,MAAA;AACE,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACF,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACI,MAAA;AACH,QAAA;AACA,QAAA;AACD,MAAA;AACA,MAAA;AACA,MAAA;AACD,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACO,MAAA;AACJ,QAAA;AAEF,MAAA;AACI,MAAA;AACC,QAAA;AAGJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AAEI,QAAA;AACJ,QAAA;AACD,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACD,MAAA;AACC,MAAA;AACA,MAAA;AACA,MAAA;AACF,QAAA;AACA,QAAA;AACH,QAAA;AACA,QAAA;AACA,MAAA;AACD,MAAA;AACM,MAAA;AACN,MAAA;AACI,MAAA;AAEE,MAAA;AACA,MAAA;AACN,MAAA;AACK,QAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACA,MAAA;AACF,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,IAAA;AACO,MAAA;AACN,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACI,MAAA;AACE,MAAA;AACA,MAAA;AACL,QAAA;AACA,QAAA;AACA,MAAA;AACG,MAAA;AACH,QAAA;AACD,MAAA;AACD,IAAA;AACD,EAAA;AACO,EAAA;AACR;Af0uFY;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;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;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","import { boundsOf, estimateNodeSize } from './layout.js';\nimport type { CanvasNode, CanvasNodeStyle, Rect, XYPosition } from './types.js';\n\n/**\n * Edge (or centre line) used by `alignNodes`.\n * `center` aligns horizontal centres (a vertical line); `middle` aligns vertical centres.\n */\nexport type AlignEdge = 'left' | 'center' | 'right' | 'top' | 'middle' | 'bottom';\n\n/** Axis used by `distributeNodes`. `both` distributes horizontally and vertically. */\nexport type DistributeAxis = 'horizontal' | 'vertical' | 'both';\n\n/**\n * `gaps` leaves equal space between neighbouring nodes, `centres` spaces their centres equally.\n */\nexport type DistributeMode = 'gaps' | 'centres';\n\n/** Dimension(s) copied by `matchSize`. */\nexport type SizeDimension = 'width' | 'height' | 'both';\n\n/**\n * Partial node change produced by the arrange helpers. `position` is in the node's own\n * coordinate space (relative to its `parentId` parent when it has one).\n */\nexport interface CanvasNodeUpdate {\n\tid: string;\n\tposition?: XYPosition;\n\twidth?: number;\n\theight?: number;\n\tzIndex?: number;\n}\n\nexport interface NodeRectOptions {\n\t/** Width used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultWidth?: number;\n\t/** Height used when a node has no measured, explicit or style size and estimation is off. */\n\tdefaultHeight?: number;\n\t/** Estimate unmeasured node sizes from their label via `estimateNodeSize` (default `true`). */\n\testimate?: boolean;\n}\n\n/** Data key marking a node as locked. See `lockNodes`. */\nexport const LOCKED_DATA_KEY = 'locked';\n/** Data key holding the interaction flags a node had before it was locked. */\nexport const LOCKED_PREVIOUS_FLAGS_KEY = 'lockedPrevious';\n\nfunction parsePx(value: unknown): number | undefined {\n\tif (typeof value === 'number' && Number.isFinite(value)) return value;\n\tif (typeof value === 'string') {\n\t\tconst match = /^\\s*(-?\\d+(?:\\.\\d+)?)(px)?\\s*$/i.exec(value);\n\t\tif (match) return Number(match[1]);\n\t}\n\treturn undefined;\n}\n\n/**\n * Reads a pixel `width` / `height` from a node style, which may be a CSS string (SvelteFlow)\n * or a style object (React Flow). Non-pixel values (`%`, `auto`, ...) are ignored.\n */\nexport function readStyleSize(style: CanvasNodeStyle | undefined | null): {\n\twidth?: number;\n\theight?: number;\n} {\n\tif (!style) return {};\n\tif (typeof style === 'object') {\n\t\treturn { width: parsePx(style.width), height: parsePx(style.height) };\n\t}\n\tconst read = (prop: string) => {\n\t\tconst match = new RegExp(`(?:^|;)\\\\s*${prop}\\\\s*:\\\\s*([^;]+)`, 'i').exec(style);\n\t\treturn match ? parsePx(match[1]) : undefined;\n\t};\n\treturn { width: read('width'), height: read('height') };\n}\n\n/**\n * Returns `style` with its pixel `width` / `height` replaced, keeping its representation\n * (string or object). Only dimensions already present in the style are rewritten, because\n * xyflow applies the node's own `width` / `height` props otherwise.\n */\nexport function withStyleSize(\n\tstyle: CanvasNodeStyle | undefined | null,\n\tsize: { width?: number; height?: number }\n): CanvasNodeStyle | undefined {\n\tif (!style) return style ?? undefined;\n\tif (typeof style === 'object') {\n\t\tconst next: Record<string, any> = { ...style };\n\t\tif (size.width !== undefined && 'width' in next) next.width = size.width;\n\t\tif (size.height !== undefined && 'height' in next) next.height = size.height;\n\t\treturn next;\n\t}\n\tlet next = style;\n\tfor (const prop of ['width', 'height'] as const) {\n\t\tconst value = size[prop];\n\t\tif (value === undefined) continue;\n\t\tnext = next.replace(\n\t\t\tnew RegExp(`((?:^|;)\\\\s*${prop}\\\\s*:\\\\s*)[^;]+`, 'i'),\n\t\t\t`$1${value}px`\n\t\t);\n\t}\n\treturn next;\n}\n\n/**\n * Resolves a node's size: measured DOM size, then explicit `width` / `height`, then a pixel\n * size in `style`, then a label-based estimate (or the defaults when `estimate` is `false`).\n */\nexport function getNodeSize(\n\tnode: CanvasNode,\n\toptions: NodeRectOptions = {}\n): { width: number; height: number } {\n\tconst styleSize = readStyleSize(node.style);\n\tlet width = node.measured?.width ?? node.width ?? styleSize.width;\n\tlet height = node.measured?.height ?? node.height ?? styleSize.height;\n\tif (width === undefined || height === undefined) {\n\t\tconst fallback =\n\t\t\toptions.estimate === false\n\t\t\t\t? { width: options.defaultWidth ?? 200, height: options.defaultHeight ?? 60 }\n\t\t\t\t: estimateNodeSize(node, options.defaultWidth, options.defaultHeight);\n\t\twidth ??= fallback.width;\n\t\theight ??= fallback.height;\n\t}\n\treturn { width, height };\n}\n\n/**\n * Absolute (flow-space) position of a node, following its `parentId` chain.\n * Cycles and missing parents are tolerated.\n */\nexport function getAbsolutePosition(\n\tnode: CanvasNode,\n\tlookup: ReadonlyMap<string, CanvasNode>\n): XYPosition {\n\tlet x = node.position.x;\n\tlet y = node.position.y;\n\tconst seen = new Set<string>([node.id]);\n\tlet parentId = node.parentId;\n\twhile (parentId && !seen.has(parentId)) {\n\t\tconst parent = lookup.get(parentId);\n\t\tif (!parent) break;\n\t\tseen.add(parentId);\n\t\tx += parent.position.x;\n\t\ty += parent.position.y;\n\t\tparentId = parent.parentId;\n\t}\n\treturn { x, y };\n}\n\n/** Builds an id lookup for a node array. */\nexport function createNodeLookup<TNode extends CanvasNode>(nodes: TNode[]): Map<string, TNode> {\n\treturn new Map(nodes.map((n) => [n.id, n]));\n}\n\n/** Absolute flow-space rectangle of a node. */\nexport function getNodeRect(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions?: NodeRectOptions\n): Rect {\n\tconst { x, y } = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\treturn { x, y, ...getNodeSize(node, options) };\n}\n\n/**\n * Whether a node has been locked with `lockNodes`.\n */\nexport function isLocked(node: CanvasNode | undefined | null): boolean {\n\treturn node?.data?.[LOCKED_DATA_KEY] === true;\n}\n\n/**\n * Locks nodes: marks `data.locked = true` and sets `draggable: false` and `deletable: false`\n * so both SvelteFlow and React Flow stop dragging and deleting them. Nodes stay `selectable`\n * so they can be unlocked again. The previous flags are kept in `data.lockedPrevious` and\n * restored by `unlockNodes`. Returns a new array; untouched nodes keep their identity.\n */\nexport function lockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || isLocked(node)) return node;\n\t\tconst previous: Record<string, boolean> = {};\n\t\tif (node.draggable !== undefined) previous.draggable = node.draggable;\n\t\tif (node.deletable !== undefined) previous.deletable = node.deletable;\n\t\treturn {\n\t\t\t...node,\n\t\t\tdraggable: false,\n\t\t\tdeletable: false,\n\t\t\tdata: { ...node.data, [LOCKED_DATA_KEY]: true, [LOCKED_PREVIOUS_FLAGS_KEY]: previous }\n\t\t};\n\t});\n}\n\n/**\n * Reverses `lockNodes`, restoring the `draggable` / `deletable` flags the node had before.\n */\nexport function unlockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\treturn nodes.map((node) => {\n\t\tif (!idSet.has(node.id) || !isLocked(node)) return node;\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: previous = {},\n\t\t\t...data\n\t\t} = node.data as Record<string, any>;\n\t\tconst next = { ...node, data } as TNode;\n\t\tdelete next.draggable;\n\t\tdelete next.deletable;\n\t\tif (previous.draggable !== undefined) next.draggable = previous.draggable;\n\t\tif (previous.deletable !== undefined) next.deletable = previous.deletable;\n\t\treturn next;\n\t});\n}\n\n/** Locks the given nodes if any of them is unlocked, otherwise unlocks them all. */\nexport function toggleLockNodes<TNode extends CanvasNode>(nodes: TNode[], ids: Iterable<string>): TNode[] {\n\tconst idSet = new Set(ids);\n\tconst anyUnlocked = nodes.some((n) => idSet.has(n.id) && !isLocked(n));\n\treturn anyUnlocked ? lockNodes(nodes, idSet) : unlockNodes(nodes, idSet);\n}\n\ninterface Item {\n\tnode: CanvasNode;\n\trect: Rect;\n\tlocked: boolean;\n}\n\nfunction collect(nodes: CanvasNode[], ids: Iterable<string>, options?: NodeRectOptions) {\n\tconst lookup = createNodeLookup(nodes);\n\tconst items: Item[] = [];\n\tconst seen = new Set<string>();\n\tfor (const id of ids) {\n\t\tconst node = lookup.get(id);\n\t\tif (!node || seen.has(id)) continue;\n\t\tseen.add(id);\n\t\titems.push({ node, rect: getNodeRect(node, lookup, options), locked: isLocked(node) });\n\t}\n\treturn { lookup, items };\n}\n\n/**\n * Converts absolute target positions into node-relative `position` updates. When a parent\n * moves in the same operation, children are made relative to the parent's new position.\n */\nfunction toPositionUpdates(\n\tlookup: Map<string, CanvasNode>,\n\ttargets: Map<string, XYPosition>\n): CanvasNodeUpdate[] {\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const [id, abs] of targets) {\n\t\tconst node = lookup.get(id)!;\n\t\tlet position = abs;\n\t\tif (node.parentId && lookup.has(node.parentId)) {\n\t\t\tconst parent = lookup.get(node.parentId)!;\n\t\t\tconst parentAbs = targets.get(parent.id) ?? getAbsolutePosition(parent, lookup);\n\t\t\tposition = { x: abs.x - parentAbs.x, y: abs.y - parentAbs.y };\n\t\t}\n\t\tif (position.x !== node.position.x || position.y !== node.position.y) {\n\t\t\tupdates.push({ id, position });\n\t\t}\n\t}\n\treturn updates;\n}\n\nexport interface AlignNodesOptions extends NodeRectOptions {\n\t/**\n\t * What to align to: the selection bounding box (default) or a fixed rectangle.\n\t * Locked nodes never move but still count towards the selection bounds, so they act as anchors.\n\t */\n\tto?: 'selection' | Rect;\n}\n\n/**\n * Aligns nodes to a shared edge or centre line of their bounding box (Freeform's\n * Align Left / Center / Right / Top / Middle / Bottom). Needs at least two nodes unless\n * `options.to` is a rectangle. Locked nodes are not moved.\n */\nexport function alignNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tedge: AlignEdge,\n\toptions: AlignNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst fixed = typeof options.to === 'object' ? options.to : null;\n\tif (items.length === 0 || (!fixed && items.length < 2)) return [];\n\tconst bounds = fixed ?? boundsOf(items.map((i) => i.rect))!;\n\n\tconst targets = new Map<string, XYPosition>();\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tlet { x, y } = rect;\n\t\tswitch (edge) {\n\t\t\tcase 'left':\n\t\t\t\tx = bounds.x;\n\t\t\t\tbreak;\n\t\t\tcase 'center':\n\t\t\t\tx = bounds.x + bounds.width / 2 - rect.width / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'right':\n\t\t\t\tx = bounds.x + bounds.width - rect.width;\n\t\t\t\tbreak;\n\t\t\tcase 'top':\n\t\t\t\ty = bounds.y;\n\t\t\t\tbreak;\n\t\t\tcase 'middle':\n\t\t\t\ty = bounds.y + bounds.height / 2 - rect.height / 2;\n\t\t\t\tbreak;\n\t\t\tcase 'bottom':\n\t\t\t\ty = bounds.y + bounds.height - rect.height;\n\t\t\t\tbreak;\n\t\t}\n\t\ttargets.set(node.id, { x, y });\n\t}\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface DistributeNodesOptions extends NodeRectOptions {\n\t/** Explicit gap between neighbours for `gaps` mode. Default: spread evenly between the outer nodes. */\n\tgap?: number;\n}\n\nfunction distributeAxis(\n\titems: Item[],\n\thorizontal: boolean,\n\tmode: DistributeMode,\n\tgapOverride: number | undefined,\n\ttargets: Map<string, XYPosition>\n) {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst size = (r: Rect) => (horizontal ? r.width : r.height);\n\tconst sorted = [...items].sort(\n\t\t(a, b) => start(a.rect) + size(a.rect) / 2 - (start(b.rect) + size(b.rect) / 2)\n\t);\n\tconst first = sorted[0].rect;\n\tconst last = sorted[sorted.length - 1].rect;\n\tconst n = sorted.length;\n\tconst set = (item: Item, value: number) => {\n\t\tif (item.locked) return;\n\t\tconst current = targets.get(item.node.id) ?? { x: item.rect.x, y: item.rect.y };\n\t\ttargets.set(item.node.id, horizontal ? { ...current, x: value } : { ...current, y: value });\n\t};\n\n\tif (mode === 'centres') {\n\t\tconst c0 = start(first) + size(first) / 2;\n\t\tconst c1 = start(last) + size(last) / 2;\n\t\tconst step = (c1 - c0) / (n - 1);\n\t\tsorted.forEach((item, i) => set(item, c0 + step * i - size(item.rect) / 2));\n\t\treturn;\n\t}\n\n\tconst span = start(last) + size(last) - start(first);\n\tconst total = sorted.reduce((sum, item) => sum + size(item.rect), 0);\n\tconst gap = gapOverride ?? (span - total) / (n - 1);\n\tlet cursor = start(first);\n\tfor (const item of sorted) {\n\t\tset(item, cursor);\n\t\tcursor += size(item.rect) + gap;\n\t}\n}\n\n/**\n * Distributes nodes along an axis (Freeform's Distribute Horizontally / Vertically / Evenly).\n * The outermost nodes stay put and the others are spaced with equal gaps (`gaps`, default)\n * or equally spaced centres (`centres`). Needs at least three nodes (two with an explicit\n * `gap`). Locked nodes keep their place in the order but are not moved.\n */\nexport function distributeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\taxis: DistributeAxis,\n\tmode: DistributeMode = 'gaps',\n\toptions: DistributeNodesOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { lookup, items } = collect(nodes, ids, options);\n\tconst minimum = options.gap !== undefined && mode === 'gaps' ? 2 : 3;\n\tif (items.length < minimum) return [];\n\tconst targets = new Map<string, XYPosition>();\n\tif (axis !== 'vertical') distributeAxis(items, true, mode, options.gap, targets);\n\tif (axis !== 'horizontal') distributeAxis(items, false, mode, options.gap, targets);\n\treturn toPositionUpdates(lookup, targets);\n}\n\nexport interface MatchSizeOptions extends NodeRectOptions {\n\t/**\n\t * Size source: the `largest` (default) or `smallest` node, the `first` id given,\n\t * or a specific node id.\n\t */\n\treference?: 'largest' | 'smallest' | 'first' | { id: string };\n\t/** Keep each node's centre in place instead of its top-left corner (default `false`). */\n\tkeepCentre?: boolean;\n}\n\n/**\n * Makes nodes the same width, height or both (Freeform's Make Same Size). Returns size\n * updates (plus position updates with `keepCentre`). Locked nodes are not resized but can\n * be the reference.\n */\nexport function matchSize(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\tdimension: SizeDimension,\n\toptions: MatchSizeOptions = {}\n): CanvasNodeUpdate[] {\n\tconst { items } = collect(nodes, ids, options);\n\tif (items.length < 2) return [];\n\tconst reference = options.reference ?? 'largest';\n\n\tconst pick = (dim: 'width' | 'height'): number => {\n\t\tif (typeof reference === 'object') {\n\t\t\tconst item = items.find((i) => i.node.id === reference.id);\n\t\t\tif (item) return item.rect[dim];\n\t\t}\n\t\tif (reference === 'first') return items[0].rect[dim];\n\t\tconst values = items.map((i) => i.rect[dim]);\n\t\treturn reference === 'smallest' ? Math.min(...values) : Math.max(...values);\n\t};\n\tconst width = dimension !== 'height' ? pick('width') : undefined;\n\tconst height = dimension !== 'width' ? pick('height') : undefined;\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const { node, rect, locked } of items) {\n\t\tif (locked) continue;\n\t\tconst update: CanvasNodeUpdate = { id: node.id };\n\t\tif (width !== undefined && width !== rect.width) update.width = width;\n\t\tif (height !== undefined && height !== rect.height) update.height = height;\n\t\tif (update.width === undefined && update.height === undefined) continue;\n\t\tif (options.keepCentre) {\n\t\t\tupdate.position = {\n\t\t\t\tx: node.position.x - ((update.width ?? rect.width) - rect.width) / 2,\n\t\t\t\ty: node.position.y - ((update.height ?? rect.height) - rect.height) / 2\n\t\t\t};\n\t\t}\n\t\tupdates.push(update);\n\t}\n\treturn updates;\n}\n\n/** Z-order operation. */\nexport type ZOrderOperation = 'bringForward' | 'sendBackward' | 'bringToFront' | 'sendToBack';\n\n/**\n * Restacks nodes (Bring Forward / Send Backward / Bring to Front / Send to Back).\n *\n * Both SvelteFlow and React Flow stack nodes by `zIndex` (missing = 0) and, for equal\n * `zIndex`, by array order. This computes that effective order for the siblings that share\n * the targets' parent, applies the operation, and returns `zIndex` updates assigning each\n * sibling its new rank. The array order is left alone, because xyflow requires parents to\n * precede their children. Locked nodes are not restacked. \"Forward\" moves past the next\n * sibling the node overlaps, falling back to the next sibling when nothing overlaps.\n */\nexport function reorderNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>,\n\toperation: ZOrderOperation,\n\toptions: NodeRectOptions = {}\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst targetIds = new Set(\n\t\t[...ids].filter((id) => lookup.has(id) && !isLocked(lookup.get(id)))\n\t);\n\tif (targetIds.size === 0) return [];\n\n\t// Restack each sibling group (nodes sharing a parent) independently.\n\tconst groups = new Map<string, CanvasNode[]>();\n\tfor (const node of nodes) {\n\t\tconst key = node.parentId ?? '';\n\t\tif (!groups.has(key)) groups.set(key, []);\n\t\tgroups.get(key)!.push(node);\n\t}\n\n\tconst updates: CanvasNodeUpdate[] = [];\n\tfor (const siblings of groups.values()) {\n\t\tif (!siblings.some((n) => targetIds.has(n.id))) continue;\n\t\tconst order = siblings\n\t\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t\t.map((entry) => entry.node);\n\t\tconst isTarget = (n: CanvasNode) => targetIds.has(n.id);\n\t\tlet next: CanvasNode[];\n\n\t\tif (operation === 'bringToFront') {\n\t\t\tnext = [...order.filter((n) => !isTarget(n)), ...order.filter(isTarget)];\n\t\t} else if (operation === 'sendToBack') {\n\t\t\tnext = [...order.filter(isTarget), ...order.filter((n) => !isTarget(n))];\n\t\t} else {\n\t\t\tnext = [...order];\n\t\t\tconst forward = operation === 'bringForward';\n\t\t\tconst rects = new Map(next.map((n) => [n.id, getNodeRect(n, lookup, options)]));\n\t\t\tconst intersects = (a: Rect, b: Rect) =>\n\t\t\t\ta.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;\n\t\t\t// Walk from the end we are moving towards so adjacent targets keep their relative order.\n\t\t\tconst indices = next.map((_, i) => i);\n\t\t\tif (forward) indices.reverse();\n\t\t\tfor (const i of indices) {\n\t\t\t\tconst node = next[i];\n\t\t\t\tif (!isTarget(node)) continue;\n\t\t\t\tconst step = forward ? 1 : -1;\n\t\t\t\tlet j = i + step;\n\t\t\t\tlet swapWith = -1;\n\t\t\t\tlet firstNonTarget = -1;\n\t\t\t\twhile (j >= 0 && j < next.length) {\n\t\t\t\t\tif (!isTarget(next[j])) {\n\t\t\t\t\t\tif (firstNonTarget === -1) firstNonTarget = j;\n\t\t\t\t\t\tif (intersects(rects.get(node.id)!, rects.get(next[j].id)!)) {\n\t\t\t\t\t\t\tswapWith = j;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tj += step;\n\t\t\t\t}\n\t\t\t\tif (swapWith === -1) swapWith = firstNonTarget;\n\t\t\t\tif (swapWith === -1) continue;\n\t\t\t\tnext.splice(i, 1);\n\t\t\t\tnext.splice(swapWith, 0, node);\n\t\t\t}\n\t\t}\n\n\t\tif (next.every((node, i) => node === order[i])) continue;\n\t\tnext.forEach((node, rank) => {\n\t\t\tconst current = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\t\tif (current !== rank) updates.push({ id: node.id, zIndex: rank });\n\t\t});\n\t}\n\treturn updates;\n}\n\n/**\n * Applies `CanvasNodeUpdate`s to a node array, returning a new array. Unchanged nodes keep\n * their identity. Size updates set `width` / `height` and rewrite pixel sizes already present\n * in `style` (string or object). An existing `measured` size is updated too, so geometry\n * helpers see the new size before xyflow re-measures the node.\n */\nexport function applyNodeUpdates<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tupdates: CanvasNodeUpdate[]\n): TNode[] {\n\tif (updates.length === 0) return nodes;\n\tconst byId = new Map<string, CanvasNodeUpdate>();\n\tfor (const update of updates) {\n\t\tbyId.set(update.id, { ...byId.get(update.id), ...update });\n\t}\n\treturn nodes.map((node) => {\n\t\tconst update = byId.get(node.id);\n\t\tif (!update) return node;\n\t\tconst next = { ...node } as TNode;\n\t\tif (update.position) next.position = { ...update.position };\n\t\tif (update.zIndex !== undefined) (next as CanvasNode).zIndex = update.zIndex;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tif (update.width !== undefined) next.width = update.width;\n\t\t\tif (update.height !== undefined) next.height = update.height;\n\t\t\tif (node.style) next.style = withStyleSize(node.style, update);\n\t\t\tif (node.measured) {\n\t\t\t\tnext.measured = {\n\t\t\t\t\t...node.measured,\n\t\t\t\t\t...(update.width !== undefined ? { width: update.width } : {}),\n\t\t\t\t\t...(update.height !== undefined ? { height: update.height } : {})\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\treturn next;\n\t});\n}\n\n/**\n * Inverse of a set of updates against the current nodes, for undo. Restores the previous\n * position / size / zIndex of every node an update touches.\n */\nexport function invertNodeUpdates(\n\tnodes: CanvasNode[],\n\tupdates: CanvasNodeUpdate[]\n): CanvasNodeUpdate[] {\n\tconst lookup = createNodeLookup(nodes);\n\tconst inverse: CanvasNodeUpdate[] = [];\n\tfor (const update of updates) {\n\t\tconst node = lookup.get(update.id);\n\t\tif (!node) continue;\n\t\tconst back: CanvasNodeUpdate = { id: node.id };\n\t\tif (update.position) back.position = { ...node.position };\n\t\tif (update.zIndex !== undefined) back.zIndex = typeof node.zIndex === 'number' ? node.zIndex : 0;\n\t\tif (update.width !== undefined || update.height !== undefined) {\n\t\t\tconst size = getNodeSize(node);\n\t\t\tif (update.width !== undefined) back.width = size.width;\n\t\t\tif (update.height !== undefined) back.height = size.height;\n\t\t}\n\t\tinverse.push(back);\n\t}\n\treturn inverse;\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type CanvasNodeUpdate, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect, XYPosition } from './types.js';\n\n/**\n * A guide line to render while dragging, in flow coordinates.\n * `edge` and `centre` guides are alignment lines; `spacing` guides mark equal gaps.\n */\nexport interface GuideLine {\n\torientation: 'vertical' | 'horizontal';\n\tkind: 'edge' | 'centre' | 'spacing';\n\tx1: number;\n\ty1: number;\n\tx2: number;\n\ty2: number;\n}\n\nexport interface SnapGuidesOptions {\n\t/** Current viewport zoom; the threshold is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** Snap distance in screen pixels (default 6). */\n\tthreshold?: number;\n\t/** Snap edges to other shapes' edges (default `true`). */\n\tedges?: boolean;\n\t/** Snap centres to other shapes' centres (default `true`). */\n\tcentres?: boolean;\n\t/** Snap to equal spacing between neighbouring shapes in the same row / column (default `true`). */\n\tspacing?: boolean;\n}\n\nexport interface SnapResult {\n\t/** Snapped top-left of the moving rectangle. */\n\tposition: XYPosition;\n\t/** Offset applied to the input rectangle (`position - rect`). */\n\toffset: XYPosition;\n\t/** Guide segments to draw for the snap that was applied. */\n\tguides: GuideLine[];\n\tsnappedX: boolean;\n\tsnappedY: boolean;\n}\n\ninterface AxisEntries {\n\tvalues: Float64Array;\n\trects: Rect[];\n}\n\nfunction buildAxis(entries: { value: number; rect: Rect }[]): AxisEntries {\n\tentries.sort((a, b) => a.value - b.value);\n\treturn {\n\t\tvalues: Float64Array.from(entries.map((e) => e.value)),\n\t\trects: entries.map((e) => e.rect)\n\t};\n}\n\n/** First index whose value is >= `v`. */\nfunction lowerBound(values: Float64Array, v: number): number {\n\tlet lo = 0;\n\tlet hi = values.length;\n\twhile (lo < hi) {\n\t\tconst mid = (lo + hi) >>> 1;\n\t\tif (values[mid] < v) lo = mid + 1;\n\t\telse hi = mid;\n\t}\n\treturn lo;\n}\n\n/**\n * Pre-sorted snap targets for a set of static rectangles. Build it once when a drag starts\n * and reuse it on every pointer move; each query is a binary search, so snapping stays\n * cheap with hundreds or thousands of shapes.\n */\nexport interface GuideIndex {\n\trects: Rect[];\n\tedgeX: AxisEntries;\n\tcentreX: AxisEntries;\n\tedgeY: AxisEntries;\n\tcentreY: AxisEntries;\n}\n\n/** Builds a `GuideIndex` from the rectangles the moving shape may snap to. */\nexport function createGuideIndex(rects: Rect[]): GuideIndex {\n\tconst edgeX: { value: number; rect: Rect }[] = [];\n\tconst centreX: { value: number; rect: Rect }[] = [];\n\tconst edgeY: { value: number; rect: Rect }[] = [];\n\tconst centreY: { value: number; rect: Rect }[] = [];\n\tfor (const rect of rects) {\n\t\tedgeX.push({ value: rect.x, rect }, { value: rect.x + rect.width, rect });\n\t\tcentreX.push({ value: rect.x + rect.width / 2, rect });\n\t\tedgeY.push({ value: rect.y, rect }, { value: rect.y + rect.height, rect });\n\t\tcentreY.push({ value: rect.y + rect.height / 2, rect });\n\t}\n\treturn {\n\t\trects,\n\t\tedgeX: buildAxis(edgeX),\n\t\tcentreX: buildAxis(centreX),\n\t\tedgeY: buildAxis(edgeY),\n\t\tcentreY: buildAxis(centreY)\n\t};\n}\n\n/** Nearest indexed value within `tolerance` of `v`, as a signed delta, or `null`. */\nfunction nearest(axis: AxisEntries, v: number, tolerance: number): number | null {\n\tconst { values } = axis;\n\tlet best: number | null = null;\n\tfor (let i = lowerBound(values, v - tolerance); i < values.length && values[i] <= v + tolerance; i++) {\n\t\tconst delta = values[i] - v;\n\t\tif (best === null || Math.abs(delta) < Math.abs(best)) best = delta;\n\t}\n\treturn best;\n}\n\n/** Rectangles whose indexed value equals `v` (within `eps`). */\nfunction matches(axis: AxisEntries, v: number, eps: number): Rect[] {\n\tconst { values, rects } = axis;\n\tconst out: Rect[] = [];\n\tfor (let i = lowerBound(values, v - eps); i < values.length && values[i] <= v + eps; i++) {\n\t\tout.push(rects[i]);\n\t}\n\treturn out;\n}\n\ninterface SpacingCandidate {\n\tdelta: number;\n\tguides: GuideLine[];\n}\n\n/**\n * Equal-spacing candidates along one axis. Looks at shapes in the same row (horizontal) or\n * column (vertical) as the moving rectangle and proposes: centring between the nearest\n * neighbours, or repeating the gap a neighbour has to its own neighbour.\n */\nfunction spacingCandidate(\n\trects: Rect[],\n\tmoving: Rect,\n\thorizontal: boolean,\n\ttolerance: number\n): SpacingCandidate | null {\n\tconst start = (r: Rect) => (horizontal ? r.x : r.y);\n\tconst end = (r: Rect) => (horizontal ? r.x + r.width : r.y + r.height);\n\tconst crossStart = (r: Rect) => (horizontal ? r.y : r.x);\n\tconst crossEnd = (r: Rect) => (horizontal ? r.y + r.height : r.x + r.width);\n\tconst size = end(moving) - start(moving);\n\tconst inLane = (r: Rect, lane: Rect) => crossStart(r) < crossEnd(lane) && crossEnd(r) > crossStart(lane);\n\n\tconst lane = rects.filter((r) => inLane(r, moving));\n\tif (lane.length === 0) return null;\n\n\tlet before: Rect | null = null;\n\tlet after: Rect | null = null;\n\tfor (const r of lane) {\n\t\tif (end(r) <= start(moving) + tolerance && (!before || end(r) > end(before))) before = r;\n\t\tif (start(r) >= end(moving) - tolerance && (!after || start(r) < start(after))) after = r;\n\t}\n\n\tconst neighbourOf = (r: Rect, forward: boolean): Rect | null => {\n\t\tlet best: Rect | null = null;\n\t\tfor (const other of lane) {\n\t\t\tif (other === r || !inLane(other, r)) continue;\n\t\t\tif (forward ? start(other) >= end(r) : end(other) <= start(r)) {\n\t\t\t\tif (!best || (forward ? start(other) < start(best) : end(other) > end(best))) best = other;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst crossMid = (a: Rect, b: Rect) =>\n\t\t(Math.max(crossStart(a), crossStart(b)) + Math.min(crossEnd(a), crossEnd(b))) / 2;\n\tconst segment = (from: number, to: number, cross: number): GuideLine =>\n\t\thorizontal\n\t\t\t? { orientation: 'horizontal', kind: 'spacing', x1: from, y1: cross, x2: to, y2: cross }\n\t\t\t: { orientation: 'vertical', kind: 'spacing', x1: cross, y1: from, x2: cross, y2: to };\n\n\tconst candidates: { pos: number; guides: (pos: number) => GuideLine[] }[] = [];\n\tif (before && after) {\n\t\tconst b = before;\n\t\tconst a = after;\n\t\tconst pos = (end(b) + start(a) - size) / 2;\n\t\tif (pos >= end(b)) {\n\t\t\tcandidates.push({\n\t\t\t\tpos,\n\t\t\t\tguides: (p) => [\n\t\t\t\t\tsegment(end(b), p, crossMid(b, moving)),\n\t\t\t\t\tsegment(p + size, start(a), crossMid(a, moving))\n\t\t\t\t]\n\t\t\t});\n\t\t}\n\t}\n\tif (before) {\n\t\tconst b = before;\n\t\tconst prev = neighbourOf(b, false);\n\t\tif (prev) {\n\t\t\tconst gap = start(b) - end(prev);\n\t\t\tcandidates.push({\n\t\t\t\tpos: end(b) + gap,\n\t\t\t\tguides: (p) => [segment(end(prev), start(b), crossMid(prev, b)), segment(end(b), p, crossMid(b, moving))]\n\t\t\t});\n\t\t}\n\t}\n\tif (after) {\n\t\tconst a = after;\n\t\tconst next = neighbourOf(a, true);\n\t\tif (next) {\n\t\t\tconst gap = start(next) - end(a);\n\t\t\tcandidates.push({\n\t\t\t\tpos: start(a) - gap - size,\n\t\t\t\tguides: (p) => [segment(p + size, start(a), crossMid(a, moving)), segment(end(a), start(next), crossMid(a, next))]\n\t\t\t});\n\t\t}\n\t}\n\n\tlet best: SpacingCandidate | null = null;\n\tfor (const c of candidates) {\n\t\tconst delta = c.pos - start(moving);\n\t\tif (Math.abs(delta) <= tolerance && (!best || Math.abs(delta) < Math.abs(best.delta))) {\n\t\t\tbest = { delta, guides: c.guides(c.pos) };\n\t\t}\n\t}\n\treturn best;\n}\n\n/**\n * Computes smart-guide snapping for a moving rectangle (a dragged node or a selection's\n * bounding box) against other shapes. Edges snap to edges and centres to centres within\n * `threshold` screen pixels (converted to flow units via `zoom`). Each axis snaps\n * independently to its nearest candidate. Returns the snapped position and the guides to draw.\n */\nexport function computeSnapGuides(\n\tmoving: Rect,\n\ttargets: Rect[] | GuideIndex,\n\toptions: SnapGuidesOptions = {}\n): SnapResult {\n\tconst index = Array.isArray(targets) ? createGuideIndex(targets) : targets;\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst tolerance = (options.threshold ?? 6) / zoom;\n\tconst useEdges = options.edges !== false;\n\tconst useCentres = options.centres !== false;\n\tconst useSpacing = options.spacing !== false;\n\tconst eps = 0.5 / zoom;\n\n\tconst snapAxis = (horizontal: boolean) => {\n\t\tconst start = horizontal ? moving.x : moving.y;\n\t\tconst size = horizontal ? moving.width : moving.height;\n\t\tconst edgeAxis = horizontal ? index.edgeX : index.edgeY;\n\t\tconst centreAxis = horizontal ? index.centreX : index.centreY;\n\t\tlet delta: number | null = null;\n\t\tconst consider = (d: number | null) => {\n\t\t\tif (d !== null && (delta === null || Math.abs(d) < Math.abs(delta))) delta = d;\n\t\t};\n\t\tif (useEdges) {\n\t\t\tconsider(nearest(edgeAxis, start, tolerance));\n\t\t\tconsider(nearest(edgeAxis, start + size, tolerance));\n\t\t}\n\t\tif (useCentres) consider(nearest(centreAxis, start + size / 2, tolerance));\n\t\tconst spacing = useSpacing ? spacingCandidate(index.rects, moving, horizontal, tolerance) : null;\n\t\tif (spacing && (delta === null || Math.abs(spacing.delta) < Math.abs(delta) - eps)) {\n\t\t\treturn { delta: spacing.delta, spacing };\n\t\t}\n\t\treturn { delta: delta as number | null, spacing: null };\n\t};\n\n\tconst sx = snapAxis(true);\n\tconst sy = snapAxis(false);\n\tconst dx = sx.delta ?? 0;\n\tconst dy = sy.delta ?? 0;\n\tconst snapped: Rect = { x: moving.x + dx, y: moving.y + dy, width: moving.width, height: moving.height };\n\tconst guides: GuideLine[] = [];\n\n\tconst alignmentGuides = (horizontal: boolean) => {\n\t\tconst start = horizontal ? snapped.x : snapped.y;\n\t\tconst size = horizontal ? snapped.width : snapped.height;\n\t\tconst lines: { value: number; kind: 'edge' | 'centre'; axis: AxisEntries }[] = [];\n\t\tif (useEdges) {\n\t\t\tconst axis = horizontal ? index.edgeX : index.edgeY;\n\t\t\tlines.push({ value: start, kind: 'edge', axis }, { value: start + size, kind: 'edge', axis });\n\t\t}\n\t\tif (useCentres) {\n\t\t\tlines.push({ value: start + size / 2, kind: 'centre', axis: horizontal ? index.centreX : index.centreY });\n\t\t}\n\t\tfor (const line of lines) {\n\t\t\tconst aligned = matches(line.axis, line.value, eps);\n\t\t\tif (aligned.length === 0) continue;\n\t\t\tconst span = boundsOf([snapped, ...aligned])!;\n\t\t\tguides.push(\n\t\t\t\thorizontal\n\t\t\t\t\t? { orientation: 'vertical', kind: line.kind, x1: line.value, y1: span.y, x2: line.value, y2: span.y + span.height }\n\t\t\t\t\t: { orientation: 'horizontal', kind: line.kind, x1: span.x, y1: line.value, x2: span.x + span.width, y2: line.value }\n\t\t\t);\n\t\t}\n\t};\n\n\tif (sx.delta !== null) {\n\t\tif (sx.spacing) guides.push(...shiftGuides(sx.spacing.guides, 0, dy));\n\t\telse alignmentGuides(true);\n\t}\n\tif (sy.delta !== null) {\n\t\tif (sy.spacing) guides.push(...shiftGuides(sy.spacing.guides, dx, 0));\n\t\telse alignmentGuides(false);\n\t}\n\n\treturn {\n\t\tposition: { x: snapped.x, y: snapped.y },\n\t\toffset: { x: dx, y: dy },\n\t\tguides,\n\t\tsnappedX: sx.delta !== null,\n\t\tsnappedY: sy.delta !== null\n\t};\n}\n\n/** Spacing guides are computed before the other axis snaps; shift their cross coordinate to match. */\nfunction shiftGuides(guides: GuideLine[], dx: number, dy: number): GuideLine[] {\n\treturn guides.map((g) =>\n\t\tg.orientation === 'horizontal'\n\t\t\t? { ...g, y1: g.y1 + dy, y2: g.y2 + dy }\n\t\t\t: { ...g, x1: g.x1 + dx, x2: g.x2 + dx }\n\t);\n}\n\n/** Ids of the given nodes plus every descendant (children move with their parent). */\nexport function collectDescendantIds(nodes: CanvasNode[], ids: Iterable<string>): Set<string> {\n\tconst result = new Set(ids);\n\tlet grew = true;\n\twhile (grew) {\n\t\tgrew = false;\n\t\tfor (const node of nodes) {\n\t\t\tif (node.parentId && result.has(node.parentId) && !result.has(node.id)) {\n\t\t\t\tresult.add(node.id);\n\t\t\t\tgrew = true;\n\t\t\t}\n\t\t}\n\t}\n\treturn result;\n}\n\n/**\n * Builds a `GuideIndex` of every node except the dragged ones and their descendants.\n * Call once at drag start and pass it to `snapNodeDrag` on every move.\n */\nexport function createNodeGuideIndex(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: NodeRectOptions & { filter?: (node: CanvasNode) => boolean } = {}\n): GuideIndex {\n\tconst exclude = collectDescendantIds(nodes, draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst rects: Rect[] = [];\n\tfor (const node of nodes) {\n\t\tif (exclude.has(node.id) || node.hidden === true) continue;\n\t\tif (options.filter && !options.filter(node)) continue;\n\t\trects.push(getNodeRect(node, lookup, options));\n\t}\n\treturn createGuideIndex(rects);\n}\n\nexport interface SnapNodeDragOptions extends SnapGuidesOptions, NodeRectOptions {\n\t/** Index from `createNodeGuideIndex`, reused across drag frames. Built on demand when omitted. */\n\tindex?: GuideIndex;\n}\n\nexport interface SnapNodeDragResult extends SnapResult {\n\t/** Position updates moving every dragged node by `offset` (node-relative coordinates). */\n\tupdates: CanvasNodeUpdate[];\n}\n\n/**\n * Snaps a drag of one or more nodes. The dragged nodes' bounding box is snapped against all\n * other nodes; the resulting offset is applied to each dragged node. Dragged nodes whose\n * parent is also dragged are skipped, because they move with the parent.\n */\nexport function snapNodeDrag(\n\tnodes: CanvasNode[],\n\tdraggingIds: Iterable<string>,\n\toptions: SnapNodeDragOptions = {}\n): SnapNodeDragResult {\n\tconst ids = new Set(draggingIds);\n\tconst lookup = createNodeLookup(nodes);\n\tconst dragged = [...ids].map((id) => lookup.get(id)).filter((n): n is CanvasNode => !!n);\n\tconst empty: SnapNodeDragResult = {\n\t\tposition: { x: 0, y: 0 },\n\t\toffset: { x: 0, y: 0 },\n\t\tguides: [],\n\t\tsnappedX: false,\n\t\tsnappedY: false,\n\t\tupdates: []\n\t};\n\tif (dragged.length === 0) return empty;\n\n\tconst bounds = boundsOf(dragged.map((n) => getNodeRect(n, lookup, options)))!;\n\tconst index = options.index ?? createNodeGuideIndex(nodes, ids, options);\n\tconst result = computeSnapGuides(bounds, index, options);\n\tconst { x: dx, y: dy } = result.offset;\n\tconst updates: CanvasNodeUpdate[] = [];\n\tif (dx !== 0 || dy !== 0) {\n\t\tfor (const node of dragged) {\n\t\t\tif (node.parentId && ids.has(node.parentId)) continue;\n\t\t\tupdates.push({ id: node.id, position: { x: node.position.x + dx, y: node.position.y + dy } });\n\t\t}\n\t}\n\treturn { ...result, updates };\n}\n","import { centre } from './layout.js';\nimport type { Position, Rect, Side, XYPosition } from './types.js';\n\n/**\n * Connection line styles supported by both SvelteFlow and React Flow\n * (`connectionLineType` / edge `type`).\n */\nexport type CanvasConnectionLineType = 'default' | 'straight' | 'step' | 'smoothstep' | 'simplebezier';\n\nexport const CONNECTION_LINE_TYPES: readonly CanvasConnectionLineType[] = [\n\t'default',\n\t'straight',\n\t'step',\n\t'smoothstep',\n\t'simplebezier'\n];\n\n/** Maps a handle `Position` to the placement `Side` vocabulary used by `placeBeside`. */\nexport function positionToSide(position: Position): Side {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'above';\n\t\tcase 'bottom':\n\t\t\treturn 'below';\n\t\tdefault:\n\t\t\treturn position;\n\t}\n}\n\n/** Maps a placement `Side` to the handle `Position` vocabulary used by xyflow. */\nexport function sideToPosition(side: Side): Position {\n\tswitch (side) {\n\t\tcase 'above':\n\t\t\treturn 'top';\n\t\tcase 'below':\n\t\t\treturn 'bottom';\n\t\tdefault:\n\t\t\treturn side;\n\t}\n}\n\n/** The handle position facing the other way. */\nexport function oppositePosition(position: Position): Position {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn 'bottom';\n\t\tcase 'bottom':\n\t\t\treturn 'top';\n\t\tcase 'left':\n\t\t\treturn 'right';\n\t\tdefault:\n\t\t\treturn 'left';\n\t}\n}\n\n/** Midpoint of a rectangle's side, where a centred handle sits. */\nexport function handlePoint(rect: Rect, position: Position): XYPosition {\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: rect.x + rect.width / 2, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: rect.y + rect.height / 2 };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: rect.y + rect.height / 2 };\n\t}\n}\n\nexport interface ConnectorSnapTarget {\n\tid: string;\n\trect: Rect;\n\t/** Sides that can accept a connection (default: all four). */\n\tpositions?: Position[];\n}\n\nexport interface ConnectorSnapOptions {\n\t/** Viewport zoom; `radius` is in screen pixels and divided by it (default 1). */\n\tzoom?: number;\n\t/** How far outside a shape the pointer may be and still attach, in screen pixels (default 24). */\n\tradius?: number;\n\t/** Node ids that cannot be attached to, e.g. the connection's source node. */\n\texcludeIds?: Iterable<string>;\n\t/**\n\t * `handle` snaps to the side's midpoint (a centred handle, default). `edge` snaps to the\n\t * closest point along that side, for floating edges.\n\t */\n\tmode?: 'handle' | 'edge';\n}\n\nexport interface ConnectorSnap {\n\tnodeId: string;\n\tposition: Position;\n\tside: Side;\n\t/** Attachment point in flow coordinates. */\n\tpoint: XYPosition;\n\t/** Distance from the pointer to `point`, in flow units. */\n\tdistance: number;\n}\n\n/**\n * Finds the shape side a connection line should attach to for a pointer (flow coordinates).\n * Shapes the pointer is inside win over shapes it is merely near; among those, the closest\n * allowed side wins. Returns `null` when nothing is within `radius`.\n */\nexport function findConnectorSnap(\n\tpointer: XYPosition,\n\ttargets: ConnectorSnapTarget[],\n\toptions: ConnectorSnapOptions = {}\n): ConnectorSnap | null {\n\tconst zoom = options.zoom && options.zoom > 0 ? options.zoom : 1;\n\tconst radius = (options.radius ?? 24) / zoom;\n\tconst exclude = new Set(options.excludeIds ?? []);\n\tconst all: Position[] = ['top', 'right', 'bottom', 'left'];\n\n\tlet best: (ConnectorSnap & { inside: boolean; rank: number }) | null = null;\n\tfor (const target of targets) {\n\t\tif (exclude.has(target.id)) continue;\n\t\tconst { rect } = target;\n\t\t// Cheap rejection: pointer outside the rect grown by the radius.\n\t\tif (\n\t\t\tpointer.x < rect.x - radius ||\n\t\t\tpointer.x > rect.x + rect.width + radius ||\n\t\t\tpointer.y < rect.y - radius ||\n\t\t\tpointer.y > rect.y + rect.height + radius\n\t\t) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst inside =\n\t\t\tpointer.x >= rect.x &&\n\t\t\tpointer.x <= rect.x + rect.width &&\n\t\t\tpointer.y >= rect.y &&\n\t\t\tpointer.y <= rect.y + rect.height;\n\n\t\tfor (const position of target.positions ?? all) {\n\t\t\tconst point =\n\t\t\t\toptions.mode === 'edge' ? closestPointOnSide(rect, position, pointer) : handlePoint(rect, position);\n\t\t\t// Rank sides by distance from the pointer to the side line, so a pointer near the\n\t\t\t// middle of a wide shape attaches to the side it is closest to.\n\t\t\tconst sidePoint = closestPointOnSide(rect, position, pointer);\n\t\t\tconst rank = Math.hypot(pointer.x - sidePoint.x, pointer.y - sidePoint.y);\n\t\t\tconst distance = Math.hypot(pointer.x - point.x, pointer.y - point.y);\n\t\t\tif (!inside && rank > radius) continue;\n\t\t\tconst better =\n\t\t\t\t!best ||\n\t\t\t\t(inside && !best.inside) ||\n\t\t\t\t(inside === best.inside && rank < best.rank);\n\t\t\tif (better) {\n\t\t\t\tbest = { nodeId: target.id, position, side: positionToSide(position), point, distance, inside, rank };\n\t\t\t}\n\t\t}\n\t}\n\tif (!best) return null;\n\tconst { inside: _inside, rank: _rank, ...snap } = best;\n\treturn snap;\n}\n\nfunction closestPointOnSide(rect: Rect, position: Position, p: XYPosition): XYPosition {\n\tconst clampX = Math.min(rect.x + rect.width, Math.max(rect.x, p.x));\n\tconst clampY = Math.min(rect.y + rect.height, Math.max(rect.y, p.y));\n\tswitch (position) {\n\t\tcase 'top':\n\t\t\treturn { x: clampX, y: rect.y };\n\t\tcase 'bottom':\n\t\t\treturn { x: clampX, y: rect.y + rect.height };\n\t\tcase 'left':\n\t\t\treturn { x: rect.x, y: clampY };\n\t\tdefault:\n\t\t\treturn { x: rect.x + rect.width, y: clampY };\n\t}\n}\n\n/**\n * Picks the facing sides for a connection between two shapes, the way Freeform attaches\n * connection lines: along the axis with the larger centre offset.\n */\nexport function nearestHandlePositions(\n\tsource: Rect,\n\ttarget: Rect\n): { source: Position; target: Position } {\n\tconst a = centre(source);\n\tconst b = centre(target);\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tif (Math.abs(dx) >= Math.abs(dy)) {\n\t\treturn dx >= 0 ? { source: 'right', target: 'left' } : { source: 'left', target: 'right' };\n\t}\n\treturn dy >= 0 ? { source: 'bottom', target: 'top' } : { source: 'top', target: 'bottom' };\n}\n","import { boundsOf } from './layout.js';\nimport { createNodeLookup, getNodeRect, type NodeRectOptions } from './arrange.js';\nimport type { CanvasNode, Rect } from './types.js';\n\n/**\n * Viewport transform in xyflow's convention: `screen = flow * zoom + (x, y)`.\n */\nexport interface CanvasViewport {\n\tx: number;\n\ty: number;\n\tzoom: number;\n}\n\n/**\n * A named, saved view of the canvas (Freeform \"Scenes\").\n */\nexport interface CanvasScene {\n\tid: string;\n\tname: string;\n\tviewport: CanvasViewport;\n\t/** Optional nodes the scene is about, e.g. to re-fit the scene after they move. */\n\tnodeIds?: string[];\n}\n\nexport interface CanvasScenesState {\n\tscenes: readonly CanvasScene[];\n\t/** Id of the scene last shown, or `null`. */\n\tcurrentId: string | null;\n\t/** Index of the current scene, or `-1`. */\n\tcurrentIndex: number;\n}\n\nexport interface CanvasScenesOptions {\n\tinitialScenes?: CanvasScene[];\n\t/** Id generator for new scenes (default: `crypto.randomUUID` with a counter fallback). */\n\tcreateId?: () => string;\n\t/** Whether `next` / `previous` wrap around at the ends (default `false`). */\n\twrap?: boolean;\n\tonChange?: (state: CanvasScenesState) => void;\n}\n\nexport interface SerializedCanvasScenes {\n\tversion: 1;\n\tscenes: CanvasScene[];\n}\n\nlet fallbackCounter = 0;\nfunction defaultId(): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {\n\t\treturn crypto.randomUUID();\n\t}\n\tfallbackCounter += 1;\n\treturn `scene-${Date.now().toString(36)}-${fallbackCounter}`;\n}\n\nfunction isFiniteNumber(value: unknown): value is number {\n\treturn typeof value === 'number' && Number.isFinite(value);\n}\n\n/** Validates the shape of a scene. */\nexport function isValidCanvasScene(value: unknown): value is CanvasScene {\n\tif (typeof value !== 'object' || value === null) return false;\n\tconst scene = value as Record<string, any>;\n\tconst vp = scene.viewport;\n\treturn (\n\t\ttypeof scene.id === 'string' &&\n\t\ttypeof scene.name === 'string' &&\n\t\ttypeof vp === 'object' &&\n\t\tvp !== null &&\n\t\tisFiniteNumber(vp.x) &&\n\t\tisFiniteNumber(vp.y) &&\n\t\tisFiniteNumber(vp.zoom) &&\n\t\tvp.zoom > 0 &&\n\t\t(scene.nodeIds === undefined ||\n\t\t\t(Array.isArray(scene.nodeIds) && scene.nodeIds.every((id: unknown) => typeof id === 'string')))\n\t);\n}\n\nfunction cloneScene(scene: CanvasScene): CanvasScene {\n\tconst copy: CanvasScene = {\n\t\tid: scene.id,\n\t\tname: scene.name,\n\t\tviewport: { x: scene.viewport.x, y: scene.viewport.y, zoom: scene.viewport.zoom }\n\t};\n\tif (scene.nodeIds) copy.nodeIds = [...scene.nodeIds];\n\treturn copy;\n}\n\n/**\n * Parses serialized scenes (a JSON string, a `{ version, scenes }` object or a bare array).\n * Invalid entries and duplicate ids are dropped.\n */\nexport function parseCanvasScenes(input: string | unknown): CanvasScene[] {\n\tlet value: unknown = input;\n\tif (typeof input === 'string') {\n\t\ttry {\n\t\t\tvalue = JSON.parse(input);\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t}\n\tconst list = Array.isArray(value)\n\t\t? value\n\t\t: typeof value === 'object' && value !== null && Array.isArray((value as any).scenes)\n\t\t\t? (value as any).scenes\n\t\t\t: [];\n\tconst seen = new Set<string>();\n\tconst scenes: CanvasScene[] = [];\n\tfor (const entry of list) {\n\t\tif (!isValidCanvasScene(entry) || seen.has(entry.id)) continue;\n\t\tseen.add(entry.id);\n\t\tscenes.push(cloneScene(entry));\n\t}\n\treturn scenes;\n}\n\n/**\n * Headless, framework-agnostic store of named viewports. Snapshots returned by `getState()`\n * are cached and only replaced on change, so it can back React's `useSyncExternalStore`.\n */\nexport class CanvasScenesStore {\n\tprivate scenes: CanvasScene[];\n\tprivate currentId: string | null = null;\n\tprivate cached: CanvasScenesState;\n\tprivate listeners = new Set<(state: CanvasScenesState) => void>();\n\tprivate readonly createId: () => string;\n\tprivate readonly onChange?: (state: CanvasScenesState) => void;\n\twrap: boolean;\n\n\tconstructor(options: CanvasScenesOptions = {}) {\n\t\tthis.scenes = parseCanvasScenes(options.initialScenes ?? []);\n\t\tthis.createId = options.createId ?? defaultId;\n\t\tthis.wrap = options.wrap ?? false;\n\t\tthis.onChange = options.onChange;\n\t\tthis.cached = this.snapshot();\n\t}\n\n\tprivate snapshot(): CanvasScenesState {\n\t\tconst currentIndex = this.currentId ? this.scenes.findIndex((s) => s.id === this.currentId) : -1;\n\t\treturn {\n\t\t\tscenes: this.scenes,\n\t\t\tcurrentId: currentIndex === -1 ? null : this.currentId,\n\t\t\tcurrentIndex\n\t\t};\n\t}\n\n\tprivate commit(scenes: CanvasScene[] = this.scenes) {\n\t\tthis.scenes = scenes;\n\t\tif (this.currentId && !scenes.some((s) => s.id === this.currentId)) this.currentId = null;\n\t\tthis.cached = this.snapshot();\n\t\tthis.onChange?.(this.cached);\n\t\tfor (const listener of this.listeners) listener(this.cached);\n\t}\n\n\tgetState(): CanvasScenesState {\n\t\treturn this.cached;\n\t}\n\n\tgetScene(id: string): CanvasScene | undefined {\n\t\treturn this.scenes.find((s) => s.id === id);\n\t}\n\n\tsubscribe(listener: (state: CanvasScenesState) => void): () => void {\n\t\tthis.listeners.add(listener);\n\t\treturn () => {\n\t\t\tthis.listeners.delete(listener);\n\t\t};\n\t}\n\n\t/** First unused \"Scene N\" name. */\n\tnextDefaultName(): string {\n\t\tconst names = new Set(this.scenes.map((s) => s.name));\n\t\tlet n = this.scenes.length + 1;\n\t\twhile (names.has(`Scene ${n}`)) n += 1;\n\t\treturn `Scene ${n}`;\n\t}\n\n\t/**\n\t * Adds a scene (appended, or inserted at `index`) and makes it current.\n\t */\n\tadd(input: { viewport: CanvasViewport; name?: string; nodeIds?: string[]; id?: string }, index?: number): CanvasScene {\n\t\tconst scene = cloneScene({\n\t\t\tid: input.id && !this.getScene(input.id) ? input.id : this.createId(),\n\t\t\tname: input.name?.trim() || this.nextDefaultName(),\n\t\t\tviewport: input.viewport,\n\t\t\tnodeIds: input.nodeIds\n\t\t});\n\t\tif (!isValidCanvasScene(scene)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tconst at = index === undefined ? scenes.length : Math.max(0, Math.min(scenes.length, index));\n\t\tscenes.splice(at, 0, scene);\n\t\tthis.currentId = scene.id;\n\t\tthis.commit(scenes);\n\t\treturn scene;\n\t}\n\n\t/** Patches a scene's name, viewport or node ids. Returns the updated scene. */\n\tupdate(id: string, patch: Partial<Omit<CanvasScene, 'id'>>): CanvasScene | undefined {\n\t\tconst index = this.scenes.findIndex((s) => s.id === id);\n\t\tif (index === -1) return undefined;\n\t\tconst current = this.scenes[index];\n\t\tconst next = cloneScene({\n\t\t\t...current,\n\t\t\t...patch,\n\t\t\tname: patch.name !== undefined ? patch.name.trim() || current.name : current.name,\n\t\t\tviewport: patch.viewport ?? current.viewport,\n\t\t\tid\n\t\t});\n\t\tif (!isValidCanvasScene(next)) throw new Error('[CanvasScenesStore] Invalid scene viewport.');\n\t\tconst scenes = [...this.scenes];\n\t\tscenes[index] = next;\n\t\tthis.commit(scenes);\n\t\treturn next;\n\t}\n\n\trename(id: string, name: string): CanvasScene | undefined {\n\t\treturn this.update(id, { name });\n\t}\n\n\t/** Moves a scene to `toIndex` (clamped). */\n\tmove(id: string, toIndex: number): void {\n\t\tconst from = this.scenes.findIndex((s) => s.id === id);\n\t\tif (from === -1) return;\n\t\tconst to = Math.max(0, Math.min(this.scenes.length - 1, Math.trunc(toIndex)));\n\t\tif (from === to) return;\n\t\tconst scenes = [...this.scenes];\n\t\tconst [scene] = scenes.splice(from, 1);\n\t\tscenes.splice(to, 0, scene);\n\t\tthis.commit(scenes);\n\t}\n\n\t/** Reorders scenes to match `ids`; unknown ids are ignored and missing scenes keep their relative order at the end. */\n\treorder(ids: string[]): void {\n\t\tconst byId = new Map(this.scenes.map((s) => [s.id, s]));\n\t\tconst ordered: CanvasScene[] = [];\n\t\tfor (const id of ids) {\n\t\t\tconst scene = byId.get(id);\n\t\t\tif (scene) {\n\t\t\t\tordered.push(scene);\n\t\t\t\tbyId.delete(id);\n\t\t\t}\n\t\t}\n\t\tthis.commit([...ordered, ...this.scenes.filter((s) => byId.has(s.id))]);\n\t}\n\n\tremove(id: string): void {\n\t\tif (!this.getScene(id)) return;\n\t\tthis.commit(this.scenes.filter((s) => s.id !== id));\n\t}\n\n\tclear(): void {\n\t\tthis.currentId = null;\n\t\tthis.commit([]);\n\t}\n\n\t/** Marks a scene as current and returns it. */\n\tgoTo(id: string): CanvasScene | undefined {\n\t\tconst scene = this.getScene(id);\n\t\tif (!scene) return undefined;\n\t\tif (this.currentId !== id) {\n\t\t\tthis.currentId = id;\n\t\t\tthis.commit();\n\t\t}\n\t\treturn scene;\n\t}\n\n\tprivate step(direction: 1 | -1): CanvasScene | undefined {\n\t\tconst count = this.scenes.length;\n\t\tif (count === 0) return undefined;\n\t\tconst index = this.cached.currentIndex;\n\t\tlet next: number;\n\t\tif (index === -1) next = direction === 1 ? 0 : count - 1;\n\t\telse next = index + direction;\n\t\tif (next < 0 || next >= count) {\n\t\t\tif (!this.wrap) return undefined;\n\t\t\tnext = (next + count) % count;\n\t\t}\n\t\treturn this.goTo(this.scenes[next].id);\n\t}\n\n\t/** Shows the next scene. Returns `undefined` at the end unless `wrap` is on. */\n\tnext(): CanvasScene | undefined {\n\t\treturn this.step(1);\n\t}\n\n\t/** Shows the previous scene. Returns `undefined` at the start unless `wrap` is on. */\n\tprevious(): CanvasScene | undefined {\n\t\treturn this.step(-1);\n\t}\n\n\ttoJSON(): SerializedCanvasScenes {\n\t\treturn { version: 1, scenes: this.scenes.map(cloneScene) };\n\t}\n\n\tserialize(): string {\n\t\treturn JSON.stringify(this.toJSON());\n\t}\n\n\t/** Replaces all scenes from serialized data (see `parseCanvasScenes`). */\n\tload(input: string | unknown): void {\n\t\tthis.currentId = null;\n\t\tthis.commit(parseCanvasScenes(input));\n\t}\n}\n\nexport function createCanvasScenesStore(options?: CanvasScenesOptions): CanvasScenesStore {\n\treturn new CanvasScenesStore(options);\n}\n\nexport interface FitViewportOptions {\n\t/** Padding around the content as a fraction of the content size per side (default 0.1, like xyflow). */\n\tpadding?: number;\n\tminZoom?: number;\n\tmaxZoom?: number;\n}\n\n/**\n * Viewport that fits `rect` into a container of `size`, centred (xyflow `fitView` maths).\n */\nexport function fitViewportToRect(\n\trect: Rect,\n\tsize: { width: number; height: number },\n\toptions: FitViewportOptions = {}\n): CanvasViewport {\n\tconst { padding = 0.1, minZoom = 0.1, maxZoom = 2 } = options;\n\tconst paddedWidth = Math.max(rect.width * (1 + padding * 2), 1);\n\tconst paddedHeight = Math.max(rect.height * (1 + padding * 2), 1);\n\tconst raw = Math.min(size.width / paddedWidth, size.height / paddedHeight);\n\tconst zoom = Math.min(maxZoom, Math.max(minZoom, raw));\n\tconst cx = rect.x + rect.width / 2;\n\tconst cy = rect.y + rect.height / 2;\n\treturn { x: size.width / 2 - cx * zoom, y: size.height / 2 - cy * zoom, zoom };\n}\n\n/**\n * Viewport that fits the given nodes (all nodes when `ids` is omitted), or `null` when none match.\n */\nexport function fitViewportToNodes(\n\tnodes: CanvasNode[],\n\tsize: { width: number; height: number },\n\tids?: Iterable<string>,\n\toptions: FitViewportOptions & NodeRectOptions = {}\n): CanvasViewport | null {\n\tconst lookup = createNodeLookup(nodes);\n\tconst wanted = ids ? new Set(ids) : null;\n\tconst rects = nodes\n\t\t.filter((n) => (wanted ? wanted.has(n.id) : true))\n\t\t.map((n) => getNodeRect(n, lookup, options));\n\tconst bounds = boundsOf(rects);\n\treturn bounds ? fitViewportToRect(bounds, size, options) : null;\n}\n","import { isEventFromTextInput } from './clipboard.js';\n\n/**\n * Canvas arrangement actions that have keyboard shortcuts.\n */\nexport type CanvasArrangeAction =\n\t| 'alignLeft'\n\t| 'alignCenter'\n\t| 'alignRight'\n\t| 'alignTop'\n\t| 'alignMiddle'\n\t| 'alignBottom'\n\t| 'distributeHorizontal'\n\t| 'distributeVertical'\n\t| 'lock'\n\t| 'unlock'\n\t| 'bringForward'\n\t| 'sendBackward'\n\t| 'bringToFront'\n\t| 'sendToBack'\n\t| 'nextScene'\n\t| 'previousScene';\n\nexport interface ArrangeShortcutBinding {\n\taction: CanvasArrangeAction;\n\t/** `KeyboardEvent.code` (layout- and Option-independent, e.g. `KeyA`, `BracketRight`). */\n\tcode: string;\n\tmod?: boolean;\n\talt?: boolean;\n\tshift?: boolean;\n\t/** Human readable label, e.g. for menus. `Mod` is ⌘ on macOS and Ctrl elsewhere. */\n\tlabel: string;\n}\n\n/**\n * Default bindings. Align/distribute follow Figma (Option/Alt + letter), layer order follows\n * the common `Mod+]` / `Mod+[` convention, lock follows Keynote/Freeform (`Mod+L`,\n * `Mod+Alt+L` to unlock) and scenes step with PageDown / PageUp.\n */\nexport const DEFAULT_ARRANGE_SHORTCUTS: readonly ArrangeShortcutBinding[] = [\n\t{ action: 'alignLeft', code: 'KeyA', alt: true, label: 'Alt+A' },\n\t{ action: 'alignCenter', code: 'KeyH', alt: true, label: 'Alt+H' },\n\t{ action: 'alignRight', code: 'KeyD', alt: true, label: 'Alt+D' },\n\t{ action: 'alignTop', code: 'KeyW', alt: true, label: 'Alt+W' },\n\t{ action: 'alignMiddle', code: 'KeyV', alt: true, label: 'Alt+V' },\n\t{ action: 'alignBottom', code: 'KeyS', alt: true, label: 'Alt+S' },\n\t{ action: 'distributeHorizontal', code: 'KeyH', alt: true, shift: true, label: 'Alt+Shift+H' },\n\t{ action: 'distributeVertical', code: 'KeyV', alt: true, shift: true, label: 'Alt+Shift+V' },\n\t{ action: 'lock', code: 'KeyL', mod: true, label: 'Mod+L' },\n\t{ action: 'unlock', code: 'KeyL', mod: true, alt: true, label: 'Mod+Alt+L' },\n\t{ action: 'bringForward', code: 'BracketRight', mod: true, label: 'Mod+]' },\n\t{ action: 'sendBackward', code: 'BracketLeft', mod: true, label: 'Mod+[' },\n\t{ action: 'bringToFront', code: 'BracketRight', mod: true, alt: true, label: 'Mod+Alt+]' },\n\t{ action: 'sendToBack', code: 'BracketLeft', mod: true, alt: true, label: 'Mod+Alt+[' },\n\t{ action: 'nextScene', code: 'PageDown', label: 'PageDown' },\n\t{ action: 'previousScene', code: 'PageUp', label: 'PageUp' }\n];\n\nconst KEY_TO_CODE: Record<string, string> = {\n\t']': 'BracketRight',\n\t'[': 'BracketLeft',\n\tPageDown: 'PageDown',\n\tPageUp: 'PageUp'\n};\n\nfunction eventCode(event: KeyboardEvent): string {\n\tif (event.code) return event.code;\n\t// Synthetic events (and some test environments) omit `code`; derive it from `key`.\n\tif (KEY_TO_CODE[event.key]) return KEY_TO_CODE[event.key];\n\tif (/^[a-z]$/i.test(event.key)) return `Key${event.key.toUpperCase()}`;\n\treturn event.key;\n}\n\n/**\n * Resolves a keydown event to an arrange action, or `null`. Events from text inputs,\n * textareas, selects, contenteditable and ProseMirror are ignored (see `isEventFromTextInput`).\n * `Mod` is ⌘ or Ctrl.\n */\nexport function matchArrangeShortcut(\n\tevent: KeyboardEvent,\n\tbindings: readonly ArrangeShortcutBinding[] = DEFAULT_ARRANGE_SHORTCUTS\n): CanvasArrangeAction | null {\n\tif (isEventFromTextInput(event)) return null;\n\tconst code = eventCode(event);\n\tconst mod = event.metaKey || event.ctrlKey;\n\tfor (const binding of bindings) {\n\t\tif (\n\t\t\tbinding.code === code &&\n\t\t\t!!binding.mod === mod &&\n\t\t\t!!binding.alt === event.altKey &&\n\t\t\t!!binding.shift === event.shiftKey\n\t\t) {\n\t\t\treturn binding.action;\n\t\t}\n\t}\n\treturn null;\n}\n","/**\n * Polygon boolean operations (union, intersection, difference, xor and divide) on\n * multi-polygons with holes.\n *\n * Algorithm (overlay + winding classification):\n * 1. Every input edge is collected into one segment set. Vertices are merged through a\n * tolerance-aware point pool, so points closer than `epsilon` become one vertex.\n * 2. Segments are split at every crossing, T-junction and collinear overlap until no two\n * segments cross (a planar arrangement). Splitting is repeated because snapping a new\n * vertex can move a segment by up to `epsilon`.\n * 3. Coincident segments (shared edges) are merged, keeping for every operand the signed\n * number of its edges that run along the segment.\n * 4. For every segment the winding number of each operand on its right side is computed\n * with a ray cast from the segment midpoint (the segment itself excluded), and the left\n * side follows from the signed multiplicities. The fill rule turns windings into\n * inside / outside flags and the operation decides whether each side is in the result.\n * 5. Segments with the result on exactly one side are kept, oriented with the result on\n * their left, and traced into rings (taking the sharpest left turn at shared vertices,\n * so rings that touch at a vertex stay separate simple rings). Rings with a positive\n * signed area are outer rings; negative ones are holes, assigned to the smallest outer\n * ring that contains them.\n *\n * Handles shared edges, touching vertices, collinear overlaps, holes, nested and disjoint\n * inputs and self-intersecting input rings (resolved by the operand's fill rule).\n *\n * Limits: the overlay is O(n²) in the number of edges (pruned by a sweep on x), which is\n * comfortable for canvas shapes (thousands of edges) but not for GIS-scale data. Features\n * narrower than `epsilon` collapse. Coordinates must be finite.\n */\nimport type { Point } from './types.js';\n\n/** A closed ring of points. The closing point may be repeated or omitted. */\nexport type PolygonRing = Point[];\n/** A polygon: the outer ring followed by its holes. */\nexport type PolygonWithHoles = PolygonRing[];\n/** A set of polygons. */\nexport type MultiPolygon = PolygonWithHoles[];\n\n/** SVG fill rule used to decide which areas of a ring set are inside. */\nexport type PolygonFillRule = 'nonzero' | 'evenodd';\n\n/** Boolean operations. `xor` is Freeform's Exclude. */\nexport type PolygonBooleanOperation = 'union' | 'intersection' | 'difference' | 'xor';\n\n/**\n * A raw ring set with a fill rule (e.g. flattened SVG path data). Ring orientation is\n * significant for `nonzero`.\n */\nexport interface PolygonOperand {\n\trings: PolygonRing[];\n\tfillRule?: PolygonFillRule;\n}\n\nexport type PolygonInput = MultiPolygon | PolygonOperand;\n\nexport interface PolygonBooleanOptions {\n\t/**\n\t * Distance below which points are treated as equal. Defaults to `1e-9` times the\n\t * largest coordinate magnitude of the inputs (at least `1e-9`).\n\t */\n\tepsilon?: number;\n}\n\n/** One region of `dividePolygons`, with the indices of the operands that cover it. */\nexport interface PolygonPiece {\n\tpolygon: PolygonWithHoles;\n\toperands: number[];\n}\n\n// ---------------------------------------------------------------------------\n// Basic measures\n// ---------------------------------------------------------------------------\n\n/** Signed shoelace area of a ring (positive when counter-clockwise in a y-up frame). */\nexport function ringSignedArea(ring: readonly Point[]): number {\n\tlet sum = 0;\n\tconst n = ring.length;\n\tfor (let i = 0; i < n; i++) {\n\t\tconst [x1, y1] = ring[i];\n\t\tconst [x2, y2] = ring[(i + 1) % n];\n\t\tsum += x1 * y2 - x2 * y1;\n\t}\n\treturn sum / 2;\n}\n\n/** Area of a polygon (outer ring minus holes). */\nexport function polygonArea(polygon: PolygonWithHoles): number {\n\tif (polygon.length === 0) return 0;\n\tlet area = Math.abs(ringSignedArea(polygon[0]));\n\tfor (let i = 1; i < polygon.length; i++) area -= Math.abs(ringSignedArea(polygon[i]));\n\treturn area;\n}\n\n/** Total area of a multi-polygon (holes subtracted). */\nexport function multiPolygonArea(multi: MultiPolygon): number {\n\tlet total = 0;\n\tfor (const polygon of multi) total += polygonArea(polygon);\n\treturn total;\n}\n\n/** Axis-aligned bounds of a multi-polygon (or ring list), or `null` when empty. */\nexport function multiPolygonBounds(\n\tinput: MultiPolygon | PolygonRing[]\n): { minX: number; minY: number; maxX: number; maxY: number } | null {\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tconst visit = (ring: PolygonRing) => {\n\t\tfor (const [x, y] of ring) {\n\t\t\tif (x < minX) minX = x;\n\t\t\tif (y < minY) minY = y;\n\t\t\tif (x > maxX) maxX = x;\n\t\t\tif (y > maxY) maxY = y;\n\t\t}\n\t};\n\tfor (const item of input as any[]) {\n\t\tif (item.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0])) {\n\t\t\tfor (const ring of item as PolygonRing[]) visit(ring);\n\t\t} else {\n\t\t\tvisit(item as PolygonRing);\n\t\t}\n\t}\n\treturn minX === Infinity ? null : { minX, minY, maxX, maxY };\n}\n\n/**\n * Whether a point is inside a ring set under a fill rule. Points exactly on an edge may\n * report either side.\n */\nexport function pointInRings(\n\tpoint: Point,\n\trings: readonly (readonly Point[])[],\n\tfillRule: PolygonFillRule = 'nonzero'\n): boolean {\n\tconst [px, py] = point;\n\tlet winding = 0;\n\tfor (const ring of rings) {\n\t\tconst n = ring.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst [ax, ay] = ring[i];\n\t\t\tconst [bx, by] = ring[(i + 1) % n];\n\t\t\tif (ay <= py && by > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding++;\n\t\t\t} else if (by <= py && ay > py) {\n\t\t\t\tif (ax + ((py - ay) / (by - ay)) * (bx - ax) > px) winding--;\n\t\t\t}\n\t\t}\n\t}\n\treturn fillRule === 'evenodd' ? (winding & 1) === 1 : winding !== 0;\n}\n\n/** Whether a point is inside a multi-polygon (holes excluded). */\nexport function pointInMultiPolygon(point: Point, multi: MultiPolygon): boolean {\n\treturn multi.some((polygon) => pointInRings(point, polygon, 'evenodd'));\n}\n\n// ---------------------------------------------------------------------------\n// Operand normalisation\n// ---------------------------------------------------------------------------\n\nfunction cleanRing(ring: readonly Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tif (!Number.isFinite(p[0]) || !Number.isFinite(p[1])) continue;\n\t\tconst last = out[out.length - 1];\n\t\tif (last && last[0] === p[0] && last[1] === p[1]) continue;\n\t\tout.push([p[0], p[1]]);\n\t}\n\twhile (out.length > 1) {\n\t\tconst first = out[0];\n\t\tconst last = out[out.length - 1];\n\t\tif (first[0] === last[0] && first[1] === last[1]) out.pop();\n\t\telse break;\n\t}\n\treturn out;\n}\n\nfunction isOperand(input: PolygonInput): input is PolygonOperand {\n\treturn !Array.isArray(input) && typeof input === 'object' && input !== null && Array.isArray(input.rings);\n}\n\ninterface NormalizedOperand {\n\trings: Point[][];\n\tfillRule: PolygonFillRule;\n}\n\n/**\n * Multi-polygons are re-oriented (outer rings positive, holes negative) and filled with\n * `nonzero`, so overlapping polygons in one operand behave as their union.\n */\nfunction normalizeInput(input: PolygonInput): NormalizedOperand {\n\tif (isOperand(input)) {\n\t\treturn {\n\t\t\trings: input.rings.map(cleanRing).filter((r) => r.length >= 3),\n\t\t\tfillRule: input.fillRule ?? 'nonzero'\n\t\t};\n\t}\n\tconst rings: Point[][] = [];\n\tfor (const polygon of input ?? []) {\n\t\tpolygon.forEach((raw, index) => {\n\t\t\tconst ring = cleanRing(raw);\n\t\t\tif (ring.length < 3) return;\n\t\t\tconst area = ringSignedArea(ring);\n\t\t\tif (area === 0) return;\n\t\t\tconst wantPositive = index === 0;\n\t\t\trings.push(area > 0 === wantPositive ? ring : ring.reverse());\n\t\t});\n\t}\n\treturn { rings, fillRule: 'nonzero' };\n}\n\n// ---------------------------------------------------------------------------\n// Overlay\n// ---------------------------------------------------------------------------\n\ninterface Vertex {\n\tid: number;\n\tx: number;\n\ty: number;\n}\n\n/** Merges points closer than `eps` (grid hash with neighbour lookup). */\nclass PointPool {\n\treadonly vertices: Vertex[] = [];\n\tprivate readonly cells = new Map<string, number[]>();\n\tconstructor(private readonly eps: number) {}\n\n\tget(x: number, y: number): Vertex {\n\t\tconst cx = Math.floor(x / this.eps);\n\t\tconst cy = Math.floor(y / this.eps);\n\t\tlet best: Vertex | null = null;\n\t\tlet bestDist = Infinity;\n\t\tfor (let dx = -1; dx <= 1; dx++) {\n\t\t\tfor (let dy = -1; dy <= 1; dy++) {\n\t\t\t\tconst ids = this.cells.get(`${cx + dx},${cy + dy}`);\n\t\t\t\tif (!ids) continue;\n\t\t\t\tfor (const id of ids) {\n\t\t\t\t\tconst v = this.vertices[id];\n\t\t\t\t\tconst d = Math.hypot(v.x - x, v.y - y);\n\t\t\t\t\tif (d <= this.eps && d < bestDist) {\n\t\t\t\t\t\tbest = v;\n\t\t\t\t\t\tbestDist = d;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (best) return best;\n\t\tconst vertex: Vertex = { id: this.vertices.length, x, y };\n\t\tthis.vertices.push(vertex);\n\t\tconst key = `${cx},${cy}`;\n\t\tconst bucket = this.cells.get(key);\n\t\tif (bucket) bucket.push(vertex.id);\n\t\telse this.cells.set(key, [vertex.id]);\n\t\treturn vertex;\n\t}\n}\n\ninterface RawSegment {\n\ta: Vertex;\n\tb: Vertex;\n\toperand: number;\n}\n\ninterface Splits {\n\tsegment: RawSegment;\n\tpoints: Map<number, { t: number; v: Vertex }>;\n}\n\n/** Projection parameter of `p` on `a→b` and its distance to the segment line. */\nfunction project(a: Vertex, b: Vertex, p: Vertex): { t: number; dist: number } {\n\tconst dx = b.x - a.x;\n\tconst dy = b.y - a.y;\n\tconst len2 = dx * dx + dy * dy;\n\tconst t = len2 === 0 ? 0 : ((p.x - a.x) * dx + (p.y - a.y) * dy) / len2;\n\tconst cx = a.x + t * dx;\n\tconst cy = a.y + t * dy;\n\treturn { t, dist: Math.hypot(p.x - cx, p.y - cy) };\n}\n\nfunction addSplit(splits: Splits, t: number, v: Vertex) {\n\tconst { a, b } = splits.segment;\n\tif (v.id === a.id || v.id === b.id) return;\n\tif (!splits.points.has(v.id)) splits.points.set(v.id, { t, v });\n}\n\n/** One pass: splits every segment at crossings / touches with other segments. */\nfunction splitPass(segments: RawSegment[], pool: PointPool, eps: number): { segments: RawSegment[]; changed: boolean } {\n\tconst order = segments\n\t\t.map((s, i) => ({\n\t\t\ti,\n\t\t\tminX: Math.min(s.a.x, s.b.x),\n\t\t\tmaxX: Math.max(s.a.x, s.b.x),\n\t\t\tminY: Math.min(s.a.y, s.b.y),\n\t\t\tmaxY: Math.max(s.a.y, s.b.y)\n\t\t}))\n\t\t.sort((p, q) => p.minX - q.minX);\n\tconst splits: Splits[] = segments.map((segment) => ({ segment, points: new Map() }));\n\n\tfor (let oi = 0; oi < order.length; oi++) {\n\t\tconst bi = order[oi];\n\t\tconst s1 = segments[bi.i];\n\t\tfor (let oj = oi + 1; oj < order.length; oj++) {\n\t\t\tconst bj = order[oj];\n\t\t\tif (bj.minX > bi.maxX + eps) break;\n\t\t\tif (bj.minY > bi.maxY + eps || bj.maxY < bi.minY - eps) continue;\n\t\t\tconst s2 = segments[bj.i];\n\t\t\tconst sharedA = s1.a.id === s2.a.id || s1.a.id === s2.b.id;\n\t\t\tconst sharedB = s1.b.id === s2.a.id || s1.b.id === s2.b.id;\n\t\t\tif (sharedA && sharedB) continue; // identical (or reversed) segment\n\n\t\t\tlet touched = false;\n\t\t\t// Endpoints of one segment lying on the other: T-junctions and collinear overlaps.\n\t\t\tfor (const [seg, other, split] of [\n\t\t\t\t[s1, s2, splits[bi.i]],\n\t\t\t\t[s2, s1, splits[bj.i]]\n\t\t\t] as const) {\n\t\t\t\tfor (const p of [other.a, other.b]) {\n\t\t\t\t\tif (p.id === seg.a.id || p.id === seg.b.id) continue;\n\t\t\t\t\tconst { t, dist } = project(seg.a, seg.b, p);\n\t\t\t\t\tconst len = Math.hypot(seg.b.x - seg.a.x, seg.b.y - seg.a.y);\n\t\t\t\t\tif (dist <= eps && t * len > eps && (1 - t) * len > eps) {\n\t\t\t\t\t\taddSplit(split, t, p);\n\t\t\t\t\t\ttouched = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (touched) continue;\n\n\t\t\t// Proper crossing.\n\t\t\tconst d1x = s1.b.x - s1.a.x;\n\t\t\tconst d1y = s1.b.y - s1.a.y;\n\t\t\tconst d2x = s2.b.x - s2.a.x;\n\t\t\tconst d2y = s2.b.y - s2.a.y;\n\t\t\tconst denom = d1x * d2y - d1y * d2x;\n\t\t\tconst len1 = Math.hypot(d1x, d1y);\n\t\t\tconst len2 = Math.hypot(d2x, d2y);\n\t\t\tif (Math.abs(denom) <= 1e-12 * len1 * len2) continue; // parallel\n\t\t\tconst ex = s2.a.x - s1.a.x;\n\t\t\tconst ey = s2.a.y - s1.a.y;\n\t\t\tconst t = (ex * d2y - ey * d2x) / denom;\n\t\t\tconst u = (ex * d1y - ey * d1x) / denom;\n\t\t\tconst tolT = eps / len1;\n\t\t\tconst tolU = eps / len2;\n\t\t\tif (t < -tolT || t > 1 + tolT || u < -tolU || u > 1 + tolU) continue;\n\t\t\tif (sharedA || sharedB) continue; // they meet at the shared endpoint only\n\t\t\tconst v = pool.get(s1.a.x + t * d1x, s1.a.y + t * d1y);\n\t\t\taddSplit(splits[bi.i], project(s1.a, s1.b, v).t, v);\n\t\t\taddSplit(splits[bj.i], project(s2.a, s2.b, v).t, v);\n\t\t}\n\t}\n\n\tlet changed = false;\n\tconst out: RawSegment[] = [];\n\tfor (const { segment, points } of splits) {\n\t\tif (points.size === 0) {\n\t\t\tout.push(segment);\n\t\t\tcontinue;\n\t\t}\n\t\tchanged = true;\n\t\tconst ordered = [...points.values()].sort((p, q) => p.t - q.t);\n\t\tlet prev = segment.a;\n\t\tfor (const { v } of ordered) {\n\t\t\tif (v.id !== prev.id) out.push({ a: prev, b: v, operand: segment.operand });\n\t\t\tprev = v;\n\t\t}\n\t\tif (prev.id !== segment.b.id) out.push({ a: prev, b: segment.b, operand: segment.operand });\n\t}\n\treturn { segments: out, changed };\n}\n\ninterface Edge {\n\ta: Vertex;\n\tb: Vertex;\n\t/** Signed multiplicity per operand (+1 per edge running a→b, −1 per edge running b→a). */\n\tmult: number[];\n}\n\ninterface Overlay {\n\tedges: Edge[];\n\t/** Winding number per operand on the right of each edge. */\n\tright: number[][];\n\t/** Winding number per operand on the left of each edge. */\n\tleft: number[][];\n\tfillRules: PolygonFillRule[];\n\teps: number;\n}\n\nfunction defaultEpsilon(operands: NormalizedOperand[]): number {\n\tlet max = 0;\n\tfor (const op of operands) {\n\t\tfor (const ring of op.rings) {\n\t\t\tfor (const [x, y] of ring) {\n\t\t\t\tconst m = Math.max(Math.abs(x), Math.abs(y));\n\t\t\t\tif (m > max) max = m;\n\t\t\t}\n\t\t}\n\t}\n\treturn Math.max(1e-9, max * 1e-9);\n}\n\nfunction buildOverlay(inputs: PolygonInput[], options: PolygonBooleanOptions): Overlay {\n\tconst operands = inputs.map(normalizeInput);\n\tconst eps = options.epsilon && options.epsilon > 0 ? options.epsilon : defaultEpsilon(operands);\n\tconst pool = new PointPool(eps);\n\tlet segments: RawSegment[] = [];\n\toperands.forEach((op, operand) => {\n\t\tfor (const ring of op.rings) {\n\t\t\tconst verts = ring.map(([x, y]) => pool.get(x, y));\n\t\t\tfor (let i = 0; i < verts.length; i++) {\n\t\t\t\tconst a = verts[i];\n\t\t\t\tconst b = verts[(i + 1) % verts.length];\n\t\t\t\tif (a.id !== b.id) segments.push({ a, b, operand });\n\t\t\t}\n\t\t}\n\t});\n\n\tfor (let pass = 0; pass < 8; pass++) {\n\t\tconst result = splitPass(segments, pool, eps);\n\t\tsegments = result.segments;\n\t\tif (!result.changed) break;\n\t}\n\n\t// Merge coincident segments.\n\tconst count = operands.length;\n\tconst byKey = new Map<string, Edge>();\n\tfor (const s of segments) {\n\t\tconst forward = s.a.id < s.b.id;\n\t\tconst a = forward ? s.a : s.b;\n\t\tconst b = forward ? s.b : s.a;\n\t\tconst key = `${a.id},${b.id}`;\n\t\tlet edge = byKey.get(key);\n\t\tif (!edge) {\n\t\t\tedge = { a, b, mult: new Array(count).fill(0) };\n\t\t\tbyKey.set(key, edge);\n\t\t}\n\t\tedge.mult[s.operand] += forward ? 1 : -1;\n\t}\n\tconst edges = [...byKey.values()].filter((e) => e.mult.some((m) => m !== 0));\n\n\tconst right: number[][] = [];\n\tconst left: number[][] = [];\n\tfor (let i = 0; i < edges.length; i++) {\n\t\tconst edge = edges[i];\n\t\t// Cast the ray across the edge rather than along it: rotating by −90° keeps windings\n\t\t// and turns a mostly horizontal edge into a mostly vertical one.\n\t\tconst horizontal = Math.abs(edge.b.y - edge.a.y) < Math.abs(edge.b.x - edge.a.x);\n\t\tconst tx = (v: Vertex) => (horizontal ? v.y : v.x);\n\t\tconst ty = (v: Vertex) => (horizontal ? -v.x : v.y);\n\t\tconst ax = tx(edge.a);\n\t\tconst ay = ty(edge.a);\n\t\tconst bx = tx(edge.b);\n\t\tconst by = ty(edge.b);\n\t\tconst mx = (ax + bx) / 2;\n\t\tconst my = (ay + by) / 2;\n\t\tconst winding = new Array(count).fill(0);\n\t\tfor (let j = 0; j < edges.length; j++) {\n\t\t\tif (j === i) continue;\n\t\t\tconst other = edges[j];\n\t\t\tconst oax = tx(other.a);\n\t\t\tconst oay = ty(other.a);\n\t\t\tconst obx = tx(other.b);\n\t\t\tconst oby = ty(other.b);\n\t\t\tlet dir = 0;\n\t\t\tif (oay <= my && oby > my) dir = 1;\n\t\t\telse if (oby <= my && oay > my) dir = -1;\n\t\t\telse continue;\n\t\t\tconst x = oax + ((my - oay) / (oby - oay)) * (obx - oax);\n\t\t\tif (x <= mx) continue;\n\t\t\tfor (let k = 0; k < count; k++) {\n\t\t\t\tif (other.mult[k] !== 0) winding[k] += dir * other.mult[k];\n\t\t\t}\n\t\t}\n\t\t// The ray starts just past the midpoint on the +x side, which is the right of the\n\t\t// edge when it points up (dy > 0) and its left otherwise.\n\t\tconst sideIsRight = by > ay;\n\t\tconst r = sideIsRight ? winding : winding.map((w, k) => w - edge.mult[k]);\n\t\tright.push(r);\n\t\tleft.push(r.map((w, k) => w + edge.mult[k]));\n\t}\n\treturn { edges, right, left, fillRules: operands.map((o) => o.fillRule), eps };\n}\n\nfunction insideFlags(windings: number[], fillRules: PolygonFillRule[]): boolean[] {\n\treturn windings.map((w, k) => (fillRules[k] === 'evenodd' ? (w & 1) === 1 : w !== 0));\n}\n\n// ---------------------------------------------------------------------------\n// Ring extraction\n// ---------------------------------------------------------------------------\n\ninterface DirectedEdge {\n\tfrom: Vertex;\n\tto: Vertex;\n\tangle: number;\n}\n\n/** Traces the kept directed edges (result on the left) into rings. */\nfunction traceRings(directed: DirectedEdge[]): Vertex[][] {\n\tconst outgoing = new Map<number, DirectedEdge[]>();\n\tfor (const e of directed) {\n\t\tconst list = outgoing.get(e.from.id);\n\t\tif (list) list.push(e);\n\t\telse outgoing.set(e.from.id, [e]);\n\t}\n\tconst TWO_PI = Math.PI * 2;\n\tconst next = (incoming: DirectedEdge): DirectedEdge | undefined => {\n\t\tconst candidates = outgoing.get(incoming.to.id);\n\t\tif (!candidates) return undefined;\n\t\tconst back = Math.atan2(incoming.from.y - incoming.to.y, incoming.from.x - incoming.to.x);\n\t\tlet best: DirectedEdge | undefined;\n\t\tlet bestTurn = Infinity;\n\t\tfor (const c of candidates) {\n\t\t\t// Clockwise sweep from the reversed incoming edge: the first hit is the sharpest\n\t\t\t// left turn, which keeps the result region on the left and splits rings at\n\t\t\t// vertices they merely touch.\n\t\t\tlet turn = back - c.angle;\n\t\t\tturn -= Math.floor(turn / TWO_PI) * TWO_PI;\n\t\t\tif (turn <= 1e-15) turn = TWO_PI;\n\t\t\tif (turn < bestTurn) {\n\t\t\t\tbestTurn = turn;\n\t\t\t\tbest = c;\n\t\t\t}\n\t\t}\n\t\treturn best;\n\t};\n\n\tconst used = new Set<DirectedEdge>();\n\tconst rings: Vertex[][] = [];\n\tfor (const start of directed) {\n\t\tif (used.has(start)) continue;\n\t\tconst ring: Vertex[] = [];\n\t\tlet edge: DirectedEdge | undefined = start;\n\t\tlet guard = directed.length + 1;\n\t\twhile (edge && !used.has(edge) && guard-- > 0) {\n\t\t\tused.add(edge);\n\t\t\tring.push(edge.from);\n\t\t\tedge = next(edge);\n\t\t}\n\t\tif (ring.length >= 3) rings.push(ring);\n\t}\n\treturn rings;\n}\n\n/** Removes vertices that lie on the straight line between their neighbours. */\nfunction removeCollinear(ring: Point[], eps: number): Point[] {\n\tlet points = ring;\n\tlet changed = true;\n\twhile (changed && points.length > 3) {\n\t\tchanged = false;\n\t\tconst out: Point[] = [];\n\t\tconst n = points.length;\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst prev = out.length > 0 ? out[out.length - 1] : points[(i - 1 + n) % n];\n\t\t\tconst cur = points[i];\n\t\t\tconst nxt = points[(i + 1) % n];\n\t\t\tconst dx = nxt[0] - prev[0];\n\t\t\tconst dy = nxt[1] - prev[1];\n\t\t\tconst len = Math.hypot(dx, dy);\n\t\t\tconst cross = (cur[0] - prev[0]) * dy - (cur[1] - prev[1]) * dx;\n\t\t\tconst dot = (cur[0] - prev[0]) * dx + (cur[1] - prev[1]) * dy;\n\t\t\tif (len > 0 && Math.abs(cross) / len <= eps && dot > 0 && dot < len * len) {\n\t\t\t\tchanged = true;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tout.push(cur);\n\t\t}\n\t\tif (out.length < 3) return out;\n\t\tpoints = out;\n\t}\n\treturn points;\n}\n\nfunction assemble(rings: Vertex[][], eps: number): MultiPolygon {\n\tconst outers: { ring: Point[]; area: number }[] = [];\n\tconst holes: { ring: Point[]; area: number }[] = [];\n\tconst minArea = eps * eps;\n\tfor (const vertices of rings) {\n\t\tconst ring = removeCollinear(\n\t\t\tvertices.map((v) => [v.x, v.y] as Point),\n\t\t\teps\n\t\t);\n\t\tif (ring.length < 3) continue;\n\t\tconst area = ringSignedArea(ring);\n\t\tif (Math.abs(area) <= minArea) continue;\n\t\t(area > 0 ? outers : holes).push({ ring, area: Math.abs(area) });\n\t}\n\touters.sort((a, b) => b.area - a.area);\n\tconst polygons: PolygonWithHoles[] = outers.map((o) => [o.ring]);\n\tfor (const hole of holes) {\n\t\t// Sample the midpoint of a hole edge: overlay edges are unique, so it is not on an\n\t\t// outer ring's boundary.\n\t\tconst [p, q] = [hole.ring[0], hole.ring[1]];\n\t\tconst sample: Point = [(p[0] + q[0]) / 2, (p[1] + q[1]) / 2];\n\t\tlet owner = -1;\n\t\tfor (let i = outers.length - 1; i >= 0; i--) {\n\t\t\t// Iterating smallest-first finds the tightest container.\n\t\t\tif (outers[i].area >= hole.area && pointInRings(sample, [outers[i].ring], 'evenodd')) {\n\t\t\t\towner = i;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (owner >= 0) polygons[owner].push(hole.ring);\n\t}\n\treturn polygons;\n}\n\nfunction extract(overlay: Overlay, isInside: (flags: boolean[]) => boolean): MultiPolygon {\n\tconst directed: DirectedEdge[] = [];\n\toverlay.edges.forEach((edge, i) => {\n\t\tconst l = isInside(insideFlags(overlay.left[i], overlay.fillRules));\n\t\tconst r = isInside(insideFlags(overlay.right[i], overlay.fillRules));\n\t\tif (l === r) return;\n\t\tconst [from, to] = l ? [edge.a, edge.b] : [edge.b, edge.a];\n\t\tdirected.push({ from, to, angle: Math.atan2(to.y - from.y, to.x - from.x) });\n\t});\n\treturn assemble(traceRings(directed), overlay.eps);\n}\n\nfunction predicate(operation: PolygonBooleanOperation): (flags: boolean[]) => boolean {\n\tswitch (operation) {\n\t\tcase 'union':\n\t\t\treturn (f) => f.some(Boolean);\n\t\tcase 'intersection':\n\t\t\treturn (f) => f.length > 0 && f.every(Boolean);\n\t\tcase 'difference':\n\t\t\treturn (f) => f[0] === true && !f.slice(1).some(Boolean);\n\t\tcase 'xor':\n\t\t\treturn (f) => f.filter(Boolean).length % 2 === 1;\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Public operations\n// ---------------------------------------------------------------------------\n\n/**\n * Applies a boolean operation to any number of operands.\n * - `union`: covered by any operand.\n * - `intersection`: covered by every operand.\n * - `difference`: the first operand minus all others.\n * - `xor`: covered by an odd number of operands (Exclude).\n *\n * Returns simple rings: outer rings have a positive signed area, holes a negative one.\n * Rings may touch at vertices but never cross.\n */\nexport function polygonBoolean(\n\toperation: PolygonBooleanOperation,\n\toperands: PolygonInput[],\n\toptions: PolygonBooleanOptions = {}\n): MultiPolygon {\n\tif (operands.length === 0) return [];\n\treturn extract(buildOverlay(operands, options), predicate(operation));\n}\n\nexport const unionPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('union', [a, b], options);\nexport const intersectPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('intersection', [a, b], options);\nexport const subtractPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('difference', [a, b], options);\nexport const xorPolygons = (a: PolygonInput, b: PolygonInput, options?: PolygonBooleanOptions) =>\n\tpolygonBoolean('xor', [a, b], options);\n\n/**\n * Splits the overlay of the operands into its distinct regions (Freeform's Divide): every\n * connected area covered by the same set of operands becomes one piece. Pieces are ordered\n * by their operand set, then by size (largest first).\n */\nexport function dividePolygons(operands: PolygonInput[], options: PolygonBooleanOptions = {}): PolygonPiece[] {\n\tif (operands.length === 0) return [];\n\tconst overlay = buildOverlay(operands, options);\n\tconst signatures = new Map<string, number[]>();\n\tconst note = (windings: number[]) => {\n\t\tconst flags = insideFlags(windings, overlay.fillRules);\n\t\tif (!flags.some(Boolean)) return;\n\t\tconst key = flags.map((f) => (f ? '1' : '0')).join('');\n\t\tif (!signatures.has(key)) signatures.set(key, flags.flatMap((f, k) => (f ? [k] : [])));\n\t};\n\toverlay.edges.forEach((_, i) => {\n\t\tnote(overlay.left[i]);\n\t\tnote(overlay.right[i]);\n\t});\n\tconst pieces: PolygonPiece[] = [];\n\tconst keys = [...signatures.keys()].sort();\n\tfor (const key of keys) {\n\t\tconst polygons = extract(overlay, (flags) => flags.map((f) => (f ? '1' : '0')).join('') === key);\n\t\tpolygons.sort((a, b) => polygonArea(b) - polygonArea(a));\n\t\tfor (const polygon of polygons) pieces.push({ polygon, operands: signatures.get(key)! });\n\t}\n\treturn pieces;\n}\n","/**\n * Shape geometry: shape descriptions (rect, rounded rect, ellipse, regular polygon, star and\n * SVG path data) to polygons, SVG path parsing / flattening, and polygons back to path data.\n */\nimport type { Point } from './types.js';\nimport type { MultiPolygon, PolygonFillRule, PolygonRing } from './polygonBoolean.js';\n\n// ---------------------------------------------------------------------------\n// Shape descriptions\n// ---------------------------------------------------------------------------\n\n/** Paint shared by every shape kind. Colours are any CSS colour. */\nexport interface ShapePaint {\n\tfill?: string;\n\tstroke?: string;\n\tstrokeWidth?: number;\n\topacity?: number;\n}\n\ninterface ShapeBase extends ShapePaint {\n\t/** Clockwise rotation in degrees (on a y-down canvas) about the centre of the node box. */\n\trotation?: number;\n}\n\n/** Rectangle filling the node box, optionally with rounded corners. */\nexport interface RectShape extends ShapeBase {\n\tkind: 'rect';\n\t/** One radius, or `[topLeft, topRight, bottomRight, bottomLeft]`. Clamped to fit. */\n\tcornerRadius?: number | readonly [number, number, number, number];\n}\n\n/** Ellipse (circle when the node is square) inscribed in the node box. */\nexport interface EllipseShape extends ShapeBase {\n\tkind: 'ellipse';\n}\n\n/** Regular polygon, stretched so its bounding box fills the node box. */\nexport interface RegularPolygonShape extends ShapeBase {\n\tkind: 'polygon';\n\t/** Number of sides (at least 3, default 6). The first vertex points up. */\n\tsides?: number;\n}\n\n/** Star, stretched so its bounding box fills the node box. */\nexport interface StarShape extends ShapeBase {\n\tkind: 'star';\n\t/** Number of points (at least 3, default 5). The first point points up. */\n\tpoints?: number;\n\t/** Inner radius as a fraction of the outer radius (default 0.5). */\n\tinnerRadius?: number;\n}\n\n/** Arbitrary SVG path data. */\nexport interface PathShape extends ShapeBase {\n\tkind: 'path';\n\td: string;\n\t/**\n\t * `[minX, minY, width, height]` of the path's coordinate space, scaled to the node box.\n\t * Without it the path coordinates are node-local pixels.\n\t */\n\tviewBox?: readonly [number, number, number, number];\n\t/** Fill rule (default `nonzero`, as in SVG). */\n\tfillRule?: PolygonFillRule;\n}\n\n/**\n * Shape description stored on a node as `data.shape`. Geometry is relative to the node\n * box (`0,0` to `width,height`), so resizing the node resizes the shape.\n */\nexport type ShapeDescription = RectShape | EllipseShape | RegularPolygonShape | StarShape | PathShape;\n\nexport type ShapeKind = ShapeDescription['kind'];\n\nexport const SHAPE_KINDS: readonly ShapeKind[] = ['rect', 'ellipse', 'polygon', 'star', 'path'];\n\n/** Whether a value is a supported shape description. */\nexport function isShapeDescription(value: unknown): value is ShapeDescription {\n\tif (!value || typeof value !== 'object') return false;\n\tconst kind = (value as { kind?: unknown }).kind;\n\tif (!SHAPE_KINDS.includes(kind as ShapeKind)) return false;\n\treturn kind !== 'path' || typeof (value as PathShape).d === 'string';\n}\n\nexport interface ShapeSize {\n\twidth: number;\n\theight: number;\n}\n\nexport interface FlattenOptions {\n\t/** Maximum distance between a curve and its polyline, in output units (default 0.25). */\n\ttolerance?: number;\n\t/** Upper bound on the segments one curve is split into (default 512). */\n\tmaxSegments?: number;\n}\n\n// ---------------------------------------------------------------------------\n// SVG path parsing\n// ---------------------------------------------------------------------------\n\n/** Normalised absolute path command produced by `parseSvgPath`. */\nexport type PathCommand =\n\t| { type: 'M'; x: number; y: number }\n\t| { type: 'L'; x: number; y: number }\n\t| { type: 'C'; x1: number; y1: number; x2: number; y2: number; x: number; y: number }\n\t| { type: 'Q'; x1: number; y1: number; x: number; y: number }\n\t| {\n\t\t\ttype: 'A';\n\t\t\trx: number;\n\t\t\try: number;\n\t\t\trotation: number;\n\t\t\tlargeArc: boolean;\n\t\t\tsweep: boolean;\n\t\t\tx: number;\n\t\t\ty: number;\n\t }\n\t| { type: 'Z' };\n\nconst PARAM_COUNTS: Record<string, number> = { m: 2, l: 2, h: 1, v: 1, c: 6, s: 4, q: 4, t: 2, a: 7, z: 0 };\n\n/** Character scanner for path data (handles `1.5.5`, `1e-3`, `-1-2` and compact arc flags). */\nclass PathScanner {\n\tprivate pos = 0;\n\tconstructor(private readonly text: string) {}\n\n\tprivate skip() {\n\t\twhile (this.pos < this.text.length && /[\\s,]/.test(this.text[this.pos])) this.pos++;\n\t}\n\n\t/** The next command letter, or `null` when a number (or the end) follows. */\n\tcommand(): string | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch && /[MmLlHhVvCcSsQqTtAaZz]/.test(ch)) {\n\t\t\tthis.pos++;\n\t\t\treturn ch;\n\t\t}\n\t\treturn null;\n\t}\n\n\tdone(): boolean {\n\t\tthis.skip();\n\t\treturn this.pos >= this.text.length;\n\t}\n\n\tnumber(): number | null {\n\t\tthis.skip();\n\t\tconst match = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?/.exec(this.text.slice(this.pos, this.pos + 64));\n\t\tif (!match) return null;\n\t\tthis.pos += match[0].length;\n\t\tconst value = Number(match[0]);\n\t\treturn Number.isFinite(value) ? value : null;\n\t}\n\n\tflag(): number | null {\n\t\tthis.skip();\n\t\tconst ch = this.text[this.pos];\n\t\tif (ch === '0' || ch === '1') {\n\t\t\tthis.pos++;\n\t\t\treturn ch === '1' ? 1 : 0;\n\t\t}\n\t\treturn null;\n\t}\n}\n\n/**\n * Parses SVG path data into absolute `M`, `L`, `C`, `Q`, `A` and `Z` commands. Relative\n * commands, `H` / `V`, smooth curves (`S` / `T`) and implicit repeated commands are\n * normalised. Parsing stops at the first malformed command (as browsers do).\n */\nexport function parseSvgPath(d: string): PathCommand[] {\n\tconst scanner = new PathScanner(d ?? '');\n\tconst commands: PathCommand[] = [];\n\tlet cx = 0;\n\tlet cy = 0;\n\tlet startX = 0;\n\tlet startY = 0;\n\tlet lastControl: { x: number; y: number; type: 'C' | 'Q' } | null = null;\n\tlet command = '';\n\n\twhile (!scanner.done()) {\n\t\tconst letter = scanner.command();\n\t\tif (letter) {\n\t\t\tcommand = letter;\n\t\t\tif (command === 'z' || command === 'Z') {\n\t\t\t\tcommands.push({ type: 'Z' });\n\t\t\t\tcx = startX;\n\t\t\t\tcy = startY;\n\t\t\t\tlastControl = null;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t} else if (!command || command === 'z' || command === 'Z') {\n\t\t\tbreak;\n\t\t}\n\t\tconst lower = command.toLowerCase();\n\t\tconst count = PARAM_COUNTS[lower];\n\t\tconst args: number[] = [];\n\t\tfor (let k = 0; k < count; k++) {\n\t\t\tconst value = lower === 'a' && (k === 3 || k === 4) ? scanner.flag() : scanner.number();\n\t\t\tif (value === null) return commands;\n\t\t\targs.push(value);\n\t\t}\n\t\tconst rel = command !== command.toUpperCase();\n\t\tconst ox = rel ? cx : 0;\n\t\tconst oy = rel ? cy : 0;\n\t\tswitch (lower) {\n\t\t\tcase 'm': {\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tstartX = cx;\n\t\t\t\tstartY = cy;\n\t\t\t\tcommands.push({ type: 'M', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\t// Extra coordinate pairs after a moveto are implicit linetos.\n\t\t\t\tcommand = rel ? 'l' : 'L';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'l':\n\t\t\t\tcx = args[0] + ox;\n\t\t\t\tcy = args[1] + oy;\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'h':\n\t\t\t\tcx = args[0] + (rel ? cx : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'v':\n\t\t\t\tcy = args[0] + (rel ? cy : 0);\n\t\t\t\tcommands.push({ type: 'L', x: cx, y: cy });\n\t\t\t\tlastControl = null;\n\t\t\t\tbreak;\n\t\t\tcase 'c': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1: args[0] + ox,\n\t\t\t\t\ty1: args[1] + oy,\n\t\t\t\t\tx2: args[2] + ox,\n\t\t\t\t\ty2: args[3] + oy,\n\t\t\t\t\tx: args[4] + ox,\n\t\t\t\t\ty: args[5] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 's': {\n\t\t\t\tconst x1: number = lastControl?.type === 'C' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'C' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'C',\n\t\t\t\t\tx1,\n\t\t\t\t\ty1,\n\t\t\t\t\tx2: args[0] + ox,\n\t\t\t\t\ty2: args[1] + oy,\n\t\t\t\t\tx: args[2] + ox,\n\t\t\t\t\ty: args[3] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x2, y: c.y2, type: 'C' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'q': {\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1: args[0] + ox, y1: args[1] + oy, x: args[2] + ox, y: args[3] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: c.x1, y: c.y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 't': {\n\t\t\t\tconst x1: number = lastControl?.type === 'Q' ? 2 * cx - lastControl.x : cx;\n\t\t\t\tconst y1: number = lastControl?.type === 'Q' ? 2 * cy - lastControl.y : cy;\n\t\t\t\tconst c: PathCommand = { type: 'Q', x1, y1, x: args[0] + ox, y: args[1] + oy };\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = { x: x1, y: y1, type: 'Q' };\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'a': {\n\t\t\t\tconst c: PathCommand = {\n\t\t\t\t\ttype: 'A',\n\t\t\t\t\trx: Math.abs(args[0]),\n\t\t\t\t\try: Math.abs(args[1]),\n\t\t\t\t\trotation: args[2],\n\t\t\t\t\tlargeArc: args[3] !== 0,\n\t\t\t\t\tsweep: args[4] !== 0,\n\t\t\t\t\tx: args[5] + ox,\n\t\t\t\t\ty: args[6] + oy\n\t\t\t\t};\n\t\t\t\tcommands.push(c);\n\t\t\t\tlastControl = null;\n\t\t\t\tcx = c.x;\n\t\t\t\tcy = c.y;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\treturn commands;\n}\n\n// ---------------------------------------------------------------------------\n// Curve flattening\n// ---------------------------------------------------------------------------\n\nfunction segmentCount(curvature: number, tolerance: number, max: number): number {\n\tif (!(curvature > 0)) return 1;\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.sqrt(curvature / tolerance))));\n}\n\nfunction flattenCubic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\t// Uniform subdivision error <= max|B''| / (8 n²) with |B''| <= 6 * max second difference.\n\tconst dd = Math.max(\n\t\tMath.hypot(p0[0] - 2 * x1 + x2, p0[1] - 2 * y1 + y2),\n\t\tMath.hypot(x1 - 2 * x2 + x, y1 - 2 * y2 + y)\n\t);\n\tconst n = segmentCount((6 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tconst a = mt * mt * mt;\n\t\tconst b = 3 * mt * mt * t;\n\t\tconst c = 3 * mt * t * t;\n\t\tconst e = t * t * t;\n\t\tout.push([a * p0[0] + b * x1 + c * x2 + e * x, a * p0[1] + b * y1 + c * y2 + e * y]);\n\t}\n}\n\nfunction flattenQuadratic(\n\tout: Point[],\n\tp0: Point,\n\tx1: number,\n\ty1: number,\n\tx: number,\n\ty: number,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst dd = Math.hypot(p0[0] - 2 * x1 + x, p0[1] - 2 * y1 + y);\n\tconst n = segmentCount((2 * dd) / 8, tolerance, max);\n\tfor (let k = 1; k <= n; k++) {\n\t\tconst t = k / n;\n\t\tconst mt = 1 - t;\n\t\tout.push([mt * mt * p0[0] + 2 * mt * t * x1 + t * t * x, mt * mt * p0[1] + 2 * mt * t * y1 + t * t * y]);\n\t}\n}\n\nfunction arcSteps(radius: number, sweep: number, tolerance: number, max: number): number {\n\tif (radius <= tolerance) return Math.max(1, Math.ceil(Math.abs(sweep) / (Math.PI / 2)));\n\tconst step = 2 * Math.acos(1 - tolerance / radius);\n\treturn Math.max(1, Math.min(max, Math.ceil(Math.abs(sweep) / step)));\n}\n\n/** Flattens an SVG elliptical arc (endpoint parameterisation, SVG 1.1 F.6.5). */\nfunction flattenArc(\n\tout: Point[],\n\tp0: Point,\n\tcmd: Extract<PathCommand, { type: 'A' }>,\n\ttolerance: number,\n\tmax: number\n) {\n\tconst [x0, y0] = p0;\n\tlet { rx, ry } = cmd;\n\tconst { x, y } = cmd;\n\tif (x0 === x && y0 === y) return;\n\tif (rx === 0 || ry === 0) {\n\t\tout.push([x, y]);\n\t\treturn;\n\t}\n\tconst phi = (cmd.rotation * Math.PI) / 180;\n\tconst cos = Math.cos(phi);\n\tconst sin = Math.sin(phi);\n\tconst dx = (x0 - x) / 2;\n\tconst dy = (y0 - y) / 2;\n\tconst x1p = cos * dx + sin * dy;\n\tconst y1p = -sin * dx + cos * dy;\n\tconst lambda = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry);\n\tif (lambda > 1) {\n\t\tconst s = Math.sqrt(lambda);\n\t\trx *= s;\n\t\try *= s;\n\t}\n\tconst num = rx * rx * ry * ry - rx * rx * y1p * y1p - ry * ry * x1p * x1p;\n\tconst den = rx * rx * y1p * y1p + ry * ry * x1p * x1p;\n\tlet coef = den === 0 ? 0 : Math.sqrt(Math.max(0, num / den));\n\tif (cmd.largeArc === cmd.sweep) coef = -coef;\n\tconst cxp = (coef * rx * y1p) / ry;\n\tconst cyp = (-coef * ry * x1p) / rx;\n\tconst cx = cos * cxp - sin * cyp + (x0 + x) / 2;\n\tconst cy = sin * cxp + cos * cyp + (y0 + y) / 2;\n\tconst angle = (ux: number, uy: number, vx: number, vy: number) => {\n\t\tconst a = Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);\n\t\treturn a;\n\t};\n\tconst theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);\n\tlet delta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);\n\tif (!cmd.sweep && delta > 0) delta -= 2 * Math.PI;\n\telse if (cmd.sweep && delta < 0) delta += 2 * Math.PI;\n\tconst n = arcSteps(Math.max(rx, ry), delta, tolerance, max);\n\tfor (let k = 1; k < n; k++) {\n\t\tconst t = theta1 + (delta * k) / n;\n\t\tconst ex = rx * Math.cos(t);\n\t\tconst ey = ry * Math.sin(t);\n\t\tout.push([cos * ex - sin * ey + cx, sin * ex + cos * ey + cy]);\n\t}\n\tout.push([x, y]);\n}\n\n/**\n * Flattens SVG path data into closed rings (one per subpath; open subpaths are closed the\n * way SVG fills them). Curves are approximated within `tolerance`.\n */\nexport function flattenSvgPath(d: string | PathCommand[], options: FlattenOptions = {}): PolygonRing[] {\n\tconst tolerance = options.tolerance && options.tolerance > 0 ? options.tolerance : 0.25;\n\tconst max = options.maxSegments ?? 512;\n\tconst commands = typeof d === 'string' ? parseSvgPath(d) : d;\n\tconst rings: PolygonRing[] = [];\n\tlet current: Point[] = [];\n\tlet start: Point = [0, 0];\n\tconst flush = () => {\n\t\tif (current.length >= 3) rings.push(current);\n\t\tcurrent = [];\n\t};\n\tconst last = (): Point => current[current.length - 1] ?? start;\n\tfor (const cmd of commands) {\n\t\tswitch (cmd.type) {\n\t\t\tcase 'M':\n\t\t\t\tflush();\n\t\t\t\tstart = [cmd.x, cmd.y];\n\t\t\t\tcurrent = [start];\n\t\t\t\tbreak;\n\t\t\tcase 'L':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tcurrent.push([cmd.x, cmd.y]);\n\t\t\t\tbreak;\n\t\t\tcase 'C':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenCubic(current, last(), cmd.x1, cmd.y1, cmd.x2, cmd.y2, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Q':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenQuadratic(current, last(), cmd.x1, cmd.y1, cmd.x, cmd.y, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'A':\n\t\t\t\tif (current.length === 0) current = [start];\n\t\t\t\tflattenArc(current, last(), cmd, tolerance, max);\n\t\t\t\tbreak;\n\t\t\tcase 'Z':\n\t\t\t\tflush();\n\t\t\t\t// A command after Z without a moveto starts at the subpath start.\n\t\t\t\tbreak;\n\t\t}\n\t}\n\tflush();\n\treturn rings.map(dedupeRing).filter((r) => r.length >= 3);\n}\n\nfunction dedupeRing(ring: Point[]): Point[] {\n\tconst out: Point[] = [];\n\tfor (const p of ring) {\n\t\tconst prev = out[out.length - 1];\n\t\tif (!prev || prev[0] !== p[0] || prev[1] !== p[1]) out.push(p);\n\t}\n\twhile (out.length > 1 && out[0][0] === out[out.length - 1][0] && out[0][1] === out[out.length - 1][1]) out.pop();\n\treturn out;\n}\n\n// ---------------------------------------------------------------------------\n// Shapes to polygons\n// ---------------------------------------------------------------------------\n\nfunction cornerRadii(shape: RectShape, width: number, height: number): [number, number, number, number] {\n\tconst raw = shape.cornerRadius ?? 0;\n\tlet radii = (typeof raw === 'number' ? [raw, raw, raw, raw] : [...raw]).map((r) =>\n\t\tNumber.isFinite(r) && r > 0 ? r : 0\n\t) as [number, number, number, number];\n\t// Scale all radii down together when adjacent corners would overlap (CSS rule).\n\tconst factor = Math.min(\n\t\t1,\n\t\twidth / Math.max(radii[0] + radii[1], 1e-12),\n\t\theight / Math.max(radii[1] + radii[2], 1e-12),\n\t\twidth / Math.max(radii[2] + radii[3], 1e-12),\n\t\theight / Math.max(radii[3] + radii[0], 1e-12)\n\t);\n\tif (factor < 1) radii = radii.map((r) => r * factor) as [number, number, number, number];\n\treturn radii;\n}\n\n/** Unit vertices of a regular polygon / star, stretched to fill the box. */\nfunction polygonVertices(count: number, inner: number | null, width: number, height: number): Point[] {\n\tconst raw: Point[] = [];\n\tconst total = inner === null ? count : count * 2;\n\tfor (let k = 0; k < total; k++) {\n\t\tconst angle = -Math.PI / 2 + (k * 2 * Math.PI) / total;\n\t\tconst r = inner !== null && k % 2 === 1 ? inner : 1;\n\t\traw.push([r * Math.cos(angle), r * Math.sin(angle)]);\n\t}\n\tlet minX = Infinity;\n\tlet minY = Infinity;\n\tlet maxX = -Infinity;\n\tlet maxY = -Infinity;\n\tfor (const [x, y] of raw) {\n\t\tminX = Math.min(minX, x);\n\t\tminY = Math.min(minY, y);\n\t\tmaxX = Math.max(maxX, x);\n\t\tmaxY = Math.max(maxY, y);\n\t}\n\tconst sx = width / (maxX - minX || 1);\n\tconst sy = height / (maxY - minY || 1);\n\treturn raw.map(([x, y]) => [(x - minX) * sx, (y - minY) * sy]);\n}\n\nfunction rotationTransform(shape: ShapeDescription, size: ShapeSize): ((p: Point) => Point) | null {\n\tconst deg = shape.rotation ?? 0;\n\tif (!deg || !Number.isFinite(deg)) return null;\n\tconst rad = (deg * Math.PI) / 180;\n\tconst cos = Math.cos(rad);\n\tconst sin = Math.sin(rad);\n\tconst cx = size.width / 2;\n\tconst cy = size.height / 2;\n\treturn ([x, y]) => [cx + (x - cx) * cos - (y - cy) * sin, cy + (x - cx) * sin + (y - cy) * cos];\n}\n\n/**\n * SVG path data for a shape in node-local pixels (exact curves: arcs for rounded corners\n * and ellipses), plus the fill rule and a `transform` for rotation / path view boxes.\n * Use it to render a shape; use `shapeToPolygon` for geometry.\n */\nexport function getShapeSvg(\n\tshape: ShapeDescription,\n\tsize: ShapeSize\n): { d: string; transform?: string; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tconst f = (n: number) => formatNumber(n, 3);\n\tconst transforms: string[] = [];\n\tif (shape.rotation) transforms.push(`rotate(${f(shape.rotation)} ${f(w / 2)} ${f(h / 2)})`);\n\tlet d = '';\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tswitch (shape.kind) {\n\t\tcase 'rect': {\n\t\t\tconst [tl, tr, br, bl] = cornerRadii(shape, w, h);\n\t\t\tif (tl + tr + br + bl === 0) {\n\t\t\t\td = `M0 0H${f(w)}V${f(h)}H0Z`;\n\t\t\t} else {\n\t\t\t\tconst arc = (r: number, x: number, y: number) => (r > 0 ? `A${f(r)} ${f(r)} 0 0 1 ${f(x)} ${f(y)}` : '');\n\t\t\t\td =\n\t\t\t\t\t`M${f(tl)} 0H${f(w - tr)}${arc(tr, w, tr)}V${f(h - br)}${arc(br, w - br, h)}` +\n\t\t\t\t\t`H${f(bl)}${arc(bl, 0, h - bl)}V${f(tl)}${arc(tl, tl, 0)}Z`;\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t\tcase 'ellipse': {\n\t\t\tconst rx = w / 2;\n\t\t\tconst ry = h / 2;\n\t\t\td = `M0 ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 ${f(w)} ${f(ry)}A${f(rx)} ${f(ry)} 0 1 1 0 ${f(ry)}Z`;\n\t\t\tbreak;\n\t\t}\n\t\tcase 'polygon':\n\t\tcase 'star': {\n\t\t\tconst points =\n\t\t\t\tshape.kind === 'polygon'\n\t\t\t\t\t? polygonVertices(Math.max(3, Math.floor(shape.sides ?? 6)), null, w, h)\n\t\t\t\t\t: polygonVertices(Math.max(3, Math.floor(shape.points ?? 5)), clampRatio(shape.innerRadius), w, h);\n\t\t\td = ringsToPath([points], f);\n\t\t\tbreak;\n\t\t}\n\t\tcase 'path': {\n\t\t\td = shape.d;\n\t\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\t\tconst vb = validViewBox(shape.viewBox);\n\t\t\tif (vb) {\n\t\t\t\ttransforms.push(\n\t\t\t\t\t`scale(${f(w / vb[2])} ${f(h / vb[3])})`,\n\t\t\t\t\t`translate(${f(-vb[0])} ${f(-vb[1])})`\n\t\t\t\t);\n\t\t\t}\n\t\t\tbreak;\n\t\t}\n\t}\n\treturn transforms.length > 0 ? { d, transform: transforms.join(' '), fillRule } : { d, fillRule };\n}\n\n/** Everything a renderer needs to draw a shape as one SVG `<path>`. */\nexport interface ShapeRenderData {\n\t/** SVG `viewBox` (`0 0 width height`): node-local pixels. */\n\tviewBox: string;\n\twidth: number;\n\theight: number;\n\td: string;\n\ttransform?: string;\n\tfillRule: PolygonFillRule;\n\tfill: string;\n\tstroke: string;\n\tstrokeWidth: number;\n\topacity?: number;\n}\n\n/**\n * Resolves the SVG drawing of a shape for a node size. Without a size, a path's view box\n * size (or 100x100) is used. Paint defaults: `fill: currentColor`, `stroke: none`,\n * `strokeWidth: 1`.\n */\nexport function getShapeRenderData(shape: ShapeDescription, size: Partial<ShapeSize> = {}): ShapeRenderData {\n\tconst vb = shape.kind === 'path' ? validViewBox(shape.viewBox) : null;\n\tconst width = size.width && size.width > 0 ? size.width : vb ? vb[2] : 100;\n\tconst height = size.height && size.height > 0 ? size.height : vb ? vb[3] : 100;\n\tconst svg = getShapeSvg(shape, { width, height });\n\tconst data: ShapeRenderData = {\n\t\tviewBox: `0 0 ${formatNumber(width, 3)} ${formatNumber(height, 3)}`,\n\t\twidth,\n\t\theight,\n\t\td: svg.d,\n\t\tfillRule: svg.fillRule,\n\t\tfill: shape.fill ?? 'currentColor',\n\t\tstroke: shape.stroke ?? 'none',\n\t\tstrokeWidth: shape.strokeWidth ?? 1\n\t};\n\tif (svg.transform) data.transform = svg.transform;\n\tif (shape.opacity !== undefined) data.opacity = shape.opacity;\n\treturn data;\n}\n\nfunction clampRatio(value: number | undefined): number {\n\tif (value === undefined || !Number.isFinite(value)) return 0.5;\n\treturn Math.min(1, Math.max(0.01, value));\n}\n\nfunction validViewBox(vb: PathShape['viewBox']): readonly [number, number, number, number] | null {\n\tif (!vb || vb.length !== 4 || !vb.every(Number.isFinite) || vb[2] <= 0 || vb[3] <= 0) return null;\n\treturn vb;\n}\n\n/**\n * Converts a shape to rings in node-local pixels (rotation applied), flattening curves at\n * `tolerance`. Returns the rings with the fill rule they must be read with.\n */\nexport function shapeToPolygon(\n\tshape: ShapeDescription,\n\tsize: ShapeSize,\n\toptions: FlattenOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } {\n\tconst { width: w, height: h } = size;\n\tif (!(w > 0) || !(h > 0)) return { rings: [], fillRule: 'nonzero' };\n\tlet rings: PolygonRing[];\n\tlet fillRule: PolygonFillRule = 'nonzero';\n\tif (shape.kind === 'path') {\n\t\tfillRule = shape.fillRule ?? 'nonzero';\n\t\tconst vb = validViewBox(shape.viewBox);\n\t\tif (vb) {\n\t\t\tconst sx = w / vb[2];\n\t\t\tconst sy = h / vb[3];\n\t\t\t// Flatten in path units with the tolerance scaled to stay within `tolerance` px.\n\t\t\tconst tolerance = (options.tolerance ?? 0.25) / Math.max(sx, sy);\n\t\t\trings = flattenSvgPath(shape.d, { ...options, tolerance }).map((ring) =>\n\t\t\t\tring.map(([x, y]) => [(x - vb[0]) * sx, (y - vb[1]) * sy] as Point)\n\t\t\t);\n\t\t} else {\n\t\t\trings = flattenSvgPath(shape.d, options);\n\t\t}\n\t} else {\n\t\tconst { d } = getShapeSvg({ ...shape, rotation: 0 }, size);\n\t\trings = flattenSvgPath(d, options);\n\t}\n\tconst rotate = rotationTransform(shape, size);\n\tif (rotate) rings = rings.map((ring) => ring.map(rotate));\n\treturn { rings, fillRule };\n}\n\n// ---------------------------------------------------------------------------\n// Polygons to path data\n// ---------------------------------------------------------------------------\n\n/** Formats a number with at most `precision` decimals, without trailing zeros. */\nexport function formatNumber(value: number, precision = 3): string {\n\tconst factor = 10 ** precision;\n\tconst rounded = Math.round(value * factor) / factor;\n\treturn Object.is(rounded, -0) ? '0' : String(rounded);\n}\n\nfunction ringsToPath(rings: readonly (readonly Point[])[], f: (n: number) => string): string {\n\tlet d = '';\n\tfor (const ring of rings) {\n\t\tif (ring.length < 2) continue;\n\t\td += `M${f(ring[0][0])} ${f(ring[0][1])}`;\n\t\tfor (let i = 1; i < ring.length; i++) d += `L${f(ring[i][0])} ${f(ring[i][1])}`;\n\t\td += 'Z';\n\t}\n\treturn d;\n}\n\n/** Perpendicular distance from `p` to the segment `a`–`b`. */\nfunction segmentDistance(p: Point, a: Point, b: Point): number {\n\tconst dx = b[0] - a[0];\n\tconst dy = b[1] - a[1];\n\tconst len2 = dx * dx + dy * dy;\n\tif (len2 === 0) return Math.hypot(p[0] - a[0], p[1] - a[1]);\n\tconst t = Math.max(0, Math.min(1, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2));\n\treturn Math.hypot(p[0] - (a[0] + t * dx), p[1] - (a[1] + t * dy));\n}\n\nfunction rdp(points: Point[], tolerance: number): Point[] {\n\tif (points.length <= 2) return points;\n\tconst keep = new Uint8Array(points.length);\n\tkeep[0] = 1;\n\tkeep[points.length - 1] = 1;\n\tconst stack: [number, number][] = [[0, points.length - 1]];\n\twhile (stack.length) {\n\t\tconst [s, e] = stack.pop()!;\n\t\tlet maxDist = -1;\n\t\tlet index = -1;\n\t\tfor (let i = s + 1; i < e; i++) {\n\t\t\tconst dist = segmentDistance(points[i], points[s], points[e]);\n\t\t\tif (dist > maxDist) {\n\t\t\t\tmaxDist = dist;\n\t\t\t\tindex = i;\n\t\t\t}\n\t\t}\n\t\tif (index !== -1 && maxDist > tolerance) {\n\t\t\tkeep[index] = 1;\n\t\t\tstack.push([s, index], [index, e]);\n\t\t}\n\t}\n\treturn points.filter((_, i) => keep[i]);\n}\n\n/**\n * Simplifies a closed ring with Ramer–Douglas–Peucker at `tolerance`. Returns an empty\n * ring when fewer than three points survive.\n */\nexport function simplifyRing(ring: readonly Point[], tolerance: number): Point[] {\n\tif (ring.length <= 3 || !(tolerance > 0)) return [...ring];\n\t// Anchor on the point farthest from the first one so the closing seam is not lost.\n\tlet far = 0;\n\tlet farDist = -1;\n\tfor (let i = 1; i < ring.length; i++) {\n\t\tconst dist = Math.hypot(ring[i][0] - ring[0][0], ring[i][1] - ring[0][1]);\n\t\tif (dist > farDist) {\n\t\t\tfarDist = dist;\n\t\t\tfar = i;\n\t\t}\n\t}\n\tconst first = rdp(ring.slice(0, far + 1) as Point[], tolerance);\n\tconst second = rdp([...ring.slice(far), ring[0]] as Point[], tolerance);\n\tconst out = [...first.slice(0, -1), ...second.slice(0, -1)];\n\treturn out.length >= 3 ? out : [];\n}\n\nexport interface PolygonToPathOptions {\n\t/** Decimal places in the output (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker tolerance; `0` (default) keeps every vertex. */\n\tsimplify?: number;\n\t/** Added to every point before formatting (e.g. to make coordinates node-local). */\n\toffset?: { x: number; y: number };\n}\n\n/**\n * Converts a multi-polygon (or a list of rings) to SVG path data using `M`/`L`/`Z`. Holes\n * keep their opposite orientation, so the path fills correctly with either fill rule.\n */\nexport function polygonToSvgPath(input: MultiPolygon | PolygonRing[], options: PolygonToPathOptions = {}): string {\n\tconst precision = options.precision ?? 3;\n\tconst f = (n: number) => formatNumber(n, precision);\n\tconst dx = options.offset?.x ?? 0;\n\tconst dy = options.offset?.y ?? 0;\n\tconst rings: Point[][] = [];\n\tfor (const item of input as any[]) {\n\t\tconst list: Point[][] =\n\t\t\titem.length > 0 && Array.isArray(item[0]) && Array.isArray(item[0][0]) ? item : [item];\n\t\tfor (const ring of list) {\n\t\t\tlet r = ring;\n\t\t\tif (options.simplify && options.simplify > 0) r = simplifyRing(r, options.simplify);\n\t\t\tif (r.length < 3) continue;\n\t\t\trings.push(dx || dy ? r.map(([x, y]) => [x + dx, y + dy] as Point) : r);\n\t\t}\n\t}\n\treturn ringsToPath(rings, f);\n}\n","/**\n * Shape nodes and Freeform-style shape boolean operations (Unite / Intersect / Subtract /\n * Exclude / Divide) on canvas nodes.\n *\n * A shape node is any node whose `data.shape` is a `ShapeDescription`. Its geometry is\n * relative to the node box, so moving or resizing the node moves / resizes the shape.\n */\nimport {\n\tcreateNodeLookup,\n\tgetAbsolutePosition,\n\tgetNodeSize,\n\tisLocked,\n\twithStyleSize,\n\tLOCKED_DATA_KEY,\n\tLOCKED_PREVIOUS_FLAGS_KEY,\n\ttype NodeRectOptions\n} from './arrange.js';\nimport {\n\tdividePolygons,\n\tmultiPolygonBounds,\n\tpolygonBoolean,\n\ttype MultiPolygon,\n\ttype PolygonBooleanOperation,\n\ttype PolygonFillRule,\n\ttype PolygonRing\n} from './polygonBoolean.js';\nimport {\n\tisShapeDescription,\n\tpolygonToSvgPath,\n\tshapeToPolygon,\n\ttype PathShape,\n\ttype ShapeDescription\n} from './shapeGeometry.js';\nimport type { CanvasNode, Point } from './types.js';\n\n/** Data key holding a node's shape description. */\nexport const SHAPE_DATA_KEY = 'shape';\n\n/** Node type suggested for shape nodes (register `ShapeNode` under it). */\nexport const SHAPE_NODE_TYPE = 'shape';\n\n/** Freeform's shape operations. `exclude` is xor; `divide` splits into every region. */\nexport type ShapeOperation = 'unite' | 'intersect' | 'subtract' | 'exclude' | 'divide';\n\nexport const SHAPE_OPERATIONS: readonly ShapeOperation[] = ['unite', 'intersect', 'subtract', 'exclude', 'divide'];\n\nconst BOOLEAN_FOR: Record<Exclude<ShapeOperation, 'divide'>, PolygonBooleanOperation> = {\n\tunite: 'union',\n\tintersect: 'intersection',\n\tsubtract: 'difference',\n\texclude: 'xor'\n};\n\n/** Whether a node carries a supported `data.shape`. */\nexport function isShapeNode(node: CanvasNode | undefined | null): boolean {\n\treturn !!node && isShapeDescription(node.data?.[SHAPE_DATA_KEY]);\n}\n\n/** The node's shape description, or `null`. */\nexport function getNodeShape(node: CanvasNode | undefined | null): ShapeDescription | null {\n\tconst shape = node?.data?.[SHAPE_DATA_KEY];\n\treturn isShapeDescription(shape) ? shape : null;\n}\n\nexport interface ShapeNodePolygonOptions extends NodeRectOptions {\n\t/** Curve flattening tolerance in flow units (default 0.25). */\n\ttolerance?: number;\n}\n\n/**\n * A shape node's outline in absolute flow coordinates (parent offsets, size and rotation\n * applied), or `null` for non-shape nodes.\n */\nexport function shapeNodeToPolygon(\n\tnode: CanvasNode,\n\tlookup?: ReadonlyMap<string, CanvasNode>,\n\toptions: ShapeNodePolygonOptions = {}\n): { rings: PolygonRing[]; fillRule: PolygonFillRule } | null {\n\tconst shape = getNodeShape(node);\n\tif (!shape) return null;\n\tconst size = getNodeSize(node, options);\n\tconst origin = node.parentId && lookup ? getAbsolutePosition(node, lookup) : node.position;\n\tconst { rings, fillRule } = shapeToPolygon(shape, size, { tolerance: options.tolerance });\n\treturn {\n\t\trings: rings.map((ring) => ring.map(([x, y]) => [x + origin.x, y + origin.y] as Point)),\n\t\tfillRule\n\t};\n}\n\n/** Why `combineShapeNodes` refused. */\nexport type CombineShapeNodesRefusal =\n\t/** A selected shape is locked; locked shapes are never consumed. */\n\t| 'locked'\n\t/** Fewer than two shape nodes were selected. */\n\t| 'too-few'\n\t/** The operation leaves nothing (e.g. intersecting shapes that do not overlap). */\n\t| 'empty';\n\nexport interface CombineShapeNodesOptions extends ShapeNodePolygonOptions {\n\t/**\n\t * Which shape's node style and data the result inherits: `bottom` (default, like\n\t * Freeform: the back-most shape), `top`, `first` (first id passed), or a node id.\n\t * Divide pieces always take the style of the top-most shape covering them.\n\t */\n\tstyleFrom?: 'bottom' | 'top' | 'first' | { id: string };\n\t/** Id factory for new nodes (default `crypto.randomUUID`, with a counter fallback). */\n\tcreateId?: (index: number) => string;\n\t/** Node `type` for the new nodes (default: the style source's type). */\n\tnodeType?: string;\n\t/** Decimal places in the path data (default 3). */\n\tprecision?: number;\n\t/** Ramer–Douglas–Peucker simplification tolerance for the result (default 0, off). */\n\tsimplify?: number;\n\t/** Point-merge tolerance passed to the boolean engine. */\n\tepsilon?: number;\n\t/** Select the new nodes (default `true`). */\n\tselect?: boolean;\n}\n\nexport type CombineShapeNodesResult<TNode extends CanvasNode = CanvasNode> =\n\t| {\n\t\t\tok: true;\n\t\t\toperation: ShapeOperation;\n\t\t\t/** Ids of the shape nodes consumed by the operation. */\n\t\t\tremoveIds: string[];\n\t\t\t/** New path shape node(s): one, or one per region for `divide`. */\n\t\t\tnodes: TNode[];\n\t }\n\t| { ok: false; operation: ShapeOperation; reason: CombineShapeNodesRefusal };\n\nlet idCounter = 0;\nfunction defaultId(index: number): string {\n\tif (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') return crypto.randomUUID();\n\tidCounter += 1;\n\treturn `shape-${Date.now().toString(36)}-${idCounter}-${index}`;\n}\n\n/** Back-to-front order: `zIndex` (missing = 0), then array order, as xyflow stacks nodes. */\nfunction stackOrder<TNode extends CanvasNode>(nodes: TNode[], ids: Set<string>): TNode[] {\n\treturn nodes\n\t\t.map((node, index) => ({ node, index, z: typeof node.zIndex === 'number' ? node.zIndex : 0 }))\n\t\t.filter((entry) => ids.has(entry.node.id))\n\t\t.sort((a, b) => a.z - b.z || a.index - b.index)\n\t\t.map((entry) => entry.node);\n}\n\n/**\n * Checks whether `combineShapeNodes` can run, without computing geometry (it cannot\n * predict an `empty` result). Use it to enable menu items.\n */\nexport function canCombineShapeNodes(\n\tnodes: CanvasNode[],\n\tids: Iterable<string>\n): { ok: true; shapeIds: string[] } | { ok: false; reason: Exclude<CombineShapeNodesRefusal, 'empty'> } {\n\tconst idSet = new Set(ids);\n\tconst shapes = nodes.filter((n) => idSet.has(n.id) && isShapeNode(n));\n\tif (shapes.some((n) => isLocked(n))) return { ok: false, reason: 'locked' };\n\tif (shapes.length < 2) return { ok: false, reason: 'too-few' };\n\treturn { ok: true, shapeIds: shapes.map((n) => n.id) };\n}\n\n/**\n * Freeform-style shape operations on canvas nodes.\n *\n * - Only shape nodes (`data.shape`) among `ids` take part; other selected nodes are ignored.\n * - Refuses (`ok: false`) when a selected shape is locked, when fewer than two shapes are\n * selected, or when the result is empty. Nothing should be changed on refusal.\n * - `subtract` removes every other shape from the back-most one; the other operations are\n * symmetric. `divide` returns one node per distinct region of the overlay.\n * - Each new node is a `path` shape: its position / size is the result's bounding box and\n * its path data is node-local with `viewBox = [0, 0, width, height]`, so it scales with\n * the node. Rotation is baked into the path. Node fields and `data` (other than the\n * shape geometry, lock state and size) come from the style source.\n * - When all shapes share a parent, the new nodes keep it; otherwise they are top-level.\n *\n * Pure: returns the change; apply it with `applyShapeCombination` (or the arrange\n * controller's `combineShapes`, which also records one undo step).\n */\nexport function combineShapeNodes<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tids: Iterable<string>,\n\toperation: ShapeOperation,\n\toptions: CombineShapeNodesOptions = {}\n): CombineShapeNodesResult<TNode> {\n\tconst idList = [...ids];\n\tconst check = canCombineShapeNodes(nodes, idList);\n\tif (!check.ok) return { ok: false, operation, reason: check.reason };\n\n\tconst lookup = createNodeLookup(nodes);\n\tconst shapeIds = new Set(check.shapeIds);\n\tconst ordered = stackOrder(nodes, shapeIds);\n\tconst operands = ordered.map((node) => shapeNodeToPolygon(node, lookup, options)!);\n\n\tconst parentIds = new Set(ordered.map((n) => n.parentId ?? ''));\n\tconst sharedParent = parentIds.size === 1 ? ordered[0].parentId : undefined;\n\tconst parentOrigin =\n\t\tsharedParent && lookup.get(sharedParent)\n\t\t\t? getAbsolutePosition(lookup.get(sharedParent)!, lookup)\n\t\t\t: { x: 0, y: 0 };\n\n\tconst pickSource = (): TNode => {\n\t\tconst style = options.styleFrom ?? 'bottom';\n\t\tif (style === 'top') return ordered[ordered.length - 1];\n\t\tif (style === 'first') {\n\t\t\tconst first = idList.find((id) => shapeIds.has(id));\n\t\t\treturn (first && lookup.get(first)) || ordered[0];\n\t\t}\n\t\tif (typeof style === 'object') return (shapeIds.has(style.id) && lookup.get(style.id)) || ordered[0];\n\t\treturn ordered[0];\n\t};\n\n\tconst createId = options.createId ?? defaultId;\n\tconst build = (polygon: MultiPolygon, source: TNode, index: number): TNode | null => {\n\t\tconst bounds = multiPolygonBounds(polygon);\n\t\tif (!bounds) return null;\n\t\tconst width = bounds.maxX - bounds.minX;\n\t\tconst height = bounds.maxY - bounds.minY;\n\t\tif (!(width > 0) || !(height > 0)) return null;\n\t\tconst d = polygonToSvgPath(polygon, {\n\t\t\tprecision: options.precision,\n\t\t\tsimplify: options.simplify,\n\t\t\toffset: { x: -bounds.minX, y: -bounds.minY }\n\t\t});\n\t\tif (!d) return null;\n\t\tconst sourceShape = getNodeShape(source)!;\n\t\tconst shape: PathShape = {\n\t\t\tkind: 'path',\n\t\t\td,\n\t\t\tviewBox: [0, 0, roundSize(width), roundSize(height)],\n\t\t\tfillRule: 'nonzero'\n\t\t};\n\t\tfor (const key of ['fill', 'stroke', 'strokeWidth', 'opacity'] as const) {\n\t\t\tif (sourceShape[key] !== undefined) (shape as any)[key] = sourceShape[key];\n\t\t}\n\t\tconst {\n\t\t\t[LOCKED_DATA_KEY]: _locked,\n\t\t\t[LOCKED_PREVIOUS_FLAGS_KEY]: _previous,\n\t\t\t...data\n\t\t} = source.data ?? {};\n\t\tconst {\n\t\t\tmeasured: _measured,\n\t\t\tselected: _selected,\n\t\t\tdragging: _dragging,\n\t\t\tpositionAbsolute: _positionAbsolute,\n\t\t\tinternals: _internals,\n\t\t\tparentId: _parentId,\n\t\t\t...rest\n\t\t} = source as CanvasNode;\n\t\tconst node: CanvasNode = {\n\t\t\t...rest,\n\t\t\tid: createId(index),\n\t\t\tposition: { x: bounds.minX - parentOrigin.x, y: bounds.minY - parentOrigin.y },\n\t\t\twidth,\n\t\t\theight,\n\t\t\tdata: { ...data, [SHAPE_DATA_KEY]: shape }\n\t\t};\n\t\tif (sharedParent) node.parentId = sharedParent;\n\t\tif (options.nodeType !== undefined) node.type = options.nodeType;\n\t\tif (source.style) node.style = withStyleSize(source.style, { width, height });\n\t\tif (options.select !== false) node.selected = true;\n\t\treturn node as TNode;\n\t};\n\n\tconst created: TNode[] = [];\n\tif (operation === 'divide') {\n\t\tconst pieces = dividePolygons(operands, { epsilon: options.epsilon });\n\t\tpieces.forEach((piece) => {\n\t\t\tconst source = ordered[piece.operands[piece.operands.length - 1]];\n\t\t\tconst node = build([piece.polygon], source, created.length);\n\t\t\tif (node) created.push(node);\n\t\t});\n\t} else {\n\t\tconst result = polygonBoolean(BOOLEAN_FOR[operation], operands, { epsilon: options.epsilon });\n\t\tconst node = build(result, pickSource(), 0);\n\t\tif (node) created.push(node);\n\t}\n\tif (created.length === 0) return { ok: false, operation, reason: 'empty' };\n\treturn { ok: true, operation, removeIds: ordered.map((n) => n.id), nodes: created };\n}\n\nfunction roundSize(value: number): number {\n\treturn Math.round(value * 1000) / 1000;\n}\n\n/**\n * Applies a successful `combineShapeNodes` result: removes the consumed nodes and inserts\n * the new ones at the array index of the first consumed node.\n * With `deselect` (default `true`) other nodes are deselected when the result is selected.\n */\nexport function applyShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\toptions: { deselect?: boolean } = {}\n): TNode[] {\n\tconst remove = new Set(change.removeIds);\n\tconst insertAt = nodes.findIndex((n) => remove.has(n.id));\n\tconst deselect = options.deselect !== false && change.nodes.some((n) => n.selected);\n\tconst out: TNode[] = [];\n\tnodes.forEach((node, index) => {\n\t\tif (index === insertAt) out.push(...change.nodes);\n\t\tif (remove.has(node.id)) return;\n\t\tout.push(deselect && node.selected ? ({ ...node, selected: false } as TNode) : node);\n\t});\n\tif (insertAt === -1) out.push(...change.nodes);\n\treturn out;\n}\n\n/**\n * Reverses `applyShapeCombination`: removes the created nodes and puts the consumed nodes\n * back at their original array indices (`previous` is the node array before the change).\n */\nexport function revertShapeCombination<TNode extends CanvasNode>(\n\tnodes: TNode[],\n\tchange: { removeIds: Iterable<string>; nodes: TNode[] },\n\tprevious: TNode[]\n): TNode[] {\n\tconst created = new Set(change.nodes.map((n) => n.id));\n\tconst removed = new Set(change.removeIds);\n\tconst out = nodes.filter((n) => !created.has(n.id));\n\tprevious.forEach((node, index) => {\n\t\tif (!removed.has(node.id) || out.some((n) => n.id === node.id)) return;\n\t\tout.splice(Math.min(index, out.length), 0, node);\n\t});\n\treturn out;\n}\n","import type { Command } from '../history/types.js';\nimport {\n\talignNodes,\n\tapplyNodeUpdates,\n\tdistributeNodes,\n\tinvertNodeUpdates,\n\tisLocked,\n\tlockNodes,\n\tmatchSize,\n\treorderNodes,\n\tunlockNodes,\n\ttype AlignEdge,\n\ttype AlignNodesOptions,\n\ttype CanvasNodeUpdate,\n\ttype DistributeAxis,\n\ttype DistributeMode,\n\ttype DistributeNodesOptions,\n\ttype MatchSizeOptions,\n\ttype NodeRectOptions,\n\ttype SizeDimension,\n\ttype ZOrderOperation\n} from './arrange.js';\nimport type { CanvasArrangeAction } from './arrangeShortcuts.js';\nimport {\n\tapplyShapeCombination,\n\tcanCombineShapeNodes,\n\tcombineShapeNodes,\n\trevertShapeCombination,\n\ttype CombineShapeNodesOptions,\n\ttype CombineShapeNodesResult,\n\ttype ShapeOperation\n} from './shapeNodes.js';\nimport {\n\tcreateNodeGuideIndex,\n\tsnapNodeDrag,\n\ttype GuideIndex,\n\ttype GuideLine,\n\ttype SnapGuidesOptions,\n\ttype SnapNodeDragResult\n} from './guides.js';\nimport type { CanvasNode, XYPosition } from './types.js';\n\n/** Minimal history surface (`HistoryManager` satisfies it). */\nexport interface ArrangeHistoryTarget {\n\texecute(command: Command): void;\n}\n\nexport interface CanvasArrangeControllerOptions<TNode extends CanvasNode = CanvasNode> {\n\tgetNodes: () => TNode[];\n\tsetNodes: (nodes: TNode[]) => void;\n\t/** Ids acted on when a method is called without ids (default: nodes with `selected: true`). */\n\tgetSelectedIds?: () => string[];\n\t/** When set, every arrange action is recorded as one undoable command. */\n\thistory?: ArrangeHistoryTarget;\n\t/** Current zoom, used to convert the guide threshold from screen pixels (default 1). */\n\tgetZoom?: () => number;\n\t/** Smart-guide options (threshold in screen px, edges / centres / spacing toggles). */\n\tguides?: Omit<SnapGuidesOptions, 'zoom'>;\n\t/** Turns drag snapping off while keeping the arrange actions (default `true`). */\n\tsnapping?: boolean;\n\t/** Size resolution for unmeasured nodes. */\n\trectOptions?: NodeRectOptions;\n\t/** Called whenever the guide segments change (set while dragging, cleared on drop). */\n\tonGuidesChange?: (guides: GuideLine[]) => void;\n}\n\n/**\n * A React Flow / SvelteFlow style node change; only `position` changes are inspected.\n * `id` is optional because a batch can contain changes without one (React Flow's\n * `add` change carries an `item` instead), so `NodeChange[]` must be accepted as is.\n */\nexport interface PositionChangeLike {\n\ttype: string;\n\tid?: string;\n\tposition?: XYPosition;\n\tdragging?: boolean;\n\t[key: string]: any;\n}\n\n/**\n * Framework-agnostic arrange controller shared by the Svelte rune and the React hook:\n * align / distribute / match size / lock / z-order actions on the selection (optionally\n * undoable through a `HistoryManager`) and smart-guide snapping during drags.\n */\nexport function createCanvasArrangeController<TNode extends CanvasNode = CanvasNode>(\n\toptions: CanvasArrangeControllerOptions<TNode>\n) {\n\tlet guides: GuideLine[] = [];\n\tlet dragIndex: GuideIndex | null = null;\n\tlet dragKey = '';\n\t// Snapped positions from the last dragging batch, re-applied to the drag-stop change.\n\tlet lastSnapped = new Map<string, XYPosition>();\n\n\tconst selectedIds = (ids?: Iterable<string>) =>\n\t\tids ? [...ids] : options.getSelectedIds?.() ?? options.getNodes().filter((n) => n.selected).map((n) => n.id);\n\n\tconst setGuides = (next: GuideLine[]) => {\n\t\tif (next.length === 0 && guides.length === 0) return;\n\t\tguides = next;\n\t\toptions.onGuidesChange?.(guides);\n\t};\n\n\t/** Applies updates as one (optionally undoable) step. Returns the updates. */\n\tfunction apply(name: string, updates: CanvasNodeUpdate[]): CanvasNodeUpdate[] {\n\t\tif (updates.length === 0) return updates;\n\t\tconst inverse = invertNodeUpdates(options.getNodes(), updates);\n\t\tconst run = (u: CanvasNodeUpdate[]) => options.setNodes(applyNodeUpdates(options.getNodes(), u));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: () => run(updates), undo: () => run(inverse) });\n\t\t} else {\n\t\t\trun(updates);\n\t\t}\n\t\treturn updates;\n\t}\n\n\tfunction setLocked(name: string, ids: string[], locked: boolean): string[] {\n\t\tconst nodes = options.getNodes();\n\t\tconst set = new Set(ids);\n\t\tconst changed = nodes.filter((n) => set.has(n.id) && isLocked(n) !== locked).map((n) => n.id);\n\t\tif (changed.length === 0) return changed;\n\t\tconst lock = () => options.setNodes(lockNodes(options.getNodes(), changed));\n\t\tconst unlock = () => options.setNodes(unlockNodes(options.getNodes(), changed));\n\t\tif (options.history) {\n\t\t\toptions.history.execute({ name, execute: locked ? lock : unlock, undo: locked ? unlock : lock });\n\t\t} else if (locked) lock();\n\t\telse unlock();\n\t\treturn changed;\n\t}\n\n\tconst rect = () => options.rectOptions ?? {};\n\n\tconst controller = {\n\t\tget guides() {\n\t\t\treturn guides;\n\t\t},\n\t\tapply,\n\t\talign(edge: AlignEdge, ids?: Iterable<string>, alignOptions: AlignNodesOptions = {}) {\n\t\t\treturn apply(`Align ${edge}`, alignNodes(options.getNodes(), selectedIds(ids), edge, { ...rect(), ...alignOptions }));\n\t\t},\n\t\tdistribute(\n\t\t\taxis: DistributeAxis,\n\t\t\tmode: DistributeMode = 'gaps',\n\t\t\tids?: Iterable<string>,\n\t\t\tdistributeOptions: DistributeNodesOptions = {}\n\t\t) {\n\t\t\treturn apply(\n\t\t\t\t`Distribute ${axis}`,\n\t\t\t\tdistributeNodes(options.getNodes(), selectedIds(ids), axis, mode, { ...rect(), ...distributeOptions })\n\t\t\t);\n\t\t},\n\t\tmatchSize(dimension: SizeDimension, ids?: Iterable<string>, sizeOptions: MatchSizeOptions = {}) {\n\t\t\treturn apply(\n\t\t\t\t'Make Same Size',\n\t\t\t\tmatchSize(options.getNodes(), selectedIds(ids), dimension, { ...rect(), ...sizeOptions })\n\t\t\t);\n\t\t},\n\t\treorder(operation: ZOrderOperation, ids?: Iterable<string>) {\n\t\t\treturn apply(operation, reorderNodes(options.getNodes(), selectedIds(ids), operation, rect()));\n\t\t},\n\t\tbringForward: (ids?: Iterable<string>) => controller.reorder('bringForward', ids),\n\t\tsendBackward: (ids?: Iterable<string>) => controller.reorder('sendBackward', ids),\n\t\tbringToFront: (ids?: Iterable<string>) => controller.reorder('bringToFront', ids),\n\t\tsendToBack: (ids?: Iterable<string>) => controller.reorder('sendToBack', ids),\n\t\tlock: (ids?: Iterable<string>) => setLocked('Lock', selectedIds(ids), true),\n\t\tunlock: (ids?: Iterable<string>) => setLocked('Unlock', selectedIds(ids), false),\n\t\ttoggleLock(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst set = new Set(list);\n\t\t\tconst anyUnlocked = options.getNodes().some((n) => set.has(n.id) && !isLocked(n));\n\t\t\treturn anyUnlocked ? controller.lock(list) : controller.unlock(list);\n\t\t},\n\t\tisLocked: (id: string) => isLocked(options.getNodes().find((n) => n.id === id)),\n\n\t\t/**\n\t\t * Freeform shape operation (Unite / Intersect / Subtract / Exclude / Divide) on the\n\t\t * shape nodes among `ids` (default: the selection). Replaces them with the result as\n\t\t * one undoable step. Refuses without changing anything when a selected shape is\n\t\t * locked, fewer than two shapes are selected, or the result is empty.\n\t\t */\n\t\tcombineShapes(\n\t\t\toperation: ShapeOperation,\n\t\t\tids?: Iterable<string>,\n\t\t\tcombineOptions: CombineShapeNodesOptions = {}\n\t\t): CombineShapeNodesResult<TNode> {\n\t\t\tconst before = options.getNodes();\n\t\t\tconst result = combineShapeNodes(before, selectedIds(ids), operation, { ...rect(), ...combineOptions });\n\t\t\tif (!result.ok) return result;\n\t\t\tconst name = `${operation[0].toUpperCase()}${operation.slice(1)} Shapes`;\n\t\t\tconst run = () => options.setNodes(applyShapeCombination(options.getNodes(), result));\n\t\t\tconst undo = () => options.setNodes(revertShapeCombination(options.getNodes(), result, before));\n\t\t\tif (options.history) options.history.execute({ name, execute: run, undo });\n\t\t\telse run();\n\t\t\treturn result;\n\t\t},\n\t\tunite: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('unite', ids, o),\n\t\tintersect: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('intersect', ids, o),\n\t\tsubtract: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('subtract', ids, o),\n\t\texclude: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('exclude', ids, o),\n\t\tdivide: (ids?: Iterable<string>, o?: CombineShapeNodesOptions) => controller.combineShapes('divide', ids, o),\n\t\t/** Whether the shape operations can run on `ids` (default: the selection). */\n\t\tcanCombineShapes: (ids?: Iterable<string>) => canCombineShapeNodes(options.getNodes(), selectedIds(ids)).ok,\n\n\t\t/**\n\t\t * Runs a shortcut action. Scene actions are not handled here (see the scenes store);\n\t\t * returns `false` for them and for unknown actions.\n\t\t */\n\t\trunAction(action: CanvasArrangeAction): boolean {\n\t\t\tswitch (action) {\n\t\t\t\tcase 'alignLeft': controller.align('left'); return true;\n\t\t\t\tcase 'alignCenter': controller.align('center'); return true;\n\t\t\t\tcase 'alignRight': controller.align('right'); return true;\n\t\t\t\tcase 'alignTop': controller.align('top'); return true;\n\t\t\t\tcase 'alignMiddle': controller.align('middle'); return true;\n\t\t\t\tcase 'alignBottom': controller.align('bottom'); return true;\n\t\t\t\tcase 'distributeHorizontal': controller.distribute('horizontal'); return true;\n\t\t\t\tcase 'distributeVertical': controller.distribute('vertical'); return true;\n\t\t\t\tcase 'lock': controller.lock(); return true;\n\t\t\t\tcase 'unlock': controller.unlock(); return true;\n\t\t\t\tcase 'bringForward': controller.bringForward(); return true;\n\t\t\t\tcase 'sendBackward': controller.sendBackward(); return true;\n\t\t\t\tcase 'bringToFront': controller.bringToFront(); return true;\n\t\t\t\tcase 'sendToBack': controller.sendToBack(); return true;\n\t\t\t\tdefault: return false;\n\t\t\t}\n\t\t},\n\n\t\t/** Caches the snap targets for a drag. Optional: `drag` builds them lazily. */\n\t\tbeginDrag(ids?: Iterable<string>) {\n\t\t\tconst list = selectedIds(ids);\n\t\t\tdragKey = list.slice().sort().join('\\u0000');\n\t\t\tdragIndex = createNodeGuideIndex(options.getNodes(), list, rect());\n\t\t},\n\n\t\t/**\n\t\t * Snaps the dragged nodes (as currently positioned in `getNodes()`) to the other\n\t\t * nodes, writes the snapped positions back through `setNodes` (not recorded in\n\t\t * history) and updates `guides`.\n\t\t */\n\t\tdrag(ids?: Iterable<string>): SnapNodeDragResult | null {\n\t\t\tif (options.snapping === false) return null;\n\t\t\tconst list = selectedIds(ids);\n\t\t\tconst key = list.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(list);\n\t\t\tconst result = snapNodeDrag(options.getNodes(), list, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tlastSnapped = new Map();\n\t\t\tif (result.updates.length > 0) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(options.getNodes(), result.updates));\n\t\t\t\tfor (const u of result.updates) if (u.position) lastSnapped.set(u.id, u.position);\n\t\t\t}\n\t\t\tsetGuides(result.guides);\n\t\t\treturn result;\n\t\t},\n\n\t\t/**\n\t\t * React Flow style: adjusts dragging `position` changes in an `onNodesChange` batch so\n\t\t * the dragged nodes snap, and updates `guides`. Returns the (possibly new) changes array.\n\t\t * A batch without a dragging position change ends the drag and clears the guides.\n\t\t */\n\t\tsnapPositionChanges<TChange extends PositionChangeLike>(changes: TChange[]): TChange[] {\n\t\t\tconst dragging = changes.filter(\n\t\t\t\t(c): c is TChange & { id: string; position: XYPosition } =>\n\t\t\t\t\tc.type === 'position' && typeof c.id === 'string' && !!c.position && !!c.dragging\n\t\t\t);\n\t\t\tif (dragging.length === 0) {\n\t\t\t\tif (!changes.some((c) => c.type === 'position' && c.dragging === false)) return changes;\n\t\t\t\t// React Flow's drag-stop change carries the raw pointer position, which would\n\t\t\t\t// undo the snap of the last frame; keep the snapped position instead.\n\t\t\t\tconst snapped = lastSnapped;\n\t\t\t\tlastSnapped = new Map(); // applied to the changes below, not by endDrag\n\t\t\t\tcontroller.endDrag();\n\t\t\t\tconst keep = (c: TChange) =>\n\t\t\t\t\tc.type === 'position' && c.dragging === false && !!c.position && c.id !== undefined && snapped.has(c.id);\n\t\t\t\tif (!changes.some(keep)) return changes;\n\t\t\t\treturn changes.map((c) => (keep(c) ? { ...c, position: snapped.get(c.id!)! } : c));\n\t\t\t}\n\t\t\tif (options.snapping === false) return changes;\n\t\t\tconst ids = dragging.map((c) => c.id);\n\t\t\tconst key = ids.slice().sort().join('\\u0000');\n\t\t\tif (!dragIndex || key !== dragKey) controller.beginDrag(ids);\n\t\t\tconst proposed = new Map(dragging.map((c) => [c.id, c.position]));\n\t\t\tconst nodes = options.getNodes().map((n) => (proposed.has(n.id) ? { ...n, position: proposed.get(n.id)! } : n));\n\t\t\tconst result = snapNodeDrag(nodes, ids, {\n\t\t\t\t...rect(),\n\t\t\t\t...options.guides,\n\t\t\t\tzoom: options.getZoom?.() ?? 1,\n\t\t\t\tindex: dragIndex!\n\t\t\t});\n\t\t\tsetGuides(result.guides);\n\t\t\tconst { x: dx, y: dy } = result.offset;\n\t\t\tlastSnapped = new Map();\n\t\t\tif (dx === 0 && dy === 0) return changes;\n\t\t\t// Children of a dragged parent move with it; only shift top-level dragged nodes.\n\t\t\tconst parents = new Map(nodes.map((n) => [n.id, n.parentId]));\n\t\t\tconst shift = new Set(ids.filter((id) => !proposed.has(parents.get(id) ?? '')));\n\t\t\treturn changes.map((c) => {\n\t\t\t\tif (!(c.type === 'position' && c.position && c.dragging && c.id !== undefined && shift.has(c.id))) return c;\n\t\t\t\tconst position = { x: c.position.x + dx, y: c.position.y + dy };\n\t\t\t\tlastSnapped.set(c.id, position);\n\t\t\t\treturn { ...c, position };\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Clears guides and the cached snap targets. Call it from the flow's drag-stop\n\t\t * handler: flow libraries write the raw pointer position when a drag ends, so the\n\t\t * last snapped positions from `drag` are written back here.\n\t\t */\n\t\tendDrag() {\n\t\t\tconst snapped = lastSnapped;\n\t\t\tdragIndex = null;\n\t\t\tdragKey = '';\n\t\t\tlastSnapped = new Map();\n\t\t\tsetGuides([]);\n\t\t\tif (snapped.size === 0) return;\n\t\t\tconst nodes = options.getNodes();\n\t\t\tconst drift = nodes.some((n) => {\n\t\t\t\tconst p = snapped.get(n.id);\n\t\t\t\treturn !!p && (n.position.x !== p.x || n.position.y !== p.y);\n\t\t\t});\n\t\t\tif (drift) {\n\t\t\t\toptions.setNodes(applyNodeUpdates(nodes, [...snapped].map(([id, position]) => ({ id, position }))));\n\t\t\t}\n\t\t}\n\t};\n\treturn controller;\n}\n\nexport type CanvasArrangeController<TNode extends CanvasNode = CanvasNode> = ReturnType<\n\ttypeof createCanvasArrangeController<TNode>\n>;\n"]}