@eifi1/ui-kit 0.53.0 → 0.55.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (902) hide show
  1. package/README.md +96 -82
  2. package/dist/account/delete-account-setting.d.ts +3 -2
  3. package/dist/account/delete-account-setting.js.map +1 -1
  4. package/dist/admin/plan-change.js +2 -2
  5. package/dist/admin/plan-change.js.map +1 -1
  6. package/dist/admin/user-roster.d.ts +6 -0
  7. package/dist/admin/user-roster.js +4 -0
  8. package/dist/admin/user-roster.js.map +1 -1
  9. package/dist/auth/auth-errors.d.ts +36 -0
  10. package/dist/auth/auth-errors.js +20 -0
  11. package/dist/auth/auth-errors.js.map +1 -1
  12. package/dist/billing/billing-labels.d.ts +26 -0
  13. package/dist/billing/billing-labels.js +9 -0
  14. package/dist/billing/billing-labels.js.map +1 -1
  15. package/dist/billing/subscription-standing.d.ts +61 -0
  16. package/dist/billing/subscription-standing.js +51 -0
  17. package/dist/billing/subscription-standing.js.map +1 -0
  18. package/dist/billing/usage-meter.d.ts +52 -0
  19. package/dist/billing/usage-meter.js +52 -0
  20. package/dist/billing/usage-meter.js.map +1 -0
  21. package/dist/chart.d.ts +1 -0
  22. package/dist/chart.js +4 -0
  23. package/dist/chart.js.map +1 -1
  24. package/dist/components/alert-banner.js +2 -3
  25. package/dist/components/alert-banner.js.map +1 -1
  26. package/dist/components/amount-input.d.ts +2 -1
  27. package/dist/components/amount-input.js +3 -15
  28. package/dist/components/amount-input.js.map +1 -1
  29. package/dist/components/autocomplete.js +2 -2
  30. package/dist/components/autocomplete.js.map +1 -1
  31. package/dist/components/bulk-action-bar.js +2 -1
  32. package/dist/components/bulk-action-bar.js.map +1 -1
  33. package/dist/components/button.d.ts +35 -1
  34. package/dist/components/button.js +5 -9
  35. package/dist/components/button.js.map +1 -1
  36. package/dist/components/calendar-heatmap.js +6 -4
  37. package/dist/components/calendar-heatmap.js.map +1 -1
  38. package/dist/components/camera-button.d.ts +89 -0
  39. package/dist/components/camera-button.js +122 -0
  40. package/dist/components/camera-button.js.map +1 -0
  41. package/dist/components/camera-capture.d.ts +66 -0
  42. package/dist/components/camera-capture.js +190 -0
  43. package/dist/components/camera-capture.js.map +1 -0
  44. package/dist/components/camera-labels.d.ts +20 -0
  45. package/dist/components/camera-labels.js +15 -0
  46. package/dist/components/camera-labels.js.map +1 -0
  47. package/dist/components/chart-edge.d.ts +9 -0
  48. package/dist/components/chart-edge.js +14 -3
  49. package/dist/components/chart-edge.js.map +1 -1
  50. package/dist/components/chart-zoom-core.js +34 -29
  51. package/dist/components/chart-zoom-core.js.map +1 -1
  52. package/dist/components/checkbox-group.js +1 -1
  53. package/dist/components/checkbox-group.js.map +1 -1
  54. package/dist/components/combobox-core.js +3 -2
  55. package/dist/components/combobox-core.js.map +1 -1
  56. package/dist/components/combobox.js +2 -2
  57. package/dist/components/combobox.js.map +1 -1
  58. package/dist/components/consent-again-notice-labels.d.ts +25 -0
  59. package/dist/components/consent-again-notice-labels.js +15 -0
  60. package/dist/components/consent-again-notice-labels.js.map +1 -0
  61. package/dist/components/consent-again-notice.d.ts +85 -0
  62. package/dist/components/consent-again-notice.js +94 -0
  63. package/dist/components/consent-again-notice.js.map +1 -0
  64. package/dist/components/country-select.js +2 -7
  65. package/dist/components/country-select.js.map +1 -1
  66. package/dist/components/currency-option-list.d.ts +2 -1
  67. package/dist/components/currency-option-list.js +10 -3
  68. package/dist/components/currency-option-list.js.map +1 -1
  69. package/dist/components/currency-select.d.ts +3 -1
  70. package/dist/components/currency-select.js +2 -2
  71. package/dist/components/currency-select.js.map +1 -1
  72. package/dist/components/danger-confirm.d.ts +22 -1
  73. package/dist/components/danger-confirm.js +52 -30
  74. package/dist/components/danger-confirm.js.map +1 -1
  75. package/dist/components/data-table-column-settings.js +2 -2
  76. package/dist/components/data-table-column-settings.js.map +1 -1
  77. package/dist/components/data-table-filter-popover.d.ts +2 -1
  78. package/dist/components/data-table-filter-popover.js +3 -2
  79. package/dist/components/data-table-filter-popover.js.map +1 -1
  80. package/dist/components/data-table-filters.d.ts +33 -3
  81. package/dist/components/data-table-filters.js +6 -4
  82. package/dist/components/data-table-filters.js.map +1 -1
  83. package/dist/components/data-table-fit.js.map +1 -1
  84. package/dist/components/data-table-virtual.js +13 -3
  85. package/dist/components/data-table-virtual.js.map +1 -1
  86. package/dist/components/date-field.js +2 -2
  87. package/dist/components/date-field.js.map +1 -1
  88. package/dist/components/date-picker.js +2 -2
  89. package/dist/components/date-picker.js.map +1 -1
  90. package/dist/components/date-range-picker.js +2 -6
  91. package/dist/components/date-range-picker.js.map +1 -1
  92. package/dist/components/dialog-frame.d.ts +5 -1
  93. package/dist/components/dialog-frame.js +6 -2
  94. package/dist/components/dialog-frame.js.map +1 -1
  95. package/dist/components/dropdown.js +7 -3
  96. package/dist/components/dropdown.js.map +1 -1
  97. package/dist/components/entity-combobox.js +2 -2
  98. package/dist/components/entity-combobox.js.map +1 -1
  99. package/dist/components/field-parts.d.ts +108 -6
  100. package/dist/components/field-parts.js +59 -6
  101. package/dist/components/field-parts.js.map +1 -1
  102. package/dist/components/field-suggestion-labels.d.ts +2 -0
  103. package/dist/components/field-suggestion-labels.js +10 -0
  104. package/dist/components/field-suggestion-labels.js.map +1 -0
  105. package/dist/components/field.js +1 -1
  106. package/dist/components/field.js.map +1 -1
  107. package/dist/components/file-button.d.ts +1 -1
  108. package/dist/components/file-button.js.map +1 -1
  109. package/dist/components/floating-panel.d.ts +7 -6
  110. package/dist/components/floating-panel.js +3 -17
  111. package/dist/components/floating-panel.js.map +1 -1
  112. package/dist/components/form-actions.d.ts +3 -2
  113. package/dist/components/form-actions.js.map +1 -1
  114. package/dist/components/hover-menu.js +3 -2
  115. package/dist/components/hover-menu.js.map +1 -1
  116. package/dist/components/icon-button.d.ts +23 -5
  117. package/dist/components/icon-button.js +6 -8
  118. package/dist/components/icon-button.js.map +1 -1
  119. package/dist/components/inline-confirm.js +2 -1
  120. package/dist/components/inline-confirm.js.map +1 -1
  121. package/dist/components/inline-entity-combobox.js +2 -2
  122. package/dist/components/inline-entity-combobox.js.map +1 -1
  123. package/dist/components/input.js +2 -2
  124. package/dist/components/input.js.map +1 -1
  125. package/dist/components/jump-calendar.js +3 -3
  126. package/dist/components/jump-calendar.js.map +1 -1
  127. package/dist/components/legal.d.ts +13 -1
  128. package/dist/components/legal.js +12 -0
  129. package/dist/components/legal.js.map +1 -1
  130. package/dist/components/legended-series-chart.d.ts +80 -0
  131. package/dist/components/legended-series-chart.js +70 -0
  132. package/dist/components/legended-series-chart.js.map +1 -0
  133. package/dist/components/lightbox.js +2 -1
  134. package/dist/components/lightbox.js.map +1 -1
  135. package/dist/components/mini-calendar.js +16 -7
  136. package/dist/components/mini-calendar.js.map +1 -1
  137. package/dist/components/modal.d.ts +48 -0
  138. package/dist/components/modal.js +26 -6
  139. package/dist/components/modal.js.map +1 -1
  140. package/dist/components/month-picker.d.ts +1 -1
  141. package/dist/components/month-picker.js +6 -6
  142. package/dist/components/month-picker.js.map +1 -1
  143. package/dist/components/multi-entity-combobox.js +2 -2
  144. package/dist/components/multi-entity-combobox.js.map +1 -1
  145. package/dist/components/multi-select.js +2 -6
  146. package/dist/components/multi-select.js.map +1 -1
  147. package/dist/components/number-input.js +2 -2
  148. package/dist/components/number-input.js.map +1 -1
  149. package/dist/components/page-header-layout.d.ts +18 -0
  150. package/dist/components/page-header-layout.js +23 -0
  151. package/dist/components/page-header-layout.js.map +1 -0
  152. package/dist/components/page-header-skeleton.d.ts +41 -0
  153. package/dist/components/page-header-skeleton.js +52 -0
  154. package/dist/components/page-header-skeleton.js.map +1 -0
  155. package/dist/components/page-header.js +4 -17
  156. package/dist/components/page-header.js.map +1 -1
  157. package/dist/components/popover.js +7 -2
  158. package/dist/components/popover.js.map +1 -1
  159. package/dist/components/release-notes-labels.d.ts +16 -0
  160. package/dist/components/release-notes-labels.js +18 -0
  161. package/dist/components/release-notes-labels.js.map +1 -0
  162. package/dist/components/release-notes.d.ts +65 -0
  163. package/dist/components/release-notes.js +65 -0
  164. package/dist/components/release-notes.js.map +1 -0
  165. package/dist/components/select.js +2 -2
  166. package/dist/components/select.js.map +1 -1
  167. package/dist/components/series-chart-labels.d.ts +9 -0
  168. package/dist/components/series-chart-labels.js +3 -1
  169. package/dist/components/series-chart-labels.js.map +1 -1
  170. package/dist/components/series-chart.d.ts +5 -0
  171. package/dist/components/series-chart.js +89 -36
  172. package/dist/components/series-chart.js.map +1 -1
  173. package/dist/components/stat-tile.d.ts +21 -2
  174. package/dist/components/stat-tile.js +4 -2
  175. package/dist/components/stat-tile.js.map +1 -1
  176. package/dist/components/toggle-group.js +3 -2
  177. package/dist/components/toggle-group.js.map +1 -1
  178. package/dist/components/treemap-layout.js.map +1 -1
  179. package/dist/components/treemap.d.ts +17 -15
  180. package/dist/components/treemap.js +167 -85
  181. package/dist/components/treemap.js.map +1 -1
  182. package/dist/currencies.d.ts +16 -2
  183. package/dist/currencies.js +2 -1
  184. package/dist/currencies.js.map +1 -1
  185. package/dist/eslint/index.js +1 -1
  186. package/dist/eslint/role-utilities.d.ts +2 -2
  187. package/dist/eslint/role-utilities.js.map +1 -1
  188. package/dist/feedback/feedback-context-labels.js +2 -1
  189. package/dist/feedback/feedback-context-labels.js.map +1 -1
  190. package/dist/feedback/feedback-context.d.ts +19 -3
  191. package/dist/feedback/feedback-context.js +12 -1
  192. package/dist/feedback/feedback-context.js.map +1 -1
  193. package/dist/feedback/feedback-diagnostics.d.ts +17 -0
  194. package/dist/feedback/feedback-diagnostics.js +27 -0
  195. package/dist/feedback/feedback-diagnostics.js.map +1 -0
  196. package/dist/feedback/feedback-dialog.d.ts +10 -0
  197. package/dist/feedback/feedback-dialog.js +6 -4
  198. package/dist/feedback/feedback-dialog.js.map +1 -1
  199. package/dist/feedback/feedback-inbox-page.d.ts +260 -0
  200. package/dist/feedback/feedback-inbox-page.js +207 -0
  201. package/dist/feedback/feedback-inbox-page.js.map +1 -0
  202. package/dist/feedback/feedback-record.d.ts +5 -0
  203. package/dist/feedback/feedback-record.js.map +1 -1
  204. package/dist/feedback/feedback-row-detail-labels.js +1 -0
  205. package/dist/feedback/feedback-row-detail-labels.js.map +1 -1
  206. package/dist/feedback/feedback-row-detail.d.ts +3 -0
  207. package/dist/feedback/feedback-row-detail.js +3 -0
  208. package/dist/feedback/feedback-row-detail.js.map +1 -1
  209. package/dist/feedback/feedback-submit-dialog.d.ts +5 -1
  210. package/dist/feedback/feedback-submit-dialog.js +15 -3
  211. package/dist/feedback/feedback-submit-dialog.js.map +1 -1
  212. package/dist/feedback/feedback-submit.d.ts +30 -2
  213. package/dist/feedback/feedback-submit.js +5 -2
  214. package/dist/feedback/feedback-submit.js.map +1 -1
  215. package/dist/feedback/feedback-vocabulary.js +2 -2
  216. package/dist/feedback/feedback-vocabulary.js.map +1 -1
  217. package/dist/feedback.d.ts +1 -0
  218. package/dist/feedback.js +6 -0
  219. package/dist/feedback.js.map +1 -1
  220. package/dist/hooks/use-debounce.d.ts +32 -5
  221. package/dist/hooks/use-debounce.js +6 -5
  222. package/dist/hooks/use-debounce.js.map +1 -1
  223. package/dist/hooks/use-file-picker.d.ts +3 -1
  224. package/dist/hooks/use-file-picker.js.map +1 -1
  225. package/dist/hooks/use-focus-trap.d.ts +19 -3
  226. package/dist/hooks/use-focus-trap.js +15 -5
  227. package/dist/hooks/use-focus-trap.js.map +1 -1
  228. package/dist/hooks/use-has-camera.d.ts +36 -0
  229. package/dist/hooks/use-has-camera.js +53 -0
  230. package/dist/hooks/use-has-camera.js.map +1 -0
  231. package/dist/hooks/use-hotkey.js +2 -1
  232. package/dist/hooks/use-hotkey.js.map +1 -1
  233. package/dist/hooks/use-overlay-history.d.ts +6 -1
  234. package/dist/hooks/use-overlay-history.js +10 -2
  235. package/dist/hooks/use-overlay-history.js.map +1 -1
  236. package/dist/i18n/defaults.js +11 -1
  237. package/dist/i18n/defaults.js.map +1 -1
  238. package/dist/i18n/german.js +78 -4
  239. package/dist/i18n/german.js.map +1 -1
  240. package/dist/i18n/kit-labels.d.ts +20 -0
  241. package/dist/i18n/kit-labels.js.map +1 -1
  242. package/dist/i18n/locales/en.js +95 -5
  243. package/dist/i18n/locales/en.js.map +3 -3
  244. package/dist/i18n/locales/es.js +74 -4
  245. package/dist/i18n/locales/es.js.map +1 -1
  246. package/dist/i18n/locales/fr.js +75 -3
  247. package/dist/i18n/locales/fr.js.map +1 -1
  248. package/dist/i18n/locales/hu.js +77 -4
  249. package/dist/i18n/locales/hu.js.map +1 -1
  250. package/dist/i18n/locales/it.js +77 -4
  251. package/dist/i18n/locales/it.js.map +1 -1
  252. package/dist/i18n/locales/zh.js +74 -4
  253. package/dist/i18n/locales/zh.js.map +1 -1
  254. package/dist/i18n/review.js +22 -1
  255. package/dist/i18n/review.js.map +1 -1
  256. package/dist/index.d.ts +27 -8
  257. package/dist/index.js +41 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/landing/landing-labels.d.ts +8 -0
  260. package/dist/landing/landing-labels.js +2 -1
  261. package/dist/landing/landing-labels.js.map +1 -1
  262. package/dist/lib/changelog.d.ts +130 -0
  263. package/dist/lib/changelog.js +137 -0
  264. package/dist/lib/changelog.js.map +1 -0
  265. package/dist/lib/clipboard-minus.js +2 -2
  266. package/dist/lib/clipboard-minus.js.map +1 -1
  267. package/dist/lib/dates.d.ts +36 -4
  268. package/dist/lib/dates.js +49 -13
  269. package/dist/lib/dates.js.map +1 -1
  270. package/dist/lib/editable-content.d.ts +33 -0
  271. package/dist/lib/editable-content.js +15 -0
  272. package/dist/lib/editable-content.js.map +1 -0
  273. package/dist/lib/focus-left.d.ts +47 -1
  274. package/dist/lib/focus-left.js +11 -2
  275. package/dist/lib/focus-left.js.map +1 -1
  276. package/dist/lib/format.d.ts +37 -2
  277. package/dist/lib/format.js +25 -1
  278. package/dist/lib/format.js.map +1 -1
  279. package/dist/lib/intl-cache.d.ts +4 -2
  280. package/dist/lib/intl-cache.js.map +1 -1
  281. package/dist/lib/server-wake.d.ts +49 -9
  282. package/dist/lib/server-wake.js +25 -14
  283. package/dist/lib/server-wake.js.map +1 -1
  284. package/dist/lib/session-refresh.d.ts +53 -0
  285. package/dist/lib/session-refresh.js +27 -1
  286. package/dist/lib/session-refresh.js.map +1 -1
  287. package/dist/lib/translation-review.d.ts +26 -0
  288. package/dist/lib/translation-review.js +14 -0
  289. package/dist/lib/translation-review.js.map +1 -1
  290. package/dist/rhf/fields.js +3 -3
  291. package/dist/rhf/fields.js.map +1 -1
  292. package/dist/shell/app-shell.d.ts +25 -1
  293. package/dist/shell/app-shell.js +3 -1
  294. package/dist/shell/app-shell.js.map +1 -1
  295. package/dist/shell/dev-menu.d.ts +3 -0
  296. package/dist/shell/dev-menu.js +4 -2
  297. package/dist/shell/dev-menu.js.map +1 -1
  298. package/dist/shell/held-changes-banner-labels.d.ts +23 -0
  299. package/dist/shell/held-changes-banner-labels.js +14 -0
  300. package/dist/shell/held-changes-banner-labels.js.map +1 -0
  301. package/dist/shell/held-changes-banner.d.ts +75 -0
  302. package/dist/shell/held-changes-banner.js +106 -0
  303. package/dist/shell/held-changes-banner.js.map +1 -0
  304. package/dist/shell/sync-status-indicator.d.ts +27 -12
  305. package/dist/shell/sync-status-indicator.js +13 -4
  306. package/dist/shell/sync-status-indicator.js.map +1 -1
  307. package/dist/shell/top-bar-parts-labels.d.ts +5 -0
  308. package/dist/shell/top-bar-parts-labels.js +3 -1
  309. package/dist/shell/top-bar-parts-labels.js.map +1 -1
  310. package/dist/shell.d.ts +2 -0
  311. package/dist/shell.js +3 -0
  312. package/dist/shell.js.map +1 -1
  313. package/dist/sync/engine.d.ts +11 -2
  314. package/dist/sync/engine.js +13 -5
  315. package/dist/sync/engine.js.map +1 -1
  316. package/dist/sync/hooks.d.ts +3 -2
  317. package/dist/sync/hooks.js +4 -2
  318. package/dist/sync/hooks.js.map +1 -1
  319. package/dist/testing/css-variables.generated.d.ts +2 -2
  320. package/dist/testing/css-variables.generated.js +1 -1
  321. package/dist/testing/css-variables.generated.js.map +1 -1
  322. package/dist/theme/chart-palette.d.ts +18 -0
  323. package/dist/theme/chart-palette.js +17 -1
  324. package/dist/theme/chart-palette.js.map +1 -1
  325. package/dist/theme/contrast-tokens.d.ts +3 -3
  326. package/dist/theme/contrast-tokens.js.map +1 -1
  327. package/dist/theme/contrast.d.ts +4 -4
  328. package/dist/theme/contrast.js.map +1 -1
  329. package/dist/tour/tour-overlay.js +2 -1
  330. package/dist/tour/tour-overlay.js.map +1 -1
  331. package/package.json +5 -9
  332. package/tokens.css +19 -54
  333. package/src/account/account-parts.ts +0 -47
  334. package/src/account/account-settings-labels.ts +0 -240
  335. package/src/account/account-settings.tsx +0 -698
  336. package/src/account/data-export-setting-labels.ts +0 -20
  337. package/src/account/data-export-setting.tsx +0 -200
  338. package/src/account/delete-account-setting-labels.ts +0 -33
  339. package/src/account/delete-account-setting.tsx +0 -341
  340. package/src/account/email-change-setting-labels.ts +0 -31
  341. package/src/account/email-change-setting.tsx +0 -443
  342. package/src/account/passkeys-setting.tsx +0 -445
  343. package/src/account/sessions-setting-labels.ts +0 -24
  344. package/src/account/sessions-setting.tsx +0 -283
  345. package/src/admin/admin-action-confirm-labels.ts +0 -19
  346. package/src/admin/admin-action-confirm.tsx +0 -509
  347. package/src/admin/admin-action-log-labels.ts +0 -83
  348. package/src/admin/admin-action-log.tsx +0 -464
  349. package/src/admin/admin-area-labels.ts +0 -114
  350. package/src/admin/admin-forbidden.tsx +0 -75
  351. package/src/admin/admin-overview.tsx +0 -258
  352. package/src/admin/admin-parts.ts +0 -68
  353. package/src/admin/admin-sections.tsx +0 -208
  354. package/src/admin/admin-tool-links.tsx +0 -90
  355. package/src/admin/invitations-panel-labels.ts +0 -31
  356. package/src/admin/invitations-panel.tsx +0 -516
  357. package/src/admin/mail-test-card-labels.ts +0 -19
  358. package/src/admin/mail-test-card.tsx +0 -189
  359. package/src/admin/plan-change-labels.ts +0 -5
  360. package/src/admin/plan-change.tsx +0 -474
  361. package/src/admin/reviewer-scope-editor-labels.ts +0 -16
  362. package/src/admin/reviewer-scope-editor.tsx +0 -224
  363. package/src/admin/role-select-labels.ts +0 -16
  364. package/src/admin/role-select.tsx +0 -282
  365. package/src/admin/transfer-ownership-dialog-labels.ts +0 -20
  366. package/src/admin/transfer-ownership-dialog.tsx +0 -162
  367. package/src/admin/user-roster-labels.ts +0 -17
  368. package/src/admin/user-roster.tsx +0 -559
  369. package/src/auth/accept-invitation-labels.ts +0 -21
  370. package/src/auth/accept-invitation.tsx +0 -210
  371. package/src/auth/auth-errors-labels.ts +0 -23
  372. package/src/auth/auth-errors.ts +0 -370
  373. package/src/auth/complete-name-dialog-labels.ts +0 -16
  374. package/src/auth/complete-name-dialog.tsx +0 -253
  375. package/src/auth/email-tag.ts +0 -75
  376. package/src/auth/forgot-password-form-labels.ts +0 -20
  377. package/src/auth/forgot-password-form.tsx +0 -221
  378. package/src/auth/form-rules.ts +0 -40
  379. package/src/auth/new-password-fields.tsx +0 -85
  380. package/src/auth/not-found-page-labels.ts +0 -12
  381. package/src/auth/not-found-page.tsx +0 -95
  382. package/src/auth/register-form-labels.ts +0 -31
  383. package/src/auth/register-form.tsx +0 -456
  384. package/src/auth/reset-password-form-labels.ts +0 -27
  385. package/src/auth/reset-password-form.tsx +0 -341
  386. package/src/auth/sign-in-form-labels.ts +0 -44
  387. package/src/auth/sign-in-form.tsx +0 -858
  388. package/src/auth/status-parts.tsx +0 -132
  389. package/src/auth/verify-email-labels.ts +0 -26
  390. package/src/auth/verify-email.tsx +0 -445
  391. package/src/billing/billing-banner.tsx +0 -258
  392. package/src/billing/billing-labels.ts +0 -381
  393. package/src/billing/billing-lock.ts +0 -108
  394. package/src/billing/billing-overview.ts +0 -157
  395. package/src/billing/billing-standing.ts +0 -80
  396. package/src/billing/cancel-request.ts +0 -97
  397. package/src/billing/cancel-subscription.tsx +0 -98
  398. package/src/billing/checkout-processing.ts +0 -394
  399. package/src/billing/checkout-return.ts +0 -90
  400. package/src/billing/pay-page.ts +0 -330
  401. package/src/billing/plan-card.tsx +0 -562
  402. package/src/billing/plan-change-labels.ts +0 -66
  403. package/src/billing/plan-limit-notice.tsx +0 -148
  404. package/src/billing/plan-limit-toast.tsx +0 -90
  405. package/src/billing/plan-limit.ts +0 -52
  406. package/src/billing/plan-price.ts +0 -98
  407. package/src/billing/plan-switch.tsx +0 -216
  408. package/src/billing/provider-page.ts +0 -84
  409. package/src/billing/subscription-actions.tsx +0 -247
  410. package/src/billing/subscription-status-chip.tsx +0 -95
  411. package/src/chart.ts +0 -103
  412. package/src/components/account-chips-labels.ts +0 -16
  413. package/src/components/account-chips.tsx +0 -474
  414. package/src/components/alert-banner.tsx +0 -670
  415. package/src/components/amount-input.tsx +0 -815
  416. package/src/components/appearance-labels.ts +0 -63
  417. package/src/components/authed-image-labels.ts +0 -11
  418. package/src/components/authed-image.tsx +0 -209
  419. package/src/components/autocomplete.tsx +0 -645
  420. package/src/components/breadcrumbs-labels.ts +0 -10
  421. package/src/components/breadcrumbs.tsx +0 -205
  422. package/src/components/bulk-action-bar-labels.ts +0 -11
  423. package/src/components/bulk-action-bar.tsx +0 -443
  424. package/src/components/button-group.tsx +0 -223
  425. package/src/components/button-look.ts +0 -256
  426. package/src/components/button.tsx +0 -339
  427. package/src/components/calculator.tsx +0 -341
  428. package/src/components/calendar-heatmap-labels.ts +0 -15
  429. package/src/components/calendar-heatmap.tsx +0 -594
  430. package/src/components/card.tsx +0 -377
  431. package/src/components/chart-edge.ts +0 -106
  432. package/src/components/chart-figure.tsx +0 -35
  433. package/src/components/chart-height.ts +0 -30
  434. package/src/components/chart-zoom-core.tsx +0 -627
  435. package/src/components/chart-zoom.tsx +0 -22
  436. package/src/components/chart.tsx +0 -554
  437. package/src/components/checkbox-group.tsx +0 -247
  438. package/src/components/checkbox.tsx +0 -346
  439. package/src/components/chip-input.tsx +0 -370
  440. package/src/components/chip-labels.ts +0 -13
  441. package/src/components/chip.tsx +0 -843
  442. package/src/components/choice-card.tsx +0 -544
  443. package/src/components/column-mapper-labels.ts +0 -38
  444. package/src/components/column-mapper.tsx +0 -655
  445. package/src/components/combobox-core.tsx +0 -1321
  446. package/src/components/combobox-suggestions.tsx +0 -131
  447. package/src/components/combobox.tsx +0 -716
  448. package/src/components/confirm-context.ts +0 -7
  449. package/src/components/confirm-dialog-labels.ts +0 -13
  450. package/src/components/confirm-dialog.tsx +0 -341
  451. package/src/components/copy-button-labels.ts +0 -13
  452. package/src/components/copy-button.tsx +0 -192
  453. package/src/components/country-select-labels.ts +0 -11
  454. package/src/components/country-select.tsx +0 -539
  455. package/src/components/currency-option-list.tsx +0 -180
  456. package/src/components/currency-select.tsx +0 -341
  457. package/src/components/danger-confirm-labels.ts +0 -20
  458. package/src/components/danger-confirm.tsx +0 -934
  459. package/src/components/data-table-cells.tsx +0 -118
  460. package/src/components/data-table-column-settings.tsx +0 -198
  461. package/src/components/data-table-filter-popover.tsx +0 -281
  462. package/src/components/data-table-filters.ts +0 -287
  463. package/src/components/data-table-fit.ts +0 -301
  464. package/src/components/data-table-groups.tsx +0 -288
  465. package/src/components/data-table-head.tsx +0 -280
  466. package/src/components/data-table-labels.ts +0 -198
  467. package/src/components/data-table-leading-row.tsx +0 -58
  468. package/src/components/data-table-mobile.tsx +0 -1117
  469. package/src/components/data-table-pagination.tsx +0 -202
  470. package/src/components/data-table-resize.ts +0 -188
  471. package/src/components/data-table-row-actions.tsx +0 -302
  472. package/src/components/data-table-row.tsx +0 -496
  473. package/src/components/data-table-selection.ts +0 -71
  474. package/src/components/data-table-shared.tsx +0 -287
  475. package/src/components/data-table-sort.ts +0 -163
  476. package/src/components/data-table-virtual.ts +0 -576
  477. package/src/components/data-table.tsx +0 -2598
  478. package/src/components/date-field.tsx +0 -684
  479. package/src/components/date-format-setting.tsx +0 -64
  480. package/src/components/date-picker.tsx +0 -404
  481. package/src/components/date-range-picker.tsx +0 -518
  482. package/src/components/description-list-labels.ts +0 -9
  483. package/src/components/description-list.tsx +0 -386
  484. package/src/components/dialog-frame-labels.ts +0 -7
  485. package/src/components/dialog-frame.tsx +0 -276
  486. package/src/components/disclosure.tsx +0 -476
  487. package/src/components/dropdown.tsx +0 -461
  488. package/src/components/empty-state.tsx +0 -136
  489. package/src/components/entity-combobox.tsx +0 -422
  490. package/src/components/error-boundary-labels.ts +0 -20
  491. package/src/components/error-boundary.tsx +0 -675
  492. package/src/components/facing-pair.ts +0 -93
  493. package/src/components/field-base-labels.ts +0 -20
  494. package/src/components/field-base.tsx +0 -656
  495. package/src/components/field-parts.tsx +0 -825
  496. package/src/components/field-strip.tsx +0 -129
  497. package/src/components/field.tsx +0 -206
  498. package/src/components/file-button.tsx +0 -295
  499. package/src/components/file-dropzone.tsx +0 -559
  500. package/src/components/floating-panel-labels.ts +0 -10
  501. package/src/components/floating-panel.tsx +0 -828
  502. package/src/components/focus-ring.ts +0 -16
  503. package/src/components/form-actions-labels.ts +0 -11
  504. package/src/components/form-actions.tsx +0 -578
  505. package/src/components/full-bleed-dialog.tsx +0 -294
  506. package/src/components/grouped-picker.tsx +0 -184
  507. package/src/components/help-popover.tsx +0 -117
  508. package/src/components/hover-menu.tsx +0 -343
  509. package/src/components/iban-input-labels.ts +0 -14
  510. package/src/components/iban-input.tsx +0 -236
  511. package/src/components/icon-button.tsx +0 -959
  512. package/src/components/icon-picker-labels.ts +0 -13
  513. package/src/components/icon-picker.tsx +0 -232
  514. package/src/components/image-grid-labels.ts +0 -11
  515. package/src/components/image-grid.tsx +0 -202
  516. package/src/components/inline-confirm.tsx +0 -116
  517. package/src/components/inline-edit-field-labels.ts +0 -11
  518. package/src/components/inline-edit-field.tsx +0 -552
  519. package/src/components/inline-entity-combobox.tsx +0 -849
  520. package/src/components/input.tsx +0 -394
  521. package/src/components/jump-calendar.tsx +0 -265
  522. package/src/components/language-select.tsx +0 -93
  523. package/src/components/legal-labels.ts +0 -105
  524. package/src/components/legal-page.tsx +0 -711
  525. package/src/components/legal.tsx +0 -668
  526. package/src/components/lightbox-labels.ts +0 -18
  527. package/src/components/lightbox.tsx +0 -399
  528. package/src/components/line-items-labels.ts +0 -14
  529. package/src/components/line-items.tsx +0 -602
  530. package/src/components/list-labels.ts +0 -12
  531. package/src/components/list.tsx +0 -870
  532. package/src/components/loading-state.tsx +0 -78
  533. package/src/components/measured-grid-labels.ts +0 -25
  534. package/src/components/measured-grid.tsx +0 -837
  535. package/src/components/menu-item.tsx +0 -241
  536. package/src/components/mini-calendar-labels.ts +0 -17
  537. package/src/components/mini-calendar.tsx +0 -789
  538. package/src/components/modal.tsx +0 -505
  539. package/src/components/money-draft.ts +0 -83
  540. package/src/components/money-field.tsx +0 -76
  541. package/src/components/month-grid.tsx +0 -229
  542. package/src/components/month-picker-labels.ts +0 -22
  543. package/src/components/month-picker.tsx +0 -1087
  544. package/src/components/multi-entity-combobox.tsx +0 -369
  545. package/src/components/multi-select.tsx +0 -376
  546. package/src/components/nav-pills.tsx +0 -198
  547. package/src/components/number-field.tsx +0 -327
  548. package/src/components/number-input.tsx +0 -558
  549. package/src/components/numeric-field.ts +0 -169
  550. package/src/components/numpad-sheet.tsx +0 -285
  551. package/src/components/one-time-code-input.tsx +0 -270
  552. package/src/components/page-contents-labels.ts +0 -9
  553. package/src/components/page-contents.tsx +0 -282
  554. package/src/components/page-header-context.ts +0 -8
  555. package/src/components/page-header.tsx +0 -298
  556. package/src/components/password-strength-labels.ts +0 -22
  557. package/src/components/password-strength.tsx +0 -241
  558. package/src/components/phone-input-labels.ts +0 -10
  559. package/src/components/phone-input.tsx +0 -282
  560. package/src/components/picker-sheet.tsx +0 -330
  561. package/src/components/pie-chart-labels.ts +0 -40
  562. package/src/components/pie-chart.tsx +0 -480
  563. package/src/components/popover-labels.ts +0 -14
  564. package/src/components/popover.tsx +0 -268
  565. package/src/components/progress-bar-labels.ts +0 -10
  566. package/src/components/progress-bar.tsx +0 -479
  567. package/src/components/qr-code.tsx +0 -76
  568. package/src/components/reauth-dialog-labels.ts +0 -13
  569. package/src/components/reauth-dialog.tsx +0 -265
  570. package/src/components/row-actions-labels.ts +0 -12
  571. package/src/components/row-actions.tsx +0 -795
  572. package/src/components/scroll-area.tsx +0 -123
  573. package/src/components/search-field.tsx +0 -192
  574. package/src/components/select.tsx +0 -294
  575. package/src/components/separator.tsx +0 -33
  576. package/src/components/series-chart-axes.ts +0 -342
  577. package/src/components/series-chart-budget.ts +0 -150
  578. package/src/components/series-chart-labels.ts +0 -33
  579. package/src/components/series-chart-thin.ts +0 -180
  580. package/src/components/series-chart-ticks.ts +0 -194
  581. package/src/components/series-chart.tsx +0 -1906
  582. package/src/components/server-wake-labels.ts +0 -11
  583. package/src/components/server-wake.tsx +0 -129
  584. package/src/components/settings-fields.tsx +0 -237
  585. package/src/components/share-card-labels.ts +0 -30
  586. package/src/components/share-card.tsx +0 -675
  587. package/src/components/signature-pad-labels.ts +0 -24
  588. package/src/components/signature-pad.tsx +0 -747
  589. package/src/components/signed-amount-labels.ts +0 -10
  590. package/src/components/signed-amount.tsx +0 -336
  591. package/src/components/skeleton.tsx +0 -80
  592. package/src/components/slider.tsx +0 -338
  593. package/src/components/sparkline-labels.ts +0 -14
  594. package/src/components/sparkline.tsx +0 -322
  595. package/src/components/spinner.tsx +0 -81
  596. package/src/components/stat-tile-labels.ts +0 -15
  597. package/src/components/stat-tile.tsx +0 -725
  598. package/src/components/status-dot.tsx +0 -170
  599. package/src/components/swatch-picker-labels.ts +0 -10
  600. package/src/components/swatch-picker.tsx +0 -283
  601. package/src/components/swipeable-row.tsx +0 -386
  602. package/src/components/switch.tsx +0 -273
  603. package/src/components/table-skeleton.tsx +0 -136
  604. package/src/components/table.tsx +0 -647
  605. package/src/components/tabs-labels.ts +0 -13
  606. package/src/components/tabs.tsx +0 -693
  607. package/src/components/text-link.tsx +0 -359
  608. package/src/components/text.tsx +0 -150
  609. package/src/components/textarea.tsx +0 -330
  610. package/src/components/tile-radio.tsx +0 -332
  611. package/src/components/time-input.tsx +0 -121
  612. package/src/components/toast-labels.ts +0 -12
  613. package/src/components/toast.tsx +0 -519
  614. package/src/components/toggle-group.tsx +0 -752
  615. package/src/components/toggle-legend.tsx +0 -394
  616. package/src/components/tooltip.tsx +0 -1149
  617. package/src/components/translation-review-editor.tsx +0 -290
  618. package/src/components/translation-review-labels.ts +0 -181
  619. package/src/components/translation-review-parts.tsx +0 -272
  620. package/src/components/translation-review.tsx +0 -1240
  621. package/src/components/tree-view.tsx +0 -589
  622. package/src/components/treemap-labels.ts +0 -47
  623. package/src/components/treemap-layout.ts +0 -210
  624. package/src/components/treemap.tsx +0 -1184
  625. package/src/components/trigger-aria.ts +0 -44
  626. package/src/components/use-anchor-dir.ts +0 -20
  627. package/src/components/use-mobile-reveal.ts +0 -62
  628. package/src/components/use-table-state.ts +0 -753
  629. package/src/components/user-avatar.tsx +0 -116
  630. package/src/components/wizard-stepper.tsx +0 -42
  631. package/src/components/write-lock-labels.ts +0 -10
  632. package/src/components/write-lock.tsx +0 -253
  633. package/src/currencies.ts +0 -70
  634. package/src/data-table.ts +0 -42
  635. package/src/demo/demo-banner.tsx +0 -153
  636. package/src/demo/demo-countdown.ts +0 -136
  637. package/src/demo/demo-ended.tsx +0 -90
  638. package/src/demo/demo-labels.ts +0 -78
  639. package/src/demo/demo-session.ts +0 -32
  640. package/src/demo/demo-start.tsx +0 -156
  641. package/src/eslint/index.ts +0 -161
  642. package/src/eslint/role-utilities.generated.ts +0 -146
  643. package/src/eslint/role-utilities.ts +0 -233
  644. package/src/feedback/feedback-attachment-download.tsx +0 -77
  645. package/src/feedback/feedback-attachment-labels.ts +0 -37
  646. package/src/feedback/feedback-attachment.tsx +0 -1239
  647. package/src/feedback/feedback-capture.ts +0 -287
  648. package/src/feedback/feedback-comment-thread.tsx +0 -353
  649. package/src/feedback/feedback-context-labels.ts +0 -10
  650. package/src/feedback/feedback-context.tsx +0 -213
  651. package/src/feedback/feedback-crash.ts +0 -547
  652. package/src/feedback/feedback-dialog-labels.ts +0 -29
  653. package/src/feedback/feedback-dialog.tsx +0 -440
  654. package/src/feedback/feedback-entry-file.tsx +0 -150
  655. package/src/feedback/feedback-inbox.tsx +0 -552
  656. package/src/feedback/feedback-labels.ts +0 -144
  657. package/src/feedback/feedback-menu-labels.ts +0 -12
  658. package/src/feedback/feedback-menu.tsx +0 -177
  659. package/src/feedback/feedback-mutation.ts +0 -20
  660. package/src/feedback/feedback-page-link.ts +0 -90
  661. package/src/feedback/feedback-public-labels.ts +0 -81
  662. package/src/feedback/feedback-public-list.tsx +0 -524
  663. package/src/feedback/feedback-public-switch.tsx +0 -102
  664. package/src/feedback/feedback-public-types.ts +0 -101
  665. package/src/feedback/feedback-record.ts +0 -423
  666. package/src/feedback/feedback-replies-context.ts +0 -9
  667. package/src/feedback/feedback-replies.tsx +0 -343
  668. package/src/feedback/feedback-rework-entry-chip.tsx +0 -18
  669. package/src/feedback/feedback-row-detail-labels.ts +0 -30
  670. package/src/feedback/feedback-row-detail.tsx +0 -858
  671. package/src/feedback/feedback-status-undo.ts +0 -109
  672. package/src/feedback/feedback-submit-dialog.tsx +0 -118
  673. package/src/feedback/feedback-submit.tsx +0 -237
  674. package/src/feedback/feedback-swipe.tsx +0 -192
  675. package/src/feedback/feedback-table-labels.ts +0 -27
  676. package/src/feedback/feedback-table.tsx +0 -912
  677. package/src/feedback/feedback-thread-labels.ts +0 -22
  678. package/src/feedback/feedback-thread-parts-labels.ts +0 -41
  679. package/src/feedback/feedback-thread.tsx +0 -905
  680. package/src/feedback/feedback-vocabulary.ts +0 -263
  681. package/src/feedback/feedback-vote-button.tsx +0 -124
  682. package/src/feedback.ts +0 -116
  683. package/src/hooks/use-anchored-panel.ts +0 -396
  684. package/src/hooks/use-anchored-rect.ts +0 -77
  685. package/src/hooks/use-announce.ts +0 -85
  686. package/src/hooks/use-authed-src.ts +0 -167
  687. package/src/hooks/use-body-scroll-lock.ts +0 -119
  688. package/src/hooks/use-breakpoint.ts +0 -133
  689. package/src/hooks/use-close-transition.ts +0 -107
  690. package/src/hooks/use-copy-to-clipboard.ts +0 -122
  691. package/src/hooks/use-debounce.ts +0 -114
  692. package/src/hooks/use-dismiss.ts +0 -371
  693. package/src/hooks/use-document-title.ts +0 -33
  694. package/src/hooks/use-file-drop.ts +0 -121
  695. package/src/hooks/use-file-picker-labels.ts +0 -28
  696. package/src/hooks/use-file-picker.tsx +0 -359
  697. package/src/hooks/use-focus-trap.ts +0 -199
  698. package/src/hooks/use-hotkey.ts +0 -189
  699. package/src/hooks/use-large-text.ts +0 -41
  700. package/src/hooks/use-long-press.ts +0 -140
  701. package/src/hooks/use-media-query.ts +0 -39
  702. package/src/hooks/use-mounted.ts +0 -39
  703. package/src/hooks/use-noindex.ts +0 -37
  704. package/src/hooks/use-overlay-history.ts +0 -714
  705. package/src/hooks/use-row-swipe.ts +0 -207
  706. package/src/hooks/use-search-param-state.ts +0 -434
  707. package/src/hooks/use-windowed-rows.ts +0 -225
  708. package/src/i18n/defaults.ts +0 -267
  709. package/src/i18n/german.ts +0 -2057
  710. package/src/i18n/kit-labels.tsx +0 -1003
  711. package/src/i18n/languages.ts +0 -349
  712. package/src/i18n/locales/de-CH.ts +0 -24
  713. package/src/i18n/locales/en.ts +0 -267
  714. package/src/i18n/locales/es.ts +0 -1997
  715. package/src/i18n/locales/fr.ts +0 -2035
  716. package/src/i18n/locales/hu.ts +0 -2002
  717. package/src/i18n/locales/it.ts +0 -2012
  718. package/src/i18n/locales/zh.ts +0 -1910
  719. package/src/i18n/plurals.ts +0 -86
  720. package/src/i18n/review.ts +0 -475
  721. package/src/i18n/shared-labels.ts +0 -138
  722. package/src/i18n/swiss.ts +0 -29
  723. package/src/index.ts +0 -1179
  724. package/src/landing/access.ts +0 -93
  725. package/src/landing/landing-actions.tsx +0 -97
  726. package/src/landing/landing-labels.ts +0 -59
  727. package/src/landing/landing-sections.tsx +0 -341
  728. package/src/landing/page-seo.ts +0 -261
  729. package/src/landing/public-header.tsx +0 -121
  730. package/src/landing/public-page.tsx +0 -106
  731. package/src/landing/routing.tsx +0 -225
  732. package/src/lib/broadcast.ts +0 -52
  733. package/src/lib/calc.ts +0 -323
  734. package/src/lib/chip-edge-snap.ts +0 -212
  735. package/src/lib/clipboard-minus.ts +0 -107
  736. package/src/lib/clipping.ts +0 -34
  737. package/src/lib/cn.ts +0 -28
  738. package/src/lib/column-mapping.ts +0 -234
  739. package/src/lib/countries.ts +0 -91
  740. package/src/lib/data-attributes.ts +0 -14
  741. package/src/lib/date-format.ts +0 -227
  742. package/src/lib/dates.ts +0 -266
  743. package/src/lib/day-format.ts +0 -96
  744. package/src/lib/decimal-marks.ts +0 -108
  745. package/src/lib/describe-failure.ts +0 -18
  746. package/src/lib/device-settings.ts +0 -147
  747. package/src/lib/direction.ts +0 -74
  748. package/src/lib/document-navigation.ts +0 -17
  749. package/src/lib/document-title.ts +0 -41
  750. package/src/lib/download.ts +0 -172
  751. package/src/lib/float-clearance.ts +0 -70
  752. package/src/lib/floating-stack.ts +0 -70
  753. package/src/lib/focus-left.ts +0 -156
  754. package/src/lib/format.ts +0 -522
  755. package/src/lib/http-status.ts +0 -38
  756. package/src/lib/iban.ts +0 -251
  757. package/src/lib/ime.ts +0 -24
  758. package/src/lib/intl-cache.ts +0 -25
  759. package/src/lib/iso-day.ts +0 -13
  760. package/src/lib/lazy-with-fallback.ts +0 -82
  761. package/src/lib/logger.ts +0 -69
  762. package/src/lib/minus.ts +0 -50
  763. package/src/lib/path-pattern.ts +0 -55
  764. package/src/lib/person-name.ts +0 -155
  765. package/src/lib/phone.ts +0 -208
  766. package/src/lib/plain-text.ts +0 -22
  767. package/src/lib/presence.ts +0 -18
  768. package/src/lib/qr-encode.ts +0 -444
  769. package/src/lib/quiet-focus.ts +0 -32
  770. package/src/lib/reduced-motion.ts +0 -22
  771. package/src/lib/refs.ts +0 -22
  772. package/src/lib/round.ts +0 -43
  773. package/src/lib/safe-storage.ts +0 -119
  774. package/src/lib/server-wake.ts +0 -315
  775. package/src/lib/session-refresh.ts +0 -472
  776. package/src/lib/share-format.ts +0 -18
  777. package/src/lib/status-tone.ts +0 -43
  778. package/src/lib/strip-fade.ts +0 -287
  779. package/src/lib/table-text.ts +0 -554
  780. package/src/lib/text-case.ts +0 -19
  781. package/src/lib/text-match.ts +0 -169
  782. package/src/lib/thenable.ts +0 -19
  783. package/src/lib/translation-review.ts +0 -717
  784. package/src/notifications/device-zone-report.ts +0 -67
  785. package/src/notifications/feedback-notification-types-labels.ts +0 -33
  786. package/src/notifications/feedback-notification-types.ts +0 -71
  787. package/src/notifications/notification-bell.tsx +0 -119
  788. package/src/notifications/notification-inbox.tsx +0 -309
  789. package/src/notifications/notification-parts.tsx +0 -81
  790. package/src/notifications/notification-types-setting.tsx +0 -406
  791. package/src/notifications/notification-types.ts +0 -89
  792. package/src/notifications/notifications-labels.ts +0 -287
  793. package/src/notifications/push-device-setting.tsx +0 -230
  794. package/src/notifications/push-devices-setting.tsx +0 -374
  795. package/src/notifications/push-worker.ts +0 -168
  796. package/src/notifications/quiet-hours-setting.tsx +0 -238
  797. package/src/notifications/unsubscribe-panel.tsx +0 -200
  798. package/src/notifications/use-push-device.ts +0 -448
  799. package/src/offline/local-first.ts +0 -100
  800. package/src/offline/pwa-update-prompt-labels.ts +0 -9
  801. package/src/offline/pwa-update-prompt.tsx +0 -78
  802. package/src/offline/query-persistence.ts +0 -391
  803. package/src/offline/service-worker-updates.ts +0 -81
  804. package/src/offline/shared-computer-labels.ts +0 -8
  805. package/src/offline/shared-computer.tsx +0 -87
  806. package/src/offline/sign-out-pending-dialog-labels.ts +0 -21
  807. package/src/offline/sign-out-pending-dialog.tsx +0 -135
  808. package/src/offline.ts +0 -37
  809. package/src/push-worker.ts +0 -25
  810. package/src/rhf/fields.tsx +0 -2187
  811. package/src/rhf/form-core.tsx +0 -324
  812. package/src/rhf/form.tsx +0 -12
  813. package/src/rhf/line-items.tsx +0 -135
  814. package/src/rhf/use-rhf-wizard-step.ts +0 -104
  815. package/src/rhf.ts +0 -21
  816. package/src/search/command-palette-labels.ts +0 -16
  817. package/src/search/command-palette.tsx +0 -624
  818. package/src/search/global-search-labels.ts +0 -13
  819. package/src/search/global-search.tsx +0 -660
  820. package/src/search/search-index.ts +0 -283
  821. package/src/search/use-command-key.ts +0 -23
  822. package/src/search.ts +0 -14
  823. package/src/settings/device-reset-setting-labels.ts +0 -20
  824. package/src/settings/device-reset-setting.tsx +0 -116
  825. package/src/settings/settings-card-classes.ts +0 -11
  826. package/src/settings/settings-catalogue.ts +0 -150
  827. package/src/settings/settings-context.ts +0 -33
  828. package/src/settings/settings-heading.tsx +0 -67
  829. package/src/settings/settings-labels.ts +0 -87
  830. package/src/settings/settings-layout.tsx +0 -471
  831. package/src/settings/settings-section.tsx +0 -108
  832. package/src/settings/use-account-appearance.ts +0 -148
  833. package/src/settings/use-account-language.ts +0 -146
  834. package/src/settings/use-settings-focus.ts +0 -128
  835. package/src/settings/use-settings-route.ts +0 -258
  836. package/src/shell/account-menu-language.tsx +0 -130
  837. package/src/shell/account-menu-text-size.tsx +0 -80
  838. package/src/shell/admin-menu.tsx +0 -183
  839. package/src/shell/app-shell-labels.ts +0 -11
  840. package/src/shell/app-shell.tsx +0 -1560
  841. package/src/shell/auth-layout.tsx +0 -186
  842. package/src/shell/company-switcher-labels.ts +0 -13
  843. package/src/shell/company-switcher.tsx +0 -136
  844. package/src/shell/dev-menu.tsx +0 -257
  845. package/src/shell/fill-height.tsx +0 -45
  846. package/src/shell/language-code.ts +0 -5
  847. package/src/shell/option-switcher-menu.tsx +0 -84
  848. package/src/shell/sync-status-indicator.tsx +0 -303
  849. package/src/shell/top-bar-actions.tsx +0 -87
  850. package/src/shell/top-bar-brand.tsx +0 -78
  851. package/src/shell/top-bar-parts-labels.ts +0 -148
  852. package/src/shell/top-bar.tsx +0 -40
  853. package/src/shell/topbar-action-menu.tsx +0 -567
  854. package/src/shell/topbar-controls.tsx +0 -269
  855. package/src/shell/tour-menu.tsx +0 -187
  856. package/src/shell.ts +0 -108
  857. package/src/sync/db.ts +0 -129
  858. package/src/sync/engine.ts +0 -1011
  859. package/src/sync/hooks.ts +0 -98
  860. package/src/sync/testing.ts +0 -73
  861. package/src/sync/wire.ts +0 -61
  862. package/src/sync.ts +0 -37
  863. package/src/table-text.ts +0 -8
  864. package/src/testing/css-variables.generated.ts +0 -112
  865. package/src/testing/css-variables.ts +0 -189
  866. package/src/testing/index.ts +0 -29
  867. package/src/testing/react.ts +0 -58
  868. package/src/testing/tour.ts +0 -41
  869. package/src/testing/viewport.ts +0 -357
  870. package/src/theme/chart-palette.ts +0 -232
  871. package/src/theme/color.ts +0 -272
  872. package/src/theme/contrast-tokens.ts +0 -268
  873. package/src/theme/contrast.ts +0 -250
  874. package/src/theme/palette-derive.ts +0 -825
  875. package/src/theme/palette-presets.ts +0 -544
  876. package/src/theme/palette-store.ts +0 -108
  877. package/src/theme/text-size.ts +0 -289
  878. package/src/theme/theme-store.ts +0 -157
  879. package/src/tour/tour-catalogue-labels.ts +0 -75
  880. package/src/tour/tour-catalogue.tsx +0 -335
  881. package/src/tour/tour-dock.ts +0 -134
  882. package/src/tour/tour-engine.ts +0 -37
  883. package/src/tour/tour-gates.ts +0 -218
  884. package/src/tour/tour-guide-button.tsx +0 -64
  885. package/src/tour/tour-guide-labels.ts +0 -15
  886. package/src/tour/tour-info.ts +0 -144
  887. package/src/tour/tour-labels.ts +0 -23
  888. package/src/tour/tour-overlay.tsx +0 -1170
  889. package/src/tour/tour.tsx +0 -750
  890. package/src/tour/use-tour-progress.ts +0 -671
  891. package/src/tour/use-tour-url-sync.ts +0 -266
  892. package/src/tour.ts +0 -21
  893. package/src/vite-env.d.ts +0 -23
  894. package/src/wizard/stepper-nav.tsx +0 -360
  895. package/src/wizard/types.ts +0 -275
  896. package/src/wizard/use-wizard.ts +0 -560
  897. package/src/wizard/validation.ts +0 -39
  898. package/src/wizard/wizard-context.tsx +0 -74
  899. package/src/wizard/wizard-labels.ts +0 -29
  900. package/src/wizard/wizard-step.tsx +0 -76
  901. package/src/wizard/wizard-summary.tsx +0 -84
  902. package/src/wizard.ts +0 -15
@@ -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
- }