@womp/kookie-ui 0.4.0 → 0.6.2

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 (548) hide show
  1. package/components.css +783 -783
  2. package/dist/cjs/components/_internal/base-menu.utils.d.ts +48 -0
  3. package/dist/cjs/components/_internal/base-menu.utils.d.ts.map +1 -0
  4. package/dist/cjs/components/_internal/base-menu.utils.js +2 -0
  5. package/dist/cjs/components/_internal/base-menu.utils.js.map +7 -0
  6. package/dist/cjs/components/_internal/dropdown-menu-drill-down.d.ts +77 -59
  7. package/dist/cjs/components/_internal/dropdown-menu-drill-down.d.ts.map +1 -1
  8. package/dist/cjs/components/_internal/dropdown-menu-drill-down.js +1 -1
  9. package/dist/cjs/components/_internal/dropdown-menu-drill-down.js.map +4 -4
  10. package/dist/cjs/components/_internal/menu-context.d.ts +7 -3
  11. package/dist/cjs/components/_internal/menu-context.d.ts.map +1 -1
  12. package/dist/cjs/components/_internal/menu-context.js +1 -1
  13. package/dist/cjs/components/_internal/menu-context.js.map +4 -4
  14. package/dist/cjs/components/_internal/shell-bottom.js +1 -1
  15. package/dist/cjs/components/_internal/shell-bottom.js.map +2 -2
  16. package/dist/cjs/components/_internal/shell-inspector.js +1 -1
  17. package/dist/cjs/components/_internal/shell-inspector.js.map +2 -2
  18. package/dist/cjs/components/_internal/shell-sidebar.js +1 -1
  19. package/dist/cjs/components/_internal/shell-sidebar.js.map +2 -2
  20. package/dist/cjs/components/accordion.js +1 -1
  21. package/dist/cjs/components/alert-dialog.js +1 -1
  22. package/dist/cjs/components/alert-dialog.js.map +2 -2
  23. package/dist/cjs/components/badge.js +1 -1
  24. package/dist/cjs/components/badge.js.map +2 -2
  25. package/dist/cjs/components/blockquote.js +1 -1
  26. package/dist/cjs/components/button.js +1 -1
  27. package/dist/cjs/components/button.js.map +2 -2
  28. package/dist/cjs/components/callout.js +1 -1
  29. package/dist/cjs/components/callout.js.map +2 -2
  30. package/dist/cjs/components/card.js +1 -1
  31. package/dist/cjs/components/card.js.map +2 -2
  32. package/dist/cjs/components/chatbar.d.ts +46 -44
  33. package/dist/cjs/components/chatbar.d.ts.map +1 -1
  34. package/dist/cjs/components/chatbar.js +2 -2
  35. package/dist/cjs/components/chatbar.js.map +3 -3
  36. package/dist/cjs/components/checkbox-cards.js +1 -1
  37. package/dist/cjs/components/checkbox-cards.js.map +2 -2
  38. package/dist/cjs/components/checkbox-group.js +1 -1
  39. package/dist/cjs/components/checkbox.js +1 -1
  40. package/dist/cjs/components/combobox.d.ts +28 -15
  41. package/dist/cjs/components/combobox.d.ts.map +1 -1
  42. package/dist/cjs/components/combobox.js +1 -1
  43. package/dist/cjs/components/combobox.js.map +4 -4
  44. package/dist/cjs/components/combobox.props.d.ts +48 -0
  45. package/dist/cjs/components/combobox.props.d.ts.map +1 -1
  46. package/dist/cjs/components/combobox.props.js +1 -1
  47. package/dist/cjs/components/combobox.props.js.map +4 -4
  48. package/dist/cjs/components/context-menu.d.ts +18 -5
  49. package/dist/cjs/components/context-menu.d.ts.map +1 -1
  50. package/dist/cjs/components/context-menu.js +1 -1
  51. package/dist/cjs/components/context-menu.js.map +4 -4
  52. package/dist/cjs/components/context-menu.props.d.ts +1 -1
  53. package/dist/cjs/components/context-menu.props.d.ts.map +1 -1
  54. package/dist/cjs/components/context-menu.props.js +1 -1
  55. package/dist/cjs/components/context-menu.props.js.map +3 -3
  56. package/dist/cjs/components/dialog.js +1 -1
  57. package/dist/cjs/components/dialog.js.map +2 -2
  58. package/dist/cjs/components/dropdown-menu.d.ts +11 -18
  59. package/dist/cjs/components/dropdown-menu.d.ts.map +1 -1
  60. package/dist/cjs/components/dropdown-menu.js +1 -1
  61. package/dist/cjs/components/dropdown-menu.js.map +4 -4
  62. package/dist/cjs/components/heading.js +1 -1
  63. package/dist/cjs/components/hover-card.js +1 -1
  64. package/dist/cjs/components/hover-card.js.map +2 -2
  65. package/dist/cjs/components/icon-button.js +1 -1
  66. package/dist/cjs/components/icon-button.js.map +2 -2
  67. package/dist/cjs/components/index.js +5 -5
  68. package/dist/cjs/components/index.js.map +4 -4
  69. package/dist/cjs/components/link.js +1 -1
  70. package/dist/cjs/components/popover.js +1 -1
  71. package/dist/cjs/components/popover.js.map +2 -2
  72. package/dist/cjs/components/radio-cards.js +1 -1
  73. package/dist/cjs/components/radio-cards.js.map +2 -2
  74. package/dist/cjs/components/radio.js +1 -1
  75. package/dist/cjs/components/radio.js.map +2 -2
  76. package/dist/cjs/components/schemas/base-button.schema.d.ts +111 -256
  77. package/dist/cjs/components/schemas/base-button.schema.d.ts.map +1 -1
  78. package/dist/cjs/components/schemas/button.schema.d.ts +136 -557
  79. package/dist/cjs/components/schemas/button.schema.d.ts.map +1 -1
  80. package/dist/cjs/components/schemas/icon-button.schema.d.ts +123 -240
  81. package/dist/cjs/components/schemas/icon-button.schema.d.ts.map +1 -1
  82. package/dist/cjs/components/schemas/index.js +1 -1
  83. package/dist/cjs/components/schemas/index.js.map +2 -2
  84. package/dist/cjs/components/schemas/shell.schema.d.ts +592 -851
  85. package/dist/cjs/components/schemas/shell.schema.d.ts.map +1 -1
  86. package/dist/cjs/components/schemas/shell.schema.js +1 -1
  87. package/dist/cjs/components/schemas/shell.schema.js.map +2 -2
  88. package/dist/cjs/components/schemas/toggle-button.schema.d.ts +138 -1026
  89. package/dist/cjs/components/schemas/toggle-button.schema.d.ts.map +1 -1
  90. package/dist/cjs/components/schemas/toggle-icon-button.schema.d.ts +124 -465
  91. package/dist/cjs/components/schemas/toggle-icon-button.schema.d.ts.map +1 -1
  92. package/dist/cjs/components/scroll-area.js +1 -1
  93. package/dist/cjs/components/segmented-control.js +1 -1
  94. package/dist/cjs/components/select.js +1 -1
  95. package/dist/cjs/components/select.js.map +2 -2
  96. package/dist/cjs/components/sheet.js +1 -1
  97. package/dist/cjs/components/sheet.js.map +2 -2
  98. package/dist/cjs/components/shell.js +1 -1
  99. package/dist/cjs/components/shell.js.map +3 -3
  100. package/dist/cjs/components/sidebar.js +1 -1
  101. package/dist/cjs/components/sidebar.js.map +4 -4
  102. package/dist/cjs/components/slider.js +1 -1
  103. package/dist/cjs/components/slot.d.ts +6 -2
  104. package/dist/cjs/components/slot.d.ts.map +1 -1
  105. package/dist/cjs/components/switch.js +1 -1
  106. package/dist/cjs/components/tab-nav.js +1 -1
  107. package/dist/cjs/components/text-field.js +2 -2
  108. package/dist/cjs/components/text.js +1 -1
  109. package/dist/cjs/components/theme-panel.js +3 -3
  110. package/dist/cjs/components/theme-panel.js.map +2 -2
  111. package/dist/cjs/components/theme.js +1 -1
  112. package/dist/cjs/components/theme.js.map +2 -2
  113. package/dist/cjs/components/theme.props.d.ts +1 -1
  114. package/dist/cjs/components/theme.props.js +1 -1
  115. package/dist/cjs/components/theme.props.js.map +2 -2
  116. package/dist/cjs/components/toggle-button.js +1 -1
  117. package/dist/cjs/components/toggle-button.js.map +2 -2
  118. package/dist/cjs/components/toggle-icon-button.js +1 -1
  119. package/dist/cjs/components/toggle-icon-button.js.map +2 -2
  120. package/dist/cjs/components/tooltip.js +1 -1
  121. package/dist/cjs/components/tooltip.js.map +2 -2
  122. package/dist/cjs/components/user-card.js +1 -1
  123. package/dist/cjs/components/user-card.js.map +2 -2
  124. package/dist/cjs/components/virtual-menu.d.ts.map +1 -1
  125. package/dist/cjs/components/virtual-menu.js +1 -1
  126. package/dist/cjs/components/virtual-menu.js.map +4 -4
  127. package/dist/cjs/helpers/extract-props.js +1 -1
  128. package/dist/cjs/helpers/extract-props.js.map +2 -2
  129. package/dist/cjs/helpers/get-margin-styles.js +1 -1
  130. package/dist/cjs/helpers/get-margin-styles.js.map +2 -2
  131. package/dist/cjs/hooks/use-breakpoint.d.ts +4 -1
  132. package/dist/cjs/hooks/use-breakpoint.d.ts.map +1 -1
  133. package/dist/cjs/hooks/use-breakpoint.js +1 -1
  134. package/dist/cjs/hooks/use-breakpoint.js.map +3 -3
  135. package/dist/cjs/index.js +5 -5
  136. package/dist/cjs/index.js.map +4 -4
  137. package/dist/cjs/props/index.js +1 -1
  138. package/dist/cjs/props/index.js.map +3 -3
  139. package/dist/esm/{chunk-P7HYVJSU.js → chunk-2TIADHPD.js} +2 -2
  140. package/dist/esm/chunk-37OGSEQR.js +2 -0
  141. package/dist/esm/chunk-37OGSEQR.js.map +7 -0
  142. package/dist/esm/{chunk-RMKXWYOV.js → chunk-3OBXZUFJ.js} +2 -2
  143. package/dist/esm/{chunk-S2HA3W3V.js → chunk-3R7XMRV5.js} +2 -2
  144. package/dist/esm/chunk-4SMJ7D4S.js +2 -0
  145. package/dist/esm/chunk-4SMJ7D4S.js.map +7 -0
  146. package/dist/esm/{chunk-S56YSRHP.js → chunk-56TS6OKQ.js} +2 -2
  147. package/dist/esm/{chunk-2FYJTSOP.js → chunk-5BYHEYO6.js} +2 -2
  148. package/dist/esm/{chunk-B24BDVG2.js → chunk-5MNBQROW.js} +2 -2
  149. package/dist/esm/{chunk-W7N7V5YD.js → chunk-7ESHXHHO.js} +2 -2
  150. package/dist/esm/{chunk-CRWYCLTE.js → chunk-ABSKRHVM.js} +2 -2
  151. package/dist/esm/chunk-ADBLURLJ.js +2 -0
  152. package/dist/esm/chunk-ADBLURLJ.js.map +7 -0
  153. package/dist/esm/{chunk-3MWWP4MW.js → chunk-BBJUB52V.js} +2 -2
  154. package/dist/esm/{chunk-PTLFMWCN.js → chunk-CQNPBTM5.js} +2 -2
  155. package/dist/esm/{chunk-GJSGKE7B.js → chunk-CTA5ODAO.js} +2 -2
  156. package/dist/esm/{chunk-GWXVETMF.js → chunk-DCAZPMOB.js} +2 -2
  157. package/dist/esm/{chunk-PLWXTEVE.js → chunk-DMJJM5IS.js} +2 -2
  158. package/dist/esm/{chunk-A2RZGGMY.js → chunk-DQAUX5ZL.js} +2 -2
  159. package/dist/esm/{chunk-6XGZPIUT.js → chunk-DX5JJYMZ.js} +2 -2
  160. package/dist/esm/{chunk-6XGZPIUT.js.map → chunk-DX5JJYMZ.js.map} +2 -2
  161. package/dist/esm/{chunk-YUP6EICB.js → chunk-DXL6V2A5.js} +2 -2
  162. package/dist/esm/{chunk-XMRIRFM7.js → chunk-FG7XM7KJ.js} +2 -2
  163. package/dist/esm/{chunk-IKSN4RNB.js → chunk-FIB3XATL.js} +3 -3
  164. package/dist/esm/chunk-FMBECDL4.js +2 -0
  165. package/dist/esm/{chunk-4XJY6FIK.js.map → chunk-FMBECDL4.js.map} +3 -3
  166. package/dist/esm/{chunk-374ZYJUP.js → chunk-HBR42MIX.js} +2 -2
  167. package/dist/esm/{chunk-2UPAYDKR.js → chunk-HIKVMKYQ.js} +2 -2
  168. package/dist/esm/{chunk-VX6ARMWP.js → chunk-HSAG7C53.js} +2 -2
  169. package/dist/esm/{chunk-OSRJRBKP.js → chunk-HW324FK3.js} +2 -2
  170. package/dist/esm/{chunk-DNHUQLXO.js → chunk-JBO5YOUM.js} +2 -2
  171. package/dist/esm/{chunk-HF5WY5YE.js → chunk-JGLAE5G7.js} +2 -2
  172. package/dist/esm/{chunk-4YFJWXL2.js → chunk-JJKQ7CGI.js} +2 -2
  173. package/dist/esm/chunk-JSW6J6RS.js +2 -0
  174. package/dist/esm/chunk-JSW6J6RS.js.map +7 -0
  175. package/dist/esm/chunk-KRDP5SQ7.js +2 -0
  176. package/dist/esm/chunk-KRDP5SQ7.js.map +7 -0
  177. package/dist/esm/{chunk-CWRNW44F.js → chunk-L6PVH3RM.js} +2 -2
  178. package/dist/esm/{chunk-MK2R6DAU.js → chunk-LAOMR7SE.js} +2 -2
  179. package/dist/esm/{chunk-FHOCUZDR.js → chunk-NIQT45PJ.js} +2 -2
  180. package/dist/esm/chunk-NKEJTTIL.js +2 -0
  181. package/dist/esm/chunk-NKEJTTIL.js.map +7 -0
  182. package/dist/esm/chunk-NVDXXGVW.js +2 -0
  183. package/dist/esm/chunk-NVDXXGVW.js.map +7 -0
  184. package/dist/esm/chunk-OS3FCYZY.js +2 -0
  185. package/dist/esm/{chunk-7QDTSBJF.js → chunk-OYF6YXH7.js} +2 -2
  186. package/dist/esm/{chunk-WFMYNZXZ.js → chunk-P5OSU4NX.js} +2 -2
  187. package/dist/esm/{chunk-QEOAXJLN.js → chunk-PTS6QYQW.js} +2 -2
  188. package/dist/esm/{chunk-CVWCKOHO.js → chunk-RLPKKCBL.js} +2 -2
  189. package/dist/esm/{chunk-LKAK5GXF.js → chunk-SPIU5JYC.js} +2 -2
  190. package/dist/esm/chunk-SZTZ2XLI.js +2 -0
  191. package/dist/esm/chunk-SZTZ2XLI.js.map +7 -0
  192. package/dist/esm/chunk-T2FZN7G3.js +2 -0
  193. package/dist/esm/chunk-T2FZN7G3.js.map +7 -0
  194. package/dist/esm/{chunk-4AQTUBVP.js → chunk-T4UHUQFJ.js} +2 -2
  195. package/dist/esm/{chunk-K6QZMNCD.js → chunk-TS623UPD.js} +2 -2
  196. package/dist/esm/{chunk-YV2KYHGR.js → chunk-UBOOUDS4.js} +2 -2
  197. package/dist/esm/{chunk-D6B7IMZU.js → chunk-UFI53QUC.js} +2 -2
  198. package/dist/esm/{chunk-AU3D6F42.js → chunk-UIRSGVJG.js} +2 -2
  199. package/dist/esm/{chunk-FXUUI6Z2.js → chunk-UUUZXUIP.js} +2 -2
  200. package/dist/esm/{chunk-25WZA7ZU.js → chunk-VA4CTVQL.js} +2 -2
  201. package/dist/esm/{chunk-TOLLPRMM.js → chunk-VNPNEJI6.js} +2 -2
  202. package/dist/esm/{chunk-6IGTK4JE.js → chunk-YMUGKYGW.js} +2 -2
  203. package/dist/esm/chunk-ZWYYPFZW.js +2 -0
  204. package/dist/esm/chunk-ZWYYPFZW.js.map +7 -0
  205. package/dist/esm/{chunk-M24UXMOQ.js → chunk-ZXZOH63L.js} +2 -2
  206. package/dist/esm/components/_internal/base-button.js +1 -1
  207. package/dist/esm/components/_internal/base-button.props.js +1 -1
  208. package/dist/esm/components/_internal/base-checkbox.props.js +1 -1
  209. package/dist/esm/components/_internal/base-menu.props.js +1 -1
  210. package/dist/esm/components/_internal/base-menu.utils.d.ts +48 -0
  211. package/dist/esm/components/_internal/base-menu.utils.d.ts.map +1 -0
  212. package/dist/esm/components/_internal/base-menu.utils.js +2 -0
  213. package/dist/esm/components/_internal/base-menu.utils.js.map +7 -0
  214. package/dist/esm/components/_internal/base-radio.props.js +1 -1
  215. package/dist/esm/components/_internal/base-tab-list.props.js +1 -1
  216. package/dist/esm/components/_internal/dropdown-menu-drill-down.d.ts +77 -59
  217. package/dist/esm/components/_internal/dropdown-menu-drill-down.d.ts.map +1 -1
  218. package/dist/esm/components/_internal/dropdown-menu-drill-down.js +1 -1
  219. package/dist/esm/components/_internal/menu-context.d.ts +7 -3
  220. package/dist/esm/components/_internal/menu-context.d.ts.map +1 -1
  221. package/dist/esm/components/_internal/menu-context.js +1 -1
  222. package/dist/esm/components/_internal/shell-bottom.js +1 -1
  223. package/dist/esm/components/_internal/shell-handles.js +1 -1
  224. package/dist/esm/components/_internal/shell-inspector.js +1 -1
  225. package/dist/esm/components/_internal/shell-prop-helpers.js +1 -1
  226. package/dist/esm/components/_internal/shell-resize.js +1 -1
  227. package/dist/esm/components/_internal/shell-sidebar.js +1 -1
  228. package/dist/esm/components/accessible-icon.js +1 -1
  229. package/dist/esm/components/accordion.js +1 -1
  230. package/dist/esm/components/accordion.props.js +1 -1
  231. package/dist/esm/components/alert-dialog.js +1 -1
  232. package/dist/esm/components/alert-dialog.props.js +1 -1
  233. package/dist/esm/components/aspect-ratio.js +1 -1
  234. package/dist/esm/components/avatar.js +1 -1
  235. package/dist/esm/components/avatar.props.js +1 -1
  236. package/dist/esm/components/badge.js +1 -1
  237. package/dist/esm/components/badge.props.js +1 -1
  238. package/dist/esm/components/blockquote.js +1 -1
  239. package/dist/esm/components/blockquote.props.js +1 -1
  240. package/dist/esm/components/box.js +1 -1
  241. package/dist/esm/components/box.props.js +1 -1
  242. package/dist/esm/components/button.js +1 -1
  243. package/dist/esm/components/button.props.js +1 -1
  244. package/dist/esm/components/callout.js +1 -1
  245. package/dist/esm/components/callout.props.js +1 -1
  246. package/dist/esm/components/card.js +1 -1
  247. package/dist/esm/components/card.props.js +1 -1
  248. package/dist/esm/components/chatbar.d.ts +46 -44
  249. package/dist/esm/components/chatbar.d.ts.map +1 -1
  250. package/dist/esm/components/chatbar.js +1 -1
  251. package/dist/esm/components/checkbox-cards.js +1 -1
  252. package/dist/esm/components/checkbox-cards.props.js +1 -1
  253. package/dist/esm/components/checkbox-group.js +1 -1
  254. package/dist/esm/components/checkbox-group.primitive.js +1 -1
  255. package/dist/esm/components/checkbox-group.props.js +1 -1
  256. package/dist/esm/components/checkbox.js +1 -1
  257. package/dist/esm/components/checkbox.props.js +1 -1
  258. package/dist/esm/components/code.js +1 -1
  259. package/dist/esm/components/code.props.js +1 -1
  260. package/dist/esm/components/combobox.d.ts +28 -15
  261. package/dist/esm/components/combobox.d.ts.map +1 -1
  262. package/dist/esm/components/combobox.js +1 -1
  263. package/dist/esm/components/combobox.props.d.ts +48 -0
  264. package/dist/esm/components/combobox.props.d.ts.map +1 -1
  265. package/dist/esm/components/combobox.props.js +1 -1
  266. package/dist/esm/components/container.js +1 -1
  267. package/dist/esm/components/container.props.js +1 -1
  268. package/dist/esm/components/context-menu.d.ts +18 -5
  269. package/dist/esm/components/context-menu.d.ts.map +1 -1
  270. package/dist/esm/components/context-menu.js +1 -1
  271. package/dist/esm/components/context-menu.props.d.ts +1 -1
  272. package/dist/esm/components/context-menu.props.d.ts.map +1 -1
  273. package/dist/esm/components/context-menu.props.js +1 -1
  274. package/dist/esm/components/data-list.js +1 -1
  275. package/dist/esm/components/data-list.props.js +1 -1
  276. package/dist/esm/components/dialog.js +1 -1
  277. package/dist/esm/components/dialog.props.js +1 -1
  278. package/dist/esm/components/dropdown-menu.d.ts +11 -18
  279. package/dist/esm/components/dropdown-menu.d.ts.map +1 -1
  280. package/dist/esm/components/dropdown-menu.js +1 -1
  281. package/dist/esm/components/dropdown-menu.props.js +1 -1
  282. package/dist/esm/components/em.js +1 -1
  283. package/dist/esm/components/em.props.js +1 -1
  284. package/dist/esm/components/flex.js +1 -1
  285. package/dist/esm/components/flex.props.js +1 -1
  286. package/dist/esm/components/grid.js +1 -1
  287. package/dist/esm/components/grid.props.js +1 -1
  288. package/dist/esm/components/heading.js +1 -1
  289. package/dist/esm/components/heading.props.js +1 -1
  290. package/dist/esm/components/hover-card.js +1 -1
  291. package/dist/esm/components/hover-card.props.js +1 -1
  292. package/dist/esm/components/icon-button.js +1 -1
  293. package/dist/esm/components/icon-button.props.js +1 -1
  294. package/dist/esm/components/icons.js +1 -1
  295. package/dist/esm/components/image.js +1 -1
  296. package/dist/esm/components/image.props.js +1 -1
  297. package/dist/esm/components/index.js +1 -1
  298. package/dist/esm/components/inset.js +1 -1
  299. package/dist/esm/components/inset.props.js +1 -1
  300. package/dist/esm/components/kbd.js +1 -1
  301. package/dist/esm/components/kbd.props.js +1 -1
  302. package/dist/esm/components/link.js +1 -1
  303. package/dist/esm/components/link.props.js +1 -1
  304. package/dist/esm/components/navbar.js +1 -1
  305. package/dist/esm/components/navbar.props.js +1 -1
  306. package/dist/esm/components/popover.js +1 -1
  307. package/dist/esm/components/popover.props.js +1 -1
  308. package/dist/esm/components/portal.js +1 -1
  309. package/dist/esm/components/progress.js +1 -1
  310. package/dist/esm/components/progress.props.js +1 -1
  311. package/dist/esm/components/quote.js +1 -1
  312. package/dist/esm/components/quote.props.js +1 -1
  313. package/dist/esm/components/radio-cards.js +1 -1
  314. package/dist/esm/components/radio-cards.props.js +1 -1
  315. package/dist/esm/components/radio-group.js +1 -1
  316. package/dist/esm/components/radio-group.props.js +1 -1
  317. package/dist/esm/components/radio.js +1 -1
  318. package/dist/esm/components/radio.props.js +1 -1
  319. package/dist/esm/components/reset.js +1 -1
  320. package/dist/esm/components/schemas/base-button.schema.d.ts +111 -256
  321. package/dist/esm/components/schemas/base-button.schema.d.ts.map +1 -1
  322. package/dist/esm/components/schemas/base-button.schema.js +1 -1
  323. package/dist/esm/components/schemas/button.schema.d.ts +136 -557
  324. package/dist/esm/components/schemas/button.schema.d.ts.map +1 -1
  325. package/dist/esm/components/schemas/button.schema.js +1 -1
  326. package/dist/esm/components/schemas/icon-button.schema.d.ts +123 -240
  327. package/dist/esm/components/schemas/icon-button.schema.d.ts.map +1 -1
  328. package/dist/esm/components/schemas/icon-button.schema.js +1 -1
  329. package/dist/esm/components/schemas/index.js +1 -1
  330. package/dist/esm/components/schemas/shell.schema.d.ts +592 -851
  331. package/dist/esm/components/schemas/shell.schema.d.ts.map +1 -1
  332. package/dist/esm/components/schemas/shell.schema.js +1 -1
  333. package/dist/esm/components/schemas/toggle-button.schema.d.ts +138 -1026
  334. package/dist/esm/components/schemas/toggle-button.schema.d.ts.map +1 -1
  335. package/dist/esm/components/schemas/toggle-button.schema.js +1 -1
  336. package/dist/esm/components/schemas/toggle-icon-button.schema.d.ts +124 -465
  337. package/dist/esm/components/schemas/toggle-icon-button.schema.d.ts.map +1 -1
  338. package/dist/esm/components/schemas/toggle-icon-button.schema.js +1 -1
  339. package/dist/esm/components/scroll-area.js +1 -1
  340. package/dist/esm/components/scroll-area.props.js +1 -1
  341. package/dist/esm/components/section.js +1 -1
  342. package/dist/esm/components/section.props.js +1 -1
  343. package/dist/esm/components/segmented-control.js +1 -1
  344. package/dist/esm/components/segmented-control.props.js +1 -1
  345. package/dist/esm/components/select.js +1 -1
  346. package/dist/esm/components/select.props.js +1 -1
  347. package/dist/esm/components/separator.js +1 -1
  348. package/dist/esm/components/separator.props.js +1 -1
  349. package/dist/esm/components/sheet.js +1 -1
  350. package/dist/esm/components/shell.context.js +1 -1
  351. package/dist/esm/components/shell.hooks.js +1 -1
  352. package/dist/esm/components/shell.js +1 -1
  353. package/dist/esm/components/shell.types.js +1 -1
  354. package/dist/esm/components/sidebar.js +1 -1
  355. package/dist/esm/components/sidebar.props.js +1 -1
  356. package/dist/esm/components/skeleton.js +1 -1
  357. package/dist/esm/components/skeleton.props.js +1 -1
  358. package/dist/esm/components/slider.js +1 -1
  359. package/dist/esm/components/slider.props.js +1 -1
  360. package/dist/esm/components/slot.d.ts +6 -2
  361. package/dist/esm/components/slot.d.ts.map +1 -1
  362. package/dist/esm/components/slot.js +1 -1
  363. package/dist/esm/components/spinner.js +1 -1
  364. package/dist/esm/components/spinner.props.js +1 -1
  365. package/dist/esm/components/strong.js +1 -1
  366. package/dist/esm/components/strong.props.js +1 -1
  367. package/dist/esm/components/switch.js +1 -1
  368. package/dist/esm/components/switch.props.js +1 -1
  369. package/dist/esm/components/tab-nav.js +1 -1
  370. package/dist/esm/components/tab-nav.props.js +1 -1
  371. package/dist/esm/components/table.js +1 -1
  372. package/dist/esm/components/table.props.js +1 -1
  373. package/dist/esm/components/tabs.js +1 -1
  374. package/dist/esm/components/tabs.props.js +1 -1
  375. package/dist/esm/components/text-area.js +1 -1
  376. package/dist/esm/components/text-area.props.js +1 -1
  377. package/dist/esm/components/text-field.js +1 -1
  378. package/dist/esm/components/text-field.props.js +1 -1
  379. package/dist/esm/components/text.js +1 -1
  380. package/dist/esm/components/text.props.js +1 -1
  381. package/dist/esm/components/theme-panel.js +1 -1
  382. package/dist/esm/components/theme.js +1 -1
  383. package/dist/esm/components/theme.props.d.ts +1 -1
  384. package/dist/esm/components/theme.props.js +1 -1
  385. package/dist/esm/components/toggle-button.js +1 -1
  386. package/dist/esm/components/toggle-icon-button.js +1 -1
  387. package/dist/esm/components/toolbar.js +1 -1
  388. package/dist/esm/components/tooltip.js +1 -1
  389. package/dist/esm/components/tooltip.props.js +1 -1
  390. package/dist/esm/components/user-card.js +1 -1
  391. package/dist/esm/components/user-card.props.js +1 -1
  392. package/dist/esm/components/virtual-menu.d.ts.map +1 -1
  393. package/dist/esm/components/virtual-menu.js +1 -1
  394. package/dist/esm/components/visually-hidden.js +1 -1
  395. package/dist/esm/helpers/compile-prop-defs.js +1 -1
  396. package/dist/esm/helpers/extract-margin-props.js +1 -1
  397. package/dist/esm/helpers/extract-props.js +1 -1
  398. package/dist/esm/helpers/font-config.js +1 -1
  399. package/dist/esm/helpers/get-margin-styles.js +1 -1
  400. package/dist/esm/helpers/get-matching-gray-color.js +1 -1
  401. package/dist/esm/helpers/get-responsive-styles.js +1 -1
  402. package/dist/esm/helpers/get-subtree.js +1 -1
  403. package/dist/esm/helpers/has-own-property.js +1 -1
  404. package/dist/esm/helpers/index.js +1 -1
  405. package/dist/esm/helpers/inert.js +1 -1
  406. package/dist/esm/helpers/is-responsive-object.js +1 -1
  407. package/dist/esm/helpers/map-prop-values.js +1 -1
  408. package/dist/esm/helpers/merge-styles.js +1 -1
  409. package/dist/esm/helpers/normalize-to-px.js +1 -1
  410. package/dist/esm/helpers/require-react-element.js +1 -1
  411. package/dist/esm/helpers/use-deprecation-warning.js +1 -1
  412. package/dist/esm/hooks/index.js +1 -1
  413. package/dist/esm/hooks/use-body-pointer-events-cleanup.js +1 -1
  414. package/dist/esm/hooks/use-breakpoint.d.ts +4 -1
  415. package/dist/esm/hooks/use-breakpoint.d.ts.map +1 -1
  416. package/dist/esm/hooks/use-breakpoint.js +1 -1
  417. package/dist/esm/hooks/use-live-announcer.js +1 -1
  418. package/dist/esm/hooks/use-prop-synced-state.js +1 -1
  419. package/dist/esm/hooks/use-toggle-state.js +1 -1
  420. package/dist/esm/hooks/use-tooltip-wrapper.js +1 -1
  421. package/dist/esm/index.js +1 -1
  422. package/dist/esm/props/as-child.prop.js +1 -1
  423. package/dist/esm/props/color.prop.js +1 -1
  424. package/dist/esm/props/font-family.prop.js +1 -1
  425. package/dist/esm/props/gap.props.js +1 -1
  426. package/dist/esm/props/height.props.js +1 -1
  427. package/dist/esm/props/high-contrast.prop.js +1 -1
  428. package/dist/esm/props/index.js +1 -1
  429. package/dist/esm/props/layout.props.js +1 -1
  430. package/dist/esm/props/leading-trim.prop.js +1 -1
  431. package/dist/esm/props/margin.props.js +1 -1
  432. package/dist/esm/props/padding.props.js +1 -1
  433. package/dist/esm/props/prop-def.js +1 -1
  434. package/dist/esm/props/radius.prop.js +1 -1
  435. package/dist/esm/props/shadow.props.js +1 -1
  436. package/dist/esm/props/text-align.prop.js +1 -1
  437. package/dist/esm/props/text-wrap.prop.js +1 -1
  438. package/dist/esm/props/truncate.prop.js +1 -1
  439. package/dist/esm/props/weight.prop.js +1 -1
  440. package/dist/esm/props/width.props.js +1 -1
  441. package/fonts.css +2 -0
  442. package/package.json +36 -36
  443. package/schemas/base-button.json +66 -60
  444. package/schemas/button.json +80 -74
  445. package/schemas/icon-button.json +78 -72
  446. package/schemas/index.json +390 -360
  447. package/schemas/toggle-button.json +84 -78
  448. package/schemas/toggle-icon-button.json +82 -76
  449. package/src/components/_internal/base-card.css +5 -5
  450. package/src/components/_internal/base-checkbox.css +17 -3
  451. package/src/components/_internal/base-dialog.css +4 -4
  452. package/src/components/_internal/base-menu.css +68 -143
  453. package/src/components/_internal/base-menu.utils.tsx +144 -0
  454. package/src/components/_internal/dropdown-menu-drill-down.tsx +281 -183
  455. package/src/components/_internal/menu-context.tsx +24 -15
  456. package/src/components/accordion.css +5 -5
  457. package/src/components/callout.css +5 -5
  458. package/src/components/chatbar.css +100 -32
  459. package/src/components/chatbar.tsx +739 -735
  460. package/src/components/checkbox-cards.css +30 -0
  461. package/src/components/combobox.css +62 -115
  462. package/src/components/combobox.props.tsx +14 -0
  463. package/src/components/combobox.tsx +440 -212
  464. package/src/components/context-menu.props.tsx +4 -1
  465. package/src/components/context-menu.tsx +108 -62
  466. package/src/components/dropdown-menu.css +41 -121
  467. package/src/components/dropdown-menu.tsx +430 -275
  468. package/src/components/hover-card.css +3 -3
  469. package/src/components/popover.css +4 -4
  470. package/src/components/radio-cards.css +30 -0
  471. package/src/components/schemas/shell.schema.ts +11 -18
  472. package/src/components/scroll-area.css +2 -3
  473. package/src/components/select.css +2 -2
  474. package/src/components/shell.css +1 -1
  475. package/src/components/table.css +5 -5
  476. package/src/components/theme.props.tsx +1 -1
  477. package/src/components/tooltip.css +1 -1
  478. package/src/components/virtual-menu.tsx +12 -8
  479. package/src/hooks/use-breakpoint.ts +19 -3
  480. package/src/styles/fonts.css +6 -1
  481. package/src/styles/tokens/font-family.css +13 -5
  482. package/src/styles/tokens/radius.css +47 -4
  483. package/styles.css +801 -784
  484. package/tokens/base.css +19 -2
  485. package/tokens.css +19 -2
  486. package/dist/esm/chunk-3XT5P2HO.js +0 -2
  487. package/dist/esm/chunk-4XJY6FIK.js +0 -2
  488. package/dist/esm/chunk-4Z33XHOK.js +0 -2
  489. package/dist/esm/chunk-4Z33XHOK.js.map +0 -7
  490. package/dist/esm/chunk-E2Z5IA2P.js +0 -2
  491. package/dist/esm/chunk-E2Z5IA2P.js.map +0 -7
  492. package/dist/esm/chunk-HVWEPVBH.js +0 -2
  493. package/dist/esm/chunk-HVWEPVBH.js.map +0 -7
  494. package/dist/esm/chunk-KKBP2CRH.js +0 -2
  495. package/dist/esm/chunk-KKBP2CRH.js.map +0 -7
  496. package/dist/esm/chunk-MOR3NSXQ.js +0 -2
  497. package/dist/esm/chunk-MOR3NSXQ.js.map +0 -7
  498. package/dist/esm/chunk-QCNUTT4R.js +0 -2
  499. package/dist/esm/chunk-QCNUTT4R.js.map +0 -7
  500. package/dist/esm/chunk-UGD7Z3L4.js +0 -2
  501. package/dist/esm/chunk-UGD7Z3L4.js.map +0 -7
  502. package/dist/esm/chunk-YEB3H7QN.js +0 -2
  503. package/dist/esm/chunk-YEB3H7QN.js.map +0 -7
  504. package/dist/esm/chunk-ZY6SQWHT.js +0 -2
  505. package/dist/esm/chunk-ZY6SQWHT.js.map +0 -7
  506. /package/dist/esm/{chunk-P7HYVJSU.js.map → chunk-2TIADHPD.js.map} +0 -0
  507. /package/dist/esm/{chunk-RMKXWYOV.js.map → chunk-3OBXZUFJ.js.map} +0 -0
  508. /package/dist/esm/{chunk-S2HA3W3V.js.map → chunk-3R7XMRV5.js.map} +0 -0
  509. /package/dist/esm/{chunk-S56YSRHP.js.map → chunk-56TS6OKQ.js.map} +0 -0
  510. /package/dist/esm/{chunk-2FYJTSOP.js.map → chunk-5BYHEYO6.js.map} +0 -0
  511. /package/dist/esm/{chunk-B24BDVG2.js.map → chunk-5MNBQROW.js.map} +0 -0
  512. /package/dist/esm/{chunk-W7N7V5YD.js.map → chunk-7ESHXHHO.js.map} +0 -0
  513. /package/dist/esm/{chunk-CRWYCLTE.js.map → chunk-ABSKRHVM.js.map} +0 -0
  514. /package/dist/esm/{chunk-3MWWP4MW.js.map → chunk-BBJUB52V.js.map} +0 -0
  515. /package/dist/esm/{chunk-PTLFMWCN.js.map → chunk-CQNPBTM5.js.map} +0 -0
  516. /package/dist/esm/{chunk-GJSGKE7B.js.map → chunk-CTA5ODAO.js.map} +0 -0
  517. /package/dist/esm/{chunk-GWXVETMF.js.map → chunk-DCAZPMOB.js.map} +0 -0
  518. /package/dist/esm/{chunk-PLWXTEVE.js.map → chunk-DMJJM5IS.js.map} +0 -0
  519. /package/dist/esm/{chunk-A2RZGGMY.js.map → chunk-DQAUX5ZL.js.map} +0 -0
  520. /package/dist/esm/{chunk-YUP6EICB.js.map → chunk-DXL6V2A5.js.map} +0 -0
  521. /package/dist/esm/{chunk-XMRIRFM7.js.map → chunk-FG7XM7KJ.js.map} +0 -0
  522. /package/dist/esm/{chunk-IKSN4RNB.js.map → chunk-FIB3XATL.js.map} +0 -0
  523. /package/dist/esm/{chunk-374ZYJUP.js.map → chunk-HBR42MIX.js.map} +0 -0
  524. /package/dist/esm/{chunk-2UPAYDKR.js.map → chunk-HIKVMKYQ.js.map} +0 -0
  525. /package/dist/esm/{chunk-VX6ARMWP.js.map → chunk-HSAG7C53.js.map} +0 -0
  526. /package/dist/esm/{chunk-OSRJRBKP.js.map → chunk-HW324FK3.js.map} +0 -0
  527. /package/dist/esm/{chunk-DNHUQLXO.js.map → chunk-JBO5YOUM.js.map} +0 -0
  528. /package/dist/esm/{chunk-HF5WY5YE.js.map → chunk-JGLAE5G7.js.map} +0 -0
  529. /package/dist/esm/{chunk-4YFJWXL2.js.map → chunk-JJKQ7CGI.js.map} +0 -0
  530. /package/dist/esm/{chunk-CWRNW44F.js.map → chunk-L6PVH3RM.js.map} +0 -0
  531. /package/dist/esm/{chunk-MK2R6DAU.js.map → chunk-LAOMR7SE.js.map} +0 -0
  532. /package/dist/esm/{chunk-FHOCUZDR.js.map → chunk-NIQT45PJ.js.map} +0 -0
  533. /package/dist/esm/{chunk-3XT5P2HO.js.map → chunk-OS3FCYZY.js.map} +0 -0
  534. /package/dist/esm/{chunk-7QDTSBJF.js.map → chunk-OYF6YXH7.js.map} +0 -0
  535. /package/dist/esm/{chunk-WFMYNZXZ.js.map → chunk-P5OSU4NX.js.map} +0 -0
  536. /package/dist/esm/{chunk-QEOAXJLN.js.map → chunk-PTS6QYQW.js.map} +0 -0
  537. /package/dist/esm/{chunk-CVWCKOHO.js.map → chunk-RLPKKCBL.js.map} +0 -0
  538. /package/dist/esm/{chunk-LKAK5GXF.js.map → chunk-SPIU5JYC.js.map} +0 -0
  539. /package/dist/esm/{chunk-4AQTUBVP.js.map → chunk-T4UHUQFJ.js.map} +0 -0
  540. /package/dist/esm/{chunk-K6QZMNCD.js.map → chunk-TS623UPD.js.map} +0 -0
  541. /package/dist/esm/{chunk-YV2KYHGR.js.map → chunk-UBOOUDS4.js.map} +0 -0
  542. /package/dist/esm/{chunk-D6B7IMZU.js.map → chunk-UFI53QUC.js.map} +0 -0
  543. /package/dist/esm/{chunk-AU3D6F42.js.map → chunk-UIRSGVJG.js.map} +0 -0
  544. /package/dist/esm/{chunk-FXUUI6Z2.js.map → chunk-UUUZXUIP.js.map} +0 -0
  545. /package/dist/esm/{chunk-25WZA7ZU.js.map → chunk-VA4CTVQL.js.map} +0 -0
  546. /package/dist/esm/{chunk-TOLLPRMM.js.map → chunk-VNPNEJI6.js.map} +0 -0
  547. /package/dist/esm/{chunk-6IGTK4JE.js.map → chunk-YMUGKYGW.js.map} +0 -0
  548. /package/dist/esm/{chunk-M24UXMOQ.js.map → chunk-ZXZOH63L.js.map} +0 -0
