@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
@@ -61,14 +61,7 @@ export const docs = {
61
61
  type: 'boolean',
62
62
  description: 'Whether to show a chevron icon on the trigger button. Set to false for icon-only triggers.',
63
63
  default: 'true',
64
- },
65
- {
66
- name: 'hasAutoFocus',
67
- type: 'boolean',
68
- description: 'Whether to auto-focus the first menu item when the menu opens. Set to false for inline showcases or documentation previews.',
69
- default: 'true',
70
- },
71
- {
64
+ }, {
72
65
  name: 'children',
73
66
  type: '(item: DropdownMenuItemData) => ReactNode',
74
67
  description: 'Custom render function for each item in the list.',
@@ -45,30 +45,43 @@ beforeEach(() => {
45
45
  describe('DropdownMenu', () => {
46
46
  it('renders trigger button with label', () => {
47
47
  render(
48
- <DropdownMenu
49
- button={{label: 'Actions'}}
50
- items={[{label: 'Item 1'}]}
51
- />,
48
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
52
49
  );
53
50
  expect(screen.getByRole('button', {name: /Actions/})).toBeInTheDocument();
54
51
  });
55
52
 
56
53
  it('renders menu with role="menu"', () => {
57
54
  render(
58
- <DropdownMenu
59
- button={{label: 'Actions'}}
60
- items={[{label: 'Item 1'}]}
61
- />,
55
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
62
56
  );
63
57
  expect(screen.getByRole('menu', {hidden: true})).toBeInTheDocument();
64
58
  });
65
59
 
60
+ it('names the menu from the trigger label (menus-13)', () => {
61
+ render(
62
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
63
+ );
64
+ expect(
65
+ screen.getByRole('menu', {name: 'Actions', hidden: true}),
66
+ ).toBeInTheDocument();
67
+ });
68
+
69
+ it('does not wrap the menu in a role="dialog" aria-modal element', () => {
70
+ render(
71
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
72
+ );
73
+ // The popup exposes its own role="menu"; it must not be nested inside a
74
+ // modal dialog, which would announce an unnamed dialog around the menu
75
+ // while focus stays on the trigger.
76
+ expect(screen.queryByRole('dialog', {hidden: true})).not.toBeInTheDocument();
77
+ expect(
78
+ document.querySelector('[aria-modal="true"]'),
79
+ ).not.toBeInTheDocument();
80
+ });
81
+
66
82
  it('defaults menu placement below', () => {
67
83
  render(
68
- <DropdownMenu
69
- button={{label: 'Actions'}}
70
- items={[{label: 'Item 1'}]}
71
- />,
84
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
72
85
  );
73
86
  const popover = screen
74
87
  .getByRole('menu', {hidden: true})
@@ -96,10 +109,7 @@ describe('DropdownMenu', () => {
96
109
 
97
110
  it('has aria-haspopup and aria-expanded attributes', () => {
98
111
  render(
99
- <DropdownMenu
100
- button={{label: 'Actions'}}
101
- items={[{label: 'Item 1'}]}
102
- />,
112
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
103
113
  );
104
114
  const button = screen.getByRole('button', {name: /Actions/});
105
115
  expect(button).toHaveAttribute('aria-haspopup', 'menu');
@@ -107,16 +117,43 @@ describe('DropdownMenu', () => {
107
117
  });
108
118
 
109
119
  it('opens menu when button is clicked', async () => {
120
+ const user = userEvent.setup();
121
+ render(
122
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
123
+ );
124
+
125
+ await user.click(screen.getByRole('button', {name: /Actions/}));
126
+ expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
127
+ });
128
+
129
+ it('closes the menu when Tab is pressed inside it (APG menu-button)', async () => {
130
+ const user = userEvent.setup();
131
+ render(
132
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Item 1'}]} />,
133
+ );
134
+
135
+ await user.click(screen.getByRole('button', {name: /Actions/}));
136
+ expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
137
+
138
+ const menu = screen.getByRole('menu', {hidden: true});
139
+ fireEvent.keyDown(menu, {key: 'Tab'});
140
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
141
+ });
142
+
143
+ it('typeahead focuses the item matching the typed character (menus-11)', async () => {
110
144
  const user = userEvent.setup();
111
145
  render(
112
146
  <DropdownMenu
113
147
  button={{label: 'Actions'}}
114
- items={[{label: 'Item 1'}]}
148
+ items={[{label: 'Cut'}, {label: 'Copy'}, {label: 'Delete'}]}
115
149
  />,
116
150
  );
117
-
118
151
  await user.click(screen.getByRole('button', {name: /Actions/}));
119
- expect(HTMLElement.prototype.showPopover).toHaveBeenCalled();
152
+ const menu = screen.getByRole('menu', {hidden: true});
153
+ fireEvent.keyDown(menu, {key: 'd'});
154
+ expect(
155
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
156
+ ).toHaveFocus();
120
157
  });
121
158
 
122
159
  it('calls onClick callback when button is clicked', async () => {
@@ -212,29 +249,6 @@ describe('DropdownMenu controlled mode', () => {
212
249
  });
213
250
  });
214
251
 
215
- describe('DropdownMenu hasAutoFocus', () => {
216
- it('does not focus menu items when hasAutoFocus is false and isMenuOpen is true', () => {
217
- const focusSpy = vi.spyOn(HTMLElement.prototype, 'focus');
218
- render(
219
- <DropdownMenu
220
- button={{label: 'Actions'}}
221
- items={[{label: 'Edit'}, {label: 'Delete'}]}
222
- isMenuOpen={true}
223
- hasAutoFocus={false}
224
- onOpenChange={() => {}}
225
- />,
226
- );
227
-
228
- const menuItems = screen.getAllByRole('menuitem', {hidden: true});
229
- const menuItemFocusCalls = focusSpy.mock.calls.filter((_, i) => {
230
- const ctx = focusSpy.mock.contexts[i];
231
- return menuItems.includes(ctx as HTMLElement);
232
- });
233
- expect(menuItemFocusCalls).toHaveLength(0);
234
- focusSpy.mockRestore();
235
- });
236
- });
237
-
238
252
  describe('DropdownMenu items', () => {
239
253
  it('renders items with labels', () => {
240
254
  render(
@@ -43,6 +43,7 @@ import {
43
43
  type DropdownMenuContextValue,
44
44
  } from './DropdownMenuContext';
45
45
  import {useListFocus} from '../hooks/useListFocus';
46
+ import {useTypeahead} from '../hooks/useTypeahead';
46
47
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
47
48
  import type {LayerPlacement} from '../Layer/useLayer';
48
49
  import {
@@ -134,13 +135,6 @@ interface DropdownMenuBaseProps extends BaseProps {
134
135
  */
135
136
  placement?: LayerPlacement;
136
137
 
137
- /**
138
- * Whether to auto-focus the first menu item when the menu opens.
139
- * Set to `false` for inline showcases or documentation previews
140
- * where stealing focus is undesirable.
141
- * @default true
142
- */
143
- hasAutoFocus?: boolean;
144
138
  'data-testid'?: string;
145
139
  }
146
140
 
@@ -192,7 +186,6 @@ export function DropdownMenu({
192
186
  onClick,
193
187
  hasChevron = true,
194
188
  placement = 'below',
195
- hasAutoFocus = true,
196
189
  className,
197
190
  style,
198
191
  xstyle,
@@ -249,6 +242,9 @@ export function DropdownMenu({
249
242
  hasLightDismiss: true,
250
243
  hasCloseButton: false,
251
244
  hasAutoFocus: false,
245
+ // The popup's own role="menu" is the exposed semantics; wrapping it in a
246
+ // modal dialog would announce an unnamed dialog around the menu.
247
+ role: 'none',
252
248
  });
253
249
 
254
250
  const closeMenu = useCallback(() => {
@@ -260,27 +256,49 @@ export function DropdownMenu({
260
256
  listRef,
261
257
  handleKeyDown: listNavKeyDown,
262
258
  focusFirst,
259
+ focusItem,
263
260
  } = useListFocus<HTMLDivElement>({
264
261
  itemSelector: '[role="menuitem"]:not([aria-disabled="true"])',
265
262
  wrap: false,
266
263
  onEscape: closeMenu,
267
264
  });
268
265
 
266
+ // First-character typeahead over the (enabled) menu items — jump to the next
267
+ // item whose label starts with the typed text (menus-11).
268
+ const getMenuItems = useCallback(
269
+ (): HTMLElement[] =>
270
+ listRef.current
271
+ ? Array.from(
272
+ listRef.current.querySelectorAll<HTMLElement>(
273
+ '[role="menuitem"]:not([aria-disabled="true"])',
274
+ ),
275
+ )
276
+ : [],
277
+ [listRef],
278
+ );
279
+ const typeahead = useTypeahead({
280
+ getItemLabels: () => getMenuItems().map(el => el.textContent),
281
+ onMatch: focusItem,
282
+ getCurrentIndex: () =>
283
+ getMenuItems().findIndex(
284
+ el =>
285
+ el === document.activeElement || el.contains(document.activeElement),
286
+ ),
287
+ });
288
+
269
289
  // Sync controlled open state → popover, and focus first item on open
270
290
  useEffect(() => {
271
291
  if (isControlled) {
272
292
  if (controlledIsOpen && !popover.isOpen) {
273
293
  popover.show();
274
- if (hasAutoFocus) {
275
- requestAnimationFrame(() => focusFirst());
276
- }
294
+ requestAnimationFrame(() => focusFirst());
277
295
  } else if (!controlledIsOpen && popover.isOpen) {
278
296
  popover.hide();
279
297
  }
280
298
  }
281
- }, [controlledIsOpen, isControlled, popover, hasAutoFocus, focusFirst]);
299
+ }, [controlledIsOpen, isControlled, popover, focusFirst]);
282
300
 
283
- // Extend useListFocus with Enter/Space activation
301
+ // Extend useListFocus with Enter/Space activation + typeahead
284
302
  const listKeyDown = useCallback(
285
303
  (e: React.KeyboardEvent) => {
286
304
  if (e.key === 'Enter' || e.key === ' ') {
@@ -291,17 +309,29 @@ export function DropdownMenu({
291
309
  }
292
310
  return;
293
311
  }
312
+ // APG menu-button pattern: Tab closes the menu. Menu items are
313
+ // tabIndex={-1} so the focus trap has nothing trappable and Tab would
314
+ // otherwise leak into the page while the menu stayed open (menus-5).
315
+ // Do NOT preventDefault — closing restores focus to the trigger, and the
316
+ // browser's default Tab then continues from there to the next element.
317
+ if (e.key === 'Tab') {
318
+ closeMenu();
319
+ return;
320
+ }
321
+ // Type-to-focus next; if it consumed a printable key, stop here.
322
+ if (typeahead.onKeyDown(e)) {
323
+ e.preventDefault();
324
+ return;
325
+ }
294
326
  listNavKeyDown(e);
295
327
  },
296
- [listNavKeyDown],
328
+ [listNavKeyDown, closeMenu, typeahead],
297
329
  );
298
330
 
299
331
  const openAndFocus = useCallback(() => {
300
332
  popover.show();
301
- if (hasAutoFocus) {
302
- requestAnimationFrame(() => focusFirst());
303
- }
304
- }, [popover, hasAutoFocus, focusFirst]);
333
+ requestAnimationFrame(() => focusFirst());
334
+ }, [popover, focusFirst]);
305
335
 
306
336
  const handleButtonClick = useCallback(() => {
307
337
  // If the menu was just closed by light dismiss (e.g. iOS Safari fires
@@ -399,6 +429,10 @@ export function DropdownMenu({
399
429
  ref={listRef}
400
430
  id={menuId}
401
431
  role="menu"
432
+ // Give the menu an accessible name from its trigger's label, so
433
+ // screen readers announce e.g. "Actions menu" rather than an unnamed
434
+ // menu (menus-13).
435
+ aria-label={button.label}
402
436
  onKeyDown={listKeyDown}
403
437
  {...mergeProps(
404
438
  themeProps('dropdown-menu'),
@@ -11,7 +11,18 @@ export const docs = {
11
11
  playground: {
12
12
  defaults: {
13
13
  label: 'Email address',
14
- children: {__element: 'TextInput', props: {label: 'Email', placeholder: 'you@example.com'}},
14
+ inputID: 'email-input',
15
+ description: 'Use Field for controls that do not already provide field under the hood.',
16
+ descriptionID: 'email-help',
17
+ children: {
18
+ __element: 'input',
19
+ props: {
20
+ id: 'email-input',
21
+ 'aria-describedby': 'email-help',
22
+ placeholder: 'you@example.com',
23
+ type: 'email',
24
+ },
25
+ },
15
26
  },
16
27
  },
17
28
  theming: {
@@ -27,7 +38,7 @@ export const docs = {
27
38
  {property: 'borderRadius', vars: ['--_field-radius']},
28
39
  ],
29
40
  },
30
- description: 'Form field wrapper that provides label, description, and optional/required indicators.',
41
+ description: 'Low-level form field wrapper for custom controls that need a label, description, and optional/required indicators.',
31
42
  props: [
32
43
  {
33
44
  name: 'label',
@@ -133,36 +144,65 @@ export const docs = {
133
144
  {name: 'FieldStatus'},
134
145
  ],
135
146
  usage: {
136
- description: 'Field wraps any input control with a label, description, and validation status. Use it to build accessible forms with consistent labeling, optional/required indicators, and inline error, warning, or success feedback.',
147
+ description: 'Field is a low-level wrapper for custom, native, or third-party controls that do not already provide field label, description, and status UI. Use it when you need the Field shell around a control you own; use styled Astryx inputs like TextInput, Typeahead, and Select directly when they already expose label, description, and validation props.',
137
148
  bestPractices: [
149
+ { guidance: true, description: 'Wrap custom controls, native inputs, or third-party widgets that need labeling, helper text, optional/required indicators, or validation status.' },
138
150
  { guidance: true, description: 'Always provide a label for accessibility, even if visually hidden with isLabelHidden.' },
139
- { guidance: true, description: 'Use the status prop with clear messages to provide inline validation feedback.' },
140
- { guidance: true, description: 'Add a description when the label alone does not explain what the field expects, like format hints or constraints.' },
151
+ { guidance: true, description: 'Use inputID and descriptionID to connect the label and description to the inner control with htmlFor and aria-describedby.' },
152
+ { guidance: false, description: 'Nest Field around styled inputs such as TextInput, Typeahead, Select, DateInput, or TextArea; those components already render their own Field shell.' },
153
+ { guidance: false, description: 'Use the attached status variant on non-bordered controls such as sliders, switches, or checkboxes; use detached so the message does not overlap the control.' },
141
154
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
142
- { guidance: false, description: 'Use the detached status variant on bordered inputs; reserve it for checkboxes, switches, and sliders.' },
143
155
  { guidance: false, description: 'Hide the label without providing an alternative way for the user to understand the field purpose.' },
144
156
  ],
145
157
  anatomy: [
146
158
  {name: 'Label', required: true, description: 'Text identifying the field. Always rendered for accessibility, optionally hidden visually.'},
147
159
  {name: 'Description', required: false, description: 'Helper text between the label and input explaining what to enter.'},
148
- {name: 'Input slot', required: true, description: 'The input control wrapped by the field: TextInput, Select, DateInput, etc.'},
160
+ {name: 'Control slot', required: true, description: 'A custom, native, or third-party control that does not already render a field shell.'},
149
161
  {name: 'Status message', required: false, description: 'Inline validation feedback showing error, warning, or success with a message.'},
150
162
  {name: 'Optional/Required indicator', required: false, description: 'Badge next to the label showing whether the field is optional or required.'},
151
163
  {name: 'Label tooltip', required: false, description: 'Info icon at the end of the label with a tooltip explaining the field.'},
152
164
  ],
153
165
  },
166
+ examples: [
167
+ {
168
+ label: 'Wrap a custom control',
169
+ code: `
170
+ function CustomSliderField() {
171
+ return (
172
+ <Field
173
+ label="Confidence"
174
+ inputID="confidence-slider"
175
+ description="Choose how strict the review should be."
176
+ descriptionID="confidence-help"
177
+ status={{type: 'success', message: 'Recommended default'}}
178
+ statusVariant="detached">
179
+ <input
180
+ id="confidence-slider"
181
+ type="range"
182
+ min={0}
183
+ max={100}
184
+ defaultValue={60}
185
+ aria-describedby="confidence-help"
186
+ />
187
+ </Field>
188
+ );
189
+ }
190
+ `,
191
+ },
192
+ ],
154
193
  };
155
194
 
156
195
  /** @type {import('../docs-types').TranslationDoc} */
157
196
  export const docsZh = {
158
197
  usage: {
159
- description: 'Field wraps any input control with a label, description, and validation status. Use it to build accessible forms with consistent labeling, optional/required indicators, and inline error, warning, or success feedback.',
198
+ description: 'Field is a low-level wrapper for custom, native, or third-party controls that do not already provide field label, description, and status UI. Use it when you need the Field shell around a control you own; use styled Astryx inputs like TextInput, Typeahead, and Select directly when they already expose label, description, and validation props.',
160
199
  bestPractices: [
200
+ { guidance: true, description: 'Wrap custom controls, native inputs, or third-party widgets that need labeling, helper text, optional/required indicators, or validation status.' },
161
201
  { guidance: true, description: 'Always provide a label for accessibility, even if visually hidden with isLabelHidden.' },
162
- { guidance: true, description: 'Use the status prop with clear messages to provide inline validation feedback.' },
163
- { guidance: true, description: 'Add a description when the label alone does not explain what the field expects, like format hints or constraints.' },
202
+ { guidance: true, description: 'Use inputID and descriptionID to connect the label and description to the inner control with htmlFor and aria-describedby.' },
203
+ { guidance: false, description: 'Nest Field around styled inputs such as TextInput, Typeahead, Select, DateInput, or TextArea; those components already render their own Field shell.' },
204
+ { guidance: false, description: 'Use the attached status variant on non-bordered controls such as sliders, switches, or checkboxes; use detached so the message does not overlap the control.' },
164
205
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
165
- { guidance: false, description: 'Use the detached status variant on bordered inputs; reserve it for checkboxes, switches, and sliders.' },
166
206
  { guidance: false, description: 'Hide the label without providing an alternative way for the user to understand the field purpose.' },
167
207
  ],
168
208
  },
@@ -171,16 +211,17 @@ export const docsZh = {
171
211
  /** @type {import('../docs-types').TranslationDoc} */
172
212
  export const docsDense = {
173
213
  description:
174
- 'Form field wrapper providing label, description + optional/required indicators.',
214
+ 'Low-level field shell for custom controls needing label/description/status.',
175
215
  usage: {
176
- description: 'Field wraps input controls with label, description, and validation. Use for accessible forms with consistent labeling and inline feedback.',
216
+ description: 'Field wraps custom/native/third-party controls lacking field UI. Use TextInput, Typeahead, Select, DateInput, or TextArea directly when they already expose label/description/status props.',
177
217
  bestPractices: [
178
- { guidance: true, description: 'Always provide a label for accessibility, even if visually hidden with isLabelHidden.' },
179
- { guidance: true, description: 'Use the status prop with clear messages to provide inline validation feedback.' },
180
- { guidance: true, description: 'Add a description when the label alone does not explain what the field expects: format hints or constraints.' },
218
+ { guidance: true, description: 'Wrap custom controls/widgets that need labeling, helper text, optional/required indicators, or validation status.' },
219
+ { guidance: true, description: 'Always provide a label; visually hide it only when context is clear.' },
220
+ { guidance: true, description: 'Wire inputID/descriptionID to htmlFor and aria-describedby on the inner control.' },
221
+ { guidance: false, description: 'Nest Field around styled inputs; it double-renders labels and status UI.' },
222
+ { guidance: false, description: 'Use attached status on sliders/switches/checkboxes; use detached so messages do not overlap.' },
181
223
  { guidance: false, description: 'Set both isOptional and isRequired on the same field.' },
182
- { guidance: false, description: 'Use the detached status variant on bordered inputs; reserve it for checkboxes, switches, and sliders.' },
183
- { guidance: false, description: 'Hide the label without providing an alternative way for user to understand the field purpose.' },
224
+ { guidance: false, description: 'Hide the label without another way to understand field purpose.' },
184
225
  ],
185
226
  },
186
227
  };
@@ -93,6 +93,38 @@ describe('Field', () => {
93
93
  expect(label).toBeVisible();
94
94
  });
95
95
 
96
+ it('renders a single-control label as a <label> with htmlFor', () => {
97
+ render(
98
+ <Field label="Email" inputID="email-input">
99
+ <input id="email-input" />
100
+ </Field>,
101
+ );
102
+ const labelEl = screen.getByText('Email');
103
+ expect(labelEl.tagName).toBe('LABEL');
104
+ expect(labelEl).toHaveAttribute('for', 'email-input');
105
+ });
106
+
107
+ it('renders a group label as a <span> (not <label>) with no htmlFor', () => {
108
+ render(
109
+ <Field
110
+ label="Plan"
111
+ inputID="plan-group"
112
+ labelID="plan-label"
113
+ isGroupLabel>
114
+ <div role="radiogroup" aria-labelledby="plan-label" />
115
+ </Field>,
116
+ );
117
+ const labelEl = screen.getByText('Plan');
118
+ // A group's accessible-name element must not be a literal <label>.
119
+ expect(labelEl.tagName).toBe('SPAN');
120
+ expect(labelEl.closest('label')).toBeNull();
121
+ expect(labelEl).not.toHaveAttribute('for');
122
+ // labelID is applied to the label element and referenced by the group.
123
+ expect(labelEl).toHaveAttribute('id', 'plan-label');
124
+ const group = screen.getByRole('radiogroup', {name: 'Plan'});
125
+ expect(group.getAttribute('aria-labelledby')).toBe(labelEl.id);
126
+ });
127
+
96
128
  it('forwards ref correctly', () => {
97
129
  const ref = vi.fn();
98
130
  render(
@@ -99,9 +99,26 @@ export interface FieldProps extends Omit<
99
99
  */
100
100
  description?: string;
101
101
  /**
102
- * ID for the input element (used for label's htmlFor attribute).
102
+ * ID of the input element this label points AT (used as the label's
103
+ * `htmlFor`). This is the id of the *control*, not of the label element —
104
+ * see `labelID` for the latter.
103
105
  */
104
106
  inputID: string;
107
+ /**
108
+ * The `id` applied TO the label element itself (distinct from `inputID`,
109
+ * which is the control the label points at). A grouping control
110
+ * (radiogroup, checkbox group) references this via `aria-labelledby` to take
111
+ * the label as its accessible name. Pair with `isGroupLabel`.
112
+ */
113
+ labelID?: string;
114
+ /**
115
+ * When the field wraps a group of controls rather than a single input, set
116
+ * this so the label renders as a non-`<label>` element (a `<span>`): a
117
+ * `<label>` semantically names one control and can't be associated with a
118
+ * group. Pair with `labelID` + `aria-labelledby` on the group.
119
+ * @default false
120
+ */
121
+ isGroupLabel?: boolean;
105
122
  /**
106
123
  * ID for the description element (use for aria-describedby on the input).
107
124
  */
@@ -172,6 +189,8 @@ export function Field({
172
189
  isLabelHidden = false,
173
190
  description,
174
191
  inputID,
192
+ labelID,
193
+ isGroupLabel = false,
175
194
  descriptionID,
176
195
  isOptional = false,
177
196
  isRequired = false,
@@ -206,6 +225,8 @@ export function Field({
206
225
  <FieldLabel
207
226
  label={label}
208
227
  inputID={inputID}
228
+ labelID={labelID}
229
+ isGroupLabel={isGroupLabel}
209
230
  isLabelHidden={isLabelHidden}
210
231
  isDisabled={isDisabled}
211
232
  isOptional={isOptional}
@@ -245,10 +266,7 @@ export function Field({
245
266
  <div {...stylex.props(styles.horizontalLabelAlign)}>{labelNode}</div>
246
267
  <div {...stylex.props(styles.inputStatusWrapper)}>
247
268
  {description && (
248
- <Text
249
- type="supporting"
250
- display="block"
251
- id={resolvedDescriptionID}>
269
+ <Text type="supporting" display="block" id={resolvedDescriptionID}>
252
270
  {description}
253
271
  </Text>
254
272
  )}
@@ -35,6 +35,7 @@ const styles = stylex.create({
35
35
  gap: spacingVars['--spacing-1'],
36
36
  fontFamily: typographyVars['--font-family-body'],
37
37
  fontSize: typeScaleVars['--text-label-size'],
38
+ lineHeight: typeScaleVars['--text-label-leading'],
38
39
  fontWeight: fontWeightVars['--font-weight-medium'],
39
40
  color: colorVars['--color-text-secondary'],
40
41
  cursor: 'pointer',
@@ -61,6 +62,7 @@ const styles = stylex.create({
61
62
  optionalRequired: {
62
63
  fontWeight: fontWeightVars['--font-weight-normal'],
63
64
  fontSize: typeScaleVars['--text-supporting-size'],
65
+ lineHeight: typeScaleVars['--text-supporting-leading'],
64
66
  color: colorVars['--color-text-secondary'],
65
67
  },
66
68
  description: {
@@ -80,9 +82,28 @@ export interface FieldLabelProps extends BaseProps<HTMLLabelElement> {
80
82
  */
81
83
  label: string;
82
84
  /**
83
- * ID of the input element this label is for.
85
+ * ID of the input element this label points AT (rendered as `htmlFor` on the
86
+ * label). This is *not* the id of the label element itself — see
87
+ * `labelID` for that.
84
88
  */
85
89
  inputID: string;
90
+ /**
91
+ * The `id` applied TO the label element itself (not the element it points
92
+ * at — that's `inputID`). A grouping control (e.g. `role="radiogroup"`) can
93
+ * reference this via `aria-labelledby` to take the label as its accessible
94
+ * name.
95
+ */
96
+ labelID?: string;
97
+ /**
98
+ * When true, the field wraps a *group* of controls (e.g. a radiogroup)
99
+ * rather than a single input. In that case the label is rendered as a
100
+ * `<span>` instead of a `<label>` — a `<label>` semantically names one form
101
+ * control and can't be associated with a group, so it must not be a literal
102
+ * label element. The group takes the label as its name via
103
+ * `labelID` + `aria-labelledby`.
104
+ * @default false
105
+ */
106
+ isGroupLabel?: boolean;
86
107
  /**
87
108
  * Whether to visually hide the label and description (still accessible
88
109
  * to screen readers). When hidden, the entire label group is rendered
@@ -140,6 +161,8 @@ export interface FieldLabelProps extends BaseProps<HTMLLabelElement> {
140
161
  export function FieldLabel({
141
162
  label,
142
163
  inputID,
164
+ labelID,
165
+ isGroupLabel = false,
143
166
  isLabelHidden = false,
144
167
  isDisabled = false,
145
168
  isOptional = false,
@@ -152,11 +175,38 @@ export function FieldLabel({
152
175
  }: FieldLabelProps) {
153
176
  const statusText = isOptional ? 'Optional' : isRequired ? 'Required' : null;
154
177
 
178
+ // A group label (e.g. for a radiogroup) must not be a literal `<label>`
179
+ // element: a `<label>` semantically names a single form control and can't be
180
+ // associated with a group. Render it as a `<span>` instead, keeping all the
181
+ // label styling and slots. The group references it via `aria-labelledby`.
182
+ const LabelElement = isGroupLabel ? 'span' : 'label';
183
+
184
+ const labelContent = (
185
+ <>
186
+ {labelIcon && renderIconSlot(labelIcon, {size: 'sm', color: 'inherit'})}
187
+ {label}
188
+ {statusText && (
189
+ <span {...stylex.props(styles.optionalRequired)}>
190
+ <span aria-hidden="true"> ∙ </span>
191
+ {statusText}
192
+ </span>
193
+ )}
194
+ {labelTooltip && (
195
+ <Tooltip content={labelTooltip} placement="above">
196
+ <Icon icon="info" size="sm" color="inherit" />
197
+ </Tooltip>
198
+ )}
199
+ </>
200
+ );
201
+
155
202
  return (
156
203
  <>
157
- <label
204
+ <LabelElement
158
205
  ref={ref}
159
- htmlFor={inputID}
206
+ id={labelID}
207
+ // `htmlFor` only applies to a real `<label>` associating with a single
208
+ // control; a group label (span) has no `htmlFor`.
209
+ htmlFor={isGroupLabel ? undefined : inputID}
160
210
  {...mergeProps(
161
211
  themeProps('field-label'),
162
212
  stylex.props(
@@ -165,20 +215,8 @@ export function FieldLabel({
165
215
  isLabelHidden && styles.srOnly,
166
216
  ),
167
217
  )}>
168
- {labelIcon && renderIconSlot(labelIcon, {size: 'sm', color: 'inherit'})}
169
- {label}
170
- {statusText && (
171
- <span {...stylex.props(styles.optionalRequired)}>
172
- <span aria-hidden="true"> ∙ </span>
173
- {statusText}
174
- </span>
175
- )}
176
- {labelTooltip && (
177
- <Tooltip content={labelTooltip} placement="above">
178
- <Icon icon="info" size="sm" color="inherit" />
179
- </Tooltip>
180
- )}
181
- </label>
218
+ {labelContent}
219
+ </LabelElement>
182
220
  {description && (
183
221
  <span
184
222
  id={descriptionID}