@eifi1/ui-kit 0.51.7 → 0.52.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 (746) hide show
  1. package/README.md +141 -23
  2. package/dist/account/account-parts.d.ts +10 -8
  3. package/dist/account/account-parts.js +1 -18
  4. package/dist/account/account-parts.js.map +1 -1
  5. package/dist/account/data-export-setting-labels.js +1 -1
  6. package/dist/account/data-export-setting-labels.js.map +1 -1
  7. package/dist/account/delete-account-setting-labels.js +1 -1
  8. package/dist/account/delete-account-setting-labels.js.map +1 -1
  9. package/dist/account/email-change-setting-labels.js +1 -1
  10. package/dist/account/email-change-setting-labels.js.map +1 -1
  11. package/dist/account/passkeys-setting.d.ts +9 -2
  12. package/dist/account/passkeys-setting.js +41 -29
  13. package/dist/account/passkeys-setting.js.map +1 -1
  14. package/dist/account/sessions-setting.d.ts +3 -2
  15. package/dist/account/sessions-setting.js +10 -8
  16. package/dist/account/sessions-setting.js.map +1 -1
  17. package/dist/admin/admin-action-confirm.js +1 -3
  18. package/dist/admin/admin-action-confirm.js.map +1 -1
  19. package/dist/admin/admin-parts.d.ts +6 -3
  20. package/dist/admin/admin-parts.js +6 -21
  21. package/dist/admin/admin-parts.js.map +1 -1
  22. package/dist/admin/mail-test-card-labels.js +1 -1
  23. package/dist/admin/mail-test-card-labels.js.map +1 -1
  24. package/dist/admin/plan-change-labels.d.ts +2 -2
  25. package/dist/admin/plan-change-labels.js +1 -17
  26. package/dist/admin/plan-change-labels.js.map +1 -1
  27. package/dist/admin/plan-change.d.ts +3 -42
  28. package/dist/admin/plan-change.js +1 -1
  29. package/dist/admin/plan-change.js.map +1 -1
  30. package/dist/admin/reviewer-scope-editor.js +3 -3
  31. package/dist/admin/reviewer-scope-editor.js.map +1 -1
  32. package/dist/auth/auth-errors-labels.d.ts +11 -0
  33. package/dist/auth/auth-errors-labels.js +13 -0
  34. package/dist/auth/auth-errors-labels.js.map +1 -0
  35. package/dist/auth/auth-errors.d.ts +7 -23
  36. package/dist/auth/auth-errors.js +2 -15
  37. package/dist/auth/auth-errors.js.map +1 -1
  38. package/dist/auth/complete-name-dialog.js +2 -1
  39. package/dist/auth/complete-name-dialog.js.map +1 -1
  40. package/dist/auth/forgot-password-form-labels.js +1 -1
  41. package/dist/auth/forgot-password-form-labels.js.map +1 -1
  42. package/dist/auth/forgot-password-form.js +7 -2
  43. package/dist/auth/forgot-password-form.js.map +1 -1
  44. package/dist/auth/register-form-labels.js +1 -1
  45. package/dist/auth/register-form-labels.js.map +1 -1
  46. package/dist/auth/reset-password-form-labels.js +1 -1
  47. package/dist/auth/reset-password-form-labels.js.map +1 -1
  48. package/dist/auth/reset-password-form.js +5 -1
  49. package/dist/auth/reset-password-form.js.map +1 -1
  50. package/dist/auth/sign-in-form-labels.js +1 -1
  51. package/dist/auth/sign-in-form-labels.js.map +1 -1
  52. package/dist/auth/status-parts.d.ts +0 -3
  53. package/dist/auth/status-parts.js +0 -4
  54. package/dist/auth/status-parts.js.map +1 -1
  55. package/dist/auth/verify-email-labels.js +1 -1
  56. package/dist/auth/verify-email-labels.js.map +1 -1
  57. package/dist/auth/verify-email.js +7 -2
  58. package/dist/auth/verify-email.js.map +1 -1
  59. package/dist/billing/cancel-subscription.d.ts +20 -8
  60. package/dist/billing/cancel-subscription.js +7 -5
  61. package/dist/billing/cancel-subscription.js.map +1 -1
  62. package/dist/billing/dialog-close.d.ts +29 -0
  63. package/dist/billing/dialog-close.js +8 -0
  64. package/dist/billing/dialog-close.js.map +1 -0
  65. package/dist/billing/plan-card.d.ts +14 -4
  66. package/dist/billing/plan-card.js +15 -8
  67. package/dist/billing/plan-card.js.map +1 -1
  68. package/dist/billing/plan-change-labels.d.ts +42 -0
  69. package/dist/billing/plan-change-labels.js +22 -0
  70. package/dist/billing/plan-change-labels.js.map +1 -0
  71. package/dist/billing/plan-switch.d.ts +19 -7
  72. package/dist/billing/plan-switch.js +8 -4
  73. package/dist/billing/plan-switch.js.map +1 -1
  74. package/dist/chart.d.ts +12 -0
  75. package/dist/chart.js +16 -1
  76. package/dist/chart.js.map +1 -1
  77. package/dist/components/account-chips.d.ts +5 -5
  78. package/dist/components/account-chips.js +11 -13
  79. package/dist/components/account-chips.js.map +1 -1
  80. package/dist/components/alert-banner.d.ts +16 -5
  81. package/dist/components/alert-banner.js +46 -1
  82. package/dist/components/alert-banner.js.map +1 -1
  83. package/dist/components/amount-input.d.ts +13 -0
  84. package/dist/components/amount-input.js +1 -0
  85. package/dist/components/amount-input.js.map +1 -1
  86. package/dist/components/autocomplete.js +57 -65
  87. package/dist/components/autocomplete.js.map +1 -1
  88. package/dist/components/bulk-action-bar.d.ts +12 -2
  89. package/dist/components/bulk-action-bar.js +65 -21
  90. package/dist/components/bulk-action-bar.js.map +1 -1
  91. package/dist/components/button-look.d.ts +5 -5
  92. package/dist/components/button-look.js.map +1 -1
  93. package/dist/components/button.d.ts +2 -2
  94. package/dist/components/button.js.map +1 -1
  95. package/dist/components/chart-edge.d.ts +16 -0
  96. package/dist/components/chart-edge.js +14 -1
  97. package/dist/components/chart-edge.js.map +1 -1
  98. package/dist/components/chart-figure.d.ts +13 -0
  99. package/dist/components/chart-figure.js +15 -0
  100. package/dist/components/chart-figure.js.map +1 -0
  101. package/dist/components/chart-zoom-core.js +1 -4
  102. package/dist/components/chart-zoom-core.js.map +1 -1
  103. package/dist/components/chart.d.ts +8 -1
  104. package/dist/components/chart.js +19 -10
  105. package/dist/components/chart.js.map +1 -1
  106. package/dist/components/combobox-core.d.ts +111 -2
  107. package/dist/components/combobox-core.js +148 -42
  108. package/dist/components/combobox-core.js.map +1 -1
  109. package/dist/components/combobox.d.ts +1 -1
  110. package/dist/components/combobox.js +141 -130
  111. package/dist/components/combobox.js.map +1 -1
  112. package/dist/components/copy-button.d.ts +6 -5
  113. package/dist/components/copy-button.js +5 -3
  114. package/dist/components/copy-button.js.map +1 -1
  115. package/dist/components/country-select.js +2 -2
  116. package/dist/components/country-select.js.map +1 -1
  117. package/dist/components/data-table-column-settings.d.ts +1 -1
  118. package/dist/components/data-table-column-settings.js +33 -7
  119. package/dist/components/data-table-column-settings.js.map +1 -1
  120. package/dist/components/data-table-filters.d.ts +4 -2
  121. package/dist/components/data-table-filters.js +2 -2
  122. package/dist/components/data-table-filters.js.map +1 -1
  123. package/dist/components/data-table-fit.d.ts +6 -4
  124. package/dist/components/data-table-fit.js +4 -1
  125. package/dist/components/data-table-fit.js.map +1 -1
  126. package/dist/components/data-table-groups.d.ts +1 -1
  127. package/dist/components/data-table-groups.js.map +1 -1
  128. package/dist/components/data-table-head.d.ts +1 -1
  129. package/dist/components/data-table-head.js +15 -12
  130. package/dist/components/data-table-head.js.map +1 -1
  131. package/dist/components/data-table-mobile.d.ts +12 -7
  132. package/dist/components/data-table-mobile.js +43 -30
  133. package/dist/components/data-table-mobile.js.map +1 -1
  134. package/dist/components/data-table-pagination.js +11 -6
  135. package/dist/components/data-table-pagination.js.map +1 -1
  136. package/dist/components/data-table-resize.d.ts +1 -1
  137. package/dist/components/data-table-resize.js.map +1 -1
  138. package/dist/components/data-table-row-actions.d.ts +2 -2
  139. package/dist/components/data-table-row-actions.js.map +1 -1
  140. package/dist/components/data-table-row.d.ts +6 -2
  141. package/dist/components/data-table-row.js +17 -10
  142. package/dist/components/data-table-row.js.map +1 -1
  143. package/dist/components/data-table-selection.d.ts +1 -1
  144. package/dist/components/data-table-selection.js.map +1 -1
  145. package/dist/components/data-table-shared.d.ts +10 -2
  146. package/dist/components/data-table-shared.js +18 -0
  147. package/dist/components/data-table-shared.js.map +1 -1
  148. package/dist/components/data-table-sort.d.ts +21 -2
  149. package/dist/components/data-table-sort.js +24 -0
  150. package/dist/components/data-table-sort.js.map +1 -1
  151. package/dist/components/data-table-virtual.d.ts +1 -1
  152. package/dist/components/data-table-virtual.js +18 -6
  153. package/dist/components/data-table-virtual.js.map +1 -1
  154. package/dist/components/data-table.d.ts +13 -4
  155. package/dist/components/data-table.js +139 -36
  156. package/dist/components/data-table.js.map +1 -1
  157. package/dist/components/date-field.d.ts +24 -2
  158. package/dist/components/date-field.js +33 -8
  159. package/dist/components/date-field.js.map +1 -1
  160. package/dist/components/date-picker.d.ts +15 -2
  161. package/dist/components/date-picker.js +55 -26
  162. package/dist/components/date-picker.js.map +1 -1
  163. package/dist/components/date-range-picker.d.ts +47 -20
  164. package/dist/components/date-range-picker.js +23 -7
  165. package/dist/components/date-range-picker.js.map +1 -1
  166. package/dist/components/error-boundary.js +37 -30
  167. package/dist/components/error-boundary.js.map +1 -1
  168. package/dist/components/{ui-labels.d.ts → field-base-labels.d.ts} +0 -2
  169. package/dist/components/{ui-labels.js → field-base-labels.js} +2 -9
  170. package/dist/components/field-base-labels.js.map +1 -0
  171. package/dist/components/field-base.d.ts +4 -3
  172. package/dist/components/field-base.js +3 -3
  173. package/dist/components/field-base.js.map +1 -1
  174. package/dist/components/field-sync-labels.d.ts +1 -0
  175. package/dist/components/field-sync-labels.js.map +1 -1
  176. package/dist/components/field-sync.d.ts +23 -1
  177. package/dist/components/field-sync.js.map +1 -1
  178. package/dist/components/form-actions.js +11 -5
  179. package/dist/components/form-actions.js.map +1 -1
  180. package/dist/components/hover-menu.d.ts +3 -0
  181. package/dist/components/hover-menu.js +5 -3
  182. package/dist/components/hover-menu.js.map +1 -1
  183. package/dist/components/icon-button.js +14 -9
  184. package/dist/components/icon-button.js.map +1 -1
  185. package/dist/components/inline-confirm.d.ts +43 -0
  186. package/dist/components/inline-confirm.js +67 -0
  187. package/dist/components/inline-confirm.js.map +1 -0
  188. package/dist/components/inline-entity-combobox.d.ts +4 -3
  189. package/dist/components/inline-entity-combobox.js +148 -140
  190. package/dist/components/inline-entity-combobox.js.map +1 -1
  191. package/dist/components/input.js +1 -1
  192. package/dist/components/input.js.map +1 -1
  193. package/dist/components/jump-calendar.js +28 -112
  194. package/dist/components/jump-calendar.js.map +1 -1
  195. package/dist/components/legal-page.d.ts +3 -1
  196. package/dist/components/legal-page.js +3 -2
  197. package/dist/components/legal-page.js.map +1 -1
  198. package/dist/components/legal.d.ts +4 -2
  199. package/dist/components/legal.js.map +1 -1
  200. package/dist/components/lightbox.d.ts +2 -2
  201. package/dist/components/lightbox.js +17 -6
  202. package/dist/components/lightbox.js.map +1 -1
  203. package/dist/components/list.d.ts +6 -2
  204. package/dist/components/list.js +3 -2
  205. package/dist/components/list.js.map +1 -1
  206. package/dist/components/measured-grid.d.ts +1 -1
  207. package/dist/components/measured-grid.js +2 -1
  208. package/dist/components/measured-grid.js.map +1 -1
  209. package/dist/components/mini-calendar.js +3 -5
  210. package/dist/components/mini-calendar.js.map +1 -1
  211. package/dist/components/modal.js +3 -1
  212. package/dist/components/modal.js.map +1 -1
  213. package/dist/components/month-grid.d.ts +55 -0
  214. package/dist/components/month-grid.js +148 -0
  215. package/dist/components/month-grid.js.map +1 -0
  216. package/dist/components/month-picker.d.ts +36 -4
  217. package/dist/components/month-picker.js +123 -146
  218. package/dist/components/month-picker.js.map +1 -1
  219. package/dist/components/multi-select.d.ts +2 -2
  220. package/dist/components/multi-select.js +11 -6
  221. package/dist/components/multi-select.js.map +1 -1
  222. package/dist/components/picker-sheet.js +2 -1
  223. package/dist/components/picker-sheet.js.map +1 -1
  224. package/dist/components/pie-chart-labels.d.ts +7 -0
  225. package/dist/components/pie-chart-labels.js +1 -0
  226. package/dist/components/pie-chart-labels.js.map +1 -1
  227. package/dist/components/pie-chart.d.ts +5 -3
  228. package/dist/components/pie-chart.js +7 -22
  229. package/dist/components/pie-chart.js.map +1 -1
  230. package/dist/components/popover.d.ts +3 -0
  231. package/dist/components/popover.js +7 -1
  232. package/dist/components/popover.js.map +1 -1
  233. package/dist/components/row-actions.js +2 -1
  234. package/dist/components/row-actions.js.map +1 -1
  235. package/dist/components/section-nav.d.ts +7 -0
  236. package/dist/components/section-nav.js.map +1 -1
  237. package/dist/components/series-chart-axes.d.ts +3 -3
  238. package/dist/components/series-chart-axes.js.map +1 -1
  239. package/dist/components/series-chart-labels.d.ts +7 -4
  240. package/dist/components/series-chart-labels.js.map +1 -1
  241. package/dist/components/series-chart.d.ts +31 -7
  242. package/dist/components/series-chart.js +72 -29
  243. package/dist/components/series-chart.js.map +1 -1
  244. package/dist/components/share-card.js +3 -3
  245. package/dist/components/share-card.js.map +1 -1
  246. package/dist/components/sign-chip-labels.d.ts +1 -0
  247. package/dist/components/sign-chip-labels.js.map +1 -1
  248. package/dist/components/sign-chip.d.ts +10 -1
  249. package/dist/components/sign-chip.js.map +1 -1
  250. package/dist/components/tabs-labels.d.ts +2 -0
  251. package/dist/components/tabs-labels.js +11 -0
  252. package/dist/components/tabs-labels.js.map +1 -0
  253. package/dist/components/tabs.d.ts +1 -1
  254. package/dist/components/tabs.js +1 -1
  255. package/dist/components/tabs.js.map +1 -1
  256. package/dist/components/toggle-legend.d.ts +2 -2
  257. package/dist/components/toggle-legend.js +6 -6
  258. package/dist/components/toggle-legend.js.map +1 -1
  259. package/dist/components/translation-review-editor.d.ts +4 -2
  260. package/dist/components/translation-review-editor.js +2 -3
  261. package/dist/components/translation-review-editor.js.map +1 -1
  262. package/dist/components/translation-review.d.ts +2 -1
  263. package/dist/components/translation-review.js +2 -2
  264. package/dist/components/translation-review.js.map +1 -1
  265. package/dist/components/treemap-labels.d.ts +26 -0
  266. package/dist/components/treemap-labels.js +10 -0
  267. package/dist/components/treemap-labels.js.map +1 -0
  268. package/dist/components/treemap-layout.d.ts +62 -0
  269. package/dist/components/treemap-layout.js +126 -0
  270. package/dist/components/treemap-layout.js.map +1 -0
  271. package/dist/components/treemap.d.ts +140 -17
  272. package/dist/components/treemap.js +324 -97
  273. package/dist/components/treemap.js.map +1 -1
  274. package/dist/components/use-table-state.d.ts +5 -5
  275. package/dist/components/use-table-state.js +4 -1
  276. package/dist/components/use-table-state.js.map +1 -1
  277. package/dist/data-table.d.ts +6 -2
  278. package/dist/data-table.js +7 -2
  279. package/dist/data-table.js.map +1 -1
  280. package/dist/eslint/index.js +1 -1
  281. package/dist/eslint/role-utilities.d.ts +36 -7
  282. package/dist/eslint/role-utilities.js +42 -9
  283. package/dist/eslint/role-utilities.js.map +1 -1
  284. package/dist/feedback/feedback-dialog.d.ts +25 -16
  285. package/dist/feedback/feedback-dialog.js +6 -2
  286. package/dist/feedback/feedback-dialog.js.map +1 -1
  287. package/dist/feedback/feedback-mutation.d.ts +30 -0
  288. package/dist/feedback/feedback-mutation.js +16 -0
  289. package/dist/feedback/feedback-mutation.js.map +1 -0
  290. package/dist/feedback/feedback-page-link.d.ts +53 -0
  291. package/dist/feedback/feedback-page-link.js +37 -0
  292. package/dist/feedback/feedback-page-link.js.map +1 -0
  293. package/dist/feedback/feedback-public-list.d.ts +12 -3
  294. package/dist/feedback/feedback-public-list.js +2 -1
  295. package/dist/feedback/feedback-public-list.js.map +1 -1
  296. package/dist/feedback/feedback-replies.d.ts +3 -1
  297. package/dist/feedback/feedback-replies.js +5 -4
  298. package/dist/feedback/feedback-replies.js.map +1 -1
  299. package/dist/feedback/feedback-row-detail-labels.js +5 -0
  300. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  301. package/dist/feedback/feedback-row-detail.d.ts +64 -28
  302. package/dist/feedback/feedback-row-detail.js +30 -28
  303. package/dist/feedback/feedback-row-detail.js.map +1 -1
  304. package/dist/feedback/feedback-status-undo.d.ts +30 -11
  305. package/dist/feedback/feedback-status-undo.js +10 -15
  306. package/dist/feedback/feedback-status-undo.js.map +1 -1
  307. package/dist/feedback/feedback-submit-dialog.d.ts +1 -1
  308. package/dist/feedback/feedback-submit-dialog.js +1 -1
  309. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  310. package/dist/feedback/feedback-submit.js +9 -3
  311. package/dist/feedback/feedback-submit.js.map +1 -1
  312. package/dist/feedback/feedback-swipe.d.ts +1 -1
  313. package/dist/feedback/feedback-swipe.js.map +1 -1
  314. package/dist/feedback/feedback-table.d.ts +21 -9
  315. package/dist/feedback/feedback-table.js +45 -25
  316. package/dist/feedback/feedback-table.js.map +1 -1
  317. package/dist/feedback/feedback-thread-labels.d.ts +2 -2
  318. package/dist/feedback/feedback-thread-labels.js.map +1 -1
  319. package/dist/feedback/feedback-thread.d.ts +24 -23
  320. package/dist/feedback/feedback-thread.js +2 -1
  321. package/dist/feedback/feedback-thread.js.map +1 -1
  322. package/dist/feedback.d.ts +34 -0
  323. package/dist/feedback.js +42 -0
  324. package/dist/feedback.js.map +1 -1
  325. package/dist/hooks/use-focus-trap.d.ts +35 -0
  326. package/dist/hooks/use-focus-trap.js +6 -4
  327. package/dist/hooks/use-focus-trap.js.map +1 -1
  328. package/dist/hooks/use-hotkey.d.ts +5 -2
  329. package/dist/hooks/use-hotkey.js +5 -4
  330. package/dist/hooks/use-hotkey.js.map +1 -1
  331. package/dist/hooks/use-long-press.d.ts +7 -4
  332. package/dist/hooks/use-long-press.js +3 -0
  333. package/dist/hooks/use-long-press.js.map +1 -1
  334. package/dist/hooks/use-media-query.js +12 -4
  335. package/dist/hooks/use-media-query.js.map +1 -1
  336. package/dist/hooks/use-mounted.d.ts +22 -0
  337. package/dist/hooks/use-mounted.js +24 -0
  338. package/dist/hooks/use-mounted.js.map +1 -0
  339. package/dist/hooks/use-overlay-history.js +19 -3
  340. package/dist/hooks/use-overlay-history.js.map +1 -1
  341. package/dist/i18n/defaults.js +4 -5
  342. package/dist/i18n/defaults.js.map +1 -1
  343. package/dist/i18n/german.js +7 -1
  344. package/dist/i18n/german.js.map +1 -1
  345. package/dist/i18n/kit-labels.d.ts +9 -2
  346. package/dist/i18n/kit-labels.js.map +1 -1
  347. package/dist/i18n/locales/en.js +20 -41
  348. package/dist/i18n/locales/en.js.map +3 -3
  349. package/dist/i18n/locales/es.js +7 -1
  350. package/dist/i18n/locales/es.js.map +1 -1
  351. package/dist/i18n/locales/fr.js +20 -10
  352. package/dist/i18n/locales/fr.js.map +1 -1
  353. package/dist/i18n/locales/hu.js +9 -1
  354. package/dist/i18n/locales/hu.js.map +1 -1
  355. package/dist/i18n/locales/it.js +7 -1
  356. package/dist/i18n/locales/it.js.map +1 -1
  357. package/dist/i18n/locales/zh.js +8 -1
  358. package/dist/i18n/locales/zh.js.map +1 -1
  359. package/dist/i18n/review.js +5 -0
  360. package/dist/i18n/review.js.map +1 -1
  361. package/dist/index.d.ts +64 -17
  362. package/dist/index.js +77 -12
  363. package/dist/index.js.map +1 -1
  364. package/dist/landing/routing.d.ts +3 -1
  365. package/dist/landing/routing.js +4 -7
  366. package/dist/landing/routing.js.map +1 -1
  367. package/dist/lib/broadcast.d.ts +17 -0
  368. package/dist/lib/broadcast.js +30 -0
  369. package/dist/lib/broadcast.js.map +1 -0
  370. package/dist/lib/date-format.d.ts +37 -2
  371. package/dist/lib/date-format.js +34 -8
  372. package/dist/lib/date-format.js.map +1 -1
  373. package/dist/lib/day-format.d.ts +46 -0
  374. package/dist/lib/day-format.js +46 -0
  375. package/dist/lib/day-format.js.map +1 -0
  376. package/dist/lib/describe-failure.d.ts +14 -0
  377. package/dist/lib/describe-failure.js +9 -0
  378. package/dist/lib/describe-failure.js.map +1 -0
  379. package/dist/lib/direction.d.ts +16 -0
  380. package/dist/lib/direction.js +9 -1
  381. package/dist/lib/direction.js.map +1 -1
  382. package/dist/lib/focus-left.d.ts +63 -0
  383. package/dist/lib/focus-left.js +67 -0
  384. package/dist/lib/focus-left.js.map +1 -0
  385. package/dist/lib/format.js +5 -14
  386. package/dist/lib/format.js.map +1 -1
  387. package/dist/lib/http-status.d.ts +24 -0
  388. package/dist/lib/http-status.js +14 -0
  389. package/dist/lib/http-status.js.map +1 -0
  390. package/dist/lib/intl-cache.d.ts +14 -0
  391. package/dist/lib/intl-cache.js +15 -0
  392. package/dist/lib/intl-cache.js.map +1 -0
  393. package/dist/lib/isin.d.ts +10 -0
  394. package/dist/lib/isin.js.map +1 -1
  395. package/dist/lib/iso-day.d.ts +11 -0
  396. package/dist/lib/iso-day.js +8 -0
  397. package/dist/lib/iso-day.js.map +1 -0
  398. package/dist/lib/lazy-with-fallback.d.ts +31 -0
  399. package/dist/lib/lazy-with-fallback.js +40 -0
  400. package/dist/lib/lazy-with-fallback.js.map +1 -0
  401. package/dist/lib/path-pattern.d.ts +34 -0
  402. package/dist/lib/path-pattern.js +24 -0
  403. package/dist/lib/path-pattern.js.map +1 -0
  404. package/dist/lib/reduced-motion.d.ts +2 -1
  405. package/dist/lib/reduced-motion.js.map +1 -1
  406. package/dist/lib/round.d.ts +2 -1
  407. package/dist/lib/round.js +2 -1
  408. package/dist/lib/round.js.map +1 -1
  409. package/dist/lib/safe-storage.d.ts +24 -5
  410. package/dist/lib/safe-storage.js +27 -2
  411. package/dist/lib/safe-storage.js.map +1 -1
  412. package/dist/lib/session-refresh.js +1 -1
  413. package/dist/lib/session-refresh.js.map +1 -1
  414. package/dist/lib/share-format.d.ts +13 -0
  415. package/dist/lib/share-format.js +10 -0
  416. package/dist/lib/share-format.js.map +1 -0
  417. package/dist/lib/text-case.d.ts +17 -0
  418. package/dist/lib/text-case.js +8 -0
  419. package/dist/lib/text-case.js.map +1 -0
  420. package/dist/lib/text-match.d.ts +29 -0
  421. package/dist/lib/text-match.js +20 -2
  422. package/dist/lib/text-match.js.map +1 -1
  423. package/dist/lib/thenable.d.ts +13 -0
  424. package/dist/lib/thenable.js +8 -0
  425. package/dist/lib/thenable.js.map +1 -0
  426. package/dist/lib/translation-review.d.ts +7 -2
  427. package/dist/lib/translation-review.js +4 -4
  428. package/dist/lib/translation-review.js.map +1 -1
  429. package/dist/notifications/device-zone-report.js +1 -1
  430. package/dist/notifications/device-zone-report.js.map +1 -1
  431. package/dist/notifications/notification-bell.d.ts +2 -1
  432. package/dist/notifications/notification-bell.js +3 -3
  433. package/dist/notifications/notification-bell.js.map +1 -1
  434. package/dist/notifications/notification-inbox.d.ts +10 -6
  435. package/dist/notifications/notification-inbox.js +26 -48
  436. package/dist/notifications/notification-inbox.js.map +1 -1
  437. package/dist/notifications/notification-parts.d.ts +3 -2
  438. package/dist/notifications/notification-parts.js +1 -3
  439. package/dist/notifications/notification-parts.js.map +1 -1
  440. package/dist/notifications/notification-types-setting.js +1 -1
  441. package/dist/notifications/notification-types-setting.js.map +1 -1
  442. package/dist/notifications/notifications-labels.d.ts +6 -0
  443. package/dist/notifications/notifications-labels.js +1 -0
  444. package/dist/notifications/notifications-labels.js.map +1 -1
  445. package/dist/notifications/push-device-setting.js +1 -1
  446. package/dist/notifications/push-device-setting.js.map +1 -1
  447. package/dist/notifications/push-devices-setting.d.ts +2 -1
  448. package/dist/notifications/push-devices-setting.js +27 -49
  449. package/dist/notifications/push-devices-setting.js.map +1 -1
  450. package/dist/notifications/quiet-hours-setting.d.ts +10 -3
  451. package/dist/notifications/quiet-hours-setting.js +65 -26
  452. package/dist/notifications/quiet-hours-setting.js.map +1 -1
  453. package/dist/notifications/unsubscribe-panel.js +4 -7
  454. package/dist/notifications/unsubscribe-panel.js.map +1 -1
  455. package/dist/notifications/use-push-device.d.ts +39 -1
  456. package/dist/notifications/use-push-device.js +28 -7
  457. package/dist/notifications/use-push-device.js.map +1 -1
  458. package/dist/offline/query-persistence.d.ts +3 -1
  459. package/dist/offline/query-persistence.js +19 -2
  460. package/dist/offline/query-persistence.js.map +1 -1
  461. package/dist/rhf/fields.d.ts +11 -4
  462. package/dist/rhf/fields.js.map +1 -1
  463. package/dist/search/command-palette.js +1 -1
  464. package/dist/search/command-palette.js.map +1 -1
  465. package/dist/search/global-search.js +19 -9
  466. package/dist/search/global-search.js.map +1 -1
  467. package/dist/search/use-command-key.d.ts +12 -1
  468. package/dist/search/use-command-key.js +3 -12
  469. package/dist/search/use-command-key.js.map +1 -1
  470. package/dist/search.d.ts +7 -0
  471. package/dist/search.js +10 -0
  472. package/dist/search.js.map +1 -1
  473. package/dist/settings/use-settings-focus.js +1 -0
  474. package/dist/settings/use-settings-focus.js.map +1 -1
  475. package/dist/shell/account-menu-language.d.ts +21 -5
  476. package/dist/shell/account-menu-language.js +3 -1
  477. package/dist/shell/account-menu-language.js.map +1 -1
  478. package/dist/shell/account-menu-text-size.d.ts +26 -9
  479. package/dist/shell/account-menu-text-size.js +3 -2
  480. package/dist/shell/account-menu-text-size.js.map +1 -1
  481. package/dist/shell/dev-menu.js +5 -18
  482. package/dist/shell/dev-menu.js.map +1 -1
  483. package/dist/shell/role-switcher.d.ts +7 -0
  484. package/dist/shell/role-switcher.js.map +1 -1
  485. package/dist/shell/top-bar-actions.d.ts +1 -1
  486. package/dist/shell/top-bar-actions.js.map +1 -1
  487. package/dist/shell/top-bar-parts-labels.d.ts +0 -2
  488. package/dist/shell/top-bar-parts-labels.js +0 -1
  489. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  490. package/dist/shell.d.ts +27 -8
  491. package/dist/shell.js +28 -7
  492. package/dist/shell.js.map +1 -1
  493. package/dist/sync/engine.d.ts +30 -4
  494. package/dist/sync/engine.js +172 -52
  495. package/dist/sync/engine.js.map +1 -1
  496. package/dist/sync.d.ts +6 -2
  497. package/dist/sync.js +6 -1
  498. package/dist/sync.js.map +1 -1
  499. package/dist/testing/css-variables.js +3 -8
  500. package/dist/testing/css-variables.js.map +1 -1
  501. package/dist/theme/contrast.d.ts +2 -1
  502. package/dist/theme/contrast.js +3 -8
  503. package/dist/theme/contrast.js.map +1 -1
  504. package/dist/theme/palette-store.js +6 -6
  505. package/dist/theme/palette-store.js.map +1 -1
  506. package/dist/theme/text-size.js +3 -8
  507. package/dist/theme/text-size.js.map +1 -1
  508. package/dist/theme/theme-store.js +3 -6
  509. package/dist/theme/theme-store.js.map +1 -1
  510. package/dist/tour/tour-catalogue.d.ts +7 -4
  511. package/dist/tour/tour-catalogue.js +12 -10
  512. package/dist/tour/tour-catalogue.js.map +1 -1
  513. package/dist/tour/tour-dock.d.ts +0 -2
  514. package/dist/tour/tour-dock.js +0 -2
  515. package/dist/tour/tour-dock.js.map +1 -1
  516. package/dist/tour/tour-gates.d.ts +4 -0
  517. package/dist/tour/tour-gates.js +14 -0
  518. package/dist/tour/tour-gates.js.map +1 -1
  519. package/dist/tour/tour-overlay.d.ts +27 -0
  520. package/dist/tour/tour-overlay.js +741 -0
  521. package/dist/tour/tour-overlay.js.map +1 -0
  522. package/dist/tour/tour.d.ts +13 -3
  523. package/dist/tour/tour.js +59 -781
  524. package/dist/tour/tour.js.map +1 -1
  525. package/dist/tour/use-tour-progress.js +1 -1
  526. package/dist/tour/use-tour-progress.js.map +1 -1
  527. package/dist/wizard/stepper-nav.d.ts +5 -1
  528. package/dist/wizard/stepper-nav.js.map +1 -1
  529. package/dist/wizard/types.d.ts +6 -2
  530. package/dist/wizard/types.js.map +1 -1
  531. package/dist/wizard/use-wizard.d.ts +3 -1
  532. package/dist/wizard/use-wizard.js.map +1 -1
  533. package/dist/wizard/validation.d.ts +3 -1
  534. package/dist/wizard/validation.js.map +1 -1
  535. package/package.json +10 -6
  536. package/src/account/account-parts.ts +5 -25
  537. package/src/account/data-export-setting-labels.ts +1 -1
  538. package/src/account/delete-account-setting-labels.ts +1 -1
  539. package/src/account/email-change-setting-labels.ts +1 -1
  540. package/src/account/passkeys-setting.tsx +60 -38
  541. package/src/account/sessions-setting.tsx +18 -12
  542. package/src/admin/admin-action-confirm.tsx +1 -5
  543. package/src/admin/admin-parts.ts +11 -25
  544. package/src/admin/mail-test-card-labels.ts +1 -1
  545. package/src/admin/plan-change-labels.ts +5 -23
  546. package/src/admin/plan-change.tsx +7 -44
  547. package/src/admin/reviewer-scope-editor.tsx +3 -3
  548. package/src/auth/auth-errors-labels.ts +23 -0
  549. package/src/auth/auth-errors.ts +11 -38
  550. package/src/auth/complete-name-dialog.tsx +4 -1
  551. package/src/auth/forgot-password-form-labels.ts +1 -1
  552. package/src/auth/forgot-password-form.tsx +11 -6
  553. package/src/auth/register-form-labels.ts +1 -1
  554. package/src/auth/reset-password-form-labels.ts +1 -1
  555. package/src/auth/reset-password-form.tsx +8 -4
  556. package/src/auth/sign-in-form-labels.ts +1 -1
  557. package/src/auth/status-parts.tsx +0 -6
  558. package/src/auth/verify-email-labels.ts +1 -1
  559. package/src/auth/verify-email.tsx +11 -6
  560. package/src/billing/cancel-subscription.tsx +26 -12
  561. package/src/billing/dialog-close.ts +34 -0
  562. package/src/billing/plan-card.tsx +36 -11
  563. package/src/billing/plan-change-labels.ts +66 -0
  564. package/src/billing/plan-switch.tsx +26 -12
  565. package/src/chart.ts +21 -3
  566. package/src/components/account-chips.tsx +20 -23
  567. package/src/components/alert-banner.tsx +82 -5
  568. package/src/components/amount-input.tsx +18 -7
  569. package/src/components/autocomplete.tsx +64 -70
  570. package/src/components/bulk-action-bar.tsx +70 -2
  571. package/src/components/button-look.ts +5 -5
  572. package/src/components/button.tsx +2 -2
  573. package/src/components/chart-edge.ts +34 -1
  574. package/src/components/chart-figure.tsx +35 -0
  575. package/src/components/chart-zoom-core.tsx +6 -5
  576. package/src/components/chart.tsx +26 -5
  577. package/src/components/combobox-core.tsx +293 -62
  578. package/src/components/combobox.tsx +183 -153
  579. package/src/components/copy-button.tsx +34 -23
  580. package/src/components/country-select.tsx +16 -11
  581. package/src/components/data-table-column-settings.tsx +52 -10
  582. package/src/components/data-table-filters.ts +6 -4
  583. package/src/components/data-table-fit.ts +19 -5
  584. package/src/components/data-table-groups.tsx +1 -1
  585. package/src/components/data-table-head.tsx +32 -22
  586. package/src/components/data-table-mobile.tsx +61 -26
  587. package/src/components/data-table-pagination.tsx +21 -6
  588. package/src/components/data-table-resize.ts +1 -1
  589. package/src/components/data-table-row-actions.tsx +3 -3
  590. package/src/components/data-table-row.tsx +25 -14
  591. package/src/components/data-table-selection.ts +1 -1
  592. package/src/components/data-table-shared.tsx +33 -1
  593. package/src/components/data-table-sort.ts +57 -2
  594. package/src/components/data-table-virtual.ts +32 -8
  595. package/src/components/data-table.tsx +239 -68
  596. package/src/components/date-field.tsx +65 -8
  597. package/src/components/date-picker.tsx +125 -48
  598. package/src/components/date-range-picker.tsx +82 -31
  599. package/src/components/error-boundary.tsx +53 -39
  600. package/src/components/field-base-labels.ts +20 -0
  601. package/src/components/field-base.tsx +13 -7
  602. package/src/components/field-sync-labels.ts +1 -0
  603. package/src/components/field-sync.tsx +23 -1
  604. package/src/components/form-actions.tsx +20 -5
  605. package/src/components/hover-menu.tsx +18 -8
  606. package/src/components/icon-button.tsx +14 -9
  607. package/src/components/inline-confirm.tsx +116 -0
  608. package/src/components/inline-entity-combobox.tsx +197 -153
  609. package/src/components/input.tsx +1 -1
  610. package/src/components/jump-calendar.tsx +30 -146
  611. package/src/components/legal-page.tsx +10 -7
  612. package/src/components/legal.tsx +4 -2
  613. package/src/components/lightbox.tsx +21 -8
  614. package/src/components/list.tsx +10 -4
  615. package/src/components/measured-grid.tsx +8 -4
  616. package/src/components/mini-calendar.tsx +6 -5
  617. package/src/components/modal.tsx +6 -1
  618. package/src/components/month-grid.tsx +229 -0
  619. package/src/components/month-picker.tsx +192 -200
  620. package/src/components/multi-select.tsx +21 -9
  621. package/src/components/picker-sheet.tsx +12 -9
  622. package/src/components/pie-chart-labels.ts +8 -0
  623. package/src/components/pie-chart.tsx +22 -28
  624. package/src/components/popover.tsx +18 -1
  625. package/src/components/row-actions.tsx +2 -1
  626. package/src/components/section-nav.tsx +7 -0
  627. package/src/components/series-chart-axes.ts +3 -3
  628. package/src/components/series-chart-labels.ts +7 -4
  629. package/src/components/series-chart.tsx +178 -69
  630. package/src/components/share-card.tsx +6 -6
  631. package/src/components/sign-chip-labels.ts +1 -0
  632. package/src/components/sign-chip.tsx +10 -1
  633. package/src/components/tabs-labels.ts +13 -0
  634. package/src/components/tabs.tsx +2 -2
  635. package/src/components/toggle-legend.tsx +16 -11
  636. package/src/components/translation-review-editor.tsx +6 -5
  637. package/src/components/translation-review.tsx +4 -3
  638. package/src/components/treemap-labels.ts +39 -0
  639. package/src/components/treemap-layout.ts +210 -0
  640. package/src/components/treemap.tsx +722 -133
  641. package/src/components/use-table-state.ts +12 -9
  642. package/src/data-table.ts +9 -2
  643. package/src/eslint/role-utilities.ts +97 -19
  644. package/src/feedback/feedback-dialog.tsx +30 -19
  645. package/src/feedback/feedback-mutation.ts +41 -0
  646. package/src/feedback/feedback-page-link.ts +90 -0
  647. package/src/feedback/feedback-public-list.tsx +17 -5
  648. package/src/feedback/feedback-replies.tsx +13 -6
  649. package/src/feedback/feedback-row-detail-labels.ts +5 -0
  650. package/src/feedback/feedback-row-detail.tsx +103 -67
  651. package/src/feedback/feedback-status-undo.ts +41 -27
  652. package/src/feedback/feedback-submit-dialog.tsx +2 -2
  653. package/src/feedback/feedback-submit.tsx +21 -7
  654. package/src/feedback/feedback-swipe.tsx +1 -1
  655. package/src/feedback/feedback-table.tsx +86 -38
  656. package/src/feedback/feedback-thread-labels.ts +2 -2
  657. package/src/feedback/feedback-thread.tsx +27 -25
  658. package/src/feedback.ts +42 -0
  659. package/src/hooks/use-focus-trap.ts +8 -24
  660. package/src/hooks/use-hotkey.ts +24 -7
  661. package/src/hooks/use-long-press.ts +16 -2
  662. package/src/hooks/use-media-query.ts +20 -4
  663. package/src/hooks/use-mounted.ts +39 -0
  664. package/src/hooks/use-overlay-history.ts +32 -3
  665. package/src/i18n/defaults.ts +4 -5
  666. package/src/i18n/german.ts +8 -2
  667. package/src/i18n/kit-labels.tsx +9 -2
  668. package/src/i18n/locales/es.ts +8 -2
  669. package/src/i18n/locales/fr.ts +21 -13
  670. package/src/i18n/locales/hu.ts +10 -2
  671. package/src/i18n/locales/it.ts +8 -2
  672. package/src/i18n/locales/zh.ts +9 -2
  673. package/src/i18n/review.ts +5 -0
  674. package/src/index.ts +103 -13
  675. package/src/landing/routing.tsx +14 -10
  676. package/src/lib/broadcast.ts +52 -0
  677. package/src/lib/date-format.ts +89 -13
  678. package/src/lib/day-format.ts +96 -0
  679. package/src/lib/describe-failure.ts +18 -0
  680. package/src/lib/direction.ts +39 -0
  681. package/src/lib/focus-left.ts +156 -0
  682. package/src/lib/format.ts +5 -17
  683. package/src/lib/http-status.ts +38 -0
  684. package/src/lib/intl-cache.ts +25 -0
  685. package/src/lib/isin.ts +10 -0
  686. package/src/lib/iso-day.ts +13 -0
  687. package/src/lib/lazy-with-fallback.ts +82 -0
  688. package/src/lib/path-pattern.ts +55 -0
  689. package/src/lib/reduced-motion.ts +2 -1
  690. package/src/lib/round.ts +7 -2
  691. package/src/lib/safe-storage.ts +48 -6
  692. package/src/lib/session-refresh.ts +1 -1
  693. package/src/lib/share-format.ts +18 -0
  694. package/src/lib/text-case.ts +19 -0
  695. package/src/lib/text-match.ts +57 -2
  696. package/src/lib/thenable.ts +19 -0
  697. package/src/lib/translation-review.ts +13 -5
  698. package/src/notifications/device-zone-report.ts +1 -1
  699. package/src/notifications/notification-bell.tsx +7 -4
  700. package/src/notifications/notification-inbox.tsx +40 -58
  701. package/src/notifications/notification-parts.tsx +3 -4
  702. package/src/notifications/notification-types-setting.tsx +1 -1
  703. package/src/notifications/notifications-labels.ts +7 -0
  704. package/src/notifications/push-device-setting.tsx +1 -1
  705. package/src/notifications/push-devices-setting.tsx +30 -55
  706. package/src/notifications/quiet-hours-setting.tsx +93 -31
  707. package/src/notifications/unsubscribe-panel.tsx +4 -7
  708. package/src/notifications/use-push-device.ts +76 -10
  709. package/src/offline/query-persistence.ts +35 -3
  710. package/src/rhf/fields.tsx +11 -4
  711. package/src/search/command-palette.tsx +2 -4
  712. package/src/search/global-search.tsx +36 -15
  713. package/src/search/use-command-key.ts +13 -12
  714. package/src/search.ts +9 -0
  715. package/src/settings/use-settings-focus.ts +3 -0
  716. package/src/shell/account-menu-language.tsx +27 -5
  717. package/src/shell/account-menu-text-size.tsx +32 -10
  718. package/src/shell/dev-menu.tsx +7 -21
  719. package/src/shell/role-switcher.tsx +7 -0
  720. package/src/shell/top-bar-actions.tsx +1 -1
  721. package/src/shell/top-bar-parts-labels.ts +0 -3
  722. package/src/shell.ts +40 -9
  723. package/src/sync/engine.ts +339 -61
  724. package/src/sync.ts +8 -1
  725. package/src/testing/css-variables.ts +8 -9
  726. package/src/theme/contrast.ts +8 -9
  727. package/src/theme/palette-store.ts +9 -9
  728. package/src/theme/text-size.ts +6 -8
  729. package/src/theme/theme-store.ts +7 -9
  730. package/src/tour/tour-catalogue.tsx +26 -15
  731. package/src/tour/tour-dock.ts +0 -2
  732. package/src/tour/tour-gates.ts +23 -0
  733. package/src/tour/tour-overlay.tsx +1170 -0
  734. package/src/tour/tour.tsx +108 -1202
  735. package/src/tour/use-tour-progress.ts +1 -1
  736. package/src/wizard/stepper-nav.tsx +3 -1
  737. package/src/wizard/types.ts +11 -2
  738. package/src/wizard/use-wizard.ts +2 -1
  739. package/src/wizard/validation.ts +8 -2
  740. package/tokens.css +2 -1
  741. package/dist/components/ui-labels.js.map +0 -1
  742. package/dist/shell/dev-switchers.d.ts +0 -6
  743. package/dist/shell/dev-switchers.js +0 -12
  744. package/dist/shell/dev-switchers.js.map +0 -1
  745. package/src/components/ui-labels.ts +0 -24
  746. package/src/shell/dev-switchers.tsx +0 -18
