@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.
Files changed (98) hide show
  1. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js +1 -0
  2. package/dist/esm/components/CnAppRoot/CnAppRoot.vue2.js.map +1 -1
  3. package/dist/esm/components/CnAppRoot/CnAppRoot.vue3.js.map +1 -1
  4. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue.js +1 -1
  5. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js +18 -4
  6. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue2.js.map +1 -1
  7. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js +63 -32
  8. package/dist/esm/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue3.js.map +1 -1
  9. package/dist/esm/components/CnChartWidget/CnChartWidget.vue.js +1 -1
  10. package/dist/esm/components/CnChartWidget/CnChartWidget.vue2.js.map +1 -1
  11. package/dist/esm/components/CnChartWidget/CnChartWidget.vue3.js.map +1 -1
  12. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +35 -0
  13. package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
  14. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js +11 -5
  15. package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
  16. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue.js +1 -1
  17. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js +13 -0
  18. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue2.js.map +1 -1
  19. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js +37 -5
  20. package/dist/esm/components/CnDetailWidgetHost/CnDetailWidgetHost.vue3.js.map +1 -1
  21. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue.js +1 -1
  22. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +40 -0
  23. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
  24. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +71 -65
  25. package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
  26. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue.js +1 -1
  27. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js +20 -0
  28. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue2.js.map +1 -1
  29. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js +2 -1
  30. package/dist/esm/components/CnFlowDetail/CnFlowDetail.vue3.js.map +1 -1
  31. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue.js +1 -1
  32. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js +2 -0
  33. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue2.js.map +1 -1
  34. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js +49 -44
  35. package/dist/esm/components/CnFlowsPage/CnFlowBoard.vue3.js.map +1 -1
  36. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js +10 -0
  37. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue2.js.map +1 -1
  38. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js +2 -1
  39. package/dist/esm/components/CnFlowsPage/CnFlowEditorPage.vue3.js.map +1 -1
  40. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js +31 -0
  41. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue2.js.map +1 -1
  42. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js +4 -3
  43. package/dist/esm/components/CnGraphCanvas/CnGraphCanvas.vue3.js.map +1 -1
  44. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +34 -2
  45. package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
  46. package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
  47. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue.js +1 -1
  48. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js +2 -0
  49. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
  50. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +2 -1
  51. package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
  52. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue.js +1 -1
  53. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js +31 -1
  54. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue2.js.map +1 -1
  55. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js +14 -4
  56. package/dist/esm/components/CnSectionsWidget/CnSectionsWidget.vue3.js.map +1 -1
  57. package/dist/esm/composables/flowGraphLayout.js +36 -7
  58. package/dist/esm/composables/flowGraphLayout.js.map +1 -1
  59. package/dist/esm/composables/useFlowStore.js +7 -3
  60. package/dist/esm/composables/useFlowStore.js.map +1 -1
  61. package/dist/esm/l10n/en.json.js +3 -1
  62. package/dist/esm/l10n/en.json.js.map +1 -1
  63. package/dist/esm/l10n/nl.json.js +3 -1
  64. package/dist/esm/l10n/nl.json.js.map +1 -1
  65. package/dist/esm/library-style.js +1 -1
  66. package/dist/esm/nextcloud-vue.css +508 -303
  67. package/dist/esm/utils/manifestTranslate.js +12 -3
  68. package/dist/esm/utils/manifestTranslate.js.map +1 -1
  69. package/dist/esm/utils/routeFilters.js +3 -3
  70. package/dist/esm/utils/routeFilters.js.map +1 -1
  71. package/dist/nextcloud-vue.cjs.js +1217 -847
  72. package/dist/nextcloud-vue.cjs.js.map +2 -2
  73. package/dist/nextcloud-vue.css +508 -303
  74. package/l10n/en.json +3 -1
  75. package/l10n/nl.json +3 -1
  76. package/package.json +1 -1
  77. package/src/components/CnAppRoot/CnAppRoot.vue +1 -0
  78. package/src/components/CnAttentionFlagWidget/CnAttentionFlagWidget.vue +50 -5
  79. package/src/components/CnChartWidget/CnChartWidget.vue +10 -8
  80. package/src/components/CnDetailPage/CnDetailPage.md +10 -0
  81. package/src/components/CnDetailPage/CnDetailPage.vue +42 -1
  82. package/src/components/CnDetailWidgetHost/CnDetailWidgetHost.vue +39 -0
  83. package/src/components/CnFilesBrowser/CnFilesBrowser.vue +95 -37
  84. package/src/components/CnFlowDetail/CnFlowDetail.vue +21 -0
  85. package/src/components/CnFlowsPage/CnFlowBoard.vue +35 -18
  86. package/src/components/CnFlowsPage/CnFlowEditorPage.vue +11 -0
  87. package/src/components/CnGraphCanvas/CnGraphCanvas.vue +34 -2
  88. package/src/components/CnIndexPage/CnIndexPage.vue +34 -2
  89. package/src/components/CnObjectSidebar/CnFilesTab.vue +3 -0
  90. package/src/components/CnSectionsWidget/CnSectionsWidget.vue +53 -2
  91. package/src/composables/flowGraphLayout.js +35 -6
  92. package/src/composables/useFlowStore.js +7 -3
  93. package/src/css/detail-page.css +6 -0
  94. package/src/css/look-board-detail.css +26 -6
  95. package/src/css/look-board-index.css +43 -0
  96. package/src/css/look-board.css +89 -3
  97. package/src/utils/manifestTranslate.js +12 -3
  98. 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 = FLOW_LAYOUT_MARGIN + (columns.get(node.id) * FLOW_LAYOUT_COLUMN_WIDTH);
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 = FLOW_LAYOUT_MARGIN + (column * FLOW_LAYOUT_COLUMN_WIDTH);
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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"}
@@ -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