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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (631) hide show
  1. package/CHANGELOG.md +105 -0
  2. package/README.md +131 -13
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +11 -5
  5. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  6. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  7. package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
  8. package/dist/Banner/Banner.d.ts +7 -0
  9. package/dist/Banner/Banner.d.ts.map +1 -1
  10. package/dist/Banner/Banner.js +9 -2
  11. package/dist/Breadcrumbs/BreadcrumbItem.d.ts +1 -1
  12. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  13. package/dist/Breadcrumbs/BreadcrumbItem.js +24 -5
  14. package/dist/Button/Button.d.ts +12 -2
  15. package/dist/Button/Button.d.ts.map +1 -1
  16. package/dist/Button/Button.js +53 -17
  17. package/dist/Calendar/Calendar.d.ts +5 -4
  18. package/dist/Calendar/Calendar.d.ts.map +1 -1
  19. package/dist/Calendar/Calendar.js +82 -43
  20. package/dist/Calendar/hooks/index.d.ts +0 -2
  21. package/dist/Calendar/hooks/index.d.ts.map +1 -1
  22. package/dist/Calendar/hooks/index.js +1 -2
  23. package/dist/Calendar/index.d.ts +3 -3
  24. package/dist/Calendar/index.d.ts.map +1 -1
  25. package/dist/Calendar/index.js +1 -1
  26. package/dist/Calendar/styles.d.ts +3 -9
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +3 -11
  29. package/dist/Carousel/Carousel.d.ts.map +1 -1
  30. package/dist/Carousel/Carousel.js +17 -3
  31. package/dist/Center/Center.d.ts +11 -1
  32. package/dist/Center/Center.d.ts.map +1 -1
  33. package/dist/Center/Center.js +9 -3
  34. package/dist/Chat/ChatMessageList.d.ts +14 -1
  35. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  36. package/dist/Chat/ChatMessageList.js +2 -0
  37. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  38. package/dist/Chat/useTriggerMenu.js +4 -1
  39. package/dist/CheckboxInput/CheckboxInput.d.ts +23 -2
  40. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  41. package/dist/CheckboxInput/CheckboxInput.js +51 -9
  42. package/dist/CheckboxList/CheckboxList.d.ts +13 -1
  43. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  44. package/dist/CheckboxList/CheckboxList.js +39 -9
  45. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  46. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  47. package/dist/Citation/Citation.d.ts.map +1 -1
  48. package/dist/Citation/Citation.js +3 -3
  49. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  50. package/dist/CodeBlock/CodeBlock.js +49 -37
  51. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  52. package/dist/CommandPalette/CommandPalette.js +5 -5
  53. package/dist/ContextMenu/ContextMenu.d.ts +4 -5
  54. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  55. package/dist/ContextMenu/ContextMenu.js +93 -11
  56. package/dist/DateInput/DateInput.d.ts +23 -1
  57. package/dist/DateInput/DateInput.d.ts.map +1 -1
  58. package/dist/DateInput/DateInput.js +59 -8
  59. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  60. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  61. package/dist/DateRangeInput/DateRangeInput.js +42 -9
  62. package/dist/DateTimeInput/DateTimeInput.d.ts +35 -2
  63. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  64. package/dist/DateTimeInput/DateTimeInput.js +78 -15
  65. package/dist/Dialog/Dialog.d.ts.map +1 -1
  66. package/dist/Dialog/Dialog.js +12 -0
  67. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -8
  68. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  69. package/dist/DropdownMenu/DropdownMenu.js +42 -14
  70. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  71. package/dist/EmptyState/EmptyState.js +7 -1
  72. package/dist/Field/Field.d.ts +19 -2
  73. package/dist/Field/Field.d.ts.map +1 -1
  74. package/dist/Field/Field.js +4 -0
  75. package/dist/Field/FieldLabel.d.ts +21 -2
  76. package/dist/Field/FieldLabel.d.ts.map +1 -1
  77. package/dist/Field/FieldLabel.js +41 -26
  78. package/dist/FileInput/FileInput.d.ts +22 -1
  79. package/dist/FileInput/FileInput.d.ts.map +1 -1
  80. package/dist/FileInput/FileInput.js +56 -10
  81. package/dist/Grid/Grid.d.ts +11 -1
  82. package/dist/Grid/Grid.d.ts.map +1 -1
  83. package/dist/Grid/Grid.js +8 -0
  84. package/dist/HoverCard/HoverCard.d.ts +2 -2
  85. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  86. package/dist/HoverCard/HoverCard.js +18 -6
  87. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  88. package/dist/HoverCard/useHoverCard.js +7 -3
  89. package/dist/InputGroup/InputGroup.d.ts +2 -2
  90. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  91. package/dist/InputGroup/InputGroup.js +14 -5
  92. package/dist/InputGroup/InputGroupContext.d.ts +4 -0
  93. package/dist/InputGroup/InputGroupContext.d.ts.map +1 -1
  94. package/dist/InputGroup/InputGroupContext.js +2 -2
  95. package/dist/InputGroup/index.d.ts +1 -0
  96. package/dist/InputGroup/index.d.ts.map +1 -1
  97. package/dist/Kbd/Kbd.d.ts.map +1 -1
  98. package/dist/Kbd/Kbd.js +34 -2
  99. package/dist/Layer/anchorName.d.ts +20 -0
  100. package/dist/Layer/anchorName.d.ts.map +1 -0
  101. package/dist/Layer/anchorName.js +36 -0
  102. package/dist/Layer/layerAnimations.stylex.d.ts +4 -0
  103. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  104. package/dist/Layer/layerAnimations.stylex.js +8 -4
  105. package/dist/Layer/useLayer.d.ts +29 -0
  106. package/dist/Layer/useLayer.d.ts.map +1 -1
  107. package/dist/Layer/useLayer.js +55 -11
  108. package/dist/Layout/Layout.d.ts +4 -11
  109. package/dist/Layout/Layout.d.ts.map +1 -1
  110. package/dist/Layout/Layout.js +10 -11
  111. package/dist/Layout/LayoutFooter.d.ts +2 -2
  112. package/dist/Layout/LayoutFooter.d.ts.map +1 -1
  113. package/dist/Layout/LayoutHeader.d.ts +2 -2
  114. package/dist/Layout/LayoutHeader.d.ts.map +1 -1
  115. package/dist/Layout/LayoutPanel.d.ts +2 -2
  116. package/dist/Layout/LayoutPanel.d.ts.map +1 -1
  117. package/dist/Layout/padding.stylex.d.ts +51 -0
  118. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  119. package/dist/Layout/padding.stylex.js +63 -0
  120. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  121. package/dist/Lightbox/Lightbox.js +4 -2
  122. package/dist/Link/Link.d.ts +17 -2
  123. package/dist/Link/Link.d.ts.map +1 -1
  124. package/dist/Link/Link.js +20 -9
  125. package/dist/Link/LinkProvider.d.ts +1 -0
  126. package/dist/Link/LinkProvider.d.ts.map +1 -1
  127. package/dist/Link/LinkProvider.js +1 -0
  128. package/dist/Markdown/Markdown.d.ts.map +1 -1
  129. package/dist/Markdown/Markdown.js +18 -3
  130. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  131. package/dist/MobileNav/MobileNav.js +13 -0
  132. package/dist/MoreMenu/MoreMenu.d.ts +1 -7
  133. package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
  134. package/dist/MoreMenu/MoreMenu.js +0 -2
  135. package/dist/MultiSelector/MultiSelector.d.ts +25 -2
  136. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  137. package/dist/MultiSelector/MultiSelector.js +103 -22
  138. package/dist/MultiSelector/hooks.d.ts +12 -1
  139. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  140. package/dist/MultiSelector/hooks.js +14 -1
  141. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  142. package/dist/NavMenu/NavHeadingMenu.js +34 -3
  143. package/dist/NumberInput/NumberInput.d.ts +23 -2
  144. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  145. package/dist/NumberInput/NumberInput.js +62 -12
  146. package/dist/Pagination/Pagination.d.ts +1 -1
  147. package/dist/Pagination/Pagination.d.ts.map +1 -1
  148. package/dist/Pagination/Pagination.js +46 -28
  149. package/dist/Popover/usePopover.d.ts +25 -2
  150. package/dist/Popover/usePopover.d.ts.map +1 -1
  151. package/dist/Popover/usePopover.js +18 -6
  152. package/dist/PowerSearch/PowerSearch.d.ts +11 -1
  153. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  154. package/dist/PowerSearch/PowerSearch.js +6 -1
  155. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  156. package/dist/ProgressBar/ProgressBar.js +3 -5
  157. package/dist/RadioList/RadioList.d.ts +20 -2
  158. package/dist/RadioList/RadioList.d.ts.map +1 -1
  159. package/dist/RadioList/RadioList.js +105 -9
  160. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  161. package/dist/RadioList/RadioListItem.js +13 -2
  162. package/dist/SegmentedControl/SegmentedControl.d.ts +13 -2
  163. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  164. package/dist/SegmentedControl/SegmentedControl.js +78 -44
  165. package/dist/SegmentedControl/SegmentedControlContext.d.ts +7 -0
  166. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  167. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  168. package/dist/SegmentedControl/SegmentedControlItem.js +13 -2
  169. package/dist/Selector/Selector.d.ts +22 -1
  170. package/dist/Selector/Selector.d.ts.map +1 -1
  171. package/dist/Selector/Selector.js +84 -23
  172. package/dist/Selector/hooks.d.ts +8 -1
  173. package/dist/Selector/hooks.d.ts.map +1 -1
  174. package/dist/Selector/hooks.js +14 -1
  175. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  176. package/dist/SideNav/SideNavItem.js +1 -1
  177. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  178. package/dist/Skeleton/Skeleton.js +8 -2
  179. package/dist/Slider/Slider.d.ts +21 -0
  180. package/dist/Slider/Slider.d.ts.map +1 -1
  181. package/dist/Slider/Slider.js +38 -11
  182. package/dist/Spinner/Spinner.d.ts.map +1 -1
  183. package/dist/Spinner/Spinner.js +1 -1
  184. package/dist/Stack/Stack.d.ts +38 -23
  185. package/dist/Stack/Stack.d.ts.map +1 -1
  186. package/dist/Stack/Stack.js +25 -1
  187. package/dist/Stack/StackItem.d.ts +12 -23
  188. package/dist/Stack/StackItem.d.ts.map +1 -1
  189. package/dist/Stack/StackItem.js +8 -1
  190. package/dist/Switch/Switch.d.ts +23 -2
  191. package/dist/Switch/Switch.d.ts.map +1 -1
  192. package/dist/Switch/Switch.js +41 -11
  193. package/dist/TabList/Tab.d.ts.map +1 -1
  194. package/dist/TabList/Tab.js +6 -0
  195. package/dist/TabList/TabList.d.ts +16 -4
  196. package/dist/TabList/TabList.d.ts.map +1 -1
  197. package/dist/TabList/TabList.js +86 -10
  198. package/dist/TabList/TabListContext.d.ts +5 -0
  199. package/dist/TabList/TabListContext.d.ts.map +1 -1
  200. package/dist/TabList/TabListContext.js +5 -0
  201. package/dist/TabList/TabMenu.d.ts.map +1 -1
  202. package/dist/TabList/TabMenu.js +6 -1
  203. package/dist/Table/BaseTable.d.ts.map +1 -1
  204. package/dist/Table/BaseTable.js +38 -16
  205. package/dist/Table/Table.d.ts.map +1 -1
  206. package/dist/Table/Table.js +36 -7
  207. package/dist/Table/TableCell.d.ts +8 -1
  208. package/dist/Table/TableCell.d.ts.map +1 -1
  209. package/dist/Table/TableCell.js +10 -10
  210. package/dist/Table/TableHeaderCell.d.ts +8 -1
  211. package/dist/Table/TableHeaderCell.d.ts.map +1 -1
  212. package/dist/Table/TableHeaderCell.js +16 -16
  213. package/dist/Table/TableRow.d.ts +6 -1
  214. package/dist/Table/TableRow.d.ts.map +1 -1
  215. package/dist/Table/TableRow.js +15 -7
  216. package/dist/Table/index.d.ts +4 -1
  217. package/dist/Table/index.d.ts.map +1 -1
  218. package/dist/Table/index.js +3 -2
  219. package/dist/Table/plugins/pagination/paginateData.d.ts +2 -1
  220. package/dist/Table/plugins/pagination/paginateData.d.ts.map +1 -1
  221. package/dist/Table/plugins/pagination/paginateData.js +8 -3
  222. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  223. package/dist/Table/plugins/pagination/useTablePagination.js +6 -1
  224. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  225. package/dist/Table/plugins/sortable/useTableSortable.js +59 -3
  226. package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
  227. package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
  228. package/dist/Table/plugins/stickyColumns/index.js +3 -0
  229. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
  230. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
  231. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
  232. package/dist/Table/tableContextMenu.d.ts +33 -0
  233. package/dist/Table/tableContextMenu.d.ts.map +1 -0
  234. package/dist/Table/tableContextMenu.js +133 -0
  235. package/dist/Table/types.d.ts +161 -5
  236. package/dist/Table/types.d.ts.map +1 -1
  237. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  238. package/dist/Table/useBaseTablePlugins.js +1 -1
  239. package/dist/Text/text.stylex.d.ts +2 -2
  240. package/dist/Text/text.stylex.js +2 -2
  241. package/dist/TextArea/TextArea.d.ts +22 -1
  242. package/dist/TextArea/TextArea.d.ts.map +1 -1
  243. package/dist/TextArea/TextArea.js +41 -9
  244. package/dist/TextInput/TextInput.d.ts +23 -2
  245. package/dist/TextInput/TextInput.d.ts.map +1 -1
  246. package/dist/TextInput/TextInput.js +55 -10
  247. package/dist/TimeInput/TimeInput.d.ts +23 -1
  248. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  249. package/dist/TimeInput/TimeInput.js +53 -10
  250. package/dist/Timestamp/Timestamp.d.ts +1 -1
  251. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  252. package/dist/Timestamp/Timestamp.js +28 -3
  253. package/dist/Toast/Toast.d.ts.map +1 -1
  254. package/dist/Toast/Toast.js +14 -0
  255. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  256. package/dist/Toast/ToastViewport.js +132 -3
  257. package/dist/ToggleButton/ToggleButton.d.ts +7 -9
  258. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  259. package/dist/ToggleButton/ToggleButton.js +34 -57
  260. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  261. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  262. package/dist/Tokenizer/Tokenizer.js +27 -5
  263. package/dist/Toolbar/Toolbar.d.ts +3 -3
  264. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  265. package/dist/Toolbar/Toolbar.js +51 -7
  266. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  267. package/dist/Tooltip/useTooltip.js +57 -9
  268. package/dist/TreeList/TreeList.d.ts +1 -1
  269. package/dist/TreeList/TreeList.d.ts.map +1 -1
  270. package/dist/TreeList/TreeList.js +72 -2
  271. package/dist/TreeList/TreeListItem.d.ts +11 -1
  272. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  273. package/dist/TreeList/TreeListItem.js +51 -12
  274. package/dist/TreeList/treeListItem.markers.stylex.d.ts +16 -0
  275. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +1 -0
  276. package/dist/TreeList/treeListItem.markers.stylex.js +22 -0
  277. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  278. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  279. package/dist/Typeahead/BaseTypeahead.js +67 -6
  280. package/dist/Typeahead/Typeahead.d.ts +24 -1
  281. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  282. package/dist/Typeahead/Typeahead.js +29 -5
  283. package/dist/VisuallyHidden/VisuallyHidden.d.ts +61 -0
  284. package/dist/VisuallyHidden/VisuallyHidden.d.ts.map +1 -0
  285. package/dist/VisuallyHidden/VisuallyHidden.js +65 -0
  286. package/dist/VisuallyHidden/index.d.ts +11 -0
  287. package/dist/VisuallyHidden/index.d.ts.map +1 -0
  288. package/dist/VisuallyHidden/index.js +13 -0
  289. package/dist/astryx.css +40 -1
  290. package/dist/astryx.umd.js +147 -0
  291. package/dist/astryx.umd.js.map +7 -0
  292. package/dist/hooks/focusableSelector.d.ts +22 -0
  293. package/dist/hooks/focusableSelector.d.ts.map +1 -0
  294. package/dist/hooks/focusableSelector.js +24 -0
  295. package/dist/hooks/index.d.ts +12 -2
  296. package/dist/hooks/index.d.ts.map +1 -1
  297. package/dist/hooks/index.js +7 -2
  298. package/dist/hooks/useAnnounce.d.ts +40 -0
  299. package/dist/hooks/useAnnounce.d.ts.map +1 -0
  300. package/dist/hooks/useAnnounce.js +142 -0
  301. package/dist/hooks/useClickableContainer.d.ts +16 -1
  302. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  303. package/dist/hooks/useClickableContainer.js +16 -4
  304. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  305. package/dist/hooks/useEntryAnimation.js +4 -4
  306. package/dist/hooks/useFocusTrap.d.ts +18 -0
  307. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  308. package/dist/hooks/useFocusTrap.js +87 -3
  309. package/dist/hooks/useGridFocus.d.ts +88 -1
  310. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  311. package/dist/hooks/useGridFocus.js +261 -62
  312. package/dist/hooks/useKeyboardHint.d.ts +82 -0
  313. package/dist/hooks/useKeyboardHint.d.ts.map +1 -0
  314. package/dist/hooks/useKeyboardHint.js +220 -0
  315. package/dist/hooks/useListFocus.d.ts +78 -7
  316. package/dist/hooks/useListFocus.d.ts.map +1 -1
  317. package/dist/hooks/useListFocus.js +305 -52
  318. package/dist/hooks/useLongPress.d.ts +21 -0
  319. package/dist/hooks/useLongPress.d.ts.map +1 -0
  320. package/dist/hooks/useLongPress.js +89 -0
  321. package/dist/hooks/useTreeFocus.d.ts +157 -0
  322. package/dist/hooks/useTreeFocus.d.ts.map +1 -0
  323. package/dist/hooks/useTreeFocus.js +337 -0
  324. package/dist/hooks/useTypeahead.d.ts +63 -0
  325. package/dist/hooks/useTypeahead.d.ts.map +1 -0
  326. package/dist/hooks/useTypeahead.js +117 -0
  327. package/dist/index.d.ts +1 -0
  328. package/dist/index.d.ts.map +1 -1
  329. package/dist/index.js +1 -0
  330. package/dist/theme/generateThemeRules.js +1 -1
  331. package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
  332. package/dist/theme/syntax/SyntaxTheme.js +1 -1
  333. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  334. package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
  335. package/dist/theme/syntax/presets.d.ts +1 -1
  336. package/dist/theme/syntax/presets.d.ts.map +1 -1
  337. package/dist/theme/syntax/presets.js +1 -1
  338. package/dist/theme/syntax/tokens.d.ts +1 -1
  339. package/dist/theme/syntax/tokens.js +1 -1
  340. package/dist/theme/types.d.ts +1 -1
  341. package/dist/utils/dateTypes.d.ts +12 -0
  342. package/dist/utils/dateTypes.d.ts.map +1 -1
  343. package/dist/utils/dateTypes.js +41 -1
  344. package/dist/utils/index.d.ts +4 -0
  345. package/dist/utils/index.d.ts.map +1 -1
  346. package/dist/utils/index.js +3 -1
  347. package/dist/utils/inputAria.d.ts +29 -0
  348. package/dist/utils/inputAria.d.ts.map +1 -0
  349. package/dist/utils/inputAria.js +30 -0
  350. package/dist/utils/timeParser.d.ts.map +1 -1
  351. package/dist/utils/timeParser.js +5 -2
  352. package/package.json +15 -5
  353. package/src/Avatar/Avatar.test.tsx +37 -0
  354. package/src/Avatar/Avatar.tsx +10 -5
  355. package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
  356. package/src/Banner/Banner.doc.mjs +8 -0
  357. package/src/Banner/Banner.test.tsx +16 -7
  358. package/src/Banner/Banner.tsx +9 -2
  359. package/src/Breadcrumbs/BreadcrumbItem.tsx +25 -7
  360. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -10
  361. package/src/Button/Button.doc.mjs +6 -0
  362. package/src/Button/Button.test.tsx +105 -11
  363. package/src/Button/Button.tsx +76 -21
  364. package/src/Calendar/Calendar.doc.mjs +4 -4
  365. package/src/Calendar/Calendar.test.tsx +168 -30
  366. package/src/Calendar/Calendar.tsx +121 -66
  367. package/src/Calendar/hooks/index.ts +0 -6
  368. package/src/Calendar/index.ts +1 -3
  369. package/src/Calendar/styles.ts +4 -9
  370. package/src/Carousel/Carousel.doc.mjs +7 -1
  371. package/src/Carousel/Carousel.test.tsx +20 -0
  372. package/src/Carousel/Carousel.tsx +16 -1
  373. package/src/Center/Center.doc.mjs +12 -2
  374. package/src/Center/Center.tsx +28 -3
  375. package/src/Chat/ChatComposer.doc.mjs +3 -0
  376. package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
  377. package/src/Chat/ChatMessageList.doc.mjs +8 -0
  378. package/src/Chat/ChatMessageList.test.tsx +18 -0
  379. package/src/Chat/ChatMessageList.tsx +16 -0
  380. package/src/Chat/useTriggerMenu.tsx +3 -0
  381. package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
  382. package/src/CheckboxInput/CheckboxInput.test.tsx +172 -27
  383. package/src/CheckboxInput/CheckboxInput.tsx +78 -6
  384. package/src/CheckboxList/CheckboxList.doc.mjs +9 -0
  385. package/src/CheckboxList/CheckboxList.test.tsx +169 -16
  386. package/src/CheckboxList/CheckboxList.tsx +56 -3
  387. package/src/CheckboxList/CheckboxListContext.tsx +7 -0
  388. package/src/Citation/Citation.doc.mjs +10 -0
  389. package/src/Citation/Citation.test.tsx +117 -0
  390. package/src/Citation/Citation.tsx +9 -1
  391. package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
  392. package/src/CodeBlock/CodeBlock.test.tsx +115 -0
  393. package/src/CodeBlock/CodeBlock.tsx +61 -33
  394. package/src/CommandPalette/CommandPalette.test.tsx +47 -1
  395. package/src/CommandPalette/CommandPalette.tsx +9 -21
  396. package/src/ContextMenu/ContextMenu.doc.mjs +4 -4
  397. package/src/ContextMenu/ContextMenu.test.tsx +155 -1
  398. package/src/ContextMenu/ContextMenu.tsx +113 -16
  399. package/src/DateInput/DateInput.doc.mjs +281 -56
  400. package/src/DateInput/DateInput.test.tsx +188 -2
  401. package/src/DateInput/DateInput.tsx +89 -10
  402. package/src/DateRangeInput/DateRangeInput.doc.mjs +251 -46
  403. package/src/DateRangeInput/DateRangeInput.test.tsx +182 -20
  404. package/src/DateRangeInput/DateRangeInput.tsx +63 -5
  405. package/src/DateTimeInput/DateTimeInput.doc.mjs +368 -67
  406. package/src/DateTimeInput/DateTimeInput.test.tsx +279 -11
  407. package/src/DateTimeInput/DateTimeInput.tsx +125 -17
  408. package/src/Dialog/Dialog.doc.mjs +2 -2
  409. package/src/Dialog/Dialog.tsx +12 -0
  410. package/src/DropdownMenu/DropdownMenu.doc.mjs +1 -8
  411. package/src/DropdownMenu/DropdownMenu.test.tsx +56 -42
  412. package/src/DropdownMenu/DropdownMenu.tsx +52 -18
  413. package/src/EmptyState/EmptyState.test.tsx +4 -2
  414. package/src/EmptyState/EmptyState.tsx +6 -2
  415. package/src/Field/Field.test.tsx +32 -0
  416. package/src/Field/Field.tsx +23 -5
  417. package/src/Field/FieldLabel.tsx +55 -17
  418. package/src/FileInput/FileInput.doc.mjs +17 -0
  419. package/src/FileInput/FileInput.test.tsx +283 -49
  420. package/src/FileInput/FileInput.tsx +76 -4
  421. package/src/Grid/Grid.doc.mjs +14 -4
  422. package/src/Grid/Grid.tsx +20 -0
  423. package/src/HoverCard/HoverCard.doc.mjs +3 -0
  424. package/src/HoverCard/HoverCard.test.tsx +189 -2
  425. package/src/HoverCard/HoverCard.tsx +20 -16
  426. package/src/HoverCard/useHoverCard.tsx +13 -10
  427. package/src/Icon/Icon.doc.mjs +8 -0
  428. package/src/InputGroup/InputGroup.doc.mjs +2 -2
  429. package/src/InputGroup/InputGroup.test.tsx +98 -60
  430. package/src/InputGroup/InputGroup.tsx +21 -4
  431. package/src/InputGroup/InputGroupContext.ts +9 -4
  432. package/src/InputGroup/index.ts +1 -0
  433. package/src/Kbd/Kbd.test.tsx +22 -4
  434. package/src/Kbd/Kbd.tsx +37 -12
  435. package/src/Layer/anchorName.test.ts +146 -0
  436. package/src/Layer/anchorName.ts +46 -0
  437. package/src/Layer/layerAnimations.stylex.ts +20 -4
  438. package/src/Layer/useLayer.doc.mjs +12 -2
  439. package/src/Layer/useLayer.tsx +100 -15
  440. package/src/Layout/Layout.doc.mjs +1 -1
  441. package/src/Layout/Layout.tsx +8 -16
  442. package/src/Layout/LayoutFooter.doc.mjs +4 -4
  443. package/src/Layout/LayoutFooter.tsx +3 -3
  444. package/src/Layout/LayoutHeader.doc.mjs +4 -4
  445. package/src/Layout/LayoutHeader.tsx +3 -3
  446. package/src/Layout/LayoutPanel.tsx +3 -3
  447. package/src/Layout/padding.stylex.ts +52 -0
  448. package/src/Lightbox/Lightbox.doc.mjs +9 -2
  449. package/src/Lightbox/Lightbox.test.tsx +53 -5
  450. package/src/Lightbox/Lightbox.tsx +9 -4
  451. package/src/Link/Link.doc.mjs +14 -0
  452. package/src/Link/Link.test.tsx +52 -2
  453. package/src/Link/Link.tsx +42 -7
  454. package/src/Link/LinkProvider.tsx +1 -0
  455. package/src/List/List.doc.mjs +7 -0
  456. package/src/Markdown/Markdown.doc.mjs +2 -0
  457. package/src/Markdown/Markdown.test.tsx +28 -26
  458. package/src/Markdown/Markdown.tsx +21 -6
  459. package/src/MetadataList/MetadataListItem.doc.mjs +4 -0
  460. package/src/MobileNav/MobileNav.doc.mjs +8 -8
  461. package/src/MobileNav/MobileNav.tsx +13 -0
  462. package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
  463. package/src/MoreMenu/MoreMenu.doc.mjs +2 -17
  464. package/src/MoreMenu/MoreMenu.tsx +0 -9
  465. package/src/MultiSelector/MultiSelector.doc.mjs +25 -0
  466. package/src/MultiSelector/MultiSelector.test.tsx +375 -8
  467. package/src/MultiSelector/MultiSelector.tsx +145 -21
  468. package/src/MultiSelector/hooks.ts +27 -0
  469. package/src/NavMenu/NavHeadingMenu.test.tsx +77 -1
  470. package/src/NavMenu/NavHeadingMenu.tsx +50 -3
  471. package/src/NumberInput/NumberInput.doc.mjs +17 -0
  472. package/src/NumberInput/NumberInput.test.tsx +242 -40
  473. package/src/NumberInput/NumberInput.tsx +89 -11
  474. package/src/OverflowList/OverflowList.doc.mjs +17 -0
  475. package/src/Pagination/Pagination.doc.mjs +3 -3
  476. package/src/Pagination/Pagination.test.tsx +250 -14
  477. package/src/Pagination/Pagination.tsx +54 -29
  478. package/src/Popover/usePopover.doc.mjs +7 -3
  479. package/src/Popover/usePopover.tsx +49 -6
  480. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  481. package/src/PowerSearch/PowerSearch.test.tsx +97 -1
  482. package/src/PowerSearch/PowerSearch.tsx +16 -3
  483. package/src/ProgressBar/ProgressBar.test.tsx +45 -27
  484. package/src/ProgressBar/ProgressBar.tsx +3 -4
  485. package/src/RadioList/RadioList.doc.mjs +9 -0
  486. package/src/RadioList/RadioList.test.tsx +252 -16
  487. package/src/RadioList/RadioList.tsx +166 -10
  488. package/src/RadioList/RadioListItem.tsx +14 -2
  489. package/src/Resizable/Resizable.doc.mjs +1 -1
  490. package/src/Section/Section.doc.mjs +11 -0
  491. package/src/SegmentedControl/SegmentedControl.doc.mjs +9 -0
  492. package/src/SegmentedControl/SegmentedControl.test.tsx +214 -42
  493. package/src/SegmentedControl/SegmentedControl.tsx +102 -54
  494. package/src/SegmentedControl/SegmentedControlContext.ts +7 -0
  495. package/src/SegmentedControl/SegmentedControlItem.tsx +14 -1
  496. package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
  497. package/src/Selector/Selector.doc.mjs +25 -0
  498. package/src/Selector/Selector.test.tsx +322 -15
  499. package/src/Selector/Selector.tsx +126 -40
  500. package/src/Selector/hooks.ts +23 -0
  501. package/src/SideNav/SideNav.test.tsx +16 -0
  502. package/src/SideNav/SideNavItem.tsx +2 -4
  503. package/src/Skeleton/Skeleton.doc.mjs +11 -1
  504. package/src/Skeleton/Skeleton.test.tsx +25 -0
  505. package/src/Skeleton/Skeleton.tsx +11 -1
  506. package/src/Slider/Slider.doc.mjs +19 -0
  507. package/src/Slider/Slider.test.tsx +158 -18
  508. package/src/Slider/Slider.tsx +54 -6
  509. package/src/Spinner/Spinner.tsx +7 -1
  510. package/src/Stack/Stack.doc.mjs +77 -0
  511. package/src/Stack/Stack.test.tsx +55 -5
  512. package/src/Stack/Stack.tsx +73 -24
  513. package/src/Stack/StackItem.test.tsx +23 -0
  514. package/src/Stack/StackItem.tsx +20 -23
  515. package/src/Switch/Switch.doc.mjs +15 -0
  516. package/src/Switch/Switch.test.tsx +162 -27
  517. package/src/Switch/Switch.tsx +61 -21
  518. package/src/TabList/Tab.tsx +6 -0
  519. package/src/TabList/TabList.doc.mjs +12 -0
  520. package/src/TabList/TabList.test.tsx +208 -0
  521. package/src/TabList/TabList.tsx +102 -12
  522. package/src/TabList/TabListContext.ts +7 -3
  523. package/src/TabList/TabMenu.tsx +6 -3
  524. package/src/Table/BaseTable.tsx +72 -41
  525. package/src/Table/Table.doc.mjs +14 -0
  526. package/src/Table/Table.test.tsx +39 -30
  527. package/src/Table/Table.tsx +27 -1
  528. package/src/Table/TableCell.tsx +25 -23
  529. package/src/Table/TableHeaderCell.tsx +28 -29
  530. package/src/Table/TableRow.tsx +37 -7
  531. package/src/Table/index.ts +6 -2
  532. package/src/Table/plugins/pagination/paginateData.ts +10 -3
  533. package/src/Table/plugins/pagination/useTablePagination.test.tsx +16 -0
  534. package/src/Table/plugins/pagination/useTablePagination.tsx +8 -1
  535. package/src/Table/plugins/sortable/useTableSortable.test.tsx +91 -2
  536. package/src/Table/plugins/sortable/useTableSortable.tsx +53 -1
  537. package/src/Table/plugins/stickyColumns/index.ts +4 -0
  538. package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
  539. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
  540. package/src/Table/tableContextMenu.test.tsx +157 -0
  541. package/src/Table/tableContextMenu.tsx +142 -0
  542. package/src/Table/types.ts +175 -4
  543. package/src/Table/useBaseTablePlugins.ts +1 -0
  544. package/src/Table/useTableColumnResize.doc.mjs +96 -0
  545. package/src/Table/useTableStickyColumns.doc.mjs +59 -0
  546. package/src/Text/Heading.doc.mjs +15 -2
  547. package/src/Text/Text.doc.mjs +7 -1
  548. package/src/Text/text.stylex.ts +2 -2
  549. package/src/TextArea/TextArea.doc.mjs +17 -0
  550. package/src/TextArea/TextArea.test.tsx +167 -35
  551. package/src/TextArea/TextArea.tsx +62 -15
  552. package/src/TextInput/TextInput.doc.mjs +17 -0
  553. package/src/TextInput/TextInput.test.tsx +168 -34
  554. package/src/TextInput/TextInput.tsx +77 -9
  555. package/src/TimeInput/TimeInput.doc.mjs +335 -60
  556. package/src/TimeInput/TimeInput.test.tsx +144 -7
  557. package/src/TimeInput/TimeInput.tsx +74 -5
  558. package/src/Timestamp/Timestamp.doc.mjs +9 -0
  559. package/src/Timestamp/Timestamp.test.tsx +44 -18
  560. package/src/Timestamp/Timestamp.tsx +30 -10
  561. package/src/Toast/Toast.tsx +14 -0
  562. package/src/Toast/ToastViewport.test.tsx +204 -0
  563. package/src/Toast/ToastViewport.tsx +167 -21
  564. package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
  565. package/src/ToggleButton/ToggleButton.test.tsx +39 -18
  566. package/src/ToggleButton/ToggleButton.tsx +46 -83
  567. package/src/Tokenizer/Tokenizer.doc.mjs +15 -0
  568. package/src/Tokenizer/Tokenizer.test.tsx +105 -1
  569. package/src/Tokenizer/Tokenizer.tsx +41 -2
  570. package/src/Toolbar/Toolbar.doc.mjs +65 -5
  571. package/src/Toolbar/Toolbar.test.tsx +109 -6
  572. package/src/Toolbar/Toolbar.tsx +59 -5
  573. package/src/Tooltip/Tooltip.test.tsx +81 -0
  574. package/src/Tooltip/useTooltip.tsx +57 -11
  575. package/src/TreeList/TreeList.test.tsx +295 -6
  576. package/src/TreeList/TreeList.tsx +73 -1
  577. package/src/TreeList/TreeListItem.tsx +51 -5
  578. package/src/TreeList/treeListItem.markers.stylex.ts +20 -0
  579. package/src/Typeahead/BaseTypeahead.tsx +86 -9
  580. package/src/Typeahead/Typeahead.doc.mjs +136 -30
  581. package/src/Typeahead/Typeahead.test.tsx +331 -1
  582. package/src/Typeahead/Typeahead.tsx +52 -1
  583. package/src/VisuallyHidden/VisuallyHidden.doc.mjs +80 -0
  584. package/src/VisuallyHidden/VisuallyHidden.test.tsx +76 -0
  585. package/src/VisuallyHidden/VisuallyHidden.tsx +113 -0
  586. package/src/VisuallyHidden/index.ts +15 -0
  587. package/src/hooks/focusableSelector.ts +25 -0
  588. package/src/hooks/index.ts +28 -2
  589. package/src/hooks/useAnnounce.test.tsx +101 -0
  590. package/src/hooks/useAnnounce.ts +168 -0
  591. package/src/hooks/useClickableContainer.ts +16 -4
  592. package/src/hooks/useEntryAnimation.ts +16 -4
  593. package/src/hooks/useFocusTrap.test.tsx +132 -0
  594. package/src/hooks/useFocusTrap.ts +98 -4
  595. package/src/hooks/useGridFocus.doc.mjs +40 -2
  596. package/src/hooks/useGridFocus.test.tsx +132 -0
  597. package/src/hooks/useGridFocus.ts +343 -67
  598. package/src/hooks/useImageMode.test.ts +133 -0
  599. package/src/hooks/useKeyboardHint.doc.mjs +101 -0
  600. package/src/hooks/useKeyboardHint.test.tsx +70 -0
  601. package/src/hooks/useKeyboardHint.tsx +332 -0
  602. package/src/hooks/useListFocus.doc.mjs +50 -10
  603. package/src/hooks/useListFocus.test.tsx +375 -0
  604. package/src/hooks/useListFocus.ts +395 -57
  605. package/src/hooks/useLongPress.test.tsx +134 -0
  606. package/src/hooks/useLongPress.ts +119 -0
  607. package/src/hooks/useTreeFocus.doc.mjs +140 -0
  608. package/src/hooks/useTreeFocus.test.tsx +214 -0
  609. package/src/hooks/useTreeFocus.ts +559 -0
  610. package/src/hooks/useTypeahead.test.tsx +109 -0
  611. package/src/hooks/useTypeahead.ts +183 -0
  612. package/src/index.ts +1 -0
  613. package/src/theme/derivedVarRegistry.test.ts +4 -0
  614. package/src/theme/generateThemeRules.test.ts +7 -8
  615. package/src/theme/generateThemeRules.ts +1 -1
  616. package/src/theme/syntax/SyntaxTheme.tsx +1 -1
  617. package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
  618. package/src/theme/syntax/presets.ts +5 -2
  619. package/src/theme/syntax/tokens.ts +1 -1
  620. package/src/theme/types.ts +1 -1
  621. package/src/utils/dateTypes.test.ts +40 -0
  622. package/src/utils/dateTypes.ts +40 -0
  623. package/src/utils/index.ts +9 -0
  624. package/src/utils/inputAria.test.ts +39 -0
  625. package/src/utils/inputAria.ts +58 -0
  626. package/src/utils/timeParser.test.ts +10 -0
  627. package/src/utils/timeParser.ts +5 -2
  628. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts +0 -57
  629. package/dist/Calendar/hooks/useCalendarRovingTabindex.d.ts.map +0 -1
  630. package/dist/Calendar/hooks/useCalendarRovingTabindex.js +0 -96
  631. package/src/Calendar/hooks/useCalendarRovingTabindex.ts +0 -118
