@eifi1/ui-kit 0.51.4 → 0.51.6

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 (759) hide show
  1. package/README.md +5 -5
  2. package/dist/account/account-settings-labels.js.map +1 -1
  3. package/dist/account/account-settings.js +11 -9
  4. package/dist/account/account-settings.js.map +1 -1
  5. package/dist/account/delete-account-setting.js +56 -65
  6. package/dist/account/delete-account-setting.js.map +1 -1
  7. package/dist/account/email-change-setting-labels.js.map +1 -1
  8. package/dist/account/email-change-setting.js.map +1 -1
  9. package/dist/account/passkeys-setting.js.map +1 -1
  10. package/dist/account/sessions-setting.js.map +1 -1
  11. package/dist/admin/admin-action-confirm.js +1 -3
  12. package/dist/admin/admin-action-confirm.js.map +1 -1
  13. package/dist/admin/admin-action-log.js.map +1 -1
  14. package/dist/admin/admin-area-labels.js +4 -1
  15. package/dist/admin/admin-area-labels.js.map +1 -1
  16. package/dist/admin/admin-overview.js +19 -11
  17. package/dist/admin/admin-overview.js.map +1 -1
  18. package/dist/admin/admin-sections.js.map +1 -1
  19. package/dist/admin/admin-tool-links.d.ts +1 -1
  20. package/dist/admin/admin-tool-links.js +7 -1
  21. package/dist/admin/admin-tool-links.js.map +1 -1
  22. package/dist/admin/invitations-panel.js +2 -13
  23. package/dist/admin/invitations-panel.js.map +1 -1
  24. package/dist/admin/mail-test-card.js.map +1 -1
  25. package/dist/admin/reviewer-scope-editor.js +4 -11
  26. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  27. package/dist/admin/role-select.js.map +1 -1
  28. package/dist/admin/user-roster.js +1 -9
  29. package/dist/admin/user-roster.js.map +1 -1
  30. package/dist/auth/accept-invitation.js.map +1 -1
  31. package/dist/auth/auth-errors.js.map +1 -1
  32. package/dist/auth/forgot-password-form.js +23 -31
  33. package/dist/auth/forgot-password-form.js.map +1 -1
  34. package/dist/auth/register-form.js.map +1 -1
  35. package/dist/auth/reset-password-form.js.map +1 -1
  36. package/dist/auth/sign-in-form.js +13 -5
  37. package/dist/auth/sign-in-form.js.map +1 -1
  38. package/dist/auth/verify-email.js +1 -8
  39. package/dist/auth/verify-email.js.map +1 -1
  40. package/dist/billing/billing-banner.js.map +1 -1
  41. package/dist/billing/billing-labels.js.map +1 -1
  42. package/dist/billing/checkout-processing.js.map +1 -1
  43. package/dist/billing/plan-card.js +91 -85
  44. package/dist/billing/plan-card.js.map +1 -1
  45. package/dist/billing/plan-limit-toast.js.map +1 -1
  46. package/dist/billing/plan-limit.js.map +1 -1
  47. package/dist/billing/plan-price.js.map +1 -1
  48. package/dist/billing/plan-switch.js.map +1 -1
  49. package/dist/billing/subscription-actions.js.map +1 -1
  50. package/dist/bin/eifi1-pay-page.mjs +6 -1
  51. package/dist/components/account-chips.js.map +1 -1
  52. package/dist/components/alert-banner.js +1 -5
  53. package/dist/components/alert-banner.js.map +1 -1
  54. package/dist/components/amount-input.js +29 -12
  55. package/dist/components/amount-input.js.map +1 -1
  56. package/dist/components/authed-image.js.map +1 -1
  57. package/dist/components/autocomplete.js.map +1 -1
  58. package/dist/components/bulk-action-bar.js +1 -4
  59. package/dist/components/bulk-action-bar.js.map +1 -1
  60. package/dist/components/button-group.js.map +1 -1
  61. package/dist/components/button-look.js.map +1 -1
  62. package/dist/components/button.js +1 -7
  63. package/dist/components/button.js.map +1 -1
  64. package/dist/components/calculator.js +5 -5
  65. package/dist/components/calculator.js.map +1 -1
  66. package/dist/components/calendar-heatmap.js +4 -10
  67. package/dist/components/calendar-heatmap.js.map +1 -1
  68. package/dist/components/card.js +1 -8
  69. package/dist/components/card.js.map +1 -1
  70. package/dist/components/chart-zoom-core.js +3 -17
  71. package/dist/components/chart-zoom-core.js.map +1 -1
  72. package/dist/components/chart.d.ts +1 -1
  73. package/dist/components/chart.js +4 -22
  74. package/dist/components/chart.js.map +1 -1
  75. package/dist/components/checkbox-group.js +1 -4
  76. package/dist/components/checkbox-group.js.map +1 -1
  77. package/dist/components/chip-input.js +4 -1
  78. package/dist/components/chip-input.js.map +1 -1
  79. package/dist/components/chip.js +10 -5
  80. package/dist/components/chip.js.map +1 -1
  81. package/dist/components/choice-card.js +58 -74
  82. package/dist/components/choice-card.js.map +1 -1
  83. package/dist/components/column-mapper-labels.js.map +1 -1
  84. package/dist/components/column-mapper.js +167 -227
  85. package/dist/components/column-mapper.js.map +1 -1
  86. package/dist/components/combobox-core.js +1 -4
  87. package/dist/components/combobox-core.js.map +1 -1
  88. package/dist/components/combobox.js +1 -4
  89. package/dist/components/combobox.js.map +1 -1
  90. package/dist/components/confirm-dialog.js.map +1 -1
  91. package/dist/components/copy-button.js.map +1 -1
  92. package/dist/components/country-select.d.ts +5 -4
  93. package/dist/components/country-select.js +3 -19
  94. package/dist/components/country-select.js.map +1 -1
  95. package/dist/components/currency-option-list.js +10 -8
  96. package/dist/components/currency-option-list.js.map +1 -1
  97. package/dist/components/currency-select.js +1 -6
  98. package/dist/components/currency-select.js.map +1 -1
  99. package/dist/components/danger-confirm.js.map +1 -1
  100. package/dist/components/data-table-cells.js.map +1 -1
  101. package/dist/components/data-table-column-settings.js.map +1 -1
  102. package/dist/components/data-table-filter-popover.js +2 -18
  103. package/dist/components/data-table-filter-popover.js.map +1 -1
  104. package/dist/components/data-table-filters.js.map +1 -1
  105. package/dist/components/data-table-fit.js.map +1 -1
  106. package/dist/components/data-table-groups.js +3 -1
  107. package/dist/components/data-table-groups.js.map +1 -1
  108. package/dist/components/data-table-head.js +44 -56
  109. package/dist/components/data-table-head.js.map +1 -1
  110. package/dist/components/data-table-mobile.js +31 -44
  111. package/dist/components/data-table-mobile.js.map +1 -1
  112. package/dist/components/data-table-pagination.js.map +1 -1
  113. package/dist/components/data-table-row-actions.js +19 -16
  114. package/dist/components/data-table-row-actions.js.map +1 -1
  115. package/dist/components/data-table-row.js.map +1 -1
  116. package/dist/components/data-table-shared.js.map +1 -1
  117. package/dist/components/data-table-sort.js.map +1 -1
  118. package/dist/components/data-table-virtual.js +4 -1
  119. package/dist/components/data-table-virtual.js.map +1 -1
  120. package/dist/components/data-table.js +30 -39
  121. package/dist/components/data-table.js.map +1 -1
  122. package/dist/components/date-picker.js +1 -6
  123. package/dist/components/date-picker.js.map +1 -1
  124. package/dist/components/date-range-picker.js.map +1 -1
  125. package/dist/components/description-list.js +10 -10
  126. package/dist/components/description-list.js.map +1 -1
  127. package/dist/components/dialog-frame.js +9 -2
  128. package/dist/components/dialog-frame.js.map +1 -1
  129. package/dist/components/disclosure.js +17 -27
  130. package/dist/components/disclosure.js.map +1 -1
  131. package/dist/components/dropdown.js +25 -35
  132. package/dist/components/dropdown.js.map +1 -1
  133. package/dist/components/empty-state.js.map +1 -1
  134. package/dist/components/entity-combobox.js.map +1 -1
  135. package/dist/components/error-boundary.js.map +1 -1
  136. package/dist/components/field-base.js +5 -5
  137. package/dist/components/field-base.js.map +1 -1
  138. package/dist/components/field-parts.d.ts +1 -1
  139. package/dist/components/field-parts.js +10 -14
  140. package/dist/components/field-parts.js.map +1 -1
  141. package/dist/components/field-sync.js +8 -10
  142. package/dist/components/field-sync.js.map +1 -1
  143. package/dist/components/file-button.js.map +1 -1
  144. package/dist/components/file-dropzone.js.map +1 -1
  145. package/dist/components/floating-panel.js +4 -1
  146. package/dist/components/floating-panel.js.map +1 -1
  147. package/dist/components/focus-ring.js.map +1 -1
  148. package/dist/components/form-actions.js.map +1 -1
  149. package/dist/components/grouped-picker.js +6 -8
  150. package/dist/components/grouped-picker.js.map +1 -1
  151. package/dist/components/hover-menu.js +1 -4
  152. package/dist/components/hover-menu.js.map +1 -1
  153. package/dist/components/iban-input-labels.js.map +1 -1
  154. package/dist/components/icon-button.js +4 -1
  155. package/dist/components/icon-button.js.map +1 -1
  156. package/dist/components/icon-picker.js +2 -7
  157. package/dist/components/icon-picker.js.map +1 -1
  158. package/dist/components/image-grid.js +41 -50
  159. package/dist/components/image-grid.js.map +1 -1
  160. package/dist/components/inline-edit-field.js.map +1 -1
  161. package/dist/components/inline-entity-combobox.js +2 -8
  162. package/dist/components/inline-entity-combobox.js.map +1 -1
  163. package/dist/components/input.js +14 -2
  164. package/dist/components/input.js.map +1 -1
  165. package/dist/components/jump-calendar.js +1 -7
  166. package/dist/components/jump-calendar.js.map +1 -1
  167. package/dist/components/legal-labels.js.map +1 -1
  168. package/dist/components/legal-page.js +14 -2
  169. package/dist/components/legal-page.js.map +1 -1
  170. package/dist/components/legal.js +12 -2
  171. package/dist/components/legal.js.map +1 -1
  172. package/dist/components/lightbox.js.map +1 -1
  173. package/dist/components/line-items.js +26 -36
  174. package/dist/components/line-items.js.map +1 -1
  175. package/dist/components/list.js +22 -10
  176. package/dist/components/list.js.map +1 -1
  177. package/dist/components/measured-grid.js +29 -53
  178. package/dist/components/measured-grid.js.map +1 -1
  179. package/dist/components/menu-item.js.map +1 -1
  180. package/dist/components/mini-calendar.js +13 -31
  181. package/dist/components/mini-calendar.js.map +1 -1
  182. package/dist/components/money-field.js.map +1 -1
  183. package/dist/components/month-picker.js +10 -21
  184. package/dist/components/month-picker.js.map +1 -1
  185. package/dist/components/multi-entity-combobox.js +3 -16
  186. package/dist/components/multi-entity-combobox.js.map +1 -1
  187. package/dist/components/multi-select.js.map +1 -1
  188. package/dist/components/nav-pills.js +10 -1
  189. package/dist/components/nav-pills.js.map +1 -1
  190. package/dist/components/number-field.js.map +1 -1
  191. package/dist/components/number-input.js +1 -7
  192. package/dist/components/number-input.js.map +1 -1
  193. package/dist/components/numpad-sheet.js +1 -4
  194. package/dist/components/numpad-sheet.js.map +1 -1
  195. package/dist/components/one-time-code-input.js +1 -7
  196. package/dist/components/one-time-code-input.js.map +1 -1
  197. package/dist/components/page-contents.js +13 -22
  198. package/dist/components/page-contents.js.map +1 -1
  199. package/dist/components/page-header.js +1 -11
  200. package/dist/components/page-header.js.map +1 -1
  201. package/dist/components/password-strength-labels.js.map +1 -1
  202. package/dist/components/password-strength.js +2 -8
  203. package/dist/components/password-strength.js.map +1 -1
  204. package/dist/components/phone-input.js +42 -57
  205. package/dist/components/phone-input.js.map +1 -1
  206. package/dist/components/picker-sheet.js.map +1 -1
  207. package/dist/components/pie-chart.js +1 -8
  208. package/dist/components/pie-chart.js.map +1 -1
  209. package/dist/components/popover.js.map +1 -1
  210. package/dist/components/progress-bar.js +16 -9
  211. package/dist/components/progress-bar.js.map +1 -1
  212. package/dist/components/qr-code.js +1 -8
  213. package/dist/components/qr-code.js.map +1 -1
  214. package/dist/components/row-actions.js +5 -1
  215. package/dist/components/row-actions.js.map +1 -1
  216. package/dist/components/search-field.js.map +1 -1
  217. package/dist/components/select.js +28 -38
  218. package/dist/components/select.js.map +1 -1
  219. package/dist/components/separator.js +1 -5
  220. package/dist/components/separator.js.map +1 -1
  221. package/dist/components/series-chart-axes.js +4 -1
  222. package/dist/components/series-chart-axes.js.map +1 -1
  223. package/dist/components/series-chart-budget.js.map +1 -1
  224. package/dist/components/series-chart-ticks.js.map +1 -1
  225. package/dist/components/series-chart.js +29 -45
  226. package/dist/components/series-chart.js.map +1 -1
  227. package/dist/components/settings-fields.js +21 -17
  228. package/dist/components/settings-fields.js.map +1 -1
  229. package/dist/components/share-card.js +4 -9
  230. package/dist/components/share-card.js.map +1 -1
  231. package/dist/components/sign-chip.js.map +1 -1
  232. package/dist/components/signature-pad.js +4 -13
  233. package/dist/components/signature-pad.js.map +1 -1
  234. package/dist/components/signed-amount.js +12 -5
  235. package/dist/components/signed-amount.js.map +1 -1
  236. package/dist/components/skeleton.js +2 -17
  237. package/dist/components/skeleton.js.map +1 -1
  238. package/dist/components/slider.js +13 -36
  239. package/dist/components/slider.js.map +1 -1
  240. package/dist/components/sparkline.js.map +1 -1
  241. package/dist/components/stat-tile.js +14 -10
  242. package/dist/components/stat-tile.js.map +1 -1
  243. package/dist/components/swatch-picker.js +14 -4
  244. package/dist/components/swatch-picker.js.map +1 -1
  245. package/dist/components/swipeable-row.d.ts +1 -1
  246. package/dist/components/swipeable-row.js +1 -8
  247. package/dist/components/swipeable-row.js.map +1 -1
  248. package/dist/components/table-skeleton.js +4 -1
  249. package/dist/components/table-skeleton.js.map +1 -1
  250. package/dist/components/table.js +12 -9
  251. package/dist/components/table.js.map +1 -1
  252. package/dist/components/tabs.js +5 -24
  253. package/dist/components/tabs.js.map +1 -1
  254. package/dist/components/text-link.js.map +1 -1
  255. package/dist/components/text.js.map +1 -1
  256. package/dist/components/tile-radio.js +1 -7
  257. package/dist/components/tile-radio.js.map +1 -1
  258. package/dist/components/time-input.js.map +1 -1
  259. package/dist/components/toast.js.map +1 -1
  260. package/dist/components/toggle-group.js +6 -1
  261. package/dist/components/toggle-group.js.map +1 -1
  262. package/dist/components/toggle-legend.js +2 -11
  263. package/dist/components/toggle-legend.js.map +1 -1
  264. package/dist/components/tooltip.js +2 -22
  265. package/dist/components/tooltip.js.map +1 -1
  266. package/dist/components/translation-review-editor.js +1 -4
  267. package/dist/components/translation-review-editor.js.map +1 -1
  268. package/dist/components/translation-review-parts.d.ts +1 -1
  269. package/dist/components/translation-review-parts.js +7 -1
  270. package/dist/components/translation-review-parts.js.map +1 -1
  271. package/dist/components/translation-review.js +18 -7
  272. package/dist/components/translation-review.js.map +1 -1
  273. package/dist/components/tree-view.js +14 -10
  274. package/dist/components/tree-view.js.map +1 -1
  275. package/dist/components/treemap.d.ts +24 -2
  276. package/dist/components/treemap.js +28 -4
  277. package/dist/components/treemap.js.map +1 -1
  278. package/dist/components/trigger-aria.js.map +1 -1
  279. package/dist/components/use-table-state.js +14 -11
  280. package/dist/components/use-table-state.js.map +1 -1
  281. package/dist/components/user-avatar.js +3 -26
  282. package/dist/components/user-avatar.js.map +1 -1
  283. package/dist/demo/demo-start.js +8 -4
  284. package/dist/demo/demo-start.js.map +1 -1
  285. package/dist/eslint/index.js +1 -1
  286. package/dist/feedback/feedback-attachment-labels.js.map +1 -1
  287. package/dist/feedback/feedback-attachment.js +1 -8
  288. package/dist/feedback/feedback-attachment.js.map +1 -1
  289. package/dist/feedback/feedback-capture.d.ts +1 -1
  290. package/dist/feedback/feedback-capture.js +3 -1
  291. package/dist/feedback/feedback-capture.js.map +1 -1
  292. package/dist/feedback/feedback-comment-thread.js +27 -13
  293. package/dist/feedback/feedback-comment-thread.js.map +1 -1
  294. package/dist/feedback/feedback-context.js +1 -4
  295. package/dist/feedback/feedback-context.js.map +1 -1
  296. package/dist/feedback/feedback-dialog.js.map +1 -1
  297. package/dist/feedback/feedback-inbox.js.map +1 -1
  298. package/dist/feedback/feedback-public-list.js +7 -1
  299. package/dist/feedback/feedback-public-list.js.map +1 -1
  300. package/dist/feedback/feedback-record.js +1 -5
  301. package/dist/feedback/feedback-record.js.map +1 -1
  302. package/dist/feedback/feedback-replies.js +25 -10
  303. package/dist/feedback/feedback-replies.js.map +1 -1
  304. package/dist/feedback/feedback-row-detail.js.map +1 -1
  305. package/dist/feedback/feedback-status-undo.js.map +1 -1
  306. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  307. package/dist/feedback/feedback-table.js +4 -1
  308. package/dist/feedback/feedback-table.js.map +1 -1
  309. package/dist/feedback/feedback-thread.js +2 -13
  310. package/dist/feedback/feedback-thread.js.map +1 -1
  311. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  312. package/dist/feedback/feedback-vote-button.js.map +1 -1
  313. package/dist/hooks/use-anchored-rect.js.map +1 -1
  314. package/dist/hooks/use-document-title.js.map +1 -1
  315. package/dist/hooks/use-file-drop.js.map +1 -1
  316. package/dist/hooks/use-file-picker-labels.js.map +1 -1
  317. package/dist/hooks/use-file-picker.js.map +1 -1
  318. package/dist/hooks/use-focus-trap.js.map +1 -1
  319. package/dist/hooks/use-overlay-history.js +10 -5
  320. package/dist/hooks/use-overlay-history.js.map +1 -1
  321. package/dist/hooks/use-row-swipe.d.ts +1 -1
  322. package/dist/hooks/use-row-swipe.js +1 -6
  323. package/dist/hooks/use-row-swipe.js.map +1 -1
  324. package/dist/i18n/defaults.js +1 -4
  325. package/dist/i18n/defaults.js.map +1 -1
  326. package/dist/i18n/german.js +4 -1
  327. package/dist/i18n/german.js.map +1 -1
  328. package/dist/i18n/kit-labels.js +2 -8
  329. package/dist/i18n/kit-labels.js.map +1 -1
  330. package/dist/i18n/languages.js.map +1 -1
  331. package/dist/i18n/locales/en.js +4 -1
  332. package/dist/i18n/locales/en.js.map +2 -2
  333. package/dist/i18n/locales/es.js +4 -1
  334. package/dist/i18n/locales/es.js.map +1 -1
  335. package/dist/i18n/locales/fr.js +4 -1
  336. package/dist/i18n/locales/fr.js.map +1 -1
  337. package/dist/i18n/locales/hu.js +4 -1
  338. package/dist/i18n/locales/hu.js.map +1 -1
  339. package/dist/i18n/locales/it.js +4 -1
  340. package/dist/i18n/locales/it.js.map +1 -1
  341. package/dist/i18n/locales/zh.js.map +1 -1
  342. package/dist/i18n/plurals.js.map +1 -1
  343. package/dist/i18n/review.js +13 -7
  344. package/dist/i18n/review.js.map +1 -1
  345. package/dist/i18n/shared-labels.js.map +1 -1
  346. package/dist/index.d.ts +16 -16
  347. package/dist/index.js +40 -23
  348. package/dist/index.js.map +1 -1
  349. package/dist/landing/access.js +1 -4
  350. package/dist/landing/access.js.map +1 -1
  351. package/dist/landing/landing-sections.js +35 -38
  352. package/dist/landing/landing-sections.js.map +1 -1
  353. package/dist/landing/page-seo.js.map +1 -1
  354. package/dist/lib/calc.js.map +1 -1
  355. package/dist/lib/clipboard-minus.js.map +1 -1
  356. package/dist/lib/dates.js.map +1 -1
  357. package/dist/lib/decimal-marks.js.map +1 -1
  358. package/dist/lib/device-settings.js +2 -1
  359. package/dist/lib/device-settings.js.map +1 -1
  360. package/dist/lib/direction.js.map +1 -1
  361. package/dist/lib/iban.js.map +1 -1
  362. package/dist/lib/logger.js +1 -5
  363. package/dist/lib/logger.js.map +1 -1
  364. package/dist/lib/person-name.js.map +1 -1
  365. package/dist/lib/phone.js.map +1 -1
  366. package/dist/lib/qr-encode.js +344 -8
  367. package/dist/lib/qr-encode.js.map +1 -1
  368. package/dist/lib/session-refresh.js.map +1 -1
  369. package/dist/lib/table-text.js +15 -3
  370. package/dist/lib/table-text.js.map +1 -1
  371. package/dist/lib/text-match.d.ts +4 -0
  372. package/dist/lib/text-match.js.map +1 -1
  373. package/dist/lib/translation-review.js.map +1 -1
  374. package/dist/notifications/notification-inbox.js +63 -44
  375. package/dist/notifications/notification-inbox.js.map +1 -1
  376. package/dist/notifications/notification-parts.js.map +1 -1
  377. package/dist/notifications/notification-types-setting.js.map +1 -1
  378. package/dist/notifications/notifications-labels.js.map +1 -1
  379. package/dist/notifications/push-device-setting.js +6 -1
  380. package/dist/notifications/push-device-setting.js.map +1 -1
  381. package/dist/notifications/push-devices-setting.js.map +1 -1
  382. package/dist/notifications/push-worker.js.map +1 -1
  383. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  384. package/dist/notifications/use-push-device.js +4 -1
  385. package/dist/notifications/use-push-device.js.map +1 -1
  386. package/dist/offline/pwa-update-prompt.d.ts +1 -1
  387. package/dist/offline/pwa-update-prompt.js +7 -1
  388. package/dist/offline/pwa-update-prompt.js.map +1 -1
  389. package/dist/offline/query-persistence.js.map +1 -1
  390. package/dist/offline.d.ts +1 -1
  391. package/dist/offline.js +6 -1
  392. package/dist/offline.js.map +1 -1
  393. package/dist/pay/pay.css +8 -1
  394. package/dist/rhf/fields.d.ts +1 -2
  395. package/dist/rhf/fields.js +1 -10
  396. package/dist/rhf/fields.js.map +1 -1
  397. package/dist/rhf/form-core.js +1 -4
  398. package/dist/rhf/form-core.js.map +1 -1
  399. package/dist/rhf/line-items.js.map +1 -1
  400. package/dist/rhf/use-rhf-wizard-step.js.map +1 -1
  401. package/dist/search/command-palette.js +71 -62
  402. package/dist/search/command-palette.js.map +1 -1
  403. package/dist/search/global-search.js +34 -5
  404. package/dist/search/global-search.js.map +1 -1
  405. package/dist/search/search-index.js.map +1 -1
  406. package/dist/settings/device-reset-setting.js +1 -6
  407. package/dist/settings/device-reset-setting.js.map +1 -1
  408. package/dist/settings/settings-heading.js.map +1 -1
  409. package/dist/settings/settings-layout.js +9 -20
  410. package/dist/settings/settings-layout.js.map +1 -1
  411. package/dist/settings/use-settings-route.js.map +1 -1
  412. package/dist/shell/app-shell.js +62 -31
  413. package/dist/shell/app-shell.js.map +1 -1
  414. package/dist/shell/auth-layout.js +1 -10
  415. package/dist/shell/auth-layout.js.map +1 -1
  416. package/dist/shell/dev-menu.js +12 -6
  417. package/dist/shell/dev-menu.js.map +1 -1
  418. package/dist/shell/option-switcher-menu.js.map +1 -1
  419. package/dist/shell/sync-status-indicator.js +7 -6
  420. package/dist/shell/sync-status-indicator.js.map +1 -1
  421. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  422. package/dist/shell/topbar-action-menu.js.map +1 -1
  423. package/dist/shell/topbar-controls.js +1 -10
  424. package/dist/shell/topbar-controls.js.map +1 -1
  425. package/dist/shell/tour-menu.js +9 -1
  426. package/dist/shell/tour-menu.js.map +1 -1
  427. package/dist/sync/db.js.map +1 -1
  428. package/dist/sync/engine.js +24 -6
  429. package/dist/sync/engine.js.map +1 -1
  430. package/dist/sync.d.ts +2 -2
  431. package/dist/sync.js +7 -1
  432. package/dist/sync.js.map +1 -1
  433. package/dist/testing/css-variables.js.map +1 -1
  434. package/dist/testing/index.d.ts +1 -1
  435. package/dist/testing/index.js +1 -3
  436. package/dist/testing/index.js.map +1 -1
  437. package/dist/testing/react.js.map +1 -1
  438. package/dist/testing/viewport.js +7 -4
  439. package/dist/testing/viewport.js.map +1 -1
  440. package/dist/theme/contrast-tokens.js +13 -2
  441. package/dist/theme/contrast-tokens.js.map +1 -1
  442. package/dist/theme/contrast.d.ts +2 -2
  443. package/dist/theme/palette-derive.d.ts +1 -1
  444. package/dist/theme/palette-derive.js +18 -29
  445. package/dist/theme/palette-derive.js.map +1 -1
  446. package/dist/theme/palette-presets.js.map +1 -1
  447. package/dist/theme/palette-store.js.map +1 -1
  448. package/dist/theme/text-size.js.map +1 -1
  449. package/dist/theme/theme-store.js +1 -3
  450. package/dist/theme/theme-store.js.map +1 -1
  451. package/dist/tour/tour-catalogue.js.map +1 -1
  452. package/dist/tour/tour-dock.js +5 -1
  453. package/dist/tour/tour-dock.js.map +1 -1
  454. package/dist/tour/tour-engine.js +1 -4
  455. package/dist/tour/tour-engine.js.map +1 -1
  456. package/dist/tour/tour.js +21 -12
  457. package/dist/tour/tour.js.map +1 -1
  458. package/dist/tour/use-tour-progress.js.map +1 -1
  459. package/dist/tour/use-tour-url-sync.js.map +1 -1
  460. package/dist/wizard/stepper-nav.js +3 -24
  461. package/dist/wizard/stepper-nav.js.map +1 -1
  462. package/dist/wizard/types.js.map +1 -1
  463. package/dist/wizard/use-wizard.js +24 -28
  464. package/dist/wizard/use-wizard.js.map +1 -1
  465. package/dist/wizard/validation.js.map +1 -1
  466. package/dist/wizard/wizard-context.js.map +1 -1
  467. package/dist/wizard/wizard-step.d.ts +1 -1
  468. package/dist/wizard/wizard-step.js +1 -8
  469. package/dist/wizard/wizard-step.js.map +1 -1
  470. package/dist/wizard/wizard-summary.js.map +1 -1
  471. package/package.json +5 -2
  472. package/src/account/account-settings-labels.ts +1 -2
  473. package/src/account/account-settings.tsx +11 -17
  474. package/src/account/delete-account-setting.tsx +1 -6
  475. package/src/account/email-change-setting-labels.ts +1 -2
  476. package/src/account/email-change-setting.tsx +3 -1
  477. package/src/account/passkeys-setting.tsx +1 -3
  478. package/src/account/sessions-setting.tsx +4 -2
  479. package/src/admin/admin-action-confirm.tsx +4 -7
  480. package/src/admin/admin-action-log.tsx +29 -29
  481. package/src/admin/admin-area-labels.ts +4 -1
  482. package/src/admin/admin-overview.tsx +8 -2
  483. package/src/admin/admin-sections.tsx +3 -1
  484. package/src/admin/admin-tool-links.tsx +7 -1
  485. package/src/admin/invitations-panel.tsx +5 -11
  486. package/src/admin/mail-test-card.tsx +1 -2
  487. package/src/admin/reviewer-scope-editor.tsx +4 -9
  488. package/src/admin/role-select.tsx +4 -2
  489. package/src/admin/user-roster.tsx +2 -11
  490. package/src/auth/accept-invitation.tsx +1 -7
  491. package/src/auth/auth-errors.ts +1 -6
  492. package/src/auth/forgot-password-form.tsx +2 -7
  493. package/src/auth/register-form.tsx +7 -8
  494. package/src/auth/reset-password-form.tsx +2 -5
  495. package/src/auth/sign-in-form.tsx +15 -14
  496. package/src/auth/verify-email.tsx +2 -7
  497. package/src/billing/billing-banner.tsx +3 -7
  498. package/src/billing/billing-labels.ts +6 -4
  499. package/src/billing/checkout-processing.ts +0 -1
  500. package/src/billing/plan-card.tsx +9 -8
  501. package/src/billing/plan-limit-toast.tsx +1 -2
  502. package/src/billing/plan-limit.ts +1 -2
  503. package/src/billing/plan-price.ts +1 -4
  504. package/src/billing/plan-switch.tsx +1 -2
  505. package/src/billing/subscription-actions.tsx +5 -2
  506. package/src/components/account-chips.tsx +1 -7
  507. package/src/components/alert-banner.tsx +2 -8
  508. package/src/components/amount-input.tsx +206 -178
  509. package/src/components/authed-image.tsx +7 -4
  510. package/src/components/autocomplete.tsx +7 -9
  511. package/src/components/bulk-action-bar.tsx +2 -7
  512. package/src/components/button-group.tsx +1 -2
  513. package/src/components/button-look.ts +6 -17
  514. package/src/components/button.tsx +1 -4
  515. package/src/components/calculator.tsx +6 -9
  516. package/src/components/calendar-heatmap.tsx +5 -10
  517. package/src/components/card.tsx +9 -13
  518. package/src/components/chart-zoom-core.tsx +6 -33
  519. package/src/components/chart.tsx +8 -31
  520. package/src/components/checkbox-group.tsx +1 -4
  521. package/src/components/chip-input.tsx +6 -6
  522. package/src/components/chip.tsx +14 -26
  523. package/src/components/choice-card.tsx +4 -18
  524. package/src/components/column-mapper-labels.ts +3 -10
  525. package/src/components/column-mapper.tsx +33 -122
  526. package/src/components/combobox-core.tsx +12 -42
  527. package/src/components/combobox.tsx +2 -6
  528. package/src/components/confirm-dialog.tsx +1 -3
  529. package/src/components/copy-button.tsx +2 -4
  530. package/src/components/country-select.tsx +9 -21
  531. package/src/components/currency-option-list.tsx +13 -14
  532. package/src/components/currency-select.tsx +2 -7
  533. package/src/components/danger-confirm.tsx +8 -9
  534. package/src/components/data-table-cells.tsx +1 -2
  535. package/src/components/data-table-column-settings.tsx +1 -3
  536. package/src/components/data-table-filter-popover.tsx +5 -19
  537. package/src/components/data-table-filters.ts +1 -6
  538. package/src/components/data-table-fit.ts +1 -3
  539. package/src/components/data-table-groups.tsx +3 -1
  540. package/src/components/data-table-head.tsx +27 -45
  541. package/src/components/data-table-mobile.tsx +99 -108
  542. package/src/components/data-table-pagination.tsx +2 -6
  543. package/src/components/data-table-row-actions.tsx +25 -24
  544. package/src/components/data-table-row.tsx +16 -17
  545. package/src/components/data-table-shared.tsx +10 -16
  546. package/src/components/data-table-sort.ts +1 -2
  547. package/src/components/data-table-virtual.ts +9 -4
  548. package/src/components/data-table.tsx +489 -501
  549. package/src/components/date-picker.tsx +1 -7
  550. package/src/components/date-range-picker.tsx +3 -13
  551. package/src/components/description-list.tsx +7 -13
  552. package/src/components/dialog-frame.tsx +12 -3
  553. package/src/components/disclosure.tsx +8 -12
  554. package/src/components/dropdown.tsx +3 -11
  555. package/src/components/empty-state.tsx +1 -3
  556. package/src/components/entity-combobox.tsx +2 -6
  557. package/src/components/error-boundary.tsx +7 -7
  558. package/src/components/field-base.tsx +8 -14
  559. package/src/components/field-parts.tsx +12 -23
  560. package/src/components/field-sync.tsx +7 -11
  561. package/src/components/file-button.tsx +7 -3
  562. package/src/components/file-dropzone.tsx +7 -12
  563. package/src/components/floating-panel.tsx +44 -51
  564. package/src/components/focus-ring.ts +1 -2
  565. package/src/components/form-actions.tsx +1 -4
  566. package/src/components/grouped-picker.tsx +6 -8
  567. package/src/components/hover-menu.tsx +1 -4
  568. package/src/components/iban-input-labels.ts +1 -2
  569. package/src/components/icon-button.tsx +46 -48
  570. package/src/components/icon-picker.tsx +4 -17
  571. package/src/components/image-grid.tsx +1 -6
  572. package/src/components/inline-edit-field.tsx +1 -5
  573. package/src/components/inline-entity-combobox.tsx +6 -21
  574. package/src/components/input.tsx +14 -2
  575. package/src/components/jump-calendar.tsx +2 -6
  576. package/src/components/legal-labels.ts +1 -2
  577. package/src/components/legal-page.tsx +18 -7
  578. package/src/components/legal.tsx +14 -5
  579. package/src/components/lightbox.tsx +1 -3
  580. package/src/components/line-items.tsx +7 -15
  581. package/src/components/list.tsx +29 -25
  582. package/src/components/measured-grid.tsx +8 -30
  583. package/src/components/menu-item.tsx +1 -3
  584. package/src/components/mini-calendar.tsx +129 -159
  585. package/src/components/money-field.tsx +4 -2
  586. package/src/components/month-picker.tsx +14 -21
  587. package/src/components/multi-entity-combobox.tsx +6 -26
  588. package/src/components/multi-select.tsx +5 -9
  589. package/src/components/nav-pills.tsx +10 -2
  590. package/src/components/number-field.tsx +18 -15
  591. package/src/components/number-input.tsx +3 -19
  592. package/src/components/numpad-sheet.tsx +9 -13
  593. package/src/components/one-time-code-input.tsx +6 -11
  594. package/src/components/page-contents.tsx +2 -9
  595. package/src/components/page-header.tsx +5 -10
  596. package/src/components/password-strength-labels.ts +1 -2
  597. package/src/components/password-strength.tsx +6 -9
  598. package/src/components/phone-input.tsx +2 -13
  599. package/src/components/picker-sheet.tsx +2 -5
  600. package/src/components/pie-chart.tsx +6 -12
  601. package/src/components/popover.tsx +2 -1
  602. package/src/components/progress-bar.tsx +16 -25
  603. package/src/components/qr-code.tsx +2 -9
  604. package/src/components/row-actions.tsx +9 -1
  605. package/src/components/search-field.tsx +4 -5
  606. package/src/components/select.tsx +2 -10
  607. package/src/components/separator.tsx +1 -5
  608. package/src/components/series-chart-axes.ts +5 -5
  609. package/src/components/series-chart-budget.ts +1 -4
  610. package/src/components/series-chart-ticks.ts +1 -4
  611. package/src/components/series-chart.tsx +43 -83
  612. package/src/components/settings-fields.tsx +20 -15
  613. package/src/components/share-card.tsx +6 -14
  614. package/src/components/sign-chip.tsx +16 -16
  615. package/src/components/signature-pad.tsx +21 -13
  616. package/src/components/signed-amount.tsx +6 -14
  617. package/src/components/skeleton.tsx +2 -13
  618. package/src/components/slider.tsx +9 -20
  619. package/src/components/sparkline.tsx +2 -13
  620. package/src/components/stat-tile.tsx +13 -24
  621. package/src/components/swatch-picker.tsx +8 -3
  622. package/src/components/swipeable-row.tsx +2 -11
  623. package/src/components/table-skeleton.tsx +4 -1
  624. package/src/components/table.tsx +12 -8
  625. package/src/components/tabs.tsx +24 -39
  626. package/src/components/text-link.tsx +6 -6
  627. package/src/components/text.tsx +1 -2
  628. package/src/components/tile-radio.tsx +6 -19
  629. package/src/components/time-input.tsx +4 -5
  630. package/src/components/toast.tsx +3 -12
  631. package/src/components/toggle-group.tsx +9 -9
  632. package/src/components/toggle-legend.tsx +4 -16
  633. package/src/components/tooltip.tsx +9 -48
  634. package/src/components/translation-review-editor.tsx +4 -4
  635. package/src/components/translation-review-parts.tsx +7 -1
  636. package/src/components/translation-review.tsx +15 -16
  637. package/src/components/tree-view.tsx +17 -20
  638. package/src/components/treemap.tsx +59 -9
  639. package/src/components/trigger-aria.ts +1 -4
  640. package/src/components/use-table-state.ts +16 -17
  641. package/src/components/user-avatar.tsx +4 -21
  642. package/src/demo/demo-start.tsx +8 -4
  643. package/src/feedback/feedback-attachment-labels.ts +2 -1
  644. package/src/feedback/feedback-attachment.tsx +16 -29
  645. package/src/feedback/feedback-capture.ts +3 -1
  646. package/src/feedback/feedback-comment-thread.tsx +9 -6
  647. package/src/feedback/feedback-context.tsx +1 -4
  648. package/src/feedback/feedback-dialog.tsx +3 -4
  649. package/src/feedback/feedback-inbox.tsx +3 -9
  650. package/src/feedback/feedback-public-list.tsx +7 -1
  651. package/src/feedback/feedback-record.ts +6 -14
  652. package/src/feedback/feedback-replies.tsx +10 -5
  653. package/src/feedback/feedback-row-detail.tsx +2 -5
  654. package/src/feedback/feedback-status-undo.ts +1 -4
  655. package/src/feedback/feedback-submit-dialog.tsx +4 -4
  656. package/src/feedback/feedback-table.tsx +12 -8
  657. package/src/feedback/feedback-thread.tsx +4 -16
  658. package/src/feedback/feedback-vocabulary.ts +72 -82
  659. package/src/feedback/feedback-vote-button.tsx +9 -8
  660. package/src/hooks/use-anchored-rect.ts +1 -4
  661. package/src/hooks/use-document-title.ts +3 -1
  662. package/src/hooks/use-file-drop.ts +1 -3
  663. package/src/hooks/use-file-picker-labels.ts +1 -2
  664. package/src/hooks/use-file-picker.tsx +1 -5
  665. package/src/hooks/use-focus-trap.ts +1 -2
  666. package/src/hooks/use-overlay-history.ts +10 -5
  667. package/src/hooks/use-row-swipe.ts +1 -6
  668. package/src/i18n/defaults.ts +1 -4
  669. package/src/i18n/german.ts +33 -49
  670. package/src/i18n/kit-labels.tsx +5 -20
  671. package/src/i18n/languages.ts +2 -8
  672. package/src/i18n/locales/en.ts +11 -31
  673. package/src/i18n/locales/es.ts +35 -49
  674. package/src/i18n/locales/fr.ts +34 -51
  675. package/src/i18n/locales/hu.ts +26 -37
  676. package/src/i18n/locales/it.ts +35 -52
  677. package/src/i18n/locales/zh.ts +14 -32
  678. package/src/i18n/plurals.ts +4 -1
  679. package/src/i18n/review.ts +18 -7
  680. package/src/i18n/shared-labels.ts +1 -2
  681. package/src/index.ts +106 -32
  682. package/src/landing/access.ts +2 -7
  683. package/src/landing/landing-sections.tsx +14 -15
  684. package/src/landing/page-seo.ts +1 -3
  685. package/src/lib/calc.ts +4 -1
  686. package/src/lib/clipboard-minus.ts +4 -1
  687. package/src/lib/dates.ts +2 -10
  688. package/src/lib/decimal-marks.ts +1 -3
  689. package/src/lib/device-settings.ts +2 -1
  690. package/src/lib/direction.ts +1 -3
  691. package/src/lib/iban.ts +89 -9
  692. package/src/lib/logger.ts +1 -5
  693. package/src/lib/person-name.ts +1 -3
  694. package/src/lib/phone.ts +5 -8
  695. package/src/lib/qr-encode.ts +32 -8
  696. package/src/lib/session-refresh.ts +5 -6
  697. package/src/lib/table-text.ts +24 -6
  698. package/src/lib/text-match.ts +15 -3
  699. package/src/lib/translation-review.ts +1 -3
  700. package/src/notifications/notification-inbox.tsx +17 -4
  701. package/src/notifications/notification-parts.tsx +1 -2
  702. package/src/notifications/notification-types-setting.tsx +19 -18
  703. package/src/notifications/notifications-labels.ts +1 -2
  704. package/src/notifications/push-device-setting.tsx +7 -1
  705. package/src/notifications/push-devices-setting.tsx +7 -3
  706. package/src/notifications/push-worker.ts +10 -2
  707. package/src/notifications/quiet-hours-setting.tsx +4 -1
  708. package/src/notifications/use-push-device.ts +4 -1
  709. package/src/offline/pwa-update-prompt.tsx +7 -1
  710. package/src/offline/query-persistence.ts +9 -6
  711. package/src/offline.ts +6 -1
  712. package/src/rhf/fields.tsx +50 -19
  713. package/src/rhf/form-core.tsx +2 -6
  714. package/src/rhf/line-items.tsx +5 -7
  715. package/src/rhf/use-rhf-wizard-step.ts +3 -12
  716. package/src/search/command-palette.tsx +15 -23
  717. package/src/search/global-search.tsx +38 -8
  718. package/src/search/search-index.ts +5 -1
  719. package/src/settings/device-reset-setting.tsx +5 -8
  720. package/src/settings/settings-heading.tsx +3 -1
  721. package/src/settings/settings-layout.tsx +6 -13
  722. package/src/settings/use-settings-route.ts +4 -3
  723. package/src/shell/app-shell.tsx +48 -24
  724. package/src/shell/auth-layout.tsx +1 -6
  725. package/src/shell/dev-menu.tsx +12 -6
  726. package/src/shell/option-switcher-menu.tsx +1 -3
  727. package/src/shell/sync-status-indicator.tsx +14 -12
  728. package/src/shell/top-bar-parts-labels.ts +1 -3
  729. package/src/shell/topbar-action-menu.tsx +9 -8
  730. package/src/shell/topbar-controls.tsx +5 -19
  731. package/src/shell/tour-menu.tsx +9 -1
  732. package/src/sync/db.ts +5 -1
  733. package/src/sync/engine.ts +44 -12
  734. package/src/sync.ts +16 -2
  735. package/src/testing/css-variables.ts +1 -2
  736. package/src/testing/index.ts +1 -6
  737. package/src/testing/react.ts +1 -2
  738. package/src/testing/viewport.ts +9 -9
  739. package/src/theme/contrast-tokens.ts +13 -2
  740. package/src/theme/palette-derive.ts +24 -50
  741. package/src/theme/palette-presets.ts +0 -1
  742. package/src/theme/palette-store.ts +1 -4
  743. package/src/theme/text-size.ts +1 -1
  744. package/src/theme/theme-store.ts +2 -6
  745. package/src/tour/tour-catalogue.tsx +4 -2
  746. package/src/tour/tour-dock.ts +5 -1
  747. package/src/tour/tour-engine.ts +2 -6
  748. package/src/tour/tour.tsx +28 -12
  749. package/src/tour/use-tour-progress.ts +8 -2
  750. package/src/tour/use-tour-url-sync.ts +3 -1
  751. package/src/vite-env.d.ts +1 -4
  752. package/src/wizard/stepper-nav.tsx +10 -35
  753. package/src/wizard/types.ts +1 -4
  754. package/src/wizard/use-wizard.ts +28 -43
  755. package/src/wizard/validation.ts +1 -5
  756. package/src/wizard/wizard-context.tsx +2 -6
  757. package/src/wizard/wizard-step.tsx +1 -8
  758. package/src/wizard/wizard-summary.tsx +2 -6
  759. package/tokens.css +393 -152
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/use-table-state.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { SetStateAction } from \"react\";\nimport * as ReactRouter from \"react-router\";\nimport { useLocation, useSearchParams } from \"react-router\";\nimport { useKitTableStorageKeyPrefix } from \"../i18n/kit-labels\";\nimport { readStored, writeStored } from \"../lib/safe-storage\";\nimport {\n decodeFilterValue,\n encodeFilterValue,\n isFilterActive,\n resolveFilter,\n} from \"./data-table-filters\";\nimport { decodeSorts, encodeSorts, normalizeSorts, type SortState } from \"./data-table-sort\";\nimport type { DataTableColumn, FilterState, ServerPagination } from \"./data-table\";\n\n// ---------- Persisted + URL-encoded view state ----------\n//\n// Moved here with `useTableState` when `DataTable` was split: these are what the hook\n// reads and writes, and nothing else in the table ever called them.\n\ninterface PersistedState {\n // Multi-sort array; blobs from before the upgrade hold a single {key, dir}\n // object (or null) — read back through normalizeSorts.\n sort: SortState[] | { key: string; dir: string } | null;\n filters: FilterState;\n pageSize: number | \"all\";\n widths?: Record<string, number>;\n hidden?: string[];\n /** 0.48: the `defaultHidden` columns the user ticked back on — an entry that beats the\n * default. Written only when non-empty, so a table without such columns stores what\n * it always did. */\n shown?: string[];\n}\n\n/** The hidden set as drawn: the stored one plus every `defaultHidden` column the view\n * holds no entry for (neither hidden nor `shown`). */\nfunction withDefaultHidden(view: { hidden: Set<string>; shown: Set<string> }, defaults: string[]): Set<string> {\n const extra = defaults.filter((k) => !view.hidden.has(k) && !view.shown.has(k));\n return extra.length === 0 ? view.hidden : new Set([...view.hidden, ...extra]);\n}\n\nexport const DEFAULT_PERSIST_PREFIX = \"hbui-table:\";\n\nfunction loadPersisted(storageKey: string | undefined, prefix: string): Partial<PersistedState> {\n if (!storageKey) return {};\n const raw = readStored(prefix + storageKey);\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedState>;\n return parsed && typeof parsed === \"object\" ? parsed : {};\n } catch {\n return {}; // corrupt JSON — the storage access itself is guarded by readStored\n }\n}\n\n/** The blob that would be stored — also the value the effect below compares by, so\n * the comparison and the write can never disagree about what \"unchanged\" means. */\nfunction serializePersisted(state: PersistedState): string {\n return JSON.stringify(state);\n}\n\n/** A stored page size as a table holds it — `\"all\"` is `Infinity` — or `fallback`. */\nfunction storedPageSize(stored: Partial<PersistedState>, fallback: number): number {\n if (stored.pageSize === \"all\") return Infinity;\n if (typeof stored.pageSize === \"number\" && stored.pageSize > 0) return stored.pageSize;\n return fallback;\n}\n\n// ---------- URL sync helpers ----------\n\nconst URL_FILTER_PREFIX = \"f.\";\nconst URL_SORT_KEY = \"sort\";\nconst URL_PAGE_KEY = \"p\";\nconst URL_PAGE_SIZE_KEY = \"ps\";\n/** The free-text search term — managed only by {@link useTableUrlState}, and only when\n * it is asked to (`search: true`); a plain `urlSync` table leaves a `q` alone. */\nconst URL_SEARCH_KEY = \"q\";\n\n/**\n * How a table is mirrored into the URL: `true`, or `{ prefix }` for a table that shares\n * its page with another URL-synced one. The prefix is put verbatim in front of EVERY key\n * the table writes and reads — `{ prefix: \"inv.\" }` gives `inv.f.<column>`, `inv.sort`,\n * `inv.p`, `inv.ps` (and `inv.q`) — so two tables on one page stop sharing a sort and a\n * page. Without one the keys are unprefixed, as they always were.\n */\nexport type TableUrlSync = boolean | { prefix?: string };\n\n/** The query keys of one table, with its prefix applied. */\ninterface UrlKeys {\n filter: string;\n sort: string;\n page: string;\n pageSize: string;\n search: string;\n}\n\nfunction urlKeys(prefix = \"\"): UrlKeys {\n return {\n filter: prefix + URL_FILTER_PREFIX,\n sort: prefix + URL_SORT_KEY,\n page: prefix + URL_PAGE_KEY,\n pageSize: prefix + URL_PAGE_SIZE_KEY,\n search: prefix + URL_SEARCH_KEY,\n };\n}\n\n/** What a table reads and writes the URL through: react-router's, or none. */\ninterface TableSearch {\n searchParams: URLSearchParams;\n setSearchParams: ReturnType<typeof useSearchParams>[1];\n locationState: unknown;\n}\n\nfunction useRouterSearch(): TableSearch {\n const [searchParams, setSearchParams] = useSearchParams();\n const location = useLocation();\n return { searchParams, setSearchParams, locationState: location.state };\n}\n\nconst NO_ROUTER_SEARCH: TableSearch = {\n searchParams: new URLSearchParams(),\n setSearchParams: () => {},\n locationState: null,\n};\nfunction useNoRouterSearch(): TableSearch {\n return NO_ROUTER_SEARCH;\n}\n\n/**\n * `useInRouterContext`, read off the namespace inside a `try` like\n * use-search-param-state's probe: a test's `vi.mock(\"react-router\", () => ({ … }))`\n * without it throws on the read. Missing, a table assumes a Router, as before 0.49.\n */\nfunction readInRouterContext(): () => boolean {\n try {\n return (ReactRouter as unknown as { useInRouterContext?: () => boolean }).useInRouterContext ?? (() => true);\n } catch {\n return () => true;\n }\n}\nconst useInRouterContext = readInRouterContext();\n\n/**\n * The URL access a table needs (0.49, KIT-3). Without `urlSync` a table needs no Router:\n * before 0.49 it called `useSearchParams()` unconditionally and threw outside one even\n * with URL sync off. With `urlSync` and no Router, a development build throws a message\n * that names the fix; a production build keeps the view in local state rather than\n * crash the page. Whether a Router is around does not change during a table's life, so\n * which hook runs is stable from render to render.\n */\nfunction useTableSearch(urlSync: boolean, owner: string): TableSearch {\n const inRouter = useInRouterContext();\n if (!inRouter && urlSync && import.meta.env?.DEV) {\n throw new Error(\n `${owner} urlSync requires a Router: render it inside a react-router Router ` +\n \"(BrowserRouter, a data router or a MemoryRouter), or leave urlSync off.\",\n );\n }\n const useSearch = inRouter ? useRouterSearch : useNoRouterSearch;\n return useSearch();\n}\n\n/** `urlSync` split into \"is it on\" and \"under which prefix\". */\nfunction resolveTableUrlSync(urlSync: TableUrlSync | undefined): { enabled: boolean; prefix: string } {\n if (!urlSync) return { enabled: false, prefix: \"\" };\n return { enabled: true, prefix: urlSync === true ? \"\" : (urlSync.prefix ?? \"\") };\n}\n\n/** What {@link readTableUrlState} found in a query string. */\nexport interface TableUrlState {\n sorts?: SortState[];\n filters?: FilterState;\n /** `Infinity` for `ps=all`. */\n pageSize?: number;\n /** 0-based (the URL's `p` is 1-based). */\n page?: number;\n}\n\n/**\n * What a table's URL says, read out of a query string: the column filters (`f.<key>`),\n * the sort, the page size and the 0-based page. Absent keys are absent here too, so a\n * caller can tell \"not in the link\" from \"the default\". The same reader `urlSync` uses\n * on mount, exported for an owner that keeps the state itself — see\n * {@link useTableUrlState}.\n *\n * Pass the ROUTER's query string — the one `setSearchParams` writes — and the table's\n * `prefix` when it has one (see {@link TableUrlSync}).\n *\n * This read `window.location.search` until 0.7.0, which is the same string only under a\n * BrowserRouter. Under a HashRouter the router's query lives after the `#`\n * (`/#/tx?f.payee=Rewe`) and `window.location.search` is empty: a shared link opened\n * unfiltered, and the table's first write then replaced the link's filter with its own\n * empty state. Reading and writing through `useSearchParams` is one source for both\n * directions under every router, MemoryRouter included.\n */\nexport function readTableUrlState<T>(\n sp: URLSearchParams,\n columns: DataTableColumn<T>[],\n prefix?: string,\n): TableUrlState {\n const keys = urlKeys(prefix);\n const out: TableUrlState = {};\n\n const filters: FilterState = {};\n for (const col of columns) {\n const filter = resolveFilter(col);\n if (!filter) continue;\n const raw = sp.get(keys.filter + col.key);\n if (raw == null) continue;\n filters[col.key] = decodeFilterValue(filter, raw);\n }\n if (Object.keys(filters).length) out.filters = filters;\n\n const sortRaw = sp.get(keys.sort);\n if (sortRaw) {\n const decoded = decodeSorts(sortRaw, new Set(columns.map((c) => c.key)));\n if (decoded.length) out.sorts = decoded;\n }\n\n const psRaw = sp.get(keys.pageSize);\n if (psRaw === \"all\") out.pageSize = Infinity;\n else if (psRaw && Number(psRaw) > 0) out.pageSize = Number(psRaw);\n\n const pRaw = sp.get(keys.page);\n if (pRaw && Number(pRaw) >= 1) out.page = Number(pRaw) - 1;\n\n return out;\n}\n\n/**\n * The managed part of the query string, as a string, for comparing by VALUE.\n *\n * Everything the table puts in the address and nothing else, in a fixed order.\n * `columns` is walked for its keys only, which is why it can be left out of the\n * effect's dependencies without lying: a new column ARRAY carrying the same keys\n * produces the same signature, and a genuinely different set of filterable columns\n * produces a different one.\n */\nfunction urlSignature<T>(\n columns: DataTableColumn<T>[],\n sorts: SortState[],\n filters: FilterState,\n pageSize: number,\n page: number,\n defaultPageSize: number,\n keys: UrlKeys,\n): string {\n const parts: string[] = [];\n for (const col of columns) {\n const state = filters[col.key];\n if (!state || !isFilterActive(state)) continue;\n const enc = encodeFilterValue(state);\n if (enc != null) parts.push(`${keys.filter}${col.key}=${enc}`);\n }\n parts.push(`${keys.sort}=${encodeSorts(sorts)}`);\n parts.push(\n `${keys.pageSize}=${\n pageSize === Infinity ? \"all\" : pageSize === defaultPageSize ? \"\" : String(pageSize)\n }`,\n );\n parts.push(`${keys.page}=${page > 0 ? String(page + 1) : \"\"}`);\n return parts.join(\"&\");\n}\n\nfunction writeUrlState<T>(\n setSearchParams: ReturnType<typeof useSearchParams>[1],\n columns: DataTableColumn<T>[],\n sorts: SortState[],\n filters: FilterState,\n pageSize: number,\n page: number,\n defaultPageSize: number,\n // `undefined`: the search term is not ours to manage, and a `q` is left as it is.\n search: string | undefined,\n // The current entry's route state, carried over the replace. A query rewrite is not\n // a navigation, and dropping the state would, for one, lose the mark a URL-bound\n // dialog's opener leaves there (see useDialogParam).\n state: unknown,\n keys: UrlKeys,\n): void {\n setSearchParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n // clear managed keys\n for (const col of columns) next.delete(keys.filter + col.key);\n next.delete(keys.sort);\n next.delete(keys.page);\n next.delete(keys.pageSize);\n if (search !== undefined) {\n next.delete(keys.search);\n if (search.trim()) next.set(keys.search, search);\n }\n // write filters\n for (const col of columns) {\n const state = filters[col.key];\n if (!state || !isFilterActive(state)) continue;\n const enc = encodeFilterValue(state);\n if (enc != null) next.set(keys.filter + col.key, enc);\n }\n // write sort\n const sortEnc = encodeSorts(sorts);\n if (sortEnc) next.set(keys.sort, sortEnc);\n // write page size if non-default\n if (pageSize === Infinity) next.set(keys.pageSize, \"all\");\n else if (pageSize !== defaultPageSize) next.set(keys.pageSize, String(pageSize));\n // write page if not first\n if (page > 0) next.set(keys.page, String(page + 1));\n return next;\n },\n { replace: true, state },\n );\n}\n\n/**\n * Where a table's view lives: what it is sorted and filtered by, which page, how wide\n * the columns are and which are hidden — plus the two places that outlives a render.\n *\n * Lifted out of `DataTable`, which was 984 lines and twenty hooks in one function. This\n * is the part that is genuinely about STATE rather than about drawing, and the three\n * rules it encodes are easy to lose among the markup:\n *\n * * **The URL is read once, on mount.** A shared or deep link populates the initial view;\n * after that the state is local, or the browser's own history would fight the user's\n * next click.\n * * **Sorting and filtering are optionally CONTROLLED.** A server-driven table passes\n * state and a callback and owns them; everything else is self-owned. That is the\n * `sortsProp ?? internalSorts` pair, and it is why the setters are exposed separately.\n * * **Persistence writes on every change, the URL only when asked.** `storageKey` is the\n * per-table identity; `urlSync` is opt-in because only some tables want their view in\n * a shareable address.\n * * **A controlled filter, sort or page size that changes sends the table back to page\n * 1.** See the note at the reset below.\n */\nexport function useTableState<T>({\n columns,\n storageKey,\n storageKeyPrefix,\n urlSync: urlSyncOption,\n defaultPageSize,\n sortsProp,\n filtersProp,\n pageSizeProp,\n}: {\n columns: DataTableColumn<T>[];\n storageKey?: string;\n storageKeyPrefix: string;\n urlSync?: TableUrlSync;\n defaultPageSize: number;\n sortsProp?: SortState[];\n filtersProp?: FilterState;\n pageSizeProp?: number;\n}) {\n const { enabled: urlSync, prefix: urlPrefix } = resolveTableUrlSync(urlSyncOption);\n const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);\n // The SAME pair is used to read and to write; see `readTableUrlState`. No Router is\n // needed without `urlSync` (0.49, KIT-3; see `useTableSearch`).\n const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, \"DataTable\");\n // Read URL once on mount so URL-encoded views (e.g. shared links, deep links from\n // other pages) populate initial state. After mount, state is local.\n const [urlInitial] = useState<TableUrlState>(() =>\n urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {},\n );\n const initial = useMemo(\n () => loadPersisted(storageKey, storageKeyPrefix),\n [storageKey, storageKeyPrefix],\n );\n const [internalSorts, setInternalSorts] = useState<SortState[]>(() =>\n urlInitial.sorts !== undefined ? urlInitial.sorts : normalizeSorts(initial.sort),\n );\n const [internalFilters, setInternalFilters] = useState<FilterState>(\n () => urlInitial.filters ?? initial.filters ?? {},\n );\n // Controlled when the owner passes state + callback (server-driven tables);\n // self-owned otherwise.\n const sorts = sortsProp ?? internalSorts;\n const filters = filtersProp ?? internalFilters;\n const [page, setPage] = useState(() => urlInitial.page ?? 0);\n\n // ---- Controlled filters/sorts reset the page ----\n //\n // A self-owned table resets to page 1 in the filter handlers. A CONTROLLED one could\n // not: the handlers hand the change to the owner and return, and `page` lives here,\n // where the owner has no way to reach it — so narrowing a filter from outside (a\n // saved view, a chip dismissed above the table) left a client-side table on page 4\n // of what was now two pages, clamped to the last one instead of the first.\n //\n // So the table does it: whenever the controlled VALUE changes, back to the first\n // page. Compared by value, because a controlled prop is a fresh object on every\n // parent render. Adjusted during render rather than in an effect, so the stale page\n // is never painted and never written to the URL. Not on mount: a deep link's `p=3`\n // must survive the owner's first render. (In `serverPagination` mode this state is\n // unused — the owner's `page` is the page, and resetting it stays the owner's job.)\n // A controlled page size counts too: the groups of `TranslationReviewPanel` share one,\n // and a group paged deep into its rows starts over when another group's pager changes it.\n const controlledSig =\n sortsProp !== undefined || filtersProp !== undefined || pageSizeProp !== undefined\n ? JSON.stringify([sortsProp ?? null, filtersProp ?? null, String(pageSizeProp)])\n : null;\n const [seenControlledSig, setSeenControlledSig] = useState(controlledSig);\n if (seenControlledSig !== controlledSig) {\n setSeenControlledSig(controlledSig);\n setPage(0);\n }\n const [internalPageSize, setPageSize] = useState<number>(() =>\n urlInitial.pageSize !== undefined ? urlInitial.pageSize : storedPageSize(initial, defaultPageSize),\n );\n const pageSize = pageSizeProp ?? internalPageSize;\n const [widths, setWidths] = useState<Record<string, number>>(() => initial.widths ?? {});\n // Hidden columns (0.48, keksdose #617): what the user chose — `hidden`, and `shown`\n // for a `defaultHidden` column ticked back on — kept apart from the defaults, which\n // are applied on read and never stored. Keyed on the default keys as a string: every\n // call site builds `columns` inline, so the array is new on every render.\n const [view, setView] = useState(() => ({\n hidden: new Set<string>(initial.hidden ?? []),\n shown: new Set<string>(initial.shown ?? []),\n }));\n const defaultHiddenSig = columns\n .filter((c) => c.defaultHidden)\n .map((c) => c.key)\n .join(\"\\u0000\");\n const hiddenCols = useMemo(\n () => withDefaultHidden(view, defaultHiddenSig ? defaultHiddenSig.split(\"\\u0000\") : []),\n [view, defaultHiddenSig],\n );\n // The panel hands over the next DRAWN set; what changed is recorded as a choice.\n const setHiddenCols = useCallback(\n (action: SetStateAction<Set<string>>) =>\n setView((v) => {\n const defaults = defaultHiddenSig ? defaultHiddenSig.split(\"\\u0000\") : [];\n const prev = withDefaultHidden(v, defaults);\n const next = typeof action === \"function\" ? action(prev) : action;\n const hidden = new Set(v.hidden);\n const shown = new Set(v.shown);\n for (const k of next) {\n if (prev.has(k)) continue;\n hidden.add(k);\n shown.delete(k);\n }\n for (const k of prev) {\n if (next.has(k)) continue;\n hidden.delete(k);\n if (defaults.includes(k)) shown.add(k);\n }\n return { hidden, shown };\n }),\n [defaultHiddenSig],\n );\n const [showSettings, setShowSettings] = useState(false);\n const headRefs = useMemo(() => new Map<string, HTMLTableCellElement>(), []);\n\n // ---- Writing out: both of these compare by VALUE ----\n //\n // A CONTROLLED table (`sorts`/`filters` props plus callbacks — every server-driven\n // one) receives a freshly-built object on every render of the page around it, and\n // `{}` is never `===` the `{}` before it. Keyed on identity, both effects below\n // therefore fired on every unrelated parent render: persistence wrote the same JSON\n // into `localStorage` each time — a synchronous main-thread write, on a page whose\n // other half may be a form being typed into — and the URL one handed react-router a\n // `replace` it answers with a render, which is the loop the audit records. A `ref`\n // holding what was last written is what makes \"changed\" mean changed.\n const lastPersisted = useRef<string | null>(null);\n const persisted = !storageKey\n ? \"\"\n : serializePersisted({\n sort: sorts,\n filters,\n pageSize: pageSize === Infinity ? \"all\" : pageSize,\n widths,\n hidden: Array.from(view.hidden),\n ...(view.shown.size > 0 ? { shown: Array.from(view.shown) } : {}),\n });\n useEffect(() => {\n if (!storageKey) return;\n // The destination is part of the signature: two tables can hold identical state,\n // and a table that swaps `storageKey` has to write under the new one even though\n // nothing about the view changed.\n const target = storageKeyPrefix + storageKey;\n const sig = `${target}\\u0000${persisted}`;\n if (lastPersisted.current === sig) return;\n lastPersisted.current = sig;\n writeStored(target, persisted);\n }, [storageKey, storageKeyPrefix, persisted]);\n\n const lastUrl = useRef<string | null>(null);\n const urlSig = urlSync\n ? urlSignature(columns, sorts, filters, pageSize, page, defaultPageSize, keys)\n : \"\";\n useEffect(() => {\n if (!urlSync) return;\n if (lastUrl.current === urlSig) return;\n lastUrl.current = urlSig;\n // `columns` is deliberately not a dependency and does not need to be: the\n // signature above is computed from it, so a column set that would produce a\n // different address produces a different signature first.\n writeUrlState(setSearchParams, columns, sorts, filters, pageSize, page, defaultPageSize, undefined, locationState, keys);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [urlSync, urlSig]);\n\n return {\n sorts,\n filters,\n setInternalSorts,\n setInternalFilters,\n page,\n setPage,\n pageSize,\n setPageSize,\n widths,\n setWidths,\n hiddenCols,\n setHiddenCols,\n showSettings,\n setShowSettings,\n headRefs,\n };\n}\n\n// ---------- One view over several tables ----------\n\n/** What {@link useSharedTableView} keeps. */\nexport interface SharedTableView {\n sorts: SortState[];\n setSorts: (next: SortState[]) => void;\n /** `Infinity` for \"all\", as a table holds it. */\n pageSize: number;\n setPageSize: (next: number) => void;\n}\n\n/**\n * One table view — the sort and the page size — for an owner that draws it as SEVERAL\n * tables over one `storageKey`: `TranslationReviewPanel`'s groups, which sort and page\n * alike (0.35, Kurvenschmiede: its grouped review stored no view at all). Pass them to\n * each table as `sorts`/`onSortsChange` and `pageSize`/`onPageSizeChange`.\n *\n * Kept in the very blob a `DataTable` with that `storageKey` keeps, not in a format of\n * its own, so the single table and the groups read each other's view. What the blob\n * holds besides (filters, column widths, hidden columns) is carried over as stored, for\n * the single table to find as it left it. Read when `active` turns on — on mount, or\n * when the owner switches to this view — and written on a change, never on a read. The\n * prefix resolves as DataTable's does: prop > provider > the kit's.\n */\nexport function useSharedTableView({\n active,\n storageKey,\n storageKeyPrefix,\n defaultPageSize,\n}: {\n active: boolean;\n storageKey?: string;\n storageKeyPrefix?: string;\n defaultPageSize: number;\n}): SharedTableView {\n const providerPrefix = useKitTableStorageKeyPrefix();\n const prefix = storageKeyPrefix ?? providerPrefix ?? DEFAULT_PERSIST_PREFIX;\n const read = () => {\n const stored = active ? loadPersisted(storageKey, prefix) : {};\n return { sorts: normalizeSorts(stored.sort), pageSize: storedPageSize(stored, defaultPageSize) };\n };\n const [view, setView] = useState(read);\n // Adjusted during render when `active` turns on, so the first render of the groups\n // already has the view the single table left, not a frame of the default.\n const [wasActive, setWasActive] = useState(active);\n if (active !== wasActive) {\n setWasActive(active);\n if (active) setView(read());\n }\n const change = (patch: Partial<typeof view>) => {\n const next = { ...view, ...patch };\n setView(next);\n if (!active || !storageKey) return;\n const stored = loadPersisted(storageKey, prefix);\n const blob: PersistedState = {\n sort: next.sorts,\n filters: stored.filters ?? {},\n pageSize: next.pageSize === Infinity ? \"all\" : next.pageSize,\n widths: stored.widths,\n hidden: stored.hidden,\n };\n writeStored(prefix + storageKey, serializePersisted(blob));\n };\n return {\n sorts: view.sorts,\n setSorts: (sorts) => change({ sorts }),\n pageSize: view.pageSize,\n setPageSize: (pageSize) => change({ pageSize }),\n };\n}\n\n// ---------- Owner-held table state, mirrored to the URL ----------\n\nexport interface UseTableUrlStateOptions<T> {\n /** The table's columns — read for their keys and filter types only. */\n columns: DataTableColumn<T>[];\n /** Default 25. A page size equal to it is left out of the URL. */\n defaultPageSize?: number;\n /** Mirror the state into the URL and seed it from there on mount. Default true.\n * `{ prefix }` puts a prefix in front of every key, for a page with two URL-synced\n * tables — see {@link TableUrlSync}. */\n urlSync?: TableUrlSync;\n /** Shorthand for `urlSync: { prefix }`; wins over a prefix given there. */\n urlPrefix?: string;\n /** Also hold a free-text search term, under `q`. Off by default, so a `q` that\n * belongs to someone else on the page is never touched. */\n search?: boolean;\n}\n\n/** The part of {@link DataTableProps} a server-driven table takes from its owner. */\nexport interface TableUrlStateProps {\n serverPagination: ServerPagination;\n filters: FilterState;\n onFiltersChange: (next: FilterState) => void;\n sorts: SortState[];\n onSortsChange: (next: SortState[]) => void;\n}\n\nexport interface UseTableUrlStateReturn {\n /** 0-based. */\n page: number;\n setPage: (page: number) => void;\n pageSize: number;\n /** Also back to the first page. */\n setPageSize: (pageSize: number) => void;\n filters: FilterState;\n /** Also back to the first page. */\n setFilters: (next: FilterState) => void;\n sorts: SortState[];\n /** Also back to the first page. */\n setSorts: (next: SortState[]) => void;\n /** The search term (`\"\"` unless `search: true`). */\n search: string;\n /** Also back to the first page. */\n setSearch: (next: string) => void;\n /**\n * Everything the table needs, to spread onto it:\n * `<DataTable {...table.tableProps(data?.total ?? 0, { isLoading })} rows={…} … />`.\n */\n tableProps: (total: number, options?: { isLoading?: boolean }) => TableUrlStateProps;\n}\n\ninterface OwnedState {\n sorts: SortState[];\n filters: FilterState;\n pageSize: number;\n page: number;\n search: string;\n}\n\n/**\n * The controlled state of a `serverPagination` table — filters, sorts, page, page size\n * and optionally a search term — kept by the OWNER and mirrored into the URL in the\n * table's own scheme (`f.<key>`, `sort`, `p`, `ps`, and `q`).\n *\n * A server-driven table owns none of this: the owner has to hold it to turn it into\n * the query that fetches `rows`, and until now it also had to restate the table's URL\n * codec to keep the view bookmarkable (kastlan's use-server-table.ts copied\n * `readUrlState` / `writeUrlState` to do it). Same rules as `urlSync`:\n *\n * * **The URL is read once, on mount** — a shared or deep link (see {@link filterHref})\n * seeds the view; after that the state is local and written out with `replace`.\n * * **Any change but the page itself goes back to page 1** — a narrower filter must not\n * leave the user on page 7 of 2.\n * * **Written by value**: an unchanged view writes nothing, however often the owner\n * renders.\n *\n * Mapping the state onto the backend's query parameters stays the owner's: that is the\n * part the kit cannot know.\n */\nexport function useTableUrlState<T>({\n columns,\n defaultPageSize = 25,\n urlSync: urlSyncOption = true,\n urlPrefix: urlPrefixOption,\n search: manageSearch = false,\n}: UseTableUrlStateOptions<T>): UseTableUrlStateReturn {\n const resolved = resolveTableUrlSync(urlSyncOption);\n const urlSync = resolved.enabled;\n const urlPrefix = urlPrefixOption ?? resolved.prefix;\n const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);\n const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, \"useTableUrlState\");\n const [state, setState] = useState<OwnedState>(() => {\n const url = urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {};\n return {\n sorts: url.sorts ?? [],\n filters: url.filters ?? {},\n pageSize: url.pageSize ?? defaultPageSize,\n page: url.page ?? 0,\n search: urlSync && manageSearch ? (searchParams.get(keys.search) ?? \"\") : \"\",\n };\n });\n\n const lastUrl = useRef<string | null>(null);\n const urlSig = urlSync\n ? urlSignature(columns, state.sorts, state.filters, state.pageSize, state.page, defaultPageSize, keys) +\n (manageSearch ? `&${keys.search}=${state.search.trim() ? state.search : \"\"}` : \"\")\n : \"\";\n useEffect(() => {\n if (!urlSync) return;\n // First run included: a link that arrives with `p=1` or a default `ps` is\n // normalised to the table's own spelling, as `urlSync` does.\n if (lastUrl.current === urlSig) return;\n lastUrl.current = urlSig;\n writeUrlState(\n setSearchParams,\n columns,\n state.sorts,\n state.filters,\n state.pageSize,\n state.page,\n defaultPageSize,\n manageSearch ? state.search : undefined,\n locationState,\n keys,\n );\n // Keyed on the signature, which is computed from everything the write reads — see\n // the same effect in useTableState.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [urlSync, urlSig]);\n\n const setPage = (page: number) => setState((s) => ({ ...s, page }));\n const setPageSize = (pageSize: number) => setState((s) => ({ ...s, pageSize, page: 0 }));\n const setFilters = (filters: FilterState) => setState((s) => ({ ...s, filters, page: 0 }));\n const setSorts = (sorts: SortState[]) => setState((s) => ({ ...s, sorts, page: 0 }));\n const setSearch = (search: string) => setState((s) => ({ ...s, search, page: 0 }));\n\n return {\n page: state.page,\n setPage,\n pageSize: state.pageSize,\n setPageSize,\n filters: state.filters,\n setFilters,\n sorts: state.sorts,\n setSorts,\n search: state.search,\n setSearch,\n tableProps: (total, options) => ({\n serverPagination: {\n page: state.page,\n pageSize: state.pageSize,\n total,\n onPageChange: setPage,\n onPageSizeChange: setPageSize,\n isLoading: options?.isLoading,\n },\n filters: state.filters,\n onFiltersChange: setFilters,\n sorts: state.sorts,\n onSortsChange: setSorts,\n }),\n };\n}\n"],"mappings":"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","names":["state"]}
1
+ {"version":3,"sources":["../../src/components/use-table-state.ts"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport type { SetStateAction } from \"react\";\nimport * as ReactRouter from \"react-router\";\nimport { useLocation, useSearchParams } from \"react-router\";\nimport { useKitTableStorageKeyPrefix } from \"../i18n/kit-labels\";\nimport { readStored, writeStored } from \"../lib/safe-storage\";\nimport { decodeFilterValue, encodeFilterValue, isFilterActive, resolveFilter } from \"./data-table-filters\";\nimport { decodeSorts, encodeSorts, normalizeSorts, type SortState } from \"./data-table-sort\";\nimport type { DataTableColumn, FilterState, ServerPagination } from \"./data-table\";\n\n// ---------- Persisted + URL-encoded view state ----------\n//\n// Moved here with `useTableState` when `DataTable` was split: these are what the hook\n// reads and writes, and nothing else in the table ever called them.\n\ninterface PersistedState {\n // Multi-sort array; blobs from before the upgrade hold a single {key, dir}\n // object (or null) — read back through normalizeSorts.\n sort: SortState[] | { key: string; dir: string } | null;\n filters: FilterState;\n pageSize: number | \"all\";\n widths?: Record<string, number>;\n hidden?: string[];\n /** 0.48: the `defaultHidden` columns the user ticked back on — an entry that beats the\n * default. Written only when non-empty, so a table without such columns stores what\n * it always did. */\n shown?: string[];\n}\n\n/** The hidden set as drawn: the stored one plus every `defaultHidden` column the view\n * holds no entry for (neither hidden nor `shown`). */\nfunction withDefaultHidden(view: { hidden: Set<string>; shown: Set<string> }, defaults: string[]): Set<string> {\n const extra = defaults.filter((k) => !view.hidden.has(k) && !view.shown.has(k));\n return extra.length === 0 ? view.hidden : new Set([...view.hidden, ...extra]);\n}\n\nexport const DEFAULT_PERSIST_PREFIX = \"hbui-table:\";\n\nfunction loadPersisted(storageKey: string | undefined, prefix: string): Partial<PersistedState> {\n if (!storageKey) return {};\n const raw = readStored(prefix + storageKey);\n if (!raw) return {};\n try {\n const parsed = JSON.parse(raw) as Partial<PersistedState>;\n return parsed && typeof parsed === \"object\" ? parsed : {};\n } catch {\n return {}; // corrupt JSON — the storage access itself is guarded by readStored\n }\n}\n\n/** The blob that would be stored — also the value the effect below compares by, so\n * the comparison and the write can never disagree about what \"unchanged\" means. */\nfunction serializePersisted(state: PersistedState): string {\n return JSON.stringify(state);\n}\n\n/** A stored page size as a table holds it — `\"all\"` is `Infinity` — or `fallback`. */\nfunction storedPageSize(stored: Partial<PersistedState>, fallback: number): number {\n if (stored.pageSize === \"all\") return Infinity;\n if (typeof stored.pageSize === \"number\" && stored.pageSize > 0) return stored.pageSize;\n return fallback;\n}\n\n// ---------- URL sync helpers ----------\n\nconst URL_FILTER_PREFIX = \"f.\";\nconst URL_SORT_KEY = \"sort\";\nconst URL_PAGE_KEY = \"p\";\nconst URL_PAGE_SIZE_KEY = \"ps\";\n/** The free-text search term — managed only by {@link useTableUrlState}, and only when\n * it is asked to (`search: true`); a plain `urlSync` table leaves a `q` alone. */\nconst URL_SEARCH_KEY = \"q\";\n\n/**\n * How a table is mirrored into the URL: `true`, or `{ prefix }` for a table that shares\n * its page with another URL-synced one. The prefix is put verbatim in front of EVERY key\n * the table writes and reads — `{ prefix: \"inv.\" }` gives `inv.f.<column>`, `inv.sort`,\n * `inv.p`, `inv.ps` (and `inv.q`) — so two tables on one page stop sharing a sort and a\n * page. Without one the keys are unprefixed, as they always were.\n */\nexport type TableUrlSync = boolean | { prefix?: string };\n\n/** The query keys of one table, with its prefix applied. */\ninterface UrlKeys {\n filter: string;\n sort: string;\n page: string;\n pageSize: string;\n search: string;\n}\n\nfunction urlKeys(prefix = \"\"): UrlKeys {\n return {\n filter: prefix + URL_FILTER_PREFIX,\n sort: prefix + URL_SORT_KEY,\n page: prefix + URL_PAGE_KEY,\n pageSize: prefix + URL_PAGE_SIZE_KEY,\n search: prefix + URL_SEARCH_KEY,\n };\n}\n\n/** What a table reads and writes the URL through: react-router's, or none. */\ninterface TableSearch {\n searchParams: URLSearchParams;\n setSearchParams: ReturnType<typeof useSearchParams>[1];\n locationState: unknown;\n}\n\nfunction useRouterSearch(): TableSearch {\n const [searchParams, setSearchParams] = useSearchParams();\n const location = useLocation();\n return { searchParams, setSearchParams, locationState: location.state };\n}\n\nconst NO_ROUTER_SEARCH: TableSearch = {\n searchParams: new URLSearchParams(),\n setSearchParams: () => {},\n locationState: null,\n};\nfunction useNoRouterSearch(): TableSearch {\n return NO_ROUTER_SEARCH;\n}\n\n/**\n * `useInRouterContext`, read off the namespace inside a `try` like\n * use-search-param-state's probe: a test's `vi.mock(\"react-router\", () => ({ … }))`\n * without it throws on the read. Missing, a table assumes a Router, as before 0.49.\n */\nfunction readInRouterContext(): () => boolean {\n try {\n return (ReactRouter as unknown as { useInRouterContext?: () => boolean }).useInRouterContext ?? (() => true);\n } catch {\n return () => true;\n }\n}\nconst useInRouterContext = readInRouterContext();\n\n/**\n * The URL access a table needs (0.49, KIT-3). Without `urlSync` a table needs no Router:\n * before 0.49 it called `useSearchParams()` unconditionally and threw outside one even\n * with URL sync off. With `urlSync` and no Router, a development build throws a message\n * that names the fix; a production build keeps the view in local state rather than\n * crash the page. Whether a Router is around does not change during a table's life, so\n * which hook runs is stable from render to render.\n */\nfunction useTableSearch(urlSync: boolean, owner: string): TableSearch {\n const inRouter = useInRouterContext();\n if (!inRouter && urlSync && import.meta.env?.DEV) {\n throw new Error(\n `${owner} urlSync requires a Router: render it inside a react-router Router ` +\n \"(BrowserRouter, a data router or a MemoryRouter), or leave urlSync off.\",\n );\n }\n const useSearch = inRouter ? useRouterSearch : useNoRouterSearch;\n return useSearch();\n}\n\n/** `urlSync` split into \"is it on\" and \"under which prefix\". */\nfunction resolveTableUrlSync(urlSync: TableUrlSync | undefined): { enabled: boolean; prefix: string } {\n if (!urlSync) return { enabled: false, prefix: \"\" };\n return { enabled: true, prefix: urlSync === true ? \"\" : (urlSync.prefix ?? \"\") };\n}\n\n/** What {@link readTableUrlState} found in a query string. */\nexport interface TableUrlState {\n sorts?: SortState[];\n filters?: FilterState;\n /** `Infinity` for `ps=all`. */\n pageSize?: number;\n /** 0-based (the URL's `p` is 1-based). */\n page?: number;\n}\n\n/**\n * What a table's URL says, read out of a query string: the column filters (`f.<key>`),\n * the sort, the page size and the 0-based page. Absent keys are absent here too, so a\n * caller can tell \"not in the link\" from \"the default\". The same reader `urlSync` uses\n * on mount, exported for an owner that keeps the state itself — see\n * {@link useTableUrlState}.\n *\n * Pass the ROUTER's query string — the one `setSearchParams` writes — and the table's\n * `prefix` when it has one (see {@link TableUrlSync}).\n *\n * This read `window.location.search` until 0.7.0, which is the same string only under a\n * BrowserRouter. Under a HashRouter the router's query lives after the `#`\n * (`/#/tx?f.payee=Rewe`) and `window.location.search` is empty: a shared link opened\n * unfiltered, and the table's first write then replaced the link's filter with its own\n * empty state. Reading and writing through `useSearchParams` is one source for both\n * directions under every router, MemoryRouter included.\n */\nexport function readTableUrlState<T>(\n sp: URLSearchParams,\n columns: DataTableColumn<T>[],\n prefix?: string,\n): TableUrlState {\n const keys = urlKeys(prefix);\n const out: TableUrlState = {};\n\n const filters: FilterState = {};\n for (const col of columns) {\n const filter = resolveFilter(col);\n if (!filter) continue;\n const raw = sp.get(keys.filter + col.key);\n if (raw == null) continue;\n filters[col.key] = decodeFilterValue(filter, raw);\n }\n if (Object.keys(filters).length) out.filters = filters;\n\n const sortRaw = sp.get(keys.sort);\n if (sortRaw) {\n const decoded = decodeSorts(sortRaw, new Set(columns.map((c) => c.key)));\n if (decoded.length) out.sorts = decoded;\n }\n\n const psRaw = sp.get(keys.pageSize);\n if (psRaw === \"all\") out.pageSize = Infinity;\n else if (psRaw && Number(psRaw) > 0) out.pageSize = Number(psRaw);\n\n const pRaw = sp.get(keys.page);\n if (pRaw && Number(pRaw) >= 1) out.page = Number(pRaw) - 1;\n\n return out;\n}\n\n/**\n * The managed part of the query string, as a string, for comparing by VALUE.\n *\n * Everything the table puts in the address and nothing else, in a fixed order.\n * `columns` is walked for its keys only, which is why it can be left out of the\n * effect's dependencies without lying: a new column ARRAY carrying the same keys\n * produces the same signature, and a genuinely different set of filterable columns\n * produces a different one.\n */\nfunction urlSignature<T>(\n columns: DataTableColumn<T>[],\n sorts: SortState[],\n filters: FilterState,\n pageSize: number,\n page: number,\n defaultPageSize: number,\n keys: UrlKeys,\n): string {\n const parts: string[] = [];\n for (const col of columns) {\n const state = filters[col.key];\n if (!state || !isFilterActive(state)) continue;\n const enc = encodeFilterValue(state);\n if (enc != null) parts.push(`${keys.filter}${col.key}=${enc}`);\n }\n parts.push(`${keys.sort}=${encodeSorts(sorts)}`);\n parts.push(\n `${keys.pageSize}=${pageSize === Infinity ? \"all\" : pageSize === defaultPageSize ? \"\" : String(pageSize)}`,\n );\n parts.push(`${keys.page}=${page > 0 ? String(page + 1) : \"\"}`);\n return parts.join(\"&\");\n}\n\nfunction writeUrlState<T>(\n setSearchParams: ReturnType<typeof useSearchParams>[1],\n columns: DataTableColumn<T>[],\n sorts: SortState[],\n filters: FilterState,\n pageSize: number,\n page: number,\n defaultPageSize: number,\n // `undefined`: the search term is not ours to manage, and a `q` is left as it is.\n search: string | undefined,\n // The current entry's route state, carried over the replace. A query rewrite is not\n // a navigation, and dropping the state would, for one, lose the mark a URL-bound\n // dialog's opener leaves there (see useDialogParam).\n state: unknown,\n keys: UrlKeys,\n): void {\n setSearchParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n // clear managed keys\n for (const col of columns) next.delete(keys.filter + col.key);\n next.delete(keys.sort);\n next.delete(keys.page);\n next.delete(keys.pageSize);\n if (search !== undefined) {\n next.delete(keys.search);\n if (search.trim()) next.set(keys.search, search);\n }\n // write filters\n for (const col of columns) {\n const state = filters[col.key];\n if (!state || !isFilterActive(state)) continue;\n const enc = encodeFilterValue(state);\n if (enc != null) next.set(keys.filter + col.key, enc);\n }\n // write sort\n const sortEnc = encodeSorts(sorts);\n if (sortEnc) next.set(keys.sort, sortEnc);\n // write page size if non-default\n if (pageSize === Infinity) next.set(keys.pageSize, \"all\");\n else if (pageSize !== defaultPageSize) next.set(keys.pageSize, String(pageSize));\n // write page if not first\n if (page > 0) next.set(keys.page, String(page + 1));\n return next;\n },\n { replace: true, state },\n );\n}\n\n/**\n * Where a table's view lives: what it is sorted and filtered by, which page, how wide\n * the columns are and which are hidden — plus the two places that outlives a render.\n *\n * Lifted out of `DataTable`, which was 984 lines and twenty hooks in one function. This\n * is the part that is genuinely about STATE rather than about drawing, and the three\n * rules it encodes are easy to lose among the markup:\n *\n * * **The URL is read once, on mount.** A shared or deep link populates the initial view;\n * after that the state is local, or the browser's own history would fight the user's\n * next click.\n * * **Sorting and filtering are optionally CONTROLLED.** A server-driven table passes\n * state and a callback and owns them; everything else is self-owned. That is the\n * `sortsProp ?? internalSorts` pair, and it is why the setters are exposed separately.\n * * **Persistence writes on every change, the URL only when asked.** `storageKey` is the\n * per-table identity; `urlSync` is opt-in because only some tables want their view in\n * a shareable address.\n * * **A controlled filter, sort or page size that changes sends the table back to page\n * 1.** See the note at the reset below.\n */\nexport function useTableState<T>({\n columns,\n storageKey,\n storageKeyPrefix,\n urlSync: urlSyncOption,\n defaultPageSize,\n sortsProp,\n filtersProp,\n pageSizeProp,\n}: {\n columns: DataTableColumn<T>[];\n storageKey?: string;\n storageKeyPrefix: string;\n urlSync?: TableUrlSync;\n defaultPageSize: number;\n sortsProp?: SortState[];\n filtersProp?: FilterState;\n pageSizeProp?: number;\n}) {\n const { enabled: urlSync, prefix: urlPrefix } = resolveTableUrlSync(urlSyncOption);\n const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);\n // The SAME pair is used to read and to write; see `readTableUrlState`. No Router is\n // needed without `urlSync` (0.49, KIT-3; see `useTableSearch`).\n const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, \"DataTable\");\n // Read URL once on mount so URL-encoded views (e.g. shared links, deep links from\n // other pages) populate initial state. After mount, state is local.\n const [urlInitial] = useState<TableUrlState>(() =>\n urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {},\n );\n const initial = useMemo(() => loadPersisted(storageKey, storageKeyPrefix), [storageKey, storageKeyPrefix]);\n const [internalSorts, setInternalSorts] = useState<SortState[]>(() =>\n urlInitial.sorts !== undefined ? urlInitial.sorts : normalizeSorts(initial.sort),\n );\n const [internalFilters, setInternalFilters] = useState<FilterState>(\n () => urlInitial.filters ?? initial.filters ?? {},\n );\n // Controlled when the owner passes state + callback (server-driven tables);\n // self-owned otherwise.\n const sorts = sortsProp ?? internalSorts;\n const filters = filtersProp ?? internalFilters;\n const [page, setPage] = useState(() => urlInitial.page ?? 0);\n\n // ---- Controlled filters/sorts reset the page ----\n //\n // A self-owned table resets to page 1 in the filter handlers. A CONTROLLED one could\n // not: the handlers hand the change to the owner and return, and `page` lives here,\n // where the owner has no way to reach it — so narrowing a filter from outside (a\n // saved view, a chip dismissed above the table) left a client-side table on page 4\n // of what was now two pages, clamped to the last one instead of the first.\n //\n // So the table does it: whenever the controlled VALUE changes, back to the first\n // page. Compared by value, because a controlled prop is a fresh object on every\n // parent render. Adjusted during render rather than in an effect, so the stale page\n // is never painted and never written to the URL. Not on mount: a deep link's `p=3`\n // must survive the owner's first render. (In `serverPagination` mode this state is\n // unused — the owner's `page` is the page, and resetting it stays the owner's job.)\n // A controlled page size counts too: the groups of `TranslationReviewPanel` share one,\n // and a group paged deep into its rows starts over when another group's pager changes it.\n const controlledSig =\n sortsProp !== undefined || filtersProp !== undefined || pageSizeProp !== undefined\n ? JSON.stringify([sortsProp ?? null, filtersProp ?? null, String(pageSizeProp)])\n : null;\n const [seenControlledSig, setSeenControlledSig] = useState(controlledSig);\n if (seenControlledSig !== controlledSig) {\n setSeenControlledSig(controlledSig);\n setPage(0);\n }\n const [internalPageSize, setPageSize] = useState<number>(() =>\n urlInitial.pageSize !== undefined ? urlInitial.pageSize : storedPageSize(initial, defaultPageSize),\n );\n const pageSize = pageSizeProp ?? internalPageSize;\n const [widths, setWidths] = useState<Record<string, number>>(() => initial.widths ?? {});\n // Hidden columns (0.48, keksdose #617): what the user chose — `hidden`, and `shown`\n // for a `defaultHidden` column ticked back on — kept apart from the defaults, which\n // are applied on read and never stored. Keyed on the default keys as a string: every\n // call site builds `columns` inline, so the array is new on every render.\n const [view, setView] = useState(() => ({\n hidden: new Set<string>(initial.hidden ?? []),\n shown: new Set<string>(initial.shown ?? []),\n }));\n const defaultHiddenSig = columns\n .filter((c) => c.defaultHidden)\n .map((c) => c.key)\n .join(\"\\u0000\");\n const hiddenCols = useMemo(\n () => withDefaultHidden(view, defaultHiddenSig ? defaultHiddenSig.split(\"\\u0000\") : []),\n [view, defaultHiddenSig],\n );\n // The panel hands over the next DRAWN set; what changed is recorded as a choice.\n const setHiddenCols = useCallback(\n (action: SetStateAction<Set<string>>) =>\n setView((v) => {\n const defaults = defaultHiddenSig ? defaultHiddenSig.split(\"\\u0000\") : [];\n const prev = withDefaultHidden(v, defaults);\n const next = typeof action === \"function\" ? action(prev) : action;\n const hidden = new Set(v.hidden);\n const shown = new Set(v.shown);\n for (const k of next) {\n if (prev.has(k)) continue;\n hidden.add(k);\n shown.delete(k);\n }\n for (const k of prev) {\n if (next.has(k)) continue;\n hidden.delete(k);\n if (defaults.includes(k)) shown.add(k);\n }\n return { hidden, shown };\n }),\n [defaultHiddenSig],\n );\n const [showSettings, setShowSettings] = useState(false);\n const headRefs = useMemo(() => new Map<string, HTMLTableCellElement>(), []);\n\n // ---- Writing out: both of these compare by VALUE ----\n //\n // A CONTROLLED table (`sorts`/`filters` props plus callbacks — every server-driven\n // one) receives a freshly-built object on every render of the page around it, and\n // `{}` is never `===` the `{}` before it. Keyed on identity, both effects below\n // therefore fired on every unrelated parent render: persistence wrote the same JSON\n // into `localStorage` each time — a synchronous main-thread write, on a page whose\n // other half may be a form being typed into — and the URL one handed react-router a\n // `replace` it answers with a render, which is the loop the audit records. A `ref`\n // holding what was last written is what makes \"changed\" mean changed.\n const lastPersisted = useRef<string | null>(null);\n const persisted = !storageKey\n ? \"\"\n : serializePersisted({\n sort: sorts,\n filters,\n pageSize: pageSize === Infinity ? \"all\" : pageSize,\n widths,\n hidden: Array.from(view.hidden),\n ...(view.shown.size > 0 ? { shown: Array.from(view.shown) } : {}),\n });\n useEffect(() => {\n if (!storageKey) return;\n // The destination is part of the signature: two tables can hold identical state,\n // and a table that swaps `storageKey` has to write under the new one even though\n // nothing about the view changed.\n const target = storageKeyPrefix + storageKey;\n const sig = `${target}\\u0000${persisted}`;\n if (lastPersisted.current === sig) return;\n lastPersisted.current = sig;\n writeStored(target, persisted);\n }, [storageKey, storageKeyPrefix, persisted]);\n\n const lastUrl = useRef<string | null>(null);\n const urlSig = urlSync ? urlSignature(columns, sorts, filters, pageSize, page, defaultPageSize, keys) : \"\";\n useEffect(() => {\n if (!urlSync) return;\n if (lastUrl.current === urlSig) return;\n lastUrl.current = urlSig;\n // `columns` is deliberately not a dependency and does not need to be: the\n // signature above is computed from it, so a column set that would produce a\n // different address produces a different signature first.\n writeUrlState(\n setSearchParams,\n columns,\n sorts,\n filters,\n pageSize,\n page,\n defaultPageSize,\n undefined,\n locationState,\n keys,\n );\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [urlSync, urlSig]);\n\n return {\n sorts,\n filters,\n setInternalSorts,\n setInternalFilters,\n page,\n setPage,\n pageSize,\n setPageSize,\n widths,\n setWidths,\n hiddenCols,\n setHiddenCols,\n showSettings,\n setShowSettings,\n headRefs,\n };\n}\n\n// ---------- One view over several tables ----------\n\n/** What {@link useSharedTableView} keeps. */\nexport interface SharedTableView {\n sorts: SortState[];\n setSorts: (next: SortState[]) => void;\n /** `Infinity` for \"all\", as a table holds it. */\n pageSize: number;\n setPageSize: (next: number) => void;\n}\n\n/**\n * One table view — the sort and the page size — for an owner that draws it as SEVERAL\n * tables over one `storageKey`: `TranslationReviewPanel`'s groups, which sort and page\n * alike (0.35, Kurvenschmiede: its grouped review stored no view at all). Pass them to\n * each table as `sorts`/`onSortsChange` and `pageSize`/`onPageSizeChange`.\n *\n * Kept in the very blob a `DataTable` with that `storageKey` keeps, not in a format of\n * its own, so the single table and the groups read each other's view. What the blob\n * holds besides (filters, column widths, hidden columns) is carried over as stored, for\n * the single table to find as it left it. Read when `active` turns on — on mount, or\n * when the owner switches to this view — and written on a change, never on a read. The\n * prefix resolves as DataTable's does: prop > provider > the kit's.\n */\nexport function useSharedTableView({\n active,\n storageKey,\n storageKeyPrefix,\n defaultPageSize,\n}: {\n active: boolean;\n storageKey?: string;\n storageKeyPrefix?: string;\n defaultPageSize: number;\n}): SharedTableView {\n const providerPrefix = useKitTableStorageKeyPrefix();\n const prefix = storageKeyPrefix ?? providerPrefix ?? DEFAULT_PERSIST_PREFIX;\n const read = () => {\n const stored = active ? loadPersisted(storageKey, prefix) : {};\n return { sorts: normalizeSorts(stored.sort), pageSize: storedPageSize(stored, defaultPageSize) };\n };\n const [view, setView] = useState(read);\n // Adjusted during render when `active` turns on, so the first render of the groups\n // already has the view the single table left, not a frame of the default.\n const [wasActive, setWasActive] = useState(active);\n if (active !== wasActive) {\n setWasActive(active);\n if (active) setView(read());\n }\n const change = (patch: Partial<typeof view>) => {\n const next = { ...view, ...patch };\n setView(next);\n if (!active || !storageKey) return;\n const stored = loadPersisted(storageKey, prefix);\n const blob: PersistedState = {\n sort: next.sorts,\n filters: stored.filters ?? {},\n pageSize: next.pageSize === Infinity ? \"all\" : next.pageSize,\n widths: stored.widths,\n hidden: stored.hidden,\n };\n writeStored(prefix + storageKey, serializePersisted(blob));\n };\n return {\n sorts: view.sorts,\n setSorts: (sorts) => change({ sorts }),\n pageSize: view.pageSize,\n setPageSize: (pageSize) => change({ pageSize }),\n };\n}\n\n// ---------- Owner-held table state, mirrored to the URL ----------\n\nexport interface UseTableUrlStateOptions<T> {\n /** The table's columns — read for their keys and filter types only. */\n columns: DataTableColumn<T>[];\n /** Default 25. A page size equal to it is left out of the URL. */\n defaultPageSize?: number;\n /** Mirror the state into the URL and seed it from there on mount. Default true.\n * `{ prefix }` puts a prefix in front of every key, for a page with two URL-synced\n * tables — see {@link TableUrlSync}. */\n urlSync?: TableUrlSync;\n /** Shorthand for `urlSync: { prefix }`; wins over a prefix given there. */\n urlPrefix?: string;\n /** Also hold a free-text search term, under `q`. Off by default, so a `q` that\n * belongs to someone else on the page is never touched. */\n search?: boolean;\n}\n\n/** The part of {@link DataTableProps} a server-driven table takes from its owner. */\nexport interface TableUrlStateProps {\n serverPagination: ServerPagination;\n filters: FilterState;\n onFiltersChange: (next: FilterState) => void;\n sorts: SortState[];\n onSortsChange: (next: SortState[]) => void;\n}\n\nexport interface UseTableUrlStateReturn {\n /** 0-based. */\n page: number;\n setPage: (page: number) => void;\n pageSize: number;\n /** Also back to the first page. */\n setPageSize: (pageSize: number) => void;\n filters: FilterState;\n /** Also back to the first page. */\n setFilters: (next: FilterState) => void;\n sorts: SortState[];\n /** Also back to the first page. */\n setSorts: (next: SortState[]) => void;\n /** The search term (`\"\"` unless `search: true`). */\n search: string;\n /** Also back to the first page. */\n setSearch: (next: string) => void;\n /**\n * Everything the table needs, to spread onto it:\n * `<DataTable {...table.tableProps(data?.total ?? 0, { isLoading })} rows={…} … />`.\n */\n tableProps: (total: number, options?: { isLoading?: boolean }) => TableUrlStateProps;\n}\n\ninterface OwnedState {\n sorts: SortState[];\n filters: FilterState;\n pageSize: number;\n page: number;\n search: string;\n}\n\n/**\n * The controlled state of a `serverPagination` table — filters, sorts, page, page size\n * and optionally a search term — kept by the OWNER and mirrored into the URL in the\n * table's own scheme (`f.<key>`, `sort`, `p`, `ps`, and `q`).\n *\n * A server-driven table owns none of this: the owner has to hold it to turn it into\n * the query that fetches `rows`, and until now it also had to restate the table's URL\n * codec to keep the view bookmarkable (kastlan's use-server-table.ts copied\n * `readUrlState` / `writeUrlState` to do it). Same rules as `urlSync`:\n *\n * * **The URL is read once, on mount** — a shared or deep link (see {@link filterHref})\n * seeds the view; after that the state is local and written out with `replace`.\n * * **Any change but the page itself goes back to page 1** — a narrower filter must not\n * leave the user on page 7 of 2.\n * * **Written by value**: an unchanged view writes nothing, however often the owner\n * renders.\n *\n * Mapping the state onto the backend's query parameters stays the owner's: that is the\n * part the kit cannot know.\n */\nexport function useTableUrlState<T>({\n columns,\n defaultPageSize = 25,\n urlSync: urlSyncOption = true,\n urlPrefix: urlPrefixOption,\n search: manageSearch = false,\n}: UseTableUrlStateOptions<T>): UseTableUrlStateReturn {\n const resolved = resolveTableUrlSync(urlSyncOption);\n const urlSync = resolved.enabled;\n const urlPrefix = urlPrefixOption ?? resolved.prefix;\n const keys = useMemo(() => urlKeys(urlPrefix), [urlPrefix]);\n const { searchParams, setSearchParams, locationState } = useTableSearch(urlSync, \"useTableUrlState\");\n const [state, setState] = useState<OwnedState>(() => {\n const url = urlSync ? readTableUrlState(searchParams, columns, urlPrefix) : {};\n return {\n sorts: url.sorts ?? [],\n filters: url.filters ?? {},\n pageSize: url.pageSize ?? defaultPageSize,\n page: url.page ?? 0,\n search: urlSync && manageSearch ? (searchParams.get(keys.search) ?? \"\") : \"\",\n };\n });\n\n const lastUrl = useRef<string | null>(null);\n const urlSig = urlSync\n ? urlSignature(columns, state.sorts, state.filters, state.pageSize, state.page, defaultPageSize, keys) +\n (manageSearch ? `&${keys.search}=${state.search.trim() ? state.search : \"\"}` : \"\")\n : \"\";\n useEffect(() => {\n if (!urlSync) return;\n // First run included: a link that arrives with `p=1` or a default `ps` is\n // normalised to the table's own spelling, as `urlSync` does.\n if (lastUrl.current === urlSig) return;\n lastUrl.current = urlSig;\n writeUrlState(\n setSearchParams,\n columns,\n state.sorts,\n state.filters,\n state.pageSize,\n state.page,\n defaultPageSize,\n manageSearch ? state.search : undefined,\n locationState,\n keys,\n );\n // Keyed on the signature, which is computed from everything the write reads — see\n // the same effect in useTableState.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [urlSync, urlSig]);\n\n const setPage = (page: number) => setState((s) => ({ ...s, page }));\n const setPageSize = (pageSize: number) => setState((s) => ({ ...s, pageSize, page: 0 }));\n const setFilters = (filters: FilterState) => setState((s) => ({ ...s, filters, page: 0 }));\n const setSorts = (sorts: SortState[]) => setState((s) => ({ ...s, sorts, page: 0 }));\n const setSearch = (search: string) => setState((s) => ({ ...s, search, page: 0 }));\n\n return {\n page: state.page,\n setPage,\n pageSize: state.pageSize,\n setPageSize,\n filters: state.filters,\n setFilters,\n sorts: state.sorts,\n setSorts,\n search: state.search,\n setSearch,\n tableProps: (total, options) => ({\n serverPagination: {\n page: state.page,\n pageSize: state.pageSize,\n total,\n onPageChange: setPage,\n onPageSizeChange: setPageSize,\n isLoading: options?.isLoading,\n },\n filters: state.filters,\n onFiltersChange: setFilters,\n sorts: state.sorts,\n onSortsChange: setSorts,\n }),\n };\n}\n"],"mappings":"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","names":["state"]}
@@ -22,27 +22,8 @@ function UserAvatar({ name, email, person, size = "md", badge, className, ...res
22
22
  const initials = (person ? personInitials(person, locale) : "") || avatarInitials(name, email);
23
23
  if (badge) {
24
24
  return /* @__PURE__ */ jsxs("span", { ...rest, className: "relative inline-flex shrink-0", children: [
25
- /* @__PURE__ */ jsx(
26
- "span",
27
- {
28
- "aria-hidden": true,
29
- className: cn(
30
- CHIP,
31
- AVATAR_SIZES[size],
32
- className
33
- ),
34
- children: initials
35
- }
36
- ),
37
- /* @__PURE__ */ jsx(
38
- StatusDot,
39
- {
40
- ring: true,
41
- tone: badge.tone ?? "danger",
42
- size: BADGE_SIZES[size],
43
- className: "absolute -end-0.5 -top-0.5"
44
- }
45
- ),
25
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn(CHIP, AVATAR_SIZES[size], className), children: initials }),
26
+ /* @__PURE__ */ jsx(StatusDot, { ring: true, tone: badge.tone ?? "danger", size: BADGE_SIZES[size], className: "absolute -end-0.5 -top-0.5" }),
46
27
  /* @__PURE__ */ jsx("span", { className: "sr-only", children: badge.label })
47
28
  ] });
48
29
  }
