@eifi1/ui-kit 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (902) hide show
  1. package/README.md +96 -82
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/admin/plan-change.js +2 -2
  5. package/dist/admin/plan-change.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +6 -0
  7. package/dist/admin/user-roster.js +4 -0
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/auth-errors.d.ts +36 -0
  10. package/dist/auth/auth-errors.js +20 -0
  11. package/dist/auth/auth-errors.js.map +1 -1
  12. package/dist/billing/billing-labels.d.ts +26 -0
  13. package/dist/billing/billing-labels.js +9 -0
  14. package/dist/billing/billing-labels.js.map +1 -1
  15. package/dist/billing/subscription-standing.d.ts +61 -0
  16. package/dist/billing/subscription-standing.js +51 -0
  17. package/dist/billing/subscription-standing.js.map +1 -0
  18. package/dist/billing/usage-meter.d.ts +52 -0
  19. package/dist/billing/usage-meter.js +52 -0
  20. package/dist/billing/usage-meter.js.map +1 -0
  21. package/dist/chart.d.ts +1 -0
  22. package/dist/chart.js +4 -0
  23. package/dist/chart.js.map +1 -1
  24. package/dist/components/alert-banner.js +2 -3
  25. package/dist/components/alert-banner.js.map +1 -1
  26. package/dist/components/amount-input.d.ts +2 -1
  27. package/dist/components/amount-input.js +3 -15
  28. package/dist/components/amount-input.js.map +1 -1
  29. package/dist/components/autocomplete.js +2 -2
  30. package/dist/components/autocomplete.js.map +1 -1
  31. package/dist/components/bulk-action-bar.js +2 -1
  32. package/dist/components/bulk-action-bar.js.map +1 -1
  33. package/dist/components/button.d.ts +35 -1
  34. package/dist/components/button.js +5 -9
  35. package/dist/components/button.js.map +1 -1
  36. package/dist/components/calendar-heatmap.js +6 -4
  37. package/dist/components/calendar-heatmap.js.map +1 -1
  38. package/dist/components/camera-button.d.ts +89 -0
  39. package/dist/components/camera-button.js +122 -0
  40. package/dist/components/camera-button.js.map +1 -0
  41. package/dist/components/camera-capture.d.ts +66 -0
  42. package/dist/components/camera-capture.js +190 -0
  43. package/dist/components/camera-capture.js.map +1 -0
  44. package/dist/components/camera-labels.d.ts +20 -0
  45. package/dist/components/camera-labels.js +15 -0
  46. package/dist/components/camera-labels.js.map +1 -0
  47. package/dist/components/chart-edge.d.ts +9 -0
  48. package/dist/components/chart-edge.js +14 -3
  49. package/dist/components/chart-edge.js.map +1 -1
  50. package/dist/components/chart-zoom-core.js +34 -29
  51. package/dist/components/chart-zoom-core.js.map +1 -1
  52. package/dist/components/checkbox-group.js +1 -1
  53. package/dist/components/checkbox-group.js.map +1 -1
  54. package/dist/components/combobox-core.js +3 -2
  55. package/dist/components/combobox-core.js.map +1 -1
  56. package/dist/components/combobox.js +2 -2
  57. package/dist/components/combobox.js.map +1 -1
  58. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  59. package/dist/components/consent-again-notice-labels.js +15 -0
  60. package/dist/components/consent-again-notice-labels.js.map +1 -0
  61. package/dist/components/consent-again-notice.d.ts +85 -0
  62. package/dist/components/consent-again-notice.js +94 -0
  63. package/dist/components/consent-again-notice.js.map +1 -0
  64. package/dist/components/country-select.js +2 -7
  65. package/dist/components/country-select.js.map +1 -1
  66. package/dist/components/currency-option-list.d.ts +2 -1
  67. package/dist/components/currency-option-list.js +10 -3
  68. package/dist/components/currency-option-list.js.map +1 -1
  69. package/dist/components/currency-select.d.ts +3 -1
  70. package/dist/components/currency-select.js +2 -2
  71. package/dist/components/currency-select.js.map +1 -1
  72. package/dist/components/danger-confirm.d.ts +22 -1
  73. package/dist/components/danger-confirm.js +52 -30
  74. package/dist/components/danger-confirm.js.map +1 -1
  75. package/dist/components/data-table-column-settings.js +2 -2
  76. package/dist/components/data-table-column-settings.js.map +1 -1
  77. package/dist/components/data-table-filter-popover.d.ts +2 -1
  78. package/dist/components/data-table-filter-popover.js +3 -2
  79. package/dist/components/data-table-filter-popover.js.map +1 -1
  80. package/dist/components/data-table-filters.d.ts +33 -3
  81. package/dist/components/data-table-filters.js +6 -4
  82. package/dist/components/data-table-filters.js.map +1 -1
  83. package/dist/components/data-table-fit.js.map +1 -1
  84. package/dist/components/data-table-virtual.js +13 -3
  85. package/dist/components/data-table-virtual.js.map +1 -1
  86. package/dist/components/date-field.js +2 -2
  87. package/dist/components/date-field.js.map +1 -1
  88. package/dist/components/date-picker.js +2 -2
  89. package/dist/components/date-picker.js.map +1 -1
  90. package/dist/components/date-range-picker.js +2 -6
  91. package/dist/components/date-range-picker.js.map +1 -1
  92. package/dist/components/dialog-frame.d.ts +5 -1
  93. package/dist/components/dialog-frame.js +6 -2
  94. package/dist/components/dialog-frame.js.map +1 -1
  95. package/dist/components/dropdown.js +7 -3
  96. package/dist/components/dropdown.js.map +1 -1
  97. package/dist/components/entity-combobox.js +2 -2
  98. package/dist/components/entity-combobox.js.map +1 -1
  99. package/dist/components/field-parts.d.ts +108 -6
  100. package/dist/components/field-parts.js +59 -6
  101. package/dist/components/field-parts.js.map +1 -1
  102. package/dist/components/field-suggestion-labels.d.ts +2 -0
  103. package/dist/components/field-suggestion-labels.js +10 -0
  104. package/dist/components/field-suggestion-labels.js.map +1 -0
  105. package/dist/components/field.js +1 -1
  106. package/dist/components/field.js.map +1 -1
  107. package/dist/components/file-button.d.ts +1 -1
  108. package/dist/components/file-button.js.map +1 -1
  109. package/dist/components/floating-panel.d.ts +7 -6
  110. package/dist/components/floating-panel.js +3 -17
  111. package/dist/components/floating-panel.js.map +1 -1
  112. package/dist/components/form-actions.d.ts +3 -2
  113. package/dist/components/form-actions.js.map +1 -1
  114. package/dist/components/hover-menu.js +3 -2
  115. package/dist/components/hover-menu.js.map +1 -1
  116. package/dist/components/icon-button.d.ts +23 -5
  117. package/dist/components/icon-button.js +6 -8
  118. package/dist/components/icon-button.js.map +1 -1
  119. package/dist/components/inline-confirm.js +2 -1
  120. package/dist/components/inline-confirm.js.map +1 -1
  121. package/dist/components/inline-entity-combobox.js +2 -2
  122. package/dist/components/inline-entity-combobox.js.map +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/input.js.map +1 -1
  125. package/dist/components/jump-calendar.js +3 -3
  126. package/dist/components/jump-calendar.js.map +1 -1
  127. package/dist/components/legal.d.ts +13 -1
  128. package/dist/components/legal.js +12 -0
  129. package/dist/components/legal.js.map +1 -1
  130. package/dist/components/legended-series-chart.d.ts +80 -0
  131. package/dist/components/legended-series-chart.js +70 -0
  132. package/dist/components/legended-series-chart.js.map +1 -0
  133. package/dist/components/lightbox.js +2 -1
  134. package/dist/components/lightbox.js.map +1 -1
  135. package/dist/components/mini-calendar.js +16 -7
  136. package/dist/components/mini-calendar.js.map +1 -1
  137. package/dist/components/modal.d.ts +48 -0
  138. package/dist/components/modal.js +26 -6
  139. package/dist/components/modal.js.map +1 -1
  140. package/dist/components/month-picker.d.ts +1 -1
  141. package/dist/components/month-picker.js +6 -6
  142. package/dist/components/month-picker.js.map +1 -1
  143. package/dist/components/multi-entity-combobox.js +2 -2
  144. package/dist/components/multi-entity-combobox.js.map +1 -1
  145. package/dist/components/multi-select.js +2 -6
  146. package/dist/components/multi-select.js.map +1 -1
  147. package/dist/components/number-input.js +2 -2
  148. package/dist/components/number-input.js.map +1 -1
  149. package/dist/components/page-header-layout.d.ts +18 -0
  150. package/dist/components/page-header-layout.js +23 -0
  151. package/dist/components/page-header-layout.js.map +1 -0
  152. package/dist/components/page-header-skeleton.d.ts +41 -0
  153. package/dist/components/page-header-skeleton.js +52 -0
  154. package/dist/components/page-header-skeleton.js.map +1 -0
  155. package/dist/components/page-header.js +4 -17
  156. package/dist/components/page-header.js.map +1 -1
  157. package/dist/components/popover.js +7 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/release-notes-labels.d.ts +16 -0
  160. package/dist/components/release-notes-labels.js +18 -0
  161. package/dist/components/release-notes-labels.js.map +1 -0
  162. package/dist/components/release-notes.d.ts +65 -0
  163. package/dist/components/release-notes.js +65 -0
  164. package/dist/components/release-notes.js.map +1 -0
  165. package/dist/components/select.js +2 -2
  166. package/dist/components/select.js.map +1 -1
  167. package/dist/components/series-chart-labels.d.ts +9 -0
  168. package/dist/components/series-chart-labels.js +3 -1
  169. package/dist/components/series-chart-labels.js.map +1 -1
  170. package/dist/components/series-chart.d.ts +5 -0
  171. package/dist/components/series-chart.js +89 -36
  172. package/dist/components/series-chart.js.map +1 -1
  173. package/dist/components/stat-tile.d.ts +21 -2
  174. package/dist/components/stat-tile.js +4 -2
  175. package/dist/components/stat-tile.js.map +1 -1
  176. package/dist/components/toggle-group.js +3 -2
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/treemap-layout.js.map +1 -1
  179. package/dist/components/treemap.d.ts +17 -15
  180. package/dist/components/treemap.js +167 -85
  181. package/dist/components/treemap.js.map +1 -1
  182. package/dist/currencies.d.ts +16 -2
  183. package/dist/currencies.js +2 -1
  184. package/dist/currencies.js.map +1 -1
  185. package/dist/eslint/index.js +1 -1
  186. package/dist/eslint/role-utilities.d.ts +2 -2
  187. package/dist/eslint/role-utilities.js.map +1 -1
  188. package/dist/feedback/feedback-context-labels.js +2 -1
  189. package/dist/feedback/feedback-context-labels.js.map +1 -1
  190. package/dist/feedback/feedback-context.d.ts +19 -3
  191. package/dist/feedback/feedback-context.js +12 -1
  192. package/dist/feedback/feedback-context.js.map +1 -1
  193. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  194. package/dist/feedback/feedback-diagnostics.js +27 -0
  195. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  196. package/dist/feedback/feedback-dialog.d.ts +10 -0
  197. package/dist/feedback/feedback-dialog.js +6 -4
  198. package/dist/feedback/feedback-dialog.js.map +1 -1
  199. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  200. package/dist/feedback/feedback-inbox-page.js +207 -0
  201. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  202. package/dist/feedback/feedback-record.d.ts +5 -0
  203. package/dist/feedback/feedback-record.js.map +1 -1
  204. package/dist/feedback/feedback-row-detail-labels.js +1 -0
  205. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  206. package/dist/feedback/feedback-row-detail.d.ts +3 -0
  207. package/dist/feedback/feedback-row-detail.js +3 -0
  208. package/dist/feedback/feedback-row-detail.js.map +1 -1
  209. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  210. package/dist/feedback/feedback-submit-dialog.js +15 -3
  211. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  212. package/dist/feedback/feedback-submit.d.ts +30 -2
  213. package/dist/feedback/feedback-submit.js +5 -2
  214. package/dist/feedback/feedback-submit.js.map +1 -1
  215. package/dist/feedback/feedback-vocabulary.js +2 -2
  216. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  217. package/dist/feedback.d.ts +1 -0
  218. package/dist/feedback.js +6 -0
  219. package/dist/feedback.js.map +1 -1
  220. package/dist/hooks/use-debounce.d.ts +32 -5
  221. package/dist/hooks/use-debounce.js +6 -5
  222. package/dist/hooks/use-debounce.js.map +1 -1
  223. package/dist/hooks/use-file-picker.d.ts +3 -1
  224. package/dist/hooks/use-file-picker.js.map +1 -1
  225. package/dist/hooks/use-focus-trap.d.ts +19 -3
  226. package/dist/hooks/use-focus-trap.js +15 -5
  227. package/dist/hooks/use-focus-trap.js.map +1 -1
  228. package/dist/hooks/use-has-camera.d.ts +36 -0
  229. package/dist/hooks/use-has-camera.js +53 -0
  230. package/dist/hooks/use-has-camera.js.map +1 -0
  231. package/dist/hooks/use-hotkey.js +2 -1
  232. package/dist/hooks/use-hotkey.js.map +1 -1
  233. package/dist/hooks/use-overlay-history.d.ts +6 -1
  234. package/dist/hooks/use-overlay-history.js +10 -2
  235. package/dist/hooks/use-overlay-history.js.map +1 -1
  236. package/dist/i18n/defaults.js +11 -1
  237. package/dist/i18n/defaults.js.map +1 -1
  238. package/dist/i18n/german.js +78 -4
  239. package/dist/i18n/german.js.map +1 -1
  240. package/dist/i18n/kit-labels.d.ts +20 -0
  241. package/dist/i18n/kit-labels.js.map +1 -1
  242. package/dist/i18n/locales/en.js +95 -5
  243. package/dist/i18n/locales/en.js.map +3 -3
  244. package/dist/i18n/locales/es.js +74 -4
  245. package/dist/i18n/locales/es.js.map +1 -1
  246. package/dist/i18n/locales/fr.js +75 -3
  247. package/dist/i18n/locales/fr.js.map +1 -1
  248. package/dist/i18n/locales/hu.js +77 -4
  249. package/dist/i18n/locales/hu.js.map +1 -1
  250. package/dist/i18n/locales/it.js +77 -4
  251. package/dist/i18n/locales/it.js.map +1 -1
  252. package/dist/i18n/locales/zh.js +74 -4
  253. package/dist/i18n/locales/zh.js.map +1 -1
  254. package/dist/i18n/review.js +22 -1
  255. package/dist/i18n/review.js.map +1 -1
  256. package/dist/index.d.ts +27 -8
  257. package/dist/index.js +41 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/landing/landing-labels.d.ts +8 -0
  260. package/dist/landing/landing-labels.js +2 -1
  261. package/dist/landing/landing-labels.js.map +1 -1
  262. package/dist/lib/changelog.d.ts +130 -0
  263. package/dist/lib/changelog.js +137 -0
  264. package/dist/lib/changelog.js.map +1 -0
  265. package/dist/lib/clipboard-minus.js +2 -2
  266. package/dist/lib/clipboard-minus.js.map +1 -1
  267. package/dist/lib/dates.d.ts +36 -4
  268. package/dist/lib/dates.js +49 -13
  269. package/dist/lib/dates.js.map +1 -1
  270. package/dist/lib/editable-content.d.ts +33 -0
  271. package/dist/lib/editable-content.js +15 -0
  272. package/dist/lib/editable-content.js.map +1 -0
  273. package/dist/lib/focus-left.d.ts +47 -1
  274. package/dist/lib/focus-left.js +11 -2
  275. package/dist/lib/focus-left.js.map +1 -1
  276. package/dist/lib/format.d.ts +37 -2
  277. package/dist/lib/format.js +25 -1
  278. package/dist/lib/format.js.map +1 -1
  279. package/dist/lib/intl-cache.d.ts +4 -2
  280. package/dist/lib/intl-cache.js.map +1 -1
  281. package/dist/lib/server-wake.d.ts +49 -9
  282. package/dist/lib/server-wake.js +25 -14
  283. package/dist/lib/server-wake.js.map +1 -1
  284. package/dist/lib/session-refresh.d.ts +53 -0
  285. package/dist/lib/session-refresh.js +27 -1
  286. package/dist/lib/session-refresh.js.map +1 -1
  287. package/dist/lib/translation-review.d.ts +26 -0
  288. package/dist/lib/translation-review.js +14 -0
  289. package/dist/lib/translation-review.js.map +1 -1
  290. package/dist/rhf/fields.js +3 -3
  291. package/dist/rhf/fields.js.map +1 -1
  292. package/dist/shell/app-shell.d.ts +25 -1
  293. package/dist/shell/app-shell.js +3 -1
  294. package/dist/shell/app-shell.js.map +1 -1
  295. package/dist/shell/dev-menu.d.ts +3 -0
  296. package/dist/shell/dev-menu.js +4 -2
  297. package/dist/shell/dev-menu.js.map +1 -1
  298. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  299. package/dist/shell/held-changes-banner-labels.js +14 -0
  300. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  301. package/dist/shell/held-changes-banner.d.ts +75 -0
  302. package/dist/shell/held-changes-banner.js +106 -0
  303. package/dist/shell/held-changes-banner.js.map +1 -0
  304. package/dist/shell/sync-status-indicator.d.ts +27 -12
  305. package/dist/shell/sync-status-indicator.js +13 -4
  306. package/dist/shell/sync-status-indicator.js.map +1 -1
  307. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  308. package/dist/shell/top-bar-parts-labels.js +3 -1
  309. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  310. package/dist/shell.d.ts +2 -0
  311. package/dist/shell.js +3 -0
  312. package/dist/shell.js.map +1 -1
  313. package/dist/sync/engine.d.ts +11 -2
  314. package/dist/sync/engine.js +13 -5
  315. package/dist/sync/engine.js.map +1 -1
  316. package/dist/sync/hooks.d.ts +3 -2
  317. package/dist/sync/hooks.js +4 -2
  318. package/dist/sync/hooks.js.map +1 -1
  319. package/dist/testing/css-variables.generated.d.ts +2 -2
  320. package/dist/testing/css-variables.generated.js +1 -1
  321. package/dist/testing/css-variables.generated.js.map +1 -1
  322. package/dist/theme/chart-palette.d.ts +18 -0
  323. package/dist/theme/chart-palette.js +17 -1
  324. package/dist/theme/chart-palette.js.map +1 -1
  325. package/dist/theme/contrast-tokens.d.ts +3 -3
  326. package/dist/theme/contrast-tokens.js.map +1 -1
  327. package/dist/theme/contrast.d.ts +4 -4
  328. package/dist/theme/contrast.js.map +1 -1
  329. package/dist/tour/tour-overlay.js +2 -1
  330. package/dist/tour/tour-overlay.js.map +1 -1
  331. package/package.json +5 -9
  332. package/tokens.css +19 -54
  333. package/src/account/account-parts.ts +0 -47
  334. package/src/account/account-settings-labels.ts +0 -240
  335. package/src/account/account-settings.tsx +0 -698
  336. package/src/account/data-export-setting-labels.ts +0 -20
  337. package/src/account/data-export-setting.tsx +0 -200
  338. package/src/account/delete-account-setting-labels.ts +0 -33
  339. package/src/account/delete-account-setting.tsx +0 -341
  340. package/src/account/email-change-setting-labels.ts +0 -31
  341. package/src/account/email-change-setting.tsx +0 -443
  342. package/src/account/passkeys-setting.tsx +0 -445
  343. package/src/account/sessions-setting-labels.ts +0 -24
  344. package/src/account/sessions-setting.tsx +0 -283
  345. package/src/admin/admin-action-confirm-labels.ts +0 -19
  346. package/src/admin/admin-action-confirm.tsx +0 -509
  347. package/src/admin/admin-action-log-labels.ts +0 -83
  348. package/src/admin/admin-action-log.tsx +0 -464
  349. package/src/admin/admin-area-labels.ts +0 -114
  350. package/src/admin/admin-forbidden.tsx +0 -75
  351. package/src/admin/admin-overview.tsx +0 -258
  352. package/src/admin/admin-parts.ts +0 -68
  353. package/src/admin/admin-sections.tsx +0 -208
  354. package/src/admin/admin-tool-links.tsx +0 -90
  355. package/src/admin/invitations-panel-labels.ts +0 -31
  356. package/src/admin/invitations-panel.tsx +0 -516
  357. package/src/admin/mail-test-card-labels.ts +0 -19
  358. package/src/admin/mail-test-card.tsx +0 -189
  359. package/src/admin/plan-change-labels.ts +0 -5
  360. package/src/admin/plan-change.tsx +0 -474
  361. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  362. package/src/admin/reviewer-scope-editor.tsx +0 -224
  363. package/src/admin/role-select-labels.ts +0 -16
  364. package/src/admin/role-select.tsx +0 -282
  365. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  366. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  367. package/src/admin/user-roster-labels.ts +0 -17
  368. package/src/admin/user-roster.tsx +0 -559
  369. package/src/auth/accept-invitation-labels.ts +0 -21
  370. package/src/auth/accept-invitation.tsx +0 -210
  371. package/src/auth/auth-errors-labels.ts +0 -23
  372. package/src/auth/auth-errors.ts +0 -370
  373. package/src/auth/complete-name-dialog-labels.ts +0 -16
  374. package/src/auth/complete-name-dialog.tsx +0 -253
  375. package/src/auth/email-tag.ts +0 -75
  376. package/src/auth/forgot-password-form-labels.ts +0 -20
  377. package/src/auth/forgot-password-form.tsx +0 -221
  378. package/src/auth/form-rules.ts +0 -40
  379. package/src/auth/new-password-fields.tsx +0 -85
  380. package/src/auth/not-found-page-labels.ts +0 -12
  381. package/src/auth/not-found-page.tsx +0 -95
  382. package/src/auth/register-form-labels.ts +0 -31
  383. package/src/auth/register-form.tsx +0 -456
  384. package/src/auth/reset-password-form-labels.ts +0 -27
  385. package/src/auth/reset-password-form.tsx +0 -341
  386. package/src/auth/sign-in-form-labels.ts +0 -44
  387. package/src/auth/sign-in-form.tsx +0 -858
  388. package/src/auth/status-parts.tsx +0 -132
  389. package/src/auth/verify-email-labels.ts +0 -26
  390. package/src/auth/verify-email.tsx +0 -445
  391. package/src/billing/billing-banner.tsx +0 -258
  392. package/src/billing/billing-labels.ts +0 -381
  393. package/src/billing/billing-lock.ts +0 -108
  394. package/src/billing/billing-overview.ts +0 -157
  395. package/src/billing/billing-standing.ts +0 -80
  396. package/src/billing/cancel-request.ts +0 -97
  397. package/src/billing/cancel-subscription.tsx +0 -98
  398. package/src/billing/checkout-processing.ts +0 -394
  399. package/src/billing/checkout-return.ts +0 -90
  400. package/src/billing/pay-page.ts +0 -330
  401. package/src/billing/plan-card.tsx +0 -562
  402. package/src/billing/plan-change-labels.ts +0 -66
  403. package/src/billing/plan-limit-notice.tsx +0 -148
  404. package/src/billing/plan-limit-toast.tsx +0 -90
  405. package/src/billing/plan-limit.ts +0 -52
  406. package/src/billing/plan-price.ts +0 -98
  407. package/src/billing/plan-switch.tsx +0 -216
  408. package/src/billing/provider-page.ts +0 -84
  409. package/src/billing/subscription-actions.tsx +0 -247
  410. package/src/billing/subscription-status-chip.tsx +0 -95
  411. package/src/chart.ts +0 -103
  412. package/src/components/account-chips-labels.ts +0 -16
  413. package/src/components/account-chips.tsx +0 -474
  414. package/src/components/alert-banner.tsx +0 -670
  415. package/src/components/amount-input.tsx +0 -815
  416. package/src/components/appearance-labels.ts +0 -63
  417. package/src/components/authed-image-labels.ts +0 -11
  418. package/src/components/authed-image.tsx +0 -209
  419. package/src/components/autocomplete.tsx +0 -645
  420. package/src/components/breadcrumbs-labels.ts +0 -10
  421. package/src/components/breadcrumbs.tsx +0 -205
  422. package/src/components/bulk-action-bar-labels.ts +0 -11
  423. package/src/components/bulk-action-bar.tsx +0 -443
  424. package/src/components/button-group.tsx +0 -223
  425. package/src/components/button-look.ts +0 -256
  426. package/src/components/button.tsx +0 -339
  427. package/src/components/calculator.tsx +0 -341
  428. package/src/components/calendar-heatmap-labels.ts +0 -15
  429. package/src/components/calendar-heatmap.tsx +0 -594
  430. package/src/components/card.tsx +0 -377
  431. package/src/components/chart-edge.ts +0 -106
  432. package/src/components/chart-figure.tsx +0 -35
  433. package/src/components/chart-height.ts +0 -30
  434. package/src/components/chart-zoom-core.tsx +0 -627
  435. package/src/components/chart-zoom.tsx +0 -22
  436. package/src/components/chart.tsx +0 -554
  437. package/src/components/checkbox-group.tsx +0 -247
  438. package/src/components/checkbox.tsx +0 -346
  439. package/src/components/chip-input.tsx +0 -370
  440. package/src/components/chip-labels.ts +0 -13
  441. package/src/components/chip.tsx +0 -843
  442. package/src/components/choice-card.tsx +0 -544
  443. package/src/components/column-mapper-labels.ts +0 -38
  444. package/src/components/column-mapper.tsx +0 -655
  445. package/src/components/combobox-core.tsx +0 -1321
  446. package/src/components/combobox-suggestions.tsx +0 -131
  447. package/src/components/combobox.tsx +0 -716
  448. package/src/components/confirm-context.ts +0 -7
  449. package/src/components/confirm-dialog-labels.ts +0 -13
  450. package/src/components/confirm-dialog.tsx +0 -341
  451. package/src/components/copy-button-labels.ts +0 -13
  452. package/src/components/copy-button.tsx +0 -192
  453. package/src/components/country-select-labels.ts +0 -11
  454. package/src/components/country-select.tsx +0 -539
  455. package/src/components/currency-option-list.tsx +0 -180
  456. package/src/components/currency-select.tsx +0 -341
  457. package/src/components/danger-confirm-labels.ts +0 -20
  458. package/src/components/danger-confirm.tsx +0 -934
  459. package/src/components/data-table-cells.tsx +0 -118
  460. package/src/components/data-table-column-settings.tsx +0 -198
  461. package/src/components/data-table-filter-popover.tsx +0 -281
  462. package/src/components/data-table-filters.ts +0 -287
  463. package/src/components/data-table-fit.ts +0 -301
  464. package/src/components/data-table-groups.tsx +0 -288
  465. package/src/components/data-table-head.tsx +0 -280
  466. package/src/components/data-table-labels.ts +0 -198
  467. package/src/components/data-table-leading-row.tsx +0 -58
  468. package/src/components/data-table-mobile.tsx +0 -1117
  469. package/src/components/data-table-pagination.tsx +0 -202
  470. package/src/components/data-table-resize.ts +0 -188
  471. package/src/components/data-table-row-actions.tsx +0 -302
  472. package/src/components/data-table-row.tsx +0 -496
  473. package/src/components/data-table-selection.ts +0 -71
  474. package/src/components/data-table-shared.tsx +0 -287
  475. package/src/components/data-table-sort.ts +0 -163
  476. package/src/components/data-table-virtual.ts +0 -576
  477. package/src/components/data-table.tsx +0 -2598
  478. package/src/components/date-field.tsx +0 -684
  479. package/src/components/date-format-setting.tsx +0 -64
  480. package/src/components/date-picker.tsx +0 -404
  481. package/src/components/date-range-picker.tsx +0 -518
  482. package/src/components/description-list-labels.ts +0 -9
  483. package/src/components/description-list.tsx +0 -386
  484. package/src/components/dialog-frame-labels.ts +0 -7
  485. package/src/components/dialog-frame.tsx +0 -276
  486. package/src/components/disclosure.tsx +0 -476
  487. package/src/components/dropdown.tsx +0 -461
  488. package/src/components/empty-state.tsx +0 -136
  489. package/src/components/entity-combobox.tsx +0 -422
  490. package/src/components/error-boundary-labels.ts +0 -20
  491. package/src/components/error-boundary.tsx +0 -675
  492. package/src/components/facing-pair.ts +0 -93
  493. package/src/components/field-base-labels.ts +0 -20
  494. package/src/components/field-base.tsx +0 -656
  495. package/src/components/field-parts.tsx +0 -825
  496. package/src/components/field-strip.tsx +0 -129
  497. package/src/components/field.tsx +0 -206
  498. package/src/components/file-button.tsx +0 -295
  499. package/src/components/file-dropzone.tsx +0 -559
  500. package/src/components/floating-panel-labels.ts +0 -10
  501. package/src/components/floating-panel.tsx +0 -828
  502. package/src/components/focus-ring.ts +0 -16
  503. package/src/components/form-actions-labels.ts +0 -11
  504. package/src/components/form-actions.tsx +0 -578
  505. package/src/components/full-bleed-dialog.tsx +0 -294
  506. package/src/components/grouped-picker.tsx +0 -184
  507. package/src/components/help-popover.tsx +0 -117
  508. package/src/components/hover-menu.tsx +0 -343
  509. package/src/components/iban-input-labels.ts +0 -14
  510. package/src/components/iban-input.tsx +0 -236
  511. package/src/components/icon-button.tsx +0 -959
  512. package/src/components/icon-picker-labels.ts +0 -13
  513. package/src/components/icon-picker.tsx +0 -232
  514. package/src/components/image-grid-labels.ts +0 -11
  515. package/src/components/image-grid.tsx +0 -202
  516. package/src/components/inline-confirm.tsx +0 -116
  517. package/src/components/inline-edit-field-labels.ts +0 -11
  518. package/src/components/inline-edit-field.tsx +0 -552
  519. package/src/components/inline-entity-combobox.tsx +0 -849
  520. package/src/components/input.tsx +0 -394
  521. package/src/components/jump-calendar.tsx +0 -265
  522. package/src/components/language-select.tsx +0 -93
  523. package/src/components/legal-labels.ts +0 -105
  524. package/src/components/legal-page.tsx +0 -711
  525. package/src/components/legal.tsx +0 -668
  526. package/src/components/lightbox-labels.ts +0 -18
  527. package/src/components/lightbox.tsx +0 -399
  528. package/src/components/line-items-labels.ts +0 -14
  529. package/src/components/line-items.tsx +0 -602
  530. package/src/components/list-labels.ts +0 -12
  531. package/src/components/list.tsx +0 -870
  532. package/src/components/loading-state.tsx +0 -78
  533. package/src/components/measured-grid-labels.ts +0 -25
  534. package/src/components/measured-grid.tsx +0 -837
  535. package/src/components/menu-item.tsx +0 -241
  536. package/src/components/mini-calendar-labels.ts +0 -17
  537. package/src/components/mini-calendar.tsx +0 -789
  538. package/src/components/modal.tsx +0 -505
  539. package/src/components/money-draft.ts +0 -83
  540. package/src/components/money-field.tsx +0 -76
  541. package/src/components/month-grid.tsx +0 -229
  542. package/src/components/month-picker-labels.ts +0 -22
  543. package/src/components/month-picker.tsx +0 -1087
  544. package/src/components/multi-entity-combobox.tsx +0 -369
  545. package/src/components/multi-select.tsx +0 -376
  546. package/src/components/nav-pills.tsx +0 -198
  547. package/src/components/number-field.tsx +0 -327
  548. package/src/components/number-input.tsx +0 -558
  549. package/src/components/numeric-field.ts +0 -169
  550. package/src/components/numpad-sheet.tsx +0 -285
  551. package/src/components/one-time-code-input.tsx +0 -270
  552. package/src/components/page-contents-labels.ts +0 -9
  553. package/src/components/page-contents.tsx +0 -282
  554. package/src/components/page-header-context.ts +0 -8
  555. package/src/components/page-header.tsx +0 -298
  556. package/src/components/password-strength-labels.ts +0 -22
  557. package/src/components/password-strength.tsx +0 -241
  558. package/src/components/phone-input-labels.ts +0 -10
  559. package/src/components/phone-input.tsx +0 -282
  560. package/src/components/picker-sheet.tsx +0 -330
  561. package/src/components/pie-chart-labels.ts +0 -40
  562. package/src/components/pie-chart.tsx +0 -480
  563. package/src/components/popover-labels.ts +0 -14
  564. package/src/components/popover.tsx +0 -268
  565. package/src/components/progress-bar-labels.ts +0 -10
  566. package/src/components/progress-bar.tsx +0 -479
  567. package/src/components/qr-code.tsx +0 -76
  568. package/src/components/reauth-dialog-labels.ts +0 -13
  569. package/src/components/reauth-dialog.tsx +0 -265
  570. package/src/components/row-actions-labels.ts +0 -12
  571. package/src/components/row-actions.tsx +0 -795
  572. package/src/components/scroll-area.tsx +0 -123
  573. package/src/components/search-field.tsx +0 -192
  574. package/src/components/select.tsx +0 -294
  575. package/src/components/separator.tsx +0 -33
  576. package/src/components/series-chart-axes.ts +0 -342
  577. package/src/components/series-chart-budget.ts +0 -150
  578. package/src/components/series-chart-labels.ts +0 -33
  579. package/src/components/series-chart-thin.ts +0 -180
  580. package/src/components/series-chart-ticks.ts +0 -194
  581. package/src/components/series-chart.tsx +0 -1906
  582. package/src/components/server-wake-labels.ts +0 -11
  583. package/src/components/server-wake.tsx +0 -129
  584. package/src/components/settings-fields.tsx +0 -237
  585. package/src/components/share-card-labels.ts +0 -30
  586. package/src/components/share-card.tsx +0 -675
  587. package/src/components/signature-pad-labels.ts +0 -24
  588. package/src/components/signature-pad.tsx +0 -747
  589. package/src/components/signed-amount-labels.ts +0 -10
  590. package/src/components/signed-amount.tsx +0 -336
  591. package/src/components/skeleton.tsx +0 -80
  592. package/src/components/slider.tsx +0 -338
  593. package/src/components/sparkline-labels.ts +0 -14
  594. package/src/components/sparkline.tsx +0 -322
  595. package/src/components/spinner.tsx +0 -81
  596. package/src/components/stat-tile-labels.ts +0 -15
  597. package/src/components/stat-tile.tsx +0 -725
  598. package/src/components/status-dot.tsx +0 -170
  599. package/src/components/swatch-picker-labels.ts +0 -10
  600. package/src/components/swatch-picker.tsx +0 -283
  601. package/src/components/swipeable-row.tsx +0 -386
  602. package/src/components/switch.tsx +0 -273
  603. package/src/components/table-skeleton.tsx +0 -136
  604. package/src/components/table.tsx +0 -647
  605. package/src/components/tabs-labels.ts +0 -13
  606. package/src/components/tabs.tsx +0 -693
  607. package/src/components/text-link.tsx +0 -359
  608. package/src/components/text.tsx +0 -150
  609. package/src/components/textarea.tsx +0 -330
  610. package/src/components/tile-radio.tsx +0 -332
  611. package/src/components/time-input.tsx +0 -121
  612. package/src/components/toast-labels.ts +0 -12
  613. package/src/components/toast.tsx +0 -519
  614. package/src/components/toggle-group.tsx +0 -752
  615. package/src/components/toggle-legend.tsx +0 -394
  616. package/src/components/tooltip.tsx +0 -1149
  617. package/src/components/translation-review-editor.tsx +0 -290
  618. package/src/components/translation-review-labels.ts +0 -181
  619. package/src/components/translation-review-parts.tsx +0 -272
  620. package/src/components/translation-review.tsx +0 -1240
  621. package/src/components/tree-view.tsx +0 -589
  622. package/src/components/treemap-labels.ts +0 -47
  623. package/src/components/treemap-layout.ts +0 -210
  624. package/src/components/treemap.tsx +0 -1184
  625. package/src/components/trigger-aria.ts +0 -44
  626. package/src/components/use-anchor-dir.ts +0 -20
  627. package/src/components/use-mobile-reveal.ts +0 -62
  628. package/src/components/use-table-state.ts +0 -753
  629. package/src/components/user-avatar.tsx +0 -116
  630. package/src/components/wizard-stepper.tsx +0 -42
  631. package/src/components/write-lock-labels.ts +0 -10
  632. package/src/components/write-lock.tsx +0 -253
  633. package/src/currencies.ts +0 -70
  634. package/src/data-table.ts +0 -42
  635. package/src/demo/demo-banner.tsx +0 -153
  636. package/src/demo/demo-countdown.ts +0 -136
  637. package/src/demo/demo-ended.tsx +0 -90
  638. package/src/demo/demo-labels.ts +0 -78
  639. package/src/demo/demo-session.ts +0 -32
  640. package/src/demo/demo-start.tsx +0 -156
  641. package/src/eslint/index.ts +0 -161
  642. package/src/eslint/role-utilities.generated.ts +0 -146
  643. package/src/eslint/role-utilities.ts +0 -233
  644. package/src/feedback/feedback-attachment-download.tsx +0 -77
  645. package/src/feedback/feedback-attachment-labels.ts +0 -37
  646. package/src/feedback/feedback-attachment.tsx +0 -1239
  647. package/src/feedback/feedback-capture.ts +0 -287
  648. package/src/feedback/feedback-comment-thread.tsx +0 -353
  649. package/src/feedback/feedback-context-labels.ts +0 -10
  650. package/src/feedback/feedback-context.tsx +0 -213
  651. package/src/feedback/feedback-crash.ts +0 -547
  652. package/src/feedback/feedback-dialog-labels.ts +0 -29
  653. package/src/feedback/feedback-dialog.tsx +0 -440
  654. package/src/feedback/feedback-entry-file.tsx +0 -150
  655. package/src/feedback/feedback-inbox.tsx +0 -552
  656. package/src/feedback/feedback-labels.ts +0 -144
  657. package/src/feedback/feedback-menu-labels.ts +0 -12
  658. package/src/feedback/feedback-menu.tsx +0 -177
  659. package/src/feedback/feedback-mutation.ts +0 -20
  660. package/src/feedback/feedback-page-link.ts +0 -90
  661. package/src/feedback/feedback-public-labels.ts +0 -81
  662. package/src/feedback/feedback-public-list.tsx +0 -524
  663. package/src/feedback/feedback-public-switch.tsx +0 -102
  664. package/src/feedback/feedback-public-types.ts +0 -101
  665. package/src/feedback/feedback-record.ts +0 -423
  666. package/src/feedback/feedback-replies-context.ts +0 -9
  667. package/src/feedback/feedback-replies.tsx +0 -343
  668. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  669. package/src/feedback/feedback-row-detail-labels.ts +0 -30
  670. package/src/feedback/feedback-row-detail.tsx +0 -858
  671. package/src/feedback/feedback-status-undo.ts +0 -109
  672. package/src/feedback/feedback-submit-dialog.tsx +0 -118
  673. package/src/feedback/feedback-submit.tsx +0 -237
  674. package/src/feedback/feedback-swipe.tsx +0 -192
  675. package/src/feedback/feedback-table-labels.ts +0 -27
  676. package/src/feedback/feedback-table.tsx +0 -912
  677. package/src/feedback/feedback-thread-labels.ts +0 -22
  678. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  679. package/src/feedback/feedback-thread.tsx +0 -905
  680. package/src/feedback/feedback-vocabulary.ts +0 -263
  681. package/src/feedback/feedback-vote-button.tsx +0 -124
  682. package/src/feedback.ts +0 -116
  683. package/src/hooks/use-anchored-panel.ts +0 -396
  684. package/src/hooks/use-anchored-rect.ts +0 -77
  685. package/src/hooks/use-announce.ts +0 -85
  686. package/src/hooks/use-authed-src.ts +0 -167
  687. package/src/hooks/use-body-scroll-lock.ts +0 -119
  688. package/src/hooks/use-breakpoint.ts +0 -133
  689. package/src/hooks/use-close-transition.ts +0 -107
  690. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  691. package/src/hooks/use-debounce.ts +0 -114
  692. package/src/hooks/use-dismiss.ts +0 -371
  693. package/src/hooks/use-document-title.ts +0 -33
  694. package/src/hooks/use-file-drop.ts +0 -121
  695. package/src/hooks/use-file-picker-labels.ts +0 -28
  696. package/src/hooks/use-file-picker.tsx +0 -359
  697. package/src/hooks/use-focus-trap.ts +0 -199
  698. package/src/hooks/use-hotkey.ts +0 -189
  699. package/src/hooks/use-large-text.ts +0 -41
  700. package/src/hooks/use-long-press.ts +0 -140
  701. package/src/hooks/use-media-query.ts +0 -39
  702. package/src/hooks/use-mounted.ts +0 -39
  703. package/src/hooks/use-noindex.ts +0 -37
  704. package/src/hooks/use-overlay-history.ts +0 -714
  705. package/src/hooks/use-row-swipe.ts +0 -207
  706. package/src/hooks/use-search-param-state.ts +0 -434
  707. package/src/hooks/use-windowed-rows.ts +0 -225
  708. package/src/i18n/defaults.ts +0 -267
  709. package/src/i18n/german.ts +0 -2057
  710. package/src/i18n/kit-labels.tsx +0 -1003
  711. package/src/i18n/languages.ts +0 -349
  712. package/src/i18n/locales/de-CH.ts +0 -24
  713. package/src/i18n/locales/en.ts +0 -267
  714. package/src/i18n/locales/es.ts +0 -1997
  715. package/src/i18n/locales/fr.ts +0 -2035
  716. package/src/i18n/locales/hu.ts +0 -2002
  717. package/src/i18n/locales/it.ts +0 -2012
  718. package/src/i18n/locales/zh.ts +0 -1910
  719. package/src/i18n/plurals.ts +0 -86
  720. package/src/i18n/review.ts +0 -475
  721. package/src/i18n/shared-labels.ts +0 -138
  722. package/src/i18n/swiss.ts +0 -29
  723. package/src/index.ts +0 -1179
  724. package/src/landing/access.ts +0 -93
  725. package/src/landing/landing-actions.tsx +0 -97
  726. package/src/landing/landing-labels.ts +0 -59
  727. package/src/landing/landing-sections.tsx +0 -341
  728. package/src/landing/page-seo.ts +0 -261
  729. package/src/landing/public-header.tsx +0 -121
  730. package/src/landing/public-page.tsx +0 -106
  731. package/src/landing/routing.tsx +0 -225
  732. package/src/lib/broadcast.ts +0 -52
  733. package/src/lib/calc.ts +0 -323
  734. package/src/lib/chip-edge-snap.ts +0 -212
  735. package/src/lib/clipboard-minus.ts +0 -107
  736. package/src/lib/clipping.ts +0 -34
  737. package/src/lib/cn.ts +0 -28
  738. package/src/lib/column-mapping.ts +0 -234
  739. package/src/lib/countries.ts +0 -91
  740. package/src/lib/data-attributes.ts +0 -14
  741. package/src/lib/date-format.ts +0 -227
  742. package/src/lib/dates.ts +0 -266
  743. package/src/lib/day-format.ts +0 -96
  744. package/src/lib/decimal-marks.ts +0 -108
  745. package/src/lib/describe-failure.ts +0 -18
  746. package/src/lib/device-settings.ts +0 -147
  747. package/src/lib/direction.ts +0 -74
  748. package/src/lib/document-navigation.ts +0 -17
  749. package/src/lib/document-title.ts +0 -41
  750. package/src/lib/download.ts +0 -172
  751. package/src/lib/float-clearance.ts +0 -70
  752. package/src/lib/floating-stack.ts +0 -70
  753. package/src/lib/focus-left.ts +0 -156
  754. package/src/lib/format.ts +0 -522
  755. package/src/lib/http-status.ts +0 -38
  756. package/src/lib/iban.ts +0 -251
  757. package/src/lib/ime.ts +0 -24
  758. package/src/lib/intl-cache.ts +0 -25
  759. package/src/lib/iso-day.ts +0 -13
  760. package/src/lib/lazy-with-fallback.ts +0 -82
  761. package/src/lib/logger.ts +0 -69
  762. package/src/lib/minus.ts +0 -50
  763. package/src/lib/path-pattern.ts +0 -55
  764. package/src/lib/person-name.ts +0 -155
  765. package/src/lib/phone.ts +0 -208
  766. package/src/lib/plain-text.ts +0 -22
  767. package/src/lib/presence.ts +0 -18
  768. package/src/lib/qr-encode.ts +0 -444
  769. package/src/lib/quiet-focus.ts +0 -32
  770. package/src/lib/reduced-motion.ts +0 -22
  771. package/src/lib/refs.ts +0 -22
  772. package/src/lib/round.ts +0 -43
  773. package/src/lib/safe-storage.ts +0 -119
  774. package/src/lib/server-wake.ts +0 -315
  775. package/src/lib/session-refresh.ts +0 -472
  776. package/src/lib/share-format.ts +0 -18
  777. package/src/lib/status-tone.ts +0 -43
  778. package/src/lib/strip-fade.ts +0 -287
  779. package/src/lib/table-text.ts +0 -554
  780. package/src/lib/text-case.ts +0 -19
  781. package/src/lib/text-match.ts +0 -169
  782. package/src/lib/thenable.ts +0 -19
  783. package/src/lib/translation-review.ts +0 -717
  784. package/src/notifications/device-zone-report.ts +0 -67
  785. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  786. package/src/notifications/feedback-notification-types.ts +0 -71
  787. package/src/notifications/notification-bell.tsx +0 -119
  788. package/src/notifications/notification-inbox.tsx +0 -309
  789. package/src/notifications/notification-parts.tsx +0 -81
  790. package/src/notifications/notification-types-setting.tsx +0 -406
  791. package/src/notifications/notification-types.ts +0 -89
  792. package/src/notifications/notifications-labels.ts +0 -287
  793. package/src/notifications/push-device-setting.tsx +0 -230
  794. package/src/notifications/push-devices-setting.tsx +0 -374
  795. package/src/notifications/push-worker.ts +0 -168
  796. package/src/notifications/quiet-hours-setting.tsx +0 -238
  797. package/src/notifications/unsubscribe-panel.tsx +0 -200
  798. package/src/notifications/use-push-device.ts +0 -448
  799. package/src/offline/local-first.ts +0 -100
  800. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  801. package/src/offline/pwa-update-prompt.tsx +0 -78
  802. package/src/offline/query-persistence.ts +0 -391
  803. package/src/offline/service-worker-updates.ts +0 -81
  804. package/src/offline/shared-computer-labels.ts +0 -8
  805. package/src/offline/shared-computer.tsx +0 -87
  806. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  807. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  808. package/src/offline.ts +0 -37
  809. package/src/push-worker.ts +0 -25
  810. package/src/rhf/fields.tsx +0 -2187
  811. package/src/rhf/form-core.tsx +0 -324
  812. package/src/rhf/form.tsx +0 -12
  813. package/src/rhf/line-items.tsx +0 -135
  814. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  815. package/src/rhf.ts +0 -21
  816. package/src/search/command-palette-labels.ts +0 -16
  817. package/src/search/command-palette.tsx +0 -624
  818. package/src/search/global-search-labels.ts +0 -13
  819. package/src/search/global-search.tsx +0 -660
  820. package/src/search/search-index.ts +0 -283
  821. package/src/search/use-command-key.ts +0 -23
  822. package/src/search.ts +0 -14
  823. package/src/settings/device-reset-setting-labels.ts +0 -20
  824. package/src/settings/device-reset-setting.tsx +0 -116
  825. package/src/settings/settings-card-classes.ts +0 -11
  826. package/src/settings/settings-catalogue.ts +0 -150
  827. package/src/settings/settings-context.ts +0 -33
  828. package/src/settings/settings-heading.tsx +0 -67
  829. package/src/settings/settings-labels.ts +0 -87
  830. package/src/settings/settings-layout.tsx +0 -471
  831. package/src/settings/settings-section.tsx +0 -108
  832. package/src/settings/use-account-appearance.ts +0 -148
  833. package/src/settings/use-account-language.ts +0 -146
  834. package/src/settings/use-settings-focus.ts +0 -128
  835. package/src/settings/use-settings-route.ts +0 -258
  836. package/src/shell/account-menu-language.tsx +0 -130
  837. package/src/shell/account-menu-text-size.tsx +0 -80
  838. package/src/shell/admin-menu.tsx +0 -183
  839. package/src/shell/app-shell-labels.ts +0 -11
  840. package/src/shell/app-shell.tsx +0 -1560
  841. package/src/shell/auth-layout.tsx +0 -186
  842. package/src/shell/company-switcher-labels.ts +0 -13
  843. package/src/shell/company-switcher.tsx +0 -136
  844. package/src/shell/dev-menu.tsx +0 -257
  845. package/src/shell/fill-height.tsx +0 -45
  846. package/src/shell/language-code.ts +0 -5
  847. package/src/shell/option-switcher-menu.tsx +0 -84
  848. package/src/shell/sync-status-indicator.tsx +0 -303
  849. package/src/shell/top-bar-actions.tsx +0 -87
  850. package/src/shell/top-bar-brand.tsx +0 -78
  851. package/src/shell/top-bar-parts-labels.ts +0 -148
  852. package/src/shell/top-bar.tsx +0 -40
  853. package/src/shell/topbar-action-menu.tsx +0 -567
  854. package/src/shell/topbar-controls.tsx +0 -269
  855. package/src/shell/tour-menu.tsx +0 -187
  856. package/src/shell.ts +0 -108
  857. package/src/sync/db.ts +0 -129
  858. package/src/sync/engine.ts +0 -1011
  859. package/src/sync/hooks.ts +0 -98
  860. package/src/sync/testing.ts +0 -73
  861. package/src/sync/wire.ts +0 -61
  862. package/src/sync.ts +0 -37
  863. package/src/table-text.ts +0 -8
  864. package/src/testing/css-variables.generated.ts +0 -112
  865. package/src/testing/css-variables.ts +0 -189
  866. package/src/testing/index.ts +0 -29
  867. package/src/testing/react.ts +0 -58
  868. package/src/testing/tour.ts +0 -41
  869. package/src/testing/viewport.ts +0 -357
  870. package/src/theme/chart-palette.ts +0 -232
  871. package/src/theme/color.ts +0 -272
  872. package/src/theme/contrast-tokens.ts +0 -268
  873. package/src/theme/contrast.ts +0 -250
  874. package/src/theme/palette-derive.ts +0 -825
  875. package/src/theme/palette-presets.ts +0 -544
  876. package/src/theme/palette-store.ts +0 -108
  877. package/src/theme/text-size.ts +0 -289
  878. package/src/theme/theme-store.ts +0 -157
  879. package/src/tour/tour-catalogue-labels.ts +0 -75
  880. package/src/tour/tour-catalogue.tsx +0 -335
  881. package/src/tour/tour-dock.ts +0 -134
  882. package/src/tour/tour-engine.ts +0 -37
  883. package/src/tour/tour-gates.ts +0 -218
  884. package/src/tour/tour-guide-button.tsx +0 -64
  885. package/src/tour/tour-guide-labels.ts +0 -15
  886. package/src/tour/tour-info.ts +0 -144
  887. package/src/tour/tour-labels.ts +0 -23
  888. package/src/tour/tour-overlay.tsx +0 -1170
  889. package/src/tour/tour.tsx +0 -750
  890. package/src/tour/use-tour-progress.ts +0 -671
  891. package/src/tour/use-tour-url-sync.ts +0 -266
  892. package/src/tour.ts +0 -21
  893. package/src/vite-env.d.ts +0 -23
  894. package/src/wizard/stepper-nav.tsx +0 -360
  895. package/src/wizard/types.ts +0 -275
  896. package/src/wizard/use-wizard.ts +0 -560
  897. package/src/wizard/validation.ts +0 -39
  898. package/src/wizard/wizard-context.tsx +0 -74
  899. package/src/wizard/wizard-labels.ts +0 -29
  900. package/src/wizard/wizard-step.tsx +0 -76
  901. package/src/wizard/wizard-summary.tsx +0 -84
  902. package/src/wizard.ts +0 -15
