@eifi1/ui-kit 0.4.1 → 0.5.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 (410) hide show
  1. package/LICENSE +32 -0
  2. package/README.md +498 -57
  3. package/dist/chart.d.ts +10 -0
  4. package/dist/chart.js +10 -0
  5. package/dist/chart.js.map +1 -0
  6. package/dist/components/account-settings.d.ts +11 -0
  7. package/dist/components/account-settings.js +19 -12
  8. package/dist/components/account-settings.js.map +1 -1
  9. package/dist/components/alert-banner.d.ts +10 -6
  10. package/dist/components/alert-banner.js +20 -16
  11. package/dist/components/alert-banner.js.map +1 -1
  12. package/dist/components/amount-input.d.ts +30 -4
  13. package/dist/components/amount-input.js +36 -14
  14. package/dist/components/amount-input.js.map +1 -1
  15. package/dist/components/calculator.d.ts +24 -7
  16. package/dist/components/calculator.js +37 -20
  17. package/dist/components/calculator.js.map +1 -1
  18. package/dist/components/chart-zoom.d.ts +156 -0
  19. package/dist/components/chart-zoom.js +262 -0
  20. package/dist/components/chart-zoom.js.map +1 -0
  21. package/dist/components/chart.d.ts +28 -3
  22. package/dist/components/chart.js +71 -32
  23. package/dist/components/chart.js.map +1 -1
  24. package/dist/components/checkbox.d.ts +51 -0
  25. package/dist/components/checkbox.js +123 -0
  26. package/dist/components/checkbox.js.map +1 -0
  27. package/dist/components/chip.d.ts +128 -0
  28. package/dist/components/chip.js +357 -0
  29. package/dist/components/chip.js.map +1 -0
  30. package/dist/components/choice-parts.d.ts +24 -0
  31. package/dist/components/choice-parts.js +18 -0
  32. package/dist/components/choice-parts.js.map +1 -0
  33. package/dist/components/combobox-core.d.ts +52 -9
  34. package/dist/components/combobox-core.js +89 -19
  35. package/dist/components/combobox-core.js.map +1 -1
  36. package/dist/components/combobox.d.ts +43 -33
  37. package/dist/components/combobox.js +409 -340
  38. package/dist/components/combobox.js.map +1 -1
  39. package/dist/components/currency-select.d.ts +48 -8
  40. package/dist/components/currency-select.js +107 -65
  41. package/dist/components/currency-select.js.map +1 -1
  42. package/dist/components/data-table-filter-popover.d.ts +15 -3
  43. package/dist/components/data-table-filter-popover.js +23 -17
  44. package/dist/components/data-table-filter-popover.js.map +1 -1
  45. package/dist/components/data-table-filters.d.ts +1 -1
  46. package/dist/components/data-table-filters.js +3 -2
  47. package/dist/components/data-table-filters.js.map +1 -1
  48. package/dist/components/data-table-labels.d.ts +53 -2
  49. package/dist/components/data-table-labels.js +31 -2
  50. package/dist/components/data-table-labels.js.map +1 -1
  51. package/dist/components/data-table-pagination.d.ts +3 -1
  52. package/dist/components/data-table-pagination.js +16 -10
  53. package/dist/components/data-table-pagination.js.map +1 -1
  54. package/dist/components/data-table-sort.js +1 -0
  55. package/dist/components/data-table-sort.js.map +1 -1
  56. package/dist/components/data-table.d.ts +1 -1
  57. package/dist/components/data-table.js +201 -85
  58. package/dist/components/data-table.js.map +1 -1
  59. package/dist/components/date-picker.d.ts +25 -9
  60. package/dist/components/date-picker.js +232 -94
  61. package/dist/components/date-picker.js.map +1 -1
  62. package/dist/components/dropdown.d.ts +74 -19
  63. package/dist/components/dropdown.js +72 -22
  64. package/dist/components/dropdown.js.map +1 -1
  65. package/dist/components/entity-combobox.d.ts +5 -4
  66. package/dist/components/entity-combobox.js +107 -76
  67. package/dist/components/entity-combobox.js.map +1 -1
  68. package/dist/components/facing-pair.d.ts +36 -0
  69. package/dist/components/facing-pair.js +40 -0
  70. package/dist/components/facing-pair.js.map +1 -0
  71. package/dist/components/field-sync.d.ts +164 -0
  72. package/dist/components/field-sync.js +264 -0
  73. package/dist/components/field-sync.js.map +1 -0
  74. package/dist/components/file-dropzone.d.ts +21 -7
  75. package/dist/components/file-dropzone.js +28 -7
  76. package/dist/components/file-dropzone.js.map +1 -1
  77. package/dist/components/full-bleed-dialog.d.ts +37 -26
  78. package/dist/components/full-bleed-dialog.js +20 -5
  79. package/dist/components/full-bleed-dialog.js.map +1 -1
  80. package/dist/components/grouped-picker.d.ts +21 -10
  81. package/dist/components/grouped-picker.js +66 -52
  82. package/dist/components/grouped-picker.js.map +1 -1
  83. package/dist/components/hover-menu.d.ts +20 -4
  84. package/dist/components/hover-menu.js +15 -4
  85. package/dist/components/hover-menu.js.map +1 -1
  86. package/dist/components/mini-calendar.d.ts +102 -14
  87. package/dist/components/mini-calendar.js +256 -70
  88. package/dist/components/mini-calendar.js.map +1 -1
  89. package/dist/components/modal.d.ts +9 -3
  90. package/dist/components/modal.js +34 -45
  91. package/dist/components/modal.js.map +1 -1
  92. package/dist/components/month-picker.d.ts +79 -0
  93. package/dist/components/month-picker.js +342 -0
  94. package/dist/components/month-picker.js.map +1 -0
  95. package/dist/components/multi-entity-combobox.d.ts +7 -6
  96. package/dist/components/multi-entity-combobox.js +112 -72
  97. package/dist/components/multi-entity-combobox.js.map +1 -1
  98. package/dist/components/multi-select.d.ts +13 -11
  99. package/dist/components/multi-select.js +161 -84
  100. package/dist/components/multi-select.js.map +1 -1
  101. package/dist/components/number-field.d.ts +92 -0
  102. package/dist/components/number-field.js +111 -0
  103. package/dist/components/number-field.js.map +1 -0
  104. package/dist/components/number-input.d.ts +21 -2
  105. package/dist/components/number-input.js +2 -1
  106. package/dist/components/number-input.js.map +1 -1
  107. package/dist/components/numpad-sheet.d.ts +25 -4
  108. package/dist/components/numpad-sheet.js +30 -13
  109. package/dist/components/numpad-sheet.js.map +1 -1
  110. package/dist/components/page-contents.d.ts +90 -0
  111. package/dist/components/page-contents.js +170 -0
  112. package/dist/components/page-contents.js.map +1 -0
  113. package/dist/components/password-strength.d.ts +106 -0
  114. package/dist/components/password-strength.js +132 -0
  115. package/dist/components/password-strength.js.map +1 -0
  116. package/dist/components/picker-sheet.d.ts +23 -15
  117. package/dist/components/picker-sheet.js +34 -14
  118. package/dist/components/picker-sheet.js.map +1 -1
  119. package/dist/components/popover.d.ts +62 -4
  120. package/dist/components/popover.js +52 -2
  121. package/dist/components/popover.js.map +1 -1
  122. package/dist/components/search-field.d.ts +25 -7
  123. package/dist/components/search-field.js +7 -5
  124. package/dist/components/search-field.js.map +1 -1
  125. package/dist/components/series-chart-labels.d.ts +14 -0
  126. package/dist/components/series-chart-labels.js +11 -0
  127. package/dist/components/series-chart-labels.js.map +1 -0
  128. package/dist/components/series-chart.d.ts +207 -0
  129. package/dist/components/series-chart.js +223 -0
  130. package/dist/components/series-chart.js.map +1 -0
  131. package/dist/components/settings-fields.d.ts +14 -4
  132. package/dist/components/settings-fields.js +19 -8
  133. package/dist/components/settings-fields.js.map +1 -1
  134. package/dist/components/signature-pad.d.ts +119 -0
  135. package/dist/components/signature-pad.js +405 -0
  136. package/dist/components/signature-pad.js.map +1 -0
  137. package/dist/components/slider.d.ts +80 -0
  138. package/dist/components/slider.js +155 -0
  139. package/dist/components/slider.js.map +1 -0
  140. package/dist/components/sparkline.d.ts +93 -0
  141. package/dist/components/sparkline.js +205 -0
  142. package/dist/components/sparkline.js.map +1 -0
  143. package/dist/components/stat-tile.d.ts +151 -0
  144. package/dist/components/stat-tile.js +236 -0
  145. package/dist/components/stat-tile.js.map +1 -0
  146. package/dist/components/swipeable-row.d.ts +25 -2
  147. package/dist/components/swipeable-row.js +31 -0
  148. package/dist/components/swipeable-row.js.map +1 -1
  149. package/dist/components/switch.d.ts +60 -0
  150. package/dist/components/switch.js +81 -0
  151. package/dist/components/switch.js.map +1 -0
  152. package/dist/components/time-input.d.ts +58 -0
  153. package/dist/components/time-input.js +50 -0
  154. package/dist/components/time-input.js.map +1 -0
  155. package/dist/components/toggle-group.d.ts +18 -3
  156. package/dist/components/toggle-group.js +10 -6
  157. package/dist/components/toggle-group.js.map +1 -1
  158. package/dist/components/toggle-legend.d.ts +115 -0
  159. package/dist/components/toggle-legend.js +114 -0
  160. package/dist/components/toggle-legend.js.map +1 -0
  161. package/dist/components/tooltip.d.ts +40 -11
  162. package/dist/components/tooltip.js +77 -20
  163. package/dist/components/tooltip.js.map +1 -1
  164. package/dist/components/treemap.d.ts +132 -0
  165. package/dist/components/treemap.js +179 -0
  166. package/dist/components/treemap.js.map +1 -0
  167. package/dist/components/ui.d.ts +159 -46
  168. package/dist/components/ui.js +220 -115
  169. package/dist/components/ui.js.map +1 -1
  170. package/dist/components/use-mobile-reveal.js +1 -0
  171. package/dist/components/use-mobile-reveal.js.map +1 -1
  172. package/dist/components/use-table-state.d.ts +1 -1
  173. package/dist/components/use-table-state.js +38 -17
  174. package/dist/components/use-table-state.js.map +1 -1
  175. package/dist/components/user-avatar.d.ts +5 -3
  176. package/dist/components/user-avatar.js +4 -2
  177. package/dist/components/user-avatar.js.map +1 -1
  178. package/dist/components/wizard-stepper.js +3 -2
  179. package/dist/components/wizard-stepper.js.map +1 -1
  180. package/dist/{data-table-filters-aXyf0Xub.d.ts → data-table-filters-DM1auUfH.d.ts} +27 -5
  181. package/dist/data-table.d.ts +7 -0
  182. package/dist/data-table.js +12 -0
  183. package/dist/data-table.js.map +1 -0
  184. package/dist/feedback/feedback-attachment.js +12 -11
  185. package/dist/feedback/feedback-attachment.js.map +1 -1
  186. package/dist/feedback/feedback-dialog.js +2 -1
  187. package/dist/feedback/feedback-dialog.js.map +1 -1
  188. package/dist/feedback/feedback-inbox.d.ts +33 -7
  189. package/dist/feedback/feedback-inbox.js +49 -37
  190. package/dist/feedback/feedback-inbox.js.map +1 -1
  191. package/dist/feedback.d.ts +5 -0
  192. package/dist/feedback.js +5 -0
  193. package/dist/feedback.js.map +1 -0
  194. package/dist/hooks/use-anchored-panel.js +1 -0
  195. package/dist/hooks/use-anchored-panel.js.map +1 -1
  196. package/dist/hooks/use-anchored-rect.js +1 -0
  197. package/dist/hooks/use-anchored-rect.js.map +1 -1
  198. package/dist/hooks/use-announce.d.ts +46 -0
  199. package/dist/hooks/use-announce.js +37 -0
  200. package/dist/hooks/use-announce.js.map +1 -0
  201. package/dist/hooks/use-body-scroll-lock.js +1 -0
  202. package/dist/hooks/use-body-scroll-lock.js.map +1 -1
  203. package/dist/hooks/use-close-transition.js +1 -0
  204. package/dist/hooks/use-close-transition.js.map +1 -1
  205. package/dist/hooks/use-dismiss.js +1 -0
  206. package/dist/hooks/use-dismiss.js.map +1 -1
  207. package/dist/hooks/use-focus-trap.d.ts +16 -0
  208. package/dist/hooks/use-focus-trap.js +67 -0
  209. package/dist/hooks/use-focus-trap.js.map +1 -0
  210. package/dist/hooks/use-media-query.js +1 -0
  211. package/dist/hooks/use-media-query.js.map +1 -1
  212. package/dist/hooks/use-overlay-history.js +27 -1
  213. package/dist/hooks/use-overlay-history.js.map +1 -1
  214. package/dist/hooks/use-row-swipe.js +1 -0
  215. package/dist/hooks/use-row-swipe.js.map +1 -1
  216. package/dist/i18n/defaults.d.ts +31 -0
  217. package/dist/i18n/defaults.js +63 -0
  218. package/dist/i18n/defaults.js.map +1 -0
  219. package/dist/i18n/kit-labels.d.ts +250 -0
  220. package/dist/i18n/kit-labels.js +184 -0
  221. package/dist/i18n/kit-labels.js.map +1 -0
  222. package/dist/index.d.ts +47 -22
  223. package/dist/index.js +28 -0
  224. package/dist/index.js.map +1 -1
  225. package/dist/lib/calc.js +1 -0
  226. package/dist/lib/calc.js.map +1 -1
  227. package/dist/lib/cn.js +1 -0
  228. package/dist/lib/cn.js.map +1 -1
  229. package/dist/lib/dates.js +1 -0
  230. package/dist/lib/dates.js.map +1 -1
  231. package/dist/lib/logger.js +1 -0
  232. package/dist/lib/logger.js.map +1 -1
  233. package/dist/lib/safe-storage.js +1 -0
  234. package/dist/lib/safe-storage.js.map +1 -1
  235. package/dist/search/command-palette.d.ts +11 -1
  236. package/dist/search/command-palette.js +21 -15
  237. package/dist/search/command-palette.js.map +1 -1
  238. package/dist/search.d.ts +2 -0
  239. package/dist/search.js +3 -0
  240. package/dist/search.js.map +1 -0
  241. package/dist/shell/app-shell.d.ts +33 -4
  242. package/dist/shell/app-shell.js +275 -78
  243. package/dist/shell/app-shell.js.map +1 -1
  244. package/dist/shell/option-switcher-menu.js +3 -2
  245. package/dist/shell/option-switcher-menu.js.map +1 -1
  246. package/dist/shell/role-switcher.d.ts +6 -0
  247. package/dist/shell/role-switcher.js +7 -4
  248. package/dist/shell/role-switcher.js.map +1 -1
  249. package/dist/shell/top-bar.d.ts +15 -7
  250. package/dist/shell/top-bar.js +3 -5
  251. package/dist/shell/top-bar.js.map +1 -1
  252. package/dist/shell/topbar-action-menu.js +3 -2
  253. package/dist/shell/topbar-action-menu.js.map +1 -1
  254. package/dist/shell/topbar-controls.d.ts +5 -2
  255. package/dist/shell/topbar-controls.js +20 -15
  256. package/dist/shell/topbar-controls.js.map +1 -1
  257. package/dist/shell.d.ts +13 -0
  258. package/dist/shell.js +8 -0
  259. package/dist/shell.js.map +1 -0
  260. package/dist/theme/chart-palette.d.ts +19 -12
  261. package/dist/theme/chart-palette.js +24 -30
  262. package/dist/theme/chart-palette.js.map +1 -1
  263. package/dist/theme/color.d.ts +78 -0
  264. package/dist/theme/color.js +148 -0
  265. package/dist/theme/color.js.map +1 -0
  266. package/dist/theme/palette-derive.d.ts +140 -0
  267. package/dist/theme/palette-derive.js +376 -0
  268. package/dist/theme/palette-derive.js.map +1 -0
  269. package/dist/theme/palette-presets.d.ts +14 -1
  270. package/dist/theme/palette-presets.js +231 -3
  271. package/dist/theme/palette-presets.js.map +1 -1
  272. package/dist/theme/palette-store.js +1 -0
  273. package/dist/theme/palette-store.js.map +1 -1
  274. package/dist/theme/theme-store.js +1 -0
  275. package/dist/theme/theme-store.js.map +1 -1
  276. package/dist/tour/tour.d.ts +2 -1
  277. package/dist/tour/tour.js +15 -9
  278. package/dist/tour/tour.js.map +1 -1
  279. package/dist/tour.d.ts +2 -0
  280. package/dist/tour.js +3 -0
  281. package/dist/tour.js.map +1 -0
  282. package/dist/wizard/stepper-nav.js +10 -8
  283. package/dist/wizard/stepper-nav.js.map +1 -1
  284. package/dist/wizard/types.d.ts +18 -2
  285. package/dist/wizard/types.js +4 -1
  286. package/dist/wizard/types.js.map +1 -1
  287. package/dist/wizard/use-wizard.js +12 -4
  288. package/dist/wizard/use-wizard.js.map +1 -1
  289. package/dist/wizard/validation.js +1 -0
  290. package/dist/wizard/validation.js.map +1 -1
  291. package/dist/wizard/wizard-context.js +1 -0
  292. package/dist/wizard/wizard-context.js.map +1 -1
  293. package/dist/wizard/wizard-step.js +1 -0
  294. package/dist/wizard/wizard-step.js.map +1 -1
  295. package/dist/wizard/wizard-summary.js +5 -3
  296. package/dist/wizard/wizard-summary.js.map +1 -1
  297. package/dist/wizard.d.ts +8 -0
  298. package/dist/wizard.js +9 -0
  299. package/dist/wizard.js.map +1 -0
  300. package/package.json +66 -13
  301. package/src/chart.ts +22 -0
  302. package/src/components/account-settings.tsx +275 -0
  303. package/src/components/alert-banner.tsx +75 -0
  304. package/src/components/amount-input.tsx +471 -0
  305. package/src/components/calculator.tsx +252 -0
  306. package/src/components/chart-zoom.tsx +499 -0
  307. package/src/components/chart.tsx +363 -0
  308. package/src/components/checkbox.tsx +222 -0
  309. package/src/components/chip.tsx +570 -0
  310. package/src/components/choice-parts.ts +33 -0
  311. package/src/components/combobox-core.tsx +540 -0
  312. package/src/components/combobox.tsx +1026 -0
  313. package/src/components/currency-select.tsx +269 -0
  314. package/src/components/data-table-filter-popover.tsx +252 -0
  315. package/src/components/data-table-filters.ts +176 -0
  316. package/src/components/data-table-labels.ts +190 -0
  317. package/src/components/data-table-pagination.tsx +126 -0
  318. package/src/components/data-table-sort.ts +70 -0
  319. package/src/components/data-table.tsx +1740 -0
  320. package/src/components/date-picker.tsx +631 -0
  321. package/src/components/dropdown.tsx +341 -0
  322. package/src/components/entity-combobox.tsx +215 -0
  323. package/src/components/facing-pair.ts +86 -0
  324. package/src/components/field-sync.tsx +472 -0
  325. package/src/components/file-dropzone.tsx +181 -0
  326. package/src/components/full-bleed-dialog.tsx +179 -0
  327. package/src/components/grouped-picker.tsx +157 -0
  328. package/src/components/hover-menu.tsx +183 -0
  329. package/src/components/mini-calendar.tsx +554 -0
  330. package/src/components/modal.tsx +287 -0
  331. package/src/components/month-picker.tsx +549 -0
  332. package/src/components/multi-entity-combobox.tsx +226 -0
  333. package/src/components/multi-select.tsx +303 -0
  334. package/src/components/number-field.tsx +225 -0
  335. package/src/components/number-input.tsx +211 -0
  336. package/src/components/numpad-sheet.tsx +264 -0
  337. package/src/components/page-contents.tsx +289 -0
  338. package/src/components/password-strength.tsx +258 -0
  339. package/src/components/picker-sheet.tsx +251 -0
  340. package/src/components/popover.tsx +195 -0
  341. package/src/components/search-field.tsx +146 -0
  342. package/src/components/series-chart-labels.ts +26 -0
  343. package/src/components/series-chart.tsx +481 -0
  344. package/src/components/settings-fields.tsx +68 -0
  345. package/src/components/signature-pad.tsx +630 -0
  346. package/src/components/slider.tsx +313 -0
  347. package/src/components/sparkline.tsx +329 -0
  348. package/src/components/stat-tile.tsx +448 -0
  349. package/src/components/swipeable-row.tsx +271 -0
  350. package/src/components/switch.tsx +170 -0
  351. package/src/components/time-input.tsx +107 -0
  352. package/src/components/toggle-group.tsx +153 -0
  353. package/src/components/toggle-legend.tsx +229 -0
  354. package/src/components/tooltip.tsx +477 -0
  355. package/src/components/treemap.tsx +390 -0
  356. package/src/components/ui.tsx +1284 -0
  357. package/src/components/use-mobile-reveal.ts +54 -0
  358. package/src/components/use-table-state.ts +295 -0
  359. package/src/components/user-avatar.tsx +61 -0
  360. package/src/components/wizard-stepper.tsx +37 -0
  361. package/src/data-table.ts +19 -0
  362. package/src/feedback/feedback-attachment.tsx +246 -0
  363. package/src/feedback/feedback-dialog.tsx +209 -0
  364. package/src/feedback/feedback-inbox.tsx +615 -0
  365. package/src/feedback.ts +8 -0
  366. package/src/hooks/use-anchored-panel.ts +169 -0
  367. package/src/hooks/use-anchored-rect.ts +78 -0
  368. package/src/hooks/use-announce.ts +85 -0
  369. package/src/hooks/use-body-scroll-lock.ts +94 -0
  370. package/src/hooks/use-close-transition.ts +99 -0
  371. package/src/hooks/use-dismiss.ts +77 -0
  372. package/src/hooks/use-focus-trap.ts +160 -0
  373. package/src/hooks/use-media-query.ts +23 -0
  374. package/src/hooks/use-overlay-history.ts +345 -0
  375. package/src/hooks/use-row-swipe.ts +212 -0
  376. package/src/i18n/defaults.ts +68 -0
  377. package/src/i18n/kit-labels.tsx +447 -0
  378. package/src/index.ts +169 -0
  379. package/src/lib/calc.ts +309 -0
  380. package/src/lib/cn.ts +5 -0
  381. package/src/lib/dates.ts +187 -0
  382. package/src/lib/logger.ts +73 -0
  383. package/src/lib/safe-storage.ts +37 -0
  384. package/src/search/command-palette.tsx +342 -0
  385. package/src/search.ts +3 -0
  386. package/src/shell/app-shell.tsx +772 -0
  387. package/src/shell/option-switcher-menu.tsx +72 -0
  388. package/src/shell/role-switcher.tsx +46 -0
  389. package/src/shell/top-bar.tsx +39 -0
  390. package/src/shell/topbar-action-menu.tsx +91 -0
  391. package/src/shell/topbar-controls.tsx +229 -0
  392. package/src/shell.ts +10 -0
  393. package/src/theme/chart-palette.ts +145 -0
  394. package/src/theme/color.ts +266 -0
  395. package/src/theme/palette-derive.ts +681 -0
  396. package/src/theme/palette-presets.ts +760 -0
  397. package/src/theme/palette-store.ts +94 -0
  398. package/src/theme/theme-store.ts +145 -0
  399. package/src/tour/tour.tsx +586 -0
  400. package/src/tour.ts +3 -0
  401. package/src/vite-env.d.ts +26 -0
  402. package/src/wizard/stepper-nav.tsx +221 -0
  403. package/src/wizard/types.ts +178 -0
  404. package/src/wizard/use-wizard.ts +345 -0
  405. package/src/wizard/validation.ts +37 -0
  406. package/src/wizard/wizard-context.tsx +29 -0
  407. package/src/wizard/wizard-step.tsx +14 -0
  408. package/src/wizard/wizard-summary.tsx +69 -0
  409. package/src/wizard.ts +14 -0
  410. package/tokens.css +222 -20
