@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
@@ -89,18 +89,18 @@ export const docs = {
89
89
  },
90
90
  usage: {
91
91
  description:
92
- 'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled \u2014 picking relative for recency, absolute for precision, or auto to let the component decide.',
92
+ 'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled; picking relative for recency, absolute for precision, or auto to let the component decide.',
93
93
  bestPractices: [
94
- {guidance: true, description: 'Use the auto format in feeds and lists so recent items show \u201c2 hours ago\u201d and older items show the full date automatically.'},
95
- {guidance: true, description: 'Keep formatting consistent within the same list or table \u2014 mixing relative and absolute timestamps in the same column confuses scanning.'},
94
+ {guidance: true, description: 'Use the auto format in feeds and lists so recent items show "2 hours ago" and older items show the full date automatically.'},
95
+ {guidance: true, description: 'Keep formatting consistent within the same list or table; mixing relative and absolute timestamps in the same column confuses scanning.'},
96
96
  {guidance: true, description: 'Enable isTimezoneShown when the audience spans multiple time zones, like a global team calendar or audit log.'},
97
97
  {guidance: true, description: 'Use isLive for active dashboards or real-time feeds so the relative time stays accurate without a page refresh.'},
98
- {guidance: false, description: 'Don\u2019t display raw Unix timestamps or ISO strings to users \u2014 always pass them through Timestamp to get a human-readable format.'},
99
- {guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI \u2014 they are meant for developer tools, logs, and machine-readable contexts.'},
100
- {guidance: false, description: 'Don\u2019t disable the tooltip on relative timestamps \u2014 users expect to hover for the full date when they see \u201c3 hours ago\u201d.'},
98
+ {guidance: false, description: 'Don\'t display raw Unix timestamps or ISO strings to users; always pass them through Timestamp to get a human-readable format.'},
99
+ {guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI; they are meant for developer tools, logs, and machine-readable contexts.'},
100
+ {guidance: false, description: 'Don\'t disable the tooltip on relative timestamps; users expect to hover for the full date when they see "3 hours ago".'},
101
101
  ],
102
102
  anatomy: [
103
- {name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like \u201c2 hours ago\u201d or \u201cMar 21, 2025\u201d.'},
103
+ {name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like "2 hours ago" or "Mar 21, 2025".'},
104
104
  {name: 'Tooltip', required: false, description: 'A hover card showing the full absolute date and time when the display is relative.'},
105
105
  ],
106
106
  },
@@ -122,18 +122,18 @@ export const docsZh = {
122
122
  },
123
123
  usage: {
124
124
  description:
125
- 'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled \u2014 picking relative for recency, absolute for precision, or auto to let the component decide.',
125
+ 'Timestamp formats a date or time value into human-readable text. Use it to show when something was created, updated, or is scheduled; picking relative for recency, absolute for precision, or auto to let the component decide.',
126
126
  bestPractices: [
127
- {guidance: true, description: 'Use the auto format in feeds and lists so recent items show \u201c2 hours ago\u201d and older items show the full date automatically.'},
128
- {guidance: true, description: 'Keep formatting consistent within the same list or table \u2014 mixing relative and absolute timestamps in the same column confuses scanning.'},
127
+ {guidance: true, description: 'Use the auto format in feeds and lists so recent items show "2 hours ago" and older items show the full date automatically.'},
128
+ {guidance: true, description: 'Keep formatting consistent within the same list or table; mixing relative and absolute timestamps in the same column confuses scanning.'},
129
129
  {guidance: true, description: 'Enable isTimezoneShown when the audience spans multiple time zones, like a global team calendar or audit log.'},
130
130
  {guidance: true, description: 'Use isLive for active dashboards or real-time feeds so the relative time stays accurate without a page refresh.'},
131
- {guidance: false, description: 'Don\u2019t display raw Unix timestamps or ISO strings to users \u2014 always pass them through Timestamp to get a human-readable format.'},
132
- {guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI \u2014 they are meant for developer tools, logs, and machine-readable contexts.'},
133
- {guidance: false, description: 'Don\u2019t disable the tooltip on relative timestamps \u2014 users expect to hover for the full date when they see \u201c3 hours ago\u201d.'},
131
+ {guidance: false, description: 'Don\'t display raw Unix timestamps or ISO strings to users; always pass them through Timestamp to get a human-readable format.'},
132
+ {guidance: false, description: 'Avoid system_date or system_time formats in user-facing UI; they are meant for developer tools, logs, and machine-readable contexts.'},
133
+ {guidance: false, description: 'Don\'t disable the tooltip on relative timestamps; users expect to hover for the full date when they see "3 hours ago".'},
134
134
  ],
135
135
  anatomy: [
136
- {name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like \u201c2 hours ago\u201d or \u201cMar 21, 2025\u201d.'},
136
+ {name: 'Formatted text', required: true, description: 'The rendered date, time, or relative label like "2 hours ago" or "Mar 21, 2025".'},
137
137
  {name: 'Tooltip', required: false, description: 'A hover card showing the full absolute date and time when the display is relative.'},
138
138
  ],
139
139
  },
@@ -144,15 +144,15 @@ export const docsDense = {
144
144
  description: 'formatted timestamp with relative/absolute/auto modes, live updates, timezone display',
145
145
  usage: {
146
146
  description:
147
- 'Timestamp formats a date or time into readable text. Use for creation dates, update times, or schedules \u2014 relative for recency, absolute for precision, auto to switch automatically.',
147
+ 'Timestamp formats a date or time into readable text. Use for creation dates, update times, or schedules; relative for recency, absolute for precision, auto to switch automatically.',
148
148
  bestPractices: [
149
- {guidance: true, description: 'Auto format in feeds and lists \u2014 recent shows relative, older shows date_time.'},
149
+ {guidance: true, description: 'Auto format in feeds and lists; recent shows relative, older shows date_time.'},
150
150
  {guidance: true, description: 'Consistent formatting within the same list or table column.'},
151
151
  {guidance: true, description: 'isTimezoneShown for multi-timezone audiences.'},
152
152
  {guidance: true, description: 'isLive for dashboards so relative time stays current.'},
153
- {guidance: false, description: 'Don\u2019t show raw Unix timestamps or ISO strings \u2014 always use Timestamp.'},
154
- {guidance: false, description: 'Avoid system_* formats in user-facing UI \u2014 those are for dev tools and logs.'},
155
- {guidance: false, description: 'Don\u2019t disable tooltip on relative timestamps \u2014 users expect the full date on hover.'},
153
+ {guidance: false, description: 'Don\'t show raw Unix timestamps or ISO strings; always use Timestamp.'},
154
+ {guidance: false, description: 'Avoid system_* formats in user-facing UI; those are for dev tools and logs.'},
155
+ {guidance: false, description: 'Don\'t disable tooltip on relative timestamps; users expect the full date on hover.'},
156
156
  ],
157
157
  },
158
158
  propDescriptions: {
@@ -167,6 +167,20 @@ export function Toast({
167
167
  };
168
168
  }, [autoHideDuration, startTimer]);
169
169
 
170
+ // Pause the auto-hide timer while the window is not focused, so a toast
171
+ // doesn't silently expire while the user is in another window or tab.
172
+ useEffect(() => {
173
+ if (!isAutoHide) {
174
+ return;
175
+ }
176
+ window.addEventListener('blur', pauseTimer);
177
+ window.addEventListener('focus', resumeTimer);
178
+ return () => {
179
+ window.removeEventListener('blur', pauseTimer);
180
+ window.removeEventListener('focus', resumeTimer);
181
+ };
182
+ }, [isAutoHide, pauseTimer, resumeTimer]);
183
+
170
184
  const handleDismiss = useCallback(() => {
171
185
  onDismiss('manual');
172
186
  }, [onDismiss]);
@@ -0,0 +1,214 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ToastViewport.test.tsx
5
+ * @input Uses vitest, @testing-library/react, ToastViewport + useToast
6
+ * @output Unit tests for toast keyboard reach and focus management
7
+ * @position Testing; validates ToastViewport.tsx + Toast.tsx focus behavior
8
+ *
9
+ * SYNC: When ToastViewport.tsx or Toast.tsx focus handling changes, update these tests
10
+ */
11
+
12
+ import {describe, it, expect, vi, beforeAll} from 'vitest';
13
+ import {render, screen, fireEvent, act} from '@testing-library/react';
14
+ import React from 'react';
15
+ import {ToastViewport} from './ToastViewport';
16
+ import {useToast} from './useToast';
17
+ import type {ToastOptions} from './types';
18
+
19
+ // Popover API is not implemented in jsdom.
20
+ beforeAll(() => {
21
+ if (typeof HTMLElement.prototype.showPopover === 'undefined') {
22
+ HTMLElement.prototype.showPopover = vi.fn();
23
+ HTMLElement.prototype.hidePopover = vi.fn();
24
+ }
25
+ });
26
+
27
+ // Module-level constant default props (avoids unstable-default-props lint).
28
+ const EMPTY_OPTIONS: ToastOptions = {body: 'placeholder'};
29
+
30
+ function ShowToastButton({
31
+ options = EMPTY_OPTIONS,
32
+ triggerLabel = 'Trigger',
33
+ }: {
34
+ options?: ToastOptions;
35
+ triggerLabel?: string;
36
+ }) {
37
+ const toast = useToast();
38
+ return (
39
+ <button type="button" onClick={() => toast(options)}>
40
+ {triggerLabel}
41
+ </button>
42
+ );
43
+ }
44
+
45
+ const INFO_A: ToastOptions = {body: 'Toast A'};
46
+ const INFO_B: ToastOptions = {body: 'Toast B'};
47
+ const AUTO_TOAST: ToastOptions = {body: 'Auto toast', autoHideDuration: 3000};
48
+
49
+ function renderViewport(children: React.ReactNode) {
50
+ return render(<ToastViewport isTopLayer={false}>{children}</ToastViewport>);
51
+ }
52
+
53
+ // Fire the transition-end that ToastViewport listens for to unmount an
54
+ // exiting toast (jsdom does not run CSS transitions).
55
+ function completeExit(toastId: string) {
56
+ const node = document.querySelector<HTMLElement>(
57
+ `[data-toast-id="${toastId}"]`,
58
+ );
59
+ if (node) {
60
+ fireEvent.transitionEnd(node, {propertyName: 'grid-template-rows'});
61
+ }
62
+ }
63
+
64
+ describe('ToastViewport keyboard reach + focus', () => {
65
+ it('F6 moves focus into the newest toast', () => {
66
+ renderViewport(
67
+ <ShowToastButton options={INFO_A} triggerLabel="Trigger A" />,
68
+ );
69
+ const trigger = screen.getByText('Trigger A');
70
+ trigger.focus();
71
+ act(() => {
72
+ fireEvent.click(trigger);
73
+ });
74
+
75
+ expect(screen.getByText('Toast A')).toBeInTheDocument();
76
+ expect(document.activeElement).toBe(trigger);
77
+
78
+ act(() => {
79
+ fireEvent.keyDown(document, {key: 'F6'});
80
+ });
81
+
82
+ // Focus lands on the dismiss button of the newest toast.
83
+ const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
84
+ expect(document.activeElement).toBe(dismiss);
85
+ });
86
+
87
+ it('dismissing a focused toast moves focus to a remaining toast, not body', () => {
88
+ renderViewport(
89
+ <>
90
+ <ShowToastButton options={INFO_A} triggerLabel="Trigger A" />
91
+ <ShowToastButton options={INFO_B} triggerLabel="Trigger B" />
92
+ </>,
93
+ );
94
+ act(() => {
95
+ fireEvent.click(screen.getByText('Trigger A'));
96
+ });
97
+ act(() => {
98
+ fireEvent.click(screen.getByText('Trigger B'));
99
+ });
100
+
101
+ const dismissButtons = screen.getAllByRole('button', {
102
+ name: 'Dismiss notification',
103
+ });
104
+ expect(dismissButtons).toHaveLength(2);
105
+
106
+ // Focus the first toast's dismiss button, then dismiss it.
107
+ const firstToast = document.querySelectorAll('[data-toast-id]')[0];
108
+ const firstToastId = firstToast.getAttribute('data-toast-id')!;
109
+ const firstDismiss = firstToast.querySelector<HTMLElement>(
110
+ 'button[aria-label="Dismiss notification"]',
111
+ )!;
112
+ firstDismiss.focus();
113
+ expect(document.activeElement).toBe(firstDismiss);
114
+
115
+ act(() => {
116
+ fireEvent.click(firstDismiss);
117
+ });
118
+ act(() => {
119
+ completeExit(firstToastId);
120
+ });
121
+
122
+ // Focus must NOT drop to <body>; it moves to the remaining toast.
123
+ expect(document.activeElement).not.toBe(document.body);
124
+ expect(document.activeElement?.getAttribute('aria-label')).toBe(
125
+ 'Dismiss notification',
126
+ );
127
+ });
128
+
129
+ it('dismissing the last focused toast restores the previously-focused element', () => {
130
+ renderViewport(
131
+ <ShowToastButton options={INFO_A} triggerLabel="Trigger A" />,
132
+ );
133
+ const trigger = screen.getByText('Trigger A');
134
+ trigger.focus();
135
+ act(() => {
136
+ fireEvent.click(trigger);
137
+ });
138
+
139
+ // F6 into the toast, remembering the trigger as the prior focus.
140
+ act(() => {
141
+ fireEvent.keyDown(document, {key: 'F6'});
142
+ });
143
+ const dismiss = screen.getByRole('button', {name: 'Dismiss notification'});
144
+ expect(document.activeElement).toBe(dismiss);
145
+
146
+ const toastId = document
147
+ .querySelector('[data-toast-id]')!
148
+ .getAttribute('data-toast-id')!;
149
+
150
+ act(() => {
151
+ fireEvent.click(dismiss);
152
+ });
153
+ act(() => {
154
+ completeExit(toastId);
155
+ });
156
+
157
+ // No toasts left — focus returns to the element focused before F6.
158
+ expect(document.activeElement).toBe(trigger);
159
+ });
160
+ });
161
+
162
+ describe('Toast blur timer pause', () => {
163
+ it('pauses the auto-hide timer while the window is blurred', () => {
164
+ vi.useFakeTimers();
165
+ try {
166
+ renderViewport(
167
+ <ShowToastButton options={AUTO_TOAST} triggerLabel="Trigger Auto" />,
168
+ );
169
+ act(() => {
170
+ fireEvent.click(screen.getByText('Trigger Auto'));
171
+ });
172
+ expect(screen.getByText('Auto toast')).toBeInTheDocument();
173
+
174
+ // Window loses focus — timer should pause.
175
+ act(() => {
176
+ window.dispatchEvent(new Event('blur'));
177
+ });
178
+ act(() => {
179
+ vi.advanceTimersByTime(5000);
180
+ });
181
+ // Still present because the timer was paused while blurred.
182
+ expect(screen.getByText('Auto toast')).toBeInTheDocument();
183
+
184
+ // Window regains focus — timer resumes and the toast dismisses.
185
+ act(() => {
186
+ window.dispatchEvent(new Event('focus'));
187
+ });
188
+ act(() => {
189
+ vi.advanceTimersByTime(5000);
190
+ });
191
+ const toastId = document
192
+ .querySelector('[data-toast-id]')
193
+ ?.getAttribute('data-toast-id');
194
+ if (toastId) {
195
+ act(() => {
196
+ completeExit(toastId);
197
+ });
198
+ }
199
+ expect(screen.queryByText('Auto toast')).not.toBeInTheDocument();
200
+ } finally {
201
+ vi.useRealTimers();
202
+ }
203
+ });
204
+ });
205
+
206
+ describe('ToastViewport region ARIA', () => {
207
+ it('exposes the notifications region without a prohibited aria-modal', () => {
208
+ renderViewport(<ShowToastButton />);
209
+ const region = screen.getByRole('region', {name: 'Notifications'});
210
+ // aria-modal is only valid on role="dialog"/"alertdialog"; a region must
211
+ // not declare it (axe: aria-allowed-attr).
212
+ expect(region).not.toHaveAttribute('aria-modal');
213
+ });
214
+ });
@@ -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,48 @@ 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 = container.querySelectorAll<HTMLElement>(
140
+ INTERACTIVE_SELECTORS,
141
+ );
142
+ for (const candidate of candidates) {
143
+ if (
144
+ candidate.getAttribute('tabindex') === '-1' ||
145
+ candidate.hasAttribute('disabled') ||
146
+ candidate.getAttribute('aria-disabled') === 'true'
147
+ ) {
148
+ continue;
149
+ }
150
+ return candidate;
151
+ }
152
+ return null;
153
+ },
154
+ [],
155
+ );
156
+
111
157
  const addToast = useCallback((entry: ToastEntry) => {
112
158
  setToasts(prev => {
113
159
  const {uniqueID, collisionBehavior = 'overwrite'} = entry.options;
@@ -124,21 +170,44 @@ export function ToastViewport({
124
170
  });
125
171
  }, []);
126
172
 
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);
173
+ const removeToast = useCallback((id: string, reason: ToastDismissReason) => {
174
+ const entry = toastsRef.current.find(t => t.id === id);
175
+ if (entry) {
176
+ entry.options.onHide?.(reason);
177
+ }
178
+ // If focus currently lives inside the toast being dismissed, remember
179
+ // that its removal must hand focus off to a neighbor (or the element
180
+ // focused before the user entered the viewport) rather than <body>.
181
+ const el = viewportRef.current;
182
+ const active = document.activeElement;
183
+ const dismissedNode =
184
+ el?.querySelector<HTMLElement>(`[data-toast-id="${id}"]`) ?? null;
185
+ if (
186
+ dismissedNode &&
187
+ active instanceof Node &&
188
+ dismissedNode.contains(active)
189
+ ) {
190
+ focusHandoffIdRef.current = id;
191
+ // Pick the neighbor to receive focus while the DOM is still intact:
192
+ // prefer the next toast, then the previous, else restore.
193
+ const remaining = toastsRef.current.filter(t => t.id !== id);
194
+ if (remaining.length > 0) {
195
+ const dismissedIndex = toastsRef.current.findIndex(t => t.id === id);
196
+ const next =
197
+ toastsRef.current[dismissedIndex + 1] ??
198
+ toastsRef.current[dismissedIndex - 1];
199
+ pendingFocusRef.current = next ? next.id : 'restore';
200
+ } else {
201
+ pendingFocusRef.current = 'restore';
132
202
  }
133
- setExitingIds(prev => {
134
- if (prev.has(id)) {
135
- return prev;
136
- }
137
- return new Set(prev).add(id);
138
- });
139
- },
140
- [],
141
- );
203
+ }
204
+ setExitingIds(prev => {
205
+ if (prev.has(id)) {
206
+ return prev;
207
+ }
208
+ return new Set(prev).add(id);
209
+ });
210
+ }, []);
142
211
 
143
212
  const handleExited = useCallback((id: string) => {
144
213
  setExitingIds(prev => {
@@ -152,6 +221,43 @@ export function ToastViewport({
152
221
  setToasts(prev => prev.filter(t => t.id !== id));
153
222
  }, []);
154
223
 
224
+ // After a dismissed toast unmounts, hand focus off so it never falls to
225
+ // <body>. Runs in a layout effect once the toast list no longer contains
226
+ // the dismissed toast.
227
+ useLayoutEffect(() => {
228
+ const handoffId = focusHandoffIdRef.current;
229
+ const target = pendingFocusRef.current;
230
+ if (handoffId == null || target == null) {
231
+ return;
232
+ }
233
+ // Wait until the dismissed toast is actually gone from the list.
234
+ if (toasts.some(t => t.id === handoffId)) {
235
+ return;
236
+ }
237
+ focusHandoffIdRef.current = null;
238
+ pendingFocusRef.current = null;
239
+ const el = viewportRef.current;
240
+ if (target !== 'restore' && el) {
241
+ const nextNode = el.querySelector<HTMLElement>(
242
+ `[data-toast-id="${target}"]`,
243
+ );
244
+ const focusable = getFocusable(nextNode) ?? nextNode;
245
+ if (focusable) {
246
+ focusable.focus();
247
+ return;
248
+ }
249
+ }
250
+ // No remaining toast to receive focus — restore the previously-focused
251
+ // element if it's still connected, else fall back to the container.
252
+ const prev = prevFocusRef.current;
253
+ prevFocusRef.current = null;
254
+ if (prev && prev.isConnected) {
255
+ prev.focus();
256
+ } else if (el) {
257
+ el.focus();
258
+ }
259
+ }, [toasts, getFocusable]);
260
+
155
261
  const findByUniqueID = useCallback((uid: string) => {
156
262
  return toastsRef.current.find(t => t.options.uniqueID === uid);
157
263
  }, []);
@@ -177,7 +283,6 @@ export function ToastViewport({
177
283
  }
178
284
 
179
285
  // Show the popover on mount so it enters the top layer
180
- const viewportRef = useRef<HTMLDivElement>(null);
181
286
  useEffect(() => {
182
287
  if (!isTopLayer) {
183
288
  return;
@@ -192,6 +297,45 @@ export function ToastViewport({
192
297
  }
193
298
  }, [isTopLayer]);
194
299
 
300
+ // F6 jumps focus into the toast viewport — the standard "go to
301
+ // notifications" affordance. Focus the first control in the newest toast,
302
+ // or the viewport container if none. Toasts are non-modal, so this only
303
+ // moves focus in; Shift+Tab / Escape let focus leave naturally.
304
+ const hasToasts = toasts.length > 0;
305
+ useEffect(() => {
306
+ if (!hasToasts) {
307
+ return;
308
+ }
309
+ const handleKeyDown = (e: KeyboardEvent) => {
310
+ if (e.key !== 'F6') {
311
+ return;
312
+ }
313
+ const el = viewportRef.current;
314
+ if (!el) {
315
+ return;
316
+ }
317
+ // Already inside the viewport — nothing to do.
318
+ const active = document.activeElement;
319
+ if (active instanceof Node && el.contains(active)) {
320
+ return;
321
+ }
322
+ e.preventDefault();
323
+ // Remember where focus was so it can be restored on dismiss.
324
+ if (active instanceof HTMLElement) {
325
+ prevFocusRef.current = active;
326
+ }
327
+ // Newest toast is the last one rendered in the DOM.
328
+ const toastNodes = el.querySelectorAll<HTMLElement>('[data-toast-id]');
329
+ const newest = toastNodes[toastNodes.length - 1] ?? null;
330
+ const focusable = getFocusable(newest) ?? newest ?? el;
331
+ focusable.focus();
332
+ };
333
+ document.addEventListener('keydown', handleKeyDown);
334
+ return () => {
335
+ document.removeEventListener('keydown', handleKeyDown);
336
+ };
337
+ }, [hasToasts, getFocusable]);
338
+
195
339
  const posStyle =
196
340
  position === 'topEnd'
197
341
  ? styles.topEnd
@@ -208,6 +352,7 @@ export function ToastViewport({
208
352
  ref={viewportRef}
209
353
  role="region"
210
354
  aria-label="Notifications"
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,
@@ -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',
@@ -172,7 +178,7 @@ export const docs = {
172
178
  name: 'xstyle',
173
179
  type: 'StyleXStyles',
174
180
  description:
175
- 'StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value \u2014 not an inline style object like style={{}}.',
181
+ 'StyleX styles for layout customization (margins, positioning, sizing). Must be a stylex.create() value; not an inline style object like style={{}}.',
176
182
  },
177
183
  ],
178
184
  theming: {
@@ -184,13 +190,14 @@ export const docs = {
184
190
  description:
185
191
  'Tokenizer is a multi-select input that lets users search, select, and manage multiple items displayed as removable chips. Use it when users need to build a set of selections from a searchable data source, like adding team members, applying tags, or choosing filters.',
186
192
  bestPractices: [
187
- {guidance: true, description: 'Write a placeholder that tells users what they can search for \u2014 "Search people\u2026" or "Add tags\u2026" \u2014 so the input is not a blank mystery.'},
193
+ {guidance: true, description: 'Write a placeholder that tells users what they can search for, such as "Search people..." or "Add tags...", so the input is not a blank mystery.'},
188
194
  {guidance: true, description: 'Set maxEntries when the number of selections should be bounded, like limiting a review to 5 approvers.'},
189
195
  {guidance: true, description: 'Use hasCreate for free-form tagging where users need to enter values that do not exist in the search source.'},
190
196
  {guidance: true, description: 'Show validation status with the status prop so users know immediately when a selection is missing or invalid.'},
191
- {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
- {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
- {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.'},
197
+ {guidance: false, description: 'Don\'t use Tokenizer for single-item selection; use Typeahead instead. Tokenizer is for building sets of two or more items.'},
198
+ {guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer; use the default token style for visual consistency across the set.'},
199
+ {guidance: false, description: 'Don\'t hide the label; 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',
@@ -361,7 +374,7 @@ export const docsZh = {
361
374
  name: 'xstyle',
362
375
  type: 'StyleXStyles',
363
376
  description:
364
- '\u7528\u4e8e\u5e03\u5c40\u81ea\u5b9a\u4e49\u7684 StyleX \u6837\u5f0f\uff08\u5916\u8fb9\u8ddd\u3001\u5b9a\u4f4d\u3001\u5c3a\u5bf8\uff09\u3002\u5fc5\u987b\u662f stylex.create() \u7684\u503c \u2014 \u4e0d\u80fd\u662f\u5185\u8054\u6837\u5f0f\u5bf9\u8c61\u5982 style={{}}\u3002',
377
+ '\u7528\u4e8e\u5e03\u5c40\u81ea\u5b9a\u4e49\u7684 StyleX \u6837\u5f0f\uff08\u5916\u8fb9\u8ddd\u3001\u5b9a\u4f4d\u3001\u5c3a\u5bf8\uff09\u3002\u5fc5\u987b\u662f stylex.create() \u7684\u503c; \u4e0d\u80fd\u662f\u5185\u8054\u6837\u5f0f\u5bf9\u8c61\u5982 style={{}}\u3002',
365
378
  },
366
379
  ],
367
380
  theming: {
@@ -373,13 +386,14 @@ export const docsZh = {
373
386
  description:
374
387
  'Tokenizer is a multi-select input that lets users search, select, and manage multiple items displayed as removable chips. Use it when users need to build a set of selections from a searchable data source, like adding team members, applying tags, or choosing filters.',
375
388
  bestPractices: [
376
- {guidance: true, description: 'Write a placeholder that tells users what they can search for \u2014 "Search people\u2026" or "Add tags\u2026" \u2014 so the input is not a blank mystery.'},
389
+ {guidance: true, description: 'Write a placeholder that tells users what they can search for, such as "Search people..." or "Add tags...", so the input is not a blank mystery.'},
377
390
  {guidance: true, description: 'Set maxEntries when the number of selections should be bounded, like limiting a review to 5 approvers.'},
378
391
  {guidance: true, description: 'Use hasCreate for free-form tagging where users need to enter values that do not exist in the search source.'},
379
392
  {guidance: true, description: 'Show validation status with the status prop so users know immediately when a selection is missing or invalid.'},
380
- {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
- {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
- {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.'},
393
+ {guidance: false, description: 'Don\'t use Tokenizer for single-item selection; use Typeahead instead. Tokenizer is for building sets of two or more items.'},
394
+ {guidance: false, description: 'Avoid applying custom colors to individual tokens inside a Tokenizer; use the default token style for visual consistency across the set.'},
395
+ {guidance: false, description: 'Don\'t hide the label; 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.'},
@@ -399,13 +413,14 @@ export const docsDense = {
399
413
  description:
400
414
  'Multi-select input for searching and selecting multiple items as removable chips. Use for team members, tags, filters, or any set built from a searchable source.',
401
415
  bestPractices: [
402
- {guidance: true, description: 'Placeholder that communicates what to search \u2014 "Search people\u2026" not blank.'},
416
+ {guidance: true, description: 'Placeholder that communicates what to search, such as "Search people..." rather than blank.'},
403
417
  {guidance: true, description: 'maxEntries when selections are bounded (e.g. 5 approvers max).'},
404
418
  {guidance: true, description: 'hasCreate for free-form tagging with values not in the source.'},
405
419
  {guidance: true, description: 'status prop for immediate validation feedback.'},
406
- {guidance: false, description: 'Don\u2019t use for single-item selection \u2014 use Typeahead instead.'},
407
- {guidance: false, description: 'Avoid custom token colors \u2014 default style for consistency.'},
408
- {guidance: false, description: 'Don\u2019t hide the label unless context makes purpose obvious.'},
420
+ {guidance: false, description: 'Don\'t use for single-item selection; use Typeahead instead.'},
421
+ {guidance: false, description: 'Avoid custom token colors; default style for consistency.'},
422
+ {guidance: false, description: 'Don\'t 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: {