@@ -52,9 +52,55 @@ export declare function tabStopsAround(frame: Element, limit?: number): {
52
52
  /**
53
53
  * Focus the first of `candidates` that is still in the page and takes the focus; true
54
54
  * when one did. Each is tried and checked, since `focus()` on a node that cannot hold it
55
- * (detached, `display: none`, disabled since) fails without a word.
55
+ * (detached, `display: none`, disabled since) fails without a word. An SVG element counts
56
+ * (a chart's tile, a slice); `<body>` and `<html>` never do — "focused" there is the
57
+ * focus lost, not given (0.52 code review 2026-10-31, held to 0.55).
56
58
  */
57
59
  export declare function focusFirst(candidates: Iterable<Element | null | undefined>): boolean;
60
+ /** {@link returnFocus}'s three answers to "where was this opened from". */
61
+ export interface FocusReturn {
62
+ /** The control it was opened from: the overlay's trigger when it names one, else what
63
+ * held the focus as it opened. */
64
+ opener?: Element | null;
65
+ /** Where the focus goes when the opener cannot take it, nearest first — the overlay's
66
+ * stated fallback. */
67
+ fallback?: Iterable<Element | null | undefined>;
68
+ /** What is closing, while it is still in the page (a panel on its way out, a list
69
+ * about to unmount): a focus inside it is one to give back. */
70
+ closing?: Element | null;
71
+ }
72
+ /**
73
+ * Where the focus goes when an overlay closes, or a control holding it leaves the page —
74
+ * the kit's ONE rule for it (0.52 code review 2026-10-31, held to 0.55). The dialogs, the
75
+ * popovers and menus, the drawers, the phone sheets, the tour, AlertBanner's × and the
76
+ * Treemap's tiles each decided it themselves, and they drifted: the trap gave the focus
77
+ * back whenever its opener was still connected — over a focus the app had just placed —
78
+ * and left it on `<body>` when it was not; the date and month pickers and the floating
79
+ * panel moved it only when it was lost; the dropdown, the combobox and the column drawer
80
+ * moved it to their trigger every time, unchecked; and a Treemap tile's note of where to
81
+ * go once outlived the commit it was for and took the focus back, from wherever the
82
+ * reader was, when the map was shown again.
83
+ *
84
+ * 1. **Only a lost focus is moved** — on `<body>`, on nothing or on a removed node
85
+ * ({@link focusIsLost}) — or one still inside what is `closing`. A focus that went
86
+ * somewhere real is the user's (an outside press landed on a field) or the app's (its
87
+ * own `focus()` in `onClose`), and stays.
88
+ * 2. **To the opener**, if it is still in the page and takes the focus: `focus()` is
89
+ * checked, as a disabled, hidden or inert opener refuses it without a word.
90
+ * 3. **Else to the stated fallback**, the first of it that does — each caller names its
91
+ * own: the trap the stops that stood around its opener, a banner the stops around its
92
+ * ×, the map the tile that holds its tab stop.
93
+ * 4. **Never to `<body>`**: no `focus()` on a node that has left, none on `<body>`, no
94
+ * `blur()`. When nothing can take the focus — the page under the overlay was replaced
95
+ * — it is left for the page's own handling of a new page, not guessed at.
96
+ * 5. **Only in the closing commit**: called from the commit that closes or removes (a
97
+ * handler that closes, a ref callback, a layout effect, an effect's cleanup), and
98
+ * nothing handed to it is kept. A note gathered earlier, where a removed node can no
99
+ * longer be measured from, is spent or dropped in that commit.
100
+ *
101
+ * True when it moved the focus.
102
+ */
103
+ export declare function returnFocus({ opener, fallback, closing }: FocusReturn): boolean;
58
104
  /**
59
105
  * Focus a container that is not a tab stop of its own — a table, a list — as the place
60
106
  * the user is in, without making it one: `tabindex="-1"` for as long as it holds the
@@ -42,12 +42,20 @@ function tabStopsAround(frame, limit = 5) {
42
42
  }
43
43
  function focusFirst(candidates) {
44
44
  for (const el of candidates) {
45
- if (!(el instanceof HTMLElement) || !el.isConnected) continue;
45
+ if (!(el instanceof HTMLElement || el instanceof SVGElement) || !el.isConnected) continue;
46
+ if (el === el.ownerDocument.body || el === el.ownerDocument.documentElement) continue;
46
47
  el.focus();
47
- if (document.activeElement === el) return true;
48
+ if (el.ownerDocument.activeElement === el) return true;
48
49
  }
49
50
  return false;
50
51
  }
52
+ function returnFocus({ opener, fallback = [], closing }) {
53
+ if (typeof document === "undefined") return false;
54
+ const active = document.activeElement;
55
+ const inside = Boolean(closing?.isConnected && active && closing.contains(active));
56
+ if (!inside && !focusIsLost()) return false;
57
+ return focusFirst([opener, ...fallback]);
58
+ }
51
59
  function focusContainer(el) {
52
60
  if (!el.hasAttribute("tabindex")) {
53
61
  el.setAttribute("tabindex", "-1");
@@ -60,6 +68,7 @@ export {
60
68
  focusContainer,
61
69
  focusFirst,
62
70
  focusIsLost,
71
+ returnFocus,
63
72
  tabStops,
64
73
  tabStopsAround,
65
74
  whenFocusLeftForNothing
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/focus-left.ts"],"sourcesContent":["// \"The focused node left\" (refactor audit 2026-10-30, Wave 1: DSP-4, DSP-15). When the\n// element holding the keyboard's focus is removed — a table row deleted, filtered out or\n// approved; a bulk bar gone with its Clear; a banner its × took away — the browser puts\n// focus on <body> and says nothing: no blur in Chromium, none in jsdom. A keyboard or\n// screen-reader user then starts again from the top of the page. The overlays give focus\n// back to their opener (use-focus-trap); these helpers are for the controls that leave\n// the page while the focus is IN them, and decide where it goes instead.\n\n/**\n * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab\n * really reaches. The kit's one copy of the rule (0.52 code review, lib/focus-left.ts):\n * this file and use-focus-trap each had it, with an exported `firstTabStop` apiece that\n * disagreed about the element itself, and a combobox panel's toolbar had a third\n * selector. The trap, the focus a removed node drops (below), the tour's field ring and\n * the toolbar all read it here, so \"the next stop\" is the same stop everywhere.\n *\n * The selector is the one `Modal` used. On its own it was not enough (round 0.34\n * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or\n * a link taken out of the tab order still counted. A panel that ends in a roving grid —\n * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`\n * but the active cell — then had a \"last\" stop no Tab ever reaches: Tab from the real\n * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the\n * first landed on a cell that is not a stop at all.\n */\n// `input[type=hidden]` is no stop: a Tab never lands on it and focus() on it does nothing,\n// but its tabIndex reads 0, so a trap could take a form's hidden field for its first or last\n// stop and Shift+Tab left the dialog (0.52 code review).\nconst TAB_STOP =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type=\"hidden\"]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not\n * inside something `hidden` or `inert`. Removes only elements a native Tab never\n * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are\n * still not walked — they need layout, which this rule has never read.\n */\nfunction isTabStop(el: HTMLElement): boolean {\n return el.tabIndex >= 0 && !el.closest(\"[hidden],[inert]\");\n}\n\nexport interface TabStopOptions {\n /** Whether `el` itself counts, first, when it is a stop. Default true: the tour's\n * target may BE the field (refactor audit 2026-10-30, DUP-9). False for \"a control\n * inside it\" — a row, a drawer, a trap's container, a toolbar. */\n includeSelf?: boolean;\n}\n\n/** The tab stops in `el`, in DOM order — `el` itself first when it is one, unless\n * `includeSelf: false`. */\nexport function tabStops(el: Element, { includeSelf = true }: TabStopOptions = {}): HTMLElement[] {\n const inside = Array.from(el.querySelectorAll<HTMLElement>(TAB_STOP)).filter(isTabStop);\n const own = includeSelf && el instanceof HTMLElement && el.matches(TAB_STOP) && isTabStop(el);\n return own ? [el, ...inside] : inside;\n}\n\n/** The first of {@link tabStops} — the control a person types into or presses — or null. */\nexport function firstTabStop(el: Element, options?: TabStopOptions): HTMLElement | null {\n return tabStops(el, options)[0] ?? null;\n}\n\n/**\n * Whether the focus has fallen out of the page: onto `<body>`, onto nothing, or onto a\n * node no longer in the document. False on the server, where nothing has focus to lose.\n * A recovery asks this first: focus that went somewhere real (a dialog, the next field,\n * an app's own `focus()`) is the user's or the app's, and is left where it is.\n */\nexport function focusIsLost(): boolean {\n if (typeof document === \"undefined\") return false;\n const el = document.activeElement;\n return !el || el === document.body || !el.isConnected;\n}\n\n/**\n * Calls `then` when a `blur`/`focusout` was the user's own move to nothing — a click on\n * the page's background, or a click Safari answers by focusing nothing (it focuses no\n * button or link on a click, and blurs what had the focus) — after which a note of where\n * the focus was is stale (0.52 code review: DataTable's row, BulkActionBar's and\n * AlertBanner's notes outlived it, and a removal much later pulled the focus, and the\n * scroll, back to them).\n *\n * No element took the focus, the document holds none, and the node that lost it could\n * still hold it. Not when it was removed, hidden, made inert or disabled: that is the page\n * taking the focus away — the very case those notes are kept for. Not a window losing the\n * focus either, which leaves the node the document's active element. Decided a microtask\n * later, once the event's own work is done: a browser that blurs a node it is removing\n * (or hiding) may do it while the node is still in place, and only after it is gone is\n * that told apart from a click.\n */\nexport function whenFocusLeftForNothing(\n e: { target: EventTarget | null; relatedTarget: EventTarget | null },\n then: () => void,\n): void {\n const el = e.target;\n if (e.relatedTarget !== null || !(el instanceof Element)) return;\n queueMicrotask(() => {\n if (!el.isConnected || el.matches(\":disabled\") || el.closest(\"[hidden],[inert]\")) return;\n // `display: none`, where the browser can say (jsdom cannot).\n if (typeof el.checkVisibility === \"function\" && !el.checkVisibility()) return;\n if (focusIsLost()) then();\n });\n}\n\n/**\n * The tab stops around `frame`, outside it, nearest first on each side — where focus\n * goes once `frame` is gone. Read BEFORE it goes: a removed node has no place in the\n * document to measure from. `limit` per side keeps a long page's stops out of memory.\n *\n * Walked outwards from the frame, `limit` stops each way (0.52 code review,\n * lib/focus-left.ts): read from every stop in the page, a banner under a 2000-row table\n * read all 2000 to keep the nearest five before it.\n */\nexport function tabStopsAround(frame: Element, limit = 5): { after: HTMLElement[]; before: HTMLElement[] } {\n const doc = frame.ownerDocument;\n const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);\n const isStop = (node: Node): node is HTMLElement =>\n node instanceof HTMLElement && node.matches(TAB_STOP) && isTabStop(node);\n // After: from the first element past the frame's own subtree, in document order.\n const after: HTMLElement[] = [];\n walker.currentNode = frame;\n let node: Node | null = walker.nextSibling();\n while (!node && walker.parentNode()) node = walker.nextSibling();\n for (; node && after.length < limit; node = walker.nextNode()) if (isStop(node)) after.push(node);\n // Before: back from the frame, nearest first (its ancestors among them, as before).\n const before: HTMLElement[] = [];\n walker.currentNode = frame;\n for (node = walker.previousNode(); node && before.length < limit; node = walker.previousNode())\n if (isStop(node)) before.push(node);\n return { after, before };\n}\n\n/**\n * Focus the first of `candidates` that is still in the page and takes the focus; true\n * when one did. Each is tried and checked, since `focus()` on a node that cannot hold it\n * (detached, `display: none`, disabled since) fails without a word.\n */\nexport function focusFirst(candidates: Iterable<Element | null | undefined>): boolean {\n for (const el of candidates) {\n if (!(el instanceof HTMLElement) || !el.isConnected) continue;\n el.focus();\n if (document.activeElement === el) return true;\n }\n return false;\n}\n\n/**\n * Focus a container that is not a tab stop of its own — a table, a list — as the place\n * the user is in, without making it one: `tabindex=\"-1\"` for as long as it holds the\n * focus, unless it already carries a `tabindex` of its own.\n */\nexport function focusContainer(el: HTMLElement): void {\n if (!el.hasAttribute(\"tabindex\")) {\n el.setAttribute(\"tabindex\", \"-1\");\n el.addEventListener(\"blur\", () => el.removeAttribute(\"tabindex\"), { once: true });\n }\n el.focus();\n}\n"],"mappings":";AA2BA,MAAM,WACJ;AAQF,SAAS,UAAU,IAA0B;AAC3C,SAAO,GAAG,YAAY,KAAK,CAAC,GAAG,QAAQ,kBAAkB;AAC3D;AAWO,SAAS,SAAS,IAAa,EAAE,cAAc,KAAK,IAAoB,CAAC,GAAkB;AAChG,QAAM,SAAS,MAAM,KAAK,GAAG,iBAA8B,QAAQ,CAAC,EAAE,OAAO,SAAS;AACtF,QAAM,MAAM,eAAe,cAAc,eAAe,GAAG,QAAQ,QAAQ,KAAK,UAAU,EAAE;AAC5F,SAAO,MAAM,CAAC,IAAI,GAAG,MAAM,IAAI;AACjC;AAGO,SAAS,aAAa,IAAa,SAA8C;AACtF,SAAO,SAAS,IAAI,OAAO,EAAE,CAAC,KAAK;AACrC;AAQO,SAAS,cAAuB;AACrC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,KAAK,SAAS;AACpB,SAAO,CAAC,MAAM,OAAO,SAAS,QAAQ,CAAC,GAAG;AAC5C;AAkBO,SAAS,wBACd,GACA,MACM;AACN,QAAM,KAAK,EAAE;AACb,MAAI,EAAE,kBAAkB,QAAQ,EAAE,cAAc,SAAU;AAC1D,iBAAe,MAAM;AACnB,QAAI,CAAC,GAAG,eAAe,GAAG,QAAQ,WAAW,KAAK,GAAG,QAAQ,kBAAkB,EAAG;AAElF,QAAI,OAAO,GAAG,oBAAoB,cAAc,CAAC,GAAG,gBAAgB,EAAG;AACvE,QAAI,YAAY,EAAG,MAAK;AAAA,EAC1B,CAAC;AACH;AAWO,SAAS,eAAe,OAAgB,QAAQ,GAAoD;AACzG,QAAM,MAAM,MAAM;AAClB,QAAM,SAAS,IAAI,iBAAiB,IAAI,iBAAiB,WAAW,YAAY;AAChF,QAAM,SAAS,CAACA,UACdA,iBAAgB,eAAeA,MAAK,QAAQ,QAAQ,KAAK,UAAUA,KAAI;AAEzE,QAAM,QAAuB,CAAC;AAC9B,SAAO,cAAc;AACrB,MAAI,OAAoB,OAAO,YAAY;AAC3C,SAAO,CAAC,QAAQ,OAAO,WAAW,EAAG,QAAO,OAAO,YAAY;AAC/D,SAAO,QAAQ,MAAM,SAAS,OAAO,OAAO,OAAO,SAAS,EAAG,KAAI,OAAO,IAAI,EAAG,OAAM,KAAK,IAAI;AAEhG,QAAM,SAAwB,CAAC;AAC/B,SAAO,cAAc;AACrB,OAAK,OAAO,OAAO,aAAa,GAAG,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,aAAa;AAC3F,QAAI,OAAO,IAAI,EAAG,QAAO,KAAK,IAAI;AACpC,SAAO,EAAE,OAAO,OAAO;AACzB;AAOO,SAAS,WAAW,YAA2D;AACpF,aAAW,MAAM,YAAY;AAC3B,QAAI,EAAE,cAAc,gBAAgB,CAAC,GAAG,YAAa;AACrD,OAAG,MAAM;AACT,QAAI,SAAS,kBAAkB,GAAI,QAAO;AAAA,EAC5C;AACA,SAAO;AACT;AAOO,SAAS,eAAe,IAAuB;AACpD,MAAI,CAAC,GAAG,aAAa,UAAU,GAAG;AAChC,OAAG,aAAa,YAAY,IAAI;AAChC,OAAG,iBAAiB,QAAQ,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EAClF;AACA,KAAG,MAAM;AACX;","names":["node"]}
1
+ {"version":3,"sources":["../../src/lib/focus-left.ts"],"sourcesContent":["// \"The focused node left\" (refactor audit 2026-10-30, Wave 1: DSP-4, DSP-15). When the\n// element holding the keyboard's focus is removed — a table row deleted, filtered out or\n// approved; a bulk bar gone with its Clear; a banner its × took away — the browser puts\n// focus on <body> and says nothing: no blur in Chromium, none in jsdom. A keyboard or\n// screen-reader user then starts again from the top of the page. These helpers are for\n// the controls that leave the page while the focus is IN them, and decide where it goes\n// instead; `returnFocus` is the one rule for that and for an overlay giving the focus\n// back as it closes (0.52 code review 2026-10-31, held to 0.55; use-focus-trap reads it).\n\n/**\n * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab\n * really reaches. The kit's one copy of the rule (0.52 code review, lib/focus-left.ts):\n * this file and use-focus-trap each had it, with an exported `firstTabStop` apiece that\n * disagreed about the element itself, and a combobox panel's toolbar had a third\n * selector. The trap, the focus a removed node drops (below), the tour's field ring and\n * the toolbar all read it here, so \"the next stop\" is the same stop everywhere.\n *\n * The selector is the one `Modal` used. On its own it was not enough (round 0.34\n * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or\n * a link taken out of the tab order still counted. A panel that ends in a roving grid —\n * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`\n * but the active cell — then had a \"last\" stop no Tab ever reaches: Tab from the real\n * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the\n * first landed on a cell that is not a stop at all.\n */\n// `input[type=hidden]` is no stop: a Tab never lands on it and focus() on it does nothing,\n// but its tabIndex reads 0, so a trap could take a form's hidden field for its first or last\n// stop and Shift+Tab left the dialog (0.52 code review).\nconst TAB_STOP =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type=\"hidden\"]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\n/**\n * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not\n * inside something `hidden` or `inert`. Removes only elements a native Tab never\n * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are\n * still not walked — they need layout, which this rule has never read.\n */\nfunction isTabStop(el: HTMLElement): boolean {\n return el.tabIndex >= 0 && !el.closest(\"[hidden],[inert]\");\n}\n\nexport interface TabStopOptions {\n /** Whether `el` itself counts, first, when it is a stop. Default true: the tour's\n * target may BE the field (refactor audit 2026-10-30, DUP-9). False for \"a control\n * inside it\" — a row, a drawer, a trap's container, a toolbar. */\n includeSelf?: boolean;\n}\n\n/** The tab stops in `el`, in DOM order — `el` itself first when it is one, unless\n * `includeSelf: false`. */\nexport function tabStops(el: Element, { includeSelf = true }: TabStopOptions = {}): HTMLElement[] {\n const inside = Array.from(el.querySelectorAll<HTMLElement>(TAB_STOP)).filter(isTabStop);\n const own = includeSelf && el instanceof HTMLElement && el.matches(TAB_STOP) && isTabStop(el);\n return own ? [el, ...inside] : inside;\n}\n\n/** The first of {@link tabStops} — the control a person types into or presses — or null. */\nexport function firstTabStop(el: Element, options?: TabStopOptions): HTMLElement | null {\n return tabStops(el, options)[0] ?? null;\n}\n\n/**\n * Whether the focus has fallen out of the page: onto `<body>`, onto nothing, or onto a\n * node no longer in the document. False on the server, where nothing has focus to lose.\n * A recovery asks this first: focus that went somewhere real (a dialog, the next field,\n * an app's own `focus()`) is the user's or the app's, and is left where it is.\n */\nexport function focusIsLost(): boolean {\n if (typeof document === \"undefined\") return false;\n const el = document.activeElement;\n return !el || el === document.body || !el.isConnected;\n}\n\n/**\n * Calls `then` when a `blur`/`focusout` was the user's own move to nothing — a click on\n * the page's background, or a click Safari answers by focusing nothing (it focuses no\n * button or link on a click, and blurs what had the focus) — after which a note of where\n * the focus was is stale (0.52 code review: DataTable's row, BulkActionBar's and\n * AlertBanner's notes outlived it, and a removal much later pulled the focus, and the\n * scroll, back to them).\n *\n * No element took the focus, the document holds none, and the node that lost it could\n * still hold it. Not when it was removed, hidden, made inert or disabled: that is the page\n * taking the focus away — the very case those notes are kept for. Not a window losing the\n * focus either, which leaves the node the document's active element. Decided a microtask\n * later, once the event's own work is done: a browser that blurs a node it is removing\n * (or hiding) may do it while the node is still in place, and only after it is gone is\n * that told apart from a click.\n */\nexport function whenFocusLeftForNothing(\n e: { target: EventTarget | null; relatedTarget: EventTarget | null },\n then: () => void,\n): void {\n const el = e.target;\n if (e.relatedTarget !== null || !(el instanceof Element)) return;\n queueMicrotask(() => {\n if (!el.isConnected || el.matches(\":disabled\") || el.closest(\"[hidden],[inert]\")) return;\n // `display: none`, where the browser can say (jsdom cannot).\n if (typeof el.checkVisibility === \"function\" && !el.checkVisibility()) return;\n if (focusIsLost()) then();\n });\n}\n\n/**\n * The tab stops around `frame`, outside it, nearest first on each side — where focus\n * goes once `frame` is gone. Read BEFORE it goes: a removed node has no place in the\n * document to measure from. `limit` per side keeps a long page's stops out of memory.\n *\n * Walked outwards from the frame, `limit` stops each way (0.52 code review,\n * lib/focus-left.ts): read from every stop in the page, a banner under a 2000-row table\n * read all 2000 to keep the nearest five before it.\n */\nexport function tabStopsAround(frame: Element, limit = 5): { after: HTMLElement[]; before: HTMLElement[] } {\n const doc = frame.ownerDocument;\n const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);\n const isStop = (node: Node): node is HTMLElement =>\n node instanceof HTMLElement && node.matches(TAB_STOP) && isTabStop(node);\n // After: from the first element past the frame's own subtree, in document order.\n const after: HTMLElement[] = [];\n walker.currentNode = frame;\n let node: Node | null = walker.nextSibling();\n while (!node && walker.parentNode()) node = walker.nextSibling();\n for (; node && after.length < limit; node = walker.nextNode()) if (isStop(node)) after.push(node);\n // Before: back from the frame, nearest first (its ancestors among them, as before).\n const before: HTMLElement[] = [];\n walker.currentNode = frame;\n for (node = walker.previousNode(); node && before.length < limit; node = walker.previousNode())\n if (isStop(node)) before.push(node);\n return { after, before };\n}\n\n/**\n * Focus the first of `candidates` that is still in the page and takes the focus; true\n * when one did. Each is tried and checked, since `focus()` on a node that cannot hold it\n * (detached, `display: none`, disabled since) fails without a word. An SVG element counts\n * (a chart's tile, a slice); `<body>` and `<html>` never do — \"focused\" there is the\n * focus lost, not given (0.52 code review 2026-10-31, held to 0.55).\n */\nexport function focusFirst(candidates: Iterable<Element | null | undefined>): boolean {\n for (const el of candidates) {\n if (!(el instanceof HTMLElement || el instanceof SVGElement) || !el.isConnected) continue;\n if (el === el.ownerDocument.body || el === el.ownerDocument.documentElement) continue;\n el.focus();\n if (el.ownerDocument.activeElement === el) return true;\n }\n return false;\n}\n\n/** {@link returnFocus}'s three answers to \"where was this opened from\". */\nexport interface FocusReturn {\n /** The control it was opened from: the overlay's trigger when it names one, else what\n * held the focus as it opened. */\n opener?: Element | null;\n /** Where the focus goes when the opener cannot take it, nearest first — the overlay's\n * stated fallback. */\n fallback?: Iterable<Element | null | undefined>;\n /** What is closing, while it is still in the page (a panel on its way out, a list\n * about to unmount): a focus inside it is one to give back. */\n closing?: Element | null;\n}\n\n/**\n * Where the focus goes when an overlay closes, or a control holding it leaves the page —\n * the kit's ONE rule for it (0.52 code review 2026-10-31, held to 0.55). The dialogs, the\n * popovers and menus, the drawers, the phone sheets, the tour, AlertBanner's × and the\n * Treemap's tiles each decided it themselves, and they drifted: the trap gave the focus\n * back whenever its opener was still connected — over a focus the app had just placed —\n * and left it on `<body>` when it was not; the date and month pickers and the floating\n * panel moved it only when it was lost; the dropdown, the combobox and the column drawer\n * moved it to their trigger every time, unchecked; and a Treemap tile's note of where to\n * go once outlived the commit it was for and took the focus back, from wherever the\n * reader was, when the map was shown again.\n *\n * 1. **Only a lost focus is moved** — on `<body>`, on nothing or on a removed node\n * ({@link focusIsLost}) — or one still inside what is `closing`. A focus that went\n * somewhere real is the user's (an outside press landed on a field) or the app's (its\n * own `focus()` in `onClose`), and stays.\n * 2. **To the opener**, if it is still in the page and takes the focus: `focus()` is\n * checked, as a disabled, hidden or inert opener refuses it without a word.\n * 3. **Else to the stated fallback**, the first of it that does — each caller names its\n * own: the trap the stops that stood around its opener, a banner the stops around its\n * ×, the map the tile that holds its tab stop.\n * 4. **Never to `<body>`**: no `focus()` on a node that has left, none on `<body>`, no\n * `blur()`. When nothing can take the focus — the page under the overlay was replaced\n * — it is left for the page's own handling of a new page, not guessed at.\n * 5. **Only in the closing commit**: called from the commit that closes or removes (a\n * handler that closes, a ref callback, a layout effect, an effect's cleanup), and\n * nothing handed to it is kept. A note gathered earlier, where a removed node can no\n * longer be measured from, is spent or dropped in that commit.\n *\n * True when it moved the focus.\n */\nexport function returnFocus({ opener, fallback = [], closing }: FocusReturn): boolean {\n if (typeof document === \"undefined\") return false;\n const active = document.activeElement;\n const inside = Boolean(closing?.isConnected && active && closing.contains(active));\n if (!inside && !focusIsLost()) return false;\n return focusFirst([opener, ...fallback]);\n}\n\n/**\n * Focus a container that is not a tab stop of its own — a table, a list — as the place\n * the user is in, without making it one: `tabindex=\"-1\"` for as long as it holds the\n * focus, unless it already carries a `tabindex` of its own.\n */\nexport function focusContainer(el: HTMLElement): void {\n if (!el.hasAttribute(\"tabindex\")) {\n el.setAttribute(\"tabindex\", \"-1\");\n el.addEventListener(\"blur\", () => el.removeAttribute(\"tabindex\"), { once: true });\n }\n el.focus();\n}\n"],"mappings":";AA4BA,MAAM,WACJ;AAQF,SAAS,UAAU,IAA0B;AAC3C,SAAO,GAAG,YAAY,KAAK,CAAC,GAAG,QAAQ,kBAAkB;AAC3D;AAWO,SAAS,SAAS,IAAa,EAAE,cAAc,KAAK,IAAoB,CAAC,GAAkB;AAChG,QAAM,SAAS,MAAM,KAAK,GAAG,iBAA8B,QAAQ,CAAC,EAAE,OAAO,SAAS;AACtF,QAAM,MAAM,eAAe,cAAc,eAAe,GAAG,QAAQ,QAAQ,KAAK,UAAU,EAAE;AAC5F,SAAO,MAAM,CAAC,IAAI,GAAG,MAAM,IAAI;AACjC;AAGO,SAAS,aAAa,IAAa,SAA8C;AACtF,SAAO,SAAS,IAAI,OAAO,EAAE,CAAC,KAAK;AACrC;AAQO,SAAS,cAAuB;AACrC,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,KAAK,SAAS;AACpB,SAAO,CAAC,MAAM,OAAO,SAAS,QAAQ,CAAC,GAAG;AAC5C;AAkBO,SAAS,wBACd,GACA,MACM;AACN,QAAM,KAAK,EAAE;AACb,MAAI,EAAE,kBAAkB,QAAQ,EAAE,cAAc,SAAU;AAC1D,iBAAe,MAAM;AACnB,QAAI,CAAC,GAAG,eAAe,GAAG,QAAQ,WAAW,KAAK,GAAG,QAAQ,kBAAkB,EAAG;AAElF,QAAI,OAAO,GAAG,oBAAoB,cAAc,CAAC,GAAG,gBAAgB,EAAG;AACvE,QAAI,YAAY,EAAG,MAAK;AAAA,EAC1B,CAAC;AACH;AAWO,SAAS,eAAe,OAAgB,QAAQ,GAAoD;AACzG,QAAM,MAAM,MAAM;AAClB,QAAM,SAAS,IAAI,iBAAiB,IAAI,iBAAiB,WAAW,YAAY;AAChF,QAAM,SAAS,CAACA,UACdA,iBAAgB,eAAeA,MAAK,QAAQ,QAAQ,KAAK,UAAUA,KAAI;AAEzE,QAAM,QAAuB,CAAC;AAC9B,SAAO,cAAc;AACrB,MAAI,OAAoB,OAAO,YAAY;AAC3C,SAAO,CAAC,QAAQ,OAAO,WAAW,EAAG,QAAO,OAAO,YAAY;AAC/D,SAAO,QAAQ,MAAM,SAAS,OAAO,OAAO,OAAO,SAAS,EAAG,KAAI,OAAO,IAAI,EAAG,OAAM,KAAK,IAAI;AAEhG,QAAM,SAAwB,CAAC;AAC/B,SAAO,cAAc;AACrB,OAAK,OAAO,OAAO,aAAa,GAAG,QAAQ,OAAO,SAAS,OAAO,OAAO,OAAO,aAAa;AAC3F,QAAI,OAAO,IAAI,EAAG,QAAO,KAAK,IAAI;AACpC,SAAO,EAAE,OAAO,OAAO;AACzB;AASO,SAAS,WAAW,YAA2D;AACpF,aAAW,MAAM,YAAY;AAC3B,QAAI,EAAE,cAAc,eAAe,cAAc,eAAe,CAAC,GAAG,YAAa;AACjF,QAAI,OAAO,GAAG,cAAc,QAAQ,OAAO,GAAG,cAAc,gBAAiB;AAC7E,OAAG,MAAM;AACT,QAAI,GAAG,cAAc,kBAAkB,GAAI,QAAO;AAAA,EACpD;AACA,SAAO;AACT;AA8CO,SAAS,YAAY,EAAE,QAAQ,WAAW,CAAC,GAAG,QAAQ,GAAyB;AACpF,MAAI,OAAO,aAAa,YAAa,QAAO;AAC5C,QAAM,SAAS,SAAS;AACxB,QAAM,SAAS,QAAQ,SAAS,eAAe,UAAU,QAAQ,SAAS,MAAM,CAAC;AACjF,MAAI,CAAC,UAAU,CAAC,YAAY,EAAG,QAAO;AACtC,SAAO,WAAW,CAAC,QAAQ,GAAG,QAAQ,CAAC;AACzC;AAOO,SAAS,eAAe,IAAuB;AACpD,MAAI,CAAC,GAAG,aAAa,UAAU,GAAG;AAChC,OAAG,aAAa,YAAY,IAAI;AAChC,OAAG,iBAAiB,QAAQ,MAAM,GAAG,gBAAgB,UAAU,GAAG,EAAE,MAAM,KAAK,CAAC;AAAA,EAClF;AACA,KAAG,MAAM;AACX;","names":["node"]}
@@ -2,8 +2,8 @@ import { type MinusStyle } from "./minus.js";
2
2
  export type { MinusStyle } from "./minus.js";
3
3
  export { formatWithMinus } from "./minus.js";
4
4
  /**
5
- * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,
6
- * for the figures an app prints OUTSIDE a kit component.
5
+ * Number, money, percentage, duration (0.54), date and relative-time formatting — `Intl`,
6
+ * in one place, for the figures an app prints OUTSIDE a kit component.
7
7
  *
8
8
  * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned
9
9
  * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`
@@ -83,6 +83,18 @@ export interface FormatMoneyOptions extends FormatNumberBaseOptions {
83
83
  /** `"symbol"` (default), `"narrowSymbol"`, `"code"` ("CHF 12.00") or `"name"`. */
84
84
  currencyDisplay?: Intl.NumberFormatOptions["currencyDisplay"];
85
85
  }
