@astryxdesign/core 0.1.1 → 0.1.2-canary.04e71b2

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 (631) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +131 -13
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  6. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  7. package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
  8. package/dist/Banner/Banner.d.ts +7 -0
  9. package/dist/Banner/Banner.d.ts.map +1 -1
  10. package/dist/Banner/Banner.js +9 -2
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  14. package/dist/Button/Button.d.ts +12 -2
  15. package/dist/Button/Button.d.ts.map +1 -1
  16. package/dist/Button/Button.js +53 -17
  17. package/dist/Calendar/Calendar.d.ts +5 -4
  18. package/dist/Calendar/Calendar.d.ts.map +1 -1
  19. package/dist/Calendar/Calendar.js +82 -43
  20. package/dist/Calendar/hooks/index.d.ts +0 -2
  21. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  22. package/dist/Calendar/hooks/index.js +1 -2
  23. package/dist/Calendar/index.d.ts +3 -3
  24. package/dist/Calendar/index.d.ts.map +1 -1
  25. package/dist/Calendar/index.js +1 -1
  26. package/dist/Calendar/styles.d.ts +3 -9
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +3 -11
  29. package/dist/Carousel/Carousel.d.ts.map +1 -1
  30. package/dist/Carousel/Carousel.js +17 -3
  31. package/dist/Center/Center.d.ts +11 -1
  32. package/dist/Center/Center.d.ts.map +1 -1
  33. package/dist/Center/Center.js +9 -3
  34. package/dist/Chat/ChatMessageList.d.ts +14 -1
  35. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  36. package/dist/Chat/ChatMessageList.js +2 -0
  37. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  38. package/dist/Chat/useTriggerMenu.js +4 -1
  39. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  40. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  41. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  42. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  43. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  44. package/dist/CheckboxList/CheckboxList.js +39 -9
  45. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  46. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +3 -3
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +49 -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 +59 -8
  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/EmptyState/EmptyState.d.ts.map +1 -1
  71. package/dist/EmptyState/EmptyState.js +7 -1
  72. package/dist/Field/Field.d.ts +19 -2
  73. package/dist/Field/Field.d.ts.map +1 -1
  74. package/dist/Field/Field.js +4 -0
  75. package/dist/Field/FieldLabel.d.ts +21 -2
  76. package/dist/Field/FieldLabel.d.ts.map +1 -1
  77. package/dist/Field/FieldLabel.js +41 -26
  78. package/dist/FileInput/FileInput.d.ts +22 -1
  79. package/dist/FileInput/FileInput.d.ts.map +1 -1
  80. package/dist/FileInput/FileInput.js +56 -10
  81. package/dist/Grid/Grid.d.ts +11 -1
  82. package/dist/Grid/Grid.d.ts.map +1 -1
  83. package/dist/Grid/Grid.js +8 -0
  84. package/dist/HoverCard/HoverCard.d.ts +2 -2
  85. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  86. package/dist/HoverCard/HoverCard.js +18 -6
  87. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  88. package/dist/HoverCard/useHoverCard.js +7 -3
  89. package/dist/InputGroup/InputGroup.d.ts +2 -2
  90. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  91. package/dist/InputGroup/InputGroup.js +14 -5
  92. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  93. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  94. package/dist/InputGroup/InputGroupContext.js +2 -2
  95. package/dist/InputGroup/index.d.ts +1 -0
  96. package/dist/InputGroup/index.d.ts.map +1 -1
  97. package/dist/Kbd/Kbd.d.ts.map +1 -1
  98. package/dist/Kbd/Kbd.js +34 -2
  99. package/dist/Layer/anchorName.d.ts +20 -0
  100. package/dist/Layer/anchorName.d.ts.map +1 -0
  101. package/dist/Layer/anchorName.js +36 -0
  102. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  103. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  104. package/dist/Layer/layerAnimations.stylex.js +8 -4
  105. package/dist/Layer/useLayer.d.ts +29 -0
  106. package/dist/Layer/useLayer.d.ts.map +1 -1
  107. package/dist/Layer/useLayer.js +55 -11
  108. package/dist/Layout/Layout.d.ts +4 -11
  109. package/dist/Layout/Layout.d.ts.map +1 -1
  110. package/dist/Layout/Layout.js +10 -11
  111. package/dist/Layout/LayoutFooter.d.ts +2 -2
  112. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  113. package/dist/Layout/LayoutHeader.d.ts +2 -2
  114. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  115. package/dist/Layout/LayoutPanel.d.ts +2 -2
  116. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  117. package/dist/Layout/padding.stylex.d.ts +51 -0
  118. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  119. package/dist/Layout/padding.stylex.js +63 -0
  120. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  121. package/dist/Lightbox/Lightbox.js +4 -2
  122. package/dist/Link/Link.d.ts +17 -2
  123. package/dist/Link/Link.d.ts.map +1 -1
  124. package/dist/Link/Link.js +20 -9
  125. package/dist/Link/LinkProvider.d.ts +1 -0
  126. package/dist/Link/LinkProvider.d.ts.map +1 -1
  127. package/dist/Link/LinkProvider.js +1 -0
  128. package/dist/Markdown/Markdown.d.ts.map +1 -1
  129. package/dist/Markdown/Markdown.js +18 -3
  130. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  131. package/dist/MobileNav/MobileNav.js +13 -0
  132. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  133. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  134. package/dist/MoreMenu/MoreMenu.js +0 -2
  135. package/dist/MultiSelector/MultiSelector.d.ts +25 -2
  136. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  137. package/dist/MultiSelector/MultiSelector.js +103 -22
  138. package/dist/MultiSelector/hooks.d.ts +12 -1
  139. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  140. package/dist/MultiSelector/hooks.js +14 -1
  141. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  142. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  143. package/dist/NumberInput/NumberInput.d.ts +23 -2
  144. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  145. package/dist/NumberInput/NumberInput.js +62 -12
  146. package/dist/Pagination/Pagination.d.ts +1 -1
  147. package/dist/Pagination/Pagination.d.ts.map +1 -1
  148. package/dist/Pagination/Pagination.js +46 -28
  149. package/dist/Popover/usePopover.d.ts +25 -2
  150. package/dist/Popover/usePopover.d.ts.map +1 -1
  151. package/dist/Popover/usePopover.js +18 -6
  152. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  153. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  154. package/dist/PowerSearch/PowerSearch.js +6 -1
  155. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  156. package/dist/ProgressBar/ProgressBar.js +3 -5
  157. package/dist/RadioList/RadioList.d.ts +20 -2
  158. package/dist/RadioList/RadioList.d.ts.map +1 -1
  159. package/dist/RadioList/RadioList.js +105 -9
  160. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  161. package/dist/RadioList/RadioListItem.js +13 -2
  162. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  163. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  164. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  165. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  166. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  167. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  168. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  169. package/dist/Selector/Selector.d.ts +22 -1
  170. package/dist/Selector/Selector.d.ts.map +1 -1
  171. package/dist/Selector/Selector.js +84 -23
  172. package/dist/Selector/hooks.d.ts +8 -1
  173. package/dist/Selector/hooks.d.ts.map +1 -1
  174. package/dist/Selector/hooks.js +14 -1
  175. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  176. package/dist/SideNav/SideNavItem.js +1 -1
  177. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  178. package/dist/Skeleton/Skeleton.js +8 -2
  179. package/dist/Slider/Slider.d.ts +21 -0
  180. package/dist/Slider/Slider.d.ts.map +1 -1
  181. package/dist/Slider/Slider.js +38 -11
  182. package/dist/Spinner/Spinner.d.ts.map +1 -1
  183. package/dist/Spinner/Spinner.js +1 -1
  184. package/dist/Stack/Stack.d.ts +38 -23
  185. package/dist/Stack/Stack.d.ts.map +1 -1
  186. package/dist/Stack/Stack.js +25 -1
  187. package/dist/Stack/StackItem.d.ts +12 -23
  188. package/dist/Stack/StackItem.d.ts.map +1 -1
  189. package/dist/Stack/StackItem.js +8 -1
  190. package/dist/Switch/Switch.d.ts +23 -2
  191. package/dist/Switch/Switch.d.ts.map +1 -1
  192. package/dist/Switch/Switch.js +41 -11
  193. package/dist/TabList/Tab.d.ts.map +1 -1
  194. package/dist/TabList/Tab.js +6 -0
  195. package/dist/TabList/TabList.d.ts +16 -4
  196. package/dist/TabList/TabList.d.ts.map +1 -1
  197. package/dist/TabList/TabList.js +86 -10
  198. package/dist/TabList/TabListContext.d.ts +5 -0
  199. package/dist/TabList/TabListContext.d.ts.map +1 -1
  200. package/dist/TabList/TabListContext.js +5 -0
  201. package/dist/TabList/TabMenu.d.ts.map +1 -1
  202. package/dist/TabList/TabMenu.js +6 -1
  203. package/dist/Table/BaseTable.d.ts.map +1 -1
  204. package/dist/Table/BaseTable.js +38 -16
  205. package/dist/Table/Table.d.ts.map +1 -1
  206. package/dist/Table/Table.js +36 -7
  207. package/dist/Table/TableCell.d.ts +8 -1
  208. package/dist/Table/TableCell.d.ts.map +1 -1
  209. package/dist/Table/TableCell.js +10 -10
  210. package/dist/Table/TableHeaderCell.d.ts +8 -1
  211. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  212. package/dist/Table/TableHeaderCell.js +16 -16
  213. package/dist/Table/TableRow.d.ts +6 -1
  214. package/dist/Table/TableRow.d.ts.map +1 -1
  215. package/dist/Table/TableRow.js +15 -7
  216. package/dist/Table/index.d.ts +4 -1
  217. package/dist/Table/index.d.ts.map +1 -1
  218. package/dist/Table/index.js +3 -2
  219. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  220. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  221. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  222. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  223. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  224. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  225. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  226. package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
  227. package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
  228. package/dist/Table/plugins/stickyColumns/index.js +3 -0
  229. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
  230. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
  231. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
  232. package/dist/Table/tableContextMenu.d.ts +33 -0
  233. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  234. package/dist/Table/tableContextMenu.js +133 -0
  235. package/dist/Table/types.d.ts +161 -5
  236. package/dist/Table/types.d.ts.map +1 -1
  237. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  238. package/dist/Table/useBaseTablePlugins.js +1 -1
  239. package/dist/Text/text.stylex.d.ts +2 -2
  240. package/dist/Text/text.stylex.js +2 -2
  241. package/dist/TextArea/TextArea.d.ts +22 -1
  242. package/dist/TextArea/TextArea.d.ts.map +1 -1
  243. package/dist/TextArea/TextArea.js +41 -9
  244. package/dist/TextInput/TextInput.d.ts +23 -2
  245. package/dist/TextInput/TextInput.d.ts.map +1 -1
  246. package/dist/TextInput/TextInput.js +55 -10
  247. package/dist/TimeInput/TimeInput.d.ts +23 -1
  248. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  249. package/dist/TimeInput/TimeInput.js +53 -10
  250. package/dist/Timestamp/Timestamp.d.ts +1 -1
  251. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  252. package/dist/Timestamp/Timestamp.js +28 -3
  253. package/dist/Toast/Toast.d.ts.map +1 -1
  254. package/dist/Toast/Toast.js +14 -0
  255. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  256. package/dist/Toast/ToastViewport.js +132 -3
  257. package/dist/ToggleButton/ToggleButton.d.ts +7 -9
  258. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  259. package/dist/ToggleButton/ToggleButton.js +34 -57
  260. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  261. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  262. package/dist/Tokenizer/Tokenizer.js +27 -5
  263. package/dist/Toolbar/Toolbar.d.ts +3 -3
  264. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  265. package/dist/Toolbar/Toolbar.js +51 -7
  266. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  267. package/dist/Tooltip/useTooltip.js +57 -9
  268. package/dist/TreeList/TreeList.d.ts +1 -1
  269. package/dist/TreeList/TreeList.d.ts.map +1 -1
  270. package/dist/TreeList/TreeList.js +72 -2
  271. package/dist/TreeList/TreeListItem.d.ts +11 -1
  272. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  273. package/dist/TreeList/TreeListItem.js +51 -12
  274. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  275. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  276. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  277. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  278. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  279. package/dist/Typeahead/BaseTypeahead.js +67 -6
  280. package/dist/Typeahead/Typeahead.d.ts +24 -1
  281. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  282. package/dist/Typeahead/Typeahead.js +29 -5
  283. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  284. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  285. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  286. package/dist/VisuallyHidden/index.d.ts +11 -0
  287. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  288. package/dist/VisuallyHidden/index.js +13 -0
  289. package/dist/astryx.css +40 -1
  290. package/dist/astryx.umd.js +147 -0
  291. package/dist/astryx.umd.js.map +7 -0
  292. package/dist/hooks/focusableSelector.d.ts +22 -0
  293. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  294. package/dist/hooks/focusableSelector.js +24 -0
  295. package/dist/hooks/index.d.ts +12 -2
  296. package/dist/hooks/index.d.ts.map +1 -1
  297. package/dist/hooks/index.js +7 -2
  298. package/dist/hooks/useAnnounce.d.ts +40 -0
  299. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  300. package/dist/hooks/useAnnounce.js +142 -0
  301. package/dist/hooks/useClickableContainer.d.ts +16 -1
  302. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  303. package/dist/hooks/useClickableContainer.js +16 -4
  304. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  305. package/dist/hooks/useEntryAnimation.js +4 -4
  306. package/dist/hooks/useFocusTrap.d.ts +18 -0
  307. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  308. package/dist/hooks/useFocusTrap.js +87 -3
  309. package/dist/hooks/useGridFocus.d.ts +88 -1
  310. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  311. package/dist/hooks/useGridFocus.js +261 -62
  312. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  313. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  314. package/dist/hooks/useKeyboardHint.js +220 -0
  315. package/dist/hooks/useListFocus.d.ts +78 -7
  316. package/dist/hooks/useListFocus.d.ts.map +1 -1
  317. package/dist/hooks/useListFocus.js +305 -52
  318. package/dist/hooks/useLongPress.d.ts +21 -0
  319. package/dist/hooks/useLongPress.d.ts.map +1 -0
  320. package/dist/hooks/useLongPress.js +89 -0
  321. package/dist/hooks/useTreeFocus.d.ts +157 -0
  322. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  323. package/dist/hooks/useTreeFocus.js +337 -0
  324. package/dist/hooks/useTypeahead.d.ts +63 -0
  325. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  326. package/dist/hooks/useTypeahead.js +117 -0
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.d.ts.map +1 -1
  329. package/dist/index.js +1 -0
  330. package/dist/theme/generateThemeRules.js +1 -1
  331. package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
  332. package/dist/theme/syntax/SyntaxTheme.js +1 -1
  333. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  334. package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
  335. package/dist/theme/syntax/presets.d.ts +1 -1
  336. package/dist/theme/syntax/presets.d.ts.map +1 -1
  337. package/dist/theme/syntax/presets.js +1 -1
  338. package/dist/theme/syntax/tokens.d.ts +1 -1
  339. package/dist/theme/syntax/tokens.js +1 -1
  340. package/dist/theme/types.d.ts +1 -1
  341. package/dist/utils/dateTypes.d.ts +12 -0
  342. package/dist/utils/dateTypes.d.ts.map +1 -1
  343. package/dist/utils/dateTypes.js +41 -1
  344. package/dist/utils/index.d.ts +4 -0
  345. package/dist/utils/index.d.ts.map +1 -1
  346. package/dist/utils/index.js +3 -1
  347. package/dist/utils/inputAria.d.ts +29 -0
  348. package/dist/utils/inputAria.d.ts.map +1 -0
  349. package/dist/utils/inputAria.js +30 -0
  350. package/dist/utils/timeParser.d.ts.map +1 -1
  351. package/dist/utils/timeParser.js +5 -2
  352. package/package.json +15 -5
  353. package/src/Avatar/Avatar.test.tsx +37 -0
  354. package/src/Avatar/Avatar.tsx +10 -5
  355. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
  356. package/src/Banner/Banner.doc.mjs +8 -0
  357. package/src/Banner/Banner.test.tsx +16 -7
  358. package/src/Banner/Banner.tsx +9 -2
  359. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  360. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  361. package/src/Button/Button.doc.mjs +6 -0
  362. package/src/Button/Button.test.tsx +105 -11
  363. package/src/Button/Button.tsx +76 -21
  364. package/src/Calendar/Calendar.doc.mjs +4 -4
  365. package/src/Calendar/Calendar.test.tsx +168 -30
  366. package/src/Calendar/Calendar.tsx +121 -66
  367. package/src/Calendar/hooks/index.ts +0 -6
  368. package/src/Calendar/index.ts +1 -3
  369. package/src/Calendar/styles.ts +4 -9
  370. package/src/Carousel/Carousel.doc.mjs +7 -1
  371. package/src/Carousel/Carousel.test.tsx +20 -0
  372. package/src/Carousel/Carousel.tsx +16 -1
  373. package/src/Center/Center.doc.mjs +12 -2
  374. package/src/Center/Center.tsx +28 -3
  375. package/src/Chat/ChatComposer.doc.mjs +3 -0
  376. package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
  377. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  378. package/src/Chat/ChatMessageList.test.tsx +18 -0
  379. package/src/Chat/ChatMessageList.tsx +16 -0
  380. package/src/Chat/useTriggerMenu.tsx +3 -0
  381. package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
  382. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  383. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  384. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  385. package/src/CheckboxList/CheckboxList.test.tsx +169 -16
  386. package/src/CheckboxList/CheckboxList.tsx +56 -3
  387. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  388. package/src/Citation/Citation.doc.mjs +10 -0
  389. package/src/Citation/Citation.test.tsx +117 -0
  390. package/src/Citation/Citation.tsx +9 -1
  391. package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
  392. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  393. package/src/CodeBlock/CodeBlock.tsx +61 -33
  394. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  395. package/src/CommandPalette/CommandPalette.tsx +9 -21
  396. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  397. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  398. package/src/ContextMenu/ContextMenu.tsx +113 -16
  399. package/src/DateInput/DateInput.doc.mjs +281 -56
  400. package/src/DateInput/DateInput.test.tsx +188 -2
  401. package/src/DateInput/DateInput.tsx +89 -10
  402. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  403. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  404. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  405. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  406. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  407. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  408. package/src/Dialog/Dialog.doc.mjs +2 -2
  409. package/src/Dialog/Dialog.tsx +12 -0
  410. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  411. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  412. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  413. package/src/EmptyState/EmptyState.test.tsx +4 -2
  414. package/src/EmptyState/EmptyState.tsx +6 -2
  415. package/src/Field/Field.test.tsx +32 -0
  416. package/src/Field/Field.tsx +23 -5
  417. package/src/Field/FieldLabel.tsx +55 -17
  418. package/src/FileInput/FileInput.doc.mjs +17 -0
  419. package/src/FileInput/FileInput.test.tsx +283 -49
  420. package/src/FileInput/FileInput.tsx +76 -4
  421. package/src/Grid/Grid.doc.mjs +14 -4
  422. package/src/Grid/Grid.tsx +20 -0
  423. package/src/HoverCard/HoverCard.doc.mjs +3 -0
  424. package/src/HoverCard/HoverCard.test.tsx +189 -2
  425. package/src/HoverCard/HoverCard.tsx +20 -16
  426. package/src/HoverCard/useHoverCard.tsx +13 -10
  427. package/src/Icon/Icon.doc.mjs +8 -0
  428. package/src/InputGroup/InputGroup.doc.mjs +2 -2
  429. package/src/InputGroup/InputGroup.test.tsx +98 -60
  430. package/src/InputGroup/InputGroup.tsx +21 -4
  431. package/src/InputGroup/InputGroupContext.ts +9 -4
  432. package/src/InputGroup/index.ts +1 -0
  433. package/src/Kbd/Kbd.test.tsx +22 -4
  434. package/src/Kbd/Kbd.tsx +37 -12
  435. package/src/Layer/anchorName.test.ts +146 -0
  436. package/src/Layer/anchorName.ts +46 -0
  437. package/src/Layer/layerAnimations.stylex.ts +20 -4
  438. package/src/Layer/useLayer.doc.mjs +12 -2
  439. package/src/Layer/useLayer.tsx +100 -15
  440. package/src/Layout/Layout.doc.mjs +1 -1
  441. package/src/Layout/Layout.tsx +8 -16
  442. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  443. package/src/Layout/LayoutFooter.tsx +3 -3
  444. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  445. package/src/Layout/LayoutHeader.tsx +3 -3
  446. package/src/Layout/LayoutPanel.tsx +3 -3
  447. package/src/Layout/padding.stylex.ts +52 -0
  448. package/src/Lightbox/Lightbox.doc.mjs +9 -2
  449. package/src/Lightbox/Lightbox.test.tsx +53 -5
  450. package/src/Lightbox/Lightbox.tsx +9 -4
  451. package/src/Link/Link.doc.mjs +14 -0
  452. package/src/Link/Link.test.tsx +52 -2
  453. package/src/Link/Link.tsx +42 -7
  454. package/src/Link/LinkProvider.tsx +1 -0
  455. package/src/List/List.doc.mjs +7 -0
  456. package/src/Markdown/Markdown.doc.mjs +2 -0
  457. package/src/Markdown/Markdown.test.tsx +28 -26
  458. package/src/Markdown/Markdown.tsx +21 -6
  459. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  460. package/src/MobileNav/MobileNav.doc.mjs +8 -8
  461. package/src/MobileNav/MobileNav.tsx +13 -0
  462. package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
  463. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  464. package/src/MoreMenu/MoreMenu.tsx +0 -9
  465. package/src/MultiSelector/MultiSelector.doc.mjs +25 -0
  466. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  467. package/src/MultiSelector/MultiSelector.tsx +145 -21
  468. package/src/MultiSelector/hooks.ts +27 -0
  469. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  470. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  471. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  472. package/src/NumberInput/NumberInput.test.tsx +242 -40
  473. package/src/NumberInput/NumberInput.tsx +89 -11
  474. package/src/OverflowList/OverflowList.doc.mjs +17 -0
  475. package/src/Pagination/Pagination.doc.mjs +3 -3
  476. package/src/Pagination/Pagination.test.tsx +250 -14
  477. package/src/Pagination/Pagination.tsx +54 -29
  478. package/src/Popover/usePopover.doc.mjs +7 -3
  479. package/src/Popover/usePopover.tsx +49 -6
  480. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  481. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  482. package/src/PowerSearch/PowerSearch.tsx +16 -3
  483. package/src/ProgressBar/ProgressBar.test.tsx +45 -27
  484. package/src/ProgressBar/ProgressBar.tsx +3 -4
  485. package/src/RadioList/RadioList.doc.mjs +9 -0
  486. package/src/RadioList/RadioList.test.tsx +252 -16
  487. package/src/RadioList/RadioList.tsx +166 -10
  488. package/src/RadioList/RadioListItem.tsx +14 -2
  489. package/src/Resizable/Resizable.doc.mjs +1 -1
  490. package/src/Section/Section.doc.mjs +11 -0
  491. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  492. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  493. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  494. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  495. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  496. package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
  497. package/src/Selector/Selector.doc.mjs +25 -0
  498. package/src/Selector/Selector.test.tsx +322 -15
  499. package/src/Selector/Selector.tsx +126 -40
  500. package/src/Selector/hooks.ts +23 -0
  501. package/src/SideNav/SideNav.test.tsx +16 -0
  502. package/src/SideNav/SideNavItem.tsx +2 -4
  503. package/src/Skeleton/Skeleton.doc.mjs +11 -1
  504. package/src/Skeleton/Skeleton.test.tsx +25 -0
  505. package/src/Skeleton/Skeleton.tsx +11 -1
  506. package/src/Slider/Slider.doc.mjs +19 -0
  507. package/src/Slider/Slider.test.tsx +158 -18
  508. package/src/Slider/Slider.tsx +54 -6
  509. package/src/Spinner/Spinner.tsx +7 -1
  510. package/src/Stack/Stack.doc.mjs +77 -0
  511. package/src/Stack/Stack.test.tsx +55 -5
  512. package/src/Stack/Stack.tsx +73 -24
  513. package/src/Stack/StackItem.test.tsx +23 -0
  514. package/src/Stack/StackItem.tsx +20 -23
  515. package/src/Switch/Switch.doc.mjs +15 -0
  516. package/src/Switch/Switch.test.tsx +162 -27
  517. package/src/Switch/Switch.tsx +61 -21
  518. package/src/TabList/Tab.tsx +6 -0
  519. package/src/TabList/TabList.doc.mjs +12 -0
  520. package/src/TabList/TabList.test.tsx +208 -0
  521. package/src/TabList/TabList.tsx +102 -12
  522. package/src/TabList/TabListContext.ts +7 -3
  523. package/src/TabList/TabMenu.tsx +6 -3
  524. package/src/Table/BaseTable.tsx +72 -41
  525. package/src/Table/Table.doc.mjs +14 -0
  526. package/src/Table/Table.test.tsx +39 -30
  527. package/src/Table/Table.tsx +27 -1
  528. package/src/Table/TableCell.tsx +25 -23
  529. package/src/Table/TableHeaderCell.tsx +28 -29
  530. package/src/Table/TableRow.tsx +37 -7
  531. package/src/Table/index.ts +6 -2
  532. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  533. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  534. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  535. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  536. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  537. package/src/Table/plugins/stickyColumns/index.ts +4 -0
  538. package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
  539. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
  540. package/src/Table/tableContextMenu.test.tsx +157 -0
  541. package/src/Table/tableContextMenu.tsx +142 -0
  542. package/src/Table/types.ts +175 -4
  543. package/src/Table/useBaseTablePlugins.ts +1 -0
  544. package/src/Table/useTableColumnResize.doc.mjs +96 -0
  545. package/src/Table/useTableStickyColumns.doc.mjs +59 -0
  546. package/src/Text/Heading.doc.mjs +15 -2
  547. package/src/Text/Text.doc.mjs +7 -1
  548. package/src/Text/text.stylex.ts +2 -2
  549. package/src/TextArea/TextArea.doc.mjs +17 -0
  550. package/src/TextArea/TextArea.test.tsx +167 -35
  551. package/src/TextArea/TextArea.tsx +62 -15
  552. package/src/TextInput/TextInput.doc.mjs +17 -0
  553. package/src/TextInput/TextInput.test.tsx +168 -34
  554. package/src/TextInput/TextInput.tsx +77 -9
  555. package/src/TimeInput/TimeInput.doc.mjs +335 -60
  556. package/src/TimeInput/TimeInput.test.tsx +144 -7
  557. package/src/TimeInput/TimeInput.tsx +74 -5
  558. package/src/Timestamp/Timestamp.doc.mjs +9 -0
  559. package/src/Timestamp/Timestamp.test.tsx +44 -18
  560. package/src/Timestamp/Timestamp.tsx +30 -10
  561. package/src/Toast/Toast.tsx +14 -0
  562. package/src/Toast/ToastViewport.test.tsx +204 -0
  563. package/src/Toast/ToastViewport.tsx +167 -21
  564. package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
  565. package/src/ToggleButton/ToggleButton.test.tsx +39 -18
  566. package/src/ToggleButton/ToggleButton.tsx +46 -83
  567. package/src/Tokenizer/Tokenizer.doc.mjs +15 -0
  568. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  569. package/src/Tokenizer/Tokenizer.tsx +41 -2
  570. package/src/Toolbar/Toolbar.doc.mjs +65 -5
  571. package/src/Toolbar/Toolbar.test.tsx +109 -6
  572. package/src/Toolbar/Toolbar.tsx +59 -5
  573. package/src/Tooltip/Tooltip.test.tsx +81 -0
  574. package/src/Tooltip/useTooltip.tsx +57 -11
  575. package/src/TreeList/TreeList.test.tsx +295 -6
  576. package/src/TreeList/TreeList.tsx +73 -1
  577. package/src/TreeList/TreeListItem.tsx +51 -5
  578. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  579. package/src/Typeahead/BaseTypeahead.tsx +86 -9
  580. package/src/Typeahead/Typeahead.doc.mjs +136 -30
  581. package/src/Typeahead/Typeahead.test.tsx +331 -1
  582. package/src/Typeahead/Typeahead.tsx +52 -1
  583. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  584. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  585. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  586. package/src/VisuallyHidden/index.ts +15 -0
  587. package/src/hooks/focusableSelector.ts +25 -0
  588. package/src/hooks/index.ts +28 -2
  589. package/src/hooks/useAnnounce.test.tsx +101 -0
  590. package/src/hooks/useAnnounce.ts +168 -0
  591. package/src/hooks/useClickableContainer.ts +16 -4
  592. package/src/hooks/useEntryAnimation.ts +16 -4
  593. package/src/hooks/useFocusTrap.test.tsx +132 -0
  594. package/src/hooks/useFocusTrap.ts +98 -4
  595. package/src/hooks/useGridFocus.doc.mjs +40 -2
  596. package/src/hooks/useGridFocus.test.tsx +132 -0
  597. package/src/hooks/useGridFocus.ts +343 -67
  598. package/src/hooks/useImageMode.test.ts +133 -0
  599. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  600. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  601. package/src/hooks/useKeyboardHint.tsx +332 -0
  602. package/src/hooks/useListFocus.doc.mjs +50 -10
  603. package/src/hooks/useListFocus.test.tsx +375 -0
  604. package/src/hooks/useListFocus.ts +395 -57
  605. package/src/hooks/useLongPress.test.tsx +134 -0
  606. package/src/hooks/useLongPress.ts +119 -0
  607. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  608. package/src/hooks/useTreeFocus.test.tsx +214 -0
  609. package/src/hooks/useTreeFocus.ts +559 -0
  610. package/src/hooks/useTypeahead.test.tsx +109 -0
  611. package/src/hooks/useTypeahead.ts +183 -0
  612. package/src/index.ts +1 -0
  613. package/src/theme/derivedVarRegistry.test.ts +4 -0
  614. package/src/theme/generateThemeRules.test.ts +7 -8
  615. package/src/theme/generateThemeRules.ts +1 -1
  616. package/src/theme/syntax/SyntaxTheme.tsx +1 -1
  617. package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
  618. package/src/theme/syntax/presets.ts +5 -2
  619. package/src/theme/syntax/tokens.ts +1 -1
  620. package/src/theme/types.ts +1 -1
  621. package/src/utils/dateTypes.test.ts +40 -0
  622. package/src/utils/dateTypes.ts +40 -0
  623. package/src/utils/index.ts +9 -0
  624. package/src/utils/inputAria.test.ts +39 -0
  625. package/src/utils/inputAria.ts +58 -0
  626. package/src/utils/timeParser.test.ts +10 -0
  627. package/src/utils/timeParser.ts +5 -2
  628. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  629. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  630. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  631. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -2,17 +2,21 @@