@@ -11,7 +11,7 @@
11
11
 
12
12
  import {useState} from 'react';
13
13
  import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
14
- import {render, screen, act} from '@testing-library/react';
14
+ import {render, screen, act, fireEvent, waitFor} from '@testing-library/react';
15
15
  import userEvent from '@testing-library/user-event';
16
16
  import {PowerSearch} from './PowerSearch';
17
17
  import type {PowerSearchConfig, PowerSearchFilter} from './types';
@@ -199,4 +199,100 @@ describe('PowerSearch', () => {
199
199
  expect(pasteResults).toEqual(typeResults);
200
200
  });
201
201
  });
202
+
203
+ describe('disabledMessage', () => {
204
+ const h = {hidden: true} as const;
205
+ const isOpen = (el: Element) => el.matches(':popover-open');
206
+
207
+ function renderSearch(props?: {onChange?: () => void}) {
208
+ return render(
209
+ <PowerSearch
210
+ config={config}
211
+ filters={[]}
212
+ onChange={props?.onChange ?? (() => {})}
213
+ isDisabled
214
+ disabledMessage="You need edit access to search"
215
+ />,
216
+ );
217
+ }
218
+
219
+ it('shows the reason tooltip on hover when disabled with a reason', async () => {
220
+ renderSearch();
221
+ const tooltip = screen.getByRole('tooltip', h);
222
+ expect(tooltip).toHaveTextContent('You need edit access to search');
223
+ const wrapper = screen.getByRole('group');
224
+ fireEvent.mouseEnter(wrapper);
225
+ await waitFor(() => expect(isOpen(tooltip)).toBe(true));
226
+ fireEvent.mouseLeave(wrapper);
227
+ await waitFor(() => expect(isOpen(tooltip)).toBe(false));
228
+ });
229
+
230
+ it('shows the reason tooltip on keyboard focus', async () => {
231
+ const user = userEvent.setup();
232
+ renderSearch();
233
+ const tooltip = screen.getByRole('tooltip', h);
234
+ await user.tab();
235
+ expect(screen.getByRole('combobox')).toHaveFocus();
236
+ await waitFor(() => expect(isOpen(tooltip)).toBe(true));
237
+ });
238
+
239
+ it('does not render a tooltip when not disabled', () => {
240
+ render(
241
+ <PowerSearch
242
+ config={config}
243
+ filters={[]}
244
+ onChange={() => {}}
245
+ disabledMessage="You need edit access to search"
246
+ />,
247
+ );
248
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
249
+ });
250
+
251
+ it('does not render a tooltip when disabled without a reason', () => {
252
+ render(
253
+ <PowerSearch
254
+ config={config}
255
+ filters={[]}
256
+ onChange={() => {}}
257
+ isDisabled
258
+ />,
259
+ );
260
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
261
+ });
262
+
263
+ it('keeps the input focusable via aria-disabled when a reason is provided', () => {
264
+ renderSearch();
265
+ const input = screen.getByRole('combobox');
266
+ expect(input).not.toBeDisabled();
267
+ expect(input).toHaveAttribute('aria-disabled', 'true');
268
+ });
269
+
270
+ it('links the reason tooltip via aria-describedby', () => {
271
+ renderSearch();
272
+ const input = screen.getByRole('combobox');
273
+ const tooltip = screen.getByRole('tooltip', h);
274
+ expect(input.getAttribute('aria-describedby')).toContain(tooltip.id);
275
+ });
276
+
277
+ it('blocks input while focusable-disabled', async () => {
278
+ const user = userEvent.setup();
279
+ renderSearch();
280
+ const input = screen.getByRole('combobox');
281
+ input.focus();
282
+ await user.keyboard('open');
283
+ expect((input as HTMLInputElement).value).toBe('');
284
+ });
285
+
286
+ it('keeps the input natively disabled when disabled without a reason', () => {
287
+ render(
288
+ <PowerSearch
289
+ config={config}
290
+ filters={[]}
291
+ onChange={() => {}}
292
+ isDisabled
293
+ />,
294
+ );
295
+ expect(screen.getByRole('combobox')).toBeDisabled();
296
+ });
297
+ });
202
298
  });
