@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
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useLocale, useNav, usePrimitives, useQuery, useTranslation } from "../../index.js";
3
+ import { ModeSwitch } from "../widgets/mode-switch.js";
4
+ // Value lives in the URL under `filter.id` (like useListUrlState's
5
+ // `<screenId>.page`, same replaceState) so a `navigate` rowAction with
6
+ // `params` can deep-link onto this filter.
7
+ export function useFilterParams(screen, enabled) {
8
+ const { Field, Input } = usePrimitives();
9
+ const t = useTranslation();
10
+ const nav = useNav();
11
+ const filter = screen.filter;
12
+ const value = filter !== undefined ? (nav.searchParams[filter.id] ?? "") : "";
13
+ const optionsQueryResult = useQuery(filter?.optionsQuery ?? "", {}, { enabled: enabled && filter?.optionsQuery !== undefined });
14
+ if (filter === undefined) {
15
+ return { params: {}, picker: null };
16
+ }
17
+ const dynamicOptions = filter.optionsQuery !== undefined
18
+ ? (optionsQueryResult.data?.rows ?? [])
19
+ : (filter.options ?? []).map((o) => ({ value: o.value, label: t(o.label) }));
20
+ const allLabel = t(filter.allLabel ?? "kumiko.dashboard.filter.all");
21
+ const options = [{ value: "", label: allLabel }, ...dynamicOptions];
22
+ const picker = (_jsx("div", { className: "max-w-xs", children: _jsx(Field, { id: `dashboard-filter-${filter.id}`, label: t(filter.label), children: _jsx(Input, { kind: "combobox", id: `dashboard-filter-${filter.id}`, name: filter.id, options: options, value: value, onChange: (next) => nav.setSearchParams({ [filter.id]: next === "" ? null : next }), placeholder: filter.placeholder !== undefined ? t(filter.placeholder) : allLabel }) }) }));
23
+ const params = value === "" ? {} : { [filter.id]: value };
24
+ return { params, picker };
25
+ }
26
+ // An unknown/stale URL value falls back to the declared default.
27
+ export function useTimeRange(screen, translate) {
28
+ const t = useTranslation();
29
+ const nav = useNav();
30
+ const resolver = useLocale();
31
+ const timeRange = screen.timeRange;
32
+ if (timeRange === undefined)
33
+ return { params: {}, control: null };
34
+ const fromUrl = nav.searchParams[timeRange.id];
35
+ const value = timeRange.options.find((o) => o.value === fromUrl)?.value ?? timeRange.default;
36
+ return {
37
+ // Metric buckets are cut server-side; without the user's zone they default
38
+ // to UTC while the axis labels are formatted in the user's zone.
39
+ params: { [timeRange.id]: value, timeZone: resolver.timeZone() },
40
+ control: (_jsx(ModeSwitch, { value: value, options: timeRange.options.map((o) => ({ value: o.value, label: translate(o.label) })), onChange: (next) => nav.setSearchParams({ [timeRange.id]: next }), ariaLabel: t("kumiko.dashboard.time-range"), testId: `dashboard-time-range-${timeRange.id}` })),
41
+ };
42
+ }
@@ -0,0 +1,8 @@
1
+ import type { DashboardScreenPanel } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { type ReactNode } from "react";
3
+ import type { Translate } from "../../headless/index.ts";
4
+ export declare function ScreenPanelTile({ panel, featureName, translate, }: {
5
+ readonly panel: DashboardScreenPanel;
6
+ readonly featureName: string;
7
+ readonly translate: Translate;
8
+ }): ReactNode;
@@ -0,0 +1,42 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext } from "react";
3
+ import { EmbeddedScreenProvider, evalVisibleWhen, KumikoScreen, useEmbeddedScreen, } from "../../index.js";
4
+ import { cn } from "../lib/cn.js";
5
+ import { EmbeddedFormProvider } from "../primitives/index.js";
6
+ import { SectionCard } from "../widgets/section-card.js";
7
+ import { PanelError, WIDE_PANEL } from "./dashboard-shared.js";
8
+ import { NO_GATE_PAYLOAD, VisibilityQueryContext, visibilityGate, } from "./dashboard-visibility.js";
9
+ function EmbeddedScreenPanelTile({ panel, target, translate, }) {
10
+ const screenBody = _jsx(KumikoScreen, { schema: target.schema, qn: target.qn, translate: translate });
11
+ const embedded = panel.chromeless === true ? (_jsx(EmbeddedFormProvider, { children: _jsx(EmbeddedScreenProvider, { children: screenBody }) })) : (screenBody);
12
+ return (_jsx("div", { className: cn("min-w-0", WIDE_PANEL), "data-testid": `dashboard-panel-${panel.id}`, children: panel.label !== undefined && panel.chromeless !== true ? (_jsx(SectionCard, { title: translate(panel.label), children: embedded })) : (_jsxs(_Fragment, { children: [panel.label !== undefined && (_jsx("h3", { className: "mb-3 text-sm font-semibold text-foreground", children: translate(panel.label) })), embedded] })) }));
13
+ }
14
+ // Own component so the target lookup (a hook) only runs for panels that actually fall back.
15
+ function FallbackScreenPanelTile({ panel, fallback, featureName, translate, }) {
16
+ const target = useEmbeddedScreen(featureName, fallback);
17
+ if (target === undefined)
18
+ return null;
19
+ return _jsx(EmbeddedScreenPanelTile, { panel: panel, target: target, translate: translate });
20
+ }
21
+ export function ScreenPanelTile({ panel, featureName, translate, }) {
22
+ const visibleWhen = panel.visibleWhen;
23
+ const visibility = useContext(VisibilityQueryContext).get(visibleWhen !== undefined ? visibilityGate(visibleWhen.query, NO_GATE_PAYLOAD).key : "");
24
+ const target = useEmbeddedScreen(featureName, panel.screen);
25
+ const verdict = visibleWhen === undefined ? "visible" : evalVisibleWhen(visibleWhen, visibility);
26
+ // Nothing while the gate loads: a fallback that flashes before the real panel flickers.
27
+ if (verdict === "loading")
28
+ return null;
29
+ if (verdict !== "visible" && panel.fallback !== undefined) {
30
+ return (_jsx(FallbackScreenPanelTile, { panel: panel, fallback: panel.fallback, featureName: featureName, translate: translate }));
31
+ }
32
+ if (target === undefined)
33
+ return null;
34
+ // A failed gate query must not silently drop the panel: on the
35
+ // account-security page that would hide every MFA enable/disable path.
36
+ if (verdict === "error" && visibility?.error) {
37
+ return (_jsx("div", { className: cn("min-w-0", WIDE_PANEL), "data-testid": `dashboard-panel-${panel.id}`, children: _jsx(PanelError, { label: panel.label !== undefined ? translate(panel.label) : panel.id, error: visibility.error, onRetry: () => void visibility.refetch() }) }));
38
+ }
39
+ if (verdict !== "visible")
40
+ return null;
41
+ return _jsx(EmbeddedScreenPanelTile, { panel: panel, target: target, translate: translate });
42
+ }
@@ -0,0 +1,42 @@
1
+ import type { DashboardPanelDefinition, DashboardScreenPanel } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ import { type ReactNode } from "react";
3
+ import { type UseQueryResult } from "../../index.ts";
4
+ import { type ScreenParams } from "./dashboard-shared.tsx";
5
+ type VisibilityQueryResult = UseQueryResult<Readonly<Record<string, unknown>>>;
6
+ export type VisibilityQueryResults = ReadonlyMap<string, VisibilityQueryResult>;
7
+ export declare const VisibilityQueryContext: import("react").Context<VisibilityQueryResults>;
8
+ export type VisibilityGate = {
9
+ readonly key: string;
10
+ readonly query: string;
11
+ readonly payload: Readonly<Record<string, unknown>>;
12
+ };
13
+ export declare const NO_GATE_PAYLOAD: Readonly<Record<string, unknown>>;
14
+ export declare function visibilityGate(query: string, payload: Readonly<Record<string, unknown>>): VisibilityGate;
15
+ export declare function VisibilityGateQueries({ gates, onResult, onForget, }: {
16
+ readonly gates: readonly VisibilityGate[];
17
+ readonly onResult: (key: string, result: VisibilityQueryResult) => void;
18
+ readonly onForget: (key: string) => void;
19
+ }): ReactNode;
20
+ export declare function useVisibilityGateResults(): {
21
+ readonly results: VisibilityQueryResults;
22
+ readonly report: (key: string, result: VisibilityQueryResult) => void;
23
+ readonly forget: (key: string) => void;
24
+ };
25
+ export declare function distinctVisibilityGates(panels: readonly DashboardPanelDefinition[], screenParams: ScreenParams): VisibilityGate[];
26
+ export declare function GatedGridCell({ panel, screenParams, label, className, children, }: {
27
+ readonly panel: Exclude<DashboardPanelDefinition, DashboardScreenPanel>;
28
+ readonly screenParams: ScreenParams;
29
+ readonly label: string;
30
+ readonly className: string | undefined;
31
+ readonly children: ReactNode;
32
+ }): ReactNode;
33
+ export declare function useDashboardVisibility(panels: readonly DashboardPanelDefinition[], screenParams: ScreenParams): {
34
+ visibilityGates: VisibilityGate[];
35
+ gateResults: {
36
+ readonly results: VisibilityQueryResults;
37
+ readonly report: (key: string, result: VisibilityQueryResult) => void;
38
+ readonly forget: (key: string) => void;
39
+ };
40
+ gateResultsWithPending: VisibilityQueryResults;
41
+ };
42
+ export {};
@@ -0,0 +1,102 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
3
+ import { evalVisibleWhen } from "../../index.js";
4
+ import { cn } from "../lib/cn.js";
5
+ import { PanelError, useDashboardQuery } from "./dashboard-shared.js";
6
+ export const VisibilityQueryContext = createContext(new Map());
7
+ export const NO_GATE_PAYLOAD = {};
8
+ export function visibilityGate(query, payload) {
9
+ return { key: `${query}|${JSON.stringify(payload)}`, query, payload };
10
+ }
11
+ // Embedded screens carry no screen filter; in-grid panels pass the filter
12
+ // (unless ignoreScreenFilter, never for a stat-group) and the time range.
13
+ function panelVisibilityGate(panel, screenParams) {
14
+ if (panel.kind === "custom" || panel.visibleWhen === undefined)
15
+ return undefined;
16
+ if (panel.kind === "screen")
17
+ return visibilityGate(panel.visibleWhen.query, NO_GATE_PAYLOAD);
18
+ const dropsFilter = panel.kind !== "stat-group" && panel.kind !== "text" && panel.ignoreScreenFilter === true;
19
+ return visibilityGate(panel.visibleWhen.query, {
20
+ ...(dropsFilter ? {} : screenParams.filterParams),
21
+ ...screenParams.rangeParams,
22
+ ...screenParams.recordParams,
23
+ });
24
+ }
25
+ // One live query per distinct gate (query + payload), shared by every panel
26
+ // that gates on it — N panels on the same status query would otherwise each
27
+ // open their own request, SSE subscription and refetch per event. The queries
28
+ // run as keyed siblings of the grid so a changing gate count never changes the
29
+ // grid's position in the tree (which would remount every panel).
30
+ function VisibilityGateQuery({ gate, onResult, onForget, }) {
31
+ const { data, error, loading, refetch } = useDashboardQuery(gate.query, gate.payload);
32
+ const { key } = gate;
33
+ useEffect(() => {
34
+ onResult(key, { data, error, loading, refetch });
35
+ }, [key, data, error, loading, refetch, onResult]);
36
+ useEffect(() => () => onForget(key), [key, onForget]);
37
+ return null;
38
+ }
39
+ export function VisibilityGateQueries({ gates, onResult, onForget, }) {
40
+ return gates.map((gate) => (_jsx(VisibilityGateQuery, { gate: gate, onResult: onResult, onForget: onForget }, gate.key)));
41
+ }
42
+ export function useVisibilityGateResults() {
43
+ const [results, setResults] = useState(new Map());
44
+ const report = useCallback((key, result) => {
45
+ setResults((prev) => new Map(prev).set(key, result));
46
+ }, []);
47
+ const forget = useCallback((key) => {
48
+ setResults((prev) => {
49
+ if (!prev.has(key))
50
+ return prev;
51
+ const next = new Map(prev);
52
+ next.delete(key);
53
+ return next;
54
+ });
55
+ }, []);
56
+ return { results, report, forget };
57
+ }
58
+ const NOOP_REFETCH = async () => { };
59
+ // A gate reports through an effect, so for its first commit (and after a payload change) no
60
+ // result exists yet; without a placeholder that reads as "hidden" and flashes the fallback.
61
+ function withPendingGates(results, gateKeys) {
62
+ if (gateKeys.every((key) => results.has(key)))
63
+ return results;
64
+ const merged = new Map(results);
65
+ for (const key of gateKeys) {
66
+ if (!merged.has(key)) {
67
+ merged.set(key, { data: null, error: null, loading: true, refetch: NOOP_REFETCH });
68
+ }
69
+ }
70
+ return merged;
71
+ }
72
+ export function distinctVisibilityGates(panels, screenParams) {
73
+ const gates = new Map();
74
+ for (const panel of panels) {
75
+ const gate = panelVisibilityGate(panel, screenParams);
76
+ if (gate !== undefined)
77
+ gates.set(gate.key, gate);
78
+ }
79
+ return [...gates.values()];
80
+ }
81
+ export function GatedGridCell({ panel, screenParams, label, className, children, }) {
82
+ const gate = panelVisibilityGate(panel, screenParams);
83
+ const visibility = useContext(VisibilityQueryContext).get(gate?.key ?? "");
84
+ const visibleWhen = panel.kind === "custom" ? undefined : panel.visibleWhen;
85
+ if (visibleWhen !== undefined) {
86
+ const verdict = evalVisibleWhen(visibleWhen, visibility);
87
+ if (verdict === "error" && visibility?.error) {
88
+ return (_jsx("div", { className: cn("min-w-0", className), "data-testid": `dashboard-panel-${panel.id}`, children: _jsx(PanelError, { label: label, error: visibility.error, onRetry: () => void visibility.refetch() }) }));
89
+ }
90
+ // Loading renders nothing too: a skeleton that then vanishes flickers.
91
+ if (verdict !== "visible")
92
+ return null;
93
+ }
94
+ return _jsx("div", { className: cn("min-w-0", className), children: children });
95
+ }
96
+ export function useDashboardVisibility(panels, screenParams) {
97
+ const visibilityGates = distinctVisibilityGates(panels, screenParams);
98
+ const gateResults = useVisibilityGateResults();
99
+ const gateKeysSignature = visibilityGates.map((gate) => gate.key).join("\n");
100
+ const gateResultsWithPending = useMemo(() => withPendingGates(gateResults.results, gateKeysSignature === "" ? [] : gateKeysSignature.split("\n")), [gateResults.results, gateKeysSignature]);
101
+ return { visibilityGates, gateResults, gateResultsWithPending };
102
+ }
@@ -0,0 +1,6 @@
1
+ export declare function committedUrl(): string;
2
+ /** Adopts the entry the browser currently shows as the committed one. */
3
+ export declare function adoptCurrentEntry(): void;
4
+ export declare function pushEntry(url: string): void;
5
+ export declare function replaceEntry(url: string): void;
6
+ export declare function handlePopstate(notify: () => void): void;
@@ -0,0 +1,115 @@
1
+ import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
2
+ import { hasUnsavedInput, requestLeave } from "../../index.js";
3
+ // Back/Forward cannot be vetoed, so the browser has already moved by the time the guard asks.
4
+ // Each entry we create carries its position in history.state; with it a declined move is
5
+ // undone with history.go(-delta) and a confirmed one replayed with history.go(delta), so the
6
+ // stack keeps its shape (no duplicated or truncated entries).
7
+ const INDEX_KEY = "kumikoNavIndex";
8
+ const committed = { url: "", index: 0 };
9
+ let leaveRequestOpen = false;
10
+ // Each history.go() we issue produces one popstate that must not be treated as user navigation.
11
+ const pendingMoves = [];
12
+ // Entries without a position (written by other code) cannot be undone relatively.
13
+ let fallbackTargetUrl;
14
+ function readUrl() {
15
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
16
+ }
17
+ function stateAt(index) {
18
+ return { [INDEX_KEY]: index };
19
+ }
20
+ function readIndex() {
21
+ const value = propertyOf(window.history.state, INDEX_KEY);
22
+ return typeof value === "number" ? value : undefined;
23
+ }
24
+ export function committedUrl() {
25
+ return committed.url;
26
+ }
27
+ /** Adopts the entry the browser currently shows as the committed one. */
28
+ export function adoptCurrentEntry() {
29
+ const index = readIndex();
30
+ committed.url = readUrl();
31
+ if (index === undefined) {
32
+ window.history.replaceState(stateAt(committed.index), "", committed.url);
33
+ }
34
+ else {
35
+ committed.index = index;
36
+ }
37
+ }
38
+ function commitCurrentUrl() {
39
+ committed.url = readUrl();
40
+ committed.index = readIndex() ?? committed.index;
41
+ }
42
+ export function pushEntry(url) {
43
+ window.history.pushState(stateAt(committed.index + 1), "", url);
44
+ committed.index += 1;
45
+ committed.url = readUrl();
46
+ }
47
+ export function replaceEntry(url) {
48
+ window.history.replaceState(stateAt(committed.index), "", url);
49
+ commitCurrentUrl();
50
+ }
51
+ export function handlePopstate(notify) {
52
+ const move = pendingMoves.shift();
53
+ if (move !== undefined) {
54
+ if (move === "replay") {
55
+ commitCurrentUrl();
56
+ notify();
57
+ }
58
+ // skip: this popstate is the echo of our own history.go()
59
+ return;
60
+ }
61
+ if (fallbackTargetUrl !== undefined) {
62
+ const target = fallbackTargetUrl;
63
+ fallbackTargetUrl = undefined;
64
+ // After a forward move the entry below our restore-push is not the target.
65
+ if (readUrl() !== target)
66
+ window.history.replaceState(stateAt(committed.index), "", target);
67
+ commitCurrentUrl();
68
+ notify();
69
+ // skip: the pending fallback move is settled
70
+ return;
71
+ }
72
+ const newIndex = readIndex();
73
+ const delta = newIndex === undefined ? 0 : newIndex - committed.index;
74
+ if (leaveRequestOpen) {
75
+ // Another Back/Forward while the dialog is open must neither stack an entry nor replace the
76
+ // open request: step back to the entry the dialog is about.
77
+ if (delta !== 0) {
78
+ pendingMoves.push("undo");
79
+ window.history.go(-delta);
80
+ }
81
+ else {
82
+ window.history.replaceState(stateAt(committed.index), "", committed.url);
83
+ }
84
+ // skip: the open leave dialog keeps the decision
85
+ return;
86
+ }
87
+ if (!hasUnsavedInput()) {
88
+ commitCurrentUrl();
89
+ notify();
90
+ // skip: nothing unsaved, the move stands
91
+ return;
92
+ }
93
+ const targetUrl = readUrl();
94
+ leaveRequestOpen = true;
95
+ const settle = () => {
96
+ leaveRequestOpen = false;
97
+ };
98
+ if (delta !== 0) {
99
+ pendingMoves.push("undo");
100
+ window.history.go(-delta);
101
+ requestLeave(() => {
102
+ settle();
103
+ pendingMoves.push("replay");
104
+ window.history.go(delta);
105
+ }, settle);
106
+ // skip: the leave request owns the rest of the move
107
+ return;
108
+ }
109
+ window.history.pushState(stateAt(committed.index), "", committed.url);
110
+ requestLeave(() => {
111
+ settle();
112
+ fallbackTargetUrl = targetUrl;
113
+ window.history.back();
114
+ }, settle);
115
+ }
@@ -0,0 +1,6 @@
1
+ import { type ReactNode } from "react";
2
+ /** Provides the tab-scoped list-state store and drops state remembered under
3
+ * another user or tenant. Mount below the auth gates so the user is known. */
4
+ export declare function ListStateStoreBoot({ children }: {
5
+ readonly children: ReactNode;
6
+ }): ReactNode;
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useMemo } from "react";
3
+ import { ListStateStoreProvider } from "../../components/list-state-store.js";
4
+ import { useOptionalCurrentUser } from "../../current-user.js";
5
+ import { resolveListStateStore } from "./session-list-state-store.js";
6
+ /** Provides the tab-scoped list-state store and drops state remembered under
7
+ * another user or tenant. Mount below the auth gates so the user is known. */
8
+ export function ListStateStoreBoot({ children }) {
9
+ const user = useOptionalCurrentUser();
10
+ const userId = user?.id;
11
+ const tenantId = user?.tenantId;
12
+ // Must run before the first list renders; an effect would let it read the previous identity's state.
13
+ const store = useMemo(() => resolveListStateStore(userId === undefined ? undefined : { userId, tenantId }), [userId, tenantId]);
14
+ return _jsx(ListStateStoreProvider, { store: store, children: children });
15
+ }
@@ -12,7 +12,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  // notification triggers (our pushPath and the popstate event), so this is not
13
13
  // generalizable.
