@astryxdesign/core 0.4.7 → 0.5.0-canary.055e75f

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 (597) hide show
  1. package/CHANGELOG.md +161 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +45 -20
  5. package/dist/AppShell/AppShell.d.ts.map +1 -1
  6. package/dist/AppShell/AppShell.js +3 -2
  7. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  8. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  9. package/dist/AspectRatio/AspectRatio.js +23 -4
  10. package/dist/Avatar/Avatar.d.ts +25 -1
  11. package/dist/Avatar/Avatar.d.ts.map +1 -1
  12. package/dist/Avatar/Avatar.js +58 -19
  13. package/dist/Avatar/index.d.ts +2 -2
  14. package/dist/Avatar/index.d.ts.map +1 -1
  15. package/dist/Avatar/index.js +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
  17. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  18. package/dist/AvatarGroup/AvatarGroup.js +8 -4
  19. package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
  20. package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
  21. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  22. package/dist/AvatarGroup/AvatarGroupOverflow.js +10 -6
  23. package/dist/Banner/Banner.d.ts +40 -15
  24. package/dist/Banner/Banner.d.ts.map +1 -1
  25. package/dist/Banner/Banner.js +51 -18
  26. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  27. package/dist/BottomSheet/BottomSheet.js +17 -4
  28. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  29. package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
  30. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  31. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  32. package/dist/Button/Button.d.ts.map +1 -1
  33. package/dist/Button/Button.js +11 -6
  34. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  35. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  36. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  37. package/dist/Calendar/Calendar.d.ts +3 -1
  38. package/dist/Calendar/Calendar.d.ts.map +1 -1
  39. package/dist/Calendar/Calendar.js +14 -15
  40. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  41. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  42. package/dist/Calendar/getInitialFocusDate.js +62 -0
  43. package/dist/Calendar/styles.d.ts +5 -14
  44. package/dist/Calendar/styles.d.ts.map +1 -1
  45. package/dist/Calendar/styles.js +15 -15
  46. package/dist/Card/Card.d.ts +6 -9
  47. package/dist/Card/Card.d.ts.map +1 -1
  48. package/dist/Card/Card.js +6 -6
  49. package/dist/Carousel/Carousel.d.ts.map +1 -1
  50. package/dist/Carousel/Carousel.js +3 -3
  51. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  52. package/dist/Chat/ChatComposerInput.js +2 -3
  53. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  54. package/dist/Chat/ChatLayout.js +3 -3
  55. package/dist/Chat/ChatMessageList.d.ts +22 -3
  56. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  57. package/dist/Chat/ChatMessageList.js +6 -3
  58. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  59. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  60. package/dist/Chat/ChatTokenizedText.js +11 -6
  61. package/dist/Chat/useChatComposerTokens.js +1 -1
  62. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  63. package/dist/CheckboxInput/CheckboxInput.js +10 -3
  64. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  65. package/dist/ClickableCard/ClickableCard.js +3 -3
  66. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  67. package/dist/CodeBlock/CodeBlock.js +15 -4
  68. package/dist/CodeBlock/highlightRanges.js +1 -1
  69. package/dist/CodeBlock/highlightStyles.js +13 -13
  70. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  71. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  72. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  73. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  74. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  75. package/dist/ContextMenu/ContextMenu.js +4 -3
  76. package/dist/DateInput/DateInput.d.ts +43 -0
  77. package/dist/DateInput/DateInput.d.ts.map +1 -1
  78. package/dist/DateInput/DateInput.js +22 -2
  79. package/dist/DateInput/NativeDateField.d.ts +10 -0
  80. package/dist/DateInput/NativeDateField.d.ts.map +1 -0
  81. package/dist/DateInput/NativeDateField.js +494 -0
  82. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  83. package/dist/DateInput/TouchDateField.js +82 -23
  84. package/dist/DateInput/index.d.ts +1 -1
  85. package/dist/DateInput/index.d.ts.map +1 -1
  86. package/dist/DateInput/nativeDateSegments.d.ts +22 -0
  87. package/dist/DateInput/nativeDateSegments.d.ts.map +1 -0
  88. package/dist/DateInput/nativeDateSegments.js +111 -0
  89. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  90. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  92. package/dist/DateTimeInput/index.d.ts +1 -1
  93. package/dist/DateTimeInput/index.d.ts.map +1 -1
  94. package/dist/Dialog/Dialog.d.ts +21 -1
  95. package/dist/Dialog/Dialog.d.ts.map +1 -1
  96. package/dist/Dialog/Dialog.js +110 -58
  97. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  98. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  99. package/dist/Field/FieldLabel.d.ts.map +1 -1
  100. package/dist/Field/FieldLabel.js +9 -1
  101. package/dist/FileInput/FileInput.d.ts.map +1 -1
  102. package/dist/FileInput/FileInput.js +3 -2
  103. package/dist/Heading/Heading.d.ts.map +1 -1
  104. package/dist/Heading/Heading.js +6 -2
  105. package/dist/HoverCard/HoverCard.d.ts +6 -0
  106. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  107. package/dist/HoverCard/HoverCard.js +104 -3
  108. package/dist/HoverCard/useHoverCard.d.ts +19 -2
  109. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  110. package/dist/HoverCard/useHoverCard.js +74 -26
  111. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  112. package/dist/Indicator/CheckboxIndicator.js +6 -2
  113. package/dist/Item/Item.d.ts.map +1 -1
  114. package/dist/Item/Item.js +4 -2
  115. package/dist/Kbd/Kbd.d.ts.map +1 -1
  116. package/dist/Kbd/Kbd.js +2 -19
  117. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  118. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  119. package/dist/Layer/LayerDepthContext.js +66 -0
  120. package/dist/Layer/index.d.ts +3 -0
  121. package/dist/Layer/index.d.ts.map +1 -1
  122. package/dist/Layer/index.js +4 -0
  123. package/dist/Layer/layerStack.d.ts +98 -0
  124. package/dist/Layer/layerStack.d.ts.map +1 -0
  125. package/dist/Layer/layerStack.js +306 -0
  126. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  127. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  128. package/dist/Layer/useLayerDismissal.js +91 -0
  129. package/dist/Lightbox/Lightbox.d.ts +1 -0
  130. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  131. package/dist/Lightbox/Lightbox.js +103 -81
  132. package/dist/Markdown/Markdown.d.ts.map +1 -1
  133. package/dist/Markdown/Markdown.js +9 -4
  134. package/dist/Markdown/index.d.ts +1 -1
  135. package/dist/Markdown/index.d.ts.map +1 -1
  136. package/dist/Markdown/parser.d.ts +38 -1
  137. package/dist/Markdown/parser.d.ts.map +1 -1
  138. package/dist/Markdown/parser.js +182 -32
  139. package/dist/MobileNav/MobileNav.d.ts +6 -1
  140. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  141. package/dist/MobileNav/MobileNav.js +61 -36
  142. package/dist/MultiSelector/MultiSelector.d.ts +32 -1
  143. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  144. package/dist/MultiSelector/MultiSelector.js +63 -11
  145. package/dist/MultiSelector/index.d.ts +1 -1
  146. package/dist/MultiSelector/index.d.ts.map +1 -1
  147. package/dist/NavIcon/NavIcon.d.ts.map +1 -1
  148. package/dist/NavIcon/NavIcon.js +5 -1
  149. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  150. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  151. package/dist/NumberInput/NumberInput.d.ts +6 -1
  152. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  153. package/dist/NumberInput/NumberInput.js +54 -104
  154. package/dist/NumberInput/numberInputCommit.d.ts +34 -0
  155. package/dist/NumberInput/numberInputCommit.d.ts.map +1 -0
  156. package/dist/NumberInput/numberInputCommit.js +71 -0
  157. package/dist/NumberInput/numberParser.d.ts +64 -0
  158. package/dist/NumberInput/numberParser.d.ts.map +1 -0
  159. package/dist/NumberInput/numberParser.js +350 -0
  160. package/dist/Outline/Outline.d.ts.map +1 -1
  161. package/dist/Outline/Outline.js +3 -2
  162. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  163. package/dist/OverflowList/OverflowList.js +3 -2
  164. package/dist/Overlay/Overlay.d.ts.map +1 -1
  165. package/dist/Overlay/Overlay.js +3 -2
  166. package/dist/Popover/usePopover.d.ts.map +1 -1
  167. package/dist/Popover/usePopover.js +20 -17
  168. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  169. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearch.js +19 -6
  171. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  172. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  173. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  174. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  175. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  176. package/dist/PowerSearch/PowerSearchValueEditor.js +110 -27
  177. package/dist/PowerSearch/types.d.ts +2 -0
  178. package/dist/PowerSearch/types.d.ts.map +1 -1
  179. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  180. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  181. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  182. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  183. package/dist/ProgressBar/ProgressBar.js +18 -5
  184. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  185. package/dist/Resizable/ResizeHandle.js +3 -2
  186. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  187. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  188. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  189. package/dist/SelectableCard/SelectableCard.js +3 -3
  190. package/dist/Selector/Selector.d.ts +17 -0
  191. package/dist/Selector/Selector.d.ts.map +1 -1
  192. package/dist/Selector/Selector.js +81 -14
  193. package/dist/SideNav/SideNav.d.ts.map +1 -1
  194. package/dist/SideNav/SideNav.js +4 -3
  195. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  196. package/dist/SideNav/SideNavHeading.js +8 -7
  197. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavItem.js +4 -3
  199. package/dist/Slider/Slider.d.ts.map +1 -1
  200. package/dist/Slider/Slider.js +37 -5
  201. package/dist/Spinner/Spinner.d.ts +3 -3
  202. package/dist/Spinner/Spinner.d.ts.map +1 -1
  203. package/dist/Spinner/Spinner.js +130 -78
  204. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  205. package/dist/StatusDot/StatusDot.js +6 -1
  206. package/dist/Stepper/Step.d.ts +140 -0
  207. package/dist/Stepper/Step.d.ts.map +1 -0
  208. package/dist/Stepper/Step.js +1045 -0
  209. package/dist/Stepper/StepStatus.d.ts +24 -0
  210. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  211. package/dist/Stepper/StepStatus.js +1 -0
  212. package/dist/Stepper/Stepper.d.ts +93 -0
  213. package/dist/Stepper/Stepper.d.ts.map +1 -0
  214. package/dist/Stepper/Stepper.js +184 -0
  215. package/dist/Stepper/StepperContext.d.ts +41 -0
  216. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  217. package/dist/Stepper/StepperContext.js +34 -0
  218. package/dist/Stepper/index.d.ts +8 -0
  219. package/dist/Stepper/index.d.ts.map +1 -0
  220. package/dist/Stepper/index.js +7 -0
  221. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  222. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  223. package/dist/Stepper/stepper.stylex.js +20 -0
  224. package/dist/Switch/Switch.d.ts.map +1 -1
  225. package/dist/Switch/Switch.js +8 -3
  226. package/dist/TabList/Tab.d.ts +15 -2
  227. package/dist/TabList/Tab.d.ts.map +1 -1
  228. package/dist/TabList/Tab.js +32 -7
  229. package/dist/TabList/TabList.d.ts +46 -7
  230. package/dist/TabList/TabList.d.ts.map +1 -1
  231. package/dist/TabList/TabList.js +353 -23
  232. package/dist/TabList/TabListContext.d.ts +11 -1
  233. package/dist/TabList/TabListContext.d.ts.map +1 -1
  234. package/dist/TabList/TabListContext.js +11 -2
  235. package/dist/TabList/TabMenu.d.ts.map +1 -1
  236. package/dist/TabList/TabMenu.js +3 -2
  237. package/dist/TabList/index.d.ts +2 -2
  238. package/dist/TabList/index.d.ts.map +1 -1
  239. package/dist/Table/BaseTable.d.ts.map +1 -1
  240. package/dist/Table/BaseTable.js +10 -2
  241. package/dist/Table/Table.d.ts.map +1 -1
  242. package/dist/Table/Table.js +3 -6
  243. package/dist/Table/TableRow.d.ts +16 -5
  244. package/dist/Table/TableRow.d.ts.map +1 -1
  245. package/dist/Table/TableRow.js +16 -5
  246. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  247. package/dist/Table/plugins/filtering/useTableFiltering.js +73 -4
  248. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  249. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  250. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  251. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  252. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  253. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  254. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  255. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  256. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  257. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  258. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  259. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  260. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  261. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
  262. package/dist/Table/types.d.ts +22 -4
  263. package/dist/Table/types.d.ts.map +1 -1
  264. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  265. package/dist/Table/useBaseTablePlugins.js +5 -0
  266. package/dist/Text/Text.d.ts.map +1 -1
  267. package/dist/Text/Text.js +6 -2
  268. package/dist/TextArea/TextArea.d.ts.map +1 -1
  269. package/dist/TextArea/TextArea.js +9 -3
  270. package/dist/TextInput/TextInput.d.ts.map +1 -1
  271. package/dist/TextInput/TextInput.js +3 -2
  272. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  273. package/dist/TimeInput/TimeInput.js +3 -2
  274. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  275. package/dist/Timestamp/Timestamp.js +5 -3
  276. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  277. package/dist/Toast/ToastViewport.js +27 -8
  278. package/dist/Token/TokenLink.d.ts.map +1 -1
  279. package/dist/Token/TokenLink.js +2 -2
  280. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  281. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  282. package/dist/Tokenizer/Tokenizer.js +2 -0
  283. package/dist/Tooltip/Tooltip.d.ts +5 -0
  284. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  285. package/dist/Tooltip/useTooltip.d.ts +5 -0
  286. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  287. package/dist/Tooltip/useTooltip.js +39 -24
  288. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  289. package/dist/TopNav/TopNavHeading.js +5 -4
  290. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  291. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  292. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  293. package/dist/TopNav/TopNavMenu.js +4 -3
  294. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  295. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  296. package/dist/Typeahead/BaseTypeahead.js +68 -29
  297. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  298. package/dist/Typeahead/Typeahead.js +3 -2
  299. package/dist/astryx.css +98 -7
  300. package/dist/hooks/index.d.ts +1 -0
  301. package/dist/hooks/index.d.ts.map +1 -1
  302. package/dist/hooks/index.js +1 -0
  303. package/dist/hooks/useFocusTrap.d.ts +7 -4
  304. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  305. package/dist/hooks/useFocusTrap.js +60 -95
  306. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  307. package/dist/hooks/useHotkeys.js +3 -18
  308. package/dist/hooks/useMergedRefs.d.ts +18 -0
  309. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  310. package/dist/hooks/useMergedRefs.js +25 -0
  311. package/dist/index.d.ts +1 -0
  312. package/dist/index.d.ts.map +1 -1
  313. package/dist/index.js +1 -0
  314. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  315. package/dist/theme/derivedVarRegistry.js +24 -4
  316. package/dist/theme/syntax/tokens.d.ts +1 -1
  317. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  318. package/dist/theme/syntax/tokens.js +5 -2
  319. package/dist/utils/groupItems.d.ts +5 -1
  320. package/dist/utils/groupItems.d.ts.map +1 -1
  321. package/dist/utils/groupItems.js +9 -9
  322. package/dist/utils/index.d.ts +1 -1
  323. package/dist/utils/index.d.ts.map +1 -1
  324. package/dist/utils/isApplePlatform.d.ts +18 -0
  325. package/dist/utils/isApplePlatform.d.ts.map +1 -0
  326. package/dist/utils/isApplePlatform.js +37 -0
  327. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  328. package/dist/utils/sharedResizeObserver.js +10 -1
  329. package/dist/utils/timeParser.d.ts +1 -1
  330. package/dist/utils/timeParser.d.ts.map +1 -1
  331. package/dist/utils/timeParser.js +26 -12
  332. package/locales/en.json +154 -10
  333. package/locales/pseudo.json +108 -0
  334. package/package.json +8 -3
  335. package/src/AlertDialog/AlertDialog.doc.mjs +114 -30
  336. package/src/AlertDialog/AlertDialog.test.tsx +211 -2
  337. package/src/AlertDialog/AlertDialog.tsx +62 -20
  338. package/src/AppShell/AppShell.tsx +3 -2
  339. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  340. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  341. package/src/AspectRatio/AspectRatio.tsx +40 -1
  342. package/src/Avatar/Avatar.doc.mjs +14 -3
  343. package/src/Avatar/Avatar.test.tsx +97 -0
  344. package/src/Avatar/Avatar.tsx +125 -52
  345. package/src/Avatar/index.ts +2 -2
  346. package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
  347. package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
  348. package/src/AvatarGroup/AvatarGroup.tsx +15 -6
  349. package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
  350. package/src/AvatarGroup/AvatarGroupOverflow.tsx +10 -4
  351. package/src/Banner/Banner.doc.mjs +13 -11
  352. package/src/Banner/Banner.test.tsx +102 -15
  353. package/src/Banner/Banner.tsx +72 -25
  354. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  355. package/src/BottomSheet/BottomSheet.tsx +17 -4
  356. package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
  357. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  358. package/src/Button/Button.doc.mjs +24 -1
  359. package/src/Button/Button.test.tsx +32 -4
  360. package/src/Button/Button.tsx +12 -5
  361. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  362. package/src/ButtonGroup/ButtonGroup.test.tsx +404 -1
  363. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  364. package/src/Calendar/Calendar.doc.mjs +4 -3
  365. package/src/Calendar/Calendar.test.tsx +103 -0
  366. package/src/Calendar/Calendar.tsx +19 -16
  367. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  368. package/src/Calendar/getInitialFocusDate.ts +93 -0
  369. package/src/Calendar/styles.ts +49 -31
  370. package/src/Card/Card.doc.mjs +10 -7
  371. package/src/Card/Card.test.tsx +44 -19
  372. package/src/Card/Card.tsx +12 -32
  373. package/src/Carousel/Carousel.tsx +3 -2
  374. package/src/Chat/ChatComposerInput.tsx +5 -1
  375. package/src/Chat/ChatLayout.tsx +3 -2
  376. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  377. package/src/Chat/ChatMessageList.test.tsx +46 -0
  378. package/src/Chat/ChatMessageList.tsx +28 -4
  379. package/src/Chat/ChatTokenizedText.tsx +17 -10
  380. package/src/Chat/useChatComposerTokens.ts +1 -1
  381. package/src/CheckboxInput/CheckboxInput.doc.mjs +2 -0
  382. package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
  383. package/src/CheckboxInput/CheckboxInput.tsx +8 -2
  384. package/src/ClickableCard/ClickableCard.tsx +3 -2
  385. package/src/CodeBlock/CodeBlock.doc.mjs +16 -4
  386. package/src/CodeBlock/CodeBlock.test.tsx +37 -0
  387. package/src/CodeBlock/CodeBlock.tsx +20 -4
  388. package/src/CodeBlock/highlightRanges.ts +1 -1
  389. package/src/CodeBlock/highlightStyles.ts +13 -13
  390. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  391. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  392. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  393. package/src/ContextMenu/ContextMenu.tsx +4 -3
  394. package/src/DateInput/DateInput.doc.mjs +16 -0
  395. package/src/DateInput/DateInput.tsx +55 -2
  396. package/src/DateInput/DateInputTouch.test.tsx +146 -32
  397. package/src/DateInput/NativeDateField.test.tsx +635 -0
  398. package/src/DateInput/NativeDateField.tsx +641 -0
  399. package/src/DateInput/TouchDateField.tsx +108 -24
  400. package/src/DateInput/index.ts +1 -0
  401. package/src/DateInput/nativeDateSegments.ts +127 -0
  402. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  403. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  404. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  405. package/src/DateTimeInput/index.ts +1 -0
  406. package/src/Dialog/Dialog.doc.mjs +8 -3
  407. package/src/Dialog/Dialog.test.tsx +429 -2
  408. package/src/Dialog/Dialog.tsx +133 -58
  409. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  410. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  411. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  412. package/src/Field/Field.doc.mjs +13 -2
  413. package/src/Field/FieldLabel.test.tsx +29 -0
  414. package/src/Field/FieldLabel.tsx +7 -0
  415. package/src/FileInput/FileInput.tsx +3 -2
  416. package/src/Heading/Heading.test.tsx +23 -1
  417. package/src/Heading/Heading.tsx +6 -2
  418. package/src/HoverCard/HoverCard.doc.mjs +13 -2
  419. package/src/HoverCard/HoverCard.test.tsx +154 -4
  420. package/src/HoverCard/HoverCard.tsx +143 -3
  421. package/src/HoverCard/useHoverCard.tsx +104 -33
  422. package/src/Indicator/CheckboxIndicator.tsx +14 -8
  423. package/src/Indicator/Indicator.doc.mjs +2 -0
  424. package/src/Item/Item.tsx +5 -4
  425. package/src/Kbd/Kbd.tsx +2 -19
  426. package/src/Layer/LayerDepthContext.tsx +62 -0
  427. package/src/Layer/index.ts +9 -0
  428. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  429. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  430. package/src/Layer/layerStack.ts +362 -0
  431. package/src/Layer/useLayer.test.tsx +62 -14
  432. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  433. package/src/Layer/useLayerDismissal.ts +173 -0
  434. package/src/Layout/Layout.doc.mjs +2 -2
  435. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  436. package/src/Lightbox/Lightbox.tsx +124 -102
  437. package/src/Link/Link.doc.mjs +39 -0
  438. package/src/Markdown/Markdown.doc.mjs +5 -0
  439. package/src/Markdown/Markdown.tsx +9 -4
  440. package/src/Markdown/incremental.test.ts +16 -2
  441. package/src/Markdown/index.ts +1 -0
  442. package/src/Markdown/parser.perf.test.ts +96 -1
  443. package/src/Markdown/parser.test.ts +178 -2
  444. package/src/Markdown/parser.ts +253 -32
  445. package/src/MobileNav/MobileNav.tsx +62 -36
  446. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  447. package/src/MultiSelector/MultiSelector.doc.mjs +49 -1
  448. package/src/MultiSelector/MultiSelector.test.tsx +277 -0
  449. package/src/MultiSelector/MultiSelector.tsx +142 -11
  450. package/src/MultiSelector/index.ts +1 -0
  451. package/src/NavIcon/NavIcon.doc.mjs +8 -2
  452. package/src/NavIcon/NavIcon.test.tsx +9 -0
  453. package/src/NavIcon/NavIcon.tsx +5 -1
  454. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  455. package/src/NumberInput/NumberInput.doc.mjs +31 -7
  456. package/src/NumberInput/NumberInput.test.tsx +436 -8
  457. package/src/NumberInput/NumberInput.tsx +62 -114
  458. package/src/NumberInput/numberInputCommit.test.ts +72 -0
  459. package/src/NumberInput/numberInputCommit.ts +103 -0
  460. package/src/NumberInput/numberParser.docblock.test.ts +234 -0
  461. package/src/NumberInput/numberParser.test.ts +222 -0
  462. package/src/NumberInput/numberParser.ts +403 -0
  463. package/src/Outline/Outline.tsx +3 -2
  464. package/src/OverflowList/OverflowList.tsx +3 -2
  465. package/src/Overlay/Overlay.tsx +3 -2
  466. package/src/Pagination/Pagination.test.tsx +3 -2
  467. package/src/Popover/usePopover.tsx +31 -28
  468. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  469. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  470. package/src/PowerSearch/PowerSearch.tsx +34 -5
  471. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +288 -0
  472. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  473. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +111 -1
  474. package/src/PowerSearch/PowerSearchValueEditor.tsx +124 -10
  475. package/src/PowerSearch/types.ts +2 -0
  476. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  477. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  478. package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
  479. package/src/ProgressBar/ProgressBar.test.tsx +24 -0
  480. package/src/ProgressBar/ProgressBar.tsx +28 -8
  481. package/src/Resizable/ResizeHandle.tsx +3 -2
  482. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  483. package/src/SelectableCard/SelectableCard.tsx +3 -2
  484. package/src/Selector/Selector.doc.mjs +26 -1
  485. package/src/Selector/Selector.test.tsx +311 -1
  486. package/src/Selector/Selector.tsx +139 -16
  487. package/src/SideNav/SideNav.doc.mjs +6 -6
  488. package/src/SideNav/SideNav.tsx +4 -2
  489. package/src/SideNav/SideNavHeading.tsx +12 -12
  490. package/src/SideNav/SideNavItem.tsx +4 -2
  491. package/src/Slider/Slider.test.tsx +106 -1
  492. package/src/Slider/Slider.tsx +55 -3
  493. package/src/Spinner/Spinner.test.tsx +147 -1
  494. package/src/Spinner/Spinner.tsx +120 -101
  495. package/src/StatusDot/StatusDot.doc.mjs +8 -2
  496. package/src/StatusDot/StatusDot.test.tsx +9 -0
  497. package/src/StatusDot/StatusDot.tsx +7 -1
  498. package/src/Stepper/Step.doc.mjs +98 -0
  499. package/src/Stepper/Step.tsx +1638 -0
  500. package/src/Stepper/StepStatus.ts +26 -0
  501. package/src/Stepper/Stepper.doc.mjs +370 -0
  502. package/src/Stepper/Stepper.test.tsx +1024 -0
  503. package/src/Stepper/Stepper.tsx +258 -0
  504. package/src/Stepper/StepperContext.ts +71 -0
  505. package/src/Stepper/index.ts +18 -0
  506. package/src/Stepper/stepper.stylex.ts +19 -0
  507. package/src/Switch/Switch.doc.mjs +2 -0
  508. package/src/Switch/Switch.test.tsx +10 -0
  509. package/src/Switch/Switch.tsx +6 -2
  510. package/src/TabList/Tab.doc.mjs +13 -1
  511. package/src/TabList/Tab.tsx +67 -7
  512. package/src/TabList/TabList.doc.mjs +22 -6
  513. package/src/TabList/TabList.test.tsx +559 -0
  514. package/src/TabList/TabList.tsx +490 -22
  515. package/src/TabList/TabListContext.ts +13 -2
  516. package/src/TabList/TabMenu.tsx +3 -2
  517. package/src/TabList/index.ts +6 -2
  518. package/src/Table/BaseTable.tsx +12 -4
  519. package/src/Table/Table.doc.mjs +9 -2
  520. package/src/Table/Table.test.tsx +132 -14
  521. package/src/Table/Table.tsx +3 -8
  522. package/src/Table/TableBody.doc.mjs +40 -0
  523. package/src/Table/TableFooter.doc.mjs +40 -0
  524. package/src/Table/TableHeader.doc.mjs +40 -0
  525. package/src/Table/TableRow.tsx +16 -5
  526. package/src/Table/plugins/filtering/useTableFiltering.test.tsx +44 -6
  527. package/src/Table/plugins/filtering/useTableFiltering.tsx +100 -4
  528. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  529. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  530. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  531. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  532. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  533. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  534. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  535. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  536. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  537. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  538. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
  539. package/src/Table/types.ts +22 -4
  540. package/src/Table/useBaseTablePlugins.ts +5 -0
  541. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  542. package/src/Table/useTablePagination.doc.mjs +7 -0
  543. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  544. package/src/Table/useTableSelection.doc.mjs +31 -0
  545. package/src/Text/Text.test.tsx +23 -1
  546. package/src/Text/Text.tsx +6 -2
  547. package/src/TextArea/TextArea.doc.mjs +8 -2
  548. package/src/TextArea/TextArea.test.tsx +11 -0
  549. package/src/TextArea/TextArea.tsx +15 -8
  550. package/src/TextInput/TextInput.doc.mjs +10 -0
  551. package/src/TextInput/TextInput.tsx +3 -2
  552. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  553. package/src/TimeInput/TimeInput.tsx +2 -2
  554. package/src/Timestamp/Timestamp.test.tsx +18 -0
  555. package/src/Timestamp/Timestamp.tsx +4 -2
  556. package/src/Toast/Toast.doc.mjs +6 -0
  557. package/src/Toast/ToastViewport.tsx +17 -1
  558. package/src/Token/TokenLink.tsx +2 -2
  559. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  560. package/src/Tokenizer/Tokenizer.tsx +4 -0
  561. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  562. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  563. package/src/Tooltip/Tooltip.test.tsx +32 -0
  564. package/src/Tooltip/Tooltip.tsx +5 -0
  565. package/src/Tooltip/useTooltip.tsx +47 -24
  566. package/src/TopNav/TopNavHeading.tsx +5 -4
  567. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  568. package/src/TopNav/TopNavMenu.tsx +4 -3
  569. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  570. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  571. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  572. package/src/Typeahead/Typeahead.test.tsx +49 -0
  573. package/src/Typeahead/Typeahead.tsx +3 -2
  574. package/src/hooks/index.ts +2 -0
  575. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  576. package/src/hooks/useFocusTrap.test.tsx +14 -0
  577. package/src/hooks/useFocusTrap.ts +62 -111
  578. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  579. package/src/hooks/useHotkeys.ts +3 -18
  580. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  581. package/src/hooks/useMergedRefs.test.tsx +62 -0
  582. package/src/hooks/useMergedRefs.ts +36 -0
  583. package/src/index.ts +1 -0
  584. package/src/raw-import.d.ts +14 -0
  585. package/src/theme/MediaTheme.doc.mjs +5 -5
  586. package/src/theme/derivedVarRegistry.test.ts +19 -3
  587. package/src/theme/derivedVarRegistry.ts +23 -4
  588. package/src/theme/generateThemeRules.test.ts +37 -0
  589. package/src/theme/syntax/tokens.ts +5 -2
  590. package/src/utils/groupItems.ts +12 -6
  591. package/src/utils/index.ts +1 -1
  592. package/src/utils/isApplePlatform.test.ts +61 -0
  593. package/src/utils/isApplePlatform.ts +37 -0
  594. package/src/utils/sharedResizeObserver.ts +10 -2
  595. package/src/utils/timeParser.test.ts +6 -1
  596. package/src/utils/timeParser.ts +35 -13
  597. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -151,6 +151,19 @@ describe('Button', () => {
151
151
  expect(ref).toHaveBeenCalledWith(expect.any(HTMLButtonElement));
152
152
  });
