@conduction/nextcloud-vue 2.122.0 → 2.123.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/esm/components/CnAppRoot/CnAppRoot.vue2.js +1 -0
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
- package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
- package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue.js +1 -1
- package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js +18 -4
- package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js +63 -32
- package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +35 -0
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +11 -5
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +13 -0
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +37 -5
- package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +40 -0
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +71 -65
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue.js +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +20 -0
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +2 -1
- package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +2 -0
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +49 -44
- package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
- package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +31 -0
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -3
- package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +34 -2
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +1 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +31 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +14 -4
- package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -1
- package/dist/esm/composables/flowGraphLayout.js +36 -7
- package/dist/esm/composables/flowGraphLayout.js.map +1 -1
- package/dist/esm/composables/useFlowStore.js +7 -3
- package/dist/esm/composables/useFlowStore.js.map +1 -1
- package/dist/esm/l10n/en.json.js +3 -1
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +3 -1
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/library-style.js +1 -1
- package/dist/esm/nextcloud-vue.css +508 -303
- package/dist/esm/utils/manifestTranslate.js +12 -3
- package/dist/esm/utils/manifestTranslate.js.map +1 -1
- package/dist/esm/utils/routeFilters.js +3 -3
- package/dist/esm/utils/routeFilters.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +1217 -847
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +508 -303
- package/l10n/en.json +3 -1
- package/l10n/nl.json +3 -1
- package/package.json +1 -1
- package/src/components/CnAppRoot/CnAppRoot.vue +1 -0
- package/src/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue +50 -5
- package/src/components/CnChartWidget/CnChartWidget.vue +10 -8
- package/src/components/CnDetailPage/CnDetailPage.md +10 -0
- package/src/components/CnDetailPage/CnDetailPage.vue +42 -1
- package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +39 -0
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +95 -37
- package/src/components/CnFlowDetail/CnFlowDetail.vue +21 -0
- package/src/components/CnFlowsPage/CnFlowBoard.vue +35 -18
- package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
- package/src/components/CnGraphCanvas/CnGraphCanvas.vue +34 -2
- package/src/components/CnIndexPage/CnIndexPage.vue +34 -2
- package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
- package/src/components/CnSectionsWidget/CnSectionsWidget.vue +53 -2
- package/src/composables/flowGraphLayout.js +35 -6
- package/src/composables/useFlowStore.js +7 -3
- package/src/css/detail-page.css +6 -0
- package/src/css/look-board-detail.css +26 -6
- package/src/css/look-board-index.css +43 -0
- package/src/css/look-board.css +89 -3
- package/src/utils/manifestTranslate.js +12 -3
- package/src/utils/routeFilters.js +3 -3
|
@@ -79,6 +79,34 @@ const FLOW_LAYOUT_MARGIN = 60;
|
|
|
79
79
|
*/
|
|
80
80
|
const FLOW_LAYOUT_TOP = 96;
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Vertical distance between layers when the run reads top to bottom
|
|
84
|
+
* (`direction: 'down'`), in canvas pixels: a step card (~62px) plus room for
|
|
85
|
+
* the line and its arrowhead, as DqFlow draws its steps 100 to 110px apart.
|
|
86
|
+
*
|
|
87
|
+
* @type {number}
|
|
88
|
+
*/
|
|
89
|
+
const FLOW_LAYOUT_LAYER_HEIGHT_DOWN = 110;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Where a node sits, from its layer (run order) and its lane (branch).
|
|
93
|
+
*
|
|
94
|
+
* `right` (default): layers are columns left to right, lanes are rows.
|
|
95
|
+
* `down`: layers are rows top to bottom, lanes are columns.
|
|
96
|
+
*
|
|
97
|
+
* @param {number} layer Zero-based layer.
|
|
98
|
+
* @param {number} lane Zero-based lane.
|
|
99
|
+
* @param {string} direction `right` or `down`.
|
|
100
|
+
* @param {number} top The y of the first layer or row.
|
|
101
|
+
* @return {{x: number, y: number}} The point.
|
|
102
|
+
*/
|
|
103
|
+
function cellPoint(layer, lane, direction, top) {
|
|
104
|
+
if (direction === 'down') {
|
|
105
|
+
return { x: FLOW_LAYOUT_MARGIN + (lane * FLOW_LAYOUT_COLUMN_WIDTH), y: top + (layer * FLOW_LAYOUT_LAYER_HEIGHT_DOWN) }
|
|
106
|
+
}
|
|
107
|
+
return { x: FLOW_LAYOUT_MARGIN + (layer * FLOW_LAYOUT_COLUMN_WIDTH), y: top + (lane * FLOW_LAYOUT_ROW_HEIGHT) }
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
/**
|
|
83
111
|
* The position a node carries, in either of the two spellings in circulation.
|
|
84
112
|
*
|
|
@@ -324,9 +352,11 @@ function orderRows(nodes, lines, columns) {
|
|
|
324
352
|
* @param {Array<object>} nodes The flow's nodes.
|
|
325
353
|
* @param {Array<{source: string, target: string}>} lines The expanded edges.
|
|
326
354
|
* @param {Array<string>} startIds The ids a run enters through.
|
|
355
|
+
* @param {object} [options] `{ direction }`: `right` (default) runs left to
|
|
356
|
+
* right, `down` runs top to bottom with branches side by side.
|
|
327
357
|
* @return {Array<object>} New node objects carrying computed positions.
|
|
328
358
|
*/
|
|
329
|
-
function layoutFlowNodes(nodes, lines, startIds) {
|
|
359
|
+
function layoutFlowNodes(nodes, lines, startIds, options = {}) {
|
|
330
360
|
const list = Array.isArray(nodes) ? nodes : [];
|
|
331
361
|
if (!list.length) {
|
|
332
362
|
return []
|
|
@@ -336,8 +366,7 @@ function layoutFlowNodes(nodes, lines, startIds) {
|
|
|
336
366
|
const rows = orderRows(list, lines, columns);
|
|
337
367
|
|
|
338
368
|
return list.map((node) => {
|
|
339
|
-
const x
|
|
340
|
-
const y = FLOW_LAYOUT_TOP + (rows.get(node.id) * FLOW_LAYOUT_ROW_HEIGHT);
|
|
369
|
+
const { x, y } = cellPoint(columns.get(node.id), rows.get(node.id), options.direction, FLOW_LAYOUT_TOP);
|
|
341
370
|
return { ...node, x, y, position: { x, y } }
|
|
342
371
|
})
|
|
343
372
|
}
|
|
@@ -359,9 +388,10 @@ function layoutFlowNodes(nodes, lines, startIds) {
|
|
|
359
388
|
* @param {Array<object>} nodes The flow's nodes, some positioned and some not.
|
|
360
389
|
* @param {Array<{source: string, target: string}>} lines The expanded edges.
|
|
361
390
|
* @param {Array<string>} startIds The ids a run enters through.
|
|
391
|
+
* @param {object} [options] `{ direction }`, as {@link layoutFlowNodes}.
|
|
362
392
|
* @return {Array<object>} The same array shape, loose nodes placed beneath.
|
|
363
393
|
*/
|
|
364
|
-
function placeLooseNodes(nodes, lines, startIds) {
|
|
394
|
+
function placeLooseNodes(nodes, lines, startIds, options = {}) {
|
|
365
395
|
const list = Array.isArray(nodes) ? nodes : [];
|
|
366
396
|
const loose = list.filter((node) => readNodePoint(node) === null);
|
|
367
397
|
if (!loose.length) {
|
|
@@ -395,11 +425,10 @@ function placeLooseNodes(nodes, lines, startIds) {
|
|
|
395
425
|
const row = rowsTaken.get(column) || 0;
|
|
396
426
|
rowsTaken.set(column, row + 1);
|
|
397
427
|
|
|
398
|
-
const x
|
|
399
|
-
const y = top + (row * FLOW_LAYOUT_ROW_HEIGHT);
|
|
428
|
+
const { x, y } = cellPoint(column, row, options.direction, top);
|
|
400
429
|
return { ...node, x, y, position: { x, y } }
|
|
401
430
|
})
|
|
402
431
|
}
|
|
403
432
|
|
|
404
|
-
export { FLOW_LAYOUT_COLUMN_WIDTH, FLOW_LAYOUT_MARGIN, FLOW_LAYOUT_ROW_HEIGHT, FLOW_LAYOUT_TOP, layoutFlowNodes, needsFullLayout, placeLooseNodes, readNodePoint };
|
|
433
|
+
export { FLOW_LAYOUT_COLUMN_WIDTH, FLOW_LAYOUT_LAYER_HEIGHT_DOWN, FLOW_LAYOUT_MARGIN, FLOW_LAYOUT_ROW_HEIGHT, FLOW_LAYOUT_TOP, layoutFlowNodes, needsFullLayout, placeLooseNodes, readNodePoint };
|
|
405
434
|
//# sourceMappingURL=flowGraphLayout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flowGraphLayout.js","sources":["../../../src/composables/flowGraphLayout.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n */\n\n/**\n * Pure layout for flow graphs whose nodes carry no usable positions.\n *\n * A flow declared in a schema's `x-openregister-flows` (and any generated or\n * imported flow) ships nodes with no coordinates at all. Rendered as-is, all\n * of them land on one point: the canvas shows one node's worth of pixels with\n * seventeen more underneath, and no amount of fit-view or zoom can separate\n * them. The run replay's per-node badges paint correctly — onto a pile nobody\n * can read.\n *\n * The functions here are PURE and deterministic: same nodes, same lines, same\n * start ids — same coordinates, every time. They never touch a store, never\n * read the DOM, and never mutate their inputs, which is what makes them safe\n * to run on a published (graph-locked) flow: the caller decides what to do\n * with the returned array, and a viewer that only renders it has mutated\n * nothing. This is also why the layout lives here and not in CnGraphCanvas:\n * per ADR-065 the canvas renders the geometry it is given and never invents\n * any.\n *\n * The algorithm is a small layered pass, on purpose. dagre and friends are\n * real dependencies with real weight, the in-app CSP allows no CDN fetch, and\n * nothing in the dependency tree ships a layout engine — while the layouts a\n * flow needs (left-to-right, branches fanned out, loops tolerated) fit in the\n * three steps below:\n *\n * 1. LAYER: longest-path columns over the graph with its back edges\n * dropped, so triggers sit left and every forward edge points right.\n * Back edges are found by depth-first search; dropping them is what\n * makes a loop terminate instead of pushing its members ever further\n * right (or hanging).\n * 2. ORDER: one barycenter pass per column — a node sits near the average\n * row of the nodes that lead to it, which keeps parallel branches apart\n * instead of braided.\n * 3. PLACE: fixed spacing on a grid. One column and row per node, so no\n * two nodes can overlap by construction.\n */\n\n/**\n * Horizontal distance between layout columns, in canvas pixels.\n *\n * Sized against the node card (~230px wide) so the edge between two adjacent\n * columns has room for its arrowhead and result badge.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_COLUMN_WIDTH = 260\n\n/**\n * Vertical distance between layout rows, in canvas pixels.\n *\n * About a card high plus a gap, for the same reason as the column width.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_ROW_HEIGHT = 170\n\n/**\n * Left margin before the first column, in canvas pixels.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_MARGIN = 60\n\n/**\n * Top margin above the first row, in canvas pixels.\n *\n * The editor's toolbar FLOATS over the canvas (position: absolute, ~52px\n * tall). A node laid out at a uniform 60px margin landed UNDER it, and an\n * overlaid node is not merely ugly — it is unreachable: the toolbar swallows\n * the pointer. Measured live: Playwright reported `cn-flow-detail__toolbar\n * subtree intercepts pointer events` on a node behind it.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_TOP = 96\n\n/**\n * The position a node carries, in either of the two spellings in circulation.\n *\n * The server stores `position: {x, y}`; the editor writes flat `x`/`y` in\n * memory. Reading only one spelling is the bug that once piled every saved\n * flow onto the origin, so both are read here, flat first — the same order\n * `useFlowStore.hasPosition()` and the canvas mapping use.\n *\n * A node AT the origin still counts as positioned: (0, 0) is a legitimate\n * place to park something. Whether a WHOLE GRAPH of identical points counts\n * is a different question, answered by {@link needsFullLayout}.\n *\n * @param {object} node A flow node.\n * @return {{x: number, y: number}|null} Its point, or null when it has none.\n */\nexport function readNodePoint(node) {\n\tconst x = Number(node?.x ?? node?.position?.x)\n\tif (!Number.isFinite(x)) {\n\t\treturn null\n\t}\n\n\tconst y = Number(node?.y ?? node?.position?.y)\n\treturn { x, y: Number.isFinite(y) ? y : 0 }\n}\n\n/**\n * Whether the whole graph needs laying out from scratch.\n *\n * True in exactly two situations:\n *\n * - NO node carries a position. Declared, generated and imported flows all\n * arrive like this.\n * - Every node that does carry a position sits on the SAME point, and at\n * least two do. That is not an arrangement, it is the same pile with\n * coordinates written down — an importer that stamps `{x: 0, y: 0}` on\n * every node produces it, and it must not be mistaken for a deliberate\n * layout. A single node parked at one point (the origin included) stays\n * where its author put it.\n *\n * Anything else — at least two distinct positioned points — is treated as\n * arranged: someone chose those places, and a layout that second-guesses them\n * throws that choice away. Position-less nodes in an arranged flow are the\n * caller's problem; {@link placeLooseNodes} slots them beneath.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @return {boolean} True when nothing about the current positions is worth keeping.\n */\nexport function needsFullLayout(nodes) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tif (!list.length) {\n\t\treturn false\n\t}\n\n\tconst points = []\n\tfor (const node of list) {\n\t\tconst point = readNodePoint(node)\n\t\tif (point !== null) {\n\t\t\tpoints.push(point)\n\t\t}\n\t}\n\n\tif (!points.length) {\n\t\treturn true\n\t}\n\n\treturn points.length >= 2\n\t\t&& points.every((p) => p.x === points[0].x && p.y === points[0].y)\n}\n\n/**\n * Column per node id: longest path from the start nodes, back edges dropped.\n *\n * Depth-first search from the start ids classifies back edges — an edge whose\n * target is still on the current DFS stack closes a loop. Those edges are\n * excluded from layering, which is what lets a flow with a loop (send back to\n * the submitter, wait, check again) lay out finitely: without it, longest\n * path around a cycle is unbounded. The back edge is still DRAWN, of course —\n * only the layering ignores it.\n *\n * Nodes the start ids cannot reach go one column past everything — never at\n * the origin, where they would hide under the entry points.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @return {Map<string, number>} Node id to zero-based column.\n */\nfunction layerColumns(nodes, lines, startIds) {\n\tconst ids = new Set(nodes.map((node) => node.id))\n\tconst outgoing = new Map()\n\tfor (const line of lines) {\n\t\tif (line.source === line.target || !ids.has(line.source) || !ids.has(line.target)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (!outgoing.has(line.source)) {\n\t\t\toutgoing.set(line.source, [])\n\t\t}\n\t\toutgoing.get(line.source).push(line.target)\n\t}\n\n\t// DFS: classify back edges and record a reverse postorder (a topological\n\t// order of the graph minus its back edges). Iterative, because a flow's\n\t// depth is user-controlled and the call stack is not.\n\tconst seen = new Set()\n\tconst onStack = new Set()\n\tconst forward = new Map()\n\tconst postorder = []\n\tfor (const startId of startIds) {\n\t\tif (!ids.has(startId) || seen.has(startId)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst stack = [{ id: startId, next: 0 }]\n\t\tseen.add(startId)\n\t\tonStack.add(startId)\n\t\twhile (stack.length) {\n\t\t\tconst frame = stack[stack.length - 1]\n\t\t\tconst targets = outgoing.get(frame.id) || []\n\t\t\tif (frame.next >= targets.length) {\n\t\t\t\tstack.pop()\n\t\t\t\tonStack.delete(frame.id)\n\t\t\t\tpostorder.push(frame.id)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst target = targets[frame.next++]\n\t\t\tif (onStack.has(target)) {\n\t\t\t\t// A back edge: drawing it is fine, layering on it is not.\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (!forward.has(frame.id)) {\n\t\t\t\tforward.set(frame.id, [])\n\t\t\t}\n\t\t\tforward.get(frame.id).push(target)\n\n\t\t\tif (!seen.has(target)) {\n\t\t\t\tseen.add(target)\n\t\t\t\tonStack.add(target)\n\t\t\t\tstack.push({ id: target, next: 0 })\n\t\t\t}\n\t\t}\n\t}\n\n\t// Longest path over the acyclic remainder, in topological order: a node\n\t// sits one column past the furthest node that leads to it.\n\tconst columns = new Map()\n\tfor (const startId of startIds) {\n\t\tif (seen.has(startId)) {\n\t\t\tcolumns.set(startId, 0)\n\t\t}\n\t}\n\tfor (let i = postorder.length - 1; i >= 0; i--) {\n\t\tconst id = postorder[i]\n\t\tconst from = columns.get(id) ?? 0\n\t\tcolumns.set(id, from)\n\t\tfor (const target of (forward.get(id) || [])) {\n\t\t\tif ((columns.get(target) ?? -1) < from + 1) {\n\t\t\t\tcolumns.set(target, from + 1)\n\t\t\t}\n\t\t}\n\t}\n\n\tconst unreachableColumn = (Math.max(-1, ...columns.values()) + 1)\n\tfor (const node of nodes) {\n\t\tif (!columns.has(node.id)) {\n\t\t\tcolumns.set(node.id, unreachableColumn)\n\t\t}\n\t}\n\n\treturn columns\n}\n\n/**\n * Row per node id: barycenter order within each column.\n *\n * One left-to-right pass. A node's sort key is the mean row of the already\n * placed nodes that lead into it, so a branch follows the branch point it\n * came from instead of braiding across the others. Ties — and nodes with no\n * placed predecessor — keep the document's own node order, which is what\n * makes the whole pass deterministic.\n *\n * @param {Array<object>} nodes The flow's nodes, in document order.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Map<string, number>} columns Node id to column, from layering.\n * @return {Map<string, number>} Node id to zero-based row within its column.\n */\nfunction orderRows(nodes, lines, columns) {\n\tconst incoming = new Map()\n\tfor (const line of lines) {\n\t\tif (line.source === line.target) {\n\t\t\tcontinue\n\t\t}\n\t\tif (!incoming.has(line.target)) {\n\t\t\tincoming.set(line.target, [])\n\t\t}\n\t\tincoming.get(line.target).push(line.source)\n\t}\n\n\tconst byColumn = new Map()\n\tfor (const [index, node] of nodes.entries()) {\n\t\tconst column = columns.get(node.id)\n\t\tif (!byColumn.has(column)) {\n\t\t\tbyColumn.set(column, [])\n\t\t}\n\t\tbyColumn.get(column).push({ id: node.id, index })\n\t}\n\n\tconst rows = new Map()\n\tconst columnKeys = [...byColumn.keys()].sort((a, b) => a - b)\n\tfor (const column of columnKeys) {\n\t\tconst members = byColumn.get(column)\n\t\tconst keyed = members.map((member) => {\n\t\t\tconst placed = (incoming.get(member.id) || [])\n\t\t\t\t.map((source) => rows.get(source))\n\t\t\t\t.filter((row) => row !== undefined)\n\t\t\tconst barycenter = placed.length\n\t\t\t\t? placed.reduce((sum, row) => sum + row, 0) / placed.length\n\t\t\t\t: member.index\n\t\t\treturn { ...member, barycenter }\n\t\t})\n\t\tkeyed.sort((a, b) => (a.barycenter - b.barycenter) || (a.index - b.index))\n\t\tfor (const [row, member] of keyed.entries()) {\n\t\t\trows.set(member.id, row)\n\t\t}\n\t}\n\n\treturn rows\n}\n\n/**\n * Lay every node out on the grid, document order preserved.\n *\n * Coordinates and NOTHING else change — each returned node is a copy of the\n * input node with `x`, `y` and `position` set, in both spellings, because\n * both are in circulation (see {@link readNodePoint}) and a layout that only\n * writes one does not survive the save round trip. The input array and its\n * nodes are never mutated.\n *\n * No two nodes can share a point: every node gets its own (column, row) cell\n * and cells are spaced by fixed offsets.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @return {Array<object>} New node objects carrying computed positions.\n */\nexport function layoutFlowNodes(nodes, lines, startIds) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tif (!list.length) {\n\t\treturn []\n\t}\n\n\tconst columns = layerColumns(list, lines, startIds)\n\tconst rows = orderRows(list, lines, columns)\n\n\treturn list.map((node) => {\n\t\tconst x = FLOW_LAYOUT_MARGIN + (columns.get(node.id) * FLOW_LAYOUT_COLUMN_WIDTH)\n\t\tconst y = FLOW_LAYOUT_TOP + (rows.get(node.id) * FLOW_LAYOUT_ROW_HEIGHT)\n\t\treturn { ...node, x, y, position: { x, y } }\n\t})\n}\n\n/**\n * Slot position-less nodes beneath an arranged graph, leaving it untouched.\n *\n * A flow with even one deliberately placed node is treated as arranged, and\n * an arranged flow is never rearranged behind its author's back. But the\n * nodes that DO lack positions still may not pile onto the origin — under\n * the arranged nodes, most likely — so they get the same layered pass as a\n * full layout, shifted below the lowest positioned node. Left-to-right order\n * still follows the run, and rows are counted among the loose nodes only.\n *\n * Positioned nodes are returned exactly as given (the same object, not a\n * copy), which is what \"untouched\" means here: reference equality is the\n * proof no field changed.\n *\n * @param {Array<object>} nodes The flow's nodes, some positioned and some not.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @return {Array<object>} The same array shape, loose nodes placed beneath.\n */\nexport function placeLooseNodes(nodes, lines, startIds) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tconst loose = list.filter((node) => readNodePoint(node) === null)\n\tif (!loose.length) {\n\t\treturn list.slice()\n\t}\n\n\tlet lowest = -Infinity\n\tfor (const node of list) {\n\t\tconst point = readNodePoint(node)\n\t\tif (point !== null && point.y > lowest) {\n\t\t\tlowest = point.y\n\t\t}\n\t}\n\tconst top = Math.max(\n\t\tFLOW_LAYOUT_TOP,\n\t\tlowest === -Infinity ? FLOW_LAYOUT_TOP : lowest + FLOW_LAYOUT_ROW_HEIGHT,\n\t)\n\n\t// Columns come from the WHOLE graph, so a loose node keeps its place in\n\t// the run order; rows count loose nodes only, so the strip below the\n\t// arranged graph stays as short as the loose nodes make it.\n\tconst columns = layerColumns(list, lines, startIds)\n\tconst rowsTaken = new Map()\n\n\treturn list.map((node) => {\n\t\tif (readNodePoint(node) !== null) {\n\t\t\treturn node\n\t\t}\n\n\t\tconst column = columns.get(node.id)\n\t\tconst row = rowsTaken.get(column) || 0\n\t\trowsTaken.set(column, row + 1)\n\n\t\tconst x = FLOW_LAYOUT_MARGIN + (column * FLOW_LAYOUT_COLUMN_WIDTH)\n\t\tconst y = top + (row * FLOW_LAYOUT_ROW_HEIGHT)\n\t\treturn { ...node, x, y, position: { x, y } }\n\t})\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,wBAAwB,GAAG,IAAG;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,sBAAsB,GAAG,IAAG;AACzC;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,kBAAkB,GAAG,GAAE;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,eAAe,GAAG,GAAE;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE;AACpC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAC;AAC/C,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;AAC1B,EAAE,OAAO,IAAI;AACb,EAAE;AACF;AACA,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAC;AAC/C,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AAC5C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAE;AAC/C,CAA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"}
|
|
1
|
+
{"version":3,"file":"flowGraphLayout.js","sources":["../../../src/composables/flowGraphLayout.js"],"sourcesContent":["/**\n * SPDX-License-Identifier: EUPL-1.2\n * SPDX-FileCopyrightText: 2026 Conduction B.V. <info@conduction.nl>\n */\n\n/**\n * Pure layout for flow graphs whose nodes carry no usable positions.\n *\n * A flow declared in a schema's `x-openregister-flows` (and any generated or\n * imported flow) ships nodes with no coordinates at all. Rendered as-is, all\n * of them land on one point: the canvas shows one node's worth of pixels with\n * seventeen more underneath, and no amount of fit-view or zoom can separate\n * them. The run replay's per-node badges paint correctly — onto a pile nobody\n * can read.\n *\n * The functions here are PURE and deterministic: same nodes, same lines, same\n * start ids — same coordinates, every time. They never touch a store, never\n * read the DOM, and never mutate their inputs, which is what makes them safe\n * to run on a published (graph-locked) flow: the caller decides what to do\n * with the returned array, and a viewer that only renders it has mutated\n * nothing. This is also why the layout lives here and not in CnGraphCanvas:\n * per ADR-065 the canvas renders the geometry it is given and never invents\n * any.\n *\n * The algorithm is a small layered pass, on purpose. dagre and friends are\n * real dependencies with real weight, the in-app CSP allows no CDN fetch, and\n * nothing in the dependency tree ships a layout engine — while the layouts a\n * flow needs (left-to-right, branches fanned out, loops tolerated) fit in the\n * three steps below:\n *\n * 1. LAYER: longest-path columns over the graph with its back edges\n * dropped, so triggers sit left and every forward edge points right.\n * Back edges are found by depth-first search; dropping them is what\n * makes a loop terminate instead of pushing its members ever further\n * right (or hanging).\n * 2. ORDER: one barycenter pass per column — a node sits near the average\n * row of the nodes that lead to it, which keeps parallel branches apart\n * instead of braided.\n * 3. PLACE: fixed spacing on a grid. One column and row per node, so no\n * two nodes can overlap by construction.\n */\n\n/**\n * Horizontal distance between layout columns, in canvas pixels.\n *\n * Sized against the node card (~230px wide) so the edge between two adjacent\n * columns has room for its arrowhead and result badge.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_COLUMN_WIDTH = 260\n\n/**\n * Vertical distance between layout rows, in canvas pixels.\n *\n * About a card high plus a gap, for the same reason as the column width.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_ROW_HEIGHT = 170\n\n/**\n * Left margin before the first column, in canvas pixels.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_MARGIN = 60\n\n/**\n * Top margin above the first row, in canvas pixels.\n *\n * The editor's toolbar FLOATS over the canvas (position: absolute, ~52px\n * tall). A node laid out at a uniform 60px margin landed UNDER it, and an\n * overlaid node is not merely ugly — it is unreachable: the toolbar swallows\n * the pointer. Measured live: Playwright reported `cn-flow-detail__toolbar\n * subtree intercepts pointer events` on a node behind it.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_TOP = 96\n\n/**\n * Vertical distance between layers when the run reads top to bottom\n * (`direction: 'down'`), in canvas pixels: a step card (~62px) plus room for\n * the line and its arrowhead, as DqFlow draws its steps 100 to 110px apart.\n *\n * @type {number}\n */\nexport const FLOW_LAYOUT_LAYER_HEIGHT_DOWN = 110\n\n/**\n * Where a node sits, from its layer (run order) and its lane (branch).\n *\n * `right` (default): layers are columns left to right, lanes are rows.\n * `down`: layers are rows top to bottom, lanes are columns.\n *\n * @param {number} layer Zero-based layer.\n * @param {number} lane Zero-based lane.\n * @param {string} direction `right` or `down`.\n * @param {number} top The y of the first layer or row.\n * @return {{x: number, y: number}} The point.\n */\nfunction cellPoint(layer, lane, direction, top) {\n\tif (direction === 'down') {\n\t\treturn { x: FLOW_LAYOUT_MARGIN + (lane * FLOW_LAYOUT_COLUMN_WIDTH), y: top + (layer * FLOW_LAYOUT_LAYER_HEIGHT_DOWN) }\n\t}\n\treturn { x: FLOW_LAYOUT_MARGIN + (layer * FLOW_LAYOUT_COLUMN_WIDTH), y: top + (lane * FLOW_LAYOUT_ROW_HEIGHT) }\n}\n\n/**\n * The position a node carries, in either of the two spellings in circulation.\n *\n * The server stores `position: {x, y}`; the editor writes flat `x`/`y` in\n * memory. Reading only one spelling is the bug that once piled every saved\n * flow onto the origin, so both are read here, flat first — the same order\n * `useFlowStore.hasPosition()` and the canvas mapping use.\n *\n * A node AT the origin still counts as positioned: (0, 0) is a legitimate\n * place to park something. Whether a WHOLE GRAPH of identical points counts\n * is a different question, answered by {@link needsFullLayout}.\n *\n * @param {object} node A flow node.\n * @return {{x: number, y: number}|null} Its point, or null when it has none.\n */\nexport function readNodePoint(node) {\n\tconst x = Number(node?.x ?? node?.position?.x)\n\tif (!Number.isFinite(x)) {\n\t\treturn null\n\t}\n\n\tconst y = Number(node?.y ?? node?.position?.y)\n\treturn { x, y: Number.isFinite(y) ? y : 0 }\n}\n\n/**\n * Whether the whole graph needs laying out from scratch.\n *\n * True in exactly two situations:\n *\n * - NO node carries a position. Declared, generated and imported flows all\n * arrive like this.\n * - Every node that does carry a position sits on the SAME point, and at\n * least two do. That is not an arrangement, it is the same pile with\n * coordinates written down — an importer that stamps `{x: 0, y: 0}` on\n * every node produces it, and it must not be mistaken for a deliberate\n * layout. A single node parked at one point (the origin included) stays\n * where its author put it.\n *\n * Anything else — at least two distinct positioned points — is treated as\n * arranged: someone chose those places, and a layout that second-guesses them\n * throws that choice away. Position-less nodes in an arranged flow are the\n * caller's problem; {@link placeLooseNodes} slots them beneath.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @return {boolean} True when nothing about the current positions is worth keeping.\n */\nexport function needsFullLayout(nodes) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tif (!list.length) {\n\t\treturn false\n\t}\n\n\tconst points = []\n\tfor (const node of list) {\n\t\tconst point = readNodePoint(node)\n\t\tif (point !== null) {\n\t\t\tpoints.push(point)\n\t\t}\n\t}\n\n\tif (!points.length) {\n\t\treturn true\n\t}\n\n\treturn points.length >= 2\n\t\t&& points.every((p) => p.x === points[0].x && p.y === points[0].y)\n}\n\n/**\n * Column per node id: longest path from the start nodes, back edges dropped.\n *\n * Depth-first search from the start ids classifies back edges — an edge whose\n * target is still on the current DFS stack closes a loop. Those edges are\n * excluded from layering, which is what lets a flow with a loop (send back to\n * the submitter, wait, check again) lay out finitely: without it, longest\n * path around a cycle is unbounded. The back edge is still DRAWN, of course —\n * only the layering ignores it.\n *\n * Nodes the start ids cannot reach go one column past everything — never at\n * the origin, where they would hide under the entry points.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @return {Map<string, number>} Node id to zero-based column.\n */\nfunction layerColumns(nodes, lines, startIds) {\n\tconst ids = new Set(nodes.map((node) => node.id))\n\tconst outgoing = new Map()\n\tfor (const line of lines) {\n\t\tif (line.source === line.target || !ids.has(line.source) || !ids.has(line.target)) {\n\t\t\tcontinue\n\t\t}\n\t\tif (!outgoing.has(line.source)) {\n\t\t\toutgoing.set(line.source, [])\n\t\t}\n\t\toutgoing.get(line.source).push(line.target)\n\t}\n\n\t// DFS: classify back edges and record a reverse postorder (a topological\n\t// order of the graph minus its back edges). Iterative, because a flow's\n\t// depth is user-controlled and the call stack is not.\n\tconst seen = new Set()\n\tconst onStack = new Set()\n\tconst forward = new Map()\n\tconst postorder = []\n\tfor (const startId of startIds) {\n\t\tif (!ids.has(startId) || seen.has(startId)) {\n\t\t\tcontinue\n\t\t}\n\n\t\tconst stack = [{ id: startId, next: 0 }]\n\t\tseen.add(startId)\n\t\tonStack.add(startId)\n\t\twhile (stack.length) {\n\t\t\tconst frame = stack[stack.length - 1]\n\t\t\tconst targets = outgoing.get(frame.id) || []\n\t\t\tif (frame.next >= targets.length) {\n\t\t\t\tstack.pop()\n\t\t\t\tonStack.delete(frame.id)\n\t\t\t\tpostorder.push(frame.id)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tconst target = targets[frame.next++]\n\t\t\tif (onStack.has(target)) {\n\t\t\t\t// A back edge: drawing it is fine, layering on it is not.\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tif (!forward.has(frame.id)) {\n\t\t\t\tforward.set(frame.id, [])\n\t\t\t}\n\t\t\tforward.get(frame.id).push(target)\n\n\t\t\tif (!seen.has(target)) {\n\t\t\t\tseen.add(target)\n\t\t\t\tonStack.add(target)\n\t\t\t\tstack.push({ id: target, next: 0 })\n\t\t\t}\n\t\t}\n\t}\n\n\t// Longest path over the acyclic remainder, in topological order: a node\n\t// sits one column past the furthest node that leads to it.\n\tconst columns = new Map()\n\tfor (const startId of startIds) {\n\t\tif (seen.has(startId)) {\n\t\t\tcolumns.set(startId, 0)\n\t\t}\n\t}\n\tfor (let i = postorder.length - 1; i >= 0; i--) {\n\t\tconst id = postorder[i]\n\t\tconst from = columns.get(id) ?? 0\n\t\tcolumns.set(id, from)\n\t\tfor (const target of (forward.get(id) || [])) {\n\t\t\tif ((columns.get(target) ?? -1) < from + 1) {\n\t\t\t\tcolumns.set(target, from + 1)\n\t\t\t}\n\t\t}\n\t}\n\n\tconst unreachableColumn = (Math.max(-1, ...columns.values()) + 1)\n\tfor (const node of nodes) {\n\t\tif (!columns.has(node.id)) {\n\t\t\tcolumns.set(node.id, unreachableColumn)\n\t\t}\n\t}\n\n\treturn columns\n}\n\n/**\n * Row per node id: barycenter order within each column.\n *\n * One left-to-right pass. A node's sort key is the mean row of the already\n * placed nodes that lead into it, so a branch follows the branch point it\n * came from instead of braiding across the others. Ties — and nodes with no\n * placed predecessor — keep the document's own node order, which is what\n * makes the whole pass deterministic.\n *\n * @param {Array<object>} nodes The flow's nodes, in document order.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Map<string, number>} columns Node id to column, from layering.\n * @return {Map<string, number>} Node id to zero-based row within its column.\n */\nfunction orderRows(nodes, lines, columns) {\n\tconst incoming = new Map()\n\tfor (const line of lines) {\n\t\tif (line.source === line.target) {\n\t\t\tcontinue\n\t\t}\n\t\tif (!incoming.has(line.target)) {\n\t\t\tincoming.set(line.target, [])\n\t\t}\n\t\tincoming.get(line.target).push(line.source)\n\t}\n\n\tconst byColumn = new Map()\n\tfor (const [index, node] of nodes.entries()) {\n\t\tconst column = columns.get(node.id)\n\t\tif (!byColumn.has(column)) {\n\t\t\tbyColumn.set(column, [])\n\t\t}\n\t\tbyColumn.get(column).push({ id: node.id, index })\n\t}\n\n\tconst rows = new Map()\n\tconst columnKeys = [...byColumn.keys()].sort((a, b) => a - b)\n\tfor (const column of columnKeys) {\n\t\tconst members = byColumn.get(column)\n\t\tconst keyed = members.map((member) => {\n\t\t\tconst placed = (incoming.get(member.id) || [])\n\t\t\t\t.map((source) => rows.get(source))\n\t\t\t\t.filter((row) => row !== undefined)\n\t\t\tconst barycenter = placed.length\n\t\t\t\t? placed.reduce((sum, row) => sum + row, 0) / placed.length\n\t\t\t\t: member.index\n\t\t\treturn { ...member, barycenter }\n\t\t})\n\t\tkeyed.sort((a, b) => (a.barycenter - b.barycenter) || (a.index - b.index))\n\t\tfor (const [row, member] of keyed.entries()) {\n\t\t\trows.set(member.id, row)\n\t\t}\n\t}\n\n\treturn rows\n}\n\n/**\n * Lay every node out on the grid, document order preserved.\n *\n * Coordinates and NOTHING else change — each returned node is a copy of the\n * input node with `x`, `y` and `position` set, in both spellings, because\n * both are in circulation (see {@link readNodePoint}) and a layout that only\n * writes one does not survive the save round trip. The input array and its\n * nodes are never mutated.\n *\n * No two nodes can share a point: every node gets its own (column, row) cell\n * and cells are spaced by fixed offsets.\n *\n * @param {Array<object>} nodes The flow's nodes.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @param {object} [options] `{ direction }`: `right` (default) runs left to\n * right, `down` runs top to bottom with branches side by side.\n * @return {Array<object>} New node objects carrying computed positions.\n */\nexport function layoutFlowNodes(nodes, lines, startIds, options = {}) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tif (!list.length) {\n\t\treturn []\n\t}\n\n\tconst columns = layerColumns(list, lines, startIds)\n\tconst rows = orderRows(list, lines, columns)\n\n\treturn list.map((node) => {\n\t\tconst { x, y } = cellPoint(columns.get(node.id), rows.get(node.id), options.direction, FLOW_LAYOUT_TOP)\n\t\treturn { ...node, x, y, position: { x, y } }\n\t})\n}\n\n/**\n * Slot position-less nodes beneath an arranged graph, leaving it untouched.\n *\n * A flow with even one deliberately placed node is treated as arranged, and\n * an arranged flow is never rearranged behind its author's back. But the\n * nodes that DO lack positions still may not pile onto the origin — under\n * the arranged nodes, most likely — so they get the same layered pass as a\n * full layout, shifted below the lowest positioned node. Left-to-right order\n * still follows the run, and rows are counted among the loose nodes only.\n *\n * Positioned nodes are returned exactly as given (the same object, not a\n * copy), which is what \"untouched\" means here: reference equality is the\n * proof no field changed.\n *\n * @param {Array<object>} nodes The flow's nodes, some positioned and some not.\n * @param {Array<{source: string, target: string}>} lines The expanded edges.\n * @param {Array<string>} startIds The ids a run enters through.\n * @param {object} [options] `{ direction }`, as {@link layoutFlowNodes}.\n * @return {Array<object>} The same array shape, loose nodes placed beneath.\n */\nexport function placeLooseNodes(nodes, lines, startIds, options = {}) {\n\tconst list = Array.isArray(nodes) ? nodes : []\n\tconst loose = list.filter((node) => readNodePoint(node) === null)\n\tif (!loose.length) {\n\t\treturn list.slice()\n\t}\n\n\tlet lowest = -Infinity\n\tfor (const node of list) {\n\t\tconst point = readNodePoint(node)\n\t\tif (point !== null && point.y > lowest) {\n\t\t\tlowest = point.y\n\t\t}\n\t}\n\tconst top = Math.max(\n\t\tFLOW_LAYOUT_TOP,\n\t\tlowest === -Infinity ? FLOW_LAYOUT_TOP : lowest + FLOW_LAYOUT_ROW_HEIGHT,\n\t)\n\n\t// Columns come from the WHOLE graph, so a loose node keeps its place in\n\t// the run order; rows count loose nodes only, so the strip below the\n\t// arranged graph stays as short as the loose nodes make it.\n\tconst columns = layerColumns(list, lines, startIds)\n\tconst rowsTaken = new Map()\n\n\treturn list.map((node) => {\n\t\tif (readNodePoint(node) !== null) {\n\t\t\treturn node\n\t\t}\n\n\t\tconst column = columns.get(node.id)\n\t\tconst row = rowsTaken.get(column) || 0\n\t\trowsTaken.set(column, row + 1)\n\n\t\tconst { x, y } = cellPoint(column, row, options.direction, top)\n\t\treturn { ...node, x, y, position: { x, y } }\n\t})\n}\n"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,wBAAwB,GAAG,IAAG;AAC3C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,sBAAsB,GAAG,IAAG;AACzC;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,kBAAkB,GAAG,GAAE;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,eAAe,GAAG,GAAE;AACjC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACY,MAAC,6BAA6B,GAAG,IAAG;AAChD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE;AAChD,CAAC,IAAI,SAAS,KAAK,MAAM,EAAE;AAC3B,EAAE,OAAO,EAAE,CAAC,EAAE,kBAAkB,IAAI,IAAI,GAAG,wBAAwB,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,KAAK,GAAG,6BAA6B,CAAC,EAAE;AACxH,EAAE;AACF,CAAC,OAAO,EAAE,CAAC,EAAE,kBAAkB,IAAI,KAAK,GAAG,wBAAwB,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,IAAI,GAAG,sBAAsB,CAAC,EAAE;AAChH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,aAAa,CAAC,IAAI,EAAE;AACpC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAC;AAC/C,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;AAC1B,EAAE,OAAO,IAAI;AACb,EAAE;AACF;AACA,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,EAAE,QAAQ,EAAE,CAAC,EAAC;AAC/C,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;AAC5C,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE;AACvC,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAE;AAC/C,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AACnB,EAAE,OAAO,KAAK;AACd,EAAE;AACF;AACA,CAAC,MAAM,MAAM,GAAG,GAAE;AAClB,CAAC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;AAC1B,EAAE,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAC;AACnC,EAAE,IAAI,KAAK,KAAK,IAAI,EAAE;AACtB,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAC;AACrB,GAAG;AACH,EAAE;AACF;AACA,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE;AACrB,EAAE,OAAO,IAAI;AACb,EAAE;AACF;AACA,CAAC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC;AAC1B,KAAK,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,YAAY,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE;AAC9C,CAAC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC,EAAC;AAClD,CAAC,MAAM,QAAQ,GAAG,IAAI,GAAG,GAAE;AAC3B,CAAC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AAC3B,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;AACrF,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;AAClC,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAC;AAChC,GAAG;AACH,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAC;AAC7C,EAAE;AACF;AACA;AACA;AACA;AACA,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,GAAE;AACvB,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,GAAE;AAC1B,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,GAAE;AAC1B,CAAC,MAAM,SAAS,GAAG,GAAE;AACrB,CAAC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AACjC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;AAC9C,GAAG,QAAQ;AACX,GAAG;AACH;AACA,EAAE,MAAM,KAAK,GAAG,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,EAAC;AAC1C,EAAE,IAAI,CAAC,GAAG,CAAC,OAAO,EAAC;AACnB,EAAE,OAAO,CAAC,GAAG,CAAC,OAAO,EAAC;AACtB,EAAE,OAAO,KAAK,CAAC,MAAM,EAAE;AACvB,GAAG,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAC;AACxC,GAAG,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,GAAE;AAC/C,GAAG,IAAI,KAAK,CAAC,IAAI,IAAI,OAAO,CAAC,MAAM,EAAE;AACrC,IAAI,KAAK,CAAC,GAAG,GAAE;AACf,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAC;AAC5B,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAC;AAC5B,IAAI,QAAQ;AACZ,IAAI;AACJ;AACA,GAAG,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,EAAC;AACvC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;AAC5B;AACA,IAAI,QAAQ;AACZ,IAAI;AACJ;AACA,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE;AAC/B,IAAI,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,EAAC;AAC7B,IAAI;AACJ,GAAG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,EAAC;AACrC;AACA,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;AAC1B,IAAI,IAAI,CAAC,GAAG,CAAC,MAAM,EAAC;AACpB,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAC;AACvB,IAAI,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAC;AACvC,IAAI;AACJ,GAAG;AACH,EAAE;AACF;AACA;AACA;AACA,CAAC,MAAM,OAAO,GAAG,IAAI,GAAG,GAAE;AAC1B,CAAC,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE;AACjC,EAAE,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE;AACzB,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,EAAC;AAC1B,GAAG;AACH,EAAE;AACF,CAAC,KAAK,IAAI,CAAC,GAAG,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE;AACjD,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,EAAC;AACzB,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAC;AACnC,EAAE,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,EAAC;AACvB,EAAE,KAAK,MAAM,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG;AAChD,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE;AAC/C,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,GAAG,CAAC,EAAC;AACjC,IAAI;AACJ,GAAG;AACH,EAAE;AACF;AACA,CAAC,MAAM,iBAAiB,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAC;AAClE,CAAC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AAC3B,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;AAC7B,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,iBAAiB,EAAC;AAC1C,GAAG;AACH,EAAE;AACF;AACA,CAAC,OAAO,OAAO;AACf,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS,SAAS,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE;AAC1C,CAAC,MAAM,QAAQ,GAAG,IAAI,GAAG,GAAE;AAC3B,CAAC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE;AAC3B,EAAE,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE;AACnC,GAAG,QAAQ;AACX,GAAG;AACH,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE;AAClC,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAC;AAChC,GAAG;AACH,EAAE,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAC;AAC7C,EAAE;AACF;AACA,CAAC,MAAM,QAAQ,GAAG,IAAI,GAAG,GAAE;AAC3B,CAAC,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE;AAC9C,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAC;AACrC,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE;AAC7B,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,EAAC;AAC3B,GAAG;AACH,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,EAAC;AACnD,EAAE;AACF;AACA,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,GAAE;AACvB,CAAC,MAAM,UAAU,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAC;AAC9D,CAAC,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE;AAClC,EAAE,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAC;AACtC,EAAE,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,KAAK;AACxC,GAAG,MAAM,MAAM,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE;AAChD,KAAK,GAAG,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AACtC,KAAK,MAAM,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,SAAS,EAAC;AACvC,GAAG,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM;AACnC,MAAM,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,GAAG,KAAK,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM;AAC/D,MAAM,MAAM,CAAC,MAAK;AAClB,GAAG,OAAO,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE;AACnC,GAAG,EAAC;AACJ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,EAAC;AAC5E,EAAE,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE;AAC/C,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,EAAC;AAC3B,GAAG;AACH,EAAE;AACF;AACA,CAAC,OAAO,IAAI;AACZ,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAG,EAAE,EAAE;AACtE,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAE;AAC/C,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE;AACnB,EAAE,OAAO,EAAE;AACX,EAAE;AACF;AACA,CAAC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAC;AACpD,CAAC,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,EAAC;AAC7C;AACA,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK;AAC3B,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,SAAS,EAAE,eAAe,EAAC;AACzG,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;AAC9C,EAAE,CAAC;AACH,CAAC;AACD;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACO,SAAS,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,GAAG,EAAE,EAAE;AACtE,CAAC,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,KAAK,GAAG,GAAE;AAC/C,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,EAAC;AAClE,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE;AACpB,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE;AACrB,EAAE;AACF;AACA,CAAC,IAAI,MAAM,GAAG,CAAC,SAAQ;AACvB,CAAC,KAAK,MAAM,IAAI,IAAI,IAAI,EAAE;AAC1B,EAAE,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAC;AACnC,EAAE,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,CAAC,GAAG,MAAM,EAAE;AAC1C,GAAG,MAAM,GAAG,KAAK,CAAC,EAAC;AACnB,GAAG;AACH,EAAE;AACF,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG;AACrB,EAAE,eAAe;AACjB,EAAE,MAAM,KAAK,CAAC,QAAQ,GAAG,eAAe,GAAG,MAAM,GAAG,sBAAsB;AAC1E,GAAE;AACF;AACA;AACA;AACA;AACA,CAAC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAC;AACpD,CAAC,MAAM,SAAS,GAAG,IAAI,GAAG,GAAE;AAC5B;AACA,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,KAAK;AAC3B,EAAE,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE;AACpC,GAAG,OAAO,IAAI;AACd,GAAG;AACH;AACA,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAC;AACrC,EAAE,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAC;AACxC,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAC;AAChC;AACA,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,SAAS,EAAE,GAAG,EAAC;AACjE,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,CAAC,EAAE,CAAC,EAAE,QAAQ,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE;AAC9C,EAAE,CAAC;AACH;;;;"}
|
|
@@ -102,6 +102,10 @@ const useFlowStore = defineStore('cnFlow', {
|
|
|
102
102
|
flow: emptyFlow(),
|
|
103
103
|
flows: [],
|
|
104
104
|
|
|
105
|
+
// How the automatic layout runs: `right` (left to right) or `down`
|
|
106
|
+
// (top to bottom). Set by the editor that hosts the canvas.
|
|
107
|
+
layoutDirection: 'right',
|
|
108
|
+
|
|
105
109
|
// Snapshots of `flow`, oldest first, for Ctrl+Z.
|
|
106
110
|
//
|
|
107
111
|
// A canvas is direct manipulation: a mis-drag or a mis-aimed Delete
|
|
@@ -1660,7 +1664,7 @@ const useFlowStore = defineStore('cnFlow', {
|
|
|
1660
1664
|
return
|
|
1661
1665
|
}
|
|
1662
1666
|
|
|
1663
|
-
this.flow.nodes = layoutFlowNodes(this.nodes, this.canvasEdges, this.startNodeIds);
|
|
1667
|
+
this.flow.nodes = layoutFlowNodes(this.nodes, this.canvasEdges, this.startNodeIds, { direction: this.layoutDirection });
|
|
1664
1668
|
this.dirty = true;
|
|
1665
1669
|
},
|
|
1666
1670
|
|
|
@@ -1694,12 +1698,12 @@ const useFlowStore = defineStore('cnFlow', {
|
|
|
1694
1698
|
}
|
|
1695
1699
|
|
|
1696
1700
|
if (needsFullLayout(nodes)) {
|
|
1697
|
-
this.flow.nodes = layoutFlowNodes(nodes, this.canvasEdges, this.startNodeIds);
|
|
1701
|
+
this.flow.nodes = layoutFlowNodes(nodes, this.canvasEdges, this.startNodeIds, { direction: this.layoutDirection });
|
|
1698
1702
|
return true
|
|
1699
1703
|
}
|
|
1700
1704
|
|
|
1701
1705
|
if (nodes.some((node) => !this.hasPosition(node))) {
|
|
1702
|
-
this.flow.nodes = placeLooseNodes(nodes, this.canvasEdges, this.startNodeIds);
|
|
1706
|
+
this.flow.nodes = placeLooseNodes(nodes, this.canvasEdges, this.startNodeIds, { direction: this.layoutDirection });
|
|
1703
1707
|
return true
|
|
1704
1708
|
}
|
|
1705
1709
|
|