14
14
  import { useCallback, useMemo, useSyncExternalStore, } from "react";
15
+ import { normalizeBasePath, prependBasePath, stripBasePath } from "../../app/base-path.js";
15
16
  import { formatPath, hasUnsavedInput, parsePath, requestLeave, resolveTarget, useNav, } from "../../index.js";
17
+ import { adoptCurrentEntry, handlePopstate, pushEntry, replaceEntry, } from "./history-leave-guard.js";
16
18
  // basePath allows the app to be mounted under a URL prefix
17
19
  // (for example, `/admin`). The read path strips the prefix before parsePath;
18
20
  // the write path prepends it before pushState/replaceState/href.
@@ -22,45 +24,11 @@ import { formatPath, hasUnsavedInput, parsePath, requestLeave, resolveTarget, us
22
24
  // returns route=undefined, and the app renders its "outside" state
23
25
  // (Not-Found, marketing layer, server-routing pickup, …). There is no
24
26
  // automatic redirect to the app root—the host app decides.
25
- function normalizeBasePath(raw) {
26
- if (raw === undefined || raw === "" || raw === "/")
27
- return "";
28
- const withLeading = raw.startsWith("/") ? raw : `/${raw}`;
29
- return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
30
- }
31
- // Returns the in-app (basePath-relative) path when `path` is under basePath;
32
- // otherwise returns `undefined`. The segment boundary is strict: "/administrator"
33
- // does not match "/admin"—otherwise a string-level prefix check would produce
34
- // false matches.
35
- function stripBasePath(path, basePath) {
36
- if (basePath === "")
37
- return path;
38
- if (path === basePath)
39
- return "/";
40
- if (path.startsWith(`${basePath}/`))
41
- return path.slice(basePath.length);
42
- return undefined;
43
- }
44
- function prependBasePath(path, basePath) {
45
- if (basePath === "")
46
- return path;
47
- // formatPath always returns an absolute in-app path: "/" or "/screen-id"
48
- // or "/screen-id/entity-id". "/" becomes basePath; otherwise, concatenate.
49
- if (path === "/" || path === "")
50
- return basePath;
51
- return `${basePath}${path}`;
52
- }
53
27
  // pushState does not fire popstate, so we maintain a listener set that
54
28
  // navigate() notifies. Back/Forward navigation goes through a window listener
55
29
  // wired exactly once.
56
30
  const listeners = new Set();
57
31
  let popstateWired = false;
58
- // URL the app currently shows; popstate has already moved the browser to the
59
- // target by the time we can ask, so a declined leave must restore this one.
60
- let committedUrl = "";
61
- function readUrl() {
62
- return `${window.location.pathname}${window.location.search}${window.location.hash}`;
63
- }
64
32
  // Stable identity for the omitted-features case — `?? []` would create a
65
33
  // fresh array every render, busting the useMemo below on every render for
66
34
  // every existing useBrowserNavApi() call site (none of which pass features).
@@ -71,19 +39,7 @@ function ensurePopstateWired() {
71
39
  if (typeof window === "undefined")
72
40
  return;
73
41
  window.addEventListener("popstate", () => {
74
- if (!hasUnsavedInput()) {
75
- committedUrl = readUrl();
76
- for (const l of listeners)
77
- l();
78
- return;
79
- }
80
- const targetUrl = readUrl();
81
- window.history.pushState(null, "", committedUrl);
82
- requestLeave(() => {
83
- // replaceState, not history.back(): the declined entry is already ours
84
- // and a forward navigation would otherwise land on the wrong entry.
85
- window.history.replaceState(null, "", targetUrl);
86
- committedUrl = targetUrl;
42
+ handlePopstate(() => {
87
43
  for (const l of listeners)
88
44
  l();
89
45
  });
@@ -99,7 +55,7 @@ function ensurePopstateWired() {
99
55
  function subscribe(listener) {
100
56
  ensurePopstateWired();
101
57
  if (typeof window !== "undefined" && listeners.size === 0)
102
- committedUrl = readUrl();
58
+ adoptCurrentEntry();
103
59
  listeners.add(listener);
104
60
  return () => {
105
61
  listeners.delete(listener);
@@ -146,8 +102,7 @@ function applySearchParamUpdates(updates) {
146
102
  if (nextSearch === window.location.search)
147
103
  return;
148
104
  const url = `${window.location.pathname}${nextSearch}${window.location.hash}`;
149
- window.history.replaceState(null, "", url);
150
- committedUrl = readUrl();
105
+ replaceEntry(url);
151
106
  for (const l of listeners)
152
107
  l();
153
108
  }
@@ -168,8 +123,7 @@ function pushPath(path, search) {
168
123
  if (unchanged)
169
124
  return;
170
125
  requestLeave(() => {
171
- window.history.pushState(null, "", `${path}${search ?? ""}`);
172
- committedUrl = readUrl();
126
+ pushEntry(`${path}${search ?? ""}`);
173
127
  for (const l of listeners)
174
128
  l();
175
129
  });
@@ -185,8 +139,7 @@ function replacePath(path, search) {
185
139
  const leavesCurrentPage = window.location.pathname !== path ||
186
140
  (search !== undefined && window.location.search !== search);
187
141
  const apply = () => {
188
- window.history.replaceState(null, "", nextUrl);
189
- committedUrl = readUrl();
142
+ replaceEntry(nextUrl);
190
143
  for (const l of listeners)
191
144
  l();
192
145
  };
@@ -0,0 +1,15 @@
1
+ import type { ListStateStore } from "../../components/list-state-store.tsx";
2
+ export type ListStateIdentity = {
3
+ readonly userId: string;
4
+ readonly tenantId?: string | undefined;
5
+ };
6
+ /** Remembered list state for the lifetime of the browser tab, keyed per tenant
7
+ * and user. Every storage failure (blocked, full) degrades to "nothing remembered". */
8
+ export declare function createSessionListStateStore(storage: Storage, identity: ListStateIdentity): ListStateStore;
9
+ /** Forgets all remembered list state and the identity marker. Logout calls it
10
+ * before a page reload, which would otherwise hide the identity change. */
11
+ export declare function clearRememberedListState(): void;
12
+ /** The store for the current identity. State remembered under another identity,
13
+ * or under one that has since gone away, is dropped first. Without an identity
14
+ * (app without auth) only the in-memory store is used and nothing is persisted. */
15
+ export declare function resolveListStateStore(identity: ListStateIdentity | undefined): ListStateStore;
@@ -0,0 +1,117 @@
1
+ import { getDefaultMemoryListStateStore } from "../../components/list-state-store.js";
2
+ import { parseStoredListStateJson } from "../../hooks/list-state-schema.js";
3
+ const KEY_PREFIX = "kumiko.list.";
4
+ const IDENTITY_KEY = `${KEY_PREFIX}identity`;
5
+ const NO_TENANT = "-";
6
+ function identityMarker(identity) {
7
+ return `${identity.tenantId ?? NO_TENANT}.${identity.userId}`;
8
+ }
9
+ function openSessionStorage() {
10
+ try {
11
+ return typeof window === "undefined" ? undefined : window.sessionStorage;
12
+ }
13
+ catch {
14
+ // Access itself throws when storage is blocked (privacy modes, sandboxed frames).
15
+ return undefined;
16
+ }
17
+ }
18
+ function prefixedKeys(storage) {
19
+ return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter((key) => key !== null && key.startsWith(KEY_PREFIX));
20
+ }
21
+ function removeAllRemembered(storage) {
22
+ for (const key of prefixedKeys(storage))
23
+ storage.removeItem(key);
24
+ }
25
+ /** Remembered list state for the lifetime of the browser tab, keyed per tenant
26
+ * and user. Every storage failure (blocked, full) degrades to "nothing remembered". */
27
+ export function createSessionListStateStore(storage, identity) {
28
+ const keyPrefix = `${KEY_PREFIX}${identityMarker(identity)}.`;
29
+ return {
30
+ read: (key) => {
31
+ try {
32
+ const raw = storage.getItem(`${keyPrefix}${key}`);
33
+ return raw === null ? undefined : parseStoredListStateJson(raw);
34
+ }
35
+ catch {
36
+ return undefined;
37
+ }
38
+ },
39
+ write: (key, state) => {
40
+ try {
41
+ storage.setItem(`${keyPrefix}${key}`, JSON.stringify(state));
42
+ }
43
+ catch {
44
+ // Quota or blocked storage: the list keeps working, it just is not remembered.
45
+ }
46
+ },
47
+ remove: (key) => {
48
+ try {
49
+ storage.removeItem(`${keyPrefix}${key}`);
50
+ }
51
+ catch {
52
+ // See write.
53
+ }
54
+ },
55
+ clear: () => {
56
+ try {
57
+ for (const key of prefixedKeys(storage).filter((k) => k !== IDENTITY_KEY)) {
58
+ storage.removeItem(key);
59
+ }
60
+ }
61
+ catch {
62
+ // See write.
63
+ }
64
+ },
65
+ };
66
+ }
67
+ /** Forgets all remembered list state and the identity marker. Logout calls it
68
+ * before a page reload, which would otherwise hide the identity change. */
69
+ export function clearRememberedListState() {
70
+ getDefaultMemoryListStateStore().clear();
71
+ lastMemoryIdentity = undefined;
72
+ const storage = openSessionStorage();
73
+ // skip: no sessionStorage available, memory store already cleared
74
+ if (storage === undefined)
75
+ return;
76
+ try {
77
+ removeAllRemembered(storage);
78
+ }
79
+ catch {
80
+ // See createSessionListStateStore.
81
+ }
82
+ }
83
+ // The in-memory store survives SPA identity changes, so it needs its own marker.
84
+ let lastMemoryIdentity;
85
+ /** The store for the current identity. State remembered under another identity,
86
+ * or under one that has since gone away, is dropped first. Without an identity
87
+ * (app without auth) only the in-memory store is used and nothing is persisted. */
88
+ export function resolveListStateStore(identity) {
89
+ const storage = openSessionStorage();
90
+ const memory = getDefaultMemoryListStateStore();
91
+ const marker = identity === undefined ? undefined : identityMarker(identity);
92
+ if (lastMemoryIdentity !== marker) {
93
+ if (lastMemoryIdentity !== undefined)
94
+ memory.clear();
95
+ lastMemoryIdentity = marker;
96
+ }
97
+ if (storage === undefined)
98
+ return memory;
99
+ try {
100
+ const storedMarker = storage.getItem(IDENTITY_KEY);
101
+ if (marker === undefined) {
102
+ if (storedMarker !== null) {
103
+ removeAllRemembered(storage);
104
+ memory.clear();
105
+ }
106
+ return memory;
107
+ }
108
+ if (storedMarker !== marker) {
109
+ removeAllRemembered(storage);
110
+ storage.setItem(IDENTITY_KEY, marker);
111
+ }
112
+ }
113
+ catch {
114
+ return memory;
115
+ }
116
+ return identity === undefined ? memory : createSessionListStateStore(storage, identity);
117
+ }