@@ -33,3 +33,42 @@ export function horizontalStep(key: string, el: Element | null | undefined): 1 |
33
33
  const forward = (key === "ArrowRight") !== isRtl(el);
34
34
  return forward ? 1 : -1;
35
35
  }
36
+
37
+ /**
38
+ * Where a key moves the focus in a list with ONE tab stop (a roving tabindex): the index
39
+ * to focus from `at` among `count` items, or `undefined` for a key that moves nothing.
40
+ * ←/→ along `dir` — → is next in `ltr` and previous in `rtl` — ↓/↑ as next and previous,
41
+ * Home and End to the ends. With `wrap`, past the last is the first and before the first
42
+ * the last (a ring: PieChart's slices); without, the ends hold (a plane: Treemap's tiles).
43
+ *
44
+ * The charts each mapped the keys themselves, and differently at the ends (0.52 code
45
+ * review of treemap.tsx, finding 9); the end rule is now a word at the call.
46
+ *
47
+ * `dir` and not an element to read it off (0.52 code review, deep, charts, findings 1 and
48
+ * 14): a list in the page's flow takes the page's (`dirOf(el)`), but a plot is laid out
49
+ * left to right in any page (see `ChartContainer`) and passes `"ltr"` — read off the
50
+ * element, Treemap's → walked an RTL page's tiles backwards, against the picture.
51
+ */
52
+ export function rovingTarget(
53
+ key: string,
54
+ dir: Direction,
55
+ at: number,
56
+ count: number,
57
+ wrap: boolean,
58
+ ): number | undefined {
59
+ const physical = key === "ArrowRight" ? 1 : key === "ArrowLeft" ? -1 : 0;
60
+ const step = dir === "rtl" ? -physical : physical;
61
+ const to = step
62
+ ? at + step
63
+ : key === "ArrowDown"
64
+ ? at + 1
65
+ : key === "ArrowUp"
66
+ ? at - 1
67
+ : key === "Home"
68
+ ? 0
69
+ : key === "End"
70
+ ? count - 1
71
+ : undefined;
72
+ if (to === undefined || count <= 0) return undefined;
73
+ return wrap ? ((to % count) + count) % count : Math.max(0, Math.min(count - 1, to));
74
+ }
@@ -0,0 +1,156 @@
1
+ // "The focused node left" (refactor audit 2026-10-30, Wave 1: DSP-4, DSP-15). When the
2
+ // element holding the keyboard's focus is removed — a table row deleted, filtered out or
3
+ // approved; a bulk bar gone with its Clear; a banner its × took away — the browser puts
4
+ // focus on <body> and says nothing: no blur in Chromium, none in jsdom. A keyboard or
5
+ // screen-reader user then starts again from the top of the page. The overlays give focus
6
+ // back to their opener (use-focus-trap); these helpers are for the controls that leave
7
+ // the page while the focus is IN them, and decide where it goes instead.
8
+
9
+ /**
10
+ * Candidate tab stops, in DOM order — narrowed by {@link isTabStop} to the ones a Tab
11
+ * really reaches. The kit's one copy of the rule (0.52 code review, lib/focus-left.ts):
12
+ * this file and use-focus-trap each had it, with an exported `firstTabStop` apiece that
13
+ * disagreed about the element itself, and a combobox panel's toolbar had a third
14
+ * selector. The trap, the focus a removed node drops (below), the tour's field ring and
15
+ * the toolbar all read it here, so "the next stop" is the same stop everywhere.
16
+ *
17
+ * The selector is the one `Modal` used. On its own it was not enough (round 0.34
18
+ * BUG-4): only the bare `[tabindex]` clause excluded `-1`, so a `button`, an `input` or
19
+ * a link taken out of the tab order still counted. A panel that ends in a roving grid —
20
+ * the calendar's days, the month grid, the picker sheet's rows, every one of them `-1`
21
+ * but the active cell — then had a "last" stop no Tab ever reaches: Tab from the real
22
+ * last stop was not wrapped and walked out of the open panel, and Shift+Tab from the
23
+ * first landed on a cell that is not a stop at all.
24
+ */
25
+ // `input[type=hidden]` is no stop: a Tab never lands on it and focus() on it does nothing,
26
+ // but its tabIndex reads 0, so a trap could take a form's hidden field for its first or last
27
+ // stop and Shift+Tab left the dialog (0.52 code review).
28
+ const TAB_STOP =
29
+ 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]):not([type="hidden"]),select:not([disabled]),[tabindex]:not([tabindex="-1"])';
30
+
31
+ /**
32
+ * A stop the browser's own Tab would land on: in the tab order (`tabIndex` ≥ 0) and not
33
+ * inside something `hidden` or `inert`. Removes only elements a native Tab never
34
+ * reaches, so no correct order changes. `visibility: hidden` and closed `<details>` are
35
+ * still not walked — they need layout, which this rule has never read.
36
+ */
37
+ function isTabStop(el: HTMLElement): boolean {
38
+ return el.tabIndex >= 0 && !el.closest("[hidden],[inert]");
39
+ }
40
+
41
+ export interface TabStopOptions {
42
+ /** Whether `el` itself counts, first, when it is a stop. Default true: the tour's
43
+ * target may BE the field (refactor audit 2026-10-30, DUP-9). False for "a control
44
+ * inside it" — a row, a drawer, a trap's container, a toolbar. */
45
+ includeSelf?: boolean;
46
+ }
47
+
48
+ /** The tab stops in `el`, in DOM order — `el` itself first when it is one, unless
49
+ * `includeSelf: false`. */
50
+ export function tabStops(el: Element, { includeSelf = true }: TabStopOptions = {}): HTMLElement[] {
51
+ const inside = Array.from(el.querySelectorAll<HTMLElement>(TAB_STOP)).filter(isTabStop);
52
+ const own = includeSelf && el instanceof HTMLElement && el.matches(TAB_STOP) && isTabStop(el);
53
+ return own ? [el, ...inside] : inside;
54
+ }
55
+
56
+ /** The first of {@link tabStops} — the control a person types into or presses — or null. */
57
+ export function firstTabStop(el: Element, options?: TabStopOptions): HTMLElement | null {
58
+ return tabStops(el, options)[0] ?? null;
59
+ }
60
+
61
+ /**
62
+ * Whether the focus has fallen out of the page: onto `<body>`, onto nothing, or onto a
63
+ * node no longer in the document. False on the server, where nothing has focus to lose.
64
+ * A recovery asks this first: focus that went somewhere real (a dialog, the next field,
65
+ * an app's own `focus()`) is the user's or the app's, and is left where it is.
66
+ */
67
+ export function focusIsLost(): boolean {
68
+ if (typeof document === "undefined") return false;
69
+ const el = document.activeElement;
70
+ return !el || el === document.body || !el.isConnected;
71
+ }
72
+
73
+ /**
74
+ * Calls `then` when a `blur`/`focusout` was the user's own move to nothing — a click on
75
+ * the page's background, or a click Safari answers by focusing nothing (it focuses no
76
+ * button or link on a click, and blurs what had the focus) — after which a note of where
77
+ * the focus was is stale (0.52 code review: DataTable's row, BulkActionBar's and
78
+ * AlertBanner's notes outlived it, and a removal much later pulled the focus, and the
79
+ * scroll, back to them).
80
+ *
81
+ * No element took the focus, the document holds none, and the node that lost it could
82
+ * still hold it. Not when it was removed, hidden, made inert or disabled: that is the page
83
+ * taking the focus away — the very case those notes are kept for. Not a window losing the
84
+ * focus either, which leaves the node the document's active element. Decided a microtask
85
+ * later, once the event's own work is done: a browser that blurs a node it is removing
86
+ * (or hiding) may do it while the node is still in place, and only after it is gone is
87
+ * that told apart from a click.
88
+ */
89
+ export function whenFocusLeftForNothing(
90
+ e: { target: EventTarget | null; relatedTarget: EventTarget | null },
91
+ then: () => void,
92
+ ): void {
93
+ const el = e.target;
94
+ if (e.relatedTarget !== null || !(el instanceof Element)) return;
95
+ queueMicrotask(() => {
96
+ if (!el.isConnected || el.matches(":disabled") || el.closest("[hidden],[inert]")) return;
97
+ // `display: none`, where the browser can say (jsdom cannot).
98
+ if (typeof el.checkVisibility === "function" && !el.checkVisibility()) return;
99
+ if (focusIsLost()) then();
100
+ });
101
+ }
102
+
103
+ /**
104
+ * The tab stops around `frame`, outside it, nearest first on each side — where focus
105
+ * goes once `frame` is gone. Read BEFORE it goes: a removed node has no place in the
106
+ * document to measure from. `limit` per side keeps a long page's stops out of memory.
107
+ *
108
+ * Walked outwards from the frame, `limit` stops each way (0.52 code review,
109
+ * lib/focus-left.ts): read from every stop in the page, a banner under a 2000-row table
110
+ * read all 2000 to keep the nearest five before it.
111
+ */
112
+ export function tabStopsAround(frame: Element, limit = 5): { after: HTMLElement[]; before: HTMLElement[] } {
113
+ const doc = frame.ownerDocument;
114
+ const walker = doc.createTreeWalker(doc.documentElement, NodeFilter.SHOW_ELEMENT);
115
+ const isStop = (node: Node): node is HTMLElement =>
116
+ node instanceof HTMLElement && node.matches(TAB_STOP) && isTabStop(node);
117
+ // After: from the first element past the frame's own subtree, in document order.
118
+ const after: HTMLElement[] = [];
119
+ walker.currentNode = frame;
120
+ let node: Node | null = walker.nextSibling();
121
+ while (!node && walker.parentNode()) node = walker.nextSibling();
122
+ for (; node && after.length < limit; node = walker.nextNode()) if (isStop(node)) after.push(node);
123
+ // Before: back from the frame, nearest first (its ancestors among them, as before).
124
+ const before: HTMLElement[] = [];
125
+ walker.currentNode = frame;
126
+ for (node = walker.previousNode(); node && before.length < limit; node = walker.previousNode())
127
+ if (isStop(node)) before.push(node);
128
+ return { after, before };
129
+ }
130
+
131
+ /**
132
+ * Focus the first of `candidates` that is still in the page and takes the focus; true
133
+ * when one did. Each is tried and checked, since `focus()` on a node that cannot hold it
134
+ * (detached, `display: none`, disabled since) fails without a word.
135
+ */
136
+ export function focusFirst(candidates: Iterable<Element | null | undefined>): boolean {
137
+ for (const el of candidates) {
138
+ if (!(el instanceof HTMLElement) || !el.isConnected) continue;
139
+ el.focus();
140
+ if (document.activeElement === el) return true;
141
+ }
142
+ return false;
143
+ }
144
+
145
+ /**
146
+ * Focus a container that is not a tab stop of its own — a table, a list — as the place
147
+ * the user is in, without making it one: `tabindex="-1"` for as long as it holds the
148
+ * focus, unless it already carries a `tabindex` of its own.
149
+ */
150
+ export function focusContainer(el: HTMLElement): void {
151
+ if (!el.hasAttribute("tabindex")) {
152
+ el.setAttribute("tabindex", "-1");
153
+ el.addEventListener("blur", () => el.removeAttribute("tabindex"), { once: true });
154
+ }
155
+ el.focus();
156
+ }
package/src/lib/format.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { useMemo } from "react";
2
2
  import { useKitLocale, useKitTimeZone } from "../i18n/kit-labels";
