@astryxdesign/core 0.1.2 → 0.1.3-canary.6d57c8d

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 (567) hide show
  1. package/CHANGELOG.md +165 -0
  2. package/README.md +12 -12
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  8. package/dist/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +2 -4
  10. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  11. package/dist/ButtonGroup/ButtonGroup.js +0 -1
  12. package/dist/Calendar/Calendar.d.ts +5 -4
  13. package/dist/Calendar/Calendar.d.ts.map +1 -1
  14. package/dist/Calendar/Calendar.js +82 -43
  15. package/dist/Calendar/hooks/index.d.ts +0 -2
  16. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  17. package/dist/Calendar/hooks/index.js +1 -2
  18. package/dist/Calendar/index.d.ts +3 -3
  19. package/dist/Calendar/index.d.ts.map +1 -1
  20. package/dist/Calendar/index.js +1 -1
  21. package/dist/Calendar/styles.d.ts +3 -9
  22. package/dist/Calendar/styles.d.ts.map +1 -1
  23. package/dist/Calendar/styles.js +3 -11
  24. package/dist/Carousel/Carousel.d.ts.map +1 -1
  25. package/dist/Carousel/Carousel.js +17 -3
  26. package/dist/Center/Center.d.ts +11 -1
  27. package/dist/Center/Center.d.ts.map +1 -1
  28. package/dist/Center/Center.js +9 -3
  29. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  30. package/dist/Chat/ChatComposerInput.js +0 -1
  31. package/dist/Chat/ChatMessageList.d.ts +14 -1
  32. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  33. package/dist/Chat/ChatMessageList.js +2 -0
  34. package/dist/Chat/useTriggerMenu.d.ts +8 -1
  35. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  36. package/dist/Chat/useTriggerMenu.js +15 -3
  37. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  40. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  41. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  42. package/dist/CheckboxList/CheckboxList.js +39 -9
  43. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  44. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  45. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  46. package/dist/CheckboxList/CheckboxListItem.js +0 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +9 -5
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +51 -37
  51. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  52. package/dist/CommandPalette/CommandPalette.js +5 -5
  53. package/dist/ContextMenu/ContextMenu.d.ts +4 -5
  54. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  55. package/dist/ContextMenu/ContextMenu.js +93 -11
  56. package/dist/DateInput/DateInput.d.ts +23 -1
  57. package/dist/DateInput/DateInput.d.ts.map +1 -1
  58. package/dist/DateInput/DateInput.js +167 -99
  59. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  60. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  61. package/dist/DateRangeInput/DateRangeInput.js +42 -9
  62. package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
  63. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  64. package/dist/DateTimeInput/DateTimeInput.js +78 -15
  65. package/dist/Dialog/Dialog.d.ts.map +1 -1
  66. package/dist/Dialog/Dialog.js +12 -0
  67. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
  68. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  69. package/dist/DropdownMenu/DropdownMenu.js +42 -14
  70. package/dist/Field/Field.d.ts +19 -2
  71. package/dist/Field/Field.d.ts.map +1 -1
  72. package/dist/Field/Field.js +4 -0
  73. package/dist/Field/FieldLabel.d.ts +21 -2
  74. package/dist/Field/FieldLabel.d.ts.map +1 -1
  75. package/dist/Field/FieldLabel.js +41 -26
  76. package/dist/FileInput/FileInput.d.ts +22 -1
  77. package/dist/FileInput/FileInput.d.ts.map +1 -1
  78. package/dist/FileInput/FileInput.js +56 -10
  79. package/dist/Grid/Grid.d.ts +11 -1
  80. package/dist/Grid/Grid.d.ts.map +1 -1
  81. package/dist/Grid/Grid.js +32 -6
  82. package/dist/InputGroup/InputGroup.d.ts +4 -3
  83. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  84. package/dist/InputGroup/InputGroup.js +16 -6
  85. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  86. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  87. package/dist/InputGroup/InputGroupContext.js +2 -2
  88. package/dist/InputGroup/groupStyles.d.ts +1 -1
  89. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  90. package/dist/InputGroup/groupStyles.js +3 -3
  91. package/dist/InputGroup/index.d.ts +1 -0
  92. package/dist/InputGroup/index.d.ts.map +1 -1
  93. package/dist/Kbd/Kbd.d.ts.map +1 -1
  94. package/dist/Kbd/Kbd.js +33 -1
  95. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  96. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  97. package/dist/Layer/layerAnimations.stylex.js +8 -4
  98. package/dist/Layer/useLayer.d.ts +10 -0
  99. package/dist/Layer/useLayer.d.ts.map +1 -1
  100. package/dist/Layer/useLayer.js +64 -9
  101. package/dist/Layout/Layout.d.ts +4 -11
  102. package/dist/Layout/Layout.d.ts.map +1 -1
  103. package/dist/Layout/Layout.js +10 -11
  104. package/dist/Layout/LayoutFooter.d.ts +2 -2
  105. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  106. package/dist/Layout/LayoutHeader.d.ts +2 -2
  107. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  108. package/dist/Layout/LayoutPanel.d.ts +2 -2
  109. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  110. package/dist/Layout/padding.stylex.d.ts +51 -0
  111. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  112. package/dist/Layout/padding.stylex.js +63 -0
  113. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  114. package/dist/Lightbox/Lightbox.js +4 -2
  115. package/dist/Link/Link.d.ts +16 -1
  116. package/dist/Link/Link.d.ts.map +1 -1
  117. package/dist/Link/Link.js +18 -7
  118. package/dist/Markdown/Markdown.d.ts.map +1 -1
  119. package/dist/Markdown/Markdown.js +7 -0
  120. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  121. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  122. package/dist/MoreMenu/MoreMenu.js +0 -2
  123. package/dist/MultiSelector/MultiSelector.d.ts +27 -2
  124. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  125. package/dist/MultiSelector/MultiSelector.js +141 -42
  126. package/dist/MultiSelector/hooks.d.ts +12 -1
  127. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  128. package/dist/MultiSelector/hooks.js +14 -1
  129. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  130. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  131. package/dist/NumberInput/NumberInput.d.ts +23 -2
  132. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  133. package/dist/NumberInput/NumberInput.js +62 -12
  134. package/dist/Pagination/Pagination.d.ts +1 -1
  135. package/dist/Pagination/Pagination.d.ts.map +1 -1
  136. package/dist/Pagination/Pagination.js +15 -1
  137. package/dist/Popover/Popover.d.ts +18 -2
  138. package/dist/Popover/Popover.d.ts.map +1 -1
  139. package/dist/Popover/Popover.js +5 -2
  140. package/dist/Popover/usePopover.d.ts +38 -3
  141. package/dist/Popover/usePopover.d.ts.map +1 -1
  142. package/dist/Popover/usePopover.js +24 -9
  143. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  144. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  145. package/dist/PowerSearch/PowerSearch.js +6 -1
  146. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  147. package/dist/ProgressBar/ProgressBar.js +3 -5
  148. package/dist/RadioList/RadioList.d.ts +20 -2
  149. package/dist/RadioList/RadioList.d.ts.map +1 -1
  150. package/dist/RadioList/RadioList.js +105 -9
  151. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  152. package/dist/RadioList/RadioListItem.js +13 -2
  153. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  154. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  155. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  156. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  157. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  159. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  160. package/dist/Selector/Selector.d.ts +24 -1
  161. package/dist/Selector/Selector.d.ts.map +1 -1
  162. package/dist/Selector/Selector.js +122 -43
  163. package/dist/Selector/hooks.d.ts +8 -1
  164. package/dist/Selector/hooks.d.ts.map +1 -1
  165. package/dist/Selector/hooks.js +14 -1
  166. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  167. package/dist/SideNav/SideNavHeading.js +2 -0
  168. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  169. package/dist/SideNav/SideNavItem.js +1 -1
  170. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  171. package/dist/Skeleton/Skeleton.js +8 -2
  172. package/dist/Slider/Slider.d.ts +21 -0
  173. package/dist/Slider/Slider.d.ts.map +1 -1
  174. package/dist/Slider/Slider.js +36 -9
  175. package/dist/Spinner/Spinner.d.ts.map +1 -1
  176. package/dist/Spinner/Spinner.js +1 -1
  177. package/dist/Stack/Stack.d.ts +38 -23
  178. package/dist/Stack/Stack.d.ts.map +1 -1
  179. package/dist/Stack/Stack.js +25 -1
  180. package/dist/Stack/StackItem.d.ts +12 -23
  181. package/dist/Stack/StackItem.d.ts.map +1 -1
  182. package/dist/Stack/StackItem.js +8 -1
  183. package/dist/Switch/Switch.d.ts +23 -2
  184. package/dist/Switch/Switch.d.ts.map +1 -1
  185. package/dist/Switch/Switch.js +41 -11
  186. package/dist/TabList/Tab.d.ts.map +1 -1
  187. package/dist/TabList/Tab.js +6 -0
  188. package/dist/TabList/TabList.d.ts +16 -4
  189. package/dist/TabList/TabList.d.ts.map +1 -1
  190. package/dist/TabList/TabList.js +87 -10
  191. package/dist/TabList/TabListContext.d.ts +5 -0
  192. package/dist/TabList/TabListContext.d.ts.map +1 -1
  193. package/dist/TabList/TabListContext.js +5 -0
  194. package/dist/TabList/TabMenu.d.ts.map +1 -1
  195. package/dist/TabList/TabMenu.js +6 -1
  196. package/dist/Table/BaseTable.d.ts.map +1 -1
  197. package/dist/Table/BaseTable.js +11 -8
  198. package/dist/Table/Table.d.ts.map +1 -1
  199. package/dist/Table/Table.js +10 -1
  200. package/dist/Table/TableCell.d.ts +8 -1
  201. package/dist/Table/TableCell.d.ts.map +1 -1
  202. package/dist/Table/TableCell.js +10 -10
  203. package/dist/Table/TableHeaderCell.d.ts +8 -1
  204. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  205. package/dist/Table/TableHeaderCell.js +16 -16
  206. package/dist/Table/index.d.ts +2 -1
  207. package/dist/Table/index.d.ts.map +1 -1
  208. package/dist/Table/index.js +2 -2
  209. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  210. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  211. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  212. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  213. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  214. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  215. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  216. package/dist/Table/tableContextMenu.d.ts +33 -0
  217. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  218. package/dist/Table/tableContextMenu.js +133 -0
  219. package/dist/Table/types.d.ts +66 -0
  220. package/dist/Table/types.d.ts.map +1 -1
  221. package/dist/TextArea/TextArea.d.ts +22 -1
  222. package/dist/TextArea/TextArea.d.ts.map +1 -1
  223. package/dist/TextArea/TextArea.js +41 -9
  224. package/dist/TextInput/TextInput.d.ts +23 -2
  225. package/dist/TextInput/TextInput.d.ts.map +1 -1
  226. package/dist/TextInput/TextInput.js +55 -10
  227. package/dist/TimeInput/TimeInput.d.ts +23 -1
  228. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  229. package/dist/TimeInput/TimeInput.js +147 -76
  230. package/dist/Toast/Toast.d.ts.map +1 -1
  231. package/dist/Toast/Toast.js +14 -0
  232. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  233. package/dist/Toast/ToastViewport.js +131 -3
  234. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  235. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.js +26 -4
  237. package/dist/Toolbar/Toolbar.d.ts +3 -3
  238. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  239. package/dist/Toolbar/Toolbar.js +51 -7
  240. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  241. package/dist/Tooltip/useTooltip.js +56 -9
  242. package/dist/TopNav/TopNavHeading.d.ts +7 -1
  243. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  244. package/dist/TopNav/TopNavHeading.js +8 -0
  245. package/dist/TreeList/TreeList.d.ts +1 -1
  246. package/dist/TreeList/TreeList.d.ts.map +1 -1
  247. package/dist/TreeList/TreeList.js +72 -2
  248. package/dist/TreeList/TreeListItem.d.ts +11 -1
  249. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  250. package/dist/TreeList/TreeListItem.js +51 -12
  251. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  252. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  253. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  254. package/dist/Typeahead/BaseTypeahead.d.ts +13 -0
  255. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  256. package/dist/Typeahead/BaseTypeahead.js +69 -6
  257. package/dist/Typeahead/Typeahead.d.ts +26 -3
  258. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  259. package/dist/Typeahead/Typeahead.js +65 -29
  260. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  261. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  262. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  263. package/dist/VisuallyHidden/index.d.ts +11 -0
  264. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  265. package/dist/VisuallyHidden/index.js +13 -0
  266. package/dist/astryx.css +18 -0
  267. package/dist/astryx.umd.js +55 -55
  268. package/dist/astryx.umd.js.map +4 -4
  269. package/dist/hooks/focusableSelector.d.ts +22 -0
  270. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  271. package/dist/hooks/focusableSelector.js +24 -0
  272. package/dist/hooks/index.d.ts +12 -2
  273. package/dist/hooks/index.d.ts.map +1 -1
  274. package/dist/hooks/index.js +7 -2
  275. package/dist/hooks/useAnnounce.d.ts +40 -0
  276. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  277. package/dist/hooks/useAnnounce.js +142 -0
  278. package/dist/hooks/useClickableContainer.d.ts +16 -1
  279. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  280. package/dist/hooks/useClickableContainer.js +16 -4
  281. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  282. package/dist/hooks/useEntryAnimation.js +4 -4
  283. package/dist/hooks/useFocusTrap.d.ts +18 -0
  284. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  285. package/dist/hooks/useFocusTrap.js +87 -3
  286. package/dist/hooks/useGridFocus.d.ts +88 -1
  287. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  288. package/dist/hooks/useGridFocus.js +261 -62
  289. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  290. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  291. package/dist/hooks/useKeyboardHint.js +220 -0
  292. package/dist/hooks/useListFocus.d.ts +78 -7
  293. package/dist/hooks/useListFocus.d.ts.map +1 -1
  294. package/dist/hooks/useListFocus.js +305 -52
  295. package/dist/hooks/useLongPress.d.ts +21 -0
  296. package/dist/hooks/useLongPress.d.ts.map +1 -0
  297. package/dist/hooks/useLongPress.js +89 -0
  298. package/dist/hooks/useTreeFocus.d.ts +157 -0
  299. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  300. package/dist/hooks/useTreeFocus.js +337 -0
  301. package/dist/hooks/useTypeahead.d.ts +63 -0
  302. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  303. package/dist/hooks/useTypeahead.js +117 -0
  304. package/dist/index.d.ts +1 -0
  305. package/dist/index.d.ts.map +1 -1
  306. package/dist/index.js +1 -0
  307. package/dist/utils/dateTypes.d.ts +12 -0
  308. package/dist/utils/dateTypes.d.ts.map +1 -1
  309. package/dist/utils/dateTypes.js +41 -1
  310. package/dist/utils/index.d.ts +4 -0
  311. package/dist/utils/index.d.ts.map +1 -1
  312. package/dist/utils/index.js +3 -1
  313. package/dist/utils/inputAria.d.ts +29 -0
  314. package/dist/utils/inputAria.d.ts.map +1 -0
  315. package/dist/utils/inputAria.js +30 -0
  316. package/dist/utils/timeParser.d.ts.map +1 -1
  317. package/dist/utils/timeParser.js +5 -2
  318. package/docs.mjs +2 -2
  319. package/package.json +12 -8
  320. package/src/Avatar/Avatar.test.tsx +37 -0
  321. package/src/Avatar/Avatar.tsx +10 -5
  322. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  323. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  324. package/src/Button/Button.tsx +3 -16
  325. package/src/ButtonGroup/ButtonGroup.test.tsx +9 -9
  326. package/src/ButtonGroup/ButtonGroup.tsx +0 -1
  327. package/src/Calendar/Calendar.doc.mjs +4 -4
  328. package/src/Calendar/Calendar.test.tsx +168 -30
  329. package/src/Calendar/Calendar.tsx +121 -66
  330. package/src/Calendar/hooks/index.ts +0 -6
  331. package/src/Calendar/index.ts +1 -3
  332. package/src/Calendar/styles.ts +4 -9
  333. package/src/Carousel/Carousel.doc.mjs +7 -1
  334. package/src/Carousel/Carousel.test.tsx +20 -0
  335. package/src/Carousel/Carousel.tsx +16 -1
  336. package/src/Center/Center.doc.mjs +12 -2
  337. package/src/Center/Center.tsx +28 -3
  338. package/src/Chat/ChatComposerInput.test.tsx +26 -7
  339. package/src/Chat/ChatComposerInput.tsx +0 -1
  340. package/src/Chat/ChatDictationButton.doc.mjs +1 -1
  341. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  342. package/src/Chat/ChatMessageList.test.tsx +18 -0
  343. package/src/Chat/ChatMessageList.tsx +16 -0
  344. package/src/Chat/useTriggerMenu.tsx +22 -4
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +10 -0
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  347. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  348. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  349. package/src/CheckboxList/CheckboxList.test.tsx +192 -26
  350. package/src/CheckboxList/CheckboxList.tsx +56 -3
  351. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  352. package/src/CheckboxList/CheckboxListItem.tsx +0 -3
  353. package/src/Citation/Citation.doc.mjs +10 -0
  354. package/src/Citation/Citation.test.tsx +121 -0
  355. package/src/Citation/Citation.tsx +15 -3
  356. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  357. package/src/CodeBlock/CodeBlock.tsx +63 -33
  358. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  359. package/src/CommandPalette/CommandPalette.tsx +9 -21
  360. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  361. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  362. package/src/ContextMenu/ContextMenu.tsx +113 -16
  363. package/src/DateInput/DateInput.doc.mjs +286 -56
  364. package/src/DateInput/DateInput.test.tsx +248 -2
  365. package/src/DateInput/DateInput.tsx +196 -100
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  368. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  371. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  372. package/src/Dialog/Dialog.doc.mjs +2 -2
  373. package/src/Dialog/Dialog.tsx +12 -0
  374. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  375. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  376. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  377. package/src/Field/Field.doc.mjs +59 -18
  378. package/src/Field/Field.test.tsx +32 -0
  379. package/src/Field/Field.tsx +23 -5
  380. package/src/Field/FieldLabel.tsx +55 -17
  381. package/src/FileInput/FileInput.doc.mjs +17 -0
  382. package/src/FileInput/FileInput.test.tsx +283 -49
  383. package/src/FileInput/FileInput.tsx +76 -4
  384. package/src/Grid/Grid.doc.mjs +17 -4
  385. package/src/Grid/Grid.test.tsx +43 -30
  386. package/src/Grid/Grid.tsx +38 -3
  387. package/src/Icon/Icon.doc.mjs +3 -3
  388. package/src/InputGroup/InputGroup.doc.mjs +111 -23
  389. package/src/InputGroup/InputGroup.test.tsx +257 -60
  390. package/src/InputGroup/InputGroup.tsx +23 -5
  391. package/src/InputGroup/InputGroupContext.ts +9 -4
  392. package/src/InputGroup/groupStyles.ts +7 -1
  393. package/src/InputGroup/index.ts +1 -0
  394. package/src/Kbd/Kbd.test.tsx +22 -4
  395. package/src/Kbd/Kbd.tsx +35 -3
  396. package/src/Layer/layerAnimations.stylex.ts +20 -4
  397. package/src/Layer/useLayer.doc.mjs +5 -0
  398. package/src/Layer/useLayer.test.tsx +111 -0
  399. package/src/Layer/useLayer.tsx +90 -9
  400. package/src/Layout/Layout.tsx +8 -16
  401. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  402. package/src/Layout/LayoutFooter.tsx +3 -3
  403. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  404. package/src/Layout/LayoutHeader.tsx +3 -3
  405. package/src/Layout/LayoutPanel.tsx +3 -3
  406. package/src/Layout/padding.stylex.ts +52 -0
  407. package/src/Lightbox/Lightbox.doc.mjs +9 -0
  408. package/src/Lightbox/Lightbox.test.tsx +53 -5
  409. package/src/Lightbox/Lightbox.tsx +9 -4
  410. package/src/Link/Link.doc.mjs +14 -0
  411. package/src/Link/Link.test.tsx +50 -0
  412. package/src/Link/Link.tsx +39 -4
  413. package/src/Markdown/Markdown.test.tsx +11 -0
  414. package/src/Markdown/Markdown.tsx +7 -0
  415. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  416. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  417. package/src/MoreMenu/MoreMenu.tsx +0 -9
  418. package/src/MultiSelector/MultiSelector.doc.mjs +30 -0
  419. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  420. package/src/MultiSelector/MultiSelector.tsx +194 -48
  421. package/src/MultiSelector/hooks.ts +27 -0
  422. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  423. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  424. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  425. package/src/NumberInput/NumberInput.test.tsx +242 -40
  426. package/src/NumberInput/NumberInput.tsx +89 -11
  427. package/src/Pagination/Pagination.doc.mjs +3 -3
  428. package/src/Pagination/Pagination.test.tsx +117 -5
  429. package/src/Pagination/Pagination.tsx +21 -1
  430. package/src/Popover/Popover.doc.mjs +26 -0
  431. package/src/Popover/Popover.test.tsx +121 -0
  432. package/src/Popover/Popover.tsx +23 -2
  433. package/src/Popover/usePopover.doc.mjs +9 -3
  434. package/src/Popover/usePopover.tsx +70 -12
  435. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  436. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  437. package/src/PowerSearch/PowerSearch.tsx +16 -3
  438. package/src/ProgressBar/ProgressBar.test.tsx +34 -26
  439. package/src/ProgressBar/ProgressBar.tsx +3 -4
  440. package/src/RadioList/RadioList.doc.mjs +9 -0
  441. package/src/RadioList/RadioList.test.tsx +252 -16
  442. package/src/RadioList/RadioList.tsx +166 -10
  443. package/src/RadioList/RadioListItem.tsx +14 -2
  444. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  445. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  446. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  447. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  448. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  449. package/src/SelectableCard/SelectableCard.doc.mjs +1 -1
  450. package/src/Selector/Selector.doc.mjs +26 -0
  451. package/src/Selector/Selector.test.tsx +387 -15
  452. package/src/Selector/Selector.tsx +175 -67
  453. package/src/Selector/hooks.ts +23 -0
  454. package/src/SideNav/SideNav.test.tsx +48 -0
  455. package/src/SideNav/SideNavHeading.tsx +8 -2
  456. package/src/SideNav/SideNavItem.tsx +2 -4
  457. package/src/Skeleton/Skeleton.test.tsx +25 -0
  458. package/src/Skeleton/Skeleton.tsx +11 -1
  459. package/src/Slider/Slider.doc.mjs +15 -0
  460. package/src/Slider/Slider.test.tsx +155 -2
  461. package/src/Slider/Slider.tsx +53 -5
  462. package/src/Spinner/Spinner.tsx +7 -1
  463. package/src/Stack/Stack.doc.mjs +77 -0
  464. package/src/Stack/Stack.test.tsx +55 -5
  465. package/src/Stack/Stack.tsx +73 -24
  466. package/src/Stack/StackItem.test.tsx +23 -0
  467. package/src/Stack/StackItem.tsx +20 -23
  468. package/src/Switch/Switch.doc.mjs +15 -0
  469. package/src/Switch/Switch.test.tsx +162 -27
  470. package/src/Switch/Switch.tsx +61 -21
  471. package/src/TabList/Tab.tsx +6 -0
  472. package/src/TabList/TabList.doc.mjs +6 -0
  473. package/src/TabList/TabList.test.tsx +251 -0
  474. package/src/TabList/TabList.tsx +103 -12
  475. package/src/TabList/TabListContext.ts +7 -3
  476. package/src/TabList/TabMenu.tsx +6 -3
  477. package/src/Table/BaseTable.tsx +21 -17
  478. package/src/Table/Table.doc.mjs +2 -0
  479. package/src/Table/Table.test.tsx +10 -0
  480. package/src/Table/Table.tsx +8 -0
  481. package/src/Table/TableCell.tsx +25 -23
  482. package/src/Table/TableHeaderCell.tsx +28 -29
  483. package/src/Table/index.ts +3 -2
  484. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  485. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  486. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  487. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  488. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  489. package/src/Table/tableContextMenu.test.tsx +157 -0
  490. package/src/Table/tableContextMenu.tsx +142 -0
  491. package/src/Table/types.ts +74 -0
  492. package/src/Table/useTableColumnResize.doc.mjs +3 -3
  493. package/src/Table/useTableStickyColumns.doc.mjs +2 -2
  494. package/src/Text/Text.doc.mjs +18 -18
  495. package/src/TextArea/TextArea.doc.mjs +17 -0
  496. package/src/TextArea/TextArea.test.tsx +167 -35
  497. package/src/TextArea/TextArea.tsx +62 -15
  498. package/src/TextInput/TextInput.doc.mjs +17 -0
  499. package/src/TextInput/TextInput.test.tsx +168 -34
  500. package/src/TextInput/TextInput.tsx +77 -9
  501. package/src/Thumbnail/Thumbnail.doc.mjs +4 -4
  502. package/src/TimeInput/TimeInput.doc.mjs +340 -60
  503. package/src/TimeInput/TimeInput.test.tsx +255 -7
  504. package/src/TimeInput/TimeInput.tsx +176 -70
  505. package/src/Timestamp/Timestamp.doc.mjs +19 -19
  506. package/src/Toast/Toast.tsx +14 -0
  507. package/src/Toast/ToastViewport.test.tsx +214 -0
  508. package/src/Toast/ToastViewport.tsx +167 -21
  509. package/src/Tokenizer/Tokenizer.doc.mjs +29 -14
  510. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  511. package/src/Tokenizer/Tokenizer.tsx +37 -1
  512. package/src/Toolbar/Toolbar.test.tsx +109 -6
  513. package/src/Toolbar/Toolbar.tsx +59 -5
  514. package/src/Tooltip/Tooltip.test.tsx +68 -0
  515. package/src/Tooltip/useTooltip.tsx +55 -8
  516. package/src/TopNav/TopNav.test.tsx +50 -0
  517. package/src/TopNav/TopNavHeading.tsx +17 -1
  518. package/src/TreeList/TreeList.test.tsx +295 -6
  519. package/src/TreeList/TreeList.tsx +73 -1
  520. package/src/TreeList/TreeListItem.tsx +51 -5
  521. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  522. package/src/Typeahead/BaseTypeahead.tsx +93 -9
  523. package/src/Typeahead/Typeahead.doc.mjs +141 -30
  524. package/src/Typeahead/Typeahead.test.tsx +331 -1
  525. package/src/Typeahead/Typeahead.tsx +105 -33
  526. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  527. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  528. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  529. package/src/VisuallyHidden/index.ts +15 -0
  530. package/src/docs-script.test.mjs +50 -0
  531. package/src/hooks/focusableSelector.ts +25 -0
  532. package/src/hooks/index.ts +28 -2
  533. package/src/hooks/useAnnounce.test.tsx +101 -0
  534. package/src/hooks/useAnnounce.ts +168 -0
  535. package/src/hooks/useClickableContainer.ts +16 -4
  536. package/src/hooks/useEntryAnimation.ts +16 -4
  537. package/src/hooks/useFocusTrap.test.tsx +132 -0
  538. package/src/hooks/useFocusTrap.ts +98 -4
  539. package/src/hooks/useGridFocus.doc.mjs +40 -2
  540. package/src/hooks/useGridFocus.test.tsx +132 -0
  541. package/src/hooks/useGridFocus.ts +343 -67
  542. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  543. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  544. package/src/hooks/useKeyboardHint.tsx +332 -0
  545. package/src/hooks/useListFocus.doc.mjs +50 -10
  546. package/src/hooks/useListFocus.test.tsx +375 -0
  547. package/src/hooks/useListFocus.ts +395 -57
  548. package/src/hooks/useLongPress.test.tsx +134 -0
  549. package/src/hooks/useLongPress.ts +119 -0
  550. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  551. package/src/hooks/useTreeFocus.test.tsx +214 -0
  552. package/src/hooks/useTreeFocus.ts +559 -0
  553. package/src/hooks/useTypeahead.test.tsx +109 -0
  554. package/src/hooks/useTypeahead.ts +183 -0
  555. package/src/index.ts +1 -0
  556. package/src/theme/derivedVarRegistry.test.ts +10 -5
  557. package/src/utils/dateTypes.test.ts +40 -0
  558. package/src/utils/dateTypes.ts +40 -0
  559. package/src/utils/index.ts +9 -0
  560. package/src/utils/inputAria.test.ts +39 -0
  561. package/src/utils/inputAria.ts +58 -0
  562. package/src/utils/timeParser.test.ts +10 -0
  563. package/src/utils/timeParser.ts +5 -2
  564. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  565. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  566. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  567. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -4,13 +4,15 @@
