@cosmicdrift/kumiko-renderer 0.374.0 → 0.377.0

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 (455) hide show
  1. package/README.md +2 -2
  2. package/dist/app/action-form-support.d.ts +9 -2
  3. package/dist/app/action-form-support.js +24 -2
  4. package/dist/app/client-actions.d.ts +2 -2
  5. package/dist/app/column-renderers.js +1 -1
  6. package/dist/app/config-edit-shim.js +1 -1
  7. package/dist/app/content-preview.js +1 -6
  8. package/dist/app/custom-screens.js +1 -1
  9. package/dist/app/dashboard-body.d.ts +1 -1
  10. package/dist/app/entity-list-expanded-row.d.ts +1 -1
  11. package/dist/app/form-schema.d.ts +1 -1
  12. package/dist/app/form-schema.js +2 -2
  13. package/dist/app/kumiko-screen.d.ts +1 -1
  14. package/dist/app/kumiko-screen.js +7 -6
  15. package/dist/app/list-empty-content.d.ts +1 -1
  16. package/dist/app/list-facets.d.ts +1 -1
  17. package/dist/app/nav.js +2 -2
  18. package/dist/app/query-state-banners.d.ts +1 -1
  19. package/dist/app/reference-link.d.ts +1 -1
  20. package/dist/app/row-actions.d.ts +1 -1
  21. package/dist/app/secret-mint-body.d.ts +1 -1
  22. package/dist/app/secrets-edit-body.d.ts +1 -1
  23. package/dist/app/use-record-actions.d.ts +1 -1
  24. package/dist/app/write-failed-error.d.ts +1 -1
  25. package/dist/components/availability-options-select.d.ts +1 -1
  26. package/dist/components/embedded-list-field.d.ts +1 -1
  27. package/dist/components/embedded-list-field.js +1 -1
  28. package/dist/components/file-list-field.d.ts +1 -1
  29. package/dist/components/grid-cell-for-field.d.ts +1 -1
  30. package/dist/components/multi-select-checkboxes.d.ts +1 -1
  31. package/dist/components/multi-select-checkboxes.js +1 -1
  32. package/dist/components/query-options-select.d.ts +1 -1
  33. package/dist/components/related-list-section.d.ts +1 -1
  34. package/dist/components/render-edit-logic.d.ts +1 -1
  35. package/dist/components/render-edit-types.d.ts +2 -2
  36. package/dist/components/render-edit.d.ts +1 -1
  37. package/dist/components/render-edit.js +4 -2
  38. package/dist/components/render-field.d.ts +1 -1
  39. package/dist/components/render-field.js +4 -1
  40. package/dist/components/render-list.d.ts +1 -1
  41. package/dist/components/render-list.js +20 -4
  42. package/dist/components/write-form-section.d.ts +1 -1
  43. package/dist/components/write-form-section.js +1 -1
  44. package/dist/context/dispatcher-context.d.ts +1 -1
  45. package/dist/format-when.js +1 -1
  46. package/dist/headless/apex/css.d.ts +3 -0
  47. package/dist/headless/apex/css.js +298 -0
  48. package/dist/headless/apex/index.d.ts +236 -0
  49. package/dist/headless/apex/index.js +377 -0
  50. package/dist/headless/apex/lightbox.d.ts +4 -0
  51. package/dist/headless/apex/lightbox.js +81 -0
  52. package/dist/headless/contracts/asset.d.ts +15 -0
  53. package/dist/headless/contracts/asset.js +17 -0
  54. package/dist/headless/contracts/index.d.ts +3 -0
  55. package/dist/headless/contracts/index.js +1 -0
  56. package/dist/headless/contracts/locale.d.ts +7 -0
  57. package/dist/headless/contracts/locale.js +11 -0
  58. package/dist/headless/contracts/primitives.d.ts +93 -0
  59. package/dist/headless/contracts/primitives.js +21 -0
  60. package/dist/headless/dispatcher/index.d.ts +2 -0
  61. package/dist/headless/dispatcher/index.js +1 -0
  62. package/dist/headless/dispatcher/types.d.ts +86 -0
  63. package/dist/headless/dispatcher/types.js +34 -0
  64. package/dist/headless/form/form-controller.d.ts +2 -0
  65. package/dist/headless/form/form-controller.js +409 -0
  66. package/dist/headless/form/index.d.ts +3 -0
  67. package/dist/headless/form/index.js +2 -0
  68. package/dist/headless/form/types.d.ts +76 -0
  69. package/dist/headless/form/types.js +1 -0
  70. package/dist/headless/form/zod-bridge.d.ts +10 -0
  71. package/dist/headless/form/zod-bridge.js +103 -0
  72. package/dist/headless/format/document-values.d.ts +12 -0
  73. package/dist/headless/format/document-values.js +70 -0
  74. package/dist/headless/format/escape.d.ts +5 -0
  75. package/dist/headless/format/escape.js +75 -0
  76. package/dist/headless/format/html-template.d.ts +9 -0
  77. package/dist/headless/format/html-template.js +39 -0
  78. package/dist/headless/format/index.d.ts +12 -0
  79. package/dist/headless/format/index.js +237 -0
  80. package/dist/headless/format/money.d.ts +3 -0
  81. package/dist/headless/format/money.js +39 -0
  82. package/dist/headless/index.d.ts +14 -0
  83. package/dist/headless/index.js +9 -0
  84. package/dist/headless/locale-routing/index.d.ts +41 -0
  85. package/dist/headless/locale-routing/index.js +145 -0
  86. package/dist/headless/nav/index.d.ts +2 -0
  87. package/dist/headless/nav/index.js +1 -0
  88. package/dist/headless/nav/resolve.d.ts +2 -0
  89. package/dist/headless/nav/resolve.js +71 -0
  90. package/dist/headless/nav/types.d.ts +26 -0
  91. package/dist/headless/nav/types.js +1 -0
  92. package/dist/headless/store/create-store.d.ts +2 -0
  93. package/dist/headless/store/create-store.js +43 -0
  94. package/dist/headless/store/equality.d.ts +1 -0
  95. package/dist/headless/store/equality.js +34 -0
  96. package/dist/headless/store/index.d.ts +3 -0
  97. package/dist/headless/store/index.js +2 -0
  98. package/dist/headless/store/types.d.ts +7 -0
  99. package/dist/headless/store/types.js +10 -0
  100. package/dist/headless/view-model/edit.d.ts +18 -0
  101. package/dist/headless/view-model/edit.js +501 -0
  102. package/dist/headless/view-model/embedded-list.d.ts +27 -0
  103. package/dist/headless/view-model/embedded-list.js +63 -0
  104. package/dist/headless/view-model/index.d.ts +7 -0
  105. package/dist/headless/view-model/index.js +3 -0
  106. package/dist/headless/view-model/list-file-display.d.ts +3 -0
  107. package/dist/headless/view-model/list-file-display.js +27 -0
  108. package/dist/headless/view-model/list.d.ts +16 -0
  109. package/dist/headless/view-model/list.js +229 -0
  110. package/dist/headless/view-model/types.d.ts +382 -0
  111. package/dist/headless/view-model/types.js +1 -0
  112. package/dist/hooks/use-ai-text.d.ts +1 -1
  113. package/dist/hooks/use-form.d.ts +1 -1
  114. package/dist/hooks/use-form.js +1 -1
  115. package/dist/hooks/use-mutation.d.ts +1 -1
  116. package/dist/hooks/use-query.d.ts +1 -1
  117. package/dist/hooks/use-store.d.ts +1 -1
  118. package/dist/hooks/use-store.js +7 -7
  119. package/dist/hooks/use-stream-handler.d.ts +1 -1
  120. package/dist/i18n.d.ts +1 -1
  121. package/dist/i18n.js +1 -1
  122. package/dist/index.js +4 -4
  123. package/dist/live/csrf.d.ts +2 -0
  124. package/dist/live/csrf.js +60 -0
  125. package/dist/live/dispatcher-live.d.ts +7 -0
  126. package/dist/live/dispatcher-live.js +286 -0
  127. package/dist/live/envelopes.d.ts +101 -0
  128. package/dist/live/envelopes.js +35 -0
  129. package/dist/live/error-mapping.d.ts +16 -0
  130. package/dist/live/error-mapping.js +82 -0
  131. package/dist/live/index.d.ts +7 -0
  132. package/dist/live/index.js +5 -0
  133. package/dist/live/locale.d.ts +2 -0
  134. package/dist/live/locale.js +25 -0
  135. package/dist/live/sse-stream.d.ts +14 -0
  136. package/dist/live/sse-stream.js +128 -0
  137. package/dist/primitives.d.ts +35 -36
  138. package/dist/primitives.js +1 -1
  139. package/dist/session/session-ended.d.ts +1 -1
  140. package/dist/tokens.d.ts +3 -4
  141. package/dist/tokens.js +4 -5
  142. package/dist/web/app/app-schema-boot.d.ts +19 -0
  143. package/dist/web/app/app-schema-boot.js +124 -0
  144. package/dist/web/app/browser-locale.d.ts +22 -0
  145. package/dist/web/app/browser-locale.js +78 -0
  146. package/dist/web/app/client-plugin.d.ts +106 -0
  147. package/dist/web/app/client-plugin.js +12 -0
  148. package/dist/web/app/create-app.d.ts +104 -0
  149. package/dist/web/app/create-app.js +498 -0
  150. package/dist/web/app/create-public-surface.d.ts +36 -0
  151. package/dist/web/app/create-public-surface.js +40 -0
  152. package/dist/web/app/dashboard-body.d.ts +3 -0
  153. package/dist/web/app/dashboard-body.js +865 -0
  154. package/dist/web/app/dashboard-refresh.d.ts +6 -0
  155. package/dist/web/app/dashboard-refresh.js +15 -0
  156. package/dist/web/app/document-lang-sync.d.ts +4 -0
  157. package/dist/web/app/document-lang-sync.js +27 -0
  158. package/dist/web/app/draft-storage.d.ts +11 -0
  159. package/dist/web/app/draft-storage.js +53 -0
  160. package/dist/web/app/nav-providers-context.d.ts +13 -0
  161. package/dist/web/app/nav-providers-context.js +18 -0
  162. package/dist/web/app/nav.d.ts +40 -0
  163. package/dist/web/app/nav.js +231 -0
  164. package/dist/web/app/plain-content-editor.d.ts +3 -0
  165. package/dist/web/app/plain-content-editor.js +30 -0
  166. package/dist/web/app/relative-time.d.ts +10 -0
  167. package/dist/web/app/relative-time.js +56 -0
  168. package/dist/web/app/resolvers-context.d.ts +12 -0
  169. package/dist/web/app/resolvers-context.js +10 -0
  170. package/dist/web/app/rich-content-editor.d.ts +3 -0
  171. package/dist/web/app/rich-content-editor.js +15 -0
  172. package/dist/web/app/root-error-fallback.d.ts +2 -0
  173. package/dist/web/app/root-error-fallback.js +21 -0
  174. package/dist/web/app/screen-error-fallback.d.ts +6 -0
  175. package/dist/web/app/screen-error-fallback.js +17 -0
  176. package/dist/web/app/tiptap-editor.d.ts +3 -0
  177. package/dist/web/app/tiptap-editor.js +120 -0
  178. package/dist/web/app/toast-action-notifier.d.ts +5 -0
  179. package/dist/web/app/toast-action-notifier.js +17 -0
  180. package/dist/web/components/config-cascade.d.ts +13 -0
  181. package/dist/web/components/config-cascade.js +155 -0
  182. package/dist/web/components/config-source-badge.d.ts +6 -0
  183. package/dist/web/components/config-source-badge.js +55 -0
  184. package/dist/web/default-color-scheme.d.ts +2 -0
  185. package/dist/web/default-color-scheme.js +3 -0
  186. package/dist/web/icons.d.ts +94 -0
  187. package/dist/web/icons.js +100 -0
  188. package/dist/web/index.d.ts +78 -0
  189. package/dist/web/index.js +53 -0
  190. package/dist/web/layout/app-layout.d.ts +21 -0
  191. package/dist/web/layout/app-layout.js +8 -0
  192. package/dist/web/layout/avatar.d.ts +12 -0
  193. package/dist/web/layout/avatar.js +63 -0
  194. package/dist/web/layout/default-app-shell.d.ts +41 -0
  195. package/dist/web/layout/default-app-shell.js +16 -0
  196. package/dist/web/layout/editor-panel.d.ts +10 -0
  197. package/dist/web/layout/editor-panel.js +29 -0
  198. package/dist/web/layout/fill-classes.d.ts +9 -0
  199. package/dist/web/layout/fill-classes.js +15 -0
  200. package/dist/web/layout/filter-app-schema-navs.d.ts +7 -0
  201. package/dist/web/layout/filter-app-schema-navs.js +68 -0
  202. package/dist/web/layout/header-action-group.d.ts +6 -0
  203. package/dist/web/layout/header-action-group.js +8 -0
  204. package/dist/web/layout/header-overflow-menu.d.ts +4 -0
  205. package/dist/web/layout/header-overflow-menu.js +8 -0
  206. package/dist/web/layout/language-menu-items.d.ts +8 -0
  207. package/dist/web/layout/language-menu-items.js +16 -0
  208. package/dist/web/layout/language-switcher.d.ts +25 -0
  209. package/dist/web/layout/language-switcher.js +46 -0
  210. package/dist/web/layout/nav-tree.d.ts +19 -0
  211. package/dist/web/layout/nav-tree.js +536 -0
  212. package/dist/web/layout/page-header-slot.d.ts +25 -0
  213. package/dist/web/layout/page-header-slot.js +50 -0
  214. package/dist/web/layout/profile-menu.d.ts +13 -0
  215. package/dist/web/layout/profile-menu.js +6 -0
  216. package/dist/web/layout/shell-breadcrumb.d.ts +11 -0
  217. package/dist/web/layout/shell-breadcrumb.js +34 -0
  218. package/dist/web/layout/shell-header.d.ts +12 -0
  219. package/dist/web/layout/shell-header.js +159 -0
  220. package/dist/web/layout/sidebar-brand.d.ts +15 -0
  221. package/dist/web/layout/sidebar-brand.js +9 -0
  222. package/dist/web/layout/sidebar-panel.d.ts +41 -0
  223. package/dist/web/layout/sidebar-panel.js +107 -0
  224. package/dist/web/layout/sidebar-user.d.ts +8 -0
  225. package/dist/web/layout/sidebar-user.js +17 -0
  226. package/dist/web/layout/sidebar.d.ts +17 -0
  227. package/dist/web/layout/sidebar.js +11 -0
  228. package/dist/web/layout/target-resolver-stub.d.ts +13 -0
  229. package/dist/web/layout/target-resolver-stub.js +61 -0
  230. package/dist/web/layout/target-url.d.ts +12 -0
  231. package/dist/web/layout/target-url.js +77 -0
  232. package/dist/web/layout/theme-label.d.ts +15 -0
  233. package/dist/web/layout/theme-label.js +35 -0
  234. package/dist/web/layout/theme-menu-item.d.ts +12 -0
  235. package/dist/web/layout/theme-menu-item.js +16 -0
  236. package/dist/web/layout/theme-toggle.d.ts +17 -0
  237. package/dist/web/layout/theme-toggle.js +22 -0
  238. package/dist/web/layout/topbar.d.ts +8 -0
  239. package/dist/web/layout/topbar.js +4 -0
  240. package/dist/web/layout/workspace-shell.d.ts +63 -0
  241. package/dist/web/layout/workspace-shell.js +220 -0
  242. package/dist/web/layout/workspace-switcher.d.ts +15 -0
  243. package/dist/web/layout/workspace-switcher.js +36 -0
  244. package/dist/web/lib/accept-attr.d.ts +1 -0
  245. package/dist/web/lib/accept-attr.js +6 -0
  246. package/dist/web/lib/clamp.d.ts +1 -0
  247. package/dist/web/lib/clamp.js +3 -0
  248. package/dist/web/lib/cn.d.ts +6 -0
  249. package/dist/web/lib/cn.js +9 -0
  250. package/dist/web/lib/download.d.ts +2 -0
  251. package/dist/web/lib/download.js +24 -0
  252. package/dist/web/lib/file-ref-download.d.ts +15 -0
  253. package/dist/web/lib/file-ref-download.js +62 -0
  254. package/dist/web/lib/file-upload-form-data.d.ts +7 -0
  255. package/dist/web/lib/file-upload-form-data.js +11 -0
  256. package/dist/web/lib/resize-image.d.ts +1 -0
  257. package/dist/web/lib/resize-image.js +66 -0
  258. package/dist/web/lib/upload-file-ref.d.ts +9 -0
  259. package/dist/web/lib/upload-file-ref.js +29 -0
  260. package/dist/web/primitives/action-menu.d.ts +34 -0
  261. package/dist/web/primitives/action-menu.js +30 -0
  262. package/dist/web/primitives/calendar-popover.d.ts +13 -0
  263. package/dist/web/primitives/calendar-popover.js +76 -0
  264. package/dist/web/primitives/combobox.d.ts +56 -0
  265. package/dist/web/primitives/combobox.js +127 -0
  266. package/dist/web/primitives/date-field.d.ts +18 -0
  267. package/dist/web/primitives/date-field.js +74 -0
  268. package/dist/web/primitives/date-input.d.ts +1 -0
  269. package/dist/web/primitives/date-input.js +5 -0
  270. package/dist/web/primitives/date-parse.d.ts +11 -0
  271. package/dist/web/primitives/date-parse.js +159 -0
  272. package/dist/web/primitives/dialog.d.ts +3 -0
  273. package/dist/web/primitives/dialog.js +42 -0
  274. package/dist/web/primitives/drawer.d.ts +3 -0
  275. package/dist/web/primitives/drawer.js +17 -0
  276. package/dist/web/primitives/dropdown-menu.d.ts +15 -0
  277. package/dist/web/primitives/dropdown-menu.js +56 -0
  278. package/dist/web/primitives/embedded-list-input.d.ts +9 -0
  279. package/dist/web/primitives/embedded-list-input.js +382 -0
  280. package/dist/web/primitives/file-thumbnail.d.ts +25 -0
  281. package/dist/web/primitives/file-thumbnail.js +90 -0
  282. package/dist/web/primitives/file-upload.d.ts +21 -0
  283. package/dist/web/primitives/file-upload.js +56 -0
  284. package/dist/web/primitives/index.d.ts +34 -0
  285. package/dist/web/primitives/index.js +2021 -0
  286. package/dist/web/primitives/json-view.d.ts +7 -0
  287. package/dist/web/primitives/json-view.js +84 -0
  288. package/dist/web/primitives/layout.d.ts +27 -0
  289. package/dist/web/primitives/layout.js +30 -0
  290. package/dist/web/primitives/lightbox.d.ts +3 -0
  291. package/dist/web/primitives/lightbox.js +36 -0
  292. package/dist/web/primitives/locale-number.d.ts +2 -0
  293. package/dist/web/primitives/locale-number.js +54 -0
  294. package/dist/web/primitives/located-timestamp-input.d.ts +22 -0
  295. package/dist/web/primitives/located-timestamp-input.js +22 -0
  296. package/dist/web/primitives/metric.d.ts +5 -0
  297. package/dist/web/primitives/metric.js +18 -0
  298. package/dist/web/primitives/modal-shell.d.ts +17 -0
  299. package/dist/web/primitives/modal-shell.js +9 -0
  300. package/dist/web/primitives/modal.d.ts +3 -0
  301. package/dist/web/primitives/modal.js +13 -0
  302. package/dist/web/primitives/money-input.d.ts +18 -0
  303. package/dist/web/primitives/money-input.js +122 -0
  304. package/dist/web/primitives/narrow-pinned-footer.d.ts +12 -0
  305. package/dist/web/primitives/narrow-pinned-footer.js +153 -0
  306. package/dist/web/primitives/number-input.d.ts +18 -0
  307. package/dist/web/primitives/number-input.js +53 -0
  308. package/dist/web/primitives/option-groups.d.ts +11 -0
  309. package/dist/web/primitives/option-groups.js +27 -0
  310. package/dist/web/primitives/page-header.d.ts +3 -0
  311. package/dist/web/primitives/page-header.js +52 -0
  312. package/dist/web/primitives/promo-panel.d.ts +3 -0
  313. package/dist/web/primitives/promo-panel.js +11 -0
  314. package/dist/web/primitives/segmented-select.d.ts +21 -0
  315. package/dist/web/primitives/segmented-select.js +62 -0
  316. package/dist/web/primitives/share-actions.d.ts +4 -0
  317. package/dist/web/primitives/share-actions.js +56 -0
  318. package/dist/web/primitives/status-badge.d.ts +3 -0
  319. package/dist/web/primitives/status-badge.js +5 -0
  320. package/dist/web/primitives/sticky-action-bar.d.ts +5 -0
  321. package/dist/web/primitives/sticky-action-bar.js +15 -0
  322. package/dist/web/primitives/tabs.d.ts +3 -0
  323. package/dist/web/primitives/tabs.js +107 -0
  324. package/dist/web/primitives/timestamp-input.d.ts +27 -0
  325. package/dist/web/primitives/timestamp-input.js +87 -0
  326. package/dist/web/primitives/toast.d.ts +19 -0
  327. package/dist/web/primitives/toast.js +63 -0
  328. package/dist/web/primitives/tz-input.d.ts +11 -0
  329. package/dist/web/primitives/tz-input.js +8 -0
  330. package/dist/web/primitives/tz-options.d.ts +4 -0
  331. package/dist/web/primitives/tz-options.js +17 -0
  332. package/dist/web/primitives/use-keyboard-inset.d.ts +1 -0
  333. package/dist/web/primitives/use-keyboard-inset.js +28 -0
  334. package/dist/web/primitives/use-narrow-viewport.d.ts +4 -0
  335. package/dist/web/primitives/use-narrow-viewport.js +46 -0
  336. package/dist/web/react-css-custom-properties.d.ts +6 -0
  337. package/dist/web/react-css-custom-properties.js +1 -0
  338. package/dist/web/sse/live-events.d.ts +15 -0
  339. package/dist/web/sse/live-events.js +148 -0
  340. package/dist/web/theme-plugin.d.ts +41 -0
  341. package/dist/web/theme-plugin.js +123 -0
  342. package/dist/web/tokens.d.ts +24 -0
  343. package/dist/web/tokens.js +203 -0
  344. package/dist/web/ui/avatar.d.ts +11 -0
  345. package/dist/web/ui/avatar.js +25 -0
  346. package/dist/web/ui/badge.d.ts +9 -0
  347. package/dist/web/ui/badge.js +26 -0
  348. package/dist/web/ui/breadcrumb.d.ts +11 -0
  349. package/dist/web/ui/breadcrumb.js +29 -0
  350. package/dist/web/ui/button.d.ts +10 -0
  351. package/dist/web/ui/button.js +37 -0
  352. package/dist/web/ui/checkbox.d.ts +4 -0
  353. package/dist/web/ui/checkbox.js +11 -0
  354. package/dist/web/ui/input.d.ts +3 -0
  355. package/dist/web/ui/input.js +8 -0
  356. package/dist/web/ui/label.d.ts +4 -0
  357. package/dist/web/ui/label.js +10 -0
  358. package/dist/web/ui/resizable.d.ts +7 -0
  359. package/dist/web/ui/resizable.js +16 -0
  360. package/dist/web/ui/separator.d.ts +4 -0
  361. package/dist/web/ui/separator.js +10 -0
  362. package/dist/web/ui/sheet.d.ts +14 -0
  363. package/dist/web/ui/sheet.js +42 -0
  364. package/dist/web/ui/sidebar.d.ts +73 -0
  365. package/dist/web/ui/sidebar.js +211 -0
  366. package/dist/web/ui/skeleton.d.ts +2 -0
  367. package/dist/web/ui/skeleton.js +7 -0
  368. package/dist/web/ui/switch.d.ts +6 -0
  369. package/dist/web/ui/switch.js +10 -0
  370. package/dist/web/ui/table.d.ts +10 -0
  371. package/dist/web/ui/table.js +30 -0
  372. package/dist/web/ui/tabs.d.ts +11 -0
  373. package/dist/web/ui/tabs.js +31 -0
  374. package/dist/web/ui/textarea.d.ts +3 -0
  375. package/dist/web/ui/textarea.js +8 -0
  376. package/dist/web/ui/tooltip.d.ts +7 -0
  377. package/dist/web/ui/tooltip.js +19 -0
  378. package/dist/web/ui/use-mobile.d.ts +1 -0
  379. package/dist/web/ui/use-mobile.js +16 -0
  380. package/dist/web/version/update-checker.d.ts +9 -0
  381. package/dist/web/version/update-checker.js +88 -0
  382. package/dist/web/widgets/ai-text-field.d.ts +36 -0
  383. package/dist/web/widgets/ai-text-field.js +137 -0
  384. package/dist/web/widgets/chart-window.d.ts +58 -0
  385. package/dist/web/widgets/chart-window.js +190 -0
  386. package/dist/web/widgets/charts.d.ts +208 -0
  387. package/dist/web/widgets/charts.js +685 -0
  388. package/dist/web/widgets/collapsible-section.d.ts +10 -0
  389. package/dist/web/widgets/collapsible-section.js +17 -0
  390. package/dist/web/widgets/dashboard-list.d.ts +21 -0
  391. package/dist/web/widgets/dashboard-list.js +45 -0
  392. package/dist/web/widgets/detail-list.d.ts +15 -0
  393. package/dist/web/widgets/detail-list.js +8 -0
  394. package/dist/web/widgets/drawer.d.ts +65 -0
  395. package/dist/web/widgets/drawer.js +142 -0
  396. package/dist/web/widgets/feed-list.d.ts +13 -0
  397. package/dist/web/widgets/feed-list.js +9 -0
  398. package/dist/web/widgets/floating-panel.d.ts +30 -0
  399. package/dist/web/widgets/floating-panel.js +242 -0
  400. package/dist/web/widgets/form-fields.d.ts +93 -0
  401. package/dist/web/widgets/form-fields.js +50 -0
  402. package/dist/web/widgets/index.d.ts +27 -0
  403. package/dist/web/widgets/index.js +30 -0
  404. package/dist/web/widgets/infinity-list.d.ts +46 -0
  405. package/dist/web/widgets/infinity-list.js +211 -0
  406. package/dist/web/widgets/mode-switch.d.ts +21 -0
  407. package/dist/web/widgets/mode-switch.js +25 -0
  408. package/dist/web/widgets/photo-slots.d.ts +29 -0
  409. package/dist/web/widgets/photo-slots.js +52 -0
  410. package/dist/web/widgets/plan-card.d.ts +36 -0
  411. package/dist/web/widgets/plan-card.js +13 -0
  412. package/dist/web/widgets/progress-bar.d.ts +19 -0
  413. package/dist/web/widgets/progress-bar.js +24 -0
  414. package/dist/web/widgets/progress-list.d.ts +19 -0
  415. package/dist/web/widgets/progress-list.js +11 -0
  416. package/dist/web/widgets/public-shell.d.ts +22 -0
  417. package/dist/web/widgets/public-shell.js +15 -0
  418. package/dist/web/widgets/query-table.d.ts +29 -0
  419. package/dist/web/widgets/query-table.js +30 -0
  420. package/dist/web/widgets/result-panel.d.ts +69 -0
  421. package/dist/web/widgets/result-panel.js +30 -0
  422. package/dist/web/widgets/section-card.d.ts +12 -0
  423. package/dist/web/widgets/section-card.js +9 -0
  424. package/dist/web/widgets/sheet-parts.d.ts +9 -0
  425. package/dist/web/widgets/sheet-parts.js +11 -0
  426. package/dist/web/widgets/side-by-side-table.d.ts +31 -0
  427. package/dist/web/widgets/side-by-side-table.js +11 -0
  428. package/dist/web/widgets/stat.d.ts +54 -0
  429. package/dist/web/widgets/stat.js +65 -0
  430. package/dist/web/widgets/states.d.ts +23 -0
  431. package/dist/web/widgets/states.js +20 -0
  432. package/dist/web/widgets/status-badge.d.ts +15 -0
  433. package/dist/web/widgets/status-badge.js +24 -0
  434. package/dist/web/widgets/step-bar.d.ts +35 -0
  435. package/dist/web/widgets/step-bar.js +60 -0
  436. package/dist/web/widgets/upload-zone.d.ts +24 -0
  437. package/dist/web/widgets/upload-zone.js +108 -0
  438. package/dist/web/widgets/use-draft.d.ts +20 -0
  439. package/dist/web/widgets/use-draft.js +21 -0
  440. package/dist/web/widgets/use-pointer-drag.d.ts +21 -0
  441. package/dist/web/widgets/use-pointer-drag.js +53 -0
  442. package/package.json +105 -7
  443. package/src/changes.json +53 -0
  444. package/src/web/fonts/OFL.txt +93 -0
  445. package/src/web/fonts/ibm-plex-mono-latin-400.woff2 +0 -0
  446. package/src/web/fonts/ibm-plex-mono-latin-500.woff2 +0 -0
  447. package/src/web/fonts/ibm-plex-mono-latin-ext-400.woff2 +0 -0
  448. package/src/web/fonts/ibm-plex-mono-latin-ext-500.woff2 +0 -0
  449. package/src/web/fonts/ibm-plex-sans-latin-400.woff2 +0 -0
  450. package/src/web/fonts/ibm-plex-sans-latin-500.woff2 +0 -0
  451. package/src/web/fonts/ibm-plex-sans-latin-600.woff2 +0 -0
  452. package/src/web/fonts/ibm-plex-sans-latin-ext-400.woff2 +0 -0
  453. package/src/web/fonts/ibm-plex-sans-latin-ext-500.woff2 +0 -0
  454. package/src/web/fonts/ibm-plex-sans-latin-ext-600.woff2 +0 -0
  455. package/src/web/styles.css +401 -0