@@ -51,11 +32,7 @@ function UserAvatar({ name, email, person, size = "md", badge, className, ...res
51
32
  {
52
33
  "aria-hidden": true,
53
34
  ...rest,
54
- className: cn(
55
- CHIP,
56
- AVATAR_SIZES[size],
57
- className
58
- ),
35
+ className: cn(CHIP, AVATAR_SIZES[size], className),
59
36
  children: initials
60
37
  }
61
38
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/components/user-avatar.tsx"],"sourcesContent":["import type { ComponentPropsWithRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { personInitials } from \"../lib/person-name\";\nimport type { PersonName } from \"../lib/person-name\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { StatusDot } from \"./status-dot\";\nimport type { StatusDotSize, StatusDotTone } from \"./status-dot\";\n\n/** Initials from a display name (first + last) or, failing that, an email —\n * e.g. \"Marcel Eifert\" → \"ME\", \"marcel@x.com\" → \"MA\". App-agnostic.\n *\n * \"Failing that\" means BLANK, not merely absent. This was `name ?? email ?? \"?\"`,\n * and `??` falls through on null/undefined alone — so an empty or whitespace-only\n * display name short-circuited the email it had in hand and landed on the guard\n * below, returning \"?\" for a user whose address says \"MA\" perfectly well. Keksdose\n * cannot reach it (its `display_name` has `min_length=1`), but this is exported as\n * app-agnostic, and any consumer whose profile name is optional free text got a wall\n * of \"?\" chips. The guard is what shows the empty case was foreseen; it just\n * answered it at the wrong end. */\nexport function avatarInitials(name?: string | null, email?: string | null): string {\n const source = [name, email].map((s) => s?.trim()).find(Boolean) ?? \"?\";\n const parts = source.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return source.slice(0, 2).toUpperCase();\n}\n\nconst AVATAR_SIZES = {\n sm: \"size-7 text-caption\",\n md: \"size-8 text-xs\",\n lg: \"size-12 text-base\",\n} as const;\n\nconst CHIP =\n \"inline-flex shrink-0 items-center justify-center rounded-full bg-inverse font-semibold text-inverse\";\n\n/** The dot grows with the avatar: 8px on `sm`, 10px on `md` (keksdose's), 12px on `lg`. */\nconst BADGE_SIZES: Record<keyof typeof AVATAR_SIZES, StatusDotSize> = { sm: \"sm\", md: \"md\", lg: \"lg\" };\n\n/** A status dot in the avatar's top corner — an unread count, presence. */\nexport interface UserAvatarBadge {\n /** What the dot MEANS, for a screen reader (\"3 unread\"). Required: a coloured circle\n * says nothing to someone who cannot see it. */\n label: ReactNode;\n /** Default `danger`, the unread dot both apps draw by hand. */\n tone?: StatusDotTone;\n}\n\n/** A `<span>`'s props, minus `children`: the content is the initials this computes\n * from `person`/`name`/`email`, so there is nothing for a caller to put inside. */\nexport interface UserAvatarProps extends Omit<ComponentPropsWithRef<\"span\">, \"children\"> {\n name?: string | null;\n email?: string | null;\n /**\n * The person's first and last name (0.29.0, docs/auth-harmonization.md §3.2) —\n * preferred over `name` whenever either part is filled in. The initials follow the\n * READER's language, the provider's `locale`: \"AE\" for Ada Example, \"EA\" in Hungarian\n * (family name first), the family name's first character alone in Chinese. See\n * {@link personInitials}.\n *\n * `name` stays the fallback for an API that still sends only `display_name`, and\n * `email` after it: a person whose two parts are both blank falls through to them.\n * A migrated user (the whole old display name in `first`, no `last`, §3.3) keeps the\n * initials they had.\n */\n person?: PersonName | null;\n size?: keyof typeof AVATAR_SIZES;\n /**\n * A status dot in the top-end corner (the top-right in LTR, top-left in RTL), ringed\n * in the surface colour so it reads as its own mark (keksdose account-menu ~385,\n * top-bar :152). Its `label` is read as text where the dot sits — inside a button\n * named by its content it joins that name. In `TopBarActionMenu`'s `trigger` the\n * button is named by the menu's `aria-label` followed by this label: \"Account menu\n * 3 unread\". The initials stay hidden either way, so the person's name is NOT part\n * of it — put it in `aria-label` (\"Marcel Eifert, account menu\") if it should be.\n */\n badge?: UserAvatarBadge | null;\n}\n\n/**\n * A round initials avatar — the common user chip shared across apps (feedback\n * #333). Purely presentational; wrap it in a button for the account menu trigger.\n */\nexport function UserAvatar({ name, email, person, size = \"md\", badge, className, ...rest }: UserAvatarProps) {\n const locale = useKitLocale();\n const initials = (person ? personInitials(person, locale) : \"\") || avatarInitials(name, email);\n if (badge) {\n // A wrapper, because the chip itself is `aria-hidden` and the badge label must not\n // be. `className` stays on the chip (a size override still sizes the circle); the\n // other attributes go on the wrapper, which is what a caller addresses. `relative`\n // also contains the sr-only text.\n return (\n <span {...rest} className=\"relative inline-flex shrink-0\">\n <span\n aria-hidden\n className={cn(\n CHIP,\n AVATAR_SIZES[size],\n className,\n )}\n >\n {initials}\n </span>\n <StatusDot\n ring\n tone={badge.tone ?? \"danger\"}\n size={BADGE_SIZES[size]}\n className=\"absolute -end-0.5 -top-0.5\"\n />\n <span className=\"sr-only\">{badge.label}</span>\n </span>\n );\n }\n return (\n <span\n // `aria-hidden` sits BEFORE the spread, unlike the structural attributes on the\n // other components here, because it is a DEFAULT rather than an invariant. The\n // avatar is decorative next to the name it belongs to — which is where all three\n // apps render it — so hiding it stops a screen reader saying \"ME\" after the word\n // \"Marcel Eifert\". A caller who renders one ALONE (an assignee column, a\n // presence dot) needs the opposite, and now has it: `aria-hidden={false}` plus an\n // `aria-label` arrives through `...rest` and wins.\n aria-hidden\n {...rest}\n className={cn(\n CHIP,\n AVATAR_SIZES[size],\n className,\n )}\n >\n {initials}\n </span>\n );\n}\n"],"mappings":";AA2FM,SACE,KADF;AA1FN,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAE/B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAcnB,SAAS,eAAe,MAAsB,OAA+B;AAClF,QAAM,SAAS,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,OAAO,KAAK;AACpE,QAAM,QAAQ,OAAO,MAAM,KAAK,EAAE,OAAO,OAAO;AAChD,MAAI,MAAM,UAAU,EAAG,SAAQ,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY;AACtE,SAAO,OAAO,MAAM,GAAG,CAAC,EAAE,YAAY;AACxC;AAEA,MAAM,eAAe;AAAA,EACnB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,OACJ;AAGF,MAAM,cAAgE,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AA8C9F,SAAS,WAAW,EAAE,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,WAAW,GAAG,KAAK,GAAoB;AAC3G,QAAM,SAAS,aAAa;AAC5B,QAAM,YAAY,SAAS,eAAe,QAAQ,MAAM,IAAI,OAAO,eAAe,MAAM,KAAK;AAC7F,MAAI,OAAO;AAKT,WACE,qBAAC,UAAM,GAAG,MAAM,WAAU,iCACxB;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,eAAW;AAAA,UACX,WAAW;AAAA,YACT;AAAA,YACA,aAAa,IAAI;AAAA,YACjB;AAAA,UACF;AAAA,UAEC;AAAA;AAAA,MACH;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAI;AAAA,UACJ,MAAM,MAAM,QAAQ;AAAA,UACpB,MAAM,YAAY,IAAI;AAAA,UACtB,WAAU;AAAA;AAAA,MACZ;AAAA,MACA,oBAAC,UAAK,WAAU,WAAW,gBAAM,OAAM;AAAA,OACzC;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MAQC,eAAW;AAAA,MACV,GAAG;AAAA,MACJ,WAAW;AAAA,QACT;AAAA,QACA,aAAa,IAAI;AAAA,QACjB;AAAA,MACF;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/components/user-avatar.tsx"],"sourcesContent":["import type { ComponentPropsWithRef, ReactNode } from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { personInitials } from \"../lib/person-name\";\nimport type { PersonName } from \"../lib/person-name\";\nimport { useKitLocale } from \"../i18n/kit-labels\";\nimport { StatusDot } from \"./status-dot\";\nimport type { StatusDotSize, StatusDotTone } from \"./status-dot\";\n\n/** Initials from a display name (first + last) or, failing that, an email —\n * e.g. \"Marcel Eifert\" → \"ME\", \"marcel@x.com\" → \"MA\". App-agnostic.\n *\n * \"Failing that\" means BLANK, not merely absent. This was `name ?? email ?? \"?\"`,\n * and `??` falls through on null/undefined alone — so an empty or whitespace-only\n * display name short-circuited the email it had in hand and landed on the guard\n * below, returning \"?\" for a user whose address says \"MA\" perfectly well. Keksdose\n * cannot reach it (its `display_name` has `min_length=1`), but this is exported as\n * app-agnostic, and any consumer whose profile name is optional free text got a wall\n * of \"?\" chips. The guard is what shows the empty case was foreseen; it just\n * answered it at the wrong end. */\nexport function avatarInitials(name?: string | null, email?: string | null): string {\n const source = [name, email].map((s) => s?.trim()).find(Boolean) ?? \"?\";\n const parts = source.split(/\\s+/).filter(Boolean);\n if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();\n return source.slice(0, 2).toUpperCase();\n}\n\nconst AVATAR_SIZES = {\n sm: \"size-7 text-caption\",\n md: \"size-8 text-xs\",\n lg: \"size-12 text-base\",\n} as const;\n\nconst CHIP = \"inline-flex shrink-0 items-center justify-center rounded-full bg-inverse font-semibold text-inverse\";\n\n/** The dot grows with the avatar: 8px on `sm`, 10px on `md` (keksdose's), 12px on `lg`. */\nconst BADGE_SIZES: Record<keyof typeof AVATAR_SIZES, StatusDotSize> = { sm: \"sm\", md: \"md\", lg: \"lg\" };\n\n/** A status dot in the avatar's top corner — an unread count, presence. */\nexport interface UserAvatarBadge {\n /** What the dot MEANS, for a screen reader (\"3 unread\"). Required: a coloured circle\n * says nothing to someone who cannot see it. */\n label: ReactNode;\n /** Default `danger`, the unread dot both apps draw by hand. */\n tone?: StatusDotTone;\n}\n\n/** A `<span>`'s props, minus `children`: the content is the initials this computes\n * from `person`/`name`/`email`, so there is nothing for a caller to put inside. */\nexport interface UserAvatarProps extends Omit<ComponentPropsWithRef<\"span\">, \"children\"> {\n name?: string | null;\n email?: string | null;\n /**\n * The person's first and last name (0.29.0, docs/auth-harmonization.md §3.2) —\n * preferred over `name` whenever either part is filled in. The initials follow the\n * READER's language, the provider's `locale`: \"AE\" for Ada Example, \"EA\" in Hungarian\n * (family name first), the family name's first character alone in Chinese. See\n * {@link personInitials}.\n *\n * `name` stays the fallback for an API that still sends only `display_name`, and\n * `email` after it: a person whose two parts are both blank falls through to them.\n * A migrated user (the whole old display name in `first`, no `last`, §3.3) keeps the\n * initials they had.\n */\n person?: PersonName | null;\n size?: keyof typeof AVATAR_SIZES;\n /**\n * A status dot in the top-end corner (the top-right in LTR, top-left in RTL), ringed\n * in the surface colour so it reads as its own mark (keksdose account-menu ~385,\n * top-bar :152). Its `label` is read as text where the dot sits — inside a button\n * named by its content it joins that name. In `TopBarActionMenu`'s `trigger` the\n * button is named by the menu's `aria-label` followed by this label: \"Account menu\n * 3 unread\". The initials stay hidden either way, so the person's name is NOT part\n * of it — put it in `aria-label` (\"Marcel Eifert, account menu\") if it should be.\n */\n badge?: UserAvatarBadge | null;\n}\n\n/**\n * A round initials avatar — the common user chip shared across apps (feedback\n * #333). Purely presentational; wrap it in a button for the account menu trigger.\n */\nexport function UserAvatar({ name, email, person, size = \"md\", badge, className, ...rest }: UserAvatarProps) {\n const locale = useKitLocale();\n const initials = (person ? personInitials(person, locale) : \"\") || avatarInitials(name, email);\n if (badge) {\n // A wrapper, because the chip itself is `aria-hidden` and the badge label must not\n // be. `className` stays on the chip (a size override still sizes the circle); the\n // other attributes go on the wrapper, which is what a caller addresses. `relative`\n // also contains the sr-only text.\n return (\n <span {...rest} className=\"relative inline-flex shrink-0\">\n <span aria-hidden className={cn(CHIP, AVATAR_SIZES[size], className)}>\n {initials}\n </span>\n <StatusDot ring tone={badge.tone ?? \"danger\"} size={BADGE_SIZES[size]} className=\"absolute -end-0.5 -top-0.5\" />\n <span className=\"sr-only\">{badge.label}</span>\n </span>\n );\n }\n return (\n <span\n // `aria-hidden` sits BEFORE the spread, unlike the structural attributes on the\n // other components here, because it is a DEFAULT rather than an invariant. The\n // avatar is decorative next to the name it belongs to — which is where all three\n // apps render it — so hiding it stops a screen reader saying \"ME\" after the word\n // \"Marcel Eifert\". A caller who renders one ALONE (an assignee column, a\n // presence dot) needs the opposite, and now has it: `aria-hidden={false}` plus an\n // `aria-label` arrives through `...rest` and wins.\n aria-hidden\n {...rest}\n className={cn(CHIP, AVATAR_SIZES[size], className)}\n >\n {initials}\n </span>\n );\n}\n"],"mappings":";AA0FM,SACE,KADF;AAzFN,SAAS,UAAU;AACnB,SAAS,sBAAsB;AAE/B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAcnB,SAAS,eAAe,MAAsB,OAA+B;AAClF,QAAM,SAAS,CAAC,MAAM,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,EAAE,KAAK,OAAO,KAAK;AACpE,QAAM,QAAQ,OAAO,MAAM,KAAK,EAAE,OAAO,OAAO;AAChD,MAAI,MAAM,UAAU,EAAG,SAAQ,MAAM,CAAC,EAAE,CAAC,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,YAAY;AACtE,SAAO,OAAO,MAAM,GAAG,CAAC,EAAE,YAAY;AACxC;AAEA,MAAM,eAAe;AAAA,EACnB,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,MAAM,OAAO;AAGb,MAAM,cAAgE,EAAE,IAAI,MAAM,IAAI,MAAM,IAAI,KAAK;AA8C9F,SAAS,WAAW,EAAE,MAAM,OAAO,QAAQ,OAAO,MAAM,OAAO,WAAW,GAAG,KAAK,GAAoB;AAC3G,QAAM,SAAS,aAAa;AAC5B,QAAM,YAAY,SAAS,eAAe,QAAQ,MAAM,IAAI,OAAO,eAAe,MAAM,KAAK;AAC7F,MAAI,OAAO;AAKT,WACE,qBAAC,UAAM,GAAG,MAAM,WAAU,iCACxB;AAAA,0BAAC,UAAK,eAAW,MAAC,WAAW,GAAG,MAAM,aAAa,IAAI,GAAG,SAAS,GAChE,oBACH;AAAA,MACA,oBAAC,aAAU,MAAI,MAAC,MAAM,MAAM,QAAQ,UAAU,MAAM,YAAY,IAAI,GAAG,WAAU,8BAA6B;AAAA,MAC9G,oBAAC,UAAK,WAAU,WAAW,gBAAM,OAAM;AAAA,OACzC;AAAA,EAEJ;AACA,SACE;AAAA,IAAC;AAAA;AAAA,MAQC,eAAW;AAAA,MACV,GAAG;AAAA,MACJ,WAAW,GAAG,MAAM,aAAa,IAAI,GAAG,SAAS;AAAA,MAEhD;AAAA;AAAA,EACH;AAEJ;","names":[]}
@@ -48,10 +48,14 @@ function DemoStart({
48
48
  const accessLink = useAccessAction(access, landingLabels);
49
49
  const [refusal, setRefusal] = useState(null);
50
50
  useNoIndex(noIndex);
51
- useOncePerKey(ONCE, () => start(), (_key, result) => {
52
- if (result.ok) onStarted(result.value);
53
- else setRefusal(refusalOf(result.error));
54
- });
51
+ useOncePerKey(
52
+ ONCE,
53
+ () => start(),
54
+ (_key, result) => {
55
+ if (result.ok) onStarted(result.value);
56
+ else setRefusal(refusalOf(result.error));
57
+ }
58
+ );
55
59
  return /* @__PURE__ */ jsxs("div", { ...rest, className: cn("flex flex-col items-center gap-4 py-4 text-center", className), children: [
56
60
  /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "w-full", children: refusal === null ? /* @__PURE__ */ jsxs("p", { className: "flex items-center justify-center gap-2 text-sm text-secondary", children: [
57
61
  /* @__PURE__ */ jsx(Spinner, { label: null, className: "size-5" }),
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/demo/demo-start.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"../lib/cn\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Spinner } from \"../components/spinner\";\nimport { useNoIndex } from \"../hooks/use-noindex\";\nimport { authErrorCode, isRateLimited, retryAfterSeconds } from \"../auth/auth-errors\";\nimport { useOncePerKey } from \"../auth/status-parts\";\nimport { useAccessAction } from \"../landing/access\";\nimport type { AccessChoice } from \"../landing/access\";\nimport type { LandingLabels } from \"../landing/landing-labels\";\nimport { useDemoLabels } from \"./demo-labels\";\nimport type { DemoLabels } from \"./demo-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\n\n/** Why a start was refused, in the page's terms (§5.2). */\ntype Refusal =\n | { reason: \"rateLimited\"; minutes?: number }\n | { reason: \"capacity\" }\n | { reason: \"unavailable\" }\n | { reason: \"failed\" };\n\n/**\n * The server's answer to `POST /auth/demo-session`, read as a refusal (§5.1, §6.2):\n * `demo_rate_limited` (429, with `Retry-After`), `demo_capacity` (429, without),\n * `demo_disabled` (404) and `demo_not_ready` (503) by their codes; a bare 429 as the\n * per-IP window too; anything else is a plain failure.\n */\nfunction refusalOf(error: unknown): Refusal {\n const code = authErrorCode(error);\n if (code === \"demo_capacity\") return { reason: \"capacity\" };\n if (code === \"demo_disabled\" || code === \"demo_not_ready\") return { reason: \"unavailable\" };\n if (code === \"demo_rate_limited\" || (code === undefined && isRateLimited(error))) {\n const seconds = retryAfterSeconds(error);\n return { reason: \"rateLimited\", minutes: seconds ? Math.ceil(seconds / 60) : undefined };\n }\n return { reason: \"failed\" };\n}\n\nfunction messageOf(refusal: Refusal, labels: DemoLabels): string {\n switch (refusal.reason) {\n case \"rateLimited\":\n return labels.rateLimited(refusal.minutes);\n case \"capacity\":\n return labels.capacity;\n case \"unavailable\":\n return labels.unavailable;\n case \"failed\":\n return labels.failed;\n }\n}\n\n/** The one start a mount makes. */\nconst ONCE = \"demo\";\n\nexport interface DemoStartProps<T> extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n /**\n * Create the demo: the app's `POST /auth/demo-session {locale}`. Resolve with what it\n * answered (the `TokenResponse`, `refresh_token: null`, `expires_at`); reject with the\n * client's error — the codes are read out of it. Called once per mount.\n */\n start: () => Promise<T>;\n /** The demo exists: store the session (and, for kastlan, `kastlan.demoSession`) and\n * replace to the app's home. The page keeps saying \"Starting the demo…\" meanwhile. */\n onStarted: (session: T) => void;\n /** \"Request access\" beside a refusal. */\n access: AccessChoice;\n /** \"Back to the start page\". Default `/`. */\n backHref?: string;\n /** `false` adds no `noindex` — for a preview. Default `true`: `/demo` is never indexed\n * (§4.3). */\n noIndex?: boolean;\n labels?: LabelOverride<DemoLabels>;\n /** The access words (`landing` namespace) for this instance. */\n landingLabels?: Partial<LandingLabels>;\n}\n\n/**\n * The `/demo` page's content (docs/landing-demo-harmonization.md §5.2): it starts the\n * demo ONCE — StrictMode's double mount included, keksdose's ref guard — and says\n * \"Starting the demo…\" until the app has the session and has moved on.\n *\n * A refusal says why, in the visitor's words, and offers \"Request access\" and \"Back to\n * the start page\":\n *\n * | code | says |\n * |---|---|\n * | `demo_rate_limited` (429) | \"Too many demos from this network. Try again in N min.\" — N from `Retry-After` |\n * | `demo_capacity` (429) | \"The demo is full right now. Try again later.\" |\n * | `demo_disabled` (404), `demo_not_ready` (503) | \"The demo isn't available right now.\" |\n * | anything else | \"The demo could not be started. Please try again.\" |\n *\n * keksdose fell back to `/register` with a toast; registration is invitation-only now,\n * so the way on is the access mail.\n *\n * Put it in the app's `AuthLayout`. A live demo session never reaches it: the `/demo`\n * route sits in `RedirectIfAuthed` WITHOUT `allowDemo`, which continues the live demo at\n * its resume target instead of starting a second one. A cold start is the kit's\n * `ServerWakeNotice`'s, once the app's watcher counts this POST\n * (`watchReadsAnd(/\\/auth\\/demo-session\\b/)`).\n */\nexport function DemoStart<T>({\n start,\n onStarted,\n access,\n backHref = \"/\",\n noIndex = true,\n labels: labelsProp,\n landingLabels,\n className,\n ...rest\n}: DemoStartProps<T>) {\n const labels = useDemoLabels(labelsProp);\n const accessLink = useAccessAction(access, landingLabels);\n const [refusal, setRefusal] = useState<Refusal | null>(null);\n useNoIndex(noIndex);\n useOncePerKey<T>(ONCE, () => start(), (_key, result) => {\n if (result.ok) onStarted(result.value);\n else setRefusal(refusalOf(result.error));\n });\n\n return (\n <div {...rest} className={cn(\"flex flex-col items-center gap-4 py-4 text-center\", className)}>\n {/* One live region, there from the start, so the refusal that replaces the\n progress line is announced (a region inserted with its text often is not). */}\n <div role=\"status\" aria-live=\"polite\" className=\"w-full\">\n {refusal === null ? (\n <p className=\"flex items-center justify-center gap-2 text-sm text-secondary\">\n <Spinner label={null} className=\"size-5\" />\n {labels.starting}\n </p>\n ) : (\n <AlertBanner tone={refusal.reason === \"failed\" ? \"danger\" : \"warning\"} className=\"text-start\">\n {messageOf(refusal, labels)}\n </AlertBanner>\n )}\n </div>\n {refusal !== null && (\n <div className=\"flex flex-wrap justify-center gap-3\">\n <Button href={accessLink.href} variant=\"accent\">\n {accessLink.label}\n </Button>\n <Button href={backHref} variant=\"secondary\">\n {labels.backToStart}\n </Button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";AAiIU,SACE,KADF;AAjIV,SAAS,gBAAgB;AAGzB,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,eAAe,eAAe,yBAAyB;AAChE,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAGhC,SAAS,qBAAqB;AAiB9B,SAAS,UAAU,OAAyB;AAC1C,QAAM,OAAO,cAAc,KAAK;AAChC,MAAI,SAAS,gBAAiB,QAAO,EAAE,QAAQ,WAAW;AAC1D,MAAI,SAAS,mBAAmB,SAAS,iBAAkB,QAAO,EAAE,QAAQ,cAAc;AAC1F,MAAI,SAAS,uBAAwB,SAAS,UAAa,cAAc,KAAK,GAAI;AAChF,UAAM,UAAU,kBAAkB,KAAK;AACvC,WAAO,EAAE,QAAQ,eAAe,SAAS,UAAU,KAAK,KAAK,UAAU,EAAE,IAAI,OAAU;AAAA,EACzF;AACA,SAAO,EAAE,QAAQ,SAAS;AAC5B;AAEA,SAAS,UAAU,SAAkB,QAA4B;AAC/D,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO,OAAO,YAAY,QAAQ,OAAO;AAAA,IAC3C,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,EAClB;AACF;AAGA,MAAM,OAAO;AAgDN,SAAS,UAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,SAAS,cAAc,UAAU;AACvC,QAAM,aAAa,gBAAgB,QAAQ,aAAa;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAyB,IAAI;AAC3D,aAAW,OAAO;AAClB,gBAAiB,MAAM,MAAM,MAAM,GAAG,CAAC,MAAM,WAAW;AACtD,QAAI,OAAO,GAAI,WAAU,OAAO,KAAK;AAAA,QAChC,YAAW,UAAU,OAAO,KAAK,CAAC;AAAA,EACzC,CAAC;AAED,SACE,qBAAC,SAAK,GAAG,MAAM,WAAW,GAAG,qDAAqD,SAAS,GAGzF;AAAA,wBAAC,SAAI,MAAK,UAAS,aAAU,UAAS,WAAU,UAC7C,sBAAY,OACX,qBAAC,OAAE,WAAU,iEACX;AAAA,0BAAC,WAAQ,OAAO,MAAM,WAAU,UAAS;AAAA,MACxC,OAAO;AAAA,OACV,IAEA,oBAAC,eAAY,MAAM,QAAQ,WAAW,WAAW,WAAW,WAAW,WAAU,cAC9E,oBAAU,SAAS,MAAM,GAC5B,GAEJ;AAAA,IACC,YAAY,QACX,qBAAC,SAAI,WAAU,uCACb;AAAA,0BAAC,UAAO,MAAM,WAAW,MAAM,SAAQ,UACpC,qBAAW,OACd;AAAA,MACA,oBAAC,UAAO,MAAM,UAAU,SAAQ,aAC7B,iBAAO,aACV;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../src/demo/demo-start.tsx"],"sourcesContent":["import { useState } from \"react\";\nimport type { ComponentPropsWithRef } from \"react\";\n\nimport { cn } from \"../lib/cn\";\nimport { AlertBanner } from \"../components/alert-banner\";\nimport { Button } from \"../components/button\";\nimport { Spinner } from \"../components/spinner\";\nimport { useNoIndex } from \"../hooks/use-noindex\";\nimport { authErrorCode, isRateLimited, retryAfterSeconds } from \"../auth/auth-errors\";\nimport { useOncePerKey } from \"../auth/status-parts\";\nimport { useAccessAction } from \"../landing/access\";\nimport type { AccessChoice } from \"../landing/access\";\nimport type { LandingLabels } from \"../landing/landing-labels\";\nimport { useDemoLabels } from \"./demo-labels\";\nimport type { DemoLabels } from \"./demo-labels\";\nimport type { LabelOverride } from \"../i18n/kit-labels\";\n\n/** Why a start was refused, in the page's terms (§5.2). */\ntype Refusal =\n | { reason: \"rateLimited\"; minutes?: number }\n | { reason: \"capacity\" }\n | { reason: \"unavailable\" }\n | { reason: \"failed\" };\n\n/**\n * The server's answer to `POST /auth/demo-session`, read as a refusal (§5.1, §6.2):\n * `demo_rate_limited` (429, with `Retry-After`), `demo_capacity` (429, without),\n * `demo_disabled` (404) and `demo_not_ready` (503) by their codes; a bare 429 as the\n * per-IP window too; anything else is a plain failure.\n */\nfunction refusalOf(error: unknown): Refusal {\n const code = authErrorCode(error);\n if (code === \"demo_capacity\") return { reason: \"capacity\" };\n if (code === \"demo_disabled\" || code === \"demo_not_ready\") return { reason: \"unavailable\" };\n if (code === \"demo_rate_limited\" || (code === undefined && isRateLimited(error))) {\n const seconds = retryAfterSeconds(error);\n return { reason: \"rateLimited\", minutes: seconds ? Math.ceil(seconds / 60) : undefined };\n }\n return { reason: \"failed\" };\n}\n\nfunction messageOf(refusal: Refusal, labels: DemoLabels): string {\n switch (refusal.reason) {\n case \"rateLimited\":\n return labels.rateLimited(refusal.minutes);\n case \"capacity\":\n return labels.capacity;\n case \"unavailable\":\n return labels.unavailable;\n case \"failed\":\n return labels.failed;\n }\n}\n\n/** The one start a mount makes. */\nconst ONCE = \"demo\";\n\nexport interface DemoStartProps<T> extends Omit<ComponentPropsWithRef<\"div\">, \"children\"> {\n /**\n * Create the demo: the app's `POST /auth/demo-session {locale}`. Resolve with what it\n * answered (the `TokenResponse`, `refresh_token: null`, `expires_at`); reject with the\n * client's error — the codes are read out of it. Called once per mount.\n */\n start: () => Promise<T>;\n /** The demo exists: store the session (and, for kastlan, `kastlan.demoSession`) and\n * replace to the app's home. The page keeps saying \"Starting the demo…\" meanwhile. */\n onStarted: (session: T) => void;\n /** \"Request access\" beside a refusal. */\n access: AccessChoice;\n /** \"Back to the start page\". Default `/`. */\n backHref?: string;\n /** `false` adds no `noindex` — for a preview. Default `true`: `/demo` is never indexed\n * (§4.3). */\n noIndex?: boolean;\n labels?: LabelOverride<DemoLabels>;\n /** The access words (`landing` namespace) for this instance. */\n landingLabels?: Partial<LandingLabels>;\n}\n\n/**\n * The `/demo` page's content (docs/landing-demo-harmonization.md §5.2): it starts the\n * demo ONCE — StrictMode's double mount included, keksdose's ref guard — and says\n * \"Starting the demo…\" until the app has the session and has moved on.\n *\n * A refusal says why, in the visitor's words, and offers \"Request access\" and \"Back to\n * the start page\":\n *\n * | code | says |\n * |---|---|\n * | `demo_rate_limited` (429) | \"Too many demos from this network. Try again in N min.\" — N from `Retry-After` |\n * | `demo_capacity` (429) | \"The demo is full right now. Try again later.\" |\n * | `demo_disabled` (404), `demo_not_ready` (503) | \"The demo isn't available right now.\" |\n * | anything else | \"The demo could not be started. Please try again.\" |\n *\n * keksdose fell back to `/register` with a toast; registration is invitation-only now,\n * so the way on is the access mail.\n *\n * Put it in the app's `AuthLayout`. A live demo session never reaches it: the `/demo`\n * route sits in `RedirectIfAuthed` WITHOUT `allowDemo`, which continues the live demo at\n * its resume target instead of starting a second one. A cold start is the kit's\n * `ServerWakeNotice`'s, once the app's watcher counts this POST\n * (`watchReadsAnd(/\\/auth\\/demo-session\\b/)`).\n */\nexport function DemoStart<T>({\n start,\n onStarted,\n access,\n backHref = \"/\",\n noIndex = true,\n labels: labelsProp,\n landingLabels,\n className,\n ...rest\n}: DemoStartProps<T>) {\n const labels = useDemoLabels(labelsProp);\n const accessLink = useAccessAction(access, landingLabels);\n const [refusal, setRefusal] = useState<Refusal | null>(null);\n useNoIndex(noIndex);\n useOncePerKey<T>(\n ONCE,\n () => start(),\n (_key, result) => {\n if (result.ok) onStarted(result.value);\n else setRefusal(refusalOf(result.error));\n },\n );\n\n return (\n <div {...rest} className={cn(\"flex flex-col items-center gap-4 py-4 text-center\", className)}>\n {/* One live region, there from the start, so the refusal that replaces the\n progress line is announced (a region inserted with its text often is not). */}\n <div role=\"status\" aria-live=\"polite\" className=\"w-full\">\n {refusal === null ? (\n <p className=\"flex items-center justify-center gap-2 text-sm text-secondary\">\n <Spinner label={null} className=\"size-5\" />\n {labels.starting}\n </p>\n ) : (\n <AlertBanner tone={refusal.reason === \"failed\" ? \"danger\" : \"warning\"} className=\"text-start\">\n {messageOf(refusal, labels)}\n </AlertBanner>\n )}\n </div>\n {refusal !== null && (\n <div className=\"flex flex-wrap justify-center gap-3\">\n <Button href={accessLink.href} variant=\"accent\">\n {accessLink.label}\n </Button>\n <Button href={backHref} variant=\"secondary\">\n {labels.backToStart}\n </Button>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";AAqIU,SACE,KADF;AArIV,SAAS,gBAAgB;AAGzB,SAAS,UAAU;AACnB,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AACvB,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAC3B,SAAS,eAAe,eAAe,yBAAyB;AAChE,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAGhC,SAAS,qBAAqB;AAiB9B,SAAS,UAAU,OAAyB;AAC1C,QAAM,OAAO,cAAc,KAAK;AAChC,MAAI,SAAS,gBAAiB,QAAO,EAAE,QAAQ,WAAW;AAC1D,MAAI,SAAS,mBAAmB,SAAS,iBAAkB,QAAO,EAAE,QAAQ,cAAc;AAC1F,MAAI,SAAS,uBAAwB,SAAS,UAAa,cAAc,KAAK,GAAI;AAChF,UAAM,UAAU,kBAAkB,KAAK;AACvC,WAAO,EAAE,QAAQ,eAAe,SAAS,UAAU,KAAK,KAAK,UAAU,EAAE,IAAI,OAAU;AAAA,EACzF;AACA,SAAO,EAAE,QAAQ,SAAS;AAC5B;AAEA,SAAS,UAAU,SAAkB,QAA4B;AAC/D,UAAQ,QAAQ,QAAQ;AAAA,IACtB,KAAK;AACH,aAAO,OAAO,YAAY,QAAQ,OAAO;AAAA,IAC3C,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,IAChB,KAAK;AACH,aAAO,OAAO;AAAA,EAClB;AACF;AAGA,MAAM,OAAO;AAgDN,SAAS,UAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,UAAU;AAAA,EACV,QAAQ;AAAA,EACR;AAAA,EACA;AAAA,EACA,GAAG;AACL,GAAsB;AACpB,QAAM,SAAS,cAAc,UAAU;AACvC,QAAM,aAAa,gBAAgB,QAAQ,aAAa;AACxD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAyB,IAAI;AAC3D,aAAW,OAAO;AAClB;AAAA,IACE;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,CAAC,MAAM,WAAW;AAChB,UAAI,OAAO,GAAI,WAAU,OAAO,KAAK;AAAA,UAChC,YAAW,UAAU,OAAO,KAAK,CAAC;AAAA,IACzC;AAAA,EACF;AAEA,SACE,qBAAC,SAAK,GAAG,MAAM,WAAW,GAAG,qDAAqD,SAAS,GAGzF;AAAA,wBAAC,SAAI,MAAK,UAAS,aAAU,UAAS,WAAU,UAC7C,sBAAY,OACX,qBAAC,OAAE,WAAU,iEACX;AAAA,0BAAC,WAAQ,OAAO,MAAM,WAAU,UAAS;AAAA,MACxC,OAAO;AAAA,OACV,IAEA,oBAAC,eAAY,MAAM,QAAQ,WAAW,WAAW,WAAW,WAAW,WAAU,cAC9E,oBAAU,SAAS,MAAM,GAC5B,GAEJ;AAAA,IACC,YAAY,QACX,qBAAC,SAAI,WAAU,uCACb;AAAA,0BAAC,UAAO,MAAM,WAAW,MAAM,SAAQ,UACpC,qBAAW,OACd;AAAA,MACA,oBAAC,UAAO,MAAM,UAAU,SAAQ,aAC7B,iBAAO,aACV;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
@@ -47,7 +47,7 @@ const roleUtilities = {
47
47
  };
48
48
  }
49
49
  };
50
- const VERSION = true ? "0.51.4" : "0.0.0-source";
50
+ const VERSION = true ? "0.51.6" : "0.0.0-source";
51
51
  const recommended = {
52
52
  name: "@eifi1/ui-kit/recommended",
53
53
  // Not the tests, as check:tokens skips the kit's: a test names the old class to assert it
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/feedback/feedback-attachment-labels.ts"],"sourcesContent":["// The English words of ./feedback-attachment, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component, so `en`\n// loads as one chunk like the other languages (0.34, KS U8). ./feedback-attachment\n// re-exports them.\n\nimport type { FeedbackAttachmentFieldLabels } from \"./feedback-attachment\";\n\n/**\n * English, in the feedback contract's wording since 0.27.0 (docs/feedback-harmonization.md\n * §4.2: keksdose's `feedback.*` keys are the canon, and the kit owns them now). Three\n * strings moved with it:\n *\n * - `attachmentAdd` \"Attach image\" → **\"Add attachment\"**: the field takes a PDF or a text\n * log as well wherever the app passes `FEEDBACK_ATTACHMENT_ACCEPT` (keksdose feedback\n * #87), and a button that says \"image\" over a picker that offers a PDF is wrong in one\n * of the two places.\n * - `attachmentPaste` → keksdose's longer line, which says WHY one would paste — a region\n * snip shows one corner rather than the whole page (Steering Design feedback #39).\n * - `attachmentLimit` for one: \"remove **it**\", as keksdose's `attachments_full_one`;\n * \"remove one\" of a single file read oddly.\n */\nexport const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {\n attachmentAdd: \"Add attachment\",\n attachmentCapture: \"Capture screenshot\",\n attachmentPaste: \"…or paste a screenshot straight from the clipboard, so you can show one corner rather than the whole page.\",\n attachmentRemove: \"Remove attachment\",\n attachmentList: \"Attachments\",\n attachmentScreenshot: \"Screenshot\",\n attachmentRemoveFile: (name) => `Remove ${name}`,\n attachmentLimit: (max) =>\n max === 1\n ? \"Up to 1 attachment — remove it to add another.\"\n : `Up to ${max} attachments — remove one to add another.`,\n attachmentUploading: \"Uploading…\",\n attachmentUploadFailed: (name) => `“${name}” could not be uploaded`,\n};\n"],"mappings":";AAqBO,MAAM,qCAAoE;AAAA,EAC/E,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,sBAAsB,CAAC,SAAS,UAAU,IAAI;AAAA,EAC9C,iBAAiB,CAAC,QAChB,QAAQ,IACJ,wDACA,SAAS,GAAG;AAAA,EAClB,qBAAqB;AAAA,EACrB,wBAAwB,CAAC,SAAS,SAAI,IAAI;AAC5C;","names":[]}
1
+ {"version":3,"sources":["../../src/feedback/feedback-attachment-labels.ts"],"sourcesContent":["// The English words of ./feedback-attachment, in a module with no component code: the\n// English catalogue (src/i18n/defaults.ts) imports them without the component, so `en`\n// loads as one chunk like the other languages (0.34, KS U8). ./feedback-attachment\n// re-exports them.\n\nimport type { FeedbackAttachmentFieldLabels } from \"./feedback-attachment\";\n\n/**\n * English, in the feedback contract's wording since 0.27.0 (docs/feedback-harmonization.md\n * §4.2: keksdose's `feedback.*` keys are the canon, and the kit owns them now). Three\n * strings moved with it:\n *\n * - `attachmentAdd` \"Attach image\" → **\"Add attachment\"**: the field takes a PDF or a text\n * log as well wherever the app passes `FEEDBACK_ATTACHMENT_ACCEPT` (keksdose feedback\n * #87), and a button that says \"image\" over a picker that offers a PDF is wrong in one\n * of the two places.\n * - `attachmentPaste` → keksdose's longer line, which says WHY one would paste — a region\n * snip shows one corner rather than the whole page (Steering Design feedback #39).\n * - `attachmentLimit` for one: \"remove **it**\", as keksdose's `attachments_full_one`;\n * \"remove one\" of a single file read oddly.\n */\nexport const DEFAULT_FEEDBACK_ATTACHMENT_LABELS: FeedbackAttachmentFieldLabels = {\n attachmentAdd: \"Add attachment\",\n attachmentCapture: \"Capture screenshot\",\n attachmentPaste:\n \"…or paste a screenshot straight from the clipboard, so you can show one corner rather than the whole page.\",\n attachmentRemove: \"Remove attachment\",\n attachmentList: \"Attachments\",\n attachmentScreenshot: \"Screenshot\",\n attachmentRemoveFile: (name) => `Remove ${name}`,\n attachmentLimit: (max) =>\n max === 1\n ? \"Up to 1 attachment — remove it to add another.\"\n : `Up to ${max} attachments — remove one to add another.`,\n attachmentUploading: \"Uploading…\",\n attachmentUploadFailed: (name) => `“${name}” could not be uploaded`,\n};\n"],"mappings":";AAqBO,MAAM,qCAAoE;AAAA,EAC/E,eAAe;AAAA,EACf,mBAAmB;AAAA,EACnB,iBACE;AAAA,EACF,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,sBAAsB;AAAA,EACtB,sBAAsB,CAAC,SAAS,UAAU,IAAI;AAAA,EAC9C,iBAAiB,CAAC,QAChB,QAAQ,IACJ,wDACA,SAAS,GAAG;AAAA,EAClB,qBAAqB;AAAA,EACrB,wBAAwB,CAAC,SAAS,SAAI,IAAI;AAC5C;","names":[]}
@@ -76,14 +76,7 @@ function SingleField({
76
76
  children: [
77
77
  /* @__PURE__ */ jsx(Heading, { text: labelsProp?.attachment }),
78
78
  value ? /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
79
- value.type.startsWith("image/") && preview ? /* @__PURE__ */ jsx(
80
- "img",
81
- {
82
- src: preview,
83
- alt: value.name,
84
- className: "h-20 w-20 rounded border border-subtle object-cover"
85
- }
86
- ) : (
79
+ value.type.startsWith("image/") && preview ? /* @__PURE__ */ jsx("img", { src: preview, alt: value.name, className: "h-20 w-20 rounded border border-subtle object-cover" }) : (
87
80
  // Non-image attachments (PDF, text) can't preview as an <img>, so
88
81
  // show a neutral file tile with the name/size beside it instead.
89
82
  /* @__PURE__ */ jsx("div", { className: "flex h-20 w-20 items-center justify-center rounded border border-subtle text-placeholder", children: /* @__PURE__ */ jsx(FileText, { className: "size-8" }) })