86
+ export interface FormatDurationOptions extends BaseOptions {
87
+ /**
88
+ * How the units are written — `Intl.NumberFormat`'s `unitDisplay`, in `locale`:
89
+ * - `"narrow"` (default): "3m 12s", "3 Min. 12 Sek." (de-CH), "3min 12s" (fr) —
90
+ * keksdose's scan readout;
91
+ * - `"short"`: "3 min 12 sec", "3 Min. 12 Sek.", "3 min 12 s";
92
+ * - `"long"`: "3 minutes 12 seconds", "3 Minuten 12 Sekunden", "3 minutes 12 secondes".
93
+ */
94
+ unitDisplay?: "narrow" | "short" | "long";
95
+ /** The minus of a negative duration — {@link FormatNumberBaseOptions.minus}. */
96
+ minus?: MinusStyle;
97
+ }
86
98
  export interface FormatPercentOptions extends FormatNumberBaseOptions {
87
99
  /**
88
100
  * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read
@@ -105,6 +117,27 @@ export declare function formatMoney(value: FormattableNumber, currency: string,
105
117
  /** A percentage in `locale` ("12%", "12 %", "+1.25%"). See {@link FormatPercentOptions.ratio}
106
118
  * for what `value` means. Default digits: at most one. */
107
119
  export declare function formatPercent(value: FormattableNumber, options?: FormatPercentOptions): string;
120
+ /**
121
+ * A number of SECONDS as an elapsed time in `locale`: "42s", "3m 12s", "1h 0m 5s" — and in
122
+ * German "3 Min. 12 Sek.", in French "3min 12s" (keksdose audit follow-up, 2026-11-01:
123
+ * keksdose's `formatElapsed`, the scan card's readout, moves here). Each unit is
124
+ * `Intl.NumberFormat`'s `style: "unit"`, so the digits and the unit's spelling follow
125
+ * `locale`; `unitDisplay` picks the form, narrow by default.
126
+ *
127
+ * - Seconds alone under a minute ("0s", "59s"); minutes and seconds under an hour ("1m 0s",
128
+ * "59m 59s"); from an hour on, hours, minutes and seconds ("1h 0m 0s"). Hours are the
129
+ * largest unit: a day and a bit is "27h 46m 40s". Every unit below the first is printed,
130
+ * zero too, so a ticking readout keeps its shape from one second to the next.
131
+ * - Whole seconds, cut toward zero: a timer reads "59s" until the minute is full.
132
+ * - The parts are joined by a space, as the readout always was — not `Intl.ListFormat`'s
133
+ * "3 Min. und 12 Sek.", which is a sentence rather than a figure.
134
+ * - Negative: the minus on the first unit, as `Intl.DurationFormat` writes one — "−3m 12s",
135
+ * with the kit's minus (`minus`). An elapsed time from a server's clock can come out a
136
+ * second below zero; clamp it before, as keksdose's card does, when that is not news.
137
+ * - `null` / `undefined` print `empty`; `NaN`, ±`Infinity` and a non-numeric string too,
138
+ * with the formatters' development warning. A numeric string ("185") is a number.
139
+ */
140
+ export declare function formatDuration(seconds: FormattableNumber, options?: FormatDurationOptions): string;
108
141
  /**
109
142
  * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:
110
143
  * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`
@@ -180,6 +213,8 @@ export interface KitFormat {
180
213
  formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;
181
214
  formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;
182
215
  formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;
216
+ /** 0.54: {@link formatDuration}, a number of seconds as an elapsed time. */
217
+ formatDuration: (seconds: FormattableNumber, options?: WithoutLocale<FormatDurationOptions>) => string;
183
218
  formatDate: (value: DateInput, style?: FormatDateStyle | Intl.DateTimeFormatOptions, options?: WithoutLocale<FormatDateOptions>) => string;
184
219
  formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;
185
220
  }
