symbiote-ui 0.3.0-alpha.4

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 (322) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/LICENSE +21 -0
  3. package/README.md +76 -0
  4. package/canvas/AutoLayout.js +731 -0
  5. package/canvas/Breadcrumb/Breadcrumb.css.js +75 -0
  6. package/canvas/Breadcrumb/Breadcrumb.js +96 -0
  7. package/canvas/Breadcrumb/Breadcrumb.tpl.js +7 -0
  8. package/canvas/CanvasConnectionRenderer.js +971 -0
  9. package/canvas/CanvasGraph/CanvasGraph.css.js +29 -0
  10. package/canvas/CanvasGraph/CanvasGraph.js +1697 -0
  11. package/canvas/CanvasGraph/CanvasGraphDrawState.js +280 -0
  12. package/canvas/CanvasGraph/CanvasGraphGeometry.js +194 -0
  13. package/canvas/CanvasViewport.js +550 -0
  14. package/canvas/ConnectionRenderer.js +1283 -0
  15. package/canvas/FlowSimulator.js +326 -0
  16. package/canvas/ForceLayout.js +226 -0
  17. package/canvas/ForceWorker.js +1303 -0
  18. package/canvas/FrameManager.js +223 -0
  19. package/canvas/GraphExplorerShell/GraphExplorerShell.css.js +136 -0
  20. package/canvas/GraphExplorerShell/GraphExplorerShell.js +129 -0
  21. package/canvas/GraphExplorerShell/GraphExplorerShell.tpl.js +12 -0
  22. package/canvas/GraphTabs/GraphTabs.css.js +101 -0
  23. package/canvas/GraphTabs/GraphTabs.js +189 -0
  24. package/canvas/GraphTabs/GraphTabs.tpl.js +12 -0
  25. package/canvas/LODManager.js +88 -0
  26. package/canvas/Minimap/Minimap.css.js +73 -0
  27. package/canvas/Minimap/Minimap.js +210 -0
  28. package/canvas/Minimap/Minimap.tpl.js +7 -0
  29. package/canvas/NodeCanvas/NodeCanvas.css.js +398 -0
  30. package/canvas/NodeCanvas/NodeCanvas.js +1499 -0
  31. package/canvas/NodeCanvas/NodeCanvas.tpl.js +22 -0
  32. package/canvas/NodeSearch/NodeSearch.css.js +97 -0
  33. package/canvas/NodeSearch/NodeSearch.js +140 -0
  34. package/canvas/NodeSearch/NodeSearch.tpl.js +25 -0
  35. package/canvas/NodeViewManager.js +748 -0
  36. package/canvas/PcbRouteDiagnostics.js +463 -0
  37. package/canvas/PcbRouter.js +1127 -0
  38. package/canvas/PinExpansion.js +134 -0
  39. package/canvas/PseudoConnection.js +84 -0
  40. package/canvas/SelectionSync.js +163 -0
  41. package/canvas/SubgraphManager.js +203 -0
  42. package/canvas/SubgraphRouter.js +452 -0
  43. package/canvas/ViewportActions.js +473 -0
  44. package/canvas/graph-explorer.js +339 -0
  45. package/canvas/graph-layout.js +148 -0
  46. package/canvas/graph-model.js +68 -0
  47. package/canvas/html-in-canvas.js +202 -0
  48. package/canvas/project-graph-builder.js +440 -0
  49. package/canvas/project-graph-model.js +183 -0
  50. package/chat/ChatComposer/ChatComposer.css.js +652 -0
  51. package/chat/ChatComposer/ChatComposer.js +304 -0
  52. package/chat/ChatList/ChatList.css.js +102 -0
  53. package/chat/ChatList/ChatList.js +99 -0
  54. package/chat/ChatList/ChatList.tpl.js +20 -0
  55. package/chat/ChatListItem/ChatListItem.css.js +117 -0
  56. package/chat/ChatListItem/ChatListItem.js +32 -0
  57. package/chat/ChatListItem/ChatListItem.tpl.js +17 -0
  58. package/chat/ChatMessageItem/ChatMessageItem.css.js +628 -0
  59. package/chat/ChatMessageItem/ChatMessageItem.js +156 -0
  60. package/chat/ChatSidebar/ChatSidebar.css.js +150 -0
  61. package/chat/ChatSidebar/ChatSidebar.js +230 -0
  62. package/chat/ChatSidebar/ChatSidebar.tpl.js +18 -0
  63. package/chat/ChatSidebar/constants.js +11 -0
  64. package/chat/ChatSidebarItem/ChatSidebarItem.css.js +445 -0
  65. package/chat/ChatSidebarItem/ChatSidebarItem.js +304 -0
  66. package/chat/ChatTranscript/ChatTranscript.css.js +90 -0
  67. package/chat/ChatTranscript/ChatTranscript.js +244 -0
  68. package/chat/chat-context.js +123 -0
  69. package/chat/message-model.js +156 -0
  70. package/cli.js +20 -0
  71. package/control/Button/Button.css.js +93 -0
  72. package/control/Button/Button.js +78 -0
  73. package/control/Button/Button.tpl.js +3 -0
  74. package/control/Field/Field.css.js +91 -0
  75. package/control/Field/Field.js +17 -0
  76. package/control/Field/Field.tpl.js +3 -0
  77. package/core/Connection.js +47 -0
  78. package/core/Editor.js +449 -0
  79. package/core/Frame.js +33 -0
  80. package/core/GraphMermaid.js +348 -0
  81. package/core/GraphText.js +228 -0
  82. package/core/Node.js +145 -0
  83. package/core/Portal.js +106 -0
  84. package/core/Socket.js +187 -0
  85. package/core/SubgraphNode.js +121 -0
  86. package/core/base-path.js +55 -0
  87. package/core/dom-utils.js +14 -0
  88. package/core/index.js +18 -0
  89. package/core/local-cache.js +26 -0
  90. package/core/state-sync.js +227 -0
  91. package/custom-elements.json +6380 -0
  92. package/discover.js +240 -0
  93. package/display/Badge/Badge.css.js +44 -0
  94. package/display/Badge/Badge.js +17 -0
  95. package/display/Badge/Badge.tpl.js +3 -0
  96. package/display/Banner/Banner.css.js +61 -0
  97. package/display/Banner/Banner.js +17 -0
  98. package/display/Banner/Banner.tpl.js +3 -0
  99. package/display/CodeBlock/CodeBlock.css.js +194 -0
  100. package/display/CodeBlock/CodeBlock.js +220 -0
  101. package/display/CodeBlock/CodeBlock.tpl.js +11 -0
  102. package/display/DataTable/DataTable.css.js +101 -0
  103. package/display/DataTable/DataTable.js +136 -0
  104. package/display/DataTable/DataTable.tpl.js +13 -0
  105. package/display/EmptyState/EmptyState.css.js +33 -0
  106. package/display/EmptyState/EmptyState.js +17 -0
  107. package/display/EmptyState/EmptyState.tpl.js +3 -0
  108. package/display/EventFeed/EventFeed.css.js +145 -0
  109. package/display/EventFeed/EventFeed.js +64 -0
  110. package/display/EventFeed/EventFeed.tpl.js +14 -0
  111. package/display/EventFeed/EventFeedItem.js +116 -0
  112. package/display/EventFeed/EventFeedItem.tpl.js +22 -0
  113. package/display/LoadingOverlay/LoadingOverlay.css.js +91 -0
  114. package/display/LoadingOverlay/LoadingOverlay.js +48 -0
  115. package/display/LoadingOverlay/LoadingOverlay.tpl.js +12 -0
  116. package/display/Metric/Metric.css.js +60 -0
  117. package/display/Metric/Metric.js +17 -0
  118. package/display/Metric/Metric.tpl.js +6 -0
  119. package/display/OutputGraphPreview/OutputGraphPreview.css.js +122 -0
  120. package/display/OutputGraphPreview/OutputGraphPreview.js +89 -0
  121. package/display/OutputGraphPreview/OutputGraphPreview.tpl.js +13 -0
  122. package/display/OutputListPreview/OutputListPreview.css.js +109 -0
  123. package/display/OutputListPreview/OutputListPreview.js +77 -0
  124. package/display/OutputListPreview/OutputListPreview.tpl.js +13 -0
  125. package/display/SourceEditor/SourceEditor.css.js +39 -0
  126. package/display/SourceEditor/SourceEditor.js +129 -0
  127. package/display/SourceEditor/SourceEditor.tpl.js +10 -0
  128. package/display/SourceViewer/SourceViewer.css.js +80 -0
  129. package/display/SourceViewer/SourceViewer.js +418 -0
  130. package/display/SourceViewer/SourceViewer.tpl.js +17 -0
  131. package/display/StatusRibbon/StatusRibbon.css.js +73 -0
  132. package/display/StatusRibbon/StatusRibbon.js +87 -0
  133. package/display/StatusRibbon/StatusRibbon.tpl.js +7 -0
  134. package/display/event-feed-adapter.js +72 -0
  135. package/display/format-utils.js +29 -0
  136. package/display/highlight.js +659 -0
  137. package/display/icons.js +37 -0
  138. package/display/markdown-formatter.js +60 -0
  139. package/display/network-approval-page.js +487 -0
  140. package/display/output-preview.js +261 -0
  141. package/effects/CellBg/CellBg.css.js +33 -0
  142. package/effects/CellBg/CellBg.js +410 -0
  143. package/effects/CellBg/CellBg.tpl.js +5 -0
  144. package/graph/canvas-adapter.js +223 -0
  145. package/graph/graph-algorithms.js +31 -0
  146. package/graph/index.js +46 -0
  147. package/graph/model.js +176 -0
  148. package/graph/project-graph-build.js +66 -0
  149. package/graph/project-graph-metadata.js +253 -0
  150. package/graph/project-package.js +128 -0
  151. package/graph/project-runtime.js +116 -0
  152. package/graph/project-transaction.js +284 -0
  153. package/graph/skeleton-utils.js +84 -0
  154. package/graph/theme-contract.js +36 -0
  155. package/graph/transaction-parser.js +56 -0
  156. package/icons/MaterialSymbols.js +69 -0
  157. package/icons/material-symbols-outlined-400.ttf +0 -0
  158. package/icons/material-symbols.css +24 -0
  159. package/index.js +95 -0
  160. package/inspector/InspectorPanel/InspectorPanel.css.js +375 -0
  161. package/inspector/InspectorPanel/InspectorPanel.js +368 -0
  162. package/inspector/InspectorPanel/InspectorPanel.tpl.js +96 -0
  163. package/inspector/TemplatePreview/TemplatePreview.css.js +104 -0
  164. package/inspector/TemplatePreview/TemplatePreview.js +145 -0
  165. package/inspector/TemplatePreview/TemplatePreview.tpl.js +33 -0
  166. package/interactions/ConnectFlow.js +304 -0
  167. package/interactions/Drag.js +104 -0
  168. package/interactions/Selector.js +133 -0
  169. package/interactions/SnapGrid.js +66 -0
  170. package/interactions/Zoom.js +139 -0
  171. package/layout/ActionZone/ActionZone.css.js +88 -0
  172. package/layout/ActionZone/ActionZone.js +261 -0
  173. package/layout/ActionZone/ActionZone.tpl.js +11 -0
  174. package/layout/CrossLayoutPortalBridge/CrossLayoutPortalBridge.js +255 -0
  175. package/layout/Layout/Layout.css.js +91 -0
  176. package/layout/Layout/Layout.js +637 -0
  177. package/layout/Layout/Layout.tpl.js +27 -0
  178. package/layout/LayoutNode/LayoutNode.css.js +302 -0
  179. package/layout/LayoutNode/LayoutNode.js +509 -0
  180. package/layout/LayoutNode/LayoutNode.tpl.js +39 -0
  181. package/layout/LayoutPreview/LayoutPreview.css.js +46 -0
  182. package/layout/LayoutPreview/LayoutPreview.js +102 -0
  183. package/layout/LayoutPreview/LayoutPreview.tpl.js +6 -0
  184. package/layout/LayoutRouter/LayoutRouter.js +274 -0
  185. package/layout/LayoutRouter/SectionRegistry.js +135 -0
  186. package/layout/LayoutRouter/routerSync.js +250 -0
  187. package/layout/LayoutSidebar/LayoutSidebar.css.js +411 -0
  188. package/layout/LayoutSidebar/LayoutSidebar.js +368 -0
  189. package/layout/LayoutSidebar/LayoutSidebar.tpl.js +26 -0
  190. package/layout/LayoutSidebar/SidebarSection.css.js +20 -0
  191. package/layout/LayoutSidebar/SidebarSection.js +184 -0
  192. package/layout/LayoutSidebar/SidebarSection.tpl.js +22 -0
  193. package/layout/LayoutTree.js +373 -0
  194. package/layout/PanelMenu/PanelMenu.css.js +43 -0
  195. package/layout/PanelMenu/PanelMenu.js +95 -0
  196. package/layout/PanelMenu/PanelMenu.tpl.js +17 -0
  197. package/layout/ProjectTabs/ProjectTabs.css.js +188 -0
  198. package/layout/ProjectTabs/ProjectTabs.js +77 -0
  199. package/layout/ProjectTabs/ProjectTabs.tpl.js +15 -0
  200. package/layout/index.js +40 -0
  201. package/list/ListDetailShell/ListDetailShell.css.js +128 -0
  202. package/list/ListDetailShell/ListDetailShell.js +72 -0
  203. package/list/ListDetailShell/ListDetailShell.tpl.js +36 -0
  204. package/list/ListItem/ListItem.css.js +111 -0
  205. package/list/ListItem/ListItem.js +66 -0
  206. package/list/ListItem/ListItem.tpl.js +18 -0
  207. package/locale/index.js +503 -0
  208. package/manifest/component-registry.js +2446 -0
  209. package/manifest/graph-schema.js +285 -0
  210. package/manifest/index.js +6 -0
  211. package/manifest/project-schema-catalog.js +246 -0
  212. package/manifest/rule-catalog.js +201 -0
  213. package/manifest/theme-catalog.js +2149 -0
  214. package/manifest/ui-schema-catalog.js +334 -0
  215. package/menu/ContextMenu/ContextMenu.css.js +61 -0
  216. package/menu/ContextMenu/ContextMenu.js +82 -0
  217. package/menu/ContextMenu/ContextMenu.tpl.js +19 -0
  218. package/navigation/QuickOpen/QuickOpen.css.js +92 -0
  219. package/navigation/QuickOpen/QuickOpen.js +185 -0
  220. package/navigation/QuickOpen/QuickOpen.tpl.js +15 -0
  221. package/navigation/quick-open-utils.js +101 -0
  222. package/node/CtrlItem/CtrlItem.css.js +41 -0
  223. package/node/CtrlItem/CtrlItem.js +24 -0
  224. package/node/CtrlItem/CtrlItem.tpl.js +17 -0
  225. package/node/GraphFrame/GraphFrame.css.js +66 -0
  226. package/node/GraphFrame/GraphFrame.js +32 -0
  227. package/node/GraphFrame/GraphFrame.tpl.js +13 -0
  228. package/node/GraphNode/GraphNode.css.js +815 -0
  229. package/node/GraphNode/GraphNode.js +173 -0
  230. package/node/GraphNode/GraphNode.tpl.js +33 -0
  231. package/node/NodeCallout/NodeCallout.css.js +91 -0
  232. package/node/NodeCallout/NodeCallout.js +281 -0
  233. package/node/NodeCallout/NodeCallout.tpl.js +8 -0
  234. package/node/NodeSocket/NodeSocket.css.js +68 -0
  235. package/node/NodeSocket/NodeSocket.js +26 -0
  236. package/node/NodeSocket/NodeSocket.tpl.js +7 -0
  237. package/node/PortItem/PortItem.css.js +93 -0
  238. package/node/PortItem/PortItem.js +87 -0
  239. package/node/PortItem/PortItem.tpl.js +10 -0
  240. package/package.json +165 -0
  241. package/palette/PaletteBrowser/PaletteBrowser.css.js +143 -0
  242. package/palette/PaletteBrowser/PaletteBrowser.js +152 -0
  243. package/palette/PaletteBrowser/PaletteBrowser.tpl.js +23 -0
  244. package/plugins/History.js +408 -0
  245. package/plugins/Readonly.js +60 -0
  246. package/rules/symbiote-3x.json +170 -0
  247. package/schemas/component-descriptor-v1.json +91 -0
  248. package/schemas/component-descriptor-v2.json +145 -0
  249. package/schemas/graph-model-v1.json +179 -0
  250. package/schemas/graph-v1.json +91 -0
  251. package/schemas/project-package-v1.json +102 -0
  252. package/schemas/project-transaction-v1.json +114 -0
  253. package/schemas/runtime-ui-v1.json +80 -0
  254. package/schemas/theme-rule-block-v1.json +73 -0
  255. package/shapes/CircleShape.js +79 -0
  256. package/shapes/CommentShape.js +35 -0
  257. package/shapes/DiamondShape.js +130 -0
  258. package/shapes/NodeShape.js +79 -0
  259. package/shapes/PillShape.js +91 -0
  260. package/shapes/RectShape.js +84 -0
  261. package/shapes/SVGShape.js +525 -0
  262. package/shapes/index.js +63 -0
  263. package/surface/Card/Card.css.js +57 -0
  264. package/surface/Card/Card.js +17 -0
  265. package/surface/Card/Card.tpl.js +3 -0
  266. package/themes/Palette.js +30 -0
  267. package/themes/Skin.js +113 -0
  268. package/themes/Theme.js +82 -0
  269. package/themes/carbon.js +135 -0
  270. package/themes/dark.js +140 -0
  271. package/themes/default-dark.js +714 -0
  272. package/themes/default-provider.css +635 -0
  273. package/themes/default-provider.js +718 -0
  274. package/themes/ebook.js +136 -0
  275. package/themes/grey.js +137 -0
  276. package/themes/light.js +139 -0
  277. package/themes/neon.js +138 -0
  278. package/themes/pcb.js +273 -0
  279. package/themes/synthwave.js +138 -0
  280. package/tokens/base.json +29 -0
  281. package/tokens/themes/carbon.json +11 -0
  282. package/tokens/themes/dark.json +12 -0
  283. package/tokens/themes/default-dark.json +1543 -0
  284. package/tokens/themes/default-provider.json +1543 -0
  285. package/tokens/themes/ebook.json +11 -0
  286. package/tokens/themes/grey.json +11 -0
  287. package/tokens/themes/light.json +12 -0
  288. package/tokens/themes/neon.json +11 -0
  289. package/tokens/themes/pcb.json +11 -0
  290. package/tokens/themes/synthwave.json +11 -0
  291. package/toolbar/QuickToolbar/QuickToolbar.css.js +152 -0
  292. package/toolbar/QuickToolbar/QuickToolbar.js +529 -0
  293. package/toolbar/QuickToolbar/QuickToolbar.tpl.js +34 -0
  294. package/tree/TreePanel/TreePanel.css.js +112 -0
  295. package/tree/TreePanel/TreePanel.js +147 -0
  296. package/tree/TreePanel/TreePanel.tpl.js +18 -0
  297. package/tree/TreeView/TreeView.css.js +122 -0
  298. package/tree/TreeView/TreeView.js +365 -0
  299. package/tree/TreeView/TreeView.tpl.js +10 -0
  300. package/ui/dialogs.js +221 -0
  301. package/ui/host-adapters.js +114 -0
  302. package/ui/index.js +660 -0
  303. package/ui/locale.js +50 -0
  304. package/ui/overlay-stack.js +89 -0
  305. package/ui/shared-styles.js +26 -0
  306. package/webmcp.js +37 -0
  307. package/xr/deep-graph.js +646 -0
  308. package/xr/emulation.js +198 -0
  309. package/xr/gesture.js +228 -0
  310. package/xr/html-canvas-renderer.js +472 -0
  311. package/xr/index.js +15 -0
  312. package/xr/layout-projection.js +1046 -0
  313. package/xr/panel-frame.js +128 -0
  314. package/xr/panel-host.js +267 -0
  315. package/xr/pointer.js +258 -0
  316. package/xr/scene-controller.js +242 -0
  317. package/xr/spatial-scene.js +212 -0
  318. package/xr/theme-bridge.js +105 -0
  319. package/xr/three-webxr-adapter.js +3439 -0
  320. package/xr/webgl-layer-renderer.js +419 -0
  321. package/xr/webxr.js +679 -0
  322. package/xr/workbench.js +516 -0