@@ -1,3 +1,4 @@
1
+ "use client";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
3
  import { Fragment, useId, useMemo, useRef, useState } from "react";
3
4
  import { createPortal } from "react-dom";
@@ -8,7 +9,9 @@ import { useDropdown } from "./dropdown.js";
8
9
  import { useAnchoredPanel } from "../hooks/use-anchored-panel.js";
9
10
  import { useMediaQuery } from "../hooks/use-media-query.js";
10
11
  import { PickerSheet, SHEET_ROW_CLASS } from "./picker-sheet.js";
11
- const LIST_CLASS = "max-h-64 overflow-y-auto rounded-md border border-slate-200 bg-white py-1 shadow-lg dark:border-slate-700 dark:bg-slate-900";
12
+ import { useActiveOptionScroll } from "./combobox-core.js";
13
+ import { DEFAULT_COMBOBOX_LABELS, useKitLabels } from "../i18n/kit-labels.js";
14
+ const LIST_CLASS = "max-h-64 overflow-y-auto rounded-md border border-[var(--border)] bg-[var(--bg-surface)] py-1 shadow-lg";
12
15
  function usePrimaryPressOnly() {
13
16
  const auxPress = useRef(false);
14
17
  return {
@@ -24,6 +27,7 @@ function usePrimaryPressOnly() {
24
27
  };
25
28
  }
26
29
  function SuggestionList({
30
+ id,
27
31
  anchorRef,
28
32
  panelRef,
29
33
  children
@@ -37,15 +41,15 @@ function SuggestionList({
37
41
  ref: panelRef,
38
42
  className: "fixed z-50",
39
43
  style: { top, left: rect.left, width: rect.width },
40
- children: /* @__PURE__ */ jsx("ul", { role: "listbox", className: LIST_CLASS, style: { maxHeight }, children })
44
+ children: /* @__PURE__ */ jsx("ul", { id, role: "listbox", className: LIST_CLASS, style: { maxHeight }, children })
41
45
  }
42
46
  ),
43
47
  document.body
44
48
  );
45
49
  }
46
50
  const rowClass = (isActive) => cn(
47
- "block w-full truncate px-3 py-1.5 text-left text-sm text-slate-900 dark:text-slate-100",
48
- isActive ? "bg-slate-100 dark:bg-slate-800" : "hover:bg-slate-50 dark:hover:bg-slate-800"
51
+ "block w-full truncate px-3 py-1.5 text-left text-sm text-[var(--text-primary)]",
52
+ isActive ? "bg-[var(--bg-active)]" : "hover:bg-[var(--bg-hover)]"
49
53
  );
50
54
  function Combobox({
51
55
  value,
@@ -65,10 +69,13 @@ function Combobox({
65
69
  autoFocus,
66
70
  onBlur,
67
71
  onSubmit,
68
- "aria-label": ariaLabel
72
+ "aria-label": ariaLabel,
73
+ ...rest
69
74
  }) {
70
75
  const generated = useId();
71
76
  const fieldId = id ?? generated;
77
+ const listboxId = `${generated}-listbox`;
78
+ const optionId = (index) => `${generated}-option-${index}`;
72
79
  const isPhone = useMediaQuery(PHONE_QUERY, false);
73
80
  const { open, setOpen, wrapperRef, panelRef } = useDropdown({ backCloses: !isPhone });
74
81
  const primaryOnly = usePrimaryPressOnly();
@@ -100,194 +107,230 @@ function Combobox({
100
107
  }
101
108
  return [...byGroup.values()].flat();
102
109
  }, [options, query, groupBy, maxSuggestions, isPhone]);
110
+ const groupHeadings = useMemo(() => {
111
+ if (!groupBy) return null;
112
+ const heads = /* @__PURE__ */ new Map();
113
+ let prev;
114
+ matches.forEach((o, i) => {
115
+ const group = groupBy(o);
116
+ if (group != null && group !== prev) heads.set(i, group);
117
+ prev = group;
118
+ });
119
+ return heads;
120
+ }, [matches, groupBy]);
103
121
  const commit = (v) => {
104
122
  onChange(v);
105
123
  setOpen(false);
106
124
  setActive(-1);
107
125
  setTyping(false);
108
126
  };
127
+ const activeId = !isPhone && active >= 0 && active < matches.length ? optionId(active) : void 0;
128
+ useActiveOptionScroll(activeId);
109
129
  const typed = value.trim();
110
130
  const createRow = createLabel && typed.length > 0 && !options.some((o) => o.toLowerCase() === typed.toLowerCase()) ? createLabel(typed) : null;
111
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: cn("relative", className), children: [
112
- label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
113
- /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: "relative", children: [
114
- /* @__PURE__ */ jsx(
115
- "input",
116
- {
117
- id: fieldId,
118
- value,
119
- placeholder,
120
- "aria-label": ariaLabel ?? label,
121
- role: "combobox",
122
- "aria-expanded": open,
123
- "aria-autocomplete": "list",
124
- "aria-invalid": invalid || void 0,
125
- autoComplete: "off",
126
- autoFocus,
127
- onBlur,
128
- onMouseDown: primaryOnly.onMouseDown,
129
- onFocus: () => {
130
- if (primaryOnly.fromAuxButton()) return;
131
- setOpen(true);
132
- setTyping(false);
133
- if (isPhone) sheetInputRef.current?.focus();
134
- },
135
- onClick: () => {
136
- setOpen(true);
137
- setTyping(false);
138
- },
139
- inputMode: isPhone ? "none" : void 0,
140
- onChange: (e) => {
141
- onChange(e.target.value);
142
- setOpen(true);
143
- setActive(-1);
144
- setTyping(true);
145
- },
146
- onKeyDown: (e) => {
147
- if (e.key === "ArrowDown") {
148
- e.preventDefault();
131
+ return (
132
+ // `rest` dresses the outer box — a `data-tour` anchor, a test id, a form-level
133
+ // `aria-describedby`. Not the NAME: that belongs on the <input> below, which is
134
+ // the combobox a reader meets. Spread FIRST, so the field's ARIA and the
135
+ // handlers carrying live #309 and dev#549 cannot be replaced from outside.
136
+ /* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
137
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
138
+ /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: "relative", children: [
139
+ /* @__PURE__ */ jsx(
140
+ "input",
141
+ {
142
+ id: fieldId,
143
+ value,
144
+ placeholder,
145
+ "aria-label": ariaLabel ?? label,
146
+ role: "combobox",
147
+ "aria-expanded": open,
148
+ "aria-controls": listboxId,
149
+ "aria-activedescendant": activeId,
150
+ "aria-autocomplete": "list",
151
+ "aria-invalid": invalid || void 0,
152
+ autoComplete: "off",
153
+ autoFocus,
154
+ onBlur,
155
+ onMouseDown: primaryOnly.onMouseDown,
156
+ onFocus: () => {
157
+ if (primaryOnly.fromAuxButton()) return;
149
158
  setOpen(true);
150
- setActive((i) => Math.min(i + 1, matches.length - 1));
151
- } else if (e.key === "ArrowUp") {
152
- e.preventDefault();
153
- setActive((i) => Math.max(i - 1, 0));
154
- } else if (e.key === "Enter") {
155
- if (open && active >= 0 && active < matches.length) {
159
+ setTyping(false);
160
+ if (isPhone) sheetInputRef.current?.focus();
161
+ },
162
+ onClick: () => {
163
+ setOpen(true);
164
+ setTyping(false);
165
+ },
166
+ inputMode: isPhone ? "none" : void 0,
167
+ onChange: (e) => {
168
+ onChange(e.target.value);
169
+ setOpen(true);
170
+ setActive(-1);
171
+ setTyping(true);
172
+ },
173
+ onKeyDown: (e) => {
174
+ if (e.key === "ArrowDown") {
175
+ e.preventDefault();
176
+ setOpen(true);
177
+ setActive((i) => Math.min(i + 1, matches.length - 1));
178
+ } else if (e.key === "ArrowUp") {
156
179
  e.preventDefault();
157
- commit(matches[active]);
158
- } else {
159
- if (onSubmit) e.preventDefault();
180
+ setActive((i) => Math.max(i - 1, 0));
181
+ } else if (e.key === "Enter") {
182
+ if (open && active >= 0 && active < matches.length) {
183
+ e.preventDefault();
184
+ commit(matches[active]);
185
+ } else {
186
+ if (onSubmit) e.preventDefault();
187
+ setOpen(false);
188
+ onSubmit?.();
189
+ }
190
+ } else if (e.key === "Escape") {
191
+ setOpen(false);
192
+ setActive(-1);
193
+ } else if (e.key === "Tab") {
160
194
  setOpen(false);
161
- onSubmit?.();
195
+ setActive(-1);
162
196
  }
163
- } else if (e.key === "Escape") {
164
- setOpen(false);
165
- setActive(-1);
166
- }
167
- },
168
- className: cn(FIELD_BASE, label !== void 0 && FIELD_FLOATING_PAD, "pr-9", invalid && FIELD_INVALID)
169
- }
170
- ),
171
- /* @__PURE__ */ jsx(
172
- ChevronDown,
173
- {
174
- "aria-hidden": true,
175
- onMouseDown: (e) => {
176
- e.preventDefault();
177
- setOpen((o) => !o);
178
- },
179
- className: "absolute right-2.5 top-1/2 size-4 -translate-y-1/2 cursor-pointer text-slate-400 dark:text-slate-500"
180
- }
181
- )
182
- ] }),
183
- isPhone && /* @__PURE__ */ jsx(
184
- PickerSheet,
185
- {
186
- open,
187
- onClose: () => setOpen(false),
188
- title: label,
189
- query: value,
190
- onQueryChange: (v) => {
191
- onChange(v);
192
- setTyping(true);
193
- },
194
- searchPlaceholder: searchPlaceholder ?? placeholder,
195
- inputRef: sheetInputRef,
196
- closeLabel,
197
- children: /* @__PURE__ */ jsxs("ul", { role: "listbox", children: [
198
- createRow && /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": false, children: /* @__PURE__ */ jsx(
199
- "button",
200
- {
201
- type: "button",
202
- onClick: () => commit(typed),
203
- className: cn(SHEET_ROW_CLASS, "font-medium text-teal-700 dark:text-teal-300"),
204
- children: createRow
205
- }
206
- ) }),
207
- matches.map((o) => {
208
- const group = groupBy?.(o);
209
- const startsGroup = group != null && group !== (matches[matches.indexOf(o) - 1] && groupBy?.(matches[matches.indexOf(o) - 1]));
210
- return /* @__PURE__ */ jsxs(Fragment, { children: [
211
- startsGroup && /* @__PURE__ */ jsx(
212
- "li",
213
- {
214
- role: "presentation",
215
- className: "px-4 pb-0.5 pt-3 text-[11px] font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400",
216
- children: group
217
- }
218
- ),
219
- /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": o === value, children: /* @__PURE__ */ jsxs(
220
- "button",
221
- {
222
- type: "button",
223
- onClick: () => commit(o),
224
- className: cn(
225
- SHEET_ROW_CLASS,
226
- optionAdornment && "flex items-center justify-between gap-2"
227
- ),
228
- children: [
229
- optionAdornment ? /* @__PURE__ */ jsx("span", { className: "truncate", children: o }) : o,
230
- optionAdornment?.(o)
231
- ]
232
- }
233
- ) })
234
- ] }, o);
235
- })
236
- ] })
237
- }
238
- ),
239
- !isPhone && open && (matches.length > 0 || createRow) && /* @__PURE__ */ jsxs(SuggestionList, { anchorRef: fieldRef, panelRef, children: [
240
- createRow && /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": false, children: /* @__PURE__ */ jsx(
241
- "button",
197
+ },
198
+ className: cn(FIELD_BASE, label !== void 0 && FIELD_FLOATING_PAD, "pr-9", invalid && FIELD_INVALID)
199
+ }
200
+ ),
201
+ /* @__PURE__ */ jsx(
202
+ ChevronDown,
203
+ {
204
+ "aria-hidden": true,
205
+ onMouseDown: (e) => {
206
+ e.preventDefault();
207
+ setOpen((o) => !o);
208
+ },
209
+ className: "absolute right-2.5 top-1/2 size-4 -translate-y-1/2 cursor-pointer text-[var(--text-placeholder)]"
210
+ }
211
+ )
212
+ ] }),
213
+ isPhone && /* @__PURE__ */ jsx(
214
+ PickerSheet,
242
215
  {
243
- type: "button",
244
- onMouseDown: (e) => {
245
- e.preventDefault();
246
- commit(typed);
216
+ open,
217
+ onClose: () => setOpen(false),
218
+ title: label,
219
+ query: value,
220
+ onQueryChange: (v) => {
221
+ onChange(v);
222
+ setTyping(true);
247
223
  },
248
- className: cn(rowClass(false), "font-medium text-teal-700 dark:text-teal-300"),
249
- children: createRow
224
+ searchPlaceholder: searchPlaceholder ?? placeholder,
225
+ inputRef: sheetInputRef,
226
+ closeLabel,
227
+ children: /* @__PURE__ */ jsxs("ul", { id: listboxId, role: "listbox", children: [
228
+ createRow && // `role="option"` rides the BUTTON, not the <li> around it: an option
229
+ // may not contain a separately focusable control, and the button is
230
+ // what a pointer presses. Same shape `CommandPalette` has always had.
231
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
232
+ "button",
233
+ {
234
+ type: "button",
235
+ role: "option",
236
+ "aria-selected": false,
237
+ onClick: () => commit(typed),
238
+ className: cn(SHEET_ROW_CLASS, "font-medium text-[var(--brand)]"),
239
+ children: createRow
240
+ }
241
+ ) }),
242
+ matches.map((o, i) => {
243
+ const group = groupHeadings?.get(i);
244
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
245
+ group !== void 0 && /* @__PURE__ */ jsx(
246
+ "li",
247
+ {
248
+ role: "presentation",
249
+ className: "px-4 pb-0.5 pt-3 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
250
+ children: group
251
+ }
252
+ ),
253
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxs(
254
+ "button",
255
+ {
256
+ type: "button",
257
+ role: "option",
258
+ "aria-selected": o === value,
259
+ onClick: () => commit(o),
260
+ className: cn(
261
+ SHEET_ROW_CLASS,
262
+ optionAdornment && "flex items-center justify-between gap-2"
263
+ ),
264
+ children: [
265
+ optionAdornment ? /* @__PURE__ */ jsx("span", { className: "truncate", children: o }) : o,
266
+ optionAdornment?.(o)
267
+ ]
268
+ }
269
+ ) })
270
+ ] }, o);
271
+ })
272
+ ] })
250
273
  }