@@ -1,23 +1,30 @@
1
1
  'use client';
2
2
 
3
3
  /**
4
- * Combobox - Simplified architecture matching shadcn's approach for optimal performance.
5
- * Built on Popover + cmdk with minimal context overhead.
6
- *
7
- * Key performance optimizations:
8
- * - Single context instead of 5 separate contexts
9
- * - No MutationObserver - cmdk handles aria internally
10
- * - disablePointerSelection - prevents React re-renders on hover
11
- * - CSS :hover handles visual feedback (no state updates)
12
- * - No label registration - use displayValue prop for trigger display
4
+ * Combobox: Radix Popover for positioning, dismissal and focus; cmdk for search,
5
+ * filtering and keyboard navigation.
6
+ *
7
+ * Render path. State is split so an interaction re-renders only what reads it:
8
+ * - `ComboboxContext` (Trigger, Value, Content): config and the selected value.
9
+ * Open state is not in it, so opening and closing re-render no consumer.
10
+ * - `ComboboxSearchContext` (Input only): the search text.
11
+ * - `ComboboxItemContext` (Items): stable values only. Items read "am I selected"
12
+ * from a store through `useSyncExternalStore`, so a selection re-renders the old
13
+ * and the new selected item, not all N.
14
+ *
15
+ * Highlight. cmdk's `data-selected` is the single highlight source, driven by both
16
+ * the pointer and the keyboard. There is no separate CSS `:hover` highlight, so two
17
+ * items are never lit at once and Enter always picks the lit item.
13
18
  */