@@ -0,0 +1,731 @@
1
+ /**
2
+ * AutoLayout — Macro-Micro hierarchical graph layout
3
+ *
4
+ * Employs a 2-level strategy:
5
+ * 1. Micro-Layout: Sugiyama-style layering with per-node dimensions.
6
+ * 2. Macro-Layout: Radial Hub-and-Spoke spiraling to pack Group Bounds.
7
+ *
8
+ * Features (v2):
9
+ * - Per-node width/height via `nodeSizes` map
10
+ * - Bi-directional crossing minimization (forward + backward sweeps)
11
+ * - Per-layer X offset based on actual max node width
12
+ * - Per-node height-aware overlap resolution
13
+ * - Layout direction: 'LR' (left-right) or 'TB' (top-bottom)
14
+ *
15
+ * @module symbiote-node/canvas/AutoLayout
16
+ * @returns {Record<string, {x: number, y: number}>}
17
+ */
18
+
19
+ export function computeAutoLayout(editor, options = {}) {
20
+ let {
21
+ nodeWidth = 180,
22
+ nodeHeight = 140,
23
+ gapX = 60,
24
+ gapY = 30,
25
+ startX = 60,
26
+ startY = 60,
27
+ crossingPasses = 4,
28
+ existingPositions = null,
29
+ groups = null,
30
+ nodeSizes = null,
31
+ direction = 'LR',
32
+ } = options;
33
+
34
+
35
+ function getSize(nodeId) {
36
+ if (nodeSizes && nodeSizes[nodeId]) {
37
+
38
+ return {
39
+ w: Math.max(nodeSizes[nodeId].w, nodeWidth),
40
+ h: Math.max(nodeSizes[nodeId].h, nodeHeight),
41
+ };
42
+ }
43
+ return { w: nodeWidth, h: nodeHeight };
44
+ }
45
+
46
+ let nodes = [...editor.getNodes()];
47
+ let connections = [...editor.getConnections()];
48
+ if (nodes.length === 0) return {};
49
+
50
+ let outgoing = new Map();
51
+ let incoming = new Map();
52
+ for (const node of nodes) {
53
+ outgoing.set(node.id, []);
54
+ incoming.set(node.id, []);
55
+ }
56
+
57
+ for (const conn of connections) {
58
+ let from = conn.from;
59
+ let to = conn.to;
60
+ if (outgoing.has(from) && incoming.has(to)) {
61
+ outgoing.get(from).push(to);
62
+ incoming.get(to).push(from);
63
+ }
64
+ }
65
+
66
+
67
+ let nodeGroupId = new Map();
68
+ let groupNodes = new Map();
69
+ if (groups) {
70
+ for (const [gId, gNodes] of Object.entries(groups)) {
71
+ groupNodes.set(gId, []);
72
+ for (const n of gNodes) {
73
+ nodeGroupId.set(n, gId);
74
+ }
75
+ }
76
+ }
77
+ for (const n of nodes) {
78
+ let gId = nodeGroupId.get(n.id);
79
+ if (!gId) {
80
+ gId = '__root__';
81
+ nodeGroupId.set(n.id, gId);
82
+ }
83
+ if (!groupNodes.has(gId)) groupNodes.set(gId, []);
84
+ groupNodes.get(gId).push(n.id);
85
+ }
86
+
87
+
88
+ let groupCrossLinks = new Map();
89
+ let groupDegrees = new Map();
90
+ for (const gId of groupNodes.keys()) {
91
+ groupDegrees.set(gId, { in: 0, out: 0, total: 0 });
92
+ groupCrossLinks.set(gId, { incoming: new Map(), outgoing: new Map() });
93
+ }
94
+
95
+ for (const [fromId, targets] of outgoing.entries()) {
96
+ let gFrom = nodeGroupId.get(fromId);
97
+ for (const toId of targets) {
98
+ let gTo = nodeGroupId.get(toId);
99
+ if (gFrom !== gTo) {
100
+ groupDegrees.get(gFrom).out++;
101
+ groupDegrees.get(gFrom).total++;
102
+ groupDegrees.get(gTo).in++;
103
+ groupDegrees.get(gTo).total++;
104
+
105
+ let outMap = groupCrossLinks.get(gFrom).outgoing;
106
+ outMap.set(gTo, (outMap.get(gTo) || 0) + 1);
107
+
108
+ let inMap = groupCrossLinks.get(gTo).incoming;
109
+ inMap.set(gFrom, (inMap.get(gFrom) || 0) + 1);
110
+ }
111
+ }
112
+ }
113
+
114
+
115
+ let centerGroup = null;
116
+ let maxCross = -1;
117
+ for (const [gId, deg] of groupDegrees.entries()) {
118
+ if (deg.total > maxCross || (deg.total === maxCross && gId === './')) {
119
+ maxCross = deg.total;
120
+ centerGroup = gId;
121
+ }
122
+ }
123
+
124
+
125
+ function computeMicroLayout(gId, subNodes) {
126
+ let finalOut = new Map();
127
+ let internalDegree = new Map();
128
+ for (const n of subNodes) {
129
+ finalOut.set(n, []);
130
+ internalDegree.set(n, 0);
131
+ }
132
+
133
+
134
+ for (const n of subNodes) {
135
+ for (const child of outgoing.get(n) || []) {
136
+ if (finalOut.has(child)) {
137
+ internalDegree.set(n, internalDegree.get(n) + 1);
138
+ internalDegree.set(child, internalDegree.get(child) + 1);
139
+ }
140
+ }
141
+ }
142
+
143
+
144
+ let linkedNodes = [];
145
+ let isolatedNodes = [];
146
+ for (const n of subNodes) {
147
+ if (internalDegree.get(n) === 0) isolatedNodes.push(n);
148
+ else linkedNodes.push(n);
149
+ }
150
+
151
+ let localPositions = {};
152
+ let maxLinkedW = 0,
153
+ maxLinkedH = 0;
154
+
155
+
156
+ if (linkedNodes.length > 0) {
157
+ let state = new Map();
158
+ for (const n of linkedNodes) state.set(n, 0);
159
+
160
+ function dfs(nId) {
161
+ state.set(nId, 1);
162
+ for (const child of outgoing.get(nId) || []) {
163
+ if (!finalOut.has(child)) continue;
164
+ if (state.get(child) === 1) continue;
165
+ finalOut.get(nId).push(child);
166
+ if (state.get(child) === 0) dfs(child);
167
+ }
168
+ state.set(nId, 2);
169
+ }
170
+ for (const n of linkedNodes) {
171
+ if (state.get(n) === 0) dfs(n);
172
+ }
173
+
174
+ let layers = new Map();
175
+ for (const n of linkedNodes) layers.set(n, 0);
176
+
177
+ for (let i = 0; i < linkedNodes.length; i++) {
178
+ let changed = false;
179
+ for (const n of linkedNodes) {
180
+ let cur = layers.get(n);
181
+ for (const child of finalOut.get(n)) {
182
+ if (layers.get(child) < cur + 1) {
183
+ layers.set(child, cur + 1);
184
+ changed = true;
185
+ }
186
+ }
187
+ }
188
+ if (!changed) break;
189
+ }
190
+
191
+ let minL = Infinity,
192
+ maxL = -Infinity;
193
+ for (const n of linkedNodes) {
194
+ let l = layers.get(n);
195
+ if (l < minL) minL = l;
196
+ if (l > maxL) maxL = l;
197
+ }
198
+ if (minL === Infinity) {
199
+ minL = 0;
200
+ maxL = 0;
201
+ }
202
+
203
+ let layerArr = [];
204
+ for (let l = 0; l <= maxL - minL; l++) layerArr.push([]);
205
+ for (const n of linkedNodes) layerArr[layers.get(n) - minL].push(n);
206
+
207
+
208
+ let yPos = new Map();
209
+ for (let l = 0; l < layerArr.length; l++) {
210
+ let curY = 0;
211
+ for (let i = 0; i < layerArr[l].length; i++) {
212
+ yPos.set(layerArr[l][i], curY);
213
+ curY += getSize(layerArr[l][i]).h + gapY;
214
+ }
215
+ }
216
+
217
+
218
+ function resolveOverlaps(layer, yMap) {
219
+ if (layer.length === 0) return;
220
+
221
+ for (let i = 1; i < layer.length; i++) {
222
+ let prevId = layer[i - 1];
223
+ let curId = layer[i];
224
+ let prevBottom = yMap.get(prevId) + getSize(prevId).h + gapY;
225
+ if (yMap.get(curId) < prevBottom) {
226
+ yMap.set(curId, prevBottom);
227
+ }
228
+ }
229
+
230
+ for (let i = layer.length - 2; i >= 0; i--) {
231
+ let curId = layer[i];
232
+ let nextId = layer[i + 1];
233
+ let maxY = yMap.get(nextId) - getSize(curId).h - gapY;
234
+ if (yMap.get(curId) > maxY) {
235
+ yMap.set(curId, maxY);
236
+ }
237
+ }
238
+ }
239
+
240
+
241
+ for (let pass = 0; pass < crossingPasses; pass++) {
242
+
243
+ for (let l = 1; l < layerArr.length; l++) {
244
+ for (let i = 0; i < layerArr[l].length; i++) {
245
+ let node = layerArr[l][i];
246
+ let parents = (incoming.get(node) || []).filter((n) => layerArr[l - 1].includes(n));
247
+ if (parents.length > 0) {
248
+ parents.sort((a, b) => yPos.get(a) - yPos.get(b));
249
+ let mid = Math.floor(parents.length / 2);
250
+ let tY = yPos.get(parents[mid]);
251
+ if (parents.length % 2 === 0)
252
+ tY = (yPos.get(parents[mid - 1]) + yPos.get(parents[mid])) / 2;
253
+ yPos.set(node, tY);
254
+ }
255
+ }
256
+ resolveOverlaps(layerArr[l], yPos);
257
+ }
258
+
259
+ for (let l = layerArr.length - 2; l >= 0; l--) {
260
+ for (let i = 0; i < layerArr[l].length; i++) {
261
+ let node = layerArr[l][i];
262
+ let children = (finalOut.get(node) || []).filter((n) => layerArr[l + 1].includes(n));
263
+ if (children.length > 0) {
264
+ children.sort((a, b) => yPos.get(a) - yPos.get(b));
265
+ let mid = Math.floor(children.length / 2);
266
+ let tY = yPos.get(children[mid]);
267
+ if (children.length % 2 === 0)
268
+ tY = (yPos.get(children[mid - 1]) + yPos.get(children[mid])) / 2;
269
+ yPos.set(node, tY);
270
+ }
271
+ }
272
+ resolveOverlaps(layerArr[l], yPos);
273
+ }
274
+ }
275
+
276
+ let minLocalY = Infinity,
277
+ maxLocalY = -Infinity;
278
+ for (const [nId, y] of yPos.entries()) {
279
+ if (y < minLocalY) minLocalY = y;
280
+ let bottom = y + getSize(nId).h;
281
+ if (bottom > maxLocalY) maxLocalY = bottom;
282
+ }
283
+ if (minLocalY === Infinity) {
284
+ minLocalY = 0;
285
+ maxLocalY = 0;
286
+ }
287
+
288
+
289
+ let layerXOffsets = [];
290
+ let xAccum = 0;
291
+ for (let l = 0; l < layerArr.length; l++) {
292
+ layerXOffsets.push(xAccum);
293
+
294
+ let maxW = 0;
295
+ for (const node of layerArr[l]) {
296
+ let nw = getSize(node).w;
297
+ if (nw > maxW) maxW = nw;
298
+ }
299
+ xAccum += maxW + gapX;
300
+ }
301
+
302
+ for (let l = 0; l < layerArr.length; l++) {
303
+ for (const node of layerArr[l]) {
304
+ localPositions[node] = {
305
+ x: layerXOffsets[l],
306
+ y: yPos.get(node) - minLocalY,
307
+ };
308
+ }
309
+ }
310
+
311
+ maxLinkedW = xAccum;
312
+ maxLinkedH = maxLocalY - minLocalY + gapY;
313
+ }
314
+
315
+
316
+ let isolatedW = 0,
317
+ isolatedH = 0;
318
+ if (isolatedNodes.length > 0) {
319
+ const MAX_COLS = 6;
320
+
321
+ let colWidths = [];
322
+ let rowHeights = [];
323
+ for (let i = 0; i < isolatedNodes.length; i++) {
324
+ let col = i % MAX_COLS;
325
+ let row = Math.floor(i / MAX_COLS);
326
+ let size = getSize(isolatedNodes[i]);
327
+ if (!colWidths[col] || size.w > colWidths[col]) colWidths[col] = size.w;
328
+ if (!rowHeights[row] || size.h > rowHeights[row]) rowHeights[row] = size.h;
329
+ }
330
+
331
+
332
+ let colX = [0];
333
+ for (let c = 1; c < colWidths.length; c++) {
334
+ colX[c] = colX[c - 1] + (colWidths[c - 1] || nodeWidth) + gapX;
335
+ }
336
+
337
+ let rowY = [0];
338
+ for (let r = 1; r < rowHeights.length; r++) {
339
+ rowY[r] = rowY[r - 1] + (rowHeights[r - 1] || nodeHeight) + gapY;
340
+ }
341
+
342
+ for (let i = 0; i < isolatedNodes.length; i++) {
343
+ let node = isolatedNodes[i];
344
+ let col = i % MAX_COLS;
345
+ let row = Math.floor(i / MAX_COLS);
346
+
347
+ localPositions[node] = {
348
+ x: colX[col] || 0,
349
+ y: maxLinkedH + (rowY[row] || 0),
350
+ };
351
+ }
352
+
353
+ let lastCol = Math.min(isolatedNodes.length, MAX_COLS) - 1;
354
+ let lastRow = rowHeights.length - 1;
355
+ isolatedW = (colX[lastCol] || 0) + (colWidths[lastCol] || nodeWidth) + gapX;
356
+ isolatedH = (rowY[lastRow] || 0) + (rowHeights[lastRow] || nodeHeight) + gapY;
357
+ }
358
+
359
+ let w = Math.max(maxLinkedW, isolatedW || nodeWidth + gapX);
360
+ let h = maxLinkedH + isolatedH;
361
+
362
+ return { localPositions, bounds: { w, h } };
363
+ }
364
+
365
+
366
+ let groupResults = new Map();
367
+ for (const [gId, subNodes] of groupNodes.entries()) {
368
+ groupResults.set(gId, computeMicroLayout(gId, subNodes));
369
+ }
370
+
371
+
372
+ const M_PI = Math.PI;
373
+ let macroPositions = new Map();
374
+ let placedRects = [];
375
+
376
+ function hitTest(r1, r2, padding = 40) {
377
+ return !(
378
+ r2.x >= r1.x + r1.w + padding ||
379
+ r2.x + r2.w + padding <= r1.x ||
380
+ r2.y >= r1.y + r1.h + padding ||
381
+ r2.y + r2.h + padding <= r1.y
382
+ );
383
+ }
384
+
385
+ function placeGroup(gId) {
386
+ let res = groupResults.get(gId);
387
+ let prefAngle = 0;
388
+
389
+
390
+ let vecX = 0,
391
+ vecY = 0;
392
+ let links = groupCrossLinks.get(gId);
393
+ for (const p of placedRects) {
394
+ let pId = p.id;
395
+ let toPlaced = links.outgoing.get(pId) || 0;
396
+ let fromPlaced = links.incoming.get(pId) || 0;
397
+
398
+ let netForce = fromPlaced - toPlaced;
399
+ if (netForce !== 0) {
400
+
401
+ let cx = p.x + p.w / 2;
402
+ let cy = p.y + p.h / 2;
403
+
404
+ vecX += Math.cos(Math.atan2(cy, cx)) * netForce;
405
+ vecY += Math.sin(Math.atan2(cy, cx)) * netForce;
406
+ }
407
+ }
408
+ if (vecX !== 0 || vecY !== 0) prefAngle = Math.atan2(vecY, vecX);
409
+
410
+
411
+ let step = Math.max(20, Math.min(res.bounds.w, res.bounds.h) * 0.2);
412
+ let maxR = 6000;
413
+ let angularStep = M_PI / 12;
414
+ for (let r = 0; r < maxR; r += step) {
415
+ for (let delta = 0; delta <= M_PI; delta += angularStep) {
416
+ for (const sign of [1, -1]) {
417
+ cycleCount++;
418
+ let a = prefAngle + delta * sign;
419
+ let x = Math.round(Math.cos(a) * r);
420
+ let y = Math.round(Math.sin(a) * r);
421
+
422
+ let rect = { x, y, w: res.bounds.w, h: res.bounds.h, id: gId };
423
+ let overlap = false;
424
+ for (const p of placedRects) {
425
+ if (hitTest(rect, p)) {
426
+ overlap = true;
427
+ break;
428
+ }
429
+ }
430
+ if (!overlap) {
431
+ macroPositions.set(gId, { x, y });
432
+ placedRects.push(rect);
433
+ return;
434
+ }
435
+ if (delta === 0) break;
436
+ }
437
+ }
438
+
439
+ if (r > 500) step = Math.max(step, 60);
440
+ if (r > 1500) step = Math.max(step, 120);
441
+ }
442
+
443
+ macroPositions.set(gId, { x: placedRects.length * 300, y: placedRects.length * 300 });
444
+ placedRects.push({
445
+ x: placedRects.length * 300,
446
+ y: placedRects.length * 300,
447
+ w: res.bounds.w,
448
+ h: res.bounds.h,
449
+ id: gId,
450
+ });
451
+ }
452
+
453
+
454
+ if (centerGroup) {
455
+ macroPositions.set(centerGroup, { x: 0, y: 0 });
456
+ let cRes = groupResults.get(centerGroup);
457
+ placedRects.push({ x: 0, y: 0, w: cRes.bounds.w, h: cRes.bounds.h, id: centerGroup });
458
+ }
459
+
460
+
461
+ let remainingGroups = Array.from(groupNodes.keys()).filter((id) => id !== centerGroup);
462
+ remainingGroups.sort((a, b) => groupDegrees.get(b).total - groupDegrees.get(a).total);
463
+
464
+ for (const gId of remainingGroups) {
465
+ placeGroup(gId);
466
+ }
467
+
468
+
469
+ let finalPositions = {};
470
+ for (const [gId, res] of groupResults.entries()) {
471
+ let macro = macroPositions.get(gId);
472
+ for (const [nId, loc] of Object.entries(res.localPositions)) {
473
+ finalPositions[nId] = {
474
+ x: startX + macro.x + loc.x,
475
+ y: startY + macro.y + loc.y,
476
+ };
477
+ }
478
+ }
479
+
480
+
481
+ if (direction === 'TB') {
482
+ for (const id in finalPositions) {
483
+ let p = finalPositions[id];
484
+ let tmp = p.x;
485
+ p.x = p.y;
486
+ p.y = tmp;
487
+ }
488
+ }
489
+
490
+
491
+ if (existingPositions) {
492
+ let sumDx = 0,
493
+ sumDy = 0,
494
+ count = 0;
495
+ for (const [id, oldPos] of Object.entries(existingPositions)) {
496
+ if (finalPositions[id] && !isNaN(oldPos.x) && !isNaN(oldPos.y)) {
497
+ sumDx += oldPos.x - finalPositions[id].x;
498
+ sumDy += oldPos.y - finalPositions[id].y;
499
+ count++;
500
+ }
501
+ }
502
+ if (count > 0) {
503
+ let avgDx = sumDx / count;
504
+ let avgDy = sumDy / count;
505
+ for (const id in finalPositions) {
506
+ finalPositions[id].x += avgDx;
507
+ finalPositions[id].y += avgDy;
508
+ }
509
+
510
+
511
+ let ids = Object.keys(finalPositions);
512
+ for (let pass = 0; pass < 3; pass++) {
513
+ let overlaps = false;
514
+ for (let i = 0; i < ids.length; i++) {
515
+ for (let j = i + 1; j < ids.length; j++) {
516
+ let p1 = finalPositions[ids[i]];
517
+ let p2 = finalPositions[ids[j]];
518
+ let s1 = getSize(ids[i]);
519
+ let s2 = getSize(ids[j]);
520
+ let dx = p1.x - p2.x,
521
+ dy = p1.y - p2.y;
522
+ let absDx = Math.abs(dx),
523
+ absDy = Math.abs(dy);
524
+
525
+
526
+ let overlapX = (s1.w + s2.w) / 2 + gapX * 0.3;
527
+ let overlapY = (s1.h + s2.h) / 2 + gapY * 0.3;
528
+
529
+ if (absDx < overlapX && absDy < overlapY) {
530
+ overlaps = true;
531
+
532
+ let penX = overlapX - absDx;
533
+ let penY = overlapY - absDy;
534
+
535
+ if (penX < penY) {
536
+
537
+ let fix = penX / 2 + 1;
538
+ p1.x += dx >= 0 ? fix : -fix;
539
+ p2.x += dx >= 0 ? -fix : fix;
540
+ } else {
541
+
542
+ let fix = penY / 2 + 1;
543
+ p1.y += dy >= 0 ? fix : -fix;
544
+ p2.y += dy >= 0 ? -fix : fix;
545
+ }
546
+ }
547
+ }
548
+ }
549
+ if (!overlaps) break;
550
+ }
551
+ }
552
+ }
553
+
554
+ for (const k in finalPositions) {
555
+ if (isNaN(finalPositions[k].x) || isNaN(finalPositions[k].y)) {
556
+ console.error('[AutoLayout] NaN intercepted for node:', k);
557
+ finalPositions[k] = { x: 0, y: 0 };
558
+ }
559
+ }
560
+
561
+ return finalPositions;
562
+ }
563
+
564
+ /**
565
+ * Tree Layout — positions nodes like a directory tree / file explorer.
566
+ *
567
+ * Algorithm: Compact tree (Reingold-Tilford inspired) with per-node dimensions.
568
+ * - Builds a tree from either: (a) dirPaths parent-child hierarchy, or (b) DAG edges
569
+ * - Positions root at top-left, children below with indentation
570
+ * - Sibling subtrees are packed tightly without overlap
571
+ * - Supports per-node dimensions via `nodeSizes`
572
+ *
573
+ * @param {NodeEditor} editor - The node editor
574
+ * @param {object} options
575
+ * @param {Object<string, { w: number, h: number }>} [options.nodeSizes] - Per-node dimensions
576
+ * @param {number} [options.gapX=40] - Horizontal indentation per depth level
577
+ * @param {number} [options.gapY=20] - Vertical gap between sibling nodes
578
+ * @param {number} [options.nodeWidth=250] - Default node width
579
+ * @param {number} [options.nodeHeight=100] - Default node height
580
+ * @param {number} [options.startX=60] - Starting X
581
+ * @param {number} [options.startY=60] - Starting Y
582
+ * @param {Object<string, string>} [options.dirPaths] - { nodeId: dirPath } — enables directory hierarchy detection
583
+ * @returns {Object<string, { x: number, y: number }>}
584
+ */
585
+ export function computeTreeLayout(editor, options = {}) {
586
+
587
+ let {
588
+ gapX = 40,
589
+ gapY = 20,
590
+ nodeWidth = 250,
591
+ nodeHeight = 100,
592
+ startX = 60,
593
+ startY = 60,
594
+ nodeSizes = null,
595
+ dirPaths = null,
596
+ } = options;
597
+
598
+ function getSize(nodeId) {
599
+ if (nodeSizes && nodeSizes[nodeId]) {
600
+
601
+ return {
602
+ w: Math.max(nodeSizes[nodeId].w, nodeWidth),
603
+ h: Math.max(nodeSizes[nodeId].h, nodeHeight),
604
+ };
605
+ }
606
+ return { w: nodeWidth, h: nodeHeight };
607
+ }
608
+
609
+ let nodes = [...editor.getNodes()];
610
+ let connections = [...editor.getConnections()];
611
+ if (nodes.length === 0) return {};
612
+
613
+
614
+ let children = new Map();
615
+ let parent = new Map();
616
+ let nodeIds = new Set(nodes.map((n) => n.id));
617
+
618
+ for (const id of nodeIds) {
619
+ children.set(id, []);
620
+ }
621
+
622
+ if (dirPaths) {
623
+
624
+
625
+ let pathToId = new Map();
626
+ for (const [nodeId, path] of Object.entries(dirPaths)) {
627
+ pathToId.set(path, nodeId);
628
+ }
629
+
630
+
631
+ let sortedPaths = [...pathToId.keys()].sort((a, b) => {
632
+ let depthA = a.split('/').filter(Boolean).length;
633
+ let depthB = b.split('/').filter(Boolean).length;
634
+ return depthA - depthB || a.localeCompare(b);
635
+ });
636
+
637
+ for (const path of sortedPaths) {
638
+ let nodeId = pathToId.get(path);
639
+
640
+
641
+ let segments = path.replace(/\/$/, '').split('/');
642
+ segments.pop();
643
+
644
+ let foundParent = false;
645
+
646
+ while (segments.length > 0) {
647
+ let parentPath = segments.join('/') + '/';
648
+ let parentId = pathToId.get(parentPath);
649
+ if (parentId && parentId !== nodeId) {
650
+ parent.set(nodeId, parentId);
651
+ children.get(parentId).push(nodeId);
652
+ foundParent = true;
653
+ break;
654
+ }
655
+ segments.pop();
656
+ }
657
+
658
+ if (!foundParent) {
659
+ let rootId = pathToId.get('./');
660
+ if (rootId && rootId !== nodeId) {
661
+ parent.set(nodeId, rootId);
662
+ children.get(rootId).push(nodeId);
663
+ }
664
+ }
665
+ }
666
+ } else {
667
+
668
+
669
+ for (const conn of connections) {
670
+ let from = conn.from;
671
+ let to = conn.to;
672
+ if (nodeIds.has(from) && nodeIds.has(to) && !parent.has(to)) {
673
+ parent.set(to, from);
674
+ children.get(from).push(to);
675
+ }
676
+ }
677
+ }
678
+
679
+
680
+ let roots = [];
681
+ for (const id of nodeIds) {
682
+ if (!parent.has(id)) roots.push(id);
683
+ }
684
+
685
+
686
+ let nodeMap = new Map(nodes.map((n) => [n.id, n]));
687
+ let dirIdSet = dirPaths ? new Set(Object.keys(dirPaths)) : new Set();
688
+ roots.sort((a, b) => {
689
+ let aIsDir = dirIdSet.has(a) || nodeMap.get(a)?._isSubgraph;
690
+ let bIsDir = dirIdSet.has(b) || nodeMap.get(b)?._isSubgraph;
691
+ if (aIsDir && !bIsDir) return -1;
692
+ if (!aIsDir && bIsDir) return 1;
693
+ let la = nodeMap.get(a)?.label || '';
694
+ let lb = nodeMap.get(b)?.label || '';
695
+ return la.localeCompare(lb);
696
+ });
697
+
698
+
699
+ for (const [, kids] of children) {
700
+ kids.sort((a, b) => {
701
+ let la = nodeMap.get(a)?.label || '';
702
+ let lb = nodeMap.get(b)?.label || '';
703
+ return la.localeCompare(lb);
704
+ });
705
+ }
706
+
707
+
708
+ let positions = {};
709
+ let cursorY = startY;
710
+
711
+ function layoutSubtree(nodeId, depth) {
712
+ let size = getSize(nodeId);
713
+ let x = startX + depth * (gapX + nodeWidth);
714
+ let y = cursorY;
715
+
716
+ positions[nodeId] = { x, y };
717
+ cursorY += size.h + gapY;
718
+
719
+
720
+ let kids = children.get(nodeId) || [];
721
+ for (const childId of kids) {
722
+ layoutSubtree(childId, depth + 1);
723
+ }
724
+ }
725
+
726
+ for (const rootId of roots) {
727
+ layoutSubtree(rootId, 0);
728
+ }
729
+
730
+ return positions;
731
+ }