4
4
 
5
5
  /**
6
6
  * @file MultiSelector.tsx
7
- * @input Uses React, StyleX, usePopover, CheckboxInput, Field, Badge, Icon
7
+ * @input Uses React, StyleX, usePopover, useTooltip, CheckboxInput, Field, Badge, Icon, InputGroupContext
8
8
  * @output Exports MultiSelector component
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
11
11
  * SYNC: When modified, update:
12
12
  * - /packages/core/src/MultiSelector/MultiSelector.doc.mjs
13
+ * - /packages/core/src/MultiSelector/MultiSelector.test.tsx
13
14
  * - /packages/core/src/MultiSelector/index.ts
15
+ * - /apps/storybook/stories/InputGroup.stories.tsx
14
16
  * - /packages/cli/templates/blocks/components/MultiSelector/ (showcase blocks)
15
17
  */
16
18
 
@@ -27,6 +29,7 @@ import React, {
27
29
  } from 'react';
28
30
  import * as stylex from '@stylexjs/stylex';
29
31
  import {usePopover} from '../Popover/usePopover';
32
+ import {useTooltip} from '../Tooltip';
30
33
  import {Icon, renderIconSlot, type IconType} from '../Icon';
31
34
  import type {IconName} from '../Icon';
32
35
  import {
@@ -64,11 +67,15 @@ import {
64
67
  getSelectableOptions,
65
68
  } from '../Selector/utils';
66
69
  import {useMultiCombobox} from './hooks';
67
- import {mergeProps} from '../utils';
70
+ import {getInputARIA, mergeProps} from '../utils';
71
+ import {useAnnounce} from '../hooks/useAnnounce';
68
72
  import type {BaseProps} from '../BaseProps';
69
73
  import type {SizeValue} from '../utils/types';
70
74
  import {useSize} from '../SizeContext/SizeContext';
71
75
  import {themeProps} from '../utils/themeProps';
76
+ import {groupStyles} from '../InputGroup/groupStyles';
77
+ import {useInputGroup} from '../InputGroup/InputGroupContext';
78
+ import {VisuallyHidden} from '../VisuallyHidden';
72
79
 
73
80
  // Sentinel value for the select-all item in keyboard navigation
74
81
  const SELECT_ALL_VALUE = '__xds_select_all__';
@@ -397,6 +404,30 @@ export interface MultiSelectorProps<
397
404
  */
398
405
  isDisabled?: boolean;
399
406
 
407
+ /**
408
+ * Explains why the selector is disabled. When set together with
409
+ * `isDisabled`, the selector shows a tooltip with this text on hover and
410
+ * keyboard focus, and the trigger stays focusable (via `aria-disabled`)
411
+ * so the reason is discoverable by keyboard and assistive technology.
412
+ * Activation stays blocked.
413
+ *
414
+ * Use this instead of wrapping a disabled selector in `Tooltip` — disabled
415
+ * controls don't emit the pointer events an external tooltip needs.
416
+ *
417
+ * @example
418
+ * ```
419
+ * <MultiSelector
420
+ * label="Columns"
421
+ * options={columns}
422
+ * value={selected}
423
+ * onChange={setSelected}
424
+ * isDisabled
425
+ * disabledMessage="Select a table first"
426
+ * />
427
+ * ```
428
+ */
429
+ disabledMessage?: string;
430
+
400
431
  /**
401
432
  * The options to display in the selector.
402
433
  * Can be strings, objects, dividers, or sections.
@@ -545,6 +576,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
545
576
  isOptional = false,
546
577
  isRequired = false,
547
578
  isDisabled = false,
579
+ disabledMessage,
548
580
  options,
549
581
  value,
550
582
  onChange,
@@ -575,9 +607,11 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
575
607
  const listboxId = useId();
576
608
  const descriptionId = useId();
577
609
  const statusMessageId = useId();
610
+ const inputLabelId = useId();
578
611
  const searchId = useId();
579
612
  const triggerRef = useRef<HTMLButtonElement>(null);
580
613
  const searchRef = useRef<HTMLInputElement>(null);
614
+ const inputGroup = useInputGroup();
581
615
 
582
616
  const [searchQuery, setSearchQuery] = useState('');
583
617
 
@@ -592,14 +626,29 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
592
626
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
593
627
  const isBusy = isLoading || optimisticValue !== value;
594
628
 
595
- // Build aria-describedby
596
- const ariaDescribedBy =
629
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
630
+ // tooltip listeners attach to the trigger container (which already exists)
631
+ // and the trigger button stays perceivable via aria-disabled instead of the
632
+ // disabled attribute. Activation is blocked by the isDisabled guards in
633
+ // useMultiCombobox (onTriggerClick / onKeyDown).
634
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
635
+ const disabledMessageTooltip = useTooltip({
636
+ placement: 'above',
637
+ // The container div is not naturally focusable; focusin bubbles up from
638
+ // the trigger button, so always attach focus listeners.
639
+ focusTrigger: 'always',
640
+ isEnabled: showsDisabledMessage,
641
+ });
642
+
643
+ const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
644
+ inputLabelId,
597
645
  [
598
646
  description ? descriptionId : null,
599
647
  status?.message ? statusMessageId : null,
600
- ]
601
- .filter(Boolean)
602
- .join(' ') || undefined;
648
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
649
+ ],
650
+ inputGroup,
651
+ );
603
652
 
604
653
  // Flatten options for keyboard navigation
605
654
  const selectableItems = useMemo(
@@ -607,6 +656,25 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
607
656
  [options],
608
657
  );
609
658
 
659
+ // Announce selection-count changes politely (comboboxes-7 announce path).
660
+ // Toggling options / select-all previously produced no audible feedback.
661
+ const announce = useAnnounce();
662
+ const announceSelection = useCallback(
663
+ (nextValue: string[]) => {
664
+ const total = selectableItems.length;
665
+ const selectableSet = new Set(selectableItems.map(item => item.value));
666
+ const selectedCount = nextValue.filter(v => selectableSet.has(v)).length;
667
+ if (selectedCount === 0) {
668
+ announce('Selection cleared');
669
+ } else if (total > 0 && selectedCount === total) {
670
+ announce('All selected');
671
+ } else {
672
+ announce(`${selectedCount} of ${total} selected`);
673
+ }
674
+ },
675
+ [announce, selectableItems],
676
+ );
677
+
610
678
  // Filter items by search query
611
679
  const filteredItems = useMemo(() => {
612
680
  if (!searchQuery) {
@@ -696,7 +764,9 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
696
764
  onHide: handleLayerHide,
697
765
  hasCloseButton: false,
698
766
  hasAutoFocus: false,
699
- dialogLabel: `${label} options`,
767
+ // The popup's own role="listbox" is the exposed semantics; the trigger
768
+ // keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
769
+ role: 'none',
700
770
  });
701
771
 
702
772
  // Open dropdown on mount when isDefaultOpen is true
@@ -708,19 +778,34 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
708
778
  }, []);
709
779
 
710
780
  // Handle toggle
711
- // Handle clear button click
781
+ // Clear all selected values. Shared by the clear button and the keyboard
782
+ // Delete/Backspace path so clearing is reachable without a mouse.
783
+ const clearValues = useCallback(() => {
784
+ onChange([]);
785
+ announceSelection([]);
786
+ if (changeAction) {
787
+ startTransition(async () => {
788
+ setOptimisticValue([]);
789
+ await changeAction([]);
790
+ });
791
+ }
792
+ }, [
793
+ onChange,
794
+ changeAction,
795
+ startTransition,
796
+ setOptimisticValue,
797
+ announceSelection,
798
+ ]);
799
+
800
+ // Whether there is at least one selected value (clearing is meaningful).
801
+ const hasValue = optimisticValue.length > 0;
802
+
712
803
  const handleClear = useCallback(
713
804
  (e: React.MouseEvent) => {
714
805
  e.stopPropagation(); // Don't open dropdown
715
- onChange([]);
716
- if (changeAction) {
717
- startTransition(async () => {
718
- setOptimisticValue([]);
719
- await changeAction([]);
720
- });
721
- }
806
+ clearValues();
722
807
  },
723
- [onChange, changeAction, startTransition, setOptimisticValue],
808
+ [clearValues],
724
809
  );
725
810
 
726
811
  const handleToggle = useCallback(
@@ -730,6 +815,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
730
815
  : [...optimisticValue, itemValue];
731
816
 
732
817
  onChange(newValue);
818
+ announceSelection(newValue);
733
819
  if (changeAction) {
734
820
  startTransition(async () => {
735
821
  setOptimisticValue(newValue);
@@ -743,6 +829,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
743
829
  changeAction,
744
830
  startTransition,
745
831
  setOptimisticValue,
832
+ announceSelection,
746
833
  ],
747
834
  );
748
835
 
@@ -788,6 +875,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
788
875
  }
789
876
 
790
877
  onChange(newValue);
878
+ announceSelection(newValue);
791
879
  if (changeAction) {
792
880
  startTransition(async () => {
793
881
  setOptimisticValue(newValue);
@@ -802,6 +890,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
802
890
  changeAction,
803
891
  startTransition,
804
892
  setOptimisticValue,
893
+ announceSelection,
805
894
  ]);
806
895
 
807
896
  // Route toggle: select-all sentinel → handleSelectAll, everything else → handleToggle
@@ -843,9 +932,24 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
843
932
  }, [popover, hasSearch, optimisticValue]),
844
933
  onClose: popover.hide,
845
934
  onToggle: handleNavigableToggle,
935
+ onClear: hasClear ? clearValues : undefined,
936
+ hasValue,
846
937
  listboxId,
847
938
  });
848
939
 
940
+ // Keep the highlighted option visible during keyboard navigation. The
941
+ // listbox is a fixed-height scroll container, so without this the virtual
942
+ // cursor walks off-screen once navigation passes the visible window. Mirrors
943
+ // CommandPaletteItem's scrollIntoView({block: 'nearest'}) behavior.
944
+ useEffect(() => {
945
+ if (!popover.isOpen || highlightedIndex < 0) {
946
+ return;
947
+ }
948
+ document
949
+ .getElementById(getItemId(highlightedIndex))
950
+ ?.scrollIntoView?.({block: 'nearest'});
951
+ }, [popover.isOpen, highlightedIndex, getItemId]);
952
+
849
953
  // Build trigger display content
850
954
  const selectedLabels = useMemo(() => {
851
955
  return optimisticValue.map(v => {
@@ -906,17 +1010,30 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
906
1010
  <input
907
1011
  ref={searchRef}
908
1012
  id={searchId}
909
- role="searchbox"
1013
+ // When hasSearch is set, focus moves into this input on open, so it —
1014
+ // not the trigger — must be the combobox reporting the highlighted
1015
+ // option via aria-activedescendant (comboboxes-4).
1016
+ role="combobox"
1017
+ aria-expanded={popover.isOpen}
910
1018
  aria-controls={listboxId}
1019
+ aria-autocomplete="list"
1020
+ aria-activedescendant={
1021
+ popover.isOpen && highlightedIndex >= 0
1022
+ ? getItemId(highlightedIndex)
1023
+ : undefined
1024
+ }
911
1025
  aria-label="Search options"
912
1026
  type="text"
913
1027
  value={searchQuery}
914
1028
  onChange={e => setSearchQuery(e.target.value)}
915
1029
  onKeyDown={e => {
916
- // Let ArrowDown/Up/Escape/Tab propagate to parent handler
1030
+ // Arrow keys navigate options; Enter toggles; Escape/Tab close.
1031
+ // Space and Home/End are left to the input (type a space / move
1032
+ // the caret), not forwarded to option navigation.
917
1033
  if (
918
1034
  e.key === 'ArrowDown' ||
919
1035
  e.key === 'ArrowUp' ||
1036
+ e.key === 'Enter' ||
920
1037
  e.key === 'Escape' ||
921
1038
  e.key === 'Tab'
922
1039
  ) {
@@ -935,6 +1052,9 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
935
1052
  searchQuery,
936
1053
  searchPlaceholder,
937
1054
  onKeyDown,
1055
+ popover.isOpen,
1056
+ highlightedIndex,
1057
+ getItemId,
938
1058
  ]);
939
1059
 
940
1060
  // Render an individual item (index-based)
@@ -1062,9 +1182,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1062
1182
 
1063
1183
  if (isDivider(option)) {
1064
1184
  flushPending();
1065
- elements.push(
1066
- <Divider key={`divider-${i}`} xstyle={styles.divider} />,
1067
- );
1185
+ elements.push(<Divider key={`divider-${i}`} xstyle={styles.divider} />);
1068
1186
  } else if (isSection(option)) {
1069
1187
  flushPending();
1070
1188
  const count = option.options.length;
@@ -1096,30 +1214,15 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1096
1214
  return elements;
1097
1215
  }, [options, renderItem, sortedItems, searchQuery, hasSelectAll]);
1098
1216
 
1099
- return (
1100
- <Field
1101
- label={label}
1102
- isLabelHidden={isLabelHidden}
1103
- description={description}
1104
- inputID={triggerId}
1105
- descriptionID={description ? descriptionId : undefined}
1106
- isOptional={isOptional}
1107
- isRequired={isRequired}
1108
- isDisabled={isDisabled}
1109
- status={
1110
- status
1111
- ? {
1112
- type: status.type,
1113
- message: status.message,
1114
- messageID: status.message ? statusMessageId : undefined,
1115
- }
1116
- : undefined
1117
- }
1118
- labelTooltip={labelTooltip}
1119
- width={width}>
1217
+ const multiSelectorContent = (
1218
+ <>
1120
1219
  <div
1121
1220
  ref={el => {
1122
1221
  popover.triggerRef(el);
1222
+ // Anchor + hover/focus listeners for the disabled-message tooltip.
1223
+ // Handlers are gated internally by isEnabled, and anchor names
1224
+ // compose, so attaching unconditionally is safe.
1225
+ disabledMessageTooltip.ref(el);
1123
1226
  }}
1124
1227
  onClick={onTriggerClick}
1125
1228
  data-testid={testId}
@@ -1133,6 +1236,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1133
1236
  optimisticValue.length === 0 && styles.triggerPlaceholder,
1134
1237
  status && inputStatusBorderStyles[status.type],
1135
1238
  status && inputStatusHoverShadowStyles[status.type],
1239
+ inputGroup && groupStyles.inGroup,
1136
1240
  xstyle,
1137
1241
  ),
1138
1242
  className,
@@ -1140,26 +1244,37 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1140
1244
  )}>
1141
1245
  {startIcon &&
1142
1246
  renderIconSlot(startIcon, {size: 'sm', color: 'secondary'})}
1247
+ {inputGroup && (
1248
+ <VisuallyHidden id={inputLabelId}>{label}</VisuallyHidden>
1249
+ )}
1143
1250
  <button
1144
1251
  ref={triggerRef}
1145
1252
  id={triggerId}
1146
1253
  type="button"
1147
- role="combobox"
1254
+ // In hasSearch mode the popup's search input is the combobox (it owns
1255
+ // focus + aria-activedescendant, comboboxes-4), so the trigger is a
1256
+ // plain button that opens the listbox — not a second combobox.
1257
+ role={hasSearch ? undefined : 'combobox'}
1148
1258
  aria-haspopup="listbox"
1149
1259
  aria-expanded={popover.isOpen}
1150
1260
  aria-controls={listboxId}
1151
1261
  aria-activedescendant={
1152
- popover.isOpen && highlightedIndex >= 0
1262
+ !hasSearch && popover.isOpen && highlightedIndex >= 0
1153
1263
  ? getItemId(highlightedIndex)
1154
1264
  : undefined
1155
1265
  }
1156
1266
  aria-describedby={ariaDescribedBy}
1267
+ aria-labelledby={ariaLabelledBy}
1157
1268
  aria-required={isRequired ? 'true' : undefined}
1158
1269
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
1159
1270
  aria-busy={isBusy || undefined}
1160
- disabled={isDisabled}
1271
+ // With a disabledMessage the trigger keeps focusability via
1272
+ // aria-disabled so the reason is focus-discoverable; activation is
1273
+ // still blocked by the isDisabled guards in useMultiCombobox.
1274
+ disabled={isDisabled && !showsDisabledMessage}
1275
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
1161
1276
  onKeyDown={onKeyDown}
1162
- tabIndex={-1}
1277
+ tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
1163
1278
  {...stylex.props(styles.trigger)}>
1164
1279
  <span {...stylex.props(styles.triggerContent)}>
1165
1280
  {renderTriggerContent()}
@@ -1169,7 +1284,6 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1169
1284
  {hasClear && value.length > 0 && !isDisabled && (
1170
1285
  <button
1171
1286
  type="button"
1172
- tabIndex={-1}
1173
1287
  onClick={handleClear}
1174
1288
  aria-label={`Clear all ${label}`}
1175
1289
  {...stylex.props(styles.clearButton)}>
@@ -1211,6 +1325,38 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1211
1325
  xstyle: styles.popover,
1212
1326
  },
1213
1327
  )}
1328
+
1329
+ {showsDisabledMessage &&
1330
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
1331
+ </>
1332
+ );
1333
+
1334
+ if (inputGroup) {
1335
+ return multiSelectorContent;
1336
+ }
1337
+
1338
+ return (
1339
+ <Field
1340
+ label={label}
1341
+ isLabelHidden={isLabelHidden}
1342
+ description={description}
1343
+ inputID={triggerId}
1344
+ descriptionID={description ? descriptionId : undefined}
1345
+ isOptional={isOptional}
1346
+ isRequired={isRequired}
1347
+ isDisabled={isDisabled}
1348
+ status={
1349
+ status
1350
+ ? {
1351
+ type: status.type,
1352
+ message: status.message,
1353
+ messageID: status.message ? statusMessageId : undefined,
1354
+ }
1355
+ : undefined
1356
+ }
1357
+ labelTooltip={labelTooltip}
1358
+ width={width}>
1359
+ {multiSelectorContent}
1214
1360
  </Field>
1215
1361
  );
1216
1362
  }
@@ -23,6 +23,17 @@ interface UseMultiComboboxOptions {
23
23
  onOpen: () => void;
24
24
  onClose: () => void;
25
25
  onToggle: (itemValue: string) => void;
26
+ /**
27
+ * Clear all selected values. When provided, pressing Delete or Backspace on
28
+ * the closed trigger clears the selection — a keyboard equivalent of the
29
+ * clear button (comboboxes-2). No-op when the popup is open or search is on.
30
+ */
31
+ onClear?: () => void;
32
+ /**
33
+ * Whether at least one value is selected (i.e. there is something to clear).
34
+ * The Delete/Backspace clear path is skipped when false.
35
+ */
36
+ hasValue?: boolean;
26
37
  listboxId: string;
27
38
  }
