@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
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
13
- import {render, screen, fireEvent, act} from '@testing-library/react';
13
+ import {render, screen, fireEvent, act, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Tokenizer} from './Tokenizer';
16
16
  import type {SearchSource, SearchableItem} from '../Typeahead/types';
@@ -763,4 +763,108 @@ describe('Tokenizer', () => {
763
763
  expect(screen.getByText('Create "NewTag"')).toBeInTheDocument();
764
764
  });
765
765
  });
766
+
767
+ describe('disabledMessage', () => {
768
+ const h = {hidden: true} as const;
769
+ const isOpen = (el: Element) => el.matches(':popover-open');
770
+
771
+ function renderTokenizer(props?: {onChange?: () => void}) {
772
+ return render(
773
+ <Tokenizer
774
+ label="Members"
775
+ searchSource={userSource}
776
+ value={[]}
777
+ onChange={props?.onChange ?? (() => {})}
778
+ isDisabled
779
+ disabledMessage="You need edit access to change members"
780
+ />,
781
+ );
782
+ }
783
+
784
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
785
+ renderTokenizer();
786
+ const tooltip = screen.getByRole('tooltip', h);
787
+ expect(tooltip).toHaveTextContent(
788
+ 'You need edit access to change members',
789
+ );
790
+ const wrapper = screen.getByRole('group', {name: 'Members'});
791
+ fireEvent.mouseEnter(wrapper);
792
+ await waitFor(() => expect(isOpen(tooltip)).toBe(true));
793
+ fireEvent.mouseLeave(wrapper);
794
+ await waitFor(() => expect(isOpen(tooltip)).toBe(false));
795
+ });
796
+
797
+ it('shows the reason tooltip on keyboard focus', async () => {
798
+ const user = userEvent.setup();
799
+ renderTokenizer();
800
+ const tooltip = screen.getByRole('tooltip', h);
801
+ await user.tab();
802
+ expect(screen.getByRole('combobox')).toHaveFocus();
803
+ await waitFor(() => expect(isOpen(tooltip)).toBe(true));
804
+ });
805
+
806
+ it('does not render a tooltip when not disabled', () => {
807
+ render(
808
+ <Tokenizer
809
+ label="Members"
810
+ searchSource={userSource}
811
+ value={[]}
812
+ onChange={() => {}}
813
+ disabledMessage="You need edit access to change members"
814
+ />,
815
+ );
816
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
817
+ });
818
+
819
+ it('does not render a tooltip when disabled without a reason', () => {
820
+ render(
821
+ <Tokenizer
822
+ label="Members"
823
+ searchSource={userSource}
824
+ value={[]}
825
+ onChange={() => {}}
826
+ isDisabled
827
+ />,
828
+ );
829
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
830
+ });
831
+
832
+ it('keeps the input focusable via aria-disabled when a reason is provided', () => {
833
+ renderTokenizer();
834
+ const input = screen.getByRole('combobox');
835
+ expect(input).not.toBeDisabled();
836
+ expect(input).toHaveAttribute('aria-disabled', 'true');
837
+ });
838
+
839
+ it('links the reason tooltip via aria-describedby', () => {
840
+ renderTokenizer();
841
+ const input = screen.getByRole('combobox');
842
+ const tooltip = screen.getByRole('tooltip', h);
843
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
844
+ });
845
+
846
+ it('blocks input while focusable-disabled', async () => {
847
+ const user = userEvent.setup();
848
+ const onChange = vi.fn();
849
+ renderTokenizer({onChange});
850
+ const input = screen.getByRole('combobox');
851
+ input.focus();
852
+ await user.keyboard('Ali');
853
+ expect((input as HTMLInputElement).value).toBe('');
854
+ expect(onChange).not.toHaveBeenCalled();
855
+ });
856
+
857
+ it('keeps the input natively disabled when disabled without a reason', () => {
858
+ render(
859
+ <Tokenizer
860
+ label="Members"
861
+ searchSource={userSource}
862
+ value={[]}
863
+ onChange={() => {}}
864
+ isDisabled
865
+ />,
866
+ );
867
+ expect(screen.getByRole('combobox')).toBeDisabled();
868
+ });
869
+ });
766
870
  });
