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,128 @@
1
+ function numberOr(value, fallback) {
2
+ let number = Number(value);
3
+ return Number.isFinite(number) ? number : fallback;
4
+ }
5
+
6
+ function clamp(value, min, max) {
7
+ return Math.max(min, Math.min(max, value));
8
+ }
9
+
10
+ function normalizePoint(point = {}) {
11
+ return {
12
+ x: clamp(numberOr(point.x, 0), 0, 1),
13
+ y: clamp(numberOr(point.y, 0), 0, 1),
14
+ };
15
+ }
16
+
17
+ function bool(value) {
18
+ return value === true;
19
+ }
20
+
21
+ export function createXRPanelFrame(panel = {}, options = {}) {
22
+ let handleSize = clamp(numberOr(options.handleSize, 0.055), 0.02, 0.16);
23
+ let headerHeight = clamp(numberOr(options.headerHeight, 0.09), 0.04, 0.22);
24
+ let actionSize = clamp(numberOr(options.actionSize, handleSize), 0.02, 0.16);
25
+ let state = options.state || panel.state || {};
26
+ return {
27
+ version: 'xr-panel-frame-v1',
28
+ panelId: String(panel.id || ''),
29
+ component: panel.component || panel.panelType || 'panel',
30
+ tokens: {
31
+ background: 'var(--sn-xr-panel-bg)',
32
+ border: 'var(--sn-xr-panel-border)',
33
+ radius: 'var(--sn-xr-panel-radius)',
34
+ shadow: 'var(--sn-xr-panel-shadow)',
35
+ pointer: 'var(--sn-xr-pointer-color)',
36
+ },
37
+ zones: {
38
+ move: { x: 0, y: 0, width: 1, height: headerHeight },
39
+ content: { x: handleSize, y: headerHeight, width: 1 - handleSize * 2, height: 1 - headerHeight - handleSize },
40
+ resize: {
41
+ north: { x: handleSize, y: 0, width: 1 - handleSize * 2, height: handleSize },
42
+ east: { x: 1 - handleSize, y: headerHeight, width: handleSize, height: 1 - headerHeight - handleSize },
43
+ south: { x: handleSize, y: 1 - handleSize, width: 1 - handleSize * 2, height: handleSize },
44
+ west: { x: 0, y: headerHeight, width: handleSize, height: 1 - headerHeight - handleSize },
45
+ northWest: { x: 0, y: 0, width: handleSize, height: handleSize },
46
+ northEast: { x: 1 - handleSize, y: 0, width: handleSize, height: handleSize },
47
+ southEast: { x: 1 - handleSize, y: 1 - handleSize, width: handleSize, height: handleSize },
48
+ southWest: { x: 0, y: 1 - handleSize, width: handleSize, height: handleSize },
49
+ },
50
+ actions: {
51
+ pin: { x: 1 - actionSize * 3, y: 0, width: actionSize, height: headerHeight },
52
+ reset: { x: 1 - actionSize * 2, y: 0, width: actionSize, height: headerHeight },
53
+ close: { x: 1 - actionSize, y: 0, width: actionSize, height: headerHeight },
54
+ },
55
+ },
56
+ state: {
57
+ hovered: bool(state.hovered || options.hovered),
58
+ selected: bool(state.selected || options.selected),
59
+ dragging: bool(state.dragging || options.dragging),
60
+ resizing: bool(state.resizing || options.resizing),
61
+ pinned: bool(state.pinned || options.pinned),
62
+ },
63
+ };
64
+ }
65
+
66
+ function contains(zone, point) {
67
+ return point.x >= zone.x &&
68
+ point.x <= zone.x + zone.width &&
69
+ point.y >= zone.y &&
70
+ point.y <= zone.y + zone.height;
71
+ }
72
+
73
+ export function hitTestXRPanelFrame(frameOrPanel = {}, point = {}, options = {}) {
74
+ let frame = frameOrPanel.version === 'xr-panel-frame-v1'
75
+ ? frameOrPanel
76
+ : createXRPanelFrame(frameOrPanel, options);
77
+ let normalizedPoint = normalizePoint(point);
78
+
79
+ for (let [action, zone] of Object.entries(frame.zones.actions || {})) {
80
+ if (contains(zone, normalizedPoint)) {
81
+ return {
82
+ version: 'xr-panel-frame-target-v1',
83
+ panelId: frame.panelId,
84
+ zone: 'action',
85
+ action,
86
+ operation: 'action',
87
+ handle: null,
88
+ point: normalizedPoint,
89
+ };
90
+ }
91
+ }
92
+
93
+ for (let [handle, zone] of Object.entries(frame.zones.resize || {})) {
94
+ if (contains(zone, normalizedPoint)) {
95
+ return {
96
+ version: 'xr-panel-frame-target-v1',
97
+ panelId: frame.panelId,
98
+ zone: 'resize',
99
+ action: null,
100
+ operation: 'resize',
101
+ handle,
102
+ point: normalizedPoint,
103
+ };
104
+ }
105
+ }
106
+
107
+ if (contains(frame.zones.move, normalizedPoint)) {
108
+ return {
109
+ version: 'xr-panel-frame-target-v1',
110
+ panelId: frame.panelId,
111
+ zone: 'move',
112
+ action: null,
113
+ operation: 'move',
114
+ handle: null,
115
+ point: normalizedPoint,
116
+ };
117
+ }
118
+
119
+ return {
120
+ version: 'xr-panel-frame-target-v1',
121
+ panelId: frame.panelId,
122
+ zone: 'content',
123
+ action: null,
124
+ operation: options.defaultContentOperation || 'focus',
125
+ handle: null,
126
+ point: normalizedPoint,
127
+ };
128
+ }
@@ -0,0 +1,267 @@
1
+ import { createXRPanelContentViewport } from './layout-projection.js';
2
+ import { createXRPanelPointerTarget } from './pointer.js';
3
+
4
+ function defaultComponentResolver(name) {
5
+ return name;
6
+ }
7
+
8
+ function defaultPropsResolver(node = {}, panel = {}) {
9
+ return {
10
+ ...(panel.state || {}),
11
+ ...(node.props || {}),
12
+ };
13
+ }
14
+
15
+ function toKebabName(value) {
16
+ return String(value || '').trim();
17
+ }
18
+
19
+ function applyAttributes(element, attrs = {}) {
20
+ for (let [name, value] of Object.entries(attrs || {})) {
21
+ if (value === false || value == null) continue;
22
+ if (value === true) {
23
+ element.setAttribute(name, '');
24
+ } else {
25
+ element.setAttribute(name, String(value));
26
+ }
27
+ }
28
+ }
29
+
30
+ function applyProps(element, props = {}) {
31
+ for (let [name, value] of Object.entries(props || {})) {
32
+ element[name] = value;
33
+ }
34
+ }
35
+
36
+ function applyThemeScope(element, node = {}, panel = {}) {
37
+ let scope = node.theme?.name || node.themeScope || panel.themeScope;
38
+ if (scope) {
39
+ element.dataset.themeScope = scope;
40
+ element.setAttribute('data-theme-scope', scope);
41
+ }
42
+ }
43
+
44
+ function setStyleProperty(element, name, value) {
45
+ if (!element?.style) return;
46
+ if (typeof element.style.setProperty === 'function') {
47
+ element.style.setProperty(name, value);
48
+ return;
49
+ }
50
+ element.style[name] = value;
51
+ }
52
+
53
+ function applyPanelViewport(element, panel) {
54
+ let viewport = panel.contentViewport || createXRPanelContentViewport(panel);
55
+ setStyleProperty(element, '--sn-xr-content-width', `${viewport.width}px`);
56
+ setStyleProperty(element, '--sn-xr-content-height', `${viewport.height}px`);
57
+ setStyleProperty(element, '--sn-xr-content-scale', String(viewport.scale));
58
+ setStyleProperty(element, '--sn-xr-panel-meter-width', `${panel.size?.[0] || 0}m`);
59
+ setStyleProperty(element, '--sn-xr-panel-meter-height', `${panel.size?.[1] || 0}m`);
60
+ setStyleProperty(element, 'width', `${viewport.width}px`);
61
+ setStyleProperty(element, 'height', `${viewport.height}px`);
62
+ return viewport;
63
+ }
64
+
65
+ function appendChildren(host, node, context) {
66
+ for (let child of node.children || []) {
67
+ host.append(createComponentElement(child, context));
68
+ }
69
+ }
70
+
71
+ function fallbackElement(documentRef, panel, reason) {
72
+ let element = documentRef.createElement('section');
73
+ element.className = 'sn-xr-panel-fallback';
74
+ element.dataset.reason = reason;
75
+ element.textContent = `XR panel fallback: ${reason}`;
76
+ element.setAttribute('role', 'note');
77
+ return element;
78
+ }
79
+
80
+ function resolveComponentTarget(node, panel, context) {
81
+ let requested = node.component || panel.component || panel.panelType;
82
+ let resolved = context.componentResolver(requested, node, panel);
83
+ if (typeof resolved === 'string') {
84
+ return { tagName: toKebabName(resolved), ComponentClass: null };
85
+ }
86
+ if (typeof resolved === 'function') {
87
+ return { tagName: toKebabName(resolved.tagName || node.component || panel.component), ComponentClass: resolved };
88
+ }
89
+ if (resolved && typeof resolved === 'object') {
90
+ return {
91
+ tagName: toKebabName(resolved.tagName || resolved.name || node.component || panel.component),
92
+ ComponentClass: typeof resolved.ComponentClass === 'function' ? resolved.ComponentClass : null,
93
+ };
94
+ }
95
+ return { tagName: '', ComponentClass: null };
96
+ }
97
+
98
+ function createComponentElement(node, context, panel = node) {
99
+ let documentRef = context.document;
100
+ let target = resolveComponentTarget(node, panel, context);
101
+ if (!target.tagName) {
102
+ return fallbackElement(documentRef, panel, 'component-unresolved');
103
+ }
104
+ if (
105
+ target.ComponentClass &&
106
+ typeof context.customElements?.define === 'function' &&
107
+ !context.customElements.get(target.tagName)
108
+ ) {
109
+ context.customElements.define(target.tagName, target.ComponentClass);
110
+ }
111
+
112
+ let element = documentRef.createElement(target.tagName);
113
+ applyProps(element, context.propsResolver(node, panel));
114
+ applyAttributes(element, node.attrs);
115
+ applyThemeScope(element, node, panel);
116
+ if (Array.isArray(node.children) && node.children.length) {
117
+ appendChildren(element, node, context);
118
+ }
119
+ return element;
120
+ }
121
+
122
+ function createContext(options = {}) {
123
+ let documentRef = options.document || globalThis.document;
124
+ if (!documentRef?.createElement) {
125
+ throw new Error('createXRPanelHost requires a document with createElement().');
126
+ }
127
+ return {
128
+ document: documentRef,
129
+ globalThis: options.globalThis || documentRef.defaultView || globalThis,
130
+ customElements: options.customElements || documentRef.defaultView?.customElements || globalThis.customElements,
131
+ componentResolver: options.componentResolver || defaultComponentResolver,
132
+ propsResolver: options.propsResolver || defaultPropsResolver,
133
+ };
134
+ }
135
+
136
+ function createHostEvent(context, type, detail) {
137
+ let EventCtor = context.globalThis?.CustomEvent;
138
+ if (typeof EventCtor === 'function') {
139
+ return new EventCtor(type, { bubbles: true, composed: true, detail });
140
+ }
141
+ return {
142
+ type,
143
+ bubbles: true,
144
+ composed: true,
145
+ detail,
146
+ };
147
+ }
148
+
149
+ function createPointerDomEvent(context, pointerEvent, target) {
150
+ let EventCtor = context.globalThis?.PointerEvent;
151
+ if (typeof EventCtor !== 'function') return null;
152
+ return new EventCtor(pointerEvent.type || 'pointermove', {
153
+ bubbles: false,
154
+ composed: false,
155
+ clientX: target.contentPoint.x,
156
+ clientY: target.contentPoint.y,
157
+ buttons: pointerEvent.buttons?.primary ? 1 : 0,
158
+ button: pointerEvent.buttons?.primary ? 0 : -1,
159
+ pointerType: pointerEvent.source === 'mouse-fallback' ? 'mouse' : 'xr',
160
+ isPrimary: true,
161
+ });
162
+ }
163
+
164
+ export function createXRPanelHost(options = {}) {
165
+ let context = createContext(options);
166
+ let panels = new Map();
167
+ let scene = null;
168
+ let themeSnapshot = options.themeSnapshot || null;
169
+ let dispatchDepth = 0;
170
+
171
+ function getState() {
172
+ return {
173
+ scene,
174
+ themeSnapshot,
175
+ mounted: panels.size,
176
+ panelIds: [...panels.keys()],
177
+ };
178
+ }
179
+
180
+ function setScene(nextScene, sceneOptions = {}) {
181
+ scene = nextScene || null;
182
+ themeSnapshot = sceneOptions.themeSnapshot || themeSnapshot || null;
183
+ panels.clear();
184
+ return getState();
185
+ }
186
+
187
+ function mountPanel(panel, container) {
188
+ if (!panel || !container?.replaceChildren) {
189
+ throw new Error('mountPanel(panel, container) requires a panel and a DOM container.');
190
+ }
191
+
192
+ let node = panel.layoutNode || panel;
193
+ let element = createComponentElement(node, context, panel);
194
+ element.dataset.xrPanelId = panel.id;
195
+ element.classList.add('sn-xr-panel-live-root');
196
+ let contentViewport = applyPanelViewport(element, panel);
197
+ applyPanelViewport(container, { ...panel, contentViewport });
198
+ container.replaceChildren(element);
199
+ panels.set(panel.id, { panel, container, element, contentViewport });
200
+ return element;
201
+ }
202
+
203
+ function unmountPanel(panelId) {
204
+ let record = panels.get(panelId);
205
+ if (!record) return false;
206
+ record.container.replaceChildren();
207
+ panels.delete(panelId);
208
+ return true;
209
+ }
210
+
211
+ function getPanelElement(panelId) {
212
+ return panels.get(panelId)?.element || null;
213
+ }
214
+
215
+ function dispatchPointerEvent(pointerEvent, options = {}) {
216
+ if (!pointerEvent) return { ok: false, reason: 'missing-pointer-event' };
217
+ let panelId = pointerEvent.targetId || pointerEvent.panelId;
218
+ if (dispatchDepth > 0) {
219
+ return { ok: false, reason: 'pointer-dispatch-reentrant', panelId };
220
+ }
221
+ let record = panels.get(panelId);
222
+ if (!record?.element?.dispatchEvent) {
223
+ return { ok: false, reason: 'panel-not-mounted', panelId };
224
+ }
225
+ let target = createXRPanelPointerTarget({
226
+ panelId,
227
+ point: pointerEvent.point,
228
+ panel: record.panel,
229
+ }, {
230
+ ...options,
231
+ contentViewport: record.contentViewport,
232
+ source: pointerEvent.source,
233
+ });
234
+ let detail = {
235
+ ...pointerEvent,
236
+ targetId: panelId,
237
+ contentPoint: target.contentPoint,
238
+ contentViewport: target.contentViewport,
239
+ };
240
+ let domEvent = createPointerDomEvent(context, detail, target);
241
+ dispatchDepth += 1;
242
+ try {
243
+ if (domEvent) {
244
+ domEvent.xrPanelPointer = detail;
245
+ record.element.dispatchEvent(domEvent);
246
+ }
247
+ record.element.dispatchEvent(createHostEvent(context, 'xr-panel-pointer', detail));
248
+ } finally {
249
+ dispatchDepth -= 1;
250
+ }
251
+ return {
252
+ ok: true,
253
+ panelId,
254
+ target,
255
+ dispatched: domEvent ? [detail.type, 'xr-panel-pointer'] : ['xr-panel-pointer'],
256
+ };
257
+ }
258
+
259
+ return {
260
+ setScene,
261
+ mountPanel,
262
+ unmountPanel,
263
+ getPanelElement,
264
+ dispatchPointerEvent,
265
+ getState,
266
+ };
267
+ }
package/xr/pointer.js ADDED
@@ -0,0 +1,258 @@
1
+ import { createXRPanelContentViewport } from './layout-projection.js';
2
+
3
+ function dot(a, b) {
4
+ return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
5
+ }
6
+
7
+ function subtract(a, b) {
8
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
9
+ }
10
+
11
+ function add(a, b) {
12
+ return [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
13
+ }
14
+
15
+ function scale(v, s) {
16
+ return [v[0] * s, v[1] * s, v[2] * s];
17
+ }
18
+
19
+ function normalize(v) {
20
+ let length = Math.hypot(v[0], v[1], v[2]);
21
+ if (!length) return [0, 0, -1];
22
+ return [v[0] / length, v[1] / length, v[2] / length];
23
+ }
24
+
25
+ function numberOr(value, fallback) {
26
+ let number = Number(value);
27
+ return Number.isFinite(number) ? number : fallback;
28
+ }
29
+
30
+ function clamp(value, min, max) {
31
+ return Math.max(min, Math.min(max, value));
32
+ }
33
+
34
+ function roundMetric(value) {
35
+ return Math.round(value * 1_000_000) / 1_000_000;
36
+ }
37
+
38
+ function degToRad(value) {
39
+ return Number(value || 0) * Math.PI / 180;
40
+ }
41
+
42
+ function panelAxes(panel) {
43
+ let yaw = degToRad(panel.rotation?.[1] || 0);
44
+ let right = [Math.cos(yaw), 0, -Math.sin(yaw)];
45
+ let up = [0, 1, 0];
46
+ let normal = normalize([Math.sin(yaw), 0, Math.cos(yaw)]);
47
+ return { right, up, normal };
48
+ }
49
+
50
+ export function hitTestXRPanel(ray, panel) {
51
+ if (!ray || !panel) return null;
52
+ let origin = ray.origin || [0, 0, 0];
53
+ let direction = normalize(ray.direction || [0, 0, -1]);
54
+ let center = panel.position || [0, 0, -1];
55
+ let [width, height] = panel.size || [1, 1];
56
+ let { right, up, normal } = panelAxes(panel);
57
+ let denom = dot(normal, direction);
58
+ if (Math.abs(denom) < 0.000001) return null;
59
+ let t = dot(normal, subtract(center, origin)) / denom;
60
+ if (t < 0) return null;
61
+
62
+ let hitPoint = add(origin, scale(direction, t));
63
+ let local = subtract(hitPoint, center);
64
+ let xMeters = dot(local, right);
65
+ let yMeters = dot(local, up);
66
+ let x = xMeters / width + 0.5;
67
+ let y = 0.5 - yMeters / height;
68
+ if (x < 0 || x > 1 || y < 0 || y > 1) return null;
69
+
70
+ return {
71
+ panelId: panel.id,
72
+ point: { x, y },
73
+ worldPoint: hitPoint,
74
+ distance: t,
75
+ panel,
76
+ };
77
+ }
78
+
79
+ export function hitTestXRPanels(ray, panels = []) {
80
+ return panels
81
+ .map((panel) => hitTestXRPanel(ray, panel))
82
+ .filter(Boolean)
83
+ .sort((a, b) => a.distance - b.distance)[0] || null;
84
+ }
85
+
86
+ export function createXRPointerEvent(hit, input = {}, type = 'pointermove') {
87
+ if (!hit) return null;
88
+ let target = createXRPanelPointerTarget(hit, input);
89
+ return {
90
+ type,
91
+ source: input.source || 'xr-controller',
92
+ targetId: hit.panelId,
93
+ point: hit.point,
94
+ contentPoint: target.contentPoint,
95
+ contentViewport: target.contentViewport,
96
+ worldPoint: hit.worldPoint,
97
+ distance: hit.distance,
98
+ buttons: {
99
+ primary: Boolean(input.primary),
100
+ secondary: Boolean(input.secondary),
101
+ },
102
+ ray: input.ray || null,
103
+ };
104
+ }
105
+
106
+ export function createXRPanelPointerTarget(hit, options = {}) {
107
+ if (!hit) return null;
108
+ let panel = options.panel || hit.panel || {};
109
+ let point = {
110
+ x: clamp(numberOr(hit.point?.x, 0), 0, 1),
111
+ y: clamp(numberOr(hit.point?.y, 0), 0, 1),
112
+ };
113
+ let contentViewport = options.contentViewport || panel.contentViewport || createXRPanelContentViewport(panel);
114
+ return {
115
+ panelId: String(hit.panelId || panel.id || ''),
116
+ targetId: String(hit.panelId || panel.id || ''),
117
+ point,
118
+ contentPoint: {
119
+ x: roundMetric(point.x * contentViewport.width),
120
+ y: roundMetric(point.y * contentViewport.height),
121
+ },
122
+ contentViewport,
123
+ source: options.source || 'xr-controller',
124
+ };
125
+ }
126
+
127
+ export function createXRPointerHit(panel, point = {}, options = {}) {
128
+ if (!panel) return null;
129
+ let normalizedPoint = {
130
+ x: clamp(numberOr(point.x, 0), 0, 1),
131
+ y: clamp(numberOr(point.y, 0), 0, 1),
132
+ };
133
+ return {
134
+ panelId: panel.id,
135
+ point: normalizedPoint,
136
+ worldPoint: options.worldPoint || null,
137
+ distance: numberOr(options.distance, 0),
138
+ panel,
139
+ };
140
+ }
141
+
142
+ export function createXRPointerHitFromDomEvent(panel, element, event, options = {}) {
143
+ if (!panel || !element?.getBoundingClientRect || !event) return null;
144
+ let rect = element.getBoundingClientRect();
145
+ return createXRPointerHit(panel, {
146
+ x: (numberOr(event.clientX, rect.left) - rect.left) / Math.max(rect.width, 1),
147
+ y: (numberOr(event.clientY, rect.top) - rect.top) / Math.max(rect.height, 1),
148
+ }, options);
149
+ }
150
+
151
+ export function createXRPointerRayFromDomEvent(event, element, options = {}) {
152
+ if (!event || !element?.getBoundingClientRect) return null;
153
+ let rect = element.getBoundingClientRect();
154
+ let width = Math.max(numberOr(rect.width, 0), 1);
155
+ let height = Math.max(numberOr(rect.height, 0), 1);
156
+ let normalizedX = (numberOr(event.clientX, rect.left) - rect.left) / width - 0.5;
157
+ let normalizedY = 0.5 - (numberOr(event.clientY, rect.top) - rect.top) / height;
158
+ let horizontalMeters = numberOr(options.horizontalMeters, 1.4);
159
+ let verticalMeters = numberOr(options.verticalMeters, 0.72);
160
+ let eyeHeight = numberOr(options.eyeHeight, 1.32);
161
+ let horizontalSkew = numberOr(options.horizontalSkew, 0.28);
162
+ let verticalSkew = numberOr(options.verticalSkew, 0.18);
163
+
164
+ return {
165
+ version: 'xr-dom-pointer-ray-v1',
166
+ source: options.source || 'dom-pointer',
167
+ origin: [
168
+ roundMetric(normalizedX * horizontalMeters),
169
+ roundMetric(eyeHeight + normalizedY * verticalMeters),
170
+ numberOr(options.originZ, 0),
171
+ ],
172
+ direction: normalize([
173
+ -normalizedX * horizontalSkew,
174
+ -normalizedY * verticalSkew,
175
+ -1,
176
+ ]),
177
+ normalized: {
178
+ x: roundMetric(normalizedX + 0.5),
179
+ y: roundMetric(0.5 - normalizedY),
180
+ },
181
+ };
182
+ }
183
+
184
+ export function normalizeXRInputRay(inputSource, frame, referenceSpace) {
185
+ let pose = frame?.getPose?.(inputSource?.targetRaySpace, referenceSpace);
186
+ let transform = pose?.transform;
187
+ if (!transform) return null;
188
+ let matrix = transform.matrix;
189
+ if (Array.isArray(matrix) || ArrayBuffer.isView(matrix)) {
190
+ return {
191
+ origin: [matrix[12], matrix[13], matrix[14]],
192
+ direction: normalize([-matrix[8], -matrix[9], -matrix[10]]),
193
+ };
194
+ }
195
+ return null;
196
+ }
197
+
198
+ function inputSourceKind(inputSource = {}) {
199
+ let mode = inputSource.targetRayMode || '';
200
+ let profiles = Array.isArray(inputSource.profiles) ? inputSource.profiles.join(' ') : '';
201
+ if (inputSource.hand) return 'hand';
202
+ if (mode === 'gaze') return 'gaze';
203
+ if (mode === 'screen') return 'screen';
204
+ if (/hand|pinch/i.test(profiles)) return 'hand';
205
+ return 'controller';
206
+ }
207
+
208
+ export function createXRInputSourceSummary(inputSource = {}, options = {}) {
209
+ let profiles = Array.isArray(inputSource.profiles) ? [...inputSource.profiles].map(String) : [];
210
+ return {
211
+ version: 'xr-input-source-summary-v1',
212
+ id: options.id || inputSource.id || null,
213
+ handedness: inputSource.handedness || 'none',
214
+ targetRayMode: inputSource.targetRayMode || null,
215
+ kind: inputSourceKind(inputSource),
216
+ primary: options.primary === true,
217
+ profiles,
218
+ capabilities: {
219
+ targetRay: Boolean(inputSource.targetRaySpace),
220
+ grip: Boolean(inputSource.gripSpace),
221
+ hand: Boolean(inputSource.hand),
222
+ gamepad: Boolean(inputSource.gamepad),
223
+ squeeze: Boolean(inputSource.gamepad || inputSource.profiles?.length),
224
+ },
225
+ };
226
+ }
227
+
228
+ function inputScore(summary, options = {}) {
229
+ let score = 0;
230
+ if (summary.targetRayMode === 'tracked-pointer') score += 40;
231
+ if (summary.kind === 'controller') score += options.preferHands ? 8 : 20;
232
+ if (summary.kind === 'hand') score += options.preferHands ? 24 : 12;
233
+ if (summary.handedness === (options.dominantHand || 'right')) score += 8;
234
+ if (summary.capabilities.targetRay) score += 8;
235
+ if (summary.capabilities.gamepad) score += 4;
236
+ return score;
237
+ }
238
+
239
+ export function selectPrimaryXRInputSource(inputSources = [], options = {}) {
240
+ let sources = [...inputSources].map((source, index) => ({
241
+ source,
242
+ summary: createXRInputSourceSummary(source, { id: source?.id || `input-${index}` }),
243
+ }));
244
+ let selected = sources
245
+ .map((item) => ({ ...item, score: inputScore(item.summary, options) }))
246
+ .sort((a, b) => b.score - a.score)[0] || null;
247
+ return {
248
+ version: 'xr-primary-input-source-v1',
249
+ selected: selected ? {
250
+ ...selected.summary,
251
+ primary: true,
252
+ score: selected.score,
253
+ } : null,
254
+ source: selected?.source || null,
255
+ summaries: sources.map((item) => item.summary),
256
+ reason: selected ? 'best-target-ray-score' : 'no-input-sources',
257
+ };
258
+ }