251
- ) }),
252
- matches.map((o, i) => {
253
- const group = groupBy?.(o);
254
- const startsGroup = group != null && group !== (matches[i - 1] && groupBy?.(matches[i - 1]));
255
- return /* @__PURE__ */ jsxs(Fragment, { children: [
256
- startsGroup && /* @__PURE__ */ jsx(
257
- "li",
258
- {
259
- role: "presentation",
260
- className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500 first:pt-1 dark:text-slate-400",
261
- children: group
262
- }
263
- ),
264
- /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": i === active, children: /* @__PURE__ */ jsxs(
265
- "button",
266
- {
267
- type: "button",
268
- onMouseDown: (e) => {
269
- e.preventDefault();
270
- commit(o);
271
- },
272
- onMouseEnter: () => setActive(i),
273
- className: cn(
274
- rowClass(i === active),
275
- group != null && "pl-6",
276
- // A row with a badge is a flex row so the label truncates and the
277
- // badge keeps its width; without one it stays the plain block the
278
- // other lists render, so nothing shifts for callers that pass none.
279
- optionAdornment && "flex items-center justify-between gap-2"
280
- ),
281
- children: [
282
- optionAdornment ? /* @__PURE__ */ jsx("span", { className: "truncate", children: o }) : o,
283
- optionAdornment?.(o)
284
- ]
285
- }
286
- ) })
287
- ] }, o);
288
- })
274
+ ),
275
+ !isPhone && open && (matches.length > 0 || createRow) && /* @__PURE__ */ jsxs(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: [
276
+ createRow && /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
277
+ "button",
278
+ {
279
+ type: "button",
280
+ role: "option",
281
+ "aria-selected": false,
282
+ tabIndex: -1,
283
+ onMouseDown: (e) => {
284
+ e.preventDefault();
285
+ commit(typed);
286
+ },
287
+ className: cn(rowClass(false), "font-medium text-[var(--brand)]"),
288
+ children: createRow
289
+ }
290
+ ) }),
291
+ matches.map((o, i) => {
292
+ const group = groupHeadings?.get(i);
293
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
294
+ group !== void 0 && /* @__PURE__ */ jsx(
295
+ "li",
296
+ {
297
+ role: "presentation",
298
+ className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)] first:pt-1",
299
+ children: group
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxs(
303
+ "button",
304
+ {
305
+ type: "button",
306
+ id: optionId(i),
307
+ role: "option",
308
+ "aria-selected": o === value,
309
+ tabIndex: -1,
310
+ onMouseDown: (e) => {
311
+ e.preventDefault();
312
+ commit(o);
313
+ },
314
+ onMouseEnter: () => setActive(i),
315
+ className: cn(
316
+ rowClass(i === active),
317
+ group != null && "pl-6",
318
+ // A row with a badge is a flex row so the label truncates and the
319
+ // badge keeps its width; without one it stays the plain block the
320
+ // other lists render, so nothing shifts for callers that pass none.
321
+ optionAdornment && "flex items-center justify-between gap-2"
322
+ ),
323
+ children: [
324
+ optionAdornment ? /* @__PURE__ */ jsx("span", { className: "truncate", children: o }) : o,
325
+ optionAdornment?.(o)
326
+ ]
327
+ }
328
+ ) })
329
+ ] }, o);
330
+ })
331
+ ] })
289
332
  ] })
