@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
@@ -200,6 +200,56 @@ describe('TopNavHeading', () => {
200
200
  expect(screen.getByTestId('logo')).toBeInTheDocument();
201
201
  });
202
202
 
203
+ describe('logo link accessible name', () => {
204
+ const logo = <img src="/logo.png" alt="" />;
205
+
206
+ // A logo image is decorative; when the logo is wrapped in a link the link
207
+ // itself needs an accessible name, otherwise axe reports link-name.
208
+ it('names the logo link in the independent-links config', () => {
209
+ render(
210
+ <TopNavHeading
211
+ logo={logo}
212
+ superheading="Suite"
213
+ superheadingHref="/suite"
214
+ heading="Product"
215
+ headingHref="/product"
216
+ />,
217
+ );
218
+ // No link should have an empty accessible name.
219
+ for (const link of screen.getAllByRole('link')) {
220
+ expect(link).toHaveAccessibleName();
221
+ }
222
+ // The logo link is named from the heading.
223
+ expect(
224
+ screen.getAllByRole('link', {name: 'Product'}).length,
225
+ ).toBeGreaterThanOrEqual(1);
226
+ });
227
+
228
+ it('names the logo link in the menu + hrefs config', () => {
229
+ render(
230
+ <TopNavHeading
231
+ logo={logo}
232
+ superheading="Suite"
233
+ superheadingHref="/suite"
234
+ heading="Product"
235
+ headingHref="/product"
236
+ menu={<a href="#menu">Menu item</a>}
237
+ />,
238
+ );
239
+ for (const link of screen.getAllByRole('link')) {
240
+ expect(link).toHaveAccessibleName();
241
+ }
242
+ });
243
+
244
+ it('names a logo-only link via logoLabel', () => {
245
+ render(<TopNavHeading logo={logo} headingHref="/home" logoLabel="Home" />);
246
+ expect(screen.getByRole('link', {name: 'Home'})).toHaveAttribute(
247
+ 'href',
248
+ '/home',
249
+ );
250
+ });
251
+ });
252
+
203
253
  it('renders as anchor when href is provided', () => {
204
254
  render(<TopNavHeading heading="Home" href="/" />);
205
255
  const link = screen.getByRole('link');
@@ -214,6 +214,12 @@ export interface TopNavHeadingProps extends BaseProps<HTMLElement> {
214
214
  * Can be an image, NavIcon, or any ReactNode.
215
215
  */
216
216
  logo?: ReactNode;
217
+ /**
218
+ * Accessible name for the logo when it links somewhere (`headingHref`) and
219
+ * has no adjacent text to name it (for example a logo-only heading). Defaults
220
+ * to `heading` when available. Ignored when the logo is not a link.
221
+ */
222
+ logoLabel?: string;
217
223
  /**
218
224
  * Product/app name.
219
225
  */
@@ -295,6 +301,7 @@ export interface TopNavHeadingProps extends BaseProps<HTMLElement> {
295
301
  export function TopNavHeading({
296
302
  as,
297
303
  logo,
304
+ logoLabel,
298
305
  heading,
299
306
  headingHref: headingHrefProp,
300
307
  href,
@@ -314,6 +321,10 @@ export function TopNavHeading({
314
321
  const LinkComponent = useLinkComponent(as);
315
322
  // Support both headingHref and legacy href
316
323
  const headingHref = headingHrefProp ?? href;
324
+ // When the logo is wrapped in a link it needs its own accessible name (the
325
+ // logo image itself is decorative). Prefer an explicit logoLabel, fall back
326
+ // to the heading text. axe: link-name.
327
+ const logoLinkLabel = logoLabel ?? heading;
317
328
 
318
329
  const rootRef = useRef<HTMLElement>(null);
319
330
 
@@ -425,6 +436,7 @@ export function TopNavHeading({
425
436
  <Element
426
437
  ref={ref as React.Ref<HTMLAnchorElement & HTMLDivElement>}
427
438
  href={headingHref}
439
+ aria-label={headingHref ? logoLabel : undefined}
428
440
  data-testid={testId}
429
441
  {...mergeProps(
430
442
  themeProps('top-nav-heading'),
@@ -533,6 +545,7 @@ export function TopNavHeading({
533
545
  (headingHref ? (
534
546
  <LinkComponent
535
547
  href={headingHref}
548
+ aria-label={logoLinkLabel}
536
549
  onClick={(e: React.MouseEvent) => e.stopPropagation()}
537
550
  {...stylex.props(styles.logo)}>
538
551
  {logo}
@@ -593,7 +606,10 @@ export function TopNavHeading({
593
606
  {...props}>
594
607
  {logo &&
595
608
  (headingHref ? (
596
- <LinkComponent href={headingHref} {...stylex.props(styles.logo)}>
609
+ <LinkComponent
610
+ href={headingHref}
611
+ aria-label={logoLinkLabel}
612
+ {...stylex.props(styles.logo)}>
597
613
  {logo}
598
614
  </LinkComponent>
599
615
  ) : (
@@ -61,6 +61,44 @@ const deepItems: TreeListItemData[] = [
61
61
  },
62
62
  ];
63
63
 
64
+ // APG keyboard fixtures (module-level to satisfy no-unstable-default-props).
65
+ const flatItems: TreeListItemData[] = [
66
+ {id: 'a', label: 'Apple'},
67
+ {id: 'b', label: 'Banana'},
68
+ {id: 'c', label: 'Cherry'},
69
+ ];
70
+
71
+ const withDisabledItems: TreeListItemData[] = [
72
+ {id: 'a', label: 'Apple'},
73
+ {id: 'b', label: 'Banana', isDisabled: true},
74
+ {id: 'c', label: 'Cherry'},
75
+ ];
76
+
77
+ const collapsedParentItems: TreeListItemData[] = [
78
+ {
79
+ id: 'parent',
80
+ label: 'Parent',
81
+ children: [
82
+ {id: 'child-1', label: 'Child 1'},
83
+ {id: 'child-2', label: 'Child 2'},
84
+ ],
85
+ },
86
+ {id: 'sibling', label: 'Sibling'},
87
+ ];
88
+
89
+ const expandedParentItems: TreeListItemData[] = [
90
+ {
91
+ id: 'parent',
92
+ label: 'Parent',
93
+ isExpanded: true,
94
+ children: [
95
+ {id: 'child-1', label: 'Child 1'},
96
+ {id: 'child-2', label: 'Child 2'},
97
+ ],
98
+ },
99
+ {id: 'sibling', label: 'Sibling'},
100
+ ];
101
+
64
102
  describe('TreeList', () => {
65
103
  // ===========================================================================
66
104
  // Basic rendering
@@ -160,6 +198,26 @@ describe('TreeList', () => {
160
198
  expect(item).not.toHaveAttribute('aria-expanded');
161
199
  });
162
200
 
201
+ it('renders a keyboard-focusable toggle button for parents without onClick/href', () => {
202
+ render(<TreeList items={nestedItems} />);
203
+ const toggle = screen.getByRole('button', {name: 'Toggle children'});
204
+ expect(toggle).toBeInTheDocument();
205
+ expect(toggle).toHaveAttribute('aria-expanded', 'false');
206
+ });
207
+
208
+ it('expands a parent from the keyboard via the toggle button', async () => {
209
+ const user = userEvent.setup();
210
+ render(<TreeList items={nestedItems} />);
211
+ // Collapsed: children are not rendered.
212
+ expect(screen.queryByText('Child 1')).not.toBeInTheDocument();
213
+ const toggle = screen.getByRole('button', {name: 'Toggle children'});
214
+ toggle.focus();
215
+ expect(toggle).toHaveFocus();
216
+ await user.keyboard('{Enter}');
217
+ expect(screen.getByText('Child 1')).toBeInTheDocument();
218
+ expect(toggle).toHaveAttribute('aria-expanded', 'true');
219
+ });
220
+
163
221
  it('renders group role for nested children', () => {
164
222
  render(<TreeList items={nestedItemsExpanded} />);
165
223
  const groups = document.querySelectorAll('[role="group"]');
@@ -210,18 +268,14 @@ describe('TreeList', () => {
210
268
  it('fires onClick when invisible button is clicked', async () => {
211
269
  const user = userEvent.setup();
212
270
  const onClick = vi.fn();
213
- const items: TreeListItemData[] = [
214
- {id: 'a', label: 'Clickable', onClick},
215
- ];
271
+ const items: TreeListItemData[] = [{id: 'a', label: 'Clickable', onClick}];
216
272
  render(<TreeList items={items} />);
217
273
  await user.click(screen.getByRole('button'));
218
274
  expect(onClick).toHaveBeenCalledTimes(1);
219
275
  });
220
276
 
221
277
  it('renders an invisible anchor when href is provided', () => {
222
- const items: TreeListItemData[] = [
223
- {id: 'a', label: 'Link', href: '/docs'},
224
- ];
278
+ const items: TreeListItemData[] = [{id: 'a', label: 'Link', href: '/docs'}];
225
279
  const {container} = render(<TreeList items={items} />);
226
280
  const anchor = container.querySelector('a');
227
281
  expect(anchor).toBeInTheDocument();
@@ -317,4 +371,239 @@ describe('TreeList', () => {
317
371
  const root = screen.getByTestId('tree');
318
372
  expect(root.className).toContain('astryx-tree-list');
319
373
  });
374
+
375
+ // ===========================================================================
376
+ // APG structural ARIA (aria-level / aria-posinset / aria-setsize)
377
+ // ===========================================================================
378
+
379
+ it('sets aria-level, aria-posinset, and aria-setsize at the top level', () => {
380
+ render(<TreeList items={flatItems} />);
381
+ const apple = screen.getByText('Apple').closest('li');
382
+ expect(apple).toHaveAttribute('aria-level', '1');
383
+ expect(apple).toHaveAttribute('aria-posinset', '1');
384
+ expect(apple).toHaveAttribute('aria-setsize', '3');
385
+
386
+ const cherry = screen.getByText('Cherry').closest('li');
387
+ expect(cherry).toHaveAttribute('aria-posinset', '3');
388
+ expect(cherry).toHaveAttribute('aria-setsize', '3');
389
+ });
390
+
391
+ it('sets aria-level/posinset/setsize correctly at deeper levels', () => {
392
+ render(<TreeList items={expandedParentItems} />);
393
+ const parent = screen.getByText('Parent').closest('li');
394
+ expect(parent).toHaveAttribute('aria-level', '1');
395
+ expect(parent).toHaveAttribute('aria-setsize', '2');
396
+
397
+ const child1 = screen.getByText('Child 1').closest('li');
398
+ expect(child1).toHaveAttribute('aria-level', '2');
399
+ expect(child1).toHaveAttribute('aria-posinset', '1');
400
+ expect(child1).toHaveAttribute('aria-setsize', '2');
401
+
402
+ const child2 = screen.getByText('Child 2').closest('li');
403
+ expect(child2).toHaveAttribute('aria-level', '2');
404
+ expect(child2).toHaveAttribute('aria-posinset', '2');
405
+ });
406
+
407
+ it('sets aria-level across three depths', () => {
408
+ render(<TreeList items={deepItems} />);
409
+ expect(screen.getByText('Root').closest('li')).toHaveAttribute(
410
+ 'aria-level',
411
+ '1',
412
+ );
413
+ expect(screen.getByText('Mid').closest('li')).toHaveAttribute(
414
+ 'aria-level',
415
+ '2',
416
+ );
417
+ expect(screen.getByText('Leaf').closest('li')).toHaveAttribute(
418
+ 'aria-level',
419
+ '3',
420
+ );
421
+ });
422
+
423
+ // ===========================================================================
424
+ // Roving tabindex
425
+ // ===========================================================================
426
+
427
+ it('makes exactly one treeitem tabbable by default (the first enabled)', () => {
428
+ render(<TreeList items={flatItems} />);
429
+ const treeitems = screen.getAllByRole('treeitem');
430
+ const tabbable = treeitems.filter(
431
+ el => el.getAttribute('tabindex') === '0',
432
+ );
433
+ expect(tabbable).toHaveLength(1);
434
+ expect(tabbable[0]).toBe(screen.getByText('Apple').closest('li'));
435
+ expect(screen.getByText('Banana').closest('li')).toHaveAttribute(
436
+ 'tabindex',
437
+ '-1',
438
+ );
439
+ });
440
+
441
+ it('defaults the tab stop to the selected item when one is selected', () => {
442
+ const items: TreeListItemData[] = [
443
+ {id: 'a', label: 'Apple'},
444
+ {id: 'b', label: 'Banana', isSelected: true},
445
+ {id: 'c', label: 'Cherry'},
446
+ ];
447
+ render(<TreeList items={items} />);
448
+ expect(screen.getByText('Banana').closest('li')).toHaveAttribute(
449
+ 'tabindex',
450
+ '0',
451
+ );
452
+ expect(screen.getByText('Apple').closest('li')).toHaveAttribute(
453
+ 'tabindex',
454
+ '-1',
455
+ );
456
+ });
457
+
458
+ it('moves the single tab stop when focus moves via keyboard', async () => {
459
+ const user = userEvent.setup();
460
+ render(<TreeList items={flatItems} />);
461
+ const apple = screen.getByText('Apple').closest('li')!;
462
+ apple.focus();
463
+ await user.keyboard('{ArrowDown}');
464
+ const treeitems = screen.getAllByRole('treeitem');
465
+ const tabbable = treeitems.filter(
466
+ el => el.getAttribute('tabindex') === '0',
467
+ );
468
+ expect(tabbable).toHaveLength(1);
469
+ expect(tabbable[0]).toBe(screen.getByText('Banana').closest('li'));
470
+ });
471
+
472
+ // ===========================================================================
473
+ // APG keyboard navigation
474
+ // ===========================================================================
475
+
476
+ it('ArrowDown / ArrowUp move focus between visible treeitems', async () => {
477
+ const user = userEvent.setup();
478
+ render(<TreeList items={flatItems} />);
479
+ const apple = screen.getByText('Apple').closest('li')!;
480
+ apple.focus();
481
+ await user.keyboard('{ArrowDown}');
482
+ expect(document.activeElement).toBe(
483
+ screen.getByText('Banana').closest('li'),
484
+ );
485
+ await user.keyboard('{ArrowDown}');
486
+ expect(document.activeElement).toBe(
487
+ screen.getByText('Cherry').closest('li'),
488
+ );
489
+ await user.keyboard('{ArrowUp}');
490
+ expect(document.activeElement).toBe(
491
+ screen.getByText('Banana').closest('li'),
492
+ );
493
+ });
494
+
495
+ it('ArrowDown / ArrowUp skip disabled treeitems', async () => {
496
+ const user = userEvent.setup();
497
+ render(<TreeList items={withDisabledItems} />);
498
+ const apple = screen.getByText('Apple').closest('li')!;
499
+ apple.focus();
500
+ await user.keyboard('{ArrowDown}');
501
+ // Banana is disabled → skipped, lands on Cherry.
502
+ expect(document.activeElement).toBe(
503
+ screen.getByText('Cherry').closest('li'),
504
+ );
505
+ await user.keyboard('{ArrowUp}');
506
+ expect(document.activeElement).toBe(
507
+ screen.getByText('Apple').closest('li'),
508
+ );
509
+ });
510
+
511
+ it('ArrowRight expands a collapsed parent, then enters the first child', async () => {
512
+ const user = userEvent.setup();
513
+ render(<TreeList items={collapsedParentItems} />);
514
+ const parent = screen.getByText('Parent').closest('li')!;
515
+ parent.focus();
516
+ expect(screen.queryByText('Child 1')).not.toBeInTheDocument();
517
+ await user.keyboard('{ArrowRight}');
518
+ // First ArrowRight expands.
519
+ expect(screen.getByText('Child 1')).toBeInTheDocument();
520
+ expect(document.activeElement).toBe(parent);
521
+ await user.keyboard('{ArrowRight}');
522
+ // Second ArrowRight moves into first child.
523
+ expect(document.activeElement).toBe(
524
+ screen.getByText('Child 1').closest('li'),
525
+ );
526
+ });
527
+
528
+ it('ArrowRight on a leaf is a no-op', async () => {
529
+ const user = userEvent.setup();
530
+ render(<TreeList items={flatItems} />);
531
+ const apple = screen.getByText('Apple').closest('li')!;
532
+ apple.focus();
533
+ await user.keyboard('{ArrowRight}');
534
+ expect(document.activeElement).toBe(apple);
535
+ });
536
+
537
+ it('ArrowLeft collapses an expanded parent, then moves to parent', async () => {
538
+ const user = userEvent.setup();
539
+ render(<TreeList items={expandedParentItems} />);
540
+ const parent = screen.getByText('Parent').closest('li')!;
541
+ // Focus a child first.
542
+ const child1 = screen.getByText('Child 1').closest('li')!;
543
+ child1.focus();
544
+ await user.keyboard('{ArrowLeft}');
545
+ // Child is a leaf → ArrowLeft moves to parent.
546
+ expect(document.activeElement).toBe(parent);
547
+ await user.keyboard('{ArrowLeft}');
548
+ // Parent is expanded → ArrowLeft collapses it.
549
+ expect(screen.queryByText('Child 1')).not.toBeInTheDocument();
550
+ });
551
+
552
+ it('Home and End move to the first and last visible treeitems', async () => {
553
+ const user = userEvent.setup();
554
+ render(<TreeList items={flatItems} />);
555
+ const banana = screen.getByText('Banana').closest('li')!;
556
+ banana.focus();
557
+ await user.keyboard('{End}');
558
+ expect(document.activeElement).toBe(
559
+ screen.getByText('Cherry').closest('li'),
560
+ );
561
+ await user.keyboard('{Home}');
562
+ expect(document.activeElement).toBe(
563
+ screen.getByText('Apple').closest('li'),
564
+ );
565
+ });
566
+
567
+ it('Enter activates the item onClick', async () => {
568
+ const user = userEvent.setup();
569
+ const onClick = vi.fn();
570
+ const items: TreeListItemData[] = [{id: 'a', label: 'Apple', onClick}];
571
+ render(<TreeList items={items} />);
572
+ const apple = screen.getByText('Apple').closest('li')!;
573
+ apple.focus();
574
+ await user.keyboard('{Enter}');
575
+ expect(onClick).toHaveBeenCalledTimes(1);
576
+ });
577
+
578
+ it('Space activates the item onClick', async () => {
579
+ const user = userEvent.setup();
580
+ const onClick = vi.fn();
581
+ const items: TreeListItemData[] = [{id: 'a', label: 'Apple', onClick}];
582
+ render(<TreeList items={items} />);
583
+ const apple = screen.getByText('Apple').closest('li')!;
584
+ apple.focus();
585
+ await user.keyboard(' ');
586
+ expect(onClick).toHaveBeenCalledTimes(1);
587
+ });
588
+
589
+ it('Enter toggles expansion for a parent without its own action', async () => {
590
+ const user = userEvent.setup();
591
+ render(<TreeList items={collapsedParentItems} />);
592
+ const parent = screen.getByText('Parent').closest('li')!;
593
+ parent.focus();
594
+ expect(screen.queryByText('Child 1')).not.toBeInTheDocument();
595
+ await user.keyboard('{Enter}');
596
+ expect(screen.getByText('Child 1')).toBeInTheDocument();
597
+ });
598
+
599
+ it('typeahead moves focus to the next item matching typed characters', async () => {
600
+ const user = userEvent.setup();
601
+ render(<TreeList items={flatItems} />);
602
+ const apple = screen.getByText('Apple').closest('li')!;
603
+ apple.focus();
604
+ await user.keyboard('c');
605
+ expect(document.activeElement).toBe(
606
+ screen.getByText('Cherry').closest('li'),
607
+ );
608
+ });
320
609
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file TreeList.tsx
7
- * @input Uses React, StyleX, theme tokens, TreeListItem, TreeListTypes
7
+ * @input Uses React, StyleX, theme tokens, TreeListItem, TreeListTypes, useTreeFocus
8
8
  * @output Exports TreeList component, TreeListProps type
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
@@ -23,6 +23,7 @@ import type {BaseProps} from '../BaseProps';
23
23
  import {TreeListItem} from './TreeListItem';
24
24
  import type {TreeListItemData, TreeListDensity} from './TreeListTypes';
25
25
  import {themeProps} from '../utils/themeProps';
26
+ import {useTreeFocus} from '../hooks/useTreeFocus';
26
27
 
27
28
  // =============================================================================
28
29
  // Types
@@ -97,6 +98,35 @@ function collectExpandedKeys(items: TreeListItemData[]): string[] {
97
98
  return keys;
98
99
  }
99
100
 
101
+ /**
102
+ * Compute the initial roving-tabindex seed: the first selected enabled item in
103
+ * document order, else the first enabled item, else the first item. The hook
104
+ * (useTreeFocus with hasRovingTabIndex) takes ownership after mount — it
105
+ * preserves this seeded `tabindex="0"` on its repair pass and moves the stop
106
+ * with keyboard navigation.
107
+ */
108
+ function findInitialTabbableId(items: TreeListItemData[]): string | undefined {
109
+ let firstEnabled: string | undefined;
110
+ const walk = (list: TreeListItemData[]): string | undefined => {
111
+ for (const item of list) {
112
+ if (item.isSelected && item.isDisabled !== true) {
113
+ return item.id;
114
+ }
115
+ if (firstEnabled == null && item.isDisabled !== true) {
116
+ firstEnabled = item.id;
117
+ }
118
+ if (item.children != null && item.children.length > 0) {
119
+ const selected = walk(item.children);
120
+ if (selected != null) {
121
+ return selected;
122
+ }
123
+ }
124
+ }
125
+ return undefined;
126
+ };
127
+ return walk(items) ?? firstEnabled ?? items[0]?.id;
128
+ }
129
+
100
130
  // =============================================================================
101
131
  // Component
102
132
  // =============================================================================
@@ -160,6 +190,42 @@ export function TreeList({
160
190
  [expandedKeysFromProps],
161
191
  );
162
192
 
193
+ // ---------------------------------------------------------------------------
194
+ // Roving tabindex + APG tree keyboard model (via useTreeFocus)
195
+ // ---------------------------------------------------------------------------
196
+
197
+ // The hook (hasRovingTabIndex) owns the tree's single tab stop: it repairs
198
+ // the stop on mount and moves it with keyboard navigation. We only seed the
199
+ // initially-tabbable treeitem in the render (selected item or first enabled);
200
+ // the hook's repair pass preserves that seeded `tabindex="0"`.
201
+ const initialTabbableId = useMemo(
202
+ () => findInitialTabbableId(items),
203
+ [items],
204
+ );
205
+
206
+ // Enter/Space activation: prefer the treeitem's own inner action (link or
207
+ // button); return true when handled so the hook does not also toggle. Scoped
208
+ // to this treeitem's own row — never a descendant treeitem's action inside an
209
+ // expanded group.
210
+ const activateItem = useCallback((current: HTMLElement): boolean => {
211
+ const candidates = current.querySelectorAll<HTMLElement>(
212
+ 'a[href], button:not([aria-label="Toggle children"])',
213
+ );
214
+ for (const candidate of candidates) {
215
+ if (candidate.closest('[role="treeitem"]') === current) {
216
+ candidate.click();
217
+ return true;
218
+ }
219
+ }
220
+ return false;
221
+ }, []);
222
+
223
+ const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
224
+ onToggleExpand: handleToggle,
225
+ onActivate: activateItem,
226
+ hasRovingTabIndex: true,
227
+ });
228
+
163
229
  function renderItems(
164
230
  items: TreeListItemData[],
165
231
  nestedLevel: number,
@@ -206,6 +272,9 @@ export function TreeList({
206
272
  onToggle={handleToggle}
207
273
  density={density}
208
274
  renderedChildren={renderedChildren}
275
+ posInSet={index + 1}
276
+ setSize={items.length}
277
+ isTabbable={item.id === initialTabbableId}
209
278
  />
210
279
  );
211
280
  });
@@ -227,8 +296,11 @@ export function TreeList({
227
296
  </div>
228
297
  )}
229
298
  <ul
299
+ ref={treeRef}
230
300
  role="tree"
231
301
  aria-labelledby={header != null ? headerId : undefined}
302
+ onKeyDown={handleKeyDown}
303
+ onFocus={handleFocus}
232
304
  {...stylex.props(styles.list)}>
233
305
  {renderItems(items, 0, [])}
234
306
  </ul>