@astryxdesign/core 0.3.0 → 0.4.0

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 (551) hide show
  1. package/CHANGELOG.md +156 -0
  2. package/dist/AlertDialog/AlertDialog.d.ts +10 -2
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +22 -3
  5. package/dist/AppShell/AppShell.d.ts +2 -2
  6. package/dist/AppShell/AppShell.d.ts.map +1 -1
  7. package/dist/AppShell/AppShell.js +40 -9
  8. package/dist/AspectRatio/AspectRatio.d.ts +13 -0
  9. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  10. package/dist/AspectRatio/AspectRatio.js +9 -0
  11. package/dist/Avatar/Avatar.d.ts.map +1 -1
  12. package/dist/Avatar/Avatar.js +33 -20
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +2 -3
  15. package/dist/Banner/Banner.d.ts.map +1 -1
  16. package/dist/Banner/Banner.js +15 -14
  17. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/BreadcrumbItem.js +21 -8
  19. package/dist/Breadcrumbs/index.d.ts +2 -1
  20. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/index.js +1 -0
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +11 -15
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +2 -1
  26. package/dist/Calendar/styles.d.ts +0 -2
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +0 -2
  29. package/dist/Carousel/Carousel.d.ts.map +1 -1
  30. package/dist/Carousel/Carousel.js +8 -12
  31. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  32. package/dist/Chat/ChatDictationButton.js +2 -1
  33. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  34. package/dist/Chat/ChatLayoutScrollButton.js +3 -1
  35. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  36. package/dist/Chat/ChatSendButton.js +4 -3
  37. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  38. package/dist/Chat/ChatToolCalls.js +17 -17
  39. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  40. package/dist/Chat/useTriggerMenu.js +2 -6
  41. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  42. package/dist/CheckboxInput/CheckboxInput.js +36 -125
  43. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  44. package/dist/ClickableCard/ClickableCard.js +3 -11
  45. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  46. package/dist/CodeBlock/CodeBlock.js +84 -67
  47. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  48. package/dist/Collapsible/Collapsible.js +35 -15
  49. package/dist/CommandPalette/CommandPaletteItem.d.ts +1 -1
  50. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  51. package/dist/CommandPalette/CommandPaletteItem.js +6 -3
  52. package/dist/CommandPalette/CommandPaletteList.js +1 -1
  53. package/dist/ComplexSelector/ComplexSelector.d.ts +1 -1
  54. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  55. package/dist/ComplexSelector/ComplexSelector.js +35 -33
  56. package/dist/ContextMenu/ContextMenu.d.ts +2 -2
  57. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  58. package/dist/ContextMenu/ContextMenu.js +1 -1
  59. package/dist/ContextMenu/index.d.ts +2 -1
  60. package/dist/ContextMenu/index.d.ts.map +1 -1
  61. package/dist/ContextMenu/index.js +1 -0
  62. package/dist/DateInput/DateInput.d.ts +9 -2
  63. package/dist/DateInput/DateInput.d.ts.map +1 -1
  64. package/dist/DateInput/DateInput.js +24 -23
  65. package/dist/DateRangeInput/DateRangeInput.d.ts +9 -2
  66. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  67. package/dist/DateRangeInput/DateRangeInput.js +11 -16
  68. package/dist/DateTimeInput/DateTimeInput.d.ts +9 -1
  69. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  70. package/dist/DateTimeInput/DateTimeInput.js +22 -17
  71. package/dist/Dialog/Dialog.d.ts.map +1 -1
  72. package/dist/Dialog/Dialog.js +4 -4
  73. package/dist/DropdownMenu/DropdownMenu.d.ts +33 -8
  74. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  75. package/dist/DropdownMenu/DropdownMenu.js +62 -20
  76. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +8 -11
  77. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +32 -31
  79. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +25 -0
  80. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuDivider.js +66 -0
  82. package/dist/DropdownMenu/DropdownMenuItem.d.ts +15 -2
  83. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuItem.js +20 -8
  85. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +5 -5
  86. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  87. package/dist/DropdownMenu/DropdownMenuRadioItem.js +24 -67
  88. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  89. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -8
  90. package/dist/DropdownMenu/index.d.ts +2 -1
  91. package/dist/DropdownMenu/index.d.ts.map +1 -1
  92. package/dist/DropdownMenu/index.js +4 -0
  93. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  94. package/dist/DropdownMenu/renderDropdownItems.js +35 -27
  95. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  96. package/dist/EmptyState/EmptyState.js +9 -5
  97. package/dist/Field/InputClearButton.d.ts +16 -3
  98. package/dist/Field/InputClearButton.d.ts.map +1 -1
  99. package/dist/Field/InputClearButton.js +15 -4
  100. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  101. package/dist/HoverCard/useHoverCard.js +3 -19
  102. package/dist/Indicator/CheckIndicator.d.ts +25 -0
  103. package/dist/Indicator/CheckIndicator.d.ts.map +1 -0
  104. package/dist/Indicator/CheckIndicator.js +155 -0
  105. package/dist/Indicator/CheckboxIndicator.d.ts +22 -0
  106. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -0
  107. package/dist/Indicator/CheckboxIndicator.js +173 -0
  108. package/dist/Indicator/RadioIndicator.d.ts +23 -0
  109. package/dist/Indicator/RadioIndicator.d.ts.map +1 -0
  110. package/dist/Indicator/RadioIndicator.js +137 -0
  111. package/dist/Indicator/index.d.ts +24 -0
  112. package/dist/Indicator/index.d.ts.map +1 -0
  113. package/dist/Indicator/index.js +26 -0
  114. package/dist/Indicator/indicator.markers.stylex.d.ts +16 -0
  115. package/dist/Indicator/indicator.markers.stylex.d.ts.map +1 -0
  116. package/dist/Indicator/indicator.markers.stylex.js +21 -0
  117. package/dist/Indicator/indicatorRegistry.d.ts +71 -0
  118. package/dist/Indicator/indicatorRegistry.d.ts.map +1 -0
  119. package/dist/Indicator/indicatorRegistry.js +93 -0
  120. package/dist/Indicator/types.d.ts +167 -0
  121. package/dist/Indicator/types.d.ts.map +1 -0
  122. package/dist/Indicator/types.js +1 -0
  123. package/dist/Indicator/useIndicator.d.ts +18 -0
  124. package/dist/Indicator/useIndicator.d.ts.map +1 -0
  125. package/dist/Indicator/useIndicator.js +30 -0
  126. package/dist/Item/Item.d.ts.map +1 -1
  127. package/dist/Item/Item.js +4 -8
  128. package/dist/Layer/useLayer.d.ts +14 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +25 -2
  131. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  132. package/dist/Lightbox/Lightbox.js +56 -53
  133. package/dist/Link/Link.d.ts.map +1 -1
  134. package/dist/Link/Link.js +4 -5
  135. package/dist/MobileNav/MobileNav.js +8 -8
  136. package/dist/MoreMenu/MoreMenu.d.ts +14 -1
  137. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  138. package/dist/MoreMenu/MoreMenu.js +4 -0
  139. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  140. package/dist/MultiSelector/MultiSelector.js +78 -75
  141. package/dist/NumberInput/NumberInput.d.ts +27 -1
  142. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  143. package/dist/NumberInput/NumberInput.js +230 -42
  144. package/dist/Outline/Outline.d.ts.map +1 -1
  145. package/dist/Outline/Outline.js +2 -3
  146. package/dist/Pagination/Pagination.d.ts.map +1 -1
  147. package/dist/Pagination/Pagination.js +61 -63
  148. package/dist/Popover/Popover.d.ts.map +1 -1
  149. package/dist/Popover/Popover.js +6 -8
  150. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  151. package/dist/PowerSearch/PowerSearch.js +1 -1
  152. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  153. package/dist/PowerSearch/PowerSearchEditPopover.js +7 -0
  154. package/dist/ProgressBar/ProgressBar.d.ts +11 -4
  155. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  156. package/dist/ProgressBar/ProgressBar.js +96 -6
  157. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  158. package/dist/RadioList/RadioListItem.js +29 -84
  159. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  160. package/dist/Resizable/ResizeHandle.js +2 -3
  161. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  162. package/dist/SegmentedControl/SegmentedControlItem.js +2 -3
  163. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  164. package/dist/SelectableCard/SelectableCard.js +3 -11
  165. package/dist/Selector/Selector.d.ts +2 -1
  166. package/dist/Selector/Selector.d.ts.map +1 -1
  167. package/dist/Selector/Selector.js +164 -94
  168. package/dist/Selector/hooks.d.ts +18 -6
  169. package/dist/Selector/hooks.d.ts.map +1 -1
  170. package/dist/Selector/hooks.js +57 -40
  171. package/dist/SideNav/SideNav.d.ts.map +1 -1
  172. package/dist/SideNav/SideNav.js +1 -1
  173. package/dist/SideNav/SideNavCollapseButton.d.ts +1 -1
  174. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  175. package/dist/SideNav/SideNavCollapseButton.js +29 -14
  176. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  177. package/dist/SideNav/SideNavHeading.js +56 -20
  178. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  179. package/dist/SideNav/SideNavItem.js +28 -22
  180. package/dist/Stack/stack.stylex.d.ts +3 -3
  181. package/dist/Switch/Switch.js +11 -10
  182. package/dist/TabList/Tab.d.ts.map +1 -1
  183. package/dist/TabList/Tab.js +2 -3
  184. package/dist/TabList/TabMenu.d.ts.map +1 -1
  185. package/dist/TabList/TabMenu.js +41 -19
  186. package/dist/Table/BaseTable.d.ts.map +1 -1
  187. package/dist/Table/BaseTable.js +8 -0
  188. package/dist/Table/index.d.ts +1 -1
  189. package/dist/Table/index.d.ts.map +1 -1
  190. package/dist/Table/index.js +1 -1
  191. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +1 -1
  192. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  193. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +22 -18
  194. package/dist/Table/plugins/rowExpansion/index.d.ts +1 -1
  195. package/dist/Table/plugins/rowExpansion/index.d.ts.map +1 -1
  196. package/dist/Table/plugins/rowExpansion/index.js +1 -1
  197. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts +41 -76
  198. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  199. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +94 -345
  200. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  201. package/dist/Table/plugins/sortable/useTableSortable.js +23 -4
  202. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  203. package/dist/Table/plugins/tree/useTableTreeData.js +29 -33
  204. package/dist/Table/tableContextMenu.d.ts.map +1 -1
  205. package/dist/Table/tableContextMenu.js +2 -1
  206. package/dist/Table/types.d.ts +11 -0
  207. package/dist/Table/types.d.ts.map +1 -1
  208. package/dist/TextArea/TextArea.d.ts +10 -1
  209. package/dist/TextArea/TextArea.d.ts.map +1 -1
  210. package/dist/TextArea/TextArea.js +16 -6
  211. package/dist/TextInput/TextInput.d.ts +10 -1
  212. package/dist/TextInput/TextInput.d.ts.map +1 -1
  213. package/dist/TextInput/TextInput.js +13 -19
  214. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  215. package/dist/Thumbnail/Thumbnail.js +32 -26
  216. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  217. package/dist/TimeInput/TimeInput.js +7 -15
  218. package/dist/Timestamp/TimestampHoverCard.d.ts.map +1 -1
  219. package/dist/Timestamp/TimestampHoverCard.js +16 -34
  220. package/dist/Token/Token.d.ts.map +1 -1
  221. package/dist/Token/Token.js +20 -11
  222. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  223. package/dist/Tokenizer/Tokenizer.js +4 -2
  224. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  225. package/dist/Tooltip/useTooltip.js +3 -19
  226. package/dist/TopNav/TopNav.d.ts.map +1 -1
  227. package/dist/TopNav/TopNav.js +6 -5
  228. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  229. package/dist/TopNav/TopNavHeading.js +54 -16
  230. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  231. package/dist/TopNav/TopNavItem.js +3 -9
  232. package/dist/TopNav/TopNavMegaMenu.d.ts +7 -0
  233. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  234. package/dist/TopNav/TopNavMegaMenu.js +146 -60
  235. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  236. package/dist/TopNav/TopNavMegaMenuItem.js +22 -3
  237. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  238. package/dist/TopNav/TopNavMenu.js +83 -38
  239. package/dist/TreeList/TreeList.d.ts.map +1 -1
  240. package/dist/TreeList/TreeList.js +13 -0
  241. package/dist/TreeList/TreeListBranches.d.ts +1 -1
  242. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  243. package/dist/TreeList/TreeListBranches.js +14 -4
  244. package/dist/TreeList/TreeListItem.d.ts +11 -1
  245. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  246. package/dist/TreeList/TreeListItem.js +57 -30
  247. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  248. package/dist/Typeahead/BaseTypeahead.js +16 -8
  249. package/dist/astryx.css +74 -85
  250. package/dist/astryx.umd.js +56 -56
  251. package/dist/astryx.umd.js.map +4 -4
  252. package/dist/hooks/containerReveal.stylex.d.ts +61 -0
  253. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -0
  254. package/dist/hooks/containerReveal.stylex.js +63 -0
  255. package/dist/hooks/index.d.ts +3 -0
  256. package/dist/hooks/index.d.ts.map +1 -1
  257. package/dist/hooks/index.js +2 -0
  258. package/dist/hooks/useClipboard.d.ts +59 -0
  259. package/dist/hooks/useClipboard.d.ts.map +1 -0
  260. package/dist/hooks/useClipboard.js +106 -0
  261. package/dist/hooks/useContainerReveal.d.ts +14 -8
  262. package/dist/hooks/useContainerReveal.d.ts.map +1 -1
  263. package/dist/hooks/useContainerReveal.js +28 -69
  264. package/dist/hooks/useIndicatorFocusRing.d.ts +28 -0
  265. package/dist/hooks/useIndicatorFocusRing.d.ts.map +1 -0
  266. package/dist/hooks/useIndicatorFocusRing.js +96 -0
  267. package/dist/hooks/useLongPress.d.ts.map +1 -1
  268. package/dist/hooks/useLongPress.js +12 -2
  269. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  270. package/dist/hooks/useScrollLock.js +33 -18
  271. package/dist/hooks/useStreamingText.d.ts +18 -3
  272. package/dist/hooks/useStreamingText.d.ts.map +1 -1
  273. package/dist/hooks/useStreamingText.js +47 -7
  274. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  275. package/dist/hooks/useTreeFocus.js +9 -3
  276. package/dist/hooks/useTypeahead.d.ts +3 -2
  277. package/dist/hooks/useTypeahead.d.ts.map +1 -1
  278. package/dist/hooks/useTypeahead.js +19 -7
  279. package/dist/index.d.ts +1 -0
  280. package/dist/index.d.ts.map +1 -1
  281. package/dist/index.js +1 -0
  282. package/dist/theme/defineTheme.d.ts +16 -0
  283. package/dist/theme/defineTheme.d.ts.map +1 -1
  284. package/dist/theme/defineTheme.js +8 -0
  285. package/dist/theme/derivedVarRegistry.d.ts +8 -0
  286. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  287. package/dist/theme/derivedVarRegistry.js +14 -13
  288. package/dist/theme/expandColorScale.d.ts +3 -1
  289. package/dist/theme/expandColorScale.d.ts.map +1 -1
  290. package/dist/theme/expandColorScale.js +17 -7
  291. package/dist/theme/expandTypeScale.d.ts.map +1 -1
  292. package/dist/theme/expandTypeScale.js +17 -0
  293. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  294. package/dist/theme/generateThemeRules.js +11 -0
  295. package/dist/theme/index.d.ts +1 -0
  296. package/dist/theme/index.d.ts.map +1 -1
  297. package/dist/utils/focusOutline.stylex.d.ts +107 -0
  298. package/dist/utils/focusOutline.stylex.d.ts.map +1 -0
  299. package/dist/utils/focusOutline.stylex.js +130 -0
  300. package/dist/utils/focusReturn.d.ts +24 -0
  301. package/dist/utils/focusReturn.d.ts.map +1 -0
  302. package/dist/utils/focusReturn.js +29 -0
  303. package/dist/utils/index.d.ts +1 -0
  304. package/dist/utils/index.d.ts.map +1 -1
  305. package/dist/utils/index.js +1 -0
  306. package/dist/utils/mergeRefs.d.ts.map +1 -1
  307. package/dist/utils/mergeRefs.js +7 -5
  308. package/dist/utils/rtlStyles.d.ts +2 -2
  309. package/dist/utils/rtlStyles.js +2 -2
  310. package/dist/utils/themeProps.d.ts +20 -1
  311. package/dist/utils/themeProps.d.ts.map +1 -1
  312. package/dist/utils/themeProps.js +8 -2
  313. package/locales/en.json +12 -0
  314. package/locales/pseudo.json +9 -0
  315. package/package.json +7 -2
  316. package/src/AlertDialog/AlertDialog.doc.mjs +16 -3
  317. package/src/AlertDialog/AlertDialog.test.tsx +208 -1
  318. package/src/AlertDialog/AlertDialog.tsx +21 -3
  319. package/src/AppShell/AppShell.doc.mjs +11 -0
  320. package/src/AppShell/AppShell.test.tsx +139 -0
  321. package/src/AppShell/AppShell.tsx +32 -22
  322. package/src/AspectRatio/AspectRatio.doc.mjs +27 -3
  323. package/src/AspectRatio/AspectRatio.test.tsx +30 -0
  324. package/src/AspectRatio/AspectRatio.tsx +13 -0
  325. package/src/Avatar/Avatar.doc.mjs +2 -13
  326. package/src/Avatar/Avatar.test.tsx +33 -8
  327. package/src/Avatar/Avatar.tsx +52 -50
  328. package/src/AvatarGroup/AvatarGroupOverflow.tsx +2 -9
  329. package/src/Banner/Banner.tsx +9 -6
  330. package/src/Breadcrumbs/BreadcrumbItem.tsx +16 -6
  331. package/src/Breadcrumbs/index.ts +5 -1
  332. package/src/Button/Button.tsx +21 -41
  333. package/src/Calendar/Calendar.tsx +2 -1
  334. package/src/Calendar/styles.ts +0 -8
  335. package/src/Carousel/Carousel.doc.mjs +3 -0
  336. package/src/Carousel/Carousel.tsx +10 -6
  337. package/src/Chat/Chat.doc.mjs +2 -0
  338. package/src/Chat/ChatDictationButton.tsx +7 -1
  339. package/src/Chat/ChatLayoutScrollButton.test.tsx +22 -0
  340. package/src/Chat/ChatLayoutScrollButton.tsx +8 -1
  341. package/src/Chat/ChatSendButton.tsx +2 -3
  342. package/src/Chat/ChatToolCalls.test.tsx +35 -0
  343. package/src/Chat/ChatToolCalls.tsx +36 -16
  344. package/src/Chat/useTriggerMenu.tsx +2 -5
  345. package/src/CheckboxInput/CheckboxInput.doc.mjs +4 -2
  346. package/src/CheckboxInput/CheckboxInput.test.tsx +115 -0
  347. package/src/CheckboxInput/CheckboxInput.tsx +45 -202
  348. package/src/ClickableCard/ClickableCard.tsx +6 -11
  349. package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
  350. package/src/CodeBlock/CodeBlock.test.tsx +91 -0
  351. package/src/CodeBlock/CodeBlock.tsx +57 -80
  352. package/src/Collapsible/Collapsible.tsx +22 -18
  353. package/src/CommandPalette/CommandPalette.test.tsx +29 -0
  354. package/src/CommandPalette/CommandPaletteItem.tsx +7 -4
  355. package/src/CommandPalette/CommandPaletteList.tsx +2 -2
  356. package/src/ComplexSelector/ComplexSelector.doc.mjs +42 -0
  357. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -0
  358. package/src/ComplexSelector/ComplexSelector.tsx +46 -29
  359. package/src/ContextMenu/ContextMenu.doc.mjs +1 -1
  360. package/src/ContextMenu/ContextMenu.test.tsx +41 -0
  361. package/src/ContextMenu/ContextMenu.tsx +3 -3
  362. package/src/ContextMenu/index.ts +5 -1
  363. package/src/DateInput/DateInput.doc.mjs +18 -3
  364. package/src/DateInput/DateInput.test.tsx +93 -11
  365. package/src/DateInput/DateInput.tsx +41 -19
  366. package/src/DateRangeInput/DateRangeInput.doc.mjs +10 -2
  367. package/src/DateRangeInput/DateRangeInput.test.tsx +110 -14
  368. package/src/DateRangeInput/DateRangeInput.tsx +24 -12
  369. package/src/DateTimeInput/DateTimeInput.doc.mjs +16 -2
  370. package/src/DateTimeInput/DateTimeInput.test.tsx +75 -0
  371. package/src/DateTimeInput/DateTimeInput.tsx +35 -8
  372. package/src/Dialog/Dialog.tsx +2 -9
  373. package/src/DropdownMenu/DropdownMenu.doc.mjs +5 -9
  374. package/src/DropdownMenu/DropdownMenu.test.tsx +534 -4
  375. package/src/DropdownMenu/DropdownMenu.tsx +113 -52
  376. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +27 -30
  377. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +34 -0
  378. package/src/DropdownMenu/DropdownMenuDivider.tsx +81 -0
  379. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +39 -4
  380. package/src/DropdownMenu/DropdownMenuItem.tsx +48 -10
  381. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +30 -76
  382. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +25 -19
  383. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +44 -14
  384. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +1 -5
  385. package/src/DropdownMenu/index.ts +8 -1
  386. package/src/DropdownMenu/renderDropdownItems.tsx +33 -34
  387. package/src/EmptyState/EmptyState.doc.mjs +4 -0
  388. package/src/EmptyState/EmptyState.test.tsx +62 -0
  389. package/src/EmptyState/EmptyState.tsx +16 -6
  390. package/src/Field/Field.doc.mjs +1 -0
  391. package/src/Field/InputClearButton.test.tsx +117 -0
  392. package/src/Field/InputClearButton.tsx +30 -3
  393. package/src/HoverCard/useHoverCard.tsx +2 -23
  394. package/src/Icon/Icon.doc.mjs +2 -1
  395. package/src/Indicator/CheckIndicator.tsx +174 -0
  396. package/src/Indicator/CheckboxIndicator.tsx +257 -0
  397. package/src/Indicator/Indicator.doc.mjs +344 -0
  398. package/src/Indicator/Indicator.test.tsx +393 -0
  399. package/src/Indicator/RadioIndicator.tsx +206 -0
  400. package/src/Indicator/index.ts +45 -0
  401. package/src/Indicator/indicator.markers.stylex.ts +19 -0
  402. package/src/Indicator/indicatorRegistry.test.tsx +106 -0
  403. package/src/Indicator/indicatorRegistry.ts +120 -0
  404. package/src/Indicator/types.ts +186 -0
  405. package/src/Indicator/useIndicator.ts +39 -0
  406. package/src/Item/Item.doc.mjs +1 -1
  407. package/src/Item/Item.tsx +7 -14
  408. package/src/Layer/useLayer.doc.mjs +2 -2
  409. package/src/Layer/useLayer.test.tsx +85 -0
  410. package/src/Layer/useLayer.tsx +39 -1
  411. package/src/Lightbox/Lightbox.doc.mjs +1 -1
  412. package/src/Lightbox/Lightbox.tsx +43 -38
  413. package/src/Link/Link.tsx +4 -11
  414. package/src/MobileNav/MobileNav.tsx +3 -3
  415. package/src/MoreMenu/MoreMenu.doc.mjs +31 -0
  416. package/src/MoreMenu/MoreMenu.test.tsx +68 -1
  417. package/src/MoreMenu/MoreMenu.tsx +19 -0
  418. package/src/MultiSelector/MultiSelector.doc.mjs +9 -1
  419. package/src/MultiSelector/MultiSelector.test.tsx +255 -16
  420. package/src/MultiSelector/MultiSelector.tsx +103 -99
  421. package/src/NumberInput/NumberInput.doc.mjs +265 -50
  422. package/src/NumberInput/NumberInput.test.tsx +726 -21
  423. package/src/NumberInput/NumberInput.tsx +353 -59
  424. package/src/Outline/Outline.tsx +2 -5
  425. package/src/Pagination/Pagination.doc.mjs +1 -1
  426. package/src/Pagination/Pagination.test.tsx +22 -7
  427. package/src/Pagination/Pagination.tsx +38 -35
  428. package/src/Popover/Popover.tsx +6 -7
  429. package/src/PowerSearch/PowerSearch.tsx +1 -1
  430. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +42 -0
  431. package/src/PowerSearch/PowerSearchEditPopover.tsx +7 -0
  432. package/src/ProgressBar/ProgressBar.doc.mjs +27 -5
  433. package/src/ProgressBar/ProgressBar.test.tsx +224 -1
  434. package/src/ProgressBar/ProgressBar.tsx +124 -22
  435. package/src/RadioList/RadioList.doc.mjs +4 -2
  436. package/src/RadioList/RadioListItem.tsx +31 -130
  437. package/src/Resizable/ResizeHandle.tsx +2 -9
  438. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -9
  439. package/src/SelectableCard/SelectableCard.tsx +4 -9
  440. package/src/Selector/Selector.doc.mjs +7 -4
  441. package/src/Selector/Selector.test.tsx +935 -25
  442. package/src/Selector/Selector.tsx +189 -106
  443. package/src/Selector/hooks.ts +77 -50
  444. package/src/SideNav/SideNav.test.tsx +46 -0
  445. package/src/SideNav/SideNav.tsx +1 -0
  446. package/src/SideNav/SideNavCollapseButton.tsx +15 -10
  447. package/src/SideNav/SideNavHeading.tsx +48 -10
  448. package/src/SideNav/SideNavItem.tsx +20 -12
  449. package/src/Stack/Stack.doc.mjs +110 -0
  450. package/src/StatusDot/StatusDot.doc.mjs +12 -3
  451. package/src/Switch/Switch.test.tsx +32 -0
  452. package/src/Switch/Switch.tsx +8 -8
  453. package/src/TabList/Tab.tsx +2 -9
  454. package/src/TabList/TabMenu.tsx +14 -21
  455. package/src/Table/BaseTable.tsx +11 -0
  456. package/src/Table/index.ts +1 -4
  457. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +30 -14
  458. package/src/Table/plugins/rowExpansion/index.ts +1 -4
  459. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +125 -175
  460. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +115 -458
  461. package/src/Table/plugins/sortable/useTableSortable.tsx +2 -6
  462. package/src/Table/plugins/tree/useTableTreeData.tsx +43 -24
  463. package/src/Table/tableContextMenu.test.tsx +27 -0
  464. package/src/Table/tableContextMenu.tsx +1 -0
  465. package/src/Table/types.ts +11 -0
  466. package/src/Table/useTableGroupedRows.doc.mjs +1 -1
  467. package/src/Table/useTableRowExpansion.doc.mjs +43 -66
  468. package/src/TextArea/TextArea.doc.mjs +42 -2
  469. package/src/TextArea/TextArea.test.tsx +219 -0
  470. package/src/TextArea/TextArea.tsx +34 -7
  471. package/src/TextInput/TextInput.doc.mjs +18 -2
  472. package/src/TextInput/TextInput.test.tsx +189 -0
  473. package/src/TextInput/TextInput.tsx +25 -31
  474. package/src/Thumbnail/Thumbnail.tsx +2 -9
  475. package/src/TimeInput/TimeInput.doc.mjs +2 -2
  476. package/src/TimeInput/TimeInput.test.tsx +30 -3
  477. package/src/TimeInput/TimeInput.tsx +9 -26
  478. package/src/Timestamp/Timestamp.doc.mjs +4 -3
  479. package/src/Timestamp/TimestampHoverCard.tsx +13 -35
  480. package/src/Token/Token.tsx +5 -28
  481. package/src/Tokenizer/Tokenizer.doc.mjs +2 -2
  482. package/src/Tokenizer/Tokenizer.test.tsx +45 -3
  483. package/src/Tokenizer/Tokenizer.tsx +10 -2
  484. package/src/Tooltip/useTooltip.tsx +2 -23
  485. package/src/TopNav/TopNav.tsx +1 -3
  486. package/src/TopNav/TopNavHeading.tsx +42 -13
  487. package/src/TopNav/TopNavItem.tsx +3 -21
  488. package/src/TopNav/TopNavMegaMenu.test.tsx +433 -33
  489. package/src/TopNav/TopNavMegaMenu.tsx +118 -53
  490. package/src/TopNav/TopNavMegaMenuItem.tsx +2 -9
  491. package/src/TopNav/TopNavMenu.tsx +34 -37
  492. package/src/TreeList/TreeList.doc.mjs +5 -0
  493. package/src/TreeList/TreeList.test.tsx +206 -7
  494. package/src/TreeList/TreeList.tsx +22 -0
  495. package/src/TreeList/TreeListBranches.tsx +25 -2
  496. package/src/TreeList/TreeListItem.tsx +89 -52
  497. package/src/Typeahead/BaseTypeahead.tsx +19 -6
  498. package/src/Typeahead/Typeahead.doc.mjs +1 -0
  499. package/src/Typeahead/Typeahead.test.tsx +76 -3
  500. package/src/hooks/containerReveal.stylex.ts +102 -0
  501. package/src/hooks/index.ts +4 -0
  502. package/src/hooks/useClipboard.doc.mjs +87 -0
  503. package/src/hooks/useClipboard.test.tsx +151 -0
  504. package/src/hooks/useClipboard.ts +135 -0
  505. package/src/hooks/useContainerReveal.doc.mjs +7 -7
  506. package/src/hooks/useContainerReveal.test.tsx +42 -62
  507. package/src/hooks/useContainerReveal.ts +36 -91
  508. package/src/hooks/useImageMode.test.ts +1 -1
  509. package/src/hooks/useIndicatorFocusRing.tsx +120 -0
  510. package/src/hooks/useLongPress.test.tsx +18 -0
  511. package/src/hooks/useLongPress.ts +12 -2
  512. package/src/hooks/useScrollLock.test.ts +72 -0
  513. package/src/hooks/useScrollLock.ts +51 -19
  514. package/src/hooks/useStreamingText.test.ts +46 -1
  515. package/src/hooks/useStreamingText.ts +49 -7
  516. package/src/hooks/useTreeFocus.test.tsx +50 -0
  517. package/src/hooks/useTreeFocus.ts +14 -3
  518. package/src/hooks/useTypeahead.test.tsx +150 -0
  519. package/src/hooks/useTypeahead.ts +24 -9
  520. package/src/i18n/InternationalizationProvider.doc.mjs +7 -2
  521. package/src/index.ts +1 -0
  522. package/src/theme/defineTheme.ts +24 -0
  523. package/src/theme/derivedVarRegistry.test.ts +21 -1
  524. package/src/theme/derivedVarRegistry.ts +19 -6
  525. package/src/theme/expandColorScale.test.ts +14 -0
  526. package/src/theme/expandColorScale.ts +24 -9
  527. package/src/theme/expandTypeScale.test.ts +43 -0
  528. package/src/theme/expandTypeScale.ts +17 -0
  529. package/src/theme/generateThemeRules.test.ts +61 -20
  530. package/src/theme/generateThemeRules.ts +11 -0
  531. package/src/theme/index.ts +13 -0
  532. package/src/utils/focusOutline.stylex.ts +163 -0
  533. package/src/utils/focusReturn.test.ts +42 -0
  534. package/src/utils/focusReturn.ts +31 -0
  535. package/src/utils/index.ts +1 -0
  536. package/src/utils/mergeRefs.test.ts +48 -0
  537. package/src/utils/mergeRefs.ts +12 -6
  538. package/src/utils/rtlStyles.ts +2 -2
  539. package/src/utils/themeProps.ts +26 -4
  540. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts +0 -9
  541. package/dist/CheckboxInput/checkbox.markers.stylex.d.ts.map +0 -1
  542. package/dist/CheckboxInput/checkbox.markers.stylex.js +0 -14
  543. package/dist/RadioList/radio.markers.stylex.d.ts +0 -7
  544. package/dist/RadioList/radio.markers.stylex.d.ts.map +0 -1
  545. package/dist/RadioList/radio.markers.stylex.js +0 -12
  546. package/dist/hooks/containerReveal.pool.stylex.d.ts +0 -303
  547. package/dist/hooks/containerReveal.pool.stylex.d.ts.map +0 -1
  548. package/dist/hooks/containerReveal.pool.stylex.js +0 -292
  549. package/src/CheckboxInput/checkbox.markers.stylex.ts +0 -12
  550. package/src/RadioList/radio.markers.stylex.ts +0 -10
  551. package/src/hooks/containerReveal.pool.stylex.ts +0 -571
