@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,24 +1,16 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { UNRESOLVED_REFERENCE_LABEL_KEYS, } from "@cosmicdrift/kumiko-framework/ui-types";
3
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
- import { extensionSectionName, useExtensionSectionComponent } from "../app/extension-sections.js";
5
- import { useReferenceLinkResolver } from "../app/reference-link.js";
6
- import { rendererWarn } from "../dev-console.js";
7
- import { computeListViewModel, EMPTY_VALUE_PLACEHOLDER } from "../headless/index.js";
8
- import { useReferenceLookup } from "../hooks/use-reference-lookup.js";
2
+ import { useState } from "react";
9
3
  import { useTranslation } from "../i18n.js";
10
4
  import { usePageHeaderCompact, usePageHeaderSlotAvailable } from "../page-header-slot.js";
11
- import { shouldRenderActionsIconOnly, usePrimitives, } from "../primitives.js";
12
- import { ListEmptyStateBody } from "./list-empty-state.js";
13
- import { needsActionConfirm } from "./render-edit-action-button.js";
14
- const SEARCH_DEBOUNCE_MS = 300;
15
- // Icon-less actions are promoted too; on phones the call site falls back to a
16
- // "plus" icon, on desktop the label carries the action.
17
- function isHeaderPromotableToolbarAction(action) {
18
- return action.style === "primary" && !needsActionConfirm(action);
19
- }
5
+ import { usePrimitives } from "../primitives.js";
6
+ import { renderReferenceLookupBridges } from "./render-list-reference-bridge.js";
7
+ import { tableChromeProps, tableFilterProps, tableLayoutProps, tableRowProps, tableSortPagingProps, } from "./render-list-table-props.js";
8
+ import { composeEmptyState, renderCreateButton, renderHeaderActions, renderMenuActionError, renderPageHeader, renderSearchInput, renderToolbarEnd, } from "./render-list-toolbar.js";
9
+ import { buildPromotedMenuItems, resolveItemNoun, resolveToolbarLabels, resolveToolbarTitle, splitToolbarActions, } from "./render-list-toolbar-model.js";
10
+ import { useRenderListColumns } from "./use-render-list-columns.js";
11
+ import { useRenderListSearch } from "./use-render-list-search.js";
20
12
  export function RenderList(props) {
21
- const { screen, entity, rows, featureName, translate: translateProp, onRowClick, emptyState, onCreate, createLabel, searchable = false, searchPlaceholder, description, emptyContent, narrowed = false, defaultFilterActive: defaultFilterActiveProp = false, itemNounKey, searchValue, onSearchChange, sort, onSortChange, pager, onReachEnd, loadingMore, hasMore, rowActions, rowActionMode, rowGrouping, rowTone, toolbarActions, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, } = props;
13
+ const { screen, entity, rows, featureName, translate: translateProp, onRowClick, emptyState, onCreate, createLabel, searchable = false, searchPlaceholder, description, emptyContent, narrowed = false, defaultFilterActive: defaultFilterActiveProp = false, itemNounKey, searchValue, onSearchChange, sort, onSortChange, pager, onReachEnd, loadingMore, hasMore, rowActions, rowActionMode, bulk, rowTone, toolbarActions, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, ...tableBlockProps } = props;
22
14
  // As in RenderEdit, fall back to the i18next context for translation;
23
15
  // otherwise column headers would display raw i18n keys.
24
16
  const t = useTranslation();
@@ -27,316 +19,100 @@ export function RenderList(props) {
27
19
  const pageHeaderSlotAvailable = usePageHeaderSlotAvailable();
28
20
  const pageHeaderCompact = usePageHeaderCompact();
29
21
  const createInPageHeader = onCreate !== undefined && PageHeader !== undefined && pageHeaderSlotAvailable;
30
- // Local search buffer + debounce. External changes (browser back,
31
- // cross-component reset) are mirrored back; typing only fires
32
- // onSearchChange after 300ms without further keystrokes.
33
- const [localQ, setLocalQ] = useState(searchValue ?? "");
34
- // Sync during render, not in a useEffect: the passive effect also fires on
35
- // mount and can flush after a keystroke landing in the same commit,
36
- // resetting localQ and swallowing the input.
37
- const [syncedSearchValue, setSyncedSearchValue] = useState(searchValue);
38
- // The parent may echo our own debounced emits back late (router/URL round
39
- // trip), possibly after a newer emit already went out; adopting such a stale
40
- // echo would erase keys typed in between. All unechoed emits are kept, and a
41
- // matching echo drops itself and every older one. The render phase only reads
42
- // the list (a discarded or repeated render must find the echo again); the
43
- // matched entries are dropped in an effect.
44
- const pendingEmitsRef = useRef([]);
45
- if (searchValue !== syncedSearchValue) {
46
- setSyncedSearchValue(searchValue);
47
- if (!pendingEmitsRef.current.includes(searchValue ?? "")) {
48
- setLocalQ(searchValue ?? "");
49
- pendingEmitsRef.current = [];
50
- }
51
- }
52
- useEffect(() => {
53
- const echoIndex = pendingEmitsRef.current.indexOf(syncedSearchValue ?? "");
54
- if (echoIndex >= 0)
55
- pendingEmitsRef.current.splice(0, echoIndex + 1);
56
- }, [syncedSearchValue]);
57
- useEffect(() => {
58
- if (onSearchChange === undefined)
59
- return;
60
- if (localQ === (searchValue ?? ""))
61
- return;
62
- // A late echo of an older emit changes searchValue but must not re-send
63
- // the value that is already in flight.
64
- if (pendingEmitsRef.current.at(-1) === localQ)
65
- return;
66
- const timer = setTimeout(() => {
67
- pendingEmitsRef.current.push(localQ);
68
- onSearchChange(localQ);
69
- }, SEARCH_DEBOUNCE_MS);
70
- return () => clearTimeout(timer);
71
- }, [localQ, searchValue, onSearchChange]);
72
- const vm = useMemo(() => computeListViewModel({ screen, entity, rows, translate, featureName }), [screen, entity, rows, translate, featureName]);
73
- // Reference-field eager loading through bridge components.
74
- // The Hooks rule requires useReferenceLookup for each reference column,
75
- // but the number of columns varies with the schema. Each column therefore
76
- // mounts a ReferenceLookupBridge that publishes its map to local state
77
- // through onMap. Each bridge makes a constant number of hook calls (one),
78
- // and React manages its lifecycle when the schema changes via key.
79
- const referenceColumns = useMemo(() => vm.columns
80
- .filter((c) => c.type === "reference" && c.refEntity !== undefined)
81
- .map((c) => ({
82
- field: c.field,
83
- refEntity: c.refEntity ?? "",
84
- // refFeature comes from parseRefTarget in the ViewModel
85
- // (default = the current featureName).
86
- refFeature: c.refFeature ?? featureName,
87
- labelField: c.refLabelField ?? "id",
88
- })), [vm.columns, featureName]);
89
- const [referenceLookups, setReferenceLookups] = useState({});
90
- const handleLookupMap = useCallback((field, lookup) => {
91
- setReferenceLookups((prev) => {
92
- // Skip the update when the lookup is identical. Otherwise the bridge
93
- // would re-render and send the same map back until React entered
94
- // an infinite loop.
95
- const current = prev[field];
96
- if (current?.map === lookup.map && current.complete === lookup.complete)
97
- return prev;
98
- return { ...prev, [field]: lookup };
99
- });
100
- }, []);
101
- const resolveReferenceLink = useReferenceLinkResolver();
102
- const enrichedColumns = useMemo(() => {
103
- if (referenceColumns.length === 0)
104
- return vm.columns;
105
- return vm.columns.map((col) => {
106
- if (col.type !== "reference")
107
- return col;
108
- // An author-declared renderer always takes precedence;
109
- // the default is used only when none is set.
110
- if (col.renderer !== undefined)
111
- return col;
112
- const published = referenceLookups[col.field];
113
- const map = published?.map;
114
- const unknownKey = UNRESOLVED_REFERENCE_LABEL_KEYS[`${col.refFeature ?? featureName}:${col.refEntity ?? ""}`];
115
- // No raw id for entities that opt in: a placeholder while the lookup
116
- // loads, the "unknown" label once the complete lookup found nothing. A
117
- // capped lookup page cannot prove absence, so it keeps the raw id.
118
- const unresolvedLabel = (id) => unknownKey === undefined
119
- ? id
120
- : published === undefined
121
- ? EMPTY_VALUE_PLACEHOLDER
122
- : published.complete
123
- ? translate(unknownKey)
124
- : id;
125
- const labelField = col.refLabelField ?? "id";
126
- const refEntity = col.refEntity ?? "";
127
- // Only a row the user can see is linkable: resolved by the server
128
- // eagerload or by the lookup query. A multi-reference links per entry via linkItemsFor.
129
- const linkFor = (value, row) => {
130
- if (value === null || value === undefined || value === "" || Array.isArray(value)) {
131
- return undefined;
132
- }
133
- const id = String(value);
134
- const eagerloaded = eagerloadedRef(row, col.field);
135
- // The server leaves a missing or unreadable target out; null is treated the same.
136
- const resolved = (typeof eagerloaded === "object" &&
137
- eagerloaded !== null &&
138
- !Array.isArray(eagerloaded)) ||
139
- map?.has(id);
140
- return resolved === true ? resolveReferenceLink(refEntity, id) : undefined;
141
- };
142
- const linkItemsFor = (value, row) => {
143
- if (!Array.isArray(value) || value.length === 0)
144
- return undefined;
145
- const eagerloaded = eagerloadedRef(row, col.field);
146
- const eagerById = new Map(Array.isArray(eagerloaded) ? eagerloaded.map((r) => [String(r["id"]), r]) : []);
147
- return value.map((v) => {
148
- const id = String(v);
149
- const target = eagerById.get(id);
150
- const label = String(target?.[labelField] ?? map?.get(id) ?? unresolvedLabel(id));
151
- // Only a row the user can see is linkable: server eagerload or lookup query.
152
- const link = target !== undefined || map?.has(id) === true
153
- ? resolveReferenceLink(refEntity, id)
154
- : undefined;
155
- return { id, label, ...(link !== undefined && { link }) };
156
- });
157
- };
158
- const renderer = (value, row) => {
159
- // Server eager loading: when the server includes _refs, read the display
160
- // value directly from the resolved row. This avoids a round trip
161
- // through the bridge map and the limit:200 constraint. The
162
- // EagerloadedRow type from @cosmicdrift/kumiko-framework/db defines
163
- // the shape of _refs.
164
- const resolved = eagerloadedRef(row, col.field);
165
- if (Array.isArray(resolved) && resolved.length > 0) {
166
- return resolved.map((r) => String(r[labelField] ?? r["id"] ?? "")).join(", ");
167
- }
168
- if (isPlainRecord(resolved)) {
169
- return String(resolved[labelField] ?? resolved["id"] ?? "");
170
- }
171
- // Renderer-side fallback when server eager loading is not active.
172
- if (Array.isArray(value)) {
173
- if (value.length === 0)
174
- return EMPTY_VALUE_PLACEHOLDER;
175
- return value.map((v) => map?.get(String(v)) ?? unresolvedLabel(String(v))).join(", ");
176
- }
177
- if (value === null || value === undefined || value === "")
178
- return EMPTY_VALUE_PLACEHOLDER;
179
- const idStr = String(value);
180
- return map?.get(idStr) ?? unresolvedLabel(idStr);
181
- };
182
- return { ...col, renderer, linkFor, linkItemsFor };
183
- });
184
- }, [
185
- vm.columns,
186
- referenceColumns,
187
- referenceLookups,
188
- resolveReferenceLink,
22
+ // Promoted actions that moved into the phone header's overflow menu have no button of
23
+ // their own, so their busy and error state lives here.
24
+ const [menuActionBusyId, setMenuActionBusyId] = useState(null);
25
+ const [menuActionError, setMenuActionError] = useState(null);
26
+ const { localQ, setLocalQ } = useRenderListSearch(searchValue, onSearchChange);
27
+ const { referenceColumns, handleLookupMap, enrichedVm } = useRenderListColumns({
28
+ screen,
29
+ entity,
30
+ rows,
189
31
  featureName,
190
32
  translate,
191
- ]);
192
- const enrichedVm = useMemo(() => ({
193
- ...vm,
194
- columns: enrichedColumns,
195
- }), [vm, enrichedColumns]);
196
- // i18n defaults for toolbar and empty-state strings. The caller can
197
- // override each one through a prop; otherwise the framework bundles provide
198
- // them (kumiko.actions.create, kumiko.list.search-placeholder, …).
199
- const effectiveCreateLabel = translate(createLabel ?? screen.createLabel ?? "kumiko.actions.create");
200
- const effectiveSearchPlaceholder = translate(searchPlaceholder ?? screen.searchPlaceholder ?? "kumiko.list.search-placeholder");
201
- const nounKey = itemNounKey ?? `${featureName}:entity:${screen.entity}:noun`;
202
- const itemNoun = (itemNounKey !== undefined || !screen.entity.startsWith("__")) &&
203
- translate(nounKey, { count: 2 }) !== nounKey
204
- ? (count) => translate(nounKey, { count })
33
+ });
34
+ const labelInput = {
35
+ screen,
36
+ translate,
37
+ createLabel,
38
+ searchPlaceholder,
39
+ itemNounKey,
40
+ featureName,
41
+ };
42
+ const { effectiveCreateLabel, effectiveSearchPlaceholder } = resolveToolbarLabels(labelInput);
43
+ const itemNoun = resolveItemNoun(labelInput);
44
+ const toolbarStart = searchable
45
+ ? renderSearchInput(Input, localQ, setLocalQ, effectiveSearchPlaceholder)
205
46
  : undefined;
206
- const toolbarStart = searchable ? (_jsx(Input, { kind: "text", id: "render-list-search", name: "search", value: localQ, onChange: setLocalQ, placeholder: effectiveSearchPlaceholder })) : undefined;
207
47
  // Toolbar-end slot: an optional header slot (for example, a cap counter,
208
48
  // at the left of the right-hand cluster), toolbar actions from the schema,
209
49
  // and an optional "+ New" button at the right edge. The header slot lives
210
50
  // inside the toolbar row, with the same card frame, rather than as loose
211
51
  // text above the screen title. Items retain array order, and "+ New" comes
212
52
  // last because it is the most common and prominent CTA.
213
- const hasToolbarActions = toolbarActions !== undefined && toolbarActions.length > 0;
214
- const hasHeaderSlot = screen.slots?.header !== undefined;
215
- const toolbarIconOnly = hasToolbarActions && shouldRenderActionsIconOnly(toolbarActions);
216
- const createButton = onCreate !== undefined && createInPageHeader && pageHeaderCompact ? (_jsx(Button, { variant: "primary", icon: "plus", size: "icon", ariaLabel: effectiveCreateLabel, title: effectiveCreateLabel, onClick: onCreate, testId: "render-list-create" })) : onCreate !== undefined ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-create", className: "px-3.5", children: effectiveCreateLabel })) : undefined;
217
- // Without onCreate (e.g. a navigate action as the list's only CTA) the primary
218
- // toolbar action takes the page-header slot like a "+ Neu" button would: an icon
219
- // on phones, labelled on desktop, so projection lists look like entity lists.
220
- const promotedToolbarAction = onCreate === undefined && PageHeader !== undefined && pageHeaderSlotAvailable
221
- ? toolbarActions?.find(isHeaderPromotableToolbarAction)
222
- : undefined;
223
- const promotedActionButton = promotedToolbarAction !== undefined ? (_jsx(ToolbarActionView, { action: pageHeaderCompact
224
- ? { ...promotedToolbarAction, icon: promotedToolbarAction.icon ?? "plus" }
225
- : promotedToolbarAction, iconOnly: pageHeaderCompact, Button: Button, Dialog: Dialog, Banner: Banner })) : undefined;
226
- const toolbarActionsInToolbar = toolbarActions?.filter((a) => a !== promotedToolbarAction) ?? [];
227
- const hasToolbarActionsInToolbar = toolbarActionsInToolbar.length > 0;
228
- const toolbarEnd = hasHeaderSlot ||
229
- hasToolbarActionsInToolbar ||
230
- (onCreate !== undefined && !createInPageHeader) ? (_jsxs(_Fragment, { children: [hasHeaderSlot && _jsx(ListHeaderSlotMount, { screen: screen }), hasToolbarActionsInToolbar &&
231
- toolbarActionsInToolbar.map((a) => (_jsx(ToolbarActionView, { action: a, iconOnly: toolbarIconOnly, Button: Button, Dialog: Dialog, Banner: Banner }, a.id))), !createInPageHeader && createButton] })) : undefined;
232
- // Empty state: by default, show a heading, description, and optional CTA
233
- // button. The caller can replace it completely through emptyState.
234
- // Without onCreate or an override, DataTable uses its default
235
- // (`kumiko.list.no-entries`).
236
- const defaultFilterActive = defaultFilterActiveProp && onFilterReset !== undefined;
237
- const composedEmptyState = emptyState ??
238
- (onCreate !== undefined || emptyContent !== undefined || narrowed || defaultFilterActive ? (_jsx(ListEmptyStateBody, { content: emptyContent, narrowed: narrowed, defaultFilterActive: defaultFilterActive, ...(onFilterReset !== undefined && { onShowAll: onFilterReset }), translate: translate, createButton: onCreate !== undefined && !narrowed && !defaultFilterActive && !createInPageHeader ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-empty-create", children: effectiveCreateLabel })) : undefined })) : undefined);
239
- // Title resolution uses the conventional i18n key `screen:<id>.title`.
240
- // If the bundle knows the key, use its translated title; otherwise use
241
- // screenId so the app developer can see that the translation is missing.
242
- const titleKey = `screen:${screen.id}.title`;
243
- const resolvedTitle = translate(titleKey);
244
- const toolbarTitle = resolvedTitle === titleKey ? screen.id : resolvedTitle;
245
- // ListSort = DataTableSort (aliased in use-list-url-state), so no cast is needed.
246
- return (_jsxs(_Fragment, { children: [createInPageHeader && PageHeader !== undefined && _jsx(PageHeader, { actions: createButton }), promotedActionButton !== undefined && PageHeader !== undefined && (_jsx(PageHeader, { actions: promotedActionButton })), referenceColumns.map((rc) => (_jsx(ReferenceLookupBridge, { field: rc.field, refEntity: rc.refEntity, labelField: rc.labelField, featureName: rc.refFeature, onMap: handleLookupMap }, rc.field))), _jsx(DataTable, { columns: enrichedVm.columns, rows: enrichedVm.rows, toolbarTitle: toolbarTitle, ...(onRowClick !== undefined && { onRowClick }), ...(composedEmptyState !== undefined && { emptyState: composedEmptyState }), ...(toolbarStart !== undefined && { toolbarStart }), ...(description !== undefined && { toolbarDescription: description }), ...(toolbarEnd !== undefined && { toolbarEnd }), ...(sort !== undefined && { sort }), ...(onSortChange !== undefined && { onSortChange }), ...(pager !== undefined && { pager }), ...(onReachEnd !== undefined && { onReachEnd }), ...(loadingMore !== undefined && { loadingMore }), ...(hasMore !== undefined && { hasMore }), ...(itemNoun !== undefined && { itemNoun }), ...(rowActions !== undefined && { rowActions }), ...(rowActionMode !== undefined && { rowActionMode }), ...(rowGrouping !== undefined && { rowGrouping }), ...(rowTone !== undefined && { rowTone }), ...(filterFacets !== undefined && { filterFacets }), ...(filterValues !== undefined && { filterValues }), ...(onFilterChange !== undefined && { onFilterChange }), ...(onFilterReset !== undefined && { onFilterReset }), ...(dateRangeFacets !== undefined && { dateRangeFacets }), ...(onDateRangeChange !== undefined && { onDateRangeChange }), ...(chromeless !== undefined && { chromeless }), ...(scrollBody !== undefined && { scrollBody }), ...(screenPadding !== undefined && { screenPadding }), ...(expandedRowIds !== undefined && { expandedRowIds }), ...(onToggleRowExpanded !== undefined && { onToggleRowExpanded }), ...(renderExpandedRow !== undefined && { renderExpandedRow }), testId: "render-list-table" })] }));
247
- }
248
- // Header slot in the list toolbar (toolbarEnd): `screen.slots.header` is
249
- // a PlatformComponent (`{ react: { __component: "X" } }`) resolved through
250
- // the same ExtensionSectionsProvider registry as entityEdit sections.
251
- // entityId is null in the list context because there is no row; the component
252
- // loads its own data (for example, a cap counter from a usage query). A
253
- // separate component is required by the Rules of Hooks because
254
- // useExtensionSectionComponent must not run conditionally in RenderList.
255
- function ListHeaderSlotMount({ screen, }) {
256
- const header = screen.slots?.header;
257
- const name = header !== undefined ? extensionSectionName(header) : undefined;
258
- const Component = useExtensionSectionComponent(name);
259
- // A declared-but-unregistered header slot renders nothing; warn so the author
260
- // finds the typo / missing clientFeatures.extensionSectionComponents entry,
261
- // mirroring the diagnostic banner in render-edit's ExtensionSectionMount
262
- // (the toolbar has no room for a banner, so a dev-warn it is).
263
- useEffect(() => {
264
- if (name !== undefined && Component === undefined) {
265
- rendererWarn(`[kumiko] List header slot component "${name}" on screen "${screen.id}" is not registered in clientFeatures.extensionSectionComponents — the header slot renders nothing.`);
266
- }
267
- }, [name, Component, screen.id]);
268
- if (Component === undefined)
269
- return null;
270
- // screenId lets a header control drive this list's URL-filter state
271
- // (useListUrlState) — e.g. a tag filter narrowing the list to an id-set.
272
- return _jsx(Component, { entityName: screen.entity, entityId: null, screenId: screen.id });
273
- }
274
- // Bridge component for each reference column. It is mounted once per
275
- // column, calls useReferenceLookup unconditionally, and publishes the map
276
- // to the parent through onMap. React manages mounting and unmounting when
277
- // the schema changes through the key prop in the parent's map loop.
278
- function ReferenceLookupBridge({ field, refEntity, labelField, featureName, onMap, }) {
279
- const lookup = useReferenceLookup(featureName, refEntity, labelField);
280
- // Use an effect rather than calling directly so setState runs outside the
281
- // render path; otherwise React warns about updating one component while
282
- // rendering another.
283
- // Do not publish while loading or after a failed lookup: a missing map means
284
- // "not resolved", whereas an empty published map means "nothing found".
285
- // Only the latter may display "Unknown".
286
- const resolved = !lookup.loading && !lookup.failed;
287
- useEffect(() => {
288
- if (resolved)
289
- onMap(field, { map: lookup.map, complete: lookup.complete });
290
- }, [field, lookup.map, lookup.complete, resolved, onMap]);
291
- return null;
292
- }
293
- // ToolbarActionView renders one button per toolbar action, with a confirm
294
- // dialog when required. It follows the inline RowAction pattern but has no
295
- // row context. The busy state covers async onTrigger; the dialog opens before
296
- // the trigger when confirmation is required for confirm/danger actions.
297
- function ToolbarActionView({ action, iconOnly = false, Button, Dialog, Banner, }) {
298
- const [busy, setBusy] = useState(false);
299
- const [confirmOpen, setConfirmOpen] = useState(false);
300
- // Toolbar actions do not have access to the toast primitive, unlike
301
- // rowActions routed through the DataTable primitive, so an inline error
302
- // banner is used to surface failures. Without it, a swallowed failure result
303
- // would look to the user as though nothing happened.
304
- const [errorText, setErrorText] = useState(null);
305
- const trigger = async () => {
306
- setBusy(true);
307
- setErrorText(null);
308
- try {
309
- await action.onTrigger();
310
- }
311
- catch (e) {
312
- setErrorText(e instanceof Error ? e.message : String(e));
313
- }
314
- finally {
315
- setBusy(false);
316
- }
53
+ const split = splitToolbarActions({
54
+ toolbarActions,
55
+ pageHeaderAvailable: PageHeader !== undefined,
56
+ pageHeaderSlotAvailable,
57
+ pageHeaderCompact,
58
+ createInPageHeader,
59
+ });
60
+ const toolbarModel = {
61
+ screen,
62
+ Button,
63
+ Dialog,
64
+ Banner,
65
+ onCreate,
66
+ createInPageHeader,
67
+ pageHeaderCompact,
68
+ effectiveCreateLabel,
69
+ split,
317
70
  };
318
- const variant = action.style ?? "secondary";
319
- const needsConfirm = needsActionConfirm(action);
320
- const showIconOnly = iconOnly && action.icon !== undefined;
321
- return (_jsxs(_Fragment, { children: [_jsx(Button, { variant: variant, loading: busy, ...(action.icon !== undefined && { icon: action.icon }), size: showIconOnly ? "icon" : "md", ...(showIconOnly && { ariaLabel: action.label, title: action.label }), onClick: () => {
322
- if (needsConfirm) {
323
- setConfirmOpen(true);
324
- }
325
- else {
326
- void trigger();
327
- }
328
- }, testId: `render-list-toolbar-action-${action.id}`, children: showIconOnly ? null : action.label }), _jsx(Dialog, { open: confirmOpen, onOpenChange: setConfirmOpen, title: action.label, ...(action.confirm !== undefined && { description: action.confirm }), confirmLabel: action.confirmLabel ?? action.label, ...(action.style === "danger" && { variant: "danger" }), onConfirm: trigger, testId: `render-list-toolbar-action-${action.id}-dialog` }), errorText !== null && (_jsx(Banner, { variant: "error", testId: `render-list-toolbar-action-${action.id}-error`, children: errorText }))] }));
329
- }
330
- function isPlainRecord(value) {
331
- return typeof value === "object" && value !== null && !Array.isArray(value);
332
- }
333
- // The server leaves a missing or unreadable target out of `_refs`, and a null
334
- // entry means the same: both read as unresolved.
335
- function eagerloadedRef(row, field) {
336
- if (!isPlainRecord(row) || !isPlainRecord(row["_refs"]))
337
- return undefined;
338
- const ref = row["_refs"][field];
339
- if (Array.isArray(ref))
340
- return ref.filter(isPlainRecord);
341
- return isPlainRecord(ref) ? ref : undefined;
71
+ const createButton = renderCreateButton(toolbarModel);
72
+ const promotedMenuItems = buildPromotedMenuItems({
73
+ promotedToolbarActions: split.promotedToolbarActions,
74
+ promotedInOverflowMenu: split.promotedInOverflowMenu,
75
+ menuActionBusyId,
76
+ setMenuActionBusyId,
77
+ setMenuActionError,
78
+ });
79
+ const headerActions = renderHeaderActions(toolbarModel, createButton);
80
+ const toolbarEnd = renderToolbarEnd(toolbarModel, createButton);
81
+ const composedEmptyState = composeEmptyState({
82
+ Button,
83
+ emptyState,
84
+ onCreate,
85
+ emptyContent,
86
+ narrowed,
87
+ defaultFilterActive: defaultFilterActiveProp && onFilterReset !== undefined,
88
+ onFilterReset,
89
+ translate,
90
+ createInPageHeader,
91
+ effectiveCreateLabel,
92
+ });
93
+ const toolbarTitle = resolveToolbarTitle(screen, translate);
94
+ return (_jsxs(_Fragment, { children: [headerActions !== undefined &&
95
+ PageHeader !== undefined &&
96
+ renderPageHeader(PageHeader, headerActions, split.promotedInOverflowMenu, promotedMenuItems), menuActionError !== null && renderMenuActionError(Banner, menuActionError), renderReferenceLookupBridges(referenceColumns, handleLookupMap), _jsx(DataTable, { columns: enrichedVm.columns, rows: enrichedVm.rows, toolbarTitle: toolbarTitle, ...tableChromeProps({
97
+ onRowClick,
98
+ composedEmptyState,
99
+ toolbarStart,
100
+ description,
101
+ toolbarEnd,
102
+ itemNoun,
103
+ }), ...tableSortPagingProps({ sort, onSortChange, pager, onReachEnd, loadingMore, hasMore }), ...tableRowProps({ rowActions, rowActionMode, bulk, rowTone }), ...tableFilterProps({
104
+ filterFacets,
105
+ filterValues,
106
+ onFilterChange,
107
+ onFilterReset,
108
+ dateRangeFacets,
109
+ onDateRangeChange,
110
+ }), ...tableLayoutProps({
111
+ chromeless,
112
+ scrollBody,
113
+ screenPadding,
114
+ expandedRowIds,
115
+ onToggleRowExpanded,
116
+ renderExpandedRow,
117
+ }), ...tableBlockProps, testId: "render-list-table" })] }));
342
118
  }
@@ -0,0 +1,20 @@
1
+ import type { EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import type { EditFieldViewModel } from "../headless/index.ts";
3
+ export declare function useReferenceInputState(field: EditFieldViewModel, featureName: string, onChange: (value: unknown) => void): {
4
+ refFeature: string;
5
+ isMultiple: boolean;
6
+ options: {
7
+ value: string;
8
+ label: string;
9
+ }[];
10
+ loading: boolean;
11
+ createTarget: {
12
+ screen: EntityEditScreenDefinition;
13
+ entity: import("@cosmicdrift/kumiko-types/fields").EntityDefinition;
14
+ } | undefined;
15
+ createOpen: boolean;
16
+ setCreateOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
17
+ createdWithoutIdWarning: boolean;
18
+ handleCreated: (newId: string | undefined) => void;
19
+ handleSearchChange: (q: string) => void;
20
+ };
@@ -0,0 +1,85 @@
1
+ import { useCallback, useMemo, useState } from "react";
2
+ import { useAppFeatures } from "../app/app-features-context.js";
3
+ import { screenAccessAllows } from "../app/screen-access.js";
4
+ import { useUserRoles } from "../context/user-roles-context.js";
5
+ import { rendererError } from "../dev-console.js";
6
+ import { REFERENCE_COMBOBOX_LIMIT } from "../hooks/reference-limits.js";
7
+ import { useQuery } from "../hooks/use-query.js";
8
+ import { referenceOptionSource } from "../hooks/use-reference-lookup.js";
9
+ import { stringArrayOf } from "./render-field-values.js";
10
+ export function useReferenceInputState(field, featureName, onChange) {
11
+ const refEntity = field.refEntity ?? "";
12
+ const refFeature = field.refFeature ?? featureName;
13
+ const isMultiple = field.refMultiple === true;
14
+ // refFeature may differ from featureName (for example,
15
+ // items.assignee → users:query:user:list). The default is the same feature,
16
+ // as provided by the ViewModel (parseRefTarget).
17
+ // refOptionsQuery replaces that source with an author-supplied handler whose
18
+ // rows already carry a composed `label`.
19
+ const { queryQn, labelKey } = referenceOptionSource(field, refFeature, refEntity);
20
+ // "+ Neu" in the combobox opens the referenced entity's create screen in a
21
+ // dialog instead of leaving the current form.
22
+ // refFeature may differ from the currently rendered feature; appFeatures
23
+ // (createKumikoApp) knows all feature schemas, not only the active screen.
24
+ // If the feature, screen, or entity is not registered, or allowCreate:false,
25
+ // onCreate remains undefined and the combobox renders without the footer.
26
+ const appFeatures = useAppFeatures();
27
+ const userRoles = useUserRoles();
28
+ const [createOpen, setCreateOpen] = useState(false);
29
+ const refTargetSchema = appFeatures.find((f) => f.featureName === refFeature);
30
+ const refCreateScreen = refTargetSchema?.screens.find((s) => s.type === "entityEdit" &&
31
+ s.entity === refEntity &&
32
+ s.allowCreate !== false &&
33
+ screenAccessAllows(s.access, userRoles));
34
+ const refEntityDef = refTargetSchema?.entities[refEntity];
35
+ // When the user types in the combobox, the server filters through the
36
+ // existing list-payload `search` parameter. The combobox debounces the
37
+ // keystroke itself (300ms) and calls onSearchChange.
38
+ // An empty initial state requests the first 50 items in default order.
39
+ const [searchTerm, setSearchTerm] = useState("");
40
+ const queryPayload = useMemo(() => searchTerm === ""
41
+ ? { limit: REFERENCE_COMBOBOX_LIMIT }
42
+ : { limit: REFERENCE_COMBOBOX_LIMIT, search: searchTerm }, [searchTerm]);
43
+ const queryResult = useQuery(queryQn, queryPayload);
44
+ const handleSearchChange = useCallback((q) => setSearchTerm(q), []);
45
+ const createTarget = !field.readOnly && refCreateScreen !== undefined && refEntityDef !== undefined
46
+ ? { screen: refCreateScreen, entity: refEntityDef }
47
+ : undefined;
48
+ const [createdWithoutIdWarning, setCreatedWithoutIdWarning] = useState(false);
49
+ const handleCreated = useCallback((newId) => {
50
+ setCreateOpen(false);
51
+ // Clear the search filter before refetching — the just-created record
52
+ // won't match whatever the user had typed pre-create, and would then
53
+ // be missing from `options` (shown as a raw id instead of a label).
54
+ setSearchTerm("");
55
+ queryResult.refetch().catch((err) => {
56
+ rendererError("render-field: refetch after create failed", err);
57
+ });
58
+ // Record was created server-side but the payload carried no id —
59
+ // can't auto-select it, surface that instead of failing silently.
60
+ setCreatedWithoutIdWarning(newId === undefined);
61
+ if (newId !== undefined) {
62
+ onChange(isMultiple ? [...stringArrayOf(field.value), newId] : newId);
63
+ }
64
+ }, [isMultiple, field.value, onChange, queryResult.refetch]);
65
+ const options = useMemo(() => {
66
+ const rows = queryResult.data?.rows ?? [];
67
+ return rows.map((row) => {
68
+ const idVal = String(row["id"] ?? "");
69
+ const label = String(row[labelKey] ?? idVal);
70
+ return { value: idVal, label };
71
+ });
72
+ }, [queryResult.data, labelKey]);
73
+ return {
74
+ refFeature,
75
+ isMultiple,
76
+ options,
77
+ loading: queryResult.loading,
78
+ createTarget,
79
+ createOpen,
80
+ setCreateOpen,
81
+ createdWithoutIdWarning,
82
+ handleCreated,
83
+ handleSearchChange,
84
+ };
85
+ }
@@ -0,0 +1,3 @@
1
+ import type { FormValues } from "../headless/index.ts";
2
+ import type { RenderEditCore } from "./render-edit-render-model.ts";
3
+ export declare function useRenderEditControlledSync<TValues extends FormValues, TCtx>({ props, state, form, draft, }: RenderEditCore<TValues, TCtx>): void;
@@ -0,0 +1,26 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { useUnsavedInputSource } from "../leave-guard.js";
3
+ import { useWizardFormDraftRestore } from "./use-wizard-form-draft.js";
4
+ export function useRenderEditControlledSync({ props, state, form, draft, }) {
5
+ const { controller, snapshot } = form;
6
+ const { extensionDirty } = state;
7
+ const onDirtyChangeRef = useRef(props.onDirtyChange);
8
+ onDirtyChangeRef.current = props.onDirtyChange;
9
+ const hasUnsavedInput = snapshot.isDirty || extensionDirty;
10
+ useEffect(() => {
11
+ onDirtyChangeRef.current?.(hasUnsavedInput);
12
+ }, [hasUnsavedInput]);
13
+ // Draft wizards keep their input server-side, so leaving loses nothing.
14
+ const persistsDraft = state.isWizard && props.screen.layout.draft === true;
15
+ useUnsavedInputSource(() => !persistsDraft && (controller.getSnapshot().isDirty || state.extensionDirtyRef.current));
16
+ useWizardFormDraftRestore({
17
+ screenId: props.screen.id,
18
+ isCreateMode: state.isCreateMode,
19
+ draftEnabled: state.draftEnabled,
20
+ dispatcher: state.dispatcher,
21
+ controller,
22
+ hasNavigatedRef: draft.refs.hasNavigatedRef,
23
+ setRawStep: state.setRawStep,
24
+ draftState: state.draftState,
25
+ });
26
+ }
@@ -0,0 +1,29 @@
1
+ import { type MutableRefObject } from "react";
2
+ import type { FormValues } from "../headless/index.ts";
3
+ import type { ResolvedRenderEditProps } from "./render-edit-render-model.ts";
4
+ import type { RenderEditForm } from "./use-render-edit-form.ts";
5
+ import type { RenderEditState } from "./use-render-edit-state.ts";
6
+ export type RenderEditRefs<TValues extends FormValues> = {
7
+ readonly onChangeRef: MutableRefObject<ResolvedRenderEditProps<TValues>["onChange"]>;
8
+ readonly deliveredControlsToRef: MutableRefObject<ResolvedRenderEditProps<TValues>["onControlsReady"]>;
9
+ readonly onControlsReadyRef: MutableRefObject<ResolvedRenderEditProps<TValues>["onControlsReady"]>;
10
+ readonly hasNavigatedRef: MutableRefObject<boolean>;
11
+ readonly submittedRef: MutableRefObject<boolean>;
12
+ readonly isSubmittingRef: MutableRefObject<boolean>;
13
+ readonly scopeFieldNamesRef: MutableRefObject<readonly string[] | undefined>;
14
+ readonly saveDraftRef: MutableRefObject<(stepIndex: number) => void>;
15
+ readonly handleSubmitRef: MutableRefObject<(options?: {
16
+ readonly saveNow?: boolean;
17
+ }) => Promise<void>>;
18
+ readonly wizardNextRef: MutableRefObject<() => void>;
19
+ readonly wizardBackRef: MutableRefObject<() => void>;
20
+ readonly currentStepRef: MutableRefObject<number>;
21
+ readonly draftSaveTimerRef: MutableRefObject<ReturnType<typeof setTimeout> | null>;
22
+ };
23
+ export type RenderEditDraft<TValues extends FormValues> = {
24
+ readonly refs: RenderEditRefs<TValues>;
25
+ readonly scopedValidate: () => boolean;
26
+ readonly markSaved: (fields?: readonly string[]) => void;
27
+ readonly patchAndScheduleDraftSave: (partial: Partial<TValues>) => void;
28
+ };
29
+ export declare function useRenderEditDraftSave<TValues extends FormValues, TCtx>(props: ResolvedRenderEditProps<TValues, TCtx>, state: RenderEditState, form: RenderEditForm<TValues>): RenderEditDraft<TValues>;