@cosmicdrift/kumiko-renderer 0.388.0 → 0.390.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 (379) hide show
  1. package/dist/app/base-path-context.d.ts +9 -0
  2. package/dist/app/base-path-context.js +13 -0
  3. package/dist/app/base-path.d.ts +3 -0
  4. package/dist/app/base-path.js +27 -0
  5. package/dist/app/config-edit-body.d.ts +1 -1
  6. package/dist/app/config-edit-body.js +15 -6
  7. package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
  8. package/dist/app/config-edit-without-secret-fields.js +28 -0
  9. package/dist/app/entity-list-facet-specs.d.ts +3 -0
  10. package/dist/app/entity-list-facet-specs.js +65 -0
  11. package/dist/app/entity-list-screen.js +74 -206
  12. package/dist/app/kumiko-screen.js +6 -14
  13. package/dist/app/list-bulk-actions.d.ts +18 -0
  14. package/dist/app/list-bulk-actions.js +54 -0
  15. package/dist/app/locked-screen-header.d.ts +9 -0
  16. package/dist/app/locked-screen-header.js +33 -0
  17. package/dist/app/projection-detail-body.js +59 -394
  18. package/dist/app/projection-detail-header-model.d.ts +11 -0
  19. package/dist/app/projection-detail-header-model.js +33 -0
  20. package/dist/app/projection-detail-header-region.d.ts +28 -0
  21. package/dist/app/projection-detail-header-region.js +81 -0
  22. package/dist/app/projection-detail-header.d.ts +29 -0
  23. package/dist/app/projection-detail-header.js +84 -0
  24. package/dist/app/projection-detail-navigation.d.ts +13 -0
  25. package/dist/app/projection-detail-navigation.js +91 -0
  26. package/dist/app/projection-detail-states.d.ts +22 -0
  27. package/dist/app/projection-detail-states.js +26 -0
  28. package/dist/app/projection-drawer-host.d.ts +12 -0
  29. package/dist/app/projection-drawer-host.js +10 -0
  30. package/dist/app/projection-list-body.d.ts +1 -1
  31. package/dist/app/projection-list-body.js +82 -197
  32. package/dist/app/projection-list-render-props.d.ts +56 -0
  33. package/dist/app/projection-list-render-props.js +47 -0
  34. package/dist/app/projection-list-render.d.ts +26 -0
  35. package/dist/app/projection-list-render.js +20 -0
  36. package/dist/app/projection-list-row-click.d.ts +12 -0
  37. package/dist/app/projection-list-row-click.js +14 -0
  38. package/dist/app/row-actions.d.ts +3 -0
  39. package/dist/app/row-actions.js +10 -5
  40. package/dist/app/screen-shared.js +9 -2
  41. package/dist/app/use-entity-range-facets.d.ts +24 -0
  42. package/dist/app/use-entity-range-facets.js +64 -0
  43. package/dist/app/use-projection-date-range.d.ts +12 -0
  44. package/dist/app/use-projection-date-range.js +21 -0
  45. package/dist/app/use-projection-detail-actions.d.ts +35 -0
  46. package/dist/app/use-projection-detail-actions.js +156 -0
  47. package/dist/app/use-projection-detail-data.d.ts +19 -0
  48. package/dist/app/use-projection-detail-data.js +47 -0
  49. package/dist/app/use-projection-detail-layout.d.ts +17 -0
  50. package/dist/app/use-projection-detail-layout.js +23 -0
  51. package/dist/app/use-projection-list-actions.d.ts +31 -0
  52. package/dist/app/use-projection-list-actions.js +73 -0
  53. package/dist/app/use-projection-list-data.d.ts +20 -0
  54. package/dist/app/use-projection-list-data.js +26 -0
  55. package/dist/app/use-projection-list-definitions.d.ts +11 -0
  56. package/dist/app/use-projection-list-definitions.js +20 -0
  57. package/dist/app/use-projection-list-query.d.ts +28 -0
  58. package/dist/app/use-projection-list-query.js +83 -0
  59. package/dist/app/use-screen-list-rows.d.ts +37 -0
  60. package/dist/app/use-screen-list-rows.js +104 -0
  61. package/dist/app/use-screen-list-table.d.ts +24 -0
  62. package/dist/app/use-screen-list-table.js +46 -0
  63. package/dist/components/list-group-page-bridges.d.ts +3 -0
  64. package/dist/components/list-group-page-bridges.js +31 -0
  65. package/dist/components/list-state-store.d.ts +16 -0
  66. package/dist/components/list-state-store.js +31 -0
  67. package/dist/components/related-list-section.js +37 -89
  68. package/dist/components/render-edit-banners.d.ts +12 -0
  69. package/dist/components/render-edit-banners.js +90 -0
  70. package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
  71. package/dist/components/render-edit-draft-save-helpers.js +42 -0
  72. package/dist/components/render-edit-field-grid.d.ts +4 -0
  73. package/dist/components/render-edit-field-grid.js +40 -0
  74. package/dist/components/render-edit-fields-section.d.ts +4 -0
  75. package/dist/components/render-edit-fields-section.js +76 -0
  76. package/dist/components/render-edit-form-actions.d.ts +4 -0
  77. package/dist/components/render-edit-form-actions.js +66 -0
  78. package/dist/components/render-edit-header-actions.d.ts +11 -0
  79. package/dist/components/render-edit-header-actions.js +120 -0
  80. package/dist/components/render-edit-layout-flags.d.ts +29 -0
  81. package/dist/components/render-edit-layout-flags.js +106 -0
  82. package/dist/components/render-edit-layout.d.ts +4 -0
  83. package/dist/components/render-edit-layout.js +79 -0
  84. package/dist/components/render-edit-render-model.d.ts +63 -0
  85. package/dist/components/render-edit-render-model.js +14 -0
  86. package/dist/components/render-edit-section-kinds.d.ts +4 -0
  87. package/dist/components/render-edit-section-kinds.js +115 -0
  88. package/dist/components/render-edit-submit-result.d.ts +8 -0
  89. package/dist/components/render-edit-submit-result.js +77 -0
  90. package/dist/components/render-edit-submit.d.ts +7 -0
  91. package/dist/components/render-edit-submit.js +113 -0
  92. package/dist/components/render-edit-titles.d.ts +17 -0
  93. package/dist/components/render-edit-titles.js +99 -0
  94. package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
  95. package/dist/components/render-edit-wizard-navigation.js +115 -0
  96. package/dist/components/render-edit.d.ts +2 -2
  97. package/dist/components/render-edit.js +63 -1128
  98. package/dist/components/render-field-choice-inputs.d.ts +7 -0
  99. package/dist/components/render-field-choice-inputs.js +52 -0
  100. package/dist/components/render-field-control.d.ts +12 -0
  101. package/dist/components/render-field-control.js +93 -0
  102. package/dist/components/render-field-display-text.d.ts +4 -0
  103. package/dist/components/render-field-display-text.js +48 -0
  104. package/dist/components/render-field-fill-action.d.ts +13 -0
  105. package/dist/components/render-field-fill-action.js +63 -0
  106. package/dist/components/render-field-input-shared.d.ts +39 -0
  107. package/dist/components/render-field-input-shared.js +20 -0
  108. package/dist/components/render-field-input.d.ts +3 -0
  109. package/dist/components/render-field-input.js +34 -0
  110. package/dist/components/render-field-meta.d.ts +6 -0
  111. package/dist/components/render-field-meta.js +51 -0
  112. package/dist/components/render-field-read-only.d.ts +11 -0
  113. package/dist/components/render-field-read-only.js +75 -0
  114. package/dist/components/render-field-reference.d.ts +15 -0
  115. package/dist/components/render-field-reference.js +103 -0
  116. package/dist/components/render-field-scalar-inputs.d.ts +10 -0
  117. package/dist/components/render-field-scalar-inputs.js +35 -0
  118. package/dist/components/render-field-text-inputs.d.ts +3 -0
  119. package/dist/components/render-field-text-inputs.js +34 -0
  120. package/dist/components/render-field-values.d.ts +18 -0
  121. package/dist/components/render-field-values.js +111 -0
  122. package/dist/components/render-field-write-only.d.ts +9 -0
  123. package/dist/components/render-field-write-only.js +29 -0
  124. package/dist/components/render-field.d.ts +2 -2
  125. package/dist/components/render-field.js +39 -669
  126. package/dist/components/render-list-header-slot.d.ts +5 -0
  127. package/dist/components/render-list-header-slot.js +30 -0
  128. package/dist/components/render-list-reference-bridge.d.ts +3 -0
  129. package/dist/components/render-list-reference-bridge.js +25 -0
  130. package/dist/components/render-list-reference-columns.d.ts +25 -0
  131. package/dist/components/render-list-reference-columns.js +137 -0
  132. package/dist/components/render-list-table-props.d.ts +52 -0
  133. package/dist/components/render-list-table-props.js +53 -0
  134. package/dist/components/render-list-toolbar-action.d.ts +27 -0
  135. package/dist/components/render-list-toolbar-action.js +40 -0
  136. package/dist/components/render-list-toolbar-model.d.ts +42 -0
  137. package/dist/components/render-list-toolbar-model.js +82 -0
  138. package/dist/components/render-list-toolbar.d.ts +38 -0
  139. package/dist/components/render-list-toolbar.js +40 -0
  140. package/dist/components/render-list-types.d.ts +113 -0
  141. package/dist/components/render-list-types.js +1 -0
  142. package/dist/components/render-list.d.ts +5 -130
  143. package/dist/components/render-list.js +95 -319
  144. package/dist/components/use-reference-input-state.d.ts +20 -0
  145. package/dist/components/use-reference-input-state.js +85 -0
  146. package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
  147. package/dist/components/use-render-edit-controlled-sync.js +26 -0
  148. package/dist/components/use-render-edit-draft-save.d.ts +29 -0
  149. package/dist/components/use-render-edit-draft-save.js +141 -0
  150. package/dist/components/use-render-edit-form.d.ts +10 -0
  151. package/dist/components/use-render-edit-form.js +101 -0
  152. package/dist/components/use-render-edit-sections.d.ts +15 -0
  153. package/dist/components/use-render-edit-sections.js +100 -0
  154. package/dist/components/use-render-edit-state.d.ts +65 -0
  155. package/dist/components/use-render-edit-state.js +158 -0
  156. package/dist/components/use-render-edit-steps.d.ts +13 -0
  157. package/dist/components/use-render-edit-steps.js +52 -0
  158. package/dist/components/use-render-list-columns.d.ts +18 -0
  159. package/dist/components/use-render-list-columns.js +41 -0
  160. package/dist/components/use-render-list-search.d.ts +4 -0
  161. package/dist/components/use-render-list-search.js +50 -0
  162. package/dist/current-user.d.ts +2 -0
  163. package/dist/data-table-aggregate-types.d.ts +47 -0
  164. package/dist/data-table-aggregate-types.js +1 -0
  165. package/dist/data-table-bulk-types.d.ts +27 -0
  166. package/dist/data-table-bulk-types.js +1 -0
  167. package/dist/data-table-facet-types.d.ts +36 -0
  168. package/dist/data-table-facet-types.js +1 -0
  169. package/dist/data-table-view-types.d.ts +73 -0
  170. package/dist/data-table-view-types.js +1 -0
  171. package/dist/headless/view-model/edit.js +3 -1
  172. package/dist/headless/view-model/types.d.ts +4 -2
  173. package/dist/hooks/list-column-layout.d.ts +19 -0
  174. package/dist/hooks/list-column-layout.js +36 -0
  175. package/dist/hooks/list-group-pages.d.ts +26 -0
  176. package/dist/hooks/list-group-pages.js +37 -0
  177. package/dist/hooks/list-reset.d.ts +8 -0
  178. package/dist/hooks/list-reset.js +15 -0
  179. package/dist/hooks/list-server-groups.d.ts +39 -0
  180. package/dist/hooks/list-server-groups.js +84 -0
  181. package/dist/hooks/list-state-schema.d.ts +28 -0
  182. package/dist/hooks/list-state-schema.js +76 -0
  183. package/dist/hooks/use-bulk-run.d.ts +41 -0
  184. package/dist/hooks/use-bulk-run.js +65 -0
  185. package/dist/hooks/use-group-load-state.d.ts +11 -0
  186. package/dist/hooks/use-group-load-state.js +31 -0
  187. package/dist/hooks/use-list-aggregates.d.ts +38 -0
  188. package/dist/hooks/use-list-aggregates.js +107 -0
  189. package/dist/hooks/use-list-table.d.ts +38 -0
  190. package/dist/hooks/use-list-table.js +91 -0
  191. package/dist/hooks/use-list-view-state.d.ts +43 -0
  192. package/dist/hooks/use-list-view-state.js +161 -0
  193. package/dist/hooks/use-related-list-rows.d.ts +27 -0
  194. package/dist/hooks/use-related-list-rows.js +81 -0
  195. package/dist/hooks/use-related-list-state.d.ts +15 -0
  196. package/dist/hooks/use-related-list-state.js +31 -0
  197. package/dist/hooks/use-remembered-list-params.d.ts +13 -0
  198. package/dist/hooks/use-remembered-list-params.js +53 -0
  199. package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
  200. package/dist/hooks/use-remembered-local-list-state.js +63 -0
  201. package/dist/i18n-defaults.js +53 -0
  202. package/dist/index.d.ts +3 -1
  203. package/dist/index.js +2 -0
  204. package/dist/primitives.d.ts +11 -47
  205. package/dist/web/app/create-app.d.ts +5 -0
  206. package/dist/web/app/create-app.js +7 -4
  207. package/dist/web/app/dashboard-body.d.ts +1 -1
  208. package/dist/web/app/dashboard-body.js +42 -246
  209. package/dist/web/app/dashboard-header.d.ts +34 -0
  210. package/dist/web/app/dashboard-header.js +58 -0
  211. package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
  212. package/dist/web/app/dashboard-screen-controls.js +42 -0
  213. package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
  214. package/dist/web/app/dashboard-screen-tiles.js +42 -0
  215. package/dist/web/app/dashboard-visibility.d.ts +42 -0
  216. package/dist/web/app/dashboard-visibility.js +102 -0
  217. package/dist/web/app/history-leave-guard.d.ts +6 -0
  218. package/dist/web/app/history-leave-guard.js +115 -0
  219. package/dist/web/app/list-state-store-boot.d.ts +6 -0
  220. package/dist/web/app/list-state-store-boot.js +15 -0
  221. package/dist/web/app/nav.js +7 -54
  222. package/dist/web/app/session-list-state-store.d.ts +15 -0
  223. package/dist/web/app/session-list-state-store.js +117 -0
  224. package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
  225. package/dist/web/app/use-dashboard-screen-state.js +69 -0
  226. package/dist/web/index.d.ts +1 -0
  227. package/dist/web/index.js +1 -0
  228. package/dist/web/lib/export-xlsx-request.d.ts +11 -0
  229. package/dist/web/lib/export-xlsx-request.js +46 -0
  230. package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
  231. package/dist/web/primitives/data-table/actions-column.js +35 -0
  232. package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
  233. package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
  234. package/dist/web/primitives/data-table/card-body.d.ts +6 -0
  235. package/dist/web/primitives/data-table/card-body.js +32 -0
  236. package/dist/web/primitives/data-table/card-row.d.ts +5 -0
  237. package/dist/web/primitives/data-table/card-row.js +31 -0
  238. package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
  239. package/dist/web/primitives/data-table/card-sort-select.js +40 -0
  240. package/dist/web/primitives/data-table/card-view.d.ts +3 -0
  241. package/dist/web/primitives/data-table/card-view.js +13 -0
  242. package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
  243. package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
  244. package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
  245. package/dist/web/primitives/data-table/column-picker-section.js +35 -0
  246. package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
  247. package/dist/web/primitives/data-table/column-resize-handle.js +48 -0
  248. package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
  249. package/dist/web/primitives/data-table/data-cell-props.js +8 -0
  250. package/dist/web/primitives/data-table/density.d.ts +14 -0
  251. package/dist/web/primitives/data-table/density.js +20 -0
  252. package/dist/web/primitives/data-table/export-button.d.ts +5 -0
  253. package/dist/web/primitives/data-table/export-button.js +37 -0
  254. package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
  255. package/dist/web/primitives/data-table/facet-cluster.js +37 -0
  256. package/dist/web/primitives/data-table/group-band.d.ts +28 -0
  257. package/dist/web/primitives/data-table/group-band.js +53 -0
  258. package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
  259. package/dist/web/primitives/data-table/measure-value.js +28 -0
  260. package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
  261. package/dist/web/primitives/data-table/pin-section.js +20 -0
  262. package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
  263. package/dist/web/primitives/data-table/reset-all-button.js +8 -0
  264. package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
  265. package/dist/web/primitives/data-table/row-render-parts.js +35 -0
  266. package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
  267. package/dist/web/primitives/data-table/server-group-header.js +51 -0
  268. package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
  269. package/dist/web/primitives/data-table/sortable-header.js +41 -0
  270. package/dist/web/primitives/data-table/table-body.d.ts +3 -0
  271. package/dist/web/primitives/data-table/table-body.js +32 -0
  272. package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
  273. package/dist/web/primitives/data-table/table-chrome.js +30 -0
  274. package/dist/web/primitives/data-table/table-content.d.ts +3 -0
  275. package/dist/web/primitives/data-table/table-content.js +41 -0
  276. package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
  277. package/dist/web/primitives/data-table/table-data-cell.js +25 -0
  278. package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
  279. package/dist/web/primitives/data-table/table-footer.js +20 -0
  280. package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
  281. package/dist/web/primitives/data-table/table-header-row.js +21 -0
  282. package/dist/web/primitives/data-table/table-view.d.ts +3 -0
  283. package/dist/web/primitives/data-table/table-view.js +25 -0
  284. package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
  285. package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
  286. package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
  287. package/dist/web/primitives/data-table/toolbar.js +53 -0
  288. package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
  289. package/dist/web/primitives/data-table/totals-footer.js +48 -0
  290. package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
  291. package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
  292. package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
  293. package/dist/web/primitives/data-table/use-column-pins.js +117 -0
  294. package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
  295. package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
  296. package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
  297. package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
  298. package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
  299. package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
  300. package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
  301. package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
  302. package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
  303. package/dist/web/primitives/data-table/use-row-selection.js +48 -0
  304. package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
  305. package/dist/web/primitives/data-table/use-view-columns.js +17 -0
  306. package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
  307. package/dist/web/primitives/data-table/view-menu.js +45 -0
  308. package/dist/web/primitives/data-table-cell.d.ts +0 -22
  309. package/dist/web/primitives/data-table-cell.js +1 -58
  310. package/dist/web/primitives/data-table.d.ts +3 -4
  311. package/dist/web/primitives/data-table.js +30 -272
  312. package/dist/web/primitives/file-value-list.d.ts +1 -1
  313. package/dist/web/primitives/file-value-list.js +5 -5
  314. package/dist/web/primitives/form-footer-parts.d.ts +25 -0
  315. package/dist/web/primitives/form-footer-parts.js +55 -0
  316. package/dist/web/primitives/form.js +33 -43
  317. package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
  318. package/dist/web/primitives/input-choice-kinds.js +67 -0
  319. package/dist/web/primitives/input-file-kinds.d.ts +3 -0
  320. package/dist/web/primitives/input-file-kinds.js +6 -0
  321. package/dist/web/primitives/input-number-kinds.d.ts +4 -0
  322. package/dist/web/primitives/input-number-kinds.js +16 -0
  323. package/dist/web/primitives/input-optional-props.d.ts +20 -0
  324. package/dist/web/primitives/input-optional-props.js +24 -0
  325. package/dist/web/primitives/input-select-presentation.d.ts +13 -0
  326. package/dist/web/primitives/input-select-presentation.js +19 -0
  327. package/dist/web/primitives/input-shared.d.ts +22 -0
  328. package/dist/web/primitives/input-shared.js +10 -0
  329. package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
  330. package/dist/web/primitives/input-temporal-kinds.js +10 -0
  331. package/dist/web/primitives/input-text-kinds.d.ts +7 -0
  332. package/dist/web/primitives/input-text-kinds.js +33 -0
  333. package/dist/web/primitives/input.js +35 -161
  334. package/dist/web/primitives/number-range-facet.d.ts +7 -0
  335. package/dist/web/primitives/number-range-facet.js +33 -0
  336. package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
  337. package/dist/web/widgets/bounded-load-cache.js +48 -26
  338. package/dist/web/widgets/chart-data.d.ts +35 -0
  339. package/dist/web/widgets/chart-data.js +37 -0
  340. package/dist/web/widgets/chart-markers.d.ts +26 -0
  341. package/dist/web/widgets/chart-markers.js +64 -0
  342. package/dist/web/widgets/chart-parts.d.ts +20 -0
  343. package/dist/web/widgets/chart-parts.js +15 -0
  344. package/dist/web/widgets/charts.d.ts +5 -78
  345. package/dist/web/widgets/charts.js +7 -485
  346. package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
  347. package/dist/web/widgets/drawer-panel-class.js +40 -0
  348. package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
  349. package/dist/web/widgets/drawer-resize-controls.js +12 -0
  350. package/dist/web/widgets/drawer-sizing.d.ts +19 -0
  351. package/dist/web/widgets/drawer-sizing.js +60 -0
  352. package/dist/web/widgets/drawer.js +17 -116
  353. package/dist/web/widgets/pdf-preview.js +9 -4
  354. package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
  355. package/dist/web/widgets/stacked-area-brush.js +91 -0
  356. package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
  357. package/dist/web/widgets/stacked-area-chart.js +45 -0
  358. package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
  359. package/dist/web/widgets/stacked-area-chrome.js +77 -0
  360. package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
  361. package/dist/web/widgets/stacked-area-geometry.js +83 -0
  362. package/dist/web/widgets/stacked-area-model.d.ts +11 -0
  363. package/dist/web/widgets/stacked-area-model.js +1 -0
  364. package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
  365. package/dist/web/widgets/stacked-area-plot.js +51 -0
  366. package/dist/web/widgets/stacked-area-props.d.ts +83 -0
  367. package/dist/web/widgets/stacked-area-props.js +16 -0
  368. package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
  369. package/dist/web/widgets/stacked-area-svg.js +38 -0
  370. package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
  371. package/dist/web/widgets/stacked-area-tooltip.js +86 -0
  372. package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
  373. package/dist/web/widgets/use-drawer-resize.js +52 -0
  374. package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
  375. package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
  376. package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
  377. package/dist/web/widgets/use-stacked-area-window.js +76 -0
  378. package/package.json +9 -9
  379. package/src/changes.json +163 -0