@@ -10,10 +10,12 @@
10
10
  */
11
11
 
12
12
  import {describe, it, expect, vi, beforeEach} from 'vitest';
13
- import {render, screen, fireEvent} from '@testing-library/react';
13
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
+ import {useState} from 'react';
15
16
  import {DropdownMenu} from './DropdownMenu';
16
17
  import {DropdownMenuItem} from './DropdownMenuItem';
18
+ import {DropdownMenuDivider} from './DropdownMenuDivider';
17
19
  import {Divider} from '../Divider';
18
20
 
19
21
  // Mock showPopover and hidePopover methods since they're not implemented in jsdom
@@ -214,9 +216,8 @@ describe('DropdownMenu', () => {
214
216
  const trigger = screen.getByRole('button', {name: /Actions/});
215
217
  trigger.focus();
216
218
  await user.click(trigger);
217
- expect(
218
- screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
219
- ).toHaveFocus();
219
+ // Pointer opens focus the menu container, not the first item (#4477).
220
+ expect(screen.getByRole('menu', {hidden: true})).toHaveFocus();
220
221
 
221
222
  const popoverEl = screen
222
223
  .getByRole('menu', {hidden: true})
@@ -265,6 +266,27 @@ describe('DropdownMenu', () => {
265
266
  ).toHaveFocus();
266
267
  });
267
268
 
269
+ it('typeahead advances past an item that already starts with the letter', async () => {
270
+ const user = userEvent.setup();
271
+ render(
272
+ <DropdownMenu
273
+ button={{label: 'Actions'}}
274
+ items={[{label: 'Copy'}, {label: 'Copy link'}, {label: 'Delete'}]}
275
+ />,
276
+ );
277
+ await user.click(screen.getByRole('button', {name: /Actions/}));
278
+ const menu = screen.getByRole('menu', {hidden: true});
279
+ screen.getByRole('menuitem', {name: 'Copy', hidden: true}).focus();
280
+
281
+ fireEvent.keyDown(menu, {key: 'c'});
282
+
283
+ // APG: a printable character moves focus to the NEXT item starting with
284
+ // it. Anchoring at the focused item instead makes the press a dead key.
285
+ expect(
286
+ screen.getByRole('menuitem', {name: 'Copy link', hidden: true}),
287
+ ).toHaveFocus();
288
+ });
289
+
268
290
  it('calls onClick callback when button is clicked', async () => {
269
291
  const user = userEvent.setup();
270
292
  const handleClick = vi.fn();
@@ -390,6 +412,169 @@ describe('DropdownMenu items', () => {
390
412
  expect(handleClick).toHaveBeenCalledTimes(1);
391
413
  });
392
414
 
415
+ it('closes the menu after an item is activated', async () => {
416
+ const user = userEvent.setup();
417
+ render(
418
+ <DropdownMenu
419
+ button={{label: 'Actions'}}
420
+ items={[{label: 'Edit', onClick: () => {}}]}
421
+ />,
422
+ );
423
+
424
+ await user.click(screen.getByRole('button', {name: /Actions/}));
425
+ await user.click(
426
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
427
+ );
428
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
429
+ });
430
+
431
+ it('keeps the menu open when the item opts out of closing', async () => {
432
+ const user = userEvent.setup();
433
+ const handleClick = vi.fn();
434
+ render(
435
+ <DropdownMenu
436
+ button={{label: 'Actions'}}
437
+ items={[
438
+ {label: 'Copy ID', onClick: handleClick, hasCloseOnSelect: false},
439
+ ]}
440
+ />,
441
+ );
442
+
443
+ await user.click(screen.getByRole('button', {name: /Actions/}));
444
+ const item = screen.getByRole('menuitem', {name: 'Copy ID', hidden: true});
445
+ await user.click(item);
446
+ expect(handleClick).toHaveBeenCalledTimes(1);
447
+ expect(HTMLElement.prototype.hidePopover).not.toHaveBeenCalled();
448
+
449
+ // Second activation still works, and focus never left the item.
450
+ await user.click(item);
451
+ expect(handleClick).toHaveBeenCalledTimes(2);
452
+ });
453
+
454
+ it('keeps the menu open on keyboard activation too', async () => {
455
+ const user = userEvent.setup();
456
+ const handleClick = vi.fn();
457
+ render(
458
+ <DropdownMenu
459
+ button={{label: 'Actions'}}
460
+ items={[
461
+ {label: 'Copy ID', onClick: handleClick, hasCloseOnSelect: false},
462
+ ]}
463
+ />,
464
+ );
465
+
466
+ await user.click(screen.getByRole('button', {name: /Actions/}));
467
+ const menu = screen.getByRole('menu', {hidden: true});
468
+ await waitFor(() => expect(menu).toHaveFocus());
469
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
470
+ fireEvent.keyDown(menu, {key: 'Enter'});
471
+
472
+ expect(handleClick).toHaveBeenCalledTimes(1);
473
+ expect(HTMLElement.prototype.hidePopover).not.toHaveBeenCalled();
474
+ expect(
475
+ screen.getByRole('menuitem', {name: 'Copy ID', hidden: true}),
476
+ ).toHaveFocus();
477
+ });
478
+
479
+ it('closes the menu on activation even when the item carries no handler', async () => {
480
+ const user = userEvent.setup();
481
+ render(
482
+ <DropdownMenu button={{label: 'Actions'}} items={[{label: 'Edit'}]} />,
483
+ );
484
+
485
+ await user.click(screen.getByRole('button', {name: /Actions/}));
486
+ await user.click(
487
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
488
+ );
489
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
490
+ });
491
+
492
+ it('keeps a row mounted when its label changes, so focus survives (data mode keys by position)', async () => {
493
+ const user = userEvent.setup();
494
+
495
+ function CopyMenu() {
496
+ const [copied, setCopied] = useState(false);
497
+ return (
498
+ <DropdownMenu
499
+ button={{label: 'Actions'}}
500
+ items={[
501
+ {
502
+ label: copied ? 'Copied' : 'Copy ID',
503
+ hasCloseOnSelect: false,
504
+ onClick: () => setCopied(true),
505
+ },
506
+ {label: 'Rename'},
507
+ ]}
508
+ />
509
+ );
510
+ }
511
+
512
+ render(<CopyMenu />);
513
+ await user.click(screen.getByRole('button', {name: /Actions/}));
514
+ const item = screen.getByRole('menuitem', {name: 'Copy ID', hidden: true});
515
+ item.focus();
516
+ await user.click(item);
517
+
518
+ const renamed = screen.getByRole('menuitem', {
519
+ name: 'Copied',
520
+ hidden: true,
521
+ });
522
+ expect(renamed).toBe(item);
523
+ expect(renamed).toHaveFocus();
524
+ });
525
+
526
+ it('follows the item, not the slot, when ids are supplied and the list changes', async () => {
527
+ const user = userEvent.setup();
528
+
529
+ // A menu whose rows are filtered by a control outside it: the focused row
530
+ // survives at a new index. Position keys cannot express this — the DOM node
531
+ // at index 0 would be reused for whatever item lands there.
532
+ function FilterableMenu({hideFirst}: {hideFirst: boolean}) {
533
+ const items = [
534
+ {id: 'edit', label: 'Edit'},
535
+ {id: 'duplicate', label: 'Duplicate'},
536
+ {id: 'archive', label: 'Archive'},
537
+ ].filter(item => !hideFirst || item.id !== 'edit');
538
+ return <DropdownMenu button={{label: 'Actions'}} items={items} />;
539
+ }
540
+
541
+ const {rerender} = render(<FilterableMenu hideFirst={false} />);
542
+ await user.click(screen.getByRole('button', {name: /Actions/}));
543
+
544
+ const duplicate = screen.getByRole('menuitem', {
545
+ name: 'Duplicate',
546
+ hidden: true,
547
+ });
548
+ duplicate.focus();
549
+
550
+ rerender(<FilterableMenu hideFirst={true} />);
551
+
552
+ // Same node, still focused, even though it moved from index 1 to index 0.
553
+ expect(
554
+ screen.getByRole('menuitem', {name: 'Duplicate', hidden: true}),
555
+ ).toBe(duplicate);
556
+ expect(duplicate).toHaveFocus();
557
+ expect(
558
+ screen.queryByRole('menuitem', {name: 'Edit', hidden: true}),
559
+ ).not.toBeInTheDocument();
560
+ });
561
+
562
+ it('does not put id on the rendered row', async () => {
563
+ const user = userEvent.setup();
564
+ render(
565
+ <DropdownMenu
566
+ button={{label: 'Actions'}}
567
+ items={[{id: 'edit', label: 'Edit'}]}
568
+ />,
569
+ );
570
+ await user.click(screen.getByRole('button', {name: /Actions/}));
571
+
572
+ // `id` is identity for React, not a DOM attribute the caller is setting.
573
+ expect(
574
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
575
+ ).not.toHaveAttribute('id', 'edit');
576
+ });
577
+
393
578
  it('does not call onClick when disabled', async () => {
394
579
  const user = userEvent.setup();
395
580
  const handleClick = vi.fn();
@@ -537,6 +722,78 @@ describe('DropdownMenu theming slots', () => {
537
722
  });
538
723
  });
539
724
 
725
+ describe('DropdownMenuItem destructive variant', () => {
726
+ it('marks a compound-mode item destructive via data-variant', () => {
727
+ render(
728
+ <DropdownMenu button={{label: 'Actions'}}>
729
+ <DropdownMenuItem
730
+ label="Delete"
731
+ variant="destructive"
732
+ onClick={() => {}}
733
+ />
734
+ <DropdownMenuItem label="Edit" onClick={() => {}} />
735
+ </DropdownMenu>,
736
+ );
737
+
738
+ const del = screen.getByRole('menuitem', {name: 'Delete', hidden: true});
739
+ const edit = screen.getByRole('menuitem', {name: 'Edit', hidden: true});
740
+ expect(del).toHaveAttribute('data-variant', 'destructive');
741
+ // Default items carry no variant attribute, so existing usage is unchanged.
742
+ expect(edit).not.toHaveAttribute('data-variant');
743
+ });
744
+
745
+ it('forwards variant from the data-driven items API', () => {
746
+ render(
747
+ <DropdownMenu
748
+ button={{label: 'Actions'}}
749
+ items={[
750
+ {label: 'Delete', variant: 'destructive', onClick: () => {}},
751
+ {label: 'Edit', onClick: () => {}},
752
+ ]}
753
+ />,
754
+ );
755
+
756
+ expect(
757
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
758
+ ).toHaveAttribute('data-variant', 'destructive');
759
+ expect(
760
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
761
+ ).not.toHaveAttribute('data-variant');
762
+ });
763
+
764
+ it('forwards variant to items nested inside a section', () => {
765
+ render(
766
+ <DropdownMenu
767
+ button={{label: 'Actions'}}
768
+ items={[
769
+ {
770
+ type: 'section',
771
+ title: 'Danger zone',
772
+ items: [
773
+ {label: 'Delete', variant: 'destructive', onClick: () => {}},
774
+ ],
775
+ },
776
+ ]}
777
+ />,
778
+ );
779
+
780
+ expect(
781
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
782
+ ).toHaveAttribute('data-variant', 'destructive');
783
+ });
784
+
785
+ it('defaults to no variant attribute', () => {
786
+ render(
787
+ <DropdownMenu button={{label: 'Actions'}}>
788
+ <DropdownMenuItem label="Edit" onClick={() => {}} />
789
+ </DropdownMenu>,
790
+ );
791
+ expect(
792
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
793
+ ).not.toHaveAttribute('data-variant');
794
+ });
795
+ });
796
+
540
797
  describe('DropdownMenu button customization', () => {
541
798
  it('renders with different button variants', () => {
542
799
  const {rerender} = render(
@@ -964,3 +1221,276 @@ describe('DropdownMenu keyboard access for menuitemradio/menuitemcheckbox (#3829
964
1221
  expect(edit).toHaveFocus();
965
1222
  });
966
1223
  });
1224
+
1225
+ describe('DropdownMenu open focus follows input modality (#4477)', () => {
1226
+ const items = [{label: 'Edit'}, {label: 'Duplicate'}, {label: 'Delete'}];
1227
+
1228
+ it('pointer open focuses the menu container, not the first item (items mode)', async () => {
1229
+ const user = userEvent.setup();
1230
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1231
+
1232
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1233
+
1234
+ const menu = screen.getByRole('menu', {hidden: true});
1235
+ await waitFor(() => expect(menu).toHaveFocus());
1236
+ expect(
1237
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1238
+ ).not.toHaveFocus();
1239
+ });
1240
+
1241
+ it('pointer open focuses the menu container in compound mode', async () => {
1242
+ const user = userEvent.setup();
1243
+ render(
1244
+ <DropdownMenu button={{label: 'Actions'}}>
1245
+ <DropdownMenuItem label="Edit" onClick={() => {}} />
1246
+ <DropdownMenuItem label="Delete" onClick={() => {}} />
1247
+ </DropdownMenu>,
1248
+ );
1249
+
1250
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1251
+
1252
+ const menu = screen.getByRole('menu', {hidden: true});
1253
+ await waitFor(() => expect(menu).toHaveFocus());
1254
+ expect(
1255
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1256
+ ).not.toHaveFocus();
1257
+ });
1258
+
1259
+ it('first ArrowDown after a pointer open moves focus to the first enabled item', async () => {
1260
+ const user = userEvent.setup();
1261
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1262
+
1263
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1264
+ const menu = screen.getByRole('menu', {hidden: true});
1265
+ await waitFor(() => expect(menu).toHaveFocus());
1266
+
1267
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
1268
+ expect(
1269
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1270
+ ).toHaveFocus();
1271
+ });
1272
+
1273
+ it('ArrowDown after a pointer open skips a disabled leading item', async () => {
1274
+ const user = userEvent.setup();
1275
+ render(
1276
+ <DropdownMenu
1277
+ button={{label: 'Actions'}}
1278
+ items={[{label: 'Edit', isDisabled: true}, {label: 'Delete'}]}
1279
+ />,
1280
+ );
1281
+
1282
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1283
+ const menu = screen.getByRole('menu', {hidden: true});
1284
+ await waitFor(() => expect(menu).toHaveFocus());
1285
+
1286
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
1287
+ expect(
1288
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
1289
+ ).toHaveFocus();
1290
+ });
1291
+
1292
+ it('keyboard open via Enter focuses the first enabled item', async () => {
1293
+ const user = userEvent.setup();
1294
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1295
+
1296
+ screen.getByRole('button', {name: /Actions/}).focus();
1297
+ await user.keyboard('{Enter}');
1298
+
1299
+ await waitFor(() =>
1300
+ expect(
1301
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1302
+ ).toHaveFocus(),
1303
+ );
1304
+ });
1305
+
1306
+ it('keyboard open via ArrowDown skips a disabled leading item', async () => {
1307
+ const user = userEvent.setup();
1308
+ render(
1309
+ <DropdownMenu
1310
+ button={{label: 'Actions'}}
1311
+ items={[{label: 'Edit', isDisabled: true}, {label: 'Delete'}]}
1312
+ />,
1313
+ );
1314
+
1315
+ screen.getByRole('button', {name: /Actions/}).focus();
1316
+ await user.keyboard('{ArrowDown}');
1317
+
1318
+ await waitFor(() =>
1319
+ expect(
1320
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
1321
+ ).toHaveFocus(),
1322
+ );
1323
+ });
1324
+
1325
+ it('a synthesized click (detail 0, AT activation) still focuses the first item', async () => {
1326
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1327
+
1328
+ // fireEvent.click dispatches a MouseEvent with detail 0 (the shape of a
1329
+ // screen reader / AT activation), so it must keep the keyboard behavior.
1330
+ fireEvent.click(screen.getByRole('button', {name: /Actions/}));
1331
+
1332
+ await waitFor(() =>
1333
+ expect(
1334
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1335
+ ).toHaveFocus(),
1336
+ );
1337
+ });
1338
+
1339
+ it('controlled pointer open focuses the menu container', async () => {
1340
+ function Controlled() {
1341
+ const [isOpen, setIsOpen] = useState(false);
1342
+ return (
1343
+ <DropdownMenu
1344
+ button={{label: 'Actions'}}
1345
+ items={items}
1346
+ isMenuOpen={isOpen}
1347
+ onOpenChange={setIsOpen}
1348
+ />
1349
+ );
1350
+ }
1351
+ const user = userEvent.setup();
1352
+ render(<Controlled />);
1353
+
1354
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1355
+
1356
+ const menu = screen.getByRole('menu', {hidden: true});
1357
+ await waitFor(() => expect(menu).toHaveFocus());
1358
+ expect(
1359
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1360
+ ).not.toHaveFocus();
1361
+ });
1362
+
1363
+ it('programmatic controlled open still focuses the first item', async () => {
1364
+ const {rerender} = render(
1365
+ <DropdownMenu
1366
+ button={{label: 'Actions'}}
1367
+ items={items}
1368
+ isMenuOpen={false}
1369
+ onOpenChange={() => {}}
1370
+ />,
1371
+ );
1372
+
1373
+ rerender(
1374
+ <DropdownMenu
1375
+ button={{label: 'Actions'}}
1376
+ items={items}
1377
+ isMenuOpen={true}
1378
+ onOpenChange={() => {}}
1379
+ />,
1380
+ );
1381
+
1382
+ await waitFor(() =>
1383
+ expect(
1384
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1385
+ ).toHaveFocus(),
1386
+ );
1387
+ });
1388
+
1389
+ it('Escape still closes the menu after a pointer open', async () => {
1390
+ const user = userEvent.setup();
1391
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1392
+
1393
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1394
+ const menu = screen.getByRole('menu', {hidden: true});
1395
+ await waitFor(() => expect(menu).toHaveFocus());
1396
+
1397
+ fireEvent.keyDown(menu, {key: 'Escape'});
1398
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
1399
+ });
1400
+
1401
+ it('Tab still closes the menu after a pointer open (APG menu-button)', async () => {
1402
+ const user = userEvent.setup();
1403
+ render(<DropdownMenu button={{label: 'Actions'}} items={items} />);
1404
+
1405
+ await user.click(screen.getByRole('button', {name: /Actions/}));
1406
+ const menu = screen.getByRole('menu', {hidden: true});
1407
+ await waitFor(() => expect(menu).toHaveFocus());
1408
+
1409
+ fireEvent.keyDown(menu, {key: 'Tab'});
1410
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalled();
1411
+ });
1412
+ });
1413
+
1414
+ describe('DropdownMenu data/compound parity', () => {
1415
+ it('renders an identical divider from either mode', () => {
1416
+ const {unmount} = render(
1417
+ <DropdownMenu
1418
+ button={{label: 'Actions'}}
1419
+ items={[{label: 'Edit'}, {type: 'divider'}, {label: 'Delete'}]}
1420
+ />,
1421
+ );
1422
+ const fromData = screen.getByRole('separator', {hidden: true}).outerHTML;
1423
+ unmount();
1424
+
1425
+ render(
1426
+ <DropdownMenu button={{label: 'Actions'}}>
1427
+ <DropdownMenuItem label="Edit" />
1428
+ <DropdownMenuDivider />
1429
+ <DropdownMenuItem label="Delete" />
1430
+ </DropdownMenu>,
1431
+ );
1432
+ const fromCompound = screen.getByRole('separator', {hidden: true});
1433
+
1434
+ expect(fromCompound.outerHTML).toBe(fromData);
1435
+ expect(fromCompound).toHaveClass('astryx-dropdown-menu-divider');
1436
+ });
1437
+
1438
+ it('skips a compound divider in the arrow-key order', async () => {
1439
+ const user = userEvent.setup();
1440
+ render(
1441
+ <DropdownMenu button={{label: 'Actions'}}>
1442
+ <DropdownMenuItem label="Edit" />
1443
+ <DropdownMenuDivider />
1444
+ <DropdownMenuItem label="Delete" />
1445
+ </DropdownMenu>,
1446
+ );
1447
+
1448
+ await user.tab();
1449
+ await user.keyboard('{ArrowDown}');
1450
+ const menu = screen.getByRole('menu', {hidden: true});
1451
+ await waitFor(() =>
1452
+ expect(
1453
+ screen.getByRole('menuitem', {name: 'Edit', hidden: true}),
1454
+ ).toHaveFocus(),
1455
+ );
1456
+
1457
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
1458
+ expect(
1459
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
1460
+ ).toHaveFocus();
1461
+ expect(screen.getByRole('separator', {hidden: true})).not.toHaveFocus();
1462
+ });
1463
+
1464
+ it('carries endContent and description through the items data API', () => {
1465
+ render(
1466
+ <DropdownMenu
1467
+ button={{label: 'Actions'}}
1468
+ items={[
1469
+ {
1470
+ label: 'Search',
1471
+ description: 'Find anything',
1472
+ endContent: <span data-testid="shortcut">⌘K</span>,
1473
+ },
1474
+ ]}
1475
+ />,
1476
+ );
1477
+
1478
+ const item = screen.getByRole('menuitem', {hidden: true});
1479
+ expect(item).toHaveTextContent('Find anything');
1480
+ expect(item).toContainElement(screen.getByTestId('shortcut'));
1481
+ });
1482
+
1483
+ it('takes a ReactNode label through the items data API', () => {
1484
+ render(
1485
+ <DropdownMenu
1486
+ button={{label: 'Actions'}}
1487
+ items={[{label: <em data-testid="rich">Rename</em>}]}
1488
+ />,
1489
+ );
1490
+
1491
+ const item = screen.getByRole('menuitem', {hidden: true});
1492
+ expect(item).toContainElement(screen.getByTestId('rich'));
1493
+ // Still typeahead- and screen-reader-addressable: both read text content.
1494
+ expect(item).toHaveAccessibleName('Rename');
1495
+ });
1496
+ });