@softov/scena 0.1.2

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 (496) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/controls/command.d.ts +18 -0
  4. package/dist/controls/command.js +150 -0
  5. package/dist/controls/events.d.ts +3 -0
  6. package/dist/controls/events.js +43 -0
  7. package/dist/controls/index.d.ts +10 -0
  8. package/dist/controls/index.js +10 -0
  9. package/dist/controls/keybinding.d.ts +9 -0
  10. package/dist/controls/keybinding.js +94 -0
  11. package/dist/controls/layout.d.ts +16 -0
  12. package/dist/controls/layout.js +201 -0
  13. package/dist/controls/manifest.d.ts +21 -0
  14. package/dist/controls/manifest.js +219 -0
  15. package/dist/controls/session.d.ts +16 -0
  16. package/dist/controls/session.js +95 -0
  17. package/dist/controls/shell.d.ts +8 -0
  18. package/dist/controls/shell.js +48 -0
  19. package/dist/controls/storage/layout-local.d.ts +6 -0
  20. package/dist/controls/storage/layout-local.js +47 -0
  21. package/dist/controls/storage/session-local.d.ts +5 -0
  22. package/dist/controls/storage/session-local.js +38 -0
  23. package/dist/controls/when.d.ts +7 -0
  24. package/dist/controls/when.js +226 -0
  25. package/dist/converters/a2ui-v0.10.d.ts +23 -0
  26. package/dist/converters/a2ui-v0.10.js +136 -0
  27. package/dist/converters/index.d.ts +1 -0
  28. package/dist/converters/index.js +1 -0
  29. package/dist/core/backends/modus-backend.d.ts +13 -0
  30. package/dist/core/backends/modus-backend.js +115 -0
  31. package/dist/core/backends/storage-backend.d.ts +7 -0
  32. package/dist/core/backends/storage-backend.js +73 -0
  33. package/dist/core/backends/yjs-backend.d.ts +3 -0
  34. package/dist/core/backends/yjs-backend.js +44 -0
  35. package/dist/core/binding-resolver.d.ts +7 -0
  36. package/dist/core/binding-resolver.js +106 -0
  37. package/dist/core/component-graph.d.ts +6 -0
  38. package/dist/core/component-graph.js +77 -0
  39. package/dist/core/component-registry.d.ts +7 -0
  40. package/dist/core/component-registry.js +87 -0
  41. package/dist/core/computed-dsl.d.ts +2 -0
  42. package/dist/core/computed-dsl.js +267 -0
  43. package/dist/core/converter-registry.d.ts +2 -0
  44. package/dist/core/converter-registry.js +37 -0
  45. package/dist/core/disposable.d.ts +3 -0
  46. package/dist/core/disposable.js +19 -0
  47. package/dist/core/dynamic-resolver.d.ts +12 -0
  48. package/dist/core/dynamic-resolver.js +134 -0
  49. package/dist/core/index.d.ts +44 -0
  50. package/dist/core/index.js +48 -0
  51. package/dist/core/label.d.ts +2 -0
  52. package/dist/core/label.js +14 -0
  53. package/dist/core/mount-menu.d.ts +2 -0
  54. package/dist/core/mount-menu.js +23 -0
  55. package/dist/core/mount-surface.d.ts +15 -0
  56. package/dist/core/mount-surface.js +214 -0
  57. package/dist/core/path-resolver.d.ts +14 -0
  58. package/dist/core/path-resolver.js +169 -0
  59. package/dist/core/permissions.d.ts +2 -0
  60. package/dist/core/permissions.js +50 -0
  61. package/dist/core/reactive-store.d.ts +17 -0
  62. package/dist/core/reactive-store.js +496 -0
  63. package/dist/core/scena.d.ts +2 -0
  64. package/dist/core/scena.js +115 -0
  65. package/dist/core/surface-presentation.d.ts +6 -0
  66. package/dist/core/surface-presentation.js +12 -0
  67. package/dist/i18n/i18n-backend.d.ts +2 -0
  68. package/dist/i18n/i18n-backend.js +35 -0
  69. package/dist/i18n/registry.d.ts +32 -0
  70. package/dist/i18n/registry.js +200 -0
  71. package/dist/porta/Limen.css +50 -0
  72. package/dist/porta/Limen.d.ts +16 -0
  73. package/dist/porta/Limen.js +14 -0
  74. package/dist/porta/PortaContext.d.ts +10 -0
  75. package/dist/porta/PortaContext.js +20 -0
  76. package/dist/porta/PortaLock.css +14 -0
  77. package/dist/porta/PortaLock.d.ts +10 -0
  78. package/dist/porta/PortaLock.js +13 -0
  79. package/dist/porta/SigillumGate.d.ts +8 -0
  80. package/dist/porta/SigillumGate.js +7 -0
  81. package/dist/porta/commands.d.ts +3 -0
  82. package/dist/porta/commands.js +125 -0
  83. package/dist/porta/index.d.ts +18 -0
  84. package/dist/porta/index.js +22 -0
  85. package/dist/porta/login-form.block.d.ts +2 -0
  86. package/dist/porta/login-form.block.js +7 -0
  87. package/dist/porta/match.d.ts +2 -0
  88. package/dist/porta/match.js +20 -0
  89. package/dist/porta/provider.d.ts +59 -0
  90. package/dist/porta/provider.js +1 -0
  91. package/dist/porta/providers/examples/google-oauth.d.ts +6 -0
  92. package/dist/porta/providers/examples/google-oauth.js +21 -0
  93. package/dist/porta/providers/examples/index.d.ts +6 -0
  94. package/dist/porta/providers/examples/index.js +3 -0
  95. package/dist/porta/providers/examples/magic-link.d.ts +8 -0
  96. package/dist/porta/providers/examples/magic-link.js +31 -0
  97. package/dist/porta/providers/examples/sms-otp.d.ts +9 -0
  98. package/dist/porta/providers/examples/sms-otp.js +31 -0
  99. package/dist/porta/providers/password.d.ts +12 -0
  100. package/dist/porta/providers/password.js +46 -0
  101. package/dist/porta/register-blocks.d.ts +3 -0
  102. package/dist/porta/register-blocks.js +31 -0
  103. package/dist/porta/register.d.ts +15 -0
  104. package/dist/porta/register.js +31 -0
  105. package/dist/porta/sigillum.d.ts +19 -0
  106. package/dist/porta/sigillum.js +41 -0
  107. package/dist/react/DefaultShell.d.ts +1 -0
  108. package/dist/react/DefaultShell.js +37 -0
  109. package/dist/react/Scena.d.ts +9 -0
  110. package/dist/react/Scena.js +20 -0
  111. package/dist/react/ScenaProvider.d.ts +8 -0
  112. package/dist/react/ScenaProvider.js +13 -0
  113. package/dist/react/ShellSplitter.d.ts +13 -0
  114. package/dist/react/ShellSplitter.js +88 -0
  115. package/dist/react/SurfaceArea.d.ts +13 -0
  116. package/dist/react/SurfaceArea.js +75 -0
  117. package/dist/react/Translate.d.ts +8 -0
  118. package/dist/react/Translate.js +10 -0
  119. package/dist/react/ViewMount.d.ts +12 -0
  120. package/dist/react/ViewMount.js +268 -0
  121. package/dist/react/core.d.ts +6 -0
  122. package/dist/react/core.js +10 -0
  123. package/dist/react/hooks/useCommand.d.ts +1 -0
  124. package/dist/react/hooks/useCommand.js +6 -0
  125. package/dist/react/hooks/useI18n.d.ts +9 -0
  126. package/dist/react/hooks/useI18n.js +16 -0
  127. package/dist/react/hooks/useLabel.d.ts +2 -0
  128. package/dist/react/hooks/useLabel.js +19 -0
  129. package/dist/react/hooks/useLayout.d.ts +2 -0
  130. package/dist/react/hooks/useLayout.js +10 -0
  131. package/dist/react/hooks/useMounts.d.ts +2 -0
  132. package/dist/react/hooks/useMounts.js +17 -0
  133. package/dist/react/hooks/useStore.d.ts +3 -0
  134. package/dist/react/hooks/useStore.js +40 -0
  135. package/dist/react/hooks/useStoreLabel.d.ts +3 -0
  136. package/dist/react/hooks/useStoreLabel.js +25 -0
  137. package/dist/react/hooks/useSurfacePresentation.d.ts +4 -0
  138. package/dist/react/hooks/useSurfacePresentation.js +16 -0
  139. package/dist/react/index.d.ts +20 -0
  140. package/dist/react/index.js +20 -0
  141. package/dist/react/mount-context.d.ts +8 -0
  142. package/dist/react/mount-context.js +20 -0
  143. package/dist/react/useSurfaceResize.d.ts +39 -0
  144. package/dist/react/useSurfaceResize.js +135 -0
  145. package/dist/runtime/args-schema.d.ts +11 -0
  146. package/dist/runtime/args-schema.js +45 -0
  147. package/dist/runtime/builtins.d.ts +3 -0
  148. package/dist/runtime/builtins.js +193 -0
  149. package/dist/runtime/client-handlers.d.ts +3 -0
  150. package/dist/runtime/client-handlers.js +19 -0
  151. package/dist/runtime/in-memory-socket.d.ts +7 -0
  152. package/dist/runtime/in-memory-socket.js +53 -0
  153. package/dist/runtime/index.d.ts +10 -0
  154. package/dist/runtime/index.js +7 -0
  155. package/dist/runtime/layout-commands.d.ts +5 -0
  156. package/dist/runtime/layout-commands.js +548 -0
  157. package/dist/runtime/opener-commands.d.ts +17 -0
  158. package/dist/runtime/opener-commands.js +96 -0
  159. package/dist/runtime/surface-bridge.d.ts +15 -0
  160. package/dist/runtime/surface-bridge.js +116 -0
  161. package/dist/styles/bar.css +155 -0
  162. package/dist/styles/base.css +285 -0
  163. package/dist/styles/geometry.css +32 -0
  164. package/dist/styles/index.d.ts +29 -0
  165. package/dist/styles/index.js +109 -0
  166. package/dist/styles/scrollbar.css +45 -0
  167. package/dist/styles/surface.css +179 -0
  168. package/dist/styles/theme.css +3 -0
  169. package/dist/styles/themes/dark.css +30 -0
  170. package/dist/styles/themes/light.css +1 -0
  171. package/dist/styles/themes/tokens.css +222 -0
  172. package/dist/types/binding-resolver.d.ts +30 -0
  173. package/dist/types/binding-resolver.js +1 -0
  174. package/dist/types/colors.d.ts +7 -0
  175. package/dist/types/colors.js +59 -0
  176. package/dist/types/command.d.ts +62 -0
  177. package/dist/types/command.js +1 -0
  178. package/dist/types/component-graph.d.ts +72 -0
  179. package/dist/types/component-graph.js +40 -0
  180. package/dist/types/component-registry.d.ts +70 -0
  181. package/dist/types/component-registry.js +1 -0
  182. package/dist/types/converter-registry.d.ts +17 -0
  183. package/dist/types/converter-registry.js +1 -0
  184. package/dist/types/disposable.d.ts +3 -0
  185. package/dist/types/disposable.js +1 -0
  186. package/dist/types/events.d.ts +75 -0
  187. package/dist/types/events.js +1 -0
  188. package/dist/types/host.d.ts +50 -0
  189. package/dist/types/host.js +1 -0
  190. package/dist/types/index.d.ts +23 -0
  191. package/dist/types/index.js +23 -0
  192. package/dist/types/keybinding.d.ts +36 -0
  193. package/dist/types/keybinding.js +1 -0
  194. package/dist/types/label.d.ts +9 -0
  195. package/dist/types/label.js +1 -0
  196. package/dist/types/layout.d.ts +117 -0
  197. package/dist/types/layout.js +1 -0
  198. package/dist/types/manifest.d.ts +152 -0
  199. package/dist/types/manifest.js +1 -0
  200. package/dist/types/mount-menu.d.ts +8 -0
  201. package/dist/types/mount-menu.js +1 -0
  202. package/dist/types/mount-surface.d.ts +87 -0
  203. package/dist/types/mount-surface.js +1 -0
  204. package/dist/types/permissions.d.ts +18 -0
  205. package/dist/types/permissions.js +1 -0
  206. package/dist/types/query.d.ts +34 -0
  207. package/dist/types/query.js +7 -0
  208. package/dist/types/reactive-store.d.ts +39 -0
  209. package/dist/types/reactive-store.js +1 -0
  210. package/dist/types/scena.d.ts +53 -0
  211. package/dist/types/scena.js +1 -0
  212. package/dist/types/scope-backend.d.ts +16 -0
  213. package/dist/types/scope-backend.js +1 -0
  214. package/dist/types/session.d.ts +39 -0
  215. package/dist/types/session.js +1 -0
  216. package/dist/types/shell.d.ts +21 -0
  217. package/dist/types/shell.js +1 -0
  218. package/dist/types/when.d.ts +10 -0
  219. package/dist/types/when.js +1 -0
  220. package/dist/ui/_utils.d.ts +8 -0
  221. package/dist/ui/_utils.js +53 -0
  222. package/dist/ui/campus/CampusMappa.d.ts +18 -0
  223. package/dist/ui/campus/CampusMappa.js +106 -0
  224. package/dist/ui/campus/CampusNodus.d.ts +25 -0
  225. package/dist/ui/campus/CampusNodus.js +211 -0
  226. package/dist/ui/campus/CampusStratum.d.ts +13 -0
  227. package/dist/ui/campus/CampusStratum.js +20 -0
  228. package/dist/ui/campus/CampusVelum.d.ts +7 -0
  229. package/dist/ui/campus/CampusVelum.js +12 -0
  230. package/dist/ui/campus/CampusView.d.ts +27 -0
  231. package/dist/ui/campus/CampusView.js +420 -0
  232. package/dist/ui/campus/context.d.ts +13 -0
  233. package/dist/ui/campus/context.js +12 -0
  234. package/dist/ui/campus/index.d.ts +14 -0
  235. package/dist/ui/campus/index.js +11 -0
  236. package/dist/ui/campus/stratum-context.d.ts +2 -0
  237. package/dist/ui/campus/stratum-context.js +10 -0
  238. package/dist/ui/campus/types.d.ts +47 -0
  239. package/dist/ui/campus/types.js +4 -0
  240. package/dist/ui/chart/BarChart.css +53 -0
  241. package/dist/ui/chart/BarChart.d.ts +24 -0
  242. package/dist/ui/chart/BarChart.js +32 -0
  243. package/dist/ui/chart/Donut.css +15 -0
  244. package/dist/ui/chart/Donut.d.ts +15 -0
  245. package/dist/ui/chart/Donut.js +18 -0
  246. package/dist/ui/chart/LineChart.css +8 -0
  247. package/dist/ui/chart/LineChart.d.ts +24 -0
  248. package/dist/ui/chart/LineChart.js +29 -0
  249. package/dist/ui/chart/catalog.d.ts +2 -0
  250. package/dist/ui/chart/catalog.js +7 -0
  251. package/dist/ui/chart/index.d.ts +6 -0
  252. package/dist/ui/chart/index.js +4 -0
  253. package/dist/ui/control/Button.css +7 -0
  254. package/dist/ui/control/Button.d.ts +17 -0
  255. package/dist/ui/control/Button.js +17 -0
  256. package/dist/ui/control/CheckBox.css +16 -0
  257. package/dist/ui/control/CheckBox.d.ts +15 -0
  258. package/dist/ui/control/CheckBox.js +23 -0
  259. package/dist/ui/control/Checks.css +6 -0
  260. package/dist/ui/control/Checks.d.ts +9 -0
  261. package/dist/ui/control/Checks.js +10 -0
  262. package/dist/ui/control/ChoicePicker.css +16 -0
  263. package/dist/ui/control/ChoicePicker.d.ts +25 -0
  264. package/dist/ui/control/ChoicePicker.js +38 -0
  265. package/dist/ui/control/DateTimeInput.css +1 -0
  266. package/dist/ui/control/DateTimeInput.d.ts +22 -0
  267. package/dist/ui/control/DateTimeInput.js +41 -0
  268. package/dist/ui/control/LocaleToggle.css +38 -0
  269. package/dist/ui/control/LocaleToggle.d.ts +11 -0
  270. package/dist/ui/control/LocaleToggle.js +23 -0
  271. package/dist/ui/control/ReloadButton.d.ts +6 -0
  272. package/dist/ui/control/ReloadButton.js +9 -0
  273. package/dist/ui/control/Slider.css +2 -0
  274. package/dist/ui/control/Slider.d.ts +21 -0
  275. package/dist/ui/control/Slider.js +37 -0
  276. package/dist/ui/control/TextField.css +0 -0
  277. package/dist/ui/control/TextField.d.ts +25 -0
  278. package/dist/ui/control/TextField.js +36 -0
  279. package/dist/ui/control/_field-parts.css +102 -0
  280. package/dist/ui/control/_field-parts.d.ts +15 -0
  281. package/dist/ui/control/_field-parts.js +17 -0
  282. package/dist/ui/control/catalog.d.ts +2 -0
  283. package/dist/ui/control/catalog.js +10 -0
  284. package/dist/ui/control/index.d.ts +13 -0
  285. package/dist/ui/control/index.js +11 -0
  286. package/dist/ui/data/DataTable.d.ts +19 -0
  287. package/dist/ui/data/DataTable.js +20 -0
  288. package/dist/ui/data/Filter.d.ts +13 -0
  289. package/dist/ui/data/Filter.js +56 -0
  290. package/dist/ui/data/List.css +16 -0
  291. package/dist/ui/data/List.d.ts +15 -0
  292. package/dist/ui/data/List.js +18 -0
  293. package/dist/ui/data/Listable.css +245 -0
  294. package/dist/ui/data/Listable.d.ts +60 -0
  295. package/dist/ui/data/Listable.js +209 -0
  296. package/dist/ui/data/Pagination.d.ts +5 -0
  297. package/dist/ui/data/Pagination.js +43 -0
  298. package/dist/ui/data/Tree.css +118 -0
  299. package/dist/ui/data/Tree.d.ts +45 -0
  300. package/dist/ui/data/Tree.js +170 -0
  301. package/dist/ui/data/catalog.d.ts +2 -0
  302. package/dist/ui/data/catalog.js +7 -0
  303. package/dist/ui/data/index.d.ts +11 -0
  304. package/dist/ui/data/index.js +7 -0
  305. package/dist/ui/display/AddPlaceholder.d.ts +6 -0
  306. package/dist/ui/display/AddPlaceholder.js +24 -0
  307. package/dist/ui/display/Alert.css +42 -0
  308. package/dist/ui/display/Alert.d.ts +14 -0
  309. package/dist/ui/display/Alert.js +13 -0
  310. package/dist/ui/display/Avatar.css +19 -0
  311. package/dist/ui/display/Avatar.d.ts +16 -0
  312. package/dist/ui/display/Avatar.js +52 -0
  313. package/dist/ui/display/Badge.css +26 -0
  314. package/dist/ui/display/Badge.d.ts +16 -0
  315. package/dist/ui/display/Badge.js +9 -0
  316. package/dist/ui/display/Card.css +15 -0
  317. package/dist/ui/display/Card.d.ts +11 -0
  318. package/dist/ui/display/Card.js +8 -0
  319. package/dist/ui/display/DetailContainer.d.ts +6 -0
  320. package/dist/ui/display/DetailContainer.js +7 -0
  321. package/dist/ui/display/DetailHeader.css +53 -0
  322. package/dist/ui/display/DetailHeader.d.ts +22 -0
  323. package/dist/ui/display/DetailHeader.js +8 -0
  324. package/dist/ui/display/DetailList.css +26 -0
  325. package/dist/ui/display/DetailList.d.ts +13 -0
  326. package/dist/ui/display/DetailList.js +5 -0
  327. package/dist/ui/display/DetailNotFound.d.ts +17 -0
  328. package/dist/ui/display/DetailNotFound.js +39 -0
  329. package/dist/ui/display/Divider.css +6 -0
  330. package/dist/ui/display/Divider.d.ts +8 -0
  331. package/dist/ui/display/Divider.js +16 -0
  332. package/dist/ui/display/Divisor.css +30 -0
  333. package/dist/ui/display/Divisor.d.ts +11 -0
  334. package/dist/ui/display/Divisor.js +43 -0
  335. package/dist/ui/display/Icon.css +10 -0
  336. package/dist/ui/display/Icon.d.ts +14 -0
  337. package/dist/ui/display/Icon.js +144 -0
  338. package/dist/ui/display/Image.css +1 -0
  339. package/dist/ui/display/Image.d.ts +16 -0
  340. package/dist/ui/display/Image.js +19 -0
  341. package/dist/ui/display/Markdown.css +38 -0
  342. package/dist/ui/display/Markdown.d.ts +8 -0
  343. package/dist/ui/display/Markdown.js +140 -0
  344. package/dist/ui/display/Progress.css +39 -0
  345. package/dist/ui/display/Progress.d.ts +15 -0
  346. package/dist/ui/display/Progress.js +17 -0
  347. package/dist/ui/display/SectionTitle.d.ts +6 -0
  348. package/dist/ui/display/SectionTitle.js +6 -0
  349. package/dist/ui/display/Skeleton.css +20 -0
  350. package/dist/ui/display/Skeleton.d.ts +11 -0
  351. package/dist/ui/display/Skeleton.js +11 -0
  352. package/dist/ui/display/Spinner.css +20 -0
  353. package/dist/ui/display/Spinner.d.ts +12 -0
  354. package/dist/ui/display/Spinner.js +12 -0
  355. package/dist/ui/display/Svg.d.ts +13 -0
  356. package/dist/ui/display/Svg.js +17 -0
  357. package/dist/ui/display/Text.css +20 -0
  358. package/dist/ui/display/Text.d.ts +13 -0
  359. package/dist/ui/display/Text.js +15 -0
  360. package/dist/ui/display/catalog.d.ts +2 -0
  361. package/dist/ui/display/catalog.js +12 -0
  362. package/dist/ui/display/index.d.ts +22 -0
  363. package/dist/ui/display/index.js +14 -0
  364. package/dist/ui/embed/HtmlEmbed.d.ts +12 -0
  365. package/dist/ui/embed/HtmlEmbed.js +16 -0
  366. package/dist/ui/embed/index.d.ts +2 -0
  367. package/dist/ui/embed/index.js +1 -0
  368. package/dist/ui/forms/DangerZone.d.ts +8 -0
  369. package/dist/ui/forms/DangerZone.js +31 -0
  370. package/dist/ui/forms/Field.css +15 -0
  371. package/dist/ui/forms/Field.d.ts +28 -0
  372. package/dist/ui/forms/Field.js +25 -0
  373. package/dist/ui/forms/FieldGroup.css +15 -0
  374. package/dist/ui/forms/FieldGroup.d.ts +7 -0
  375. package/dist/ui/forms/FieldGroup.js +7 -0
  376. package/dist/ui/forms/Form.css +14 -0
  377. package/dist/ui/forms/Form.d.ts +18 -0
  378. package/dist/ui/forms/Form.js +17 -0
  379. package/dist/ui/forms/FormActions.css +11 -0
  380. package/dist/ui/forms/FormActions.d.ts +18 -0
  381. package/dist/ui/forms/FormActions.js +10 -0
  382. package/dist/ui/forms/FormSection.css +19 -0
  383. package/dist/ui/forms/FormSection.d.ts +7 -0
  384. package/dist/ui/forms/FormSection.js +7 -0
  385. package/dist/ui/forms/LoginForm.css +105 -0
  386. package/dist/ui/forms/LoginForm.d.ts +12 -0
  387. package/dist/ui/forms/LoginForm.js +137 -0
  388. package/dist/ui/forms/SchemaForm.css +81 -0
  389. package/dist/ui/forms/SchemaForm.d.ts +26 -0
  390. package/dist/ui/forms/SchemaForm.js +186 -0
  391. package/dist/ui/forms/SettingsContainer.d.ts +8 -0
  392. package/dist/ui/forms/SettingsContainer.js +8 -0
  393. package/dist/ui/forms/catalog.d.ts +2 -0
  394. package/dist/ui/forms/catalog.js +11 -0
  395. package/dist/ui/forms/index.d.ts +19 -0
  396. package/dist/ui/forms/index.js +12 -0
  397. package/dist/ui/forms/schema-walk.d.ts +50 -0
  398. package/dist/ui/forms/schema-walk.js +116 -0
  399. package/dist/ui/index.d.ts +58 -0
  400. package/dist/ui/index.js +63 -0
  401. package/dist/ui/layout/BarLayout.d.ts +2 -0
  402. package/dist/ui/layout/BarLayout.js +18 -0
  403. package/dist/ui/layout/Column.css +6 -0
  404. package/dist/ui/layout/Column.d.ts +12 -0
  405. package/dist/ui/layout/Column.js +13 -0
  406. package/dist/ui/layout/FloatingLayout.d.ts +2 -0
  407. package/dist/ui/layout/FloatingLayout.js +12 -0
  408. package/dist/ui/layout/Grid.css +5 -0
  409. package/dist/ui/layout/Grid.d.ts +13 -0
  410. package/dist/ui/layout/Grid.js +19 -0
  411. package/dist/ui/layout/InlineLayout.d.ts +2 -0
  412. package/dist/ui/layout/InlineLayout.js +13 -0
  413. package/dist/ui/layout/MountTitle.d.ts +12 -0
  414. package/dist/ui/layout/MountTitle.js +42 -0
  415. package/dist/ui/layout/RailLayout.d.ts +2 -0
  416. package/dist/ui/layout/RailLayout.js +38 -0
  417. package/dist/ui/layout/Row.css +16 -0
  418. package/dist/ui/layout/Row.d.ts +13 -0
  419. package/dist/ui/layout/Row.js +21 -0
  420. package/dist/ui/layout/SectionHeader.d.ts +10 -0
  421. package/dist/ui/layout/SectionHeader.js +65 -0
  422. package/dist/ui/layout/SingleLayout.d.ts +2 -0
  423. package/dist/ui/layout/SingleLayout.js +25 -0
  424. package/dist/ui/layout/SpatialCard.d.ts +9 -0
  425. package/dist/ui/layout/SpatialCard.js +30 -0
  426. package/dist/ui/layout/SpatialLayout.d.ts +2 -0
  427. package/dist/ui/layout/SpatialLayout.js +446 -0
  428. package/dist/ui/layout/SplitLayout.d.ts +2 -0
  429. package/dist/ui/layout/SplitLayout.js +159 -0
  430. package/dist/ui/layout/Splitter.d.ts +12 -0
  431. package/dist/ui/layout/Splitter.js +64 -0
  432. package/dist/ui/layout/StackHeader.d.ts +9 -0
  433. package/dist/ui/layout/StackHeader.js +87 -0
  434. package/dist/ui/layout/StackLayout.d.ts +2 -0
  435. package/dist/ui/layout/StackLayout.js +137 -0
  436. package/dist/ui/layout/TabLayout.d.ts +2 -0
  437. package/dist/ui/layout/TabLayout.js +145 -0
  438. package/dist/ui/layout/TabPanelLayout.d.ts +20 -0
  439. package/dist/ui/layout/TabPanelLayout.js +615 -0
  440. package/dist/ui/layout/catalog.d.ts +2 -0
  441. package/dist/ui/layout/catalog.js +10 -0
  442. package/dist/ui/layout/index.d.ts +17 -0
  443. package/dist/ui/layout/index.js +17 -0
  444. package/dist/ui/layout/register.d.ts +3 -0
  445. package/dist/ui/layout/register.js +25 -0
  446. package/dist/ui/media/Audio.css +1 -0
  447. package/dist/ui/media/Audio.d.ts +12 -0
  448. package/dist/ui/media/Audio.js +8 -0
  449. package/dist/ui/media/Video.css +1 -0
  450. package/dist/ui/media/Video.d.ts +16 -0
  451. package/dist/ui/media/Video.js +9 -0
  452. package/dist/ui/media/catalog.d.ts +2 -0
  453. package/dist/ui/media/catalog.js +5 -0
  454. package/dist/ui/media/index.d.ts +2 -0
  455. package/dist/ui/media/index.js +3 -0
  456. package/dist/ui/menu/ActionList.css +262 -0
  457. package/dist/ui/menu/ActionList.d.ts +19 -0
  458. package/dist/ui/menu/ActionList.js +369 -0
  459. package/dist/ui/menu/ContainerMenu.d.ts +13 -0
  460. package/dist/ui/menu/ContainerMenu.js +57 -0
  461. package/dist/ui/menu/ContextMenu.d.ts +27 -0
  462. package/dist/ui/menu/ContextMenu.js +93 -0
  463. package/dist/ui/menu/index.d.ts +10 -0
  464. package/dist/ui/menu/index.js +7 -0
  465. package/dist/ui/menu/token.d.ts +22 -0
  466. package/dist/ui/menu/token.js +145 -0
  467. package/dist/ui/menu/useChatPicker.d.ts +24 -0
  468. package/dist/ui/menu/useChatPicker.js +210 -0
  469. package/dist/ui/navigation/Breadcrumb.css +30 -0
  470. package/dist/ui/navigation/Breadcrumb.d.ts +14 -0
  471. package/dist/ui/navigation/Breadcrumb.js +12 -0
  472. package/dist/ui/navigation/Link.css +13 -0
  473. package/dist/ui/navigation/Link.d.ts +18 -0
  474. package/dist/ui/navigation/Link.js +43 -0
  475. package/dist/ui/navigation/Tabs.css +39 -0
  476. package/dist/ui/navigation/Tabs.d.ts +18 -0
  477. package/dist/ui/navigation/Tabs.js +55 -0
  478. package/dist/ui/navigation/Toolbar.css +25 -0
  479. package/dist/ui/navigation/Toolbar.d.ts +12 -0
  480. package/dist/ui/navigation/Toolbar.js +21 -0
  481. package/dist/ui/navigation/catalog.d.ts +2 -0
  482. package/dist/ui/navigation/catalog.js +5 -0
  483. package/dist/ui/navigation/index.d.ts +8 -0
  484. package/dist/ui/navigation/index.js +5 -0
  485. package/dist/ui/overlay/Modal.css +35 -0
  486. package/dist/ui/overlay/Modal.d.ts +12 -0
  487. package/dist/ui/overlay/Modal.js +57 -0
  488. package/dist/ui/overlay/Popup.d.ts +12 -0
  489. package/dist/ui/overlay/Popup.js +74 -0
  490. package/dist/ui/overlay/catalog.d.ts +2 -0
  491. package/dist/ui/overlay/catalog.js +4 -0
  492. package/dist/ui/overlay/index.d.ts +1 -0
  493. package/dist/ui/overlay/index.js +2 -0
  494. package/dist/ui/register.d.ts +6 -0
  495. package/dist/ui/register.js +26 -0
  496. package/package.json +115 -0