@@ -40,7 +40,7 @@ function devWarnings() {
40
40
  return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;
41
41
  }
42
42
  const warnedSites = /* @__PURE__ */ new Set();
43
- const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\b/;
43
+ const OWN_FRAME = /\b(?:formatNumber|formatMoney|formatPercent|formatDuration|toFormattable|warnInvalid|callSite)\b/;
44
44
  function callSite() {
45
45
  const frames = (new Error().stack ?? "").split("\n").slice(1);
46
46
  return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? "";
@@ -104,6 +104,28 @@ function formatPercent(value, options = {}) {
104
104
  });
105
105
  return formatWithMinus(formatter, ratio, options.minus);
106
106
  }
107
+ const DURATION_UNITS = [
108
+ ["hour", 3600],
109
+ ["minute", 60],
110
+ ["second", 1]
111
+ ];
112
+ function formatDuration(seconds, options = {}) {
113
+ const n = toFormattable("formatDuration", seconds);
114
+ if (n === void 0) return options.empty ?? EMPTY_FORMATTED_VALUE;
115
+ const unitDisplay = options.unitDisplay ?? "narrow";
116
+ const negative = n < 0;
117
+ let rest = Math.trunc(Math.abs(n));
118
+ const parts = [];
119
+ for (const [unit, size] of DURATION_UNITS) {
120
+ const count = Math.floor(rest / size);
121
+ rest -= count * size;
122
+ if (parts.length === 0 && count === 0 && unit !== "second") continue;
123
+ const formatter = numberFormatter(options.locale, { style: "unit", unit, unitDisplay });
124
+ const signed = negative && parts.length === 0 && count > 0 ? -count : count;
125
+ parts.push(formatWithMinus(formatter, signed, options.minus));
126
+ }
127
+ return parts.join(" ");
128
+ }
107
129
  const DATE_STYLES = {
108
130
  short: { dateStyle: "short" },
109
131
  medium: { dateStyle: "medium" },
@@ -187,6 +209,7 @@ function useKitFormat(locale) {
187
209
  formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),
188
210
  formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),
