@pixerate/editor 0.14.3 → 0.15.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 (71) hide show
  1. package/dist/agent/index.cjs +28 -220
  2. package/dist/agent/index.cjs.map +1 -1
  3. package/dist/agent/index.d.cts +1 -1
  4. package/dist/agent/index.d.ts +1 -1
  5. package/dist/agent/index.js +5 -154
  6. package/dist/agent/index.js.map +1 -1
  7. package/dist/canvas/index.cjs +113 -377
  8. package/dist/canvas/index.cjs.map +1 -1
  9. package/dist/canvas/index.d.cts +2 -2
  10. package/dist/canvas/index.d.ts +2 -2
  11. package/dist/canvas/index.js +18 -208
  12. package/dist/canvas/index.js.map +1 -1
  13. package/dist/chunk-33UVEDOS.cjs +233 -0
  14. package/dist/chunk-33UVEDOS.cjs.map +1 -0
  15. package/dist/chunk-7FWEWE6T.js +1888 -0
  16. package/dist/chunk-7FWEWE6T.js.map +1 -0
  17. package/dist/chunk-BYCOFZAA.js +514 -0
  18. package/dist/chunk-BYCOFZAA.js.map +1 -0
  19. package/dist/chunk-CYKQLDYH.cjs +1290 -0
  20. package/dist/chunk-CYKQLDYH.cjs.map +1 -0
  21. package/dist/chunk-GLSIZKOX.js +17 -0
  22. package/dist/chunk-GLSIZKOX.js.map +1 -0
  23. package/dist/chunk-GWHJVZPI.js +233 -0
  24. package/dist/chunk-GWHJVZPI.js.map +1 -0
  25. package/dist/chunk-LDES3EOS.cjs +1888 -0
  26. package/dist/chunk-LDES3EOS.cjs.map +1 -0
  27. package/dist/chunk-MPPXH7U5.cjs +336 -0
  28. package/dist/chunk-MPPXH7U5.cjs.map +1 -0
  29. package/dist/chunk-NNJRJJSC.js +1290 -0
  30. package/dist/chunk-NNJRJJSC.js.map +1 -0
  31. package/dist/chunk-O7LOPQMH.js +336 -0
  32. package/dist/chunk-O7LOPQMH.js.map +1 -0
  33. package/dist/chunk-SEDR75VH.cjs +17 -0
  34. package/dist/chunk-SEDR75VH.cjs.map +1 -0
  35. package/dist/chunk-SHWP2A43.cjs +514 -0
  36. package/dist/chunk-SHWP2A43.cjs.map +1 -0
  37. package/dist/chunk-VHZCXHS4.js +222 -0
  38. package/dist/chunk-VHZCXHS4.js.map +1 -0
  39. package/dist/chunk-WADCJ7A7.cjs +222 -0
  40. package/dist/chunk-WADCJ7A7.cjs.map +1 -0
  41. package/dist/extensions/index.cjs +25 -1920
  42. package/dist/extensions/index.cjs.map +1 -1
  43. package/dist/extensions/index.js +22 -1868
  44. package/dist/extensions/index.js.map +1 -1
  45. package/dist/grammar/index.cjs +44 -387
  46. package/dist/grammar/index.cjs.map +1 -1
  47. package/dist/grammar/index.js +23 -321
  48. package/dist/grammar/index.js.map +1 -1
  49. package/dist/history/index.cjs +6 -257
  50. package/dist/history/index.cjs.map +1 -1
  51. package/dist/history/index.js +4 -227
  52. package/dist/history/index.js.map +1 -1
  53. package/dist/image-editor/index.cjs +63 -106
  54. package/dist/image-editor/index.cjs.map +1 -1
  55. package/dist/index.cjs +324 -7232
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +0 -4
  58. package/dist/index.d.ts +0 -4
  59. package/dist/index.js +258 -7007
  60. package/dist/index.js.map +1 -1
  61. package/dist/serializers/index.cjs +28 -558
  62. package/dist/serializers/index.cjs.map +1 -1
  63. package/dist/serializers/index.js +16 -496
  64. package/dist/serializers/index.js.map +1 -1
  65. package/dist/spreadsheet/index.cjs +21 -1319
  66. package/dist/spreadsheet/index.cjs.map +1 -1
  67. package/dist/spreadsheet/index.js +14 -1274
  68. package/dist/spreadsheet/index.js.map +1 -1
  69. package/dist/{types-lGtRobRV.d.cts → types-DC8b2TsA.d.cts} +1 -1
  70. package/dist/{types-lGtRobRV.d.ts → types-DC8b2TsA.d.ts} +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,222 @@