@@ -0,0 +1,548 @@
1
+ import { combineDisposables } from '../core/disposable.js';
2
+ import { translate } from '../i18n/registry.js';
3
+ function joinAbs(root, rel) {
4
+ if (rel.startsWith('$/'))
5
+ return rel;
6
+ if (!root)
7
+ return (rel.startsWith('/') ? `$${rel}` : `$/${rel}`);
8
+ const base = root.endsWith('/') ? root.slice(0, -1) : root;
9
+ const tail = rel.startsWith('/') ? rel : `/${rel}`;
10
+ return `${base}${tail}`;
11
+ }
12
+ // Registers the mount-level / group-level / surface-level commands from
13
+ // COMMANDS-list.md. Layouts inject `$/tab/*`, `$/group/*`, `$/surface/*`
14
+ // capability paths when opening their context menus; these commands read
15
+ // from those paths to find their targets. `when` clauses gate visibility
16
+ // via commands.list({enabled: true}) — `when` is NOT checked at execute
17
+ // time (decoupled in controls/command.ts), so the run body must be its
18
+ // own last-mile guard if anything is required.
19
+ export function registerLayoutCommands(scena) {
20
+ const subs = [];
21
+ // ── Helpers ──────────────────────────────────────────────────────────────
22
+ function ctxTab(ctx) {
23
+ return ctx.store.get('$/tab/key');
24
+ }
25
+ function ctxGroup(ctx) {
26
+ return ctx.store.get('$/group/key');
27
+ }
28
+ function ctxSurface(ctx) {
29
+ return (ctx.store.get('$/surface/name') ?? 'main');
30
+ }
31
+ function ctxTabIndex(ctx) {
32
+ return Number(ctx.store.get('$/tab/index') ?? 0);
33
+ }
34
+ // Read the surface's current state from layout. Most commands operate
35
+ // through `surfaces.close` / `surfaces.open`; only group-level tree
36
+ // mutations need the layout tree directly.
37
+ function surfaceState(ctx) {
38
+ const surface = ctxSurface(ctx);
39
+ return ctx.scena.layout.get().surfaces[surface];
40
+ }
41
+ function setTabPanelTree(ctx, tree) {
42
+ const surface = ctxSurface(ctx);
43
+ const cur = ctx.scena.layout.get().surfaces[surface];
44
+ ctx.scena.layout.setSurface(surface, {
45
+ ...cur,
46
+ tabPanel: tree ? { tree } : undefined,
47
+ });
48
+ }
49
+ // Set of pinned mount keys on the right-clicked surface. Bulk-close
50
+ // commands (closeOthers, closeToRight, ...) filter through this so pinned
51
+ // tabs survive "close all but me"-style operations — matches VS Code.
52
+ function pinnedKeys(ctx) {
53
+ const cur = surfaceState(ctx);
54
+ return new Set(cur?.split?.pinned ?? []);
55
+ }
56
+ // Pin / unpin a tab on the right-clicked surface. Pinned mount keys live
57
+ // in `state.split.pinned` so they persist with the layout; layouts swap
58
+ // the × close affordance for a pin icon when a tab is in this set.
59
+ function togglePinned(ctx, key, pin) {
60
+ const surface = ctxSurface(ctx);
61
+ const cur = ctx.scena.layout.get().surfaces[surface];
62
+ const current = new Set(cur?.split?.pinned ?? []);
63
+ if (pin)
64
+ current.add(key);
65
+ else
66
+ current.delete(key);
67
+ ctx.scena.layout.setSurface(surface, {
68
+ ...cur,
69
+ split: { ...(cur?.split ?? {}), pinned: [...current] },
70
+ });
71
+ }
72
+ // Walk the tab-panel tree to find a leaf by id; returns the leaf + the
73
+ // ordered list of tab keys in it (so commands can find siblings).
74
+ function findLeaf(tree, leafId) {
75
+ if (tree.kind === 'leaf')
76
+ return tree.id === leafId ? tree : null;
77
+ return findLeaf(tree.first, leafId) ?? findLeaf(tree.second, leafId);
78
+ }
79
+ // Collect all leaves so commands like `tab.moveToGroup` can list them.
80
+ function collectLeaves(tree, out = []) {
81
+ if (tree.kind === 'leaf') {
82
+ out.push(tree);
83
+ return out;
84
+ }
85
+ collectLeaves(tree.first, out);
86
+ collectLeaves(tree.second, out);
87
+ return out;
88
+ }
89
+ function replaceLeaf(node, leafId, replacement) {
90
+ if (node.kind === 'leaf')
91
+ return node.id === leafId ? replacement : node;
92
+ return {
93
+ ...node,
94
+ first: replaceLeaf(node.first, leafId, replacement),
95
+ second: replaceLeaf(node.second, leafId, replacement),
96
+ };
97
+ }
98
+ function mapLeaves(node, fn) {
99
+ if (node.kind === 'leaf')
100
+ return fn(node);
101
+ const first = mapLeaves(node.first, fn);
102
+ const second = mapLeaves(node.second, fn);
103
+ if (first && second)
104
+ return { ...node, first, second };
105
+ return first ?? second;
106
+ }
107
+ function newLeafId() {
108
+ return `g_${Math.random().toString(36).slice(2, 10)}`;
109
+ }
110
+ // ── Mount-level commands ─────────────────────────────────────────────────
111
+ subs.push(scena.commands.register({
112
+ id: 'tab.close',
113
+ title: () => translate('tabs/close', 'Close tab'),
114
+ icon: '✕',
115
+ color: 'red',
116
+ slots: ['tab:context', 'card:context'],
117
+ // Pinned tabs hide the close affordance entirely — the menu shows
118
+ // Unpin instead, and the user must unpin first to close. Matches
119
+ // VS Code-style pin semantics.
120
+ when: '$/tab/isPinned == false',
121
+ run: (ctx) => {
122
+ const key = ctxTab(ctx);
123
+ if (!key)
124
+ return;
125
+ ctx.store.set(joinAbs(ctx.dataContext, '/lastClosed'), String(key));
126
+ ctx.surfaces.close(key);
127
+ ctx.host?.closeMenu();
128
+ },
129
+ }), scena.commands.register({
130
+ id: 'tab.closeOthers',
131
+ // title: 'Close others',
132
+ title: () => translate('tabs/closeOthers', 'Close others'),
133
+ icon: '−',
134
+ color: 'orange',
135
+ slots: ['tab:context'],
136
+ when: '$/group/tabCount > 1',
137
+ run: (ctx) => {
138
+ const key = ctxTab(ctx);
139
+ const leafId = ctx.store.get('$/tab/leafId');
140
+ if (!key)
141
+ return;
142
+ const keys = tabsInLeafOrSurface(ctx, leafId);
143
+ const pinned = pinnedKeys(ctx);
144
+ for (const k of keys)
145
+ if (k !== key && !pinned.has(k))
146
+ ctx.surfaces.close(k);
147
+ ctx.host?.closeMenu();
148
+ },
149
+ }), scena.commands.register({
150
+ id: 'tab.closeToRight',
151
+ title: () => translate('tabs/closeRight', 'Close to the right'),
152
+ icon: '⇥',
153
+ color: 'orange',
154
+ slots: ['tab:context'],
155
+ when: '$/group/tabCount > 1',
156
+ run: (ctx) => {
157
+ const key = ctxTab(ctx);
158
+ if (!key)
159
+ return;
160
+ const leafId = ctx.store.get('$/tab/leafId');
161
+ const keys = tabsInLeafOrSurface(ctx, leafId);
162
+ const idx = ctxTabIndex(ctx);
163
+ const pinned = pinnedKeys(ctx);
164
+ for (let i = idx + 1; i < keys.length; i++) {
165
+ const k = keys[i];
166
+ if (k && !pinned.has(k))
167
+ ctx.surfaces.close(k);
168
+ }
169
+ ctx.host?.closeMenu();
170
+ },
171
+ }), scena.commands.register({
172
+ id: 'tab.closeToLeft',
173
+ title: () => translate('tabs/closeLeft', 'Close to the left'),
174
+ icon: '⇤',
175
+ color: 'orange',
176
+ slots: ['tab:context'],
177
+ when: '$/group/tabCount > 1 && $/tab/index > 0',
178
+ run: (ctx) => {
179
+ const leafId = ctx.store.get('$/tab/leafId');
180
+ const keys = tabsInLeafOrSurface(ctx, leafId);
181
+ const idx = ctxTabIndex(ctx);
182
+ const pinned = pinnedKeys(ctx);
183
+ for (let i = 0; i < idx; i++) {
184
+ const k = keys[i];
185
+ if (k && !pinned.has(k))
186
+ ctx.surfaces.close(k);
187
+ }
188
+ ctx.host?.closeMenu();
189
+ },
190
+ }), scena.commands.register({
191
+ id: 'tab.pin',
192
+ title: () => translate('tabs/pin', 'Pin tab'),
193
+ icon: '📌︎',
194
+ color: 'amber',
195
+ slots: ['tab:context'],
196
+ // Only visible when this tab is NOT already pinned. The previous
197
+ // implementation wrote a global `/pinned = true` boolean via
198
+ // `joinAbs(ctx.dataContext, '/pinned')` — that affected nothing the
199
+ // layouts read. Per-tab pin state belongs in the surface's
200
+ // `state.split.pinned` array so it persists with the layout.
201
+ when: '$/tab/isPinned == false',
202
+ run: (ctx) => {
203
+ const key = ctxTab(ctx);
204
+ if (!key)
205
+ return;
206
+ togglePinned(ctx, key, true);
207
+ ctx.host?.closeMenu();
208
+ },
209
+ }), scena.commands.register({
210
+ id: 'tab.unpin',
211
+ title: () => translate('tabs/unpin', 'Unpin tab'),
212
+ icon: '📌︎',
213
+ color: 'amber',
214
+ slots: ['tab:context'],
215
+ when: '$/tab/isPinned == true',
216
+ run: (ctx) => {
217
+ const key = ctxTab(ctx);
218
+ if (!key)
219
+ return;
220
+ togglePinned(ctx, key, false);
221
+ ctx.host?.closeMenu();
222
+ },
223
+ }), scena.commands.register({
224
+ id: 'tab.copyKey',
225
+ title: () => translate('tabs/copyKey', 'Copy key'),
226
+ icon: '⎘',
227
+ color: 'sky',
228
+ slots: ['tab:context', 'card:context'],
229
+ run: (ctx) => {
230
+ const key = ctxTab(ctx);
231
+ if (!key)
232
+ return;
233
+ void navigator.clipboard?.writeText(key);
234
+ ctx.host?.closeMenu();
235
+ },
236
+ }), scena.commands.register({
237
+ id: 'tab.openToRight',
238
+ title: () => translate('tabs/openToRight', 'Open to the right'),
239
+ icon: '⇨',
240
+ color: 'blue',
241
+ slots: ['tab:context'],
242
+ when: '$/tab/canSplit == true',
243
+ run: (ctx) => splitTabIntoNewLeaf(ctx, 'right'),
244
+ }), scena.commands.register({
245
+ id: 'tab.openToBottom',
246
+ title: () => translate('tabs/openToBottom', 'Open to the bottom'),
247
+ icon: '⇩',
248
+ color: 'blue',
249
+ slots: ['tab:context'],
250
+ when: '$/tab/canSplit == true',
251
+ run: (ctx) => splitTabIntoNewLeaf(ctx, 'bottom'),
252
+ }), scena.commands.register({
253
+ id: 'tab.openToLeft',
254
+ title: () => translate('tabs/openToLeft', 'Open to the left'),
255
+ icon: '⇦',
256
+ color: 'blue',
257
+ slots: ['tab:context'],
258
+ when: '$/tab/canSplit == true',
259
+ run: (ctx) => splitTabIntoNewLeaf(ctx, 'left'),
260
+ }), scena.commands.register({
261
+ id: 'tab.openToTop',
262
+ title: () => translate('tabs/openToTop', 'Open to the top'),
263
+ icon: '⇧',
264
+ color: 'blue',
265
+ slots: ['tab:context'],
266
+ when: '$/tab/canSplit == true',
267
+ run: (ctx) => splitTabIntoNewLeaf(ctx, 'top'),
268
+ }));
269
+ // ── Group-level commands ─────────────────────────────────────────────────
270
+ subs.push(scena.commands.register({
271
+ id: 'group.closeAll',
272
+ title: () => translate('tabs/closeAll', 'Close all in group'),
273
+ icon: '✖',
274
+ color: 'red',
275
+ slots: ['tab:context', 'tab-group:context'],
276
+ when: '$/group/tabCount > 0',
277
+ run: (ctx) => {
278
+ const leafId = ctxGroup(ctx);
279
+ const keys = tabsInLeafOrSurface(ctx, leafId);
280
+ const pinned = pinnedKeys(ctx);
281
+ for (const k of keys)
282
+ if (!pinned.has(k))
283
+ ctx.surfaces.close(k);
284
+ ctx.host?.closeMenu();
285
+ },
286
+ }), scena.commands.register({
287
+ id: 'group.splitRight',
288
+ title: () => translate('tabs/splitRight', 'Split group right'),
289
+ icon: '⇨',
290
+ color: 'blue',
291
+ slots: ['tab-group:context'],
292
+ when: '$/tab/canSplit == true',
293
+ run: (ctx) => splitGroup(ctx, 'row', 'second'),
294
+ }), scena.commands.register({
295
+ id: 'group.splitDown',
296
+ title: () => translate('tabs/splitDown', 'Split group down'),
297
+ icon: '⇩',
298
+ color: 'blue',
299
+ slots: ['tab-group:context'],
300
+ when: '$/tab/canSplit == true',
301
+ run: (ctx) => splitGroup(ctx, 'column', 'second'),
302
+ }), scena.commands.register({
303
+ id: 'group.splitLeft',
304
+ title: () => translate('tabs/splitLeft', 'Split group left'),
305
+ icon: '⇦',
306
+ color: 'blue',
307
+ slots: ['tab-group:context'],
308
+ when: '$/tab/canSplit == true',
309
+ run: (ctx) => splitGroup(ctx, 'row', 'first'),
310
+ }), scena.commands.register({
311
+ id: 'group.splitUp',
312
+ title: () => translate('tabs/splitUp', 'Split group up'),
313
+ icon: '⇧',
314
+ color: 'blue',
315
+ slots: ['tab-group:context'],
316
+ when: '$/tab/canSplit == true',
317
+ run: (ctx) => splitGroup(ctx, 'column', 'first'),
318
+ }),
319
+ // scena.commands.register({
320
+ // id: 'group.closeOthersInGroup',
321
+ // title: () => translate('tabs/closeOthersInGroup', 'Close others (except active)'),
322
+ // icon: '✖',
323
+ // color: 'red',
324
+ // slots: ['tab-group:context'],
325
+ // when: '$/group/tabCount > 1',
326
+ // run: (ctx) => {
327
+ // const leafId = ctxGroup(ctx);
328
+ // const state = surfaceState(ctx);
329
+ // const activeKey = state?.activeContainerKey;
330
+ // const keys = tabsInLeafOrSurface(ctx, leafId);
331
+ // const pinned = pinnedKeys(ctx);
332
+ // for (const k of keys) if (k !== activeKey && !pinned.has(k)) ctx.surfaces.close(k);
333
+ // ctx.host?.closeMenu();
334
+ // },
335
+ // }),
336
+ scena.commands.register({
337
+ id: 'group.closeOtherGroups',
338
+ title: () => translate('tabs/closeOtherGroups', 'Close others (except active group)'),
339
+ icon: '✖',
340
+ color: 'red',
341
+ slots: ['tab-group:context'],
342
+ // when: '$/surface/layoutId == "tab-panel"',
343
+ run: (ctx) => {
344
+ const leafId = ctxGroup(ctx);
345
+ const state = surfaceState(ctx);
346
+ const tree = state?.tabPanel?.tree;
347
+ if (!tree || !leafId)
348
+ return;
349
+ const target = findLeaf(tree, leafId);
350
+ if (!target)
351
+ return;
352
+ // Close every tab not in target leaf, except pinned ones.
353
+ const keep = new Set(target.tabs);
354
+ const pinned = pinnedKeys(ctx);
355
+ const others = [];
356
+ for (const leaf of collectLeaves(tree)) {
357
+ if (leaf.id === leafId)
358
+ continue;
359
+ for (const k of leaf.tabs)
360
+ if (!keep.has(k) && !pinned.has(k))
361
+ others.push(k);
362
+ }
363
+ for (const k of others)
364
+ ctx.surfaces.close(k);
365
+ // Drop all other leaves from the tree. Pinned tabs in those leaves
366
+ // were skipped above and remain in the registry — they'll get
367
+ // re-homed into the surviving target leaf by TabPanelLayout's
368
+ // orphan-reattach logic on the next render.
369
+ const next = mapLeaves(tree, (l) => (l.id === leafId ? l : null));
370
+ setTabPanelTree(ctx, next);
371
+ ctx.host?.closeMenu();
372
+ },
373
+ }), scena.commands.register({
374
+ id: 'group.close',
375
+ title: () => translate('tabs/closeGroup', 'Close group'),
376
+ icon: '✖',
377
+ color: 'red',
378
+ slots: ['tab-group:context'],
379
+ when: '$/group/canClose == true',
380
+ run: (ctx) => {
381
+ const leafId = ctxGroup(ctx);
382
+ const state = surfaceState(ctx);
383
+ const tree = state?.tabPanel?.tree;
384
+ if (!tree || !leafId)
385
+ return;
386
+ const target = findLeaf(tree, leafId);
387
+ if (!target)
388
+ return;
389
+ const pinned = pinnedKeys(ctx);
390
+ for (const k of target.tabs)
391
+ if (!pinned.has(k))
392
+ ctx.surfaces.close(k);
393
+ const next = mapLeaves(tree, (l) => (l.id === leafId ? null : l));
394
+ setTabPanelTree(ctx, next);
395
+ ctx.host?.closeMenu();
396
+ },
397
+ }));
398
+ // ── Surface-level commands ───────────────────────────────────────────────
399
+ subs.push(scena.commands.register({
400
+ id: 'surface.closeAll',
401
+ title: () => translate('tabs/closeAll', 'Close all'),
402
+ icon: '✖',
403
+ color: 'red',
404
+ slots: ['surface:context'],
405
+ when: '$/surface/mountCount > 0',
406
+ run: (ctx) => {
407
+ const surface = ctxSurface(ctx);
408
+ const mounts = ctx.scena.surfaces.listAt(surface);
409
+ const pinned = pinnedKeys(ctx);
410
+ for (const m of mounts)
411
+ if (!pinned.has(m.key))
412
+ ctx.surfaces.close(m.key);
413
+ ctx.host?.closeMenu();
414
+ },
415
+ }), ...layoutSwitchCommands(scena));
416
+ // Prune stale entries from every surface's `state.split.pinned` whenever
417
+ // a mount closes — covers the bulk-close commands above AND any other
418
+ // close path (× button, programmatic close, etc.). Without this, closing
419
+ // a pinned tab leaves its key in the array, so re-opening the same key
420
+ // later would resurrect a phantom pinned state.
421
+ subs.push(scena.events.on('scena:mount:closed', (raw) => {
422
+ const ev = raw;
423
+ const layout = scena.layout.get();
424
+ for (const [surface, st] of Object.entries(layout.surfaces)) {
425
+ const pinned = st?.split?.pinned;
426
+ if (!pinned || !pinned.includes(ev.key))
427
+ continue;
428
+ scena.layout.setSurface(surface, {
429
+ ...st,
430
+ split: { ...st.split, pinned: pinned.filter((k) => k !== ev.key) },
431
+ });
432
+ }
433
+ }));
434
+ return combineDisposables(...subs);
435
+ // ── Internal helpers (closures over the helpers above) ───────────────────
436
+ function tabsInLeafOrSurface(ctx, leafId) {
437
+ const surface = ctxSurface(ctx);
438
+ if (leafId) {
439
+ const state = ctx.scena.layout.get().surfaces[surface];
440
+ const tree = state?.tabPanel?.tree;
441
+ if (tree) {
442
+ const leaf = findLeaf(tree, leafId);
443
+ if (leaf)
444
+ return [...leaf.tabs];
445
+ }
446
+ }
447
+ return ctx.scena.surfaces.listAt(surface).map((m) => m.key);
448
+ }
449
+ // For TabPanelLayout: split the right-clicked leaf, moving the right-clicked
450
+ // tab into a new sibling leaf on the given side. Wires the new leaf into
451
+ // the tree via replaceLeaf. The drag-drop equivalent of this lives in
452
+ // TabPanelLayout's splitLeafOnDrop — same shape.
453
+ function splitTabIntoNewLeaf(ctx, side) {
454
+ const key = ctxTab(ctx);
455
+ const leafId = ctx.store.get('$/tab/leafId');
456
+ if (!key || !leafId)
457
+ return;
458
+ const state = surfaceState(ctx);
459
+ const tree = state?.tabPanel?.tree;
460
+ if (!tree)
461
+ return;
462
+ const sourceLeaf = findLeaf(tree, leafId);
463
+ if (!sourceLeaf)
464
+ return;
465
+ // Remove the tab from its source leaf.
466
+ const remaining = sourceLeaf.tabs.filter((k) => k !== key);
467
+ const sourceUpdated = {
468
+ ...sourceLeaf,
469
+ tabs: remaining,
470
+ activeKey: remaining.includes(sourceLeaf.activeKey ?? '')
471
+ ? sourceLeaf.activeKey
472
+ : remaining[0],
473
+ };
474
+ const newLeaf = {
475
+ kind: 'leaf',
476
+ id: newLeafId(),
477
+ tabs: [key],
478
+ activeKey: key,
479
+ };
480
+ const direction = side === 'left' || side === 'right' ? 'row' : 'column';
481
+ const first = side === 'left' || side === 'top' ? newLeaf : sourceUpdated;
482
+ const second = side === 'left' || side === 'top' ? sourceUpdated : newLeaf;
483
+ const split = {
484
+ kind: 'split',
485
+ direction,
486
+ ratio: 0.5,
487
+ first,
488
+ second,
489
+ };
490
+ const next = replaceLeaf(tree, leafId, split);
491
+ setTabPanelTree(ctx, next);
492
+ ctx.host?.closeMenu();
493
+ }
494
+ // For TabPanelLayout: split the right-clicked GROUP (leaf), placing the
495
+ // existing leaf on one side and an empty leaf on the other.
496
+ function splitGroup(ctx, direction, newSide) {
497
+ const leafId = ctxGroup(ctx);
498
+ if (!leafId)
499
+ return;
500
+ const state = surfaceState(ctx);
501
+ const tree = state?.tabPanel?.tree;
502
+ if (!tree)
503
+ return;
504
+ const leaf = findLeaf(tree, leafId);
505
+ if (!leaf)
506
+ return;
507
+ const empty = {
508
+ kind: 'leaf',
509
+ id: newLeafId(),
510
+ tabs: [],
511
+ activeKey: undefined,
512
+ };
513
+ const split = {
514
+ kind: 'split',
515
+ direction,
516
+ ratio: 0.5,
517
+ first: newSide === 'first' ? empty : leaf,
518
+ second: newSide === 'first' ? leaf : empty,
519
+ };
520
+ const next = replaceLeaf(tree, leafId, split);
521
+ setTabPanelTree(ctx, next);
522
+ ctx.host?.closeMenu();
523
+ }
524
+ }
525
+ // Returns the layout-switch commands as a flat array so they can be spread
526
+ // into the main subs list.
527
+ function layoutSwitchCommands(scena) {
528
+ const targets = [
529
+ { id: 'surface.setLayout.tab', title: 'Layout: Tabs', layoutId: 'tab' },
530
+ { id: 'surface.setLayout.tabPanel', title: 'Layout: Tab groups', layoutId: 'tab-panel' },
531
+ { id: 'surface.setLayout.split', title: 'Layout: Split', layoutId: 'split' },
532
+ { id: 'surface.setLayout.spatial', title: 'Layout: Spatial', layoutId: 'spatial' },
533
+ { id: 'surface.setLayout.stack', title: 'Layout: Stack', layoutId: 'stack' },
534
+ { id: 'surface.setLayout.single', title: 'Layout: Single', layoutId: 'single' },
535
+ ];
536
+ return targets.map((t) => scena.commands.register({
537
+ id: t.id,
538
+ title: t.title,
539
+ slots: ['surface:context'],
540
+ when: `$/surface/layoutId != "${t.layoutId}"`,
541
+ run: (ctx) => {
542
+ const surface = (ctx.store.get('$/surface/name') ?? 'main');
543
+ const cur = ctx.scena.layout.get().surfaces[surface];
544
+ ctx.scena.layout.setSurface(surface, { ...cur, layout: t.layoutId });
545
+ ctx.host?.closeMenu();
546
+ },
547
+ }));
548
+ }
@@ -0,0 +1,17 @@
1
+ import type { Scena } from '../types/scena.js';
2
+ import type { Disposable } from '../types/disposable.js';
3
+ import type { CommandContext } from '../types/command.js';
4
+ import type { ComponentDefinition } from '../types/component-registry.js';
5
+ import type { SurfaceName } from '../types/mount-surface.js';
6
+ export interface RegisterOpenerCommandsOptions {
7
+ resourceKind: string;
8
+ slots?: string[];
9
+ category?: string;
10
+ commandIdPrefix?: string;
11
+ surface?: SurfaceName;
12
+ mountKey?: (resourceId: string, viewerComponent: string) => string;
13
+ resourceProps?: (resourceId: string) => Record<string, unknown>;
14
+ resourceTitle?: (resourceId: string) => string;
15
+ onOpen?: (ctx: CommandContext, viewer: ComponentDefinition, resourceId: string) => void;
16
+ }
17
+ export declare function registerOpenerCommands(scena: Scena, opts: RegisterOpenerCommandsOptions): Disposable;
@@ -0,0 +1,96 @@
1
+ import { disposableFrom } from '../core/disposable.js';
2
+ import { resolveLabel } from '../core/label.js';
3
+ import { translate } from '../i18n/registry.js';
4
+ export function registerOpenerCommands(scena, opts) {
5
+ const slots = opts.slots ?? ['file:context'];
6
+ const category = opts.category ?? 'Open with';
7
+ const surface = opts.surface ?? 'main';
8
+ const mountKey = opts.mountKey ?? ((id, comp) => `${opts.resourceKind}:${comp}:${id}`);
9
+ const commandIdPrefix = opts.commandIdPrefix ?? `openWith.${opts.resourceKind}`;
10
+ const resourceProps = opts.resourceProps ?? ((id) => ({ path: id }));
11
+ const resourceTitle = opts.resourceTitle ?? ((id) => id);
12
+ const kindGuard = `$/resource/kind == "${opts.resourceKind}"`;
13
+ // viewer component id → disposable of its generated command. Used both
14
+ // to dispose departed viewers' commands and to skip re-registering ones
15
+ // that are still around on resync.
16
+ const active = new Map();
17
+ function registerFor(def) {
18
+ // ComponentOpensSpec.selector is a WhenClause (`/...` paths). The when
19
+ // engine reads `$/...` paths, so we rewrite the leading `/` to `$/` if
20
+ // the caller didn't already use the absolute form. A prior version
21
+ // also rewrote later embedded paths but accidentally double-prefixed
22
+ // the leading `$/` to `$$/`, which the when parser couldn't resolve —
23
+ // breaking single-path selectors like `/resource/ext == "md"`.
24
+ const sel = def.opens?.selector;
25
+ const selWhen = typeof sel === 'string' ? (sel.startsWith('$/') ? sel : sel.replace(/^\//, '$/')) : null;
26
+ const when = selWhen ? `${kindGuard} && ${selWhen}` : kindGuard;
27
+ const sub = scena.commands.register({
28
+ id: `${commandIdPrefix}.${def.component}`,
29
+ title: resolveLabel(def.opens?.title ?? def.component, {
30
+ get: (path) => {
31
+ // Expose the resource's properties for label resolution (e.g.
32
+ // `{ path }` bindings in the title can read `$/resource/path`).
33
+ if (path.startsWith('$/'))
34
+ return scena.store.get(path);
35
+ return undefined;
36
+ },
37
+ translate: (key) => translate(key),
38
+ }) ?? def.component,
39
+ icon: def.opens?.icon,
40
+ color: def.opens?.color,
41
+ category,
42
+ slots,
43
+ when,
44
+ run: (ctx) => {
45
+ const id = ctx.store.get('$/resource/id');
46
+ if (!id)
47
+ return;
48
+ ctx.scena.surfaces.open({
49
+ surface,
50
+ key: mountKey(id, def.component),
51
+ resource: { component: def.component, ...resourceProps(id) },
52
+ // Tab/section header gets the resource's display name + the
53
+ // viewer's own icon/color so e.g. a Markdown-opened .md file
54
+ // tab shows the filename in violet with the memo glyph.
55
+ props: {
56
+ title: resourceTitle(id),
57
+ icon: def.opens?.icon,
58
+ color: def.opens?.color,
59
+ },
60
+ });
61
+ opts.onOpen?.(ctx, def, id);
62
+ ctx.host?.closeMenu();
63
+ },
64
+ });
65
+ active.set(def.component, sub);
66
+ }
67
+ function sync() {
68
+ const present = new Set();
69
+ for (const def of scena.components.findOpeners(opts.resourceKind)) {
70
+ present.add(def.component);
71
+ if (active.has(def.component))
72
+ continue; // already registered
73
+ registerFor(def);
74
+ }
75
+ // Dispose commands for viewers no longer present.
76
+ for (const [comp, sub] of active) {
77
+ if (present.has(comp))
78
+ continue;
79
+ sub.dispose();
80
+ active.delete(comp);
81
+ }
82
+ }
83
+ // Initial pass, then subscribe to component-registry changes.
84
+ sync();
85
+ const sub = scena.events.on('scena:registry:changed', (payload) => {
86
+ if (payload.registry !== 'components')
87
+ return;
88
+ sync();
89
+ });
90
+ return disposableFrom(() => {
91
+ sub.dispose();
92
+ for (const s of active.values())
93
+ s.dispose();
94
+ active.clear();
95
+ });
96
+ }
@@ -0,0 +1,15 @@
1
+ import type { Disposable } from '../types/disposable.js';
2
+ import type { EventBus } from '../types/events.js';
3
+ import type { ReactiveStore, SocketBridge } from '../types/reactive-store.js';
4
+ import type { MountSurfaceRegistry, SurfaceName } from '../types/mount-surface.js';
5
+ import type { ConverterRegistry } from '../types/converter-registry.js';
6
+ interface Deps {
7
+ store: ReactiveStore;
8
+ surfaces: MountSurfaceRegistry;
9
+ converters: ConverterRegistry;
10
+ socket: SocketBridge;
11
+ events: EventBus;
12
+ defaultSurface?: SurfaceName;
13
+ }
14
+ export declare function createSurfaceBridge(deps: Deps): Disposable;
15
+ export {};