153
153
 
154
+ it('keeps its merged ref attached across unrelated rerenders', () => {
155
+ const ref = vi.fn();
156
+ const {rerender} = render(<Button label="Test" ref={ref} />);
157
+ const button = screen.getByRole('button');
158
+ expect(ref).toHaveBeenLastCalledWith(button);
159
+ ref.mockClear();
160
+
161
+ rerender(<Button label="Test" variant="primary" ref={ref} />);
162
+
163
+ expect(ref).not.toHaveBeenCalled();
164
+ expect(screen.getByRole('button')).toBe(button);
165
+ });
166
+
154
167
  // endContent tests
155
168
  it('renders endContent after label', () => {
156
169
  render(
@@ -463,6 +476,21 @@ describe('Button', () => {
463
476
  });
464
477
 
465
478
  describe('elevation', () => {
479
+ it('reflects each elevation level as a theme attribute', () => {
480
+ const attrFor = (elevation: 'none' | 'low' | 'med' | 'high') => {
481
+ const {container} = render(
482
+ <Button label="Save" elevation={elevation} />,
483
+ );
484
+ return container
485
+ .querySelector('button')!
486
+ .getAttribute('data-elevation');
487
+ };
488
+ expect(attrFor('none')).toBe('none');
489
+ expect(attrFor('low')).toBe('low');
490
+ expect(attrFor('med')).toBe('med');
491
+ expect(attrFor('high')).toBe('high');
492
+ });
493
+
466
494
  it('renders a distinct class for each elevation level', () => {
467
495
  const classFor = (elevation: 'none' | 'low' | 'med' | 'high') => {
468
496
  const {container} = render(
@@ -480,13 +508,13 @@ describe('Button', () => {
480
508
  });
481
509
 
482
510
  it('defaults to flat (elevation none)', () => {
483
- const {container: def} = render(<Button label="Save" />);
511
+ const {container} = render(<Button label="Save" />);
512
+ const button = container.querySelector('button')!;
513
+ expect(button).toHaveAttribute('data-elevation', 'none');
484
514
  const {container: none} = render(
485
515
  <Button label="Save" elevation="none" />,
486
516
  );
487
- expect(def.querySelector('button')!.className).toBe(
488
- none.querySelector('button')!.className,
489
- );
517
+ expect(button.className).toBe(none.querySelector('button')!.className);
490
518
  });
491
519
  });
492
520
 
@@ -42,7 +42,8 @@ import {VisuallyHidden} from '../VisuallyHidden';
42
42
  import {EDGE_COMP_ATTR} from '../Layout/edgeCompensation.stylex';
43
43
  import {useSize} from '../SizeContext/SizeContext';
44
44
  import {useButtonGroup} from '../ButtonGroup/ButtonGroupContext';
45
- import {mergeProps, mergeRefs} from '../utils';
45
+ import {mergeProps} from '../utils';
46
+ import {useMergedRefs} from '../hooks/useMergedRefs';
46
47
  import {useLinkComponent} from '../Link/useLinkComponent';
47
48
  import type {LinkComponentType} from '../Link/types';
48
49
  import {themeProps} from '../utils/themeProps';
@@ -688,7 +689,13 @@ export function Button({
688
689
  );
689
690
 
690
691
  const sharedMergedProps = mergeProps(
691
- themeProps('button', {variant, size}),
692
+ // Inside a group the group owns the surface's elevation, so the button
693
+ // reflects the tier it actually paints rather than the prop it was handed.
694
+ themeProps('button', {
695
+ variant,
696
+ size,
697
+ elevation: buttonGroup ? 'none' : elevation,
698
+ }),
692
699
  sharedStylexProps,
693
700
  className,
694
701
  style,
@@ -757,9 +764,9 @@ export function Button({
757
764
  : null;
758
765
 
759
766
  // Merge the consumer ref with the tooltip hook's trigger ref so both point at
760
- // the same element. mergeRefs tolerates undefined, so this is a no-op for the
761
- // tooltip side when no tooltip is set.
762
- const mergedButtonRef = mergeRefs(
767
+ // the same element. useMergedRefs tolerates undefined, so this is a no-op for
768
+ // the tooltip side when no tooltip is set.
769
+ const mergedButtonRef = useMergedRefs(
763
770
  ref,
764
771
  tooltip != null ? tooltipHook.ref : undefined,
765
772
  );
@@ -15,7 +15,7 @@ export const docs = {
15
15
  },
16
16
  theming: {
17
17
  targets: [
18
- {className: 'astryx-button-group', visualProps: ['size', 'orientation']},
18
+ {className: 'astryx-button-group', visualProps: ['size', 'orientation', 'elevation']},
19
19
  ],
20
20
  },
21
21
  components: [
@@ -46,6 +46,9 @@ export const docs = {
46
46
  {guidance: false, description: "Don't mix wildly different actions. A Save button next to a Delete button in the same group is confusing."},
47
47
  {guidance: false, description: "Don't use ButtonGroup for navigation. Use SegmentedControl or TabList for switching between views."},
48
48
  {guidance: false, description: "Don't nest ButtonGroups. If you need multiple groups, place them side by side with a gap."},
49
+ {guidance: true, description: 'Name the group for what its buttons act on. The label is the group\'s accessible name and a screen reader reads it before each member.'},
50
+ {guidance: true, description: 'Keep the group a single Tab stop. Arrow keys move between members along the orientation, Home/End jump to the ends, and disabled members are skipped. This is the WAI-ARIA APG roving tabindex technique: https://www.w3.org/WAI/ARIA/apg/practices/keyboard-interface/#kbd_roving_tabindex'},
51
+ {guidance: false, description: "Don't disable the group to show that an action is in flight. A disabled member drops focus, so a keyboard user loses their place; leave the group enabled and show progress on the button that started the work."},
49
52
  ],
50
53
  anatomy: [
51
54
  {name: 'Button', required: true, description: 'One or more Button or IconButton children that form the connected group.'},
@@ -84,6 +87,9 @@ export const docsZh = {
84
87
  {guidance: false, description: '不要混合差异很大的操作,将保存按钮和删除按钮放在同一组中会令人困惑。'},
85
88
  {guidance: false, description: '不要使用 ButtonGroup 进行导航,使用 SegmentedControl 或 TabList 切换视图。'},
86
89
  {guidance: false, description: '不要嵌套 ButtonGroup。如需多个组,请并排放置并留有间隔。'},
90
+ {guidance: true, description: '按钮组的标签应说明这些按钮作用于什么。该标签是按钮组的无障碍名称,屏幕阅读器会在每个成员之前朗读它。'},
91
+ {guidance: true, description: '按钮组是单个 Tab 停靠点。方向键沿排列方向在成员间移动焦点,Home/End 跳到首尾,禁用成员会被跳过——即 WAI-ARIA APG 的 roving tabindex 技术。'},
92
+ {guidance: false, description: '不要用禁用整个按钮组来表示操作进行中。禁用的成员会失去焦点,键盘用户会丢失位置;请保持按钮组可用,并在发起操作的按钮上显示进度。'},
87
93
  ],
88
94
  anatomy: [
89
95
  {name: '按钮', required: true, description: '一个或多个 Button 或 IconButton 子元素,形成连接的组。'},
@@ -105,6 +111,9 @@ export const docsDense = {
105
111
  {guidance: false, description: "Don't mix unrelated actions in one group."},
106
112
  {guidance: false, description: "Don't use for navigation. Use SegmentedControl or TabList."},
107
113
  {guidance: false, description: "Don't nest ButtonGroups."},
114
+ {guidance: true, description: 'Label the group for what its buttons act on; it is the accessible name.'},
115
+ {guidance: true, description: 'Single Tab stop; arrows move between members, Home/End to ends, disabled skipped (APG roving tabindex).'},
116
+ {guidance: false, description: "Don't disable the group for an in-flight action; a disabled member drops focus."},
108
117
  ],
109
118
  },
110
119
  components: [
@@ -12,7 +12,9 @@
12
12
  */
13
13
 
14
14
  import {describe, it, expect} from 'vitest';
15
- import {render, screen} from '@testing-library/react';
15
+ import type {ReactNode} from 'react';
16
+ import {render, screen, fireEvent} from '@testing-library/react';
17
+ import userEvent from '@testing-library/user-event';
16
18
  import {transformSync} from '@babel/core';
17
19
  import stylexBabelPlugin from '@stylexjs/babel-plugin';
18
20
  import {readFileSync} from 'node:fs';
@@ -611,5 +613,406 @@ describe('ButtonGroup', () => {
611
613
  none.querySelector('[role="group"]')!.className,
612
614
  );
613
615
  });
616
+
617
+ it('exposes the elevation to a theme as a data attribute', () => {
618
+ render(
619
+ <ButtonGroup label="Actions" elevation="high">
620
+ <Button label="One" />
621
+ </ButtonGroup>,
622
+ );
623
+
624
+ expect(screen.getByRole('group')).toHaveAttribute(
625
+ 'data-elevation',
626
+ 'high',
627
+ );
628
+ });
629
+
630
+ it('reports flat on a member, since the group owns the surface', () => {
631
+ render(
632
+ <ButtonGroup label="Actions" elevation="high">
633
+ <Button label="One" elevation="high" />
634
+ </ButtonGroup>,
635
+ );
636
+
637
+ expect(screen.getByRole('button', {name: 'One'})).toHaveAttribute(
638
+ 'data-elevation',
639
+ 'none',
640
+ );
641
+ });
642
+ });
643
+
644
+ // ===========================================================================
645
+ // Keyboard navigation
646
+ //
647
+ // The group wires useListFocus, so arrow keys move focus between members.
648
+ // The last two tests cover the boundary: a member can own a layer, whose
649
+ // keys belong to that layer, and the group itself can sit inside a layer,
650
+ // whose keys are still the group's own. Both must hold at once.
651
+ // ===========================================================================
652
+ describe('keyboard navigation', () => {
653
+ const clipboard = (
654
+ <ButtonGroup label="Actions">
655
+ <Button label="Copy" />
656
+ <Button label="Cut" />
657
+ <Button label="Paste" />
658
+ </ButtonGroup>
659
+ );
660
+
661
+ it('moves focus between members with the arrow keys, wrapping at the end', async () => {
662
+ const user = userEvent.setup();
663
+ render(clipboard);
664
+
665
+ screen.getByRole('button', {name: 'Copy'}).focus();
666
+
667
+ await user.keyboard('{ArrowRight}');
668
+ expect(screen.getByRole('button', {name: 'Cut'})).toHaveFocus();
669
+
670
+ await user.keyboard('{ArrowRight}{ArrowRight}');
671
+ expect(screen.getByRole('button', {name: 'Copy'})).toHaveFocus();
672
+
673
+ await user.keyboard('{ArrowLeft}');
674
+ expect(screen.getByRole('button', {name: 'Paste'})).toHaveFocus();
675
+ });
676
+
677
+ it('jumps to the first and last member with Home and End', async () => {
678
+ const user = userEvent.setup();
679
+ render(clipboard);
680
+
681
+ screen.getByRole('button', {name: 'Cut'}).focus();
682
+
683
+ await user.keyboard('{End}');
684
+ expect(screen.getByRole('button', {name: 'Paste'})).toHaveFocus();
685
+
686
+ await user.keyboard('{Home}');
687
+ expect(screen.getByRole('button', {name: 'Copy'})).toHaveFocus();
688
+ });
689
+
690
+ it('uses the vertical arrows when the group is vertical', async () => {
691
+ const user = userEvent.setup();
692
+ render(
693
+ <ButtonGroup label="Actions" orientation="vertical">
694
+ <Button label="Copy" />
695
+ <Button label="Cut" />
696
+ </ButtonGroup>,
697
+ );
698
+
699
+ screen.getByRole('button', {name: 'Copy'}).focus();
700
+
701
+ await user.keyboard('{ArrowDown}');
702
+ expect(screen.getByRole('button', {name: 'Cut'})).toHaveFocus();
703
+
704
+ await user.keyboard('{ArrowUp}');
705
+ expect(screen.getByRole('button', {name: 'Copy'})).toHaveFocus();
706
+ });
707
+
708
+ it('follows visual direction in RTL', async () => {
709
+ const user = userEvent.setup();
710
+ // dir sits on the group because jsdom resolves computed direction from
711
+ // the element's own attribute, not from an ancestor.
712
+ render(
713
+ <ButtonGroup label="Actions" dir="rtl">
714
+ <Button label="Copy" />
715
+ <Button label="Cut" />
716
+ <Button label="Paste" />
717
+ </ButtonGroup>,
718
+ );
719
+ screen.getByRole('button', {name: 'Copy'}).focus();
720
+
721
+ await user.keyboard('{ArrowLeft}');
722
+ expect(screen.getByRole('button', {name: 'Cut'})).toHaveFocus();
723
+
724
+ await user.keyboard('{ArrowRight}');
725
+ expect(screen.getByRole('button', {name: 'Copy'})).toHaveFocus();
726
+ });
727
+
728
+ it('skips a disabled member', async () => {
729
+ const user = userEvent.setup();
730
+ render(
731
+ <ButtonGroup label="Actions">
732
+ <Button label="Copy" />
733
+ <Button label="Cut" isDisabled />
734
+ <Button label="Paste" />
735
+ </ButtonGroup>,
736
+ );
737
+
738
+ screen.getByRole('button', {name: 'Copy'}).focus();
739
+
740
+ await user.keyboard('{ArrowRight}');
741
+ expect(screen.getByRole('button', {name: 'Paste'})).toHaveFocus();
742
+ });
743
+
744
+ it('leaves arrow keys to a member\u2019s open menu', async () => {
745
+ const user = userEvent.setup();
746
+ render(
747
+ <ButtonGroup label="Approve action">
748
+ <Button label="Allow once" />
749
+ <DropdownMenu
750
+ hasChevron={false}
751
+ button={{label: 'Allow options', isIconOnly: true}}
752
+ items={[{label: 'Allow for 30 minutes'}, {label: 'Always allow'}]}
753
+ />
754
+ </ButtonGroup>,
755
+ );
756
+
757
+ await user.click(screen.getByRole('button', {name: /Allow options/}));
758
+ const item = screen.getByRole('menuitem', {
759
+ name: 'Allow for 30 minutes',
760
+ hidden: true,
761
+ });
762
+ item.focus();
763
+
764
+ // Assert positively: the menu item keeps focus. `.not.toHaveFocus()` on
765
+ // the group's buttons would also pass if the group had thrown focus
766
+ // somewhere else entirely.
767
+ fireEvent.keyDown(item, {key: 'ArrowRight'});
768
+ expect(item).toHaveFocus();
769
+
770
+ fireEvent.keyDown(item, {key: 'ArrowLeft'});
771
+ expect(item).toHaveFocus();
772
+
773
+ // End belongs to the menu's own list focus, so it lands on the last menu
774
+ // item rather than the group's last button.
775
+ fireEvent.keyDown(item, {key: 'End'});
776
+ expect(
777
+ screen.getByRole('menuitem', {name: 'Always allow', hidden: true}),
778
+ ).toHaveFocus();
779
+ });
780
+
781
+ it('leaves a link member in the arrow order', async () => {
782
+ const user = userEvent.setup();
783
+ render(
784
+ <ButtonGroup label="Docs actions">
785
+ <Button label="Save" />
786
+ <Button label="Docs" href="https://example.com" />
787
+ <Button label="Print" />
788
+ </ButtonGroup>,
789
+ );
790
+
791
+ screen.getByRole('button', {name: 'Save'}).focus();
792
+
793
+ await user.keyboard('{ArrowRight}');
794
+ expect(screen.getByRole('link', {name: 'Docs'})).toHaveFocus();
795
+
796
+ await user.keyboard('{ArrowRight}');
797
+ expect(screen.getByRole('button', {name: 'Print'})).toHaveFocus();
798
+ });
799
+
800
+ it('still moves between its own members when the group sits inside a popover', async () => {
801
+ const user = userEvent.setup();
802
+ // A group rendered inside a Popover, ContextMenu, HoverCard or Toast has
803
+ // a `[popover]` ancestor, because that is how useLayer mounts a layer.
804
+ // A bare `popover` div is the same DOM fact without the layer harness.
805
+ render(
806
+ <div popover="manual">
807
+ <ButtonGroup label="Actions">
808
+ <Button label="Copy" />
809
+ <Button label="Cut" />
810
+ <Button label="Paste" />
811
+ </ButtonGroup>
812
+ </div>,
813
+ );
814
+
815
+ // A closed popover is display:none in jsdom, so its contents read as
816
+ // inaccessible; focus and key handling still work on them.
817
+ const button = (name: string) =>
818
+ screen.getByRole('button', {name, hidden: true});
819
+
820
+ button('Copy').focus();
821
+
822
+ await user.keyboard('{ArrowRight}');
823
+ expect(button('Cut')).toHaveFocus();
824
+
825
+ await user.keyboard('{End}');
826
+ expect(button('Paste')).toHaveFocus();
827
+
828
+ await user.keyboard('{Home}');
829
+ expect(button('Copy')).toHaveFocus();
830
+ });
831
+ });
832
+
833
+ // ===========================================================================
834
+ // Roving tabindex
835
+ //
836
+ // The group is a single tab stop: exactly one enabled member carries
837
+ // tabindex="0", the rest carry -1, and the stop moves with arrow navigation.
838
+ // Entering and leaving is asserted through the real tab order as well as the
839
+ // attribute, because the attribute alone says nothing about a member the
840
+ // itemSelector missed — such a member stays natively tabbable and quietly
841
+ // adds a second stop.
842
+ // ===========================================================================
843
+ describe('roving tabindex', () => {
844
+ const tabIndexOf = (name: string) =>
845
+ screen.getByRole('button', {name}).getAttribute('tabindex');
846
+
847
+ const bracketed = (group: ReactNode) => (
848
+ <>
849
+ <button type="button">before</button>
850
+ {group}
851
+ <button type="button">after</button>
852
+ </>
853
+ );
854
+
855
+ const clipboard = (
856
+ <ButtonGroup label="Actions">
857
+ <Button label="Copy" />
858
+ <Button label="Cut" />
859
+ <Button label="Paste" />
860
+ </ButtonGroup>
861
+ );
862
+
863
+ it('gives the group one tab stop, on the first member', () => {
864
+ render(clipboard);
865
+
866
+ expect(tabIndexOf('Copy')).toBe('0');
867
+ expect(tabIndexOf('Cut')).toBe('-1');
868
+ expect(tabIndexOf('Paste')).toBe('-1');
869
+ });
870
+
871
+ it('takes one Tab to enter and one to leave', async () => {
872
+ const user = userEvent.setup();
873
+ render(bracketed(clipboard));
874
+
875
+ await user.tab();
876
+ expect(screen.getByRole('button', {name: 'before'})).toHaveFocus();
877
+
878
+ await user.tab();
879
+ expect(screen.getByRole('button', {name: 'Copy'})).toHaveFocus();
880
+
881
+ await user.tab();
882
+ expect(screen.getByRole('button', {name: 'after'})).toHaveFocus();
883
+ });
884
+
885
+ it('moves the tab stop with arrow navigation', async () => {
886
+ const user = userEvent.setup();
887
+ render(clipboard);
888
+
889
+ screen.getByRole('button', {name: 'Copy'}).focus();
890
+ await user.keyboard('{ArrowRight}');
891
+
892
+ expect(tabIndexOf('Copy')).toBe('-1');
893
+ expect(tabIndexOf('Cut')).toBe('0');
894
+ });
895
+
896
+ it('never parks the tab stop on a disabled member', () => {
897
+ render(
898
+ <ButtonGroup label="Actions">
899
+ <Button label="Copy" isDisabled />
900
+ <Button label="Cut" />
901
+ <Button label="Paste" />
902
+ </ButtonGroup>,
903
+ );
904
+
905
+ expect(tabIndexOf('Copy')).toBe('-1');
906
+ expect(tabIndexOf('Cut')).toBe('0');
907
+ });
908
+
909
+ it('repairs the tab stop when the member holding it unmounts', () => {
910
+ const {rerender} = render(
911
+ <ButtonGroup label="Actions">
912
+ <Button label="Copy" />
913
+ <Button label="Cut" />
914
+ </ButtonGroup>,
915
+ );
916
+ expect(tabIndexOf('Copy')).toBe('0');
917
+
918
+ rerender(
919
+ <ButtonGroup label="Actions">
920
+ <Button label="Cut" />
921
+ </ButtonGroup>,
922
+ );
923
+
924
+ expect(tabIndexOf('Cut')).toBe('0');
925
+ });
926
+
927
+ it('repairs the tab stop when the member holding it becomes disabled', () => {
928
+ const {rerender} = render(
929
+ <ButtonGroup label="Actions">
930
+ <Button label="Copy" />
931
+ <Button label="Cut" />
932
+ </ButtonGroup>,
933
+ );
934
+ expect(tabIndexOf('Copy')).toBe('0');
935
+
936
+ rerender(
937
+ <ButtonGroup label="Actions">
938
+ <Button label="Copy" isDisabled />
939
+ <Button label="Cut" />
940
+ </ButtonGroup>,
941
+ );
942
+
943
+ expect(tabIndexOf('Copy')).toBe('-1');
944
+ expect(tabIndexOf('Cut')).toBe('0');
945
+ });
946
+
947
+ it('counts a link member as the tab stop', async () => {
948
+ const user = userEvent.setup();
949
+ render(
950
+ bracketed(
951
+ <ButtonGroup label="Docs actions">
952
+ <Button label="Docs" href="https://example.com" />
953
+ <Button label="Print" />
954
+ </ButtonGroup>,
955
+ ),
956
+ );
957
+
958
+ expect(
959
+ screen.getByRole('link', {name: 'Docs'}).getAttribute('tabindex'),
960
+ ).toBe('0');
961
+ expect(tabIndexOf('Print')).toBe('-1');
962
+
963
+ await user.tab();
964
+ await user.tab();
965
+ expect(screen.getByRole('link', {name: 'Docs'})).toHaveFocus();
966
+
967
+ await user.tab();
968
+ expect(screen.getByRole('button', {name: 'after'})).toHaveFocus();
969
+ });
970
+
971
+ it('contributes no tab stop when the whole group is disabled', async () => {
972
+ const user = userEvent.setup();
973
+ render(
974
+ bracketed(
975
+ <ButtonGroup label="Actions" isDisabled>
976
+ <Button label="Copy" />
977
+ <Button label="Cut" />
978
+ </ButtonGroup>,
979
+ ),
980
+ );
981
+
982
+ await user.tab();
983
+ expect(screen.getByRole('button', {name: 'before'})).toHaveFocus();
984
+
985
+ await user.tab();
986
+ expect(screen.getByRole('button', {name: 'after'})).toHaveFocus();
987
+ });
988
+
989
+ it('leaves the tab stop on its own members when a member owns an open menu', async () => {
990
+ const user = userEvent.setup();
991
+ render(
992
+ <ButtonGroup label="Approve action">
993
+ <Button label="Allow once" />
994
+ <DropdownMenu
995
+ hasChevron={false}
996
+ button={{label: 'Allow options', isIconOnly: true}}
997
+ items={[{label: 'Allow for 30 minutes'}, {label: 'Always allow'}]}
998
+ />
999
+ </ButtonGroup>,
1000
+ );
1001
+
1002
+ const trigger = screen.getByRole('button', {name: /Allow options/});
1003
+ expect(tabIndexOf('Allow once')).toBe('0');
1004
+ expect(trigger.getAttribute('tabindex')).toBe('-1');
1005
+
1006
+ await user.click(trigger);
1007
+
1008
+ // The menu's own items are a different list level (the boundary), so the
1009
+ // group must not stamp them or hand them its tab stop.
1010
+ const item = screen.getByRole('menuitem', {
1011
+ name: 'Allow for 30 minutes',
1012
+ hidden: true,
1013
+ });
1014
+ expect(item.getAttribute('tabindex')).not.toBe('0');
1015
+ expect(tabIndexOf('Allow once')).toBe('0');
1016
+ });
614
1017
  });
615
1018
  });
@@ -28,12 +28,13 @@ import {radiusVars, shadowVars} from '../theme/tokens.stylex';
28
28
  import type {Elevation} from '../utils/types';
29
29
  import {SizeProvider, useSize} from '../SizeContext/SizeContext';
30
30
  import {useListFocus} from '../hooks/useListFocus';
31
- import {mergeProps, mergeRefs, composeEventHandlers} from '../utils';
31
+ import {mergeProps, composeEventHandlers} from '../utils';
32
32
  import type {BaseProps} from '../BaseProps';
33
33
  import {ButtonGroupContext} from './ButtonGroupContext';
34
34
  import type {ButtonGroupOrientation} from './ButtonGroupContext';
35
35
  import {themeProps} from '../utils/themeProps';
36
36
 
37
+ import {useMergedRefs} from '../hooks/useMergedRefs';
37
38
  // =============================================================================
38
39
  // Props
39
40
  // =============================================================================
@@ -130,6 +131,10 @@ const elevationStyles = stylex.create({
130
131
  * Children automatically detect the group via context and apply position-aware
131
132
  * styles in pure CSS.
132
133
  *
134
+ * The group is a single tab stop: arrow keys move between members along the
135
+ * orientation, Home/End jump to the ends, focus wraps, and disabled members are
136
+ * skipped (the APG roving tabindex technique, via `useListFocus`).
137
+ *
133
138
  * Members that render their own layer — a Button with a `tooltip`, or a
134
139
  * DropdownMenu — compose correctly, including as the trailing member.
135
140
  *
@@ -166,13 +171,25 @@ export function ButtonGroup({
166
171
  ref,
167
172
  'data-testid': testId,
168
173
  onKeyDown,
174
+ onFocus,
169
175
  ...props
170
176
  }: ButtonGroupProps): ReactNode {
171
177
  const size = useSize(sizeProp, 'md');
172
178
 
173
- const {listRef, handleKeyDown} = useListFocus<HTMLDivElement>({
174
- itemSelector: 'button, [tabindex="0"]',
179
+ const {listRef, handleKeyDown, handleFocus} = useListFocus<HTMLDivElement>({
180
+ // Roving rewrites `tabindex` on every item, so the selector cannot key off
181
+ // a value: `[tabindex="0"]` would drop each member the moment it is
182
+ // stamped -1. `a[href]` catches a Button rendered with `href`.
183
+ itemSelector: 'button, a[href], [tabindex]',
184
+ // A member's layer renders inline inside the group (useLayer does not
185
+ // portal it), so a key pressed in an open DropdownMenu bubbles here. The
186
+ // boundary is the group's own root or any layer, whichever is nearer to
187
+ // the target. Naming the group's own root is what keeps a group rendered
188
+ // inside a Popover or Toast working: bailing on any [popover] ancestor
189
+ // instead would swallow every arrow key it owns.
190
+ boundarySelector: '[role="group"], [popover]',
175
191
  orientation,
192
+ hasRovingTabIndex: true,
176
193
  });
177
194
 
178
195
  const contextValue = useMemo(
@@ -184,10 +201,10 @@ export function ButtonGroup({
184
201
  <ButtonGroupContext value={contextValue}>
185
202
  <SizeProvider value={size}>
186
203
  <div
187
- ref={mergeRefs(ref, listRef)}
204
+ ref={useMergedRefs(ref, listRef)}
188
205
  {...props}
189
206
  {...mergeProps(
190
- themeProps('button-group', {size, orientation}),
207
+ themeProps('button-group', {size, orientation, elevation}),
191
208
  stylex.props(
192
209
  styles.group,
193
210
  orientation === 'vertical' && styles.vertical,
@@ -200,6 +217,7 @@ export function ButtonGroup({
200
217
  role="group"
201
218
  aria-label={label}
202
219
  onKeyDown={composeEventHandlers(onKeyDown, handleKeyDown)}
220
+ onFocus={composeEventHandlers(onFocus, handleFocus)}
203
221
  aria-disabled={isDisabled || undefined}
204
222
  data-testid={testId}>
205
223
  {children}
@@ -83,7 +83,8 @@ export const docs = {
83
83
  {
84
84
  name: 'focusDate',
85
85
  type: 'ISODateString',
86
- description: 'Controlled visible month.',
86
+ description:
87
+ 'Controlled visible month. Unset, the calendar opens on the selected date, else on today clamped into the min/max window.',
87
88
  },
88
89
  {
89
90
  name: 'onFocusDateChange',
@@ -156,7 +157,7 @@ export const docsZh = {
156
157
  {name: 'dateConstraints', type: 'Array<(date: Date) => boolean>', description: '自定义约束函数。'},
157
158
  {name: 'maxRangeSpan', type: 'number', description: '范围模式:范围最多可跨越的天数,含首尾两端(7 = 7 天窗口)。选定起始日后限制窗口大小。'},
158
159
  {name: 'minRangeSpan', type: 'number', description: '范围模式:范围最少需跨越的天数,含首尾两端(2 表示禁止单日范围)。默认为 1。'},
159
- {name: 'focusDate', type: 'ISODateString', description: '受控可见月份。'},
160
+ {name: 'focusDate', type: 'ISODateString', description: '受控可见月份。未设置时,日历打开时显示已选日期所在月份;若无选中值,则显示今天,并将其限制在 min/max 范围内。'},
160
161
  {name: 'onFocusDateChange', type: '(focusDate: ISODateString) => void', description: '导航回调函数。'},
161
162
  {name: 'handleRef', type: 'React.Ref<CalendarHandle>', description: '日历导航的命令式句柄,包括 navigateTo()。'},
162
163
  {name: 'hasOutsideDays', type: 'boolean', description: '显示相邻月份的日期。', default: 'true'},
@@ -221,7 +222,7 @@ export const docsDense = {
221
222
  dateConstraints: 'custom constraint fns',
222
223
  maxRangeSpan: 'range mode: max days a range may span, both ends counted (7 = 7-day window)',
223
224
  minRangeSpan: 'range mode: min days a range must span, both ends counted (default 1)',
224
- focusDate: 'controlled visible month',
225
+ focusDate: 'controlled visible month (default: selected date, else today clamped into min/max)',
225
226
  onFocusDateChange: 'navigation callback',
226
227
  handleRef: 'imperative navigation handle',
227
228
  hasOutsideDays: 'show days from adjacent months',