3
3
  import { parseIsoDate } from "./dates";
4
+ import { dateFormatter } from "./intl-cache";
5
+ import { isBareIsoDay } from "./iso-day";
4
6
  import { formatWithMinus, type MinusStyle } from "./minus";
5
7
  import { roundHalfAway } from "./round";
6
8
 
@@ -278,9 +280,6 @@ export interface FormatDateOptions extends BaseOptions {
278
280
  timeZone?: string;
279
281
  }
280
282
 
281
- /** A bare ISO calendar day, `"YYYY-MM-DD"`. */
282
- const BARE_ISO_DAY = /^\d{4}-\d{2}-\d{2}$/;
283
-
284
283
  /** What {@link formatDate} and {@link formatRelativeTime} accept. */
285
284
  export type DateInput = string | number | Date | null | undefined;
286
285
 
@@ -292,22 +291,11 @@ export type DateInput = string | number | Date | null | undefined;
292
291
  */
293
292
  export function toDate(value: DateInput): Date | null {
294
293
  if (value === null || value === undefined || value === "") return null;
295
- if (typeof value === "string" && BARE_ISO_DAY.test(value)) return parseIsoDate(value);
294
+ if (isBareIsoDay(value)) return parseIsoDate(value);
296
295
  const d = value instanceof Date ? value : new Date(value);
297
296
  return Number.isNaN(d.getTime()) ? null : d;
298
297
  }