28
39
 
@@ -49,6 +60,8 @@ export function useMultiCombobox({
49
60
  onOpen,
50
61
  onClose,
51
62
  onToggle,
63
+ onClear,
64
+ hasValue = false,
52
65
  listboxId,
53
66
  }: UseMultiComboboxOptions): UseMultiComboboxResult {
54
67
  const [highlightedIndex, setHighlightedIndex] = useState<number>(-1);
@@ -179,6 +192,18 @@ export function useMultiCombobox({
179
192
  }
180
193
  break;
181
194
 
195
+ case 'Delete':
196
+ case 'Backspace':
197
+ // Keyboard equivalent of the clear button (comboboxes-2): clear all
198
+ // selected values from the closed trigger so clearing is not
199
+ // mouse-only. Skipped in search mode (keys edit the query) and while
200
+ // the popup is open (arrow navigation owns interaction).
201
+ if (!hasSearch && !isOpen && onClear != null && hasValue) {
202
+ e.preventDefault();
203
+ onClear();
204
+ }
205
+ break;
206
+
182
207
  default:
183
208
  // Typeahead only when search is not present
184
209
  if (!hasSearch && e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
@@ -218,6 +243,8 @@ export function useMultiCombobox({
218
243
  getEnabledIndices,
219
244
  typeahead,
220
245
  hasSearch,
246
+ onClear,
247
+ hasValue,
221
248
  ],
222
249
  );
223
250
 
@@ -1,7 +1,7 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
3
  import {describe, it, expect, vi} from 'vitest';
4
- import {render, screen} from '@testing-library/react';
4
+ import {render, screen, fireEvent} from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import {NavHeadingMenu} from './NavHeadingMenu';
7
7
  import {NavHeadingMenuItem} from './NavHeadingMenuItem';
@@ -204,6 +204,82 @@ describe('keyboard navigation', () => {
204
204
  await user.keyboard('{End}');
205
205
  expect(document.activeElement).toBe(items[2]);
206
206
  });
207
+
208
+ it('activates a focused onClick-only item with Enter', async () => {
209
+ const user = userEvent.setup();
210
+ const onClick = vi.fn();
211
+ render(
212
+ <NavHeadingMenu>
213
+ <NavHeadingMenuItem label="Action" onClick={onClick} />
214
+ </NavHeadingMenu>,
215
+ );
216
+ const item = screen.getByRole('menuitem');
217
+ item.focus();
218
+
219
+ await user.keyboard('{Enter}');
220
+ expect(onClick).toHaveBeenCalledOnce();
221
+ });
222
+
223
+ it('activates a focused onClick-only item with Space', async () => {
224
+ const user = userEvent.setup();
225
+ const onClick = vi.fn();
226
+ render(
227
+ <NavHeadingMenu>
228
+ <NavHeadingMenuItem label="Action" onClick={onClick} />
229
+ </NavHeadingMenu>,
230
+ );
231
+ const item = screen.getByRole('menuitem');
232
+ item.focus();
233
+
234
+ await user.keyboard('{ }');
235
+ expect(onClick).toHaveBeenCalledOnce();
236
+ });
237
+
238
+ it('does not activate a disabled item with Enter', async () => {
239
+ const user = userEvent.setup();
240
+ const onClick = vi.fn();
241
+ render(
242
+ <NavHeadingMenu>
243
+ <NavHeadingMenuItem label="Action" onClick={onClick} isDisabled />
244
+ </NavHeadingMenu>,
245
+ );
246
+ const item = screen.getByRole('menuitem');
247
+ item.focus();
248
+
249
+ await user.keyboard('{Enter}');
250
+ expect(onClick).not.toHaveBeenCalled();
251
+ });
252
+
253
+ it('typeahead focuses the item matching the typed character (menus-11)', () => {
254
+ render(
255
+ <NavHeadingMenu>
256
+ <NavHeadingMenuItem label="Cut" />
257
+ <NavHeadingMenuItem label="Paste" />
258
+ </NavHeadingMenu>,
259
+ );
260
+ const menu = screen.getByRole('menu');
261
+ fireEvent.keyDown(menu, {key: 'p'});
262
+ expect(screen.getByRole('menuitem', {name: 'Paste'})).toHaveFocus();
263
+ });
264
+
265
+ it('typeahead skips a disabled menuitem (menus-11)', () => {
266
+ render(
267
+ <NavHeadingMenu>
268
+ <NavHeadingMenuItem label="Cut" />
269
+ <NavHeadingMenuItem label="Paste (disabled)" isDisabled />
270
+ <NavHeadingMenuItem label="Paste special" />
271
+ </NavHeadingMenu>,
272
+ );
273
+ const menu = screen.getByRole('menu');
274
+ // Typing "p" must land on the enabled "Paste special", never the disabled
275
+ // "Paste (disabled)" — the disabled item is excluded from the typeahead
276
+ // selector entirely.
277
+ fireEvent.keyDown(menu, {key: 'p'});
278
+ expect(screen.getByRole('menuitem', {name: 'Paste special'})).toHaveFocus();
279
+ expect(
280
+ screen.getByRole('menuitem', {name: 'Paste (disabled)'}),
281
+ ).not.toHaveFocus();
282
+ });
207
283
  });
208
284
 
209
285
  describe('context forwarding', () => {
@@ -12,12 +12,13 @@
12
12
  * - /packages/core/src/NavMenu/index.ts
13
13
  */
14
14
 
15
- import React, {useMemo, type ReactNode} from 'react';
15
+ import React, {useCallback, useMemo, type ReactNode} from 'react';
16
16
  import * as stylex from '@stylexjs/stylex';
17
17
  import {spacingVars} from '../theme/tokens.stylex';
18
18
  import {mergeProps, mergeRefs} from '../utils';
19
19
  import type {BaseProps} from '../BaseProps';
20
20
  import {useListFocus} from '../hooks/useListFocus';
21
+ import {useTypeahead} from '../hooks/useTypeahead';
21
22
  import {themeProps} from '../utils/themeProps';
22
23
  import {
23
24
  NavHeadingMenuContext,
@@ -98,10 +99,56 @@ export function NavHeadingMenu({
98
99
  const closeCtx = useNavHeadingCloseContext();
99
100
  const closeMenu = closeCtx?.closeMenu;
100
101
 
101
- const {listRef, handleKeyDown} = useListFocus({
102
+ const {listRef, handleKeyDown, focusItem} = useListFocus({
103
+ itemSelector: '[role="menuitem"]:not([aria-disabled="true"])',
102
104
  onEscape: closeMenu,
103
105
  });
104
106
 
107
+ // First-character typeahead over the (enabled) menu items (menus-11).
108
+ const getMenuItems = useCallback(
109
+ (): HTMLElement[] =>
110
+ listRef.current
111
+ ? Array.from(
112
+ listRef.current.querySelectorAll<HTMLElement>(
113
+ '[role="menuitem"]:not([aria-disabled="true"])',
114
+ ),
115
+ )
116
+ : [],
117
+ [listRef],
118
+ );
119
+ const typeahead = useTypeahead({
120
+ getItemLabels: () => getMenuItems().map(el => el.textContent),
121
+ onMatch: focusItem,
122
+ getCurrentIndex: () =>
123
+ getMenuItems().findIndex(
124
+ el =>
125
+ el === document.activeElement || el.contains(document.activeElement),
126
+ ),
127
+ });
128
+
129
+ // Extend useListFocus with Enter/Space activation. Items rendered without an
130
+ // `href` are `<div role="menuitem">` elements, which have no native keyboard
131
+ // activation — without this, Enter/Space on a focused onClick-only item does
132
+ // nothing. Anchor items (with `href`) already activate on Enter natively.
133
+ const listKeyDown = useCallback(
134
+ (e: React.KeyboardEvent) => {
135
+ if (e.key === 'Enter' || e.key === ' ') {
136
+ const focused = document.activeElement as HTMLElement | null;
137
+ if (focused?.getAttribute('role') === 'menuitem') {
138
+ e.preventDefault();
139
+ focused.click();
140
+ return;
141
+ }
142
+ }
143
+ if (typeahead.onKeyDown(e)) {
144
+ e.preventDefault();
145
+ return;
146
+ }
147
+ handleKeyDown(e);
148
+ },
149
+ [handleKeyDown, typeahead],
150
+ );
151
+
105
152
  const ctx = useMemo(
106
153
  () => ({
107
154
  closeMenu: closeMenu ?? (() => {}),
@@ -117,7 +164,7 @@ export function NavHeadingMenu({
117
164
  <div
118
165
  ref={mergeRefs(ref, listRef)}
119
166
  role="menu"
120
- onKeyDown={handleKeyDown}
167
+ onKeyDown={listKeyDown}
121
168
  data-testid={testId}
122
169
  {...mergeProps(
123
170
  themeProps('nav-heading-menu', {size}),