@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,279 +1,37 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cardColumnRoles } from "@cosmicdrift/kumiko-framework/ui-types";
3
- import { ArrowDown, ArrowUp, ChevronDown, ChevronRight, Search, SlidersHorizontal, X, } from "lucide-react";
4
- import { Fragment, useId, useState, } from "react";
5
- import { useOptionalLocale, useOptionalTranslation } from "../../index.js";
6
- import { UNIFORM_BAR_CONTROLS_CLASSES } from "../layout/uniform-bar-controls.js";
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
7
2
  import { cn } from "../lib/cn.js";
8
- import { Button as UiButton } from "../ui/button.js";
9
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
10
- import { CardLeadingImage } from "./file-thumbnail.js";
3
+ import { renderTableContent } from "./data-table/table-content.js";
4
+ import { buildModelFacetCluster, hasToolbarContent, renderToolbar, renderToolbarDescription, } from "./data-table/toolbar.js";
5
+ import { useDataTableState } from "./data-table/use-data-table-state.js";
11
6
  import { screenPaddingClassName } from "./layout.js";
12
- import { useIsNarrowViewport } from "./use-narrow-viewport.js";
13
- import "../react-css-custom-properties.js";
14
- import { buildTableItems, cellLinkProp, cellTitle, DataTableCell, FIT_CONTENT_CELL_CLASS, FirstCellLink, isBadgeColumn, isEmptyCellValue, isUnlabeledFalse, NUMERIC_COLUMN_TYPES, rowToneClass, SortableHeader, showsBareCheckMark, TABULAR_COLUMN_TYPES, } from "./data-table-cell.js";
15
- import { DateRangeFacetFilter, FacetFilter } from "./facet-filter.js";
16
- import { InfiniteSentinel, Pager, TABLE_FOOTER_BAR_CLASS } from "./pager.js";
17
- import { RowActionsCell } from "./row-actions.js";
18
- // The vendored Table wraps <table> in an `overflow-x-auto` container, which
19
- // would become the scrollport of a sticky header. A scrollBody table scrolls
20
- // in its parent instead, so it renders the bare <table>.
21
- function FlushTable({ className, ...props }) {
22
- return (_jsx("table", { "data-slot": "table", className: cn("w-full caption-bottom text-sm", className), ...props }));
7
+ // `screenPadding`: on a list screen this wrapper IS the screen container,
8
+ // so it takes the same token as FormScreenShell/PageSection instead of its
9
+ // own inset — a list ends at the same footer distance as a form.
10
+ // Explicit `false` (vs. the default/omitted case) means a host that
11
+ // already pads its own children — a relatedList section nested in a
12
+ // tabs-mode Card — so this wrapper adds no inset of its
13
+ // own, or its content would sit ~24px deeper than sibling banners in the
14
+ // same Card body.
15
+ function screenPaddingOf(screenPadding) {
16
+ if (screenPadding === true)
17
+ return screenPaddingClassName;
18
+ return screenPadding === false ? undefined : "p-6";
23
19
  }
24
- // Header cells stick to the scroll surface; the bottom rule is an inset
25
- // shadow because a border on a sticky cell scrolls away under border-collapse.
26
- // Column weight (medium vs. semibold for the sorted one) lives on the th itself
27
- // in SortableHeader: a `[&_th]:font-*` rule here would outrank it.
28
- // Child selectors only: an expanded row nests a whole table inside a cell, and
29
- // descendant selectors would make its header sticky and re-pad its cells.
30
- const FILL_TABLE_CLASS = cn("[&>thead>tr>th]:sticky [&>thead>tr>th]:top-0 [&>thead>tr>th]:z-10 [&>thead>tr>th]:h-9 [&>thead>tr>th]:bg-muted [&>thead>tr>th]:px-2 [&>thead>tr>th]:text-[13px]", "[&>thead>tr>th]:shadow-[inset_0_-1px_0_var(--color-border)] [&>thead>tr>th_button]:text-[13px] [&>tbody>tr>td]:px-2 [&>tbody>tr>td]:py-0", "[&>*>tr>:first-child]:pl-6 [&>*>tr>:last-child]:pr-6");
31
- // `!`: beats the first/last-child padding of FILL_TABLE_CLASS, which would
32
- // otherwise widen the narrow toggle column / pad the full-width expansion cell.
33
- // The nested table's sticky actions cell (last td) gets the muted surface
34
- // instead of its own md:bg-background, so it blends into the area.
35
- const EXPAND_TOGGLE_CELL_CLASS = "w-10 px-1! py-0!";
36
- const EXPANSION_CELL_CLASS = "whitespace-normal bg-muted p-0! align-top md:[&_td:last-child]:bg-muted";
37
- const EXPANSION_CONTENT_CLASS = "pt-1 pr-4 pb-3.5 pl-[52px]";
38
- function RowExpandToggle({ expanded, controlsId, label, onToggle, testId, }) {
39
- return (_jsx(UiButton, { type: "button", variant: "ghost", size: "icon", className: "size-8 shrink-0 max-md:size-11", "aria-expanded": expanded, ...(expanded && { "aria-controls": controlsId }), "aria-label": label, "data-testid": testId, onClick: (e) => {
40
- e.stopPropagation();
41
- onToggle();
42
- }, onKeyDown: (e) => e.stopPropagation(), children: _jsx(ChevronRight, { className: cn("size-4 transition-transform", expanded && "rotate-90"), "aria-hidden": "true" }) }));
20
+ function wrapperClassName({ fillsHeight, screenPadding }) {
21
+ return cn("flex flex-col w-full",
22
+ // scrollBody: the wrapper fills its container so the footer sits at the
23
+ // same spot at 3 and 300 rows; the 24px insets live in toolbar, cells
24
+ // and footer, so no outer padding.
25
+ fillsHeight ? "h-full min-h-0" : cn("gap-4", screenPaddingOf(screenPadding)));
43
26
  }
44
- function DataTableFooter({ count, itemNoun, testId, }) {
45
- const t = useOptionalTranslation();
46
- const noun = itemNoun?.(count);
47
- const label = noun !== undefined
48
- ? (t?.("kumiko.list.count.noun", { count: count.toLocaleString(), noun }) ??
49
- `${count.toLocaleString()} ${noun}`)
50
- : count === 1
51
- ? (t?.("kumiko.list.count.one") ?? "1 entry")
52
- : (t?.("kumiko.list.count.other", { count: count.toLocaleString() }) ??
53
- `${count.toLocaleString()} entries`);
54
- return (_jsx("div", { "data-testid": testId, className: TABLE_FOOTER_BAR_CLASS, children: _jsx("span", { "data-testid": `${testId}-count`, children: label }) }));
55
- }
56
- // Cell content is a ReactNode, so the tooltip text is read from the rendered DOM.
57
- function showFullTextOnHover(event) {
58
- const element = event.currentTarget;
59
- element.title = element.scrollWidth > element.clientWidth ? (element.textContent ?? "") : "";
60
- }
61
- export function DefaultDataTable({ columns, rows, onRowClick, sort, onSortChange, emptyState, toolbarStart, toolbarDescription, toolbarEnd, pager, onReachEnd, loadingMore, hasMore, itemNoun, rowActions, rowActionMode, rowGrouping, rowTone, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, testId, onCellChange, getRowTestId, getCellTestId, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, }) {
62
- // One locale/translate subscription per table — not per cell.
63
- // Optional hooks: a bare DataTable outside LocaleProvider must not crash.
64
- const tableTranslate = useOptionalTranslation();
65
- const tableLocale = useOptionalLocale();
66
- // Below 768px a table scrolls its columns out of reach with no visible
67
- // affordance. Cards replace the table entirely below the breakpoint — same
68
- // single-mount pattern as EmbeddedListInput/embedded-list-input.tsx.
69
- const isNarrow = useIsNarrowViewport();
70
- const [facetsOpenNarrow, setFacetsOpenNarrow] = useState(false);
71
- const [toggledGroups, setToggledGroups] = useState(new Set());
72
- // Toolbar wrapper: shared container for toolbar + table so the two belong
73
- // together visually. The toolbar is NOT sticky: lists typically scroll with
74
- // the page container, not internally. Sticky would compete with the topbar.
75
- // The row click already runs rowClick actions, so with a clickable row they
76
- // must not reappear in the kebab (a list whose only action is the rowClick
77
- // one gets no actions column at all).
78
- const rowIsLink = onRowClick !== undefined;
79
- const menuActions = rowActions !== undefined && rowIsLink
80
- ? rowActions.filter((action) => action.rowClick !== true)
81
- : rowActions;
82
- // The table keeps every action in "inline" mode, and with editable cells the
83
- // first cell is not a keyboard link (FirstCellLink is off), so the kebab is
84
- // then the only keyboard route to the rowClick action.
85
- const tableActions = rowActionMode === "inline" || onCellChange !== undefined ? rowActions : menuActions;
86
- const rowClickAction = rowActions?.find((action) => action.rowClick === true);
87
- const rowClickLabel = rowClickAction?.label;
88
- const rowClickIsHidden = (row) => rowClickAction?.isVisible !== undefined && !rowClickAction.isVisible(row);
89
- const hasTableActions = tableActions !== undefined && tableActions.length > 0;
90
- const isEmpty = rows.length === 0;
91
- const expansionIdPrefix = useId();
92
- const isRowExpandable = renderExpandedRow !== undefined && onToggleRowExpanded !== undefined;
93
- const rowTestId = (row) => getRowTestId?.(row) ?? `row-${row.id}`;
94
- const expansionDomId = (row) => `${expansionIdPrefix}-${row.id}`;
95
- // The expand toggle of a row, shared by table and card layout. Named after
96
- // the first data column, the one a user recognizes the row by.
97
- function renderExpandToggle(row) {
98
- if (!isRowExpandable)
99
- return null;
100
- const expanded = expandedRowIds?.has(row.id) === true;
101
- const firstColumn = columns[0];
102
- const title = (firstColumn !== undefined ? cellTitle(row.values[firstColumn.field]) : undefined) ?? "";
103
- const labelKey = expanded ? "kumiko.list.row.collapse" : "kumiko.list.row.expand";
104
- return (_jsx(RowExpandToggle, { expanded: expanded, controlsId: expansionDomId(row), label: tableTranslate?.(labelKey, { title }) ?? `${expanded ? "Collapse" : "Expand"} ${title}`, onToggle: () => onToggleRowExpanded(row.id), testId: `${rowTestId(row)}-toggle` }));
105
- }
106
- function renderExpansion(row, as, colSpan) {
107
- if (!isRowExpandable || expandedRowIds?.has(row.id) !== true)
108
- return null;
109
- const content = renderExpandedRow(row);
110
- if (as === "item") {
111
- return (_jsx("li", { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "border-b border-border-row bg-muted px-4 pt-1 pb-3.5", children: content }));
112
- }
113
- return (_jsx(TableRow, { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: colSpan, className: EXPANSION_CELL_CLASS, children: _jsx("div", { className: EXPANSION_CONTENT_CLASS, children: content }) }) }));
114
- }
115
- const fillsHeight = scrollBody === true;
116
- const TableRoot = fillsHeight ? FlushTable : Table;
117
- const emptyBlock = (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-empty` : "render-list-empty", className: cn("flex flex-col items-center justify-center p-12 text-sm text-muted-foreground gap-3", chromeless !== true && !fillsHeight && "rounded-md border border-dashed"), children: emptyState ?? _jsx("span", { children: tableTranslate?.("kumiko.list.no-entries") ?? "No entries." }) }));
118
- const tableItems = buildTableItems(rows, rowGrouping, toggledGroups);
119
- function renderGroupToggle(item) {
120
- return (_jsxs("button", { type: "button", "aria-expanded": !item.collapsed, "data-testid": `row-group-${item.key}-toggle`, onClick: () => setToggledGroups((prev) => {
121
- const next = new Set(prev);
122
- if (!next.delete(item.key))
123
- next.add(item.key);
124
- return next;
125
- }), className: "flex w-full items-center gap-2 px-4 py-2 text-left text-xs font-medium text-foreground-secondary", children: [item.collapsed ? (_jsx(ChevronRight, { className: "size-3.5" })) : (_jsx(ChevronDown, { className: "size-3.5" })), item.label] }));
126
- }
127
- function tableInner() {
128
- if (isEmpty)
129
- return emptyBlock;
130
- return (_jsx("div", { className: cn(!fillsHeight && "overflow-hidden", !fillsHeight && chromeless !== true && "rounded-lg border bg-card"), children: _jsxs(TableRoot, { "data-testid": testId, className: fillsHeight ? FILL_TABLE_CLASS : undefined, children: [_jsx(TableHeader, { className: "bg-muted", children: _jsxs(TableRow, { className: "hover:bg-transparent", children: [isRowExpandable && (_jsx(TableHead, { "data-testid": "column-expand", className: EXPAND_TOGGLE_CELL_CLASS })), columns.map((col) => (_jsx(SortableHeader, { field: col.field, label: col.label, sortable: col.sortable === true, highlighted: col.highlighted === true, numeric: NUMERIC_COLUMN_TYPES.has(col.type), fitContent: col.fileDisplay !== undefined, ...(sort !== undefined && sort !== null && { sort }), ...(onSortChange !== undefined && { onSortChange }) }, col.field))), hasTableActions && (_jsx(TableHead, { "data-testid": "column-actions",
131
- // From md: sticky right-0 + bg-muted (= header tone) so the
132
- // action column stays at the right edge during horizontal
133
- // scroll on wider screens. Below md, actions scroll in
134
- // normal flow — sticky there would pin the column over the
135
- // preceding data column instead of the (already narrow)
136
- // page edge. No border-l: a permanent divider looks heavy;
137
- // the sticky bg already sets the column apart during scroll.
138
- className: "w-px text-right text-muted-foreground md:sticky md:right-0 md:z-10 md:bg-muted", "aria-label": tableTranslate?.("kumiko.list.actions") ?? "Actions" }))] }) }), _jsx(TableBody, { children: tableItems.map((item) => {
139
- if (item.kind === "group") {
140
- return (_jsx(TableRow, { "data-testid": `row-group-${item.key}`, children: _jsx(TableCell, { colSpan: columns.length + (isRowExpandable ? 1 : 0) + (hasTableActions ? 1 : 0), className: "bg-muted/40 p-0", children: renderGroupToggle(item) }) }, `group-${item.key}`));
141
- }
142
- const row = item.row;
143
- const opensOnClick = rowIsLink && !rowClickIsHidden(row);
144
- return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), onClick: opensOnClick ? () => onRowClick(row) : undefined, className: cn(rowToneClass(rowTone?.(row)), opensOnClick && "cursor-pointer", fillsHeight && "h-10 border-border-row hover:bg-muted"), children: [isRowExpandable && (_jsx(TableCell, { "data-testid": getCellTestId?.(row, "expand") ?? `cell-${row.id}-expand`, className: EXPAND_TOGGLE_CELL_CLASS, children: renderExpandToggle(row) })), columns.map((col, colIndex) => (_jsx(TableCell, { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, "data-highlighted": col.highlighted === true ? "true" : undefined,
145
- // Text wraps instead of truncating: nowrap cells keep their
146
- // full width, so a wide table scrolled sideways and its
147
- // last column slid under the sticky actions column with
148
- // nothing visibly cut off. max-w-xs bounds a single column;
149
- // badges and dates stay nowrap by their own classes.
150
- className: cn(col.fileDisplay === undefined
151
- ? "max-w-xs whitespace-normal break-words"
152
- : FIT_CONTENT_CELL_CLASS, colIndex === 0
153
- ? "font-medium text-foreground"
154
- : "text-foreground-secondary", NUMERIC_COLUMN_TYPES.has(col.type) && "text-right", TABULAR_COLUMN_TYPES.has(col.type) && "tabular-nums", col.highlighted === true && "bg-accent/40"), title: cellTitle(row.values[col.field]), children: _jsx(FirstCellLink, { enabled: colIndex === 0 && opensOnClick && onCellChange === undefined, onOpen: () => onRowClick?.(row), empty: isEmptyCellValue(row.values[col.field]), emptyLabel: rowClickLabel, children: _jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && {
155
- optionLabels: col.optionLabels,
156
- }), ...(col.optionTones !== undefined && { optionTones: col.optionTones }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && { fileDisplay: col.fileDisplay }), ...(onCellChange !== undefined && {
157
- onChange: (value) => onCellChange(row.id, col.field, value),
158
- }), ...cellLinkProp(col, row, !(colIndex === 0 && opensOnClick && onCellChange === undefined)) }) }) }, col.field))), hasTableActions && (_jsx(TableCell, { "data-testid": getCellTestId?.(row, "actions") ?? `cell-${row.id}-actions`,
159
- // From md: sticky-right so the actions stay visible on the
160
- // right edge during horizontal scroll. Below md, actions
161
- // scroll with the row like any other cell — sticky there
162
- // would hide the neighboring data column on narrow
163
- // viewports. bg-background sets the column apart during
164
- // scroll — no border-l (divider too heavy).
165
- className: cn("whitespace-nowrap text-right md:sticky md:right-0 md:z-10 md:bg-background", fillsHeight && "md:bg-card"),
166
- // Action-cell events must not trigger the row click/activation
167
- // (typically "Open Detail" — the user wanted the action,
168
- // not navigation). We stopPropagation for mouse and
169
- // keyboard so a11y stays consistent.
170
- onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: _jsx(RowActionsCell, { row: row, actions: tableActions, mode: rowActionMode, rowIsLink: rowIsLink }) }))] }), renderExpansion(row, "row", columns.length + (isRowExpandable ? 1 : 0) + (hasTableActions ? 1 : 0))] }, row.id));
171
- }) })] }) }));
172
- }
173
- // No column headers below the breakpoint, so the click-to-sort affordance
174
- // on SortableHeader has nothing to attach to. A single native <select> is
175
- // the whole fix — no custom widget, no menu — fed straight from the
176
- // columns marked sortable in the ViewModel. Options carry the resolved
177
- // {field, dir} directly so onChange can look the pick up by value instead
178
- // of parsing/casting the option string back apart.
179
- const sortableColumns = columns.filter((col) => col.sortable);
180
- const sortOptions = sortableColumns.flatMap((col) => [
181
- { value: `${col.field}:asc`, field: col.field, dir: "asc", label: `${col.label} ↑` },
182
- { value: `${col.field}:desc`, field: col.field, dir: "desc", label: `${col.label} ↓` },
183
- ]);
184
- const { image: cardImageColumn, title: cardTitleColumn, status: cardStatusColumn, meta: cardMetaColumns, } = cardColumnRoles(columns);
185
- function cardCell(row, col) {
186
- // A bare check mark in the subtitle line says nothing without its column.
187
- if (showsBareCheckMark(col.type, row.values[col.field], col.renderer))
188
- return col.label;
189
- return (_jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && { optionLabels: col.optionLabels }), ...(col.optionTones !== undefined && { optionTones: col.optionTones }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && { fileDisplay: col.fileDisplay, compactFile: true }), ...(onCellChange !== undefined && {
190
- onChange: (value) => onCellChange(row.id, col.field, value),
191
- }), ...cellLinkProp(col, row, !(rowIsLink && !rowClickIsHidden(row))) }));
192
- }
193
- function renderCard(row) {
194
- const metaColumns = cardMetaColumns.filter((col) => !isEmptyCellValue(row.values[col.field]) &&
195
- !isUnlabeledFalse(col.type, row.values[col.field], col.renderer));
196
- const showStatus = cardStatusColumn !== undefined && !isEmptyCellValue(row.values[cardStatusColumn.field]);
197
- const hasMenu = menuActions !== undefined && menuActions.length > 0;
198
- const opensOnClick = rowIsLink && !rowClickIsHidden(row);
199
- const body = (_jsxs(_Fragment, { children: [cardImageColumn?.fileDisplay !== undefined && (_jsx("div", { "data-testid": `card-image-${row.id}`, className: "shrink-0", children: _jsx(CardLeadingImage, { value: row.values[cardImageColumn.field], display: cardImageColumn.fileDisplay }) })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-2", children: [cardTitleColumn !== undefined && (_jsx("span", { "data-testid": getCellTestId?.(row, cardTitleColumn.field) ??
200
- `cell-${row.id}-${cardTitleColumn.field}`, className: "min-w-0 break-words text-base font-semibold text-foreground", children: cardCell(row, cardTitleColumn) })), showStatus && (_jsx("span", { "data-testid": getCellTestId?.(row, cardStatusColumn.field) ??
201
- `cell-${row.id}-${cardStatusColumn.field}`, className: "shrink-0", children: cardCell(row, cardStatusColumn) }))] }), metaColumns.length > 0 && (_jsx("div", { className: "min-w-0 overflow-x-clip", children: _jsx("div", { "data-testid": `card-meta-${row.id}`, className: "-ms-3 flex min-w-0 flex-wrap items-center text-[13px] leading-5 tabular-nums text-foreground-secondary", children: metaColumns.map((col) => (_jsxs("div", { className: "flex h-5 min-w-0 max-w-full items-center", children: [_jsx("span", { "aria-hidden": "true", className: "w-3 shrink-0 text-center", children: "\u00B7" }), _jsx("div", { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, className: "min-w-0 truncate leading-5", onPointerEnter: showFullTextOnHover, children: isBadgeColumn(col) ? (_jsx("span", { className: "inline-flex align-middle", children: cardCell(row, col) })) : (cardCell(row, col)) })] }, col.field))) }) }))] }), opensOnClick && (_jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }))] }));
202
- const rowItem = (_jsxs("li", { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), className: cn("flex min-h-[72px] items-center gap-2 border-b border-border-row py-3", rowToneClass(rowTone?.(row)), isRowExpandable ? "pl-2" : "pl-4", hasMenu ? "pr-2" : "pr-4"), ...(opensOnClick && { onClick: () => onRowClick(row) }), children: [renderExpandToggle(row), opensOnClick ? (_jsx("button", { type: "button", className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2 bg-transparent p-0 text-left", onKeyDown: (e) => {
203
- if (e.key !== "Enter")
204
- return;
205
- e.preventDefault();
206
- e.stopPropagation();
207
- onRowClick(row);
208
- }, children: body })) : (body), hasMenu && (_jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: _jsx(RowActionsCell, { row: row, actions: menuActions, mode: rowActionMode, rowIsLink: rowIsLink, compact: true }) }))] }, row.id));
209
- if (!isRowExpandable)
210
- return rowItem;
211
- // The expansion is a sibling <li>, outside the row's click target, so
212
- // clicks in the sub-area never navigate the parent row.
213
- return (_jsxs(Fragment, { children: [rowItem, renderExpansion(row, "item", 0)] }, row.id));
214
- }
215
- function cardsInner() {
216
- if (isEmpty)
217
- return emptyBlock;
218
- const sortLabel = sort !== undefined && sort !== null
219
- ? (tableTranslate?.("kumiko.list.sort.by", {
220
- column: columns.find((col) => col.field === sort.field)?.label ?? sort.field,
221
- }) ?? `Sorted by ${sort.field}`)
222
- : (tableTranslate?.("kumiko.list.sort.unsorted") ?? "Unsorted");
223
- const SortArrow = sort?.dir === "desc" ? ArrowDown : ArrowUp;
224
- // URL state can carry a sort on a column that is not sortable; without an
225
- // option for it the select would claim "Unsorted" while the list is sorted.
226
- const currentSortValue = sort !== undefined && sort !== null ? `${sort.field}:${sort.dir}` : "";
227
- const currentSortIsUnlisted = sort !== undefined && sort !== null && !sortOptions.some((o) => o.value === currentSortValue);
228
- return (_jsx("div", { "data-testid": testId, className: "contents", children: _jsxs("div", { "data-testid": testId !== undefined ? `${testId}-cards` : "render-list-cards", className: "flex flex-col", children: [onSortChange !== undefined && sortableColumns.length > 0 && (_jsxs("div", { className: "relative flex h-9 items-center gap-1.5 px-4 text-[13px] text-foreground-secondary", children: [_jsx("span", { "aria-hidden": "true", children: sortLabel }), sort !== undefined && sort !== null && (_jsx(SortArrow, { className: "size-3.5", "aria-hidden": "true" })), _jsxs("select", { "aria-label": tableTranslate?.("kumiko.list.sort.label") ?? "Sort", "data-testid": testId !== undefined ? `${testId}-sort` : "render-list-sort", value: currentSortValue, onChange: (e) => {
229
- const raw = e.target.value;
230
- if (raw === "") {
231
- onSortChange(null);
232
- return;
233
- }
234
- const picked = sortOptions.find((o) => o.value === raw);
235
- if (picked === undefined)
236
- return;
237
- onSortChange({ field: picked.field, dir: picked.dir });
238
- }, className: "absolute inset-0 size-full cursor-pointer opacity-0", children: [_jsx("option", { value: "", children: tableTranslate?.("kumiko.list.sort.unsorted") ?? "Unsorted" }), currentSortIsUnlisted && (_jsx("option", { value: currentSortValue, children: `${columns.find((col) => col.field === sort.field)?.label ?? sort.field} ${sort.dir === "desc" ? "↓" : "↑"}` })), sortOptions.map((o) => (_jsx("option", { value: o.value, children: o.label }, o.value)))] })] })), _jsx("ul", { className: "m-0 list-none border-t border-border-row p-0", children: tableItems.map((item) => item.kind === "group" ? (_jsx("li", { "data-testid": `row-group-${item.key}`, className: "border-b border-border-row bg-muted/40", children: renderGroupToggle(item) }, `group-${item.key}`)) : (renderCard(item.row))) })] }) }));
239
- }
240
- const tableContent = isNarrow ? cardsInner() : tableInner();
241
- // The pager is ALWAYS rendered below the table (even with rows=[]) so the
242
- // user can page back on a filter hit of zero without losing the list.
243
- // Except total === 0: then there is nothing to paginate. Incompatible with
244
- // infinite scroll: the caller sets either pager OR onReachEnd.
245
- const pagerElement = pager !== undefined && pager.total > 0 ? (_jsx(Pager, { page: pager.page, limit: pager.limit, total: pager.total, onPageChange: pager.onPageChange, ...(pager.pageSizeOptions !== undefined && { pageSizeOptions: pager.pageSizeOptions }), ...(pager.onPageSizeChange !== undefined && { onPageSizeChange: pager.onPageSizeChange }), ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-pager` : "render-list-pager", pinned: fillsHeight })) : undefined;
246
- const sentinelElement = pagerElement === undefined && onReachEnd !== undefined ? (_jsx(InfiniteSentinel, { onReachEnd: onReachEnd, loadingMore: loadingMore === true, hasMore: hasMore !== false, rowsCount: rows.length, testId: testId !== undefined ? `${testId}-sentinel` : "render-list-sentinel" })) : undefined;
247
- // With scrollBody the footer (pager, else the entry count) sits OUTSIDE the
248
- // scroll surface so it stays put at any row count; the infinite sentinel
249
- // stays INSIDE, or it would never intersect the scrollport.
250
- const content = fillsHeight ? (_jsxs(_Fragment, { children: [_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-scroll` : "render-list-scroll", className: "min-h-0 flex-1 overflow-auto border-t border-border bg-card", children: [tableContent, sentinelElement] }), pagerElement ?? (_jsx(DataTableFooter, { count: rows.length, ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-footer` : "render-list-footer" }))] })) : (_jsxs(_Fragment, { children: [tableContent, pagerElement, sentinelElement] }));
251
- const hasFacets = filterFacets !== undefined && filterFacets.length > 0 && onFilterChange !== undefined;
252
- const hasDateRangeFacets = dateRangeFacets !== undefined && dateRangeFacets.length > 0 && onDateRangeChange !== undefined;
253
- const hasActiveFilters = filterValues !== undefined && Object.values(filterValues).some((v) => v.length > 0);
254
- const facetCluster = hasFacets || hasDateRangeFacets ? (_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [hasFacets &&
255
- filterFacets.map((facet) => (_jsx(FacetFilter, { facet: facet, selected: filterValues?.[facet.field] ?? [], onChange: onFilterChange, dense: fillsHeight }, facet.field))), hasDateRangeFacets &&
256
- dateRangeFacets.map((facet) => (_jsx(DateRangeFacetFilter, { facet: facet, onChange: onDateRangeChange }, facet.field))), hasActiveFilters && onFilterReset !== undefined && (_jsxs(UiButton, { variant: "ghost", size: "sm", className: "h-9 px-2", onClick: onFilterReset, "data-testid": "facet-reset", children: [tableTranslate?.("kumiko.list.filter.reset") ?? "Reset", _jsx(X, {})] }))] })) : undefined;
257
- const collapsesFacetsBehindButton = isNarrow && fillsHeight && facetCluster !== undefined;
258
- const hasToolbar = toolbarStart !== undefined || toolbarEnd !== undefined || facetCluster !== undefined;
27
+ export function DefaultDataTable(props) {
28
+ const model = useDataTableState(props);
29
+ const facetCluster = buildModelFacetCluster(model);
30
+ const hasToolbar = hasToolbarContent(model, facetCluster);
259
31
  // dashboard-01 pattern: the toolbar (search + facets + "+ New") sits ABOVE
260
32
  // the table in the same padding block; no separate bg bar, no screen title
261
- // (that lives in the shell's breadcrumb).
262
- return (_jsxs("div", { className: cn("flex flex-col w-full",
263
- // scrollBody: the wrapper fills its container so the footer sits at the
264
- // same spot at 3 and 300 rows; the 24px insets live in toolbar, cells
265
- // and footer, so no outer padding.
266
- fillsHeight
267
- ? "h-full min-h-0"
268
- : cn("gap-4", screenPadding === true
269
- ? screenPaddingClassName
270
- : screenPadding === false
271
- ? undefined
272
- : "p-6")), children: [hasToolbar && (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-toolbar` : "render-list-toolbar", className: cn("flex flex-wrap items-center", UNIFORM_BAR_CONTROLS_CLASSES, fillsHeight
273
- ? isNarrow
274
- ? "shrink-0 gap-2 p-3"
275
- : "min-h-13 shrink-0 gap-2 px-6 py-2 md:py-0"
276
- : "gap-3"), children: [toolbarStart !== undefined && (_jsxs("div", { className: cn(fillsHeight
277
- ? "relative w-full md:w-[300px] max-md:flex-1 [&_input]:border-border-strong [&_input]:pl-8 [&_input]:max-md:h-11"
278
- : "flex-1 min-w-48 max-w-sm"), children: [fillsHeight && (_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" })), toolbarStart] })), collapsesFacetsBehindButton ? (_jsxs(_Fragment, { children: [_jsx(UiButton, { type: "button", variant: "outline", size: "icon", className: "size-11", "aria-label": tableTranslate?.("kumiko.list.filter.toggle") ?? "Filter", "aria-expanded": facetsOpenNarrow, "data-testid": testId !== undefined ? `${testId}-filter-toggle` : "filter-toggle", onClick: () => setFacetsOpenNarrow((open) => !open), children: _jsx(SlidersHorizontal, { className: "size-4", "aria-hidden": "true" }) }), facetsOpenNarrow && _jsx("div", { className: "w-full", children: facetCluster })] })) : (facetCluster), toolbarEnd !== undefined && (_jsx("div", { className: "flex flex-wrap items-center gap-2 ml-auto", children: toolbarEnd }))] })), toolbarDescription !== undefined && (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0 pb-2 text-sm text-muted-foreground", fillsHeight && (isNarrow ? "px-3" : "px-6"), fillsHeight && !hasToolbar && "pt-4"), children: toolbarDescription })), content] }));
33
+ // (that lives in the shell's breadcrumb). The toolbar is NOT sticky: lists
34
+ // typically scroll with the page container, not internally. Sticky would
35
+ // compete with the topbar.
36
+ return (_jsxs("div", { className: wrapperClassName(model), children: [hasToolbar && renderToolbar(model, facetCluster), renderToolbarDescription(model, hasToolbar), renderTableContent(model)] }));
279
37
  }
@@ -1,3 +1,3 @@
1
1
  import { type ReactNode } from "react";
2
2
  import { type FileValueListProps } from "../../index.ts";
3
- export declare function FileValueList({ fileIds, testId }: FileValueListProps): ReactNode;
3
+ export declare function FileValueList({ fileIds, previewOpenByDefault, testId, }: FileValueListProps): ReactNode;
@@ -6,17 +6,17 @@ import { Button } from "../ui/button.js";
6
6
  import { PdfPreview } from "../widgets/pdf-preview.js";
7
7
  import { fileOriginalUrl, useFileMeta } from "./file-thumbnail.js";
8
8
  const PDF_MIME_TYPE = "application/pdf";
9
- function FileValueRow({ fileId, testId, }) {
9
+ function FileValueRow({ fileId, previewOpenInitially, testId, }) {
10
10
  const t = useTranslation();
11
11
  const meta = useFileMeta(fileId);
12
- const [previewOpen, setPreviewOpen] = useState(false);
12
+ const [previewOpen, setPreviewOpen] = useState(previewOpenInitially);
13
13
  if (!isUuid(fileId))
14
14
  return _jsx("span", { className: "text-sm", children: fileId });
15
15
  const downloadUrl = fileOriginalUrl(fileId);
16
- return (_jsxs("li", { className: "flex flex-col gap-2", "data-testid": testId, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("span", { className: "text-sm", children: meta?.fileName ?? fileId.slice(0, 8) }), _jsx("a", { href: downloadUrl, download: meta?.fileName ?? "", className: "text-primary text-sm underline underline-offset-2", ...(testId !== undefined && { "data-testid": `${testId}-download` }), children: t("kumiko.field.file-list.download") }), meta?.mimeType === PDF_MIME_TYPE && (_jsx(Button, { type: "button", variant: "link", size: "sm", "aria-expanded": previewOpen, onClick: () => setPreviewOpen((open) => !open), ...(testId !== undefined && { "data-testid": `${testId}-preview-toggle` }), children: t(previewOpen ? "kumiko.field.file-list.hidePreview" : "kumiko.field.file-list.preview") }))] }), previewOpen && (_jsx(PdfPreview, { source: { url: downloadUrl }, testId: `file-preview-${fileId}` }))] }));
16
+ return (_jsxs("li", { className: "flex flex-col gap-2", "data-testid": testId, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("span", { className: "text-sm", children: meta?.fileName ?? fileId.slice(0, 8) }), _jsx("a", { href: downloadUrl, download: meta?.fileName ?? "", className: "text-primary text-sm underline underline-offset-2", ...(testId !== undefined && { "data-testid": `${testId}-download` }), children: t("kumiko.field.file-list.download") }), meta?.mimeType === PDF_MIME_TYPE && (_jsx(Button, { type: "button", variant: "link", size: "sm", "aria-expanded": previewOpen, onClick: () => setPreviewOpen((open) => !open), ...(testId !== undefined && { "data-testid": `${testId}-preview-toggle` }), children: t(previewOpen ? "kumiko.field.file-list.hidePreview" : "kumiko.field.file-list.preview") }))] }), previewOpen && meta?.mimeType === PDF_MIME_TYPE && (_jsx(PdfPreview, { source: { url: downloadUrl }, testId: `file-preview-${fileId}` }))] }));
17
17
  }
18
- export function FileValueList({ fileIds, testId }) {
18
+ export function FileValueList({ fileIds, previewOpenByDefault = false, testId, }) {
19
19
  if (fileIds.length === 0)
20
20
  return null;
21
- return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId) => (_jsx(FileValueRow, { fileId: fileId, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
21
+ return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId, index) => (_jsx(FileValueRow, { fileId: fileId, previewOpenInitially: previewOpenByDefault && index === fileIds.length - 1, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
22
22
  }
@@ -0,0 +1,25 @@
1
+ import type { CSSProperties, ReactNode } from "react";
2
+ import type { useTranslation } from "../../index.ts";
3
+ type Translate = ReturnType<typeof useTranslation>;
4
+ export type FormFooterModel = {
5
+ readonly t: Translate;
6
+ readonly testId: string | undefined;
7
+ readonly chromeless: boolean;
8
+ readonly stickyActions: boolean | undefined;
9
+ readonly fillHeight: boolean | undefined;
10
+ readonly unsavedCount: number | undefined;
11
+ readonly railed: boolean;
12
+ readonly pinned: boolean;
13
+ readonly secondaryActions: ReactNode;
14
+ readonly renderedSecondary: ReactNode;
15
+ readonly renderedActions: ReactNode;
16
+ readonly hasNonPrimaryOverflow: boolean;
17
+ readonly fixedFooterStyle: CSSProperties | undefined;
18
+ };
19
+ export declare function unsavedStatusText(t: Translate, unsavedCount: number): string;
20
+ export declare function renderNarrowPinnedFooter(model: Pick<FormFooterModel, "t" | "testId" | "unsavedCount">, nodes: readonly ReactNode[], keyboardInset: number): ReactNode;
21
+ export declare function footerRowClassName(model: FormFooterModel): string;
22
+ export declare function renderUnsavedStatus(model: FormFooterModel, unsavedCount: number): ReactNode;
23
+ export declare function renderSecondaryActionsGroup(model: FormFooterModel): ReactNode;
24
+ export declare function renderPrimaryActionsGroup(model: FormFooterModel): ReactNode;
25
+ export {};
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../lib/cn.js";
3
+ import { cardFooterBorder } from "./card-styles.js";
4
+ import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
5
+ import { STICKY_FOOTER_SAFE_AREA_CLASS } from "./sticky-action-bar.js";
6
+ export function unsavedStatusText(t, unsavedCount) {
7
+ if (unsavedCount === 0)
8
+ return t("kumiko.form.noUnsavedChanges");
9
+ if (unsavedCount === 1)
10
+ return t("kumiko.form.unsaved.one");
11
+ return t("kumiko.form.unsaved.other", { count: unsavedCount });
12
+ }
13
+ export function renderNarrowPinnedFooter(model, nodes, keyboardInset) {
14
+ const { t, testId, unsavedCount } = model;
15
+ return (_jsx(NarrowPinnedFooter, { nodes: nodes, overflowLabel: unsavedCount !== undefined && unsavedCount > 0
16
+ ? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
17
+ : t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
18
+ ? unsavedStatusText(t, unsavedCount)
19
+ : undefined, testId: testId, keyboardInset: keyboardInset }));
20
+ }
21
+ export function footerRowClassName(model) {
22
+ const { pinned, chromeless, fillHeight, railed } = model;
23
+ return cn(pinned
24
+ ? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
25
+ : "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0");
26
+ }
27
+ export function renderUnsavedStatus(model, unsavedCount) {
28
+ const { t, testId, pinned } = model;
29
+ return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
30
+ // Only the stacked (flex-col-reverse) footer moves the status above the
31
+ // actions; the pinned single-row footer keeps it on the left.
32
+ !pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] }));
33
+ }
34
+ export function renderSecondaryActionsGroup(model) {
35
+ const { testId, pinned, hasNonPrimaryOverflow, renderedSecondary } = model;
36
+ return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
37
+ // min-h-11: link-variant buttons (Copy link) are h-auto and would fall
38
+ // below the 24px touch-target minimum on narrow viewports.
39
+ "flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary }));
40
+ }
41
+ export function renderPrimaryActionsGroup(model) {
42
+ const { testId, pinned, stickyActions, fixedFooterStyle, renderedActions } = model;
43
+ return (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
44
+ ? "shrink-0 max-md:[&>button]:min-h-11"
45
+ : "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
46
+ // Below sm (640px): pin only the primary action to the viewport
47
+ // bottom instead of normal flow, so a virtual keyboard shrinking
48
+ // the viewport can't push it out of reach. `fixed`
49
+ // escapes the card's `overflow-hidden` (only transform/filter/
50
+ // contain ancestors trap it, confirmed against
51
+ // AppLayout/SidebarInset — neither sets those).
52
+ stickyActions === true &&
53
+ !pinned &&
54
+ cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }));
55
+ }
@@ -2,13 +2,13 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Children, Fragment, isValidElement, useContext, useEffect, useRef, useState, } from "react";
3
3
  import { STICKY_PRIMARY_ACTION_PROP, useTranslation, } from "../../index.js";
4
4
  import { cn } from "../lib/cn.js";
5
+ import { footerRowClassName, renderNarrowPinnedFooter, renderPrimaryActionsGroup, renderSecondaryActionsGroup, renderUnsavedStatus, } from "./form-footer-parts.js";
5
6
  import { screenPaddingClassName, screenWidthClassName } from "./layout.js";
6
- import { NarrowPinnedFooter } from "./narrow-pinned-footer.js";
7
- import { STICKY_FOOTER_SAFE_AREA_CLASS, STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
7
+ import { STICKY_FOOTER_SPACER_CLASS } from "./sticky-action-bar.js";
8
8
  import { useKeyboardInset } from "./use-keyboard-inset.js";
9
9
  import { useIsBelowSmViewport } from "./use-narrow-viewport.js";
10
10
  import "../react-css-custom-properties.js";
11
- import { cardFooterBorder, cardHeaderBorder } from "./card-styles.js";
11
+ import { cardHeaderBorder } from "./card-styles.js";
12
12
  import { InsideFormContext, ScreenFormContext, ScreenWidthContext } from "./contexts.js";
13
13
  // Extension sections that render their own <form> via BareFormProvider
14
14
  // (legacy custom-form pattern, e.g. ChangeEmailSection) land
@@ -138,21 +138,7 @@ function flattenActionNodes(node) {
138
138
  }
139
139
  return Children.toArray(node);
140
140
  }
141
- function unsavedStatusText(t, unsavedCount) {
142
- if (unsavedCount === 0)
143
- return t("kumiko.form.noUnsavedChanges");
144
- if (unsavedCount === 1)
145
- return t("kumiko.form.unsaved.one");
146
- return t("kumiko.form.unsaved.other", { count: unsavedCount });
147
- }
148
- // Footer wrapper for DefaultForm's card and chromeless layouts alike — only
149
- // the card-derived horizontal padding/border differs between them.
150
- export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
151
- const t = useTranslation();
152
- const isBelowSm = useIsBelowSmViewport();
153
- const keyboardInset = useKeyboardInset();
154
- if (actions === undefined && secondaryActions === undefined)
155
- return null;
141
+ function splitStickyActions(stickyActions, actions, secondaryActions) {
156
142
  // Only split when sticky: the non-sticky (regular, non-wizard) footer must
157
143
  // reproduce the previous DOM exactly (form-action-bar.test.tsx pins
158
144
  // `-actions`/`-actions-secondary` content 1:1 to the `actions`/
@@ -170,6 +156,17 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
170
156
  const renderedActions = stickyActions === true ? (hasPrimaryAction ? primaryActionNodes : actions) : actions;
171
157
  const hasNonPrimaryOverflow = nonPrimaryActionNodes !== undefined && nonPrimaryActionNodes.length > 0;
172
158
  const renderedSecondary = stickyActions === true && hasPrimaryAction ? (_jsxs(_Fragment, { children: [secondaryActions, nonPrimaryActionNodes] })) : (secondaryActions);
159
+ return { renderedActions, renderedSecondary, hasNonPrimaryOverflow };
160
+ }
161
+ // Footer wrapper for DefaultForm's card and chromeless layouts alike — only
162
+ // the card-derived horizontal padding/border differs between them.
163
+ export function FormFooter({ actions, secondaryActions, testId, chromeless, stickyActions, fillHeight, unsavedCount, railed = false, }) {
164
+ const t = useTranslation();
165
+ const isBelowSm = useIsBelowSmViewport();
166
+ const keyboardInset = useKeyboardInset();
167
+ if (actions === undefined && secondaryActions === undefined)
168
+ return null;
169
+ const { renderedActions, renderedSecondary, hasNonPrimaryOverflow } = splitStickyActions(stickyActions, actions, secondaryActions);
173
170
  const pinned = isPinnedFooter(stickyActions, fillHeight);
174
171
  const fixedFooterStyle = stickyActions === true && !pinned && isBelowSm && keyboardInset > 0
175
172
  ? { bottom: keyboardInset }
@@ -177,32 +174,25 @@ export function FormFooter({ actions, secondaryActions, testId, chromeless, stic
177
174
  // Below sm the pinned footer swaps to NarrowPinnedFooter; the subtree remounts, so a
178
175
  // confirm dialog owned by an action button closes on a breakpoint change (rotate, resize).
179
176
  if (pinned && isBelowSm) {
180
- return (_jsx(NarrowPinnedFooter, { nodes: [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], overflowLabel: unsavedCount !== undefined && unsavedCount > 0
181
- ? `${t("kumiko.list.row-actions.more")}, ${unsavedStatusText(t, unsavedCount)}`
182
- : t("kumiko.list.row-actions.more"), unsavedCount: unsavedCount, unsavedStatus: unsavedCount !== undefined && unsavedCount > 0
183
- ? unsavedStatusText(t, unsavedCount)
184
- : undefined, testId: testId, keyboardInset: keyboardInset }));
177
+ return renderNarrowPinnedFooter({ t, testId, unsavedCount }, [...flattenActionNodes(secondaryActions), ...flattenActionNodes(actions)], keyboardInset);
185
178
  }
186
- return (_jsxs("div", { className: cn(pinned
187
- ? cn("flex h-14 shrink-0 items-center justify-end gap-2 border-t border-border bg-card px-6", !railed && "md:pl-10")
188
- : "flex flex-col-reverse gap-3 py-3 sm:flex-row sm:items-center sm:justify-between sm:py-4", !pinned && !chromeless && "px-[var(--card-padding)]", !pinned && !chromeless && cardFooterBorder, !pinned && fillHeight === true && "shrink-0"), children: [unsavedCount !== undefined && (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-unsaved` : undefined, className: cn("mr-auto flex min-w-0 items-center gap-2 text-[13px]",
189
- // Only the stacked (flex-col-reverse) footer moves the status above the
190
- // actions; the pinned single-row footer keeps it on the left.
191
- !pinned && "max-sm:order-last", unsavedCount > 0 ? "text-foreground-secondary" : "text-muted-foreground"), children: [unsavedCount > 0 && (_jsx("span", { className: "size-1.5 shrink-0 rounded-full bg-status-active", "aria-hidden": "true" })), _jsx("span", { className: "truncate", children: unsavedStatusText(t, unsavedCount) })] })), (secondaryActions !== undefined || hasNonPrimaryOverflow) && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions-secondary` : undefined, className: cn(
192
- // min-h-11: link-variant buttons (Copy link) are h-auto and would fall
193
- // below the 24px touch-target minimum on narrow viewports.
194
- "flex flex-wrap items-center gap-2 max-sm:[&_button]:min-h-11 max-sm:[&_button]:px-2 max-sm:[&_button]:text-xs", pinned && hasNonPrimaryOverflow && "mr-auto"), children: renderedSecondary })), renderedActions !== undefined && (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-actions` : undefined, style: fixedFooterStyle, className: cn("flex items-center gap-2", pinned
195
- ? "shrink-0 max-md:[&>button]:min-h-11"
196
- : "flex-wrap max-sm:w-full max-sm:[&>button]:flex-1 max-sm:[&>button]:min-h-11 sm:ml-auto",
197
- // Below sm (640px): pin only the primary action to the viewport
198
- // bottom instead of normal flow, so a virtual keyboard shrinking
199
- // the viewport can't push it out of reach. `fixed`
200
- // escapes the card's `overflow-hidden` (only transform/filter/
201
- // contain ancestors trap it, confirmed against
202
- // AppLayout/SidebarInset — neither sets those).
203
- stickyActions === true &&
204
- !pinned &&
205
- cn("max-sm:fixed max-sm:inset-x-0 max-sm:bottom-0 max-sm:z-20 max-sm:bg-background max-sm:p-4 max-sm:shadow-[0_-4px_12px_-4px_rgb(0_0_0_/_0.15)]", STICKY_FOOTER_SAFE_AREA_CLASS)), children: renderedActions }))] }));
179
+ const model = {
180
+ t,
181
+ testId,
182
+ chromeless,
183
+ stickyActions,
184
+ fillHeight,
185
+ unsavedCount,
186
+ railed,
187
+ pinned,
188
+ secondaryActions,
189
+ renderedSecondary,
190
+ renderedActions,
191
+ hasNonPrimaryOverflow,
192
+ fixedFooterStyle,
193
+ };
194
+ return (_jsxs("div", { className: footerRowClassName(model), children: [unsavedCount !== undefined && renderUnsavedStatus(model, unsavedCount), (secondaryActions !== undefined || hasNonPrimaryOverflow) &&
195
+ renderSecondaryActionsGroup(model), renderedActions !== undefined && renderPrimaryActionsGroup(model)] }));
206
196
  }
207
197
  export const SECTION_NAV_MIN_ITEMS = 3;
208
198
  // "On this page" navigation next to a screen form. The active entry follows
@@ -0,0 +1,4 @@
1
+ import type { KindRenderer } from "./input-shared.ts";
2
+ export declare const renderBooleanKind: KindRenderer<"boolean">;
3
+ export declare const renderSelectKind: KindRenderer<"select">;
4
+ export declare const renderComboboxKind: KindRenderer<"combobox">;