@@ -358,6 +358,16 @@ export interface PowerSearchProps extends Omit<
358
358
  isReadOnly?: boolean;
359
359
  /** Whether the input is disabled. @default false */
360
360
  isDisabled?: boolean;
361
+ /**
362
+ * Explains why the search is disabled. When set together with `isDisabled`,
363
+ * the search shows a tooltip with this text on hover and keyboard focus, and
364
+ * the input stays focusable (via `aria-disabled`) so the reason is
365
+ * discoverable by keyboard and assistive technology. Input stays blocked.
366
+ *
367
+ * Use this instead of wrapping a disabled PowerSearch in `Tooltip` — disabled
368
+ * controls don't emit the pointer events an external tooltip needs.
369
+ */
370
+ disabledMessage?: string;
361
371
  /**
362
372
  * Icon to display at the start of the input.
363
373
  * Accepts a ReactNode (e.g. `<Icon icon={SearchIcon} />`) or an SVG icon component directly.
@@ -508,6 +518,7 @@ export function PowerSearch({
508
518
  hasClear = true,
509
519
  isReadOnly = false,
510
520
  isDisabled = false,
521
+ disabledMessage,
511
522
  startIcon,
512
523
  onFocus,
513
524
  onBlur,
@@ -546,6 +557,9 @@ export function PowerSearch({
546
557
  hasLightDismiss: true,
547
558
  hasCloseButton: false,
548
559
  hasAutoFocus: false,
560
+ // The popup's own listbox/menu content is the exposed semantics; focus
561
+ // stays on the tokenizer input, so a modal dialog wrapper is incorrect.
562
+ role: 'none',
549
563
  });
550
564
 
551
565
  // Wrapper that manages layer visibility and tokenizer focus alongside state
@@ -795,9 +809,7 @@ export function PowerSearch({
795
809
  filter.value.value[0].photo
796
810
  ) {
797
811
  const entity = filter.value.value[0];
798
- tokenIcon = (
799
- <Avatar src={entity.photo} name={entity.label} size={16} />
800
- );
812
+ tokenIcon = <Avatar src={entity.photo} name={entity.label} size={16} />;
801
813
  }
802
814
 
803
815
  return (
@@ -982,6 +994,7 @@ export function PowerSearch({
982
994
  startIcon={startIcon}
983
995
  endContent={combinedEndContent}
984
996
  isDisabled={isDisabled}
997
+ disabledMessage={disabledMessage}
985
998
  size={size}
986
999
  tokenOverflowBehavior={tokenOverflowBehavior}
987
1000
  hasEntriesOnFocus
@@ -7,11 +7,19 @@ import {ProgressBar} from './ProgressBar';
7
7
  describe('ProgressBar', () => {
8
8
  it('renders with default props', () => {
9
9
  render(<ProgressBar value={50} label="Progress" />);
10
- const meter = screen.getByRole('meter');
11
- expect(meter).toBeInTheDocument();
12
- expect(meter).toHaveAttribute('aria-valuenow', '50');
13
- expect(meter).toHaveAttribute('aria-valuemin', '0');
14
- expect(meter).toHaveAttribute('aria-valuemax', '100');
10
+ const progressbar = screen.getByRole('progressbar');
11
+ expect(progressbar).toBeInTheDocument();
12
+ expect(progressbar).toHaveAttribute('aria-valuenow', '50');
13
+ expect(progressbar).toHaveAttribute('aria-valuemin', '0');
14
+ expect(progressbar).toHaveAttribute('aria-valuemax', '100');
15
+ });
16
+
17
+ it('uses role="progressbar" (not "meter") for determinate progress', () => {
18
+ // A determinate ProgressBar conveys task completion, so it must be a
19
+ // progressbar (announced on update), not a meter (a static gauge).
20
+ render(<ProgressBar value={50} label="Progress" />);
21
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
22
+ expect(screen.queryByRole('meter')).not.toBeInTheDocument();
15
23
  });
16
24
 
17
25
  it('renders visible label by default', () => {
@@ -23,8 +31,8 @@ describe('ProgressBar', () => {
23
31
  render(<ProgressBar value={50} label="Hidden label" isLabelHidden />);
24
32
  const label = screen.getByText('Hidden label');
25
33
  expect(label).toBeInTheDocument();
26
- const meter = screen.getByRole('meter');
27
- expect(meter).toHaveAttribute('aria-labelledby');
34
+ const progressbar = screen.getByRole('progressbar');
35
+ expect(progressbar).toHaveAttribute('aria-labelledby');
28
36
  });
29
37
 
30
38
  it('shows value label when hasValueLabel is true', () => {
@@ -43,33 +51,33 @@ describe('ProgressBar', () => {
43
51
  />,
44
52
  );
45
53
  expect(screen.getByText('3 GB / 5 GB')).toBeInTheDocument();
46
- const meter = screen.getByRole('meter');
47
- expect(meter).toHaveAttribute('aria-valuetext', '3 GB / 5 GB');
54
+ const progressbar = screen.getByRole('progressbar');
55
+ expect(progressbar).toHaveAttribute('aria-valuetext', '3 GB / 5 GB');
48
56
  });
49
57
 
50
58
  it('sets aria-valuetext from formatValueLabel', () => {
51
59
  render(<ProgressBar value={50} label="Progress" />);
52
- const meter = screen.getByRole('meter');
53
- expect(meter).toHaveAttribute('aria-valuetext', '50%');
60
+ const progressbar = screen.getByRole('progressbar');
61
+ expect(progressbar).toHaveAttribute('aria-valuetext', '50%');
54
62
  });
55
63
 
56
64
  it('respects custom max', () => {
57
65
  render(<ProgressBar value={3} max={10} label="Steps" />);
58
- const meter = screen.getByRole('meter');
59
- expect(meter).toHaveAttribute('aria-valuenow', '3');
60
- expect(meter).toHaveAttribute('aria-valuemax', '10');
66
+ const progressbar = screen.getByRole('progressbar');
67
+ expect(progressbar).toHaveAttribute('aria-valuenow', '3');
68
+ expect(progressbar).toHaveAttribute('aria-valuemax', '10');
61
69
  });
62
70
 
63
71
  it('clamps value to [0, max]', () => {
64
72
  const {rerender} = render(
65
73
  <ProgressBar value={150} max={100} label="Over" />,
66
74
  );
67
- let meter = screen.getByRole('meter');
68
- expect(meter).toHaveAttribute('aria-valuenow', '100');
75
+ let progressbar = screen.getByRole('progressbar');
76
+ expect(progressbar).toHaveAttribute('aria-valuenow', '100');
69
77
 
70
78
  rerender(<ProgressBar value={-10} max={100} label="Under" />);
71
- meter = screen.getByRole('meter');
72
- expect(meter).toHaveAttribute('aria-valuenow', '0');
79
+ progressbar = screen.getByRole('progressbar');
80
+ expect(progressbar).toHaveAttribute('aria-valuenow', '0');
73
81
  });
74
82
 
75
83
  it('forwards ref to outer container', () => {
@@ -79,9 +87,7 @@ describe('ProgressBar', () => {
79
87
  });
80
88
 
81
89
  it('passes data-testid', () => {
82
- render(
83
- <ProgressBar value={50} label="Test" data-testid="my-progress" />,
84
- );
90
+ render(<ProgressBar value={50} label="Test" data-testid="my-progress" />);
85
91
  expect(screen.getByTestId('my-progress')).toBeInTheDocument();
86
92
  });
87
93
 
@@ -97,14 +103,14 @@ describe('ProgressBar', () => {
97
103
  const {unmount} = render(
98
104
  <ProgressBar value={50} label={variant} variant={variant} />,
99
105
  );
100
- expect(screen.getByRole('meter')).toBeInTheDocument();
106
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
101
107
  unmount();
102
108
  }
103
109
  });
104
110
 
105
111
  it('renders at fixed 8px track height', () => {
106
112
  render(<ProgressBar value={50} label="Progress" />);
107
- expect(screen.getByRole('meter')).toBeInTheDocument();
113
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
108
114
  });
109
115
 
110
116
  it('shows value label with hidden label', () => {
@@ -115,11 +121,23 @@ describe('ProgressBar', () => {
115
121
  expect(screen.getByText('Hidden')).toBeInTheDocument();
116
122
  });
117
123
 
124
+ it('renders no visible value label when isLabelHidden without hasValueLabel', () => {
125
+ // Mirrors the intended "accessible label only" composition: the text
126
+ // label is kept for assistive tech (visually hidden) while no extra
127
+ // visible value label is surfaced.
128
+ render(<ProgressBar value={42} label="Context usage" isLabelHidden />);
129
+ expect(screen.queryByText('42%')).not.toBeInTheDocument();
130
+ const label = screen.getByText('Context usage');
131
+ expect(label).toBeInTheDocument();
132
+ const progressbar = screen.getByRole('progressbar');
133
+ expect(progressbar).toHaveAttribute('aria-labelledby', label.id);
134
+ });
135
+
118
136
  it('handles zero max gracefully', () => {
119
137
  render(<ProgressBar value={0} max={0} label="Empty" />);
120
- const meter = screen.getByRole('meter');
121
- expect(meter).toHaveAttribute('aria-valuenow', '0');
122
- expect(meter).toHaveAttribute('aria-valuemax', '0');
138
+ const progressbar = screen.getByRole('progressbar');
139
+ expect(progressbar).toHaveAttribute('aria-valuenow', '0');
140
+ expect(progressbar).toHaveAttribute('aria-valuemax', '0');
123
141
  });
124
142
 
125
143
  // Disabled state
@@ -128,7 +146,7 @@ describe('ProgressBar', () => {
128
146
  render(
129
147
  <ProgressBar value={50} label="Canceled" isDisabled hasValueLabel />,
130
148
  );
131
- expect(screen.getByRole('meter')).toBeInTheDocument();
149
+ expect(screen.getByRole('progressbar')).toBeInTheDocument();
132
150
  expect(screen.getByText('50%')).toBeInTheDocument();
133
151
  });
134
152
 
@@ -31,6 +31,7 @@ import {
31
31
  import {mergeProps} from '../utils';
32
32
  import type {BaseProps} from '../BaseProps';
33
33
  import {themeProps} from '../utils/themeProps';
34
+ import {VisuallyHidden} from '../VisuallyHidden';
34
35
 
35
36
  /**
36
37
  * Extensible variant map for ProgressBar.
@@ -313,14 +314,12 @@ export function ProgressBar({
313
314
  )}
314
315
  </div>
315
316
  ) : (
316
- <span id={labelId} {...stylex.props(styles.visuallyHidden)}>
317
- {label}
318
- </span>
317
+ <VisuallyHidden id={labelId}>{label}</VisuallyHidden>
319
318
  )}
320
319
 
321
320
  {/* Progress track */}
