@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.
- package/dist/agent/index.cjs +28 -220
- package/dist/agent/index.cjs.map +1 -1
- package/dist/agent/index.d.cts +1 -1
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +5 -154
- package/dist/agent/index.js.map +1 -1
- package/dist/canvas/index.cjs +113 -377
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +2 -2
- package/dist/canvas/index.d.ts +2 -2
- package/dist/canvas/index.js +18 -208
- package/dist/canvas/index.js.map +1 -1
- package/dist/chunk-33UVEDOS.cjs +233 -0
- package/dist/chunk-33UVEDOS.cjs.map +1 -0
- package/dist/chunk-7FWEWE6T.js +1888 -0
- package/dist/chunk-7FWEWE6T.js.map +1 -0
- package/dist/chunk-BYCOFZAA.js +514 -0
- package/dist/chunk-BYCOFZAA.js.map +1 -0
- package/dist/chunk-CYKQLDYH.cjs +1290 -0
- package/dist/chunk-CYKQLDYH.cjs.map +1 -0
- package/dist/chunk-GLSIZKOX.js +17 -0
- package/dist/chunk-GLSIZKOX.js.map +1 -0
- package/dist/chunk-GWHJVZPI.js +233 -0
- package/dist/chunk-GWHJVZPI.js.map +1 -0
- package/dist/chunk-LDES3EOS.cjs +1888 -0
- package/dist/chunk-LDES3EOS.cjs.map +1 -0
- package/dist/chunk-MPPXH7U5.cjs +336 -0
- package/dist/chunk-MPPXH7U5.cjs.map +1 -0
- package/dist/chunk-NNJRJJSC.js +1290 -0
- package/dist/chunk-NNJRJJSC.js.map +1 -0
- package/dist/chunk-O7LOPQMH.js +336 -0
- package/dist/chunk-O7LOPQMH.js.map +1 -0
- package/dist/chunk-SEDR75VH.cjs +17 -0
- package/dist/chunk-SEDR75VH.cjs.map +1 -0
- package/dist/chunk-SHWP2A43.cjs +514 -0
- package/dist/chunk-SHWP2A43.cjs.map +1 -0
- package/dist/chunk-VHZCXHS4.js +222 -0
- package/dist/chunk-VHZCXHS4.js.map +1 -0
- package/dist/chunk-WADCJ7A7.cjs +222 -0
- package/dist/chunk-WADCJ7A7.cjs.map +1 -0
- package/dist/extensions/index.cjs +25 -1920
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +22 -1868
- package/dist/extensions/index.js.map +1 -1
- package/dist/grammar/index.cjs +44 -387
- package/dist/grammar/index.cjs.map +1 -1
- package/dist/grammar/index.js +23 -321
- package/dist/grammar/index.js.map +1 -1
- package/dist/history/index.cjs +6 -257
- package/dist/history/index.cjs.map +1 -1
- package/dist/history/index.js +4 -227
- package/dist/history/index.js.map +1 -1
- package/dist/image-editor/index.cjs +63 -106
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/index.cjs +324 -7232
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +0 -4
- package/dist/index.d.ts +0 -4
- package/dist/index.js +258 -7007
- package/dist/index.js.map +1 -1
- package/dist/serializers/index.cjs +28 -558
- package/dist/serializers/index.cjs.map +1 -1
- package/dist/serializers/index.js +16 -496
- package/dist/serializers/index.js.map +1 -1
- package/dist/spreadsheet/index.cjs +21 -1319
- package/dist/spreadsheet/index.cjs.map +1 -1
- package/dist/spreadsheet/index.js +14 -1274
- package/dist/spreadsheet/index.js.map +1 -1
- package/dist/{types-lGtRobRV.d.cts → types-DC8b2TsA.d.cts} +1 -1
- package/dist/{types-lGtRobRV.d.ts → types-DC8b2TsA.d.ts} +1 -1
- 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":"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","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"]}
|