@eifi1/ui-kit 0.54.0 → 0.56.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 (847) hide show
  1. package/README.md +90 -76
  2. package/dist/admin/plan-change.js +2 -2
  3. package/dist/admin/plan-change.js.map +1 -1
  4. package/dist/admin/user-roster.d.ts +6 -0
  5. package/dist/admin/user-roster.js +4 -0
  6. package/dist/admin/user-roster.js.map +1 -1
  7. package/dist/billing/billing-labels.d.ts +26 -0
  8. package/dist/billing/billing-labels.js +9 -0
  9. package/dist/billing/billing-labels.js.map +1 -1
  10. package/dist/billing/subscription-standing.d.ts +61 -0
  11. package/dist/billing/subscription-standing.js +51 -0
  12. package/dist/billing/subscription-standing.js.map +1 -0
  13. package/dist/billing/usage-meter.d.ts +52 -0
  14. package/dist/billing/usage-meter.js +52 -0
  15. package/dist/billing/usage-meter.js.map +1 -0
  16. package/dist/chart.d.ts +1 -0
  17. package/dist/chart.js +4 -0
  18. package/dist/chart.js.map +1 -1
  19. package/dist/components/alert-banner.js +2 -3
  20. package/dist/components/alert-banner.js.map +1 -1
  21. package/dist/components/amount-input.js +3 -15
  22. package/dist/components/amount-input.js.map +1 -1
  23. package/dist/components/autocomplete.js +2 -2
  24. package/dist/components/autocomplete.js.map +1 -1
  25. package/dist/components/bulk-action-bar.js +2 -1
  26. package/dist/components/bulk-action-bar.js.map +1 -1
  27. package/dist/components/button.d.ts +35 -1
  28. package/dist/components/button.js +6 -9
  29. package/dist/components/button.js.map +1 -1
  30. package/dist/components/calendar-heatmap.js +2 -2
  31. package/dist/components/calendar-heatmap.js.map +1 -1
  32. package/dist/components/camera-button.d.ts +89 -0
  33. package/dist/components/camera-button.js +122 -0
  34. package/dist/components/camera-button.js.map +1 -0
  35. package/dist/components/camera-capture.d.ts +66 -0
  36. package/dist/components/camera-capture.js +190 -0
  37. package/dist/components/camera-capture.js.map +1 -0
  38. package/dist/components/camera-labels.d.ts +20 -0
  39. package/dist/components/camera-labels.js +15 -0
  40. package/dist/components/camera-labels.js.map +1 -0
  41. package/dist/components/chart-edge.d.ts +9 -0
  42. package/dist/components/chart-edge.js +14 -3
  43. package/dist/components/chart-edge.js.map +1 -1
  44. package/dist/components/chart-zoom-core.js +34 -29
  45. package/dist/components/chart-zoom-core.js.map +1 -1
  46. package/dist/components/checkbox-group.js +1 -1
  47. package/dist/components/checkbox-group.js.map +1 -1
  48. package/dist/components/combobox-core.js +3 -2
  49. package/dist/components/combobox-core.js.map +1 -1
  50. package/dist/components/combobox.js +2 -2
  51. package/dist/components/combobox.js.map +1 -1
  52. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  53. package/dist/components/consent-again-notice-labels.js +15 -0
  54. package/dist/components/consent-again-notice-labels.js.map +1 -0
  55. package/dist/components/consent-again-notice.d.ts +85 -0
  56. package/dist/components/consent-again-notice.js +94 -0
  57. package/dist/components/consent-again-notice.js.map +1 -0
  58. package/dist/components/country-select.js +2 -7
  59. package/dist/components/country-select.js.map +1 -1
  60. package/dist/components/currency-select.js +2 -2
  61. package/dist/components/currency-select.js.map +1 -1
  62. package/dist/components/danger-confirm.js +2 -2
  63. package/dist/components/danger-confirm.js.map +1 -1
  64. package/dist/components/data-table-column-settings.js +2 -2
  65. package/dist/components/data-table-column-settings.js.map +1 -1
  66. package/dist/components/data-table-fit.js.map +1 -1
  67. package/dist/components/data-table-virtual.js +13 -3
  68. package/dist/components/data-table-virtual.js.map +1 -1
  69. package/dist/components/date-field.js +2 -2
  70. package/dist/components/date-field.js.map +1 -1
  71. package/dist/components/date-picker.js +2 -2
  72. package/dist/components/date-picker.js.map +1 -1
  73. package/dist/components/date-range-picker.js +2 -6
  74. package/dist/components/date-range-picker.js.map +1 -1
  75. package/dist/components/dialog-frame.d.ts +3 -1
  76. package/dist/components/dialog-frame.js.map +1 -1
  77. package/dist/components/dropdown.js +7 -3
  78. package/dist/components/dropdown.js.map +1 -1
  79. package/dist/components/entity-combobox.js +2 -2
  80. package/dist/components/entity-combobox.js.map +1 -1
  81. package/dist/components/field-parts.d.ts +108 -6
  82. package/dist/components/field-parts.js +59 -6
  83. package/dist/components/field-parts.js.map +1 -1
  84. package/dist/components/field-suggestion-labels.d.ts +2 -0
  85. package/dist/components/field-suggestion-labels.js +10 -0
  86. package/dist/components/field-suggestion-labels.js.map +1 -0
  87. package/dist/components/field.js +1 -1
  88. package/dist/components/field.js.map +1 -1
  89. package/dist/components/file-button.d.ts +1 -1
  90. package/dist/components/file-button.js.map +1 -1
  91. package/dist/components/floating-panel.d.ts +7 -6
  92. package/dist/components/floating-panel.js +3 -17
  93. package/dist/components/floating-panel.js.map +1 -1
  94. package/dist/components/form-actions.d.ts +3 -2
  95. package/dist/components/form-actions.js.map +1 -1
  96. package/dist/components/hover-menu.js +3 -2
  97. package/dist/components/hover-menu.js.map +1 -1
  98. package/dist/components/icon-button.d.ts +23 -5
  99. package/dist/components/icon-button.js +7 -8
  100. package/dist/components/icon-button.js.map +1 -1
  101. package/dist/components/inline-confirm.js +2 -1
  102. package/dist/components/inline-confirm.js.map +1 -1
  103. package/dist/components/inline-entity-combobox.js +2 -2
  104. package/dist/components/inline-entity-combobox.js.map +1 -1
  105. package/dist/components/input.js +2 -2
  106. package/dist/components/input.js.map +1 -1
  107. package/dist/components/jump-calendar.js +3 -3
  108. package/dist/components/jump-calendar.js.map +1 -1
  109. package/dist/components/legal.d.ts +13 -1
  110. package/dist/components/legal.js +12 -0
  111. package/dist/components/legal.js.map +1 -1
  112. package/dist/components/legended-series-chart.d.ts +83 -0
  113. package/dist/components/legended-series-chart.js +77 -0
  114. package/dist/components/legended-series-chart.js.map +1 -0
  115. package/dist/components/lightbox.js +2 -1
  116. package/dist/components/lightbox.js.map +1 -1
  117. package/dist/components/mini-calendar.js +13 -5
  118. package/dist/components/mini-calendar.js.map +1 -1
  119. package/dist/components/modal.d.ts +23 -2
  120. package/dist/components/modal.js +24 -6
  121. package/dist/components/modal.js.map +1 -1
  122. package/dist/components/month-picker.d.ts +1 -1
  123. package/dist/components/month-picker.js +6 -6
  124. package/dist/components/month-picker.js.map +1 -1
  125. package/dist/components/multi-entity-combobox.js +2 -2
  126. package/dist/components/multi-entity-combobox.js.map +1 -1
  127. package/dist/components/multi-select.js +2 -6
  128. package/dist/components/multi-select.js.map +1 -1
  129. package/dist/components/number-input.js +2 -2
  130. package/dist/components/number-input.js.map +1 -1
  131. package/dist/components/page-header-layout.d.ts +18 -0
  132. package/dist/components/page-header-layout.js +23 -0
  133. package/dist/components/page-header-layout.js.map +1 -0
  134. package/dist/components/page-header-skeleton.d.ts +41 -0
  135. package/dist/components/page-header-skeleton.js +52 -0
  136. package/dist/components/page-header-skeleton.js.map +1 -0
  137. package/dist/components/page-header.js +4 -17
  138. package/dist/components/page-header.js.map +1 -1
  139. package/dist/components/popover.js +7 -2
  140. package/dist/components/popover.js.map +1 -1
  141. package/dist/components/release-notes-labels.d.ts +16 -0
  142. package/dist/components/release-notes-labels.js +18 -0
  143. package/dist/components/release-notes-labels.js.map +1 -0
  144. package/dist/components/release-notes.d.ts +65 -0
  145. package/dist/components/release-notes.js +65 -0
  146. package/dist/components/release-notes.js.map +1 -0
  147. package/dist/components/select.js +2 -2
  148. package/dist/components/select.js.map +1 -1
  149. package/dist/components/series-chart-labels.d.ts +9 -0
  150. package/dist/components/series-chart-labels.js +3 -1
  151. package/dist/components/series-chart-labels.js.map +1 -1
  152. package/dist/components/series-chart.d.ts +5 -0
  153. package/dist/components/series-chart.js +89 -36
  154. package/dist/components/series-chart.js.map +1 -1
  155. package/dist/components/toggle-group.js +3 -2
  156. package/dist/components/toggle-group.js.map +1 -1
  157. package/dist/components/treemap-layout.js.map +1 -1
  158. package/dist/components/treemap.d.ts +17 -15
  159. package/dist/components/treemap.js +167 -85
  160. package/dist/components/treemap.js.map +1 -1
  161. package/dist/eslint/index.js +1 -1
  162. package/dist/eslint/role-utilities.d.ts +2 -2
  163. package/dist/eslint/role-utilities.js.map +1 -1
  164. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  165. package/dist/feedback/feedback-inbox-page.js +207 -0
  166. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  167. package/dist/feedback/feedback-vocabulary.js +2 -2
  168. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  169. package/dist/feedback.d.ts +1 -0
  170. package/dist/feedback.js +6 -0
  171. package/dist/feedback.js.map +1 -1
  172. package/dist/hooks/use-debounce.d.ts +32 -5
  173. package/dist/hooks/use-debounce.js +6 -5
  174. package/dist/hooks/use-debounce.js.map +1 -1
  175. package/dist/hooks/use-file-picker.d.ts +3 -1
  176. package/dist/hooks/use-file-picker.js.map +1 -1
  177. package/dist/hooks/use-focus-trap.d.ts +19 -3
  178. package/dist/hooks/use-focus-trap.js +15 -5
  179. package/dist/hooks/use-focus-trap.js.map +1 -1
  180. package/dist/hooks/use-has-camera.d.ts +36 -0
  181. package/dist/hooks/use-has-camera.js +53 -0
  182. package/dist/hooks/use-has-camera.js.map +1 -0
  183. package/dist/hooks/use-hotkey.js +1 -10
  184. package/dist/hooks/use-hotkey.js.map +1 -1
  185. package/dist/hooks/use-overlay-history.d.ts +6 -1
  186. package/dist/hooks/use-overlay-history.js +10 -2
  187. package/dist/hooks/use-overlay-history.js.map +1 -1
  188. package/dist/i18n/defaults.js +11 -1
  189. package/dist/i18n/defaults.js.map +1 -1
  190. package/dist/i18n/german.js +75 -3
  191. package/dist/i18n/german.js.map +1 -1
  192. package/dist/i18n/kit-labels.d.ts +20 -0
  193. package/dist/i18n/kit-labels.js.map +1 -1
  194. package/dist/i18n/locales/en.js +92 -4
  195. package/dist/i18n/locales/en.js.map +3 -3
  196. package/dist/i18n/locales/es.js +71 -3
  197. package/dist/i18n/locales/es.js.map +1 -1
  198. package/dist/i18n/locales/fr.js +72 -2
  199. package/dist/i18n/locales/fr.js.map +1 -1
  200. package/dist/i18n/locales/hu.js +74 -3
  201. package/dist/i18n/locales/hu.js.map +1 -1
  202. package/dist/i18n/locales/it.js +74 -3
  203. package/dist/i18n/locales/it.js.map +1 -1
  204. package/dist/i18n/locales/zh.js +71 -3
  205. package/dist/i18n/locales/zh.js.map +1 -1
  206. package/dist/i18n/review.js +22 -1
  207. package/dist/i18n/review.js.map +1 -1
  208. package/dist/index.d.ts +24 -5
  209. package/dist/index.js +35 -1
  210. package/dist/index.js.map +1 -1
  211. package/dist/landing/landing-labels.d.ts +8 -0
  212. package/dist/landing/landing-labels.js +2 -1
  213. package/dist/landing/landing-labels.js.map +1 -1
  214. package/dist/lib/changelog.d.ts +130 -0
  215. package/dist/lib/changelog.js +137 -0
  216. package/dist/lib/changelog.js.map +1 -0
  217. package/dist/lib/clipboard-minus.js +2 -2
  218. package/dist/lib/clipboard-minus.js.map +1 -1
  219. package/dist/lib/dates.d.ts +29 -3
  220. package/dist/lib/dates.js +41 -12
  221. package/dist/lib/dates.js.map +1 -1
  222. package/dist/lib/editable-content.d.ts +33 -0
  223. package/dist/lib/editable-content.js +15 -0
  224. package/dist/lib/editable-content.js.map +1 -0
  225. package/dist/lib/focus-left.d.ts +47 -1
  226. package/dist/lib/focus-left.js +11 -2
  227. package/dist/lib/focus-left.js.map +1 -1
  228. package/dist/lib/server-wake.d.ts +49 -9
  229. package/dist/lib/server-wake.js +25 -14
  230. package/dist/lib/server-wake.js.map +1 -1
  231. package/dist/lib/session-refresh.d.ts +53 -0
  232. package/dist/lib/session-refresh.js +27 -1
  233. package/dist/lib/session-refresh.js.map +1 -1
  234. package/dist/rhf/fields.js +3 -3
  235. package/dist/rhf/fields.js.map +1 -1
  236. package/dist/shell/app-shell.d.ts +25 -1
  237. package/dist/shell/app-shell.js +3 -1
  238. package/dist/shell/app-shell.js.map +1 -1
  239. package/dist/shell/dev-menu.d.ts +3 -0
  240. package/dist/shell/dev-menu.js +4 -2
  241. package/dist/shell/dev-menu.js.map +1 -1
  242. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  243. package/dist/shell/held-changes-banner-labels.js +14 -0
  244. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  245. package/dist/shell/held-changes-banner.d.ts +75 -0
  246. package/dist/shell/held-changes-banner.js +106 -0
  247. package/dist/shell/held-changes-banner.js.map +1 -0
  248. package/dist/shell/sync-status-indicator.d.ts +27 -12
  249. package/dist/shell/sync-status-indicator.js +13 -4
  250. package/dist/shell/sync-status-indicator.js.map +1 -1
  251. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  252. package/dist/shell/top-bar-parts-labels.js +3 -1
  253. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  254. package/dist/shell.d.ts +2 -0
  255. package/dist/shell.js +3 -0
  256. package/dist/shell.js.map +1 -1
  257. package/dist/sync/engine.d.ts +11 -2
  258. package/dist/sync/engine.js +13 -5
  259. package/dist/sync/engine.js.map +1 -1
  260. package/dist/sync/hooks.d.ts +3 -2
  261. package/dist/sync/hooks.js +4 -2
  262. package/dist/sync/hooks.js.map +1 -1
  263. package/dist/testing/css-variables.generated.d.ts +2 -2
  264. package/dist/testing/css-variables.generated.js +1 -1
  265. package/dist/testing/css-variables.generated.js.map +1 -1
  266. package/dist/theme/chart-palette.d.ts +18 -0
  267. package/dist/theme/chart-palette.js +17 -1
  268. package/dist/theme/chart-palette.js.map +1 -1
  269. package/dist/theme/contrast-tokens.d.ts +3 -3
  270. package/dist/theme/contrast-tokens.js.map +1 -1
  271. package/dist/theme/contrast.d.ts +4 -4
  272. package/dist/theme/contrast.js.map +1 -1
  273. package/dist/tour/tour-overlay.js +2 -1
  274. package/dist/tour/tour-overlay.js.map +1 -1
  275. package/package.json +9 -12
  276. package/tokens.css +19 -54
  277. package/src/account/account-parts.ts +0 -47
  278. package/src/account/account-settings-labels.ts +0 -240
  279. package/src/account/account-settings.tsx +0 -698
  280. package/src/account/data-export-setting-labels.ts +0 -20
  281. package/src/account/data-export-setting.tsx +0 -200
  282. package/src/account/delete-account-setting-labels.ts +0 -33
  283. package/src/account/delete-account-setting.tsx +0 -342
  284. package/src/account/email-change-setting-labels.ts +0 -31
  285. package/src/account/email-change-setting.tsx +0 -443
  286. package/src/account/passkeys-setting.tsx +0 -445
  287. package/src/account/sessions-setting-labels.ts +0 -24
  288. package/src/account/sessions-setting.tsx +0 -283
  289. package/src/admin/admin-action-confirm-labels.ts +0 -19
  290. package/src/admin/admin-action-confirm.tsx +0 -509
  291. package/src/admin/admin-action-log-labels.ts +0 -83
  292. package/src/admin/admin-action-log.tsx +0 -464
  293. package/src/admin/admin-area-labels.ts +0 -114
  294. package/src/admin/admin-forbidden.tsx +0 -75
  295. package/src/admin/admin-overview.tsx +0 -258
  296. package/src/admin/admin-parts.ts +0 -68
  297. package/src/admin/admin-sections.tsx +0 -208
  298. package/src/admin/admin-tool-links.tsx +0 -90
  299. package/src/admin/invitations-panel-labels.ts +0 -31
  300. package/src/admin/invitations-panel.tsx +0 -516
  301. package/src/admin/mail-test-card-labels.ts +0 -19
  302. package/src/admin/mail-test-card.tsx +0 -189
  303. package/src/admin/plan-change-labels.ts +0 -5
  304. package/src/admin/plan-change.tsx +0 -474
  305. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  306. package/src/admin/reviewer-scope-editor.tsx +0 -224
  307. package/src/admin/role-select-labels.ts +0 -16
  308. package/src/admin/role-select.tsx +0 -282
  309. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  310. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  311. package/src/admin/user-roster-labels.ts +0 -17
  312. package/src/admin/user-roster.tsx +0 -559
  313. package/src/auth/accept-invitation-labels.ts +0 -21
  314. package/src/auth/accept-invitation.tsx +0 -210
  315. package/src/auth/auth-errors-labels.ts +0 -23
  316. package/src/auth/auth-errors.ts +0 -439
  317. package/src/auth/complete-name-dialog-labels.ts +0 -16
  318. package/src/auth/complete-name-dialog.tsx +0 -253
  319. package/src/auth/email-tag.ts +0 -75
  320. package/src/auth/forgot-password-form-labels.ts +0 -20
  321. package/src/auth/forgot-password-form.tsx +0 -221
  322. package/src/auth/form-rules.ts +0 -40
  323. package/src/auth/new-password-fields.tsx +0 -85
  324. package/src/auth/not-found-page-labels.ts +0 -12
  325. package/src/auth/not-found-page.tsx +0 -95
  326. package/src/auth/register-form-labels.ts +0 -31
  327. package/src/auth/register-form.tsx +0 -456
  328. package/src/auth/reset-password-form-labels.ts +0 -27
  329. package/src/auth/reset-password-form.tsx +0 -341
  330. package/src/auth/sign-in-form-labels.ts +0 -44
  331. package/src/auth/sign-in-form.tsx +0 -858
  332. package/src/auth/status-parts.tsx +0 -132
  333. package/src/auth/verify-email-labels.ts +0 -26
  334. package/src/auth/verify-email.tsx +0 -445
  335. package/src/billing/billing-banner.tsx +0 -258
  336. package/src/billing/billing-labels.ts +0 -381
  337. package/src/billing/billing-lock.ts +0 -108
  338. package/src/billing/billing-overview.ts +0 -157
  339. package/src/billing/billing-standing.ts +0 -80
  340. package/src/billing/cancel-request.ts +0 -97
  341. package/src/billing/cancel-subscription.tsx +0 -98
  342. package/src/billing/checkout-processing.ts +0 -394
  343. package/src/billing/checkout-return.ts +0 -90
  344. package/src/billing/pay-page.ts +0 -330
  345. package/src/billing/plan-card.tsx +0 -562
  346. package/src/billing/plan-change-labels.ts +0 -66
  347. package/src/billing/plan-limit-notice.tsx +0 -148
  348. package/src/billing/plan-limit-toast.tsx +0 -90
  349. package/src/billing/plan-limit.ts +0 -52
  350. package/src/billing/plan-price.ts +0 -98
  351. package/src/billing/plan-switch.tsx +0 -216
  352. package/src/billing/provider-page.ts +0 -84
  353. package/src/billing/subscription-actions.tsx +0 -247
  354. package/src/billing/subscription-status-chip.tsx +0 -95
  355. package/src/chart.ts +0 -103
  356. package/src/components/account-chips-labels.ts +0 -16
  357. package/src/components/account-chips.tsx +0 -474
  358. package/src/components/alert-banner.tsx +0 -670
  359. package/src/components/amount-input.tsx +0 -816
  360. package/src/components/appearance-labels.ts +0 -63
  361. package/src/components/authed-image-labels.ts +0 -11
  362. package/src/components/authed-image.tsx +0 -209
  363. package/src/components/autocomplete.tsx +0 -645
  364. package/src/components/breadcrumbs-labels.ts +0 -10
  365. package/src/components/breadcrumbs.tsx +0 -205
  366. package/src/components/bulk-action-bar-labels.ts +0 -11
  367. package/src/components/bulk-action-bar.tsx +0 -443
  368. package/src/components/button-group.tsx +0 -223
  369. package/src/components/button-look.ts +0 -256
  370. package/src/components/button.tsx +0 -339
  371. package/src/components/calculator.tsx +0 -341
  372. package/src/components/calendar-heatmap-labels.ts +0 -15
  373. package/src/components/calendar-heatmap.tsx +0 -600
  374. package/src/components/card.tsx +0 -377
  375. package/src/components/chart-edge.ts +0 -106
  376. package/src/components/chart-figure.tsx +0 -35
  377. package/src/components/chart-height.ts +0 -30
  378. package/src/components/chart-zoom-core.tsx +0 -627
  379. package/src/components/chart-zoom.tsx +0 -22
  380. package/src/components/chart.tsx +0 -554
  381. package/src/components/checkbox-group.tsx +0 -247
  382. package/src/components/checkbox.tsx +0 -346
  383. package/src/components/chip-input.tsx +0 -370
  384. package/src/components/chip-labels.ts +0 -13
  385. package/src/components/chip.tsx +0 -843
  386. package/src/components/choice-card.tsx +0 -544
  387. package/src/components/column-mapper-labels.ts +0 -38
  388. package/src/components/column-mapper.tsx +0 -655
  389. package/src/components/combobox-core.tsx +0 -1321
  390. package/src/components/combobox-suggestions.tsx +0 -131
  391. package/src/components/combobox.tsx +0 -716
  392. package/src/components/confirm-context.ts +0 -7
  393. package/src/components/confirm-dialog-labels.ts +0 -13
  394. package/src/components/confirm-dialog.tsx +0 -341
  395. package/src/components/copy-button-labels.ts +0 -13
  396. package/src/components/copy-button.tsx +0 -192
  397. package/src/components/country-select-labels.ts +0 -11
  398. package/src/components/country-select.tsx +0 -539
  399. package/src/components/currency-option-list.tsx +0 -188
  400. package/src/components/currency-select.tsx +0 -343
  401. package/src/components/danger-confirm-labels.ts +0 -20
  402. package/src/components/danger-confirm.tsx +0 -987
  403. package/src/components/data-table-cells.tsx +0 -118
  404. package/src/components/data-table-column-settings.tsx +0 -198
  405. package/src/components/data-table-filter-popover.tsx +0 -289
  406. package/src/components/data-table-filters.ts +0 -315
  407. package/src/components/data-table-fit.ts +0 -301
  408. package/src/components/data-table-groups.tsx +0 -288
  409. package/src/components/data-table-head.tsx +0 -280
  410. package/src/components/data-table-labels.ts +0 -198
  411. package/src/components/data-table-leading-row.tsx +0 -58
  412. package/src/components/data-table-mobile.tsx +0 -1117
  413. package/src/components/data-table-pagination.tsx +0 -202
  414. package/src/components/data-table-resize.ts +0 -188
  415. package/src/components/data-table-row-actions.tsx +0 -302
  416. package/src/components/data-table-row.tsx +0 -496
  417. package/src/components/data-table-selection.ts +0 -71
  418. package/src/components/data-table-shared.tsx +0 -287
  419. package/src/components/data-table-sort.ts +0 -163
  420. package/src/components/data-table-virtual.ts +0 -576
  421. package/src/components/data-table.tsx +0 -2598
  422. package/src/components/date-field.tsx +0 -684
  423. package/src/components/date-format-setting.tsx +0 -64
  424. package/src/components/date-picker.tsx +0 -404
  425. package/src/components/date-range-picker.tsx +0 -518
  426. package/src/components/description-list-labels.ts +0 -9
  427. package/src/components/description-list.tsx +0 -386
  428. package/src/components/dialog-frame-labels.ts +0 -7
  429. package/src/components/dialog-frame.tsx +0 -284
  430. package/src/components/disclosure.tsx +0 -476
  431. package/src/components/dropdown.tsx +0 -461
  432. package/src/components/empty-state.tsx +0 -136
  433. package/src/components/entity-combobox.tsx +0 -422
  434. package/src/components/error-boundary-labels.ts +0 -20
  435. package/src/components/error-boundary.tsx +0 -675
  436. package/src/components/facing-pair.ts +0 -93
  437. package/src/components/field-base-labels.ts +0 -20
  438. package/src/components/field-base.tsx +0 -656
  439. package/src/components/field-parts.tsx +0 -825
  440. package/src/components/field-strip.tsx +0 -129
  441. package/src/components/field.tsx +0 -206
  442. package/src/components/file-button.tsx +0 -295
  443. package/src/components/file-dropzone.tsx +0 -559
  444. package/src/components/floating-panel-labels.ts +0 -10
  445. package/src/components/floating-panel.tsx +0 -828
  446. package/src/components/focus-ring.ts +0 -16
  447. package/src/components/form-actions-labels.ts +0 -11
  448. package/src/components/form-actions.tsx +0 -578
  449. package/src/components/full-bleed-dialog.tsx +0 -294
  450. package/src/components/grouped-picker.tsx +0 -184
  451. package/src/components/help-popover.tsx +0 -117
  452. package/src/components/hover-menu.tsx +0 -343
  453. package/src/components/iban-input-labels.ts +0 -14
  454. package/src/components/iban-input.tsx +0 -236
  455. package/src/components/icon-button.tsx +0 -959
  456. package/src/components/icon-picker-labels.ts +0 -13
  457. package/src/components/icon-picker.tsx +0 -232
  458. package/src/components/image-grid-labels.ts +0 -11
  459. package/src/components/image-grid.tsx +0 -202
  460. package/src/components/inline-confirm.tsx +0 -116
  461. package/src/components/inline-edit-field-labels.ts +0 -11
  462. package/src/components/inline-edit-field.tsx +0 -552
  463. package/src/components/inline-entity-combobox.tsx +0 -849
  464. package/src/components/input.tsx +0 -394
  465. package/src/components/jump-calendar.tsx +0 -265
  466. package/src/components/language-select.tsx +0 -93
  467. package/src/components/legal-labels.ts +0 -105
  468. package/src/components/legal-page.tsx +0 -711
  469. package/src/components/legal.tsx +0 -668
  470. package/src/components/lightbox-labels.ts +0 -18
  471. package/src/components/lightbox.tsx +0 -399
  472. package/src/components/line-items-labels.ts +0 -14
  473. package/src/components/line-items.tsx +0 -602
  474. package/src/components/list-labels.ts +0 -12
  475. package/src/components/list.tsx +0 -870
  476. package/src/components/loading-state.tsx +0 -78
  477. package/src/components/measured-grid-labels.ts +0 -25
  478. package/src/components/measured-grid.tsx +0 -837
  479. package/src/components/menu-item.tsx +0 -241
  480. package/src/components/mini-calendar-labels.ts +0 -17
  481. package/src/components/mini-calendar.tsx +0 -792
  482. package/src/components/modal.tsx +0 -534
  483. package/src/components/money-draft.ts +0 -83
  484. package/src/components/money-field.tsx +0 -76
  485. package/src/components/month-grid.tsx +0 -229
  486. package/src/components/month-picker-labels.ts +0 -22
  487. package/src/components/month-picker.tsx +0 -1087
  488. package/src/components/multi-entity-combobox.tsx +0 -369
  489. package/src/components/multi-select.tsx +0 -376
  490. package/src/components/nav-pills.tsx +0 -198
  491. package/src/components/number-field.tsx +0 -327
  492. package/src/components/number-input.tsx +0 -559
  493. package/src/components/numeric-field.ts +0 -169
  494. package/src/components/numpad-sheet.tsx +0 -285
  495. package/src/components/one-time-code-input.tsx +0 -270
  496. package/src/components/page-contents-labels.ts +0 -9
  497. package/src/components/page-contents.tsx +0 -282
  498. package/src/components/page-header-context.ts +0 -8
  499. package/src/components/page-header.tsx +0 -298
  500. package/src/components/password-strength-labels.ts +0 -22
  501. package/src/components/password-strength.tsx +0 -241
  502. package/src/components/phone-input-labels.ts +0 -10
  503. package/src/components/phone-input.tsx +0 -282
  504. package/src/components/picker-sheet.tsx +0 -330
  505. package/src/components/pie-chart-labels.ts +0 -40
  506. package/src/components/pie-chart.tsx +0 -480
  507. package/src/components/popover-labels.ts +0 -14
  508. package/src/components/popover.tsx +0 -268
  509. package/src/components/progress-bar-labels.ts +0 -10
  510. package/src/components/progress-bar.tsx +0 -479
  511. package/src/components/qr-code.tsx +0 -76
  512. package/src/components/reauth-dialog-labels.ts +0 -13
  513. package/src/components/reauth-dialog.tsx +0 -265
  514. package/src/components/row-actions-labels.ts +0 -12
  515. package/src/components/row-actions.tsx +0 -795
  516. package/src/components/scroll-area.tsx +0 -123
  517. package/src/components/search-field.tsx +0 -192
  518. package/src/components/select.tsx +0 -294
  519. package/src/components/separator.tsx +0 -33
  520. package/src/components/series-chart-axes.ts +0 -342
  521. package/src/components/series-chart-budget.ts +0 -150
  522. package/src/components/series-chart-labels.ts +0 -33
  523. package/src/components/series-chart-thin.ts +0 -180
  524. package/src/components/series-chart-ticks.ts +0 -194
  525. package/src/components/series-chart.tsx +0 -1906
  526. package/src/components/server-wake-labels.ts +0 -11
  527. package/src/components/server-wake.tsx +0 -129
  528. package/src/components/settings-fields.tsx +0 -237
  529. package/src/components/share-card-labels.ts +0 -30
  530. package/src/components/share-card.tsx +0 -675
  531. package/src/components/signature-pad-labels.ts +0 -24
  532. package/src/components/signature-pad.tsx +0 -747
  533. package/src/components/signed-amount-labels.ts +0 -10
  534. package/src/components/signed-amount.tsx +0 -336
  535. package/src/components/skeleton.tsx +0 -80
  536. package/src/components/slider.tsx +0 -338
  537. package/src/components/sparkline-labels.ts +0 -14
  538. package/src/components/sparkline.tsx +0 -322
  539. package/src/components/spinner.tsx +0 -81
  540. package/src/components/stat-tile-labels.ts +0 -15
  541. package/src/components/stat-tile.tsx +0 -754
  542. package/src/components/status-dot.tsx +0 -170
  543. package/src/components/swatch-picker-labels.ts +0 -10
  544. package/src/components/swatch-picker.tsx +0 -283
  545. package/src/components/swipeable-row.tsx +0 -386
  546. package/src/components/switch.tsx +0 -273
  547. package/src/components/table-skeleton.tsx +0 -136
  548. package/src/components/table.tsx +0 -647
  549. package/src/components/tabs-labels.ts +0 -13
  550. package/src/components/tabs.tsx +0 -693
  551. package/src/components/text-link.tsx +0 -359
  552. package/src/components/text.tsx +0 -150
  553. package/src/components/textarea.tsx +0 -330
  554. package/src/components/tile-radio.tsx +0 -332
  555. package/src/components/time-input.tsx +0 -121
  556. package/src/components/toast-labels.ts +0 -12
  557. package/src/components/toast.tsx +0 -519
  558. package/src/components/toggle-group.tsx +0 -752
  559. package/src/components/toggle-legend.tsx +0 -394
  560. package/src/components/tooltip.tsx +0 -1149
  561. package/src/components/translation-review-editor.tsx +0 -290
  562. package/src/components/translation-review-labels.ts +0 -181
  563. package/src/components/translation-review-parts.tsx +0 -272
  564. package/src/components/translation-review.tsx +0 -1240
  565. package/src/components/tree-view.tsx +0 -589
  566. package/src/components/treemap-labels.ts +0 -47
  567. package/src/components/treemap-layout.ts +0 -210
  568. package/src/components/treemap.tsx +0 -1184
  569. package/src/components/trigger-aria.ts +0 -44
  570. package/src/components/use-anchor-dir.ts +0 -20
  571. package/src/components/use-mobile-reveal.ts +0 -62
  572. package/src/components/use-table-state.ts +0 -753
  573. package/src/components/user-avatar.tsx +0 -116
  574. package/src/components/wizard-stepper.tsx +0 -42
  575. package/src/components/write-lock-labels.ts +0 -10
  576. package/src/components/write-lock.tsx +0 -253
  577. package/src/currencies.ts +0 -90
  578. package/src/data-table.ts +0 -42
  579. package/src/demo/demo-banner.tsx +0 -153
  580. package/src/demo/demo-countdown.ts +0 -136
  581. package/src/demo/demo-ended.tsx +0 -90
  582. package/src/demo/demo-labels.ts +0 -78
  583. package/src/demo/demo-session.ts +0 -32
  584. package/src/demo/demo-start.tsx +0 -156
  585. package/src/eslint/index.ts +0 -161
  586. package/src/eslint/role-utilities.generated.ts +0 -146
  587. package/src/eslint/role-utilities.ts +0 -233
  588. package/src/feedback/feedback-attachment-download.tsx +0 -77
  589. package/src/feedback/feedback-attachment-labels.ts +0 -37
  590. package/src/feedback/feedback-attachment.tsx +0 -1239
  591. package/src/feedback/feedback-capture.ts +0 -287
  592. package/src/feedback/feedback-comment-thread.tsx +0 -353
  593. package/src/feedback/feedback-context-labels.ts +0 -11
  594. package/src/feedback/feedback-context.tsx +0 -245
  595. package/src/feedback/feedback-crash.ts +0 -547
  596. package/src/feedback/feedback-diagnostics.tsx +0 -41
  597. package/src/feedback/feedback-dialog-labels.ts +0 -29
  598. package/src/feedback/feedback-dialog.tsx +0 -452
  599. package/src/feedback/feedback-entry-file.tsx +0 -150
  600. package/src/feedback/feedback-inbox.tsx +0 -552
  601. package/src/feedback/feedback-labels.ts +0 -144
  602. package/src/feedback/feedback-menu-labels.ts +0 -12
  603. package/src/feedback/feedback-menu.tsx +0 -177
  604. package/src/feedback/feedback-mutation.ts +0 -20
  605. package/src/feedback/feedback-page-link.ts +0 -90
  606. package/src/feedback/feedback-public-labels.ts +0 -81
  607. package/src/feedback/feedback-public-list.tsx +0 -524
  608. package/src/feedback/feedback-public-switch.tsx +0 -102
  609. package/src/feedback/feedback-public-types.ts +0 -101
  610. package/src/feedback/feedback-record.ts +0 -428
  611. package/src/feedback/feedback-replies-context.ts +0 -9
  612. package/src/feedback/feedback-replies.tsx +0 -343
  613. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  614. package/src/feedback/feedback-row-detail-labels.ts +0 -31
  615. package/src/feedback/feedback-row-detail.tsx +0 -871
  616. package/src/feedback/feedback-status-undo.ts +0 -109
  617. package/src/feedback/feedback-submit-dialog.tsx +0 -131
  618. package/src/feedback/feedback-submit.tsx +0 -270
  619. package/src/feedback/feedback-swipe.tsx +0 -192
  620. package/src/feedback/feedback-table-labels.ts +0 -27
  621. package/src/feedback/feedback-table.tsx +0 -912
  622. package/src/feedback/feedback-thread-labels.ts +0 -22
  623. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  624. package/src/feedback/feedback-thread.tsx +0 -905
  625. package/src/feedback/feedback-vocabulary.ts +0 -263
  626. package/src/feedback/feedback-vote-button.tsx +0 -124
  627. package/src/feedback.ts +0 -116
  628. package/src/hooks/use-anchored-panel.ts +0 -396
  629. package/src/hooks/use-anchored-rect.ts +0 -77
  630. package/src/hooks/use-announce.ts +0 -85
  631. package/src/hooks/use-authed-src.ts +0 -167
  632. package/src/hooks/use-body-scroll-lock.ts +0 -119
  633. package/src/hooks/use-breakpoint.ts +0 -133
  634. package/src/hooks/use-close-transition.ts +0 -107
  635. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  636. package/src/hooks/use-debounce.ts +0 -114
  637. package/src/hooks/use-dismiss.ts +0 -371
  638. package/src/hooks/use-document-title.ts +0 -33
  639. package/src/hooks/use-file-drop.ts +0 -121
  640. package/src/hooks/use-file-picker-labels.ts +0 -28
  641. package/src/hooks/use-file-picker.tsx +0 -359
  642. package/src/hooks/use-focus-trap.ts +0 -199
  643. package/src/hooks/use-hotkey.ts +0 -212
  644. package/src/hooks/use-large-text.ts +0 -41
  645. package/src/hooks/use-long-press.ts +0 -140
  646. package/src/hooks/use-media-query.ts +0 -39
  647. package/src/hooks/use-mounted.ts +0 -39
  648. package/src/hooks/use-noindex.ts +0 -37
  649. package/src/hooks/use-overlay-history.ts +0 -714
  650. package/src/hooks/use-row-swipe.ts +0 -207
  651. package/src/hooks/use-search-param-state.ts +0 -434
  652. package/src/hooks/use-windowed-rows.ts +0 -225
  653. package/src/i18n/defaults.ts +0 -267
  654. package/src/i18n/german.ts +0 -2059
  655. package/src/i18n/kit-labels.tsx +0 -1003
  656. package/src/i18n/languages.ts +0 -349
  657. package/src/i18n/locales/de-CH.ts +0 -24
  658. package/src/i18n/locales/en.ts +0 -267
  659. package/src/i18n/locales/es.ts +0 -1999
  660. package/src/i18n/locales/fr.ts +0 -2037
  661. package/src/i18n/locales/hu.ts +0 -2004
  662. package/src/i18n/locales/it.ts +0 -2014
  663. package/src/i18n/locales/zh.ts +0 -1912
  664. package/src/i18n/plurals.ts +0 -86
  665. package/src/i18n/review.ts +0 -475
  666. package/src/i18n/shared-labels.ts +0 -138
  667. package/src/i18n/swiss.ts +0 -29
  668. package/src/index.ts +0 -1191
  669. package/src/landing/access.ts +0 -93
  670. package/src/landing/landing-actions.tsx +0 -97
  671. package/src/landing/landing-labels.ts +0 -59
  672. package/src/landing/landing-sections.tsx +0 -341
  673. package/src/landing/page-seo.ts +0 -261
  674. package/src/landing/public-header.tsx +0 -121
  675. package/src/landing/public-page.tsx +0 -106
  676. package/src/landing/routing.tsx +0 -225
  677. package/src/lib/broadcast.ts +0 -52
  678. package/src/lib/calc.ts +0 -323
  679. package/src/lib/chip-edge-snap.ts +0 -212
  680. package/src/lib/clipboard-minus.ts +0 -107
  681. package/src/lib/clipping.ts +0 -34
  682. package/src/lib/cn.ts +0 -28
  683. package/src/lib/column-mapping.ts +0 -234
  684. package/src/lib/countries.ts +0 -91
  685. package/src/lib/data-attributes.ts +0 -14
  686. package/src/lib/date-format.ts +0 -227
  687. package/src/lib/dates.ts +0 -288
  688. package/src/lib/day-format.ts +0 -96
  689. package/src/lib/decimal-marks.ts +0 -108
  690. package/src/lib/describe-failure.ts +0 -18
  691. package/src/lib/device-settings.ts +0 -147
  692. package/src/lib/direction.ts +0 -74
  693. package/src/lib/document-navigation.ts +0 -17
  694. package/src/lib/document-title.ts +0 -41
  695. package/src/lib/download.ts +0 -172
  696. package/src/lib/float-clearance.ts +0 -70
  697. package/src/lib/floating-stack.ts +0 -70
  698. package/src/lib/focus-left.ts +0 -156
  699. package/src/lib/format.ts +0 -585
  700. package/src/lib/http-status.ts +0 -38
  701. package/src/lib/iban.ts +0 -251
  702. package/src/lib/ime.ts +0 -24
  703. package/src/lib/intl-cache.ts +0 -27
  704. package/src/lib/iso-day.ts +0 -13
  705. package/src/lib/lazy-with-fallback.ts +0 -82
  706. package/src/lib/logger.ts +0 -69
  707. package/src/lib/minus.ts +0 -50
  708. package/src/lib/path-pattern.ts +0 -55
  709. package/src/lib/person-name.ts +0 -155
  710. package/src/lib/phone.ts +0 -208
  711. package/src/lib/plain-text.ts +0 -22
  712. package/src/lib/presence.ts +0 -18
  713. package/src/lib/qr-encode.ts +0 -444
  714. package/src/lib/quiet-focus.ts +0 -32
  715. package/src/lib/reduced-motion.ts +0 -22
  716. package/src/lib/refs.ts +0 -22
  717. package/src/lib/round.ts +0 -43
  718. package/src/lib/safe-storage.ts +0 -119
  719. package/src/lib/server-wake.ts +0 -315
  720. package/src/lib/session-refresh.ts +0 -472
  721. package/src/lib/share-format.ts +0 -18
  722. package/src/lib/status-tone.ts +0 -43
  723. package/src/lib/strip-fade.ts +0 -287
  724. package/src/lib/table-text.ts +0 -554
  725. package/src/lib/text-case.ts +0 -19
  726. package/src/lib/text-match.ts +0 -169
  727. package/src/lib/thenable.ts +0 -19
  728. package/src/lib/translation-review.ts +0 -769
  729. package/src/notifications/device-zone-report.ts +0 -67
  730. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  731. package/src/notifications/feedback-notification-types.ts +0 -71
  732. package/src/notifications/notification-bell.tsx +0 -119
  733. package/src/notifications/notification-inbox.tsx +0 -309
  734. package/src/notifications/notification-parts.tsx +0 -81
  735. package/src/notifications/notification-types-setting.tsx +0 -406
  736. package/src/notifications/notification-types.ts +0 -89
  737. package/src/notifications/notifications-labels.ts +0 -287
  738. package/src/notifications/push-device-setting.tsx +0 -230
  739. package/src/notifications/push-devices-setting.tsx +0 -374
  740. package/src/notifications/push-worker.ts +0 -168
  741. package/src/notifications/quiet-hours-setting.tsx +0 -238
  742. package/src/notifications/unsubscribe-panel.tsx +0 -200
  743. package/src/notifications/use-push-device.ts +0 -448
  744. package/src/offline/local-first.ts +0 -100
  745. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  746. package/src/offline/pwa-update-prompt.tsx +0 -78
  747. package/src/offline/query-persistence.ts +0 -391
  748. package/src/offline/service-worker-updates.ts +0 -81
  749. package/src/offline/shared-computer-labels.ts +0 -8
  750. package/src/offline/shared-computer.tsx +0 -87
  751. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  752. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  753. package/src/offline.ts +0 -37
  754. package/src/push-worker.ts +0 -25
  755. package/src/rhf/fields.tsx +0 -2187
  756. package/src/rhf/form-core.tsx +0 -324
  757. package/src/rhf/form.tsx +0 -12
  758. package/src/rhf/line-items.tsx +0 -135
  759. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  760. package/src/rhf.ts +0 -21
  761. package/src/search/command-palette-labels.ts +0 -16
  762. package/src/search/command-palette.tsx +0 -624
  763. package/src/search/global-search-labels.ts +0 -13
  764. package/src/search/global-search.tsx +0 -660
  765. package/src/search/search-index.ts +0 -283
  766. package/src/search/use-command-key.ts +0 -23
  767. package/src/search.ts +0 -14
  768. package/src/settings/device-reset-setting-labels.ts +0 -20
  769. package/src/settings/device-reset-setting.tsx +0 -116
  770. package/src/settings/settings-card-classes.ts +0 -11
  771. package/src/settings/settings-catalogue.ts +0 -150
  772. package/src/settings/settings-context.ts +0 -33
  773. package/src/settings/settings-heading.tsx +0 -67
  774. package/src/settings/settings-labels.ts +0 -87
  775. package/src/settings/settings-layout.tsx +0 -471
  776. package/src/settings/settings-section.tsx +0 -108
  777. package/src/settings/use-account-appearance.ts +0 -148
  778. package/src/settings/use-account-language.ts +0 -146
  779. package/src/settings/use-settings-focus.ts +0 -128
  780. package/src/settings/use-settings-route.ts +0 -258
  781. package/src/shell/account-menu-language.tsx +0 -130
  782. package/src/shell/account-menu-text-size.tsx +0 -80
  783. package/src/shell/admin-menu.tsx +0 -183
  784. package/src/shell/app-shell-labels.ts +0 -11
  785. package/src/shell/app-shell.tsx +0 -1560
  786. package/src/shell/auth-layout.tsx +0 -186
  787. package/src/shell/company-switcher-labels.ts +0 -13
  788. package/src/shell/company-switcher.tsx +0 -136
  789. package/src/shell/dev-menu.tsx +0 -257
  790. package/src/shell/fill-height.tsx +0 -45
  791. package/src/shell/language-code.ts +0 -5
  792. package/src/shell/option-switcher-menu.tsx +0 -84
  793. package/src/shell/sync-status-indicator.tsx +0 -303
  794. package/src/shell/top-bar-actions.tsx +0 -87
  795. package/src/shell/top-bar-brand.tsx +0 -78
  796. package/src/shell/top-bar-parts-labels.ts +0 -148
  797. package/src/shell/top-bar.tsx +0 -40
  798. package/src/shell/topbar-action-menu.tsx +0 -567
  799. package/src/shell/topbar-controls.tsx +0 -269
  800. package/src/shell/tour-menu.tsx +0 -187
  801. package/src/shell.ts +0 -108
  802. package/src/sync/db.ts +0 -129
  803. package/src/sync/engine.ts +0 -1011
  804. package/src/sync/hooks.ts +0 -98
  805. package/src/sync/testing.ts +0 -73
  806. package/src/sync/wire.ts +0 -61
  807. package/src/sync.ts +0 -37
  808. package/src/table-text.ts +0 -8
  809. package/src/testing/css-variables.generated.ts +0 -112
  810. package/src/testing/css-variables.ts +0 -189
  811. package/src/testing/index.ts +0 -29
  812. package/src/testing/react.ts +0 -58
  813. package/src/testing/tour.ts +0 -41
  814. package/src/testing/viewport.ts +0 -357
  815. package/src/theme/chart-palette.ts +0 -232
  816. package/src/theme/color.ts +0 -272
  817. package/src/theme/contrast-tokens.ts +0 -268
  818. package/src/theme/contrast.ts +0 -250
  819. package/src/theme/palette-derive.ts +0 -825
  820. package/src/theme/palette-presets.ts +0 -544
  821. package/src/theme/palette-store.ts +0 -108
  822. package/src/theme/text-size.ts +0 -289
  823. package/src/theme/theme-store.ts +0 -157
  824. package/src/tour/tour-catalogue-labels.ts +0 -75
  825. package/src/tour/tour-catalogue.tsx +0 -335
  826. package/src/tour/tour-dock.ts +0 -134
  827. package/src/tour/tour-engine.ts +0 -37
  828. package/src/tour/tour-gates.ts +0 -218
  829. package/src/tour/tour-guide-button.tsx +0 -64
  830. package/src/tour/tour-guide-labels.ts +0 -15
  831. package/src/tour/tour-info.ts +0 -144
  832. package/src/tour/tour-labels.ts +0 -23
  833. package/src/tour/tour-overlay.tsx +0 -1170
  834. package/src/tour/tour.tsx +0 -750
  835. package/src/tour/use-tour-progress.ts +0 -671
  836. package/src/tour/use-tour-url-sync.ts +0 -266
  837. package/src/tour.ts +0 -21
  838. package/src/vite-env.d.ts +0 -23
  839. package/src/wizard/stepper-nav.tsx +0 -360
  840. package/src/wizard/types.ts +0 -275
  841. package/src/wizard/use-wizard.ts +0 -560
  842. package/src/wizard/validation.ts +0 -39
  843. package/src/wizard/wizard-context.tsx +0 -74
  844. package/src/wizard/wizard-labels.ts +0 -29
  845. package/src/wizard/wizard-step.tsx +0 -76
  846. package/src/wizard/wizard-summary.tsx +0 -84
  847. package/src/wizard.ts +0 -15