14
19
 
15
20
  import * as React from 'react';
16
21
  import classNames from 'classnames';
17
- import { useControllableState } from 'radix-ui/internal';
18
- import { Command as CommandPrimitive } from 'cmdk';
22
+ import { Popover as PopoverPrimitive } from 'radix-ui';
23
+ import { useCallbackRef, useComposedRefs, useControllableState, useLayoutEffect } from 'radix-ui/internal';
24
+ import { Command as CommandPrimitive, useCommandState } from 'cmdk';
19
25
 
20
26
  import { extractProps } from '../helpers/extract-props.js';
27
+ import { useDeprecatedPanelBackgroundWarning } from '../helpers/use-deprecation-warning.js';
21
28
  import { comboboxRootPropDefs, comboboxTriggerPropDefs, comboboxContentPropDefs } from './combobox.props.js';
22
29
  import { marginPropDefs } from '../props/margin.props.js';
23
30
  import { ChevronDownIcon, ThickCheckIcon } from './icons.js';
@@ -25,8 +32,9 @@ import { Theme, useThemeContext } from './theme.js';
25
32
  import { requireReactElement } from '../helpers/require-react-element.js';
26
33
  import * as Popover from './popover.js';
27
34
  import { Slottable } from './slot.js';
28
- import { textFieldRootPropDefs } from './text-field.props.js';
35
+ import { VisuallyHidden } from './visually-hidden.js';
29
36
 