@@ -1,6 +1,9 @@
1
1
  import type { ConfigCascade, ConfigScope, ConfigValueSource } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import type { FieldIconKey, FormWidth, IconKey, NavIconKey, RowActionDisplay, SelectOptionTone } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { type ComponentType, type FormEvent, type KeyboardEvent, type ReactNode, type Ref } from "react";
4
+ import type { DataTableBulk } from "./data-table-bulk-types.ts";
5
+ import type { DataTableDateRangeFacet, DataTableFacet } from "./data-table-facet-types.ts";
6
+ import type { DataTableTableBlock } from "./data-table-view-types.ts";
4
7
  import type { FieldIssue, ListColumnViewModel, ListRowViewModel, RuntimeRenderer } from "./headless/index.ts";
5
8
  export type { RuntimeRenderer };
6
9
  /** Escape hatch for `data-*` test hooks (e.g. an E2E selector) that don't
@@ -647,55 +650,11 @@ export type DataTableRowAction = {
647
650
  export declare function shouldRenderActionsIconOnly(actions: readonly {
648
651
  readonly icon?: IconKey;
649
652
  }[]): boolean;
650
- export type DataTableFacet = {
651
- /** Entity-Feldname (= URL-State-Key + payload.filters[].field). */
652
- readonly field: string;
653
- /** Translated Button-Label ("Status", "Active"). */
654
- readonly label: string;
655
- /** Selectable values with display labels. `value` is the raw filter value
656
- * (a select option or `"true"`/`"false"`), transported as a string. */
657
- readonly options: readonly {
658
- readonly value: string;
659
- readonly label: string;
660
- }[];
661
- /** Present: render the facet as a row of single-choice toggle chips instead
662
- * of a dropdown. A chip is pressed while the selection equals its values. */
663
- readonly chips?: readonly DataTableFacetChip[];
664
- /** Chips only: show each chip's `count` next to its label. */
665
- readonly showCounts?: boolean;
666
- /** Chips only: hide chips whose `count` is 0 (unless pressed). */
667
- readonly hideEmpty?: boolean;
668
- };
669
- export type DataTableFacetChip = {
670
- readonly id: string;
671
- /** Translated chip label. */
672
- readonly label: string;
673
- /** Selection a click sets; `[]` clears the facet. */
674
- readonly values: readonly string[];
675
- /** Matching row count, undefined while unknown. */
676
- readonly count?: number;
677
- };
678
- export type DataTableDateRangeFacet = {
679
- /** The facet's namespace (its date field) — a key, not a filter value. */
680
- readonly field: string;
681
- /** Translated label ("When"). */
682
- readonly label: string;
683
- readonly from: string;
684
- readonly to: string;
685
- };
686
- /** Splits rows into collapsible groups (order of first appearance). A
687
- * renderer without grouping support shows the flat list. A group whose
688
- * `headerLabel` is undefined shows its rows without a header and never
689
- * collapses. */
690
- export type DataTableRowGrouping = {
691
- readonly keyOf: (row: ListRowViewModel) => string;
692
- readonly headerLabel: (groupKey: string, rows: readonly ListRowViewModel[]) => string | undefined;
693
- readonly startsCollapsed: (groupKey: string) => boolean;
694
- };
653
+ export type { BulkRowOutcome, DataTableBulk, DataTableBulkAction, } from "./data-table-bulk-types.ts";
654
+ export type { DataTableDateRangeFacet, DataTableFacet, DataTableFacetChip, } from "./data-table-facet-types.ts";
695
655
  export type DataTableProps = {
696
656
  readonly columns: readonly ListColumnViewModel[];
697
657
  readonly rows: readonly ListRowViewModel[];
698
- readonly rowGrouping?: DataTableRowGrouping;
699
658
  /** Tints a row. Renderers without row tinting ignore it. */
700
659
  readonly rowTone?: (row: ListRowViewModel) => SelectOptionTone | undefined;
701
660
  readonly onRowClick?: (row: ListRowViewModel) => void;
@@ -723,6 +682,8 @@ export type DataTableProps = {
723
682
  * kebab). Actions then sit at the same x-position across all rows
724
683
  * (no drifting with label width) and all lists in an app look alike. */
725
684
  readonly rowActionMode?: DataTableRowActionMode;
685
+ /** Present: row checkboxes plus a selection bar that runs these actions once per selected row. */
686
+ readonly bulk?: DataTableBulk;
726
687
  /** Custom empty-state content (e.g. Icon + Heading + CTA-Button).
727
688
  * The default renderer wraps it in a dashed-border box. */
728
689
  readonly emptyState?: ReactNode;
@@ -832,7 +793,8 @@ export type DataTableProps = {
832
793
  /** Content of the area under an expanded row. Native renderers implement
833
794
  * the same three props. */
834
795
  readonly renderExpandedRow?: (row: ListRowViewModel) => ReactNode;
835
- };
796
+ } & DataTableTableBlock;
797
+ export type DataTableTableBlockProps = Pick<DataTableProps, keyof DataTableTableBlock>;
836
798
  /** Cell type for one column of an embedded-list field. Mirrors
837
799
  * EmbeddedListCellViewModel["type"] from `@cosmicdrift/kumiko-renderer/headless`. */