2
2
 
3
3
  'use client';
4
4
 
5
- import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
5
+ import {
6
+ useCallback,
7
+ useEffect,
8
+ useLayoutEffect,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
6
13
  import * as stylex from '@stylexjs/stylex';
7
14
  import {spacingVars, durationVars, easeVars} from '../theme/tokens.stylex';
8
15
  import {mergeProps} from '../utils';
16
+ import {INTERACTIVE_SELECTORS} from '../hooks/useClickableContainer';
9
17
  import {Toast} from './Toast';
10
18
  import {ToastContext, type ToastContextValue} from './ToastContext';
11
- import type {
12
- ToastEntry,
13
- ToastPosition,
14
- ToastDismissReason,
15
- } from './types';
19
+ import type {ToastEntry, ToastPosition, ToastDismissReason} from './types';
16
20
 
17
21
  const styles = stylex.create({
18
22
  viewport: {
@@ -108,6 +112,47 @@ export function ToastViewport({
108
112
  const toastsRef = useRef(toasts);
109
113
  toastsRef.current = toasts;
110
114
 
115
+ // Show the popover on mount so it enters the top layer.
116
+ const viewportRef = useRef<HTMLDivElement>(null);
117
+ // The element that was focused before the user jumped into the viewport
118
+ // (via F6). Used to restore focus once all toasts are dismissed so focus
119
+ // never falls to <body>.
120
+ const prevFocusRef = useRef<HTMLElement | null>(null);
121
+ // When a toast is dismissed while focus lives inside it, we need to move
122
+ // focus to a sensible neighbor after that toast unmounts. This holds the
123
+ // id of the toast whose removal should trigger a focus handoff.
124
+ const focusHandoffIdRef = useRef<string | null>(null);
125
+ // The next toast id that should receive focus once the dismissed toast has
126
+ // unmounted, or 'restore' to fall back to the previously-focused element.
127
+ const pendingFocusRef = useRef<string | 'restore' | null>(null);
128
+
129
+ // Collect a focusable control within a toast node, if any.
130
+ // Reuses the canonical INTERACTIVE_SELECTORS list (native controls plus
131
+ // role-based interactive elements) instead of a hand-rolled subset, then
132
+ // narrows to the first candidate that can actually receive focus —
133
+ // excluding elements opted out with `tabindex="-1"` and disabled controls.
134
+ const getFocusable = useCallback(
135
+ (container: HTMLElement | null): HTMLElement | null => {
136
+ if (!container) {
137
+ return null;
138
+ }
139
+ const candidates =
140
+ container.querySelectorAll<HTMLElement>(INTERACTIVE_SELECTORS);
141
+ for (const candidate of candidates) {
142
+ if (
143
+ candidate.getAttribute('tabindex') === '-1' ||
144
+ candidate.hasAttribute('disabled') ||
145
+ candidate.getAttribute('aria-disabled') === 'true'
146
+ ) {
147
+ continue;
148
+ }
149
+ return candidate;
150
+ }
151
+ return null;
152
+ },
153
+ [],
154
+ );
155
+
111
156
  const addToast = useCallback((entry: ToastEntry) => {
112
157
  setToasts(prev => {
113
158
  const {uniqueID, collisionBehavior = 'overwrite'} = entry.options;
@@ -124,21 +169,44 @@ export function ToastViewport({
124
169
  });
125
170
  }, []);
126
171
 
127
- const removeToast = useCallback(
128
- (id: string, reason: ToastDismissReason) => {
129
- const entry = toastsRef.current.find(t => t.id === id);
130
- if (entry) {
131
- entry.options.onHide?.(reason);
172
+ const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
173
+ const entry = toastsRef.current.find(t => t.id === id);
174
+ if (entry) {
175
+ entry.options.onHide?.(reason);
176
+ }
177
+ // If focus currently lives inside the toast being dismissed, remember
178
+ // that its removal must hand focus off to a neighbor (or the element
179
+ // focused before the user entered the viewport) rather than <body>.
180
+ const el = viewportRef.current;
181
+ const active = document.activeElement;
182
+ const dismissedNode =
183
+ el?.querySelector<HTMLElement>(`[data-toast-id="${id}"]`) ?? null;
184
+ if (
185
+ dismissedNode &&
186
+ active instanceof Node &&
187
+ dismissedNode.contains(active)
188
+ ) {
189
+ focusHandoffIdRef.current = id;
190
+ // Pick the neighbor to receive focus while the DOM is still intact:
191
+ // prefer the next toast, then the previous, else restore.
192
+ const remaining = toastsRef.current.filter(t => t.id !== id);
193
+ if (remaining.length > 0) {
194
+ const dismissedIndex = toastsRef.current.findIndex(t => t.id === id);
195
+ const next =
196
+ toastsRef.current[dismissedIndex + 1] ??
197
+ toastsRef.current[dismissedIndex - 1];
198
+ pendingFocusRef.current = next ? next.id : 'restore';
199
+ } else {
200
+ pendingFocusRef.current = 'restore';
132
201
  }
133
- setExitingIds(prev => {
134
- if (prev.has(id)) {
135
- return prev;
136
- }
137
- return new Set(prev).add(id);
138
- });
139
- },
140
- [],
141
- );
202
+ }
203
+ setExitingIds(prev => {
204
+ if (prev.has(id)) {
205
+ return prev;
206
+ }
207
+ return new Set(prev).add(id);
208
+ });
209
+ }, []);
142
210
 
143
211
  const handleExited = useCallback((id: string) => {
144
212
  setExitingIds(prev => {
@@ -152,6 +220,43 @@ export function ToastViewport({
152
220
  setToasts(prev => prev.filter(t => t.id !== id));
153
221
  }, []);
154
222
 
223
+ // After a dismissed toast unmounts, hand focus off so it never falls to
224
+ // <body>. Runs in a layout effect once the toast list no longer contains
225
+ // the dismissed toast.
226
+ useLayoutEffect(() => {
227
+ const handoffId = focusHandoffIdRef.current;
228
+ const target = pendingFocusRef.current;
229
+ if (handoffId == null || target == null) {
230
+ return;
231
+ }
232
+ // Wait until the dismissed toast is actually gone from the list.
233
+ if (toasts.some(t => t.id === handoffId)) {
234
+ return;
235
+ }
236
+ focusHandoffIdRef.current = null;
237
+ pendingFocusRef.current = null;
238
+ const el = viewportRef.current;
239
+ if (target !== 'restore' && el) {
240
+ const nextNode = el.querySelector<HTMLElement>(
241
+ `[data-toast-id="${target}"]`,
242
+ );
243
+ const focusable = getFocusable(nextNode) ?? nextNode;
244
+ if (focusable) {
245
+ focusable.focus();
246
+ return;
247
+ }
248
+ }
249
+ // No remaining toast to receive focus — restore the previously-focused
250
+ // element if it's still connected, else fall back to the container.
251
+ const prev = prevFocusRef.current;
252
+ prevFocusRef.current = null;
253
+ if (prev && prev.isConnected) {
254
+ prev.focus();
255
+ } else if (el) {
256
+ el.focus();
257
+ }
258
+ }, [toasts, getFocusable]);
259
+
155
260
  const findByUniqueID = useCallback((uid: string) => {
156
261
  return toastsRef.current.find(t => t.options.uniqueID === uid);
157
262
  }, []);
@@ -177,7 +282,6 @@ export function ToastViewport({
177
282
  }
178
283
 
179
284
  // Show the popover on mount so it enters the top layer
180
- const viewportRef = useRef<HTMLDivElement>(null);
181
285
  useEffect(() => {
182
286
  if (!isTopLayer) {
183
287
  return;
@@ -192,6 +296,45 @@ export function ToastViewport({
192
296
  }
193
297
  }, [isTopLayer]);
194
298
 
299
+ // F6 jumps focus into the toast viewport — the standard "go to
300
+ // notifications" affordance. Focus the first control in the newest toast,
301
+ // or the viewport container if none. Toasts are non-modal, so this only
302
+ // moves focus in; Shift+Tab / Escape let focus leave naturally.
303
+ const hasToasts = toasts.length > 0;
304
+ useEffect(() => {
305
+ if (!hasToasts) {
306
+ return;
307
+ }
308
+ const handleKeyDown = (e: KeyboardEvent) => {
309
+ if (e.key !== 'F6') {
310
+ return;
311
+ }
312
+ const el = viewportRef.current;
313
+ if (!el) {
314
+ return;
315
+ }
316
+ // Already inside the viewport — nothing to do.
317
+ const active = document.activeElement;
318
+ if (active instanceof Node && el.contains(active)) {
319
+ return;
320
+ }
321
+ e.preventDefault();
322
+ // Remember where focus was so it can be restored on dismiss.
323
+ if (active instanceof HTMLElement) {
324
+ prevFocusRef.current = active;
325
+ }
326
+ // Newest toast is the last one rendered in the DOM.
327
+ const toastNodes = el.querySelectorAll<HTMLElement>('[data-toast-id]');
328
+ const newest = toastNodes[toastNodes.length - 1] ?? null;
329
+ const focusable = getFocusable(newest) ?? newest ?? el;
330
+ focusable.focus();
331
+ };
332
+ document.addEventListener('keydown', handleKeyDown);
333
+ return () => {
334
+ document.removeEventListener('keydown', handleKeyDown);
335
+ };
336
+ }, [hasToasts, getFocusable]);
337
+
195
338
  const posStyle =
196
339
  position === 'topEnd'
197
340
  ? styles.topEnd
@@ -208,6 +351,8 @@ export function ToastViewport({
208
351
  ref={viewportRef}
209
352
  role="region"
210
353
  aria-label="Notifications"
354
+ aria-modal={false}
355
+ tabIndex={-1}
211
356
  // popover="manual" promotes to the top layer (above dialogs).
212
357
  // Omitted inside dialogs where the viewport is already in a top layer.
213
358
  popover={isTopLayer ? 'manual' : undefined}
@@ -223,6 +368,7 @@ export function ToastViewport({
223
368
  return (
224
369
  <div
225
370
  key={entry.id}
371
+ data-toast-id={entry.id}
226
372
  {...stylex.props(
227
373
  styles.toastWrapper,
228
374
  isExiting && styles.toastWrapperExiting,
@@ -34,13 +34,13 @@ export const docs = {
34
34
  },
35
35
  {
36
36
  name: 'onPressedChange',
37
- type: '(isPressed: boolean) => void',
38
- description: 'Called when pressed state should change. Ignored when inside a group.',
37
+ type: '(isPressed: boolean, event: MouseEvent) => void',
38
+ description: 'Called when pressed state should change. Receives the next state and the click event; call event.preventDefault() to skip pressedChangeAction. Ignored when inside a group.',
39
39
  },
40
40
  {
41
41
  name: 'pressedChangeAction',
42
42
  type: '(isPressed: boolean) => void | Promise<void>',
43
- description: 'Action handler for API- or navigation-backed toggles, run in a transition. Shows an optimistic pressed state immediately and a (debounced) spinner while pending; interruptible by re-clicks.',
43
+ description: 'Action handler for API- or navigation-backed toggles, run in a transition. Shows an optimistic pressed state immediately and a spinner while pending; the button stays interruptible by re-clicks.',
44
44
  },
45
45
  {
46
46
  name: 'size',
@@ -74,6 +74,12 @@ export const docs = {
74
74
  },
75
75
  ],
76
76
  },
77
+ {
78
+ name: 'isIconOnly',
79
+ type: 'boolean',
80
+ description: 'When true, renders as a square icon-only button with `label` as the aria-label and an automatic tooltip from the label.',
81
+ default: 'false',
82
+ },
77
83
  {
78
84
  name: 'pressedIcon',
79
85
  type: 'ReactNode',
@@ -9,9 +9,9 @@
9
9
  */
10
10
 
11
11
  import {describe, it, expect, vi} from 'vitest';
12
- import {render, screen, act, fireEvent, waitFor} from '@testing-library/react';
12
+ import {render, screen, act, fireEvent} from '@testing-library/react';
13
13
  import userEvent from '@testing-library/user-event';
14
- import {useState} from 'react';
14
+ import {useState, type MouseEvent} from 'react';
15
15
  import {ToggleButton} from './ToggleButton';
16
16
  import {ToggleButtonGroup} from './ToggleButtonGroup';
17
17
 
@@ -88,7 +88,7 @@ describe('ToggleButton', () => {
88
88
  );
89
89
 
90
90
  await user.click(screen.getByRole('button'));
91
- expect(handleChange).toHaveBeenCalledWith(true);
91
+ expect(handleChange).toHaveBeenCalledWith(true, expect.anything());
92
92
  });
93
93
 
94
94
  it('calls onPressedChange with false when clicking pressed button', async () => {
@@ -103,7 +103,7 @@ describe('ToggleButton', () => {
103
103
  );
104
104
 
105
105
  await user.click(screen.getByRole('button'));
106
- expect(handleChange).toHaveBeenCalledWith(false);
106
+ expect(handleChange).toHaveBeenCalledWith(false, expect.anything());
107
107
  });
108
108
 
109
109
  it('renders pressedIcon when pressed', () => {
@@ -193,7 +193,7 @@ describe('ToggleButton', () => {
193
193
  expect(screen.getByTestId('bold-toggle')).toBeInTheDocument();
194
194
  });
195
195
 
196
- it('shows the optimistic pressed state immediately, before any spinner', async () => {
196
+ it('shows the optimistic pressed state and stays interruptible while pending', async () => {
197
197
  const user = userEvent.setup();
198
198
  let resolveAction: (() => void) | undefined;
199
199
  const pressedChangeAction = vi.fn(
@@ -217,12 +217,13 @@ describe('ToggleButton', () => {
217
217
 
218
218
  await user.click(button);
219
219
 
220
- // The optimistic state flips immediately. The spinner is debounced, so the
221
- // button is not disabled or aria-busy yet — it stays interruptible.
220
+ // The optimistic state flips immediately and the spinner shows via
221
+ // aria-busy, but the button is never disabled — it stays clickable so the
222
+ // action can be interrupted by another click.
222
223
  expect(pressedChangeAction).toHaveBeenCalledWith(true);
223
224
  expect(button).toHaveAttribute('aria-pressed', 'true');
225
+ expect(button).toHaveAttribute('aria-busy', 'true');
224
226
  expect(button).not.toBeDisabled();
225
- expect(button).not.toHaveAttribute('aria-busy', 'true');
226
227
 
227
228
  // Settle the action so the pending transition doesn't leak into later tests.
228
229
  await act(async () => {
@@ -231,7 +232,7 @@ describe('ToggleButton', () => {
231
232
  });
232
233
  });
233
234
 
234
- it('shows a loading spinner once the action stays pending past the delay', async () => {
235
+ it('clears the loading state once the action settles', async () => {
235
236
  const user = userEvent.setup();
236
237
  let resolveAction: (() => void) | undefined;
237
238
  const pressedChangeAction = vi.fn(
@@ -253,18 +254,15 @@ describe('ToggleButton', () => {
253
254
  const button = screen.getByRole('button', {name: 'Favorite'});
254
255
  await user.click(button);
255
256
 
256
- // The optimistic state shows immediately; the spinner is debounced and
257
- // appears only after the action stays pending past the delay window.
258
- expect(button).toHaveAttribute('aria-pressed', 'true');
259
- await waitFor(() => expect(button).toBeDisabled());
260
257
  expect(button).toHaveAttribute('aria-busy', 'true');
258
+ expect(button).not.toBeDisabled();
261
259
 
262
260
  await act(async () => {
263
261
  resolveAction?.();
264
262
  await Promise.resolve();
265
263
  });
266
- expect(button).not.toBeDisabled();
267
264
  expect(button).not.toHaveAttribute('aria-busy', 'true');
265
+ expect(button).not.toBeDisabled();
268
266
  });
269
267
 
270
268
  it('interrupts an in-flight action on re-click (true -> false -> true)', async () => {
@@ -290,9 +288,8 @@ describe('ToggleButton', () => {
290
288
  const button = screen.getByRole('button', {name: 'Favorite'});
291
289
 
292
290
  // Each click derives the next state from the optimistic (in-progress)
293
- // value, so rapid clicks toggle rather than being dropped. fireEvent keeps
294
- // the clicks within the spinner debounce window so the button stays
295
- // interruptible.
291
+ // value, so rapid clicks toggle rather than being dropped. The button is
292
+ // never disabled while pending, so every click lands and interrupts.
296
293
  await act(async () => {
297
294
  fireEvent.click(button);
298
295
  });
@@ -335,9 +332,33 @@ describe('ToggleButton', () => {
335
332
  const button = screen.getByRole('button', {name: 'Favorite'});
336
333
  await user.click(button);
337
334
 
338
- expect(onPressedChange).toHaveBeenCalledWith(true);
335
+ expect(onPressedChange).toHaveBeenCalledWith(true, expect.anything());
339
336
  expect(pressedChangeAction).toHaveBeenCalledWith(true);
340
337
  });
338
+
339
+ it('skips pressedChangeAction when onPressedChange calls preventDefault', async () => {
340
+ const user = userEvent.setup();
341
+ const pressedChangeAction = vi.fn();
342
+ const onPressedChange = vi.fn(
343
+ (_next: boolean, event: MouseEvent<HTMLButtonElement>) => {
344
+ event.preventDefault();
345
+ },
346
+ );
347
+
348
+ render(
349
+ <ToggleButton
350
+ label="Favorite"
351
+ isPressed={false}
352
+ onPressedChange={onPressedChange}
353
+ pressedChangeAction={pressedChangeAction}
354
+ />,
355
+ );
356
+
357
+ await user.click(screen.getByRole('button', {name: 'Favorite'}));
358
+
359
+ expect(onPressedChange).toHaveBeenCalledWith(true, expect.anything());
360
+ expect(pressedChangeAction).not.toHaveBeenCalled();
361
+ });
341
362
  });
342
363
 
343
364
  // =============================================================================
@@ -20,14 +20,7 @@
20
20
  * - /packages/cli/templates/blocks/components/ToggleButton/ (showcase blocks)
21
21
  */
22
22
 
23
- import React, {
24
- useCallback,
25
- useEffect,
26
- useOptimistic,
27
- useState,
28
- useTransition,
29
- type ReactNode,
30
- } from 'react';
23
+ import React, {useOptimistic, type ReactNode} from 'react';
31
24
  import * as stylex from '@stylexjs/stylex';
32
25
  import {colorVars, fontWeightVars} from '../theme/tokens.stylex';
33
26
 
@@ -36,37 +29,6 @@ import {useToggleButtonGroup} from './ToggleButtonGroup';
36
29
  import type {BaseProps} from '../BaseProps';
37
30
  import {themeProps} from '../utils/themeProps';
38
31
 
39
- // =============================================================================
40
- // Constants & helpers
41
- // =============================================================================
42
-
43
- /**
44
- * The spinner only appears once the action has been pending for this long.
45
- * A fast action shows the optimistic pressed state immediately with no spinner
46
- * flash, and rapid re-clicks can interrupt the in-flight action before the
47
- * button locks behind the spinner.
48
- */
49
- const PENDING_SPINNER_DELAY_MS = 150;
50
-
51
- /**
52
- * Returns `true` only once `active` has stayed `true` for `delayMs`.
53
- * Used to debounce the loading spinner so the optimistic state shows first.
54
- */
55
- function useDelayed(active: boolean, delayMs: number): boolean {
56
- const [delayed, setDelayed] = useState(false);
57
- useEffect(() => {
58
- if (!active) {
59
- return undefined;
60
- }
61
- const timer = setTimeout(() => setDelayed(true), delayMs);
62
- return () => {
63
- clearTimeout(timer);
64
- setDelayed(false);
65
- };
66
- }, [active, delayMs]);
67
- return active && delayed;
68
- }
69
-
70
32
  // =============================================================================
71
33
  // Styles
72
34
  // =============================================================================
@@ -122,23 +84,24 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
122
84
  isPressed?: boolean;
123
85
 
124
86
  /**
125
- * Called when the pressed state should change.
87
+ * Called when the pressed state should change. Receives the next pressed
88
+ * state and the originating click event. Call `event.preventDefault()` to
89
+ * opt out of running `pressedChangeAction` (e.g. to handle the toggle
90
+ * entirely in this callback).
126
91
  * When used inside ToggleButtonGroup, this is handled by the group
127
92
  * and this prop is ignored.
128
93
  */
129
- onPressedChange?: (isPressed: boolean) => void;
94
+ onPressedChange?: (
95
+ isPressed: boolean,
96
+ event: React.MouseEvent<HTMLButtonElement>,
97
+ ) => void;
130
98
 
131
99
  /**
132
100
  * Action handler for API- or navigation-backed toggles, run inside a
133
- * transition. The button shows a loading spinner while the action is
134
- * pending — whether it returns a promise or synchronously triggers a
101
+ * transition by Button. The button shows a loading spinner while the action
102
+ * is pending — whether it returns a promise or synchronously triggers a
135
103
  * suspending update (e.g. a router navigation that suspends on data).
136
104
  *
137
- * Because it runs in a transition, the toggle is *interruptible*: clicking
138
- * again while an action is pending starts a new transition with the next
139
- * optimistic state, so the action reflects the latest intent rather than
140
- * being dropped.
141
- *
142
105
  * @example
143
106
  * ```
144
107
  * <ToggleButton
@@ -272,54 +235,52 @@ export function ToggleButton({
272
235
  const size = sizeProp ?? group?.size ?? 'md';
273
236
  const isDisabled = group?.isDisabled ?? isDisabledProp;
274
237
 
275
- // Track the pressed state optimistically. While an action is pending, the
276
- // button reflects the intended (optimistic) state immediately, and a click
277
- // mid-flight derives its next state from this value — so rapid toggles read
278
- // true -> false -> true rather than stalling on the last committed value.
238
+ // Track the pressed state optimistically so the button reflects the intended
239
+ // state immediately while an async action is pending. The optimistic update
240
+ // is applied inside Button's clickAction transition (below), satisfying
241
+ // React's "useOptimistic must run in a transition" rule.
279
242
  const [optimisticPressed, setOptimisticPressed] =
280
243
  useOptimistic(committedPressed);
281
244
  const isPressed = optimisticPressed;
282
245
 
246
+ // Next state derives from the *optimistic* value, not the committed one, so a
247
+ // re-click while an action is pending reads the in-flight intent and toggles
248
+ // (true -> false -> true) rather than recomputing from the stale committed
249
+ // state. handleClick and clickAction share this so the callback and the
250
+ // optimistic update always agree within a single click.
251
+ const nextPressed = !isPressed;
252
+
283
253
  const resolvedIcon = isPressed && pressedIcon ? pressedIcon : icon;
284
254
 
285
- // Run the toggle inside a transition. The action is interruptible: clicking
286
- // again while it is pending starts a fresh transition with the next
287
- // optimistic state instead of being dropped, so there is no re-entry guard.
288
- // Both onPressedChange and pressedChangeAction run inside the transition,
289
- // which means a synchronous-but-suspending handler (e.g. a router navigation
290
- // that suspends on data) also drives the pending state — not just promises.
291
- const [isPending, startTransition] = useTransition();
292
- // Debounce the spinner so a fast action shows the optimistic state without a
293
- // spinner flash, and rapid re-clicks can interrupt before the button locks.
294
- const showSpinner = useDelayed(isPending, PENDING_SPINNER_DELAY_MS);
295
- const isLoadingState = isLoading || showSpinner;
296
-
297
- const handleClick = useCallback(() => {
255
+ // Synchronous part of the toggle. Button calls onClick before clickAction and
256
+ // skips clickAction when the event is defaultPrevented, so calling
257
+ // preventDefault() inside onPressedChange opts out of pressedChangeAction.
258
+ const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
298
259
  if (isDisabled) {
299
260
  return;
300
261
  }
301
-
302
262
  if (group && value != null) {
303
263
  // Group mode delegates selection to the group; no async-action path.
304
264
  group.toggle(value);
265
+ event.preventDefault();
305
266
  return;
306
267
  }
307
-
308
- const newState = !optimisticPressed;
309
- startTransition(async () => {
310
- setOptimisticPressed(newState);
311
- onPressedChangeProp?.(newState);
312
- await pressedChangeAction?.(newState);
313
- });
314
- }, [
315
- isDisabled,
316
- group,
317
- value,
318
- optimisticPressed,
319
- onPressedChangeProp,
320
- pressedChangeAction,
321
- setOptimisticPressed,
322
- ]);
268
+ onPressedChangeProp?.(nextPressed, event);
269
+ };
270
+
271
+ // Async part of the toggle, run inside Button's transition (so the optimistic
272
+ // update is valid and Button's isPending drives the pending spinner). Skipped
273
+ // automatically by Button when handleClick called preventDefault, and never
274
+ // wired in group mode (where there is no async action). Button is rendered
275
+ // interruptible, so it stays clickable while pending and a re-click starts a
276
+ // fresh transition that interrupts the previous one.
277
+ const clickAction =
278
+ group && value != null
279
+ ? undefined
280
+ : async () => {
281
+ setOptimisticPressed(nextPressed);
282
+ await pressedChangeAction?.(nextPressed);
283
+ };
323
284
 
324
285
  // isIconOnly prop is the source of truth for icon-only rendering.
325
286
  const labelContent =
@@ -352,7 +313,8 @@ export function ToggleButton({
352
313
  variant="ghost"
353
314
  size={size}
354
315
  isDisabled={isDisabled}
355
- isLoading={isLoadingState}
316
+ isLoading={isLoading}
317
+ isInterruptible
356
318
  isIconOnly={isIconOnly}
357
319
  aria-pressed={isPressed}
358
320
  icon={resolvedIcon}
@@ -363,6 +325,7 @@ export function ToggleButton({
363
325
  xstyle={[isPressed ? pressedStyles.background : undefined, xstyle]}
364
326
  style={style}
365
327
  onClick={handleClick}
328
+ clickAction={clickAction}
366
329
  {...props}>
367
330
  {labelContent}
368
331
  </Button>
@@ -70,6 +70,12 @@ export const docs = {
70
70
  description: 'Disables the input and all token interactions.',
71
71
  default: 'false',
72
72
  },
73
+ {
74
+ name: 'disabledMessage',
75
+ type: 'string',
76
+ description:
77
+ 'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
78
+ },
73
79
  {
74
80
  name: 'status',
75
81
  type: 'InputStatus',
@@ -191,6 +197,7 @@ export const docs = {
191
197
  {guidance: false, description: 'Don\u2019t use Tokenizer for single-item selection \u2014 use Typeahead instead. Tokenizer is for building sets of two or more items.'},
192
198
  {guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer \u2014 use the default token style for visual consistency across the set.'},
193
199
  {guidance: false, description: 'Don\u2019t hide the label \u2014 every Tokenizer needs a visible label so users understand what they are selecting. Use isLabelHidden only when surrounding context makes the purpose obvious.'},
200
+ {guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
194
201
  ],
195
202
  anatomy: [
196
203
  {name: 'Label', required: true, description: 'The visible text above the input describing what the user is selecting. Also used as the accessible name.'},
@@ -270,6 +277,12 @@ export const docsZh = {
270
277
  description: '\u7981\u7528\u8f93\u5165\u6846\u548c\u6240\u6709\u6807\u8bb0\u4ea4\u4e92\u3002',
271
278
  default: 'false',
272
279
  },
280
+ {
281
+ name: 'disabledMessage',
282
+ type: 'string',
283
+ description:
284
+ 'Explains why the tokenizer is disabled. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the input focusable via aria-disabled (input stays blocked). Use this instead of wrapping a disabled Tokenizer in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
285
+ },
273
286
  {
274
287
  name: 'status',
275
288
  type: 'InputStatus',
@@ -380,6 +393,7 @@ export const docsZh = {
380
393
  {guidance: false, description: 'Don\u2019t use Tokenizer for single-item selection \u2014 use Typeahead instead. Tokenizer is for building sets of two or more items.'},
381
394
  {guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer \u2014 use the default token style for visual consistency across the set.'},
382
395
  {guidance: false, description: 'Don\u2019t hide the label \u2014 every Tokenizer needs a visible label so users understand what they are selecting. Use isLabelHidden only when surrounding context makes the purpose obvious.'},
396
+ {guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
383
397
  ],
384
398
  anatomy: [
385
399
  {name: 'Label', required: true, description: 'The visible text above the input describing what the user is selecting. Also used as the accessible name.'},
@@ -406,6 +420,7 @@ export const docsDense = {
406
420
  {guidance: false, description: 'Don\u2019t use for single-item selection \u2014 use Typeahead instead.'},
407
421
  {guidance: false, description: 'Avoid custom token colors \u2014 default style for consistency.'},
408
422
  {guidance: false, description: 'Don\u2019t hide the label unless context makes purpose obvious.'},
423
+ {guidance: false, description: 'Wrap a disabled Tokenizer in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.'},
409
424
  ],
410
425
  },
411
426
  propDescriptions: {