@@ -42,6 +42,7 @@ import {Token} from '../Token';
42
42
  import {renderIconSlot, type IconType} from '../Icon';
43
43
  import {OverflowList} from '../OverflowList';
44
44
  import {useLayer} from '../Layer/useLayer';
45
+ import {useTooltip} from '../Tooltip';
45
46
  import {
46
47
  colorVars,
47
48
  spacingVars,
@@ -145,6 +146,17 @@ export interface TokenizerProps<T extends SearchableItem> extends Omit<
145
146
  emptySearchResultsText?: string;
146
147
  /** Whether the input is disabled. @default false */
147
148
  isDisabled?: boolean;
149
+ /**
150
+ * Explains why the tokenizer is disabled. When set together with
151
+ * `isDisabled`, the tokenizer shows a tooltip with this text on hover and
152
+ * keyboard focus, and the input stays focusable (via `aria-disabled`) so the
153
+ * reason is discoverable by keyboard and assistive technology. Input stays
154
+ * blocked.
155
+ *
156
+ * Use this instead of wrapping a disabled tokenizer in `Tooltip` — disabled
157
+ * controls don't emit the pointer events an external tooltip needs.
158
+ */
159
+ disabledMessage?: string;
148
160
  /** Show clear button (clears all tokens). @default false */
149
161
  hasClear?: boolean;
150
162
  /**
@@ -365,6 +377,7 @@ export function Tokenizer<T extends SearchableItem>({
365
377
  maxMenuItems,
366
378
  emptySearchResultsText,
367
379
  isDisabled = false,
380
+ disabledMessage,
368
381
  hasClear = false,
369
382
  endContent,
370
383
  hasAutoFocus,
@@ -390,6 +403,19 @@ export function Tokenizer<T extends SearchableItem>({
390
403
  const inputRef = useRef<HTMLInputElement>(null);
391
404
  const wrapperRef = useRef<HTMLDivElement>(null);
392
405
 
406
+ // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
407
+ // tooltip listeners attach to the input wrapper and the typeahead input stays
408
+ // perceivable via aria-disabled instead of the disabled attribute. Input is
409
+ // blocked by the isDisabled guards in BaseTypeahead and handleWrapperClick.
410
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
411
+ const disabledMessageTooltip = useTooltip({
412
+ placement: 'above',
413
+ // The wrapper is not naturally focusable; focusin bubbles up from the
414
+ // input, so always attach focus listeners.
415
+ focusTrigger: 'always',
416
+ isEnabled: showsDisabledMessage,
417
+ });
418
+
393
419
  useImperativeHandle(handleRef, () => ({
394
420
  focus() {
395
421
  inputRef.current?.focus();
@@ -620,6 +646,7 @@ export function Tokenizer<T extends SearchableItem>({
620
646
  [
621
647
  description ? descriptionId : null,
622
648
  status?.message ? statusMessageId : null,
649
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
623
650
  ]
624
651
  .filter(Boolean)
625
652
  .join(' ') || undefined;
@@ -659,7 +686,13 @@ export function Tokenizer<T extends SearchableItem>({
659
686
 
660
687
  const wrapperContent = (
661
688
  <div
662
- ref={wrapperRef}
689
+ ref={el => {
690
+ wrapperRef.current = el;
691
+ // Anchor + hover/focus listeners for the disabled-message tooltip.
692
+ // Handlers are gated internally by isEnabled, so attaching
693
+ // unconditionally is safe.
694
+ disabledMessageTooltip.ref(el);
695
+ }}
663
696
  role="group"
664
697
  aria-label={label}
665
698
  onClick={handleWrapperClick}
@@ -710,6 +743,7 @@ export function Tokenizer<T extends SearchableItem>({
710
743
  maxMenuItems={maxMenuItems}
711
744
  emptySearchResultsText={emptySearchResultsText}
712
745
  isDisabled={isDisabled}
746
+ isFocusableDisabled={showsDisabledMessage}
713
747
  hasAutoFocus={hasAutoFocus}
714
748
  inputId={inputId}
715
749
  ariaDescribedBy={ariaDescribedBy}
@@ -831,6 +865,8 @@ export function Tokenizer<T extends SearchableItem>({
831
865
  className={className}
832
866
  style={style}>
833
867
  {tokenizerContent}
868
+ {showsDisabledMessage &&
869
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
834
870
  </Field>
835
871
  );
836
872
  }
@@ -71,9 +71,12 @@ describe('Toolbar', () => {
71
71
  expect(screen.getByTestId('center')).toBeInTheDocument();
72
72
  expect(screen.getByTestId('end')).toBeInTheDocument();
73
73
 
74
- // Three-slot layout produces 3 child divs
74
+ // Three-slot layout produces 3 child divs (plus the aria-hidden
75
+ // keyboard-hint popover, which is excluded here as an implementation detail)
75
76
  const toolbar = screen.getByRole('toolbar');
76
- expect(toolbar.children).toHaveLength(3);
77
+ expect(toolbar.querySelectorAll(':scope > :not([popover])')).toHaveLength(
78
+ 3,
79
+ );
77
80
  });
78
81
 
79
82
  it('renders two-slot layout without centerContent', () => {
@@ -87,9 +90,12 @@ describe('Toolbar', () => {
87
90
  expect(screen.getByTestId('start')).toBeInTheDocument();
88
91
  expect(screen.getByTestId('end')).toBeInTheDocument();
89
92
 
90
- // Two-slot layout produces 2 child divs
93
+ // Two-slot layout produces 2 child divs (plus the aria-hidden
94
+ // keyboard-hint popover, excluded here)
91
95
  const toolbar = screen.getByRole('toolbar');
92
- expect(toolbar.children).toHaveLength(2);
96
+ expect(toolbar.querySelectorAll(':scope > :not([popover])')).toHaveLength(
97
+ 2,
98
+ );
93
99
  });
94
100
 
95
101
  it('renders start-only layout', () => {
@@ -101,7 +107,9 @@ describe('Toolbar', () => {
101
107
  );
102
108
  expect(screen.getByTestId('start')).toBeInTheDocument();
103
109
  const toolbar = screen.getByRole('toolbar');
104
- expect(toolbar.children).toHaveLength(1);
110
+ expect(toolbar.querySelectorAll(':scope > :not([popover])')).toHaveLength(
111
+ 1,
112
+ );
105
113
  });
106
114
 
107
115
  it('renders end-only layout', () => {
@@ -113,7 +121,9 @@ describe('Toolbar', () => {
113
121
  );
114
122
  expect(screen.getByTestId('end')).toBeInTheDocument();
115
123
  const toolbar = screen.getByRole('toolbar');
116
- expect(toolbar.children).toHaveLength(1);
124
+ expect(toolbar.querySelectorAll(':scope > :not([popover])')).toHaveLength(
125
+ 1,
126
+ );
117
127
  });
118
128
 
119
129
  it('sets aria-orientation to horizontal by default', () => {
@@ -256,4 +266,97 @@ describe('Toolbar', () => {
256
266
  render(<Toolbar label="Actions" data-testid="my-toolbar" />);
257
267
  expect(screen.getByTestId('my-toolbar')).toBe(screen.getByRole('toolbar'));
258
268
  });
269
+
270
+ it('is a single tab stop — only one item is tabbable (navigation-3)', () => {
271
+ render(
272
+ <Toolbar
273
+ label="Actions"
274
+ startContent={
275
+ <>
276
+ <button type="button">Cut</button>
277
+ <button type="button">Copy</button>
278
+ <button type="button">Paste</button>
279
+ </>
280
+ }
281
+ />,
282
+ );
283
+ const buttons = screen.getAllByRole('button');
284
+ const tabbable = buttons.filter(b => b.getAttribute('tabindex') === '0');
285
+ expect(tabbable).toHaveLength(1);
286
+ expect(buttons[0]).toHaveAttribute('tabindex', '0');
287
+ expect(buttons[1]).toHaveAttribute('tabindex', '-1');
288
+ expect(buttons[2]).toHaveAttribute('tabindex', '-1');
289
+ });
290
+
291
+ it('does not steal caret keys from a text input mid-line (navigation-4)', async () => {
292
+ const user = userEvent.setup();
293
+ render(
294
+ <Toolbar
295
+ label="Search"
296
+ startContent={
297
+ <>
298
+ <input type="text" aria-label="Query" defaultValue="hello" />
299
+ <button type="button">Go</button>
300
+ </>
301
+ }
302
+ />,
303
+ );
304
+ const inputEl = screen.getByLabelText('Query');
305
+ if (!(inputEl instanceof HTMLInputElement)) {
306
+ throw new Error('expected an input');
307
+ }
308
+ inputEl.focus();
309
+ inputEl.setSelectionRange(1, 1); // caret mid-line
310
+ await user.keyboard('{ArrowRight}');
311
+ // Caret movement stays in the input; focus is not stolen by the toolbar.
312
+ expect(document.activeElement).toBe(inputEl);
313
+ });
314
+
315
+ it('composes consumer onKeyDown with internal arrow navigation', async () => {
316
+ const user = userEvent.setup();
317
+ const onKeyDown = vi.fn();
318
+
319
+ render(
320
+ <Toolbar
321
+ label="Actions"
322
+ onKeyDown={onKeyDown}
323
+ startContent={
324
+ <>
325
+ <button type="button">Cut</button>
326
+ <button type="button">Copy</button>
327
+ </>
328
+ }
329
+ />,
330
+ );
331
+
332
+ const buttons = screen.getAllByRole('button');
333
+ buttons[0].focus();
334
+ await user.keyboard('{ArrowRight}');
335
+
336
+ expect(onKeyDown).toHaveBeenCalled();
337
+ expect(buttons[1]).toHaveFocus();
338
+ });
339
+
340
+ it('respects preventDefault from consumer onKeyDown', async () => {
341
+ const user = userEvent.setup();
342
+
343
+ render(
344
+ <Toolbar
345
+ label="Actions"
346
+ onKeyDown={e => e.preventDefault()}
347
+ startContent={
348
+ <>
349
+ <button type="button">Cut</button>
350
+ <button type="button">Copy</button>
351
+ </>
352
+ }
353
+ />,
354
+ );
355
+
356
+ const buttons = screen.getAllByRole('button');
357
+ buttons[0].focus();
358
+ await user.keyboard('{ArrowRight}');
359
+
360
+ expect(buttons[0]).toHaveFocus();
361
+ });
259
362
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file Toolbar.tsx
7
- * @input Uses Section, SizeContext, useListFocus, StyleX, spacingVars, sizeVars
7
+ * @input Uses Section, SizeContext, useListFocus, useKeyboardHint, StyleX, spacingVars, sizeVars
8
8
  * @output Exports Toolbar component and ToolbarProps
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
@@ -16,7 +16,7 @@
16
16
  * - /packages/cli/templates/blocks/components/Toolbar/ (showcase blocks)
17
17
  */
18
18
 
19
- import type {ReactNode} from 'react';
19
+ import {useCallback, type ReactNode} from 'react';
20
20
  import type {BaseProps} from '../BaseProps';
21
21
  import type {SectionVariant} from '../Section/Section';
22
22
  import type {SpacingStep} from '../utils/types';
@@ -26,6 +26,7 @@ import {spacingVars, sizeVars} from '../theme/tokens.stylex';
26
26
  import {mergeProps} from '../utils';
27
27
  import {Section} from '../Section/Section';
28
28
  import {useListFocus} from '../hooks/useListFocus';
29
+ import {useKeyboardHint} from '../hooks/useKeyboardHint';
29
30
  import {SizeProvider} from '../SizeContext/SizeContext';
30
31
  import {edgeCompSlot} from '../Layout/edgeCompensation.stylex';
31
32
  import {themeProps} from '../utils/themeProps';
@@ -227,6 +228,12 @@ export function Toolbar({
227
228
  className,
228
229
  style,
229
230
  ref,
231
+ onKeyDown: onKeyDownProp,
232
+ onFocus: onFocusProp,
233
+ onBlur: onBlurProp,
234
+ role: _role,
235
+ 'aria-label': _ariaLabel,
236
+ 'aria-orientation': _ariaOrientation,
230
237
  ...props
231
238
  }: ToolbarProps) {
232
239
  const hasCenterContent = centerContent != null;
@@ -235,11 +242,55 @@ export function Toolbar({
235
242
 
236
243
  const gapVar = spacingVars[spacingStepToVar[gap]] as string;
237
244
 
238
- const {listRef, handleKeyDown} = useListFocus<HTMLDivElement>({
239
- itemSelector: 'button, input, [tabindex="0"]',
245
+ const {listRef, handleKeyDown, handleFocus} = useListFocus<HTMLDivElement>({
246
+ itemSelector: 'button, input, [tabindex]',
240
247
  orientation,
248
+ hasRovingTabIndex: true,
249
+ hasCaretGuard: true,
241
250
  });
242
251
 
252
+ const {
253
+ hintElement,
254
+ onKeyDown: onHintKeyDown,
255
+ onFocus: onHintFocus,
256
+ onBlur: onHintBlur,
257
+ } = useKeyboardHint({orientation});
258
+
259
+ const handleToolbarKeyDown = useCallback(
260
+ (e: React.KeyboardEvent<HTMLDivElement>) => {
261
+ onKeyDownProp?.(e);
262
+ if (e.defaultPrevented) {
263
+ return;
264
+ }
265
+ onHintKeyDown(e);
266
+ handleKeyDown(e);
267
+ },
268
+ [onKeyDownProp, onHintKeyDown, handleKeyDown],
269
+ );
270
+
271
+ const handleToolbarFocus = useCallback(
272
+ (e: React.FocusEvent<HTMLDivElement>) => {
273
+ onFocusProp?.(e);
274
+ if (e.defaultPrevented) {
275
+ return;
276
+ }
277
+ onHintFocus(e);
278
+ handleFocus(e);
279
+ },
280
+ [onFocusProp, onHintFocus, handleFocus],
281
+ );
282
+
283
+ const handleToolbarBlur = useCallback(
284
+ (e: React.FocusEvent<HTMLDivElement>) => {
285
+ onBlurProp?.(e);
286
+ if (e.defaultPrevented) {
287
+ return;
288
+ }
289
+ onHintBlur(e);
290
+ },
291
+ [onBlurProp, onHintBlur],
292
+ );
293
+
243
294
  return (
244
295
  <SizeProvider value={size}>
245
296
  <Section
@@ -255,7 +306,9 @@ export function Toolbar({
255
306
  role="toolbar"
256
307
  aria-label={label}
257
308
  aria-orientation={orientation}
258
- onKeyDown={handleKeyDown}
309
+ onKeyDown={handleToolbarKeyDown}
310
+ onFocus={handleToolbarFocus}
311
+ onBlur={handleToolbarBlur}
259
312
  {...mergeProps(
260
313
  themeProps('toolbar', {size}),
261
314
  stylex.props(
@@ -320,6 +373,7 @@ export function Toolbar({
320
373
  )}
321
374
  </>
322
375
  )}
376
+ {hintElement}
323
377
  </div>
324
378
  </Section>
325
379
  </SizeProvider>
@@ -153,4 +153,72 @@ describe('Tooltip', () => {
153
153
  });
154
154
  });
155
155
  });
156
+
157
+ describe('WCAG 1.4.13 — content on hover or focus', () => {
158
+ it('dismisses on Escape while visible (dismissible)', async () => {
159
+ const onOpenChange = vi.fn();
160
+ render(
161
+ <Tooltip content="Dismiss me" onOpenChange={onOpenChange} delay={0}>
162
+ <button type="button">Trigger</button>
163
+ </Tooltip>,
164
+ );
165
+
166
+ const trigger = screen.getByRole('button', {name: 'Trigger'});
167
+ fireEvent.mouseEnter(trigger);
168
+ await waitFor(() => {
169
+ expect(onOpenChange).toHaveBeenCalledWith(true);
170
+ });
171
+
172
+ fireEvent.keyDown(document, {key: 'Escape'});
173
+ await waitFor(() => {
174
+ expect(onOpenChange).toHaveBeenCalledWith(false);
175
+ });
176
+ });
177
+
178
+ it('ignores Escape during IME composition', async () => {
179
+ const onOpenChange = vi.fn();
180
+ render(
181
+ <Tooltip content="Stay" onOpenChange={onOpenChange} delay={0}>
182
+ <button type="button">Trigger</button>
183
+ </Tooltip>,
184
+ );
185
+
186
+ const trigger = screen.getByRole('button', {name: 'Trigger'});
187
+ fireEvent.mouseEnter(trigger);
188
+ await waitFor(() => {
189
+ expect(onOpenChange).toHaveBeenCalledWith(true);
190
+ });
191
+ onOpenChange.mockClear();
192
+
193
+ fireEvent.keyDown(document, {key: 'Escape', isComposing: true});
194
+ // Give any (incorrect) async hide a chance to run.
195
+ await new Promise(r => setTimeout(r, 20));
196
+ expect(onOpenChange).not.toHaveBeenCalledWith(false);
197
+ });
198
+
199
+ it('stays open when the pointer moves onto the tooltip surface (hoverable)', async () => {
200
+ const onOpenChange = vi.fn();
201
+ render(
202
+ <Tooltip content="Hover me" onOpenChange={onOpenChange} delay={0}>
203
+ <button type="button">Trigger</button>
204
+ </Tooltip>,
205
+ );
206
+
207
+ const trigger = screen.getByRole('button', {name: 'Trigger'});
208
+ fireEvent.mouseEnter(trigger);
209
+ await waitFor(() => {
210
+ expect(onOpenChange).toHaveBeenCalledWith(true);
211
+ });
212
+ onOpenChange.mockClear();
213
+
214
+ // Pointer leaves the trigger but enters the tooltip surface before the
215
+ // hover-bridge grace period elapses — the tooltip must not hide.
216
+ fireEvent.mouseLeave(trigger);
217
+ const layer = screen.getByRole('tooltip', {hidden: true});
218
+ fireEvent.mouseEnter(layer);
219
+
220
+ await new Promise(r => setTimeout(r, 150));
221
+ expect(onOpenChange).not.toHaveBeenCalledWith(false);
222
+ });
223
+ });
156
224
  });
@@ -37,6 +37,13 @@ import {
37
37
  typeScaleVars,
38
38
  } from '../theme/tokens.stylex';
39
39
 
40
+ /**
41
+ * Grace period (ms) before hiding on pointer-leave when no explicit `hideDelay`
42
+ * is set, so the pointer can travel across the small gap from the trigger onto
43
+ * the tooltip surface without the tooltip disappearing (WCAG 1.4.13 hoverable).
44
+ */
45
+ const HOVER_BRIDGE_DELAY = 100;
46
+
40
47
  const styles = stylex.create({
41
48
  // Base container styles - inverted colors for high contrast
42
49
  container: {
@@ -291,21 +298,29 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
291
298
  }, delay);
292
299
  }, [isEnabled, isOpen, clearTimeouts, layer, delay]);
293
300
 
294
- // Schedule hide with delay (suppressed when isOpen is true)
301
+ // Schedule hide with delay (suppressed when isOpen is true).
302
+ // A small hover bridge (when hideDelay is 0) lets the pointer travel from the
303
+ // trigger onto the tooltip surface without the tooltip vanishing — required
304
+ // for WCAG 1.4.13 (Content on Hover or Focus: hoverable).
295
305
  const scheduleHide = useCallback(() => {
296
306
  if (isOpen === true) {
297
307
  return;
298
308
  }
299
309
  clearTimeouts();
300
- if (hideDelay > 0) {
301
- hideTimeoutRef.current = setTimeout(() => {
302
- layer.hide();
303
- }, hideDelay);
304
- } else {
310
+ const effectiveHideDelay = hideDelay > 0 ? hideDelay : HOVER_BRIDGE_DELAY;
311
+ hideTimeoutRef.current = setTimeout(() => {
305
312
  layer.hide();
306
- }
313
+ }, effectiveHideDelay);
307
314
  }, [isOpen, clearTimeouts, layer, hideDelay]);
308
315
 
316
+ // Cancel a pending hide (e.g. the pointer entered the tooltip surface).
317
+ const cancelHide = useCallback(() => {
318
+ if (hideTimeoutRef.current) {
319
+ clearTimeout(hideTimeoutRef.current);
320
+ hideTimeoutRef.current = null;
321
+ }
322
+ }, []);
323
+
309
324
  // Event handlers
310
325
  const handleMouseEnter = useCallback(() => {
311
326
  // Suppress tooltips on touch devices — hover is simulated and eats a tap
@@ -420,6 +435,32 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
420
435
  }
421
436
  }, [isOpen, clearTimeouts, layer]);
422
437
 
438
+ // Dismiss on Escape (WCAG 1.4.13 — dismissible). Uncontrolled tooltips only;
439
+ // a controlled tooltip's visibility is owned by the consumer. The listener is
440
+ // mounted for the lifetime of an uncontrolled tooltip rather than gated on
441
+ // `layer.isOpen` (React state, which can lag a frame behind the DOM) —
442
+ // `layer.hide()` self-guards and no-ops when the layer is already closed.
443
+ // Guarded against IME composition-cancel.
444
+ useEffect(() => {
445
+ if (isOpen !== undefined) {
446
+ return;
447
+ }
448
+ const handleKeyDown = (e: KeyboardEvent) => {
449
+ if (e.key !== 'Escape') {
450
+ return;
451
+ }
452
+ if (e.isComposing || e.keyCode === 229) {
453
+ return;
454
+ }
455
+ clearTimeouts();
456
+ layer.hide();
457
+ };
458
+ document.addEventListener('keydown', handleKeyDown);
459
+ return () => {
460
+ document.removeEventListener('keydown', handleKeyDown);
461
+ };
462
+ }, [isOpen, layer, clearTimeouts]);
463
+
423
464
  // Render function that wraps layer.render with tooltip styling
424
465
  const renderTooltip = useCallback(
425
466
  (children: ReactNode, props?: ContextRenderProps): ReactNode => {
@@ -430,6 +471,12 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
430
471
  role: 'tooltip',
431
472
  xstyle: [popoverXstyle, layerAnimations[renderPlacement]],
432
473
  className: themeProps('tooltip').className,
474
+ // Keep the tooltip open while the pointer is over the surface itself
475
+ // (WCAG 1.4.13 hoverable). These sit on the layer container — the
476
+ // element the user actually hovers — not the inner content div, since
477
+ // mouseenter/leave do not bubble.
478
+ onMouseEnter: cancelHide,
479
+ onMouseLeave: scheduleHide,
433
480
  };
434
481
 
435
482
  return layer.render(
@@ -437,7 +484,7 @@ export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
437
484
  renderProps,
438
485
  );
439
486
  },
440
- [layer, placement, alignment, popoverXstyle],
487
+ [layer, placement, alignment, popoverXstyle, cancelHide, scheduleHide],
441
488
  );
442
489
 
443
490
  return {