@astryxdesign/core 0.1.9 → 0.2.0-canary.0e01213

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 (625) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/Avatar/Avatar.d.ts +42 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +190 -59
  5. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
  8. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  9. package/dist/AvatarGroup/AvatarGroup.js +58 -6
  10. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  11. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  12. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  13. package/dist/Banner/Banner.js +2 -2
  14. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  15. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  16. package/dist/Breadcrumbs/index.d.ts +1 -0
  17. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/index.js +2 -1
  19. package/dist/Button/Button.d.ts +1 -1
  20. package/dist/Button/Button.d.ts.map +1 -1
  21. package/dist/Button/Button.js +6 -4
  22. package/dist/Calendar/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar/Calendar.js +50 -9
  24. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  25. package/dist/Calendar/dayCellUtils.js +2 -2
  26. package/dist/Calendar/styles.d.ts +30 -31
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +30 -31
  29. package/dist/Card/Card.js +1 -1
  30. package/dist/Carousel/Carousel.d.ts.map +1 -1
  31. package/dist/Carousel/Carousel.js +22 -12
  32. package/dist/Center/Center.d.ts +20 -3
  33. package/dist/Center/Center.d.ts.map +1 -1
  34. package/dist/Center/Center.js +10 -2
  35. package/dist/Chat/ChatComposerInput.js +1 -1
  36. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  37. package/dist/Chat/ChatDictationButton.js +3 -1
  38. package/dist/Chat/ChatLayout.d.ts +7 -0
  39. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  40. package/dist/Chat/ChatLayout.js +16 -5
  41. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  42. package/dist/Chat/ChatMessageBubble.js +8 -8
  43. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  44. package/dist/Chat/ChatToolCalls.js +10 -3
  45. package/dist/Chat/useTriggerMenu.js +2 -2
  46. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  47. package/dist/CheckboxInput/CheckboxInput.js +6 -6
  48. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  49. package/dist/CheckboxList/CheckboxListItem.js +19 -4
  50. package/dist/Citation/Citation.d.ts +22 -1
  51. package/dist/Citation/Citation.d.ts.map +1 -1
  52. package/dist/Citation/Citation.js +20 -6
  53. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  54. package/dist/CodeBlock/CodeBlock.js +16 -10
  55. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  56. package/dist/Collapsible/Collapsible.js +3 -1
  57. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  58. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  59. package/dist/CommandPalette/CommandPalette.js +42 -3
  60. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  61. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  62. package/dist/ContextMenu/ContextMenu.js +23 -5
  63. package/dist/ContextMenu/index.d.ts +1 -0
  64. package/dist/ContextMenu/index.d.ts.map +1 -1
  65. package/dist/ContextMenu/index.js +3 -1
  66. package/dist/DateInput/DateInput.d.ts +10 -2
  67. package/dist/DateInput/DateInput.d.ts.map +1 -1
  68. package/dist/DateInput/DateInput.js +33 -19
  69. package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
  70. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  71. package/dist/DateRangeInput/DateRangeInput.js +23 -20
  72. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  73. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  74. package/dist/Divider/Divider.d.ts +2 -2
  75. package/dist/Divider/Divider.d.ts.map +1 -1
  76. package/dist/Divider/Divider.js +13 -0
  77. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  78. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  79. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  80. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +18 -12
  81. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  82. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +42 -64
  83. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuItem.js +5 -2
  85. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
  86. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  87. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
  88. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  89. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  90. package/dist/DropdownMenu/DropdownMenuRadioItem.js +10 -3
  91. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  92. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  93. package/dist/DropdownMenu/DropdownMenuSubMenu.js +435 -0
  94. package/dist/DropdownMenu/index.d.ts +10 -0
  95. package/dist/DropdownMenu/index.d.ts.map +1 -1
  96. package/dist/DropdownMenu/index.js +13 -0
  97. package/dist/DropdownMenu/menuItemHover.d.ts +24 -0
  98. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  99. package/dist/DropdownMenu/menuItemHover.js +34 -0
  100. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  101. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  102. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  103. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  104. package/dist/DropdownMenu/renderDropdownItems.js +27 -10
  105. package/dist/Field/Field.d.ts +3 -1
  106. package/dist/Field/Field.d.ts.map +1 -1
  107. package/dist/Field/Field.js +4 -1
  108. package/dist/Field/FieldLabel.d.ts +2 -1
  109. package/dist/Field/FieldLabel.d.ts.map +1 -1
  110. package/dist/Field/FieldLabel.js +9 -5
  111. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  112. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  113. package/dist/Field/inputStyles.stylex.js +1 -0
  114. package/dist/FieldStatus/FieldStatus.d.ts +10 -0
  115. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  116. package/dist/FieldStatus/FieldStatus.js +51 -5
  117. package/dist/FileInput/FileInput.d.ts +10 -2
  118. package/dist/FileInput/FileInput.d.ts.map +1 -1
  119. package/dist/FileInput/FileInput.js +93 -65
  120. package/dist/Heading/Heading.d.ts.map +1 -1
  121. package/dist/Heading/Heading.js +3 -2
  122. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  123. package/dist/HoverCard/useHoverCard.js +10 -4
  124. package/dist/Icon/Icon.d.ts +15 -1
  125. package/dist/Icon/Icon.d.ts.map +1 -1
  126. package/dist/Icon/Icon.js +19 -3
  127. package/dist/Item/Item.d.ts +16 -3
  128. package/dist/Item/Item.d.ts.map +1 -1
  129. package/dist/Item/Item.js +31 -6
  130. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  131. package/dist/Layer/layerAnimations.stylex.js +10 -2
  132. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  133. package/dist/Lightbox/Lightbox.js +34 -15
  134. package/dist/List/List.d.ts.map +1 -1
  135. package/dist/List/List.js +6 -3
  136. package/dist/List/ListItem.d.ts +11 -1
  137. package/dist/List/ListItem.d.ts.map +1 -1
  138. package/dist/List/ListItem.js +2 -0
  139. package/dist/Markdown/Markdown.d.ts.map +1 -1
  140. package/dist/Markdown/Markdown.js +7 -0
  141. package/dist/MetadataList/MetadataList.d.ts +1 -1
  142. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  143. package/dist/MetadataList/MetadataList.js +4 -2
  144. package/dist/MultiSelector/MultiSelector.d.ts +9 -1
  145. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  146. package/dist/MultiSelector/MultiSelector.js +87 -45
  147. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  148. package/dist/NumberInput/NumberInput.d.ts +10 -2
  149. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  150. package/dist/NumberInput/NumberInput.js +22 -17
  151. package/dist/Outline/Outline.js +2 -2
  152. package/dist/Pagination/Pagination.d.ts.map +1 -1
  153. package/dist/Pagination/Pagination.js +11 -3
  154. package/dist/PowerSearch/PowerSearch.d.ts +9 -2
  155. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  156. package/dist/PowerSearch/PowerSearch.js +2 -0
  157. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  158. package/dist/ProgressBar/ProgressBar.js +6 -1
  159. package/dist/RadioList/RadioList.d.ts +2 -2
  160. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  161. package/dist/RadioList/RadioListItem.js +5 -5
  162. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  163. package/dist/Resizable/ResizeHandle.js +49 -19
  164. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  165. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  166. package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
  167. package/dist/Selector/Selector.d.ts +8 -0
  168. package/dist/Selector/Selector.d.ts.map +1 -1
  169. package/dist/Selector/Selector.js +72 -19
  170. package/dist/SideNav/SideNav.d.ts.map +1 -1
  171. package/dist/SideNav/SideNav.js +5 -5
  172. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  173. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  174. package/dist/SideNav/SideNavItem.d.ts +1 -1
  175. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  176. package/dist/SideNav/SideNavItem.js +12 -10
  177. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  178. package/dist/Skeleton/Skeleton.js +2 -2
  179. package/dist/Slider/Slider.d.ts.map +1 -1
  180. package/dist/Slider/Slider.js +87 -34
  181. package/dist/Spinner/Spinner.d.ts +3 -2
  182. package/dist/Spinner/Spinner.d.ts.map +1 -1
  183. package/dist/Spinner/Spinner.js +11 -2
  184. package/dist/Switch/Switch.d.ts +8 -1
  185. package/dist/Switch/Switch.d.ts.map +1 -1
  186. package/dist/Switch/Switch.js +171 -91
  187. package/dist/TabList/Tab.js +2 -2
  188. package/dist/TabList/TabList.d.ts +2 -11
  189. package/dist/TabList/TabList.d.ts.map +1 -1
  190. package/dist/TabList/TabList.js +5 -9
  191. package/dist/TabList/TabListContext.d.ts +0 -5
  192. package/dist/TabList/TabListContext.d.ts.map +1 -1
  193. package/dist/TabList/TabListContext.js +0 -5
  194. package/dist/TabList/TabMenu.js +1 -1
  195. package/dist/Table/BaseTable.d.ts.map +1 -1
  196. package/dist/Table/BaseTable.js +30 -4
  197. package/dist/Table/TableCell.js +3 -3
  198. package/dist/Table/TableHeaderCell.js +3 -3
  199. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  200. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  201. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +18 -3
  202. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  203. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +58 -30
  204. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  205. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  206. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  207. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
  208. package/dist/Table/plugins/tree/useTableTreeData.d.ts +11 -0
  209. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  210. package/dist/Table/plugins/tree/useTableTreeData.js +65 -25
  211. package/dist/Table/types.d.ts +32 -0
  212. package/dist/Table/types.d.ts.map +1 -1
  213. package/dist/Text/Text.d.ts +10 -1
  214. package/dist/Text/Text.d.ts.map +1 -1
  215. package/dist/Text/Text.js +25 -2
  216. package/dist/Text/index.d.ts +2 -2
  217. package/dist/Text/index.d.ts.map +1 -1
  218. package/dist/TextArea/TextArea.d.ts +10 -1
  219. package/dist/TextArea/TextArea.d.ts.map +1 -1
  220. package/dist/TextArea/TextArea.js +119 -52
  221. package/dist/TextInput/TextInput.d.ts +10 -2
  222. package/dist/TextInput/TextInput.d.ts.map +1 -1
  223. package/dist/TextInput/TextInput.js +20 -17
  224. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  225. package/dist/Thumbnail/Thumbnail.js +51 -45
  226. package/dist/TimeInput/TimeInput.d.ts +10 -2
  227. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  228. package/dist/TimeInput/TimeInput.js +17 -17
  229. package/dist/Timestamp/Timestamp.d.ts +31 -3
  230. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  231. package/dist/Timestamp/Timestamp.js +48 -77
  232. package/dist/Timestamp/formatInstant.d.ts +41 -0
  233. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  234. package/dist/Timestamp/formatInstant.js +177 -0
  235. package/dist/Timestamp/index.d.ts +1 -0
  236. package/dist/Timestamp/index.d.ts.map +1 -1
  237. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  238. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  239. package/dist/Timestamp/tooltipEntries.js +163 -0
  240. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  241. package/dist/Toast/ToastViewport.js +61 -9
  242. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  243. package/dist/ToggleButton/ToggleButton.js +3 -1
  244. package/dist/Token/Token.d.ts +34 -4
  245. package/dist/Token/Token.d.ts.map +1 -1
  246. package/dist/Token/Token.js +85 -45
  247. package/dist/Token/TokenLink.d.ts +62 -0
  248. package/dist/Token/TokenLink.d.ts.map +1 -0
  249. package/dist/Token/TokenLink.js +76 -0
  250. package/dist/Token/index.d.ts +1 -1
  251. package/dist/Token/index.d.ts.map +1 -1
  252. package/dist/Tokenizer/Tokenizer.d.ts +9 -2
  253. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  254. package/dist/Tokenizer/Tokenizer.js +4 -2
  255. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  256. package/dist/Tooltip/useTooltip.js +17 -1
  257. package/dist/TopNav/TopNavItem.d.ts +3 -1
  258. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  259. package/dist/TopNav/TopNavItem.js +30 -3
  260. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  261. package/dist/TopNav/TopNavMegaMenu.js +9 -3
  262. package/dist/TreeList/TreeList.d.ts.map +1 -1
  263. package/dist/TreeList/TreeList.js +1 -0
  264. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  265. package/dist/TreeList/TreeListBranches.js +8 -6
  266. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  267. package/dist/TreeList/TreeListItem.js +32 -14
  268. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  269. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  270. package/dist/Typeahead/BaseTypeahead.js +22 -11
  271. package/dist/Typeahead/Typeahead.d.ts +9 -2
  272. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  273. package/dist/Typeahead/Typeahead.js +11 -2
  274. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  275. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  276. package/dist/Typeahead/TypeaheadItem.js +20 -10
  277. package/dist/astryx.css +115 -35
  278. package/dist/astryx.umd.js +51 -51
  279. package/dist/astryx.umd.js.map +4 -4
  280. package/dist/hooks/containerReveal.pool.stylex.d.ts +281 -0
  281. package/dist/hooks/containerReveal.pool.stylex.d.ts.map +1 -0
  282. package/dist/hooks/containerReveal.pool.stylex.js +281 -0
  283. package/dist/hooks/index.d.ts +4 -0
  284. package/dist/hooks/index.d.ts.map +1 -1
  285. package/dist/hooks/index.js +3 -1
  286. package/dist/hooks/isRtlElement.d.ts +18 -0
  287. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  288. package/dist/hooks/isRtlElement.js +27 -0
  289. package/dist/hooks/useAnnounce.d.ts +3 -0
  290. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  291. package/dist/hooks/useAnnounce.js +39 -1
  292. package/dist/hooks/useContainerReveal.d.ts +83 -0
  293. package/dist/hooks/useContainerReveal.d.ts.map +1 -0
  294. package/dist/hooks/useContainerReveal.js +123 -0
  295. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  296. package/dist/hooks/useFocusTrap.js +52 -11
  297. package/dist/hooks/useGridFocus.d.ts +6 -1
  298. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  299. package/dist/hooks/useGridFocus.js +12 -9
  300. package/dist/hooks/useHotkeys.d.ts +9 -0
  301. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  302. package/dist/hooks/useHotkeys.js +9 -0
  303. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  304. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  305. package/dist/hooks/useInputStatusIcon.js +169 -0
  306. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  307. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  308. package/dist/hooks/useKeyboardHint.js +4 -2
  309. package/dist/hooks/useListFocus.d.ts +39 -1
  310. package/dist/hooks/useListFocus.d.ts.map +1 -1
  311. package/dist/hooks/useListFocus.js +54 -9
  312. package/dist/i18n/InternationalizationContext.d.ts +1 -0
  313. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  314. package/dist/i18n/InternationalizationContext.js +3 -0
  315. package/dist/i18n/InternationalizationProvider.d.ts +8 -1
  316. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  317. package/dist/i18n/InternationalizationProvider.js +5 -1
  318. package/dist/i18n/getLocaleDirection.d.ts +18 -0
  319. package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
  320. package/dist/i18n/getLocaleDirection.js +29 -0
  321. package/dist/i18n/index.d.ts +6 -0
  322. package/dist/i18n/index.d.ts.map +1 -1
  323. package/dist/i18n/index.js +7 -1
  324. package/dist/i18n/useDirection.d.ts +2 -0
  325. package/dist/i18n/useDirection.d.ts.map +1 -0
  326. package/dist/i18n/useDirection.js +27 -0
  327. package/dist/theme/defineTheme.d.ts +6 -0
  328. package/dist/theme/defineTheme.d.ts.map +1 -1
  329. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  330. package/dist/theme/derivedVarRegistry.js +13 -0
  331. package/dist/theme/expandColorScale.d.ts +19 -2
  332. package/dist/theme/expandColorScale.d.ts.map +1 -1
  333. package/dist/theme/expandColorScale.js +38 -10
  334. package/dist/theme/index.d.ts +3 -3
  335. package/dist/theme/index.d.ts.map +1 -1
  336. package/dist/theme/types.d.ts +39 -2
  337. package/dist/theme/types.d.ts.map +1 -1
  338. package/dist/utils/index.d.ts +1 -0
  339. package/dist/utils/index.d.ts.map +1 -1
  340. package/dist/utils/index.js +2 -1
  341. package/dist/utils/plainDate.d.ts +17 -0
  342. package/dist/utils/plainDate.d.ts.map +1 -1
  343. package/dist/utils/plainDate.js +23 -4
  344. package/dist/utils/rtlStyles.d.ts +24 -0
  345. package/dist/utils/rtlStyles.d.ts.map +1 -0
  346. package/dist/utils/rtlStyles.js +27 -0
  347. package/locales/en.json +120 -0
  348. package/locales/pseudo.json +90 -0
  349. package/package.json +8 -3
  350. package/src/Avatar/Avatar.doc.mjs +50 -3
  351. package/src/Avatar/Avatar.test.tsx +218 -0
  352. package/src/Avatar/Avatar.tsx +285 -79
  353. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  354. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  355. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
  356. package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
  357. package/src/AvatarGroup/AvatarGroup.tsx +58 -3
  358. package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
  359. package/src/Banner/Banner.tsx +6 -6
  360. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  361. package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
  362. package/src/Breadcrumbs/index.ts +4 -0
  363. package/src/Button/Button.doc.mjs +0 -4
  364. package/src/Button/Button.test.tsx +16 -0
  365. package/src/Button/Button.tsx +6 -4
  366. package/src/Calendar/Calendar.test.tsx +170 -20
  367. package/src/Calendar/Calendar.tsx +73 -16
  368. package/src/Calendar/dayCellUtils.test.ts +18 -18
  369. package/src/Calendar/dayCellUtils.ts +2 -2
  370. package/src/Calendar/styles.ts +33 -39
  371. package/src/Card/Card.tsx +1 -1
  372. package/src/Carousel/Carousel.tsx +25 -6
  373. package/src/Center/Center.doc.mjs +38 -0
  374. package/src/Center/Center.test.tsx +75 -0
  375. package/src/Center/Center.tsx +37 -2
  376. package/src/Chat/ChatComposer.doc.mjs +2 -2
  377. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  378. package/src/Chat/ChatComposerInput.tsx +2 -2
  379. package/src/Chat/ChatDictationButton.tsx +7 -1
  380. package/src/Chat/ChatLayout.test.tsx +57 -0
  381. package/src/Chat/ChatLayout.tsx +26 -5
  382. package/src/Chat/ChatMessageBubble.tsx +15 -10
  383. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  384. package/src/Chat/ChatToolCalls.tsx +9 -2
  385. package/src/Chat/useTriggerMenu.tsx +1 -1
  386. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  387. package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
  388. package/src/CheckboxInput/CheckboxInput.tsx +21 -6
  389. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  390. package/src/CheckboxList/CheckboxList.test.tsx +94 -3
  391. package/src/CheckboxList/CheckboxListItem.tsx +17 -10
  392. package/src/Citation/Citation.doc.mjs +4 -3
  393. package/src/Citation/Citation.test.tsx +97 -0
  394. package/src/Citation/Citation.tsx +43 -11
  395. package/src/CodeBlock/CodeBlock.test.tsx +17 -0
  396. package/src/CodeBlock/CodeBlock.tsx +38 -11
  397. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  398. package/src/Collapsible/Collapsible.test.tsx +7 -0
  399. package/src/Collapsible/Collapsible.tsx +16 -6
  400. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  401. package/src/CommandPalette/CommandPalette.tsx +41 -2
  402. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  403. package/src/ContextMenu/ContextMenu.test.tsx +59 -2
  404. package/src/ContextMenu/ContextMenu.tsx +22 -11
  405. package/src/ContextMenu/index.ts +5 -0
  406. package/src/DateInput/DateInput.doc.mjs +25 -0
  407. package/src/DateInput/DateInput.test.tsx +230 -0
  408. package/src/DateInput/DateInput.tsx +51 -29
  409. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  410. package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
  411. package/src/DateRangeInput/DateRangeInput.tsx +42 -30
  412. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  413. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  414. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  415. package/src/Divider/Divider.test.tsx +32 -8
  416. package/src/Divider/Divider.tsx +14 -1
  417. package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -2
  418. package/src/DropdownMenu/DropdownMenu.test.tsx +95 -0
  419. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  420. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +49 -66
  421. package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
  422. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
  423. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
  424. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +19 -4
  425. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +84 -2
  426. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  427. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +680 -0
  428. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +569 -0
  429. package/src/DropdownMenu/index.ts +16 -0
  430. package/src/DropdownMenu/menuItemHover.ts +40 -0
  431. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  432. package/src/DropdownMenu/renderDropdownItems.tsx +40 -14
  433. package/src/Field/Field.doc.mjs +4 -0
  434. package/src/Field/Field.tsx +14 -9
  435. package/src/Field/FieldLabel.test.tsx +25 -0
  436. package/src/Field/FieldLabel.tsx +12 -3
  437. package/src/Field/inputStyles.stylex.ts +2 -0
  438. package/src/FieldStatus/FieldStatus.doc.mjs +1 -0
  439. package/src/FieldStatus/FieldStatus.test.tsx +133 -0
  440. package/src/FieldStatus/FieldStatus.tsx +61 -4
  441. package/src/FileInput/FileInput.doc.mjs +23 -2
  442. package/src/FileInput/FileInput.test.tsx +104 -13
  443. package/src/FileInput/FileInput.tsx +106 -79
  444. package/src/Heading/Heading.tsx +2 -1
  445. package/src/HoverCard/useHoverCard.tsx +15 -3
  446. package/src/Icon/Icon.doc.mjs +13 -0
  447. package/src/Icon/Icon.test.tsx +89 -0
  448. package/src/Icon/Icon.tsx +51 -3
  449. package/src/Item/Item.doc.mjs +5 -0
  450. package/src/Item/Item.test.tsx +88 -0
  451. package/src/Item/Item.tsx +57 -11
  452. package/src/Layer/layerAnimations.stylex.ts +23 -0
  453. package/src/Lightbox/Lightbox.test.tsx +33 -0
  454. package/src/Lightbox/Lightbox.tsx +30 -10
  455. package/src/List/List.test.tsx +17 -29
  456. package/src/List/List.tsx +6 -1
  457. package/src/List/ListItem.doc.mjs +12 -0
  458. package/src/List/ListItem.tsx +13 -0
  459. package/src/Markdown/Markdown.test.tsx +21 -0
  460. package/src/Markdown/Markdown.tsx +4 -0
  461. package/src/MetadataList/MetadataList.test.tsx +24 -0
  462. package/src/MetadataList/MetadataList.tsx +6 -2
  463. package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
  464. package/src/MultiSelector/MultiSelector.test.tsx +436 -0
  465. package/src/MultiSelector/MultiSelector.tsx +133 -67
  466. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  467. package/src/NumberInput/NumberInput.doc.mjs +21 -0
  468. package/src/NumberInput/NumberInput.test.tsx +71 -0
  469. package/src/NumberInput/NumberInput.tsx +35 -26
  470. package/src/Outline/Outline.test.tsx +12 -12
  471. package/src/Outline/Outline.tsx +2 -2
  472. package/src/Pagination/Pagination.tsx +11 -3
  473. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  474. package/src/PowerSearch/PowerSearch.test.tsx +23 -0
  475. package/src/PowerSearch/PowerSearch.tsx +10 -1
  476. package/src/ProgressBar/ProgressBar.test.tsx +34 -0
  477. package/src/ProgressBar/ProgressBar.tsx +16 -1
  478. package/src/RadioList/RadioList.doc.mjs +6 -0
  479. package/src/RadioList/RadioList.test.tsx +26 -4
  480. package/src/RadioList/RadioList.tsx +2 -2
  481. package/src/RadioList/RadioListItem.tsx +12 -5
  482. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  483. package/src/Resizable/ResizeHandle.tsx +70 -20
  484. package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
  485. package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
  486. package/src/Selector/Selector.doc.mjs +15 -0
  487. package/src/Selector/Selector.test.tsx +400 -0
  488. package/src/Selector/Selector.tsx +95 -23
  489. package/src/SideNav/SideNav.tsx +1 -8
  490. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  491. package/src/SideNav/SideNavItem.tsx +5 -3
  492. package/src/Skeleton/Skeleton.test.tsx +16 -0
  493. package/src/Skeleton/Skeleton.tsx +13 -1
  494. package/src/Slider/Slider.doc.mjs +6 -0
  495. package/src/Slider/Slider.test.tsx +240 -12
  496. package/src/Slider/Slider.tsx +96 -23
  497. package/src/Spinner/Spinner.test.tsx +10 -8
  498. package/src/Spinner/Spinner.tsx +15 -5
  499. package/src/Switch/Switch.doc.mjs +142 -42
  500. package/src/Switch/Switch.test.tsx +111 -0
  501. package/src/Switch/Switch.tsx +149 -32
  502. package/src/TabList/Tab.tsx +2 -2
  503. package/src/TabList/TabList.doc.mjs +0 -6
  504. package/src/TabList/TabList.test.tsx +3 -15
  505. package/src/TabList/TabList.tsx +6 -17
  506. package/src/TabList/TabListContext.ts +0 -6
  507. package/src/TabList/TabMenu.tsx +2 -2
  508. package/src/Table/BaseTable.tsx +34 -2
  509. package/src/Table/Table.doc.mjs +11 -0
  510. package/src/Table/Table.test.tsx +85 -0
  511. package/src/Table/TableCell.tsx +3 -3
  512. package/src/Table/TableHeaderCell.tsx +3 -3
  513. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  514. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
  515. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +48 -21
  516. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  517. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  518. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
  519. package/src/Table/plugins/tree/useTableTreeData.test.tsx +161 -2
  520. package/src/Table/plugins/tree/useTableTreeData.tsx +71 -14
  521. package/src/Table/types.ts +33 -0
  522. package/src/Table/useTableRowExpansion.doc.mjs +55 -2
  523. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  524. package/src/Table/useTableTreeData.doc.mjs +9 -0
  525. package/src/Text/Heading.doc.mjs +3 -3
  526. package/src/Text/Text.doc.mjs +1 -1
  527. package/src/Text/Text.test.tsx +42 -0
  528. package/src/Text/Text.tsx +25 -1
  529. package/src/Text/index.ts +2 -6
  530. package/src/TextArea/TextArea.doc.mjs +23 -2
  531. package/src/TextArea/TextArea.test.tsx +132 -4
  532. package/src/TextArea/TextArea.tsx +161 -67
  533. package/src/TextInput/TextInput.doc.mjs +21 -0
  534. package/src/TextInput/TextInput.test.tsx +150 -0
  535. package/src/TextInput/TextInput.tsx +28 -25
  536. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  537. package/src/Thumbnail/Thumbnail.test.tsx +24 -1
  538. package/src/Thumbnail/Thumbnail.tsx +28 -35
  539. package/src/TimeInput/TimeInput.doc.mjs +21 -0
  540. package/src/TimeInput/TimeInput.test.tsx +23 -0
  541. package/src/TimeInput/TimeInput.tsx +24 -25
  542. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  543. package/src/Timestamp/Timestamp.test.tsx +371 -0
  544. package/src/Timestamp/Timestamp.tsx +96 -93
  545. package/src/Timestamp/formatInstant.ts +218 -0
  546. package/src/Timestamp/index.ts +4 -0
  547. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  548. package/src/Timestamp/tooltipEntries.ts +210 -0
  549. package/src/Toast/ToastViewport.test.tsx +218 -8
  550. package/src/Toast/ToastViewport.tsx +71 -12
  551. package/src/ToggleButton/ToggleButton.test.tsx +24 -0
  552. package/src/ToggleButton/ToggleButton.tsx +19 -1
  553. package/src/Token/Token.test.tsx +166 -15
  554. package/src/Token/Token.tsx +104 -47
  555. package/src/Token/TokenLink.tsx +107 -0
  556. package/src/Token/index.ts +1 -1
  557. package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
  558. package/src/Tokenizer/Tokenizer.test.tsx +23 -0
  559. package/src/Tokenizer/Tokenizer.tsx +14 -2
  560. package/src/Tooltip/Tooltip.test.tsx +46 -0
  561. package/src/Tooltip/useTooltip.tsx +20 -0
  562. package/src/TopNav/TopNav.test.tsx +93 -2
  563. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  564. package/src/TopNav/TopNavItem.tsx +35 -6
  565. package/src/TopNav/TopNavMegaMenu.tsx +26 -2
  566. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  567. package/src/TreeList/TreeList.doc.mjs +6 -0
  568. package/src/TreeList/TreeList.test.tsx +50 -10
  569. package/src/TreeList/TreeList.tsx +5 -0
  570. package/src/TreeList/TreeListBranches.tsx +8 -6
  571. package/src/TreeList/TreeListItem.tsx +33 -11
  572. package/src/Typeahead/BaseTypeahead.tsx +42 -15
  573. package/src/Typeahead/Typeahead.doc.mjs +13 -0
  574. package/src/Typeahead/Typeahead.test.tsx +112 -0
  575. package/src/Typeahead/Typeahead.tsx +17 -1
  576. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  577. package/src/__tests__/babelPluginAddExtensions.test.ts +190 -0
  578. package/src/__tests__/forcedColors.ts +60 -0
  579. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  580. package/src/doc-mjs.d.ts +19 -0
  581. package/src/hooks/containerReveal.pool.stylex.ts +464 -0
  582. package/src/hooks/index.ts +13 -0
  583. package/src/hooks/isRtlElement.ts +27 -0
  584. package/src/hooks/useAnnounce.test.tsx +75 -2
  585. package/src/hooks/useAnnounce.ts +47 -1
  586. package/src/hooks/useContainerReveal.test.tsx +116 -0
  587. package/src/hooks/useContainerReveal.ts +180 -0
  588. package/src/hooks/useFocusTrap.test.tsx +104 -0
  589. package/src/hooks/useFocusTrap.ts +61 -12
  590. package/src/hooks/useGridFocus.doc.mjs +3 -3
  591. package/src/hooks/useGridFocus.test.tsx +41 -0
  592. package/src/hooks/useGridFocus.ts +21 -10
  593. package/src/hooks/useHotkeys.ts +9 -0
  594. package/src/hooks/useInputStatusIcon.test.tsx +221 -0
  595. package/src/hooks/useInputStatusIcon.tsx +235 -0
  596. package/src/hooks/useKeyboardHint.test.tsx +22 -0
  597. package/src/hooks/useKeyboardHint.tsx +6 -2
  598. package/src/hooks/useListFocus.doc.mjs +25 -3
  599. package/src/hooks/useListFocus.test.tsx +153 -1
  600. package/src/hooks/useListFocus.ts +102 -9
  601. package/src/i18n/InternationalizationContext.ts +4 -0
  602. package/src/i18n/InternationalizationProvider.tsx +12 -2
  603. package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
  604. package/src/i18n/__tests__/useDirection.test.tsx +66 -0
  605. package/src/i18n/getLocaleDirection.ts +29 -0
  606. package/src/i18n/index.ts +6 -0
  607. package/src/i18n/useDirection.ts +29 -0
  608. package/src/reset.css +0 -26
  609. package/src/reset.test.ts +60 -0
  610. package/src/theme/defineTheme.ts +6 -0
  611. package/src/theme/derivedVarRegistry.test.ts +1 -0
  612. package/src/theme/derivedVarRegistry.ts +6 -0
  613. package/src/theme/expandColorScale.test.ts +144 -0
  614. package/src/theme/expandColorScale.ts +53 -15
  615. package/src/theme/generateThemeRules.test.ts +42 -0
  616. package/src/theme/index.ts +4 -8
  617. package/src/theme/types.ts +43 -13
  618. package/src/utils/index.ts +2 -0
  619. package/src/utils/plainDate.test.ts +24 -6
  620. package/src/utils/plainDate.ts +18 -4
  621. package/src/utils/rtlStyles.ts +27 -0
  622. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts +0 -9
  623. package/dist/Thumbnail/thumbnail.markers.stylex.d.ts.map +0 -1
  624. package/dist/Thumbnail/thumbnail.markers.stylex.js +0 -14
  625. package/src/Thumbnail/thumbnail.markers.stylex.ts +0 -12