838
800
  export type EmbeddedListCellType = "text" | "number" | "boolean" | "date" | "money" | "decimal" | "select" | "reference" | "timestamp";
@@ -1403,6 +1365,8 @@ export type JsonViewProps = {
1403
1365
  * PDFs, an inline preview. Used for `renderer: { format: "file" }` on a detail field. */
1404
1366
  export type FileValueListProps = {
1405
1367
  readonly fileIds: readonly string[];
1368
+ /** Starts the inline PDF preview of the newest file open; uploads append, so that is the last id. */
1369
+ readonly previewOpenByDefault?: boolean;
1406
1370
  readonly testId?: string;
1407
1371
  };
1408
1372
  /** Band above the tabs of a record detail: optional subtitle line plus the
@@ -92,6 +92,11 @@ export type CreateKumikoAppOptions = {
92
92
  readonly hasWorkspaces?: boolean;
93
93
  readonly features?: readonly FeatureSchema[];
94
94
  }) => NavApi;
95
+ /** URL prefix the app is mounted under (for example "/admin"). The default nav adapter reads
96
+ * and writes URLs relative to it, and the auth gates and pages, which sit outside the
97
+ * NavProvider, use it to match and build their paths. A custom `navAdapter` handles its own
98
+ * prefix. */
99
+ readonly basePath?: string;
95
100
  /** App-wide default width for form/detail screens that don't set their
96
101
  * own `layout.width`. Default "4xl" (unchanged behavior) — a
97
102
  * consumer app that wants e.g. bundled-feature screens to match its own
@@ -3,7 +3,7 @@ import { isOpenToAllGranted } from "@cosmicdrift/kumiko-framework/ui-types";
3
3
  import { createContext, useContext, useEffect, useMemo, useRef, useState, } from "react";
4
4
  import { createRoot } from "react-dom/client";
5
5
  import { rendererError, rendererWarn } from "../../dev-console.js";
6
- import { AppFeaturesProvider, ClientActionsProvider, ColumnRenderersProvider, ContentEditorsProvider, CustomScreensProvider, createSessionEndedSignal, DashboardBodyProvider, DispatcherProvider, DraftStorageProvider, ErrorBoundary, ExtensionSectionsProvider, hasDetailScreen, KumikoScreen, kumikoDefaultTranslations, LeaveGuardDialog, LiveEventsProvider, LocaleProvider, mergeTranslations, NavProvider, navigateWithReturnTo, PrimitivesProvider, qualifyScreenId, ScreenMissingState, SessionEndedSignalProvider, TokensProvider, toAppSchema, translationsByLocaleFromKeys, useNav, usePrimitives, useTranslation, withSessionEndedDetection, } from "../../index.js";
6
+ import { AppFeaturesProvider, BasePathProvider, ClientActionsProvider, ColumnRenderersProvider, ContentEditorsProvider, CustomScreensProvider, createSessionEndedSignal, DashboardBodyProvider, DispatcherProvider, DraftStorageProvider, ErrorBoundary, ExtensionSectionsProvider, hasDetailScreen, KumikoScreen, kumikoDefaultTranslations, LeaveGuardDialog, LiveEventsProvider, LocaleProvider, mergeTranslations, NavProvider, navigateWithReturnTo, PrimitivesProvider, qualifyScreenId, ScreenMissingState, SessionEndedSignalProvider, TokensProvider, toAppSchema, translationsByLocaleFromKeys, useNav, usePrimitives, useTranslation, withSessionEndedDetection, } from "../../index.js";
7
7
  import { createLiveDispatcher } from "../../live/index.js";
8
8
  import { lastSegment } from "../layout/nav-tree.js";
9
9
  import { clearFileMetaCache } from "../primitives/file-thumbnail.js";
@@ -19,6 +19,7 @@ import { stackWrappers } from "./client-plugin.js";
19
19
  import { WebDashboardBody } from "./dashboard-body.js";
20
20
  import { DocumentLangSync } from "./document-lang-sync.js";
21
21
  import { createBrowserDraftStorage } from "./draft-storage.js";
22
+ import { ListStateStoreBoot } from "./list-state-store-boot.js";
22
23
  import { useBrowserNavApi } from "./nav.js";
23
24
  import { NavProvidersProvider } from "./nav-providers-context.js";
24
25
  import { ResolversProvider } from "./resolvers-context.js";
@@ -237,7 +238,7 @@ function KumikoAppRoot(props) {
237
238
  warnMissingCustomScreens(loaded, props.customScreens);
238
239
  setApp(loaded);
239
240
  }, schemaIsRoleProjected: isFetchMode, ...(isFetchMode && { onUnmountResetSchema: () => setApp(undefined) }), ...(props.translate !== undefined && { translate: props.translate }), ...(props.onRowClick !== undefined && { onRowClick: props.onRowClick }), ...(props.shell !== undefined && { shell: props.shell }) }));
240
- return (_jsx(TokensBoot, { children: _jsxs(LocaleProvider, { resolver: props.localeResolver, fallbackBundles: fallbackBundles, children: [_jsx(DocumentLangSync, { resolver: props.localeResolver }), _jsx(PrimitivesProvider, { value: props.primitives, children: _jsx(ScreenWidthProvider, { width: props.screenWidth, children: _jsx(AppFeaturesProvider, { features: app?.features ?? EMPTY_FEATURES, children: _jsx(DispatcherProvider, { dispatcher: props.dispatcher, children: _jsx(DraftStorageProvider, { value: props.draftStorage, children: _jsx(LiveEventsProvider, { value: props.liveEvents, children: _jsx(DashboardBodyProvider, { value: WebDashboardBody, children: _jsx(CustomScreensProvider, { value: props.customScreens, children: _jsx(ColumnRenderersProvider, { value: props.columnRenderers, children: _jsx(ContentEditorsProvider, { value: props.contentEditors, children: _jsx(ExtensionSectionsProvider, { value: props.extensionSectionComponents, children: _jsx(ClientActionsProvider, { value: props.clientActions, children: _jsx(NavProvidersProvider, { value: navProviders, entities: navEntities, children: _jsx(ResolversProvider, { resolvers: props.resolvers, children: _jsx(ToastProvider, { children: _jsxs(ToastActionNotifier, { children: [_jsx(UpdateChecker, {}), _jsx(LeaveGuardDialog, {}), _jsx(SessionEndedSignalProvider, { signal: props.sessionEndedSignal, children: stackWrappers(props.providers, stackWrappers(props.gates, screenNode)) })] }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) })] }) }));
241
+ return (_jsx(TokensBoot, { children: _jsxs(LocaleProvider, { resolver: props.localeResolver, fallbackBundles: fallbackBundles, children: [_jsx(DocumentLangSync, { resolver: props.localeResolver }), _jsx(PrimitivesProvider, { value: props.primitives, children: _jsx(ScreenWidthProvider, { width: props.screenWidth, children: _jsx(AppFeaturesProvider, { features: app?.features ?? EMPTY_FEATURES, children: _jsx(DispatcherProvider, { dispatcher: props.dispatcher, children: _jsx(DraftStorageProvider, { value: props.draftStorage, children: _jsx(LiveEventsProvider, { value: props.liveEvents, children: _jsx(DashboardBodyProvider, { value: WebDashboardBody, children: _jsx(CustomScreensProvider, { value: props.customScreens, children: _jsx(ColumnRenderersProvider, { value: props.columnRenderers, children: _jsx(ContentEditorsProvider, { value: props.contentEditors, children: _jsx(ExtensionSectionsProvider, { value: props.extensionSectionComponents, children: _jsx(ClientActionsProvider, { value: props.clientActions, children: _jsx(NavProvidersProvider, { value: navProviders, entities: navEntities, children: _jsx(ResolversProvider, { resolvers: props.resolvers, children: _jsx(ToastProvider, { children: _jsxs(ToastActionNotifier, { children: [_jsx(UpdateChecker, {}), _jsx(LeaveGuardDialog, {}), _jsx(SessionEndedSignalProvider, { signal: props.sessionEndedSignal, children: stackWrappers(props.providers, stackWrappers(props.gates, _jsx(ListStateStoreBoot, { children: screenNode }))) })] }) }) }) }) }) }) }) }) }) }) }) }) }) }) }) })] }) }));
241
242
  }
242
243
  // Sits INSIDE the clientFeature gates (e.g. an auth gate) — a gate that
243
244
  // renders a login placeholder instead of children keeps this component
@@ -340,7 +341,9 @@ export function createKumikoApp(options = {}) {
340
341
  }
341
342
  }
342
343
  const localeResolver = options.locale ?? createBrowserLocaleResolver();
343
- const navAdapter = options.navAdapter ?? useBrowserNavApi;
344
+ const { basePath } = options;
345
+ const useBrowserNavApiWithBasePath = (navOptions) => useBrowserNavApi({ ...navOptions, ...(basePath !== undefined && { basePath }) });
346
+ const navAdapter = options.navAdapter ?? useBrowserNavApiWithBasePath;
344
347
  const reporter = options.reportClientErrors === false ? undefined : createClientErrorReporter();
345
348
  reporter?.installGlobalHandlers();
346
349
  const handleBoundaryError = (error, info, context) => {
@@ -353,7 +356,7 @@ export function createKumikoApp(options = {}) {
353
356
  options.onError?.(error, info, context);
354
357
  };
355
358
  const root = createRoot(container);
356
- root.render(_jsx(ErrorBoundary, { scope: "root", onError: handleBoundaryError, fallback: ({ errorId }) => _jsx(RootErrorFallback, { errorId: errorId }), children: _jsx(BoundaryErrorHandlerContext.Provider, { value: handleBoundaryError, children: _jsx(KumikoAppRoot, { initialApp: initialApp, screenQn: options.screenQn, ...(options.schemaUrl !== undefined && { schemaUrl: options.schemaUrl }), clientFeatures: clientFeatures, providers: providers, gates: gates, customScreens: customScreens, columnRenderers: columnRenderers, extensionSectionComponents: extensionSectionComponents, clientActions: clientActions, contentEditors: contentEditors, resolvers: resolvers, dispatcher: dispatcher, sessionEndedSignal: sessionEndedSignal, draftStorage: draftStorage, primitives: primitives, liveEvents: liveEvents, localeResolver: localeResolver, navAdapter: navAdapter, screenWidth: options.screenWidth, ...(options.translate !== undefined && { translate: options.translate }), ...(options.onRowClick !== undefined && { onRowClick: options.onRowClick }), ...(options.shell !== undefined && { shell: options.shell }) }) }) }));
359
+ root.render(_jsx(ErrorBoundary, { scope: "root", onError: handleBoundaryError, fallback: ({ errorId }) => _jsx(RootErrorFallback, { errorId: errorId }), children: _jsx(BoundaryErrorHandlerContext.Provider, { value: handleBoundaryError, children: _jsx(BasePathProvider, { basePath: basePath, children: _jsx(KumikoAppRoot, { initialApp: initialApp, screenQn: options.screenQn, ...(options.schemaUrl !== undefined && { schemaUrl: options.schemaUrl }), clientFeatures: clientFeatures, providers: providers, gates: gates, customScreens: customScreens, columnRenderers: columnRenderers, extensionSectionComponents: extensionSectionComponents, clientActions: clientActions, contentEditors: contentEditors, resolvers: resolvers, dispatcher: dispatcher, sessionEndedSignal: sessionEndedSignal, draftStorage: draftStorage, primitives: primitives, liveEvents: liveEvents, localeResolver: localeResolver, navAdapter: navAdapter, screenWidth: options.screenWidth, ...(options.translate !== undefined && { translate: options.translate }), ...(options.onRowClick !== undefined && { onRowClick: options.onRowClick }), ...(options.shell !== undefined && { shell: options.shell }) }) }) }) }));
357
360
  return { root };
358
361
  }
359
362
  // TokensBoot uses the browser-backed TokensApi hook (class-based
@@ -1,3 +1,3 @@
1
- import { type ReactNode } from "react";
1
+ import type { ReactNode } from "react";
2
2
  import { type DashboardBodyProps } from "../../index.ts";
3
3
  export declare function WebDashboardBody({ featureName, screen, translate, }: DashboardBodyProps): ReactNode;
@@ -1,55 +1,12 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
3
- import { ErrorReference } from "../../app/error-reference.js";
4
- import { dispatcherErrorText, EmbeddedScreenProvider, evalVisibleWhen, HeaderActionsBar, KumikoScreen, useEmbeddedScreen, useLocale, useNav, usePageHeaderCompact, usePageHeaderSlotAvailable, usePrimitives, useQuery, useRecordActions, useTranslation, } from "../../index.js";
5
- import { cn } from "../lib/cn.js";
6
- import { EmbeddedFormProvider } from "../primitives/index.js";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useNav, usePageHeaderCompact, usePageHeaderSlotAvailable, usePrimitives, useTranslation, } from "../../index.js";
7
3
  import { PageSection } from "../primitives/layout.js";
8
- import { ModeSwitch } from "../widgets/mode-switch.js";
9
- import { SectionCard } from "../widgets/section-card.js";
10
- import { StatusBadge } from "../widgets/status-badge.js";
4
+ import { buildScopeBadge, dashboardDescription, recordTitleText, renderActionErrorBanner, renderDescriptionAndNotice, renderHeaderActionsBar, renderPageHeaderSlot, renderRecordError, renderRecordMissing, renderTitleRow, } from "./dashboard-header.js";
11
5
  import { ChartPanelBody, CustomPanelBody, FeedPanelBody, ListPanelBody, ProgressListPanelBody, StatGroupPanelBody, StatPanelBody, TextPanelBody, } from "./dashboard-panels.js";
12
- import { resolveRefreshIntervalMs } from "./dashboard-refresh.js";
13
- import { DashboardQueryContext, HALF_PANEL, PanelError, PanelLoadedContext, useDashboardFormats, useDashboardQuery, useLoadedAt, useRefetchRegistry, WIDE_PANEL, } from "./dashboard-shared.js";
14
- // Value lives in the URL under `filter.id` (like useListUrlState's
15
- // `<screenId>.page`, same replaceState) so a `navigate` rowAction with
16
- // `params` can deep-link onto this filter.
17
- function useFilterParams(screen, enabled) {
18
- const { Field, Input } = usePrimitives();
19
- const t = useTranslation();
20
- const nav = useNav();
21
- const filter = screen.filter;
22
- const value = filter !== undefined ? (nav.searchParams[filter.id] ?? "") : "";
23
- const optionsQueryResult = useQuery(filter?.optionsQuery ?? "", {}, { enabled: enabled && filter?.optionsQuery !== undefined });
24
- if (filter === undefined) {
25
- return { params: {}, picker: null };
26
- }
27
- const dynamicOptions = filter.optionsQuery !== undefined
28
- ? (optionsQueryResult.data?.rows ?? [])
29
- : (filter.options ?? []).map((o) => ({ value: o.value, label: t(o.label) }));
30
- const allLabel = t(filter.allLabel ?? "kumiko.dashboard.filter.all");
31
- const options = [{ value: "", label: allLabel }, ...dynamicOptions];
32
- const picker = (_jsx("div", { className: "max-w-xs", children: _jsx(Field, { id: `dashboard-filter-${filter.id}`, label: t(filter.label), children: _jsx(Input, { kind: "combobox", id: `dashboard-filter-${filter.id}`, name: filter.id, options: options, value: value, onChange: (next) => nav.setSearchParams({ [filter.id]: next === "" ? null : next }), placeholder: filter.placeholder !== undefined ? t(filter.placeholder) : allLabel }) }) }));
33
- const params = value === "" ? {} : { [filter.id]: value };
34
- return { params, picker };
35
- }
36
- // An unknown/stale URL value falls back to the declared default.
37
- function useTimeRange(screen, translate) {
38
- const t = useTranslation();
39
- const nav = useNav();
40
- const resolver = useLocale();
41
- const timeRange = screen.timeRange;
42
- if (timeRange === undefined)
43
- return { params: {}, control: null };
44
- const fromUrl = nav.searchParams[timeRange.id];
45
- const value = timeRange.options.find((o) => o.value === fromUrl)?.value ?? timeRange.default;
46
- return {
47
- // Metric buckets are cut server-side; without the user's zone they default
48
- // to UTC while the axis labels are formatted in the user's zone.
49
- params: { [timeRange.id]: value, timeZone: resolver.timeZone() },
50
- control: (_jsx(ModeSwitch, { value: value, options: timeRange.options.map((o) => ({ value: o.value, label: translate(o.label) })), onChange: (next) => nav.setSearchParams({ [timeRange.id]: next }), ariaLabel: t("kumiko.dashboard.time-range"), testId: `dashboard-time-range-${timeRange.id}` })),
51
- };
52
- }
6
+ import { ScreenPanelTile } from "./dashboard-screen-tiles.js";
7
+ import { DashboardQueryContext, HALF_PANEL, PanelLoadedContext, useDashboardFormats, WIDE_PANEL, } from "./dashboard-shared.js";
8
+ import { GatedGridCell, useDashboardVisibility, VisibilityGateQueries, VisibilityQueryContext, } from "./dashboard-visibility.js";
9
+ import { useDashboardHeaderActions, useDashboardRecord, useDashboardScreenParams, } from "./use-dashboard-screen-state.js";
53
10
  function panelSpanClassName(panel) {
54
11
  if (panel.kind === "stat")
55
12
  return undefined;
@@ -88,212 +45,51 @@ function PanelBody({ panel, label, screenId, screenParams, translate, }) {
88
45
  }
89
46
  return (_jsx(ProgressListPanelBody, { panel: panel, label: text, screenParams: screenParams, translate: translate }));
90
47
  }
91
- const VisibilityQueryContext = createContext(new Map());
92
- const NO_GATE_PAYLOAD = {};
93
- function visibilityGate(query, payload) {
94
- return { key: `${query}|${JSON.stringify(payload)}`, query, payload };
95
- }
96
- // Embedded screens carry no screen filter; in-grid panels pass the filter
97
- // (unless ignoreScreenFilter, never for a stat-group) and the time range.
98
- function panelVisibilityGate(panel, screenParams) {
99
- if (panel.kind === "custom" || panel.visibleWhen === undefined)
100
- return undefined;
101
- if (panel.kind === "screen")
102
- return visibilityGate(panel.visibleWhen.query, NO_GATE_PAYLOAD);
103
- const dropsFilter = panel.kind !== "stat-group" && panel.kind !== "text" && panel.ignoreScreenFilter === true;
104
- return visibilityGate(panel.visibleWhen.query, {
105
- ...(dropsFilter ? {} : screenParams.filterParams),
106
- ...screenParams.rangeParams,
107
- ...screenParams.recordParams,
108
- });
109
- }
110
- // One live query per distinct gate (query + payload), shared by every panel
111
- // that gates on it — N panels on the same status query would otherwise each
112
- // open their own request, SSE subscription and refetch per event. The queries
113
- // run as keyed siblings of the grid so a changing gate count never changes the
114
- // grid's position in the tree (which would remount every panel).
115
- function VisibilityGateQuery({ gate, onResult, onForget, }) {
116
- const { data, error, loading, refetch } = useDashboardQuery(gate.query, gate.payload);
117
- const { key } = gate;
118
- useEffect(() => {
119
- onResult(key, { data, error, loading, refetch });
120
- }, [key, data, error, loading, refetch, onResult]);
121
- useEffect(() => () => onForget(key), [key, onForget]);
122
- return null;
123
- }
124
- function VisibilityGateQueries({ gates, onResult, onForget, }) {
125
- return gates.map((gate) => (_jsx(VisibilityGateQuery, { gate: gate, onResult: onResult, onForget: onForget }, gate.key)));
126
- }
127
- function useVisibilityGateResults() {
128
- const [results, setResults] = useState(new Map());
129
- const report = useCallback((key, result) => {
130
- setResults((prev) => new Map(prev).set(key, result));
131
- }, []);
132
- const forget = useCallback((key) => {
133
- setResults((prev) => {
134
- if (!prev.has(key))
135
- return prev;
136
- const next = new Map(prev);
137
- next.delete(key);
138
- return next;
139
- });
140
- }, []);
141
- return { results, report, forget };
142
- }
143
- const NOOP_REFETCH = async () => { };
144
- // A gate reports through an effect, so for its first commit (and after a payload change) no
145
- // result exists yet; without a placeholder that reads as "hidden" and flashes the fallback.
146
- function withPendingGates(results, gateKeys) {
147
- if (gateKeys.every((key) => results.has(key)))
148
- return results;
149
- const merged = new Map(results);
150
- for (const key of gateKeys) {
151
- if (!merged.has(key)) {
152
- merged.set(key, { data: null, error: null, loading: true, refetch: NOOP_REFETCH });
153
- }
48
+ function renderDashboardPanel(panel, featureName, screenId, screenParams, translate) {
49
+ if (panel.kind === "screen") {
50
+ return (_jsx(ScreenPanelTile, { panel: panel, featureName: featureName, translate: translate }, panel.id));
154
51
  }
155
- return merged;
156
- }
157
- function distinctVisibilityGates(panels, screenParams) {
158
- const gates = new Map();
159
- for (const panel of panels) {
160
- const gate = panelVisibilityGate(panel, screenParams);
161
- if (gate !== undefined)
162
- gates.set(gate.key, gate);
163
- }
164
- return [...gates.values()];
165
- }
166
- function GatedGridCell({ panel, screenParams, label, className, children, }) {
167
- const gate = panelVisibilityGate(panel, screenParams);
168
- const visibility = useContext(VisibilityQueryContext).get(gate?.key ?? "");
169
- const visibleWhen = panel.kind === "custom" ? undefined : panel.visibleWhen;
170
- if (visibleWhen !== undefined) {
171
- const verdict = evalVisibleWhen(visibleWhen, visibility);
172
- if (verdict === "error" && visibility?.error) {
173
- return (_jsx("div", { className: cn("min-w-0", className), "data-testid": `dashboard-panel-${panel.id}`, children: _jsx(PanelError, { label: label, error: visibility.error, onRetry: () => void visibility.refetch() }) }));
174
- }
175
- // Loading renders nothing too: a skeleton that then vanishes flickers.
176
- if (verdict !== "visible")
177
- return null;
178
- }
179
- return _jsx("div", { className: cn("min-w-0", className), children: children });
180
- }
181
- function EmbeddedScreenPanelTile({ panel, target, translate, }) {
182
- const screenBody = _jsx(KumikoScreen, { schema: target.schema, qn: target.qn, translate: translate });
183
- const embedded = panel.chromeless === true ? (_jsx(EmbeddedFormProvider, { children: _jsx(EmbeddedScreenProvider, { children: screenBody }) })) : (screenBody);
184
- return (_jsx("div", { className: cn("min-w-0", WIDE_PANEL), "data-testid": `dashboard-panel-${panel.id}`, children: panel.label !== undefined && panel.chromeless !== true ? (_jsx(SectionCard, { title: translate(panel.label), children: embedded })) : (_jsxs(_Fragment, { children: [panel.label !== undefined && (_jsx("h3", { className: "mb-3 text-sm font-semibold text-foreground", children: translate(panel.label) })), embedded] })) }));
185
- }
186
- // Own component so the target lookup (a hook) only runs for panels that actually fall back.
187
- function FallbackScreenPanelTile({ panel, fallback, featureName, translate, }) {
188
- const target = useEmbeddedScreen(featureName, fallback);
189
- if (target === undefined)
190
- return null;
191
- return _jsx(EmbeddedScreenPanelTile, { panel: panel, target: target, translate: translate });
192
- }
193
- function ScreenPanelTile({ panel, featureName, translate, }) {
194
- const visibleWhen = panel.visibleWhen;
195
- const visibility = useContext(VisibilityQueryContext).get(visibleWhen !== undefined ? visibilityGate(visibleWhen.query, NO_GATE_PAYLOAD).key : "");
196
- const target = useEmbeddedScreen(featureName, panel.screen);
197
- const verdict = visibleWhen === undefined ? "visible" : evalVisibleWhen(visibleWhen, visibility);
198
- // Nothing while the gate loads: a fallback that flashes before the real panel flickers.
199
- if (verdict === "loading")
200
- return null;
201
- if (verdict !== "visible" && panel.fallback !== undefined) {
202
- return (_jsx(FallbackScreenPanelTile, { panel: panel, fallback: panel.fallback, featureName: featureName, translate: translate }));
203
- }
204
- if (target === undefined)
205
- return null;
206
- // A failed gate query must not silently drop the panel: on the
207
- // account-security page that would hide every MFA enable/disable path.
208
- if (verdict === "error" && visibility?.error) {
209
- return (_jsx("div", { className: cn("min-w-0", WIDE_PANEL), "data-testid": `dashboard-panel-${panel.id}`, children: _jsx(PanelError, { label: panel.label !== undefined ? translate(panel.label) : panel.id, error: visibility.error, onRetry: () => void visibility.refetch() }) }));
210
- }
211
- if (verdict !== "visible")
212
- return null;
213
- return _jsx(EmbeddedScreenPanelTile, { panel: panel, target: target, translate: translate });
214
- }
215
- const NO_RECORD_ROW = {};
216
- function recordTitleText(row, field) {
217
- if (row === null || field === undefined)
218
- return undefined;
219
- const value = row[field];
220
- if (typeof value !== "string" && typeof value !== "number")
221
- return undefined;
222
- return String(value) === "" ? undefined : String(value);
52
+ const label = panel.kind === "custom" || panel.label === undefined ? undefined : translate(panel.label);
53
+ return (_jsx(GatedGridCell, { panel: panel, screenParams: screenParams, label: label ?? panel.id, className: panelSpanClassName(panel), children: _jsx(PanelBody, { panel: panel, label: label, screenId: screenId, screenParams: screenParams, translate: translate }) }, panel.id));
223
54
  }
224
55
  export function WebDashboardBody({ featureName, screen, translate, }) {
225
56
  const t = useTranslation();
226
57
  const effectiveTranslate = translate ?? t;
227
- const { Text, Banner, PageHeader, Heading, Button, Dialog, ActionOverflowMenu, Grid } = usePrimitives();
58
+ const primitives = usePrimitives();
228
59
  const nav = useNav();
229
60
  const headerSlotAvailable = usePageHeaderSlotAvailable();
230
61
  const headerCompactContext = usePageHeaderCompact();
231
62
  const { formatDateTime } = useDashboardFormats();
232
- const recordBinding = screen.record;
233
- const recordId = recordBinding !== undefined ? (nav.searchParams[recordBinding.param] ?? "") : "";
234
- const recordMissing = recordBinding !== undefined && recordId === "";
235
- const recordPayloadKey = recordBinding?.payloadKey ?? recordBinding?.param;
236
- const recordParams = recordPayloadKey !== undefined && recordId !== "" ? { [recordPayloadKey]: recordId } : {};
237
- const recordQueryName = recordBinding?.query;
238
- const { params: filterParams, picker } = useFilterParams(screen, !recordMissing);
239
- const { params: rangeParams, control: rangeControl } = useTimeRange(screen, effectiveTranslate);
240
- const screenParams = { filterParams, rangeParams, recordParams };
241
- const { loadedAtMs, reportLoaded } = useLoadedAt(screenParams);
242
- const visibilityGates = distinctVisibilityGates(screen.panels, screenParams);
243
- const gateResults = useVisibilityGateResults();
244
- const gateKeysSignature = visibilityGates.map((gate) => gate.key).join("\n");
245
- const gateResultsWithPending = useMemo(() => withPendingGates(gateResults.results, gateKeysSignature === "" ? [] : gateKeysSignature.split("\n")), [gateResults.results, gateKeysSignature]);
246
- // The record query is not a panel query: it needs the interval before the
247
- // registry context below exists, and `refresh.field` reads from its own row.
248
- const [refreshIntervalMs, setRefreshIntervalMs] = useState(() => resolveRefreshIntervalMs(screen.refresh, null));
249
- const recordQuery = useQuery(recordQueryName ?? "", recordParams, {
250
- enabled: recordQueryName !== undefined && !recordMissing,
251
- live: true,
252
- ...(refreshIntervalMs !== undefined && { refetchIntervalMs: refreshIntervalMs }),
253
- });
254
- const { data: recordRow, refetch: refetchRecord } = recordQuery;
255
- useEffect(() => {
256
- setRefreshIntervalMs(resolveRefreshIntervalMs(screen.refresh, recordRow));
257
- }, [screen.refresh, recordRow]);
258
- const { register, refetchRegistered } = useRefetchRegistry();
259
- const queryRuntime = useMemo(() => ({ refreshIntervalMs, register }), [refreshIntervalMs, register]);
260
- const refetchAll = useCallback(async () => {
261
- await Promise.all([
262
- recordQueryName !== undefined ? refetchRecord() : Promise.resolve(),
263
- refetchRegistered(),
264
- ]);
265
- }, [recordQueryName, refetchRecord, refetchRegistered]);
266
- const [actionError, setActionError] = useState(null);
267
- const idOnlyRow = useMemo(() => (recordPayloadKey !== undefined ? { id: recordId, [recordPayloadKey]: recordId } : null), [recordId, recordPayloadKey]);
268
- const actionRow = recordQueryName !== undefined ? recordRow : idOnlyRow;
269
- const headerActions = useRecordActions({
270
- actions: actionRow !== null && !recordMissing ? screen.actions : undefined,
271
- record: actionRow ?? NO_RECORD_ROW,
272
- recordId,
273
- translate: effectiveTranslate,
274
- onWriteSuccess: refetchAll,
275
- });
276
- const scope = screen.scope;
277
- // A raw description is agent-facing prose; only an i18n key is user-facing copy.
278
- const translatedDescription = screen.description !== undefined ? effectiveTranslate(screen.description) : undefined;
279
- const description = translatedDescription !== screen.description ? translatedDescription : undefined;
280
- const scopeBadge = scope !== undefined ? (_jsx(StatusBadge, { tone: "accent", testId: `dashboard-${screen.id}-scope`, children: effectiveTranslate(scope.badge) })) : null;
281
- if (recordMissing) {
282
- return (_jsx(PageSection, { className: "flex flex-col gap-4", testId: `dashboard-${screen.id}`, children: _jsx(Banner, { variant: "info", testId: `dashboard-${screen.id}-record-missing`, children: t("kumiko.dashboard.record.missing") }) }));
63
+ const params = useDashboardScreenParams(screen, effectiveTranslate, nav);
64
+ const { screenParams } = params;
65
+ const visibility = useDashboardVisibility(screen.panels, screenParams);
66
+ const record = useDashboardRecord(screen, params);
67
+ const { recordQuery, recordRow, refetchRecord } = record;
68
+ const { actionError, setActionError, headerActions } = useDashboardHeaderActions(screen, params, record, effectiveTranslate);
69
+ const description = dashboardDescription(screen, effectiveTranslate);
70
+ const scopeBadge = buildScopeBadge(screen, effectiveTranslate);
71
+ if (params.recordMissing) {
72
+ return (_jsx(PageSection, { className: "flex flex-col gap-4", testId: `dashboard-${screen.id}`, children: renderRecordMissing(primitives, t, screen.id) }));
283
73
  }
284
- const usesPageHeaderSlot = headerSlotAvailable && PageHeader !== undefined;
285
- const recordTitle = recordTitleText(recordRow, recordBinding?.titleField);
286
- const headerActionsBar = headerActions !== undefined && headerActions.length > 0 ? (_jsx(Grid, { columns: "auto", testId: `dashboard-${screen.id}-actions`, children: _jsx(HeaderActionsBar, { actions: headerActions, Button: Button, Dialog: Dialog, ActionOverflowMenu: ActionOverflowMenu, onError: setActionError, collapseAfterPrimary: usesPageHeaderSlot, compact: headerCompactContext && usesPageHeaderSlot, PageHeader: PageHeader, overflowTestId: `dashboard-${screen.id}-actions-overflow` }) })) : null;
74
+ const usesPageHeaderSlot = headerSlotAvailable && primitives.PageHeader !== undefined;
75
+ const model = {
76
+ screen,
77
+ t,
78
+ translate: effectiveTranslate,
79
+ primitives,
80
+ headerActions,
81
+ setActionError,
82
+ headerCompactContext,
83
+ usesPageHeaderSlot,
84
+ recordTitle: recordTitleText(recordRow, params.recordBinding?.titleField),
85
+ scopeBadge,
86
+ picker: params.picker,
87
+ rangeControl: params.rangeControl,
88
+ loadedAtMs: params.loadedAtMs,
89
+ formatDateTime,
90
+ };
91
+ const headerActionsBar = renderHeaderActionsBar(model);
287
92
  // The shell header owns the title; the badge and the actions sit in it there.
288
93
  const badgeInHeader = scopeBadge !== null && usesPageHeaderSlot;
289
- const showPageHeader = usesPageHeaderSlot && (badgeInHeader || recordTitle !== undefined || headerActionsBar !== null);
290
- return (_jsx(PanelLoadedContext.Provider, { value: reportLoaded, children: _jsx(DashboardQueryContext.Provider, { value: queryRuntime, children: _jsxs(PageSection, { className: "flex flex-col gap-4", testId: `dashboard-${screen.id}`, children: [showPageHeader && (_jsx(PageHeader, { ...(recordTitle !== undefined && { title: recordTitle }), ...(badgeInHeader && { status: scopeBadge }), ...(headerActionsBar !== null && { actions: headerActionsBar }) })), _jsxs("div", { className: "flex flex-wrap items-end justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [!usesPageHeaderSlot && recordTitle !== undefined && (_jsx(Heading, { variant: "page", testId: `dashboard-${screen.id}-record-title`, children: recordTitle })), !badgeInHeader && scopeBadge, picker] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [!usesPageHeaderSlot && headerActionsBar, screen.showUpdatedAt !== false && (_jsx("span", { "data-testid": `dashboard-${screen.id}-updated-at`, className: "text-xs tabular-nums text-muted-foreground", children: t("kumiko.dashboard.updated-at", { time: formatDateTime(loadedAtMs) }) })), rangeControl] })] }), actionError !== null && (_jsxs(Banner, { variant: "error", testId: "render-edit-action-error", children: [actionError.message, _jsx(ErrorReference, { requestId: actionError.requestId })] })), recordQuery.error !== null && recordRow === null && (_jsxs("div", { role: "alert", "data-testid": `dashboard-${screen.id}-record-error`, className: "flex flex-col items-start gap-2 text-sm", children: [_jsx("span", { className: "text-status-bad", children: dispatcherErrorText(recordQuery.error, t) }), _jsx(Button, { variant: "secondary", onClick: () => void refetchRecord(), children: t("kumiko.dashboard.panel.error.retry") })] })), description !== undefined && (_jsx(Text, { variant: "muted", testId: `dashboard-${screen.id}-description`, children: description })), scope?.notice !== undefined && (_jsx(Banner, { variant: "info", testId: `dashboard-${screen.id}-scope-notice`, children: effectiveTranslate(scope.notice) })), _jsxs(VisibilityQueryContext.Provider, { value: gateResultsWithPending, children: [_jsx(VisibilityGateQueries, { gates: visibilityGates, onResult: gateResults.report, onForget: gateResults.forget }), _jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: screen.panels.map((panel) => {
291
- if (panel.kind === "screen") {
292
- return (_jsx(ScreenPanelTile, { panel: panel, featureName: featureName, translate: effectiveTranslate }, panel.id));
293
- }
294
- const label = panel.kind === "custom" || panel.label === undefined
295
- ? undefined
296
- : effectiveTranslate(panel.label);
297
- return (_jsx(GatedGridCell, { panel: panel, screenParams: screenParams, label: label ?? panel.id, className: panelSpanClassName(panel), children: _jsx(PanelBody, { panel: panel, label: label, screenId: screen.id, screenParams: screenParams, translate: effectiveTranslate }) }, panel.id));
298
- }) })] })] }) }) }));
94
+ return (_jsx(PanelLoadedContext.Provider, { value: params.reportLoaded, children: _jsx(DashboardQueryContext.Provider, { value: record.queryRuntime, children: _jsxs(PageSection, { className: "flex flex-col gap-4", testId: `dashboard-${screen.id}`, children: [renderPageHeaderSlot(model, headerActionsBar, badgeInHeader), renderTitleRow(model, headerActionsBar, badgeInHeader), renderActionErrorBanner(primitives, actionError), renderRecordError(model, recordQuery.error, recordRow, refetchRecord), renderDescriptionAndNotice(model, description), _jsxs(VisibilityQueryContext.Provider, { value: visibility.gateResultsWithPending, children: [_jsx(VisibilityGateQueries, { gates: visibility.visibilityGates, onResult: visibility.gateResults.report, onForget: visibility.gateResults.forget }), _jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children: screen.panels.map((panel) => renderDashboardPanel(panel, featureName, screen.id, screenParams, effectiveTranslate)) })] })] }) }) }));
299
95
  }
@@ -0,0 +1,34 @@
1
+ import type { DashboardScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { Dispatch, ReactNode, SetStateAction } from "react";
3
+ import { type ErrorNotice } from "../../app/error-reference.tsx";
4
+ import type { DispatcherError, Translate } from "../../headless/index.ts";
5
+ import { type usePrimitives, type useRecordActions } from "../../index.ts";
6
+ import type { useDashboardFormats, useLoadedAt } from "./dashboard-shared.tsx";
7
+ type Primitives = ReturnType<typeof usePrimitives>;
8
+ export type DashboardHeaderModel = {
9
+ readonly screen: DashboardScreenDefinition;
10
+ readonly t: Translate;
11
+ readonly translate: Translate;
12
+ readonly primitives: Primitives;
13
+ readonly headerActions: ReturnType<typeof useRecordActions>;
14
+ readonly setActionError: Dispatch<SetStateAction<ErrorNotice | null>>;
15
+ readonly headerCompactContext: boolean;
16
+ readonly usesPageHeaderSlot: boolean;
17
+ readonly recordTitle: string | undefined;
18
+ readonly scopeBadge: ReactNode;
19
+ readonly picker: ReactNode;
20
+ readonly rangeControl: ReactNode;
21
+ readonly loadedAtMs: ReturnType<typeof useLoadedAt>["loadedAtMs"];
22
+ readonly formatDateTime: ReturnType<typeof useDashboardFormats>["formatDateTime"];
23
+ };
24
+ export declare function recordTitleText(row: Readonly<Record<string, unknown>> | null, field: string | undefined): string | undefined;
25
+ export declare function buildScopeBadge(screen: DashboardScreenDefinition, translate: Translate): ReactNode;
26
+ export declare function dashboardDescription(screen: DashboardScreenDefinition, translate: Translate): string | undefined;
27
+ export declare function renderRecordMissing(primitives: Primitives, t: Translate, screenId: string): ReactNode;
28
+ export declare function renderHeaderActionsBar(model: DashboardHeaderModel): ReactNode;
29
+ export declare function renderPageHeaderSlot(model: DashboardHeaderModel, headerActionsBar: ReactNode, badgeInHeader: boolean): ReactNode;
30
+ export declare function renderTitleRow(model: DashboardHeaderModel, headerActionsBar: ReactNode, badgeInHeader: boolean): ReactNode;
31
+ export declare function renderActionErrorBanner(primitives: Primitives, actionError: ErrorNotice | null): ReactNode;
32
+ export declare function renderRecordError(model: DashboardHeaderModel, error: DispatcherError | null, recordRow: Readonly<Record<string, unknown>> | null, refetchRecord: () => Promise<void>): ReactNode;
33
+ export declare function renderDescriptionAndNotice(model: DashboardHeaderModel, description: string | undefined): ReactNode;
34
+ export {};
@@ -0,0 +1,58 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ErrorReference } from "../../app/error-reference.js";
3
+ import { dispatcherErrorText, HeaderActionsBar, } from "../../index.js";
4
+ import { StatusBadge } from "../widgets/status-badge.js";
5
+ export function recordTitleText(row, field) {
6
+ if (row === null || field === undefined)
7
+ return undefined;
8
+ const value = row[field];
9
+ if (typeof value !== "string" && typeof value !== "number")
10
+ return undefined;
11
+ return String(value) === "" ? undefined : String(value);
12
+ }
13
+ export function buildScopeBadge(screen, translate) {
14
+ const scope = screen.scope;
15
+ return scope !== undefined ? (_jsx(StatusBadge, { tone: "accent", testId: `dashboard-${screen.id}-scope`, children: translate(scope.badge) })) : null;
16
+ }
17
+ export function dashboardDescription(screen, translate) {
18
+ // A raw description is agent-facing prose; only an i18n key is user-facing copy.
19
+ const translatedDescription = screen.description !== undefined ? translate(screen.description) : undefined;
20
+ return translatedDescription !== screen.description ? translatedDescription : undefined;
21
+ }
22
+ export function renderRecordMissing(primitives, t, screenId) {
23
+ const { Banner } = primitives;
24
+ return (_jsx(Banner, { variant: "info", testId: `dashboard-${screenId}-record-missing`, children: t("kumiko.dashboard.record.missing") }));
25
+ }
26
+ export function renderHeaderActionsBar(model) {
27
+ const { screen, headerActions, headerCompactContext, usesPageHeaderSlot, setActionError } = model;
28
+ const { Button, Dialog, ActionOverflowMenu, Grid, PageHeader } = model.primitives;
29
+ return headerActions !== undefined && headerActions.length > 0 ? (_jsx(Grid, { columns: "auto", testId: `dashboard-${screen.id}-actions`, children: _jsx(HeaderActionsBar, { actions: headerActions, Button: Button, Dialog: Dialog, ActionOverflowMenu: ActionOverflowMenu, onError: setActionError, collapseAfterPrimary: usesPageHeaderSlot, compact: headerCompactContext && usesPageHeaderSlot, PageHeader: PageHeader, overflowTestId: `dashboard-${screen.id}-actions-overflow` }) })) : null;
30
+ }
31
+ export function renderPageHeaderSlot(model, headerActionsBar, badgeInHeader) {
32
+ const { PageHeader } = model.primitives;
33
+ const { recordTitle, scopeBadge, usesPageHeaderSlot } = model;
34
+ const showPageHeader = usesPageHeaderSlot && (badgeInHeader || recordTitle !== undefined || headerActionsBar !== null);
35
+ return (showPageHeader &&
36
+ PageHeader !== undefined && (_jsx(PageHeader, { ...(recordTitle !== undefined && { title: recordTitle }), ...(badgeInHeader && { status: scopeBadge }), ...(headerActionsBar !== null && { actions: headerActionsBar }) })));
37
+ }
38
+ export function renderTitleRow(model, headerActionsBar, badgeInHeader) {
39
+ const { Heading } = model.primitives;
40
+ const { screen, usesPageHeaderSlot, recordTitle, scopeBadge, picker, rangeControl } = model;
41
+ return (_jsxs("div", { className: "flex flex-wrap items-end justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 flex-wrap items-center gap-2", children: [!usesPageHeaderSlot && recordTitle !== undefined && (_jsx(Heading, { variant: "page", testId: `dashboard-${screen.id}-record-title`, children: recordTitle })), !badgeInHeader && scopeBadge, picker] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [!usesPageHeaderSlot && headerActionsBar, screen.showUpdatedAt !== false && (_jsx("span", { "data-testid": `dashboard-${screen.id}-updated-at`, className: "text-xs tabular-nums text-muted-foreground", children: model.t("kumiko.dashboard.updated-at", {
42
+ time: model.formatDateTime(model.loadedAtMs),
43
+ }) })), rangeControl] })] }));
44
+ }
45
+ export function renderActionErrorBanner(primitives, actionError) {
46
+ const { Banner } = primitives;
47
+ return (actionError !== null && (_jsxs(Banner, { variant: "error", testId: "render-edit-action-error", children: [actionError.message, _jsx(ErrorReference, { requestId: actionError.requestId })] })));
48
+ }
49
+ export function renderRecordError(model, error, recordRow, refetchRecord) {
50
+ const { Button } = model.primitives;
51
+ return (error !== null &&
52
+ recordRow === null && (_jsxs("div", { role: "alert", "data-testid": `dashboard-${model.screen.id}-record-error`, className: "flex flex-col items-start gap-2 text-sm", children: [_jsx("span", { className: "text-status-bad", children: dispatcherErrorText(error, model.t) }), _jsx(Button, { variant: "secondary", onClick: () => void refetchRecord(), children: model.t("kumiko.dashboard.panel.error.retry") })] })));
53
+ }
54
+ export function renderDescriptionAndNotice(model, description) {
55
+ const { Text, Banner } = model.primitives;
56
+ const { screen } = model;
57
+ return (_jsxs(_Fragment, { children: [description !== undefined && (_jsx(Text, { variant: "muted", testId: `dashboard-${screen.id}-description`, children: description })), screen.scope?.notice !== undefined && (_jsx(Banner, { variant: "info", testId: `dashboard-${screen.id}-scope-notice`, children: model.translate(screen.scope.notice) }))] }));
58
+ }
@@ -0,0 +1,11 @@
1
+ import type { DashboardScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { ReactNode } from "react";
3
+ import type { Translate } from "../../headless/index.ts";
4
+ export declare function useFilterParams(screen: DashboardScreenDefinition, enabled: boolean): {
5
+ readonly params: Readonly<Record<string, unknown>>;
6
+ readonly picker: ReactNode;
7
+ };
8
+ export declare function useTimeRange(screen: DashboardScreenDefinition, translate: Translate): {
9
+ readonly params: Readonly<Record<string, unknown>>;
10
+ readonly control: ReactNode;
11
+ };