290
- ] });
333
+ );
291
334
  }
292
335
  function InlineEntityCombobox({
293
336
  value,
@@ -305,10 +348,13 @@ function InlineEntityCombobox({
305
348
  clearable,
306
349
  clearLabel,
307
350
  invalid,
308
- "aria-label": ariaLabel
351
+ "aria-label": ariaLabel,
352
+ ...rest
309
353
  }) {
310
354
  const generated = useId();
311
355
  const fieldId = id ?? generated;
356
+ const listboxId = `${generated}-listbox`;
357
+ const optionId = (index) => `${generated}-option-${index}`;
312
358
  const isPhone = useMediaQuery(PHONE_QUERY, false);
313
359
  const { open, setOpen, wrapperRef, panelRef } = useDropdown({ backCloses: !isPhone });
314
360
  const primaryOnly = usePrimaryPressOnly();
@@ -317,6 +363,11 @@ function InlineEntityCombobox({
317
363
  const sheetInputRef = useRef(null);
318
364
  const fieldRef = useRef(null);
319
365
  const [sheetQuery, setSheetQuery] = useState("");
366
+ const labels = useKitLabels("combobox", DEFAULT_COMBOBOX_LABELS, {
367
+ search: searchPlaceholder,
368
+ noResults: emptyLabel,
369
+ clear: clearLabel
370
+ });
320
371
  const selected = useMemo(
321
372
  () => value == null ? null : options.find((o) => o.value === value) ?? null,
322
373
  [options, value]
@@ -330,10 +381,10 @@ function InlineEntityCombobox({
330
381
  if (query) {
331
382
  const hit = (s) => s?.toLowerCase().includes(query) ?? false;
332
383
  const starts = options.filter((o) => o.label.toLowerCase().startsWith(query));
333
- const rest = options.filter(
384
+ const rest2 = options.filter(
334
385
  (o) => !o.label.toLowerCase().startsWith(query) && (hit(o.label) || hit(o.sublabel) || hit(o.group))
335
386
  );
336
- ranked = [...starts, ...rest];
387
+ ranked = [...starts, ...rest2];
337
388
  }
338
389
  if (!ranked.some((o) => o.group)) return ranked;
339
390
  const blocks = /* @__PURE__ */ new Map();
@@ -344,6 +395,8 @@ function InlineEntityCombobox({
344
395
  }
345
396
  return [...blocks.values()].flat();
346
397
  }, [options, query]);
398
+ const activeId = !isPhone && active >= 0 && active < matches.length ? optionId(active) : void 0;
399
+ useActiveOptionScroll(activeId);
347
400
  const close = () => {
348
401
  setOpen(false);
349
402
  setActive(-1);
@@ -368,180 +421,196 @@ function InlineEntityCombobox({
368
421
  }
369
422
  close();
370
423
  };
371
- return /* @__PURE__ */ jsxs("div", { ref: wrapperRef, className: cn("relative", className), children: [
372
- label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
373
- /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: "relative", children: [
374
- /* @__PURE__ */ jsx(
375
- "input",
376
- {
377
- id: fieldId,
378
- value: shown,
379
- placeholder,
380
- "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
381
- role: "combobox",
382
- "aria-expanded": open,
383
- "aria-autocomplete": "list",
384
- "aria-invalid": invalid || void 0,
385
- autoComplete: "off",
386
- disabled,
387
- autoFocus,
388
- inputMode: isPhone ? "none" : void 0,
389
- onMouseDown: primaryOnly.onMouseDown,
390
- onFocus: (e) => {
391
- if (primaryOnly.fromAuxButton()) return;
392
- setText(shown);
393
- e.currentTarget.select();
394
- setOpen(true);
395
- if (isPhone) sheetInputRef.current?.focus();
396
- },
397
- onBlur: () => {
398
- if (!isPhone) reconcile();
399
- },
400
- onChange: (e) => {
401
- setText(e.target.value);
402
- setOpen(true);
403
- setActive(-1);
404
- },
405
- onKeyDown: (e) => {
406
- if (e.key === "ArrowDown") {
407
- e.preventDefault();
424
+ return (
425
+ // `rest` dresses the outer box — a `data-tour` anchor, a test id, a form-level
426
+ // `aria-describedby`. Not the NAME: that belongs on the <input> below, which is
427
+ // the combobox a reader meets. Spread FIRST, so the field's ARIA and the
428
+ // handlers carrying live #309 and dev#549 cannot be replaced from outside.
429
+ /* @__PURE__ */ jsxs("div", { ...rest, ref: wrapperRef, className: cn("relative", className), children: [
430
+ label !== void 0 && /* @__PURE__ */ jsx(FieldLabel, { children: label }),
431
+ /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: "relative", children: [
432
+ /* @__PURE__ */ jsx(
433
+ "input",
434
+ {
435
+ id: fieldId,
436
+ value: shown,
437
+ placeholder,
438
+ "aria-label": ariaLabel ?? (typeof label === "string" ? label : void 0),
439
+ role: "combobox",
440
+ "aria-expanded": open,
441
+ "aria-controls": listboxId,
442
+ "aria-activedescendant": activeId,
443
+ "aria-autocomplete": "list",
444
+ "aria-invalid": invalid || void 0,
445
+ autoComplete: "off",
446
+ disabled,
447
+ autoFocus,
448
+ inputMode: isPhone ? "none" : void 0,
449
+ onMouseDown: primaryOnly.onMouseDown,
450
+ onFocus: (e) => {
451
+ if (primaryOnly.fromAuxButton()) return;
452
+ setText(shown);
453
+ e.currentTarget.select();
408
454
  setOpen(true);
409
- setActive((i) => Math.min(i + 1, matches.length - 1));
410
- } else if (e.key === "ArrowUp") {
411
- e.preventDefault();
412
- setActive((i) => Math.max(i - 1, 0));
413
- } else if (e.key === "Enter") {
414
- if (open && active >= 0 && active < matches.length) {
455
+ if (isPhone) sheetInputRef.current?.focus();
456
+ },
457
+ onBlur: () => {
458
+ if (!isPhone) reconcile();
459
+ },
460
+ onChange: (e) => {
461
+ setText(e.target.value);
462
+ setOpen(true);
463
+ setActive(-1);
464
+ },
465
+ onKeyDown: (e) => {
466
+ if (e.key === "ArrowDown") {
415
467
  e.preventDefault();
416
- commit(matches[active]);
417
- } else {
468
+ setOpen(true);
469
+ setActive((i) => Math.min(i + 1, matches.length - 1));
470
+ } else if (e.key === "ArrowUp") {
471
+ e.preventDefault();
472
+ setActive((i) => Math.max(i - 1, 0));
473
+ } else if (e.key === "Enter") {
474
+ if (open && active >= 0 && active < matches.length) {
475
+ e.preventDefault();
476
+ commit(matches[active]);
477
+ } else {
478
+ reconcile();
479
+ }
480
+ } else if (e.key === "Escape") {
481
+ setText(null);
482
+ close();
483
+ } else if (e.key === "Tab") {
418
484
  reconcile();
419
485
  }
420
- } else if (e.key === "Escape") {
421
- setText(null);
422
- close();
423
- }
424
- },
425
- className: cn(FIELD_BASE, label !== void 0 && FIELD_FLOATING_PAD, "pr-9", invalid && FIELD_INVALID)
426
- }
427
- ),
428
- showClear ? /* @__PURE__ */ jsx(
429
- "button",
430
- {
431
- type: "button",
432
- tabIndex: -1,
433
- "aria-label": clearLabel ?? "Clear",
434
- onMouseDown: (e) => e.preventDefault(),
435
- onClick: () => {
436
- setText(null);
437
- close();
438
- onChange(null);
439
- },
440
- className: cn(
441
- "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400",
442
- "hover:text-slate-600 dark:text-slate-500 dark:hover:text-slate-300"
443
- ),
444
- children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-4" })
445
- }
446
- ) : /* @__PURE__ */ jsx(
447
- ChevronDown,
448
- {
449
- "aria-hidden": true,
450
- onMouseDown: (e) => {
451
- e.preventDefault();
452
- setOpen((o) => !o);
453
- },
454
- className: cn(
455
- "absolute right-2.5 top-1/2 size-4 -translate-y-1/2 text-slate-400 dark:text-slate-500",
456
- disabled ? "opacity-50" : "cursor-pointer"
457
- )
458
- }
459
- )
460
- ] }),
461
- isPhone && /* @__PURE__ */ jsx(
462
- PickerSheet,
463
- {
464
- open,
465
- onClose: reconcile,
466
- title: label,
467
- query: sheetQuery,
468
- onQueryChange: (v) => {
469
- setSheetQuery(v);
470
- setActive(-1);
471
- },
472
- searchPlaceholder: searchPlaceholder ?? placeholder,
473
- inputRef: sheetInputRef,
474
- closeLabel,
475
- children: /* @__PURE__ */ jsxs("ul", { role: "listbox", children: [
476
- matches.map((o, i) => (
477
- // Keyed by group AND value, like combobox-core.tsx: an option may
478
- // deliberately appear twice (see `reconcile`), and a bare value key
479
- // would collide.
480
- /* @__PURE__ */ jsxs(Fragment, { children: [
481
- o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
482
- "li",
483
- {
484
- role: "presentation",
485
- className: "px-4 pb-0.5 pt-3 text-[11px] font-semibold uppercase tracking-wide text-slate-500 dark:text-slate-400",
486
- children: o.group
487
- }
488
- ),
489
- /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": o.value === value, children: /* @__PURE__ */ jsx(
490
- "button",
491
- {
492
- type: "button",
493
- onClick: () => commit(o),
494
- className: cn(SHEET_ROW_CLASS, o.value === value && "font-medium"),
495
- children: o.label
496
- }
497
- ) })
498
- ] }, `${o.group ?? ""}|${String(o.value)}`)
499
- )),
500
- matches.length === 0 && emptyLabel && /* @__PURE__ */ jsx("li", { className: "px-4 py-3 text-sm text-slate-500 dark:text-slate-400", children: emptyLabel })
501
- ] })
502
- }
503
- ),
504
- !isPhone && open && matches.length > 0 && /* @__PURE__ */ jsx(SuggestionList, { anchorRef: fieldRef, panelRef, children: matches.map((o, i) => (
505
- // A group heading is emitted at each group boundary rather than repeating
506
- // the group on every row (feedback #136). `matches` is group-contiguous,
507
- // so comparing with the previous row is enough. Fragment key sits here;
508
- // the heading and the option carry their own list semantics — keyed by
509
- // group AND value, because an option may deliberately appear twice (see
510
- // `reconcile`).
511
- /* @__PURE__ */ jsxs(Fragment, { children: [
512
- o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
513
- "li",
514
- {
515
- role: "presentation",
516
- className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-slate-500 first:pt-1 dark:text-slate-400",
517
- children: o.group
486
+ },
487
+ className: cn(FIELD_BASE, label !== void 0 && FIELD_FLOATING_PAD, "pr-9", invalid && FIELD_INVALID)
518
488
  }
519
489
  ),
520
- /* @__PURE__ */ jsx("li", { role: "option", "aria-selected": o.value === value, children: /* @__PURE__ */ jsxs(
490
+ showClear ? /* @__PURE__ */ jsx(
521
491
  "button",
522
492
  {
523
493
  type: "button",
494
+ tabIndex: -1,
495
+ "aria-label": labels.clear,
496
+ onMouseDown: (e) => e.preventDefault(),
497
+ onClick: () => {
498
+ setText(null);
499
+ close();
500
+ onChange(null);
501
+ },
502
+ className: cn(
503
+ "absolute right-2 top-1/2 -translate-y-1/2 rounded p-0.5 text-[var(--text-placeholder)]",
504
+ "hover:text-[var(--text-secondary)]"
505
+ ),
506
+ children: /* @__PURE__ */ jsx(X, { "aria-hidden": true, className: "size-4" })
507
+ }
508
+ ) : /* @__PURE__ */ jsx(
509
+ ChevronDown,
510
+ {
511
+ "aria-hidden": true,
524
512
  onMouseDown: (e) => {
525
513
  e.preventDefault();
526
- commit(o);
514
+ setOpen((o) => !o);
527
515
  },
528
- onMouseEnter: () => setActive(i),
529
516
  className: cn(
530
- rowClass(i === active),
531
- // Indented under its heading, so the hierarchy is readable at a
532
- // glance instead of inferred from grey trailing text.
533
- o.group && "pl-6",
534
- o.value === value && "font-medium"
535
- ),
536
- children: [
537
- o.label,
538
- o.sublabel && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-slate-400 dark:text-slate-500", children: o.sublabel })
539
- ]
517
+ "absolute right-2.5 top-1/2 size-4 -translate-y-1/2 text-[var(--text-placeholder)]",
518
+ disabled ? "opacity-50" : "cursor-pointer"
519
+ )
540
520
  }
541
- ) })
542
- ] }, `${o.group ?? ""}|${String(o.value)}`)
543
- )) })
544
- ] });
521
+ )
522
+ ] }),
523
+ isPhone && /* @__PURE__ */ jsx(
524
+ PickerSheet,
525
+ {
526
+ open,
527
+ onClose: reconcile,
528
+ title: label,
529
+ query: sheetQuery,
530
+ onQueryChange: (v) => {
531
+ setSheetQuery(v);
532
+ setActive(-1);
533
+ },
534
+ searchPlaceholder: searchPlaceholder ?? placeholder ?? labels.search,
535
+ inputRef: sheetInputRef,
536
+ closeLabel,
537
+ children: /* @__PURE__ */ jsxs("ul", { id: listboxId, role: "listbox", children: [
538
+ matches.map((o, i) => (
539
+ // Keyed by group AND value, like combobox-core.tsx: an option may
540
+ // deliberately appear twice (see `reconcile`), and a bare value key
541
+ // would collide.
542
+ /* @__PURE__ */ jsxs(Fragment, { children: [
543
+ o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
544
+ "li",
545
+ {
546
+ role: "presentation",
547
+ className: "px-4 pb-0.5 pt-3 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)]",
548
+ children: o.group
549
+ }
550
+ ),
551
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsx(
552
+ "button",
553
+ {
554
+ type: "button",
555
+ role: "option",
556
+ "aria-selected": o.value === value,
557
+ onClick: () => commit(o),
558
+ className: cn(SHEET_ROW_CLASS, o.value === value && "font-medium"),
559
+ children: o.label
560
+ }
561
+ ) })
562
+ ] }, `${o.group ?? ""}|${String(o.value)}`)
563
+ )),
564
+ matches.length === 0 && /* @__PURE__ */ jsx("li", { className: "px-4 py-3 text-sm text-[var(--text-muted)]", children: labels.noResults })
565
+ ] })
566
+ }
567
+ ),
568
+ !isPhone && open && matches.length > 0 && /* @__PURE__ */ jsx(SuggestionList, { id: listboxId, anchorRef: fieldRef, panelRef, children: matches.map((o, i) => (
569
+ // A group heading is emitted at each group boundary rather than repeating
570
+ // the group on every row (feedback #136). `matches` is group-contiguous,
571
+ // so comparing with the previous row is enough. Fragment key sits here;
572
+ // the heading and the option carry their own list semantics — keyed by
573
+ // group AND value, because an option may deliberately appear twice (see
574
+ // `reconcile`).
575
+ /* @__PURE__ */ jsxs(Fragment, { children: [
576
+ o.group && o.group !== matches[i - 1]?.group && /* @__PURE__ */ jsx(
577
+ "li",
578
+ {
579
+ role: "presentation",
580
+ className: "px-3 pb-0.5 pt-2 text-[11px] font-semibold uppercase tracking-wide text-[var(--text-muted)] first:pt-1",
581
+ children: o.group
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsx("li", { role: "presentation", children: /* @__PURE__ */ jsxs(
585
+ "button",
586
+ {
587
+ type: "button",
588
+ id: optionId(i),
589
+ role: "option",
590
+ "aria-selected": o.value === value,
591
+ tabIndex: -1,
592
+ onMouseDown: (e) => {
593
+ e.preventDefault();
594
+ commit(o);
595
+ },
596
+ onMouseEnter: () => setActive(i),
597
+ className: cn(
598
+ rowClass(i === active),
599
+ // Indented under its heading, so the hierarchy is readable at a
600
+ // glance instead of inferred from grey trailing text.
601
+ o.group && "pl-6",
602
+ o.value === value && "font-medium"
603
+ ),
604
+ children: [
605
+ o.label,
606
+ o.sublabel && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-[var(--text-placeholder)]", children: o.sublabel })
607
+ ]
608
+ }
609
+ ) })
610
+ ] }, `${o.group ?? ""}|${String(o.value)}`)
611
+ )) })
612
+ ] })
613
+ );
545
614
  }
546
615
  export {
547
616
  Combobox,