189
211
  formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),
212
+ formatDuration: (seconds, options) => formatDuration(seconds, { locale: kitLocale, ...options }),
190
213
  formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),
191
214
  formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options })
192
215
  }),
@@ -211,6 +234,7 @@ export {
211
234
  EMPTY_FORMATTED_VALUE,
212
235
  currencyMinorDigits,
213
236
  formatDate,
237
+ formatDuration,
214
238
  formatMoney,
215
239
  formatNumber,
216
240
  formatPercent,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale, useKitTimeZone } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\nimport { formatWithMinus, type MinusStyle } from \"./minus\";\nimport { roundHalfAway } from \"./round\";\n\nexport type { MinusStyle } from \"./minus\";\n// 0.50 (KIT-1 follow-up): the kit's minus for an app's OWN `Intl.NumberFormat` wrapper.\nexport { formatWithMinus } from \"./minus\";\n\n/**\n * Number, money, percentage, date and relative-time formatting — `Intl`, in one place,\n * for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, an unparseable date — formats as `empty`\n * (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or \"Invalid\n * Date\". A number may also arrive as a numeric STRING (0.49, KIT-2) — \"1850.00\", \"-12.5\",\n * \"0\": a Decimal field off a Python API — and is formatted like the number. Anything\n * else (a non-numeric string, an object, `NaN`, ±`Infinity`) prints `empty` too, but is\n * a bug upstream rather than a gap in the data, so a development build says so on the\n * console, once per call site. Zero is a figure: `0` and `\"0\"` print \"CHF 0.00\", never\n * the dash.\n *\n * A negative figure carries the typographic minus, U+2212 (0.49, KIT-1), in every\n * locale — `Intl` writes a hyphen in most. `minus: \"ascii\"` for text a machine reads\n * back (a CSV, the clipboard).\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n /**\n * The minus sign of a negative figure (0.49, KIT-1). `\"typographic\"` (default):\n * U+2212, as wide as \"+\" and the digits, as `SignedAmount` prints it — in every\n * locale, also where `Intl` writes a hyphen. `\"ascii\"`: U+002D, for an export a\n * machine parses back (a CSV download, the clipboard) — there, also pass\n * `options: { useGrouping: false }` or write the raw number.\n */\n minus?: MinusStyle;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\n/**\n * What the number formatters take (0.49, KIT-2): a number, a numeric string (\"1850.00\",\n * \"-12.5\", \"1e3\" — what a Decimal arrives as in JSON), or `null` / `undefined` for no\n * value. A string is read with `Number()`, so a figure with more significant digits than\n * a double holds (about 15–17) is printed as its nearest double.\n */\nexport type FormattableNumber = number | string | null | undefined;\n\n/** A plain decimal: optional sign, digits with an optional point, an optional exponent.\n * No grouping marks, no decimal comma, no currency — those are formatted text, and\n * reading them back is a locale's guess. */\nconst NUMERIC_STRING = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one (the consumer's bundler makes `import.meta.env.DEV` false) and never\n * under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\nconst warnedSites = new Set<string>();\n/** This module's own frames, skipped to find the caller (\"Object.formatMoney\" is a\n * `useKitFormat` wrapper). */\nconst OWN_FRAME = /\\b(?:formatNumber|formatMoney|formatPercent|toFormattable|warnInvalid|callSite)\\b/;\n\n/** The first stack frame outside the formatters: where the bad value was passed. */\nfunction callSite(): string {\n const frames = (new Error().stack ?? \"\").split(\"\\n\").slice(1);\n return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? \"\";\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") return String(value);\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\n/** A development build's console note about an invalid value — once per call site. */\nfunction warnInvalid(fn: string, value: unknown): void {\n if (!devWarnings()) return;\n const site = callSite();\n const key = `${fn}|${site || typeof value}`;\n if (warnedSites.has(key)) return;\n warnedSites.add(key);\n const type = Array.isArray(value) ? \"array\" : typeof value;\n console.warn(\n `[ui-kit] ${fn} got ${describeValue(value)} (${type}), which is not a number: printed the empty value. ` +\n \"Pass a number, a numeric string, or null/undefined for a missing value.\" +\n (site ? `\\n at ${site.replace(/^at /, \"\")}` : \"\"),\n );\n}\n\n/**\n * `value` as a finite number to format, or `undefined` to print `empty`. `null` and\n * `undefined` are a missing value and pass quietly; anything that is neither a finite\n * number nor a numeric string is an invalid one — ±Infinity included (lenkbank, adopting\n * 0.13: a division by zero upstream; \"∞\" in a money column reads like data) — and a\n * development build warns. `-0` is folded into `0`: zero carries no sign.\n */\nfunction toFormattable(fn: string, value: unknown): number | undefined {\n if (value === null || value === undefined) return undefined;\n let n: number | undefined;\n if (typeof value === \"number\") n = value;\n else if (typeof value === \"string\" && NUMERIC_STRING.test(value.trim())) n = Number(value.trim());\n if (n === undefined || !Number.isFinite(n)) {\n warnInvalid(fn, value);\n return undefined;\n }\n return n + 0;\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: FormattableNumber, options: FormatNumberOptions = {}): string {\n const n = toFormattable(\"formatNumber\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = formatWithMinus(numberFormatter(options.locale, numberOptions(options)), n, options.minus);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(value: FormattableNumber, currency: string, options: FormatMoneyOptions = {}): string {\n const n = toFormattable(\"formatMoney\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const formatter = numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, n, options.minus);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: FormattableNumber, options: FormatPercentOptions = {}): string {\n const n = toFormattable(\"formatPercent\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? n / 100 : n;\n const formatter = numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, ratio, options.minus);\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. A bare\n * \"YYYY-MM-DD\" ignores it: a calendar day is the day it names in every zone. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (isBareIsoDay(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n // A bare \"YYYY-MM-DD\" is a calendar day, which has no zone: it is the day it names\n // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the\n // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).\n if (isBareIsoDay(value)) {\n const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\n return dateFormatter(options.locale, { ...base, timeZone: \"UTC\" }).format(day);\n }\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The zone of the date printed past `absoluteAfterDays` (0.46), and (0.51.7) the zone\n * whose calendar days \"yesterday\" and \"2 days ago\" count. Left out: the device's. */\n timeZone?: string;\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\n/**\n * `d`'s calendar day in `timeZone` (left out: the device's), as a whole number of days:\n * that day's UTC midnight over a day's length. Two of them subtract to whole calendar\n * days — a 23- or 25-hour DST day still counts one.\n */\nfunction dayNumber(d: Date, timeZone: string | undefined): number {\n if (!timeZone) return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY;\n // `en-US`: Gregorian, Latin digits — the parts are read back as numbers.\n const fields: Intl.DateTimeFormatOptions = { timeZone, year: \"numeric\", month: \"numeric\", day: \"numeric\" };\n const parts = dateFormatter(\"en-US\", fields).formatToParts(d);\n const part = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((p) => p.type === type)?.value);\n return Date.UTC(part(\"year\"), part(\"month\") - 1, part(\"day\")) / DAY;\n}\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded; days are\n * CALENDAR days (0.51.7), in `timeZone` when given: \"yesterday\" is the day before today,\n * however few hours ago that was.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n if (unit === \"day\") {\n // By the calendar, not in 24-hour units (refactor audit 2026-10-30, DSP-7): rounded\n // elapsed time made 00:10 seen at 23:50 \"yesterday\" and 01:00 yesterday seen at 23:30\n // \"2 days ago\", beside an exact date (DateMark's second line, the feedback thread's\n // tooltip) that said otherwise. Counted in `timeZone`, the zone that date is printed\n // in; a bare \"YYYY-MM-DD\" is the day it names wherever it is read, as in formatDate.\n // Still the same day (22 hours and more since just after midnight): in hours.\n const bareDay = isBareIsoDay(value);\n const days = dayNumber(d, bareDay ? undefined : options.timeZone) - dayNumber(now, options.timeZone);\n return days === 0 ? rtf.format(Math.round(diff / HOUR), \"hour\") : rtf.format(days, \"day\");\n }\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;\n formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n // 0.46 (§10.3): the provider's `timeZone` for timestamps; a call's own wins, and a bare\n // \"YYYY-MM-DD\" ignores it (formatDate).\n const timeZone = useKitTimeZone();\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),\n }),\n [kitLocale, timeZone],\n );\n}\n\n/* ── Currency minor units ──────────────────────────────────────────────────── */\n\n/**\n * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,\n * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule\n * AmountInput settles a typed figure by, for a host that needs it outside the field.\n */\nexport function currencyMinorDigits(currency: string | null | undefined): number | undefined {\n if (!currency) return undefined;\n try {\n return new Intl.NumberFormat(\"en\", { style: \"currency\", currency }).resolvedOptions().maximumFractionDigits;\n } catch {\n return undefined;\n }\n}\n\n/** The options form of {@link roundToCurrency}'s third argument. */\nexport interface RoundToCurrencyOptions {\n /** Decimals to round to, whatever the currency — the plain `digits` argument. */\n digits?: number;\n /**\n * Decimals for a currency `Intl` cannot resolve — no code yet (`null`, `\"\"`), or a\n * malformed one (\"EURO\", \"€\"). Left out, such a value comes back unchanged, as\n * before. Note that `Intl` resolves ANY well-formed three-letter code, a made-up\n * \"XYZ\" included (to 2), so this is the case of a missing or broken code, not of a\n * rare currency.\n */\n fallbackDigits?: number;\n}\n\n/**\n * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,\n * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *\n * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency\n * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an\n * FX estimate set into an AmountInput showed four decimals) — unless the options form\n * gives `fallbackDigits`: `roundToCurrency(v, code, { fallbackDigits: 2 })` rounds a\n * figure whose currency is not chosen yet to cents rather than leaving 0.1 + 0.2 as is.\n * A finite value never comes back NaN or -0, however small or large (BUG-11):\n * 0.1 + 0.2 - 0.3 → 0, -0.004 → 0.\n */\nexport function roundToCurrency(\n value: number,\n currency: string | null | undefined,\n digits?: number | RoundToCurrencyOptions,\n): number {\n const options = typeof digits === \"object\" && digits !== null ? digits : { digits };\n const places = options.digits ?? currencyMinorDigits(currency) ?? options.fallbackDigits;\n if (places === undefined) return value;\n return roundHalfAway(value, places);\n}\n"],"mappings":"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","names":["formatWithMinus"]}
