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,84 @@
1
+ /**
2
+ * RectShape — standard rectangular node card
3
+ *
4
+ * Sockets arranged vertically on left (inputs) and right (outputs).
5
+ * Default shape for most nodes.
6
+ *
7
+ * @module symbiote-node/shapes/RectShape
8
+ */
9
+
10
+ import { NodeShape } from './NodeShape.js';
11
+
12
+ export class RectShape extends NodeShape {
13
+ name = 'rect';
14
+
15
+ /** @type {number} */
16
+ #headerHeight;
17
+
18
+ /**
19
+ * @param {object} [config]
20
+ * @param {number} [config.headerHeight=36] - Height of header area
21
+ */
22
+ constructor(config = {}) {
23
+ super();
24
+ this.#headerHeight = config.headerHeight || 36;
25
+ }
26
+
27
+ getSocketPosition(side, index, total, { width, height }) {
28
+ let bodyHeight = height - this.#headerHeight;
29
+ let spacing = bodyHeight / (total + 1);
30
+ let y = this.#headerHeight + spacing * (index + 1);
31
+
32
+ return {
33
+ x: side === 'input' ? 0 : width,
34
+ y,
35
+ angle: side === 'input' ? 180 : 0,
36
+ };
37
+ }
38
+
39
+ /**
40
+ * Get pin position on a specific side of the rectangle.
41
+ * Required for PCB path style — without this, ConnectionRenderer
42
+ * skips rect nodes entirely.
43
+ *
44
+ * @param {'top'|'right'|'bottom'|'left'} side
45
+ * @param {number} t - position along the side (0..1), 0.5 = center
46
+ * @param {{ width: number, height: number }} size
47
+ * @returns {{ x: number, y: number, angle: number }}
48
+ */
49
+ getSidePosition(side, t, size) {
50
+ const NORMALS = { top: -90, right: 0, bottom: 90, left: 180 };
51
+ const MARGIN = 0.2;
52
+ let effectiveT = MARGIN + t * (1 - 2 * MARGIN);
53
+
54
+ let x, y;
55
+ switch (side) {
56
+ case 'top':
57
+ x = size.width * effectiveT;
58
+ y = 0;
59
+ break;
60
+ case 'right':
61
+ x = size.width;
62
+ y = size.height * effectiveT;
63
+ break;
64
+ case 'bottom':
65
+ x = size.width * effectiveT;
66
+ y = size.height;
67
+ break;
68
+ case 'left':
69
+ x = 0;
70
+ y = size.height * effectiveT;
71
+ break;
72
+ }
73
+
74
+ return { x, y, angle: NORMALS[side] };
75
+ }
76
+
77
+ getBorderRadius() {
78
+ return 'var(--sn-node-radius)';
79
+ }
80
+
81
+ getMinSize() {
82
+ return { minWidth: 180, minHeight: 60 };
83
+ }
84
+ }
@@ -0,0 +1,525 @@
1
+ /**
2
+ * SVGShape — universal shape from any SVG path
3
+ *
4
+ * Uses SVGPathElement.getPointAtLength() for dynamic connector placement.
5
+ * Any SVG icon path can become a node shape — the outer contour defines
6
+ * the visual fill and connector positions are computed along the perimeter.
7
+ *
8
+ * Port placement strategy:
9
+ * - Inputs placed on left portion of path perimeter
10
+ * - Outputs placed on right portion of path perimeter
11
+ * - Connector angle = normal from center → edge point
12
+ * - Aspect ratio of original SVG is preserved (xMidYMid meet)
13
+ *
14
+ * @module symbiote-node/shapes/SVGShape
15
+ */
16
+
17
+ import { NodeShape } from './NodeShape.js';
18
+
19
+ /**
20
+ * Offscreen SVG namespace for path computation
21
+ * @type {SVGSVGElement|null}
22
+ */
23
+ let _offscreenSVG = null;
24
+
25
+ /**
26
+ * Get or create an offscreen SVG element for path calculations
27
+ * @returns {SVGSVGElement}
28
+ */
29
+ function getOffscreenSVG() {
30
+ if (_offscreenSVG) return _offscreenSVG;
31
+ if (typeof document === 'undefined') return null;
32
+ _offscreenSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
33
+ _offscreenSVG.style.position = 'absolute';
34
+ _offscreenSVG.style.width = '0';
35
+ _offscreenSVG.style.height = '0';
36
+ _offscreenSVG.style.overflow = 'hidden';
37
+ document.body.appendChild(_offscreenSVG);
38
+ return _offscreenSVG;
39
+ }
40
+
41
+ /**
42
+ * Compute scaling params for viewBox → element mapping
43
+ * Preserves aspect ratio (equivalent to SVG preserveAspectRatio="xMidYMid meet")
44
+ *
45
+ * @param {number[]} vb - [x, y, w, h] viewBox
46
+ * @param {{ width: number, height: number }} size - element size
47
+ * @returns {{ scale: number, offsetX: number, offsetY: number }}
48
+ */
49
+ function computeMapping(vb, size) {
50
+ let [vx, vy, vw, vh] = vb;
51
+ let scale = Math.min(size.width / vw, size.height / vh);
52
+ let renderedW = vw * scale;
53
+ let renderedH = vh * scale;
54
+ return {
55
+ scale,
56
+ offsetX: (size.width - renderedW) / 2 - vx * scale,
57
+ offsetY: (size.height - renderedH) / 2 - vy * scale,
58
+ };
59
+ }
60
+
61
+ export class SVGShape extends NodeShape {
62
+ /** @type {string} */
63
+ name;
64
+
65
+ /** @type {string} - SVG path d attribute */
66
+ pathData;
67
+
68
+ /** @type {string} - viewBox of original SVG */
69
+ viewBox;
70
+
71
+ /** @type {number[]} - parsed viewBox [x, y, w, h] */
72
+ #vb;
73
+
74
+ /** @type {boolean} - whether shape has standard header */
75
+ #header;
76
+
77
+ /** @type {{ minWidth: number, minHeight: number }} */
78
+ #minSize;
79
+
80
+ /** @type {Map<string, {x:number,y:number,angle:number}>} - position cache */
81
+ #posCache = new Map();
82
+
83
+ /**
84
+ * @param {string} name - Shape identifier
85
+ * @param {object} options
86
+ * @param {string} options.pathData - SVG path d attribute
87
+ * @param {string} [options.viewBox='0 0 24 24'] - Original SVG viewBox
88
+ * @param {boolean} [options.header=false] - Show header/controls
89
+ * @param {{ minWidth?: number, minHeight?: number }} [options.minSize]
90
+ */
91
+ constructor(name, { pathData, viewBox = '0 0 24 24', header = false, minSize = {} }) {
92
+ super();
93
+ this.name = name;
94
+ this.pathData = pathData;
95
+ this.viewBox = viewBox;
96
+ this.#vb = viewBox.split(' ').map(Number);
97
+ this.#header = header;
98
+ this.#minSize = {
99
+ minWidth: minSize.minWidth || 100,
100
+ minHeight: minSize.minHeight || 100,
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Scale a point from viewBox coordinates to element pixel coordinates
106
+ * Uses aspect-ratio-preserving mapping (xMidYMid meet)
107
+ *
108
+ * @param {number} px - x in viewBox
109
+ * @param {number} py - y in viewBox
110
+ * @param {{ width: number, height: number }} size - element size
111
+ * @returns {{ x: number, y: number }}
112
+ */
113
+ #scalePoint(px, py, size) {
114
+ let { scale, offsetX, offsetY } = computeMapping(this.#vb, size);
115
+ return {
116
+ x: px * scale + offsetX,
117
+ y: py * scale + offsetY,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Get center of SVG shape in viewBox coordinates
123
+ * @returns {{ x: number, y: number }}
124
+ */
125
+ #getCenter() {
126
+ let [vx, vy, vw, vh] = this.#vb;
127
+ return { x: vx + vw / 2, y: vy + vh / 2 };
128
+ }
129
+
130
+ /**
131
+ * Get a path element for computations (uses offscreen SVG)
132
+ * @returns {SVGPathElement|null}
133
+ */
134
+ #getPathElement() {
135
+ let svg = getOffscreenSVG();
136
+ if (!svg) return null;
137
+ let pathEl = svg.querySelector(`[data-shape="${this.name}"]`);
138
+ if (!pathEl) {
139
+ pathEl = document.createElementNS('http://www.w3.org/2000/svg', 'path');
140
+ pathEl.setAttribute('d', this.pathData);
141
+ pathEl.setAttribute('data-shape', this.name);
142
+ svg.appendChild(pathEl);
143
+ }
144
+ return pathEl;
145
+ }
146
+
147
+ /**
148
+ * Find the point on the SVG path that a ray from center at a given angle hits.
149
+ * Uses dense sampling along the path perimeter and finds the point
150
+ * whose angle from center best matches the target angle.
151
+ *
152
+ * @param {number} targetAngle - angle in radians from center
153
+ * @param {SVGPathElement} pathEl
154
+ * @returns {{ x: number, y: number }} - point in viewBox coordinates
155
+ */
156
+ #findPointAtAngle(targetAngle, pathEl) {
157
+ let totalLen = pathEl.getTotalLength();
158
+ let center = this.#getCenter();
159
+
160
+
161
+ let bestDist = Infinity;
162
+ let bestLen = 0;
163
+ const COARSE = 128;
164
+
165
+ for (let i = 0; i <= COARSE; i++) {
166
+ let len = (totalLen * i) / COARSE;
167
+ let pt = pathEl.getPointAtLength(len);
168
+ let angle = Math.atan2(pt.y - center.y, pt.x - center.x);
169
+ let diff = Math.abs(angle - targetAngle);
170
+ if (diff > Math.PI) diff = 2 * Math.PI - diff;
171
+ if (diff < bestDist) {
172
+ bestDist = diff;
173
+ bestLen = len;
174
+ }
175
+ }
176
+
177
+
178
+ let searchRadius = totalLen / COARSE;
179
+ const FINE = 32;
180
+ let startLen = Math.max(0, bestLen - searchRadius);
181
+ let endLen = Math.min(totalLen, bestLen + searchRadius);
182
+
183
+ for (let i = 0; i <= FINE; i++) {
184
+ let len = startLen + ((endLen - startLen) * i) / FINE;
185
+ let pt = pathEl.getPointAtLength(len);
186
+ let angle = Math.atan2(pt.y - center.y, pt.x - center.x);
187
+ let diff = Math.abs(angle - targetAngle);
188
+ if (diff > Math.PI) diff = 2 * Math.PI - diff;
189
+ if (diff < bestDist) {
190
+ bestDist = diff;
191
+ bestLen = len;
192
+ }
193
+ }
194
+
195
+ return pathEl.getPointAtLength(bestLen);
196
+ }
197
+
198
+ /**
199
+ * Get socket position on the shape outline.
200
+ * Results are cached — same params always return same position.
201
+ *
202
+ * @param {'input'|'output'} side
203
+ * @param {number} index - ordinal index of this port
204
+ * @param {number} total - total ports on this side
205
+ * @param {{ width: number, height: number }} size - node dimensions
206
+ * @returns {{ x: number, y: number, angle: number }}
207
+ */
208
+ getSocketPosition(side, index, total, size) {
209
+
210
+ let key = `${side}|${index}|${total}|${size.width}|${size.height}`;
211
+ if (this.#posCache.has(key)) return this.#posCache.get(key);
212
+
213
+ let pathEl = this.#getPathElement();
214
+
215
+ if (!pathEl) {
216
+ let y = (size.height * (index + 1)) / (total + 1);
217
+ let result = side === 'input' ? { x: 0, y, angle: 180 } : { x: size.width, y, angle: 0 };
218
+ this.#posCache.set(key, result);
219
+ return result;
220
+ }
221
+
222
+
223
+ let centerAngle = side === 'input' ? Math.PI : 0;
224
+ let arcSpan = Math.PI * 0.6;
225
+ let targetAngle;
226
+
227
+ if (total === 1) {
228
+ targetAngle = centerAngle;
229
+ } else {
230
+ let startAngle = centerAngle - arcSpan / 2;
231
+ let step = arcSpan / (total - 1);
232
+ targetAngle = startAngle + step * index;
233
+ }
234
+
235
+ let pt = this.#findPointAtAngle(targetAngle, pathEl);
236
+ let scaled = this.#scalePoint(pt.x, pt.y, size);
237
+
238
+
239
+ let totalLen = pathEl.getTotalLength();
240
+ let center = this.#getCenter();
241
+ let bestLen = 0,
242
+ bestDist = Infinity;
243
+ const SCAN = 128;
244
+ for (let i = 0; i <= SCAN; i++) {
245
+ let len = (totalLen * i) / SCAN;
246
+ let p = pathEl.getPointAtLength(len);
247
+ let dist = (p.x - pt.x) ** 2 + (p.y - pt.y) ** 2;
248
+ if (dist < bestDist) {
249
+ bestDist = dist;
250
+ bestLen = len;
251
+ }
252
+ }
253
+ let delta = 0.5;
254
+ let prevPt = pathEl.getPointAtLength(Math.max(0, bestLen - delta));
255
+ let nextPt = pathEl.getPointAtLength(Math.min(totalLen, bestLen + delta));
256
+ let tx = nextPt.x - prevPt.x,
257
+ ty = nextPt.y - prevPt.y;
258
+ let nx = -ty,
259
+ ny = tx;
260
+ let radX = pt.x - center.x,
261
+ radY = pt.y - center.y;
262
+ if (nx * radX + ny * radY < 0) {
263
+ nx = ty;
264
+ ny = -tx;
265
+ }
266
+ let angleDeg = (Math.atan2(ny, nx) * 180) / Math.PI;
267
+
268
+ let result = { x: scaled.x, y: scaled.y, angle: angleDeg };
269
+ this.#posCache.set(key, result);
270
+ return result;
271
+ }
272
+
273
+ /**
274
+ * Get edge point at a specific angle (direction toward target node).
275
+ * Used for dynamic connectors that slide along the perimeter.
276
+ *
277
+ * @param {number} angle - angle in radians from center to target
278
+ * @param {{ width: number, height: number }} size - element dimensions
279
+ * @returns {{ x: number, y: number, angle: number }}
280
+ */
281
+ getEdgePoint(angle, size) {
282
+
283
+ let rounded = Math.round(angle * 1000) / 1000;
284
+ let key = `edge|${rounded}|${size.width}|${size.height}`;
285
+ if (this.#posCache.has(key)) return this.#posCache.get(key);
286
+
287
+ let pathEl = this.#getPathElement();
288
+ if (!pathEl) {
289
+ let cx = size.width / 2;
290
+ let cy = size.height / 2;
291
+ return {
292
+ x: cx + Math.cos(angle) * cx,
293
+ y: cy + Math.sin(angle) * cy,
294
+ angle: (angle * 180) / Math.PI,
295
+ };
296
+ }
297
+
298
+ let pt = this.#findPointAtAngle(angle, pathEl);
299
+ let scaled = this.#scalePoint(pt.x, pt.y, size);
300
+
301
+
302
+ let totalLen = pathEl.getTotalLength();
303
+ let center = this.#getCenter();
304
+
305
+
306
+ let bestLen = 0,
307
+ bestDist = Infinity;
308
+ const SCAN = 128;
309
+ for (let i = 0; i <= SCAN; i++) {
310
+ let len = (totalLen * i) / SCAN;
311
+ let p = pathEl.getPointAtLength(len);
312
+ let dist = (p.x - pt.x) ** 2 + (p.y - pt.y) ** 2;
313
+ if (dist < bestDist) {
314
+ bestDist = dist;
315
+ bestLen = len;
316
+ }
317
+ }
318
+
319
+
320
+ let delta = 0.5;
321
+ let prevLen = Math.max(0, bestLen - delta);
322
+ let nextLen = Math.min(totalLen, bestLen + delta);
323
+ let prevPt = pathEl.getPointAtLength(prevLen);
324
+ let nextPt = pathEl.getPointAtLength(nextLen);
325
+
326
+
327
+ let tx = nextPt.x - prevPt.x;
328
+ let ty = nextPt.y - prevPt.y;
329
+
330
+
331
+ let nx = -ty,
332
+ ny = tx;
333
+ let radX = pt.x - center.x;
334
+ let radY = pt.y - center.y;
335
+
336
+ if (nx * radX + ny * radY < 0) {
337
+ nx = ty;
338
+ ny = -tx;
339
+ }
340
+
341
+ let angleDeg = (Math.atan2(ny, nx) * 180) / Math.PI;
342
+
343
+ let result = { x: scaled.x, y: scaled.y, angle: angleDeg };
344
+
345
+
346
+ if (this.#posCache.size > 360) {
347
+ let first = this.#posCache.keys().next().value;
348
+ this.#posCache.delete(first);
349
+ }
350
+ this.#posCache.set(key, result);
351
+ return result;
352
+ }
353
+
354
+ /**
355
+ * Get a pin position on a specific side of the shape.
356
+ *
357
+ * Side-based placement: pins are placed along a side edge (top/right/bottom/left).
358
+ * The position within the side is controlled by t (0 = start, 1 = end).
359
+ *
360
+ * @param {'top'|'right'|'bottom'|'left'} side - which side to place pin on
361
+ * @param {number} t - position along the side (0..1), 0.5 = center
362
+ * @param {{ width: number, height: number }} size - element dimensions
363
+ * @returns {{ x: number, y: number, angle: number }} - angle is outward normal in degrees
364
+ */
365
+ getSidePosition(side, t, size) {
366
+ let key = `side|${side}|${(t * 100) | 0}|${size.width}|${size.height}`;
367
+ if (this.#posCache.has(key)) return this.#posCache.get(key);
368
+
369
+
370
+ const NORMALS = { top: -90, right: 0, bottom: 90, left: 180 };
371
+ let angleDeg = NORMALS[side];
372
+
373
+
374
+ let pathEl = this.#getPathElement();
375
+ if (pathEl) {
376
+
377
+
378
+ const RANGES = {
379
+ right: { from: -Math.PI / 4, to: Math.PI / 4 },
380
+ bottom: { from: Math.PI / 4, to: (3 * Math.PI) / 4 },
381
+ left: { from: (3 * Math.PI) / 4, to: (5 * Math.PI) / 4 },
382
+ top: { from: (-3 * Math.PI) / 4, to: -Math.PI / 4 },
383
+ };
384
+
385
+ let range = RANGES[side];
386
+
387
+ let sideKey = `sidepts|${side}|${size.width}|${size.height}`;
388
+ let sidePoints;
389
+
390
+ if (this.#posCache.has(sideKey)) {
391
+ sidePoints = this.#posCache.get(sideKey);
392
+ } else {
393
+ sidePoints = [];
394
+ const SAMPLES = 16;
395
+ for (let i = 0; i <= SAMPLES; i++) {
396
+ let a = range.from + (range.to - range.from) * (i / SAMPLES);
397
+ let pt = this.#findPointAtAngle(a, pathEl);
398
+ let sp = this.#scalePoint(pt.x, pt.y, size);
399
+ sidePoints.push(sp);
400
+ }
401
+ this.#posCache.set(sideKey, sidePoints);
402
+ }
403
+
404
+
405
+ const MARGIN = 0.2;
406
+ let effectiveT = MARGIN + t * (1 - 2 * MARGIN);
407
+ let idx = effectiveT * (sidePoints.length - 1);
408
+ let lo = Math.floor(idx);
409
+ let hi = Math.min(lo + 1, sidePoints.length - 1);
410
+ let frac = idx - lo;
411
+
412
+ let x = sidePoints[lo].x + (sidePoints[hi].x - sidePoints[lo].x) * frac;
413
+ let y = sidePoints[lo].y + (sidePoints[hi].y - sidePoints[lo].y) * frac;
414
+
415
+ let result = { x, y, angle: angleDeg };
416
+ this.#posCache.set(key, result);
417
+ return result;
418
+ }
419
+
420
+
421
+ let x, y;
422
+ switch (side) {
423
+ case 'top':
424
+ x = size.width * (0.2 + t * 0.6);
425
+ y = 0;
426
+ break;
427
+ case 'right':
428
+ x = size.width;
429
+ y = size.height * (0.2 + t * 0.6);
430
+ break;
431
+ case 'bottom':
432
+ x = size.width * (0.2 + t * 0.6);
433
+ y = size.height;
434
+ break;
435
+ case 'left':
436
+ x = 0;
437
+ y = size.height * (0.2 + t * 0.6);
438
+ break;
439
+ }
440
+
441
+ let result = { x, y, angle: angleDeg };
442
+ this.#posCache.set(key, result);
443
+ return result;
444
+ }
445
+
446
+ getClipPath(_size) {
447
+ return null;
448
+ }
449
+
450
+ getOutlinePath(_size) {
451
+ return this.pathData;
452
+ }
453
+
454
+ getBorderRadius() {
455
+ return '0';
456
+ }
457
+
458
+ get hasHeader() {
459
+ return this.#header;
460
+ }
461
+
462
+ get hasControls() {
463
+ return this.#header;
464
+ }
465
+
466
+ getMinSize() {
467
+ return this.#minSize;
468
+ }
469
+ }
470
+
471
+
472
+ /**
473
+ * Register an SVG shape from a path string
474
+ * @param {string} name
475
+ * @param {string} pathData - SVG d attribute
476
+ * @param {object} [options]
477
+ * @returns {SVGShape}
478
+ */
479
+ export function createSVGShape(name, pathData, options = {}) {
480
+ return new SVGShape(name, { pathData, ...options });
481
+ }
482
+
483
+
484
+ export let SVG_PRESETS = {
485
+ disc: 'M12 2A10 10 0 1 1 12 22A10 10 0 1 1 12 2Z',
486
+
487
+
488
+ hexagon: 'M12 2L22 8.5V15.5L12 22L2 15.5V8.5Z',
489
+
490
+
491
+ pentagon: 'M12 2L22 9.27L18.18 21H5.82L2 9.27Z',
492
+
493
+
494
+ star: 'M12 2L15.09 8.26L22 9.27L17 14.14L18.18 21.02L12 17.77L5.82 21.02L7 14.14L2 9.27L8.91 8.26Z',
495
+
496
+
497
+ cloud:
498
+ 'M19.35 10.04A7.49 7.49 0 0012 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 000 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z',
499
+
500
+
501
+ shield: 'M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4z',
502
+
503
+
504
+ octagon: 'M7.86 2H16.14L22 7.86V16.14L16.14 22H7.86L2 16.14V7.86Z',
505
+
506
+
507
+ parallelogram: 'M6 2H22L18 22H2Z',
508
+
509
+
510
+ trapezoid: 'M4 22H20L23 2H1Z',
511
+
512
+
513
+ cylinder: 'M4 6C4 4 8 2 12 2S20 4 20 6V18C20 20 16 22 12 22S4 20 4 18Z',
514
+
515
+
516
+ database:
517
+ 'M12 3C7.58 3 4 4.79 4 7V17C4 19.21 7.59 21 12 21S20 19.21 20 17V7C20 4.79 16.42 3 12 3Z',
518
+
519
+
520
+ bolt: 'M7 2V13H10V22L17 10H13L17 2Z',
521
+
522
+
523
+ heart:
524
+ 'M12 21.35L10.55 20.03C5.4 15.36 2 12.28 2 8.5C2 5.42 4.42 3 7.5 3C9.24 3 10.91 3.81 12 5.09C13.09 3.81 14.76 3 16.5 3C19.58 3 22 5.42 22 8.5C22 12.28 18.6 15.36 13.45 20.04L12 21.35Z',
525
+ };
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shape registry — maps shape names to implementations
3
+ * @module symbiote-node/shapes/index
4
+ */
5
+
6
+ import { NodeShape } from './NodeShape.js';
7
+ import { RectShape } from './RectShape.js';
8
+ import { PillShape } from './PillShape.js';
9
+ import { CircleShape } from './CircleShape.js';
10
+ import { DiamondShape } from './DiamondShape.js';
11
+ import { CommentShape } from './CommentShape.js';
12
+ import { SVGShape, createSVGShape, SVG_PRESETS } from './SVGShape.js';
13
+
14
+ /** @type {Map<string, NodeShape>} */
15
+ let registry = new Map();
16
+
17
+
18
+ const RECT = new RectShape();
19
+ const PILL = new PillShape();
20
+ const CIRCLE = new CircleShape();
21
+ const DIAMOND = new DiamondShape();
22
+ const COMMENT = new CommentShape();
23
+
24
+ registry.set('rect', RECT);
25
+ registry.set('pill', PILL);
26
+ registry.set('circle', CIRCLE);
27
+ registry.set('diamond', DIAMOND);
28
+ registry.set('comment', COMMENT);
29
+
30
+
31
+ for (const [name, pathData] of Object.entries(SVG_PRESETS)) {
32
+ registry.set(name, createSVGShape(name, pathData));
33
+ }
34
+
35
+ /**
36
+ * Get shape by name
37
+ * @param {string} name
38
+ * @returns {NodeShape}
39
+ */
40
+ export function getShape(name) {
41
+ return registry.get(name) || RECT;
42
+ }
43
+
44
+ /**
45
+ * Register custom shape
46
+ * @param {string} name
47
+ * @param {NodeShape} shape
48
+ */
49
+ export function registerShape(name, shape) {
50
+ registry.set(name, shape);
51
+ }
52
+
53
+ export {
54
+ NodeShape,
55
+ RectShape,
56
+ PillShape,
57
+ CircleShape,
58
+ DiamondShape,
59
+ CommentShape,
60
+ SVGShape,
61
+ createSVGShape,
62
+ SVG_PRESETS,
63
+ };