@@ -0,0 +1,35 @@
1
+ import { useOptionalTranslation, useTokenController } from "../../index.js";
2
+ // light -> dark -> auto -> light: a click describes and applies the NEXT step.
3
+ export function nextThemePreference(current) {
4
+ if (current === "light")
5
+ return "dark";
6
+ if (current === "dark")
7
+ return "auto";
8
+ return "light";
9
+ }
10
+ export function useThemeToggleLabel({ titleInDark, titleInLight, titleForAuto, }) {
11
+ const { mode, preference = mode, setPreference, toggleMode } = useTokenController();
12
+ // Optional: the toggle also renders outside a LocaleProvider (samples, tests).
13
+ const translate = useOptionalTranslation();
14
+ // A TokensApi without setPreference is a plain light/dark implementation: no auto step.
15
+ const nextPreference = setPreference
16
+ ? nextThemePreference(preference)
17
+ : mode === "dark"
18
+ ? "light"
19
+ : "dark";
20
+ const titles = {
21
+ light: titleInDark ?? translate?.("kumiko.theme.light") ?? "Light theme",
22
+ dark: titleInLight ?? translate?.("kumiko.theme.dark") ?? "Dark theme",
23
+ auto: titleForAuto ?? translate?.("kumiko.theme.auto") ?? "Automatic theme",
24
+ };
25
+ return {
26
+ nextPreference,
27
+ title: titles[nextPreference],
28
+ cyclePreference: () => {
29
+ if (setPreference)
30
+ setPreference(nextPreference);
31
+ else
32
+ toggleMode();
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,12 @@
1
+ import type { ReactNode } from "react";
2
+ export type ThemeMenuItemProps = {
3
+ readonly lightIcon?: ReactNode;
4
+ readonly darkIcon?: ReactNode;
5
+ readonly autoIcon?: ReactNode;
6
+ readonly titleInDark?: string;
7
+ readonly titleInLight?: string;
8
+ readonly titleForAuto?: string;
9
+ readonly testId?: string;
10
+ };
11
+ /** Theme toggle as a dropdown entry, for a user menu that carries the shell controls. */
12
+ export declare function ThemeMenuItem({ lightIcon, darkIcon, autoIcon, titleInDark, titleInLight, titleForAuto, testId, }: ThemeMenuItemProps): ReactNode;
@@ -0,0 +1,16 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { DropdownMenuItem } from "../primitives/dropdown-menu.js";
3
+ import { useThemeToggleLabel } from "./theme-label.js";
4
+ /** Theme toggle as a dropdown entry, for a user menu that carries the shell controls. */
5
+ export function ThemeMenuItem({ lightIcon = "☀", darkIcon = "☾", autoIcon = "◐", titleInDark, titleInLight, titleForAuto, testId, }) {
6
+ const { nextPreference, title, cyclePreference } = useThemeToggleLabel({
7
+ titleInDark,
8
+ titleInLight,
9
+ titleForAuto,
10
+ });
11
+ const icons = { light: lightIcon, dark: darkIcon, auto: autoIcon };
12
+ return (_jsxs(DropdownMenuItem, { "data-testid": testId, onSelect: (event) => {
13
+ event.preventDefault();
14
+ cyclePreference();
15
+ }, children: [_jsx("span", { "aria-hidden": "true", children: icons[nextPreference] }), _jsx("span", { children: title })] }));
16
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactNode } from "react";
2
+ export type ThemeToggleProps = {
3
+ /** Icon for the step to light mode. Default: ☀ */
4
+ readonly lightIcon?: ReactNode;
5
+ /** Icon for the step to dark mode. Default: ☾ */
6
+ readonly darkIcon?: ReactNode;
7
+ /** Icon for the step to auto mode. Default: ◐ */
8
+ readonly autoIcon?: ReactNode;
9
+ /** Title/aria-label for the step to light mode. Default: i18n `kumiko.theme.light` */
10
+ readonly titleInDark?: string;
11
+ /** Title/aria-label for the step to dark mode. Default: i18n `kumiko.theme.dark` */
12
+ readonly titleInLight?: string;
13
+ /** Title/aria-label for the step to auto mode. Default: i18n `kumiko.theme.auto` */
14
+ readonly titleForAuto?: string;
15
+ readonly testId?: string;
16
+ };
17
+ export declare function ThemeToggle({ lightIcon, darkIcon, autoIcon, titleInDark, titleInLight, titleForAuto, testId, }: ThemeToggleProps): ReactNode;
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // ThemeToggle — Button der die Theme-Wahl weiterschaltet (hell → dunkel → auto).
3
+ // Icon-Slots als Props, damit renderer/web keine Icon-Lib als Hard-
4
+ // Dependency zieht. Default: Unicode-Glyphs (☀ / ☾) — funktionieren in
5
+ // jedem Browser, jede App kann lucide/heroicons/eigene SVG via Props
6
+ // reinreichen.
7
+ import { useContext } from "react";
8
+ import { HeaderOverflowMenuContext, headerOverflowMenuItemClass } from "./header-overflow-menu.js";
9
+ import { useThemeToggleLabel } from "./theme-label.js";
10
+ export function ThemeToggle({ lightIcon = "☀", darkIcon = "☾", autoIcon = "◐", titleInDark, titleInLight, titleForAuto, testId, }) {
11
+ const inOverflowMenu = useContext(HeaderOverflowMenuContext);
12
+ const { nextPreference, title, cyclePreference } = useThemeToggleLabel({
13
+ titleInDark,
14
+ titleInLight,
15
+ titleForAuto,
16
+ });
17
+ const icon = { light: lightIcon, dark: darkIcon, auto: autoIcon }[nextPreference];
18
+ if (inOverflowMenu) {
19
+ return (_jsxs("button", { type: "button", role: "menuitem", onClick: cyclePreference, className: headerOverflowMenuItemClass, "data-testid": testId, children: [_jsx("span", { "aria-hidden": "true", children: icon }), _jsx("span", { children: title })] }));
20
+ }
21
+ return (_jsx("button", { type: "button", onClick: cyclePreference, className: "inline-flex h-8 w-8 items-center justify-center rounded-md border bg-background text-muted-foreground hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", title: title, "aria-label": title, "data-testid": testId, children: icon }));
22
+ }
@@ -0,0 +1,8 @@
1
+ import type { ReactNode } from "react";
2
+ export type TopbarProps = {
3
+ readonly start?: ReactNode;
4
+ readonly center?: ReactNode;
5
+ readonly end?: ReactNode;
6
+ readonly testId?: string;
7
+ };
8
+ export declare function Topbar({ start, center, end, testId }: TopbarProps): ReactNode;
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ export function Topbar({ start, center, end, testId }) {
3
+ return (_jsxs("header", { "data-testid": testId, "data-kumiko-layout": "topbar", className: "flex h-12 items-center gap-4 border-b bg-background px-4 text-sm", children: [start !== undefined && _jsx("div", { className: "flex items-center gap-3", children: start }), center !== undefined && _jsx("nav", { className: "flex flex-1 items-center gap-6", children: center }), end !== undefined && _jsx("div", { className: "ml-auto flex items-center gap-2", children: end })] }));
4
+ }
@@ -0,0 +1,63 @@
1
+ import { type ReactNode } from "react";
2
+ import type { AppSchema, FeatureSchema, WorkspaceSchema } from "../../index.ts";
3
+ export type WorkspaceShellUser = {
4
+ readonly id: string;
5
+ readonly roles: readonly string[];
6
+ };
7
+ export type WorkspaceShellProps = {
8
+ /** Branding / logo on the left side of the topbar. */
9
+ readonly brand: ReactNode;
10
+ /** Schema mit `workspaces` populated — die engine baut das aus
11
+ * registry.getAllWorkspaces() + getWorkspaceNavs(). Akzeptiert
12
+ * AppSchema (multi-feature) oder die legacy FeatureSchema (single-
13
+ * feature, workspaces inline). Ohne Workspaces fällt der Shell auf
14
+ * plain NavTree ohne Switcher zurück. */
15
+ readonly schema: AppSchema | FeatureSchema;
16
+ /** Optional topbar end-slot — TenantSwitcher / ThemeToggle / UserMenu. */
17
+ readonly topbarActions?: ReactNode;
18
+ /** Current user. Drives which workspaces appear in the switcher. */
19
+ readonly user?: WorkspaceShellUser;
20
+ /** Initial active workspace short id ("admin"). When omitted: default
21
+ * workspace from schema, otherwise first accessible. Useful for SSR
22
+ * and tests to pre-seed without hitting the URL. */
23
+ readonly initialWorkspaceId?: string;
24
+ /** Footer-Slot unten in der Sidebar — Profile-Row, Help-Link, Build-
25
+ * Info. Klebt am unteren Rand via `mt-auto` (siehe Sidebar.footer).
26
+ * Symmetrisch zu DefaultAppShell.sidebarFooter. */
27
+ readonly sidebarFooter?: ReactNode;
28
+ /** Runtime badge slot per nav leaf (bare nav id), same as DefaultAppShell. */
29
+ readonly navBadges?: ReadonlyMap<string, ReactNode>;
30
+ /** Viewport-fit shell. true → fixed viewport height (`h-svh`), content
31
+ * scrolls INSIDE instead of the whole page. Default true — set
32
+ * `fill={false}` to opt back into page scroll. */
33
+ readonly fill?: boolean;
34
+ /** Screen content. */
35
+ readonly children: ReactNode;
36
+ };
37
+ export declare function WorkspaceShell({ brand, schema, topbarActions, user, initialWorkspaceId, sidebarFooter, navBadges, fill, children, }: WorkspaceShellProps): ReactNode;
38
+ export declare function filterByAccess(workspaces: readonly WorkspaceSchema[], userRoles: readonly string[] | undefined): readonly WorkspaceSchema[];
39
+ /** Returns the short screen-id of the first nav-member that points at
40
+ * a screen. Walks the workspace's nav-list in order and skips section-
41
+ * headers (NavDefinitions ohne `screen`) so ein workspace dessen erstes
42
+ * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
43
+ *
44
+ * Returns "" when:
45
+ * - navMembers is undefined or empty
46
+ * - all members are section-headers (no `screen` property)
47
+ * - all members are unknown to the schema (drift zwischen
48
+ * r.workspace.nav und den registrierten navs — boot-validator fängt
49
+ * das server-side, hier nur graceful fallback)
50
+ *
51
+ * The caller MUST loop-guard on "": rendering with screenId="" macht
52
+ * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
53
+ * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
54
+ * jedem Re-Render erneut feuern (siehe matching guard dort).
55
+ *
56
+ * Earlier code used `lastSegment(navQn)` which only worked when the
57
+ * feature followed the convention nav.id === screen.id (samples/apps/
58
+ * workspaces does this). Apps with distinct ids (publicstatus: nav
59
+ * "components" → screen "component-list") got "Screen not found".
60
+ *
61
+ * Exported für Edge-Case-Tests. */
62
+ export declare function firstNavScreenId(app: AppSchema, navMembers: readonly string[] | undefined): string;
63
+ export declare function resolveDefaultId(visible: readonly WorkspaceSchema[], preferredShortId: string | undefined): string | undefined;
@@ -0,0 +1,220 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // WorkspaceShell — App-shell for multi-persona apps. Renders the
3
+ // switcher in the topbar center slot, picks the active workspace from
4
+ // URL state (?w=<id>) or default, and feeds the active workspace's
5
+ // nav-membership down to NavTree as an allow-set.
6
+ //
7
+ // Apps that don't need workspaces stick with DefaultAppShell. Both
8
+ // shells live side-by-side; createKumikoApp's `shell` prop picks one.
9
+ //
10
+ // Active-workspace resolution priority:
11
+ // 1. URL workspace segment `/<workspace>/...` (user-driven, shareable)
12
+ // 2. `initialWorkspaceId` prop (caller-pinned, SSR/test)
13
+ // 3. WorkspaceDefinition with default:true (engine-validated unique)
14
+ // 4. First workspace the user has access to
15
+ //
16
+ // State lives in the URL as the single source of truth via the standard
17
+ // nav route (`useNav().route.workspaceId`). No local React state —
18
+ // tenant-switches and role refreshes heal automatically: `visible`
19
+ // recomputes, the URL id no longer matches a visible workspace, the
20
+ // resolution chain falls through to the default. Reloads / bookmarks /
21
+ // shared links keep the active workspace because it's part of the URL.
22
+ //
23
+ // The switcher's onSelect calls nav.navigate({ workspaceId, screenId })
24
+ // where screenId is the first nav-member of the target workspace, so a
25
+ // click lands on a real screen instead of an unresolved root URL.
26
+ //
27
+ // Roles gating:
28
+ // * access.openToAll → always shown
29
+ // * access.roles → shown only when user.roles ∩ access.roles ≠ ∅
30
+ // * access undefined → shown to everyone (engine convention — same
31
+ // rule that NavDefinition.access follows)
32
+ import { isUiAccessGranted } from "@cosmicdrift/kumiko-framework/ui-types";
33
+ import { useCallback, useLayoutEffect, useMemo } from "react";
34
+ import { qualifyNavId, toAppSchema, UserRolesProvider, useNav, useTranslation, } from "../../index.js";
35
+ import { useResolvers } from "../app/resolvers-context.js";
36
+ import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarInset, SidebarProvider, SidebarRail, } from "../ui/sidebar.js";
37
+ import { EditorPanel } from "./editor-panel.js";
38
+ import { fillClasses } from "./fill-classes.js";
39
+ import { lastSegment, NavTree } from "./nav-tree.js";
40
+ import { PageHeaderSlotProvider } from "./page-header-slot.js";
41
+ import { ShellHeader } from "./shell-header.js";
42
+ import { SidebarPanelProvider, useSidebarPanelHost } from "./sidebar-panel.js";
43
+ import { parseTargetFromSearchParams } from "./target-url.js";
44
+ import { WorkspaceSwitcher } from "./workspace-switcher.js";
45
+ export function WorkspaceShell({ brand, schema, topbarActions, user, initialWorkspaceId, sidebarFooter, navBadges, fill = true, children, }) {
46
+ const app = useMemo(() => toAppSchema(schema), [schema]);
47
+ const visible = useMemo(() => filterByAccess(app.workspaces ?? [], user?.roles), [app.workspaces, user?.roles]);
48
+ const nav = useNav();
49
+ const t = useTranslation();
50
+ const routeWorkspaceId = nav.route?.workspaceId;
51
+ // Single source of truth: URL > initial > engine-default > first visible.
52
+ // Recomputed on every dependency change — no local state, no stale-id
53
+ // healing useEffect. If the URL points at a workspace the user can no
54
+ // longer access (e.g. after a tenant-switch), the chain falls through
55
+ // to the resolveDefaultId fallback.
56
+ const activeId = useMemo(() => {
57
+ if (routeWorkspaceId !== undefined &&
58
+ visible.some((ws) => ws.definition.id === routeWorkspaceId)) {
59
+ return routeWorkspaceId;
60
+ }
61
+ return resolveDefaultId(visible, initialWorkspaceId);
62
+ }, [routeWorkspaceId, visible, initialWorkspaceId]);
63
+ const handleSelect = useCallback((id) => {
64
+ // Pick the first nav-member that actually points at a screen so
65
+ // the URL lands on something renderable instead of `/<workspace>`.
66
+ // Section-headers (nav-Einträge ohne `screen`) werden übersprungen.
67
+ const target = visible.find((ws) => ws.definition.id === id);
68
+ const screenId = firstNavScreenId(app, target?.navMembers);
69
+ nav.navigate({ workspaceId: id, screenId });
70
+ }, [nav, app, visible]);
71
+ // Initial sync: covers two URL states that need a default-fill so
72
+ // NavTree links and RoutedScreen have something to chew on.
73
+ //
74
+ // 1. No workspace in URL ("/" or wrong workspace id) → fill the
75
+ // active workspace AND its first nav-member's screen.
76
+ // 2. Workspace present but screen missing ("/admin") → fill in the
77
+ // first nav-member's screen, keep the workspace.
78
+ //
79
+ // replace, NOT navigate: these are default-fills, not user actions.
80
+ // Using pushState would create a history entry the user never asked
81
+ // for — Browser-Back from /admin/x → / → effect re-pushes →
82
+ // Back-loop. replaceState swaps in place: Back leaves the app cleanly.
83
+ //
84
+ // useLayoutEffect, NOT useEffect: the children below this shell render
85
+ // RoutedScreen with the URL's current screenId. If that's "" (URL was
86
+ // "/" or "/admin"), KumikoScreen renders a "Screen not found" banner
87
+ // for the empty qn. useEffect would let that banner paint to the
88
+ // screen for one frame before the URL got fixed. useLayoutEffect runs
89
+ // synchronously between commit and paint, so the user only ever sees
90
+ // the resolved screen. (No SSR here, otherwise we'd need a guard.)
91
+ useLayoutEffect(() => {
92
+ if (activeId === undefined)
93
+ return;
94
+ const routeScreenEmpty = nav.route?.screenId === undefined || nav.route.screenId === "";
95
+ const workspaceMatches = routeWorkspaceId === activeId;
96
+ if (workspaceMatches && !routeScreenEmpty)
97
+ return; // URL is fine
98
+ const target = visible.find((ws) => ws.definition.id === activeId);
99
+ const navMembers = target?.navMembers;
100
+ if ((navMembers === undefined || navMembers.length === 0) && !routeScreenEmpty) {
101
+ // Workspace exists but no nav members — keep whatever screen the
102
+ // user typed, just lock the workspace prefix.
103
+ nav.replace({ workspaceId: activeId, screenId: nav.route?.screenId ?? "" });
104
+ return;
105
+ }
106
+ const screenId = firstNavScreenId(app, navMembers);
107
+ // Loop-guard: wenn keiner der nav-members eine screen-property hat
108
+ // (alle section-headers oder unbekannte QNs), würde nav.replace mit
109
+ // screenId="" den Effect bei jedem Re-Render erneut feuern und den
110
+ // User auf "/<workspace>/" pinnen. Lieber gar nicht replacen — der
111
+ // Workspace-Selector kann den User dann manuell auf einen leaf-nav
112
+ // schicken.
113
+ if (screenId === "")
114
+ return;
115
+ nav.replace({ workspaceId: activeId, screenId });
116
+ }, [activeId, routeWorkspaceId, visible, nav, app]);
117
+ const activeWorkspace = useMemo(() => visible.find((ws) => ws.definition.id === activeId), [visible, activeId]);
118
+ // Filter resolution has THREE branches and getting them right matters:
119
+ // * Schema declares workspaces + active resolved → filter to its members
120
+ // * Schema declares workspaces + NO active visible → empty allow-set
121
+ // (NOT undefined). This catches the "user has no accessible workspace"
122
+ // case — falling back to "no filter" would leak nav items the user
123
+ // shouldn't see, e.g. admin entries to a driver after a role change.
124
+ // * Schema doesn't declare workspaces at all → undefined (no filter).
125
+ // Apps that haven't opted into workspaces yet get every nav as before.
126
+ const allowedNavQns = useMemo(() => {
127
+ const hasWorkspaceMode = app.workspaces !== undefined && app.workspaces.length > 0;
128
+ if (!hasWorkspaceMode)
129
+ return undefined;
130
+ if (activeWorkspace === undefined)
131
+ return new Set();
132
+ return new Set(activeWorkspace.navMembers);
133
+ }, [app.workspaces, activeWorkspace]);
134
+ const switcher = activeId !== undefined && (_jsx(WorkspaceSwitcher, { workspaces: visible, activeId: activeId, onSelect: handleSelect }));
135
+ // EINE Nav: immer NavTree. Dynamische Knoten (r.nav({ provider: true }))
136
+ // ziehen ihre Children client-seitig aus den registrierten navProviders —
137
+ // der frühere navigation:"tree"/VisualTree-Zweig ist hier eingefaltet.
138
+ // Content-Bereich: ein aktives target (aus den nav.searchParams, gesetzt
139
+ // beim Klick auf einen target-Knoten) rendert den EditorPanel statt des
140
+ // RoutedScreen — sonst die normalen screen-children.
141
+ // (WorkspaceDefinition.navigation ist seitdem ein No-op; deferred removal.)
142
+ const resolvers = useResolvers();
143
+ const activeTarget = parseTargetFromSearchParams(nav.searchParams);
144
+ const sidebarContent = (_jsx(NavTree, { schema: app, ...(user !== undefined && { user }), ...(allowedNavQns !== undefined && { allowedNavQns }), ...(navBadges !== undefined && { navBadges }) }));
145
+ // Modern shell (like DefaultAppShell): collapsible icon-rail sidebar
146
+ // carries brand + workspace switcher + nav + footer; the SidebarInset
147
+ // renders the shared ShellHeader (panel toggle + breadcrumb + actions)
148
+ // above the content. topbarActions render right inside the header row
149
+ // instead of a separate topbar — one header line, not two.
150
+ const fillCls = fillClasses(fill);
151
+ // Zweite Spalte nur wenn ein Screen sie fuellt (sidebar-09-Muster) — sonst
152
+ // stuende auf jedem anderen Screen ein leerer Streifen neben der Navigation.
153
+ const panel = useSidebarPanelHost();
154
+ return (_jsx(SidebarProvider, { ...fillCls.provider, children: _jsxs(SidebarPanelProvider, { value: panel.value, children: [_jsxs(Sidebar, { collapsible: "icon", mobileTitle: t("kumiko.sidebar.title"), mobileDescription: t("kumiko.sidebar.description"), children: [_jsxs(SidebarHeader, { "data-kumiko-layout": "sidebar-header", children: [brand, switcher] }), _jsx(SidebarContent, { "data-kumiko-layout": "sidebar-nav", children: sidebarContent }), sidebarFooter !== undefined && (_jsx(SidebarFooter, { "data-kumiko-layout": "sidebar-footer", className: "border-t border-sidebar-border", children: sidebarFooter })), _jsx(SidebarRail, { "aria-label": t("kumiko.sidebar.toggle"), title: t("kumiko.sidebar.toggle") })] }), _jsx("div", { ref: panel.ref, "data-kumiko-layout": "sidebar-panel", className: panel.occupied ? "flex h-svh shrink-0 flex-col" : "hidden" }), _jsx(SidebarInset, { className: fillCls.inset, children: _jsxs(PageHeaderSlotProvider, { children: [_jsx(ShellHeader, { schema: app, ...(user !== undefined && { user }), ...(topbarActions !== undefined && { headerActions: topbarActions }) }), _jsx("main", { className: fillCls.main, children: activeTarget !== undefined ? (_jsx(EditorPanel, { resolvers: resolvers })) : (_jsx(UserRolesProvider, { roles: user?.roles, children: children })) })] }) })] }) }));
155
+ }
156
+ // --- helpers (exported for tests) ---
157
+ export function filterByAccess(workspaces, userRoles) {
158
+ return [...workspaces]
159
+ .filter((ws) => isUiAccessGranted(ws.definition.access, userRoles))
160
+ .sort(byOrderThenInsertion);
161
+ }
162
+ function byOrderThenInsertion(a, b) {
163
+ // Missing `order` sorts last; ties keep insertion order via stable sort.
164
+ const ao = a.definition.order ?? Number.POSITIVE_INFINITY;
165
+ const bo = b.definition.order ?? Number.POSITIVE_INFINITY;
166
+ return ao - bo;
167
+ }
168
+ /** Returns the short screen-id of the first nav-member that points at
169
+ * a screen. Walks the workspace's nav-list in order and skips section-
170
+ * headers (NavDefinitions ohne `screen`) so ein workspace dessen erstes
171
+ * Member ein Header ist trotzdem auf einen renderbaren Screen auflöst.
172
+ *
173
+ * Returns "" when:
174
+ * - navMembers is undefined or empty
175
+ * - all members are section-headers (no `screen` property)
176
+ * - all members are unknown to the schema (drift zwischen
177
+ * r.workspace.nav und den registrierten navs — boot-validator fängt
178
+ * das server-side, hier nur graceful fallback)
179
+ *
180
+ * The caller MUST loop-guard on "": rendering with screenId="" macht
181
+ * KumikoScreen den not-found-banner zeigen — und ein nav.replace mit
182
+ * leerem screenId würde den useLayoutEffect in WorkspaceShell bei
183
+ * jedem Re-Render erneut feuern (siehe matching guard dort).
184
+ *
185
+ * Earlier code used `lastSegment(navQn)` which only worked when the
186
+ * feature followed the convention nav.id === screen.id (samples/apps/
187
+ * workspaces does this). Apps with distinct ids (publicstatus: nav
188
+ * "components" → screen "component-list") got "Screen not found".
189
+ *
190
+ * Exported für Edge-Case-Tests. */
191
+ export function firstNavScreenId(app, navMembers) {
192
+ if (navMembers === undefined || navMembers.length === 0)
193
+ return "";
194
+ for (const navQn of navMembers) {
195
+ for (const feature of app.features) {
196
+ for (const nav of feature.navs ?? []) {
197
+ if (qualifyNavId(feature.featureName, nav.id) !== navQn)
198
+ continue;
199
+ if (nav.screen === undefined)
200
+ break; // section-header → nächstes member
201
+ return lastSegment(nav.screen);
202
+ }
203
+ }
204
+ }
205
+ return "";
206
+ }
207
+ export function resolveDefaultId(visible, preferredShortId) {
208
+ // 1. Caller-pinned preference (URL or test prop) wins if it's accessible.
209
+ if (preferredShortId !== undefined) {
210
+ const match = visible.find((ws) => ws.definition.id === preferredShortId);
211
+ if (match !== undefined)
212
+ return match.definition.id;
213
+ }
214
+ // 2. Engine-declared default if accessible.
215
+ const defaulted = visible.find((ws) => ws.definition.default === true);
216
+ if (defaulted !== undefined)
217
+ return defaulted.definition.id;
218
+ // 3. First workspace the user can see.
219
+ return visible[0]?.definition.id;
220
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from "react";
2
+ import type { WorkspaceSchema } from "../../index.ts";
3
+ export type WorkspaceSwitcherProps = {
4
+ readonly workspaces: readonly WorkspaceSchema[];
5
+ readonly activeId: string;
6
+ readonly onSelect: (workspaceQn: string) => void;
7
+ readonly testId?: string;
8
+ };
9
+ /**
10
+ * Requires an ancestor `SidebarProvider` (see `../ui/sidebar`) — this
11
+ * component renders `SidebarMenuButton`, which calls `useSidebar()`
12
+ * internally and throws ("useSidebar must be used within a
13
+ * SidebarProvider.") when rendered outside one.
14
+ */
15
+ export declare function WorkspaceSwitcher({ workspaces, activeId, onSelect, testId, }: WorkspaceSwitcherProps): ReactNode;
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // WorkspaceSwitcher — dumb component for picking the active workspace.
3
+ // Receives the role-filtered + order-sorted workspace list, the active
4
+ // id, and a callback. Stays presentational so WorkspaceShell can own the
5
+ // state (URL ?w=, defaults, role filtering) and tests can hand any list
6
+ // in directly.
7
+ //
8
+ // Dropdown instead of a tab row: a row of fixed buttons overflowed the
9
+ // sidebar width with 3+ workspaces (or even 2 longer names) —
10
+ // truncate+scroll then hid the last entry entirely, unclickable. A
11
+ // dropdown has constant trigger width regardless of count/length.
12
+ //
13
+ // Hidden entirely when collapsed instead of an icon/initial: a label at
14
+ // icon width isn't readable anyway, and the user switches workspaces via
15
+ // the full sidebar — no attempt to keep this operable in the icon rail
16
+ // too (same as the search box in nav-tree.tsx).
17
+ import { ChevronsUpDown } from "lucide-react";
18
+ import { useTranslation } from "../../index.js";
19
+ import { DropdownMenu, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuTrigger, } from "../primitives/dropdown-menu.js";
20
+ import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "../ui/sidebar.js";
21
+ /**
22
+ * Requires an ancestor `SidebarProvider` (see `../ui/sidebar`) — this
23
+ * component renders `SidebarMenuButton`, which calls `useSidebar()`
24
+ * internally and throws ("useSidebar must be used within a
25
+ * SidebarProvider.") when rendered outside one.
26
+ */
27
+ export function WorkspaceSwitcher({ workspaces, activeId, onSelect, testId, }) {
28
+ const t = useTranslation();
29
+ // Single workspace doesn't need a switcher — the user has no choice
30
+ // anyway. Render nothing instead of a useless one-button row.
31
+ if (workspaces.length <= 1)
32
+ return null;
33
+ const labelOf = (ws) => ws.definition.label.includes(".") ? t(ws.definition.label) : ws.definition.label;
34
+ const active = workspaces.find((ws) => ws.definition.id === activeId);
35
+ return (_jsx(SidebarMenu, { "data-testid": testId, "data-kumiko-layout": "workspace-switcher", className: "group-data-[collapsible=icon]:hidden", children: _jsx(SidebarMenuItem, { children: _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs(SidebarMenuButton, { "data-testid": "workspace-switcher-trigger", "aria-label": t("kumiko.workspace.switch"), children: [_jsx("span", { className: "truncate", children: active !== undefined ? labelOf(active) : t("kumiko.workspace.select") }), _jsx(ChevronsUpDown, { className: "ml-auto" })] }) }), _jsx(DropdownMenuContent, { align: "start", className: "min-w-[10rem]", children: _jsx(DropdownMenuRadioGroup, { value: activeId, onValueChange: onSelect, children: workspaces.map((ws) => (_jsx(DropdownMenuRadioItem, { value: ws.definition.id, "data-testid": `workspace-tab-${ws.definition.id}`, children: _jsx("span", { className: "truncate", children: labelOf(ws) }) }, ws.definition.id))) }) })] }) }) }));
36
+ }
@@ -0,0 +1 @@
1
+ export declare function toAcceptAttr(accept?: readonly string[]): string | undefined;
@@ -0,0 +1,6 @@
1
+ // "jpg" → ".jpg", "image/png" stays as-is. Empty list → no accept attribute.
2
+ export function toAcceptAttr(accept) {
3
+ if (accept === undefined || accept.length === 0)
4
+ return undefined;
5
+ return accept.map((a) => (a.startsWith(".") || a.includes("/") ? a : `.${a}`)).join(",");
6
+ }
@@ -0,0 +1 @@
1
+ export declare function clamp(value: number, min: number, max: number): number;
@@ -0,0 +1,3 @@
1
+ export function clamp(value, min, max) {
2
+ return Math.min(Math.max(value, min), max);
3
+ }
@@ -0,0 +1,6 @@
1
+ import { type ClassValue } from "clsx";
2
+ /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
3
+ * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
4
+ * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
5
+ * zu mergen ohne Duplikate. */
6
+ export declare function cn(...inputs: ClassValue[]): string;
@@ -0,0 +1,9 @@
1
+ import { clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+ /** shadcn-Standard-Helper: clsx für konditionales Zusammenstecken,
4
+ * tailwind-merge für Konfliktauflösung (z.B. `px-2 px-4` → `px-4`).
5
+ * Jedes Primitive nutzt das um Consumer-Klassen mit Default-Klassen
6
+ * zu mergen ohne Duplikate. */
7
+ export function cn(...inputs) {
8
+ return twMerge(clsx(inputs));
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { Dispatcher, DispatcherError } from "../../headless/index.ts";
2
+ export declare function postWithDownload(dispatcher: Dispatcher, type: string, payload: unknown): Promise<DispatcherError | null>;
@@ -0,0 +1,24 @@
1
+ // Dispatch a query/handler whose result carries `{ url }` (a signed,
2
+ // short-lived storage URL with `content-disposition: attachment`), then
3
+ // navigate the browser to it so the file downloads. The query rides the
4
+ // live dispatcher, so it carries the `X-CSRF-Token` header automatically —
5
+ // unlike a plain `<a href>` navigation, which sends only cookies and trips
6
+ // the CSRF double-submit check on any server-side re-dispatch.
7
+ //
8
+ // Web-only (uses `window`). Returns the dispatcher error on failure (so the
9
+ // caller can surface its i18nKey), or null on success.
10
+ export async function postWithDownload(dispatcher, type, payload) {
11
+ const res = await dispatcher.query(type, payload);
12
+ if (!res.isSuccess)
13
+ return res.error;
14
+ if (!res.data?.url) {
15
+ return {
16
+ code: "download_url_missing",
17
+ httpStatus: 502,
18
+ i18nKey: "errors.download.urlMissing",
19
+ message: "download query returned no url",
20
+ };
21
+ }
22
+ window.location.assign(res.data.url);
23
+ return null;
24
+ }
@@ -0,0 +1,15 @@
1
+ export declare class FileDownloadFailedError extends Error {
2
+ readonly status: number;
3
+ constructor(status: number);
4
+ }
5
+ export declare function fileNameFromContentDisposition(header: string | null): string | undefined;
6
+ export declare function fetchFileRefBlob(fileRefId: string): Promise<{
7
+ blob: Blob;
8
+ fileName: string | undefined;
9
+ }>;
10
+ export declare function saveBlobAsFile(fileName: string, blob: Blob, options?: {
11
+ bom?: boolean;
12
+ }): void;
13
+ export declare function downloadFileRef(fileRefId: string, options?: {
14
+ fileName?: string;
15
+ }): Promise<void>;
@@ -0,0 +1,62 @@
1
+ import { CSRF_HEADER_NAME, readCsrfToken } from "../../live/index.js";
2
+ const UTF8_BOM = String.fromCharCode(0xfeff);
3
+ export class FileDownloadFailedError extends Error {
4
+ status;
5
+ constructor(status) {
6
+ super(`download failed (${status})`);
7
+ this.status = status;
8
+ }
9
+ }
10
+ const EXTENDED_FILENAME = /filename\*\s*=\s*(?:[^']*)'[^']*'([^;]+)/i;
11
+ const PLAIN_FILENAME = /(?:^|;)\s*filename\s*=\s*(?:"((?:[^"\\]|\\.)*)"|([^;]+))/i;
12
+ export function fileNameFromContentDisposition(header) {
13
+ if (header === null)
14
+ return undefined;
15
+ const extended = EXTENDED_FILENAME.exec(header)?.[1];
16
+ if (extended !== undefined) {
17
+ try {
18
+ return decodeURIComponent(extended.trim());
19
+ }
20
+ catch {
21
+ // malformed percent-encoding: fall back to the plain filename
22
+ }
23
+ }
24
+ const plain = PLAIN_FILENAME.exec(header);
25
+ const value = plain?.[1]?.replace(/\\(.)/g, "$1") ?? plain?.[2]?.trim();
26
+ return value === undefined || value === "" ? undefined : value;
27
+ }
28
+ export async function fetchFileRefBlob(fileRefId) {
29
+ const csrf = readCsrfToken();
30
+ const res = await fetch(`/api/files/${encodeURIComponent(fileRefId)}`, {
31
+ ...(csrf !== undefined && { headers: { [CSRF_HEADER_NAME]: csrf } }),
32
+ });
33
+ if (!res.ok)
34
+ throw new FileDownloadFailedError(res.status);
35
+ return {
36
+ blob: await res.blob(),
37
+ fileName: fileNameFromContentDisposition(res.headers.get("content-disposition")),
38
+ };
39
+ }
40
+ export function saveBlobAsFile(fileName, blob, options) {
41
+ const payload = options?.bom === true ? new Blob([UTF8_BOM, blob], { type: blob.type }) : blob;
42
+ const url = URL.createObjectURL(payload);
43
+ try {
44
+ const anchor = document.createElement("a");
45
+ anchor.href = url;
46
+ anchor.download = fileName;
47
+ document.body.appendChild(anchor);
48
+ try {
49
+ anchor.click();
50
+ }
51
+ finally {
52
+ anchor.remove();
53
+ }
54
+ }
55
+ finally {
56
+ URL.revokeObjectURL(url);
57
+ }
58
+ }
59
+ export async function downloadFileRef(fileRefId, options) {
60
+ const { blob, fileName } = await fetchFileRefBlob(fileRefId);
61
+ saveBlobAsFile(fileName ?? options?.fileName ?? fileRefId, blob);
62
+ }
@@ -0,0 +1,7 @@
1
+ export type FileUploadFormDataInput = {
2
+ readonly file: Blob;
3
+ readonly entityType?: string | undefined;
4
+ readonly entityId?: string | undefined;
5
+ readonly fieldName?: string | undefined;
6
+ };
7
+ export declare function buildFileUploadFormData(input: FileUploadFormDataInput): FormData;
@@ -0,0 +1,11 @@
1
+ export function buildFileUploadFormData(input) {
2
+ const formData = new FormData();
3
+ formData.append("file", input.file);
4
+ if (input.entityType !== undefined)
5
+ formData.append("entityType", input.entityType);
6
+ if (input.entityId !== undefined)
7
+ formData.append("entityId", input.entityId);
8
+ if (input.fieldName !== undefined)
9
+ formData.append("fieldName", input.fieldName);
10
+ return formData;
11
+ }
@@ -0,0 +1 @@
1
+ export declare function resizeImageBeforeUpload(file: File, maxEdge?: number): Promise<File>;