1
+ {"version":3,"sources":["../../src/lib/format.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useKitLocale, useKitTimeZone } from \"../i18n/kit-labels\";\nimport { parseIsoDate } from \"./dates\";\nimport { dateFormatter } from \"./intl-cache\";\nimport { isBareIsoDay } from \"./iso-day\";\nimport { formatWithMinus, type MinusStyle } from \"./minus\";\nimport { roundHalfAway } from \"./round\";\n\nexport type { MinusStyle } from \"./minus\";\n// 0.50 (KIT-1 follow-up): the kit's minus for an app's OWN `Intl.NumberFormat` wrapper.\nexport { formatWithMinus } from \"./minus\";\n\n/**\n * Number, money, percentage, duration (0.54), date and relative-time formatting — `Intl`,\n * in one place, for the figures an app prints OUTSIDE a kit component.\n *\n * Each app grew its own: kastlan's `utils/formatters.ts` (a Swiss-pinned\n * `formatNumber`/`formatCurrency`, a date-fns `formatDate`, and a `formatRelativeTime`\n * whose \"5m ago\" / \"3h ago\" / \"2d ago\" stayed English in every language), its tenancy\n * `format.ts` (`formatChf`, a `formatPct` gluing a \"+\" onto `toFixed(2)`), keksdose's\n * own set. Every one of them is `Intl` with a locale bolted on, so the only thing\n * worth sharing is the locale — and the kit already has one, the provider's.\n *\n * The plain functions take a `locale` (none: the runtime default, as every `Intl` API\n * reads `undefined`). Inside a component, {@link useKitFormat} hands back the same\n * functions bound to the `<UiKitProvider locale>`, so a page formats exactly like the\n * kit components on it.\n *\n * A missing value — `null`, `undefined`, an unparseable date — formats as `empty`\n * (default \"—\", the dash `StatTile` shows for no value), never as \"NaN\" or \"Invalid\n * Date\". A number may also arrive as a numeric STRING (0.49, KIT-2) — \"1850.00\", \"-12.5\",\n * \"0\": a Decimal field off a Python API — and is formatted like the number. Anything\n * else (a non-numeric string, an object, `NaN`, ±`Infinity`) prints `empty` too, but is\n * a bug upstream rather than a gap in the data, so a development build says so on the\n * console, once per call site. Zero is a figure: `0` and `\"0\"` print \"CHF 0.00\", never\n * the dash.\n *\n * A negative figure carries the typographic minus, U+2212 (0.49, KIT-1), in every\n * locale — `Intl` writes a hyphen in most. `minus: \"ascii\"` for text a machine reads\n * back (a CSV, the clipboard).\n */\n\n/** What every formatter here prints for a missing value, unless told otherwise. */\nexport const EMPTY_FORMATTED_VALUE = \"—\";\n\n/** Fraction digits: a number pins both the minimum and the maximum (`2` → \"3.10\");\n * an object sets them apart (a meter reading, 1–3). */\nexport type FormatDigits = number | { min?: number; max?: number };\n\ninterface BaseOptions {\n /** BCP 47 tag. Left out: the runtime default (the hook: the provider's). */\n locale?: string;\n /** Printed for a missing value. Default {@link EMPTY_FORMATTED_VALUE}. */\n empty?: string;\n}\n\n/** The options every number formatter shares (money and percent carry their own unit). */\nexport interface FormatNumberBaseOptions extends BaseOptions {\n digits?: FormatDigits;\n /** Compact notation: 12 400 → \"12K\". */\n compact?: boolean;\n /** `\"exceptZero\"` for a change (\"+3\", \"−3\", \"0\"). Default: the minus only. */\n signDisplay?: Intl.NumberFormatOptions[\"signDisplay\"];\n /** Any further `Intl.NumberFormat` options, merged over the ones above. */\n options?: Intl.NumberFormatOptions;\n /**\n * The minus sign of a negative figure (0.49, KIT-1). `\"typographic\"` (default):\n * U+2212, as wide as \"+\" and the digits, as `SignedAmount` prints it — in every\n * locale, also where `Intl` writes a hyphen. `\"ascii\"`: U+002D, for an export a\n * machine parses back (a CSV download, the clipboard) — there, also pass\n * `options: { useGrouping: false }` or write the raw number.\n */\n minus?: MinusStyle;\n}\n\n/** The options {@link formatNumber} takes: the shared ones plus a free-text `unit`. */\nexport interface FormatNumberOptions extends FormatNumberBaseOptions {\n /**\n * A unit printed after the figure, joined by a narrow no-break space (U+202F):\n * \"3.400 N\", \"12 kg\", \"4,5 m²\". lenkbank (P8) prints forces, masses and lengths in\n * every table and summary, and each call site glued `${n} N` together with a plain\n * space — which wraps the \"N\" onto the next line in a narrow column and reads loose\n * beside the digit grouping. The narrow no-break space is the SI brochure's and the\n * Swiss typesetting rule's separator between value and unit, and it never breaks.\n *\n * Free text, not `Intl`'s `style: \"unit\"`: that one only knows the ECMA-402\n * sanctioned units (no \"N\", no \"kN\", no \"m³/h\"), so an app would still be gluing.\n * A missing value prints `empty` ALONE — \"— N\" says there is a newton-shaped\n * nothing, which is noise in a column that already heads its unit.\n */\n unit?: string;\n}\n\n/** Narrow no-break space (U+202F): between a figure and its {@link FormatNumberOptions.unit}. */\nconst UNIT_SEPARATOR = \"\\u202F\";\n\nexport interface FormatMoneyOptions extends FormatNumberBaseOptions {\n /** `\"symbol\"` (default), `\"narrowSymbol\"`, `\"code\"` (\"CHF 12.00\") or `\"name\"`. */\n currencyDisplay?: Intl.NumberFormatOptions[\"currencyDisplay\"];\n}\n\nexport interface FormatDurationOptions extends BaseOptions {\n /**\n * How the units are written — `Intl.NumberFormat`'s `unitDisplay`, in `locale`:\n * - `\"narrow\"` (default): \"3m 12s\", \"3 Min. 12 Sek.\" (de-CH), \"3min 12s\" (fr) —\n * keksdose's scan readout;\n * - `\"short\"`: \"3 min 12 sec\", \"3 Min. 12 Sek.\", \"3 min 12 s\";\n * - `\"long\"`: \"3 minutes 12 seconds\", \"3 Minuten 12 Sekunden\", \"3 minutes 12 secondes\".\n */\n unitDisplay?: \"narrow\" | \"short\" | \"long\";\n /** The minus of a negative duration — {@link FormatNumberBaseOptions.minus}. */\n minus?: MinusStyle;\n}\n\nexport interface FormatPercentOptions extends FormatNumberBaseOptions {\n /**\n * `true` (default): `value` is a RATIO, as `Intl` and `StatTile`'s percent delta read\n * it — `0.12` is 12 %. `false`: it is already a percentage — `12` is 12 %\n * (kastlan's rent `change_pct`).\n */\n ratio?: boolean;\n}\n\n// One formatter per option set: a table formats the same way thousands of times, and\n// constructing an Intl formatter is the expensive part.\nconst numberFormatters = new Map<string, Intl.NumberFormat>();\nfunction numberFormatter(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = numberFormatters.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormatters.set(key, f);\n }\n return f;\n}\n\nfunction digitOptions(digits: FormatDigits | undefined): Intl.NumberFormatOptions {\n if (digits === undefined) return {};\n if (typeof digits === \"number\") return { minimumFractionDigits: digits, maximumFractionDigits: digits };\n return {\n ...(digits.min !== undefined ? { minimumFractionDigits: digits.min } : null),\n ...(digits.max !== undefined ? { maximumFractionDigits: digits.max } : null),\n };\n}\n\nfunction numberOptions(o: FormatNumberBaseOptions): Intl.NumberFormatOptions {\n return {\n ...digitOptions(o.digits),\n ...(o.compact ? { notation: \"compact\" } : null),\n ...(o.signDisplay ? { signDisplay: o.signDisplay } : null),\n ...o.options,\n };\n}\n\n/**\n * What the number formatters take (0.49, KIT-2): a number, a numeric string (\"1850.00\",\n * \"-12.5\", \"1e3\" — what a Decimal arrives as in JSON), or `null` / `undefined` for no\n * value. A string is read with `Number()`, so a figure with more significant digits than\n * a double holds (about 15–17) is printed as its nearest double.\n */\nexport type FormattableNumber = number | string | null | undefined;\n\n/** A plain decimal: optional sign, digits with an optional point, an optional exponent.\n * No grouping marks, no decimal comma, no currency — those are formatted text, and\n * reading them back is a locale's guess. */\nconst NUMERIC_STRING = /^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?$/;\n\n/** The kit's dev switch, as `lib/logger.ts` reads it: a development build, never a\n * production one (the consumer's bundler makes `import.meta.env.DEV` false) and never\n * under vitest. Read per call so a test can switch it on with `vi.stubEnv`. */\nfunction devWarnings(): boolean {\n return Boolean(import.meta.env?.DEV) && !import.meta.env?.VITEST;\n}\n\nconst warnedSites = new Set<string>();\n/** This module's own frames, skipped to find the caller (\"Object.formatMoney\" is a\n * `useKitFormat` wrapper). */\nconst OWN_FRAME = /\\b(?:formatNumber|formatMoney|formatPercent|formatDuration|toFormattable|warnInvalid|callSite)\\b/;\n\n/** The first stack frame outside the formatters: where the bad value was passed. */\nfunction callSite(): string {\n const frames = (new Error().stack ?? \"\").split(\"\\n\").slice(1);\n return frames.find((frame) => !OWN_FRAME.test(frame))?.trim() ?? \"\";\n}\n\nfunction describeValue(value: unknown): string {\n if (typeof value === \"number\" || typeof value === \"bigint\" || typeof value === \"boolean\") return String(value);\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return Object.prototype.toString.call(value);\n }\n}\n\n/** A development build's console note about an invalid value — once per call site. */\nfunction warnInvalid(fn: string, value: unknown): void {\n if (!devWarnings()) return;\n const site = callSite();\n const key = `${fn}|${site || typeof value}`;\n if (warnedSites.has(key)) return;\n warnedSites.add(key);\n const type = Array.isArray(value) ? \"array\" : typeof value;\n console.warn(\n `[ui-kit] ${fn} got ${describeValue(value)} (${type}), which is not a number: printed the empty value. ` +\n \"Pass a number, a numeric string, or null/undefined for a missing value.\" +\n (site ? `\\n at ${site.replace(/^at /, \"\")}` : \"\"),\n );\n}\n\n/**\n * `value` as a finite number to format, or `undefined` to print `empty`. `null` and\n * `undefined` are a missing value and pass quietly; anything that is neither a finite\n * number nor a numeric string is an invalid one — ±Infinity included (lenkbank, adopting\n * 0.13: a division by zero upstream; \"∞\" in a money column reads like data) — and a\n * development build warns. `-0` is folded into `0`: zero carries no sign.\n */\nfunction toFormattable(fn: string, value: unknown): number | undefined {\n if (value === null || value === undefined) return undefined;\n let n: number | undefined;\n if (typeof value === \"number\") n = value;\n else if (typeof value === \"string\" && NUMERIC_STRING.test(value.trim())) n = Number(value.trim());\n if (n === undefined || !Number.isFinite(n)) {\n warnInvalid(fn, value);\n return undefined;\n }\n return n + 0;\n}\n\n/** A number in `locale`: \"1,234.5\", \"1.234,5\", \"1’234.5\" — with `unit`, \"3.400 N\". */\nexport function formatNumber(value: FormattableNumber, options: FormatNumberOptions = {}): string {\n const n = toFormattable(\"formatNumber\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const figure = formatWithMinus(numberFormatter(options.locale, numberOptions(options)), n, options.minus);\n return options.unit ? `${figure}${UNIT_SEPARATOR}${options.unit}` : figure;\n}\n\n/** An amount of `currency` (ISO 4217) in `locale`: \"CHF 1’234.50\", \"1.234,50 €\". */\nexport function formatMoney(value: FormattableNumber, currency: string, options: FormatMoneyOptions = {}): string {\n const n = toFormattable(\"formatMoney\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const formatter = numberFormatter(options.locale, {\n style: \"currency\",\n currency,\n ...(options.currencyDisplay ? { currencyDisplay: options.currencyDisplay } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, n, options.minus);\n}\n\n/** A percentage in `locale` (\"12%\", \"12 %\", \"+1.25%\"). See {@link FormatPercentOptions.ratio}\n * for what `value` means. Default digits: at most one. */\nexport function formatPercent(value: FormattableNumber, options: FormatPercentOptions = {}): string {\n const n = toFormattable(\"formatPercent\", value);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const ratio = options.ratio === false ? n / 100 : n;\n const formatter = numberFormatter(options.locale, {\n style: \"percent\",\n ...(options.digits === undefined ? { maximumFractionDigits: 1 } : null),\n ...numberOptions(options),\n });\n return formatWithMinus(formatter, ratio, options.minus);\n}\n\n/** The units a duration is written in, largest first, with their length in seconds. */\nconst DURATION_UNITS = [\n [\"hour\", 3600],\n [\"minute\", 60],\n [\"second\", 1],\n] as const;\n\n/**\n * A number of SECONDS as an elapsed time in `locale`: \"42s\", \"3m 12s\", \"1h 0m 5s\" — and in\n * German \"3 Min. 12 Sek.\", in French \"3min 12s\" (keksdose audit follow-up, 2026-11-01:\n * keksdose's `formatElapsed`, the scan card's readout, moves here). Each unit is\n * `Intl.NumberFormat`'s `style: \"unit\"`, so the digits and the unit's spelling follow\n * `locale`; `unitDisplay` picks the form, narrow by default.\n *\n * - Seconds alone under a minute (\"0s\", \"59s\"); minutes and seconds under an hour (\"1m 0s\",\n * \"59m 59s\"); from an hour on, hours, minutes and seconds (\"1h 0m 0s\"). Hours are the\n * largest unit: a day and a bit is \"27h 46m 40s\". Every unit below the first is printed,\n * zero too, so a ticking readout keeps its shape from one second to the next.\n * - Whole seconds, cut toward zero: a timer reads \"59s\" until the minute is full.\n * - The parts are joined by a space, as the readout always was — not `Intl.ListFormat`'s\n * \"3 Min. und 12 Sek.\", which is a sentence rather than a figure.\n * - Negative: the minus on the first unit, as `Intl.DurationFormat` writes one — \"−3m 12s\",\n * with the kit's minus (`minus`). An elapsed time from a server's clock can come out a\n * second below zero; clamp it before, as keksdose's card does, when that is not news.\n * - `null` / `undefined` print `empty`; `NaN`, ±`Infinity` and a non-numeric string too,\n * with the formatters' development warning. A numeric string (\"185\") is a number.\n */\nexport function formatDuration(seconds: FormattableNumber, options: FormatDurationOptions = {}): string {\n const n = toFormattable(\"formatDuration\", seconds);\n if (n === undefined) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const unitDisplay = options.unitDisplay ?? \"narrow\";\n const negative = n < 0;\n let rest = Math.trunc(Math.abs(n));\n const parts: string[] = [];\n for (const [unit, size] of DURATION_UNITS) {\n const count = Math.floor(rest / size);\n rest -= count * size;\n // Units above the first that has a count are left out; seconds always print.\n if (parts.length === 0 && count === 0 && unit !== \"second\") continue;\n const formatter = numberFormatter(options.locale, { style: \"unit\", unit, unitDisplay });\n // `count > 0`: under a second either way is \"0s\", with no sign on it.\n const signed = negative && parts.length === 0 && count > 0 ? -count : count;\n parts.push(formatWithMinus(formatter, signed, options.minus));\n }\n return parts.join(\" \");\n}\n\n/* ── Dates ─────────────────────────────────────────────────────────────────── */\n\n/**\n * The shapes a date is printed in, each a set of `Intl.DateTimeFormat` options:\n * - `short` / `medium` / `long` / `full` — the date alone, at that `dateStyle`\n * (\"08/07/2026\", \"8 Jul 2026\", \"8 July 2026\", \"Wednesday, 8 July 2026\" in en-GB).\n * - `dateTime` — medium date and short time: a timestamp (\"8 Jul 2026, 14:05\").\n * - `time` — the short time alone (\"14:05\").\n * - `monthYear` — \"July 2026\".\n *\n * Or pass `Intl.DateTimeFormatOptions` for anything else. A date-fns PATTERN\n * (\"dd.MM.yyyy\") is deliberately not accepted: a pattern is one locale's order baked\n * into a string, which is how an English page ends up with German dates.\n */\nexport type FormatDateStyle = \"short\" | \"medium\" | \"long\" | \"full\" | \"dateTime\" | \"time\" | \"monthYear\";\n\nconst DATE_STYLES: Record<FormatDateStyle, Intl.DateTimeFormatOptions> = {\n short: { dateStyle: \"short\" },\n medium: { dateStyle: \"medium\" },\n long: { dateStyle: \"long\" },\n full: { dateStyle: \"full\" },\n dateTime: { dateStyle: \"medium\", timeStyle: \"short\" },\n time: { timeStyle: \"short\" },\n monthYear: { month: \"long\", year: \"numeric\" },\n};\n\nexport interface FormatDateOptions extends BaseOptions {\n /** An IANA zone (\"Europe/Zurich\") for a timestamp. Left out: the device's. A bare\n * \"YYYY-MM-DD\" ignores it: a calendar day is the day it names in every zone. */\n timeZone?: string;\n}\n\n/** What {@link formatDate} and {@link formatRelativeTime} accept. */\nexport type DateInput = string | number | Date | null | undefined;\n\n/**\n * `value` as a Date, or null. A bare \"YYYY-MM-DD\" is a CALENDAR day and is read on the\n * local calendar (`parseIsoDate`) — `new Date(\"2026-07-08\")` is UTC midnight, which is\n * the 7th anywhere west of Greenwich. Anything else (a full ISO timestamp, epoch\n * milliseconds, a Date) is an instant and goes through `Date` as is.\n */\nexport function toDate(value: DateInput): Date | null {\n if (value === null || value === undefined || value === \"\") return null;\n if (isBareIsoDay(value)) return parseIsoDate(value);\n const d = value instanceof Date ? value : new Date(value);\n return Number.isNaN(d.getTime()) ? null : d;\n}\n\n/**\n * A date or timestamp in `locale`. Takes an ISO date (\"2026-07-08\", read on the local\n * calendar), an ISO timestamp (\"2026-07-08T14:05:00Z\"), epoch milliseconds or a Date.\n * `style` defaults to `medium`; see {@link FormatDateStyle}.\n *\n * `formatIsoDate` (`@eifi1/ui-kit/dates`) remains for a plain \"YYYY-MM-DD\" with an\n * explicit locale; this is its superset for timestamps and the named styles.\n */\nexport function formatDate(\n value: DateInput,\n style: FormatDateStyle | Intl.DateTimeFormatOptions = \"medium\",\n options: FormatDateOptions = {},\n): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const base = typeof style === \"string\" ? DATE_STYLES[style] : style;\n // A bare \"YYYY-MM-DD\" is a calendar day, which has no zone: it is the day it names\n // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the\n // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).\n if (isBareIsoDay(value)) {\n const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));\n return dateFormatter(options.locale, { ...base, timeZone: \"UTC\" }).format(day);\n }\n return dateFormatter(options.locale, options.timeZone ? { ...base, timeZone: options.timeZone } : base).format(d);\n}\n\nexport interface FormatRelativeTimeOptions extends BaseOptions {\n /** The zone of the date printed past `absoluteAfterDays` (0.46), and (0.51.7) the zone\n * whose calendar days \"yesterday\" and \"2 days ago\" count. Left out: the device's. */\n timeZone?: string;\n /** The moment it is measured from. Default: now. */\n now?: DateInput;\n /** `\"auto\"` (default): \"yesterday\", \"now\", \"next week\". `\"always\"`: \"1 day ago\". */\n numeric?: Intl.RelativeTimeFormatNumeric;\n /** `\"long\"` (default) \"3 hours ago\", `\"short\"` \"3 hr. ago\", `\"narrow\"` \"3h ago\". */\n style?: Intl.RelativeTimeFormatStyle;\n /**\n * Past this many days either way, print the date instead ({@link formatDate} in\n * `absoluteStyle`) — kastlan's feed switches to the date after a week, because \"5\n * weeks ago\" makes a reader do sums. Default: never.\n */\n absoluteAfterDays?: number;\n /** The style of that date. Default `medium`. */\n absoluteStyle?: FormatDateStyle | Intl.DateTimeFormatOptions;\n}\n\nconst SECOND = 1000;\nconst MINUTE = 60 * SECOND;\nconst HOUR = 60 * MINUTE;\nconst DAY = 24 * HOUR;\n\n/** [the largest |difference| a unit is used for, the unit, its length]. */\nconst RELATIVE_STEPS: Array<[number, Intl.RelativeTimeFormatUnit, number]> = [\n [45 * SECOND, \"second\", SECOND],\n [45 * MINUTE, \"minute\", MINUTE],\n [22 * HOUR, \"hour\", HOUR],\n [6.5 * DAY, \"day\", DAY],\n [26 * DAY, \"week\", 7 * DAY],\n [320 * DAY, \"month\", 30.44 * DAY],\n [Infinity, \"year\", 365.25 * DAY],\n];\n\n/**\n * `d`'s calendar day in `timeZone` (left out: the device's), as a whole number of days:\n * that day's UTC midnight over a day's length. Two of them subtract to whole calendar\n * days — a 23- or 25-hour DST day still counts one.\n */\nfunction dayNumber(d: Date, timeZone: string | undefined): number {\n if (!timeZone) return Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()) / DAY;\n // `en-US`: Gregorian, Latin digits — the parts are read back as numbers.\n const fields: Intl.DateTimeFormatOptions = { timeZone, year: \"numeric\", month: \"numeric\", day: \"numeric\" };\n const parts = dateFormatter(\"en-US\", fields).formatToParts(d);\n const part = (type: Intl.DateTimeFormatPartTypes) => Number(parts.find((p) => p.type === type)?.value);\n return Date.UTC(part(\"year\"), part(\"month\") - 1, part(\"day\")) / DAY;\n}\n\nconst relativeFormatters = new Map<string, Intl.RelativeTimeFormat>();\nfunction relativeFormatter(\n locale: string | undefined,\n numeric: Intl.RelativeTimeFormatNumeric,\n style: Intl.RelativeTimeFormatStyle,\n): Intl.RelativeTimeFormat {\n const key = `${locale ?? \"\"}|${numeric}|${style}`;\n let f = relativeFormatters.get(key);\n if (!f) {\n f = new Intl.RelativeTimeFormat(locale, { numeric, style });\n relativeFormatters.set(key, f);\n }\n return f;\n}\n\n/**\n * How long ago (or until) `value` is, in `locale`'s words, through\n * `Intl.RelativeTimeFormat`: \"3 hours ago\", \"vor 3 Stunden\", \"il y a 3 heures\",\n * \"in 2 days\", and under 45 seconds \"now\". The unit is picked by size — seconds,\n * minutes, hours, days, weeks, months, years — and the count is rounded; days are\n * CALENDAR days (0.51.7), in `timeZone` when given: \"yesterday\" is the day before today,\n * however few hours ago that was.\n */\nexport function formatRelativeTime(value: DateInput, options: FormatRelativeTimeOptions = {}): string {\n const d = toDate(value);\n if (!d) return options.empty ?? EMPTY_FORMATTED_VALUE;\n const now = toDate(options.now ?? new Date()) ?? new Date();\n const diff = d.getTime() - now.getTime();\n const abs = Math.abs(diff);\n if (options.absoluteAfterDays !== undefined && abs > options.absoluteAfterDays * DAY) {\n return formatDate(d, options.absoluteStyle ?? \"medium\", options);\n }\n const rtf = relativeFormatter(options.locale, options.numeric ?? \"auto\", options.style ?? \"long\");\n const [, unit, size] = RELATIVE_STEPS.find(([limit]) => abs < limit)!;\n if (unit === \"day\") {\n // By the calendar, not in 24-hour units (refactor audit 2026-10-30, DSP-7): rounded\n // elapsed time made 00:10 seen at 23:50 \"yesterday\" and 01:00 yesterday seen at 23:30\n // \"2 days ago\", beside an exact date (DateMark's second line, the feedback thread's\n // tooltip) that said otherwise. Counted in `timeZone`, the zone that date is printed\n // in; a bare \"YYYY-MM-DD\" is the day it names wherever it is read, as in formatDate.\n // Still the same day (22 hours and more since just after midnight): in hours.\n const bareDay = isBareIsoDay(value);\n const days = dayNumber(d, bareDay ? undefined : options.timeZone) - dayNumber(now, options.timeZone);\n return days === 0 ? rtf.format(Math.round(diff / HOUR), \"hour\") : rtf.format(days, \"day\");\n }\n // Under 45 seconds is \"now\" rather than \"12 seconds ago\": a feed that ticks through\n // the seconds is noise, and nothing reads a timestamp to the second.\n const count = unit === \"second\" ? 0 : Math.round(diff / size);\n // `+ 0` folds -0 into 0: `format(-0, …)` says \"0 seconds ago\" instead of \"now\".\n return rtf.format(count + 0, unit);\n}\n\n/* ── Bound to the provider ─────────────────────────────────────────────────── */\n\ntype WithoutLocale<T> = Omit<T, \"locale\"> & { locale?: string };\n\n/** What {@link useKitFormat} returns: the formatters above with the provider's locale\n * as their default (an explicit `locale` in the options still wins). */\nexport interface KitFormat {\n /** The locale they format in — the provider's, or `undefined` (runtime default). */\n locale: string | undefined;\n formatNumber: (value: FormattableNumber, options?: WithoutLocale<FormatNumberOptions>) => string;\n formatMoney: (value: FormattableNumber, currency: string, options?: WithoutLocale<FormatMoneyOptions>) => string;\n formatPercent: (value: FormattableNumber, options?: WithoutLocale<FormatPercentOptions>) => string;\n /** 0.54: {@link formatDuration}, a number of seconds as an elapsed time. */\n formatDuration: (seconds: FormattableNumber, options?: WithoutLocale<FormatDurationOptions>) => string;\n formatDate: (\n value: DateInput,\n style?: FormatDateStyle | Intl.DateTimeFormatOptions,\n options?: WithoutLocale<FormatDateOptions>,\n ) => string;\n formatRelativeTime: (value: DateInput, options?: WithoutLocale<FormatRelativeTimeOptions>) => string;\n}\n\n/**\n * The formatters, bound to the `<UiKitProvider locale>` (or to `locale`, which wins\n * over it — the same precedence as a component's `locale` prop):\n *\n * const { formatMoney, formatRelativeTime } = useKitFormat();\n * formatMoney(row.balance, \"CHF\");\n */\nexport function useKitFormat(locale?: string): KitFormat {\n const kitLocale = useKitLocale(locale);\n // 0.46 (§10.3): the provider's `timeZone` for timestamps; a call's own wins, and a bare\n // \"YYYY-MM-DD\" ignores it (formatDate).\n const timeZone = useKitTimeZone();\n return useMemo<KitFormat>(\n () => ({\n locale: kitLocale,\n formatNumber: (value, options) => formatNumber(value, { locale: kitLocale, ...options }),\n formatMoney: (value, currency, options) => formatMoney(value, currency, { locale: kitLocale, ...options }),\n formatPercent: (value, options) => formatPercent(value, { locale: kitLocale, ...options }),\n formatDuration: (seconds, options) => formatDuration(seconds, { locale: kitLocale, ...options }),\n formatDate: (value, style, options) => formatDate(value, style, { locale: kitLocale, timeZone, ...options }),\n formatRelativeTime: (value, options) => formatRelativeTime(value, { locale: kitLocale, timeZone, ...options }),\n }),\n [kitLocale, timeZone],\n );\n}\n\n/* ── Currency minor units ──────────────────────────────────────────────────── */\n\n/**\n * The decimals a currency is written with — its minor unit: CHF and EUR 2, JPY 0,\n * KWD 3 — from `Intl`, or `undefined` for a code `Intl` does not accept. The rule\n * AmountInput settles a typed figure by, for a host that needs it outside the field.\n */\nexport function currencyMinorDigits(currency: string | null | undefined): number | undefined {\n if (!currency) return undefined;\n try {\n return new Intl.NumberFormat(\"en\", { style: \"currency\", currency }).resolvedOptions().maximumFractionDigits;\n } catch {\n return undefined;\n }\n}\n\n/** The options form of {@link roundToCurrency}'s third argument. */\nexport interface RoundToCurrencyOptions {\n /** Decimals to round to, whatever the currency — the plain `digits` argument. */\n digits?: number;\n /**\n * Decimals for a currency `Intl` cannot resolve — no code yet (`null`, `\"\"`), or a\n * malformed one (\"EURO\", \"€\"). Left out, such a value comes back unchanged, as\n * before. Note that `Intl` resolves ANY well-formed three-letter code, a made-up\n * \"XYZ\" included (to 2), so this is the case of a missing or broken code, not of a\n * rare currency.\n */\n fallbackDigits?: number;\n}\n\n/**\n * `value` rounded to the currency's minor unit (or to `digits`), half away from zero,\n * in decimal arithmetic: 93.4213 CHF → 93.42, 1.005 EUR → 1.01 (`Math.round(1.005 *\n * 100)` is 100, since 1.005 is 1.00499… in binary). A non-finite value, or a currency\n * `Intl` does not know without `digits`, comes back unchanged (keksdose live #356: an\n * FX estimate set into an AmountInput showed four decimals) — unless the options form\n * gives `fallbackDigits`: `roundToCurrency(v, code, { fallbackDigits: 2 })` rounds a\n * figure whose currency is not chosen yet to cents rather than leaving 0.1 + 0.2 as is.\n * A finite value never comes back NaN or -0, however small or large (BUG-11):\n * 0.1 + 0.2 - 0.3 → 0, -0.004 → 0.\n */\nexport function roundToCurrency(\n value: number,\n currency: string | null | undefined,\n digits?: number | RoundToCurrencyOptions,\n): number {\n const options = typeof digits === \"object\" && digits !== null ? digits : { digits };\n const places = options.digits ?? currencyMinorDigits(currency) ?? options.fallbackDigits;\n if (places === undefined) return value;\n return roundHalfAway(value, places);\n}\n"],"mappings":"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","names":["formatWithMinus"]}
@@ -4,10 +4,12 @@
4
4
  * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on