1
+ // src/canvas/layout.ts
2
+ import dagre from "@dagrejs/dagre";
3
+ var DEFAULT_NODE_WIDTH = 200;
4
+ var DEFAULT_NODE_HEIGHT = 60;
5
+ var DEFAULT_PLACEMENT_GAP = 48;
6
+ function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
7
+ const rect = screenDimensions ?? (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 });
8
+ const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
9
+ return {
10
+ x: flowPos.x - width / 2,
11
+ y: flowPos.y - height / 2
12
+ };
13
+ }
14
+ function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
15
+ if (nodes.length === 0) {
16
+ return [];
17
+ }
18
+ const bounds = getNodesBounds(nodes);
19
+ const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
20
+ const centeredPosition = getCenteredNodePosition(
21
+ bounds.width,
22
+ bounds.height,
23
+ screenToFlowPosition,
24
+ screenDimensions
25
+ );
26
+ const deltaX = centeredPosition.x - boundingBoxOrigin.x;
27
+ const deltaY = centeredPosition.y - boundingBoxOrigin.y;
28
+ return nodes.map((node) => ({
29
+ ...node,
30
+ position: {
31
+ x: node.position.x + deltaX,
32
+ y: node.position.y + deltaY
33
+ }
34
+ }));
35
+ }
36
+ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
37
+ if (nodes.length === 0) return [];
38
+ const {
39
+ direction = "LR",
40
+ ranksep = 100,
41
+ nodesep = 25,
42
+ defaultNodeWidth = DEFAULT_NODE_WIDTH,
43
+ defaultNodeHeight = DEFAULT_NODE_HEIGHT
44
+ } = options;
45
+ const dagreGraph = new dagre.graphlib.Graph();
46
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
47
+ dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
48
+ const nodeIds = new Set(nodes.map((node) => node.id));
49
+ nodes.forEach((node) => {
50
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
51
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
52
+ dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
53
+ });
54
+ edges.forEach((edge) => {
55
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
56
+ dagreGraph.setEdge(edge.source, edge.target);
57
+ }
58
+ });
59
+ dagre.layout(dagreGraph);
60
+ return nodes.map((node) => {
61
+ const nodeWithPosition = dagreGraph.node(node.id);
62
+ const nodeWidth = node.measured?.width ?? node.width ?? defaultNodeWidth;
63
+ const nodeHeight = node.measured?.height ?? node.height ?? defaultNodeHeight;
64
+ return {
65
+ ...node,
66
+ position: {
67
+ x: nodeWithPosition.x - nodeWidth / 2,
68
+ y: nodeWithPosition.y - nodeHeight / 2
69
+ }
70
+ };
71
+ });
72
+ }
73
+ function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
74
+ const label = String(node.data?.label ?? node.data?.title ?? node.id ?? "");
75
+ const lines = label.split(/\n|<br\s*\/?>/);
76
+ const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
77
+ const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
78
+ const height = Math.max(defaultHeight, lines.length * 20 + 24);
79
+ return { width, height };
80
+ }
81
+ function boundsOf(rects) {
82
+ if (!rects.length) return null;
83
+ const minX = Math.min(...rects.map((r) => r.x));
84
+ const minY = Math.min(...rects.map((r) => r.y));
85
+ const maxX = Math.max(...rects.map((r) => r.x + r.width));
86
+ const maxY = Math.max(...rects.map((r) => r.y + r.height));
87
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
88
+ }
89
+ function centre(r) {
90
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
91
+ }
92
+ function overlaps(a, b, pad = 16) {
93
+ return a.x < b.x + b.width + pad && a.x + a.width + pad > b.x && a.y < b.y + b.height + pad && a.y + a.height + pad > b.y;
94
+ }
95
+ function flowSide(direction = "LR") {
96
+ switch (direction) {
97
+ case "LR":
98
+ return "right";
99
+ case "RL":
100
+ return "left";
101
+ case "BT":
102
+ return "above";
103
+ case "TB":
104
+ default:
105
+ return "below";
106
+ }
107
+ }
108
+ function ensureLayout(nodes, edges, options = {}) {
109
+ if (nodes.length === 0) return [];
110
+ const stored = options.storedPositions ?? {};
111
+ const missing = nodes.filter((n) => !stored[n.id]);
112
+ if (missing.length === 0) {
113
+ return nodes.map((n) => ({
114
+ ...n,
115
+ position: { ...stored[n.id] }
116
+ }));
117
+ }
118
+ const autoLayouted = getLayoutedNodes(nodes, edges, void 0, options);
119
+ const autoMap = new Map(
120
+ autoLayouted.map((n) => [n.id, n.position])
121
+ );
122
+ const both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));
123
+ if (both.length === 0) {
124
+ return autoLayouted;
125
+ }
126
+ let dx = 0;
127
+ let dy = 0;
128
+ for (const n of both) {
129
+ const s = stored[n.id];
130
+ const a = autoMap.get(n.id);
131
+ dx += s.x - a.x;
132
+ dy += s.y - a.y;
133
+ }
134
+ dx /= both.length;
135
+ dy /= both.length;
136
+ return nodes.map((node) => {
137
+ if (stored[node.id]) {
138
+ return {
139
+ ...node,
140
+ position: { ...stored[node.id] }
141
+ };
142
+ }
143
+ const autoPos = autoMap.get(node.id) ?? { x: 0, y: 0 };
144
+ return {
145
+ ...node,
146
+ position: {
147
+ x: Math.round(autoPos.x + dx),
148
+ y: Math.round(autoPos.y + dy)
149
+ }
150
+ };
151
+ });
152
+ }
153
+ function placeBeside(rects, size, side, nearNodeId, options = {}) {
154
+ const {
155
+ gap = DEFAULT_PLACEMENT_GAP,
156
+ ignore = [],
157
+ maxAttempts = 32
158
+ } = options;
159
+ const skip = new Set([nearNodeId, ...ignore].filter(Boolean));
160
+ const obstacles = Object.entries(rects).filter(([id]) => !skip.has(id)).map(([, r]) => r);
161
+ const anchor = nearNodeId && rects[nearNodeId] || boundsOf(Object.values(rects)) || { x: 0, y: 0, width: 0, height: 0 };
162
+ const anchorCenter = centre(anchor);
163
+ let baseX;
164
+ let baseY;
165
+ switch (side) {
166
+ case "right":
167
+ baseX = anchor.x + anchor.width + gap;
168
+ baseY = anchorCenter.y - size.height / 2;
169
+ break;
170
+ case "left":
171
+ baseX = anchor.x - gap - size.width;
172
+ baseY = anchorCenter.y - size.height / 2;
173
+ break;
174
+ case "above":
175
+ baseX = anchorCenter.x - size.width / 2;
176
+ baseY = anchor.y - gap - size.height;
177
+ break;
178
+ case "below":
179
+ default:
180
+ baseX = anchorCenter.x - size.width / 2;
181
+ baseY = anchor.y + anchor.height + gap;
182
+ break;
183
+ }
184
+ const isHorizontal = side === "right" || side === "left";
185
+ const step = (isHorizontal ? size.height : size.width) + 20;
186
+ for (let i = 0; i < maxAttempts; i++) {
187
+ const offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);
188
+ const candidateBox = {
189
+ x: isHorizontal ? baseX : baseX + offset,
190
+ y: isHorizontal ? baseY + offset : baseY,
191
+ width: size.width,
192
+ height: size.height
193
+ };
194
+ if (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {
195
+ return {
196
+ x: Math.round(candidateBox.x),
197
+ y: Math.round(candidateBox.y)
198
+ };
199
+ }
200
+ }
201
+ return {
202
+ x: Math.round(baseX),
203
+ y: Math.round(baseY)
204
+ };
205
+ }
206
+
207
+ export {
208
+ DEFAULT_NODE_WIDTH,
209
+ DEFAULT_NODE_HEIGHT,
210
+ DEFAULT_PLACEMENT_GAP,
211
+ getCenteredNodePosition,
212
+ centerNodes,
213
+ getLayoutedNodes,
214
+ estimateNodeSize,
215
+ boundsOf,
216
+ centre,
217
+ overlaps,
218
+ flowSide,
219
+ ensureLayout,
220
+ placeBeside
221
+ };
222
+ //# sourceMappingURL=chunk-VHZCXHS4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/canvas/layout.ts"],"sourcesContent":["import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasLayoutOptions,\n\tCanvasNode,\n\tIncrementalLayoutOptions,\n\tPlaceBesideOptions,\n\tRect,\n\tSide,\n\tXYPosition\n} from './types.js';\n\nexport const DEFAULT_NODE_WIDTH = 200;\nexport const DEFAULT_NODE_HEIGHT = 60;\nexport const DEFAULT_PLACEMENT_GAP = 48;\n\n/**\n * Calculates upper-left flow coordinate for an element to be centered in the viewport.\n */\nexport function getCenteredNodePosition(\n\twidth = 0,\n\theight = 0,\n\tscreenToFlowPosition: (position: XYPosition) => XYPosition,\n\tscreenDimensions?: { width: number; height: number }\n): XYPosition {\n\tconst rect =\n\t\tscreenDimensions ??\n\t\t(typeof document !== 'undefined'\n\t\t\t? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }\n\t\t\t: { width: 1920, height: 1080 });\n\n\tconst flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });\n\treturn {\n\t\tx: flowPos.x - width / 2,\n\t\ty: flowPos.y - height / 2\n\t};\n}\n\n/**\n * Updates positions of nodes so their collective bounding box is centered in the current viewport.\n */\nexport function centerNodes<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tscreenToFlowPosition: (position: XYPosition) => XYPosition,\n\tgetNodesBounds: (nodes: TNode[]) => Rect,\n\tscreenDimensions?: { width: number; height: number }\n): TNode[] {\n\tif (nodes.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst bounds = getNodesBounds(nodes);\n\tconst boundingBoxOrigin = { x: bounds.x, y: bounds.y };\n\n\tconst centeredPosition = getCenteredNodePosition(\n\t\tbounds.width,\n\t\tbounds.height,\n\t\tscreenToFlowPosition,\n\t\tscreenDimensions\n\t);\n\n\tconst deltaX = centeredPosition.x - boundingBoxOrigin.x;\n\tconst deltaY = centeredPosition.y - boundingBoxOrigin.y;\n\n\treturn nodes.map((node) => ({\n\t\t...node,\n\t\tposition: {\n\t\t\tx: node.position.x + deltaX,\n\t\t\ty: node.position.y + deltaY\n\t\t}\n\t}));\n}\n\n/**\n * Automatically calculates directed hierarchical layout of nodes and edges using Dagre.\n */\nexport function getLayoutedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\tgetNodesBounds?: (nodes: TNode[]) => Rect,\n\toptions: CanvasLayoutOptions = {}\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\n\tconst {\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = DEFAULT_NODE_WIDTH,\n\t\tdefaultNodeHeight = DEFAULT_NODE_HEIGHT\n\t} = options;\n\n\tconst dagreGraph = new dagre.graphlib.Graph();\n\tdagreGraph.setDefaultEdgeLabel(() => ({}));\n\tdagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(nodes.map((node) => node.id));\n\n\tnodes.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\tdagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tedges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tdagreGraph.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(dagreGraph);\n\n\treturn nodes.map((node) => {\n\t\tconst nodeWithPosition = dagreGraph.node(node.id);\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\treturn {\n\t\t\t...node,\n\t\t\tposition: {\n\t\t\t\tx: nodeWithPosition.x - nodeWidth / 2,\n\t\t\t\ty: nodeWithPosition.y - nodeHeight / 2\n\t\t\t}\n\t\t};\n\t});\n}\n\n/**\n * Heuristic text-based node size estimator when DOM measurement is not available.\n */\nexport function estimateNodeSize(\n\tnode: CanvasNode,\n\tdefaultWidth = DEFAULT_NODE_WIDTH,\n\tdefaultHeight = DEFAULT_NODE_HEIGHT\n): { width: number; height: number } {\n\tconst label = String(node.data?.label ?? node.data?.title ?? node.id ?? '');\n\tconst lines = label.split(/\\n|<br\\s*\\/?>/);\n\tconst longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, '').length), 1);\n\tconst width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));\n\tconst height = Math.max(defaultHeight, lines.length * 20 + 24);\n\treturn { width, height };\n}\n\n/**\n * Computes bounding rectangle of an array of rectangles.\n */\nexport function boundsOf(rects: Rect[]): Rect | null {\n\tif (!rects.length) return null;\n\tconst minX = Math.min(...rects.map((r) => r.x));\n\tconst minY = Math.min(...rects.map((r) => r.y));\n\tconst maxX = Math.max(...rects.map((r) => r.x + r.width));\n\tconst maxY = Math.max(...rects.map((r) => r.y + r.height));\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Center coordinate of a rectangle.\n */\nexport function centre(r: Rect): XYPosition {\n\treturn { x: r.x + r.width / 2, y: r.y + r.height / 2 };\n}\n\n/**\n * Determines whether two rectangles overlap, including an optional padding distance.\n */\nexport function overlaps(a: Rect, b: Rect, pad = 16): boolean {\n\treturn (\n\t\ta.x < b.x + b.width + pad &&\n\t\ta.x + a.width + pad > b.x &&\n\t\ta.y < b.y + b.height + pad &&\n\t\ta.y + a.height + pad > b.y\n\t);\n}\n\n/**\n * Maps directed layout flow to its natural forward growth side.\n */\nexport function flowSide(direction: 'LR' | 'TB' | 'RL' | 'BT' = 'LR'): Side {\n\tswitch (direction) {\n\t\tcase 'LR':\n\t\t\treturn 'right';\n\t\tcase 'RL':\n\t\t\treturn 'left';\n\t\tcase 'BT':\n\t\t\treturn 'above';\n\t\tcase 'TB':\n\t\tdefault:\n\t\t\treturn 'below';\n\t}\n}\n\n/**\n * Incremental layout blending:\n * Keeps established/manual node positions intact. Runs Dagre on the entire graph,\n * computes the centroid translation offset between auto and stored positions of existing nodes,\n * and places unpositioned/new nodes at (auto + offset).\n */\nexport function ensureLayout<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions: IncrementalLayoutOptions = {}\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\n\tconst stored = options.storedPositions ?? {};\n\tconst missing = nodes.filter((n) => !stored[n.id]);\n\n\t// Case 1: Every node has an established coordinate -> preserve verbatim\n\tif (missing.length === 0) {\n\t\treturn nodes.map((n) => ({\n\t\t\t...n,\n\t\t\tposition: { ...stored[n.id]! }\n\t\t}));\n\t}\n\n\t// Calculate full dagre auto-layout\n\tconst autoLayouted = getLayoutedNodes(nodes, edges, undefined, options);\n\tconst autoMap = new Map<string, XYPosition>(\n\t\tautoLayouted.map((n) => [n.id, n.position])\n\t);\n\n\t// Case 2: No nodes have stored coordinates -> return auto layout directly\n\tconst both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));\n\tif (both.length === 0) {\n\t\treturn autoLayouted;\n\t}\n\n\t// Case 3: Blend! Align on centroid translation offset of overlapping nodes\n\tlet dx = 0;\n\tlet dy = 0;\n\tfor (const n of both) {\n\t\tconst s = stored[n.id]!;\n\t\tconst a = autoMap.get(n.id)!;\n\t\tdx += s.x - a.x;\n\t\tdy += s.y - a.y;\n\t}\n\tdx /= both.length;\n\tdy /= both.length;\n\n\treturn nodes.map((node) => {\n\t\tif (stored[node.id]) {\n\t\t\treturn {\n\t\t\t\t...node,\n\t\t\t\tposition: { ...stored[node.id]! }\n\t\t\t};\n\t\t}\n\t\tconst autoPos = autoMap.get(node.id) ?? { x: 0, y: 0 };\n\t\treturn {\n\t\t\t...node,\n\t\t\tposition: {\n\t\t\t\tx: Math.round(autoPos.x + dx),\n\t\t\t\ty: Math.round(autoPos.y + dy)\n\t\t\t}\n\t\t};\n\t});\n}\n\n/**\n * Finds a free top-left coordinate for a node of `size` adjacent to `nearNodeId` (or the cluster),\n * sliding along the perpendicular axis past any intersecting bounding boxes to guarantee non-overlapping placement.\n */\nexport function placeBeside(\n\trects: Record<string, Rect>,\n\tsize: { width: number; height: number },\n\tside: Side,\n\tnearNodeId?: string,\n\toptions: PlaceBesideOptions = {}\n): XYPosition {\n\tconst {\n\t\tgap = DEFAULT_PLACEMENT_GAP,\n\t\tignore = [],\n\t\tmaxAttempts = 32\n\t} = options;\n\n\tconst skip = new Set([nearNodeId, ...ignore].filter(Boolean));\n\tconst obstacles = Object.entries(rects)\n\t\t.filter(([id]) => !skip.has(id))\n\t\t.map(([, r]) => r);\n\n\tconst anchor =\n\t\t(nearNodeId && rects[nearNodeId]) ||\n\t\tboundsOf(Object.values(rects)) ||\n\t\t{ x: 0, y: 0, width: 0, height: 0 };\n\n\tconst anchorCenter = centre(anchor);\n\n\tlet baseX: number;\n\tlet baseY: number;\n\n\tswitch (side) {\n\t\tcase 'right':\n\t\t\tbaseX = anchor.x + anchor.width + gap;\n\t\t\tbaseY = anchorCenter.y - size.height / 2;\n\t\t\tbreak;\n\t\tcase 'left':\n\t\t\tbaseX = anchor.x - gap - size.width;\n\t\t\tbaseY = anchorCenter.y - size.height / 2;\n\t\t\tbreak;\n\t\tcase 'above':\n\t\t\tbaseX = anchorCenter.x - size.width / 2;\n\t\t\tbaseY = anchor.y - gap - size.height;\n\t\t\tbreak;\n\t\tcase 'below':\n\t\tdefault:\n\t\t\tbaseX = anchorCenter.x - size.width / 2;\n\t\t\tbaseY = anchor.y + anchor.height + gap;\n\t\t\tbreak;\n\t}\n\n\t// Slide perpendicular to growth direction, alternating positive and negative steps (+1, -1, +2, -2...)\n\tconst isHorizontal = side === 'right' || side === 'left';\n\tconst step = (isHorizontal ? size.height : size.width) + 20;\n\n\tfor (let i = 0; i < maxAttempts; i++) {\n\t\tconst offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);\n\t\tconst candidateBox: Rect = {\n\t\t\tx: isHorizontal ? baseX : baseX + offset,\n\t\t\ty: isHorizontal ? baseY + offset : baseY,\n\t\t\twidth: size.width,\n\t\t\theight: size.height\n\t\t};\n\n\t\tif (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {\n\t\t\treturn {\n\t\t\t\tx: Math.round(candidateBox.x),\n\t\t\t\ty: Math.round(candidateBox.y)\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tx: Math.round(baseX),\n\t\ty: Math.round(baseY)\n\t};\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,222 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }// src/canvas/layout.ts
2
+ var _dagre = require('@dagrejs/dagre'); var _dagre2 = _interopRequireDefault(_dagre);
3
+ var DEFAULT_NODE_WIDTH = 200;
4
+ var DEFAULT_NODE_HEIGHT = 60;
5
+ var DEFAULT_PLACEMENT_GAP = 48;
6
+ function getCenteredNodePosition(width = 0, height = 0, screenToFlowPosition, screenDimensions) {
7
+ const rect = _nullishCoalesce(screenDimensions, () => ( (typeof document !== "undefined" ? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight } : { width: 1920, height: 1080 })));
8
+ const flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });
9
+ return {
10
+ x: flowPos.x - width / 2,
11
+ y: flowPos.y - height / 2
12
+ };
13
+ }
14
+ function centerNodes(nodes, screenToFlowPosition, getNodesBounds, screenDimensions) {
15
+ if (nodes.length === 0) {
16
+ return [];
17
+ }
18
+ const bounds = getNodesBounds(nodes);
19
+ const boundingBoxOrigin = { x: bounds.x, y: bounds.y };
20
+ const centeredPosition = getCenteredNodePosition(
21
+ bounds.width,
22
+ bounds.height,
23
+ screenToFlowPosition,
24
+ screenDimensions
25
+ );
26
+ const deltaX = centeredPosition.x - boundingBoxOrigin.x;
27
+ const deltaY = centeredPosition.y - boundingBoxOrigin.y;
28
+ return nodes.map((node) => ({
29
+ ...node,
30
+ position: {
31
+ x: node.position.x + deltaX,
32
+ y: node.position.y + deltaY
33
+ }
34
+ }));
35
+ }
36
+ function getLayoutedNodes(nodes, edges, getNodesBounds, options = {}) {
37
+ if (nodes.length === 0) return [];
38
+ const {
39
+ direction = "LR",
40
+ ranksep = 100,
41
+ nodesep = 25,
42
+ defaultNodeWidth = DEFAULT_NODE_WIDTH,
43
+ defaultNodeHeight = DEFAULT_NODE_HEIGHT
44
+ } = options;
45
+ const dagreGraph = new _dagre2.default.graphlib.Graph();
46
+ dagreGraph.setDefaultEdgeLabel(() => ({}));
47
+ dagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });
48
+ const nodeIds = new Set(nodes.map((node) => node.id));
49
+ nodes.forEach((node) => {
50
+ const nodeWidth = _nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _ => _.measured, 'optionalAccess', _2 => _2.width]), () => ( node.width)), () => ( defaultNodeWidth));
51
+ const nodeHeight = _nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _3 => _3.measured, 'optionalAccess', _4 => _4.height]), () => ( node.height)), () => ( defaultNodeHeight));
52
+ dagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });
53
+ });
54
+ edges.forEach((edge) => {
55
+ if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {
56
+ dagreGraph.setEdge(edge.source, edge.target);
57
+ }
58
+ });
59
+ _dagre2.default.layout(dagreGraph);
60
+ return nodes.map((node) => {
61
+ const nodeWithPosition = dagreGraph.node(node.id);
62
+ const nodeWidth = _nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _5 => _5.measured, 'optionalAccess', _6 => _6.width]), () => ( node.width)), () => ( defaultNodeWidth));
63
+ const nodeHeight = _nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _7 => _7.measured, 'optionalAccess', _8 => _8.height]), () => ( node.height)), () => ( defaultNodeHeight));
64
+ return {
65
+ ...node,
66
+ position: {
67
+ x: nodeWithPosition.x - nodeWidth / 2,
68
+ y: nodeWithPosition.y - nodeHeight / 2
69
+ }
70
+ };
71
+ });
72
+ }
73
+ function estimateNodeSize(node, defaultWidth = DEFAULT_NODE_WIDTH, defaultHeight = DEFAULT_NODE_HEIGHT) {
74
+ const label = String(_nullishCoalesce(_nullishCoalesce(_nullishCoalesce(_optionalChain([node, 'access', _9 => _9.data, 'optionalAccess', _10 => _10.label]), () => ( _optionalChain([node, 'access', _11 => _11.data, 'optionalAccess', _12 => _12.title]))), () => ( node.id)), () => ( "")));
75
+ const lines = label.split(/\n|<br\s*\/?>/);
76
+ const longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, "").length), 1);
77
+ const width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));
78
+ const height = Math.max(defaultHeight, lines.length * 20 + 24);
79
+ return { width, height };
80
+ }
81
+ function boundsOf(rects) {
82
+ if (!rects.length) return null;
83
+ const minX = Math.min(...rects.map((r) => r.x));
84
+ const minY = Math.min(...rects.map((r) => r.y));
85
+ const maxX = Math.max(...rects.map((r) => r.x + r.width));
86
+ const maxY = Math.max(...rects.map((r) => r.y + r.height));
87
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
88
+ }
89
+ function centre(r) {
90
+ return { x: r.x + r.width / 2, y: r.y + r.height / 2 };
91
+ }
92
+ function overlaps(a, b, pad = 16) {
93
+ return a.x < b.x + b.width + pad && a.x + a.width + pad > b.x && a.y < b.y + b.height + pad && a.y + a.height + pad > b.y;
94
+ }
95
+ function flowSide(direction = "LR") {
96
+ switch (direction) {
97
+ case "LR":
98
+ return "right";
99
+ case "RL":
100
+ return "left";
101
+ case "BT":
102
+ return "above";
103
+ case "TB":
104
+ default:
105
+ return "below";
106
+ }
107
+ }
108
+ function ensureLayout(nodes, edges, options = {}) {
109
+ if (nodes.length === 0) return [];
110
+ const stored = _nullishCoalesce(options.storedPositions, () => ( {}));
111
+ const missing = nodes.filter((n) => !stored[n.id]);
112
+ if (missing.length === 0) {
113
+ return nodes.map((n) => ({
114
+ ...n,
115
+ position: { ...stored[n.id] }
116
+ }));
117
+ }
118
+ const autoLayouted = getLayoutedNodes(nodes, edges, void 0, options);
119
+ const autoMap = new Map(
120
+ autoLayouted.map((n) => [n.id, n.position])
121
+ );
122
+ const both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));
123
+ if (both.length === 0) {
124
+ return autoLayouted;
125
+ }
126
+ let dx = 0;
127
+ let dy = 0;
128
+ for (const n of both) {
129
+ const s = stored[n.id];
130
+ const a = autoMap.get(n.id);
131
+ dx += s.x - a.x;
132
+ dy += s.y - a.y;
133
+ }
134
+ dx /= both.length;
135
+ dy /= both.length;
136
+ return nodes.map((node) => {
137
+ if (stored[node.id]) {
138
+ return {
139
+ ...node,
140
+ position: { ...stored[node.id] }
141
+ };
142
+ }
143
+ const autoPos = _nullishCoalesce(autoMap.get(node.id), () => ( { x: 0, y: 0 }));
144
+ return {
145
+ ...node,
146
+ position: {
147
+ x: Math.round(autoPos.x + dx),
148
+ y: Math.round(autoPos.y + dy)
149
+ }
150
+ };
151
+ });
152
+ }
153
+ function placeBeside(rects, size, side, nearNodeId, options = {}) {
154
+ const {
155
+ gap = DEFAULT_PLACEMENT_GAP,
156
+ ignore = [],
157
+ maxAttempts = 32
158
+ } = options;
159
+ const skip = new Set([nearNodeId, ...ignore].filter(Boolean));
160
+ const obstacles = Object.entries(rects).filter(([id]) => !skip.has(id)).map(([, r]) => r);
161
+ const anchor = nearNodeId && rects[nearNodeId] || boundsOf(Object.values(rects)) || { x: 0, y: 0, width: 0, height: 0 };
162
+ const anchorCenter = centre(anchor);
163
+ let baseX;
164
+ let baseY;
165
+ switch (side) {
166
+ case "right":
167
+ baseX = anchor.x + anchor.width + gap;
168
+ baseY = anchorCenter.y - size.height / 2;
169
+ break;
170
+ case "left":
171
+ baseX = anchor.x - gap - size.width;
172
+ baseY = anchorCenter.y - size.height / 2;
173
+ break;
174
+ case "above":
175
+ baseX = anchorCenter.x - size.width / 2;
176
+ baseY = anchor.y - gap - size.height;
177
+ break;
178
+ case "below":
179
+ default:
180
+ baseX = anchorCenter.x - size.width / 2;
181
+ baseY = anchor.y + anchor.height + gap;
182
+ break;
183
+ }
184
+ const isHorizontal = side === "right" || side === "left";
185
+ const step = (isHorizontal ? size.height : size.width) + 20;
186
+ for (let i = 0; i < maxAttempts; i++) {
187
+ const offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);
188
+ const candidateBox = {
189
+ x: isHorizontal ? baseX : baseX + offset,
190
+ y: isHorizontal ? baseY + offset : baseY,
191
+ width: size.width,
192
+ height: size.height
193
+ };
194
+ if (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {
195
+ return {
196
+ x: Math.round(candidateBox.x),
197
+ y: Math.round(candidateBox.y)
198
+ };
199
+ }
200
+ }
201
+ return {
202
+ x: Math.round(baseX),
203
+ y: Math.round(baseY)
204
+ };
205
+ }
206
+
207
+
208
+
209
+
210
+
211
+
212
+
213
+
214
+
215
+
216
+
217
+
218
+
219
+
220
+
221
+ exports.DEFAULT_NODE_WIDTH = DEFAULT_NODE_WIDTH; exports.DEFAULT_NODE_HEIGHT = DEFAULT_NODE_HEIGHT; exports.DEFAULT_PLACEMENT_GAP = DEFAULT_PLACEMENT_GAP; exports.getCenteredNodePosition = getCenteredNodePosition; exports.centerNodes = centerNodes; exports.getLayoutedNodes = getLayoutedNodes; exports.estimateNodeSize = estimateNodeSize; exports.boundsOf = boundsOf; exports.centre = centre; exports.overlaps = overlaps; exports.flowSide = flowSide; exports.ensureLayout = ensureLayout; exports.placeBeside = placeBeside;
222
+ //# sourceMappingURL=chunk-WADCJ7A7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/editor/editor/packages/core/dist/chunk-WADCJ7A7.cjs","../src/canvas/layout.ts"],"names":[],"mappings":"AAAA;ACAA,qFAAkB;AAYX,IAAM,mBAAA,EAAqB,GAAA;AAC3B,IAAM,oBAAA,EAAsB,EAAA;AAC5B,IAAM,sBAAA,EAAwB,EAAA;AAK9B,SAAS,uBAAA,CACf,MAAA,EAAQ,CAAA,EACR,OAAA,EAAS,CAAA,EACT,oBAAA,EACA,gBAAA,EACa;AACb,EAAA,MAAM,KAAA,mBACL,gBAAA,UAAA,CACC,OAAO,SAAA,IAAa,YAAA,EAClB,EAAE,KAAA,EAAO,QAAA,CAAS,eAAA,CAAgB,WAAA,EAAa,MAAA,EAAQ,QAAA,CAAS,eAAA,CAAgB,aAAa,EAAA,EAC7F,EAAE,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,KAAK,CAAA,GAAA;AAEhC,EAAA,MAAM,QAAA,EAAU,oBAAA,CAAqB,EAAE,CAAA,EAAG,IAAA,CAAK,MAAA,EAAQ,CAAA,EAAG,CAAA,EAAG,IAAA,CAAK,OAAA,EAAS,EAAE,CAAC,CAAA;AAC9E,EAAA,OAAO;AAAA,IACN,CAAA,EAAG,OAAA,CAAQ,EAAA,EAAI,MAAA,EAAQ,CAAA;AAAA,IACvB,CAAA,EAAG,OAAA,CAAQ,EAAA,EAAI,OAAA,EAAS;AAAA,EACzB,CAAA;AACD;AAKO,SAAS,WAAA,CACf,KAAA,EACA,oBAAA,EACA,cAAA,EACA,gBAAA,EACU;AACV,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG;AACvB,IAAA,OAAO,CAAC,CAAA;AAAA,EACT;AAEA,EAAA,MAAM,OAAA,EAAS,cAAA,CAAe,KAAK,CAAA;AACnC,EAAA,MAAM,kBAAA,EAAoB,EAAE,CAAA,EAAG,MAAA,CAAO,CAAA,EAAG,CAAA,EAAG,MAAA,CAAO,EAAE,CAAA;AAErD,EAAA,MAAM,iBAAA,EAAmB,uBAAA;AAAA,IACxB,MAAA,CAAO,KAAA;AAAA,IACP,MAAA,CAAO,MAAA;AAAA,IACP,oBAAA;AAAA,IACA;AAAA,EACD,CAAA;AAEA,EAAA,MAAM,OAAA,EAAS,gBAAA,CAAiB,EAAA,EAAI,iBAAA,CAAkB,CAAA;AACtD,EAAA,MAAM,OAAA,EAAS,gBAAA,CAAiB,EAAA,EAAI,iBAAA,CAAkB,CAAA;AAEtD,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAA,GAAA,CAAU;AAAA,IAC3B,GAAG,IAAA;AAAA,IACH,QAAA,EAAU;AAAA,MACT,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI,MAAA;AAAA,MACrB,CAAA,EAAG,IAAA,CAAK,QAAA,CAAS,EAAA,EAAI;AAAA,IACtB;AAAA,EACD,CAAA,CAAE,CAAA;AACH;AAKO,SAAS,gBAAA,CAIf,KAAA,EACA,KAAA,EACA,cAAA,EACA,QAAA,EAA+B,CAAC,CAAA,EACtB;AACV,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,OAAO,CAAC,CAAA;AAEhC,EAAA,MAAM;AAAA,IACL,UAAA,EAAY,IAAA;AAAA,IACZ,QAAA,EAAU,GAAA;AAAA,IACV,QAAA,EAAU,EAAA;AAAA,IACV,iBAAA,EAAmB,kBAAA;AAAA,IACnB,kBAAA,EAAoB;AAAA,EACrB,EAAA,EAAI,OAAA;AAEJ,EAAA,MAAM,WAAA,EAAa,IAAI,eAAA,CAAM,QAAA,CAAS,KAAA,CAAM,CAAA;AAC5C,EAAA,UAAA,CAAW,mBAAA,CAAoB,CAAA,EAAA,GAAA,CAAO,CAAC,CAAA,CAAE,CAAA;AACzC,EAAA,UAAA,CAAW,QAAA,CAAS,EAAE,OAAA,EAAS,SAAA,EAAW,OAAA,EAAS,QAAQ,CAAC,CAAA;AAE5D,EAAA,MAAM,QAAA,EAAU,IAAI,GAAA,CAAI,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAA,GAAS,IAAA,CAAK,EAAE,CAAC,CAAA;AAEpD,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAA,GAAS;AACvB,IAAA,MAAM,UAAA,oDAAY,IAAA,mBAAK,QAAA,6BAAU,OAAA,UAAU,IAAA,CAAa,OAAA,UAAS,kBAAA;AACjE,IAAA,MAAM,WAAA,oDAAa,IAAA,qBAAK,QAAA,6BAAU,QAAA,UAAW,IAAA,CAAa,QAAA,UAAU,mBAAA;AACpE,IAAA,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,EAAA,EAAI,EAAE,KAAA,EAAO,SAAA,EAAW,MAAA,EAAQ,WAAW,CAAC,CAAA;AAAA,EACrE,CAAC,CAAA;AAED,EAAA,KAAA,CAAM,OAAA,CAAQ,CAAC,IAAA,EAAA,GAAS;AACvB,IAAA,GAAA,CAAI,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,MAAM,EAAA,GAAK,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,MAAM,CAAA,EAAG;AACzD,MAAA,UAAA,CAAW,OAAA,CAAQ,IAAA,CAAK,MAAA,EAAQ,IAAA,CAAK,MAAM,CAAA;AAAA,IAC5C;AAAA,EACD,CAAC,CAAA;AAED,EAAA,eAAA,CAAM,MAAA,CAAO,UAAU,CAAA;AAEvB,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAA,GAAS;AAC1B,IAAA,MAAM,iBAAA,EAAmB,UAAA,CAAW,IAAA,CAAK,IAAA,CAAK,EAAE,CAAA;AAChD,IAAA,MAAM,UAAA,oDAAY,IAAA,qBAAK,QAAA,6BAAU,OAAA,UAAU,IAAA,CAAa,OAAA,UAAS,kBAAA;AACjE,IAAA,MAAM,WAAA,oDAAa,IAAA,qBAAK,QAAA,6BAAU,QAAA,UAAW,IAAA,CAAa,QAAA,UAAU,mBAAA;AAEpE,IAAA,OAAO;AAAA,MACN,GAAG,IAAA;AAAA,MACH,QAAA,EAAU;AAAA,QACT,CAAA,EAAG,gBAAA,CAAiB,EAAA,EAAI,UAAA,EAAY,CAAA;AAAA,QACpC,CAAA,EAAG,gBAAA,CAAiB,EAAA,EAAI,WAAA,EAAa;AAAA,MACtC;AAAA,IACD,CAAA;AAAA,EACD,CAAC,CAAA;AACF;AAKO,SAAS,gBAAA,CACf,IAAA,EACA,aAAA,EAAe,kBAAA,EACf,cAAA,EAAgB,mBAAA,EACoB;AACpC,EAAA,MAAM,MAAA,EAAQ,MAAA,oEAAO,IAAA,qBAAK,IAAA,+BAAM,OAAA,0BAAS,IAAA,uBAAK,IAAA,+BAAM,SAAA,UAAS,IAAA,CAAK,IAAA,UAAM,IAAE,CAAA;AAC1E,EAAA,MAAM,MAAA,EAAQ,KAAA,CAAM,KAAA,CAAM,eAAe,CAAA;AACzC,EAAA,MAAM,QAAA,EAAU,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,CAAQ,UAAA,EAAY,EAAE,CAAA,CAAE,MAAM,CAAA,EAAG,CAAC,CAAA;AACjF,EAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,GAAA,EAAK,IAAA,CAAK,GAAA,CAAI,YAAA,EAAc,QAAA,EAAU,EAAA,EAAI,EAAE,CAAC,CAAA;AACpE,EAAA,MAAM,OAAA,EAAS,IAAA,CAAK,GAAA,CAAI,aAAA,EAAe,KAAA,CAAM,OAAA,EAAS,GAAA,EAAK,EAAE,CAAA;AAC7D,EAAA,OAAO,EAAE,KAAA,EAAO,OAAO,CAAA;AACxB;AAKO,SAAS,QAAA,CAAS,KAAA,EAA4B;AACpD,EAAA,GAAA,CAAI,CAAC,KAAA,CAAM,MAAA,EAAQ,OAAO,IAAA;AAC1B,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,CAAC,CAAC,CAAA;AAC9C,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,CAAC,CAAC,CAAA;AAC9C,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,KAAK,CAAC,CAAA;AACxD,EAAA,MAAM,KAAA,EAAO,IAAA,CAAK,GAAA,CAAI,GAAG,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAM,CAAC,CAAA;AACzD,EAAA,OAAO,EAAE,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,KAAA,EAAO,KAAA,EAAO,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,KAAK,CAAA;AACpE;AAKO,SAAS,MAAA,CAAO,CAAA,EAAqB;AAC3C,EAAA,OAAO,EAAE,CAAA,EAAG,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAG,CAAA,EAAG,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,OAAA,EAAS,EAAE,CAAA;AACtD;AAKO,SAAS,QAAA,CAAS,CAAA,EAAS,CAAA,EAAS,IAAA,EAAM,EAAA,EAAa;AAC7D,EAAA,OACC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,GACtB,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,MAAA,EAAQ,IAAA,EAAM,CAAA,CAAE,EAAA,GACxB,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,OAAA,EAAS,IAAA,GACvB,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,OAAA,EAAS,IAAA,EAAM,CAAA,CAAE,CAAA;AAE3B;AAKO,SAAS,QAAA,CAAS,UAAA,EAAuC,IAAA,EAAY;AAC3E,EAAA,OAAA,CAAQ,SAAA,EAAW;AAAA,IAClB,KAAK,IAAA;AACJ,MAAA,OAAO,OAAA;AAAA,IACR,KAAK,IAAA;AACJ,MAAA,OAAO,MAAA;AAAA,IACR,KAAK,IAAA;AACJ,MAAA,OAAO,OAAA;AAAA,IACR,KAAK,IAAA;AAAA,IACL,OAAA;AACC,MAAA,OAAO,OAAA;AAAA,EACT;AACD;AAQO,SAAS,YAAA,CAIf,KAAA,EACA,KAAA,EACA,QAAA,EAAoC,CAAC,CAAA,EAC3B;AACV,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,IAAW,CAAA,EAAG,OAAO,CAAC,CAAA;AAEhC,EAAA,MAAM,OAAA,mBAAS,OAAA,CAAQ,eAAA,UAAmB,CAAC,GAAA;AAC3C,EAAA,MAAM,QAAA,EAAU,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM,CAAC,MAAA,CAAO,CAAA,CAAE,EAAE,CAAC,CAAA;AAGjD,EAAA,GAAA,CAAI,OAAA,CAAQ,OAAA,IAAW,CAAA,EAAG;AACzB,IAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,EAAA,GAAA,CAAO;AAAA,MACxB,GAAG,CAAA;AAAA,MACH,QAAA,EAAU,EAAE,GAAG,MAAA,CAAO,CAAA,CAAE,EAAE,EAAG;AAAA,IAC9B,CAAA,CAAE,CAAA;AAAA,EACH;AAGA,EAAA,MAAM,aAAA,EAAe,gBAAA,CAAiB,KAAA,EAAO,KAAA,EAAO,KAAA,CAAA,EAAW,OAAO,CAAA;AACtE,EAAA,MAAM,QAAA,EAAU,IAAI,GAAA;AAAA,IACnB,YAAA,CAAa,GAAA,CAAI,CAAC,CAAA,EAAA,GAAM,CAAC,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,QAAQ,CAAC;AAAA,EAC3C,CAAA;AAGA,EAAA,MAAM,KAAA,EAAO,KAAA,CAAM,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM,MAAA,CAAO,CAAA,CAAE,EAAE,EAAA,GAAK,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,EAAE,CAAC,CAAA;AAClE,EAAA,GAAA,CAAI,IAAA,CAAK,OAAA,IAAW,CAAA,EAAG;AACtB,IAAA,OAAO,YAAA;AAAA,EACR;AAGA,EAAA,IAAI,GAAA,EAAK,CAAA;AACT,EAAA,IAAI,GAAA,EAAK,CAAA;AACT,EAAA,IAAA,CAAA,MAAW,EAAA,GAAK,IAAA,EAAM;AACrB,IAAA,MAAM,EAAA,EAAI,MAAA,CAAO,CAAA,CAAE,EAAE,CAAA;AACrB,IAAA,MAAM,EAAA,EAAI,OAAA,CAAQ,GAAA,CAAI,CAAA,CAAE,EAAE,CAAA;AAC1B,IAAA,GAAA,GAAM,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,CAAA;AACd,IAAA,GAAA,GAAM,CAAA,CAAE,EAAA,EAAI,CAAA,CAAE,CAAA;AAAA,EACf;AACA,EAAA,GAAA,GAAM,IAAA,CAAK,MAAA;AACX,EAAA,GAAA,GAAM,IAAA,CAAK,MAAA;AAEX,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAA,GAAS;AAC1B,IAAA,GAAA,CAAI,MAAA,CAAO,IAAA,CAAK,EAAE,CAAA,EAAG;AACpB,MAAA,OAAO;AAAA,QACN,GAAG,IAAA;AAAA,QACH,QAAA,EAAU,EAAE,GAAG,MAAA,CAAO,IAAA,CAAK,EAAE,EAAG;AAAA,MACjC,CAAA;AAAA,IACD;AACA,IAAA,MAAM,QAAA,mBAAU,OAAA,CAAQ,GAAA,CAAI,IAAA,CAAK,EAAE,CAAA,UAAK,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,EAAE,GAAA;AACrD,IAAA,OAAO;AAAA,MACN,GAAG,IAAA;AAAA,MACH,QAAA,EAAU;AAAA,QACT,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,EAAA,EAAI,EAAE,CAAA;AAAA,QAC5B,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,OAAA,CAAQ,EAAA,EAAI,EAAE;AAAA,MAC7B;AAAA,IACD,CAAA;AAAA,EACD,CAAC,CAAA;AACF;AAMO,SAAS,WAAA,CACf,KAAA,EACA,IAAA,EACA,IAAA,EACA,UAAA,EACA,QAAA,EAA8B,CAAC,CAAA,EAClB;AACb,EAAA,MAAM;AAAA,IACL,IAAA,EAAM,qBAAA;AAAA,IACN,OAAA,EAAS,CAAC,CAAA;AAAA,IACV,YAAA,EAAc;AAAA,EACf,EAAA,EAAI,OAAA;AAEJ,EAAA,MAAM,KAAA,EAAO,IAAI,GAAA,CAAI,CAAC,UAAA,EAAY,GAAG,MAAM,CAAA,CAAE,MAAA,CAAO,OAAO,CAAC,CAAA;AAC5D,EAAA,MAAM,UAAA,EAAY,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,CACpC,MAAA,CAAO,CAAC,CAAC,EAAE,CAAA,EAAA,GAAM,CAAC,IAAA,CAAK,GAAA,CAAI,EAAE,CAAC,CAAA,CAC9B,GAAA,CAAI,CAAC,CAAC,EAAE,CAAC,CAAA,EAAA,GAAM,CAAC,CAAA;AAElB,EAAA,MAAM,OAAA,EACJ,WAAA,GAAc,KAAA,CAAM,UAAU,EAAA,GAC/B,QAAA,CAAS,MAAA,CAAO,MAAA,CAAO,KAAK,CAAC,EAAA,GAC7B,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,EAAE,CAAA;AAEnC,EAAA,MAAM,aAAA,EAAe,MAAA,CAAO,MAAM,CAAA;AAElC,EAAA,IAAI,KAAA;AACJ,EAAA,IAAI,KAAA;AAEJ,EAAA,OAAA,CAAQ,IAAA,EAAM;AAAA,IACb,KAAK,OAAA;AACJ,MAAA,MAAA,EAAQ,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,MAAA,EAAQ,GAAA;AAClC,MAAA,MAAA,EAAQ,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,OAAA,EAAS,CAAA;AACvC,MAAA,KAAA;AAAA,IACD,KAAK,MAAA;AACJ,MAAA,MAAA,EAAQ,MAAA,CAAO,EAAA,EAAI,IAAA,EAAM,IAAA,CAAK,KAAA;AAC9B,MAAA,MAAA,EAAQ,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,OAAA,EAAS,CAAA;AACvC,MAAA,KAAA;AAAA,IACD,KAAK,OAAA;AACJ,MAAA,MAAA,EAAQ,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,MAAA,EAAQ,CAAA;AACtC,MAAA,MAAA,EAAQ,MAAA,CAAO,EAAA,EAAI,IAAA,EAAM,IAAA,CAAK,MAAA;AAC9B,MAAA,KAAA;AAAA,IACD,KAAK,OAAA;AAAA,IACL,OAAA;AACC,MAAA,MAAA,EAAQ,YAAA,CAAa,EAAA,EAAI,IAAA,CAAK,MAAA,EAAQ,CAAA;AACtC,MAAA,MAAA,EAAQ,MAAA,CAAO,EAAA,EAAI,MAAA,CAAO,OAAA,EAAS,GAAA;AACnC,MAAA,KAAA;AAAA,EACF;AAGA,EAAA,MAAM,aAAA,EAAe,KAAA,IAAS,QAAA,GAAW,KAAA,IAAS,MAAA;AAClD,EAAA,MAAM,KAAA,EAAA,CAAQ,aAAA,EAAe,IAAA,CAAK,OAAA,EAAS,IAAA,CAAK,KAAA,EAAA,EAAS,EAAA;AAEzD,EAAA,IAAA,CAAA,IAAS,EAAA,EAAI,CAAA,EAAG,EAAA,EAAI,WAAA,EAAa,CAAA,EAAA,EAAK;AACrC,IAAA,MAAM,OAAA,EAAS,EAAA,IAAM,EAAA,EAAI,EAAA,EAAI,IAAA,CAAK,IAAA,CAAK,EAAA,EAAI,CAAC,EAAA,EAAI,KAAA,EAAA,CAAQ,EAAA,EAAI,EAAA,IAAM,EAAA,EAAI,EAAA,EAAI,CAAA,CAAA,CAAA;AAC1E,IAAA,MAAM,aAAA,EAAqB;AAAA,MAC1B,CAAA,EAAG,aAAA,EAAe,MAAA,EAAQ,MAAA,EAAQ,MAAA;AAAA,MAClC,CAAA,EAAG,aAAA,EAAe,MAAA,EAAQ,OAAA,EAAS,KAAA;AAAA,MACnC,KAAA,EAAO,IAAA,CAAK,KAAA;AAAA,MACZ,MAAA,EAAQ,IAAA,CAAK;AAAA,IACd,CAAA;AAEA,IAAA,GAAA,CAAI,CAAC,SAAA,CAAU,IAAA,CAAK,CAAC,QAAA,EAAA,GAAa,QAAA,CAAS,YAAA,EAAc,QAAA,EAAU,CAAC,CAAC,CAAA,EAAG;AACvE,MAAA,OAAO;AAAA,QACN,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,CAAC,CAAA;AAAA,QAC5B,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,YAAA,CAAa,CAAC;AAAA,MAC7B,CAAA;AAAA,IACD;AAAA,EACD;AAEA,EAAA,OAAO;AAAA,IACN,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,KAAK,CAAA;AAAA,IACnB,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,KAAK;AAAA,EACpB,CAAA;AACD;ADvIA;AACA;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACF,0gBAAC","file":"/home/runner/work/editor/editor/packages/core/dist/chunk-WADCJ7A7.cjs","sourcesContent":[null,"import dagre from '@dagrejs/dagre';\nimport type {\n\tCanvasEdge,\n\tCanvasLayoutOptions,\n\tCanvasNode,\n\tIncrementalLayoutOptions,\n\tPlaceBesideOptions,\n\tRect,\n\tSide,\n\tXYPosition\n} from './types.js';\n\nexport const DEFAULT_NODE_WIDTH = 200;\nexport const DEFAULT_NODE_HEIGHT = 60;\nexport const DEFAULT_PLACEMENT_GAP = 48;\n\n/**\n * Calculates upper-left flow coordinate for an element to be centered in the viewport.\n */\nexport function getCenteredNodePosition(\n\twidth = 0,\n\theight = 0,\n\tscreenToFlowPosition: (position: XYPosition) => XYPosition,\n\tscreenDimensions?: { width: number; height: number }\n): XYPosition {\n\tconst rect =\n\t\tscreenDimensions ??\n\t\t(typeof document !== 'undefined'\n\t\t\t? { width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }\n\t\t\t: { width: 1920, height: 1080 });\n\n\tconst flowPos = screenToFlowPosition({ x: rect.width / 2, y: rect.height / 2 });\n\treturn {\n\t\tx: flowPos.x - width / 2,\n\t\ty: flowPos.y - height / 2\n\t};\n}\n\n/**\n * Updates positions of nodes so their collective bounding box is centered in the current viewport.\n */\nexport function centerNodes<TNode extends CanvasNode = CanvasNode>(\n\tnodes: TNode[],\n\tscreenToFlowPosition: (position: XYPosition) => XYPosition,\n\tgetNodesBounds: (nodes: TNode[]) => Rect,\n\tscreenDimensions?: { width: number; height: number }\n): TNode[] {\n\tif (nodes.length === 0) {\n\t\treturn [];\n\t}\n\n\tconst bounds = getNodesBounds(nodes);\n\tconst boundingBoxOrigin = { x: bounds.x, y: bounds.y };\n\n\tconst centeredPosition = getCenteredNodePosition(\n\t\tbounds.width,\n\t\tbounds.height,\n\t\tscreenToFlowPosition,\n\t\tscreenDimensions\n\t);\n\n\tconst deltaX = centeredPosition.x - boundingBoxOrigin.x;\n\tconst deltaY = centeredPosition.y - boundingBoxOrigin.y;\n\n\treturn nodes.map((node) => ({\n\t\t...node,\n\t\tposition: {\n\t\t\tx: node.position.x + deltaX,\n\t\t\ty: node.position.y + deltaY\n\t\t}\n\t}));\n}\n\n/**\n * Automatically calculates directed hierarchical layout of nodes and edges using Dagre.\n */\nexport function getLayoutedNodes<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\tgetNodesBounds?: (nodes: TNode[]) => Rect,\n\toptions: CanvasLayoutOptions = {}\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\n\tconst {\n\t\tdirection = 'LR',\n\t\tranksep = 100,\n\t\tnodesep = 25,\n\t\tdefaultNodeWidth = DEFAULT_NODE_WIDTH,\n\t\tdefaultNodeHeight = DEFAULT_NODE_HEIGHT\n\t} = options;\n\n\tconst dagreGraph = new dagre.graphlib.Graph();\n\tdagreGraph.setDefaultEdgeLabel(() => ({}));\n\tdagreGraph.setGraph({ rankdir: direction, ranksep, nodesep });\n\n\tconst nodeIds = new Set(nodes.map((node) => node.id));\n\n\tnodes.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\tdagreGraph.setNode(node.id, { width: nodeWidth, height: nodeHeight });\n\t});\n\n\tedges.forEach((edge) => {\n\t\tif (nodeIds.has(edge.source) && nodeIds.has(edge.target)) {\n\t\t\tdagreGraph.setEdge(edge.source, edge.target);\n\t\t}\n\t});\n\n\tdagre.layout(dagreGraph);\n\n\treturn nodes.map((node) => {\n\t\tconst nodeWithPosition = dagreGraph.node(node.id);\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\treturn {\n\t\t\t...node,\n\t\t\tposition: {\n\t\t\t\tx: nodeWithPosition.x - nodeWidth / 2,\n\t\t\t\ty: nodeWithPosition.y - nodeHeight / 2\n\t\t\t}\n\t\t};\n\t});\n}\n\n/**\n * Heuristic text-based node size estimator when DOM measurement is not available.\n */\nexport function estimateNodeSize(\n\tnode: CanvasNode,\n\tdefaultWidth = DEFAULT_NODE_WIDTH,\n\tdefaultHeight = DEFAULT_NODE_HEIGHT\n): { width: number; height: number } {\n\tconst label = String(node.data?.label ?? node.data?.title ?? node.id ?? '');\n\tconst lines = label.split(/\\n|<br\\s*\\/?>/);\n\tconst longest = Math.max(...lines.map((l) => l.replace(/<[^>]+>/g, '').length), 1);\n\tconst width = Math.min(480, Math.max(defaultWidth, longest * 8 + 48));\n\tconst height = Math.max(defaultHeight, lines.length * 20 + 24);\n\treturn { width, height };\n}\n\n/**\n * Computes bounding rectangle of an array of rectangles.\n */\nexport function boundsOf(rects: Rect[]): Rect | null {\n\tif (!rects.length) return null;\n\tconst minX = Math.min(...rects.map((r) => r.x));\n\tconst minY = Math.min(...rects.map((r) => r.y));\n\tconst maxX = Math.max(...rects.map((r) => r.x + r.width));\n\tconst maxY = Math.max(...rects.map((r) => r.y + r.height));\n\treturn { x: minX, y: minY, width: maxX - minX, height: maxY - minY };\n}\n\n/**\n * Center coordinate of a rectangle.\n */\nexport function centre(r: Rect): XYPosition {\n\treturn { x: r.x + r.width / 2, y: r.y + r.height / 2 };\n}\n\n/**\n * Determines whether two rectangles overlap, including an optional padding distance.\n */\nexport function overlaps(a: Rect, b: Rect, pad = 16): boolean {\n\treturn (\n\t\ta.x < b.x + b.width + pad &&\n\t\ta.x + a.width + pad > b.x &&\n\t\ta.y < b.y + b.height + pad &&\n\t\ta.y + a.height + pad > b.y\n\t);\n}\n\n/**\n * Maps directed layout flow to its natural forward growth side.\n */\nexport function flowSide(direction: 'LR' | 'TB' | 'RL' | 'BT' = 'LR'): Side {\n\tswitch (direction) {\n\t\tcase 'LR':\n\t\t\treturn 'right';\n\t\tcase 'RL':\n\t\t\treturn 'left';\n\t\tcase 'BT':\n\t\t\treturn 'above';\n\t\tcase 'TB':\n\t\tdefault:\n\t\t\treturn 'below';\n\t}\n}\n\n/**\n * Incremental layout blending:\n * Keeps established/manual node positions intact. Runs Dagre on the entire graph,\n * computes the centroid translation offset between auto and stored positions of existing nodes,\n * and places unpositioned/new nodes at (auto + offset).\n */\nexport function ensureLayout<\n\tTNode extends CanvasNode = CanvasNode,\n\tTEdge extends CanvasEdge = CanvasEdge\n>(\n\tnodes: TNode[],\n\tedges: TEdge[],\n\toptions: IncrementalLayoutOptions = {}\n): TNode[] {\n\tif (nodes.length === 0) return [];\n\n\tconst stored = options.storedPositions ?? {};\n\tconst missing = nodes.filter((n) => !stored[n.id]);\n\n\t// Case 1: Every node has an established coordinate -> preserve verbatim\n\tif (missing.length === 0) {\n\t\treturn nodes.map((n) => ({\n\t\t\t...n,\n\t\t\tposition: { ...stored[n.id]! }\n\t\t}));\n\t}\n\n\t// Calculate full dagre auto-layout\n\tconst autoLayouted = getLayoutedNodes(nodes, edges, undefined, options);\n\tconst autoMap = new Map<string, XYPosition>(\n\t\tautoLayouted.map((n) => [n.id, n.position])\n\t);\n\n\t// Case 2: No nodes have stored coordinates -> return auto layout directly\n\tconst both = nodes.filter((n) => stored[n.id] && autoMap.has(n.id));\n\tif (both.length === 0) {\n\t\treturn autoLayouted;\n\t}\n\n\t// Case 3: Blend! Align on centroid translation offset of overlapping nodes\n\tlet dx = 0;\n\tlet dy = 0;\n\tfor (const n of both) {\n\t\tconst s = stored[n.id]!;\n\t\tconst a = autoMap.get(n.id)!;\n\t\tdx += s.x - a.x;\n\t\tdy += s.y - a.y;\n\t}\n\tdx /= both.length;\n\tdy /= both.length;\n\n\treturn nodes.map((node) => {\n\t\tif (stored[node.id]) {\n\t\t\treturn {\n\t\t\t\t...node,\n\t\t\t\tposition: { ...stored[node.id]! }\n\t\t\t};\n\t\t}\n\t\tconst autoPos = autoMap.get(node.id) ?? { x: 0, y: 0 };\n\t\treturn {\n\t\t\t...node,\n\t\t\tposition: {\n\t\t\t\tx: Math.round(autoPos.x + dx),\n\t\t\t\ty: Math.round(autoPos.y + dy)\n\t\t\t}\n\t\t};\n\t});\n}\n\n/**\n * Finds a free top-left coordinate for a node of `size` adjacent to `nearNodeId` (or the cluster),\n * sliding along the perpendicular axis past any intersecting bounding boxes to guarantee non-overlapping placement.\n */\nexport function placeBeside(\n\trects: Record<string, Rect>,\n\tsize: { width: number; height: number },\n\tside: Side,\n\tnearNodeId?: string,\n\toptions: PlaceBesideOptions = {}\n): XYPosition {\n\tconst {\n\t\tgap = DEFAULT_PLACEMENT_GAP,\n\t\tignore = [],\n\t\tmaxAttempts = 32\n\t} = options;\n\n\tconst skip = new Set([nearNodeId, ...ignore].filter(Boolean));\n\tconst obstacles = Object.entries(rects)\n\t\t.filter(([id]) => !skip.has(id))\n\t\t.map(([, r]) => r);\n\n\tconst anchor =\n\t\t(nearNodeId && rects[nearNodeId]) ||\n\t\tboundsOf(Object.values(rects)) ||\n\t\t{ x: 0, y: 0, width: 0, height: 0 };\n\n\tconst anchorCenter = centre(anchor);\n\n\tlet baseX: number;\n\tlet baseY: number;\n\n\tswitch (side) {\n\t\tcase 'right':\n\t\t\tbaseX = anchor.x + anchor.width + gap;\n\t\t\tbaseY = anchorCenter.y - size.height / 2;\n\t\t\tbreak;\n\t\tcase 'left':\n\t\t\tbaseX = anchor.x - gap - size.width;\n\t\t\tbaseY = anchorCenter.y - size.height / 2;\n\t\t\tbreak;\n\t\tcase 'above':\n\t\t\tbaseX = anchorCenter.x - size.width / 2;\n\t\t\tbaseY = anchor.y - gap - size.height;\n\t\t\tbreak;\n\t\tcase 'below':\n\t\tdefault:\n\t\t\tbaseX = anchorCenter.x - size.width / 2;\n\t\t\tbaseY = anchor.y + anchor.height + gap;\n\t\t\tbreak;\n\t}\n\n\t// Slide perpendicular to growth direction, alternating positive and negative steps (+1, -1, +2, -2...)\n\tconst isHorizontal = side === 'right' || side === 'left';\n\tconst step = (isHorizontal ? size.height : size.width) + 20;\n\n\tfor (let i = 0; i < maxAttempts; i++) {\n\t\tconst offset = i === 0 ? 0 : Math.ceil(i / 2) * step * (i % 2 === 1 ? 1 : -1);\n\t\tconst candidateBox: Rect = {\n\t\t\tx: isHorizontal ? baseX : baseX + offset,\n\t\t\ty: isHorizontal ? baseY + offset : baseY,\n\t\t\twidth: size.width,\n\t\t\theight: size.height\n\t\t};\n\n\t\tif (!obstacles.some((obstacle) => overlaps(candidateBox, obstacle, 8))) {\n\t\t\treturn {\n\t\t\t\tx: Math.round(candidateBox.x),\n\t\t\t\ty: Math.round(candidateBox.y)\n\t\t\t};\n\t\t}\n\t}\n\n\treturn {\n\t\tx: Math.round(baseX),\n\t\ty: Math.round(baseY)\n\t};\n}\n"]}