@astryxdesign/core 0.1.2 → 0.1.3

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
@@ -0,0 +1,559 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useTreeFocus.ts
7
+ * @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
8
+ * @output Exports useTreeFocus hook for WAI-ARIA tree keyboard navigation
9
+ * @position Core hook; used by TreeList for roving tabindex + APG tree keyboard model
10
+ *
11
+ * SYNC: When modified, update:
12
+ * - /packages/core/src/hooks/index.ts
13
+ * - /packages/core/src/hooks/useTreeFocus.doc.mjs
14
+ * - /packages/core/src/hooks/useTreeFocus.test.tsx
15
+ */
16
+
17
+ import {useCallback, useRef} from 'react';
18
+ import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
19
+
20
+ /** Keys handled by the tree keyboard model (used to gate typeahead). */
21
+ const NAVIGATION_KEYS = new Set([
22
+ 'ArrowDown',
23
+ 'ArrowUp',
24
+ 'ArrowRight',
25
+ 'ArrowLeft',
26
+ 'Home',
27
+ 'End',
28
+ 'Enter',
29
+ ' ',
30
+ ]);
31
+
32
+ /** Default reset delay for the typeahead buffer. */
33
+ const DEFAULT_TYPEAHEAD_RESET_MS = 500;
34
+
35
+ /**
36
+ * Configuration for tree focus behavior.
37
+ *
38
+ * The tree keyboard model differs from a linear list (useListFocus): while
39
+ * ArrowUp/ArrowDown/Home/End roam linearly over the *visible* treeitems,
40
+ * ArrowRight/ArrowLeft carry tree semantics (expand/collapse, move to
41
+ * first-child / parent). The hook stays generic by taking callbacks for the
42
+ * tree-specific bits (expansion toggling, activation) — mirroring how
43
+ * useGridFocus takes `isCellFocusable` rather than hardcoding disabled logic.
44
+ */
45
+ export interface UseTreeFocusOptions {
46
+ /**
47
+ * Selector for treeitems within the tree. Matches ALL visible treeitems in
48
+ * DOM order (collapsed subtrees are not rendered, so they are naturally
49
+ * excluded). Disabled treeitems are still matched and then skipped via
50
+ * {@link UseTreeFocusOptions.isItemDisabled}.
51
+ * @default '[role="treeitem"]'
52
+ */
53
+ itemSelector?: string;
54
+
55
+ /**
56
+ * Predicate determining whether a treeitem matched by `itemSelector` is
57
+ * disabled and must be skipped during arrow/Home/End navigation. A `.focus()`
58
+ * on a disabled item silently no-ops, so navigation would otherwise stall.
59
+ *
60
+ * @default reads `data-tree-disabled` (present ⇒ disabled)
61
+ */
62
+ isItemDisabled?: (item: HTMLElement) => boolean;
63
+
64
+ /**
65
+ * Reads the nesting level (aria-level style, 1-based) of a treeitem. Used to
66
+ * resolve first-child (ArrowRight) and parent (ArrowLeft) targets from the
67
+ * flat visible-item list.
68
+ *
69
+ * @default reads the `aria-level` attribute (falling back to `1`)
70
+ */
71
+ getLevel?: (item: HTMLElement) => number;
72
+
73
+ /**
74
+ * Whether a treeitem is an expanded parent. Read DOM-side (aria-expanded)
75
+ * by default so it reflects the rendered tree without prop plumbing.
76
+ *
77
+ * @default `aria-expanded === 'true'`
78
+ */
79
+ isExpanded?: (item: HTMLElement) => boolean;
80
+
81
+ /**
82
+ * Whether a treeitem is a collapsed parent (has children but is closed).
83
+ *
84
+ * @default `aria-expanded === 'false'`
85
+ */
86
+ isCollapsed?: (item: HTMLElement) => boolean;
87
+
88
+ /**
89
+ * Resolve the stable id for a treeitem, passed to `onToggleExpand` /
90
+ * `onActivate`. Returns `undefined` when the element carries no id.
91
+ *
92
+ * @default reads the `data-tree-id` attribute
93
+ */
94
+ getItemId?: (item: HTMLElement) => string | undefined;
95
+
96
+ /**
97
+ * Called to expand/collapse the treeitem with the given id (ArrowRight on a
98
+ * collapsed parent, ArrowLeft on an expanded parent, and Enter/Space on a
99
+ * parent that has no inner action).
100
+ */
101
+ onToggleExpand?: (id: string) => void;
102
+
103
+ /**
104
+ * Called when Enter/Space activates a treeitem. Return `true` if the
105
+ * activation was handled (e.g. an inner link/button was clicked); return
106
+ * `false`/`undefined` to let the hook fall back to toggling expansion for a
107
+ * parent. When omitted, the hook falls straight through to expansion
108
+ * toggling for parents.
109
+ *
110
+ * @param item The focused treeitem element.
111
+ * @param id The treeitem's id (per `getItemId`), if any.
112
+ */
113
+ onActivate?: (
114
+ item: HTMLElement,
115
+ id: string | undefined,
116
+ ) => boolean | undefined;
117
+
118
+ /**
119
+ * Whether typeahead (jump to next item whose text starts with the typed
120
+ * characters) is enabled.
121
+ * @default true
122
+ */
123
+ typeahead?: boolean;
124
+
125
+ /** Reset delay for the typeahead buffer, in ms. @default 500 */
126
+ typeaheadResetMs?: number;
127
+
128
+ /**
129
+ * Notified whenever the hook moves focus to a treeitem, with its id (if any).
130
+ * TreeList uses this to move its single roving tab stop.
131
+ */
132
+ onActiveChange?: (id: string | undefined) => void;
133
+
134
+ /**
135
+ * Roving-tabindex ownership. When true, the hook manages a single tab stop
136
+ * across the visible treeitems: exactly one enabled treeitem carries
137
+ * `tabindex="0"` and the rest `tabindex="-1"`. The tab stop is repaired on
138
+ * mount and whenever items mount/unmount or toggle disabled, and moves with
139
+ * keyboard navigation. Attach the returned {@link UseTreeFocusReturn.handleFocus}
140
+ * to the container's `onFocus` to keep the stop in sync after clicks or
141
+ * programmatic focus.
142
+ *
143
+ * On mount the hook preserves an existing `tabindex="0"` treeitem (so a
144
+ * consumer can seed the active item in its render); if none exists it
145
+ * promotes the first enabled treeitem.
146
+ *
147
+ * When false (the default), the hook only *moves* focus (`.focus()`) and
148
+ * never touches `tabindex` — the caller owns tab-stop management.
149
+ * @default false
150
+ */
151
+ hasRovingTabIndex?: boolean;
152
+ }
153
+
154
+ /**
155
+ * Return type for useTreeFocus hook.
156
+ */
157
+ export interface UseTreeFocusReturn<T extends HTMLElement = HTMLElement> {
158
+ /** Ref to attach to the tree container element (role="tree"). */
159
+ treeRef: React.RefObject<T | null>;
160
+
161
+ /** Key down handler to attach to the tree container. */
162
+ handleKeyDown: (e: React.KeyboardEvent) => void;
163
+
164
+ /**
165
+ * Focus handler to attach to the container's `onFocus`. Keeps the roving tab
166
+ * stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it
167
+ * is always safe to attach.
168
+ */
169
+ handleFocus: (e: React.FocusEvent) => void;
170
+
171
+ /** Focus the first enabled visible treeitem. */
172
+ focusFirst: () => void;
173
+
174
+ /** Focus the last enabled visible treeitem. */
175
+ focusLast: () => void;
176
+ }
177
+
178
+ /**
179
+ * Hook for managing roving-tabindex focus + the WAI-ARIA tree keyboard model.
180
+ *
181
+ * A tree is not a linear list: ArrowUp/ArrowDown/Home/End roam linearly over
182
+ * the *visible* treeitems (skipping disabled ones), but ArrowRight/ArrowLeft
183
+ * carry tree semantics. This is why a tree needs its own hook rather than
184
+ * reusing `useListFocus` — the same reason 2D grids get `useGridFocus`.
185
+ *
186
+ * - ArrowDown / ArrowUp: move to next/previous visible treeitem (skip disabled)
187
+ * - ArrowRight: collapsed parent → expand; expanded parent → first child;
188
+ * leaf → no-op
189
+ * - ArrowLeft: expanded parent → collapse; otherwise → move to parent treeitem
190
+ * - Home / End: first / last visible treeitem
191
+ * - Enter / Space: activate (`onActivate`), falling back to expansion toggle
192
+ * - Printable characters: typeahead to the next matching treeitem
193
+ *
194
+ * The hook is DOM-query based (reads aria-expanded/aria-level and data-*
195
+ * attributes) and stays generic via option callbacks for the tree-specific
196
+ * bits (`onToggleExpand`, `onActivate`, `getLevel`, `isItemDisabled`, …).
197
+ *
198
+ * @example
199
+ * ```
200
+ * const {treeRef, handleKeyDown, handleFocus} = useTreeFocus<HTMLUListElement>({
201
+ * onToggleExpand: id => toggle(id),
202
+ * hasRovingTabIndex: true,
203
+ * });
204
+ *
205
+ * <ul ref={treeRef} role="tree" onKeyDown={handleKeyDown} onFocus={handleFocus}>
206
+ * {items.map(item => <li role="treeitem" tabIndex={-1}>{item.label}</li>)}
207
+ * </ul>
208
+ * ```
209
+ */
210
+ export function useTreeFocus<T extends HTMLElement = HTMLElement>(
211
+ options: UseTreeFocusOptions = {},
212
+ ): UseTreeFocusReturn<T> {
213
+ const {
214
+ itemSelector = '[role="treeitem"]',
215
+ isItemDisabled,
216
+ getLevel,
217
+ isExpanded,
218
+ isCollapsed,
219
+ getItemId,
220
+ onToggleExpand,
221
+ onActivate,
222
+ typeahead = true,
223
+ typeaheadResetMs = DEFAULT_TYPEAHEAD_RESET_MS,
224
+ onActiveChange,
225
+ hasRovingTabIndex = false,
226
+ } = options;
227
+
228
+ const treeRef = useRef<T>(null);
229
+
230
+ const typeaheadRef = useRef<{buffer: string; timer: number | null}>({
231
+ buffer: '',
232
+ timer: null,
233
+ });
234
+
235
+ /** Visible treeitems in DOM order (collapsed subtrees are not rendered). */
236
+ const getItems = useCallback((): HTMLElement[] => {
237
+ const root = treeRef.current;
238
+ if (root == null) {
239
+ return [];
240
+ }
241
+ return Array.from(root.querySelectorAll<HTMLElement>(itemSelector));
242
+ }, [itemSelector]);
243
+
244
+ const itemDisabled = useCallback(
245
+ (el: HTMLElement): boolean =>
246
+ isItemDisabled
247
+ ? isItemDisabled(el)
248
+ : el.dataset.treeDisabled != null ||
249
+ el.getAttribute('aria-disabled') === 'true',
250
+ [isItemDisabled],
251
+ );
252
+
253
+ const levelOf = useCallback(
254
+ (el: HTMLElement): number =>
255
+ getLevel ? getLevel(el) : Number(el.getAttribute('aria-level') ?? '1'),
256
+ [getLevel],
257
+ );
258
+
259
+ const expandedOf = useCallback(
260
+ (el: HTMLElement): boolean =>
261
+ isExpanded ? isExpanded(el) : el.getAttribute('aria-expanded') === 'true',
262
+ [isExpanded],
263
+ );
264
+
265
+ const collapsedOf = useCallback(
266
+ (el: HTMLElement): boolean =>
267
+ isCollapsed
268
+ ? isCollapsed(el)
269
+ : el.getAttribute('aria-expanded') === 'false',
270
+ [isCollapsed],
271
+ );
272
+
273
+ const idOf = useCallback(
274
+ (el: HTMLElement): string | undefined =>
275
+ getItemId ? getItemId(el) : el.dataset.treeId,
276
+ [getItemId],
277
+ );
278
+
279
+ // --- Roving tabindex ownership (opt-in via `hasRovingTabIndex`) -------------
280
+
281
+ /**
282
+ * Set `tabindex` on a treeitem, but only when it differs (avoids redundant
283
+ * DOM writes).
284
+ */
285
+ const setTabIndex = useCallback((el: HTMLElement, value: 0 | -1) => {
286
+ if (el.getAttribute('tabindex') !== String(value)) {
287
+ el.setAttribute('tabindex', String(value));
288
+ }
289
+ }, []);
290
+
291
+ /**
292
+ * Make `target` the sole tabbable treeitem: 0 on it, -1 on every other
293
+ * visible treeitem.
294
+ */
295
+ const moveTabStop = useCallback(
296
+ (items: HTMLElement[], target: HTMLElement) => {
297
+ for (const el of items) {
298
+ setTabIndex(el, el === target ? 0 : -1);
299
+ }
300
+ },
301
+ [setTabIndex],
302
+ );
303
+
304
+ /**
305
+ * Repair the roving tab stop: exactly one enabled treeitem is tabbable (0),
306
+ * the rest are -1. Prefer an existing `tabindex="0"` treeitem (so a consumer
307
+ * can seed the active item in its render); otherwise promote the first
308
+ * enabled treeitem.
309
+ */
310
+ const syncTabStops = useCallback(() => {
311
+ const items = getItems();
312
+ const enabled = items.filter(el => !itemDisabled(el));
313
+ if (enabled.length === 0) {
314
+ return;
315
+ }
316
+ const current = enabled.find(el => el.getAttribute('tabindex') === '0');
317
+ moveTabStop(items, current ?? enabled[0]);
318
+ }, [getItems, itemDisabled, moveTabStop]);
319
+
320
+ // Keep the tab stop valid across renders (items added/removed, disabled
321
+ // toggled). Runs after every commit but only when roving tabindex is on.
322
+ useIsomorphicLayoutEffect(() => {
323
+ if (hasRovingTabIndex) {
324
+ syncTabStops();
325
+ }
326
+ });
327
+
328
+ /** Move focus to a treeitem and notify the active-change listener. */
329
+ const focusItem = useCallback(
330
+ (el: HTMLElement | undefined) => {
331
+ if (el == null) {
332
+ return;
333
+ }
334
+ if (hasRovingTabIndex) {
335
+ moveTabStop(getItems(), el);
336
+ }
337
+ onActiveChange?.(idOf(el));
338
+ el.focus();
339
+ },
340
+ [idOf, onActiveChange, hasRovingTabIndex, moveTabStop, getItems],
341
+ );
342
+
343
+ /**
344
+ * Focus the first enabled treeitem from `start`, moving by `dir`. No wrap —
345
+ * a tree's linear roam clamps at the ends.
346
+ */
347
+ const focusEnabledFrom = useCallback(
348
+ (items: HTMLElement[], start: number, dir: 1 | -1) => {
349
+ for (let i = start; i >= 0 && i < items.length; i += dir) {
350
+ const candidate = items[i];
351
+ if (candidate != null && !itemDisabled(candidate)) {
352
+ focusItem(candidate);
353
+ return;
354
+ }
355
+ }
356
+ },
357
+ [itemDisabled, focusItem],
358
+ );
359
+
360
+ const focusFirst = useCallback(() => {
361
+ focusEnabledFrom(getItems(), 0, 1);
362
+ }, [getItems, focusEnabledFrom]);
363
+
364
+ const focusLast = useCallback(() => {
365
+ const items = getItems();
366
+ focusEnabledFrom(items, items.length - 1, -1);
367
+ }, [getItems, focusEnabledFrom]);
368
+
369
+ const runTypeahead = useCallback(
370
+ (
371
+ e: React.KeyboardEvent,
372
+ items: HTMLElement[],
373
+ currentIndex: number,
374
+ ): void => {
375
+ const state = typeaheadRef.current;
376
+ if (state.timer != null) {
377
+ clearTimeout(state.timer);
378
+ }
379
+ state.buffer += e.key.toLowerCase();
380
+ state.timer = setTimeout(() => {
381
+ typeaheadRef.current.buffer = '';
382
+ }, typeaheadResetMs) as unknown as number;
383
+
384
+ const query = state.buffer;
385
+ const start = currentIndex < 0 ? 0 : currentIndex;
386
+ const ordered = [...items.slice(start + 1), ...items.slice(0, start + 1)];
387
+ const match = ordered.find(
388
+ item =>
389
+ !itemDisabled(item) &&
390
+ (item.textContent ?? '').trim().toLowerCase().startsWith(query),
391
+ );
392
+ if (match != null) {
393
+ e.preventDefault();
394
+ focusItem(match);
395
+ }
396
+ },
397
+ [typeaheadResetMs, itemDisabled, focusItem],
398
+ );
399
+
400
+ const handleKeyDown = useCallback(
401
+ (e: React.KeyboardEvent) => {
402
+ const items = getItems();
403
+ if (items.length === 0) {
404
+ return;
405
+ }
406
+
407
+ const active = document.activeElement;
408
+ // Resolve the treeitem that owns focus: the nearest treeitem ancestor of
409
+ // the active element (never an outer treeitem that merely contains it).
410
+ const activeItem =
411
+ active instanceof Element ? active.closest('[role="treeitem"]') : null;
412
+ const currentIndex = items.findIndex(item => item === activeItem);
413
+ const current = currentIndex >= 0 ? items[currentIndex] : undefined;
414
+
415
+ // Typeahead: printable single characters jump to the next matching item.
416
+ if (
417
+ typeahead &&
418
+ e.key.length === 1 &&
419
+ !e.ctrlKey &&
420
+ !e.metaKey &&
421
+ !e.altKey &&
422
+ NAVIGATION_KEYS.has(e.key) === false
423
+ ) {
424
+ runTypeahead(e, items, currentIndex);
425
+ return;
426
+ }
427
+
428
+ if (!NAVIGATION_KEYS.has(e.key)) {
429
+ return;
430
+ }
431
+
432
+ switch (e.key) {
433
+ case 'ArrowDown': {
434
+ e.preventDefault();
435
+ focusEnabledFrom(items, currentIndex < 0 ? 0 : currentIndex + 1, 1);
436
+ break;
437
+ }
438
+ case 'ArrowUp': {
439
+ e.preventDefault();
440
+ focusEnabledFrom(
441
+ items,
442
+ currentIndex < 0 ? items.length - 1 : currentIndex - 1,
443
+ -1,
444
+ );
445
+ break;
446
+ }
447
+ case 'ArrowRight': {
448
+ if (current == null) {
449
+ break;
450
+ }
451
+ e.preventDefault();
452
+ if (collapsedOf(current)) {
453
+ // Collapsed parent → expand.
454
+ const id = idOf(current);
455
+ if (id != null) {
456
+ onToggleExpand?.(id);
457
+ }
458
+ } else if (expandedOf(current)) {
459
+ // Expanded parent → move to first child.
460
+ const next = items[currentIndex + 1];
461
+ if (next != null && levelOf(next) > levelOf(current)) {
462
+ focusItem(next);
463
+ }
464
+ }
465
+ // Leaf → no-op.
466
+ break;
467
+ }
468
+ case 'ArrowLeft': {
469
+ if (current == null) {
470
+ break;
471
+ }
472
+ e.preventDefault();
473
+ if (expandedOf(current)) {
474
+ // Expanded parent → collapse.
475
+ const id = idOf(current);
476
+ if (id != null) {
477
+ onToggleExpand?.(id);
478
+ }
479
+ } else {
480
+ // Otherwise → move to parent treeitem (nearest shallower item
481
+ // scanning upward in visible order).
482
+ const currentLevel = levelOf(current);
483
+ for (let i = currentIndex - 1; i >= 0; i--) {
484
+ const candidate = items[i];
485
+ if (candidate != null && levelOf(candidate) < currentLevel) {
486
+ focusItem(candidate);
487
+ break;
488
+ }
489
+ }
490
+ }
491
+ break;
492
+ }
493
+ case 'Home': {
494
+ e.preventDefault();
495
+ focusEnabledFrom(items, 0, 1);
496
+ break;
497
+ }
498
+ case 'End': {
499
+ e.preventDefault();
500
+ focusEnabledFrom(items, items.length - 1, -1);
501
+ break;
502
+ }
503
+ case 'Enter':
504
+ case ' ': {
505
+ if (current == null || itemDisabled(current)) {
506
+ break;
507
+ }
508
+ e.preventDefault();
509
+ const id = idOf(current);
510
+ // Activate the item: prefer the consumer's onActivate (e.g. click an
511
+ // inner link/button). Fall back to toggling expansion for a parent
512
+ // without its own action.
513
+ const handled = onActivate ? onActivate(current, id) === true : false;
514
+ if (!handled && current.getAttribute('aria-expanded') != null) {
515
+ if (id != null) {
516
+ onToggleExpand?.(id);
517
+ }
518
+ }
519
+ break;
520
+ }
521
+ default:
522
+ break;
523
+ }
524
+ },
525
+ [
526
+ getItems,
527
+ typeahead,
528
+ runTypeahead,
529
+ focusEnabledFrom,
530
+ focusItem,
531
+ collapsedOf,
532
+ expandedOf,
533
+ idOf,
534
+ levelOf,
535
+ itemDisabled,
536
+ onToggleExpand,
537
+ onActivate,
538
+ ],
539
+ );
540
+
541
+ /**
542
+ * Keep the roving stop pointing at whatever ended up focused (e.g. a click
543
+ * or programmatic focus) so the next Tab behaves correctly. No-op unless
544
+ * roving tabindex is enabled.
545
+ */
546
+ const handleFocus = useCallback(() => {
547
+ if (hasRovingTabIndex) {
548
+ syncTabStops();
549
+ }
550
+ }, [hasRovingTabIndex, syncTabStops]);
551
+
552
+ return {
553
+ treeRef,
554
+ handleKeyDown,
555
+ handleFocus,
556
+ focusFirst,
557
+ focusLast,
558
+ };
559
+ }
@@ -0,0 +1,109 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useTypeahead.test.tsx
5
+ * @input Uses vitest, @testing-library/react, useTypeahead hook
6
+ * @output Unit tests for useTypeahead first-character search
7
+ * @position Testing; validates useTypeahead.ts
8
+ *
9
+ * SYNC: When useTypeahead.ts changes, update tests to match new behavior
10
+ */
11
+
12
+ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
+ import {renderHook} from '@testing-library/react';
14
+ import {useTypeahead} from './useTypeahead';
15
+
16
+ const LABELS = ['Apple', 'Apricot', 'Banana', 'Cherry'] as const;
17
+ const NO_DISABLED: number[] = [];
18
+
19
+ function setup(opts?: {current?: number; disabledIndices?: number[]}) {
20
+ const onMatch = vi.fn();
21
+ const disabled = opts?.disabledIndices ?? NO_DISABLED;
22
+ const {result} = renderHook(() =>
23
+ useTypeahead({
24
+ getItemLabels: () => LABELS,
25
+ onMatch,
26
+ getCurrentIndex: () => opts?.current ?? -1,
27
+ isDisabled: (i: number) => disabled.includes(i),
28
+ }),
29
+ );
30
+ return {onMatch, api: result.current};
31
+ }
32
+
33
+ function key(k: string): KeyboardEvent {
34
+ return new KeyboardEvent('keydown', {key: k});
35
+ }
36
+
37
+ describe('useTypeahead', () => {
38
+ beforeEach(() => vi.useFakeTimers());
39
+ afterEach(() => vi.useRealTimers());
40
+
41
+ it('matches the first item whose label starts with the typed character', () => {
42
+ const {onMatch, api} = setup();
43
+ const handled = api.onKeyDown(key('b'));
44
+ expect(handled).toBe(true);
45
+ expect(onMatch).toHaveBeenCalledWith(2); // Banana
46
+ });
47
+
48
+ it('accumulates the buffer to disambiguate (a → ap → apr)', () => {
49
+ const {onMatch, api} = setup();
50
+ api.onKeyDown(key('a')); // Apple (first "a")
51
+ expect(onMatch).toHaveBeenLastCalledWith(0);
52
+ api.onKeyDown(key('p')); // "ap" still Apple
53
+ expect(onMatch).toHaveBeenLastCalledWith(0);
54
+ api.onKeyDown(key('r')); // "apr" → Apricot
55
+ expect(onMatch).toHaveBeenLastCalledWith(1);
56
+ });
57
+
58
+ it('cycles through same-letter matches on repeated presses', () => {
59
+ // Start with nothing focused; repeated "a" walks Apple → Apricot → wrap.
60
+ const onMatch = vi.fn();
61
+ let current = -1;
62
+ const {result} = renderHook(() =>
63
+ useTypeahead({
64
+ getItemLabels: () => LABELS,
65
+ onMatch: (i: number) => {
66
+ current = i;
67
+ onMatch(i);
68
+ },
69
+ getCurrentIndex: () => current,
70
+ }),
71
+ );
72
+ result.current.onKeyDown(key('a'));
73
+ expect(onMatch).toHaveBeenLastCalledWith(0); // Apple (first match)
74
+ result.current.onKeyDown(key('a'));
75
+ expect(onMatch).toHaveBeenLastCalledWith(1); // advance to Apricot
76
+ result.current.onKeyDown(key('a'));
77
+ expect(onMatch).toHaveBeenLastCalledWith(0); // wrap back to Apple
78
+ });
79
+
80
+ it('resets the buffer after the timeout', () => {
81
+ const {onMatch, api} = setup();
82
+ api.onKeyDown(key('a')); // Apple
83
+ api.onKeyDown(key('p')); // Apple ("ap")
84
+ vi.advanceTimersByTime(800);
85
+ api.onKeyDown(key('b')); // fresh buffer → Banana
86
+ expect(onMatch).toHaveBeenLastCalledWith(2);
87
+ });
88
+
89
+ it('skips disabled items', () => {
90
+ const {onMatch, api} = setup({disabledIndices: [0]}); // Apple disabled
91
+ api.onKeyDown(key('a'));
92
+ expect(onMatch).toHaveBeenCalledWith(1); // Apricot
93
+ });
94
+
95
+ it('ignores control keys and modifier chords', () => {
96
+ const {onMatch, api} = setup();
97
+ expect(api.onKeyDown(key('ArrowDown'))).toBe(false);
98
+ expect(
99
+ api.onKeyDown(new KeyboardEvent('keydown', {key: 'a', metaKey: true})),
100
+ ).toBe(false);
101
+ expect(onMatch).not.toHaveBeenCalled();
102
+ });
103
+
104
+ it('does not treat a bare Space as typeahead', () => {
105
+ const {onMatch, api} = setup();
106
+ expect(api.onKeyDown(key(' '))).toBe(false);
107
+ expect(onMatch).not.toHaveBeenCalled();
108
+ });
109
+ });