37
+ import type { textFieldRootPropDefs } from './text-field.props.js';
30
38
  import type { MarginProps } from '../props/margin.props.js';
31
39
  import type { ComponentPropsWithout, RemovedProps } from '../helpers/component-props.js';
32
40
  import type { GetPropDefTypes } from '../props/prop-def.js';
@@ -35,55 +43,100 @@ type TextFieldVariant = (typeof textFieldRootPropDefs.variant.values)[number];
35
43
  type ComboboxValue = string | null;
36
44
  type CommandFilter = (value: string, search: string, keywords?: string[]) => number;
37
45
 
38
- /**
39
- * Single unified context - contains only essential shared state.
40
- * This replaces the previous 5 separate contexts for better performance.
41
- */
46
+ // Hoisted so extractProps' compiled prop-def cache (keyed by object identity) hits.
47
+ const triggerSizingPropDefs = { size: comboboxRootPropDefs.size, highContrast: comboboxRootPropDefs.highContrast };
48
+ const contentSizePropDefs = { size: comboboxContentPropDefs.size };
49
+
50
+ const warned = new Set<string>();
51
+ function warnOnce(message: string) {
52
+ if (warned.has(message)) return;
53
+ warned.add(message);
54
+ console.warn(message);
55
+ }
56
+
57
+ // ============================================================================
58
+ // Selection store
59
+ // ============================================================================
60
+
61
+ interface SelectionStore {
62
+ get: () => ComboboxValue;
63
+ set: (value: ComboboxValue) => void;
64
+ subscribe: (listener: () => void) => () => void;
65
+ }
66
+
67
+ function createSelectionStore(initial: ComboboxValue): SelectionStore {
68
+ let current = initial;
69
+ const listeners = new Set<() => void>();
70
+ return {
71
+ get: () => current,
72
+ set: (next) => {
73
+ if (Object.is(next, current)) return;
74
+ current = next;
75
+ listeners.forEach((listener) => listener());
76
+ },
77
+ subscribe: (listener) => {
78
+ listeners.add(listener);
79
+ return () => {
80
+ listeners.delete(listener);
81
+ };
82
+ },
83
+ };
84
+ }
85
+
86
+ // ============================================================================
87
+ // Contexts
88
+ // ============================================================================
89
+
42
90
  interface ComboboxContextValue {
43
- // Selection state
44
91
  value: ComboboxValue;
45
- onSelect: (value: string) => void;
46
- // Open state
47
- open: boolean;
92
+ displayValue?: string;
48
93
  setOpen: (open: boolean) => void;
49
- // Config (static, rarely changes)
50
94
  size?: ComboboxRootOwnProps['size'];
51
95
  highContrast?: boolean;
52
96
  placeholder?: string;
53
97
  searchPlaceholder?: string;
54
98
  disabled?: boolean;
55
99
  color?: string;
56
- // Display
57
- displayValue?: string;
58
- // Accessibility
59
- listboxId: string;
100
+ loop: boolean;
101
+ shouldFilter: boolean;
102
+ filter?: CommandFilter;
103
+ triggerRef: React.RefObject<HTMLButtonElement | null>;
104
+ /** Reads the trigger's accessible name from the DOM (aria-label, aria-labelledby or <label>). */
105
+ getTriggerLabel: () => string | undefined;
60
106
  }