322
321
  <div
323
- role={isIndeterminate ? 'progressbar' : 'meter'}
322
+ role="progressbar"
324
323
  aria-valuenow={isIndeterminate ? undefined : clampedValue}
325
324
  aria-valuemin={isIndeterminate ? undefined : 0}
326
325
  aria-valuemax={isIndeterminate ? undefined : max}
@@ -74,6 +74,12 @@ export const docs = {
74
74
  description: 'Whether all radio items are disabled.',
75
75
  default: 'false',
76
76
  },
77
+ {
78
+ name: 'disabledMessage',
79
+ type: 'string',
80
+ description:
81
+ 'Explains why the group is disabled. Applies to the whole-group disabled state (isDisabled), not per item. With isDisabled, shows a tooltip on hover/keyboard focus and keeps the radios focusable via aria-disabled (selection stays blocked). Use this instead of wrapping a disabled RadioList in Tooltip — disabled controls swallow the hover events an external Tooltip needs.',
82
+ },
77
83
  {
78
84
  name: 'isRequired',
79
85
  type: 'boolean',
@@ -121,6 +127,7 @@ export const docs = {
121
127
  { guidance: false, description: 'Use when multiple selections are needed; use CheckboxList instead.' },
122
128
  { guidance: false, description: 'Use for long lists; use Selector for better discoverability.' },
123
129
  { guidance: false, description: 'Use horizontal layout with more than 4 options; it wraps awkwardly.' },
130
+ { guidance: false, description: 'Wrap a disabled RadioList in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.' },
124
131
  ],
125
132
  anatomy: [
126
133
  {name: 'Header', required: false, description: 'Optional heading above the radio list.'},
@@ -142,6 +149,7 @@ export const docsZh = {
142
149
  { guidance: false, description: 'Use when multiple selections are needed; use CheckboxList instead.' },
143
150
  { guidance: false, description: 'Use for long lists; use Selector for better discoverability.' },
144
151
  { guidance: false, description: 'Use horizontal layout with more than 4 options; it wraps awkwardly.' },
152
+ { guidance: false, description: 'Wrap a disabled RadioList in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.' },
145
153
  ],
146
154
  anatomy: [
147
155
  {name: 'Header', required: false, description: 'Optional heading above the radio list.'},
@@ -165,6 +173,7 @@ export const docsDense = {
165
173
  { guidance: false, description: 'Use when multiple selections are needed; use CheckboxList instead.' },
166
174
  { guidance: false, description: 'Use for long lists; use Selector for better discoverability.' },
167
175
  { guidance: false, description: 'Use horizontal layout with more than 4 options; it wraps awkwardly.' },
176
+ { guidance: false, description: 'Wrap a disabled RadioList in Tooltip to explain why it is disabled; disabled controls swallow the hover events the wrapper needs. Use the disabledMessage prop instead.' },
168
177
  ],
169
178
  anatomy: [
170
179
  {name: 'Header', required: false, description: 'Optional heading above the radio list.'},
@@ -9,12 +9,39 @@
9
9
  * SYNC: When RadioList.tsx or RadioListItem.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi} from 'vitest';
13
- import {render, screen} from '@testing-library/react';
12
+ import {describe, it, expect, vi, beforeEach} from 'vitest';
13
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {RadioList} from './RadioList';
16
16
  import {RadioListItem} from './RadioListItem';
17
17
 
18
+ // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
19
+ // reflects its open state via a `popover-open` attribute the tests can assert.
20
+ beforeEach(() => {
21
+ HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
22
+ this.setAttribute('popover-open', '');
23
+ const event = new Event('toggle', {bubbles: false});
24
+ Object.defineProperty(event, 'newState', {value: 'open'});
25
+ this.dispatchEvent(event);
26
+ });
27
+ HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
28
+ this.removeAttribute('popover-open');
29
+ const event = new Event('toggle', {bubbles: false});
30
+ Object.defineProperty(event, 'newState', {value: 'closed'});
31
+ this.dispatchEvent(event);
32
+ });
33
+ const originalMatches = HTMLElement.prototype.matches;
34
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
+ (HTMLElement.prototype as any).matches = function (
36
+ selector: string,
37
+ ): boolean {
38
+ if (selector === ':popover-open') {
39
+ return this.hasAttribute('popover-open');
40
+ }
41
+ return originalMatches.call(this, selector);
42
+ };
43
+ });
44
+
18
45
  describe('RadioList', () => {
19
46
  it('renders with label', () => {
20
47
  render(
@@ -104,11 +131,7 @@ describe('RadioList', () => {
104
131
  const user = userEvent.setup();
105
132
  const handleChange = vi.fn();
106
133
  render(
107
- <RadioList
108
- label="Preference"
109
- value=""
110
- onChange={handleChange}
111
- isDisabled>
134
+ <RadioList label="Preference" value="" onChange={handleChange} isDisabled>
112
135
  <RadioListItem label="Option A" value="a" />
113
136
  </RadioList>,
114
137
  );
@@ -257,11 +280,7 @@ describe('RadioList', () => {
257
280
  it('supports data-testid on RadioListItem', () => {
258
281
  render(
259
282
  <RadioList label="Preference" value="" onChange={() => {}}>
260
- <RadioListItem
261
- label="Option A"
262
- value="a"
263
- data-testid="my-radio-item"
264
- />
283
+ <RadioListItem label="Option A" value="a" data-testid="my-radio-item" />
265
284
  </RadioList>,
266
285
  );
267
286
  expect(screen.getByTestId('my-radio-item')).toBeInTheDocument();
@@ -279,11 +298,32 @@ describe('RadioList', () => {
279
298
  );
280
299
  const label = screen.getByText('Hidden label');
281
300
  expect(label).toBeInTheDocument();
282
- // The radiogroup should still be labeled
283
- expect(screen.getByRole('radiogroup')).toHaveAttribute(
284
- 'aria-label',
285
- 'Hidden label',
301
+ // The radiogroup is named by the label element via aria-labelledby (not a
302
+ // duplicated aria-label), so its accessible name is still "Hidden label".
303
+ expect(
304
+ screen.getByRole('radiogroup', {name: 'Hidden label'}),
305
+ ).toBeInTheDocument();
306
+ expect(screen.getByRole('radiogroup')).toHaveAttribute('aria-labelledby');
307
+ expect(screen.getByRole('radiogroup')).not.toHaveAttribute('aria-label');
308
+ });
309
+
310
+ it('renders the group label as a span, not a label element (forms-14)', () => {
311
+ render(
312
+ <RadioList label="Plan" value="" onChange={() => {}}>
313
+ <RadioListItem label="Free" value="free" />
314
+ <RadioListItem label="Pro" value="pro" />
315
+ </RadioList>,
286
316
  );
317
+ // A radiogroup's accessible name must not come from a literal `<label>`
318
+ // element — a `<label>` names a single control and can't be associated
319
+ // with a group. It is rendered as a `<span>` and referenced via
320
+ // aria-labelledby (with no orphaned htmlFor).
321
+ const labelEl = screen.getByText('Plan');
322
+ expect(labelEl.tagName).toBe('SPAN');
323
+ expect(labelEl.closest('label')).toBeNull();
324
+ expect(labelEl).not.toHaveAttribute('for');
325
+ const group = screen.getByRole('radiogroup', {name: 'Plan'});
326
+ expect(group.getAttribute('aria-labelledby')).toBe(labelEl.id);
287
327
  });
288
328
 
289
329
  it('renders description on items', () => {
@@ -340,4 +380,200 @@ describe('RadioList', () => {
340
380
  'true',
341
381
  );
342
382
  });
383
+
384
+ describe('focus management (no-selection tab stop)', () => {
385
+ it('keeps focus on the selected radio when a value is selected', () => {
386
+ render(
387
+ <RadioList label="Preference" value="b" onChange={() => {}}>
388
+ <RadioListItem label="Option A" value="a" />
389
+ <RadioListItem label="Option B" value="b" />
390
+ <RadioListItem label="Option C" value="c" />
391
+ </RadioList>,
392
+ );
393
+ const selected = screen.getByLabelText('Option B');
394
+ // A selected value provides a deterministic native tab stop; focusing it
395
+ // must not be redirected elsewhere.
396
+ selected.focus();
397
+ expect(selected).toHaveFocus();
398
+ });
399
+
400
+ it('redirects to the first radio when focus enters an unselected group forward', () => {
401
+ render(
402
+ <>
403
+ <button type="button">before</button>
404
+ <RadioList label="Preference" value="" onChange={() => {}}>
405
+ <RadioListItem label="Option A" value="a" />
406
+ <RadioListItem label="Option B" value="b" />
407
+ <RadioListItem label="Option C" value="c" />
408
+ </RadioList>
409
+ </>,
410
+ );
411
+ const radios = screen.getAllByRole('radio');
412
+ const outside = screen.getByText('before');
413
+ outside.focus();
414
+ // Forward entry: the browser lands on a leading radio; the group keeps the
415
+ // first radio as the deterministic tab stop.
416
+ radios[0].focus();
417
+ expect(radios[0]).toHaveFocus();
418
+
419
+ // Landing on a middle radio from outside is normalized to the first.
420
+ outside.focus();
421
+ radios[1].focus();
422
+ expect(radios[0]).toHaveFocus();
423
+ });
424
+
425
+ it('redirects to the last radio when focus enters an unselected group backward', () => {
426
+ render(
427
+ <>
428
+ <RadioList label="Preference" value="" onChange={() => {}}>
429
+ <RadioListItem label="Option A" value="a" />
430
+ <RadioListItem label="Option B" value="b" />
431
+ <RadioListItem label="Option C" value="c" />
432
+ </RadioList>
433
+ <button type="button">after</button>
434
+ </>,
435
+ );
436
+ const radios = screen.getAllByRole('radio');
437
+ const outside = screen.getByText('after');
438
+ outside.focus();
439
+ // Backward (Shift+Tab) entry: the browser focuses the last radio; the
440
+ // group keeps it as the deterministic tab stop rather than jumping away.
441
+ radios[radios.length - 1].focus();
442
+ expect(radios[radios.length - 1]).toHaveFocus();
443
+ });
444
+
445
+ it('does not hijack focus moving between radios within the group', () => {
446
+ render(
447
+ <RadioList label="Preference" value="" onChange={() => {}}>
448
+ <RadioListItem label="Option A" value="a" />
449
+ <RadioListItem label="Option B" value="b" />
450
+ <RadioListItem label="Option C" value="c" />
451
+ </RadioList>,
452
+ );
453
+ const radios = screen.getAllByRole('radio');
454
+ // Enter the group from outside, then move to a middle radio as arrow-key
455
+ // navigation would. Intra-group movement must not be redirected back.
456
+ radios[0].focus();
457
+ radios[1].focus();
458
+ expect(radios[1]).toHaveFocus();
459
+ radios[2].focus();
460
+ expect(radios[2]).toHaveFocus();
461
+ });
462
+
463
+ it('skips disabled radios when choosing the deterministic tab stop', () => {
464
+ render(
465
+ <>
466
+ <button type="button">before</button>
467
+ <RadioList label="Preference" value="" onChange={() => {}}>
468
+ <RadioListItem label="Option A" value="a" isDisabled />
469
+ <RadioListItem label="Option B" value="b" />
470
+ <RadioListItem label="Option C" value="c" />
471
+ <RadioListItem label="Option D" value="d" />
472
+ </RadioList>
473
+ </>,
474
+ );
475
+ const outside = screen.getByText('before');
476
+ const optionB = screen.getByLabelText('Option B');
477
+ const optionC = screen.getByLabelText('Option C');
478
+ // A middle enabled radio (C) entered from outside is normalized to the
479
+ // first *enabled* radio (B) — the disabled Option A is skipped.
480
+ outside.focus();
481
+ optionC.focus();
482
+ expect(optionB).toHaveFocus();
483
+ });
484
+ });
485
+
486
+ describe('disabledMessage', () => {
487
+ const h = {hidden: true} as const;
488
+
489
+ function renderGroup(props?: {onChange?: (v: string) => void}) {
490
+ return render(
491
+ <RadioList
492
+ label="Plan"
493
+ value="free"
494
+ onChange={props?.onChange ?? (() => {})}
495
+ isDisabled
496
+ disabledMessage="Upgrade your account to change plans">
497
+ <RadioListItem label="Free" value="free" />
498
+ <RadioListItem label="Pro" value="pro" />
499
+ </RadioList>,
500
+ );
501
+ }
502
+
503
+ it('shows the reason tooltip on hover when the group is disabled with a reason', async () => {
504
+ renderGroup();
505
+ const tooltip = screen.getByRole('tooltip', h);
506
+ expect(tooltip).toHaveTextContent('Upgrade your account to change plans');
507
+ const group = screen.getByRole('radiogroup');
508
+ fireEvent.mouseEnter(group);
509
+ await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
510
+ fireEvent.mouseLeave(group);
511
+ await waitFor(() => expect(tooltip).not.toHaveAttribute('popover-open'));
512
+ });
513
+
514
+ it('shows the reason tooltip on keyboard focus', async () => {
515
+ const user = userEvent.setup();
516
+ renderGroup();
517
+ const tooltip = screen.getByRole('tooltip', h);
518
+ await user.tab();
519
+ await waitFor(() => expect(tooltip).toHaveAttribute('popover-open'));
520
+ });
521
+
522
+ it('does not render a tooltip when not disabled', () => {
523
+ render(
524
+ <RadioList
525
+ label="Plan"
526
+ value="free"
527
+ onChange={() => {}}
528
+ disabledMessage="Upgrade your account to change plans">
529
+ <RadioListItem label="Free" value="free" />
530
+ </RadioList>,
531
+ );
532
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
533
+ });
534
+
535
+ it('does not render a tooltip when disabled without a reason', () => {
536
+ render(
537
+ <RadioList label="Plan" value="free" onChange={() => {}} isDisabled>
538
+ <RadioListItem label="Free" value="free" />
539
+ </RadioList>,
540
+ );
541
+ expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
542
+ });
543
+
544
+ it('keeps radios focusable via aria-disabled when a reason is provided', () => {
545
+ renderGroup();
546
+ for (const radio of screen.getAllByRole('radio', h)) {
547
+ expect(radio).not.toBeDisabled();
548
+ expect(radio).toHaveAttribute('aria-disabled', 'true');
549
+ }
550
+ });
551
+
552
+ it('links the reason tooltip from the group via aria-describedby', () => {
553
+ renderGroup();
554
+ const group = screen.getByRole('radiogroup');
555
+ const tooltip = screen.getByRole('tooltip', h);
556
+ expect(group.getAttribute('aria-describedby')).toContain(tooltip.id);
557
+ });
558
+
559
+ it('blocks selection while focusable-disabled', () => {
560
+ const onChange = vi.fn();
561
+ renderGroup({onChange});
562
+ const pro = screen.getByRole('radio', {name: 'Pro', hidden: true});
563
+ fireEvent.click(pro);
564
+ expect(onChange).not.toHaveBeenCalled();
565
+ });
566
+
567
+ it('keeps radios natively disabled when disabled without a reason', () => {
568
+ render(
569
+ <RadioList label="Plan" value="free" onChange={() => {}} isDisabled>
570
+ <RadioListItem label="Free" value="free" />
571
+ <RadioListItem label="Pro" value="pro" />
572
+ </RadioList>,
573
+ );
574
+ for (const radio of screen.getAllByRole('radio', h)) {
575
+ expect(radio).toBeDisabled();
576
+ }
577
+ });
578
+ });
343
579
  });