5
5
  * a 270-row register when each cell built its own (dev#632), and kept its own renderer of
6
6
  * the date-format preference because the kit's built one per call (refactor audit
7
- * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one.
7
+ * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one, and
8
+ * since 0.54 `lib/dates`' `formatIsoDate` (keksdose audit follow-up, 2026-11-01).
8
9
  *
9
10
  * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache
10
- * lives apart from it.
11
+ * lives apart from it. It imports nothing, which `@eifi1/ui-kit/dates` (no package at
12
+ * all) relies on.
11
13
  */
12
14
  /** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does
13
15
  * for a tag it refuses — and caches nothing then. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/lib/intl-cache.ts"],"sourcesContent":["/**\n * One `Intl.DateTimeFormat` per locale and option set, for every date the kit prints.\n *\n * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on\n * a 270-row register when each cell built its own (dev#632), and kept its own renderer of\n * the date-format preference because the kit's built one per call (refactor audit\n * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one.\n *\n * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache\n * lives apart from it.\n */\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\n\n/** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does\n * for a tag it refuses — and caches nothing then. */\nexport function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n"],"mappings":";AAYA,MAAM,iBAAiB,oBAAI,IAAiC;AAIrD,SAAS,cAAc,QAA4B,SAA0D;AAClH,QAAM,MAAM,GAAG,UAAU,EAAE,IAAI,KAAK,UAAU,OAAO,CAAC;AACtD,MAAI,IAAI,eAAe,IAAI,GAAG;AAC9B,MAAI,CAAC,GAAG;AACN,QAAI,IAAI,KAAK,eAAe,QAAQ,OAAO;AAC3C,mBAAe,IAAI,KAAK,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/lib/intl-cache.ts"],"sourcesContent":["/**\n * One `Intl.DateTimeFormat` per locale and option set, for every date the kit prints.\n *\n * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on\n * a 270-row register when each cell built its own (dev#632), and kept its own renderer of\n * the date-format preference because the kit's built one per call (refactor audit\n * 2026-10-30, DUP-2). `lib/format` and `lib/date-format` both format through this one, and\n * since 0.54 `lib/dates`' `formatIsoDate` (keksdose audit follow-up, 2026-11-01).\n *\n * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache\n * lives apart from it. It imports nothing, which `@eifi1/ui-kit/dates` (no package at\n * all) relies on.\n */\n\nconst dateFormatters = new Map<string, Intl.DateTimeFormat>();\n\n/** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does\n * for a tag it refuses — and caches nothing then. */\nexport function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n let f = dateFormatters.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormatters.set(key, f);\n }\n return f;\n}\n"],"mappings":";AAcA,MAAM,iBAAiB,oBAAI,IAAiC;AAIrD,SAAS,cAAc,QAA4B,SAA0D;AAClH,QAAM,MAAM,GAAG,UAAU,EAAE,IAAI,KAAK,UAAU,OAAO,CAAC;AACtD,MAAI,IAAI,eAAe,IAAI,GAAG;AAC9B,MAAI,CAAC,GAAG;AACN,QAAI,IAAI,KAAK,eAAe,QAAQ,OAAO;AAC3C,mBAAe,IAAI,KAAK,CAAC;AAAA,EAC3B;AACA,SAAO;AACT;","names":[]}
