@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
@@ -42,8 +42,9 @@ import { ColumnSettingsPanel } from "./data-table-column-settings";
42
42
  import { useLargeText } from "../hooks/use-large-text";
43
43
  import { cn } from "../lib/cn";
44
44
  import { isFilterActive, resolveFilter, rowMatches } from "./data-table-filters";
45
+ import { matchesSearch, searchWords } from "../lib/text-match";
45
46
  import type { ColumnFilter, FilterValue } from "./data-table-filters";
46
- import { nextSorts } from "./data-table-sort";
47
+ import { compareSortValues, nextSorts } from "./data-table-sort";
47
48
  import type { SortCycle, SortDir, SortState } from "./data-table-sort";
48
49
  import { Pagination } from "./data-table-pagination";
49
50
  import type { SwipeAction } from "./swipeable-row";
@@ -61,6 +62,7 @@ import {
61
62
  } from "../i18n/kit-labels";
62
63
  import { useEdgeFade } from "../lib/strip-fade";
63
64
  import { dirOf, type Direction } from "../lib/direction";
65
+ import { focusContainer, focusFirst, focusIsLost, tabStops, whenFocusLeftForNothing } from "../lib/focus-left";
64
66
 
65
67
  // ---------- Types ----------
66
68
 
@@ -71,7 +73,8 @@ export interface DataTableColumn<T> {
71
73
  /** What a client sort orders the column by. Strings collate in the table's `locale`
72
74
  * (digits as numbers, case and accents left out; 0.51.7, audit 2026-10-30 DSP-3),
73
75
  * numbers compare as numbers and sort before strings, `null` and `undefined` sort
74
- * last in either direction. */
76
+ * last in either direction. The order is `compareSortValues`, exported for an app's
77
+ * own list beside the table (0.52). */
75
78
  sortBy?: (row: T) => string | number | null | undefined;
76
79
  filter?: ColumnFilter<T>;
77
80
  filterBy?: (row: T) => string;
@@ -266,8 +269,11 @@ export interface ServerPagination {
266
269
  }
267
270
 
268
271
  export interface DataTableProps<T> {
269
- rows: T[];
270
- columns: DataTableColumn<T>[];
272
+ /** `readonly` since 0.52 (refactor audit 2026-10-30, API-7): the table never changes the
273
+ * arrays it is handed, so a caller's `as const` list or a query's frozen result needs no
274
+ * copy. Only widens: a mutable array is still one. The same for `columns` and `sorts`. */
275
+ rows: readonly T[];
276
+ columns: readonly DataTableColumn<T>[];
271
277
  rowKey: (row: T) => string | number;
272
278
  defaultPageSize?: number;
273
279
  onRowClick?: (row: T) => void;
@@ -289,6 +295,11 @@ export interface DataTableProps<T> {
289
295
  * not wrap table cells, and the absolutely-positioned row overlay that is the
290
296
  * usual workaround would sit on top of the per-cell controls these rows carry
291
297
  * (status toggles, the selection checkbox, nested links) and swallow them.
298
+ *
299
+ * With `onRowClick` a plain click opens the row through it, in place. Without one
300
+ * (0.52, refactor audit 2026-10-30, DSP-23 b) the click follows the link — through the
301
+ * provider's `linkComponent` for an in-app path, as every kit link — and the phone
302
+ * card is that link too; before, the click was cancelled and opened nothing.
292
303
  */
293
304
  rowHref?: (row: T) => string | undefined;
294
305
  expandedRow?: (row: T) => ReactNode | null;
@@ -442,7 +453,7 @@ export interface DataTableProps<T> {
442
453
  */
443
454
  filters?: FilterState;
444
455
  onFiltersChange?: (next: FilterState) => void;
445
- sorts?: SortState[];
456
+ sorts?: readonly SortState[];
446
457
  onSortsChange?: (next: SortState[]) => void;
447
458
  /**
448
459
  * Controlled page size, as `sorts` above: the table pages by it and hands its pager's
@@ -931,6 +942,28 @@ export interface DataTableDesktopSwipeActions<T> {
931
942
  */
932
943
  const FOOTER_CELL = "sticky bottom-0 z-10 bg-surface-2 shadow-[inset_0_2px_0_var(--border-strong)]";
933
944
 
945
+ /** Where focus that leaves a row is still the row's (DSP-4): an overlay — a confirm, a
946
+ * menu, a sheet — hands it back or closes with the row it was about. The kit marks its
947
+ * own (`data-kit-layer`); an app's dialog says what it is. */
948
+ const OVERLAY_SELECTOR = '[data-kit-layer],[role="dialog"],[role="alertdialog"],[aria-modal="true"],[role="menu"]';
949
+
950
+ /** `rows`' places by `rowKey`, the first row of a key winning as a `findIndex`'s did; the
951
+ * map built at the first lookup (0.52 code review, data-table.tsx). */
952
+ function keyIndex<T>(rows: readonly T[], rowKey: (row: T) => string | number): (key: string | number) => number {
953
+ let at: Map<string | number, number> | null = null;
954
+ return (key) => {
955
+ if (!at) {
956
+ at = new Map();
957
+ for (let i = rows.length - 1; i >= 0; i--) at.set(rowKey(rows[i]), i);
958
+ }
959
+ return at.get(key) ?? -1;
960
+ };
961
+ }
962
+
963
+ /** How long a filter's "n of m rows" waits for the typing to pause (DSP-23 d): longer than
964
+ * the gap between two keystrokes, short enough to answer the word just typed. */
965
+ const FILTER_ANNOUNCE_DELAY_MS = 500;
966
+
934
967
  // ---------- Phone sort control (keksdose K10) ----------
935
968
 
936
969
  /**
@@ -962,38 +995,6 @@ export const DEFAULT_DATA_TABLE_SORT_LABELS: DataTableSortLabels = {
962
995
 
963
996
  const flipDir = (d: SortDir): SortDir => (d === "asc" ? "desc" : "asc");
964
997
 
965
- /**
966
- * The client sort's and the derived select options' string order (audit 2026-10-30
967
- * DSP-3): the table locale's collation, digits as numbers ("Item 2" before "Item 10"),
968
- * case and accents left out (two values that differ only by them go to the next sort
969
- * key, then keep the order given). `<` and `Array.sort()` compared UTF-16 code units —
970
- * every lower-case initial after every capital, Ä/Ö/Ü after Z — on the payees, names,
971
- * insurers and titles all three apps sort. Built once per table locale, not per
972
- * comparison: `localeCompare` with a locale sets one up on every call, n·log n times a
973
- * sort. An unknown tag (a typo in a provider's `locale`) throws a RangeError, and a
974
- * sort is no reason for a table to stop rendering: the runtime's own collation then,
975
- * as CountrySelect falls back.
976
- */
977
- function sortCollator(locale: string | undefined): Intl.Collator {
978
- const options: Intl.CollatorOptions = { numeric: true, sensitivity: "base" };
979
- try {
980
- return new Intl.Collator(locale, options);
981
- } catch {
982
- return new Intl.Collator(undefined, options);
983
- }
984
- }
985
-
986
- /** Two present `sortBy` values, ascending. Strings collate; a number sorts before a
987
- * string (`<` called that pair equal, so they stayed wherever they were given); any
988
- * other pair — numbers, and a `Date` an untyped caller returns — keeps `<` / `>`. */
989
- function compareSortValues(a: string | number, b: string | number, collator: Intl.Collator): number {
990
- const aText = typeof a === "string";
991
- const bText = typeof b === "string";
992
- if (aText && bText) return collator.compare(a, b);
993
- if (aText !== bText) return aText ? 1 : -1;
994
- return a < b ? -1 : a > b ? 1 : 0;
995
- }
996
-
997
998
  // `virtualize`'s constants (0.48.2): the band levels a group's `<tbody>` opens with.
998
999
  const NO_BANDS: readonly (1 | 2)[] = [];
999
1000
  const BAND_1: readonly (1 | 2)[] = [1];
@@ -1009,6 +1010,21 @@ import { DEFAULT_PERSIST_PREFIX, useTableState, type TableUrlSync } from "./use-
1009
1010
  import { useMobileReveal } from "./use-mobile-reveal";
1010
1011
  import { GROW_CELL_CLASS, GROW_PEER_CELL_CLASS, useColumnFit } from "./data-table-fit";
1011
1012
 
1013
+ /**
1014
+ * rowMatches for one column's filter state, the text filter's query folded once (0.52
1015
+ * code review, data-table-filters.ts): folded again for every row it tested, a 5000-row
1016
+ * table folded the same query 5000 times per keystroke. Module-private: data-table-filters
1017
+ * is re-exported whole by @eifi1/ui-kit/data-table, and this is no public API.
1018
+ */
1019
+ function rowMatcher<T>(col: DataTableColumn<T>, state: FilterValue, locale?: string): (row: T) => boolean {
1020
+ const filter = resolveFilter(col);
1021
+ if (filter?.type === "text" && state.type === "text" && state.q.trim()) {
1022
+ const words = searchWords(state.q, locale);
1023
+ return (row) => matchesSearch(words, [filter.getValue(row)], locale);
1024
+ }
1025
+ return (row) => rowMatches(col, row, state, locale);
1026
+ }
1027
+
1012
1028
  export function DataTable<T>({
1013
1029
  rows,
1014
1030
  columns: columnsProp,
@@ -1093,7 +1109,6 @@ export function DataTable<T>({
1093
1109
  const storageKeyPrefix = storageKeyPrefixProp ?? providerStorageKeyPrefix ?? DEFAULT_PERSIST_PREFIX;
1094
1110
  const labels = useMemo(() => resolveDataTableLabels(labelOverrides, labelsProp), [labelOverrides, labelsProp]);
1095
1111
  const locale = useKitLocale(localeProp);
1096
- const collator = useMemo(() => sortCollator(locale), [locale]);
1097
1112
 
1098
1113
  // ---- Row actions ---- (data-table-row-actions.tsx)
1099
1114
  // The actions column joins the caller's columns; on a phone the same actions are the
@@ -1150,13 +1165,15 @@ export function DataTable<T>({
1150
1165
  // (a drag or "Auto-size columns" before it grew, KS N2) is ignored, and it has no
1151
1166
  // handle, so it can never pin it again. Ignored rather than deleted from storage, as
1152
1167
  // the widths above; the next resize or auto-size writes a blob without it.
1168
+ // Keyed on the `grow` column's key, not the columns array: an inline `columns={…}`
1169
+ // gave every render new widths, and the fit measured the table for it (DSP-19).
1170
+ const growKey = columns.find((c) => c.grow)?.key;
1153
1171
  const widths = useMemo(() => {
1154
1172
  if (!resizable) return {};
1155
- const grow = columns.find((c) => c.grow)?.key;
1156
- if (!grow || !(grow in storedWidths)) return storedWidths;
1157
- const { [grow]: _ignored, ...rest } = storedWidths;
1173
+ if (!growKey || !(growKey in storedWidths)) return storedWidths;
1174
+ const { [growKey]: _ignored, ...rest } = storedWidths;
1158
1175
  return rest;
1159
- }, [resizable, storedWidths, columns]);
1176
+ }, [resizable, storedWidths, growKey]);
1160
1177
  const hiddenCols = useMemo(
1161
1178
  () => (columnSettings ? storedHiddenCols : new Set<string>()),
1162
1179
  [columnSettings, storedHiddenCols],
@@ -1201,13 +1218,13 @@ export function DataTable<T>({
1201
1218
  if (v && !seen.has(v)) seen.add(v);
1202
1219
  }
1203
1220
  map[col.key] = Array.from(seen)
1204
- .sort(collator.compare)
1221
+ .sort((a, b) => compareSortValues(a, b, locale))
1205
1222
  .map((v) => ({ value: v, label: v }));
1206
1223
  }
1207
1224
  return map;
1208
1225
  // `columns` by signature — see the note above the signatures.
1209
1226
  // eslint-disable-next-line react-hooks/exhaustive-deps
1210
- }, [columnSig, selectOptionsSig, rows, collator]);
1227
+ }, [columnSig, selectOptionsSig, rows, locale]);
1211
1228
 
1212
1229
  const filtered = useMemo(() => {
1213
1230
  if (serverRows) return rows;
@@ -1215,7 +1232,8 @@ export function DataTable<T>({
1215
1232
  for (const col of columns) {
1216
1233
  const state = filters[col.key];
1217
1234
  if (!state || !isFilterActive(state)) continue;
1218
- result = result.filter((row) => rowMatches(col, row, state, locale));
1235
+ // One matcher per filter, its query folded once (0.52 code review, data-table-filters.ts).
1236
+ result = result.filter(rowMatcher(col, state, locale));
1219
1237
  }
1220
1238
  return result;
1221
1239
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -1240,14 +1258,16 @@ export function DataTable<T>({
1240
1258
  if (av == null && bv == null) continue;
1241
1259
  if (av == null) return 1;
1242
1260
  if (bv == null) return -1;
1243
- const order = compareSortValues(av, bv, collator);
1261
+ // The order `compareSortValues` exports (0.52): one rule, here and in an app's
1262
+ // own list beside the table. Nulls are last above, in either direction.
1263
+ const order = compareSortValues(av, bv, locale);
1244
1264
  if (order !== 0) return dir === "asc" ? order : -order;
1245
1265
  }
1246
1266
  return 0;
1247
1267
  });
1248
1268
  return copy;
1249
1269
  // eslint-disable-next-line react-hooks/exhaustive-deps
1250
- }, [filtered, sorts, columnSig, serverRows, collator]);
1270
+ }, [filtered, sorts, columnSig, serverRows, locale]);
1251
1271
 
1252
1272
  // `paginated={false}` is expressed as "page size = everything" rather than as a
1253
1273
  // second code path, so slicing, the page clamp and the range summary all keep
@@ -1266,10 +1286,16 @@ export function DataTable<T>({
1266
1286
  const safePage = isServer
1267
1287
  ? Math.min(Math.max(0, serverPagination!.page), totalPages - 1)
1268
1288
  : Math.min(page, totalPages - 1);
1269
- const slice =
1270
- isServer || unpaged || pageSize === Infinity
1271
- ? sorted
1272
- : sorted.slice(safePage * effectivePageSize, safePage * effectivePageSize + effectivePageSize);
1289
+ // Memoised (refactor audit 2026-10-30, DSP-19): the fit, the rows' window and the
1290
+ // range selection compare the page's rows by identity, and a fresh `slice(…)` on every
1291
+ // render of a paginated table — the default — had the fit lay the whole table out on a
1292
+ // ticked box, a keystroke, any parent render.
1293
+ const wholeList = isServer || unpaged || pageSize === Infinity;
1294
+ const slice = useMemo(
1295
+ () =>
1296
+ wholeList ? sorted : sorted.slice(safePage * effectivePageSize, safePage * effectivePageSize + effectivePageSize),
1297
+ [wholeList, sorted, safePage, effectivePageSize],
1298
+ );
1273
1299
 
1274
1300
  const toggleSort = (key: string, shiftKey: boolean) => {
1275
1301
  const col = columns.find((c) => c.key === key);
@@ -1362,6 +1388,16 @@ export function DataTable<T>({
1362
1388
  // Seeded with the mount value: a live region that speaks on first paint talks over
1363
1389
  // the page the user has just opened, and every table on that page does it at once.
1364
1390
  const announcedFilterKey = useRef(filterKey);
1391
+ // Spoken once the typing pauses (refactor audit 2026-10-30, DSP-23 d): every keystroke
1392
+ // in a text filter is a new filter, and "12 of 137 rows" on each one — through the live
1393
+ // region's 50 ms reset — talked over the very letters being typed. The count is read
1394
+ // when the sentence goes out, so it is the rows the pause left on screen.
1395
+ const filterSpeech = useRef({ shown: filtered.length, total: rows.length, labels });
1396
+ useLayoutEffect(() => {
1397
+ filterSpeech.current = { shown: filtered.length, total: rows.length, labels };
1398
+ });
1399
+ const filterTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
1400
+ useEffect(() => () => clearTimeout(filterTimer.current), []);
1365
1401
  useEffect(() => {
1366
1402
  if (announcedFilterKey.current === filterKey) return;
1367
1403
  announcedFilterKey.current = filterKey;
@@ -1370,8 +1406,12 @@ export function DataTable<T>({
1370
1406
  // filtered total — so the "n of m" sentence has no second number to give here.
1371
1407
  // Saying nothing is better than announcing the page size as a result count.
1372
1408
  if (serverRows) return;
1373
- announce(labels.filterResults(filtered.length, rows.length));
1374
- }, [filterKey, filtered.length, rows.length, serverRows, announce, labels]);
1409
+ clearTimeout(filterTimer.current);
1410
+ filterTimer.current = setTimeout(() => {
1411
+ const { shown, total, labels: words } = filterSpeech.current;
1412
+ announce(words.filterResults(shown, total));
1413
+ }, FILTER_ANNOUNCE_DELAY_MS);
1414
+ }, [filterKey, serverRows, announce]);
1375
1415
 
1376
1416
  // 1-based, because that is what the page strip shows and what a person says.
1377
1417
  const goToPage = (p: number) => {
@@ -1390,8 +1430,11 @@ export function DataTable<T>({
1390
1430
  headRefs,
1391
1431
  scroller: () => desktopScroller.current,
1392
1432
  enabled: !usePhoneLayout(),
1393
- // Measured only when one of these changed (0.48.2): not on a ticked box.
1394
- content: [slice, columns, hiddenCols, widths, density, large],
1433
+ // Measured only when one of these changed (0.48.2): not on a ticked box. The columns
1434
+ // by their keys (DSP-19): an inline `columns={…}` is a new array every render. The
1435
+ // locale (0.52 code review, data-table.tsx): the cells' dates, numbers and the kit's
1436
+ // words follow it under the same rows and keys; the fit adds the headers' drawn text.
1437
+ content: [slice, columnSig, hiddenCols, widths, density, large, locale],
1395
1438
  });
1396
1439
  const visibleColumns = useMemo(
1397
1440
  () => columns.filter((c) => !hiddenCols.has(c.key) && !autoHidden.has(c.key)),
@@ -1446,7 +1489,10 @@ export function DataTable<T>({
1446
1489
  const footerLabelCol = showFooter
1447
1490
  ? (visibleColumns.find((c) => !c.footer && !isTableOwnColumn(c.key)) ?? visibleColumns[0])
1448
1491
  : undefined;
1449
- const footerValue = (col: DataTableColumn<T>) => col.footer?.(sorted, slice);
1492
+ // `footer` keeps its `T[]` parameters: narrowed to `readonly`, a caller's footer typed
1493
+ // `(rows: Row[]) => …` would no longer fit (API-7 only widens). The table hands over the
1494
+ // arrays it holds, as before, and changes neither.
1495
+ const footerValue = (col: DataTableColumn<T>) => col.footer?.(sorted as T[], slice as T[]);
1450
1496
  const footerHeadingId = useId();
1451
1497
  // Each open row's detail row, for its disclosure button's `aria-controls`.
1452
1498
  const detailIdBase = useId();
@@ -1588,8 +1634,9 @@ export function DataTable<T>({
1588
1634
  const [phoneLeadHeight, setPhoneLeadHeight] = useState(0);
1589
1635
 
1590
1636
  const { mobileSlice, canRevealMoreMobile, loadMoreRef } = useMobileReveal({
1591
- rows: sorted,
1592
- slice,
1637
+ // Read only there; its `T[]` widens to `readonly T[]` with its owner (API-7).
1638
+ rows: sorted as T[],
1639
+ slice: slice as T[],
1593
1640
  isServer,
1594
1641
  isMdUp,
1595
1642
  unpaged,
@@ -1639,12 +1686,18 @@ export function DataTable<T>({
1639
1686
  // the pager.
1640
1687
  const rowsFocusable = !!onRowClick;
1641
1688
  const [focusRowKey, setFocusRowKey] = useState<string | number | null>(null);
1689
+ // The rows' places by key (0.52 code review, data-table.tsx): the tab stop's check
1690
+ // below, the focus note and its recovery each walked the rows calling `rowKey` — on
1691
+ // every render, at each focus, in every commit while a row was noted: under
1692
+ // `virtualize`, 3000 rows, ~15000 calls per arrow step at the table's end. Built at the
1693
+ // first lookup, once per array, so a table nobody focuses builds none. Keyed on the
1694
+ // rows alone: `rowKey` is a pure function of its row, and a call site writes it inline.
1695
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1696
+ const sliceIndex = useMemo(() => keyIndex(slice, rowKey), [slice]);
1697
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1698
+ const mobileIndex = useMemo(() => keyIndex(mobileSlice, rowKey), [mobileSlice]);
1642
1699
  const tabRowKey =
1643
- focusRowKey !== null && slice.some((r) => rowKey(r) === focusRowKey)
1644
- ? focusRowKey
1645
- : slice.length
1646
- ? rowKey(slice[0])
1647
- : null;
1700
+ focusRowKey !== null && sliceIndex(focusRowKey) !== -1 ? focusRowKey : slice.length ? rowKey(slice[0]) : null;
1648
1701
 
1649
1702
  // What the memoised rows' handlers read at event time (0.48.2, K-052-2): the latest
1650
1703
  // props, refreshed every commit, so a row that bailed out of a render still toggles
@@ -1653,7 +1706,38 @@ export function DataTable<T>({
1653
1706
  // move over every row, rendered or not: the target is rendered, then focused.
1654
1707
  const virtualOn = !!virtualize && isMdUp && slice.length > 0;
1655
1708
  const focusRowRef = useRef<((index: number) => void) | undefined>(undefined);
1709
+ // ---- When the focused row leaves (refactor audit 2026-10-30, DSP-4) ----
1710
+ // The row holding the focus — its key, and where it stood — noted as the focus comes
1711
+ // into it (the rows report it, through React, so a menu or a dialog a row opened in a
1712
+ // portal is the row's too). A delete, a status change that filters the row out, an
1713
+ // approval: the row's DOM goes, the focus with it to <body>, and the commit that took it
1714
+ // puts the focus on the row now in its place (below).
1715
+ const heldRow = useRef<{ key: string | number; index: number } | null>(null);
1716
+ const noteRowFocus = (key: string | number) => {
1717
+ heldRow.current = { key, index: (isMdUp ? sliceIndex : mobileIndex)(key) };
1718
+ };
1719
+ // A gone row's note waiting for the overlay that holds the focus (below): watched as
1720
+ // a node until it leaves the page, since its close need not render the table.
1721
+ const overlayWatch = useRef<{ layer: Element; stop: () => void } | null>(null);
1722
+ const settleRef = useRef<() => void>(() => {});
1723
+ const watchOverlay = (layer: Element) => {
1724
+ if (overlayWatch.current?.layer === layer) return;
1725
+ overlayWatch.current?.stop();
1726
+ const observer = new MutationObserver(() => {
1727
+ if (layer.isConnected) return;
1728
+ stop();
1729
+ settleRef.current();
1730
+ });
1731
+ const stop = () => {
1732
+ observer.disconnect();
1733
+ if (overlayWatch.current?.layer === layer) overlayWatch.current = null;
1734
+ };
1735
+ observer.observe(layer.ownerDocument.body, { childList: true, subtree: true });
1736
+ overlayWatch.current = { layer, stop };
1737
+ };
1738
+ useEffect(() => () => overlayWatch.current?.stop(), []);
1656
1739
  const latestRowCtx: DataTableRowCtx<T> = {
1740
+ noteFocus: noteRowFocus,
1657
1741
  moveFocus: virtualOn
1658
1742
  ? (from, key) => {
1659
1743
  const target = rowNavTarget(from, key, slice.length);
@@ -1698,7 +1782,7 @@ export function DataTable<T>({
1698
1782
 
1699
1783
  // One mobile card (`MobileRow`, data-table-mobile.tsx). A function so the flat list
1700
1784
  // and the grouped list (below) share identical row markup.
1701
- const renderMobileRow = (row: T) => {
1785
+ const renderMobileRow = (row: T, index: number) => {
1702
1786
  const key = rowKey(row);
1703
1787
  const expanded = isExpanded?.(row) ?? false;
1704
1788
  return (
@@ -1706,6 +1790,7 @@ export function DataTable<T>({
1706
1790
  key={key}
1707
1791
  ctx={rowCtx}
1708
1792
  rowKey={key}
1793
+ rowIndex={index}
1709
1794
  row={row}
1710
1795
  expanded={expanded}
1711
1796
  expansion={expanded ? openedRowContent(row) : null}
@@ -1801,15 +1886,72 @@ export function DataTable<T>({
1801
1886
  focusRowRef.current = focusRowAt;
1802
1887
  }, [focusRowAt]);
1803
1888
 
1889
+ // The commit that took the focused row (DSP-4): if the focus fell with it — to <body>,
1890
+ // not into a dialog or a field the app chose — it goes to the row now at that index,
1891
+ // else the last row, else the table itself (`tabIndex=-1` while empty, named by
1892
+ // `labels.table`); the card on a phone. A focusable row (`onRowClick`) takes it itself,
1893
+ // any other its first control. The remaining count is said, as a filter's is: the
1894
+ // focus moved without the user moving it. translation-review's `keepFocusAt` is the
1895
+ // kit's earlier copy of the rule, for its groups.
1896
+ //
1897
+ // Every commit while a row is noted, a lookup by its key. A focus the user took
1898
+ // elsewhere drops the note (`onBlur` on the root, below), and since the 0.52 code review
1899
+ // so does one that went to nothing — a click on the page's background: kept, it was the
1900
+ // one case where a later removal (a refetch, a sync) pulled the focus back in.
1901
+ const settleFocus = () => {
1902
+ const held = heldRow.current;
1903
+ if (!held) return;
1904
+ const now = (isMdUp ? sliceIndex : mobileIndex)(held.key);
1905
+ if (now !== -1) {
1906
+ held.index = now;
1907
+ return;
1908
+ }
1909
+ const list = isMdUp ? tableRef.current : phoneListRef.current;
1910
+ // In an overlay — a confirm, an edit drawer — the focus is not lost yet: the overlay
1911
+ // hands it back to its opener as it closes, and the opener was in the row (0.52 code
1912
+ // review, data-table.tsx: an optimistic delete with its confirm still open dropped the
1913
+ // note, and the confirm's close dropped the focus to <body>). The note waits for the
1914
+ // overlay to leave the page, watched as a node: the table need not render when it does
1915
+ // (`useConfirm`'s provider, a Modal's exit). Not an overlay the table itself is in.
1916
+ const layer = focusIsLost() ? null : document.activeElement?.closest(OVERLAY_SELECTOR);
1917
+ if (layer && !(list && layer.contains(list))) {
1918
+ watchOverlay(layer);
1919
+ return;
1920
+ }
1921
+ heldRow.current = null;
1922
+ overlayWatch.current?.stop();
1923
+ if (!list || !focusIsLost()) return;
1924
+ const shown = isMdUp ? slice : mobileSlice;
1925
+ const at = shown.length === 0 ? -1 : Math.min(Math.max(held.index, 0), shown.length - 1);
1926
+ if (at === -1) list.focus();
1927
+ else if (isMdUp && virtualOn && rowsFocusable) focusRowRef.current?.(at);
1928
+ else {
1929
+ const row = list.querySelector<HTMLElement>(
1930
+ isMdUp ? `:scope > tbody > tr[data-row-index="${at}"]` : `:scope > li[data-row-index="${at}"]`,
1931
+ );
1932
+ // Each of the row's stops in turn, then the table (0.52 code review): `focus()` on
1933
+ // one that cannot hold it — `display: none`, in a closed <details>, a hidden input —
1934
+ // does nothing, and the focus stayed on <body>.
1935
+ const stops = !row ? [] : isMdUp && rowsFocusable ? [row] : tabStops(row, { includeSelf: false });
1936
+ if (!focusFirst(stops)) focusContainer(list);
1937
+ }
1938
+ announce(labels.filterResults(paginationTotal, isServer ? paginationTotal : rows.length));
1939
+ };
1940
+ useLayoutEffect(() => {
1941
+ settleRef.current = settleFocus;
1942
+ settleFocus();
1943
+ });
1944
+
1804
1945
  // `mobileGroupBy` may be a desktop `groupBy` level (0.48), its label and end slot too.
1805
1946
  const mobileLevel = typeof mobileGroupBy === "function" ? null : (mobileGroupBy ?? null);
1806
1947
  const mobileKey = typeof mobileGroupBy === "function" ? mobileGroupBy : mobileGroupBy?.key;
1807
- const mobileGroups: { key: string; rows: T[] }[] | null = mobileKey
1808
- ? mobileSlice.reduce<{ key: string; rows: T[] }[]>((acc, row) => {
1948
+ // `start`: the section's first card's place in the list (its `data-row-index`, DSP-4).
1949
+ const mobileGroups: { key: string; rows: T[]; start: number }[] | null = mobileKey
1950
+ ? mobileSlice.reduce<{ key: string; rows: T[]; start: number }[]>((acc, row, i) => {
1809
1951
  const key = mobileKey(row);
1810
1952
  const last = acc[acc.length - 1];
1811
1953
  if (last && last.key === key) last.rows.push(row);
1812
- else acc.push({ key, rows: [row] });
1954
+ else acc.push({ key, rows: [row], start: i });
1813
1955
  return acc;
1814
1956
  }, [])
1815
1957
  : null;
@@ -1833,6 +1975,24 @@ export function DataTable<T>({
1833
1975
  // `<main>` and every `FillHeight` between it and this table key off it with
1834
1976
  // `has-[[data-fill-height]]`, so a page asks for the fill by passing the prop.
1835
1977
  data-fill-height={fillHeight ? "" : undefined}
1978
+ // The focused row's note (DSP-4): cleared on the way down to whatever takes the
1979
+ // focus, and set again on the way up by the row that holds it. Focus leaving for
1980
+ // something real outside the table drops it — unless that is an overlay (a confirm,
1981
+ // a menu), which hands the focus back, or closes with the row it asked about.
1982
+ onFocusCapture={() => {
1983
+ heldRow.current = null;
1984
+ overlayWatch.current?.stop();
1985
+ }}
1986
+ onBlur={(e) => {
1987
+ const to = e.relatedTarget;
1988
+ if (to instanceof Element && !to.closest(OVERLAY_SELECTOR)) heldRow.current = null;
1989
+ // Focus that left for nothing — a click on the page (0.52 code review): no
1990
+ // element to say where, and a later removal of the row is no longer the user's.
1991
+ const held = heldRow.current;
1992
+ whenFocusLeftForNothing(e, () => {
1993
+ if (heldRow.current === held) heldRow.current = null;
1994
+ });
1995
+ }}
1836
1996
  className={cn(
1837
1997
  "group/table",
1838
1998
  frame ? "overflow-clip" : "min-w-0",
@@ -1924,6 +2084,10 @@ export function DataTable<T>({
1924
2084
  )}
1925
2085
  <ul
1926
2086
  ref={phoneListRef}
2087
+ // Named as the table is, and focusable while empty: where the focus goes when
2088
+ // the last card leaves (DSP-4).
2089
+ aria-label={labels.table}
2090
+ tabIndex={mobileSlice.length === 0 ? -1 : undefined}
1927
2091
  className={cn(
1928
2092
  "divide-y divide-subtle",
1929
2093
  // Under `fillHeight` the list takes the room its parent gives it, and in a
@@ -1964,7 +2128,7 @@ export function DataTable<T>({
1964
2128
  <span className="ms-auto tabular-nums">{mobileLevel.end(g.key, g.rows)}</span>
1965
2129
  )}
1966
2130
  </li>
1967
- {g.rows.map(renderMobileRow)}
2131
+ {g.rows.map((r, i) => renderMobileRow(r, g.start + i))}
1968
2132
  </Fragment>
1969
2133
  ))
1970
2134
  : mobileSlice.map(renderMobileRow)}
@@ -2047,6 +2211,11 @@ export function DataTable<T>({
2047
2211
  behind it shows, so the panel names the row it opened from. */}
2048
2212
  <FullBleedDialog
2049
2213
  open={dialogOpen}
2214
+ // The open row's editor is the row's focus too: a Delete in it takes the row,
2215
+ // and the dialog with it (DSP-4).
2216
+ onFocus={() => {
2217
+ if (mobileDialogRow) noteRowFocus(rowKey(mobileDialogRow));
2218
+ }}
2050
2219
  onClose={closeDialog}
2051
2220
  closeLabel={labels.close}
2052
2221
  header={mobileDialogRow ? mobilePrimaryCol?.cell(mobileDialogRow) : null}
@@ -2091,6 +2260,8 @@ export function DataTable<T>({
2091
2260
  className={cn("w-full", compact ? "text-xs" : "text-sm")}
2092
2261
  data-fit-grow={growDrawn ? "" : undefined}
2093
2262
  aria-label={labels.table}
2263
+ // Focusable while empty: where the focus goes when the last row leaves (DSP-4).
2264
+ tabIndex={slice.length === 0 ? -1 : undefined}
2094
2265
  aria-busy={isLoading || undefined}
2095
2266
  // `virtualize` (0.48.2): every row counted, rendered or not.
2096
2267
  aria-rowcount={rowWindow?.rowCount}