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,373 @@
1
+ /**
2
+ * @fileoverview BSP (Binary Space Partitioning) Layout Tree
3
+ * Implements Blender-style area splitting/joining mechanics.
4
+ */
5
+
6
+ /**
7
+ * @typedef {'horizontal' | 'vertical'} SplitDirection
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} PanelNode
12
+ * @property {string} id - Unique node ID
13
+ * @property {'panel'} type - Node type
14
+ * @property {string} panelType - Panel content type (e.g., 'viewport', 'timeline')
15
+ * @property {boolean} [collapsed] - Whether panel is collapsed
16
+ * @property {Object} [panelState] - Panel-specific state
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} SplitNode
21
+ * @property {string} id - Unique node ID
22
+ * @property {'split'} type - Node type
23
+ * @property {SplitDirection} direction - Split direction
24
+ * @property {number} ratio - Split ratio (0-1), size of first child
25
+ * @property {LayoutNode} first - First child node
26
+ * @property {LayoutNode} second - Second child node
27
+ */
28
+
29
+ /**
30
+ * @typedef {PanelNode | SplitNode} LayoutNode
31
+ */
32
+
33
+ let idCounter = 0
34
+
35
+ /**
36
+ * Generate unique node ID
37
+ * @returns {string}
38
+ */
39
+ export function generateId() {
40
+ return `node_${++idCounter}_${Date.now().toString(36)}`
41
+ }
42
+
43
+ /**
44
+ * Create a panel node
45
+ * @param {string} panelType - Panel content type
46
+ * @param {Object} [panelState] - Initial panel state
47
+ * @returns {PanelNode}
48
+ */
49
+ export function createPanel(panelType, panelState = {}) {
50
+ return {
51
+ id: generateId(),
52
+ type: 'panel',
53
+ panelType,
54
+ panelState,
55
+ collapsed: false,
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Create a split node
61
+ * @param {SplitDirection} direction - Split direction
62
+ * @param {LayoutNode} first - First child
63
+ * @param {LayoutNode} second - Second child
64
+ * @param {number} [ratio=0.5] - Split ratio
65
+ * @returns {SplitNode}
66
+ */
67
+ export function createSplit(direction, first, second, ratio = 0.5) {
68
+ return {
69
+ id: generateId(),
70
+ type: 'split',
71
+ direction,
72
+ ratio,
73
+ first,
74
+ second,
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Find a node by ID in the tree
80
+ * @param {LayoutNode} root - Root node
81
+ * @param {string} id - Node ID to find
82
+ * @returns {LayoutNode | null}
83
+ */
84
+ export function findNode(root, id) {
85
+ if (root.id === id) return root
86
+ if (root.type === 'split') {
87
+ return findNode(root.first, id) || findNode(root.second, id)
88
+ }
89
+ return null
90
+ }
91
+
92
+ /**
93
+ * Find parent of a node
94
+ * @param {LayoutNode} root - Root node
95
+ * @param {string} id - Child node ID
96
+ * @returns {{ parent: SplitNode, which: 'first' | 'second' } | null}
97
+ */
98
+ export function findParent(root, id) {
99
+ if (root.type !== 'split') return null
100
+
101
+ if (root.first.id === id) return { parent: root, which: 'first' }
102
+ if (root.second.id === id) return { parent: root, which: 'second' }
103
+
104
+ return findParent(root.first, id) || findParent(root.second, id)
105
+ }
106
+
107
+ /**
108
+ * Split a panel into two
109
+ * @param {LayoutNode} root - Root node
110
+ * @param {string} panelId - Panel ID to split
111
+ * @param {SplitDirection} direction - Split direction
112
+ * @param {number} [ratio=0.5] - Split ratio
113
+ * @param {string} [newPanelType] - Type for new panel (defaults to same as original)
114
+ * @returns {LayoutNode} - New root node
115
+ */
116
+ export function splitPanel(root, panelId, direction, ratio = 0.5, newPanelType) {
117
+ let node = findNode(root, panelId)
118
+ if (!node || node.type !== 'panel') {
119
+ return root
120
+ }
121
+
122
+ let newPanel = createPanel(newPanelType || node.panelType)
123
+ let splitNode = createSplit(direction, node, newPanel, ratio)
124
+
125
+
126
+ if (root.id === panelId) {
127
+ return splitNode
128
+ }
129
+
130
+
131
+ let parentInfo = findParent(root, panelId)
132
+ if (parentInfo) {
133
+ parentInfo.parent[parentInfo.which] = splitNode
134
+ }
135
+
136
+ return root
137
+ }
138
+
139
+ /**
140
+ * Join two panels (remove one panel and its parent split)
141
+ * @param {LayoutNode} root - Root node
142
+ * @param {string} panelToRemove - Panel ID to remove
143
+ * @returns {LayoutNode} - New root node
144
+ */
145
+ export function joinPanels(root, panelToRemove) {
146
+ let parentInfo = findParent(root, panelToRemove)
147
+ if (!parentInfo) {
148
+ return root
149
+ }
150
+
151
+ let { parent, which } = parentInfo
152
+ let survivor = which === 'first' ? parent.second : parent.first
153
+
154
+
155
+ let grandparentInfo = findParent(root, parent.id)
156
+ if (!grandparentInfo) {
157
+ return survivor
158
+ }
159
+
160
+
161
+ grandparentInfo.parent[grandparentInfo.which] = survivor
162
+ return root
163
+ }
164
+
165
+ /**
166
+ * Update split ratio
167
+ * @param {LayoutNode} root - Root node
168
+ * @param {string} splitId - Split node ID
169
+ * @param {number} ratio - New ratio (0-1)
170
+ * @returns {LayoutNode} - Same root (mutated)
171
+ */
172
+ export function resizeSplit(root, splitId, ratio) {
173
+ let node = findNode(root, splitId)
174
+ if (!node || node.type !== 'split') {
175
+ return root
176
+ }
177
+
178
+ node.ratio = Math.max(0.1, Math.min(0.9, ratio))
179
+ return root
180
+ }
181
+
182
+ /**
183
+ * Serialize layout to JSON string
184
+ * @param {LayoutNode} root - Root node
185
+ * @returns {string}
186
+ */
187
+ export function serialize(root) {
188
+ return JSON.stringify(root)
189
+ }
190
+
191
+ /**
192
+ * Deserialize layout from JSON string
193
+ * @param {string} json - JSON string
194
+ * @returns {LayoutNode}
195
+ */
196
+ export function deserialize(json) {
197
+ return JSON.parse(json)
198
+ }
199
+
200
+ /**
201
+ * Clone a layout tree (deep copy)
202
+ * @param {LayoutNode} root - Root node
203
+ * @returns {LayoutNode}
204
+ */
205
+ export function clone(root) {
206
+ return deserialize(serialize(root))
207
+ }
208
+
209
+ /**
210
+ * Get all panel nodes in tree
211
+ * @param {LayoutNode} root - Root node
212
+ * @returns {PanelNode[]}
213
+ */
214
+ export function getAllPanels(root) {
215
+ return collectPanels(root)
216
+ }
217
+
218
+ export function isPanelNode(node) {
219
+ return !!node && node.type === 'panel'
220
+ }
221
+
222
+ export function isSplitNode(node) {
223
+ return !!node && node.type === 'split'
224
+ }
225
+
226
+ /**
227
+ * Collect panel nodes from a layout tree.
228
+ * @param {LayoutNode | Object | null} root
229
+ * @param {Object} [options]
230
+ * @param {boolean} [options.includeGlobal=true] Include panels marked as global.
231
+ * @returns {PanelNode[]}
232
+ */
233
+ export function collectPanels(root, options = {}) {
234
+ let { includeGlobal = true } = options
235
+ let panels = []
236
+
237
+ function walk(node) {
238
+ if (!node) return
239
+ if (Array.isArray(node)) {
240
+ node.forEach(walk)
241
+ return
242
+ }
243
+ if (isPanelNode(node)) {
244
+ if (includeGlobal || !node.global) panels.push(node)
245
+ return
246
+ }
247
+ if (node.first) walk(node.first)
248
+ if (node.second) walk(node.second)
249
+ }
250
+
251
+ walk(root)
252
+ if (includeGlobal && root && typeof root === 'object' && !Array.isArray(root)) {
253
+ walk(root.global)
254
+ walk(root.globals)
255
+ walk(root.globalPanels)
256
+ }
257
+ return panels
258
+ }
259
+
260
+ /**
261
+ * Collect panel type ids from a layout tree.
262
+ * @param {LayoutNode | Object | null} root
263
+ * @param {Object} [options]
264
+ * @param {boolean} [options.includeGlobal=true] Include panels marked as global.
265
+ * @returns {string[]}
266
+ */
267
+ export function collectPanelTypes(root, options = {}) {
268
+ return collectPanels(root, options)
269
+ .map((panel) => panel.panelType)
270
+ .filter(Boolean)
271
+ }
272
+
273
+ /**
274
+ * Return the first non-global panel type from a layout tree.
275
+ * @param {LayoutNode | Object | null} root
276
+ * @returns {string | null}
277
+ */
278
+ export function getPrimaryPanelType(root) {
279
+ return collectPanels(root, { includeGlobal: false })[0]?.panelType || null
280
+ }
281
+
282
+ /**
283
+ * Check that every requested panel type exists in a layout tree.
284
+ * @param {LayoutNode | Object | null} root
285
+ * @param {Iterable<string>} requiredPanelTypes
286
+ * @param {Object} [options]
287
+ * @returns {boolean}
288
+ */
289
+ export function hasEveryPanelType(root, requiredPanelTypes, options = {}) {
290
+ let available = new Set(collectPanelTypes(root, options))
291
+ for (let panelType of requiredPanelTypes || []) {
292
+ if (!available.has(panelType)) return false
293
+ }
294
+ return true
295
+ }
296
+
297
+ /**
298
+ * Check whether any of the requested panel types exists in a layout tree.
299
+ * @param {LayoutNode | Object | null} root
300
+ * @param {Iterable<string>} panelTypes
301
+ * @param {Object} [options]
302
+ * @returns {boolean}
303
+ */
304
+ export function hasAnyPanelType(root, panelTypes, options = {}) {
305
+ let available = new Set(collectPanelTypes(root, options))
306
+ for (let panelType of panelTypes || []) {
307
+ if (available.has(panelType)) return true
308
+ }
309
+ return false
310
+ }
311
+
312
+ /**
313
+ * Build sidebar submenu descriptors from panels in a layout tree.
314
+ * @param {LayoutNode | Object | null} root
315
+ * @param {object} panelDefinitions
316
+ * @param {Object} [options]
317
+ * @param {boolean} [options.includeGlobal=false] Include panels marked as global.
318
+ * @param {number} [options.minPanels=2] Minimum count required before returning items.
319
+ * @returns {{title: string, icon: string, panelId: string, isMaster: boolean, panelType: string}[]}
320
+ */
321
+ export function createSidebarSubPanels(root, panelDefinitions = {}, options = {}) {
322
+ let { includeGlobal = false, minPanels = 2 } = options
323
+ let panels = collectPanels(root, { includeGlobal })
324
+ if (panels.length < minPanels) return []
325
+ return panels.map((panel, index) => {
326
+ let panelType = panel.panelType || 'panel'
327
+ let config = panelDefinitions[panelType] || {}
328
+ return {
329
+ title: config.title || panelType,
330
+ icon: config.icon || 'dashboard',
331
+ panelId: panel.id || `${panelType}-${index}`,
332
+ isMaster: index === 0,
333
+ panelType,
334
+ }
335
+ })
336
+ }
337
+
338
+ /**
339
+ * Update a node's properties by ID
340
+ * @param {LayoutNode} root - Root node
341
+ * @param {string} nodeId - Node ID to update
342
+ * @param {Object} updates - Properties to update
343
+ * @returns {boolean} - True if node was found and updated
344
+ */
345
+ export function updateNode(root, nodeId, updates) {
346
+ let node = findNode(root, nodeId)
347
+ if (!node) return false
348
+ Object.assign(node, updates)
349
+ return true
350
+ }
351
+
352
+ /**
353
+ * Validate that a layout tree matches validation rules
354
+ * @param {LayoutNode} root
355
+ * @param {Object} config
356
+ * @param {Iterable<string>} [config.disallowedPanelTypes]
357
+ * @param {Iterable<string>} [config.requiredPanelTypes]
358
+ * @param {string} [config.expectedPrimary]
359
+ * @returns {boolean}
360
+ */
361
+ export function matchesSection(root, config = {}) {
362
+ if (!root) return false
363
+ if (config.disallowedPanelTypes) {
364
+ if (hasAnyPanelType(root, config.disallowedPanelTypes)) return false
365
+ }
366
+ if (config.requiredPanelTypes) {
367
+ if (!hasEveryPanelType(root, config.requiredPanelTypes)) return false
368
+ }
369
+ if (config.expectedPrimary) {
370
+ return collectPanelTypes(root, { includeGlobal: false }).includes(config.expectedPrimary)
371
+ }
372
+ return true
373
+ }
@@ -0,0 +1,43 @@
1
+ import { css } from '@symbiotejs/symbiote';
2
+
3
+ export let styles = css`
4
+ panel-menu {
5
+ position: fixed;
6
+ z-index: var(--sn-panel-menu-z, var(--sn-overlay-z-base, 20000));
7
+ pointer-events: none;
8
+
9
+ .menu-container {
10
+ pointer-events: auto;
11
+ background: var(--sn-ctx-bg);
12
+ border: 1px solid var(--sn-ctx-border);
13
+ border-radius: 6px;
14
+ box-shadow: 0 4px 12px var(--sn-shadow-color);
15
+ min-width: 160px;
16
+ padding: 4px 0;
17
+ }
18
+
19
+ .menu-item {
20
+ display: flex;
21
+ align-items: center;
22
+ gap: 8px;
23
+ padding: 8px 12px;
24
+ cursor: pointer;
25
+ color: var(--sn-text);
26
+ font-size: 0.85rem;
27
+ transition: background 0.1s;
28
+
29
+ &:hover {
30
+ background: var(--sn-node-hover);
31
+ }
32
+
33
+ &[active] {
34
+ color: var(--sn-node-selected);
35
+ }
36
+
37
+ .material-symbols-outlined {
38
+ font-size: 18px;
39
+ opacity: 0.8;
40
+ }
41
+ }
42
+ }
43
+ `;
@@ -0,0 +1,95 @@
1
+ /**
2
+ * @fileoverview PanelMenu - Dropdown menu for panel type selection
3
+ */
4
+
5
+ import Symbiote from '@symbiotejs/symbiote';
6
+ import { ensureMaterialSymbols } from '../../icons/MaterialSymbols.js';
7
+ import { bringOverlayToFront } from '../../ui/overlay-stack.js';
8
+ import { template } from './PanelMenu.tpl.js';
9
+ import { styles } from './PanelMenu.css.js';
10
+
11
+ export class PanelMenu extends Symbiote {
12
+ static isoMode = true;
13
+
14
+ init$ = {
15
+ visible: false,
16
+ panelId: '',
17
+ items: [],
18
+ currentType: '',
19
+
20
+ onItemClick: (e) => {
21
+ let type = e.target.closest('[data-type]')?.dataset.type;
22
+ if (type) {
23
+ this.dispatchEvent(
24
+ new CustomEvent('panel-type-select', {
25
+ bubbles: true,
26
+ composed: true,
27
+ detail: { panelId: this.$.panelId, type },
28
+ })
29
+ );
30
+ this.hide();
31
+ }
32
+ },
33
+ };
34
+
35
+ /**
36
+ * Show menu at position
37
+ * @param {number} x
38
+ * @param {number} y
39
+ * @param {string} panelId
40
+ * @param {string} currentType
41
+ * @param {Array<{type: string, title: string, icon: string}>} items
42
+ */
43
+ show(x, y, panelId, currentType, items) {
44
+ ensureMaterialSymbols(items.map((item) => item.icon || 'dashboard'));
45
+ this.$.panelId = panelId;
46
+ this.$.currentType = currentType;
47
+ ensureMaterialSymbols(items.map((item) => item.icon || 'dashboard'));
48
+
49
+
50
+ this.$.items = items.map((item) => ({
51
+ ...item,
52
+ icon: item.icon || 'dashboard',
53
+ title: item.title || item.type,
54
+ isActive: item.type === currentType,
55
+ }));
56
+
57
+ this.style.left = `${x}px`;
58
+ this.style.top = `${y}px`;
59
+ bringOverlayToFront(this);
60
+ this.$.visible = true;
61
+
62
+
63
+ if (typeof setTimeout !== 'undefined') {
64
+ setTimeout(() => {
65
+ if (typeof document !== 'undefined') {
66
+ document.addEventListener('pointerdown', this._onOutsideClick);
67
+ }
68
+ }, 0);
69
+ }
70
+ }
71
+
72
+ hide() {
73
+ this.$.visible = false;
74
+ if (typeof document !== 'undefined') {
75
+ document.removeEventListener('pointerdown', this._onOutsideClick);
76
+ }
77
+ }
78
+
79
+ _onOutsideClick = (e) => {
80
+ if (!this.contains(e.target)) {
81
+ this.hide();
82
+ }
83
+ };
84
+
85
+ disconnectedCallback() {
86
+ if (typeof document !== 'undefined') {
87
+ document.removeEventListener('pointerdown', this._onOutsideClick);
88
+ }
89
+ }
90
+ }
91
+
92
+ PanelMenu.template = template;
93
+ PanelMenu.rootStyles = styles;
94
+
95
+ PanelMenu.reg('panel-menu');
@@ -0,0 +1,17 @@
1
+ import { html } from '@symbiotejs/symbiote';
2
+
3
+ export let template = html`
4
+ <div class="menu-container" ${{ '@hidden': '!visible' }}>
5
+ <div class="menu-items" itemize="items">
6
+ <template>
7
+ <div
8
+ class="menu-item"
9
+ ${{ onclick: '^onItemClick', '@data-type': 'type', '@active': 'isActive' }}
10
+ >
11
+ <span class="material-symbols-outlined">{{icon}}</span>
12
+ <span>{{title}}</span>
13
+ </div>
14
+ </template>
15
+ </div>
16
+ </div>
17
+ `;
@@ -0,0 +1,188 @@
1
+ export default /*css*/ `
2
+ :host,
3
+ project-tabs {
4
+ display: block;
5
+ height: var(--sn-tabs-height);
6
+ background: var(--sn-tabs-bg);
7
+ flex-shrink: 0;
8
+ user-select: none;
9
+ position: relative;
10
+ }
11
+
12
+ :host::after,
13
+ project-tabs::after {
14
+ content: '';
15
+ position: absolute;
16
+ bottom: 0;
17
+ left: 0;
18
+ right: 0;
19
+ height: 1px;
20
+ background: var(--sn-tabs-border);
21
+ z-index: 1;
22
+ }
23
+
24
+ .tab-bar {
25
+ display: flex;
26
+ align-items: flex-end;
27
+ height: 100%;
28
+ padding: 0 12px;
29
+ overflow-x: auto;
30
+ scrollbar-width: none;
31
+ position: relative;
32
+ z-index: 2;
33
+ }
34
+
35
+ .tab-bar::-webkit-scrollbar {
36
+ display: none;
37
+ }
38
+
39
+ .tab,
40
+ project-tab-item {
41
+ box-sizing: border-box;
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 6px;
45
+ padding: 0 10px 0 12px;
46
+ height: var(--sn-tabs-item-height);
47
+ border: 1px solid transparent;
48
+ border-bottom: none;
49
+ background: transparent;
50
+ color: var(--sn-text-dim);
51
+ cursor: pointer;
52
+ font-size: 12px;
53
+ font-family: inherit;
54
+ white-space: nowrap;
55
+ transition: background 0.15s, color 0.15s;
56
+ position: relative;
57
+ border-radius: var(--sn-tabs-radius);
58
+ margin: 0 2px;
59
+ }
60
+
61
+ .tab .material-symbols-outlined,
62
+ project-tab-item .material-symbols-outlined {
63
+ font-size: 15px;
64
+ color: var(--tab-accent, var(--sn-tabs-accent));
65
+ }
66
+
67
+ .tab:hover,
68
+ project-tab-item:hover {
69
+ background: var(--sn-tabs-hover-bg);
70
+ color: var(--sn-text);
71
+ }
72
+
73
+ .tab[active],
74
+ project-tab-item[active] {
75
+ background: var(--sn-tabs-active-bg);
76
+ color: var(--sn-text);
77
+ }
78
+
79
+ .tab[active]::before,
80
+ project-tab-item[active]::before {
81
+ content: '';
82
+ position: absolute;
83
+ bottom: 0;
84
+ left: calc(-1 * var(--sn-tabs-corner-size));
85
+ width: var(--sn-tabs-corner-size);
86
+ height: var(--sn-tabs-corner-size);
87
+ pointer-events: none;
88
+ background: radial-gradient(circle at 0 0, transparent var(--sn-tabs-corner-cut), var(--sn-tabs-active-bg) var(--sn-tabs-corner-size));
89
+ }
90
+
91
+ .tab[active]::after,
92
+ project-tab-item[active]::after {
93
+ content: '';
94
+ position: absolute;
95
+ bottom: 0;
96
+ right: calc(-1 * var(--sn-tabs-corner-size));
97
+ width: var(--sn-tabs-corner-size);
98
+ height: var(--sn-tabs-corner-size);
99
+ pointer-events: none;
100
+ background: radial-gradient(circle at 100% 0, transparent var(--sn-tabs-corner-cut), var(--sn-tabs-active-bg) var(--sn-tabs-corner-size));
101
+ }
102
+
103
+ .tab:not([active]):not(:hover)::after,
104
+ project-tab-item:not([active]):not(:hover)::after {
105
+ content: '';
106
+ position: absolute;
107
+ right: -2px;
108
+ top: 25%;
109
+ height: 50%;
110
+ width: 1px;
111
+ background: var(--sn-tabs-divider);
112
+ }
113
+
114
+ .tab:not([active]):not(:hover):has(+ .tab[active])::after,
115
+ .tab:not([active]):not(:hover):has(+ project-tab-item[active])::after,
116
+ .tab:not([active]):not(:hover):has(+ .tab:hover)::after,
117
+ .tab:not([active]):not(:hover):has(+ project-tab-item:hover)::after,
118
+ project-tab-item:not([active]):not(:hover):has(+ project-tab-item[active])::after,
119
+ project-tab-item:not([active]):not(:hover):has(+ project-tab-item:hover)::after,
120
+ .tab:not([active]):not(:hover):has(+ div > project-tab-item:first-child[active])::after,
121
+ .tab:not([active]):not(:hover):has(+ div > project-tab-item:first-child:hover)::after,
122
+ .tab:not([active]):not(:hover):last-child::after,
123
+ project-tab-item:not([active]):not(:hover):last-child::after,
124
+ .tab:not([active]):not(:hover):has(+ div:empty)::after {
125
+ content: none;
126
+ }
127
+
128
+ .tab-close {
129
+ display: flex;
130
+ align-items: center;
131
+ justify-content: center;
132
+ width: 16px;
133
+ height: 16px;
134
+ border-radius: 50%;
135
+ background: transparent;
136
+ border: none;
137
+ color: var(--sn-text-dim);
138
+ cursor: pointer;
139
+ font-size: 14px;
140
+ padding: 0;
141
+ line-height: 1;
142
+ opacity: 0;
143
+ transition: opacity 0.15s, background 0.15s, color 0.15s;
144
+ }
145
+
146
+ .tab:hover .tab-close,
147
+ .tab[active] .tab-close,
148
+ project-tab-item:hover .tab-close,
149
+ project-tab-item[active] .tab-close {
150
+ opacity: 1;
151
+ }
152
+
153
+ .tab-close:hover {
154
+ background: color-mix(in srgb, var(--sn-text) 20%, transparent);
155
+ color: var(--sn-text);
156
+ }
157
+
158
+ .tab-add {
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ width: 28px;
163
+ height: 28px;
164
+ border-radius: 50%;
165
+ border: none;
166
+ background: transparent;
167
+ color: var(--sn-text-dim);
168
+ cursor: pointer;
169
+ font-size: 18px;
170
+ transition: background 0.15s, color 0.15s;
171
+ margin-left: 4px;
172
+ margin-bottom: 2px;
173
+ }
174
+
175
+ .tab-add:hover {
176
+ background: color-mix(in srgb, var(--sn-text) 10%, transparent);
177
+ color: var(--sn-text);
178
+ }
179
+
180
+ .tab-filler {
181
+ flex: 1;
182
+ }
183
+
184
+ .tab-items {
185
+ display: flex;
186
+ align-items: stretch;
187
+ }
188
+ `;