@@ -1,1321 +0,0 @@
1
- import { Fragment, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
- import { createPortal } from "react-dom";
3
- import type {
4
- ComponentPropsWithoutRef,
5
- Dispatch,
6
- KeyboardEvent as ReactKeyboardEvent,
7
- ReactNode,
8
- RefObject,
9
- SetStateAction,
10
- } from "react";
11
- import { Check, Plus } from "lucide-react";
12
- import { cn } from "../lib/cn";
13
- import { CLIPS_ATTRIBUTE } from "../lib/clipping";
14
- import { tabStops } from "../lib/focus-left";
15
- import { DropdownSearchHeader } from "./dropdown";
16
- import { PickerSheet, SHEET_ROW_CLASS } from "./picker-sheet";
17
- import { usePhoneLayout, useRemPx } from "../hooks/use-breakpoint";
18
- import { FLOATING_LABEL_STATIC } from "./field-parts";
19
- import { Spinner } from "./spinner";
20
- import { useAnchorDir } from "./use-anchor-dir";
21
- import { type AnchorRect } from "../hooks/use-anchored-rect";
22
- import { anchoredInline, useAnchoredPanel, viewportSize, type AnchoredPanel } from "../hooks/use-anchored-panel";
23
- import { pressInside, takeEscape, useDismissLayer, useOutsideClick } from "../hooks/use-dismiss";
24
- import { useOverlayHistory } from "../hooks/use-overlay-history";
25
- import { isImeEnter } from "../lib/ime";
26
- import { matchesSearch, sameName, searchWords } from "../lib/text-match";
27
- import { DEFAULT_COMBOBOX_LABELS, useKitLabels, useKitLocale } from "../i18n/kit-labels";
28
- import { hasContent } from "../lib/presence";
29
-
30
- /**
31
- * What an entity picker hands `onChange` when it is cleared — `clearValue` on
32
- * {@link EntityCombobox} and {@link InlineEntityCombobox}. `null` by default.
33
- *
34
- * A closed set rather than any value, on purpose: every member is something no
35
- * option can be (an option's value is a `string | number`, and `""` is never a real
36
- * id), so the picker can also READ it back — a field whose `value` is the clear
37
- * value shows as empty, with no clear button, exactly as `null` does. An arbitrary
38
- * sentinel could collide with an id and would make that read ambiguous.
39
- *
40
- * No `undefined`: a prop set to `undefined` is a prop not passed, so it would read as
41
- * the default and emit `null` — the one outcome the caller had asked not to get.
42
- */
43
- export type ComboClearValue = null | "";
44
-
45
- export interface ComboOption<V extends string | number> {
46
- value: V;
47
- label: string;
48
- sublabel?: string;
49
- icon?: ReactNode;
50
- /**
51
- * Section this option belongs to. Where a consumer renders it as a heading with the
52
- * members indented beneath — {@link InlineEntityCombobox} does — this is a genuine
53
- * grouping, not decoration: the name appears once per section rather than trailing
54
- * every row in small grey type, which is barely legible on a phone (feedback #136).
55
- * The heading stays at the top of the list while its rows scroll beneath it.
56
- *
57
- * NOT searched (0.40, kk run 79): a group is where a row is filed, not what it is
58
- * called — with first-letter groups ("A", "B", … "#"), typing "a" matched every row
59
- * filed under A. Searching a category's name means putting it in `sublabel`.
60
- */
61
- group?: string;
62
- /**
63
- * Shown but not takeable: rendered dimmed with `aria-disabled`, passed over by the
64
- * arrow keys, and a click or Enter on it does nothing. For a row whose existence is
65
- * the information — a write-locked result, an account that is closed — where
66
- * leaving it out would read as "there is no such thing". Say WHY in `sublabel`: a
67
- * hover tooltip is the one explanation a phone cannot show.
68
- *
69
- * Honoured by {@link Autocomplete}, {@link InlineEntityCombobox} and the panel
70
- * pickers ({@link EntityCombobox}, {@link MultiEntityCombobox}); on those an already
71
- * chosen disabled option stays chosen — the row only refuses to be toggled.
72
- */
73
- disabled?: boolean;
74
- }
75
-
76
- /** A row the user may take — present and not {@link ComboOption.disabled}. */
77
- export const isOptionEnabled = (o: { disabled?: boolean } | undefined): boolean => o !== undefined && !o.disabled;
78
-
79
- /**
80
- * The next takeable row from `from`, stepping `dir` — what the arrow keys use so a
81
- * disabled row is passed over, as the APG allows. Stays on `from` at the end of the
82
- * list (the family's arrows clamp rather than wrap) and answers `-1` when `from` is
83
- * not a row and there is nothing to step to.
84
- */
85
- export function stepEnabled(rows: readonly { disabled?: boolean }[], from: number, dir: 1 | -1): number {
86
- for (let i = from + dir; i >= 0 && i < rows.length; i += dir) {
87
- if (!rows[i].disabled) return i;
88
- }
89
- return from >= 0 && from < rows.length ? from : -1;
90
- }
91
-
92
- /** `i` when that row is takeable, else the nearest takeable one after it, else
93
- * before it, else `-1`. For a highlight that starts on a fixed index (the panel
94
- * pickers open on row 0) and must never rest on a disabled row. */
95
- export function settleEnabled(rows: readonly { disabled?: boolean }[], i: number): number {
96
- if (i < 0 || i >= rows.length) return -1;
97
- if (!rows[i].disabled) return i;
98
- const after = stepEnabled(rows, i, 1);
99
- if (after !== i) return after;
100
- const before = stepEnabled(rows, i, -1);
101
- return before !== i ? before : -1;
102
- }
103
-
104
- /** Classes a disabled row adds over its normal ones — dimmed, no hover, the
105
- * kit's soft-disabled look (`DangerConfirm`, `SignaturePad`). */
106
- export const DISABLED_ROW_CLASS = "cursor-not-allowed opacity-50 hover:bg-transparent";
107
-
108
- /**
109
- * Whether an entity picker offers its create row — one rule for
110
- * {@link EntityCombobox}, {@link MultiEntityCombobox} and {@link InlineEntityCombobox}.
111
- *
112
- * With a query: while the trimmed query names no option exactly — an exact hit is a
113
- * record the list is already showing, and a second way to "make" it would mint a
114
- * duplicate. "Exactly" as the list matches, folded by `locale` ({@link sameName}): with
115
- * plain `toLowerCase`, "cafe" listed Café and offered "Create “cafe”" beside it (refactor
116
- * audit 2026-10-30, DUP-5). With NO query: only when the caller named the row for that
117
- * case (`createEmptyLabel`) — a row reading `Create “”` says nothing, and the kit cannot
118
- * know the noun ("Create cash account") that would make it say something (keksdose G9).
119
- *
120
- * `names` is what the query is compared against: the loaded rows for a panel picker
121
- * (its async list is all it knows), every option for the inline one. `locale`: the
122
- * provider's, which the list's match folds by; left out, the runtime's.
123
- */
124
- export function offersCreate(
125
- onCreate: unknown,
126
- query: string,
127
- names: readonly string[],
128
- createEmptyLabel: string | undefined,
129
- locale?: string,
130
- ): boolean {
131
- if (!onCreate) return false;
132
- if (!query) return Boolean(createEmptyLabel);
133
- return !names.some((n) => sameName(n, query, locale));
134
- }
135
-
136
- /**
137
- * {@link EndHintRow}'s `hint` and `className` for a combobox-family field, keeping the
138
- * 0.22 rule (keksdose): a field that was passed `hint` at all — even `undefined` — keeps
139
- * ONE box, so a hint coming and going never rebuilds the control under the cursor.
140
- *
141
- * A text hint is a caption after the control and never moved it; the case this covers
142
- * is the "?" (a {@link FieldHint}) on a field with no label line, which sits at the end
143
- * edge in a row of its own. Without this the row existed only while the "?" did, and
144
- * the control was re-parented — remounted, focus and caret lost — each time it came or
145
- * went. With `hint` passed the row stays, its end slot empty and its gap closed, so
146
- * nothing on screen moves either.
147
- */
148
- export function endHintRowProps(
149
- hintPassed: boolean,
150
- hasLabel: boolean,
151
- labelHint: ReactNode | undefined,
152
- ): { hint: ReactNode | undefined; className?: string } {
153
- if (hasLabel) return { hint: undefined };
154
- if (labelHint !== undefined) return { hint: labelHint };
155
- return hintPassed ? { hint: null, className: "gap-0" } : { hint: undefined };
156
- }
157
-
158
- /**
159
- * The family's floating label, as a real `<label for>` (lenkbank): the input is then
160
- * named the way {@link Input}'s is, so `getByLabelText` and any helper that walks
161
- * `<label htmlFor>` finds it. Same placement and type as {@link FieldLabel}, which
162
- * stays a `<span>` for the TRIGGER pickers — a `<button>` is labelable, but those
163
- * compose their own "label: value" name. `pointer-events-none` (from the class)
164
- * keeps a press on it landing on the field under it, as before.
165
- */
166
- export function ComboboxFieldLabel({
167
- htmlFor,
168
- className,
169
- children,
170
- ...rest
171
- }: ComponentPropsWithoutRef<"label"> & { htmlFor: string }) {
172
- return (
173
- <label {...rest} htmlFor={htmlFor} className={cn(FLOATING_LABEL_STATIC, "z-10", className)}>
174
- {children}
175
- </label>
176
- );
177
- }
178
-
179
- /**
180
- * Where a combobox's rows come from, and how a query narrows them. Shared by the
181
- * trigger pickers ({@link useComboboxCore}) and the inline {@link Autocomplete}, so
182
- * the two cannot drift in what a failed lookup or a too-short query does.
183
- */
184
- export interface OptionSourceOptions<V extends string | number> {
185
- /** Already-loaded options (client-side filtered unless `filter` is false), also
186
- * used to resolve labels. */
187
- options?: ComboOption<V>[];
188
- /** Async option source, debounced and race-safe; stale responses are ignored,
189
- * and a rejection is caught and reported as `failed`. */
190
- loadOptions?: (query: string) => Promise<ComboOption<V>[]>;
191
- /** External loading flag, OR-ed with the internal async state. */
192
- loading?: boolean;
193
- /**
194
- * Narrow `options` by the query (label/sublabel substring, folded for search: no case,
195
- * no accents, the locale's case rules — "zurich" finds Zürich, DUP-5). Default `true`.
196
- *
197
- * `false` shows `options` exactly as given, in the given order — for a list the
198
- * caller has already searched and ranked on a server (Keksdose's geocoder), which a
199
- * second, client-side substring filter could only get wrong: a provider that
200
- * matches "Bahnhofstr." to "Bahnhofstrasse" is right, and a label filter would
201
- * throw that row away.
202
- */
203
- filter?: boolean;
204
- /**
205
- * Below this many (trimmed) characters nothing is offered and `loadOptions` is not
206
- * called. Default `0` — the pickers list everything on open. Set it for a source
207
- * that is costly or rate-limited: one letter against a geocoder matches half a
208
- * continent, and a request per opened panel is a request nobody asked for.
209
- */
210
- minChars?: number;
211
- /** Quiet time after the last keystroke before `loadOptions` runs. Default 150 ms. */
212
- debounceMs?: number;
213
- }
214
-
215
- export interface OptionSource<V extends string | number> {
216
- results: ComboOption<V>[];
217
- busy: boolean;
218
- /** The last lookup for this query REJECTED. The rows are emptied with it: results
219
- * from an earlier query under a failed one would read as the answer to it. */
220
- failed: boolean;
221
- /** The query is shorter than `minChars`, so nothing was asked. */
222
- tooShort: boolean;
223
- }
224
-
225
- /**
226
- * The rows half of a combobox: client-side filtering, or a debounced, race-safe
227
- * `loadOptions` with its loading and failure states.
228
- *
229
- * `active` gates the async side — a closed list asks nothing. Loading is DERIVED
230
- * ("the settled answer is for a different query") rather than flagged on in the
231
- * effect, so there is no render between a keystroke and the debounce in which an
232
- * empty list could claim "No results" for a question still being asked.
233
- */
234
- export function useOptionSource<V extends string | number>({
235
- options,
236
- loadOptions,
237
- loading,
238
- filter = true,
239
- minChars = 0,
240
- debounceMs = 150,
241
- query,
242
- active,
243
- }: OptionSourceOptions<V> & { query: string; active: boolean }): OptionSource<V> {
244
- const isAsync = typeof loadOptions === "function";
245
- const tooShort = query.trim().length < minChars;
246
- const [settled, setSettled] = useState<{
247
- query: string | null;
248
- results: ComboOption<V>[];
249
- failed: boolean;
250
- }>({ query: null, results: [], failed: false });
251
- const loadRef = useRef(loadOptions);
252
- useEffect(() => {
253
- loadRef.current = loadOptions;
254
- });
255
- const reqId = useRef(0);
256
-
257
- useEffect(() => {
258
- // Bumped even when nothing is asked, so a request still in flight from a longer
259
- // query cannot land after the user has deleted back below `minChars`.
260
- const id = ++reqId.current;
261
- if (!active || !isAsync || tooShort) return;
262
- const t = setTimeout(
263
- async () => {
264
- try {
265
- const r = await loadRef.current!(query);
266
- if (reqId.current === id) setSettled({ query, results: r, failed: false });
267
- } catch {
268
- // The one path the old `try/finally` had no answer for: the list kept the
269
- // previous query's rows and said nothing (Keksdose proposal §2).
270
- if (reqId.current === id) setSettled({ query, results: [], failed: true });
271
- }
272
- },
273
- query ? debounceMs : 0,
274
- );
275
- return () => clearTimeout(t);
276
- }, [query, active, isAsync, tooShort, debounceMs]);
277
-
278
- // Folded as every kit list folds (DUP-5, refactor audit 2026-10): plain `toLowerCase`
279
- // here meant "zurich" found no Zürich in kastlan's eighteen entity pickers, and the
280
- // Turkish "ı" no "IRMAK".
281
- const locale = useKitLocale();
282
- const clientResults = useMemo(() => {
283
- if (isAsync) return [];
284
- const src = options ?? [];
285
- // Every word of the query, in the label or the sublabel (0.51.4): "migros zürich".
286
- const words = searchWords(query, locale);
287
- if (!filter || words.length === 0) return src;
288
- return src.filter((o) => matchesSearch(words, [o.label, o.sublabel], locale));
289
- }, [isAsync, options, query, filter, locale]);
290
-
291
- const pending = isAsync && active && !tooShort && settled.query !== query;
292
- const failed = isAsync && !tooShort && settled.failed;
293
- return {
294
- results: tooShort ? [] : isAsync ? (failed ? [] : settled.results) : clientResults,
295
- busy: Boolean(loading) || pending,
296
- failed: failed && !pending,
297
- tooShort,
298
- };
299
- }
300
-
301
- export interface ComboboxCoreOptions<V extends string | number> extends OptionSourceOptions<V> {
302
- /**
303
- * Back closes the open anchored list, not the page or the dialog behind it — the rule
304
- * `useDropdown` keeps for its lists (keksdose live #309 rework). Default `false`, as the
305
- * entity pickers have always been; CurrencySelect, MultiSelect, GroupedPicker and
306
- * AmountInput's currency chip, which came over from `useDropdown` (DUP-4), pass `true`
307
- * so they keep it. On a phone the `PickerSheet` registers Back itself.
308
- */
309
- backCloses?: boolean;
310
- }
311
-
312
- export interface ComboboxCore<V extends string | number> {
313
- triggerRef: RefObject<HTMLButtonElement | null>;
314
- panelRef: RefObject<HTMLDivElement | null>;
315
- inputRef: RefObject<HTMLInputElement | null>;
316
- open: boolean;
317
- setOpen: Dispatch<SetStateAction<boolean>>;
318
- query: string;
319
- setQuery: Dispatch<SetStateAction<string>>;
320
- active: number;
321
- setActive: Dispatch<SetStateAction<number>>;
322
- results: ComboOption<V>[];
323
- busy: boolean;
324
- /** See {@link OptionSource}. */
325
- failed: boolean;
326
- tooShort: boolean;
327
- minChars: number;
328
- rect: AnchorRect | null;
329
- /** Where the dropdown goes, clamped to the visible viewport (feedback #135). */
330
- placement: AnchoredPanel;
331
- /** Put an option in the label cache, so {@link ComboboxCore.resolve} still knows a
332
- * value the caller just chose after the result list has moved on. */
333
- rememberOption: (o: ComboOption<V>) => void;
334
- resolve: (v: V) => ComboOption<V> | null;
335
- /** Close and hand focus back to the trigger — the keyboard paths only. See
336
- * {@link useDropdown} for why a pointer dismissal must not do this. */
337
- closeToTrigger: () => void;
338
- }
339
-
340
- /**
341
- * Shared plumbing for the entity pickers: open/query/active state, portalled
342
- * anchoring + outside-click/Escape dismissal, focus-on-open, a debounced
343
- * race-safe async (or client-side) result list, and a label cache so a selected
344
- * value still renders its label after the async list has moved on. The single-
345
- * and multi-value comboboxes render their triggers on top of this and share
346
- * {@link ComboboxPanel} for the dropdown.
347
- */
348
- export function useComboboxCore<V extends string | number>({
349
- options,
350
- minChars = 0,
351
- backCloses = false,
352
- ...source
353
- }: ComboboxCoreOptions<V>): ComboboxCore<V> {
354
- const triggerRef = useRef<HTMLButtonElement>(null);
355
- const panelRef = useRef<HTMLDivElement>(null);
356
- const inputRef = useRef<HTMLInputElement>(null);
357
- const [open, setOpen] = useState(false);
358
- const [query, setQuery] = useState("");
359
- const [active, setActive] = useState(0);
360
-
361
- const close = () => setOpen(false);
362
- // Escape unmounts the panel the user is typing in; without this the browser drops
363
- // focus on <body>, so the caret disappears and the next Tab restarts at the top of
364
- // the page. A pointer dismissal deliberately does NOT do this — the press has
365
- // already decided where focus belongs.
366
- const closeToTrigger = () => {
367
- setOpen(false);
368
- triggerRef.current?.focus();
369
- };
370
- // Placement, not just the anchor rect: the panel has to dodge the on-screen
371
- // keyboard its own search box summons (feedback #135).
372
- const placement = useAnchoredPanel(triggerRef, open);
373
- const rect = placement.rect;
374
- // Outside-click dismissal belongs to the ANCHORED panel, and only to it. On a
375
- // phone `ComboboxPanel` renders a full-screen `PickerSheet` instead (live #200),
376
- // portalled to <body> and carrying its own ways out — the close X, Escape, and
377
- // Back through `useOverlayHistory` — so there is no outside left to press.
378
- //
379
- // Leaving the listener on there was not merely redundant, it was the bug:
380
- // `panelRef` is attached to the desktop panel alone, so with the sheet up every
381
- // press inside it answered "outside". While `useOutsideClick` listened for
382
- // `mousedown` the rows survived by accident — they commit on `onMouseDown` too,
383
- // and won the race — but `pointerdown` PRECEDES `mousedown`, so the sheet closed
384
- // at finger-down and the commit landed on an unmounted tree: a tapped row kept the
385
- // old value, and tapping the search box or the X was equally fatal. `PickerSheet`
386
- // stops `mousedown` for exactly this reason, but that guard was written for
387
- // `useDropdown`'s listener and never covered this hook.
388
- const isPhone = usePhoneLayout();
389
- useOutsideClick([triggerRef, panelRef], close, open && !isPhone);
390
- // A layer on the Escape stack while the anchored panel is open (Wave 2, DUP-1), so a
391
- // dialog around the picker closes only after it (BUG-1). `ComboboxPanel` takes the key
392
- // where focus is. On a phone the `PickerSheet` is the layer, as it is the outside.
393
- useDismissLayer(open && !isPhone, closeToTrigger);
394
- // See `backCloses`. The same close path a pointer dismissal takes.
395
- useOverlayHistory(backCloses && open && !isPhone, close);
396
-
397
- // Reset the query each time the panel opens, and the highlighted row whenever the
398
- // visible set changes (a new query, or the panel opening or closing). Adjusted while
399
- // rendering against the previous values rather than in an effect, so the render that
400
- // opens the panel already searches for "" and highlights the first row.
401
- const [prevOpen, setPrevOpen] = useState(open);
402
- const [prevQuery, setPrevQuery] = useState(query);
403
- if (open !== prevOpen || query !== prevQuery) {
404
- const opened = open && !prevOpen;
405
- setPrevOpen(open);
406
- setPrevQuery(opened ? "" : query);
407
- if (opened) setQuery("");
408
- setActive(0);
409
- }
410
-
411
- // Focus the search box each time the panel opens.
412
- useEffect(() => {
413
- if (!open) return;
414
- const id = requestAnimationFrame(() => inputRef.current?.focus());
415
- return () => cancelAnimationFrame(id);
416
- }, [open]);
417
-
418
- // Debounced, race-safe async search (only when loadOptions is provided) or the
419
- // client-side filter — see {@link useOptionSource}.
420
- const { results, busy, failed, tooShort } = useOptionSource<V>({
421
- ...source,
422
- options,
423
- minChars,
424
- query,
425
- active: open,
426
- });
427
-
428
- // Accumulate every option we've seen so a selected value can render its label
429
- // even after the async list has moved on to other query results.
430
- const cacheRef = useRef(new Map<V, ComboOption<V>>());
431
- useEffect(() => {
432
- for (const o of options ?? []) cacheRef.current.set(o.value, o);
433
- for (const o of results) cacheRef.current.set(o.value, o);
434
- }, [options, results]);
435
- const resolve = (v: V): ComboOption<V> | null =>
436
- options?.find((o) => o.value === v) ?? cacheRef.current.get(v) ?? null;
437
- const rememberOption = (o: ComboOption<V>) => {
438
- cacheRef.current.set(o.value, o);
439
- };
440
-
441
- return {
442
- triggerRef,
443
- panelRef,
444
- inputRef,
445
- open,
446
- setOpen,
447
- query,
448
- setQuery,
449
- active,
450
- setActive,
451
- results,
452
- busy,
453
- failed,
454
- tooShort,
455
- minChars,
456
- rect,
457
- placement,
458
- rememberOption,
459
- resolve,
460
- closeToTrigger,
461
- };
462
- }
463
-
464
- // The family's `error` is every field's `useFieldError` (field-parts) since 0.34 — the
465
- // restatement that stood here ignored a form library's `aria-invalid` (DUP-8).
466
-
467
- /** The anchored suggestion list's box and rows — one look for {@link Combobox},
468
- * {@link InlineEntityCombobox} and {@link Autocomplete}. */
469
- /**
470
- * A group's heading in a picker list stays at the top of the list while its rows scroll
471
- * under it (kk run 79 gap 3): with first-letter groups over two hundred payees, the
472
- * heading is the one thing that says where in the alphabet the thumb is. Opaque — the
473
- * list's own `bg-surface` — so the rows pass beneath it rather than through it. Shared
474
- * by every list that heads groups (the panel and the sheet, `Combobox`, `Autocomplete`,
475
- * `InlineEntityCombobox`). Inside its {@link OptionGroup} since 0.52, it sticks while its
476
- * own group is in view and the next group's heading pushes it out.
477
- */
478
- export const GROUP_HEADING_STICKY = "sticky top-0 z-10 bg-surface";
479
-
480
- /** One run of rows that share a group, as {@link groupRuns} finds them: `group` is
481
- * `null` for rows filed under none, which stand bare in the list; `start` is the index
482
- * of the run's first row in the whole list (the option ids and the arrows count by it). */
483
- export interface GroupRun<T> {
484
- group: string | null;
485
- start: number;
486
- rows: T[];
487
- }
488
-
489
- /**
490
- * A list's rows, cut where the group changes — the runs {@link OptionGroup} wraps. The
491
- * lists keep their rows group-contiguous (the panel's source order, or the ranked list
492
- * regrouped by its best member), so a run is "the rows up to the next change of group".
493
- * `groupOf` is asked once per row. An empty group name is no group: a heading of nothing
494
- * names nothing.
495
- */
496
- export function groupRuns<T>(rows: readonly T[], groupOf: (row: T) => string | null | undefined): GroupRun<T>[] {
497
- const runs: GroupRun<T>[] = [];
498
- rows.forEach((row, i) => {
499
- const raw = groupOf(row);
500
- const group = raw === undefined || raw === null || raw === "" ? null : raw;
501
- const last = runs[runs.length - 1];
502
- if (last && last.group === group) last.rows.push(row);
503
- else runs.push({ group, start: i, rows: [row] });
504
- });
505
- return runs;
506
- }
507
-
508
- /**
509
- * A group of options under its heading, inside a `role="listbox"`: a `role="group"`
510
- * NAMED by the heading, so a reader moving through the list hears which group a row is
511
- * in (refactor audit 2026-10-30, INP-8). The heading was loose text among the options,
512
- * and focus stays in the search box with the row announced through
513
- * `aria-activedescendant` — keksdose's "Groceries" under Recent and again under its own
514
- * group were two identical names, and an account filed by its type never said the type.
515
- * The heading stays a visible first item of the group (sticky within it); the columns of
516
- * {@link ComboboxPanel} carry the same `role="group"`.
517
- *
518
- * `id` must be unique in the document (the list's id plus the run's start); `className`
519
- * places the heading's text in its list.
520
- */
521
- export function OptionGroup({
522
- id,
523
- heading,
524
- className,
525
- children,
526
- }: {
527
- id: string;
528
- heading: ReactNode;
529
- className?: string;
530
- children: ReactNode;
531
- }) {
532
- return (
533
- <li role="presentation">
534
- <ul role="group" aria-labelledby={id}>
535
- <li
536
- role="presentation"
537
- id={id}
538
- className={cn(GROUP_HEADING_STICKY, "font-semibold uppercase tracking-wide text-muted", className)}
539
- >
540
- {heading}
541
- </li>
542
- {children}
543
- </ul>
544
- </li>
545
- );
546
- }
547
-
548
- /**
549
- * A list's rows in their {@link groupRuns}: a run filed under no group stands bare, any
550
- * other is an {@link OptionGroup} under its heading. The one drawing of it for
551
- * {@link ComboboxPanel}, {@link Combobox}, {@link InlineEntityCombobox} and
552
- * {@link Autocomplete}, which had a copy each (0.52 code review, combobox-core.tsx).
553
- *
554
- * A run is keyed by its group and how many runs of that group came before it — NOT by
555
- * the index of its first row, which every keystroke that drops a row from an earlier
556
- * group moves: each later group was re-keyed and React rebuilt it, every row of it, per
557
- * keystroke. A row is keyed by `rowKey` among the rows of its run; an option may stand
558
- * in two groups (keksdose's "Recent", live #203), never twice in one.
559
- *
560
- * `row(o, i, grouped)`: `i` is the row's index in the whole list (the option ids and the
561
- * arrows count by it), `grouped` whether it stands under a heading. `headingClassName`
562
- * places a run's heading text in its list. The group's id is the list's id and the run's
563
- * first row, unique in the document.
564
- */
565
- export function GroupedOptions<T>({
566
- runs,
567
- listboxId,
568
- rowKey,
569
- row,
570
- headingClassName,
571
- }: {
572
- runs: readonly GroupRun<T>[];
573
- listboxId: string;
574
- rowKey: (o: T) => string;
575
- row: (o: T, index: number, grouped: boolean) => ReactNode;
576
- headingClassName: (run: GroupRun<T>) => string;
577
- }) {
578
- return keyRuns(runs).map(({ run, key }) => {
579
- const grouped = run.group !== null;
580
- const rows = run.rows.map((o, k) => <Fragment key={rowKey(o)}>{row(o, run.start + k, grouped)}</Fragment>);
581
- if (!grouped) return <Fragment key={key}>{rows}</Fragment>;
582
- return (
583
- <OptionGroup
584
- key={key}
585
- id={`${listboxId}-group-${run.start}`}
586
- heading={run.group}
587
- className={headingClassName(run)}
588
- >
589
- {rows}
590
- </OptionGroup>
591
- );
592
- });
593
- }
594
-
595
- /** Each run with its key — see {@link GroupedOptions}: the group and how many runs of it
596
- * came before, which a query narrowing an earlier group does not move. */
597
- function keyRuns<T>(runs: readonly GroupRun<T>[]): { run: GroupRun<T>; key: string }[] {
598
- const seen = new Map<string | null, number>();
599
- return runs.map((run) => {
600
- const nth = seen.get(run.group) ?? 0;
601
- seen.set(run.group, nth + 1);
602
- // The count first: it never holds a ":", so no group's name can make two keys equal.
603
- return { run, key: run.group === null ? String(nth) : `${nth}:${run.group}` };
604
- });
605
- }
606
-
607
- /** The anchored list's heading text: the first group's sits closer to the list's top
608
- * edge, unless a row stands above it (`first: false`, Combobox's create row). */
609
- export const anchoredHeadingClass = (run: GroupRun<unknown>, first = true) =>
610
- cn("px-3 pb-0.5 text-caption", run.start === 0 && first ? "pt-1" : "pt-2");
611
-
612
- /** The typed comboboxes' phone sheet's heading text ({@link Combobox},
613
- * {@link InlineEntityCombobox}). */
614
- export const SHEET_HEADING_CLASS = "px-4 pb-0.5 pt-3 text-caption";
615
-
616
- export const SUGGESTION_LIST_CLASS =
617
- "max-h-64 overflow-y-auto rounded-md border border-subtle bg-surface py-1 shadow-lg";
618
-
619
- // One line at Normal; from Large up a long suggestion wraps and its row grows (text size
620
- // §10.17, round 0.34 T1), like every other option list of the kit.
621
- export const suggestionRowClass = (isActive: boolean) =>
622
- cn(
623
- "block w-full truncate-until-large px-3 py-1.5 text-start text-sm text-primary",
624
- isActive ? "bg-active" : "hover:bg-hover",
625
- );
626
-
627
- /**
628
- * Keep the option the keyboard is on inside the visible part of the list.
629
- *
630
- * `aria-activedescendant` moves a highlight WITHOUT moving focus, which is the whole
631
- * point of it — and the cost is that the browser scrolls nothing on its own. In a
632
- * panel capped at a few hundred pixels over an account list two hundred long, Down
633
- * walked the highlight straight out of the box and the list sat still.
634
- *
635
- * Optional-called because jsdom does not implement `scrollIntoView`: three consumers
636
- * run this component in their own suites, and a picker that throws there is a worse
637
- * trade than a list that does not scroll in a test.
638
- */
639
- export function useActiveOptionScroll(activeId: string | undefined): void {
640
- useEffect(() => {
641
- if (!activeId) return;
642
- document.getElementById(activeId)?.scrollIntoView?.({ block: "nearest" });
643
- }, [activeId]);
644
- }
645
-
646
- /**
647
- * What a TYPED combobox's list says — {@link Combobox} and {@link InlineEntityCombobox},
648
- * which had a copy each, the second saying "see the twin" (0.52 code review,
649
- * inline-entity-combobox.tsx; the rules are refactor audit 2026-10-30, INP-6):
650
- *
651
- * - `showing`: the anchored list is on screen — open, not inert, with a row to show (an
652
- * option, or `extraRow`, the create row). The list is then a layer on the Escape stack
653
- * (Wave 2, DUP-1). Never on a phone, where the sheet is the list and the layer.
654
- * - `expanded`, the field's `aria-expanded`: a list on screen. An open list with nothing
655
- * to show said "expanded" with no listbox in the document.
656
- * - `activeOption`: the id of the row the keyboard is on — `active`, a TAKEABLE row's
657
- * index, else -1 — in whichever list shows, kept scrolled into view.
658
- * - `statusRegion`: a polite live region with the row count, or "No results" for a query
659
- * that left nothing to show or do. Focus stays in the field, or in the sheet's search
660
- * box, so a list changing under it was silent. A list opened on no query asked nothing.
661
- * Render it outside the list, so the list going cannot take it along: on a phone inside
662
- * the sheet, which hides from a reader what lies outside it. There it mounts with the
663
- * sheet, so the count the sheet opens with is not announced — as a ComboboxPanel's is
664
- * not, in either shell — and every change after it is.
665
- */
666
- export function useTypedListStatus({
667
- isPhone,
668
- open,
669
- inert,
670
- rowCount,
671
- extraRow,
672
- query,
673
- active,
674
- optionId,
675
- labels,
676
- }: {
677
- isPhone: boolean;
678
- open: boolean;
679
- inert: boolean;
680
- /** The options the list shows. */
681
- rowCount: number;
682
- /** A create row shows: the list is not empty, it offers to fill itself. */
683
- extraRow: boolean;
684
- query: string;
685
- active: number;
686
- optionId: (index: number) => string;
687
- labels: { resultCount: (count: number) => string; noResults: string };
688
- }): { showing: boolean; expanded: boolean; activeOption: string | undefined; statusRegion: ReactNode } {
689
- const showing = !isPhone && open && !inert && (rowCount > 0 || extraRow);
690
- const expanded = isPhone ? open && !inert : showing;
691
- const activeOption = expanded && active >= 0 ? optionId(active) : undefined;
692
- useActiveOptionScroll(activeOption);
693
- const status =
694
- !open || inert ? null : rowCount > 0 ? labels.resultCount(rowCount) : query && !extraRow ? labels.noResults : null;
695
- const statusRegion = (
696
- <div role="status" aria-live="polite" className="sr-only">
697
- {status}
698
- </div>
699
- );
700
- return { showing, expanded, activeOption, statusRegion };
701
- }
702
-
703
- /** The anchored panel's least width, in rem: 220 px at Normal (§3.2). */
704
- const PANEL_MIN_REM = 13.75;
705
-
706
- /** The room the anchored panel keeps from either edge of the glass, in px —
707
- * `anchoredInline`'s default. */
708
- const PANEL_EDGE = 8;
709
-
710
- /**
711
- * How wide the anchored panel is. The phone sheet is the screen's width whatever this
712
- * says.
713
- */
714
- export interface ComboboxPanelWidth {
715
- /** The least width, in rem — never under the trigger's own. Default 13.75 (220 px at
716
- * Normal, §3.2). */
717
- minRem?: number;
718
- /**
719
- * As wide as the rows make it (`max-content`), between the least width and `maxRem`,
720
- * instead of exactly the least — measured, so the clamp to the glass still knows it.
721
- * CurrencySelect's list grows to show the full currency names (feedback #308);
722
- * MultiSelect's rows to show their hints (keksdose #147). A `w-*` class in the panel's
723
- * `className` does not apply: the panel is portalled to <body>, where `w-full` would be
724
- * the screen.
725
- */
726
- fit?: boolean;
727
- /** With `fit`, the most it grows to, in rem. Default: the glass. */
728
- maxRem?: number;
729
- }
730
-
731
- export interface ComboboxPanelProps<V extends string | number> extends ComponentPropsWithoutRef<"div"> {
732
- core: ComboboxCore<V>;
733
- /** What the trigger's `aria-controls` names. Owned by the component that renders
734
- * the trigger, not by the core: one id has to reach both, and only one of them
735
- * can be the origin. */
736
- listboxId: string;
737
- /** Default: `combobox.search` from the {@link UiKitProvider}, else English. */
738
- searchPlaceholder?: string;
739
- /** Default: `combobox.noResults` from the {@link UiKitProvider}, else English. */
740
- emptyLabel?: string;
741
- multi?: boolean;
742
- isSelected: (v: V) => boolean;
743
- onChoose: (o: ComboOption<V>) => void;
744
- showCreate: boolean;
745
- onCreate: () => void;
746
- createContent: ReactNode;
747
- /**
748
- * The create row is shown but not takeable, and this says why — the lock's reason
749
- * when the picker's `createCommit` meets a locked {@link WriteLockProvider}. Rendered
750
- * as the row's second line rather than a tooltip, as {@link ComboOption.disabled}
751
- * asks of every disabled row: a hover is the one explanation a phone cannot show.
752
- */
753
- createDisabledReason?: ReactNode;
754
- /** What the sheet calls itself on a phone — the field's label. Ignored on
755
- * desktop, where the panel sits under the field that already says it. */
756
- sheetTitle?: ReactNode;
757
- closeLabel?: string;
758
- /** Default: `combobox.loadError` from the {@link UiKitProvider}, else English. */
759
- loadErrorLabel?: string;
760
- /**
761
- * What a row shows in place of its label and sublabel — for a row that says more than
762
- * two strings: CurrencySelect's symbol, code and name side by side. The option's
763
- * `icon`, the multi checkbox and the check mark stay the panel's. Its text is the
764
- * option's accessible name, so separate the parts with a space.
765
- */
766
- renderOption?: (o: ComboOption<V>) => ReactNode;
767
- /** Controls between the search box and the list, in the anchored panel and the phone
768
- * sheet alike — MultiSelect's "Select all" and "Clear". In the anchored panel Tab from
769
- * the search box walks them before it leaves (0.52); keep them focusable when they
770
- * have nothing to do (`aria-disabled`), or a press drops their focus to <body>. */
771
- toolbar?: ReactNode;
772
- /** See {@link ComboboxPanelWidth}. */
773
- width?: ComboboxPanelWidth;
774
- /** Which of the trigger's edges the anchored panel hangs from, by reading direction.
775
- * Default `start`; `end` grows the panel toward the start, so a field with content
776
- * after it is not pushed past the page (CurrencySelect, feedback #308). */
777
- align?: "start" | "end";
778
- /**
779
- * Lay the rows out in columns, one block per `group`, with the group's name above it —
780
- * GroupedPicker's panel (feedback #267/#276): a large grouped set reads as a sheet of
781
- * short lists side by side rather than one long one. Each block is a `role="group"`
782
- * named by its heading. The arrow keys walk the rows in reading order, down each
783
- * column. The phone sheet keeps one column with headings.
784
- */
785
- columns?: boolean;
786
- }
787
-
788
- /**
789
- * The portalled dropdown (search header + result rows + optional create row)
790
- * shared by the single- and multi-value comboboxes. Start-aligned to the trigger
791
- * and sized to its width; `multi` swaps the trailing check for a leading
792
- * checkbox. `onChoose` decides whether to close (single) or stay open (multi).
793
- *
794
- * ## The ARIA, which is `CommandPalette`'s
795
- *
796
- * This rendered a `role="listbox"` of `role="option"` rows and stopped there, so a
797
- * reader was handed a text box and silence: the highlight the arrow keys move lived
798
- * only in a background colour, and nothing tied the box being typed in to the list
799
- * being filtered. `CommandPalette` has had the whole model since it was written —
800
- * `aria-controls` from the field to the list, `aria-activedescendant` naming the row
801
- * the keyboard is on — and it was the only thing in the package that did.
802
- *
803
- * Two distinct states, deliberately not conflated:
804
- * - `aria-selected` is CHOSEN. It is the check mark, and in `multi` the checkbox.
805
- * - `aria-activedescendant` is where the KEYBOARD is. Announcing that as selected
806
- * would tell a reader that the row they are merely passing over is the answer.
807
- *
808
- * `role="option"` sits on the `<button>`, not on the `<li>` around it, for the same
809
- * reason it does in `CommandPalette`: an option may not contain a separately
810
- * focusable control, and the button is the thing a pointer presses. The rows are
811
- * `tabIndex={-1}` because focus never leaves the search box — that is what
812
- * `aria-activedescendant` is FOR.
813
- */
814
- export function ComboboxPanel<V extends string | number>({
815
- core,
816
- listboxId,
817
- searchPlaceholder,
818
- emptyLabel,
819
- multi,
820
- isSelected,
821
- onChoose,
822
- showCreate,
823
- onCreate,
824
- createContent,
825
- createDisabledReason,
826
- sheetTitle,
827
- closeLabel,
828
- loadErrorLabel,
829
- renderOption,
830
- toolbar,
831
- width,
832
- align = "start",
833
- columns,
834
- className,
835
- style,
836
- ...rest
837
- }: ComboboxPanelProps<V>) {
838
- const createLocked = hasContent(createDisabledReason);
839
- const {
840
- rect,
841
- placement,
842
- panelRef,
843
- inputRef,
844
- query,
845
- setQuery,
846
- results,
847
- busy,
848
- failed,
849
- tooShort,
850
- active,
851
- setActive,
852
- closeToTrigger,
853
- } = core;
854
- // The phone gets a full-screen sheet instead of an anchored panel (live #200).
855
- // Same core, same results, same handlers — only the container differs, so the
856
- // two presentations cannot drift in what they offer.
857
- const isPhone = usePhoneLayout();
858
- // Every row the keyboard can land on, the create row included — passed over, like a
859
- // disabled option, while a write lock holds it.
860
- const rows: readonly { disabled?: boolean }[] = showCreate ? [...results, { disabled: createLocked }] : results;
861
- const rowCount = rows.length;
862
- const optionId = (index: number) => `${listboxId}-option-${index}`;
863
- // The core opens on row 0 and knows nothing of `disabled`; the highlight is settled
864
- // HERE, derived, so it can never rest on a row that cannot be taken — whatever the
865
- // async list turned out to hold.
866
- const current = settleEnabled(rows, active);
867
- const activeId = current >= 0 ? optionId(current) : undefined;
868
- useActiveOptionScroll(activeId);
869
- // The consumers resolve their own props against the provider already; this is
870
- // for the loading row, which no consumer names, and for a caller that renders the
871
- // panel directly and leaves the two optional strings out.
872
- const labels = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS);
873
- // Portalled to <body>, the panel leaves the subtree its `dir` came from — so the
874
- // direction is read off the trigger and put back on the panel below.
875
- const dir = useAnchorDir(core.triggerRef, core.open);
876
- const remPx = useRemPx();
877
- // A `fit` panel's measured width — what it takes to keep it on the glass (see
878
- // `ComboboxPanelWidth.fit`). Observed rather than read once: the rows change under the
879
- // search, and a filtered list of short names is a narrower panel. `null` where nothing
880
- // is laid out (jsdom), and the least width is used.
881
- const fit = width?.fit === true && !isPhone;
882
- const [fitWidth, setFitWidth] = useState<number | null>(null);
883
- const placed = core.open && rect !== null;
884
- useLayoutEffect(() => {
885
- const el = panelRef.current;
886
- if (!fit || !placed || !el || typeof ResizeObserver === "undefined") return;
887
- const observer = new ResizeObserver(() => {
888
- const measured = el.getBoundingClientRect().width;
889
- setFitWidth(measured > 0 ? measured : null);
890
- });
891
- observer.observe(el);
892
- return () => observer.disconnect();
893
- }, [fit, placed, panelRef]);
894
-
895
- // The toolbar's own box, so the keyboard can tell its buttons from the search box.
896
- const toolbarRef = useRef<HTMLDivElement>(null);
897
- // The kit's one tab-stop rule (0.52 code review, lib/focus-left.ts): this had a third
898
- // copy of the selector, without its `hidden`/`inert` half. An `aria-disabled` button
899
- // stays a stop — it keeps its focus (INP-9).
900
- const toolbarStops = () => (toolbarRef.current ? tabStops(toolbarRef.current, { includeSelf: false }) : []);
901
- // A POINTER press on the toolbar leaves the focus in the search box, as a press on a
902
- // row does: since a toolbar button's keys are its own (INP-9), a mouse press — which
903
- // focuses a button in Chrome and Firefox — left the arrows on "Select all", where they
904
- // did nothing; until 0.52 they moved the list's highlight (0.52 code review,
905
- // combobox-core.tsx). Tab still reaches the buttons. A field in a toolbar keeps its
906
- // press: it is there to be typed in.
907
- const toolbarBox = hasContent(toolbar) && (
908
- // eslint-disable-next-line jsx-a11y/no-static-element-interactions -- never focused itself: it only keeps a pointer press on its buttons from taking the focus out of the search box.
909
- <div
910
- ref={toolbarRef}
911
- onMouseDown={(e) => {
912
- if (!(e.target as Element).closest("input, select, textarea, [contenteditable]")) e.preventDefault();
913
- }}
914
- >
915
- {toolbar}
916
- </div>
917
- );
918
- /** Tab on a toolbar button in the anchored panel: Shift+Tab off the first goes back to
919
- * the search box, Tab off the last leaves as the search box's Tab did; between two of
920
- * them the browser's own Tab is right, as they stand next to each other. */
921
- const tabInToolbar = (e: ReactKeyboardEvent) => {
922
- const stops = toolbarStops();
923
- const at = stops.indexOf(e.target as HTMLElement);
924
- if (e.shiftKey && at === 0) {
925
- e.preventDefault();
926
- inputRef.current?.focus();
927
- } else if (!e.shiftKey && at === stops.length - 1) {
928
- e.preventDefault();
929
- closeToTrigger();
930
- }
931
- };
932
-
933
- // The WAI-ARIA APG's listbox keyboard, in full: Up/Down step, Home/End jump, Enter
934
- // commits, Tab leaves, Escape closes. On the panel's search box — in the anchored
935
- // panel and, through `onSearchKeyDown`, in the phone sheet's header (BUG-5).
936
- const onKeyDown = (e: ReactKeyboardEvent) => {
937
- // Escape goes to the top of the kit's Escape stack — this list, which opened over
938
- // any dialog it sits in — and stops HERE (round 0.34 BUG-1, Wave 2 DUP-1). The
939
- // panel's events travel up the React tree through that dialog, portal or not, and
940
- // one press used to close the dialog and throw the form away, list still open.
941
- if (takeEscape(e)) return;
942
- // A key on a toolbar button is the button's — Enter and Space press it — and not the
943
- // list's: Enter there toggled the highlighted row instead of pressing "Select all".
944
- // Only Tab is the panel's (refactor audit 2026-10-30, INP-9). The phone sheet hands
945
- // this handler its search box's keys alone, and its trap keeps Tab.
946
- if (toolbarRef.current?.contains(e.target as Node)) {
947
- if (e.key === "Tab" && !isPhone) tabInToolbar(e);
948
- return;
949
- }
950
- // The Enter that confirms an IME composition is the IME's (BUG-9).
951
- if (isImeEnter(e)) return;
952
- // Disabled rows are passed over (`stepEnabled`), and each step starts from the
953
- // SETTLED highlight, not the raw index, so the first Down from a disabled row 0
954
- // goes to the row after the one already shown as highlighted.
955
- if (e.key === "ArrowDown") {
956
- e.preventDefault();
957
- setActive(stepEnabled(rows, current, 1));
958
- } else if (e.key === "ArrowUp") {
959
- e.preventDefault();
960
- setActive(current < 0 ? -1 : stepEnabled(rows, current, -1));
961
- } else if (e.key === "Home") {
962
- e.preventDefault();
963
- setActive(stepEnabled(rows, -1, 1));
964
- } else if (e.key === "End") {
965
- e.preventDefault();
966
- setActive(stepEnabled(rows, rowCount, -1));
967
- } else if (e.key === "Enter") {
968
- e.preventDefault();
969
- if (current < 0) return;
970
- if (current < results.length) onChoose(results[current]);
971
- else if (showCreate && !createLocked) onCreate();
972
- } else if (e.key === "Tab" && !isPhone) {
973
- // The panel is PORTALLED to <body>, so the browser's own Tab would move from
974
- // here to whatever follows the portal — i.e. off the end of the document. The
975
- // trade is one extra Tab (trigger, then onward) against never stranding focus
976
- // at the bottom of the page. The phone sheet is a dialog, and its trap keeps Tab.
977
- // A panel with a toolbar is walked through it first: the rows are not tab stops,
978
- // so MultiSelect's "Select all" and "Clear" were out of a keyboard's reach (INP-9).
979
- e.preventDefault();
980
- const first = e.shiftKey ? undefined : toolbarStops()[0];
981
- if (first) first.focus();
982
- else closeToTrigger();
983
- }
984
- };
985
-
986
- // What the list holds, said in ONE place that is both on screen and a polite
987
- // live region. Focus stays in the search box, so the list changing under it was
988
- // silent — a reader typed and heard nothing back, and a failed lookup was the same
989
- // silence as a slow one. Outside the listbox, which may own only options.
990
- //
991
- // Three different empties, and only one of them is "nothing matched": a lookup
992
- // that failed must not claim there is nothing to find, and a query under
993
- // `minChars` was never asked. With rows showing, the count is for the reader only.
994
- const message: ReactNode =
995
- busy && results.length === 0 ? (
996
- // The ellipsis is the picture; the words are for a reader, who otherwise met
997
- // an empty-looking list with a lone "…" in it — or, in most readers, nothing
998
- // at all, since punctuation alone is skipped.
999
- <>
1000
- <span aria-hidden>…</span>
1001
- <span className="sr-only">{labels.loading}</span>
1002
- </>
1003
- ) : busy ? (
1004
- // Loading with the LAST query's rows still up. This was `null`, so a refetch —
1005
- // a new query, or a caller's `loading` while it reloads — looked exactly like a
1006
- // finished list: rows that were about to be replaced, with nothing saying so.
1007
- // A line of its own under the rows, in words, so it reads without the spinner.
1008
- <span className="flex items-center gap-2">
1009
- <Spinner label={null} className="size-3.5 border" />
1010
- {labels.loading}
1011
- </span>
1012
- ) : results.length === 0 && !showCreate ? (
1013
- failed ? (
1014
- (loadErrorLabel ?? labels.loadError)
1015
- ) : tooShort ? (
1016
- labels.minChars(core.minChars)
1017
- ) : (
1018
- (emptyLabel ?? labels.noResults)
1019
- )
1020
- ) : null;
1021
- // `relative` so the sr-only text has a local containing block (see
1022
- // sr-only-containment.test).
1023
- const announcement = (
1024
- <div
1025
- role="status"
1026
- aria-live="polite"
1027
- className={cn(
1028
- "relative",
1029
- message !== null && cn("py-2 text-sm", isPhone ? "px-4" : "px-3"),
1030
- failed ? "text-danger" : "text-muted",
1031
- )}
1032
- >
1033
- {message ?? (results.length > 0 && <span className="sr-only">{labels.resultCount(results.length)}</span>)}
1034
- </div>
1035
- );
1036
-
1037
- if (!core.open || typeof document === "undefined") return null;
1038
-
1039
- // One option row — the same in both containers and in both layouts.
1040
- const optionRow = (o: ComboOption<V>, i: number) => {
1041
- const selected = isSelected(o.value);
1042
- return (
1043
- <li role="presentation">
1044
- <button
1045
- type="button"
1046
- id={optionId(i)}
1047
- role="option"
1048
- aria-selected={selected}
1049
- aria-disabled={o.disabled || undefined}
1050
- tabIndex={-1}
1051
- onMouseDown={(e) => {
1052
- e.preventDefault();
1053
- if (!o.disabled) onChoose(o);
1054
- }}
1055
- onMouseEnter={() => {
1056
- if (!o.disabled) setActive(i);
1057
- }}
1058
- className={cn(
1059
- isPhone
1060
- ? SHEET_ROW_CLASS
1061
- : columns
1062
- ? // A cell of a column (GroupedPicker): tighter, so four columns fit.
1063
- "flex w-full items-center gap-1.5 rounded px-1.5 py-0.5 text-start text-sm"
1064
- : "flex w-full items-center gap-2 px-3 py-1.5 text-start text-sm",
1065
- // The row the arrows are on shows it on a phone too: the sheet takes
1066
- // the keyboard (BUG-5), and a tablet or a desktop at Large has one.
1067
- o.disabled ? DISABLED_ROW_CLASS : i === current ? "bg-active" : !isPhone && "hover:bg-hover",
1068
- // After SHEET_ROW_CLASS, so its hover is the one cancelled.
1069
- o.disabled && isPhone && DISABLED_ROW_CLASS,
1070
- )}
1071
- >
1072
- {multi && (
1073
- <span
1074
- // The checkbox is a PICTURE of `aria-selected` above, so a reader
1075
- // that met both would be told twice.
1076
- aria-hidden
1077
- className={cn(
1078
- "flex size-4 shrink-0 items-center justify-center rounded border",
1079
- selected ? "border-[var(--bg-inverse)] bg-inverse text-inverse" : "border-strong bg-surface",
1080
- )}
1081
- >
1082
- {selected && <Check className="size-3" />}
1083
- </span>
1084
- )}
1085
- {o.icon && <span className="shrink-0">{o.icon}</span>}
1086
- {renderOption ? (
1087
- <span className="flex min-w-0 flex-1 items-center gap-2">{renderOption(o)}</span>
1088
- ) : (
1089
- /* Label and sublabel wrap from Large up (text size §10.17, round 0.34 T1). */
1090
- <span className="min-w-0 flex-1">
1091
- <span className={cn("block truncate-until-large text-primary", columns && selected && "font-medium")}>
1092
- {o.label}
1093
- </span>
1094
- {o.sublabel && <span className="block truncate-until-large text-xs text-placeholder">{o.sublabel}</span>}
1095
- </span>
1096
- )}
1097
- {!multi && selected && <Check className="size-4 shrink-0 text-brand" />}
1098
- </button>
1099
- </li>
1100
- );
1101
- };
1102
-
1103
- const createRow = showCreate && (
1104
- <li role="presentation">
1105
- <button
1106
- type="button"
1107
- id={optionId(results.length)}
1108
- role="option"
1109
- // Never chosen — it is an action, and it disappears the moment it has
1110
- // been taken. Whether the keyboard is ON it is `aria-activedescendant`'s
1111
- // job to say.
1112
- aria-selected={false}
1113
- aria-disabled={createLocked || undefined}
1114
- tabIndex={-1}
1115
- onMouseDown={(e) => {
1116
- e.preventDefault();
1117
- if (!createLocked) onCreate();
1118
- }}
1119
- onMouseEnter={() => {
1120
- if (!createLocked) setActive(results.length);
1121
- }}
1122
- className={cn(
1123
- isPhone ? SHEET_ROW_CLASS : "flex w-full items-center gap-2 px-3 py-1.5 text-start text-sm",
1124
- "text-secondary",
1125
- createLocked ? DISABLED_ROW_CLASS : current === results.length ? "bg-active" : !isPhone && "hover:bg-hover",
1126
- // After SHEET_ROW_CLASS, so its hover is the one cancelled.
1127
- createLocked && isPhone && DISABLED_ROW_CLASS,
1128
- )}
1129
- >
1130
- <Plus className="size-4 shrink-0" />
1131
- <span className="min-w-0 flex-1">
1132
- <span className="block truncate-until-large">{createContent}</span>
1133
- {createLocked && (
1134
- <span className="block truncate-until-large text-xs text-placeholder">{createDisabledReason}</span>
1135
- )}
1136
- </span>
1137
- </button>
1138
- </li>
1139
- );
1140
-
1141
- // The rows' group runs — `results` keeps its source order, so a run is the rows up to
1142
- // the next change of group. In columns, a block per run.
1143
- const runs = groupRuns(results, (o) => o.group);
1144
-
1145
- const list = (
1146
- <ul
1147
- id={listboxId}
1148
- role="listbox"
1149
- aria-multiselectable={multi}
1150
- // The rows are provisional while a lookup is in flight — whether or not the
1151
- // previous query's are still showing.
1152
- aria-busy={busy || undefined}
1153
- // It scrolls, and says so where jsdom can see it (see `CLIPS_ATTRIBUTE`): a
1154
- // Tooltip in a row then portals out in a test exactly as it does in the
1155
- // browser, instead of joining the option's accessible name (keksdose E8).
1156
- {...{ [CLIPS_ATTRIBUTE]: "" }}
1157
- className={cn(
1158
- "min-h-0 flex-1 overflow-y-auto",
1159
- columns && !isPhone ? "columns-2 gap-4 p-3 md:columns-3 xl:columns-4" : "py-1",
1160
- isPhone && "flex-none",
1161
- )}
1162
- >
1163
- {columns && !isPhone ? (
1164
- keyRuns(runs).map(({ run, key }) => (
1165
- // A column block: its heading is a picture of the group's name, which the
1166
- // `role="group"` carries for a reader — the listbox may own options and
1167
- // groups, and a heading of loose text in it is neither. Keyed as
1168
- // GroupedOptions keys a run, so a filter does not rebuild the later columns.
1169
- <li key={key} role="presentation" className="mb-3 break-inside-avoid">
1170
- {run.group !== null && (
1171
- <div aria-hidden className="mb-1 text-xs font-semibold uppercase tracking-wide text-muted">
1172
- {run.group}
1173
- </div>
1174
- )}
1175
- <ul role="group" aria-label={run.group ?? undefined} className="space-y-0.5">
1176
- {run.rows.map((o, k) => (
1177
- <Fragment key={String(o.value)}>{optionRow(o, run.start + k)}</Fragment>
1178
- ))}
1179
- </ul>
1180
- </li>
1181
- ))
1182
- ) : (
1183
- // One heading per group rather than a grey suffix on every row (#136), and the
1184
- // thing the native <select>'s <optgroup> used to give the account picker for
1185
- // free (live #200) — a `role="group"` it names (INP-8). Keksdose repeats
1186
- // recently-used categories in a "Recent" group at the top (live #203): a value
1187
- // is a row's key within its group alone.
1188
- <GroupedOptions
1189
- runs={runs}
1190
- listboxId={listboxId}
1191
- rowKey={(o) => String(o.value)}
1192
- row={optionRow}
1193
- headingClassName={(run) => (isPhone ? "px-4 pb-1 pt-3 text-xs" : anchoredHeadingClass(run))}
1194
- />
1195
- )}
1196
- {createRow}
1197
- </ul>
1198
- );
1199
-
1200
- // The search box is NAMED, not only prompted: a placeholder is gone the moment a
1201
- // character is typed, and jsdom's readers and some real ones never took it for a name
1202
- // (BUG-12: GroupedPicker's filter had "no name beyond its placeholder").
1203
- const searchText = searchPlaceholder ?? labels.search;
1204
-
1205
- if (isPhone) {
1206
- return (
1207
- <PickerSheet
1208
- // First, so the sheet's own props below win: `rest` is typed as div
1209
- // attributes, and the DOM's `title` is a tooltip string where the sheet's is
1210
- // its heading — one name, two meanings, and the sheet's is the one the caller
1211
- // asked for.
1212
- {...rest}
1213
- className={className}
1214
- style={style}
1215
- open
1216
- // The X, Done, Escape and Back hand focus to the trigger, as taking a row does:
1217
- // these triggers open on a click, never on focus, so focusing one re-opens
1218
- // nothing — and the sheet, which cannot know that, leaves focus on <body>
1219
- // (refactor audit 2026-10-30, INP-7).
1220
- onClose={closeToTrigger}
1221
- title={sheetTitle}
1222
- query={query}
1223
- onQueryChange={setQuery}
1224
- searchPlaceholder={searchText}
1225
- searchLabel={searchText}
1226
- inputRef={inputRef}
1227
- closeLabel={closeLabel}
1228
- // The sheet's search box is in its HEADER, outside the list: the keys go to it
1229
- // there, and it names the list and the active row (BUG-5). A div around the list
1230
- // used to catch them, and never did — focus is in the box, not in the list.
1231
- onSearchKeyDown={onKeyDown}
1232
- listboxId={listboxId}
1233
- activeId={activeId}
1234
- // A multi list stays open as rows are ticked, so the sheet needs a way out
1235
- // that is not the X in its corner: the kit's sticky "Done" (keksdose #412).
1236
- // A single pick closes the sheet itself.
1237
- done={multi}
1238
- >
1239
- {toolbarBox}
1240
- <div className="relative">
1241
- {list}
1242
- {announcement}
1243
- </div>
1244
- </PickerSheet>
1245
- );
1246
- }
1247
-
1248
- if (!rect) return null;
1249
- // The panel hangs from the trigger's START edge (or, `align="end"`, its end): its left
1250
- // in a left-to-right form, its right in a right-to-left one, at least 13.75rem wide
1251
- // (220 px at Normal, in rem so its text keeps its room at Large, §3.2; `width.minRem`)
1252
- // and never wider than the glass — and clamped to the glass, which in a left-to-right
1253
- // form it never was (Wave 2, DUP-7). It is placed by the edge it hangs from, so a `fit`
1254
- // panel grows away from the trigger's edge before its width is measured; a box hung by
1255
- // its `right` is measured from inside any scrollbar, as the glass's width is.
1256
- const glass = viewportSize().width - 2 * PANEL_EDGE;
1257
- const least = Math.min(Math.max(rect.width, (width?.minRem ?? PANEL_MIN_REM) * remPx), glass);
1258
- const most = Math.max(least, Math.min(width?.maxRem !== undefined ? width.maxRem * remPx : glass, glass));
1259
- const panelWidth = fit ? Math.min(Math.max(fitWidth ?? least, least), most) : least;
1260
- const at = anchoredInline(rect, panelWidth, { dir, align, edge: PANEL_EDGE });
1261
- const inline = (dir === "rtl") === (align === "start") ? { right: at.right } : { left: at.left };
1262
-
1263
- return createPortal(
1264
- /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- never focused
1265
- itself: it catches the listbox keys bubbling from the search <input> inside it,
1266
- which holds focus while the panel is open. */
1267
- <div
1268
- // Spread FIRST: `onKeyDown` below is the listbox keyboard (Up/Down/Home/End/
1269
- // Enter/Tab) and the placement is measured rather than chosen, so neither is a
1270
- // caller's to replace by accident — while a `data-*` anchor or an
1271
- // `aria-describedby` has nothing to collide with.
1272
- {...rest}
1273
- ref={panelRef}
1274
- // A kit layer, for the tour's layer order (0.50, docs/tours-harmonization.md §11.6).
1275
- data-kit-layer=""
1276
- dir={dir}
1277
- onKeyDown={onKeyDown}
1278
- // A press in a panel opened from a row is inside this list for the core's outside
1279
- // press, though portalled elsewhere (dev#623).
1280
- onPointerDownCapture={(e) => {
1281
- rest.onPointerDownCapture?.(e);
1282
- pressInside(panelRef)?.(e);
1283
- }}
1284
- // `placement` keeps the panel inside the region actually on screen: on a phone
1285
- // the search box below pulls up the keyboard, and a panel pinned under a
1286
- // low trigger would otherwise sit entirely behind it (feedback #135). Flipped
1287
- // above, it hangs from its bottom edge, so a list shorter than its cap still meets
1288
- // the trigger (keksdose K1); the edge not in use is cleared, a caller's included.
1289
- style={{
1290
- ...style,
1291
- position: "fixed",
1292
- top: placement.above ? undefined : placement.top,
1293
- bottom: placement.above ? placement.bottom : undefined,
1294
- ...inline,
1295
- // A `fit` panel takes its width from its rows between the two bounds; any other
1296
- // is exactly the least width.
1297
- ...(fit ? { width: "max-content", minWidth: least, maxWidth: most } : { width: panelWidth }),
1298
- maxHeight: placement.maxHeight,
1299
- }}
1300
- className={cn("z-50 flex flex-col rounded-md border border-subtle bg-surface shadow-lg", className)}
1301
- >
1302
- <DropdownSearchHeader
1303
- query={query}
1304
- onQueryChange={setQuery}
1305
- inputRef={inputRef}
1306
- placeholder={searchText}
1307
- aria-label={searchText}
1308
- // Focus is in this box while the list is up, so this is what has to name the
1309
- // list and the row the arrows are on.
1310
- listboxId={listboxId}
1311
- activeId={activeId}
1312
- />
1313
- {toolbarBox}
1314
- {/* One list, two containers: the desktop panel and the phone sheet render
1315
- the same rows through the same handlers (live #200). */}
1316
- {list}
1317
- {announcement}
1318
- </div>,
1319
- document.body,
1320
- );
1321
- }