@@ -7,7 +7,9 @@
7
7
  * @input Uses React useCallback; DOM APIs for a singleton live-region pair
8
8
  * @output Exports useAnnounce hook and AnnouncePoliteness type
9
9
  * @position Core a11y hook; provides imperative screen-reader announcements
10
- * via persistently-mounted polite/assertive live regions
10
+ * via persistently-mounted polite/assertive live regions; regions are
11
+ * auto-cleared shortly after announcing so stale status text does not
12
+ * linger in the accessibility tree
11
13
  *
12
14
  * SYNC: When modified, update:
13
15
  * - /packages/core/src/hooks/index.ts
@@ -47,6 +49,40 @@ interface LiveRegions {
47
49
  assertive: HTMLElement;
48
50
  }
49
51
 
52
+ /**
53
+ * How long an announcement stays in its region before being auto-cleared.
54
+ * Long enough for screen readers to pick the message up and finish reading
55
+ * it; clearing afterwards keeps stale status text out of the accessibility
56
+ * tree for users who browse the DOM later. Each announce resets the timer.
57
+ */
58
+ const CLEAR_DELAY_MS = 2000;
59
+
60
+ const clearTimers: {
61
+ [K in AnnouncePoliteness]: ReturnType<typeof setTimeout> | null;
62
+ } = {
63
+ polite: null,
64
+ assertive: null,
65
+ };
66
+
67
+ function cancelScheduledClear(politeness: AnnouncePoliteness): void {
68
+ const timer = clearTimers[politeness];
69
+ if (timer != null) {
70
+ clearTimeout(timer);
71
+ clearTimers[politeness] = null;
72
+ }
73
+ }
74
+
75
+ function scheduleClear(
76
+ politeness: AnnouncePoliteness,
77
+ target: HTMLElement,
78
+ ): void {
79
+ cancelScheduledClear(politeness);
80
+ clearTimers[politeness] = setTimeout(() => {
81
+ clearTimers[politeness] = null;
82
+ target.textContent = '';
83
+ }, CLEAR_DELAY_MS);
84
+ }
85
+
50
86
  // Singleton: the live regions are created ONCE and stay mounted for the