299
298
 
300
- const dateFormatters = new Map<string, Intl.DateTimeFormat>();
301
- function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
302
- const key = `${locale ?? ""}|${JSON.stringify(options)}`;
303
- let f = dateFormatters.get(key);
304
- if (!f) {
305
- f = new Intl.DateTimeFormat(locale, options);
306
- dateFormatters.set(key, f);
307
- }
308
- return f;
309
- }
310
-
311
299
  /**
312
300
  * A date or timestamp in `locale`. Takes an ISO date ("2026-07-08", read on the local
313
301
  * calendar), an ISO timestamp ("2026-07-08T14:05:00Z"), epoch milliseconds or a Date.
@@ -327,7 +315,7 @@ export function formatDate(
327
315
  // A bare "YYYY-MM-DD" is a calendar day, which has no zone: it is the day it names
328
316
  // wherever it is read, so a `timeZone` (in `options` or the style) never moves it — the
329
317
  // device's local midnight shown in a zone west of it was the day before (round 0.45, N9).
330
- if (typeof value === "string" && BARE_ISO_DAY.test(value)) {
318
+ if (isBareIsoDay(value)) {
331
319
  const day = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
332
320
  return dateFormatter(options.locale, { ...base, timeZone: "UTC" }).format(day);
333
321
  }
@@ -425,7 +413,7 @@ export function formatRelativeTime(value: DateInput, options: FormatRelativeTime
425
413
  // tooltip) that said otherwise. Counted in `timeZone`, the zone that date is printed
426
414
  // in; a bare "YYYY-MM-DD" is the day it names wherever it is read, as in formatDate.
427
415
  // Still the same day (22 hours and more since just after midnight): in hours.
428
- const bareDay = typeof value === "string" && BARE_ISO_DAY.test(value);
416
+ const bareDay = isBareIsoDay(value);
429
417
  const days = dayNumber(d, bareDay ? undefined : options.timeZone) - dayNumber(now, options.timeZone);
430
418
  return days === 0 ? rtf.format(Math.round(diff / HOUR), "hour") : rtf.format(days, "day");
431
419
  }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The HTTP status of a thrown error, as one probe (DUP-11, refactor audit 2026-10: the
3
+ * session refresh carried its own copy).
4
+ *
5
+ * It lived in `auth/auth-errors.ts` until 0.52, which made `lib/session-refresh` and
6
+ * `/sync`'s engine import the auth folder — and put auth's English error words in the
7
+ * `/sync` entry's closure — for a three-line reader of `status` (refactor audit
8
+ * 2026-10-30, MOD-6). `auth-errors.ts` re-exports it, so nothing that imported it there
9
+ * moved.
10
+ *
11
+ * @internal Package-private — not re-exported from the barrel.
12
+ */
13
+
14
+ type Bag = Record<string, unknown>;
15
+
16
+ const isBag = (value: unknown): value is Bag => typeof value === "object" && value !== null;
17
+
18
+ /** An HTTP status as a number, or undefined. */
19
+ function statusOf(value: unknown): number | undefined {
20
+ return typeof value === "number" && Number.isInteger(value) ? value : undefined;
21
+ }
22
+
23
+ /**
24
+ * The HTTP status a thrown error carries, wherever the apps' HTTP clients put it —
25
+ * without depending on any of them:
26
+ *
27
+ * 1. `err.response.status` — axios (all three apps' clients);
28
+ * 2. `err.status` — ofetch's `FetchError`, the generated OpenAPI clients' `ApiError`, and
29
+ * a `Response` thrown as it came;
30
+ * 3. `err.statusCode` — ofetch's alias, and Node-style errors.
31
+ *
32
+ * `undefined` when none is a whole number.
33
+ */
34
+ export function httpStatusOf(err: unknown): number | undefined {
35
+ if (!isBag(err)) return undefined;
36
+ const response = err.response;
37
+ return (isBag(response) ? statusOf(response.status) : undefined) ?? statusOf(err.status) ?? statusOf(err.statusCode);
38
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * One `Intl.DateTimeFormat` per locale and option set, for every date the kit prints.
3
+ *
4
+ * Constructing the formatter is the expensive part: keksdose measured 392 ms per click on
5
+ * a 270-row register when each cell built its own (dev#632), and kept its own renderer of
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.
8
+ *
9
+ * @internal Not part of the barrel — `lib/format` is re-exported whole, so the cache
10
+ * lives apart from it.
11
+ */
12
+
13
+ const dateFormatters = new Map<string, Intl.DateTimeFormat>();
14
+
15
+ /** The cached formatter for `locale` and `options`. Throws as `Intl.DateTimeFormat` does
16
+ * for a tag it refuses — and caches nothing then. */
17
+ export function dateFormatter(locale: string | undefined, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {
18
+ const key = `${locale ?? ""}|${JSON.stringify(options)}`;
19
+ let f = dateFormatters.get(key);
20
+ if (!f) {
21
+ f = new Intl.DateTimeFormat(locale, options);
22
+ dateFormatters.set(key, f);
23
+ }
24
+ return f;
25
+ }
package/src/lib/isin.ts CHANGED
@@ -9,6 +9,10 @@
9
9
  * with every letter expanded to two digits (A = 10 … Z = 35) — which is all that is
10
10
  * checked here. Whether the prefix is an assigned code, and whether the security
11
11
  * exists, is a lookup, not a checksum.
12
+ *
13
+ * Deprecated in 0.52, removed in 0.53 (decision 4 of the refactor audit 2026-10-30,
14
+ * MOD-5): keksdose, which they were written for, never imported them, and no other app
15
+ * does.
12
16
  */
13
17
 
14
18
  /** Letters to their two-digit numbers (A = 10 … Z = 35), digits as they are. */
@@ -36,6 +40,8 @@ function luhnSum(digits: string, doubleRightmost: boolean): number {
36
40
  /**
37
41
  * The ISIN as it is printed: upper case, unbroken (ISO 6166 has no grouping), with
38
42
  * the spaces and hyphens a paste from a broker's statement carries dropped.
43
+ *
44
+ * @deprecated 0.52 — none: no app checks an ISIN with it; copy lib/isin.ts (MOD-5); removed in 0.53.
39
45
  */
40
46
  export function formatIsin(value: string): string {
41
47
  return value.replace(/[\s-]/g, "").toUpperCase();
@@ -44,6 +50,8 @@ export function formatIsin(value: string): string {
44
50
  /**
45
51
  * The check digit for the first eleven characters of an ISIN (prefix and national
46
52
  * number). For BUILDING one — a test fixture, a demo — rather than checking it.
53
+ *
54
+ * @deprecated 0.52 — none: no app checks an ISIN with it; copy lib/isin.ts (MOD-5); removed in 0.53.
47
55
  */
48
56
  export function isinCheckDigit(body: string): string {
49
57
  const sum = luhnSum(expandLetters(formatIsin(body)), true);
@@ -54,6 +62,8 @@ export function isinCheckDigit(body: string): string {
54
62
  * Whether `value` is a valid ISIN: two letters, nine letters or digits, one digit,
55
63
  * and a matching Luhn check digit over the letter-expanded form. Spaces, hyphens and
56
64
  * lower case are fine (see {@link formatIsin}).
65
+ *
66
+ * @deprecated 0.52 — none: no app checks an ISIN with it; copy lib/isin.ts (MOD-5); removed in 0.53.
57
67
  */
58
68
  export function isValidIsin(value: string): boolean {
59
69
  const isin = formatIsin(value);
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The bare ISO calendar day, `"YYYY-MM-DD"` — no time, no zone: the day it names in every
3
+ * zone. `toDate` and `formatDate` (lib/format), the kit's day ladder (`useDayFormatter`,
4
+ * lib/day-format) and the preference's renderers (lib/date-format) each kept a copy of this
5
+ * pattern (0.52 code review, lib/day-format.ts). Were one to learn a new shape (say
6
+ * `YYYY-MM-DDT00:00`) alone, the ladder would ask the provider for a different day than the
7
+ * kit prints. One rule.
8
+ *
9
+ * @internal Not part of the barrel: `lib/format` is re-exported whole, so it lives apart.
10
+ */
11
+ export function isBareIsoDay(value: unknown): value is string {
12
+ return typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value);
13
+ }
@@ -0,0 +1,82 @@
1
+ import { createContext, createElement, lazy, useCallback, useContext, useLayoutEffect, useState } from "react";
2
+ import type { ComponentType, LazyExoticComponent } from "react";
3
+
4
+ /** What a {@link lazyWithFallback} fallback is given besides the component's own props. */
5
+ export interface LazyFallbackProps {
6
+ /** What the import rejected with. Already on `console.error`. */
7
+ error: unknown;
8
+ /** Fetch the module again and draw it here if it comes (back online, the deploy's
9
+ * new chunk). Call it on the person's next attempt, never at once: offline, a retry
10
+ * on mount would fetch in a loop. */
11
+ retry: () => void;
12
+ }
13
+
14
+ interface Attempt<P> {
15
+ Component: LazyExoticComponent<ComponentType<P>>;
16
+ /** Its import failed and its fallback was drawn: the next mount, or `retry`, takes a
17
+ * fresh attempt. Marked when the fallback COMMITS, not when the import rejects: a
18
+ * first mount that suspended is thrown away and rendered anew, and an attempt marked
19
+ * at the rejection would hand that re-render a fresh import — which fails, and so on. */
20
+ spent: boolean;
21
+ }
22
+
23
+ const RetryContext = createContext<() => void>(() => {});
24
+
25
+ /**
26
+ * `React.lazy` for a module the kit loads late (refactor audit 2026-10, MOD-1: the tour
27
+ * overlay, the search palette, the feedback dialog), where an import that fails — a tab
28
+ * left open across a deploy, a local-first app offline — draws `Fallback` instead of
29
+ * throwing.
30
+ *
31
+ * `React.lazy` keeps what its import settled to, so one failed fetch threw on every render
32
+ * for as long as the component stayed mounted, to the nearest error boundary. All three
33
+ * sit above the routes — in the provider every app mounts, in the top bar — where that
34
+ * boundary is the whole app's: a white page because somebody pressed ⌘K or "Take the
35
+ * tour" (0.52 code review, tour.tsx: the tour's own fix, written by hand with a module-level
36
+ * `let`, made shared here).
37
+ *
38
+ * A failure is logged and the next mount (or the fallback's `retry`) fetches again; a
39
+ * re-render of a mounted fallback does not. `load` resolves to the component itself,
40
+ * through a LITERAL relative dynamic import of its module (as feedback-submit's), so an
41
+ * app's bundler can split it off. Render the result under a `Suspense`, as a lazy
42
+ * component. (No import call is spelled out here: the build's fix-esm-extensions step
43
+ * reads specifiers in the .d.ts comments too, and would try to resolve an example.)
44
+ */
45
+ export function lazyWithFallback<P extends object>(
46
+ load: () => Promise<ComponentType<P>>,
47
+ Fallback: ComponentType<P & LazyFallbackProps>,
48
+ ): ComponentType<P> {
49
+ const attempt = (): Attempt<P> => {
50
+ const made: Attempt<P> = {
51
+ spent: false,
52
+ Component: lazy<ComponentType<P>>(() =>
53
+ load().then(
54
+ (Loaded) => ({ default: Loaded }),
55
+ (error: unknown) => {
56
+ console.error(error);
57
+ function Failed(props: P) {
58
+ const retry = useContext(RetryContext);
59
+ useLayoutEffect(() => {
60
+ made.spent = true;
61
+ }, []);
62
+ return createElement(Fallback, { ...props, error, retry });
63
+ }
64
+ return { default: Failed };
65
+ },
66
+ ),
67
+ ),
68
+ };
69
+ return made;
70
+ };
71
+ let latest = attempt();
72
+ /** The attempt a new mount takes: the latest, or a fresh one once that has failed. */
73
+ const take = () => (latest.spent ? (latest = attempt()) : latest);
74
+
75
+ function LazyWithFallback(props: P) {
76
+ // Fixed per mount, so a re-render of a mounted fallback fetches nothing.
77
+ const [current, setCurrent] = useState(take);
78
+ const retry = useCallback(() => setCurrent(take), []);
79
+ return createElement(RetryContext.Provider, { value: retry }, createElement(current.Component, props));
80
+ }
81
+ return LazyWithFallback;
82
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Whether a pathname is one a `PathPattern` names — the rule `useLastVisitedPage`'s
3
+ * `exclude` reads (landing/routing.tsx) and the feedback guard's one-shot routes
4
+ * (refactor audit 2026-10-30, FLW-7), here without the router: `landing/routing` imports
5
+ * react-router, which `/feedback` must not drag in. The one copy: routing kept a private
6
+ * one, and the two had drifted within the round (0.52 code review, this file).
7
+ *
8
+ * A string is that path and everything under it (`/join` is `/join` and `/join/abc`, not
9
+ * `/joined`; `/` is only `/`); a RegExp is tested on the pathname (a `g` flag dropped, so
10
+ * `lastIndex` never carries over between calls).
11
+ *
12
+ * Case-INSENSITIVE, as React Router matches a route unless it sets `caseSensitive` (0.52
13
+ * code review, feedback/feedback-page-link.ts): the FLW-7 guard compared case-sensitively,
14
+ * and `/Join?token=` went out as a link the router still rendered as `/join`. A RegExp
15
+ * gets the `i` flag for the same reason. Matching more than a case-sensitive route would
16
+ * only turns a link into text, or leaves a page unrecorded as a resume target: both
17
+ * callers' safe side.
18
+ *
19
+ * @internal Package-private — not re-exported from the barrel.
20
+ */
21
+ export function matchesPathPattern(pathname: string, pattern: string | RegExp): boolean {
22
+ if (typeof pattern !== "string") {
23
+ return new RegExp(pattern.source, `${pattern.flags.replace(/[gi]/g, "")}i`).test(pathname);
24
+ }
25
+ const path = pathname.toLowerCase();
26
+ if (pattern === "/") return path === "/";
27
+ const base = (pattern.endsWith("/") ? pattern.slice(0, -1) : pattern).toLowerCase();
28
+ return path === base || path.startsWith(`${base}/`);
29
+ }
30
+
31
+ /**
32
+ * The pathname of `href` (a path with its query and hash) as React Router matches it:
33
+ * each segment percent-decoded, an encoded `/` kept as `%2F` so it never splits a segment
34
+ * (react-router 8, `decodePath` in `matchRoutesImpl`). The 0.52 code review
35
+ * (feedback/feedback-page-link.ts) found `/j%6Fin?token=` passing the FLW-7 guard as a
36
+ * link that the router decoded to `/join`.
37
+ *
38
+ * A malformed escape leaves its own segment as written, where the router gives up on the
39
+ * whole path: the rest still decodes, so the answer is never less than the router's.
40
+ *
41
+ * @internal Package-private — not re-exported from the barrel.
42
+ */
43
+ export function routerPathname(href: string): string {
44
+ return href
45
+ .replace(/[?#].*$/, "")
46
+ .split("/")
47
+ .map((segment) => {
48
+ try {
49
+ return decodeURIComponent(segment).replace(/\//g, "%2F");
50
+ } catch {
51
+ return segment;
52
+ }
53
+ })
54
+ .join("/");
55
+ }
@@ -13,7 +13,8 @@
13
13
  * screen only delays its `onClose` — every test suite, the kit's and the apps', reads a
14
14
  * closed overlay as gone at once.
15
15
  *
16
- * Package-private — not re-exported from the barrel.
16
+ * Public since 0.52 (DUP-8, refactor audit 2026-10-30): Kurvenschmiede's chain animation
17
+ * carried a copy of its own.
17
18
  */
18
19
  export function prefersReducedMotion(whereUnknown = false): boolean {
19
20
  if (typeof window === "undefined" || typeof window.matchMedia !== "function") return whereUnknown;
package/src/lib/round.ts CHANGED
@@ -25,14 +25,19 @@ function shiftDecimal(value: number, by: number): number {
25
25
  * `n` rounded to `digits` decimals, half away from zero, in decimal arithmetic:
26
26
  * 1.005 → 1.01, -2.5 at 0 → -3. Never NaN for a finite `n`, and never -0 — `formatMoney`
27
27
  * prints -0 as "-0,00 €", so a negative that rounds away comes back as 0. A non-finite
28
- * `n` comes back unchanged.
28
+ * `n` comes back unchanged. `digits` counts in whole decimals, as `toFixed` takes it: 2.5
29
+ * is 2, and NaN is 0.
29
30
  */
30
31
  export function roundHalfAway(n: number, digits: number): number {
31
32
  if (!Number.isFinite(n)) return n;
33
+ // "1.2345e2.5" is NaN, and `|| 0` below made it 0: MeasuredGrid's `digits`, which went to
34
+ // `toFixed` before DUP-14 brought it here, wrote every computed cell of a table with a
35
+ // fractional one as "0" (0.52 code review, deep, charts, finding 5).
36
+ const places = Math.trunc(digits) || 0;
32
37
  const abs = Math.abs(n);
33
38
  // Every double from 2^53 up is a whole number already, and scaling one near
34
39
  // Number.MAX_VALUE up would overflow to Infinity.
35
- const magnitude = abs >= 2 ** 53 ? abs : shiftDecimal(Math.round(shiftDecimal(abs, digits)), -digits);
40
+ const magnitude = abs >= 2 ** 53 ? abs : shiftDecimal(Math.round(shiftDecimal(abs, places)), -places);
36
41
  // The sign goes back on after rounding, so `|| 0` must see the signed result.
37
42
  return (n < 0 ? -magnitude : magnitude) || 0;
38
43
  }
@@ -16,8 +16,13 @@
16
16
  *
17
17
  * Since 0.34 (DUP-11, refactor audit 2026-10) it also removes, and guards
18
18
  * `sessionStorage` the same way: `landing/routing`, `billing/checkout-processing` and
19
- * the demo banner each wrote that guard again by hand. `removeStored` and the session
20
- * pair are package-private — the barrel exports `readStored` / `writeStored` only.
19
+ * the demo banner each wrote that guard again by hand.
20
+ *
21
+ * Since 0.52 all of it but {@link storedKeys} is public, with a JSON pair beside it
22
+ * (DUP-8, refactor audit 2026-10-30). Kept package-private, the guard was written again
23
+ * four times in the kit's showcase and in 16 files across the three apps, some of them
24
+ * without it: a token read that throws where storage is blocked takes every request
25
+ * down with it. (The kit's own DevMenu, 0.51, wrote the session pair a second time too.)
21
26
  */
22
27
 
23
28
  /** `access` on the storage area, or `fallback` where there is none (SSR) or it throws. */
@@ -37,9 +42,13 @@ export function readStored(key: string): string | null {
37
42
  }
38
43
 
39
44
  /** Store `value` under `key`, silently doing nothing when storage is unavailable
40
- * or full. A preference that cannot be persisted is not worth an exception. */
41
- export function writeStored(key: string, value: string): void {
42
- guarded("localStorage", undefined, (storage) => storage.setItem(key, value));
45
+ * or full. A preference that cannot be persisted is not worth an exception. `null`
46
+ * removes the key, as {@link writeSessionStored}'s does (0.52; the two pairs wrote
47
+ * differently, and `null` past the types stored the string "null" — 0.52 code review). */
48
+ export function writeStored(key: string, value: string | null): void {
49
+ guarded("localStorage", undefined, (storage) =>
50
+ value === null ? storage.removeItem(key) : storage.setItem(key, value),
51
+ );
43
52
  }
44
53
 
45
54
  /** Forget `key`, silently doing nothing when storage is blocked — there is nothing to
@@ -60,10 +69,43 @@ export function writeSessionStored(key: string, value: string | null): void {
60
69
  );
61
70
  }
62
71
 
72
+ /**
73
+ * The JSON stored under `key`, when it parses and `isShape` accepts it; else `null`. Absent,
74
+ * blocked, malformed and another shape all read as "never set" — the caller's default —
75
+ * which is what the six hand-written `JSON.parse(readStored(key))` reads in the kit each
76
+ * answered in their own try/catch (DUP-8). `isShape` gets the parsed value as `unknown`:
77
+ * the shape check is the one part that is the caller's. A shape check that throws reads
78
+ * as another shape: null.
79
+ */
80
+ export function readStoredJson<T>(key: string, isShape: (value: unknown) => value is T): T | null {
81
+ const text = readStored(key);
82
+ if (text === null) return null;
83
+ try {
84
+ const value: unknown = JSON.parse(text);
85
+ return isShape(value) ? value : null;
86
+ } catch {
87
+ // not JSON, or a shape check that read past what is there
88
+ return null;
89
+ }
90
+ }
91
+
92
+ /** Store `value` as JSON under `key`, silently doing nothing when storage is unavailable
93
+ * or full, or the value has no JSON form (a cycle, a BigInt) — {@link writeStored}'s rule. */
94
+ export function writeStoredJson(key: string, value: unknown): void {
95
+ let text: string | undefined;
96
+ try {
97
+ text = JSON.stringify(value);
98
+ } catch {
99
+ return;
100
+ }
101
+ // `undefined` (and a function) has no JSON form either: stringify answers undefined.
102
+ if (text !== undefined) writeStored(key, text);
103
+ }
104
+
63
105
  /**
64
106
  * Every key in `area` that starts with `prefix`, or `[]` where storage refuses — for the
65
107
  * reset of this device's settings, which removes a table's views by their prefix
66
- * (docs/settings-harmonization.md §11.4). Package-private, like {@link removeStored}.
108
+ * (docs/settings-harmonization.md §11.4). Package-private: the reset's alone.
67
109
  */
68
110
  export function storedKeys(prefix: string, area: "local" | "session" = "local"): string[] {
69
111
  return guarded(area === "session" ? "sessionStorage" : "localStorage", [], (storage) => {
@@ -36,7 +36,7 @@
36
36
  */
37
37
 
38
38
  import type { AxiosLikeInstance, ServerWakeAxiosConfig } from "./server-wake";
39
- import { httpStatusOf } from "../auth/auth-errors";
39
+ import { httpStatusOf } from "./http-status";
40
40
 
41
41
  /** What the app's `refresh` resolves: the new access token, plus whatever its `store`
42
42
  * needs (its refresh token, the user). */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * How a chart says a share of its whole — PieChart's slices and centre, Treemap's tiles
3
+ * and groups. Internal, not exported from the barrel.
4
+ *
5
+ * Each chart held a copy of the same two `Intl` formats and the same switch between them
6
+ * (0.52 code review of treemap.tsx, finding 9); one copy is the rule both say.
7
+ */
8
+
9
+ /**
10
+ * A formatter for shares (0–1) in `locale`, as a percent: one decimal under 10 %, where
11
+ * rounding to whole percents starts to lie — a slice of 0.4 % said as "0%" says it is not
12
+ * there — and whole percents above. Make it once per locale (it builds two `Intl` formats).
13
+ */
14
+ export function shareFormat(locale: string | undefined): (share: number) => string {
15
+ const whole = new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 0 });
16
+ const fine = new Intl.NumberFormat(locale, { style: "percent", maximumFractionDigits: 1 });
17
+ return (share) => (share < 0.1 ? fine : whole).format(share);
18
+ }