@@ -35,6 +35,12 @@
35
35
  * It never arms while the browser is offline, whatever `shouldWatch` says: that state
36
36
  * has its own, truthful indicator, and a request that never left the device is not
37
37
  * evidence of anything about the server.
38
+ *
39
+ * A READ THAT IS HEAVY ON PURPOSE (0.55) — a report the server computes for a few
40
+ * seconds — says so with `slowOk` on its request (the axios config, or `track`'s
41
+ * request): it is counted, but never called slow, so it shows nothing while it is under
42
+ * `wakingMs`. Past `wakingMs` it is the server waking like any other read. A read that
43
+ * can take longer than that on a warm server stays out of the watch (`shouldWatch`).
38
44
  */
39
45
  /** `slow` = longer than a warm server ever takes; `waking` = long enough that a cold
40
46
  * start is the only sensible explanation. */
@@ -61,21 +67,53 @@ export interface ServerWakeRequest {
61
67
  /** axios's `responseType`. `"blob"`, `"arraybuffer"` and `"stream"` mark a download,
62
68
  * which the default filter does not count. */
63
69
  responseType?: string;
70
+ /**
71
+ * This read is slow on purpose (0.55; the per-request opt-out adopt-0.51 left open): it
72
+ * still counts, but its own age never shows "taking longer than usual" — only past
73
+ * `wakingMs`, when a cold start is the likelier story, does it show the server waking.
74
+ * The stage then follows the other requests out beside it, as ever.
75
+ *
76
+ * ONLY for a read that is heavy but answers under `wakingMs` (7 s) on a warm server: a
77
+ * report computed on request, a large page of rows. A read that can take longer — an
78
+ * export rendered on the server, a slow third party behind the route — would say "the
79
+ * server is waking up" on a warm server; keep it out of the watch through `shouldWatch`
80
+ * instead (kastlan's advice), as a download and an upload are.
81
+ *
82
+ * On axios it rides the request config: `api.get(url, { slowOk: true })` (declare it on
83
+ * `AxiosRequestConfig` once, by module augmentation); through `track`, on its request.
84
+ */
85
+ slowOk?: boolean;
64
86
  }
87
+ /**
88
+ * One counted request, as {@link ServerWakeWatcher.start} names it (0.55, keksdose's 0.51
89
+ * review N4): a positive whole number, unique for the watcher's lifetime, which
90
+ * {@link ServerWakeWatcher.end} takes back to end exactly that request. `start` returns
91
+ * `false` where it counts nothing, as before: the token is truthy where `start` returned
92
+ * `true` until 0.55, so code that only tests it reads as it did.
93
+ */
94
+ export type ServerWakeToken = number;
65
95
  export interface ServerWakeWatcher {
66
96
  /**
67
97
  * Count a request that is going out, and arm the escalation if it is the first.
68
- * Returns whether it was counted — the caller MUST call {@link end} exactly once
69
- * for every `true`, or the counter never returns to zero and the notice sticks.
70
- * (The axios adapter stashes it on the request config for that reason.)
98
+ * Returns its {@link ServerWakeToken}, or `false` when it was not counted (until 0.55
99
+ * `true` / `false`; the token is truthy where `true` was). The caller MUST end every
100
+ * counted request exactly once, or the counter never returns to zero and the notice
101
+ * sticks: `end(token)`, which ends that request, or a bare `end()`, as before. (The
102
+ * axios adapter stashes the token on the request config for that reason.)
71
103
  * `request` reaches the filter as its third argument; default `{ method, url }`.
72
104
  */
73
- start(method?: string, url?: string, request?: ServerWakeRequest): boolean;
74
- /** A counted request settled — either way: an error still proves the server
75
- * answered or gave up. The stage then follows the oldest request still out (0.51), so
76
- * the notice goes away when the last one does, or sooner when the rest are recent.
77
- * Never takes the counter below zero. */
78
- end(): void;
105
+ start(method?: string, url?: string, request?: ServerWakeRequest): ServerWakeToken | false;
106
+ /**
107
+ * A counted request settled — either way: an error still proves the server answered or
108
+ * gave up. The stage then follows the oldest request still out (0.51), so the notice
109
+ * goes away when the last one does, or sooner when the rest are recent.
110
+ *
111
+ * With `token` (0.55), exactly that request is retired, so the age the stage is read
112
+ * from is the real one; a token that was ended already (or reset away), or `false`, is
113
+ * a no-op. A bare `end()` — the call before 0.55 — retires the OLDEST start, which can
114
+ * make a notice come late, never falsely. Never takes the counter below zero.
115
+ */
116
+ end(token?: ServerWakeToken | false): void;
79
117
  /** `start` + `end` around a promise (or a function returning one), for a request
80
118
  * that does not go through an adapter. Resolves and rejects as the work does. */
81
119
  track<T>(work: PromiseLike<T> | (() => PromiseLike<T>), request?: ServerWakeRequest): Promise<T>;
@@ -108,6 +146,8 @@ export interface ServerWakeAxiosConfig {
108
146
  method?: string;
109
147
  url?: string;
110
148
  responseType?: string;
149
+ /** A read slow on purpose — see {@link ServerWakeRequest.slowOk} (0.55). */
150
+ slowOk?: boolean;
111
151
  }
112
152
  /** The slice of an interceptor manager the adapter uses. A method (not a function
113
153
  * property) so axios's wider `use(onFulfilled?, onRejected?, options?)` fits it. */