51
87
  // lifetime of the document. This is the crux of reliable announcements — many
52
88
  // screen readers will not announce content injected into a live region that is
@@ -103,6 +139,10 @@ function announceMessage(
103
139
  requestAnimationFrame(() => {
104
140
  target.textContent = message;
105
141
  });
142
+ // Auto-clear so the last message does not linger in the accessibility tree
143
+ // indefinitely. Scheduled per announce, so a newer announcement always
144
+ // resets the countdown (the delay comfortably outlasts the rAF re-set).
145
+ scheduleClear(politeness, target);
106
146
  }
107
147
 
108
148
  /**
@@ -111,6 +151,7 @@ function announceMessage(
111
151
  * so stale status text does not linger in the accessibility tree.
112
152
  */
113
153
  function clearRegion(politeness: AnnouncePoliteness): void {
154
+ cancelScheduledClear(politeness);
114
155
  if (!regions) {
115
156
  return;
116
157
  }
@@ -126,6 +167,9 @@ function clearRegion(politeness: AnnouncePoliteness): void {
126
167
  * The polite and assertive regions are created once on first use and kept
127
168
  * mounted, so announcements are reliable even for messages that appear
128
169
  * immediately (unlike a live region rendered together with its content).
170
+ * Each message is automatically cleared a couple of seconds after being
171
+ * announced, so users browsing the DOM later do not encounter stale status
172
+ * text; announcing again before the clear simply resets the countdown.
129
173
  *
130
174
  * @example
131
175
  * ```
@@ -160,6 +204,8 @@ export function useAnnounce(): AnnounceFn {
160
204
  * @internal
161
205
  */
162
206
  export function __resetLiveRegionsForTest(): void {
207
+ cancelScheduledClear('polite');
208
+ cancelScheduledClear('assertive');
163
209
  if (regions) {
164
210
  regions.polite.remove();
165
211
  regions.assertive.remove();
@@ -0,0 +1,116 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useContainerReveal.test.tsx
5
+ * @input Uses vitest, @testing-library/react, useContainerReveal
6
+ * @output Unit tests for the marker-pool free-list, per-instance scoping, the
7
+ * inert (disabled) path, and the content-option → style-block mapping.
8
+ * @position Testing; validates useContainerReveal.ts and its pool.
9
+ *
10
+ * SYNC: When useContainerReveal.ts changes, update these tests.
11
+ */
12
+
13
+ import {describe, it, expect} from 'vitest';
14
+ import {renderHook, render, act} from '@testing-library/react';
15
+ import {useContainerReveal} from './useContainerReveal';
16
+ import {POOL_SIZE} from './containerReveal.pool.stylex';
17
+
18
+ describe('useContainerReveal', () => {
19
+ it('returns spreadable getter props for container and content', () => {
20
+ const {result} = renderHook(() => useContainerReveal());
21
+ const container = result.current.getContainerProps();
22
+ const content = result.current.getContentRevealProps();
23
+ expect(container).toHaveProperty('className');
24
+ expect(content).toHaveProperty('className');
25
+ expect(typeof container.className).toBe('string');
26
+ expect(typeof content.className).toBe('string');
27
+ });
28
+
29
+ it('is inert when disabled: no marker class, empty content props', () => {
30
+ const {result} = renderHook(() =>
31
+ useContainerReveal({isEnabled: false}),
32
+ );
33
+ expect(result.current.getContainerProps()).toEqual({});
34
+ expect(result.current.getContentRevealProps()).toEqual({});
35
+ });
36
+
37
+ it('gives two concurrently mounted containers DISTINCT marker classes', () => {
38
+ // The core leak-safety property: nested/concurrent containers must never
39
+ // share a marker, or an ancestor container's :hover would reveal a
40
+ // descendant container's content.
41
+ const a = renderHook(() => useContainerReveal());
42
+ const b = renderHook(() => useContainerReveal());
43
+ const classA = a.result.current.getContainerProps().className;
44
+ const classB = b.result.current.getContainerProps().className;
45
+ expect(classA).toBeTruthy();
46
+ expect(classB).toBeTruthy();
47
+ expect(classA).not.toBe(classB);
48
+ a.unmount();
49
+ b.unmount();
50
+ });
51
+
52
+ it('recycles a slot after unmount (free-list release)', () => {
53
+ const first = renderHook(() => useContainerReveal());
54
+ const firstClass = first.result.current.getContainerProps().className;
55
+ first.unmount();
56
+ // A fresh mount should be able to reclaim the just-released slot.
57
+ const second = renderHook(() => useContainerReveal());
58
+ const secondClass = second.result.current.getContainerProps().className;
59
+ expect(secondClass).toBe(firstClass);
60
+ second.unmount();
61
+ });
62
+
63
+ it('reveal and conceal map to different style blocks', () => {
64
+ const {result} = renderHook(() => useContainerReveal());
65
+ const reveal = result.current.getContentRevealProps().className;
66
+ const conceal = result.current.getContentRevealProps({
67
+ isRevealInverted: true,
68
+ }).className;
69
+ expect(reveal).toBeTruthy();
70
+ expect(conceal).toBeTruthy();
71
+ expect(reveal).not.toBe(conceal);
72
+ });
73
+
74
+ it('layout-preserved reveal differs from clipped reveal', () => {
75
+ const {result} = renderHook(() => useContainerReveal());
76
+ const clipped = result.current.getContentRevealProps().className;
77
+ const preserved = result.current.getContentRevealProps({
78
+ isLayoutPreserved: true,
79
+ }).className;
80
+ expect(clipped).not.toBe(preserved);
81
+ });
82
+
83
+ it('assigns distinct markers across a full pool of concurrent containers', () => {
84
+ function Probe({onClass}: {onClass: (c: string) => void}) {
85
+ const {getContainerProps} = useContainerReveal();
86
+ onClass(getContainerProps().className ?? '');
87
+ return null;
88
+ }
89
+ const classes: string[] = [];
90
+ const {unmount} = render(
91
+ <>
92
+ {Array.from({length: POOL_SIZE}, (_, i) => (
93
+ <Probe key={i} onClass={(c) => classes.push(c)} />
94
+ ))}
95
+ </>,
96
+ );
97
+ const unique = new Set(classes.filter(Boolean));
98
+ expect(unique.size).toBe(POOL_SIZE);
99
+ unmount();
100
+ });
101
+
102
+ it('survives repeated mount/unmount without leaking the pool', () => {
103
+ // Mount and unmount a full pool several times; each cycle must still hand
104
+ // out POOL_SIZE distinct markers, proving slots are released.
105
+ for (let cycle = 0; cycle < 3; cycle++) {
106
+ const hooks = Array.from({length: POOL_SIZE}, () =>
107
+ renderHook(() => useContainerReveal()),
108
+ );
109
+ const classes = hooks.map(
110
+ (h) => h.result.current.getContainerProps().className,
111
+ );
112
+ expect(new Set(classes).size).toBe(POOL_SIZE);
113
+ act(() => hooks.forEach((h) => h.unmount()));
114
+ }
115
+ });
116
+ });
@@ -0,0 +1,180 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useContainerReveal.ts
7
+ * @input Uses React, the generated marker pool, useDevWarning
8
+ * @output Exports useContainerReveal — a headless hover/focus reveal primitive
9
+ * @position Core hook. Consumed by any component that reveals (or conceals)
10
+ * content when its container is hovered or focused — e.g. Thumbnail's remove
11
+ * button, TreeList row actions.
12
+ *
13
+ * Gives a container a scoped hover/focus-within trigger that reveals or
14
+ * conceals content inside it, entirely in CSS (no hover state in JS, no
15
+ * re-render on hover). The caller authors NO StyleX: the hook hands out a
16
+ * marker + matching reveal styles from a pre-compiled pool.
17
+ *
18
+ * ACCESSIBILITY (WCAG 2.2 by construction):
19
+ * - Revealed content is visually hidden at rest via the clip recipe, so it
20
+ * stays in the accessibility tree and tab order — never display:none.
21
+ * - Keyboard: revealed on :focus-within, so tabbing in shows it.
22
+ * - Touch: always visible on coarse pointers; never gated behind hover.
23
+ * - Concealed (inverted) content is a mouse-only visual swap: it ignores
24
+ * :focus-within (a keyboard user must never watch content vanish) and stays
25
+ * visible on touch and in the a11y tree.
26
+ * - Motion: honors prefers-reduced-motion.
27
+ *
28
+ * SYNC: When modified, update:
29
+ * - /packages/core/src/hooks/index.ts (export)
30
+ * - containerReveal.pool.stylex.ts (if the slot shape changes)
31
+ */
32
+
33
+ import {useState, useEffect, type CSSProperties} from 'react';
34
+ import * as stylex from '@stylexjs/stylex';
35
+ import {POOL, POOL_SIZE, type RevealSlot} from './containerReveal.pool.stylex';
36
+ import {useDevWarning} from './useDevWarning';
37
+
38
+ // Module-level free-list. Each mounted, enabled useContainerReveal claims a
39
+ // distinct pool slot so that any two CONCURRENTLY mounted containers — in
40
+ // particular a nested container inside another's revealed content — get
41
+ // different markers and cannot leak hover/focus into one another. Slots are
42
+ // returned on unmount. Sibling containers that would only ever collide when
43
+ // the pool is exhausted are harmless (a sibling is never an ancestor), so the
44
+ // pool only needs to cover the number of concurrently mounted containers, not
45
+ // instances over time.
46
+ const claimed: boolean[] = new Array(POOL_SIZE).fill(false);
47
+
48
+ interface Claim {
49
+ /** Pool index to use. */
50
+ index: number;
51
+ /** True when the pool was full and this claim fell back to a shared slot. */
52
+ isExhausted: boolean;
53
+ }
54
+
55
+ function claimSlot(): Claim {
56
+ for (let i = 0; i < POOL_SIZE; i++) {
57
+ if (!claimed[i]) {
58
+ claimed[i] = true;
59
+ return {index: i, isExhausted: false};
60
+ }
61
+ }
62
+ // Exhausted: fall back to slot 0. Safe for siblings (a sibling is never an
63
+ // ancestor); only nesting deeper than POOL_SIZE could reintroduce a leak.
64
+ // Surfaced via useDevWarning so the pool can be grown.
65
+ return {index: 0, isExhausted: true};
66
+ }
67
+
68
+ function releaseSlot(index: number, isExhausted: boolean): void {
69
+ // A fallback claim never owned the slot exclusively; leave it as-is.
70
+ if (!isExhausted) {
71
+ claimed[index] = false;
72
+ }
73
+ }
74
+
75
+ export interface UseContainerRevealOptions {
76
+ /**
77
+ * When false the hook is inert: no marker is applied and content getters
78
+ * return no styles, so content is always shown. Lets a component gate on its
79
+ * own prop (e.g. `revealOn === 'hover'`).
80
+ * @default true
81
+ */
82
+ isEnabled?: boolean;
83
+ }
84
+
85
+ export interface ContentRevealOptions {
86
+ /**
87
+ * Conceal-on-hover instead of reveal-on-hover: content is visible at rest
88
+ * and fades out while the container is hovered. Mouse-only and visual —
89
+ * stays in the a11y tree, ignores focus-within, stays visible on touch.
90
+ * @default false
91
+ */
92
+ isRevealInverted?: boolean;
93
+ /**
94
+ * Reserve the content's layout box while hidden (opacity-only) instead of
95
+ * collapsing it, to avoid layout shift when it appears.
96
+ * @default false
97
+ */
98
+ isLayoutPreserved?: boolean;
99
+ }
100
+
101
+ export interface UseContainerRevealReturn {
102
+ /** Spread onto the container whose hover/focus-within drives the reveal. */
103
+ getContainerProps: () => {className?: string; style?: CSSProperties};
104
+ /** Spread onto each revealed / concealed child. */
105
+ getContentRevealProps: (
106
+ options?: ContentRevealOptions,
107
+ ) => {className?: string; style?: CSSProperties};
108
+ }
109
+
110
+ const EMPTY = Object.freeze({});
111
+
112
+ /**
113
+ * Scoped, CSS-only hover/focus reveal for content inside a container.
114
+ *
115
+ * @example
116
+ * ```tsx
117
+ * const {getContainerProps, getContentRevealProps} = useContainerReveal({
118
+ * isEnabled: revealOn === 'hover',
119
+ * });
120
+ *
121
+ * <div {...mergeProps(getContainerProps(), stylex.props(styles.row))}>
122
+ * {label}
123
+ * <span {...mergeProps(getContentRevealProps(), stylex.props(styles.actions))}>
124
+ * {actions}
125
+ * </span>
126
+ * </div>
127
+ * ```
128
+ */
129
+ export function useContainerReveal(
130
+ options: UseContainerRevealOptions = {},
131
+ ): UseContainerRevealReturn {
132
+ const {isEnabled = true} = options;
133
+
134
+ // Claim a slot for this container's lifetime. Assigned in the initializer
135
+ // (not the render body) and released on unmount so the free-list stays
136
+ // effect-scoped and StrictMode-safe.
137
+ const [claim] = useState<Claim | null>(() =>
138
+ isEnabled ? claimSlot() : null,
139
+ );
140
+
141
+ useEffect(() => {
142
+ if (claim == null) {
143
+ return;
144
+ }
145
+ return () => releaseSlot(claim.index, claim.isExhausted);
146
+ }, [claim]);
147
+
148
+ useDevWarning(
149
+ 'useContainerReveal',
150
+ `More than ${POOL_SIZE} reveal containers are mounted at once; nested ` +
151
+ `containers beyond the pool may share a marker. Add markers to ` +
152
+ `containerReveal.pool.stylex.ts and raise POOL_SIZE.`,
153
+ claim?.isExhausted ?? false,
154
+ );
155
+
156
+ if (claim == null) {
157
+ return {
158
+ getContainerProps: () => EMPTY,
159
+ getContentRevealProps: () => EMPTY,
160
+ };
161
+ }
162
+
163
+ const slot: RevealSlot = POOL[claim.index];
164
+
165
+ return {
166
+ getContainerProps: () => stylex.props(slot.marker),
167
+ getContentRevealProps: (contentOptions: ContentRevealOptions = {}) => {
168
+ const {isRevealInverted = false, isLayoutPreserved = false} =
169
+ contentOptions;
170
+ const style = isRevealInverted
171
+ ? isLayoutPreserved
172
+ ? slot.concealLayoutPreserved
173
+ : slot.conceal
174
+ : isLayoutPreserved
175
+ ? slot.revealLayoutPreserved
176
+ : slot.reveal;
177
+ return stylex.props(style);
178
+ },
179
+ };
180
+ }
@@ -50,6 +50,39 @@ function EscapeTrap({
50
50
  );
51
51
  }
52
52
 
53
+ function NestedInnerTrap({onEscape}: {onEscape: () => void}) {
54
+ const {containerRef} = useFocusTrap<HTMLDivElement>({
55
+ isActive: true,
56
+ onEscape,
57
+ });
58
+ return (
59
+ <div ref={containerRef} data-testid="nested-inner">
60
+ <button type="button">inner-btn</button>
61
+ </div>
62
+ );
63
+ }
64
+
65
+ function NestedTraps({
66
+ onOuterEscape,
67
+ onInnerEscape,
68
+ showInner = true,
69
+ }: {
70
+ onOuterEscape: () => void;
71
+ onInnerEscape: () => void;
72
+ showInner?: boolean;
73
+ }) {
74
+ const {containerRef} = useFocusTrap<HTMLDivElement>({
75
+ isActive: true,
76
+ onEscape: onOuterEscape,
77
+ });
78
+ return (
79
+ <div ref={containerRef} data-testid="nested-outer">
80
+ <button type="button">outer-btn</button>
81
+ {showInner && <NestedInnerTrap onEscape={onInnerEscape} />}
82
+ </div>
83
+ );
84
+ }
85
+
53
86
  function RestoreTrap({isActive}: {isActive: boolean}) {
54
87
  const {containerRef} = useFocusTrap<HTMLDivElement>({isActive});
55
88
  return (
@@ -118,6 +151,47 @@ describe('useFocusTrap tabbable model (infra-8)', () => {
118
151
  // Focus skips the inert button and lands on the real one.
119
152
  expect(screen.getByTestId('real-btn')).toHaveFocus();
120
153
  });
154
+
155
+ it('ignores an aria-hidden subtree when finding focusables', () => {
156
+ render(
157
+ <Trap>
158
+ <div aria-hidden="true">
159
+ <button type="button" data-testid="aria-hidden-btn">
160
+ Hidden from AT
161
+ </button>
162
+ </div>
163
+ <button type="button" data-testid="real-btn">
164
+ Real
165
+ </button>
166
+ </Trap>,
167
+ );
168
+ fireEvent.click(screen.getByTestId('focus-first'));
169
+ // An element AT cannot perceive must not be a trap tab stop (WCAG 4.1.2).
170
+ expect(screen.getByTestId('real-btn')).toHaveFocus();
171
+ });
172
+
173
+ it('excludes an aria-hidden focusable from the Tab wrap boundary', () => {
174
+ render(
175
+ <Trap>
176
+ <button type="button" data-testid="first">
177
+ First
178
+ </button>
179
+ <button type="button" data-testid="visible-last">
180
+ Visible last
181
+ </button>
182
+ <div aria-hidden="true">
183
+ <button type="button" data-testid="aria-hidden-btn">
184
+ Hidden from AT
185
+ </button>
186
+ </div>
187
+ </Trap>,
188
+ );
189
+ // The last VISIBLE-to-AT element is the wrap boundary: Tab from it wraps
190
+ // to the first element instead of landing on the aria-hidden button.
191
+ screen.getByTestId('visible-last').focus();
192
+ fireEvent.keyDown(document, {key: 'Tab'});
193
+ expect(screen.getByTestId('first')).toHaveFocus();
194
+ });
121
195
  });
122
196
 
123
197
  describe('FOCUSABLE_SELECTOR href matching', () => {
@@ -198,6 +272,36 @@ describe('useFocusTrap Escape coordination', () => {
198
272
  expect(onEscape).toHaveBeenCalledTimes(1);
199
273
  });
200
274
 
275
+ it('resolves Escape to the DOM-nested inner trap when both mount in one commit', () => {
276
+ const outer = vi.fn();
277
+ const inner = vi.fn();
278
+ // Outer and inner mount in the SAME React commit. React runs child
279
+ // effects before parent effects, so the inner trap is PUSHED first —
280
+ // DOM containment, not push order, must decide who answers Escape.
281
+ render(<NestedTraps onOuterEscape={outer} onInnerEscape={inner} />);
282
+ fireEvent.keyDown(document, {key: 'Escape'});
283
+ expect(inner).toHaveBeenCalledTimes(1);
284
+ expect(outer).not.toHaveBeenCalled();
285
+ });
286
+
287
+ it('falls back to the outer trap once the nested inner trap unmounts', () => {
288
+ const outer = vi.fn();
289
+ const inner = vi.fn();
290
+ const {rerender} = render(
291
+ <NestedTraps onOuterEscape={outer} onInnerEscape={inner} />,
292
+ );
293
+ rerender(
294
+ <NestedTraps
295
+ onOuterEscape={outer}
296
+ onInnerEscape={inner}
297
+ showInner={false}
298
+ />,
299
+ );
300
+ fireEvent.keyDown(document, {key: 'Escape'});
301
+ expect(outer).toHaveBeenCalledTimes(1);
302
+ expect(inner).not.toHaveBeenCalled();
303
+ });
304
+
201
305
  it('does not respond after the trap is deactivated', () => {
202
306
  const onEscape = vi.fn();
203
307
  const {rerender} = render(
@@ -26,24 +26,62 @@ import {FOCUSABLE_SELECTOR} from './focusableSelector';
26
26
  * Every active `useFocusTrap` used to attach its own document-level `keydown`
27
27
  * listener with no coordination, so a single Escape press closed *every* open
28
28
  * layer at once (e.g. a popover nested inside a Dialog closed both). Tracking
29
- * traps in a shared stack lets only the most recently activated (top-most)
30
- * trap respond to Escape.
29
+ * traps in a shared stack lets only the top-most trap respond to Escape.
30
+ *
31
+ * "Top-most" is resolved by DOM containment first, push order second. Push
32
+ * order alone is not reliable: React runs child effects before parent
33
+ * effects, so when an outer and an inner (DOM-nested) trap mount in the SAME
34
+ * commit, the inner trap pushes first and the outer trap would wrongly win a
35
+ * pure last-pushed comparison.
31
36
  */
32
- const escapeStack: (() => void)[] = [];
37
+ interface EscapeStackEntry {
38
+ handler: () => void;
39
+ getContainer: () => HTMLElement | null;
40
+ }
33
41
 
34
- function pushEscapeHandler(handler: () => void): void {
35
- escapeStack.push(handler);
42
+ const escapeStack: EscapeStackEntry[] = [];
43
+
44
+ function pushEscapeHandler(entry: EscapeStackEntry): void {
45
+ escapeStack.push(entry);
36
46
  }
37
47
 
38
48
  function removeEscapeHandler(handler: () => void): void {
39
- const index = escapeStack.lastIndexOf(handler);
40
- if (index !== -1) {
41
- escapeStack.splice(index, 1);
49
+ for (let i = escapeStack.length - 1; i >= 0; i--) {
50
+ if (escapeStack[i].handler === handler) {
51
+ escapeStack.splice(i, 1);
52
+ return;
53
+ }
42
54
  }
43
55
  }
44
56
 
57
+ /**
58
+ * Resolve the top-most trap: walk the stack in push order, keeping the
59
+ * deepest container by DOM containment. When a later entry's container
60
+ * contains the current candidate's container, the candidate is nested inside
61
+ * it and stays on top; otherwise the later push wins (containment for nested
62
+ * traps, push order as the tiebreaker for unrelated ones).
63
+ */
45
64
  function isTopEscapeHandler(handler: () => void): boolean {
46
- return escapeStack[escapeStack.length - 1] === handler;
65
+ if (escapeStack.length === 0) {
66
+ return false;
67
+ }
68
+ let top = escapeStack[0];
69
+ for (let i = 1; i < escapeStack.length; i++) {
70
+ const entry = escapeStack[i];
71
+ const topContainer = top.getContainer();
72
+ const entryContainer = entry.getContainer();
73
+ if (
74
+ topContainer != null &&
75
+ entryContainer != null &&
76
+ entryContainer !== topContainer &&
77
+ entryContainer.contains(topContainer)
78
+ ) {
79
+ // The current top is nested inside this entry — it stays on top.
80
+ continue;
81
+ }
82
+ top = entry;
83
+ }
84
+ return top.handler === handler;
47
85
  }
48
86
 
49
87
  /**
@@ -73,7 +111,9 @@ export function isImeKeyEvent(event: {
73
111
  /**
74
112
  * Whether an element is currently perceivable/focusable — excludes ones hidden
75
113
  * via `display:none`/`visibility:hidden` or inside an `inert`/`hidden` subtree,
76
- * which the browser skips for Tab.
114
+ * which the browser skips for Tab, and ones inside an `aria-hidden="true"`
115
+ * subtree, which sighted-keyboard users could Tab to while AT skips them
116
+ * (WCAG 4.1.2 — focusable content must be exposed to assistive tech).
77
117
  */
78
118
  function isVisiblyFocusable(el: HTMLElement): boolean {
79
119
  if (el.hasAttribute('inert') || el.closest('[inert]')) {
@@ -82,6 +122,10 @@ function isVisiblyFocusable(el: HTMLElement): boolean {
82
122
  if (el.hidden || el.closest('[hidden]')) {
83
123
  return false;
84
124
  }
125
+ // closest() matches the element itself as well as any ancestor.
126
+ if (el.closest('[aria-hidden="true"]')) {
127
+ return false;
128
+ }
85
129
  // offsetParent is null for display:none (and fixed elements); pair with a
86
130
  // visibility check via getComputedStyle when available.
87
131
  if (typeof window !== 'undefined' && window.getComputedStyle) {
@@ -323,12 +367,17 @@ export function useFocusTrap<T extends HTMLElement = HTMLElement>(
323
367
 
324
368
  // Register this trap on the shared Escape stack so only the top-most
325
369
  // active trap responds to Escape. A stable identity per active period is
326
- // enough — we push on activate and remove on cleanup.
370
+ // enough — we push on activate and remove on cleanup. The container is
371
+ // read lazily: the ref may not be attached yet at effect time, and the
372
+ // stack resolves top-most by DOM containment at keydown time.
327
373
  const escapeHandler = () => {
328
374
  onEscape?.();
329
375
  };
330
376
  if (onEscape) {
331
- pushEscapeHandler(escapeHandler);
377
+ pushEscapeHandler({
378
+ handler: escapeHandler,
379
+ getContainer: () => containerRef.current,
380
+ });
332
381
  }
333
382
 
334
383
  const handleKeyDown = (event: KeyboardEvent) => {
@@ -64,8 +64,8 @@ export const docs = {
64
64
  {
65
65
  name: 'options.isRtl',
66
66
  type: 'boolean',
67
- description: 'Swap ArrowLeft/ArrowRight so horizontal navigation follows visual direction in right-to-left contexts.',
68
- default: 'false',
67
+ description: 'Swap ArrowLeft/ArrowRight so horizontal navigation follows visual direction in right-to-left contexts. When omitted, auto-detected from the container computed direction on keydown.',
68
+ default: "undefined (auto-detect from the container's computed direction)",
69
69
  required: false,
70
70
  },
71
71
  {
@@ -138,7 +138,7 @@ export const docsDense = {
138
138
  'options.onNavigateAfter': 'callback when navigation would go after last cell. Receives column index + offset.',
139
139
  'options.onPageUp': 'callback for Page Up key (e.g. navigate to previous month in calendars).',
140
140
  'options.onPageDown': 'callback for Page Down key (e.g. navigate to next month in calendars).',
141
- 'options.isRtl': 'swap ArrowLeft/ArrowRight for RTL horizontal navigation. default false.',
141
+ 'options.isRtl': 'swap ArrowLeft/ArrowRight for RTL horizontal navigation. default: auto-detect from container computed direction; explicit boolean wins.',
142
142
  'options.hasRovingTabIndex': 'own a single roving tab stop across the grid (one focus target tabindex=0, rest -1); repaired on render + moved with arrows. Attach handleFocus to onFocus. default false.',
143
143
  },
144
144
  returnDescriptions: {