61
107
 
62
108
  const ComboboxContext = React.createContext<ComboboxContextValue | null>(null);
63
109
 
64
- const useComboboxContext = (componentName?: string) => {
110
+ const useComboboxContext = (componentName: string) => {
65
111
  const ctx = React.useContext(ComboboxContext);
66
112
  if (!ctx) {
67
- const name = componentName ? `Combobox.${componentName}` : 'Combobox components';
68
- throw new Error(`${name} must be used within Combobox.Root`);
113
+ throw new Error(`Combobox.${componentName} must be used within Combobox.Root`);
69
114
  }
70
115
  return ctx;
71
116
  };
72
117
 
73
- /**
74
- * Context for Content-specific values (variant, material).
75
- * Separate from main context as it's only needed inside Content.
76
- */
118
+ interface ComboboxSearchContextValue {
119
+ value: string;
120
+ setValue: (value: string) => void;
121
+ }
122
+
123
+ const ComboboxSearchContext = React.createContext<ComboboxSearchContextValue | null>(null);
124
+
125
+ interface ComboboxItemContextValue {
126
+ store: SelectionStore;
127
+ onSelect: (value: string) => void;
128
+ disabled?: boolean;
129
+ }
130
+
131
+ const ComboboxItemContext = React.createContext<ComboboxItemContextValue | null>(null);
132
+
133
+ /** Values resolved by Content and read by Input and List. */
77
134
  interface ComboboxContentContextValue {
78
135
  variant: 'solid' | 'soft';
79
- size: string;
136
+ sizeClassName?: string;
80
137
  color?: string;
81
138
  material?: string;
82
- highContrast?: boolean;
83
- // cmdk config passed down
84
- loop?: boolean;
85
- shouldFilter?: boolean;
86
- filter?: CommandFilter;
139
+ label?: string;
87
140
  }
88
141
 
89
142
  const ComboboxContentContext = React.createContext<ComboboxContentContextValue | null>(null);
@@ -110,11 +163,15 @@ type ComboboxRootOwnProps = GetPropDefTypes<typeof comboboxRootPropDefs> & {
110
163
  shouldFilter?: boolean;
111
164
  loop?: boolean;
112
165
  disabled?: boolean;
166
+ /** Clear the search text when an option is selected. Defaults to true. */
113
167
  resetSearchOnSelect?: boolean;
168
+ /** Clear the search text when the popup is dismissed without a selection. Defaults to true. */
169
+ resetSearchOnClose?: boolean;
114
170
  color?: (typeof comboboxTriggerPropDefs.color.values)[number];
115
171
  /**
116
- * Display value for the trigger. Required for optimal performance.
117
- * Can be a string or function that receives the current value.
172
+ * Text shown in the trigger for the selected value. Without it the raw value is shown.
173
+ * Can be a string or a function that receives the current value. Pass a stable
174
+ * function (module scope or `useCallback`): an inline function runs on every render.
118
175
  * @example displayValue={(v) => items.find(i => i.value === v)?.label}
119
176
  */
120
177
  displayValue?: string | ((value: ComboboxValue) => string | undefined);
@@ -123,6 +180,32 @@ type ComboboxRootOwnProps = GetPropDefTypes<typeof comboboxRootPropDefs> & {
123
180
  type PopoverRootProps = React.ComponentPropsWithoutRef<typeof Popover.Root>;
124
181
  interface ComboboxRootProps extends PopoverRootProps, ComboboxRootOwnProps {}
125
182
 
183
+ function readElementLabel(el: HTMLElement): string | undefined {
184
+ const ariaLabel = el.getAttribute('aria-label')?.trim();
185
+ if (ariaLabel) return ariaLabel;
186
+
187
+ const labelledBy = el.getAttribute('aria-labelledby');
188
+ if (labelledBy) {
189
+ const text = labelledBy
190
+ .split(/\s+/)
191
+ .map((id) => el.ownerDocument.getElementById(id)?.textContent?.trim())
192
+ .filter(Boolean)
193
+ .join(' ');
194
+ if (text) return text;
195
+ }
196
+
197
+ const labels = (el as HTMLButtonElement).labels;
198
+ if (labels && labels.length > 0) {
199
+ const text = Array.from(labels)
200
+ .map((label) => label.textContent?.trim())
201
+ .filter(Boolean)
202
+ .join(' ');
203
+ if (text) return text;
204
+ }
205
+
206
+ return undefined;
207
+ }
208
+
126
209
  const ComboboxRoot: React.FC<ComboboxRootProps> = (props) => {
127
210
  const {
128
211
  children,
@@ -144,14 +227,12 @@ const ComboboxRoot: React.FC<ComboboxRootProps> = (props) => {
144
227
  loop = true,
145
228
  disabled,
146
229
  resetSearchOnSelect = true,
230
+ resetSearchOnClose = true,
147
231
  color,
148
232
  displayValue: displayValueProp,
149
233
  ...rootProps
150
234
  } = props;
151
235
 
152
- // Generate stable ID for listbox accessibility linkage
153
- const listboxId = React.useId();
154
-
155
236
  const [open, setOpen] = useControllableState({
156
237
  prop: openProp,
157
238
  defaultProp: defaultOpen,
@@ -170,71 +251,70 @@ const ComboboxRoot: React.FC<ComboboxRootProps> = (props) => {
170
251
  onChange: onSearchValueChange,
171
252
  });
172
253
 
173
- // Resolve displayValue
174
- const resolvedDisplayValue = React.useMemo(() => {
175
- if (displayValueProp == null) return undefined;
176
- if (typeof displayValueProp === 'function') {
177
- return displayValueProp(value);
178
- }
179
- return displayValueProp;
180
- }, [displayValueProp, value]);
181
-
182
- // Handle selection - batches all state updates
183
- const onSelect = React.useCallback(
184
- (nextValue: string) => {
185
- setValue(nextValue);
186
- setOpen(false);
187
- if (resetSearchOnSelect) {
188
- setSearchValue('');
189
- }
190
- },
191
- [setOpen, setSearchValue, setValue, resetSearchOnSelect],
192
- );
254
+ const resolvedDisplayValue = typeof displayValueProp === 'function' ? displayValueProp(value) : displayValueProp;
255
+
256
+ // Items subscribe to this store instead of the context, so a selection
257
+ // re-renders only the items whose selected state flips.
258
+ const [store] = React.useState(() => createSelectionStore(value));
259
+ useLayoutEffect(() => {
260
+ store.set(value);
261
+ }, [store, value]);
262
+
263
+ // The radix setters change identity whenever their controlled prop changes.
264
+ // Reading them through callback refs keeps these handlers stable, so the
265
+ // contexts below do not change on every keystroke or selection.
266
+ const handleOpenChange = useCallbackRef((nextOpen: boolean) => {
267
+ setOpen(nextOpen);
268
+ if (!nextOpen && resetSearchOnClose) setSearchValue('');
269
+ });
270
+
271
+ const onSelect = useCallbackRef((nextValue: string) => {
272
+ setValue(nextValue);
273
+ setOpen(false);
274
+ if (resetSearchOnSelect) setSearchValue('');
275
+ });
276
+
277
+ const triggerRef = React.useRef<HTMLButtonElement | null>(null);
278
+ const getTriggerLabel = React.useCallback(() => (triggerRef.current ? readElementLabel(triggerRef.current) : undefined), []);
193
279
 
194
- // Single context value - memoized for performance
195
280
  const contextValue = React.useMemo<ComboboxContextValue>(
196
281
  () => ({
197
282
  value,
198
- onSelect,
199
- open,
200
- setOpen,
283
+ displayValue: resolvedDisplayValue,
284
+ setOpen: handleOpenChange,
201
285
  size,
202
286
  highContrast,
203
287
  placeholder,
204
288
  searchPlaceholder,
205
289
  disabled,
206
290
  color,
207
- displayValue: resolvedDisplayValue,
208
- listboxId,
291
+ loop,
292
+ shouldFilter,
293
+ filter,
294
+ triggerRef,
295
+ getTriggerLabel,
209
296
  }),
210
- [value, onSelect, open, setOpen, size, highContrast, placeholder, searchPlaceholder, disabled, color, resolvedDisplayValue, listboxId],
297
+ [value, resolvedDisplayValue, handleOpenChange, size, highContrast, placeholder, searchPlaceholder, disabled, color, loop, shouldFilter, filter, getTriggerLabel],
211
298
  );
212
299
 
213
- // Store cmdk config in a ref to avoid context updates
214
- const cmdkConfigRef = React.useRef({ filter, shouldFilter, loop, searchValue, setSearchValue });
215
- cmdkConfigRef.current = { filter, shouldFilter, loop, searchValue, setSearchValue };
300
+ const searchContextValue = React.useMemo<ComboboxSearchContextValue>(() => ({ value: searchValue, setValue: setSearchValue }), [searchValue, setSearchValue]);
301
+
302
+ const itemContextValue = React.useMemo<ComboboxItemContextValue>(() => ({ store, onSelect, disabled }), [store, onSelect, disabled]);
216
303
 
217
304
  return (
218
305
  <ComboboxContext.Provider value={contextValue}>
219
- <CmdkConfigContext.Provider value={cmdkConfigRef}>
220
- <Popover.Root open={open} onOpenChange={setOpen} {...rootProps}>
221
- {children}
222
- </Popover.Root>
223
- </CmdkConfigContext.Provider>
306
+ <ComboboxSearchContext.Provider value={searchContextValue}>
307
+ <ComboboxItemContext.Provider value={itemContextValue}>
308
+ <Popover.Root open={open} onOpenChange={handleOpenChange} {...rootProps}>
309
+ {children}
310
+ </Popover.Root>
311
+ </ComboboxItemContext.Provider>
312
+ </ComboboxSearchContext.Provider>
224
313
  </ComboboxContext.Provider>
225
314
  );
226
315
  };
227
316
  ComboboxRoot.displayName = 'Combobox.Root';
228
317
 
229
- // Separate ref context for cmdk config to avoid re-renders
230
- const CmdkConfigContext = React.createContext<React.MutableRefObject<{
231
- filter?: CommandFilter;
232
- shouldFilter: boolean;
233
- loop: boolean;
234
- searchValue: string;
235
- setSearchValue: (value: string) => void;
236
- }> | null>(null);
237
-
238
318
  // ============================================================================
239
319
  // Trigger
240
320
  // ============================================================================
@@ -247,28 +327,78 @@ interface ComboboxTriggerProps extends NativeTriggerProps, MarginProps, Combobox
247
327
  const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, ComboboxTriggerProps>((props, forwardedRef) => {
248
328
  const ctx = useComboboxContext('Trigger');
249
329
 
250
- const { children, className, placeholder, disabled, readOnly, error, loading, color, radius, ...triggerProps } = extractProps(
251
- { size: ctx.size, highContrast: ctx.highContrast, ...props },
252
- { size: comboboxRootPropDefs.size, highContrast: comboboxRootPropDefs.highContrast },
253
- comboboxTriggerPropDefs,
254
- marginPropDefs,
255
- );
256
-
257
- const { material, panelBackground } = props;
330
+ const {
331
+ children,
332
+ className,
333
+ placeholder,
334
+ disabled,
335
+ readOnly,
336
+ error,
337
+ loading,
338
+ color,
339
+ radius,
340
+ material,
341
+ onClick,
342
+ onKeyDown,
343
+ type: buttonType,
344
+ ...triggerProps
345
+ } = extractProps({ size: ctx.size, highContrast: ctx.highContrast, ...props }, triggerSizingPropDefs, comboboxTriggerPropDefs, marginPropDefs);
346
+
347
+ // `panelBackground` has a className prop def, so extractProps strips it.
348
+ const { panelBackground } = props;
349
+ const resolvedMaterial = material ?? panelBackground;
258
350
  const isDisabled = disabled ?? ctx.disabled;
259
351
  const resolvedColor = color ?? ctx.color;
352
+ // Read-only and loading triggers stay focusable but do not open.
353
+ const isBlocked = Boolean(readOnly || loading);
354
+ const isPlaceholder = ctx.value == null && ctx.displayValue == null;
355
+
356
+ const composedRef = useComposedRefs(forwardedRef, ctx.triggerRef);
357
+
358
+ const { getTriggerLabel } = ctx;
359
+ React.useEffect(() => {
360
+ if (process.env.NODE_ENV === 'production') return;
361
+ if (!getTriggerLabel()) {
362
+ warnOnce(
363
+ 'Combobox.Trigger has no accessible name. The combobox role does not take its name from its content. Add `aria-label`, `aria-labelledby`, or a `<label htmlFor>` that points at the trigger.',
364
+ );
365
+ }
366
+ }, [getTriggerLabel]);
367
+
368
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
369
+ onClick?.(event);
370
+ // Radix skips its open toggle when the event is default-prevented.
371
+ if (isBlocked) event.preventDefault();
372
+ };
373
+
374
+ const handleKeyDown = (event: React.KeyboardEvent<HTMLButtonElement>) => {
375
+ onKeyDown?.(event);
376
+ if (event.defaultPrevented || isBlocked || isDisabled) return;
377
+ if (event.key === 'ArrowDown') {
378
+ event.preventDefault();
379
+ ctx.setOpen(true);
380
+ }
381
+ };
260
382
 
261
383
  const defaultContent = (
262
384
  <>
263
385
  <span className="rt-SelectTriggerInner">
264
- <span className="rt-ComboboxValue">
265
- {ctx.displayValue ?? ctx.value ?? placeholder ?? ctx.placeholder}
266
- </span>
386
+ <span className="rt-ComboboxValue">{ctx.displayValue ?? ctx.value ?? placeholder ?? ctx.placeholder}</span>
267
387
  </span>
268
388
  {loading ? (
269
389
  <div className="rt-SelectIcon rt-SelectLoadingIcon" aria-hidden="true">
270
390
  <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
271
- <circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1" strokeLinecap="round" strokeDasharray="6 34" strokeDashoffset="0" className="rt-SelectLoadingSpinner" />
391
+ <circle
392
+ cx="8"
393
+ cy="8"
394
+ r="7"
395
+ stroke="currentColor"
396
+ strokeWidth="2"
397
+ strokeLinecap="round"
398
+ strokeDasharray="32"
399
+ strokeDashoffset="32"
400
+ className="rt-SelectLoadingIconCircle"
401
+ />
272
402
  </svg>
273
403
  </div>
274
404
  ) : (
@@ -277,29 +407,32 @@ const ComboboxTrigger = React.forwardRef<ComboboxTriggerElement, ComboboxTrigger
277
407
  </>
278
408
  );
279
409
 
280
- const { type: buttonType, ...restTriggerProps } = triggerProps;
281
-
410
+ // Radix Popover.Trigger supplies aria-expanded, aria-controls (the popup id) and
411
+ // aria-haspopup="dialog", which matches the Popover dialog this opens.
282
412
  return (
283
413
  <Popover.Trigger disabled={isDisabled}>
284
414
  <button
285
415
  data-accent-color={resolvedColor}
286
416
  data-radius={radius}
287
- data-panel-background={panelBackground}
288
- data-material={material}
417
+ data-panel-background={resolvedMaterial}
418
+ data-material={resolvedMaterial}
289
419
  data-error={error}
290
420
  data-loading={loading}
291
421
  data-disabled={isDisabled || undefined}
292
422
  data-read-only={readOnly || undefined}
293
- {...restTriggerProps}
423
+ data-placeholder={isPlaceholder ? '' : undefined}
424
+ {...triggerProps}
425
+ // eslint-disable-next-line jsx-a11y/role-has-required-aria-props -- Popover.Trigger (Slot) adds aria-expanded and aria-controls
294
426
  role="combobox"
295
- aria-autocomplete="list"
296
- aria-controls={ctx.open ? ctx.listboxId : undefined}
297
- aria-expanded={ctx.open}
298
- aria-haspopup="listbox"
299
427
  aria-disabled={isDisabled || undefined}
428
+ aria-readonly={readOnly || undefined}
429
+ aria-invalid={error || undefined}
430
+ aria-busy={loading || undefined}
300
431
  type={buttonType ?? 'button'}
301
432
  disabled={isDisabled}
302
- ref={forwardedRef}
433
+ onClick={handleClick}
434
+ onKeyDown={handleKeyDown}
435
+ ref={composedRef}
303
436
  className={classNames('rt-reset', 'rt-SelectTrigger', 'rt-ComboboxTrigger', className)}
304
437
  >
305
438
  {children ? requireReactElement(children) : defaultContent}
@@ -318,87 +451,151 @@ interface ComboboxValueProps extends React.ComponentPropsWithoutRef<'span'> {
318
451
  placeholder?: string;
319
452
  }
320
453
 
321
- const ComboboxValue = React.forwardRef<ComboboxValueElement, ComboboxValueProps>(
322
- ({ placeholder, children, className, ...valueProps }, forwardedRef) => {
323
- const ctx = useComboboxContext('Value');
324
- const display = ctx.displayValue ?? ctx.value ?? undefined;
325
- return (
326
- <span {...valueProps} ref={forwardedRef} className={classNames('rt-ComboboxValue', className)}>
327
- {display ?? children ?? placeholder ?? ctx.placeholder}
328
- </span>
329
- );
330
- },
331
- );
454
+ const ComboboxValue = React.forwardRef<ComboboxValueElement, ComboboxValueProps>(({ placeholder, children, className, ...valueProps }, forwardedRef) => {
455
+ const ctx = useComboboxContext('Value');
456
+ const display = ctx.displayValue ?? ctx.value ?? undefined;
457
+ return (
458
+ <span {...valueProps} ref={forwardedRef} className={classNames('rt-ComboboxValue', className)}>
459
+ {display ?? children ?? placeholder ?? ctx.placeholder}
460
+ </span>
461
+ );
462
+ });
332
463
  ComboboxValue.displayName = 'Combobox.Value';
333
464
 
334
465
  // ============================================================================
335
466
  // Content
336
467
  // ============================================================================
337
468
 
338
- type ComboboxContentElement = React.ElementRef<typeof Popover.Content>;
469
+ type ComboboxContentElement = React.ElementRef<typeof PopoverPrimitive.Content>;
339
470
  type ComboboxContentOwnProps = GetPropDefTypes<typeof comboboxContentPropDefs> & {
340
- container?: React.ComponentPropsWithoutRef<typeof Popover.Content>['container'];
471
+ container?: React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Portal>['container'];
341
472
  };
342
- interface ComboboxContentProps extends Omit<ComponentPropsWithout<typeof Popover.Content, RemovedProps>, 'size'>, ComboboxContentOwnProps {}
473
+ interface ComboboxContentProps extends Omit<ComponentPropsWithout<typeof PopoverPrimitive.Content, RemovedProps>, 'size'>, ComboboxContentOwnProps {}
474
+
475
+ /** Announces the filtered result count to screen readers after typing settles. */
476
+ function ComboboxAnnouncer() {
477
+ const count = useCommandState((state) => state.filtered.count);
478
+ const search = useCommandState((state) => state.search);
479
+ const [message, setMessage] = React.useState('');
480
+
481
+ React.useEffect(() => {
482
+ const next = !search ? '' : count === 0 ? 'No results' : `${count} result${count === 1 ? '' : 's'}`;
483
+ const timeout = window.setTimeout(() => setMessage(next), search ? 400 : 0);
484
+ return () => window.clearTimeout(timeout);
485
+ }, [count, search]);
486
+
487
+ return (
488
+ <VisuallyHidden role="status" aria-live="polite" aria-atomic="true">
489
+ {message}
490
+ </VisuallyHidden>
491
+ );
492
+ }
343
493
 
344
494
  const ComboboxContent = React.forwardRef<ComboboxContentElement, ComboboxContentProps>((props, forwardedRef) => {
345
495
  const ctx = useComboboxContext('Content');
346
- const cmdkConfigRef = React.useContext(CmdkConfigContext);
347
496
  const themeContext = useThemeContext();
348
- const effectiveMaterial = themeContext.panelBackground;
349
497
 
350
- const sizeProp = props.size ?? ctx.size ?? comboboxContentPropDefs.size.default;
351
- const variantProp = props.variant ?? comboboxContentPropDefs.variant.default;
352
- const highContrastProp = props.highContrast ?? ctx.highContrast ?? comboboxContentPropDefs.highContrast.default;
498
+ useDeprecatedPanelBackgroundWarning(props.panelBackground);
499
+
500
+ const material = props.material ?? props.panelBackground ?? themeContext.material ?? themeContext.panelBackground;
501
+ const size = props.size ?? ctx.size ?? comboboxContentPropDefs.size.default;
502
+ const variant = props.variant ?? comboboxContentPropDefs.variant.default;
503
+ const highContrast = props.highContrast ?? ctx.highContrast ?? comboboxContentPropDefs.highContrast.default;
504
+
505
+ const {
506
+ className,
507
+ children,
508
+ color,
509
+ forceMount,
510
+ container,
511
+ material: _material,
512
+ panelBackground: _panelBackground,
513
+ onOpenAutoFocus,
514
+ 'aria-label': _ariaLabel,
515
+ 'aria-labelledby': ariaLabelledByProp,
516
+ ...contentProps
517
+ } = extractProps({ ...props, size, variant, highContrast }, comboboxContentPropDefs);
353
518
 
354
- const { className, children, color, forceMount, container, ...contentProps } = extractProps(
355
- { ...props, size: sizeProp, variant: variantProp, highContrast: highContrastProp },
356
- comboboxContentPropDefs,
357
- );
358
519
  const resolvedColor = color || ctx.color || themeContext.accentColor;
359
520
 
521
+
522
+ // The trigger's name labels the popup, the search field and the listbox.
523
+ // Read once per mount (the content remounts on every open); fall back to a
524
+ // layout effect when the trigger was not attached yet (defaultOpen, forceMount).
525
+ const { getTriggerLabel } = ctx;
526
+ const [triggerLabel, setTriggerLabel] = React.useState(getTriggerLabel);
527
+ useLayoutEffect(() => {
528
+ if (triggerLabel !== undefined) return;
529
+ const label = getTriggerLabel();
530
+ if (label !== undefined) setTriggerLabel(label);
531
+ }, [getTriggerLabel, triggerLabel]);
532
+
533
+ const label = props['aria-label'] ?? triggerLabel;
534
+
360
535
  const contentContextValue = React.useMemo<ComboboxContentContextValue>(
361
536
  () => ({
362
- variant: variantProp,
363
- size: String(sizeProp),
537
+ variant,
538
+ // Responsive-aware size class for the search field (never String() a responsive object).
539
+ sizeClassName: extractProps({ size }, contentSizePropDefs).className || undefined,
364
540
  color: resolvedColor,
365
- material: effectiveMaterial,
366
- highContrast: highContrastProp,
367
- loop: cmdkConfigRef?.current.loop,
368
- shouldFilter: cmdkConfigRef?.current.shouldFilter,
369
- filter: cmdkConfigRef?.current.filter,
541
+ material,
542
+ label,
370
543
  }),
371
- [variantProp, sizeProp, resolvedColor, effectiveMaterial, highContrastProp, cmdkConfigRef],
544
+ [variant, size, resolvedColor, material, label],
372
545
  );
373
546
 
547
+ const commandRef = React.useRef<HTMLDivElement>(null);
548
+
549
+ // Without a search field, focus would land on the popup itself, outside cmdk's
550
+ // keydown handler. Focus the listbox instead so arrow keys and Enter work.
551
+ const handleOpenAutoFocus = (event: Event) => {
552
+ onOpenAutoFocus?.(event);
553
+ if (event.defaultPrevented) return;
554
+ const command = commandRef.current;
555
+ if (!command || command.querySelector('[cmdk-input]')) return;
556
+ event.preventDefault();
557
+ const list = command.querySelector<HTMLElement>('[cmdk-list]');
558
+ (list ?? command).focus({ preventScroll: true });
559
+ };
560
+
561
+ // Start the highlight on the selected option (cmdk trims item values).
562
+ const initialHighlight = ctx.value?.trim() || undefined;
563
+
374
564
  return (
375
- <Popover.Content
376
- size={sizeProp}
377
- data-accent-color={resolvedColor}
378
- data-material={effectiveMaterial}
379
- data-panel-background={effectiveMaterial}
380
- align="start"
381
- sideOffset={4}
382
- collisionPadding={10}
383
- {...contentProps}
384
- forceMount={forceMount === true ? true : undefined}
385
- container={container}
386
- ref={forwardedRef}
387
- className={classNames('rt-PopperContent', 'rt-BaseMenuContent', 'rt-ComboboxContent', className)}
388
- >
565
+ <PopoverPrimitive.Portal container={container} forceMount={forceMount ? true : undefined}>
389
566
  <Theme asChild>
390
- <ComboboxContentContext.Provider value={contentContextValue}>
391
- <CommandPrimitive
392
- loop={cmdkConfigRef?.current.loop}
393
- shouldFilter={cmdkConfigRef?.current.shouldFilter}
394
- filter={cmdkConfigRef?.current.filter}
395
- className="rt-ComboboxCommand"
396
- >
397
- {children}
398
- </CommandPrimitive>
399
- </ComboboxContentContext.Provider>
567
+ <PopoverPrimitive.Content
568
+ data-accent-color={resolvedColor}
569
+ data-material={material}
570
+ data-panel-background={material}
571
+ align="start"
572
+ sideOffset={4}
573
+ collisionPadding={10}
574
+ aria-label={ariaLabelledByProp ? undefined : label}
575
+ aria-labelledby={ariaLabelledByProp}
576
+ {...contentProps}
577
+ forceMount={forceMount ? true : undefined}
578
+ onOpenAutoFocus={handleOpenAutoFocus}
579
+ ref={forwardedRef}
580
+ className={classNames('rt-PopperContent', 'rt-BaseMenuContent', 'rt-ComboboxContent', className)}
581
+ >
582
+ <ComboboxContentContext.Provider value={contentContextValue}>
583
+ <CommandPrimitive
584
+ ref={commandRef}
585
+ loop={ctx.loop}
586
+ shouldFilter={ctx.shouldFilter}
587
+ filter={ctx.filter}
588
+ label={label}
589
+ defaultValue={initialHighlight}
590
+ className="rt-ComboboxCommand"
591
+ >
592
+ {children}
593
+ <ComboboxAnnouncer />
594
+ </CommandPrimitive>
595
+ </ComboboxContentContext.Provider>
596
+ </PopoverPrimitive.Content>
400
597
  </Theme>
401
- </Popover.Content>
598
+ </PopoverPrimitive.Portal>
402
599
  );
403
600
  });
404
601
  ComboboxContent.displayName = 'Combobox.Content';
@@ -419,22 +616,19 @@ interface ComboboxInputProps extends Omit<React.ComponentPropsWithoutRef<typeof
419
616
  const ComboboxInput = React.forwardRef<ComboboxInputElement, ComboboxInputProps>(
420
617
  ({ className, startAdornment, endAdornment, placeholder, variant: inputVariant, value, onValueChange, ...inputProps }, forwardedRef) => {
421
618
  const ctx = useComboboxContext('Input');
619
+ const search = React.useContext(ComboboxSearchContext);
422
620
  const contentContext = useComboboxContentContext();
423
- const cmdkConfigRef = React.useContext(CmdkConfigContext);
424
621
 
425
622
  const contentVariant = contentContext?.variant ?? 'solid';
426
623
  const textFieldVariant = inputVariant ?? (contentVariant === 'solid' ? 'surface' : 'soft');
427
624
 
428
- // Use controlled search value from ref (avoids context re-renders)
429
- const searchValue = value ?? cmdkConfigRef?.current.searchValue ?? '';
430
- const handleSearchChange = onValueChange ?? cmdkConfigRef?.current.setSearchValue;
431
-
432
- // Use size from contentContext (already resolved to string) or fallback
433
- const sizeClass = contentContext?.size ? `rt-r-size-${contentContext.size}` : undefined;
625
+ // The search text lives in Root; only this component subscribes to it.
626
+ const searchValue = value ?? search?.value ?? '';
627
+ const handleSearchChange = onValueChange ?? search?.setValue;
434
628
 
435
629
  const inputField = (
436
630
  <div
437
- className={classNames('rt-TextFieldRoot', 'rt-ComboboxInputRoot', sizeClass, `rt-variant-${textFieldVariant}`)}
631
+ className={classNames('rt-TextFieldRoot', 'rt-ComboboxInputRoot', contentContext?.sizeClassName, `rt-variant-${textFieldVariant}`)}
438
632
  data-accent-color={contentContext?.color}
439
633
  data-material={contentContext?.material}
440
634
  data-panel-background={contentContext?.material}
@@ -462,25 +656,20 @@ const ComboboxInput = React.forwardRef<ComboboxInputElement, ComboboxInputProps>
462
656
  ComboboxInput.displayName = 'Combobox.Input';
463
657
 
464
658
  // ============================================================================
465
- // List (simplified - no MutationObserver, no ScrollArea wrapper)
659
+ // List
466
660
  // ============================================================================
467
661
 
468
662
  type ComboboxListElement = React.ElementRef<typeof CommandPrimitive.List>;
469
663
  interface ComboboxListProps extends React.ComponentPropsWithoutRef<typeof CommandPrimitive.List> {}
470
664
 
471
- const ComboboxList = React.forwardRef<ComboboxListElement, ComboboxListProps>(({ className, children, ...listProps }, forwardedRef) => {
472
- const ctx = useComboboxContext('List');
665
+ // cmdk's List owns its id (referenced by the input's aria-controls), role and
666
+ // aria-label (set from `label`), so none of them are overridden here.
667
+ const ComboboxList = React.forwardRef<ComboboxListElement, ComboboxListProps>(({ className, children, label, ...listProps }, forwardedRef) => {
668
+ const contentContext = useComboboxContentContext();
473
669
  return (
474
670
  <div className="rt-ComboboxScrollArea">
475
671
  <div className={classNames('rt-BaseMenuViewport', 'rt-ComboboxViewport')}>
476
- <CommandPrimitive.List
477
- {...listProps}
478
- id={ctx.listboxId}
479
- ref={forwardedRef}
480
- role="listbox"
481
- aria-label="Options"
482
- className={classNames('rt-ComboboxList', className)}
483
- >
672
+ <CommandPrimitive.List {...listProps} label={label ?? contentContext?.label} ref={forwardedRef} className={classNames('rt-ComboboxList', className)}>
484
673
  {children}
485
674
  </CommandPrimitive.List>
486
675
  </div>
@@ -520,9 +709,25 @@ ComboboxGroup.displayName = 'Combobox.Group';
520
709
  type ComboboxLabelElement = React.ElementRef<'div'>;
521
710
  interface ComboboxLabelProps extends React.ComponentPropsWithoutRef<'div'> {}
522
711
 
523
- const ComboboxLabel = React.forwardRef<ComboboxLabelElement, ComboboxLabelProps>(({ className, ...labelProps }, forwardedRef) => (
524
- <div {...labelProps} ref={forwardedRef} className={classNames('rt-BaseMenuLabel', 'rt-ComboboxLabel', className)} />
525
- ));
712
+ const ComboboxLabel = React.forwardRef<ComboboxLabelElement, ComboboxLabelProps>(({ className, id: idProp, ...labelProps }, forwardedRef) => {
713
+ const generatedId = React.useId();
714
+ const id = idProp ?? generatedId;
715
+ const localRef = React.useRef<HTMLDivElement>(null);
716
+ const composedRef = useComposedRefs(forwardedRef, localRef);
717
+
718
+ // cmdk renders the group's role="group" element itself and gives no way to
719
+ // label it from a child, so name it after this label when inside a Group.
720
+ useLayoutEffect(() => {
721
+ const group = localRef.current?.closest('[cmdk-group-items]');
722
+ if (!group || group.hasAttribute('aria-labelledby')) return;
723
+ group.setAttribute('aria-labelledby', id);
724
+ return () => {
725
+ if (group.getAttribute('aria-labelledby') === id) group.removeAttribute('aria-labelledby');
726
+ };
727
+ }, [id]);
728
+
729
+ return <div {...labelProps} id={id} ref={composedRef} className={classNames('rt-BaseMenuLabel', 'rt-ComboboxLabel', className)} />;
730
+ });
526
731
  ComboboxLabel.displayName = 'Combobox.Label';
527
732
 
528
733
  // ============================================================================
@@ -538,57 +743,80 @@ const ComboboxSeparator = React.forwardRef<ComboboxSeparatorElement, ComboboxSep
538
743
  ComboboxSeparator.displayName = 'Combobox.Separator';
539
744
 
540
745
  // ============================================================================
541
- // Item (simplified - minimal context usage)
746
+ // Item
542
747
  // ============================================================================
543
748
 
749
+ /** Plain text of a React node tree, used as the item's search text. */
750
+ function getNodeText(node: React.ReactNode): string {
751
+ if (node == null || typeof node === 'boolean') return '';
752
+ if (typeof node === 'string' || typeof node === 'number') return String(node);
753
+ if (Array.isArray(node)) return node.map(getNodeText).join('');
754
+ if (React.isValidElement<{ children?: React.ReactNode }>(node)) return getNodeText(node.props.children);
755
+ return '';
756
+ }
757
+
544
758
  type ComboboxItemElement = React.ElementRef<typeof CommandPrimitive.Item>;
545
759
  interface ComboboxItemProps extends Omit<React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>, 'keywords' | 'onSelect'> {
546
- /** Display label for search. Falls back to children text content. */
760
+ /**
761
+ * Text matched by search. Defaults to the text of `children`. Set it when the
762
+ * children are components whose text is not in the JSX (it cannot be read).
763
+ */
547
764
  label?: string;
548
765
  /** Additional keywords for search filtering. */
549
766
  keywords?: string[];
550
- /** Called when item is selected. */
767
+ /** Called when the item is selected. */
551
768
  onSelect?: (value: string) => void;
552
769
  }
553
770
 
554
771
  const ComboboxItem = React.forwardRef<ComboboxItemElement, ComboboxItemProps>(
555
772
  ({ className, children, label, value, disabled, onSelect, keywords, ...itemProps }, forwardedRef) => {
556
- const ctx = useComboboxContext('Item');
557
- const contentContext = useComboboxContentContext();
558
-
559
- const isSelected = value != null && ctx.value === value;
560
- const isDisabled = disabled ?? ctx.disabled ?? false;
561
- const sizeClass = contentContext?.size ? `rt-r-size-${contentContext.size}` : undefined;
562
-
563
- // Extract stable onSelect to avoid depending on entire ctx object
564
- const onSelectFromContext = ctx.onSelect;
773
+ const itemContext = React.useContext(ComboboxItemContext);
774
+ if (!itemContext) {
775
+ throw new Error('Combobox.Item must be used within Combobox.Root');
776
+ }
777
+ const { store, onSelect: onSelectFromRoot, disabled: rootDisabled } = itemContext;
778
+
779
+ // Without `value`, cmdk uses the trimmed text of string children.
780
+ const itemValue = value ?? (typeof children === 'string' ? children.trim() : undefined);
781
+ const getIsSelected = () => itemValue !== undefined && store.get() === itemValue;
782
+ const isSelected = React.useSyncExternalStore(store.subscribe, getIsSelected, getIsSelected);
783
+
784
+ const onSelectProp = useCallbackRef(onSelect);
785
+ // cmdk passes its resolved value (the `value` prop, or the text when `value` is omitted).
786
+ const handleSelect = React.useCallback(
787
+ (resolvedValue: string) => {
788
+ const nextValue = value ?? resolvedValue;
789
+ onSelectFromRoot(nextValue);
790
+ onSelectProp(nextValue);
791
+ },
792
+ [value, onSelectFromRoot, onSelectProp],
793
+ );
565
794
 
566
- // Handle selection
567
- const handleSelect = React.useCallback(() => {
568
- if (value != null) {
569
- onSelectFromContext(value);
570
- onSelect?.(value);
571
- }
572
- }, [onSelectFromContext, value, onSelect]);
795
+ // cmdk matches only `value` and `keywords`, so add the visible text as a keyword.
796
+ const text = (label ?? getNodeText(children)).replace(/\s+/g, ' ').trim();
797
+ const searchKeywords = text ? (keywords ? [text, ...keywords] : [text]) : keywords;
798
+ // cmdk stores keywords only when `value` changes. Remount the cmdk item when
799
+ // they change so a new label or keyword list is picked up.
800
+ const keywordsKey = searchKeywords ? searchKeywords.join('') : '';
573
801
 
574
- // Use provided keywords or label for search
575
- const searchKeywords = keywords ?? (label ? [label] : undefined);
802
+ const isDisabled = disabled ?? rootDisabled ?? false;
576
803
 
577
804
  return (
578
805
  <CommandPrimitive.Item
806
+ key={keywordsKey}
579
807
  {...itemProps}
580
808
  value={value}
581
809
  keywords={searchKeywords}
582
- role="option"
583
- aria-selected={isSelected}
810
+ // cmdk owns aria-selected (the highlighted option); the chosen option is aria-checked.
811
+ aria-checked={isSelected}
584
812
  disabled={isDisabled || undefined}
585
813
  ref={forwardedRef}
586
814
  onSelect={handleSelect}
587
815
  className={classNames('rt-reset', 'rt-BaseMenuItem', 'rt-ComboboxItem', className)}
588
816
  >
589
817
  {isSelected ? (
590
- <span className={classNames('rt-BaseMenuItemIndicator', 'rt-ComboboxItemIndicator', sizeClass)}>
591
- <ThickCheckIcon className={classNames('rt-BaseMenuItemIndicatorIcon', 'rt-ComboboxItemIndicatorIcon', sizeClass)} />
818
+ <span className={classNames('rt-BaseMenuItemIndicator', 'rt-ComboboxItemIndicator')}>
819
+ <ThickCheckIcon className={classNames('rt-BaseMenuItemIndicatorIcon', 'rt-ComboboxItemIndicatorIcon')} />
592
820
  </span>
593
821
  ) : null}
594
822
  <Slottable>{children}</Slottable>