@astryxdesign/core 0.7.5 → 0.7.6-canary.00146a7

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 (1898) hide show
  1. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/AlertDialog/AlertDialog.js +5 -5
  4. package/dist/AppShell/AppShell.d.ts.map +1 -1
  5. package/dist/AppShell/AppShell.js +177 -5
  6. package/dist/AppShell/observeMainOverflow.d.ts +9 -0
  7. package/dist/AppShell/observeMainOverflow.d.ts.map +1 -0
  8. package/dist/AppShell/observeMainOverflow.js +125 -0
  9. package/dist/Avatar/Avatar.d.ts.map +1 -1
  10. package/dist/Avatar/Avatar.js +12 -3
  11. package/dist/Badge/Badge.d.ts.map +1 -1
  12. package/dist/Badge/Badge.js +8 -1
  13. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  14. package/dist/BottomSheet/BottomSheet.js +7 -3
  15. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  16. package/dist/BottomSheet/BottomSheetPanel.js +38 -10
  17. package/dist/BottomSheet/BottomSheetSwitcher.d.ts +1 -1
  18. package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
  19. package/dist/BottomSheet/useMobileKeyboard.d.ts.map +1 -1
  20. package/dist/BottomSheet/useMobileKeyboard.js +44 -8
  21. package/dist/BottomSheet/useSheetGestures.d.ts +48 -6
  22. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  23. package/dist/BottomSheet/useSheetGestures.js +97 -13
  24. package/dist/Breadcrumbs/BreadcrumbItem.js +4 -2
  25. package/dist/Button/Button.js +4 -4
  26. package/dist/Carousel/Carousel.d.ts +3 -1
  27. package/dist/Carousel/Carousel.d.ts.map +1 -1
  28. package/dist/Carousel/Carousel.js +12 -3
  29. package/dist/Center/Center.d.ts +1 -1
  30. package/dist/Center/Center.js +1 -1
  31. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  32. package/dist/Chat/ChatComposer.js +10 -5
  33. package/dist/Chat/ChatComposerDrawer.d.ts +1 -1
  34. package/dist/Chat/ChatComposerDrawer.js +1 -1
  35. package/dist/Chat/ChatComposerInput.d.ts +1 -1
  36. package/dist/Chat/ChatComposerInput.js +1 -1
  37. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  38. package/dist/Chat/ChatLayout.js +1 -1
  39. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  40. package/dist/Chat/ChatMessageList.js +3 -1
  41. package/dist/Chat/ChatPastedTextToken.d.ts.map +1 -1
  42. package/dist/Chat/ChatPastedTextToken.js +15 -4
  43. package/dist/Chat/ChatSystemMessage.d.ts.map +1 -1
  44. package/dist/Chat/ChatSystemMessage.js +2 -1
  45. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  46. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  47. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  48. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  49. package/dist/CheckboxList/CheckboxList.js +5 -2
  50. package/dist/CheckboxList/CheckboxListContext.d.ts +8 -0
  51. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  52. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  53. package/dist/CheckboxList/CheckboxListItem.js +5 -2
  54. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  55. package/dist/ClickableCard/ClickableCard.js +37 -4
  56. package/dist/Code/Code.d.ts +1 -1
  57. package/dist/Code/Code.js +1 -1
  58. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  59. package/dist/CommandPalette/CommandPaletteInput.js +8 -5
  60. package/dist/ComplexSelector/ComplexSelector.d.ts +2 -2
  61. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  62. package/dist/ComplexSelector/ComplexSelector.js +48 -6
  63. package/dist/ContextMenu/ContextMenu.d.ts +6 -0
  64. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  65. package/dist/ContextMenu/ContextMenu.js +31 -7
  66. package/dist/DateInput/DateInput.d.ts +3 -2
  67. package/dist/DateInput/DateInput.d.ts.map +1 -1
  68. package/dist/DateInput/DateInput.js +12 -4
  69. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  70. package/dist/DateInput/MonthScroller.js +108 -23
  71. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  72. package/dist/DateInput/TouchDateField.js +8 -3
  73. package/dist/DateInput/Wheel.d.ts.map +1 -1
  74. package/dist/DateInput/Wheel.js +11 -7
  75. package/dist/DateInput/usePointerDragScroll.d.ts +10 -1
  76. package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -1
  77. package/dist/DateInput/usePointerDragScroll.js +25 -9
  78. package/dist/DateInput/useScrollSettle.d.ts +3 -1
  79. package/dist/DateInput/useScrollSettle.d.ts.map +1 -1
  80. package/dist/DateInput/useScrollSettle.js +37 -8
  81. package/dist/DateRangeInput/DateRangeInput.d.ts +3 -2
  82. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  83. package/dist/DateRangeInput/DateRangeInput.js +9 -5
  84. package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
  85. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  86. package/dist/DateTimeInput/DateTimeInput.js +23 -12
  87. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
  88. package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
  89. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  90. package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
  91. package/dist/Dialog/Dialog.d.ts.map +1 -1
  92. package/dist/Dialog/Dialog.js +30 -10
  93. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  94. package/dist/Dialog/DialogHeader.js +5 -4
  95. package/dist/Divider/Divider.d.ts.map +1 -1
  96. package/dist/Divider/Divider.js +2 -1
  97. package/dist/Drawer/Drawer.d.ts +2 -1
  98. package/dist/Drawer/Drawer.d.ts.map +1 -1
  99. package/dist/Drawer/Drawer.js +8 -3
  100. package/dist/DropdownMenu/DropdownMenu.d.ts +1 -1
  101. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  102. package/dist/DropdownMenu/DropdownMenu.js +56 -30
  103. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +3 -2
  104. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  105. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -3
  106. package/dist/DropdownMenu/DropdownMenuGroup.d.ts.map +1 -1
  107. package/dist/DropdownMenu/DropdownMenuGroup.js +2 -1
  108. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  109. package/dist/DropdownMenu/DropdownMenuItem.js +4 -3
  110. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +4 -3
  111. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  112. package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -3
  113. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  114. package/dist/DropdownMenu/DropdownMenuSubMenu.js +10 -6
  115. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  116. package/dist/DropdownMenu/MenuBottomSheetActionList.js +15 -8
  117. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +20 -2
  118. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  119. package/dist/DropdownMenu/lazyMenuBottomSheet.js +31 -3
  120. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  121. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  122. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  123. package/dist/EmptyState/EmptyState.js +2 -1
  124. package/dist/Field/Field.d.ts +2 -2
  125. package/dist/Field/Field.d.ts.map +1 -1
  126. package/dist/Field/FieldLabel.d.ts +1 -1
  127. package/dist/Field/FieldLabel.d.ts.map +1 -1
  128. package/dist/Field/FieldLabel.js +23 -6
  129. package/dist/Field/InputClearButton.js +3 -3
  130. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  131. package/dist/Field/PanelSearchInput.js +12 -7
  132. package/dist/Field/fieldStatusPlacement.d.ts +15 -0
  133. package/dist/Field/fieldStatusPlacement.d.ts.map +1 -0
  134. package/dist/Field/fieldStatusPlacement.js +1 -0
  135. package/dist/FieldStatus/FieldStatus.d.ts +16 -28
  136. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  137. package/dist/FieldStatus/FieldStatus.js +27 -5
  138. package/dist/FieldStatus/index.d.ts +7 -0
  139. package/dist/FieldStatus/index.d.ts.map +1 -1
  140. package/dist/FileInput/FileInput.d.ts +10 -2
  141. package/dist/FileInput/FileInput.d.ts.map +1 -1
  142. package/dist/FileInput/FileInput.js +93 -36
  143. package/dist/Heading/Heading.js +1 -1
  144. package/dist/Icon/Icon.js +1 -1
  145. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  146. package/dist/Indicator/CheckIndicator.js +2 -1
  147. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  148. package/dist/Indicator/CheckboxIndicator.js +3 -1
  149. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  150. package/dist/Indicator/RadioIndicator.js +3 -1
  151. package/dist/Item/Item.d.ts.map +1 -1
  152. package/dist/Item/Item.js +26 -12
  153. package/dist/Item/ItemSwipeLayer.d.ts.map +1 -1
  154. package/dist/Item/ItemSwipeLayer.js +9 -3
  155. package/dist/Layer/popoverOperations.d.ts +27 -0
  156. package/dist/Layer/popoverOperations.d.ts.map +1 -0
  157. package/dist/Layer/popoverOperations.js +159 -0
  158. package/dist/Layer/useLayer.d.ts +9 -1
  159. package/dist/Layer/useLayer.d.ts.map +1 -1
  160. package/dist/Layer/useLayer.js +126 -69
  161. package/dist/Layer/useTouchTrigger.d.ts +35 -3
  162. package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
  163. package/dist/Layer/useTouchTrigger.js +80 -11
  164. package/dist/Layout/Layout.js +1 -1
  165. package/dist/Layout/LayoutContent.js +1 -1
  166. package/dist/Layout/LayoutFooter.js +1 -1
  167. package/dist/Layout/LayoutHeader.js +1 -1
  168. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  169. package/dist/Lightbox/Lightbox.js +5 -0
  170. package/dist/Link/Link.d.ts.map +1 -1
  171. package/dist/Link/Link.js +18 -4
  172. package/dist/List/List.js +1 -1
  173. package/dist/List/ListItem.js +3 -3
  174. package/dist/Markdown/Markdown.d.ts.map +1 -1
  175. package/dist/Markdown/Markdown.js +1 -0
  176. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  177. package/dist/MetadataList/MetadataList.js +2 -1
  178. package/dist/MetadataList/MetadataListItem.d.ts.map +1 -1
  179. package/dist/MetadataList/MetadataListItem.js +2 -1
  180. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  181. package/dist/MobileNav/MobileNav.js +7 -2
  182. package/dist/MultiSelector/MultiSelector.d.ts +3 -3
  183. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  184. package/dist/MultiSelector/MultiSelector.js +61 -35
  185. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  186. package/dist/MultiSelector/hooks.js +4 -2
  187. package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
  188. package/dist/NavMenu/NavHeadingMenuItem.js +5 -29
  189. package/dist/NumberInput/NumberInput.d.ts +6 -3
  190. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  191. package/dist/NumberInput/NumberInput.js +21 -4
  192. package/dist/NumberInput/numberParser.d.ts +3 -0
  193. package/dist/NumberInput/numberParser.d.ts.map +1 -1
  194. package/dist/NumberInput/numberParser.js +7 -44
  195. package/dist/Outline/Outline.d.ts.map +1 -1
  196. package/dist/Outline/Outline.js +27 -0
  197. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  198. package/dist/OverflowList/OverflowList.js +2 -1
  199. package/dist/Pagination/Pagination.d.ts.map +1 -1
  200. package/dist/Popover/Popover.d.ts.map +1 -1
  201. package/dist/Popover/Popover.js +34 -14
  202. package/dist/Popover/usePopover.d.ts +7 -1
  203. package/dist/Popover/usePopover.d.ts.map +1 -1
  204. package/dist/Popover/usePopover.js +18 -5
  205. package/dist/PowerSearch/PowerSearch.d.ts +3 -2
  206. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  207. package/dist/PowerSearch/PowerSearch.js +41 -32
  208. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  209. package/dist/ProgressBar/ProgressBar.js +16 -12
  210. package/dist/RadioList/RadioList.d.ts.map +1 -1
  211. package/dist/RadioList/RadioList.js +7 -2
  212. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  213. package/dist/RadioList/RadioListItem.js +19 -5
  214. package/dist/RadioList/RadioListReasonContext.d.ts +2 -0
  215. package/dist/RadioList/RadioListReasonContext.d.ts.map +1 -0
  216. package/dist/RadioList/RadioListReasonContext.js +22 -0
  217. package/dist/Resizable/resizableModel.d.ts +64 -0
  218. package/dist/Resizable/resizableModel.d.ts.map +1 -0
  219. package/dist/Resizable/resizableModel.js +287 -0
  220. package/dist/Resizable/useResizable.d.ts +2 -2
  221. package/dist/Resizable/useResizable.d.ts.map +1 -1
  222. package/dist/Resizable/useResizable.js +74 -288
  223. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  224. package/dist/SegmentedControl/SegmentedControl.js +22 -5
  225. package/dist/SegmentedControl/SegmentedControlContext.d.ts +6 -0
  226. package/dist/SegmentedControl/SegmentedControlContext.d.ts.map +1 -1
  227. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  228. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  229. package/dist/SegmentedControl/SegmentedControlItem.js +20 -4
  230. package/dist/Selector/Selector.d.ts +3 -3
  231. package/dist/Selector/Selector.d.ts.map +1 -1
  232. package/dist/Selector/Selector.js +14 -8
  233. package/dist/Selector/hooks.js +2 -2
  234. package/dist/Selector/useSelectorPresentation.d.ts +14 -1
  235. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  236. package/dist/Selector/useSelectorPresentation.js +22 -5
  237. package/dist/SideNav/SideNavItem.d.ts +2 -2
  238. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  239. package/dist/SideNav/SideNavItem.js +18 -7
  240. package/dist/SideNav/railMotion.d.ts +5 -5
  241. package/dist/SideNav/railMotion.d.ts.map +1 -1
  242. package/dist/SideNav/railMotion.js +69 -177
  243. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  244. package/dist/Skeleton/Skeleton.js +1 -1
  245. package/dist/Slider/Slider.d.ts.map +1 -1
  246. package/dist/Slider/Slider.js +110 -19
  247. package/dist/Spinner/Spinner.d.ts.map +1 -1
  248. package/dist/Spinner/Spinner.js +3 -3
  249. package/dist/Stack/Stack.js +1 -1
  250. package/dist/Stack/StackItem.js +1 -1
  251. package/dist/Stepper/Step.d.ts.map +1 -1
  252. package/dist/Stepper/Step.js +26 -6
  253. package/dist/Switch/Switch.d.ts.map +1 -1
  254. package/dist/Switch/Switch.js +10 -3
  255. package/dist/TabList/TabList.d.ts.map +1 -1
  256. package/dist/TabList/TabList.js +16 -1
  257. package/dist/Table/BaseTable.d.ts.map +1 -1
  258. package/dist/Table/BaseTable.js +7 -6
  259. package/dist/Table/Table.d.ts.map +1 -1
  260. package/dist/Table/Table.js +36 -10
  261. package/dist/Table/glidingDivider.d.ts +8 -1
  262. package/dist/Table/glidingDivider.d.ts.map +1 -1
  263. package/dist/Table/glidingDivider.js +8 -3
  264. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  265. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  266. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  267. package/dist/Table/plugins/sortable/useTableSortable.js +46 -58
  268. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  269. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +1 -2
  270. package/dist/Text/Text.js +1 -1
  271. package/dist/Text/useTruncation.d.ts.map +1 -1
  272. package/dist/Text/useTruncation.js +23 -5
  273. package/dist/TextArea/TextArea.d.ts +3 -3
  274. package/dist/TextArea/TextArea.d.ts.map +1 -1
  275. package/dist/TextArea/TextArea.js +13 -2
  276. package/dist/TextInput/TextInput.d.ts +3 -2
  277. package/dist/TextInput/TextInput.d.ts.map +1 -1
  278. package/dist/TextInput/TextInput.js +3 -3
  279. package/dist/TimeInput/TimeInput.d.ts +9 -4
  280. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  281. package/dist/TimeInput/TimeInput.js +21 -14
  282. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  283. package/dist/TimeInput/TouchTimeField.js +5 -4
  284. package/dist/Timer/Timer.js +1 -1
  285. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  286. package/dist/Timestamp/Timestamp.js +2 -2
  287. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  288. package/dist/Toast/Toast.d.ts.map +1 -1
  289. package/dist/Toast/Toast.js +60 -20
  290. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  291. package/dist/Toast/ToastViewport.js +101 -54
  292. package/dist/Toast/useToast.d.ts.map +1 -1
  293. package/dist/Toast/useToast.js +8 -2
  294. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  295. package/dist/ToggleButton/ToggleButton.js +11 -3
  296. package/dist/ToggleButton/ToggleButtonGroup.d.ts +7 -1
  297. package/dist/ToggleButton/ToggleButtonGroup.d.ts.map +1 -1
  298. package/dist/ToggleButton/ToggleButtonGroup.js +16 -2
  299. package/dist/Token/Token.d.ts +1 -1
  300. package/dist/Token/Token.d.ts.map +1 -1
  301. package/dist/Token/Token.js +52 -5
  302. package/dist/Token/TokenLink.d.ts.map +1 -1
  303. package/dist/Token/TokenLink.js +14 -2
  304. package/dist/Tokenizer/Tokenizer.d.ts +3 -2
  305. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  306. package/dist/Tokenizer/Tokenizer.js +101 -56
  307. package/dist/Tokenizer/TokenizerBottomSheet.d.ts +1 -1
  308. package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
  309. package/dist/Tokenizer/TokenizerBottomSheet.js +12 -18
  310. package/dist/Tokenizer/TokenizerClearAllMessageContext.d.ts +2 -0
  311. package/dist/Tokenizer/TokenizerClearAllMessageContext.d.ts.map +1 -0
  312. package/dist/Tokenizer/TokenizerClearAllMessageContext.js +17 -0
  313. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  314. package/dist/Toolbar/Toolbar.js +2 -1
  315. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  316. package/dist/TopNav/TopNavMegaMenu.js +7 -3
  317. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  318. package/dist/TopNav/TopNavMenu.js +1 -0
  319. package/dist/TreeList/TreeList.d.ts.map +1 -1
  320. package/dist/TreeList/TreeList.js +6 -3
  321. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  322. package/dist/TreeList/TreeListItem.js +31 -7
  323. package/dist/Typeahead/BaseTypeahead.d.ts +8 -5
  324. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  325. package/dist/Typeahead/BaseTypeahead.js +303 -48
  326. package/dist/Typeahead/Typeahead.d.ts +4 -3
  327. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  328. package/dist/Typeahead/Typeahead.js +43 -9
  329. package/dist/Typeahead/index.d.ts +1 -1
  330. package/dist/Typeahead/index.d.ts.map +1 -1
  331. package/dist/Typeahead/types.d.ts +26 -3
  332. package/dist/Typeahead/types.d.ts.map +1 -1
  333. package/dist/astryx.css +27 -6
  334. package/dist/gestures/GestureArena.d.ts +32 -1
  335. package/dist/gestures/GestureArena.d.ts.map +1 -1
  336. package/dist/gestures/GestureArena.js +110 -9
  337. package/dist/gestures/GestureHost.d.ts +351 -0
  338. package/dist/gestures/GestureHost.d.ts.map +1 -0
  339. package/dist/gestures/GestureHost.js +1773 -0
  340. package/dist/gestures/GestureRecognizer.d.ts +13 -1
  341. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  342. package/dist/gestures/GestureRecognizer.js +19 -1
  343. package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
  344. package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
  345. package/dist/gestures/LongPressGestureRecognizer.js +3 -0
  346. package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
  347. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  348. package/dist/gestures/PanGestureRecognizer.js +3 -0
  349. package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
  350. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  351. package/dist/gestures/PinchGestureRecognizer.js +128 -0
  352. package/dist/gestures/PressTracker.d.ts +112 -0
  353. package/dist/gestures/PressTracker.d.ts.map +1 -0
  354. package/dist/gestures/PressTracker.js +267 -0
  355. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
  356. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  357. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
  358. package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
  359. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  360. package/dist/gestures/SwipeGestureRecognizer.js +148 -0
  361. package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
  362. package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
  363. package/dist/gestures/TapGestureRecognizer.js +3 -0
  364. package/dist/gestures/VelocityTracker.d.ts +6 -2
  365. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  366. package/dist/gestures/VelocityTracker.js +7 -3
  367. package/dist/gestures/haptics.d.ts +65 -0
  368. package/dist/gestures/haptics.d.ts.map +1 -0
  369. package/dist/gestures/haptics.js +178 -0
  370. package/dist/gestures/index.d.ts +37 -0
  371. package/dist/gestures/index.d.ts.map +1 -0
  372. package/dist/gestures/index.js +25 -0
  373. package/dist/gestures/nativeScroll.d.ts +42 -0
  374. package/dist/gestures/nativeScroll.d.ts.map +1 -0
  375. package/dist/gestures/nativeScroll.js +228 -0
  376. package/dist/gestures/touchAction.d.ts +60 -0
  377. package/dist/gestures/touchAction.d.ts.map +1 -0
  378. package/dist/gestures/touchAction.js +157 -0
  379. package/dist/gestures/touchActionRule.d.ts +34 -0
  380. package/dist/gestures/touchActionRule.d.ts.map +1 -0
  381. package/dist/gestures/touchActionRule.js +140 -0
  382. package/dist/gestures/types.d.ts +18 -7
  383. package/dist/gestures/types.d.ts.map +1 -1
  384. package/dist/gestures/types.js +22 -2
  385. package/dist/gestures/useGestureRecognizer.d.ts +56 -0
  386. package/dist/gestures/useGestureRecognizer.d.ts.map +1 -0
  387. package/dist/gestures/useGestureRecognizer.js +226 -0
  388. package/dist/hooks/index.d.ts +1 -1
  389. package/dist/hooks/index.d.ts.map +1 -1
  390. package/dist/hooks/menuPressMarker.d.ts +13 -0
  391. package/dist/hooks/menuPressMarker.d.ts.map +1 -0
  392. package/dist/hooks/menuPressMarker.js +15 -0
  393. package/dist/hooks/modalAnnounce.d.ts.map +1 -1
  394. package/dist/hooks/modalAnnounce.js +107 -61
  395. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -1
  396. package/dist/hooks/scrollKeyboardDelegation.js +7 -3
  397. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  398. package/dist/hooks/useAdaptivePresentation.js +4 -1
  399. package/dist/hooks/useAnnounce.d.ts +101 -42
  400. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  401. package/dist/hooks/useAnnounce.js +161 -75
  402. package/dist/hooks/useAnnounceRenderedText.d.ts +11 -3
  403. package/dist/hooks/useAnnounceRenderedText.d.ts.map +1 -1
  404. package/dist/hooks/useAnnounceRenderedText.js +5 -3
  405. package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -1
  406. package/dist/hooks/useBusyAnnouncement.js +6 -12
  407. package/dist/hooks/useClickableContainer.d.ts +24 -4
  408. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  409. package/dist/hooks/useClickableContainer.js +41 -14
  410. package/dist/hooks/useFocusReturnVisibility.d.ts +8 -1
  411. package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -1
  412. package/dist/hooks/useFocusReturnVisibility.js +14 -8
  413. package/dist/hooks/useInputStatusIcon.d.ts +2 -2
  414. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  415. package/dist/hooks/useInputStatusIcon.js +1 -2
  416. package/dist/hooks/useListFocus.d.ts +27 -0
  417. package/dist/hooks/useListFocus.d.ts.map +1 -1
  418. package/dist/hooks/useListFocus.js +35 -4
  419. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  420. package/dist/hooks/useMenuHover.js +12 -1
  421. package/dist/hooks/useMenuPress.d.ts +2 -2
  422. package/dist/hooks/useMenuPress.d.ts.map +1 -1
  423. package/dist/hooks/useMenuPress.js +5 -6
  424. package/dist/hooks/useScrollOverflow.d.ts +2 -1
  425. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  426. package/dist/hooks/useScrollOverflow.js +56 -10
  427. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  428. package/dist/hooks/useTreeFocus.js +13 -5
  429. package/dist/hooks/useTypeahead.d.ts +3 -1
  430. package/dist/hooks/useTypeahead.d.ts.map +1 -1
  431. package/dist/hooks/useTypeahead.js +21 -15
  432. package/dist/i18n/generated-locales/af-ZA.generated.d.ts.map +1 -1
  433. package/dist/i18n/generated-locales/af-ZA.generated.js +0 -5
  434. package/dist/i18n/generated-locales/ar-SA.generated.d.ts.map +1 -1
  435. package/dist/i18n/generated-locales/ar-SA.generated.js +0 -5
  436. package/dist/i18n/generated-locales/ca-ES.generated.d.ts.map +1 -1
  437. package/dist/i18n/generated-locales/ca-ES.generated.js +0 -5
  438. package/dist/i18n/generated-locales/cs-CZ.generated.d.ts.map +1 -1
  439. package/dist/i18n/generated-locales/cs-CZ.generated.js +0 -5
  440. package/dist/i18n/generated-locales/da-DK.generated.d.ts.map +1 -1
  441. package/dist/i18n/generated-locales/da-DK.generated.js +0 -5
  442. package/dist/i18n/generated-locales/de-DE.generated.d.ts.map +1 -1
  443. package/dist/i18n/generated-locales/de-DE.generated.js +0 -5
  444. package/dist/i18n/generated-locales/el-GR.generated.d.ts.map +1 -1
  445. package/dist/i18n/generated-locales/el-GR.generated.js +0 -5
  446. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  447. package/dist/i18n/generated-locales/en.generated.js +14 -5
  448. package/dist/i18n/generated-locales/es-ES.generated.d.ts.map +1 -1
  449. package/dist/i18n/generated-locales/es-ES.generated.js +0 -5
  450. package/dist/i18n/generated-locales/fi-FI.generated.d.ts.map +1 -1
  451. package/dist/i18n/generated-locales/fi-FI.generated.js +0 -5
  452. package/dist/i18n/generated-locales/fr-FR.generated.d.ts.map +1 -1
  453. package/dist/i18n/generated-locales/fr-FR.generated.js +0 -5
  454. package/dist/i18n/generated-locales/he-IL.generated.d.ts.map +1 -1
  455. package/dist/i18n/generated-locales/he-IL.generated.js +0 -5
  456. package/dist/i18n/generated-locales/hu-HU.generated.d.ts.map +1 -1
  457. package/dist/i18n/generated-locales/hu-HU.generated.js +0 -5
  458. package/dist/i18n/generated-locales/it-IT.generated.d.ts.map +1 -1
  459. package/dist/i18n/generated-locales/it-IT.generated.js +0 -5
  460. package/dist/i18n/generated-locales/ja-JP.generated.d.ts.map +1 -1
  461. package/dist/i18n/generated-locales/ja-JP.generated.js +0 -5
  462. package/dist/i18n/generated-locales/ko-KR.generated.d.ts.map +1 -1
  463. package/dist/i18n/generated-locales/ko-KR.generated.js +0 -5
  464. package/dist/i18n/generated-locales/nl-NL.generated.d.ts.map +1 -1
  465. package/dist/i18n/generated-locales/nl-NL.generated.js +0 -5
  466. package/dist/i18n/generated-locales/no-NO.generated.d.ts.map +1 -1
  467. package/dist/i18n/generated-locales/no-NO.generated.js +0 -5
  468. package/dist/i18n/generated-locales/pl-PL.generated.d.ts.map +1 -1
  469. package/dist/i18n/generated-locales/pl-PL.generated.js +0 -5
  470. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  471. package/dist/i18n/generated-locales/pseudo.generated.js +14 -5
  472. package/dist/i18n/generated-locales/pt-BR.generated.d.ts.map +1 -1
  473. package/dist/i18n/generated-locales/pt-BR.generated.js +0 -5
  474. package/dist/i18n/generated-locales/pt-PT.generated.d.ts.map +1 -1
  475. package/dist/i18n/generated-locales/pt-PT.generated.js +0 -5
  476. package/dist/i18n/generated-locales/ro-RO.generated.d.ts.map +1 -1
  477. package/dist/i18n/generated-locales/ro-RO.generated.js +0 -5
  478. package/dist/i18n/generated-locales/ru-RU.generated.d.ts.map +1 -1
  479. package/dist/i18n/generated-locales/ru-RU.generated.js +0 -5
  480. package/dist/i18n/generated-locales/sr-SP.generated.d.ts.map +1 -1
  481. package/dist/i18n/generated-locales/sr-SP.generated.js +0 -5
  482. package/dist/i18n/generated-locales/sv-SE.generated.d.ts.map +1 -1
  483. package/dist/i18n/generated-locales/sv-SE.generated.js +0 -5
  484. package/dist/i18n/generated-locales/tr-TR.generated.d.ts.map +1 -1
  485. package/dist/i18n/generated-locales/tr-TR.generated.js +0 -5
  486. package/dist/i18n/generated-locales/uk-UA.generated.d.ts.map +1 -1
  487. package/dist/i18n/generated-locales/uk-UA.generated.js +0 -5
  488. package/dist/i18n/generated-locales/vi-VN.generated.d.ts.map +1 -1
  489. package/dist/i18n/generated-locales/vi-VN.generated.js +0 -5
  490. package/dist/i18n/generated-locales/zh-CN.generated.d.ts.map +1 -1
  491. package/dist/i18n/generated-locales/zh-CN.generated.js +0 -5
  492. package/dist/i18n/generated-locales/zh-TW.generated.d.ts.map +1 -1
  493. package/dist/i18n/generated-locales/zh-TW.generated.js +0 -5
  494. package/dist/i18n/useListFormatter.d.ts +9 -0
  495. package/dist/i18n/useListFormatter.d.ts.map +1 -0
  496. package/dist/i18n/useListFormatter.js +41 -0
  497. package/dist/motion/animatedLayout.d.ts +130 -5
  498. package/dist/motion/animatedLayout.d.ts.map +1 -1
  499. package/dist/motion/animatedLayout.js +771 -44
  500. package/dist/motion/animator.d.ts +83 -8
  501. package/dist/motion/animator.d.ts.map +1 -1
  502. package/dist/motion/animator.js +739 -121
  503. package/dist/motion/coast.d.ts +4 -1
  504. package/dist/motion/coast.d.ts.map +1 -1
  505. package/dist/motion/coast.js +4 -2
  506. package/dist/motion/reducedMotion.d.ts.map +1 -1
  507. package/dist/motion/reducedMotion.js +6 -2
  508. package/dist/motion/reducedMotionPlan.d.ts +7 -0
  509. package/dist/motion/reducedMotionPlan.d.ts.map +1 -1
  510. package/dist/motion/reducedMotionPlan.js +14 -1
  511. package/dist/motion/resolveMotion.d.ts +42 -0
  512. package/dist/motion/resolveMotion.d.ts.map +1 -0
  513. package/dist/motion/resolveMotion.js +43 -0
  514. package/dist/motion/roleValue.d.ts +2 -1
  515. package/dist/motion/roleValue.d.ts.map +1 -1
  516. package/dist/motion/roleValue.js +2 -1
  517. package/dist/motion/uikit/timing.d.ts +7 -1
  518. package/dist/motion/uikit/timing.d.ts.map +1 -1
  519. package/dist/motion/uikit/timing.js +19 -1
  520. package/dist/privateAttributes/segmentDisabled.stylex.d.ts +5 -0
  521. package/dist/privateAttributes/segmentDisabled.stylex.d.ts.map +1 -0
  522. package/dist/privateAttributes/segmentDisabled.stylex.js +28 -0
  523. package/dist/theme/Theme.d.ts +10 -0
  524. package/dist/theme/Theme.d.ts.map +1 -1
  525. package/dist/theme/Theme.js +8 -4
  526. package/dist/theme/defineTheme.d.ts +19 -5
  527. package/dist/theme/defineTheme.d.ts.map +1 -1
  528. package/dist/theme/defineTheme.js +64 -4
  529. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  530. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  531. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  532. package/dist/theme/domainTokens/index.d.ts +5 -4
  533. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  534. package/dist/theme/domainTokens/index.js +5 -9
  535. package/dist/theme/index.d.ts +1 -0
  536. package/dist/theme/index.d.ts.map +1 -1
  537. package/dist/theme/motionKnobs.d.ts +56 -0
  538. package/dist/theme/motionKnobs.d.ts.map +1 -0
  539. package/dist/theme/motionKnobs.js +48 -0
  540. package/dist/theme/resolveThemeValues.d.ts +11 -4
  541. package/dist/theme/resolveThemeValues.d.ts.map +1 -1
  542. package/dist/theme/resolveThemeValues.js +40 -4
  543. package/dist/theme/themeAdaptations.d.ts +7 -3
  544. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  545. package/dist/theme/themeAdaptations.js +36 -7
  546. package/dist/theme/themeMotion.d.ts +96 -0
  547. package/dist/theme/themeMotion.d.ts.map +1 -0
  548. package/dist/theme/themeMotion.js +177 -0
  549. package/dist/theme/tokenReferences.d.ts +31 -0
  550. package/dist/theme/tokenReferences.d.ts.map +1 -0
  551. package/dist/theme/tokenReferences.js +132 -0
  552. package/dist/theme/tokens.d.ts +1 -0
  553. package/dist/theme/tokens.d.ts.map +1 -1
  554. package/dist/theme/tokens.js +9 -129
  555. package/dist/theme/tokens.stylex.d.ts +12 -0
  556. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  557. package/dist/theme/tokens.stylex.js +12 -0
  558. package/dist/theme/useTheme.d.ts.map +1 -1
  559. package/dist/theme/useTheme.js +7 -1
  560. package/dist/utils/focusOutline.stylex.d.ts +6 -0
  561. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  562. package/dist/utils/focusOutline.stylex.js +7 -0
  563. package/dist/utils/focusReturn.d.ts +19 -1
  564. package/dist/utils/focusReturn.d.ts.map +1 -1
  565. package/dist/utils/focusReturn.js +13 -4
  566. package/dist/utils/getNodeText.d.ts +8 -0
  567. package/dist/utils/getNodeText.d.ts.map +1 -0
  568. package/dist/utils/getNodeText.js +38 -0
  569. package/dist/utils/interactionModality.d.ts +84 -4
  570. package/dist/utils/interactionModality.d.ts.map +1 -1
  571. package/dist/utils/interactionModality.js +152 -16
  572. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  573. package/dist/utils/interactionOverlay.stylex.js +17 -8
  574. package/dist/utils/plainDate.d.ts +2 -0
  575. package/dist/utils/plainDate.d.ts.map +1 -1
  576. package/dist/utils/plainDate.js +46 -3
  577. package/dist/utils/presentation.d.ts +50 -0
  578. package/dist/utils/presentation.d.ts.map +1 -0
  579. package/dist/utils/presentation.js +125 -0
  580. package/dist/utils/pressDisabled.d.ts +21 -0
  581. package/dist/utils/pressDisabled.d.ts.map +1 -0
  582. package/dist/utils/pressDisabled.js +32 -0
  583. package/dist/utils/pressFeedback.d.ts.map +1 -1
  584. package/dist/utils/pressFeedback.js +25 -4
  585. package/dist/utils/primaryClick.d.ts +11 -0
  586. package/dist/utils/primaryClick.d.ts.map +1 -0
  587. package/dist/utils/primaryClick.js +25 -0
  588. package/dist/utils/sharedMutationObserver.d.ts +19 -11
  589. package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
  590. package/dist/utils/sharedMutationObserver.js +66 -26
  591. package/dist/utils/timeFormat.d.ts +47 -0
  592. package/dist/utils/timeFormat.d.ts.map +1 -0
  593. package/dist/utils/timeFormat.js +240 -0
  594. package/dist/utils/touchPresentation.d.ts +2 -2
  595. package/dist/utils/touchPresentation.d.ts.map +1 -1
  596. package/dist/utils/touchPresentation.js +13 -8
  597. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  598. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  599. package/dist/utils/touchPresentation.stylex.js +33 -0
  600. package/dist/utils/touchQueries.d.ts +13 -0
  601. package/dist/utils/touchQueries.d.ts.map +1 -0
  602. package/dist/utils/touchQueries.js +15 -0
  603. package/dist/utils/typeaheadMatch.d.ts +24 -0
  604. package/dist/utils/typeaheadMatch.d.ts.map +1 -0
  605. package/dist/utils/typeaheadMatch.js +112 -0
  606. package/dist/utils/unicodeDigits.d.ts +20 -0
  607. package/dist/utils/unicodeDigits.d.ts.map +1 -0
  608. package/dist/utils/unicodeDigits.js +70 -0
  609. package/dist/utils/useFocusAcrossSwap.d.ts +19 -0
  610. package/dist/utils/useFocusAcrossSwap.d.ts.map +1 -0
  611. package/dist/utils/useFocusAcrossSwap.js +41 -0
  612. package/locales/af-ZA.json +0 -21
  613. package/locales/ar-SA.json +0 -21
  614. package/locales/ca-ES.json +0 -21
  615. package/locales/cs-CZ.json +0 -21
  616. package/locales/da-DK.json +0 -21
  617. package/locales/de-DE.json +0 -21
  618. package/locales/el-GR.json +0 -21
  619. package/locales/en.json +60 -24
  620. package/locales/es-ES.json +0 -21
  621. package/locales/fi-FI.json +0 -21
  622. package/locales/fr-FR.json +0 -21
  623. package/locales/he-IL.json +0 -21
  624. package/locales/hu-HU.json +0 -21
  625. package/locales/it-IT.json +0 -21
  626. package/locales/ja-JP.json +0 -21
  627. package/locales/ko-KR.json +0 -21
  628. package/locales/nl-NL.json +0 -21
  629. package/locales/no-NO.json +0 -21
  630. package/locales/pl-PL.json +0 -21
  631. package/locales/pseudo.json +41 -14
  632. package/locales/pt-BR.json +0 -21
  633. package/locales/pt-PT.json +0 -21
  634. package/locales/ro-RO.json +0 -21
  635. package/locales/ru-RU.json +0 -21
  636. package/locales/sr-SP.json +0 -21
  637. package/locales/sv-SE.json +0 -21
  638. package/locales/tr-TR.json +0 -21
  639. package/locales/uk-UA.json +0 -21
  640. package/locales/vi-VN.json +0 -21
  641. package/locales/zh-CN.json +0 -21
  642. package/locales/zh-TW.json +0 -21
  643. package/package.json +20 -5
  644. package/src/AlertDialog/AlertDialog.tsx +6 -5
  645. package/src/AppShell/AppShell.doc.mjs +1 -1
  646. package/src/AppShell/AppShell.tsx +216 -5
  647. package/src/AppShell/observeMainOverflow.ts +143 -0
  648. package/src/Avatar/Avatar.tsx +18 -1
  649. package/src/Badge/Badge.doc.mjs +1 -1
  650. package/src/Badge/Badge.tsx +20 -2
  651. package/src/BottomSheet/BottomSheet.tsx +5 -3
  652. package/src/BottomSheet/BottomSheetPanel.tsx +41 -9
  653. package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -1
  654. package/src/BottomSheet/useMobileKeyboard.ts +51 -10
  655. package/src/BottomSheet/useSheetGestures.ts +129 -21
  656. package/src/Breadcrumbs/BreadcrumbItem.tsx +2 -1
  657. package/src/Button/Button.tsx +4 -4
  658. package/src/Carousel/Carousel.doc.mjs +1 -1
  659. package/src/Carousel/Carousel.tsx +10 -2
  660. package/src/Center/Center.tsx +1 -1
  661. package/src/Chat/ChatComposer.tsx +7 -10
  662. package/src/Chat/ChatComposerDrawer.tsx +1 -1
  663. package/src/Chat/ChatComposerInput.tsx +1 -1
  664. package/src/Chat/ChatLayout.tsx +1 -6
  665. package/src/Chat/ChatMessageList.tsx +1 -0
  666. package/src/Chat/ChatPastedTextToken.tsx +14 -4
  667. package/src/Chat/ChatSystemMessage.tsx +4 -1
  668. package/src/Chat/ChatToolCalls.tsx +5 -0
  669. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  670. package/src/CheckboxList/CheckboxList.tsx +7 -1
  671. package/src/CheckboxList/CheckboxListContext.tsx +8 -0
  672. package/src/CheckboxList/CheckboxListItem.tsx +8 -2
  673. package/src/ClickableCard/ClickableCard.tsx +48 -4
  674. package/src/Code/Code.tsx +1 -1
  675. package/src/CommandPalette/CommandPaletteInput.tsx +5 -8
  676. package/src/ComplexSelector/ComplexSelector.tsx +63 -8
  677. package/src/ContextMenu/ContextMenu.tsx +30 -6
  678. package/src/DateInput/DateInput.tsx +31 -16
  679. package/src/DateInput/MonthScroller.tsx +108 -24
  680. package/src/DateInput/TouchDateField.tsx +14 -3
  681. package/src/DateInput/Wheel.tsx +9 -6
  682. package/src/DateInput/usePointerDragScroll.ts +34 -10
  683. package/src/DateInput/useScrollSettle.ts +38 -7
  684. package/src/DateRangeInput/DateRangeInput.tsx +36 -17
  685. package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
  686. package/src/DateTimeInput/DateTimeInput.tsx +46 -24
  687. package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
  688. package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
  689. package/src/Dialog/Dialog.tsx +28 -11
  690. package/src/Dialog/DialogHeader.tsx +5 -4
  691. package/src/Divider/Divider.tsx +2 -1
  692. package/src/Drawer/Drawer.tsx +14 -3
  693. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  694. package/src/DropdownMenu/DropdownMenu.tsx +67 -30
  695. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +2 -2
  696. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +15 -5
  697. package/src/DropdownMenu/DropdownMenuGroup.tsx +2 -1
  698. package/src/DropdownMenu/DropdownMenuItem.tsx +7 -1
  699. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +2 -2
  700. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +16 -6
  701. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +11 -4
  702. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +12 -8
  703. package/src/DropdownMenu/lazyMenuBottomSheet.ts +36 -7
  704. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  705. package/src/EmptyState/EmptyState.tsx +2 -1
  706. package/src/Field/Field.doc.mjs +2 -1
  707. package/src/Field/Field.tsx +2 -2
  708. package/src/Field/FieldLabel.doc.mjs +4 -2
  709. package/src/Field/FieldLabel.tsx +23 -6
  710. package/src/Field/InputClearButton.tsx +3 -3
  711. package/src/Field/PanelSearchInput.tsx +9 -10
  712. package/src/Field/fieldStatusPlacement.ts +18 -0
  713. package/src/FieldStatus/FieldStatus.tsx +35 -5
  714. package/src/FieldStatus/index.ts +7 -0
  715. package/src/FileInput/FileInput.doc.mjs +3 -3
  716. package/src/FileInput/FileInput.tsx +107 -37
  717. package/src/Heading/Heading.tsx +1 -1
  718. package/src/HoverCard/HoverCard.doc.mjs +3 -3
  719. package/src/HoverCard/useHoverCard.doc.mjs +1 -1
  720. package/src/Icon/Icon.tsx +1 -1
  721. package/src/Indicator/CheckIndicator.tsx +2 -1
  722. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  723. package/src/Indicator/RadioIndicator.tsx +3 -1
  724. package/src/Item/Item.tsx +21 -13
  725. package/src/Item/ItemSwipeLayer.tsx +9 -2
  726. package/src/Layer/popoverOperations.ts +172 -0
  727. package/src/Layer/useLayer.tsx +168 -81
  728. package/src/Layer/useTouchTrigger.ts +107 -18
  729. package/src/Layout/Layout.tsx +1 -1
  730. package/src/Layout/LayoutContent.tsx +1 -1
  731. package/src/Layout/LayoutFooter.tsx +1 -1
  732. package/src/Layout/LayoutHeader.tsx +1 -1
  733. package/src/Lightbox/Lightbox.tsx +5 -0
  734. package/src/Link/Link.tsx +20 -2
  735. package/src/List/List.tsx +1 -1
  736. package/src/List/ListItem.tsx +3 -3
  737. package/src/Markdown/Markdown.tsx +1 -0
  738. package/src/MetadataList/MetadataList.tsx +4 -2
  739. package/src/MetadataList/MetadataListItem.tsx +2 -1
  740. package/src/MobileNav/MobileNav.tsx +9 -1
  741. package/src/MultiSelector/MultiSelector.tsx +82 -46
  742. package/src/MultiSelector/hooks.ts +7 -5
  743. package/src/NavMenu/NavHeadingMenuItem.tsx +5 -39
  744. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  745. package/src/NumberInput/NumberInput.tsx +39 -6
  746. package/src/NumberInput/numberParser.ts +7 -45
  747. package/src/Outline/Outline.tsx +23 -0
  748. package/src/OverflowList/OverflowList.tsx +2 -1
  749. package/src/Pagination/Pagination.tsx +1 -0
  750. package/src/Popover/Popover.tsx +36 -16
  751. package/src/Popover/usePopover.tsx +17 -5
  752. package/src/PowerSearch/PowerSearch.tsx +46 -33
  753. package/src/ProgressBar/ProgressBar.tsx +58 -10
  754. package/src/RadioList/RadioList.tsx +13 -2
  755. package/src/RadioList/RadioListItem.tsx +20 -3
  756. package/src/RadioList/RadioListReasonContext.ts +25 -0
  757. package/src/Resizable/resizableModel.ts +364 -0
  758. package/src/Resizable/useResizable.ts +88 -353
  759. package/src/SegmentedControl/SegmentedControl.tsx +43 -8
  760. package/src/SegmentedControl/SegmentedControlContext.ts +6 -0
  761. package/src/SegmentedControl/SegmentedControlItem.tsx +23 -6
  762. package/src/Selector/Selector.tsx +23 -9
  763. package/src/Selector/hooks.ts +2 -2
  764. package/src/Selector/useSelectorPresentation.ts +39 -4
  765. package/src/SideNav/SideNavItem.tsx +27 -10
  766. package/src/SideNav/railMotion.ts +73 -179
  767. package/src/Skeleton/Skeleton.tsx +3 -1
  768. package/src/Slider/Slider.tsx +124 -22
  769. package/src/Spinner/Spinner.tsx +5 -3
  770. package/src/Stack/Stack.tsx +1 -1
  771. package/src/Stack/StackItem.tsx +1 -1
  772. package/src/Stepper/Step.tsx +33 -6
  773. package/src/Stepper/Stepper.doc.mjs +1 -1
  774. package/src/Switch/Switch.tsx +11 -3
  775. package/src/TabList/TabList.tsx +16 -1
  776. package/src/Table/BaseTable.tsx +10 -6
  777. package/src/Table/Table.tsx +43 -11
  778. package/src/Table/glidingDivider.ts +8 -3
  779. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  780. package/src/Table/plugins/sortable/useTableSortable.tsx +46 -73
  781. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
  782. package/src/Text/Text.tsx +1 -1
  783. package/src/Text/useTruncation.ts +23 -5
  784. package/src/TextArea/TextArea.tsx +18 -3
  785. package/src/TextInput/TextInput.tsx +5 -3
  786. package/src/TimeInput/TimeInput.doc.mjs +8 -8
  787. package/src/TimeInput/TimeInput.tsx +38 -20
  788. package/src/TimeInput/TouchTimeField.tsx +4 -6
  789. package/src/Timer/Timer.tsx +1 -1
  790. package/src/Timestamp/Timestamp.tsx +6 -2
  791. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  792. package/src/Toast/Toast.tsx +104 -36
  793. package/src/Toast/ToastViewport.tsx +121 -53
  794. package/src/Toast/useToast.tsx +8 -3
  795. package/src/ToggleButton/ToggleButton.tsx +10 -3
  796. package/src/ToggleButton/ToggleButtonGroup.tsx +20 -1
  797. package/src/Token/Token.doc.mjs +2 -2
  798. package/src/Token/Token.tsx +75 -5
  799. package/src/Token/TokenLink.tsx +21 -7
  800. package/src/Tokenizer/Tokenizer.doc.mjs +12 -8
  801. package/src/Tokenizer/Tokenizer.tsx +119 -65
  802. package/src/Tokenizer/TokenizerBottomSheet.tsx +12 -18
  803. package/src/Tokenizer/TokenizerClearAllMessageContext.ts +21 -0
  804. package/src/Toolbar/Toolbar.tsx +2 -1
  805. package/src/Tooltip/Tooltip.doc.mjs +3 -3
  806. package/src/Tooltip/useTooltip.doc.mjs +1 -1
  807. package/src/TopNav/TopNavMegaMenu.tsx +8 -4
  808. package/src/TopNav/TopNavMenu.tsx +1 -0
  809. package/src/TreeList/TreeList.tsx +6 -3
  810. package/src/TreeList/TreeListItem.tsx +31 -7
  811. package/src/Typeahead/BaseTypeahead.doc.mjs +10 -7
  812. package/src/Typeahead/BaseTypeahead.tsx +361 -48
  813. package/src/Typeahead/Typeahead.doc.mjs +21 -4
  814. package/src/Typeahead/Typeahead.tsx +53 -11
  815. package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
  816. package/src/Typeahead/index.ts +1 -1
  817. package/src/Typeahead/types.ts +27 -3
  818. package/src/gestures/GestureArena.ts +111 -11
  819. package/src/gestures/GestureHost.ts +2064 -0
  820. package/src/gestures/GestureRecognizer.ts +21 -1
  821. package/src/gestures/LongPressGestureRecognizer.ts +4 -0
  822. package/src/gestures/PanGestureRecognizer.ts +4 -0
  823. package/src/gestures/PinchGestureRecognizer.ts +148 -0
  824. package/src/gestures/PressTracker.ts +386 -0
  825. package/src/gestures/README.md +420 -0
  826. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
  827. package/src/gestures/SwipeGestureRecognizer.ts +197 -0
  828. package/src/gestures/TapGestureRecognizer.ts +4 -0
  829. package/src/gestures/VelocityTracker.ts +10 -3
  830. package/src/gestures/haptics.ts +196 -0
  831. package/src/gestures/index.ts +79 -0
  832. package/src/gestures/nativeScroll.ts +277 -0
  833. package/src/gestures/touchAction.ts +191 -0
  834. package/src/gestures/touchActionRule.ts +157 -0
  835. package/src/gestures/types.ts +30 -9
  836. package/src/gestures/useGestureRecognizer.ts +305 -0
  837. package/src/hooks/index.ts +5 -1
  838. package/src/hooks/menuPressMarker.ts +15 -0
  839. package/src/hooks/modalAnnounce.ts +129 -72
  840. package/src/hooks/scrollKeyboardDelegation.ts +9 -3
  841. package/src/hooks/useAdaptivePresentation.ts +4 -1
  842. package/src/hooks/useAnnounce.doc.mjs +35 -3
  843. package/src/hooks/useAnnounce.ts +209 -102
  844. package/src/hooks/useAnnounceRenderedText.ts +17 -3
  845. package/src/hooks/useBusyAnnouncement.ts +7 -10
  846. package/src/hooks/useClickableContainer.ts +48 -17
  847. package/src/hooks/useFocusReturnVisibility.ts +15 -12
  848. package/src/hooks/useInputStatusIcon.tsx +2 -2
  849. package/src/hooks/useListFocus.ts +54 -3
  850. package/src/hooks/useMenuHover.ts +15 -1
  851. package/src/hooks/useMenuPress.ts +5 -5
  852. package/src/hooks/useScrollOverflow.doc.mjs +1 -1
  853. package/src/hooks/useScrollOverflow.ts +59 -10
  854. package/src/hooks/useTreeFocus.doc.mjs +2 -2
  855. package/src/hooks/useTreeFocus.ts +16 -10
  856. package/src/hooks/useTypeahead.doc.mjs +2 -2
  857. package/src/hooks/useTypeahead.ts +21 -20
  858. package/src/i18n/generated-locales/af-ZA.generated.ts +0 -7
  859. package/src/i18n/generated-locales/ar-SA.generated.ts +0 -6
  860. package/src/i18n/generated-locales/ca-ES.generated.ts +0 -7
  861. package/src/i18n/generated-locales/cs-CZ.generated.ts +0 -6
  862. package/src/i18n/generated-locales/da-DK.generated.ts +0 -6
  863. package/src/i18n/generated-locales/de-DE.generated.ts +0 -6
  864. package/src/i18n/generated-locales/el-GR.generated.ts +0 -7
  865. package/src/i18n/generated-locales/en.generated.ts +17 -6
  866. package/src/i18n/generated-locales/es-ES.generated.ts +0 -7
  867. package/src/i18n/generated-locales/fi-FI.generated.ts +0 -6
  868. package/src/i18n/generated-locales/fr-FR.generated.ts +0 -7
  869. package/src/i18n/generated-locales/he-IL.generated.ts +0 -6
  870. package/src/i18n/generated-locales/hu-HU.generated.ts +0 -6
  871. package/src/i18n/generated-locales/it-IT.generated.ts +0 -7
  872. package/src/i18n/generated-locales/ja-JP.generated.ts +0 -6
  873. package/src/i18n/generated-locales/ko-KR.generated.ts +0 -6
  874. package/src/i18n/generated-locales/nl-NL.generated.ts +0 -6
  875. package/src/i18n/generated-locales/no-NO.generated.ts +0 -6
  876. package/src/i18n/generated-locales/pl-PL.generated.ts +0 -7
  877. package/src/i18n/generated-locales/pseudo.generated.ts +17 -6
  878. package/src/i18n/generated-locales/pt-BR.generated.ts +0 -7
  879. package/src/i18n/generated-locales/pt-PT.generated.ts +0 -7
  880. package/src/i18n/generated-locales/ro-RO.generated.ts +0 -6
  881. package/src/i18n/generated-locales/ru-RU.generated.ts +0 -7
  882. package/src/i18n/generated-locales/sr-SP.generated.ts +0 -6
  883. package/src/i18n/generated-locales/sv-SE.generated.ts +0 -6
  884. package/src/i18n/generated-locales/tr-TR.generated.ts +0 -7
  885. package/src/i18n/generated-locales/uk-UA.generated.ts +0 -7
  886. package/src/i18n/generated-locales/vi-VN.generated.ts +0 -6
  887. package/src/i18n/generated-locales/zh-CN.generated.ts +0 -6
  888. package/src/i18n/generated-locales/zh-TW.generated.ts +0 -6
  889. package/src/i18n/useListFormatter.ts +42 -0
  890. package/src/motion/MEASUREMENTS.md +2 -1
  891. package/src/motion/README.md +113 -33
  892. package/src/motion/animatedLayout.ts +977 -45
  893. package/src/motion/animator.ts +987 -132
  894. package/src/motion/coast.ts +10 -3
  895. package/src/motion/reducedMotion.ts +12 -2
  896. package/src/motion/reducedMotionPlan.ts +20 -1
  897. package/src/motion/resolveMotion.ts +68 -0
  898. package/src/motion/roleValue.ts +2 -1
  899. package/src/motion/uikit/timing.ts +15 -1
  900. package/src/privateAttributes/segmentDisabled.stylex.ts +28 -0
  901. package/src/theme/Theme.doc.mjs +1 -1
  902. package/src/theme/Theme.tsx +8 -4
  903. package/src/theme/defineTheme.ts +117 -8
  904. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  905. package/src/theme/domainTokens/index.ts +6 -10
  906. package/src/theme/index.ts +5 -0
  907. package/src/theme/motionKnobs.ts +82 -0
  908. package/src/theme/resolveThemeValues.ts +68 -6
  909. package/src/theme/themeAdaptations.ts +52 -12
  910. package/src/theme/themeMotion.ts +221 -0
  911. package/src/theme/tokenReferences.ts +149 -0
  912. package/src/theme/tokens.stylex.ts +10 -0
  913. package/src/theme/tokens.ts +15 -158
  914. package/src/theme/useTheme.ts +6 -1
  915. package/src/utils/focusOutline.stylex.ts +28 -0
  916. package/src/utils/focusReturn.ts +19 -6
  917. package/src/utils/getNodeText.ts +38 -0
  918. package/src/utils/interactionModality.ts +197 -17
  919. package/src/utils/interactionOverlay.stylex.ts +12 -3
  920. package/src/utils/plainDate.ts +58 -3
  921. package/src/utils/presentation.ts +180 -0
  922. package/src/utils/pressDisabled.ts +32 -0
  923. package/src/utils/pressFeedback.ts +39 -14
  924. package/src/utils/primaryClick.ts +28 -0
  925. package/src/utils/sharedMutationObserver.ts +87 -29
  926. package/src/utils/timeFormat.ts +314 -0
  927. package/src/utils/touchPresentation.stylex.ts +43 -0
  928. package/src/utils/touchPresentation.ts +13 -8
  929. package/src/utils/touchQueries.ts +15 -0
  930. package/src/utils/typeaheadMatch.ts +141 -0
  931. package/src/utils/unicodeDigits.ts +71 -0
  932. package/src/utils/useFocusAcrossSwap.ts +55 -0
  933. package/dist/Item/ItemMarkerColumns.a11y.chromium.spec.d.ts +0 -2
  934. package/dist/Item/ItemMarkerColumns.a11y.chromium.spec.js +0 -132
  935. package/dist/Lightbox/LightboxKeyboard.a11y.browser.spec.d.ts +0 -2
  936. package/dist/Lightbox/LightboxKeyboard.a11y.browser.spec.js +0 -78
  937. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.d.ts +0 -2
  938. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +0 -65
  939. package/dist/List/ListMarkers.a11y.chromium.spec.d.ts +0 -2
  940. package/dist/List/ListMarkers.a11y.chromium.spec.js +0 -677
  941. package/dist/Markdown/Markdown.fr23.bench.d.ts +0 -2
  942. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +0 -1
  943. package/dist/Markdown/Markdown.fr23.bench.js +0 -152
  944. package/dist/Markdown/MarkdownTable.a11y.chromium.spec.d.ts +0 -2
  945. package/dist/Markdown/MarkdownTable.a11y.chromium.spec.js +0 -590
  946. package/dist/Markdown/plugins/headingLinks.a11y.chromium.spec.d.ts +0 -2
  947. package/dist/Markdown/plugins/headingLinks.a11y.chromium.spec.js +0 -657
  948. package/dist/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.d.ts +0 -2
  949. package/dist/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.js +0 -153
  950. package/dist/MultiSelector/CreateRow.a11y.chromium.spec.d.ts +0 -2
  951. package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +0 -116
  952. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.d.ts +0 -2
  953. package/dist/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.js +0 -49
  954. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.d.ts +0 -2
  955. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +0 -273
  956. package/dist/Outline/OutlineFocus.a11y.chromium.spec.d.ts +0 -2
  957. package/dist/Outline/OutlineFocus.a11y.chromium.spec.js +0 -421
  958. package/dist/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.d.ts +0 -2
  959. package/dist/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.js +0 -118
  960. package/dist/reset.a11y.chromium.spec.d.ts +0 -2
  961. package/dist/reset.a11y.chromium.spec.js +0 -231
  962. package/dist/reset.until-found.a11y.browser.spec.d.ts +0 -2
  963. package/dist/reset.until-found.a11y.browser.spec.js +0 -72
  964. package/src/AlertDialog/AlertDialog.spec.md +0 -259
  965. package/src/AlertDialog/AlertDialog.test.tsx +0 -519
  966. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +0 -116
  967. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +0 -52
  968. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +0 -68
  969. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +0 -144
  970. package/src/AlertDialog/useImperativeAlertDialog.test.tsx +0 -163
  971. package/src/AppShell/AppShell.spec.md +0 -334
  972. package/src/AppShell/AppShell.test.tsx +0 -1148
  973. package/src/AspectRatio/AspectRatio.spec.md +0 -179
  974. package/src/AspectRatio/AspectRatio.test.tsx +0 -398
  975. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +0 -158
  976. package/src/Avatar/Avatar.spec.md +0 -253
  977. package/src/Avatar/Avatar.test.tsx +0 -867
  978. package/src/Avatar/AvatarStatusDot.spec.md +0 -208
  979. package/src/Avatar/AvatarStatusDot.test.tsx +0 -306
  980. package/src/AvatarGroup/AvatarGroup.spec.md +0 -205
  981. package/src/AvatarGroup/AvatarGroup.test.tsx +0 -313
  982. package/src/AvatarGroup/AvatarGroupOverflow.spec.md +0 -210
  983. package/src/AvatarGroup/AvatarGroupOverflow.test.tsx +0 -263
  984. package/src/Badge/Badge.spec.md +0 -207
  985. package/src/Badge/Badge.test.tsx +0 -125
  986. package/src/Banner/Banner.spec.md +0 -282
  987. package/src/Banner/Banner.test.tsx +0 -879
  988. package/src/Blockquote/Blockquote.spec.md +0 -171
  989. package/src/Blockquote/Blockquote.test.tsx +0 -121
  990. package/src/BottomSheet/BottomSheet.spec.md +0 -457
  991. package/src/BottomSheet/BottomSheet.test.tsx +0 -3108
  992. package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +0 -248
  993. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +0 -195
  994. package/src/BottomSheet/BottomSheetPanel.test.tsx +0 -549
  995. package/src/BottomSheet/BottomSheetReplace.test.tsx +0 -1638
  996. package/src/BottomSheet/BottomSheetStacking.announce.test.tsx +0 -246
  997. package/src/BottomSheet/BottomSheetStacking.test.tsx +0 -3184
  998. package/src/BottomSheet/BottomSheetSwitcher.spec.md +0 -367
  999. package/src/BottomSheet/BottomSheetSwitcher.test.tsx +0 -2010
  1000. package/src/BottomSheet/__tests__/BottomSheetDrag.a11y.chromium.spec.ts +0 -129
  1001. package/src/BottomSheet/__tests__/BottomSheetFocusReturn.a11y.browser.spec.ts +0 -97
  1002. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +0 -415
  1003. package/src/BottomSheet/__tests__/BottomSheetNativeModal.a11y.browser.spec.ts +0 -462
  1004. package/src/BottomSheet/__tests__/BottomSheetPinchZoom.a11y.chromium.spec.ts +0 -247
  1005. package/src/BottomSheet/__tests__/BottomSheetToastOutlet.a11y.chromium.spec.ts +0 -83
  1006. package/src/BottomSheet/snapOffsets.test.ts +0 -254
  1007. package/src/BottomSheet/useSheetGestures.test.ts +0 -1638
  1008. package/src/Breadcrumbs/BreadcrumbItem.spec.md +0 -269
  1009. package/src/Breadcrumbs/Breadcrumbs.spec.md +0 -165
  1010. package/src/Breadcrumbs/Breadcrumbs.test.tsx +0 -921
  1011. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +0 -120
  1012. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.renders.tsx +0 -45
  1013. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.states.ts +0 -81
  1014. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.test.tsx +0 -125
  1015. package/src/Button/Button.spec.md +0 -322
  1016. package/src/Button/Button.test.tsx +0 -864
  1017. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +0 -355
  1018. package/src/Button/__tests__/Button.a11y.harnesses.tsx +0 -47
  1019. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -25
  1020. package/src/Button/__tests__/Button.a11y.renders.tsx +0 -139
  1021. package/src/Button/__tests__/Button.a11y.states.ts +0 -317
  1022. package/src/Button/__tests__/Button.a11y.test.tsx +0 -187
  1023. package/src/ButtonGroup/ButtonGroup.spec.md +0 -220
  1024. package/src/ButtonGroup/ButtonGroup.test.tsx +0 -1025
  1025. package/src/Calendar/Calendar.spec.md +0 -209
  1026. package/src/Calendar/Calendar.test.tsx +0 -2186
  1027. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +0 -127
  1028. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +0 -12
  1029. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +0 -57
  1030. package/src/Calendar/__tests__/Calendar.a11y.states.ts +0 -139
  1031. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +0 -137
  1032. package/src/Calendar/dayCellUtils.test.ts +0 -520
  1033. package/src/Calendar/getInitialFocusDate.test.ts +0 -94
  1034. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +0 -54
  1035. package/src/Calendar/hooks/useCalendarConstraints.test.ts +0 -153
  1036. package/src/Calendar/hooks/useCalendarDays.test.ts +0 -265
  1037. package/src/Calendar/hooks/useCalendarNavigation.test.ts +0 -252
  1038. package/src/Card/Card.spec.md +0 -205
  1039. package/src/Card/Card.test.tsx +0 -202
  1040. package/src/Carousel/Carousel.spec.md +0 -146
  1041. package/src/Carousel/Carousel.test.tsx +0 -915
  1042. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +0 -193
  1043. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +0 -27
  1044. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +0 -39
  1045. package/src/Carousel/__tests__/Carousel.a11y.states.ts +0 -139
  1046. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +0 -127
  1047. package/src/Center/Center.spec.md +0 -205
  1048. package/src/Center/Center.test.tsx +0 -406
  1049. package/src/Chat/ChatComposer.spec.md +0 -230
  1050. package/src/Chat/ChatComposer.test.tsx +0 -364
  1051. package/src/Chat/ChatComposerDrawer.spec.md +0 -267
  1052. package/src/Chat/ChatComposerDrawer.test.tsx +0 -130
  1053. package/src/Chat/ChatComposerInput.spec.md +0 -224
  1054. package/src/Chat/ChatComposerInput.test.tsx +0 -2136
  1055. package/src/Chat/ChatComposerTokenElement.spec.md +0 -152
  1056. package/src/Chat/ChatComposerTokenElement.test.tsx +0 -85
  1057. package/src/Chat/ChatDictationButton.spec.md +0 -160
  1058. package/src/Chat/ChatDictationButton.test.tsx +0 -108
  1059. package/src/Chat/ChatLayout.spec.md +0 -271
  1060. package/src/Chat/ChatLayout.test.tsx +0 -311
  1061. package/src/Chat/ChatLayoutScrollButton.spec.md +0 -338
  1062. package/src/Chat/ChatLayoutScrollButton.test.tsx +0 -99
  1063. package/src/Chat/ChatMessage.spec.md +0 -265
  1064. package/src/Chat/ChatMessage.test.tsx +0 -344
  1065. package/src/Chat/ChatMessageBubble.spec.md +0 -263
  1066. package/src/Chat/ChatMessageBubble.test.tsx +0 -294
  1067. package/src/Chat/ChatMessageList.spec.md +0 -222
  1068. package/src/Chat/ChatMessageList.test.tsx +0 -198
  1069. package/src/Chat/ChatMessageMetadata.spec.md +0 -220
  1070. package/src/Chat/ChatMessageMetadata.test.tsx +0 -66
  1071. package/src/Chat/ChatPastedTextToken.test.tsx +0 -71
  1072. package/src/Chat/ChatSendButton.spec.md +0 -227
  1073. package/src/Chat/ChatSendButton.test.tsx +0 -309
  1074. package/src/Chat/ChatSystemMessage.spec.md +0 -224
  1075. package/src/Chat/ChatSystemMessage.test.tsx +0 -189
  1076. package/src/Chat/ChatTokenizedText.spec.md +0 -190
  1077. package/src/Chat/ChatTokenizedText.test.tsx +0 -163
  1078. package/src/Chat/ChatToolCalls.spec.md +0 -194
  1079. package/src/Chat/ChatToolCalls.test.tsx +0 -413
  1080. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +0 -577
  1081. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
  1082. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +0 -457
  1083. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +0 -91
  1084. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +0 -91
  1085. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +0 -465
  1086. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +0 -416
  1087. package/src/Chat/__tests__/ChatSendButton.a11y.chromium.spec.ts +0 -1523
  1088. package/src/Chat/__tests__/ChatSystemMessage.a11y.chromium.spec.ts +0 -1004
  1089. package/src/Chat/__tests__/ChatTokenizedText.a11y.chromium.spec.ts +0 -574
  1090. package/src/Chat/__tests__/useChatStreamScroll.a11y.chromium.spec.ts +0 -263
  1091. package/src/Chat/__tests__/useChatStreamScroll.fixture.tsx +0 -61
  1092. package/src/Chat/useChatDictation.test.tsx +0 -369
  1093. package/src/Chat/useChatNewMessages.test.tsx +0 -209
  1094. package/src/Chat/useChatPasteAsToken.test.ts +0 -191
  1095. package/src/Chat/useChatStreamScroll.test.tsx +0 -381
  1096. package/src/Chat/useTriggerMenu.test.tsx +0 -994
  1097. package/src/CheckboxInput/CheckboxInput.spec.md +0 -218
  1098. package/src/CheckboxInput/CheckboxInput.test.tsx +0 -926
  1099. package/src/CheckboxInput/__tests__/Checkbox.a11y.chromium.spec.ts +0 -354
  1100. package/src/CheckboxInput/__tests__/Checkbox.a11y.known-failures.ts +0 -54
  1101. package/src/CheckboxInput/__tests__/Checkbox.a11y.renders.tsx +0 -310
  1102. package/src/CheckboxInput/__tests__/Checkbox.a11y.states.ts +0 -404
  1103. package/src/CheckboxInput/__tests__/Checkbox.a11y.test.tsx +0 -128
  1104. package/src/CheckboxList/CheckboxList.fr10.test.tsx +0 -51
  1105. package/src/CheckboxList/CheckboxList.spec.md +0 -332
  1106. package/src/CheckboxList/CheckboxList.test.tsx +0 -1159
  1107. package/src/CheckboxList/CheckboxListItem.spec.md +0 -302
  1108. package/src/CheckboxList/__tests__/CheckboxList.a11y.chromium.spec.ts +0 -1998
  1109. package/src/Citation/Citation.spec.md +0 -179
  1110. package/src/Citation/Citation.test.tsx +0 -361
  1111. package/src/ClickableCard/ClickableCard.spec.md +0 -241
  1112. package/src/ClickableCard/ClickableCard.test.tsx +0 -286
  1113. package/src/Code/Code.spec.md +0 -170
  1114. package/src/Code/Code.test.tsx +0 -75
  1115. package/src/CodeBlock/CodeBlock.highlightStyles.test.tsx +0 -87
  1116. package/src/CodeBlock/CodeBlock.spec.md +0 -243
  1117. package/src/CodeBlock/CodeBlock.test.tsx +0 -752
  1118. package/src/CodeBlock/CodeLanguage.test.tsx +0 -173
  1119. package/src/CodeBlock/__tests__/CodeBlockCopyClearance.a11y.chromium.spec.ts +0 -290
  1120. package/src/CodeBlock/__tests__/CodeBlockLanguages.a11y.chromium.spec.ts +0 -64
  1121. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +0 -126
  1122. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +0 -94
  1123. package/src/CodeBlock/highlightRanges.test.ts +0 -253
  1124. package/src/CodeBlock/languages.test.ts +0 -402
  1125. package/src/CodeBlock/tokenizer.test.ts +0 -280
  1126. package/src/CodeBlock/tokenizerBudget.test.ts +0 -112
  1127. package/src/Collapsible/Collapsible.spec.md +0 -245
  1128. package/src/Collapsible/Collapsible.test.tsx +0 -620
  1129. package/src/Collapsible/CollapsibleGroup.spec.md +0 -201
  1130. package/src/Collapsible/CollapsibleGroup.test.tsx +0 -656
  1131. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +0 -183
  1132. package/src/Collapsible/__tests__/Collapsible.a11y.states.ts +0 -117
  1133. package/src/Collapsible/__tests__/Collapsible.a11y.test.tsx +0 -140
  1134. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.chromium.spec.ts +0 -559
  1135. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +0 -45
  1136. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +0 -139
  1137. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +0 -135
  1138. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +0 -129
  1139. package/src/Collapsible/useCollapsible.test.tsx +0 -236
  1140. package/src/CommandPalette/CommandPalette.perf.test.ts +0 -100
  1141. package/src/CommandPalette/CommandPalette.spec.md +0 -278
  1142. package/src/CommandPalette/CommandPalette.test.tsx +0 -750
  1143. package/src/CommandPalette/CommandPaletteEmpty.spec.md +0 -190
  1144. package/src/CommandPalette/CommandPaletteEmpty.test.tsx +0 -101
  1145. package/src/CommandPalette/CommandPaletteFooter.test.tsx +0 -122
  1146. package/src/CommandPalette/CommandPaletteGroup.test.tsx +0 -129
  1147. package/src/CommandPalette/CommandPaletteInput.spec.md +0 -215
  1148. package/src/CommandPalette/CommandPaletteInput.test.tsx +0 -539
  1149. package/src/CommandPalette/CommandPaletteItem.spec.md +0 -223
  1150. package/src/CommandPalette/CommandPaletteItem.test.tsx +0 -417
  1151. package/src/CommandPalette/CommandPaletteList.spec.md +0 -194
  1152. package/src/CommandPalette/CommandPaletteList.test.tsx +0 -184
  1153. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +0 -600
  1154. package/src/CommandPalette/__tests__/CommandPaletteEmptyAudit.a11y.chromium.spec.ts +0 -715
  1155. package/src/CommandPalette/__tests__/CommandPaletteFooter.a11y.chromium.spec.ts +0 -582
  1156. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +0 -336
  1157. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +0 -523
  1158. package/src/ComplexSelector/ComplexSelector.focusableDisabled.test.tsx +0 -76
  1159. package/src/ComplexSelector/ComplexSelector.fr10.test.tsx +0 -262
  1160. package/src/ComplexSelector/ComplexSelector.spec.md +0 -266
  1161. package/src/ComplexSelector/ComplexSelector.test.tsx +0 -1023
  1162. package/src/ComplexSelector/__tests__/ComplexSelector.a11y.chromium.spec.ts +0 -965
  1163. package/src/ContextMenu/ContextMenu.spec.md +0 -251
  1164. package/src/ContextMenu/ContextMenu.test.tsx +0 -1582
  1165. package/src/ContextMenu/ContextMenu.triggerProps.test.tsx +0 -54
  1166. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +0 -439
  1167. package/src/ContextMenu/__tests__/ContextMenuCursorAnchor.a11y.browser.spec.ts +0 -154
  1168. package/src/ContextMenu/__tests__/ContextMenuLongPress.a11y.chromium.spec.ts +0 -117
  1169. package/src/DateInput/DateInput.fr10.test.tsx +0 -283
  1170. package/src/DateInput/DateInput.spec.md +0 -319
  1171. package/src/DateInput/DateInput.test.tsx +0 -1459
  1172. package/src/DateInput/DateInputTouch.test.tsx +0 -2828
  1173. package/src/DateInput/NativeDateField.test.tsx +0 -741
  1174. package/src/DateInput/Presentation.test.tsx +0 -121
  1175. package/src/DateInput/__tests__/DateInputAudit.a11y.chromium.spec.ts +0 -577
  1176. package/src/DateInput/__tests__/WheelPinchZoom.a11y.chromium.spec.ts +0 -171
  1177. package/src/DateRangeInput/DateRangeInput.fr10.test.tsx +0 -165
  1178. package/src/DateRangeInput/DateRangeInput.spec.md +0 -203
  1179. package/src/DateRangeInput/DateRangeInput.test.tsx +0 -1108
  1180. package/src/DateTimeInput/DateTimeInput.fr10.test.tsx +0 -319
  1181. package/src/DateTimeInput/DateTimeInput.spec.md +0 -246
  1182. package/src/DateTimeInput/DateTimeInput.test.tsx +0 -2575
  1183. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +0 -1071
  1184. package/src/DateTimeInput/NativePickerSegments.test.tsx +0 -657
  1185. package/src/DateTimeInput/Presentation.test.tsx +0 -89
  1186. package/src/Dialog/Dialog.source-build.test.mjs +0 -43
  1187. package/src/Dialog/Dialog.spec.md +0 -189
  1188. package/src/Dialog/Dialog.test.tsx +0 -1385
  1189. package/src/Dialog/DialogHeader.test.tsx +0 -477
  1190. package/src/Dialog/__tests__/Dialog.a11y.browser.spec.ts +0 -111
  1191. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +0 -218
  1192. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -28
  1193. package/src/Dialog/__tests__/Dialog.a11y.renders.tsx +0 -107
  1194. package/src/Dialog/__tests__/Dialog.a11y.states.ts +0 -104
  1195. package/src/Dialog/__tests__/Dialog.a11y.test.tsx +0 -102
  1196. package/src/Dialog/__tests__/Dialog.reduced-motion.a11y.browser.spec.ts +0 -252
  1197. package/src/Dialog/modules/DialogHeader.spec.md +0 -211
  1198. package/src/Dialog/useImperativeDialog.test.tsx +0 -78
  1199. package/src/Divider/Divider.spec.md +0 -252
  1200. package/src/Divider/Divider.test.tsx +0 -155
  1201. package/src/Drawer/Drawer.spec.md +0 -357
  1202. package/src/Drawer/Drawer.test.tsx +0 -1300
  1203. package/src/Drawer/DrawerDirection.test.tsx +0 -127
  1204. package/src/Drawer/DrawerHeader.test.tsx +0 -102
  1205. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +0 -402
  1206. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +0 -298
  1207. package/src/DropdownMenu/DropdownMenu.spec.md +0 -582
  1208. package/src/DropdownMenu/DropdownMenu.ssr.test.tsx +0 -93
  1209. package/src/DropdownMenu/DropdownMenu.test.tsx +0 -4340
  1210. package/src/DropdownMenu/DropdownMenuContext.public.test.ts +0 -22
  1211. package/src/DropdownMenu/DropdownMenuDivider.spec.md +0 -192
  1212. package/src/DropdownMenu/DropdownMenuItem.spec.md +0 -209
  1213. package/src/DropdownMenu/DropdownMenuLazySheet.test.tsx +0 -126
  1214. package/src/DropdownMenu/DropdownMenuRadioGroup.spec.md +0 -190
  1215. package/src/DropdownMenu/DropdownMenuRadioItem.spec.md +0 -226
  1216. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +0 -635
  1217. package/src/DropdownMenu/DropdownMenuSheetFirstOpen.test.tsx +0 -72
  1218. package/src/DropdownMenu/DropdownMenuSubMenu.spec.md +0 -225
  1219. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +0 -2408
  1220. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +0 -281
  1221. package/src/DropdownMenu/__tests__/DropdownMenuDrillIn.a11y.chromium.spec.ts +0 -175
  1222. package/src/DropdownMenu/__tests__/DropdownMenuFocusReturn.a11y.browser.spec.ts +0 -74
  1223. package/src/DropdownMenu/__tests__/DropdownMenuGroupAudit.a11y.chromium.spec.ts +0 -335
  1224. package/src/DropdownMenu/__tests__/DropdownMenuItemAudit.a11y.chromium.spec.ts +0 -367
  1225. package/src/DropdownMenu/__tests__/DropdownMenuRadioGroupAudit.a11y.chromium.spec.ts +0 -328
  1226. package/src/DropdownMenu/__tests__/DropdownMenuRadioItemAudit.a11y.chromium.spec.ts +0 -540
  1227. package/src/DropdownMenu/__tests__/DropdownMenuSubMenuAudit.a11y.chromium.spec.ts +0 -671
  1228. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +0 -146
  1229. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +0 -26
  1230. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +0 -65
  1231. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +0 -138
  1232. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +0 -136
  1233. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +0 -137
  1234. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +0 -12
  1235. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +0 -67
  1236. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +0 -141
  1237. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +0 -138
  1238. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +0 -231
  1239. package/src/DropdownMenu/__tests__/MenuHoverFocus.a11y.chromium.spec.ts +0 -238
  1240. package/src/DropdownMenu/__tests__/MenuPinchZoom.a11y.chromium.spec.ts +0 -173
  1241. package/src/DropdownMenu/__tests__/MenuPress.a11y.chromium.spec.ts +0 -503
  1242. package/src/DropdownMenu/__tests__/SubMenuHeaderFooter.a11y.chromium.spec.ts +0 -268
  1243. package/src/DropdownMenu/__tests__/warmMenuBottomSheet.tsx +0 -43
  1244. package/src/DropdownMenu/modules/useMenuHover.spec.md +0 -283
  1245. package/src/DropdownMenu/modules/useMenuPress.spec.md +0 -348
  1246. package/src/EmptyState/EmptyState.announce.test.tsx +0 -227
  1247. package/src/EmptyState/EmptyState.spec.md +0 -197
  1248. package/src/EmptyState/EmptyState.test.tsx +0 -284
  1249. package/src/Field/Field.spec.md +0 -226
  1250. package/src/Field/Field.test.tsx +0 -800
  1251. package/src/Field/FieldLabel.spec.md +0 -183
  1252. package/src/Field/FieldLabel.test.tsx +0 -244
  1253. package/src/Field/InputClearButton.test.tsx +0 -395
  1254. package/src/Field/__tests__/FieldLabelAudit.a11y.chromium.spec.ts +0 -297
  1255. package/src/Field/fieldInputFamily.test.tsx +0 -160
  1256. package/src/FieldStatus/FieldStatus.spec.md +0 -224
  1257. package/src/FieldStatus/FieldStatus.test.tsx +0 -482
  1258. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +0 -206
  1259. package/src/FieldStatus/__tests__/StatusMessage.a11y.known-failures.ts +0 -13
  1260. package/src/FieldStatus/__tests__/StatusMessage.a11y.renders.tsx +0 -287
  1261. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +0 -302
  1262. package/src/FieldStatus/__tests__/StatusMessage.a11y.test.tsx +0 -154
  1263. package/src/FileInput/FileInput.fr10.test.tsx +0 -21
  1264. package/src/FileInput/FileInput.spec.md +0 -199
  1265. package/src/FileInput/FileInput.test.tsx +0 -1270
  1266. package/src/FormLayout/FormLayout.spec.md +0 -210
  1267. package/src/FormLayout/FormLayout.test.tsx +0 -444
  1268. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +0 -46
  1269. package/src/Grid/Grid.spec.md +0 -193
  1270. package/src/Grid/Grid.test.tsx +0 -509
  1271. package/src/Grid/GridSpan.spec.md +0 -158
  1272. package/src/HStack/HStack.test.tsx +0 -129
  1273. package/src/Heading/Heading.spec.md +0 -198
  1274. package/src/Heading/Heading.test.tsx +0 -328
  1275. package/src/HoverCard/HoverCard.spec.md +0 -244
  1276. package/src/HoverCard/HoverCard.test.tsx +0 -1339
  1277. package/src/Icon/Icon.spec.md +0 -244
  1278. package/src/Icon/Icon.test.tsx +0 -990
  1279. package/src/Icon/globalIconRegistry.test.tsx +0 -613
  1280. package/src/IconButton/IconButton.spec.md +0 -197
  1281. package/src/IconButton/IconButton.test.tsx +0 -156
  1282. package/src/Indicator/CheckIndicator.spec.md +0 -159
  1283. package/src/Indicator/CheckboxIndicator.spec.md +0 -161
  1284. package/src/Indicator/Indicator.test.tsx +0 -389
  1285. package/src/Indicator/indicatorRegistry.test.tsx +0 -106
  1286. package/src/InputGroup/InputGroup.spec.md +0 -199
  1287. package/src/InputGroup/InputGroup.test.tsx +0 -502
  1288. package/src/InputGroup/InputGroupText.spec.md +0 -139
  1289. package/src/InteractiveRoleContext/InteractiveRoleContext.test.tsx +0 -185
  1290. package/src/Item/Item.source-build.test.mjs +0 -88
  1291. package/src/Item/Item.spec.md +0 -226
  1292. package/src/Item/Item.swipeLayer.test.tsx +0 -74
  1293. package/src/Item/Item.test.tsx +0 -1455
  1294. package/src/Item/ItemMarkerColumns.a11y.chromium.spec.ts +0 -211
  1295. package/src/Item/__tests__/ItemSwipeActions.a11y.chromium.spec.ts +0 -337
  1296. package/src/Item/__tests__/ItemSwipePinchZoom.a11y.chromium.spec.ts +0 -186
  1297. package/src/Kbd/Kbd.spec.md +0 -142
  1298. package/src/Kbd/Kbd.test.tsx +0 -299
  1299. package/src/Layer/LayerProvider.spec.md +0 -149
  1300. package/src/Layer/LayerProvider.test.tsx +0 -143
  1301. package/src/Layer/__tests__/LayerReducedMotion.a11y.chromium.spec.ts +0 -176
  1302. package/src/Layer/__tests__/TouchTrigger.a11y.chromium.spec.ts +0 -200
  1303. package/src/Layer/anchorName.test.ts +0 -146
  1304. package/src/Layer/layerDismissalFamilies.test.tsx +0 -487
  1305. package/src/Layer/layerDismissalInvariants.test.tsx +0 -501
  1306. package/src/Layer/layerHost.test.ts +0 -117
  1307. package/src/Layer/layerScopedContext.test.tsx +0 -499
  1308. package/src/Layer/layerViewportInset.source-build.test.mjs +0 -90
  1309. package/src/Layer/layerViewportInset.test.ts +0 -120
  1310. package/src/Layer/useLayer.test.tsx +0 -2423
  1311. package/src/Layer/useLayerDismissal.test.tsx +0 -580
  1312. package/src/Layer/useTouchTrigger.test.ts +0 -70
  1313. package/src/Layout/Layout.spec.md +0 -247
  1314. package/src/Layout/Layout.test.tsx +0 -375
  1315. package/src/Layout/LayoutSlots.test.tsx +0 -376
  1316. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +0 -122
  1317. package/src/Layout/__tests__/Layout.a11y.renders.tsx +0 -110
  1318. package/src/Layout/__tests__/Layout.a11y.states.ts +0 -93
  1319. package/src/Layout/__tests__/Layout.a11y.test.tsx +0 -142
  1320. package/src/Layout/__tests__/childrenAsContent.test.tsx +0 -59
  1321. package/src/Layout/__tests__/contentWidth.test.tsx +0 -303
  1322. package/src/Layout/__tests__/edgeCompensation.test.tsx +0 -191
  1323. package/src/Layout/__tests__/nestedPadding.test.tsx +0 -264
  1324. package/src/Layout/overlayPaddingReset.test.tsx +0 -224
  1325. package/src/Lightbox/Lightbox.spec.md +0 -199
  1326. package/src/Lightbox/Lightbox.test.tsx +0 -819
  1327. package/src/Lightbox/LightboxKeyboard.a11y.browser.spec.ts +0 -78
  1328. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +0 -70
  1329. package/src/Lightbox/useLightbox.test.tsx +0 -263
  1330. package/src/Link/Link.spec.md +0 -229
  1331. package/src/Link/Link.test.tsx +0 -457
  1332. package/src/Link/LinkProvider.spec.md +0 -167
  1333. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +0 -143
  1334. package/src/Link/__tests__/Link.a11y.renders.tsx +0 -97
  1335. package/src/Link/__tests__/Link.a11y.states.ts +0 -104
  1336. package/src/Link/__tests__/Link.a11y.test.tsx +0 -127
  1337. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +0 -262
  1338. package/src/Link/useLinkComponent.test.tsx +0 -572
  1339. package/src/Link/useLinkify.test.tsx +0 -146
  1340. package/src/List/List.spec.md +0 -336
  1341. package/src/List/List.test.tsx +0 -1052
  1342. package/src/List/ListItem.source-build.test.mjs +0 -41
  1343. package/src/List/ListMarkers.a11y.chromium.spec.ts +0 -904
  1344. package/src/Markdown/Markdown.codeLanguages.test.tsx +0 -137
  1345. package/src/Markdown/Markdown.fr23.bench.ts +0 -176
  1346. package/src/Markdown/Markdown.fr23.perf.test.ts +0 -484
  1347. package/src/Markdown/Markdown.helpers.perf.test.ts +0 -495
  1348. package/src/Markdown/Markdown.public.test.ts +0 -309
  1349. package/src/Markdown/Markdown.renderBoundary.test.tsx +0 -72
  1350. package/src/Markdown/Markdown.rerender.test.tsx +0 -143
  1351. package/src/Markdown/Markdown.spec.md +0 -595
  1352. package/src/Markdown/Markdown.test.tsx +0 -1383
  1353. package/src/Markdown/MarkdownTable.a11y.chromium.spec.ts +0 -727
  1354. package/src/Markdown/__tests__/MarkdownNesting.a11y.chromium.spec.ts +0 -102
  1355. package/src/Markdown/angleDestinations.test.tsx +0 -104
  1356. package/src/Markdown/ast.test.ts +0 -374
  1357. package/src/Markdown/atxHeadingIndentation.test.tsx +0 -53
  1358. package/src/Markdown/blockNesting.test.tsx +0 -124
  1359. package/src/Markdown/blockquoteMarkers.test.tsx +0 -69
  1360. package/src/Markdown/characterReferences.test.ts +0 -47
  1361. package/src/Markdown/codeSpans.test.tsx +0 -65
  1362. package/src/Markdown/emphasis.test.tsx +0 -214
  1363. package/src/Markdown/fenceInfo.test.tsx +0 -47
  1364. package/src/Markdown/imageAlt.test.tsx +0 -84
  1365. package/src/Markdown/incremental.test.ts +0 -1198
  1366. package/src/Markdown/incrementalIndentation.test.tsx +0 -36
  1367. package/src/Markdown/indentedFences.test.tsx +0 -374
  1368. package/src/Markdown/indentedTables.test.tsx +0 -53
  1369. package/src/Markdown/linkBrackets.test.tsx +0 -116
  1370. package/src/Markdown/linkDestinationLinear.test.tsx +0 -54
  1371. package/src/Markdown/linkDestinations.test.tsx +0 -127
  1372. package/src/Markdown/linkTextCode.test.tsx +0 -65
  1373. package/src/Markdown/listBullets.test.tsx +0 -55
  1374. package/src/Markdown/listContinuation.test.tsx +0 -94
  1375. package/src/Markdown/listThematicBreak.test.tsx +0 -49
  1376. package/src/Markdown/longLinkLabels.test.tsx +0 -49
  1377. package/src/Markdown/modules/headingLinks.spec.md +0 -262
  1378. package/src/Markdown/modules/remark.spec.md +0 -269
  1379. package/src/Markdown/modules/softBreaks.spec.md +0 -137
  1380. package/src/Markdown/modules/sourceLines.spec.md +0 -162
  1381. package/src/Markdown/openFenceFinalNewline.test.tsx +0 -85
  1382. package/src/Markdown/parser.perf.test.ts +0 -398
  1383. package/src/Markdown/parser.public.test.ts +0 -144
  1384. package/src/Markdown/parser.test.ts +0 -1925
  1385. package/src/Markdown/plugin-renderer/MarkdownPluginNodeRenderer.test.tsx +0 -300
  1386. package/src/Markdown/plugins/frontmatter.test.tsx +0 -150
  1387. package/src/Markdown/plugins/headingLinks.a11y.chromium.spec.ts +0 -856
  1388. package/src/Markdown/plugins/headingLinks.ssr.test.tsx +0 -143
  1389. package/src/Markdown/plugins/headingLinks.test.tsx +0 -637
  1390. package/src/Markdown/plugins/protocol.test.tsx +0 -1083
  1391. package/src/Markdown/plugins/semanticFence.test.tsx +0 -655
  1392. package/src/Markdown/plugins/softBreaks.test.tsx +0 -172
  1393. package/src/Markdown/plugins/sourceDecoration.test.tsx +0 -799
  1394. package/src/Markdown/plugins/sourceLines.test.tsx +0 -715
  1395. package/src/Markdown/plugins/textTransform.test.ts +0 -467
  1396. package/src/Markdown/remark.test.tsx +0 -1877
  1397. package/src/Markdown/streaming.test.ts +0 -148
  1398. package/src/Markdown/streamingListShapes.test.tsx +0 -93
  1399. package/src/Markdown/trailingSpaces.test.tsx +0 -45
  1400. package/src/MetadataList/MetadataList.test.tsx +0 -383
  1401. package/src/MobileNav/MobileNav.spec.md +0 -306
  1402. package/src/MobileNav/MobileNav.test.tsx +0 -281
  1403. package/src/MobileNav/MobileNavCloseEdgeCases.test.tsx +0 -584
  1404. package/src/MobileNav/MobileNavCloseTiming.test.ts +0 -58
  1405. package/src/MobileNav/MobileNavCloseVisibility.test.tsx +0 -172
  1406. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +0 -212
  1407. package/src/MobileNav/MobileNavFocus.test.tsx +0 -553
  1408. package/src/MobileNav/MobileNavPinchZoom.a11y.chromium.spec.ts +0 -147
  1409. package/src/MobileNav/MobileNavReopen.test.tsx +0 -118
  1410. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +0 -109
  1411. package/src/MobileNav/MobileNavToggle.test.tsx +0 -179
  1412. package/src/MobileNav/__tests__/MobileNavWidth.a11y.chromium.spec.ts +0 -103
  1413. package/src/MoreMenu/MoreMenu.spec.md +0 -268
  1414. package/src/MoreMenu/MoreMenu.test.tsx +0 -356
  1415. package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +0 -149
  1416. package/src/MultiSelector/MultiSelector.disabledFocus.a11y.chromium.spec.ts +0 -72
  1417. package/src/MultiSelector/MultiSelector.focusableDisabled.test.tsx +0 -121
  1418. package/src/MultiSelector/MultiSelector.fr10.test.tsx +0 -221
  1419. package/src/MultiSelector/MultiSelector.spec.md +0 -354
  1420. package/src/MultiSelector/MultiSelector.test.tsx +0 -4702
  1421. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +0 -310
  1422. package/src/MultiSelector/__tests__/MultiSelector.a11y.chromium.spec.ts +0 -46
  1423. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +0 -128
  1424. package/src/NavIcon/NavIcon.spec.md +0 -158
  1425. package/src/NavIcon/NavIcon.test.tsx +0 -41
  1426. package/src/NavMenu/NavHeadingMenu.spec.md +0 -268
  1427. package/src/NavMenu/NavHeadingMenu.test.tsx +0 -714
  1428. package/src/NavMenu/__tests__/NavMenusSlideSelection.a11y.chromium.spec.ts +0 -137
  1429. package/src/NumberInput/NumberInput.fr10.test.tsx +0 -21
  1430. package/src/NumberInput/NumberInput.spec.md +0 -248
  1431. package/src/NumberInput/NumberInput.test.tsx +0 -2636
  1432. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +0 -124
  1433. package/src/NumberInput/__tests__/NumberInput.a11y.known-failures.ts +0 -26
  1434. package/src/NumberInput/__tests__/NumberInput.a11y.renders.tsx +0 -77
  1435. package/src/NumberInput/__tests__/NumberInput.a11y.states.ts +0 -116
  1436. package/src/NumberInput/__tests__/NumberInput.a11y.test.tsx +0 -154
  1437. package/src/NumberInput/numberInputCommit.test.ts +0 -72
  1438. package/src/NumberInput/numberParser.docblock.test.ts +0 -234
  1439. package/src/NumberInput/numberParser.test.ts +0 -222
  1440. package/src/Outline/Outline.spec.md +0 -162
  1441. package/src/Outline/Outline.test.tsx +0 -1069
  1442. package/src/Outline/OutlineFocus.a11y.chromium.spec.ts +0 -551
  1443. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +0 -151
  1444. package/src/Outline/parseOutlineFromMarkdown.test.ts +0 -104
  1445. package/src/OverflowList/OverflowList.spec.md +0 -157
  1446. package/src/OverflowList/OverflowList.test.tsx +0 -1073
  1447. package/src/Overlay/Overlay.spec.md +0 -304
  1448. package/src/Overlay/Overlay.test.tsx +0 -424
  1449. package/src/Pagination/Pagination.spec.md +0 -226
  1450. package/src/Pagination/Pagination.test.tsx +0 -1754
  1451. package/src/Popover/Popover.spec.md +0 -525
  1452. package/src/Popover/Popover.test.tsx +0 -1424
  1453. package/src/Popover/PopoverPresentation.test.tsx +0 -952
  1454. package/src/Popover/__tests__/PopoverSecondDocument.a11y.chromium.spec.ts +0 -205
  1455. package/src/PowerSearch/PowerSearch.spec.md +0 -661
  1456. package/src/PowerSearch/PowerSearch.test.tsx +0 -1003
  1457. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +0 -1190
  1458. package/src/PowerSearch/PowerSearchFilterEditor.test.tsx +0 -377
  1459. package/src/PowerSearch/PowerSearchToken.test.tsx +0 -230
  1460. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +0 -754
  1461. package/src/PowerSearch/formatFilterValue.test.ts +0 -339
  1462. package/src/PowerSearch/resolveDateTimeRangePart.test.ts +0 -40
  1463. package/src/PowerSearch/usePowerSearchConfig.test.ts +0 -832
  1464. package/src/PowerSearch/usePowerSearchSource.test.ts +0 -621
  1465. package/src/ProgressBar/ProgressBar.spec.md +0 -261
  1466. package/src/ProgressBar/ProgressBar.test.tsx +0 -852
  1467. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +0 -115
  1468. package/src/ProgressBar/__tests__/ProgressBar.a11y.known-failures.ts +0 -12
  1469. package/src/ProgressBar/__tests__/ProgressBar.a11y.renders.tsx +0 -38
  1470. package/src/ProgressBar/__tests__/ProgressBar.a11y.states.ts +0 -123
  1471. package/src/ProgressBar/__tests__/ProgressBar.a11y.test.tsx +0 -107
  1472. package/src/RadioList/RadioList.fr10.test.tsx +0 -67
  1473. package/src/RadioList/RadioList.spec.md +0 -227
  1474. package/src/RadioList/RadioList.test.tsx +0 -967
  1475. package/src/RadioList/RadioListItem.press.source-build.test.mjs +0 -80
  1476. package/src/RadioList/RadioListItem.spec.md +0 -223
  1477. package/src/RadioList/__tests__/RadioGroup.a11y.chromium.spec.ts +0 -274
  1478. package/src/RadioList/__tests__/RadioGroup.a11y.known-failures.ts +0 -12
  1479. package/src/RadioList/__tests__/RadioGroup.a11y.renders.tsx +0 -232
  1480. package/src/RadioList/__tests__/RadioGroup.a11y.states.ts +0 -503
  1481. package/src/RadioList/__tests__/RadioGroup.a11y.test.tsx +0 -217
  1482. package/src/Resizable/Resizable.spec.md +0 -278
  1483. package/src/Resizable/ResizeHandle.test.tsx +0 -1067
  1484. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +0 -120
  1485. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +0 -39
  1486. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +0 -80
  1487. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +0 -197
  1488. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +0 -123
  1489. package/src/Resizable/shippedDirections.test.ts +0 -462
  1490. package/src/Resizable/useResizable.test.ts +0 -1682
  1491. package/src/Resizable/utils.test.ts +0 -74
  1492. package/src/ScrollableArea/ScrollableArea.spec.md +0 -190
  1493. package/src/ScrollableArea/ScrollableArea.test.tsx +0 -304
  1494. package/src/ScrollableArea/modules/useScrollableArea.spec.md +0 -149
  1495. package/src/Section/Section.spec.md +0 -197
  1496. package/src/Section/Section.test.tsx +0 -376
  1497. package/src/SegmentedControl/SegmentedControl.spec.md +0 -261
  1498. package/src/SegmentedControl/SegmentedControl.test.tsx +0 -897
  1499. package/src/SegmentedControl/SegmentedControlItem.spec.md +0 -206
  1500. package/src/SelectableCard/SelectableCard.spec.md +0 -230
  1501. package/src/SelectableCard/SelectableCard.test.tsx +0 -216
  1502. package/src/Selector/Selector.focusableDisabled.test.tsx +0 -127
  1503. package/src/Selector/Selector.fr10.test.tsx +0 -149
  1504. package/src/Selector/Selector.sheetActiveDescendant.test.tsx +0 -95
  1505. package/src/Selector/Selector.source-build.test.mjs +0 -79
  1506. package/src/Selector/Selector.spec.md +0 -379
  1507. package/src/Selector/Selector.test.tsx +0 -5028
  1508. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +0 -27
  1509. package/src/Selector/__tests__/Combobox.a11y.states.ts +0 -145
  1510. package/src/Selector/__tests__/ComboboxField.a11y.chromium.spec.ts +0 -458
  1511. package/src/Selector/__tests__/EmptyStateAnnouncement.a11y.chromium.spec.ts +0 -91
  1512. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +0 -131
  1513. package/src/Selector/__tests__/Listbox.a11y.dom.ts +0 -38
  1514. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +0 -63
  1515. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +0 -12
  1516. package/src/Selector/__tests__/Listbox.a11y.states.ts +0 -232
  1517. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +0 -46
  1518. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +0 -109
  1519. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +0 -126
  1520. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +0 -121
  1521. package/src/Selector/useCombobox.public.test.tsx +0 -89
  1522. package/src/Selector/useCombobox.test.tsx +0 -444
  1523. package/src/SideNav/SideNav.spec.md +0 -247
  1524. package/src/SideNav/SideNav.test.tsx +0 -4128
  1525. package/src/SideNav/SideNavCollapseButton.spec.md +0 -161
  1526. package/src/SideNav/SideNavHeading.spec.md +0 -226
  1527. package/src/SideNav/SideNavItem.spec.md +0 -246
  1528. package/src/SideNav/SideNavRailMotion.test.tsx +0 -620
  1529. package/src/SideNav/SideNavSection.spec.md +0 -153
  1530. package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +0 -417
  1531. package/src/SizeContext/SizeContext.test.tsx +0 -226
  1532. package/src/Skeleton/Skeleton.spec.md +0 -184
  1533. package/src/Skeleton/Skeleton.test.tsx +0 -148
  1534. package/src/Slider/Slider.fr10.test.tsx +0 -93
  1535. package/src/Slider/Slider.spec.md +0 -285
  1536. package/src/Slider/Slider.test.tsx +0 -1630
  1537. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +0 -113
  1538. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +0 -12
  1539. package/src/Slider/__tests__/Slider.a11y.renders.tsx +0 -42
  1540. package/src/Slider/__tests__/Slider.a11y.states.ts +0 -106
  1541. package/src/Slider/__tests__/Slider.a11y.test.tsx +0 -105
  1542. package/src/Spinner/Spinner.spec.md +0 -231
  1543. package/src/Spinner/Spinner.test.tsx +0 -484
  1544. package/src/Stack/Stack.spec.md +0 -203
  1545. package/src/Stack/Stack.test.tsx +0 -473
  1546. package/src/Stack/StackItem.test.tsx +0 -105
  1547. package/src/StatusDot/StatusDot.spec.md +0 -224
  1548. package/src/StatusDot/StatusDot.test.tsx +0 -213
  1549. package/src/Stepper/Step.spec.md +0 -206
  1550. package/src/Stepper/Stepper.public.test.ts +0 -91
  1551. package/src/Stepper/Stepper.spec.md +0 -327
  1552. package/src/Stepper/Stepper.test.tsx +0 -2068
  1553. package/src/Switch/Switch.focusRing.source-build.test.mjs +0 -81
  1554. package/src/Switch/Switch.spec.md +0 -277
  1555. package/src/Switch/Switch.test.tsx +0 -1071
  1556. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +0 -211
  1557. package/src/Switch/__tests__/Switch.a11y.known-failures.ts +0 -28
  1558. package/src/Switch/__tests__/Switch.a11y.states.ts +0 -275
  1559. package/src/Switch/__tests__/Switch.a11y.test.tsx +0 -144
  1560. package/src/TabList/Tab.spec.md +0 -201
  1561. package/src/TabList/TabList.spec.md +0 -217
  1562. package/src/TabList/TabList.test.tsx +0 -1701
  1563. package/src/TabList/TabMenu.spec.md +0 -198
  1564. package/src/TabList/__tests__/TabListOverflow.interaction.browser.spec.ts +0 -99
  1565. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +0 -199
  1566. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +0 -12
  1567. package/src/TabList/__tests__/Tabs.a11y.renders.tsx +0 -92
  1568. package/src/TabList/__tests__/Tabs.a11y.states.ts +0 -247
  1569. package/src/TabList/__tests__/Tabs.a11y.test.tsx +0 -153
  1570. package/src/Table/Table.perf.test.tsx +0 -569
  1571. package/src/Table/Table.spec.md +0 -395
  1572. package/src/Table/Table.test.tsx +0 -1851
  1573. package/src/Table/TableHeaderCell.test.tsx +0 -291
  1574. package/src/Table/TableSelectionToolbar.test.tsx +0 -109
  1575. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +0 -129
  1576. package/src/Table/__tests__/Table.a11y.contracts.ts +0 -35
  1577. package/src/Table/__tests__/Table.a11y.renders.tsx +0 -112
  1578. package/src/Table/__tests__/Table.a11y.states.ts +0 -140
  1579. package/src/Table/__tests__/Table.a11y.test.tsx +0 -198
  1580. package/src/Table/__tests__/TableMinWidth.a11y.chromium.spec.ts +0 -75
  1581. package/src/Table/__tests__/TableRowGlide.a11y.browser.spec.ts +0 -382
  1582. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +0 -138
  1583. package/src/Table/__tests__/TableStickyDividers.a11y.browser.spec.ts +0 -560
  1584. package/src/Table/plugins/activation/useTableActivation.spec.md +0 -254
  1585. package/src/Table/plugins/columnResize/useTableColumnResize.spec.md +0 -373
  1586. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +0 -919
  1587. package/src/Table/plugins/columnSettings/useTableColumnSettings.test.tsx +0 -175
  1588. package/src/Table/plugins/columnSettings/useTableColumnSettingsState.test.tsx +0 -296
  1589. package/src/Table/plugins/filtering/useTableFilterState.test.tsx +0 -143
  1590. package/src/Table/plugins/filtering/useTableFiltering.test.tsx +0 -357
  1591. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +0 -112
  1592. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +0 -409
  1593. package/src/Table/plugins/infiniteScroll/useTableInfiniteScroll.spec.md +0 -244
  1594. package/src/Table/plugins/pagination/useTablePagination-perf.test.tsx +0 -232
  1595. package/src/Table/plugins/pagination/useTablePagination.test.tsx +0 -666
  1596. package/src/Table/plugins/rowExpansion/useTableRowExpansion.glide.test.tsx +0 -280
  1597. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +0 -681
  1598. package/src/Table/plugins/rowIndex/useTableRowIndex.test.tsx +0 -132
  1599. package/src/Table/plugins/rowStatus/useTableRowStatus.public.test.ts +0 -92
  1600. package/src/Table/plugins/rowStatus/useTableRowStatus.spec.md +0 -355
  1601. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +0 -519
  1602. package/src/Table/plugins/selection/useTableSelection-perf.test.tsx +0 -258
  1603. package/src/Table/plugins/selection/useTableSelection.test.tsx +0 -424
  1604. package/src/Table/plugins/selection/useTableSelectionState.test.tsx +0 -451
  1605. package/src/Table/plugins/sortable/__tests__/useTableSortable.a11y.test.tsx +0 -174
  1606. package/src/Table/plugins/sortable/__tests__/useTableSortable.a11y.ts +0 -86
  1607. package/src/Table/plugins/sortable/useTableSortable.test.tsx +0 -857
  1608. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +0 -592
  1609. package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +0 -334
  1610. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +0 -163
  1611. package/src/Table/plugins/tree/useTableTreeData.test.tsx +0 -992
  1612. package/src/Table/plugins/tree/useTableTreeState.test.tsx +0 -740
  1613. package/src/Table/plugins/virtualization/useTableVirtualization.spec.md +0 -266
  1614. package/src/Table/tableContextMenu.test.tsx +0 -208
  1615. package/src/Text/Text.spec.md +0 -193
  1616. package/src/Text/Text.test.tsx +0 -405
  1617. package/src/Text/useTruncation.test.tsx +0 -69
  1618. package/src/TextArea/TextArea.fr10.test.tsx +0 -21
  1619. package/src/TextArea/TextArea.spec.md +0 -303
  1620. package/src/TextArea/TextArea.test.tsx +0 -1257
  1621. package/src/TextInput/TextInput.fr10.test.tsx +0 -21
  1622. package/src/TextInput/TextInput.spec.md +0 -260
  1623. package/src/TextInput/TextInput.test.tsx +0 -1259
  1624. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +0 -214
  1625. package/src/TextInput/__tests__/TextInput.a11y.known-failures.ts +0 -40
  1626. package/src/TextInput/__tests__/TextInput.a11y.renders.tsx +0 -202
  1627. package/src/TextInput/__tests__/TextInput.a11y.states.ts +0 -384
  1628. package/src/TextInput/__tests__/TextInput.a11y.test.tsx +0 -130
  1629. package/src/Thumbnail/Thumbnail.spec.md +0 -244
  1630. package/src/Thumbnail/Thumbnail.test.tsx +0 -384
  1631. package/src/TimeInput/NativeTimeInput.test.tsx +0 -326
  1632. package/src/TimeInput/Presentation.test.tsx +0 -171
  1633. package/src/TimeInput/TimeInput.fr10.test.tsx +0 -312
  1634. package/src/TimeInput/TimeInput.spec.md +0 -273
  1635. package/src/TimeInput/TimeInput.test.tsx +0 -831
  1636. package/src/Timer/Timer.spec.md +0 -215
  1637. package/src/Timer/Timer.test.tsx +0 -302
  1638. package/src/Timestamp/Timestamp.spec.md +0 -252
  1639. package/src/Timestamp/Timestamp.test.tsx +0 -1636
  1640. package/src/Timestamp/tooltipEntries.test.ts +0 -566
  1641. package/src/Toast/Toast.spec.md +0 -300
  1642. package/src/Toast/ToastViewport.modalHost.test.tsx +0 -891
  1643. package/src/Toast/ToastViewport.test.tsx +0 -2333
  1644. package/src/Toast/useToast.test.tsx +0 -258
  1645. package/src/ToggleButton/ToggleButton.spec.md +0 -256
  1646. package/src/ToggleButton/ToggleButton.test.tsx +0 -973
  1647. package/src/ToggleButton/ToggleButtonGroup.spec.md +0 -195
  1648. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +0 -177
  1649. package/src/ToggleButton/__tests__/ToggleButton.a11y.renders.tsx +0 -133
  1650. package/src/ToggleButton/__tests__/ToggleButton.a11y.states.ts +0 -167
  1651. package/src/ToggleButton/__tests__/ToggleButton.a11y.test.tsx +0 -103
  1652. package/src/Token/Token.spec.md +0 -271
  1653. package/src/Token/Token.test.tsx +0 -640
  1654. package/src/Token/__tests__/TokenRemoveTarget.a11y.chromium.spec.ts +0 -87
  1655. package/src/Tokenizer/Tokenizer.fr10.test.tsx +0 -28
  1656. package/src/Tokenizer/Tokenizer.props-type.test.tsx +0 -126
  1657. package/src/Tokenizer/Tokenizer.spec.md +0 -601
  1658. package/src/Tokenizer/Tokenizer.ssr.test.tsx +0 -196
  1659. package/src/Tokenizer/Tokenizer.test.tsx +0 -2121
  1660. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +0 -2719
  1661. package/src/Tokenizer/__tests__/TokenizerEndLane.a11y.chromium.spec.ts +0 -505
  1662. package/src/Toolbar/Toolbar.spec.md +0 -228
  1663. package/src/Toolbar/Toolbar.test.tsx +0 -570
  1664. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +0 -164
  1665. package/src/Toolbar/__tests__/Toolbar.a11y.renders.tsx +0 -63
  1666. package/src/Toolbar/__tests__/Toolbar.a11y.states.ts +0 -142
  1667. package/src/Toolbar/__tests__/Toolbar.a11y.test.tsx +0 -126
  1668. package/src/Tooltip/Tooltip.spec.md +0 -267
  1669. package/src/Tooltip/Tooltip.test.tsx +0 -819
  1670. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +0 -130
  1671. package/src/Tooltip/__tests__/Tooltip.a11y.renders.tsx +0 -86
  1672. package/src/Tooltip/__tests__/Tooltip.a11y.states.ts +0 -132
  1673. package/src/Tooltip/__tests__/Tooltip.a11y.test.tsx +0 -126
  1674. package/src/Tooltip/__tests__/TooltipFocusReturn.a11y.chromium.spec.ts +0 -130
  1675. package/src/Tooltip/__tests__/TooltipHoverBridge.a11y.chromium.spec.ts +0 -75
  1676. package/src/Tooltip/__tests__/TooltipTallContent.a11y.browser.spec.ts +0 -673
  1677. package/src/Tooltip/__tests__/TooltipTallContentTracking.a11y.browser.spec.ts +0 -1162
  1678. package/src/Tooltip/useTooltip.test.tsx +0 -483
  1679. package/src/TopNav/TopNav.spec.md +0 -190
  1680. package/src/TopNav/TopNav.test.tsx +0 -836
  1681. package/src/TopNav/TopNavHeading.spec.md +0 -211
  1682. package/src/TopNav/TopNavItem.spec.md +0 -189
  1683. package/src/TopNav/TopNavMegaMenu.reduced-motion.source-build.test.mjs +0 -83
  1684. package/src/TopNav/TopNavMegaMenu.spec.md +0 -190
  1685. package/src/TopNav/TopNavMegaMenu.test.tsx +0 -1132
  1686. package/src/TopNav/TopNavMegaMenuFeaturedCard.spec.md +0 -150
  1687. package/src/TopNav/TopNavMegaMenuItem.spec.md +0 -155
  1688. package/src/TopNav/TopNavMenu.spec.md +0 -202
  1689. package/src/TopNav/TopNavMenu.test.tsx +0 -485
  1690. package/src/TopNav/__tests__/TopNavMobileLongHeading.a11y.chromium.spec.ts +0 -75
  1691. package/src/TreeList/TreeList.spec.md +0 -328
  1692. package/src/TreeList/TreeList.test.tsx +0 -1711
  1693. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +0 -138
  1694. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +0 -44
  1695. package/src/TreeList/__tests__/TreeList.a11y.states.ts +0 -164
  1696. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +0 -138
  1697. package/src/TreeList/__tests__/TreeListGuideAlignment.interaction.browser.spec.ts +0 -127
  1698. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +0 -83
  1699. package/src/Typeahead/BaseTypeahead.emptyState.a11y.chromium.spec.ts +0 -138
  1700. package/src/Typeahead/BaseTypeahead.spec.md +0 -270
  1701. package/src/Typeahead/BaseTypeahead.test.tsx +0 -553
  1702. package/src/Typeahead/Typeahead.fr10.test.tsx +0 -28
  1703. package/src/Typeahead/Typeahead.spec.md +0 -257
  1704. package/src/Typeahead/Typeahead.test.tsx +0 -2824
  1705. package/src/Typeahead/TypeaheadItem.test.tsx +0 -134
  1706. package/src/VStack/VStack.test.tsx +0 -128
  1707. package/src/VisuallyHidden/VisuallyHidden.spec.md +0 -170
  1708. package/src/VisuallyHidden/VisuallyHidden.test.tsx +0 -115
  1709. package/src/__tests__/BusyOwnerAnnouncement.test.tsx +0 -853
  1710. package/src/__tests__/CardPress.a11y.chromium.spec.ts +0 -353
  1711. package/src/__tests__/DisabledTriggerFocus.a11y.chromium.spec.ts +0 -87
  1712. package/src/__tests__/PressModality.a11y.chromium.spec.ts +0 -392
  1713. package/src/__tests__/PressedState.a11y.chromium.spec.ts +0 -814
  1714. package/src/__tests__/TestIcon.tsx +0 -26
  1715. package/src/__tests__/babelPluginAddExtensions.test.ts +0 -190
  1716. package/src/__tests__/fastRoleQueries.ts +0 -66
  1717. package/src/__tests__/forcedColors.ts +0 -60
  1718. package/src/__tests__/pressState.ts +0 -212
  1719. package/src/__tests__/stubMatchMedia.test.ts +0 -79
  1720. package/src/__tests__/stubMatchMedia.ts +0 -95
  1721. package/src/__tests__/stylexDeclarations.ts +0 -51
  1722. package/src/__tests__/unthemedLength.ts +0 -34
  1723. package/src/docPropLiterals.test.ts +0 -964
  1724. package/src/docPropReferences.test.ts +0 -178
  1725. package/src/docs-script.test.mjs +0 -55
  1726. package/src/gestures/__tests__/LongPressGestureRecognizer.test.ts +0 -141
  1727. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +0 -327
  1728. package/src/gestures/__tests__/TapGestureRecognizer.test.ts +0 -154
  1729. package/src/gestures/__tests__/VelocityTracker.streams.test.ts +0 -243
  1730. package/src/gestures/__tests__/VelocityTracker.test.ts +0 -261
  1731. package/src/gestures/__tests__/arena.test.ts +0 -824
  1732. package/src/gestures/__tests__/arenaContinuous.test.ts +0 -207
  1733. package/src/gestures/__tests__/arenaTapLongPress.test.ts +0 -211
  1734. package/src/gestures/__tests__/fixtures/pointer-streams/README.md +0 -82
  1735. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-fling.json +0 -130
  1736. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-slow.json +0 -860
  1737. package/src/gestures/__tests__/fixtures/pointer-streams/android-chromium-stop-release.json +0 -357
  1738. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-fling.json +0 -66
  1739. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-slow.json +0 -268
  1740. package/src/gestures/__tests__/fixtures/pointer-streams/android-firefox-stop-release.json +0 -128
  1741. package/src/gestures/__tests__/fixtures/pointer-streams/capture.html +0 -123
  1742. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-fling.json +0 -64
  1743. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-slow.json +0 -268
  1744. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-mouse-stop-release.json +0 -124
  1745. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-fling.json +0 -64
  1746. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-slow.json +0 -268
  1747. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-pen-stop-release.json +0 -124
  1748. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-fling.json +0 -84
  1749. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-slow.json +0 -381
  1750. package/src/gestures/__tests__/fixtures/pointer-streams/desktop-chromium-touch-stop-release.json +0 -171
  1751. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-fling.json +0 -45
  1752. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-slow.json +0 -101
  1753. package/src/gestures/__tests__/fixtures/pointer-streams/ios-safari-stop-release.json +0 -130
  1754. package/src/gestures/__tests__/harness.ts +0 -204
  1755. package/src/hooks/__tests__/MenuPressShadowRoot.a11y.chromium.spec.ts +0 -144
  1756. package/src/hooks/__tests__/menuLinkClicks.ts +0 -71
  1757. package/src/hooks/actionGroupTracker.exports.test.ts +0 -28
  1758. package/src/hooks/actionGroupTracker.test.ts +0 -510
  1759. package/src/hooks/computeOverflow.test.ts +0 -422
  1760. package/src/hooks/deprecatedApis.public.test.ts +0 -33
  1761. package/src/hooks/menuPressGesture.test.ts +0 -253
  1762. package/src/hooks/scrollKeyboardDelegation.test.ts +0 -155
  1763. package/src/hooks/scrollbarGutter.test.ts +0 -167
  1764. package/src/hooks/useAnnounce.modal.test.tsx +0 -1291
  1765. package/src/hooks/useAnnounce.test.tsx +0 -293
  1766. package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +0 -226
  1767. package/src/hooks/useAutoMediaMode.test.ts +0 -109
  1768. package/src/hooks/useBusyAnnouncement.adopters.test.tsx +0 -69
  1769. package/src/hooks/useBusyAnnouncement.test.tsx +0 -112
  1770. package/src/hooks/useClickableContainer.test.tsx +0 -236
  1771. package/src/hooks/useClipboard.test.tsx +0 -151
  1772. package/src/hooks/useContainerReveal.test.tsx +0 -163
  1773. package/src/hooks/useDevWarning.test.tsx +0 -45
  1774. package/src/hooks/useEntryAnimation.ssr.test.tsx +0 -143
  1775. package/src/hooks/useFocusTrap.test.tsx +0 -635
  1776. package/src/hooks/useFocusTrapEscapeShim.test.tsx +0 -166
  1777. package/src/hooks/useGridFocus.test.tsx +0 -165
  1778. package/src/hooks/useHotkeys.test.ts +0 -265
  1779. package/src/hooks/useImageMode.test.ts +0 -133
  1780. package/src/hooks/useInputContainer.test.tsx +0 -180
  1781. package/src/hooks/useInputStatusIcon.test.tsx +0 -240
  1782. package/src/hooks/useIsHydrating.test.tsx +0 -70
  1783. package/src/hooks/useKeyboardHint.test.tsx +0 -92
  1784. package/src/hooks/useListFocus.test.tsx +0 -664
  1785. package/src/hooks/useLongPress.test.tsx +0 -152
  1786. package/src/hooks/useMediaQuery.test.ts +0 -165
  1787. package/src/hooks/useMenuHover.test.tsx +0 -536
  1788. package/src/hooks/useMenuPress.test.tsx +0 -565
  1789. package/src/hooks/useMergedRefs.test.tsx +0 -62
  1790. package/src/hooks/useOverflow.test.ts +0 -504
  1791. package/src/hooks/useRenamedProp.test.tsx +0 -95
  1792. package/src/hooks/useScrollLock.test.ts +0 -248
  1793. package/src/hooks/useScrollOverflow.test.tsx +0 -222
  1794. package/src/hooks/useScrollableArea.test.tsx +0 -836
  1795. package/src/hooks/useStreamingText.test.ts +0 -237
  1796. package/src/hooks/useTreeFocus.test.tsx +0 -351
  1797. package/src/hooks/useTypeahead.test.tsx +0 -351
  1798. package/src/i18n/InternationalizationProvider.spec.md +0 -252
  1799. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +0 -128
  1800. package/src/i18n/__tests__/e2e-pagination.test.tsx +0 -172
  1801. package/src/i18n/__tests__/e2e-powersearch.test.tsx +0 -166
  1802. package/src/i18n/__tests__/getLocaleDirection.test.ts +0 -40
  1803. package/src/i18n/__tests__/resolve.test.ts +0 -325
  1804. package/src/i18n/__tests__/useCollator.test.tsx +0 -115
  1805. package/src/i18n/__tests__/useDirection.test.tsx +0 -66
  1806. package/src/i18n/__tests__/useLocale.test.tsx +0 -52
  1807. package/src/i18n/useTranslator.test.tsx +0 -267
  1808. package/src/inputFontFloor.test.ts +0 -106
  1809. package/src/internal/tokenizer-presentation.test.ts +0 -29
  1810. package/src/internal/touch.test.ts +0 -44
  1811. package/src/motion/__tests__/animatedLayout.interaction.browser.spec.ts +0 -1613
  1812. package/src/motion/__tests__/animatedLayoutAdopters.interaction.browser.spec.ts +0 -217
  1813. package/src/motion/__tests__/animatedLayoutBundle.ts +0 -17
  1814. package/src/motion/animatedLayout.boundary.test.ts +0 -155
  1815. package/src/motion/animatedLayout.swap.test.ts +0 -827
  1816. package/src/motion/animatedLayout.test.ts +0 -1154
  1817. package/src/motion/animatedLayoutAdopters.test.tsx +0 -500
  1818. package/src/motion/animator.test.ts +0 -2653
  1819. package/src/motion/bezier.test.ts +0 -52
  1820. package/src/motion/coast.test.ts +0 -99
  1821. package/src/motion/decay.test.ts +0 -120
  1822. package/src/motion/projection.test.ts +0 -107
  1823. package/src/motion/reducedMotion.test.ts +0 -129
  1824. package/src/motion/reducedMotionPlan.test.ts +0 -182
  1825. package/src/motion/resolveSnapPoint.test.ts +0 -189
  1826. package/src/motion/roles.test.ts +0 -256
  1827. package/src/motion/rubberBand.test.ts +0 -88
  1828. package/src/motion/selectPage.test.ts +0 -31
  1829. package/src/motion/spring.test.ts +0 -650
  1830. package/src/motion/uikit/gestures.test.ts +0 -89
  1831. package/src/motion/uikit/springRows.test.ts +0 -71
  1832. package/src/motion/uikit/uikit.test.ts +0 -364
  1833. package/src/motion/uikit/values.test.ts +0 -110
  1834. package/src/naming.test.ts +0 -44
  1835. package/src/postinstall.test.mjs +0 -216
  1836. package/src/reset.a11y.chromium.spec.ts +0 -238
  1837. package/src/reset.test.ts +0 -205
  1838. package/src/reset.until-found.a11y.browser.spec.ts +0 -76
  1839. package/src/reset.until-found.test.ts +0 -32
  1840. package/src/serverSafeComponents.test.ts +0 -675
  1841. package/src/stylexValidation.source-build.test.mjs +0 -42
  1842. package/src/tailwind-theme.test.ts +0 -153
  1843. package/src/theme/MediaTheme.dom.test.tsx +0 -97
  1844. package/src/theme/Theme.test.tsx +0 -337
  1845. package/src/theme/__tests__/SpacingRootFontSize.a11y.chromium.spec.ts +0 -199
  1846. package/src/theme/contrast.test.ts +0 -94
  1847. package/src/theme/declarationBoundary.test.ts +0 -126
  1848. package/src/theme/defineTheme.test.ts +0 -2028
  1849. package/src/theme/derivedVarRegistry.test.ts +0 -588
  1850. package/src/theme/expandColorScale.test.ts +0 -651
  1851. package/src/theme/expandMotionScale.test.ts +0 -145
  1852. package/src/theme/expandRadiusScale.test.ts +0 -59
  1853. package/src/theme/expandTypeScale.test.ts +0 -351
  1854. package/src/theme/extensibleAxes.test.ts +0 -432
  1855. package/src/theme/generateThemeRules.test.ts +0 -1870
  1856. package/src/theme/hct.test.ts +0 -93
  1857. package/src/theme/onMediaTokens.test.ts +0 -431
  1858. package/src/theme/publicThemeHelperContract.test.ts +0 -67
  1859. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +0 -91
  1860. package/src/theme/syntax/serverSafeSyntax.test.ts +0 -68
  1861. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +0 -40
  1862. package/src/theme/themeAdaptations.test.ts +0 -1795
  1863. package/src/theme/themeRegistry.test.ts +0 -59
  1864. package/src/theme/themingTargets.test.ts +0 -589
  1865. package/src/theme/tokenValueCompat.test.ts +0 -204
  1866. package/src/theme/tokens.test.ts +0 -441
  1867. package/src/theme/useTheme.test.tsx +0 -322
  1868. package/src/unicodePropertyEscapes.source-build.test.mjs +0 -69
  1869. package/src/utils/chainEventHandlers.test.ts +0 -39
  1870. package/src/utils/characters.test.ts +0 -141
  1871. package/src/utils/color.test.ts +0 -233
  1872. package/src/utils/composeEventHandlers.test.ts +0 -71
  1873. package/src/utils/customPropertyArmLayering.test.ts +0 -131
  1874. package/src/utils/dateParser.test.ts +0 -402
  1875. package/src/utils/dateTypes.test.ts +0 -40
  1876. package/src/utils/devWarning.test.ts +0 -56
  1877. package/src/utils/edgeAutoscroll.test.ts +0 -192
  1878. package/src/utils/focusReturn.test.ts +0 -119
  1879. package/src/utils/getKey.test.ts +0 -25
  1880. package/src/utils/hitArea.test.ts +0 -628
  1881. package/src/utils/ime.test.ts +0 -32
  1882. package/src/utils/inputAria.test.ts +0 -39
  1883. package/src/utils/interactionModality.test.ts +0 -100
  1884. package/src/utils/isApplePlatform.test.ts +0 -61
  1885. package/src/utils/mergeProps.test.ts +0 -166
  1886. package/src/utils/mergeRefs.test.ts +0 -48
  1887. package/src/utils/parseStyleKey.test.ts +0 -56
  1888. package/src/utils/plainDate.test.ts +0 -848
  1889. package/src/utils/pressFeedback.test.ts +0 -874
  1890. package/src/utils/pressGesture.test.ts +0 -323
  1891. package/src/utils/pressPaintSource.test.ts +0 -65
  1892. package/src/utils/pressableCoverage.test.ts +0 -192
  1893. package/src/utils/safeUrl.test.ts +0 -101
  1894. package/src/utils/sharedMutationObserver.test.ts +0 -53
  1895. package/src/utils/sharedResizeObserver.test.ts +0 -283
  1896. package/src/utils/themeProps.test.ts +0 -62
  1897. package/src/utils/timeParser.test.ts +0 -295
  1898. package/src/utils/touchPresentation.test.ts +0 -323
@@ -1,4702 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file MultiSelector.test.tsx
5
- * @input Uses vitest, @testing-library/react, @testing-library/user-event
6
- * @output Unit tests for MultiSelector
7
- * @position Tests; validates MultiSelector behavior
8
- *
9
- * SYNC: When MultiSelector.tsx API changes, update these tests.
10
- */
11
-
12
- import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
- import {
14
- act,
15
- render,
16
- screen,
17
- fireEvent,
18
- waitFor,
19
- within,
20
- } from '@testing-library/react';
21
- import userEvent from '@testing-library/user-event';
22
- import * as stylex from '@stylexjs/stylex';
23
- import {
24
- MultiSelector,
25
- type MultiSelectorHandle,
26
- type MultiSelectorChange,
27
- } from './MultiSelector';
28
- import {useRef, useState} from 'react';
29
- import {Icon} from '../Icon';
30
- import {InternationalizationProvider} from '../i18n';
31
- import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
32
- import {__resetInteractionModalityForTest} from '../utils/interactionModality';
33
- import {defineTheme} from '../theme/defineTheme';
34
- import {generateThemeCSS} from '../theme/generateThemeRules';
35
- import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
36
- import {groupStyles} from '../InputGroup/groupStyles';
37
- import {InputGroup, InputGroupText} from '../InputGroup';
38
- import {colorVars, focusVars} from '../theme/tokens.stylex';
39
- import {COMPACT_TOUCH_PRESENTATION_QUERY} from '../hooks/useAdaptivePresentation';
40
-
41
- /**
42
- * Enters a search query as one change. Typing it key by key can land several
43
- * keystrokes in one frame, whose result announcements are then joined into
44
- * one (spec:AST-086 FR9), so a test of what one query announces pastes it.
45
- */
46
- async function enterQuery(
47
- user: ReturnType<typeof userEvent.setup>,
48
- input: HTMLElement,
49
- query: string,
50
- ): Promise<void> {
51
- await user.click(input);
52
- await user.paste(query);
53
- }
54
-
55
- function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
56
- const {prose, component} = generateThemeCSS(theme);
57
- return [prose, component].filter(Boolean).join('\n\n');
58
- }
59
- // Module-level constants to satisfy @eslint-react/no-unstable-default-props.
60
- const ANNOUNCE_OPTIONS = ['Apple', 'Banana', 'Orange'] as const;
61
- const EMPTY_VALUE: string[] = [];
62
-
63
- function politeRegion(): HTMLElement | null {
64
- return document.querySelector('[data-astryx-live-region="polite"]');
65
- }
66
-
67
- // Mock showPopover and hidePopover methods since they're not implemented in jsdom
68
- beforeEach(() => {
69
- __resetInteractionModalityForTest();
70
- HTMLDialogElement.prototype.showModal = vi.fn(function (
71
- this: HTMLDialogElement,
72
- ) {
73
- this.setAttribute('open', '');
74
- });
75
- HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
76
- this.removeAttribute('open');
77
- });
78
- vi.stubGlobal(
79
- 'matchMedia',
80
- vi.fn().mockImplementation((query: string) => ({
81
- matches: false,
82
- media: query,
83
- onchange: null,
84
- addEventListener: vi.fn(),
85
- removeEventListener: vi.fn(),
86
- addListener: vi.fn(),
87
- removeListener: vi.fn(),
88
- dispatchEvent: vi.fn(),
89
- })),
90
- );
91
- HTMLElement.prototype.showPopover = vi.fn(function (this: HTMLElement) {
92
- this.setAttribute('popover-open', '');
93
- const event = new Event('toggle', {bubbles: false});
94
- Object.defineProperty(event, 'newState', {value: 'open'});
95
- this.dispatchEvent(event);
96
- });
97
- HTMLElement.prototype.hidePopover = vi.fn(function (this: HTMLElement) {
98
- this.removeAttribute('popover-open');
99
- const event = new Event('toggle', {bubbles: false});
100
- Object.defineProperty(event, 'newState', {value: 'closed'});
101
- this.dispatchEvent(event);
102
- });
103
- const originalMatches = HTMLElement.prototype.matches;
104
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
- (HTMLElement.prototype as any).matches = function (
106
- selector: string,
107
- ): boolean {
108
- if (selector === ':popover-open') {
109
- return this.hasAttribute('popover-open');
110
- }
111
- return originalMatches.call(this, selector);
112
- };
113
- });
114
-
115
- afterEach(() => {
116
- __resetLiveRegionsForTest();
117
- });
118
-
119
- // Helper: jsdom popover content is in the DOM but may not be
120
- // "visible" in the accessibility tree. Use hidden: true to find it.
121
- const h = {hidden: true} as const;
122
-
123
- /** A busy control's Spinner is decorative and silent (spec:AST-086 FR8). */
124
- function expectSilentBusySpinner() {
125
- const spinner = document.querySelector('.astryx-spinner');
126
- expect(spinner).not.toBeNull();
127
- expect(spinner).toHaveAttribute('aria-hidden', 'true');
128
- expect(screen.queryByRole('status', {name: 'Loading'})).toBeNull();
129
- }
130
-
131
- describe('MultiSelector', () => {
132
- const defaultOptions = ['Apple', 'Banana', 'Orange'];
133
-
134
- it('uses a bottom sheet and stays open while toggling options', async () => {
135
- const user = userEvent.setup();
136
- const onChange = vi.fn();
137
- render(
138
- <MultiSelector
139
- label="Fruit"
140
- options={defaultOptions}
141
- value={[]}
142
- onChange={onChange}
143
- presentation="bottom-sheet"
144
- />,
145
- );
146
-
147
- const trigger = screen.getByRole('combobox');
148
- await user.click(trigger);
149
-
150
- expect(
151
- await screen.findByRole('dialog', {name: 'Fruit'}),
152
- ).toBeInTheDocument();
153
- expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
154
-
155
- await user.click(screen.getByRole('option', {name: 'Banana'}));
156
- expect(onChange).toHaveBeenCalledWith(['Banana']);
157
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
158
- });
159
-
160
- it('uses a bottom sheet for adaptive presentation on compact touch', async () => {
161
- vi.stubGlobal(
162
- 'matchMedia',
163
- vi.fn().mockImplementation((query: string) => ({
164
- matches: query === COMPACT_TOUCH_PRESENTATION_QUERY,
165
- media: query,
166
- onchange: null,
167
- addEventListener: vi.fn(),
168
- removeEventListener: vi.fn(),
169
- addListener: vi.fn(),
170
- removeListener: vi.fn(),
171
- dispatchEvent: vi.fn(),
172
- })),
173
- );
174
- const user = userEvent.setup();
175
- render(
176
- <MultiSelector
177
- label="Fruit"
178
- options={defaultOptions}
179
- value={[]}
180
- onChange={() => {}}
181
- presentation="adaptive"
182
- />,
183
- );
184
-
185
- await user.click(screen.getByRole('combobox'));
186
- expect(
187
- await screen.findByRole('dialog', {name: 'Fruit'}),
188
- ).toBeInTheDocument();
189
- expect(HTMLElement.prototype.showPopover).not.toHaveBeenCalled();
190
- });
191
-
192
- it('moves keyboard focus into a bottom-sheet listbox', async () => {
193
- const user = userEvent.setup();
194
- render(
195
- <MultiSelector
196
- label="Fruit"
197
- options={defaultOptions}
198
- value={[]}
199
- onChange={() => {}}
200
- presentation="bottom-sheet"
201
- />,
202
- );
203
-
204
- const trigger = screen.getByRole('combobox');
205
- trigger.focus();
206
- await user.keyboard('{Enter}');
207
-
208
- await waitFor(() => expect(screen.getByRole('listbox')).toHaveFocus());
209
- });
210
-
211
- it('restores touch focus without painting a trigger focus ring', async () => {
212
- render(
213
- <MultiSelector
214
- label="Fruit"
215
- options={defaultOptions}
216
- value={[]}
217
- onChange={() => {}}
218
- presentation="bottom-sheet"
219
- />,
220
- );
221
-
222
- const trigger = screen.getByRole('combobox');
223
- fireEvent.pointerDown(trigger, {pointerType: 'touch'});
224
- fireEvent.click(trigger, {detail: 1});
225
- const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
226
- fireEvent.pointerDown(dialog, {pointerType: 'touch'});
227
- fireEvent.click(dialog, {detail: 1});
228
-
229
- trigger.focus();
230
- expect(trigger).toHaveFocus();
231
- expect(trigger.parentElement).toHaveClass(
232
- stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
233
- );
234
- });
235
-
236
- it('shows no disabled focus ring after a pointer pick (spec:AST-085 FR6)', async () => {
237
- const field = (isDisabled: boolean) => (
238
- <MultiSelector
239
- label="Fruit"
240
- options={defaultOptions}
241
- value={[]}
242
- onChange={() => {}}
243
- presentation="bottom-sheet"
244
- isDisabled={isDisabled}
245
- />
246
- );
247
- const {rerender} = render(field(false));
248
- const trigger = screen.getByRole('combobox');
249
- fireEvent.pointerDown(trigger, {pointerType: 'touch'});
250
- fireEvent.click(trigger, {detail: 1});
251
- const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
252
- fireEvent.pointerDown(dialog, {pointerType: 'touch'});
253
- fireEvent.click(dialog, {detail: 1});
254
- rerender(field(true));
255
- const ring = stylex
256
- .props(
257
- stylex.create({
258
- ring: {
259
- boxShadow: {
260
- default: null,
261
- ':has(> [aria-disabled="true"]:focus-visible)': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
262
- },
263
- },
264
- }).ring,
265
- )
266
- .className!.split(' ')
267
- .pop()!;
268
- const wrapper = screen.getByRole('combobox').parentElement!;
269
- expect(wrapper).toHaveClass(
270
- stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
271
- );
272
- expect(wrapper).not.toHaveClass(ring);
273
- });
274
-
275
- it('renders with label', () => {
276
- render(
277
- <MultiSelector
278
- label="Fruit"
279
- options={defaultOptions}
280
- value={[]}
281
- onChange={() => {}}
282
- />,
283
- );
284
- expect(screen.getByLabelText('Fruit')).toBeInTheDocument();
285
- });
286
-
287
- it('applies distinct trigger chrome to the secondary variant', () => {
288
- // data-testid reaches the native control; the variant is on the trigger root.
289
- const triggerRoot = () =>
290
- screen
291
- .getByTestId('selector')
292
- .closest('.astryx-multi-selector') as HTMLElement;
293
- const {rerender} = render(
294
- <MultiSelector
295
- label="Fruit"
296
- options={defaultOptions}
297
- value={[]}
298
- onChange={() => {}}
299
- variant="input"
300
- data-testid="selector"
301
- />,
302
- );
303
- const inputClasses = triggerRoot().className;
304
-
305
- rerender(
306
- <MultiSelector
307
- label="Fruit"
308
- options={defaultOptions}
309
- value={[]}
310
- onChange={() => {}}
311
- variant="secondary"
312
- data-testid="selector"
313
- />,
314
- );
315
- const secondary = triggerRoot();
316
- const secondaryClasses = secondary.className;
317
- expect(secondary).toHaveAttribute('data-variant', 'secondary');
318
-
319
- rerender(
320
- <MultiSelector
321
- label="Fruit"
322
- options={defaultOptions}
323
- value={[]}
324
- onChange={() => {}}
325
- variant="ghost"
326
- data-testid="selector"
327
- />,
328
- );
329
- const ghostClasses = triggerRoot().className;
330
-
331
- expect(secondaryClasses).not.toBe(inputClasses);
332
- expect(secondaryClasses).not.toBe(ghostClasses);
333
- });
334
-
335
- it('renders custom option content with renderOption', async () => {
336
- const user = userEvent.setup();
337
- render(
338
- <MultiSelector
339
- label="Fruit"
340
- options={[{value: 'apple', label: 'Apple'}]}
341
- value={[]}
342
- onChange={() => {}}
343
- renderOption={option => (
344
- <span data-testid="custom-option">{option.label}</span>
345
- )}
346
- />,
347
- );
348
-
349
- await user.click(screen.getByRole('combobox'));
350
- expect(screen.getByTestId('custom-option')).toHaveTextContent('Apple');
351
- });
352
- it('renders placeholder when no value selected', () => {
353
- render(
354
- <MultiSelector
355
- label="Fruit"
356
- options={defaultOptions}
357
- value={[]}
358
- onChange={() => {}}
359
- placeholder="Pick fruits..."
360
- />,
361
- );
362
- expect(screen.getByText('Pick fruits...')).toBeInTheDocument();
363
- });
364
-
365
- it('shows count display by default', () => {
366
- render(
367
- <MultiSelector
368
- label="Fruit"
369
- options={defaultOptions}
370
- value={['Apple', 'Banana']}
371
- onChange={() => {}}
372
- />,
373
- );
374
- expect(screen.getByText('2 selected')).toBeInTheDocument();
375
- });
376
-
377
- it('formats the count display with formatValue', () => {
378
- render(
379
- <MultiSelector
380
- label="Spaces"
381
- options={defaultOptions}
382
- value={['Apple', 'Banana']}
383
- onChange={() => {}}
384
- formatValue={items => `${items.length} spaces selected`}
385
- />,
386
- );
387
- expect(screen.getByText('2 spaces selected')).toBeInTheDocument();
388
- });
389
-
390
- it('passes selected values and resolved labels to formatValue', () => {
391
- const formatValue = vi.fn(
392
- (items: {value: string; label: string}[]) =>
393
- `${items[0].label} and ${items.length - 1} more`,
394
- );
395
- render(
396
- <MultiSelector
397
- label="Fruit"
398
- options={[
399
- {value: 'a', label: 'Apple'},
400
- {value: 'b', label: 'Banana'},
401
- ]}
402
- value={['a', 'b']}
403
- onChange={() => {}}
404
- formatValue={formatValue}
405
- />,
406
- );
407
- expect(formatValue).toHaveBeenCalledWith([
408
- {value: 'a', label: 'Apple'},
409
- {value: 'b', label: 'Banana'},
410
- ]);
411
- expect(screen.getByText('Apple and 1 more')).toBeInTheDocument();
412
- });
413
-
414
- it('formats the labels display with formatValue', () => {
415
- render(
416
- <MultiSelector
417
- label="Fruit"
418
- options={defaultOptions}
419
- value={['Apple', 'Banana', 'Orange']}
420
- onChange={() => {}}
421
- triggerDisplay="labels"
422
- formatValue={items => items.map(item => item.label).join(' & ')}
423
- />,
424
- );
425
- expect(screen.getByText('Apple & Banana & Orange')).toBeInTheDocument();
426
- });
427
-
428
- it('ignores formatValue in badges display', () => {
429
- render(
430
- <MultiSelector
431
- label="Fruit"
432
- options={defaultOptions}
433
- value={['Apple', 'Banana']}
434
- onChange={() => {}}
435
- triggerDisplay="badges"
436
- formatValue={() => 'formatted'}
437
- />,
438
- );
439
- expect(screen.queryByText('formatted')).not.toBeInTheDocument();
440
- const trigger = within(screen.getByRole('combobox'));
441
- expect(trigger.getByText('Apple')).toBeInTheDocument();
442
- expect(trigger.getByText('Banana')).toBeInTheDocument();
443
- });
444
-
445
- it('shows the placeholder rather than calling formatValue when empty', () => {
446
- const formatValue = vi.fn(() => 'formatted');
447
- render(
448
- <MultiSelector
449
- label="Fruit"
450
- options={defaultOptions}
451
- value={[]}
452
- onChange={() => {}}
453
- placeholder="Pick fruits..."
454
- formatValue={formatValue}
455
- />,
456
- );
457
- expect(screen.getByText('Pick fruits...')).toBeInTheDocument();
458
- expect(formatValue).not.toHaveBeenCalled();
459
- });
460
-
461
- it('shows labels display', () => {
462
- render(
463
- <MultiSelector
464
- label="Fruit"
465
- options={defaultOptions}
466
- value={['Apple', 'Banana']}
467
- onChange={() => {}}
468
- triggerDisplay="labels"
469
- />,
470
- );
471
- expect(screen.getByText('Apple, Banana')).toBeInTheDocument();
472
- });
473
-
474
- it('shows labels display with overflow', () => {
475
- render(
476
- <MultiSelector
477
- label="Fruit"
478
- options={['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple']}
479
- value={['Apple', 'Banana', 'Orange', 'Mango', 'Pineapple']}
480
- onChange={() => {}}
481
- triggerDisplay="labels"
482
- />,
483
- );
484
- expect(screen.getByText('Apple, Banana, Orange, +2')).toBeInTheDocument();
485
- });
486
-
487
- it('opens dropdown on click', async () => {
488
- const user = userEvent.setup();
489
- render(
490
- <MultiSelector
491
- label="Fruit"
492
- options={defaultOptions}
493
- value={[]}
494
- onChange={() => {}}
495
- />,
496
- );
497
- const trigger = screen.getByRole('combobox');
498
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
499
-
500
- await user.click(trigger);
501
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
502
- });
503
-
504
- it('toggles item on click without closing dropdown', async () => {
505
- const user = userEvent.setup();
506
- const onChange = vi.fn();
507
- render(
508
- <MultiSelector
509
- label="Fruit"
510
- options={defaultOptions}
511
- value={[]}
512
- onChange={onChange}
513
- />,
514
- );
515
-
516
- await user.click(screen.getByRole('combobox'));
517
-
518
- const options = screen.getAllByRole('option', h);
519
- await user.click(options[0]);
520
-
521
- expect(onChange).toHaveBeenCalledWith(['Apple']);
522
- // Dropdown should still be open
523
- expect(screen.getByRole('combobox')).toHaveAttribute(
524
- 'aria-expanded',
525
- 'true',
526
- );
527
- });
528
-
529
- it('deselects item when clicking selected item', async () => {
530
- const user = userEvent.setup();
531
- const onChange = vi.fn();
532
- render(
533
- <MultiSelector
534
- label="Fruit"
535
- options={defaultOptions}
536
- value={['Apple', 'Banana']}
537
- onChange={onChange}
538
- />,
539
- );
540
-
541
- await user.click(screen.getByRole('combobox'));
542
- const options = screen.getAllByRole('option', h);
543
- await user.click(options[0]); // Click Apple to deselect
544
-
545
- expect(onChange).toHaveBeenCalledWith(['Banana']);
546
- });
547
-
548
- it('does not toggle disabled items', async () => {
549
- const user = userEvent.setup();
550
- const onChange = vi.fn();
551
- render(
552
- <MultiSelector
553
- label="Fruit"
554
- options={[
555
- {value: 'apple', label: 'Apple', disabled: true},
556
- {value: 'banana', label: 'Banana'},
557
- ]}
558
- value={[]}
559
- onChange={onChange}
560
- />,
561
- );
562
-
563
- await user.click(screen.getByRole('combobox'));
564
- const options = screen.getAllByRole('option', h);
565
- await user.click(options[0]); // Click disabled Apple
566
-
567
- expect(onChange).not.toHaveBeenCalled();
568
- });
569
-
570
- it('renders disabled state', () => {
571
- render(
572
- <MultiSelector
573
- label="Fruit"
574
- options={defaultOptions}
575
- value={[]}
576
- onChange={() => {}}
577
- isDisabled
578
- />,
579
- );
580
- const trigger = screen.getByRole('combobox');
581
- expect(trigger).not.toHaveAttribute('disabled');
582
- expect(trigger).toHaveAttribute('aria-disabled', 'true');
583
- });
584
-
585
- it('has correct ARIA attributes', () => {
586
- render(
587
- <MultiSelector
588
- label="Fruit"
589
- options={defaultOptions}
590
- value={[]}
591
- onChange={() => {}}
592
- isRequired
593
- />,
594
- );
595
- const trigger = screen.getByRole('combobox');
596
- expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');
597
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
598
- expect(trigger).toHaveAttribute('aria-required', 'true');
599
- });
600
-
601
- it('shows error status with aria-invalid', () => {
602
- render(
603
- <MultiSelector
604
- label="Fruit"
605
- options={defaultOptions}
606
- value={[]}
607
- onChange={() => {}}
608
- status={{type: 'error', message: 'Required'}}
609
- />,
610
- );
611
- const trigger = screen.getByRole('combobox');
612
- expect(trigger).toHaveAttribute('aria-invalid', 'true');
613
- expect(screen.getByText('Required')).toBeInTheDocument();
614
- });
615
-
616
- it('closes on Escape', async () => {
617
- const user = userEvent.setup();
618
- render(
619
- <MultiSelector
620
- label="Fruit"
621
- options={defaultOptions}
622
- value={[]}
623
- onChange={() => {}}
624
- />,
625
- );
626
-
627
- const trigger = screen.getByRole('combobox');
628
- await user.click(trigger);
629
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
630
-
631
- await user.keyboard('{Escape}');
632
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
633
- });
634
-
635
- it('closes combobox on Tab and moves focus to next element', async () => {
636
- const user = userEvent.setup();
637
- render(
638
- <>
639
- <MultiSelector
640
- label="Fruit"
641
- options={defaultOptions}
642
- value={[]}
643
- onChange={() => {}}
644
- />
645
- <button type="button">Next</button>
646
- </>,
647
- );
648
-
649
- const trigger = screen.getByRole('combobox');
650
- await user.click(trigger);
651
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
652
-
653
- await user.keyboard('{Tab}');
654
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
655
- expect(screen.getByRole('button', {name: 'Next'})).toHaveFocus();
656
- });
657
-
658
- it('supports keyboard navigation with ArrowDown/ArrowUp', async () => {
659
- const user = userEvent.setup();
660
- render(
661
- <MultiSelector
662
- label="Fruit"
663
- options={defaultOptions}
664
- value={[]}
665
- onChange={() => {}}
666
- />,
667
- );
668
-
669
- const trigger = screen.getByRole('combobox');
670
- await user.click(trigger);
671
-
672
- await user.keyboard('{ArrowDown}');
673
- const activeId = trigger.getAttribute('aria-activedescendant');
674
- expect(activeId).toBeTruthy();
675
- });
676
-
677
- it('End/Home jump the highlight to the last/first option (non-search)', async () => {
678
- const user = userEvent.setup();
679
- render(
680
- <MultiSelector
681
- label="Fruit"
682
- options={defaultOptions}
683
- value={[]}
684
- onChange={() => {}}
685
- />,
686
- );
687
-
688
- const trigger = screen.getByRole('combobox');
689
- await user.click(trigger);
690
- const options = screen.getAllByRole('option', h);
691
-
692
- await user.keyboard('{End}');
693
- expect(trigger).toHaveAttribute(
694
- 'aria-activedescendant',
695
- options[options.length - 1].id,
696
- );
697
- await user.keyboard('{Home}');
698
- expect(trigger).toHaveAttribute('aria-activedescendant', options[0].id);
699
- });
700
-
701
- it('toggles item with Enter key', async () => {
702
- const user = userEvent.setup();
703
- const onChange = vi.fn();
704
- render(
705
- <MultiSelector
706
- label="Fruit"
707
- options={defaultOptions}
708
- value={[]}
709
- onChange={onChange}
710
- />,
711
- );
712
-
713
- const trigger = screen.getByRole('combobox');
714
- await user.click(trigger);
715
- await user.keyboard('{Enter}');
716
- expect(onChange).toHaveBeenCalledWith(['Apple']);
717
- });
718
-
719
- it('does not toggle the highlighted option on a composing Enter (IME)', async () => {
720
- const user = userEvent.setup();
721
- const onChange = vi.fn();
722
- render(
723
- <MultiSelector
724
- label="Fruit"
725
- options={defaultOptions}
726
- value={[]}
727
- onChange={onChange}
728
- hasSearch
729
- />,
730
- );
731
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
732
- const search = screen.getByRole('combobox', h);
733
- // Filter to Banana and highlight it so an unguarded Enter would toggle it.
734
- await user.type(search, 'ban');
735
- await user.keyboard('{ArrowDown}');
736
- expect(search).toHaveAttribute('aria-activedescendant');
737
-
738
- // The composing keydown (isComposing / legacy keyCode 229) that commits an
739
- // IME candidate must not be read as "toggle the highlighted option".
740
- fireEvent.keyDown(search, {key: 'Enter', isComposing: true});
741
- expect(onChange).not.toHaveBeenCalled();
742
- fireEvent.keyDown(search, {key: 'Enter', keyCode: 229});
743
- expect(onChange).not.toHaveBeenCalled();
744
-
745
- // A real, non-composing Enter still toggles the highlighted option.
746
- fireEvent.keyDown(search, {key: 'Enter'});
747
- expect(onChange).toHaveBeenCalledWith(['Banana']);
748
- });
749
-
750
- it('toggles the correct item when selected items are sorted to top', async () => {
751
- const user = userEvent.setup();
752
- const onChange = vi.fn();
753
- // Orange is selected, so sorted order is: Orange, Apple, Banana
754
- render(
755
- <MultiSelector
756
- label="Fruit"
757
- options={defaultOptions}
758
- value={['Orange']}
759
- onChange={onChange}
760
- />,
761
- );
762
-
763
- const trigger = screen.getByRole('combobox');
764
- await user.click(trigger);
765
- // highlightedIndex starts at 0 which is Orange (sorted first)
766
- await user.keyboard('{ArrowDown}');
767
- // Now at index 1 which should be Apple
768
- await user.keyboard('{Enter}');
769
- expect(onChange).toHaveBeenCalledWith(['Orange', 'Apple']);
770
- });
771
-
772
- it('renders select-all checkbox when hasSelectAll', async () => {
773
- const user = userEvent.setup();
774
- render(
775
- <MultiSelector
776
- label="Fruit"
777
- options={defaultOptions}
778
- value={[]}
779
- onChange={() => {}}
780
- hasSelectAll
781
- />,
782
- );
783
-
784
- await user.click(screen.getByRole('combobox'));
785
- expect(screen.getByText('Select all')).toBeInTheDocument();
786
- });
787
-
788
- it('select-all selects all enabled items', async () => {
789
- const user = userEvent.setup();
790
- const onChange = vi.fn();
791
- render(
792
- <MultiSelector
793
- label="Fruit"
794
- options={[
795
- {value: 'apple', label: 'Apple'},
796
- {value: 'banana', label: 'Banana', disabled: true},
797
- {value: 'orange', label: 'Orange'},
798
- ]}
799
- value={[]}
800
- onChange={onChange}
801
- hasSelectAll
802
- />,
803
- );
804
-
805
- await user.click(screen.getByRole('combobox'));
806
- const selectAll = screen.getByText('Select all');
807
- await user.click(selectAll);
808
-
809
- expect(onChange).toHaveBeenCalledWith(['apple', 'orange']);
810
- });
811
-
812
- it('select-all deselects all when all are selected', async () => {
813
- const user = userEvent.setup();
814
- const onChange = vi.fn();
815
- render(
816
- <MultiSelector
817
- label="Fruit"
818
- options={defaultOptions}
819
- value={['Apple', 'Banana', 'Orange']}
820
- onChange={onChange}
821
- hasSelectAll
822
- />,
823
- );
824
-
825
- await user.click(screen.getByRole('combobox'));
826
- const selectAll = screen.getByText('Select all');
827
- await user.click(selectAll);
828
-
829
- expect(onChange).toHaveBeenCalledWith([]);
830
- });
831
-
832
- it('select-all is a role="option" in the listbox', async () => {
833
- const user = userEvent.setup();
834
- render(
835
- <MultiSelector
836
- label="Fruit"
837
- options={defaultOptions}
838
- value={[]}
839
- onChange={() => {}}
840
- hasSelectAll
841
- />,
842
- );
843
-
844
- await user.click(screen.getByRole('combobox'));
845
- const options = screen.getAllByRole('option', h);
846
- expect(options[0]).toHaveTextContent('Select all');
847
- });
848
-
849
- it('select-all accessible name reflects none/partial/all selection', async () => {
850
- const user = userEvent.setup();
851
- const options = [
852
- {value: 'apple', label: 'Apple'},
853
- {value: 'banana', label: 'Banana'},
854
- ];
855
- const {rerender} = render(
856
- <MultiSelector
857
- label="Fruit"
858
- options={options}
859
- value={[]}
860
- onChange={() => {}}
861
- hasSelectAll
862
- />,
863
- );
864
-
865
- await user.click(screen.getByRole('combobox'));
866
-
867
- // None selected: plain name, not selected
868
- let selectAll = screen.getAllByRole('option', h)[0];
869
- expect(selectAll).not.toHaveAccessibleName(/partially selected/);
870
- expect(selectAll).toHaveAttribute('aria-selected', 'false');
871
-
872
- // Partial: aria-selected="mixed" is invalid on role="option", so the
873
- // indeterminate state must be conveyed through the accessible name.
874
- rerender(
875
- <MultiSelector
876
- label="Fruit"
877
- options={options}
878
- value={['apple']}
879
- onChange={() => {}}
880
- hasSelectAll
881
- />,
882
- );
883
- selectAll = screen.getAllByRole('option', h)[0];
884
- expect(selectAll).toHaveAccessibleName('Select all, partially selected');
885
- expect(selectAll).toHaveAttribute('aria-selected', 'false');
886
-
887
- // All selected: plain name again, selected
888
- rerender(
889
- <MultiSelector
890
- label="Fruit"
891
- options={options}
892
- value={['apple', 'banana']}
893
- onChange={() => {}}
894
- hasSelectAll
895
- />,
896
- );
897
- selectAll = screen.getAllByRole('option', h)[0];
898
- expect(selectAll).not.toHaveAccessibleName(/partially selected/);
899
- expect(selectAll).toHaveAttribute('aria-selected', 'true');
900
- });
901
-
902
- it('select-all toggles via keyboard Enter', async () => {
903
- const user = userEvent.setup();
904
- const onChange = vi.fn();
905
- render(
906
- <MultiSelector
907
- label="Fruit"
908
- options={defaultOptions}
909
- value={[]}
910
- onChange={onChange}
911
- hasSelectAll
912
- />,
913
- );
914
-
915
- const trigger = screen.getByRole('combobox');
916
- await user.click(trigger);
917
- // highlightedIndex starts at 0 which is select-all
918
- await user.keyboard('{Enter}');
919
- expect(onChange).toHaveBeenCalledWith(['Apple', 'Banana', 'Orange']);
920
- });
921
-
922
- it('renders search input when hasSearch', async () => {
923
- const user = userEvent.setup();
924
- render(
925
- <MultiSelector
926
- label="Fruit"
927
- options={defaultOptions}
928
- value={[]}
929
- onChange={() => {}}
930
- hasSearch
931
- />,
932
- );
933
-
934
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
935
- const searchInput = screen.getByRole('combobox', h);
936
- expect(searchInput).toBeInTheDocument();
937
- expect(searchInput).toHaveAttribute('aria-autocomplete', 'list');
938
- });
939
-
940
- it('filters options when searching', async () => {
941
- const user = userEvent.setup();
942
- render(
943
- <MultiSelector
944
- label="Fruit"
945
- options={defaultOptions}
946
- value={[]}
947
- onChange={() => {}}
948
- hasSearch
949
- />,
950
- );
951
-
952
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
953
- const searchInput = screen.getByRole('combobox', h);
954
- await user.type(searchInput, 'app');
955
-
956
- const options = screen.getAllByRole('option', h);
957
- expect(options).toHaveLength(1);
958
- });
959
-
960
- describe('grouped search', () => {
961
- const GROUPED = [
962
- {
963
- type: 'section' as const,
964
- title: 'Citrus',
965
- options: [
966
- {value: 'orange', label: 'Orange'},
967
- {value: 'lemon', label: 'Lemon'},
968
- ],
969
- },
970
- {
971
- type: 'section' as const,
972
- title: 'Berries',
973
- options: [
974
- {value: 'strawberry', label: 'Strawberry'},
975
- {value: 'blueberry', label: 'Blueberry'},
976
- ],
977
- },
978
- ];
979
-
980
- it('keeps the group header above matching items while searching', async () => {
981
- const user = userEvent.setup();
982
- render(
983
- <MultiSelector
984
- label="Fruit"
985
- options={GROUPED}
986
- value={[]}
987
- onChange={() => {}}
988
- hasSearch
989
- />,
990
- );
991
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
992
- await user.type(screen.getByRole('combobox', h), 'orange');
993
-
994
- expect(
995
- screen.getByRole('group', {name: 'Citrus', ...h}),
996
- ).toBeInTheDocument();
997
- const options = screen.getAllByRole('option', h);
998
- expect(options).toHaveLength(1);
999
- expect(options[0]).toHaveTextContent('Orange');
1000
- });
1001
-
1002
- it('hides a group whose items have no match', async () => {
1003
- const user = userEvent.setup();
1004
- render(
1005
- <MultiSelector
1006
- label="Fruit"
1007
- options={GROUPED}
1008
- value={[]}
1009
- onChange={() => {}}
1010
- hasSearch
1011
- />,
1012
- );
1013
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1014
- await user.type(screen.getByRole('combobox', h), 'berry');
1015
-
1016
- expect(
1017
- screen.getByRole('group', {name: 'Berries', ...h}),
1018
- ).toBeInTheDocument();
1019
- expect(
1020
- screen.queryByRole('group', {name: 'Citrus', ...h}),
1021
- ).not.toBeInTheDocument();
1022
- expect(screen.getAllByRole('option', h)).toHaveLength(2);
1023
- });
1024
- });
1025
-
1026
- it('PageDown/PageUp jump the highlight to the last/first filtered option', async () => {
1027
- const user = userEvent.setup();
1028
- render(
1029
- <MultiSelector
1030
- label="Fruit"
1031
- options={defaultOptions}
1032
- value={[]}
1033
- onChange={() => {}}
1034
- hasSearch
1035
- />,
1036
- );
1037
-
1038
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1039
- const searchInput = screen.getByRole('combobox', h);
1040
- // Filter to Banana and Orange so "last" means last *visible* option.
1041
- await user.type(searchInput, 'an');
1042
- const options = screen.getAllByRole('option', h);
1043
- expect(options).toHaveLength(2);
1044
-
1045
- await user.keyboard('{PageDown}');
1046
- expect(searchInput).toHaveAttribute(
1047
- 'aria-activedescendant',
1048
- options[options.length - 1].id,
1049
- );
1050
- await user.keyboard('{PageUp}');
1051
- expect(searchInput).toHaveAttribute('aria-activedescendant', options[0].id);
1052
- });
1053
-
1054
- it('Home/End move the search caret, not the option highlight', async () => {
1055
- const user = userEvent.setup();
1056
- render(
1057
- <MultiSelector
1058
- label="Fruit"
1059
- options={defaultOptions}
1060
- value={[]}
1061
- onChange={() => {}}
1062
- hasSearch
1063
- />,
1064
- );
1065
-
1066
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1067
- const searchInput = screen.getByRole<HTMLInputElement>('combobox', h);
1068
- await user.type(searchInput, 'an');
1069
- expect(searchInput.selectionStart).toBe(2);
1070
- const activeBefore = searchInput.getAttribute('aria-activedescendant');
1071
- // Home/End stay on the input for caret movement (APG editable combobox);
1072
- // the option highlight must not move.
1073
- await user.keyboard('{Home}');
1074
- expect(searchInput.selectionStart).toBe(0);
1075
- expect(searchInput.getAttribute('aria-activedescendant')).toBe(
1076
- activeBefore,
1077
- );
1078
- await user.keyboard('{End}');
1079
- expect(searchInput.selectionStart).toBe(2);
1080
- expect(searchInput.getAttribute('aria-activedescendant')).toBe(
1081
- activeBefore,
1082
- );
1083
- });
1084
-
1085
- it('shows empty state when search has no results', async () => {
1086
- const user = userEvent.setup();
1087
- render(
1088
- <MultiSelector
1089
- label="Fruit"
1090
- options={defaultOptions}
1091
- value={[]}
1092
- onChange={() => {}}
1093
- hasSearch
1094
- />,
1095
- );
1096
-
1097
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1098
- const searchInput = screen.getByRole('combobox', h);
1099
- await user.type(searchInput, 'xyz');
1100
-
1101
- expect(
1102
- within(screen.getByRole('listbox', h)).getByText('No results found'),
1103
- ).toBeInTheDocument();
1104
- });
1105
-
1106
- it('empty-state message is not exposed as a listbox child', async () => {
1107
- const user = userEvent.setup();
1108
- render(
1109
- <MultiSelector
1110
- label="Fruit"
1111
- options={defaultOptions}
1112
- value={[]}
1113
- onChange={() => {}}
1114
- hasSearch
1115
- />,
1116
- );
1117
-
1118
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1119
- await user.type(screen.getByRole('combobox', h), 'xyz');
1120
-
1121
- // role="listbox" only permits option/group children — the visual
1122
- // empty-state message must be presentational (it is announced through
1123
- // the result-count live region instead).
1124
- const listbox = screen.getByRole('listbox', h);
1125
- const empty = within(listbox).getByText('No results found');
1126
- expect(empty).toHaveAttribute('role', 'presentation');
1127
- });
1128
-
1129
- it('renders emptySearchText in place of the default message', async () => {
1130
- const user = userEvent.setup();
1131
- render(
1132
- <MultiSelector
1133
- label="Fruit"
1134
- options={defaultOptions}
1135
- value={[]}
1136
- onChange={() => {}}
1137
- hasSearch
1138
- emptySearchText="Nothing like that here"
1139
- />,
1140
- );
1141
-
1142
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1143
- await user.type(screen.getByRole('combobox', h), 'xyz');
1144
-
1145
- const listbox = screen.getByRole('listbox', h);
1146
- expect(
1147
- within(listbox).getByText('Nothing like that here'),
1148
- ).toBeInTheDocument();
1149
- expect(
1150
- within(listbox).queryByText('No results found'),
1151
- ).not.toBeInTheDocument();
1152
- });
1153
-
1154
- it('renders emptyText, not emptySearchText, with no options and no query', async () => {
1155
- const user = userEvent.setup();
1156
- render(
1157
- <MultiSelector
1158
- label="Fruit"
1159
- options={[]}
1160
- value={[]}
1161
- onChange={() => {}}
1162
- hasSearch
1163
- emptySearchText="Nothing like that here"
1164
- />,
1165
- );
1166
-
1167
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1168
-
1169
- const listbox = screen.getByRole('listbox', h);
1170
- expect(within(listbox).getByText('No options')).toBeInTheDocument();
1171
- expect(
1172
- within(listbox).queryByText('Nothing like that here'),
1173
- ).not.toBeInTheDocument();
1174
- });
1175
-
1176
- it('announces emptySearchText, not the catalog default', async () => {
1177
- const user = userEvent.setup();
1178
- render(
1179
- <MultiSelector
1180
- label="Fruit"
1181
- options={defaultOptions}
1182
- value={[]}
1183
- onChange={() => {}}
1184
- hasSearch
1185
- emptySearchText="Nothing like that here"
1186
- />,
1187
- );
1188
-
1189
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1190
- await enterQuery(user, screen.getByRole('combobox', h), 'xyz');
1191
-
1192
- // The panel message is role="presentation", so the live region is the
1193
- // only thing a screen reader gets — it has to say the same words.
1194
- await waitFor(() =>
1195
- expect(politeRegion()?.textContent).toBe('Nothing like that here'),
1196
- );
1197
- });
1198
-
1199
- // `emptySearchText` is typed ReactNode, which invites an element — a
1200
- // "no results, create one" row is exactly what a dead end is for. The
1201
- // announcement used to take the prop only when it was a string and
1202
- // announce the catalog default otherwise, so a screen-reader user was told
1203
- // something the sighted user was not reading (`spec:AST-056` AR1).
1204
- it('announces the text of an element emptySearchText', async () => {
1205
- const user = userEvent.setup();
1206
- render(
1207
- <MultiSelector
1208
- label="Fruit"
1209
- options={defaultOptions}
1210
- value={[]}
1211
- onChange={() => {}}
1212
- hasSearch
1213
- emptySearchText={
1214
- <span>
1215
- Nothing like that here. <a href="/new">Add a fruit</a>
1216
- </span>
1217
- }
1218
- />,
1219
- );
1220
-
1221
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1222
- await enterQuery(user, screen.getByRole('combobox', h), 'xyz');
1223
-
1224
- await waitFor(() =>
1225
- expect(politeRegion()?.textContent).toBe(
1226
- 'Nothing like that here. Add a fruit',
1227
- ),
1228
- );
1229
- });
1230
-
1231
- it('announces the text of an element emptyText', async () => {
1232
- const user = userEvent.setup();
1233
- render(
1234
- <MultiSelector
1235
- label="Fruit"
1236
- options={[]}
1237
- value={[]}
1238
- onChange={() => {}}
1239
- emptyText={
1240
- <span>
1241
- Add a fruit first<span aria-hidden="true"> →</span>
1242
- </span>
1243
- }
1244
- />,
1245
- );
1246
-
1247
- await user.click(screen.getByRole('combobox', {name: 'Fruit'}));
1248
-
1249
- // The decorative arrow is hidden from the accessibility tree on screen,
1250
- // so it stays out of the announcement too.
1251
- await waitFor(() =>
1252
- expect(politeRegion()?.textContent).toBe('Add a fruit first'),
1253
- );
1254
- });
1255
-
1256
- it('announces text a child component renders, which is only in the DOM', async () => {
1257
- const user = userEvent.setup();
1258
- // The words exist nowhere in the node the caller passed — they are
1259
- // produced inside this component's own render. Reading the DOM after
1260
- // render is what makes them announceable.
1261
- function Message() {
1262
- return <span>Nothing like that here</span>;
1263
- }
1264
- render(
1265
- <MultiSelector
1266
- label="Fruit"
1267
- options={defaultOptions}
1268
- value={[]}
1269
- onChange={() => {}}
1270
- hasSearch
1271
- emptySearchText={<Message />}
1272
- />,
1273
- );
1274
-
1275
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1276
- await enterQuery(user, screen.getByRole('combobox', h), 'xyz');
1277
-
1278
- await waitFor(() =>
1279
- expect(politeRegion()?.textContent).toBe('Nothing like that here'),
1280
- );
1281
- });
1282
-
1283
- it('announces the empty state when opened with no options', async () => {
1284
- const user = userEvent.setup();
1285
- render(
1286
- <MultiSelector
1287
- label="Fruit"
1288
- options={[]}
1289
- value={[]}
1290
- onChange={() => {}}
1291
- emptyText="Add a fruit first"
1292
- />,
1293
- );
1294
-
1295
- await user.click(screen.getByRole('combobox', {name: 'Fruit'}));
1296
-
1297
- await waitFor(() =>
1298
- expect(politeRegion()?.textContent).toBe('Add a fruit first'),
1299
- );
1300
- });
1301
-
1302
- it('shows and announces nothing while isLoading', async () => {
1303
- const user = userEvent.setup();
1304
- render(
1305
- <MultiSelector
1306
- label="Fruit"
1307
- options={[]}
1308
- value={[]}
1309
- onChange={() => {}}
1310
- isLoading
1311
- />,
1312
- );
1313
-
1314
- const trigger = screen.getByRole('combobox', {name: 'Fruit'});
1315
- await user.click(trigger);
1316
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
1317
-
1318
- // useAnnounce writes on the next animation frame, so an immediate read
1319
- // would pass whether or not anything was announced.
1320
- await act(
1321
- async () =>
1322
- void (await new Promise(resolve => requestAnimationFrame(resolve))),
1323
- );
1324
-
1325
- // The options have not arrived, so "No options" would be a claim the
1326
- // component cannot make; the trigger's spinner carries the state.
1327
- const listbox = screen.getByRole('listbox', h);
1328
- expect(within(listbox).queryByText('No options')).not.toBeInTheDocument();
1329
- // Only the busy start is spoken (spec:AST-086 FR8), never an empty or
1330
- // no-results message the panel is suppressing.
1331
- // (The channel clears its text on a timer, so it may already be empty.)
1332
- expect(['', 'Loading']).toContain(politeRegion()?.textContent ?? '');
1333
- });
1334
-
1335
- it('announces nothing while isLoading, matching the silent panel', async () => {
1336
- const user = userEvent.setup();
1337
- render(
1338
- <MultiSelector
1339
- label="Fruit"
1340
- options={defaultOptions}
1341
- value={[]}
1342
- onChange={() => {}}
1343
- hasSearch
1344
- isLoading
1345
- emptySearchText="Nothing like that here"
1346
- />,
1347
- );
1348
-
1349
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1350
- await user.type(screen.getByRole('combobox', h), 'xyz');
1351
-
1352
- // useAnnounce writes on the next animation frame, so an immediate read
1353
- // would pass whether or not anything was announced.
1354
- await act(
1355
- async () =>
1356
- void (await new Promise(resolve => requestAnimationFrame(resolve))),
1357
- );
1358
-
1359
- // The panel is deliberately blank while loading; the live region is the
1360
- // only channel left, so a result there would be a claim the screen
1361
- // refuses to make.
1362
- const listbox = screen.getByRole('listbox', h);
1363
- expect(
1364
- within(listbox).queryByText('Nothing like that here'),
1365
- ).not.toBeInTheDocument();
1366
- // Only the busy start is spoken (spec:AST-086 FR8), never an empty or
1367
- // no-results message the panel is suppressing.
1368
- // (The channel clears its text on a timer, so it may already be empty.)
1369
- expect(['', 'Loading']).toContain(politeRegion()?.textContent ?? '');
1370
- });
1371
-
1372
- it('renders emptyText when there are no options and no search input', async () => {
1373
- const user = userEvent.setup();
1374
- render(
1375
- <MultiSelector
1376
- label="Fruit"
1377
- options={[]}
1378
- value={[]}
1379
- onChange={() => {}}
1380
- emptyText="Add a fruit first"
1381
- />,
1382
- );
1383
-
1384
- // Without hasSearch the trigger itself is the combobox.
1385
- await user.click(screen.getByRole('combobox', {name: 'Fruit'}));
1386
-
1387
- const listbox = screen.getByRole('listbox', h);
1388
- expect(within(listbox).getByText('Add a fruit first')).toBeInTheDocument();
1389
- });
1390
-
1391
- describe('result announcements', () => {
1392
- it('announces the match count politely while searching', async () => {
1393
- const user = userEvent.setup();
1394
- render(
1395
- <InternationalizationProvider
1396
- locale="fr"
1397
- overrides={{
1398
- fr: {
1399
- '@astryx.multiSelector.resultCount':
1400
- '{count, number} {count, plural, one {résultat} other {résultats}}',
1401
- },
1402
- }}>
1403
- <MultiSelector
1404
- label="Fruit"
1405
- options={defaultOptions}
1406
- value={EMPTY_VALUE}
1407
- onChange={() => {}}
1408
- hasSearch
1409
- />
1410
- </InternationalizationProvider>,
1411
- );
1412
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1413
- // "an" matches Banana and Orange.
1414
- await enterQuery(user, screen.getByRole('combobox', h), 'an');
1415
- await waitFor(() => {
1416
- // The plural branch of a message no catalog supplies.
1417
- expect(politeRegion()?.textContent).toBe('2 résultats');
1418
- });
1419
- });
1420
-
1421
- it('announces the singular form when one option matches', async () => {
1422
- const user = userEvent.setup();
1423
- render(
1424
- <InternationalizationProvider
1425
- locale="fr"
1426
- overrides={{
1427
- fr: {
1428
- '@astryx.multiSelector.resultCount':
1429
- '{count, number} {count, plural, one {résultat} other {résultats}}',
1430
- },
1431
- }}>
1432
- <MultiSelector
1433
- label="Fruit"
1434
- options={defaultOptions}
1435
- value={EMPTY_VALUE}
1436
- onChange={() => {}}
1437
- hasSearch
1438
- />
1439
- </InternationalizationProvider>,
1440
- );
1441
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1442
- // "app" matches only Apple. Exact match, so "1 résultats" would fail.
1443
- await enterQuery(user, screen.getByRole('combobox', h), 'app');
1444
- await waitFor(() => {
1445
- expect(politeRegion()?.textContent).toBe('1 résultat');
1446
- });
1447
- });
1448
-
1449
- it('announces the empty-results message when nothing matches', async () => {
1450
- const user = userEvent.setup();
1451
- render(
1452
- <InternationalizationProvider
1453
- locale="fr"
1454
- overrides={{
1455
- fr: {'@astryx.multiSelector.emptySearchResults': 'Aucun résultat'},
1456
- }}>
1457
- <MultiSelector
1458
- label="Fruit"
1459
- options={defaultOptions}
1460
- value={EMPTY_VALUE}
1461
- onChange={() => {}}
1462
- hasSearch
1463
- />
1464
- </InternationalizationProvider>,
1465
- );
1466
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1467
- await enterQuery(user, screen.getByRole('combobox', h), 'xyz');
1468
- await waitFor(() => {
1469
- expect(politeRegion()?.textContent).toBe('Aucun résultat');
1470
- });
1471
- });
1472
-
1473
- it('speaks the result count from a provider catalog (plural)', async () => {
1474
- const user = userEvent.setup();
1475
- render(
1476
- <InternationalizationProvider
1477
- locale="fr"
1478
- messages={{
1479
- fr: {
1480
- '@astryx.multiSelector.resultCount': {
1481
- defaultMessage:
1482
- '{count, number} {count, plural, one {résultat} other {résultats}}',
1483
- },
1484
- },
1485
- }}>
1486
- <MultiSelector
1487
- label="Fruit"
1488
- options={defaultOptions}
1489
- value={EMPTY_VALUE}
1490
- onChange={() => {}}
1491
- hasSearch
1492
- />
1493
- </InternationalizationProvider>,
1494
- );
1495
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1496
- // "an" matches Banana and Orange.
1497
- await enterQuery(user, screen.getByRole('combobox', h), 'an');
1498
- await waitFor(() => {
1499
- // Same key through the catalog path rather than `overrides`.
1500
- expect(politeRegion()?.textContent).toBe('2 résultats');
1501
- });
1502
- });
1503
-
1504
- it('does not announce results until the user searches', async () => {
1505
- const user = userEvent.setup();
1506
- render(
1507
- <MultiSelector
1508
- label="Fruit"
1509
- options={defaultOptions}
1510
- value={EMPTY_VALUE}
1511
- onChange={() => {}}
1512
- hasSearch
1513
- />,
1514
- );
1515
- // Popover closed: nothing announced.
1516
- expect(politeRegion()?.textContent ?? '').toBe('');
1517
- // Open with an empty query: still nothing announced.
1518
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
1519
- expect(politeRegion()?.textContent ?? '').toBe('');
1520
- });
1521
- });
1522
-
1523
- it('renders with description', () => {
1524
- render(
1525
- <MultiSelector
1526
- label="Fruit"
1527
- description="Choose your fruits"
1528
- options={defaultOptions}
1529
- value={[]}
1530
- onChange={() => {}}
1531
- />,
1532
- );
1533
- expect(screen.getByText('Choose your fruits')).toBeInTheDocument();
1534
- });
1535
-
1536
- it('supports data-testid', () => {
1537
- render(
1538
- <MultiSelector
1539
- label="Fruit"
1540
- options={defaultOptions}
1541
- value={[]}
1542
- onChange={() => {}}
1543
- data-testid="fruit-selector"
1544
- />,
1545
- );
1546
- expect(screen.getByTestId('fruit-selector')).toBeInTheDocument();
1547
- });
1548
-
1549
- it('renders sections with dividers', async () => {
1550
- const user = userEvent.setup();
1551
- render(
1552
- <MultiSelector
1553
- label="Fruit"
1554
- options={[
1555
- {value: 'apple', label: 'Apple'},
1556
- {
1557
- type: 'section',
1558
- title: 'Citrus',
1559
- options: [
1560
- {value: 'orange', label: 'Orange'},
1561
- {value: 'lemon', label: 'Lemon'},
1562
- ],
1563
- },
1564
- ]}
1565
- value={[]}
1566
- onChange={() => {}}
1567
- />,
1568
- );
1569
-
1570
- await user.click(screen.getByRole('combobox'));
1571
- const options = screen.getAllByRole('option', h);
1572
- expect(options).toHaveLength(3);
1573
- expect(screen.getByText('Citrus')).toBeInTheDocument();
1574
- });
1575
-
1576
- it('shows loading state with a spinner and aria-busy', () => {
1577
- render(
1578
- <MultiSelector
1579
- label="Fruit"
1580
- options={defaultOptions}
1581
- value={[]}
1582
- onChange={() => {}}
1583
- isLoading
1584
- />,
1585
- );
1586
- expect(screen.getByRole('combobox')).toHaveAttribute('aria-busy', 'true');
1587
- expectSilentBusySpinner();
1588
- });
1589
-
1590
- it('renders with custom selectAllLabel', async () => {
1591
- const user = userEvent.setup();
1592
- render(
1593
- <MultiSelector
1594
- label="Fruit"
1595
- options={defaultOptions}
1596
- value={[]}
1597
- onChange={() => {}}
1598
- hasSelectAll
1599
- selectAllLabel="Check all"
1600
- />,
1601
- );
1602
-
1603
- await user.click(screen.getByRole('combobox'));
1604
- expect(screen.getByText('Check all')).toBeInTheDocument();
1605
- });
1606
-
1607
- it('sorts selected items to top', async () => {
1608
- const user = userEvent.setup();
1609
- render(
1610
- <MultiSelector
1611
- label="Fruit"
1612
- options={['Apple', 'Banana', 'Orange']}
1613
- value={['Orange']}
1614
- onChange={() => {}}
1615
- />,
1616
- );
1617
-
1618
- await user.click(screen.getByRole('combobox'));
1619
- const options = screen.getAllByRole('option', h);
1620
- // Orange is selected so it should appear first
1621
- expect(options[0]).toHaveAttribute('aria-selected', 'true');
1622
- expect(options[0]).toHaveTextContent('Orange');
1623
- expect(options[1]).toHaveTextContent('Apple');
1624
- expect(options[2]).toHaveTextContent('Banana');
1625
- });
1626
-
1627
- it('sorts selected items to top within sections', async () => {
1628
- const user = userEvent.setup();
1629
- render(
1630
- <MultiSelector
1631
- label="Fruit"
1632
- options={[
1633
- {
1634
- type: 'section',
1635
- title: 'Citrus',
1636
- options: [
1637
- {value: 'orange', label: 'Orange'},
1638
- {value: 'lemon', label: 'Lemon'},
1639
- {value: 'lime', label: 'Lime'},
1640
- ],
1641
- },
1642
- ]}
1643
- value={['lime']}
1644
- onChange={() => {}}
1645
- />,
1646
- );
1647
-
1648
- await user.click(screen.getByRole('combobox'));
1649
- const options = screen.getAllByRole('option', h);
1650
- // Lime is selected so it should appear first within the section
1651
- expect(options[0]).toHaveTextContent('Lime');
1652
- expect(options[1]).toHaveTextContent('Orange');
1653
- expect(options[2]).toHaveTextContent('Lemon');
1654
- });
1655
-
1656
- it('has displayName', () => {
1657
- expect(MultiSelector.displayName).toBe('MultiSelector');
1658
- });
1659
-
1660
- describe('keyboard accessibility', () => {
1661
- it('trigger is focusable via Tab when enabled', async () => {
1662
- const user = userEvent.setup();
1663
- render(
1664
- <MultiSelector
1665
- label="Fruit"
1666
- options={defaultOptions}
1667
- value={[]}
1668
- onChange={() => {}}
1669
- />,
1670
- );
1671
- await user.tab();
1672
- expect(screen.getByRole('combobox')).toHaveFocus();
1673
- });
1674
-
1675
- it('trigger keeps its tab stop when disabled (spec:AST-085 FR1)', () => {
1676
- render(
1677
- <MultiSelector
1678
- label="Fruit"
1679
- options={defaultOptions}
1680
- value={[]}
1681
- onChange={() => {}}
1682
- isDisabled
1683
- />,
1684
- );
1685
- expect(screen.getByRole('combobox')).toHaveAttribute('tabIndex', '0');
1686
- });
1687
-
1688
- it('opens the listbox with ArrowDown from a focused trigger', async () => {
1689
- const user = userEvent.setup();
1690
- render(
1691
- <MultiSelector
1692
- label="Fruit"
1693
- options={defaultOptions}
1694
- value={[]}
1695
- onChange={() => {}}
1696
- />,
1697
- );
1698
- const trigger = screen.getByRole('combobox');
1699
- await user.tab();
1700
- expect(trigger).toHaveFocus();
1701
- await user.keyboard('{ArrowDown}');
1702
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
1703
- });
1704
-
1705
- it('clear button is reachable by keyboard', () => {
1706
- render(
1707
- <MultiSelector
1708
- label="Fruit"
1709
- options={defaultOptions}
1710
- value={['Apple', 'Banana']}
1711
- onChange={() => {}}
1712
- hasClear
1713
- />,
1714
- );
1715
- const clear = screen.getByRole('button', {name: 'Clear all Fruit'});
1716
- expect(clear).not.toHaveAttribute('tabIndex', '-1');
1717
- });
1718
-
1719
- it('returns focus to the trigger when the clear button is activated by keyboard', async () => {
1720
- function Controlled() {
1721
- const [value, setValue] = useState(['Apple', 'Banana']);
1722
- return (
1723
- <MultiSelector
1724
- label="Fruit"
1725
- options={defaultOptions}
1726
- value={value}
1727
- onChange={setValue}
1728
- hasClear
1729
- />
1730
- );
1731
- }
1732
- const user = userEvent.setup();
1733
- render(<Controlled />);
1734
- screen.getByRole('button', {name: 'Clear all Fruit'}).focus();
1735
- await user.keyboard('{Enter}');
1736
- expect(
1737
- screen.queryByRole('button', {name: 'Clear all Fruit'}),
1738
- ).not.toBeInTheDocument();
1739
- expect(screen.getByRole('combobox')).toHaveFocus();
1740
- });
1741
-
1742
- it('leaves focus in the search input when the clear button is clicked with the list open', async () => {
1743
- function Controlled() {
1744
- const [value, setValue] = useState(['Apple', 'Banana']);
1745
- return (
1746
- <MultiSelector
1747
- label="Fruit"
1748
- options={defaultOptions}
1749
- value={value}
1750
- onChange={setValue}
1751
- hasClear
1752
- hasSearch
1753
- />
1754
- );
1755
- }
1756
- const user = userEvent.setup();
1757
- const {container} = render(<Controlled />);
1758
- await user.click(container.querySelector('[aria-haspopup]') as Element);
1759
- const search = screen.getByRole('combobox', h);
1760
- search.focus();
1761
- await user.click(screen.getByRole('button', {name: 'Clear all Fruit'}));
1762
- expect(
1763
- screen.queryByRole('button', {name: 'Clear all Fruit'}),
1764
- ).not.toBeInTheDocument();
1765
- expect(search).toHaveFocus();
1766
- });
1767
-
1768
- it('scrolls the highlighted option into view during arrow navigation', async () => {
1769
- const scrollIntoView = vi.fn();
1770
- Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
1771
- configurable: true,
1772
- value: scrollIntoView,
1773
- });
1774
- try {
1775
- const user = userEvent.setup();
1776
- const longOptions = Array.from(
1777
- {length: 20},
1778
- (_, i) => `Option ${i + 1}`,
1779
- );
1780
- render(
1781
- <MultiSelector
1782
- label="Fruit"
1783
- options={longOptions}
1784
- value={[]}
1785
- onChange={() => {}}
1786
- />,
1787
- );
1788
-
1789
- const trigger = screen.getByRole('combobox');
1790
- await user.click(trigger);
1791
- scrollIntoView.mockClear();
1792
- await user.keyboard('{ArrowDown}');
1793
- await user.keyboard('{ArrowDown}');
1794
-
1795
- expect(scrollIntoView).toHaveBeenCalledWith({block: 'nearest'});
1796
- } finally {
1797
- delete (HTMLElement.prototype as unknown as {scrollIntoView?: unknown})
1798
- .scrollIntoView;
1799
- }
1800
- });
1801
-
1802
- it('highlights on hover without scrolling, keyboard still scrolls (#6077)', async () => {
1803
- // Hover must highlight only: scrollIntoView under a stationary pointer
1804
- // moves the next option under it, re-highlighting and scrolling again —
1805
- // a runaway auto-scroll loop with no user input.
1806
- const scrollIntoView = vi.fn();
1807
- Object.defineProperty(HTMLElement.prototype, 'scrollIntoView', {
1808
- configurable: true,
1809
- value: scrollIntoView,
1810
- });
1811
- try {
1812
- const user = userEvent.setup();
1813
- const longOptions = Array.from(
1814
- {length: 20},
1815
- (_, i) => `Option ${i + 1}`,
1816
- );
1817
- render(
1818
- <MultiSelector
1819
- label="Fruit"
1820
- options={longOptions}
1821
- value={[]}
1822
- onChange={() => {}}
1823
- />,
1824
- );
1825
-
1826
- const trigger = screen.getByRole('combobox');
1827
- await user.click(trigger);
1828
- scrollIntoView.mockClear();
1829
-
1830
- const options = screen.getAllByRole('option', {hidden: true});
1831
- fireEvent.mouseEnter(options[3]);
1832
-
1833
- expect(trigger.getAttribute('aria-activedescendant')).toBe(
1834
- options[3].id,
1835
- );
1836
- expect(scrollIntoView).not.toHaveBeenCalled();
1837
-
1838
- await user.keyboard('{ArrowDown}');
1839
- expect(scrollIntoView).toHaveBeenCalledWith({block: 'nearest'});
1840
- } finally {
1841
- delete (HTMLElement.prototype as unknown as {scrollIntoView?: unknown})
1842
- .scrollIntoView;
1843
- }
1844
- });
1845
-
1846
- it('clears all values via Delete on the focused trigger', async () => {
1847
- const user = userEvent.setup();
1848
- const onChange = vi.fn();
1849
- render(
1850
- <MultiSelector
1851
- label="Fruit"
1852
- options={defaultOptions}
1853
- value={['Apple', 'Banana']}
1854
- onChange={onChange}
1855
- hasClear
1856
- />,
1857
- );
1858
- const trigger = screen.getByRole('combobox');
1859
- trigger.focus();
1860
- await user.keyboard('{Delete}');
1861
- expect(onChange).toHaveBeenCalledWith([]);
1862
- });
1863
-
1864
- it('clears all values via Backspace on the focused trigger', async () => {
1865
- const user = userEvent.setup();
1866
- const onChange = vi.fn();
1867
- render(
1868
- <MultiSelector
1869
- label="Fruit"
1870
- options={defaultOptions}
1871
- value={['Apple', 'Banana']}
1872
- onChange={onChange}
1873
- hasClear
1874
- />,
1875
- );
1876
- const trigger = screen.getByRole('combobox');
1877
- trigger.focus();
1878
- await user.keyboard('{Backspace}');
1879
- expect(onChange).toHaveBeenCalledWith([]);
1880
- });
1881
-
1882
- it('does not clear via Delete when nothing is selected', async () => {
1883
- const user = userEvent.setup();
1884
- const onChange = vi.fn();
1885
- render(
1886
- <MultiSelector
1887
- label="Fruit"
1888
- options={defaultOptions}
1889
- value={[]}
1890
- onChange={onChange}
1891
- hasClear
1892
- />,
1893
- );
1894
- const trigger = screen.getByRole('combobox');
1895
- trigger.focus();
1896
- await user.keyboard('{Delete}');
1897
- expect(onChange).not.toHaveBeenCalled();
1898
- });
1899
- });
1900
-
1901
- describe('announcements', () => {
1902
- it('announces the selection count politely when toggling an option', async () => {
1903
- const user = userEvent.setup();
1904
- render(
1905
- <InternationalizationProvider
1906
- locale="fr"
1907
- overrides={{
1908
- fr: {
1909
- '@astryx.multiSelector.selectionCount':
1910
- '{count, number} sur {total, number} sélectionnés',
1911
- },
1912
- }}>
1913
- <MultiSelector
1914
- label="Fruit"
1915
- options={[...ANNOUNCE_OPTIONS]}
1916
- value={EMPTY_VALUE}
1917
- onChange={() => {}}
1918
- />
1919
- </InternationalizationProvider>,
1920
- );
1921
- await user.click(screen.getByRole('combobox'));
1922
- const options = screen.getAllByRole('option', {hidden: true});
1923
- await user.click(options[0]);
1924
- await waitFor(() => {
1925
- // Both arguments land, in the order the message asks for them.
1926
- expect(politeRegion()?.textContent).toBe('1 sur 3 sélectionnés');
1927
- });
1928
- });
1929
-
1930
- it('announces the all-selected message when select-all selects everything', async () => {
1931
- const user = userEvent.setup();
1932
- render(
1933
- <InternationalizationProvider
1934
- locale="fr"
1935
- overrides={{
1936
- fr: {'@astryx.multiSelector.allSelected': 'Tout est là'},
1937
- }}>
1938
- <MultiSelector
1939
- label="Fruit"
1940
- options={[...ANNOUNCE_OPTIONS]}
1941
- value={EMPTY_VALUE}
1942
- onChange={() => {}}
1943
- hasSelectAll
1944
- />
1945
- </InternationalizationProvider>,
1946
- );
1947
- await user.click(screen.getByRole('combobox'));
1948
- await user.click(screen.getByText('Select all'));
1949
- await waitFor(() => {
1950
- expect(politeRegion()?.textContent).toBe('Tout est là');
1951
- });
1952
- });
1953
-
1954
- it('announces the selection-cleared message when clearing', async () => {
1955
- const user = userEvent.setup();
1956
- render(
1957
- <InternationalizationProvider
1958
- locale="fr"
1959
- overrides={{
1960
- fr: {'@astryx.multiSelector.selectionCleared': 'Plus rien'},
1961
- }}>
1962
- <MultiSelector
1963
- label="Fruit"
1964
- options={[...ANNOUNCE_OPTIONS]}
1965
- value={['Apple', 'Banana']}
1966
- onChange={() => {}}
1967
- hasClear
1968
- />
1969
- </InternationalizationProvider>,
1970
- );
1971
- await user.click(screen.getByRole('button', {name: 'Clear all Fruit'}));
1972
- await waitFor(() => {
1973
- expect(politeRegion()?.textContent).toBe('Plus rien');
1974
- });
1975
- });
1976
-
1977
- it('prefers a provider override over the provider catalog', async () => {
1978
- const user = userEvent.setup();
1979
- render(
1980
- <InternationalizationProvider
1981
- locale="fr"
1982
- messages={{
1983
- fr: {
1984
- '@astryx.multiSelector.selectionCount': {
1985
- defaultMessage: '{count, number} du catalogue',
1986
- },
1987
- },
1988
- }}
1989
- overrides={{
1990
- fr: {
1991
- '@astryx.multiSelector.selectionCount':
1992
- '{count, number} remplacé',
1993
- },
1994
- }}>
1995
- <MultiSelector
1996
- label="Fruit"
1997
- options={[...ANNOUNCE_OPTIONS]}
1998
- value={EMPTY_VALUE}
1999
- onChange={() => {}}
2000
- />
2001
- </InternationalizationProvider>,
2002
- );
2003
- await user.click(screen.getByRole('combobox'));
2004
- const options = screen.getAllByRole('option', {hidden: true});
2005
- await user.click(options[0]);
2006
- await waitFor(() => {
2007
- expect(politeRegion()?.textContent).toBe('1 remplacé');
2008
- });
2009
- });
2010
- });
2011
-
2012
- describe('disabledMessage', () => {
2013
- it('shows the reason tooltip on hover when disabled with a reason', async () => {
2014
- render(
2015
- <MultiSelector
2016
- label="Fruit"
2017
- options={defaultOptions}
2018
- value={[]}
2019
- onChange={() => {}}
2020
- isDisabled
2021
- disabledMessage="Select a table first"
2022
- data-testid="fruit-multi-selector"
2023
- />,
2024
- );
2025
-
2026
- // data-testid lands on the trigger; the reason tooltip anchors to the
2027
- // control box around it.
2028
- const container = screen
2029
- .getByTestId('fruit-multi-selector')
2030
- .closest('.astryx-multi-selector') as HTMLElement;
2031
- const tooltip = screen.getByRole('tooltip', h);
2032
- expect(tooltip).toHaveTextContent('Select a table first');
2033
-
2034
- fireEvent.mouseEnter(container);
2035
- await waitFor(() => {
2036
- expect(tooltip).toHaveAttribute('popover-open');
2037
- });
2038
-
2039
- fireEvent.mouseLeave(container);
2040
- await waitFor(() => {
2041
- expect(tooltip).not.toHaveAttribute('popover-open');
2042
- });
2043
- });
2044
-
2045
- it('shows the reason tooltip on keyboard focus', async () => {
2046
- const user = userEvent.setup();
2047
- render(
2048
- <MultiSelector
2049
- label="Fruit"
2050
- options={defaultOptions}
2051
- value={[]}
2052
- onChange={() => {}}
2053
- isDisabled
2054
- disabledMessage="Select a table first"
2055
- />,
2056
- );
2057
-
2058
- const tooltip = screen.getByRole('tooltip', h);
2059
- await user.tab();
2060
- expect(screen.getByRole('combobox')).toHaveFocus();
2061
- await waitFor(() => {
2062
- expect(tooltip).toHaveAttribute('popover-open');
2063
- });
2064
- });
2065
-
2066
- it('does not render a tooltip when not disabled', () => {
2067
- render(
2068
- <MultiSelector
2069
- label="Fruit"
2070
- options={defaultOptions}
2071
- value={[]}
2072
- onChange={() => {}}
2073
- disabledMessage="Select a table first"
2074
- />,
2075
- );
2076
- expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
2077
- });
2078
-
2079
- it('does not render a tooltip when disabled without a reason', () => {
2080
- render(
2081
- <MultiSelector
2082
- label="Fruit"
2083
- options={defaultOptions}
2084
- value={[]}
2085
- onChange={() => {}}
2086
- isDisabled
2087
- />,
2088
- );
2089
- expect(screen.queryByRole('tooltip', h)).not.toBeInTheDocument();
2090
- });
2091
-
2092
- it('keeps the trigger focusable via aria-disabled when a reason is provided', () => {
2093
- render(
2094
- <MultiSelector
2095
- label="Fruit"
2096
- options={defaultOptions}
2097
- value={[]}
2098
- onChange={() => {}}
2099
- isDisabled
2100
- disabledMessage="Select a table first"
2101
- />,
2102
- );
2103
- const trigger = screen.getByRole('combobox');
2104
- expect(trigger).not.toBeDisabled();
2105
- expect(trigger).toHaveAttribute('aria-disabled', 'true');
2106
- expect(trigger).toHaveAttribute('tabIndex', '0');
2107
- });
2108
-
2109
- it('links the reason tooltip from the trigger via aria-describedby', () => {
2110
- render(
2111
- <MultiSelector
2112
- label="Fruit"
2113
- options={defaultOptions}
2114
- value={[]}
2115
- onChange={() => {}}
2116
- isDisabled
2117
- disabledMessage="Select a table first"
2118
- />,
2119
- );
2120
- const trigger = screen.getByRole('combobox');
2121
- const tooltip = screen.getByRole('tooltip', h);
2122
- expect(trigger.getAttribute('aria-describedby')).toContain(tooltip.id);
2123
- });
2124
-
2125
- it('blocks activation while focusable-disabled', async () => {
2126
- const user = userEvent.setup();
2127
- const onChange = vi.fn();
2128
- render(
2129
- <MultiSelector
2130
- label="Fruit"
2131
- options={defaultOptions}
2132
- value={[]}
2133
- onChange={onChange}
2134
- isDisabled
2135
- disabledMessage="Select a table first"
2136
- />,
2137
- );
2138
-
2139
- const trigger = screen.getByRole('combobox');
2140
- await user.click(trigger);
2141
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
2142
-
2143
- await user.keyboard('{Enter}');
2144
- await user.keyboard('{ArrowDown}');
2145
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
2146
- expect(onChange).not.toHaveBeenCalled();
2147
- });
2148
-
2149
- it('stays focusable when disabled without a reason (spec:AST-085 FR1)', () => {
2150
- render(
2151
- <MultiSelector
2152
- label="Fruit"
2153
- options={defaultOptions}
2154
- value={[]}
2155
- onChange={() => {}}
2156
- isDisabled
2157
- />,
2158
- );
2159
- const trigger = screen.getByRole('combobox');
2160
- expect(trigger).not.toHaveAttribute('disabled');
2161
- expect(trigger).toHaveAttribute('aria-disabled', 'true');
2162
- expect(trigger).toHaveAttribute('tabIndex', '0');
2163
- });
2164
- });
2165
- describe('isReadOnly', () => {
2166
- const readOnlyCases = [
2167
- {presentation: 'popover', hasSearch: false},
2168
- {presentation: 'popover', hasSearch: true},
2169
- {presentation: 'bottom-sheet', hasSearch: false},
2170
- {presentation: 'bottom-sheet', hasSearch: true},
2171
- {presentation: 'adaptive', hasSearch: false},
2172
- {presentation: 'adaptive', hasSearch: true},
2173
- ] as const;
2174
-
2175
- const sheetTransitionCases = [
2176
- {presentation: 'bottom-sheet', hasSearch: false},
2177
- {presentation: 'bottom-sheet', hasSearch: true},
2178
- {presentation: 'adaptive', hasSearch: false},
2179
- {presentation: 'adaptive', hasSearch: true},
2180
- ] as const;
2181
-
2182
- const disabledPrecedenceCases = (
2183
- ['popover', 'bottom-sheet', 'adaptive'] as const
2184
- ).flatMap(presentation =>
2185
- [false, true].flatMap(hasSearch =>
2186
- [false, true].map(hasDisabledMessage => ({
2187
- presentation,
2188
- hasSearch,
2189
- hasDisabledMessage,
2190
- })),
2191
- ),
2192
- );
2193
-
2194
- function stubCompactTouch(presentation: 'bottom-sheet' | 'adaptive') {
2195
- if (presentation !== 'adaptive') {
2196
- return;
2197
- }
2198
- vi.mocked(matchMedia).mockImplementation(
2199
- (query: string) =>
2200
- ({
2201
- matches: query === COMPACT_TOUCH_PRESENTATION_QUERY,
2202
- media: query,
2203
- onchange: null,
2204
- addEventListener: vi.fn(),
2205
- removeEventListener: vi.fn(),
2206
- addListener: vi.fn(),
2207
- removeListener: vi.fn(),
2208
- dispatchEvent: vi.fn(),
2209
- }) as MediaQueryList,
2210
- );
2211
- }
2212
-
2213
- it.each(readOnlyCases)(
2214
- 'uses a read-only combobox with no rendered popup ($presentation, search=$hasSearch)',
2215
- async ({presentation, hasSearch}) => {
2216
- const user = userEvent.setup();
2217
- const {container} = render(
2218
- <form>
2219
- <MultiSelector
2220
- label="Fruit"
2221
- htmlName="fruit"
2222
- options={defaultOptions}
2223
- value={['Apple', 'Banana']}
2224
- onChange={() => {}}
2225
- hasClear
2226
- hasSearch={hasSearch}
2227
- presentation={presentation}
2228
- isDefaultOpen
2229
- triggerDisplay="labels"
2230
- isReadOnly
2231
- />
2232
- </form>,
2233
- );
2234
-
2235
- const trigger = screen.getByRole('combobox', {name: 'Fruit'});
2236
- expect(trigger.tagName).toBe('BUTTON');
2237
- expect(trigger).toHaveTextContent('Apple, Banana');
2238
- expect(trigger).toHaveAttribute('aria-readonly', 'true');
2239
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
2240
- const readOnlyDescription = screen.getByText('Read only');
2241
- expect(trigger.getAttribute('aria-describedby')).toContain(
2242
- readOnlyDescription.id,
2243
- );
2244
- expect(trigger).not.toHaveAttribute('aria-controls');
2245
- expect(trigger).not.toHaveAttribute('aria-haspopup');
2246
- expect(screen.getAllByRole('combobox', h)).toHaveLength(1);
2247
- expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
2248
- expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
2249
- expect(
2250
- screen.queryByRole('button', {name: 'Clear all Fruit'}),
2251
- ).not.toBeInTheDocument();
2252
- expect(
2253
- container.querySelector('.astryx-multi-selector-indicator-icon'),
2254
- ).toBeNull();
2255
- expect(
2256
- container.querySelector('.astryx-multi-selector'),
2257
- ).toHaveAttribute('data-readonly', 'readonly');
2258
- expect(
2259
- new FormData(container.querySelector('form')!).getAll('fruit'),
2260
- ).toEqual(['Apple', 'Banana']);
2261
-
2262
- await user.tab();
2263
- expect(trigger).toHaveFocus();
2264
- },
2265
- );
2266
-
2267
- it('keeps loading feedback while blocking the selection surface', async () => {
2268
- const user = userEvent.setup();
2269
- render(
2270
- <MultiSelector
2271
- label="Fruit"
2272
- options={defaultOptions}
2273
- value={['Apple']}
2274
- onChange={() => {}}
2275
- isLoading
2276
- isReadOnly
2277
- />,
2278
- );
2279
-
2280
- const trigger = screen.getByRole('combobox', {name: 'Fruit'});
2281
- expect(trigger).toHaveAttribute('aria-busy', 'true');
2282
- expectSilentBusySpinner();
2283
-
2284
- await user.click(trigger);
2285
- expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
2286
- });
2287
-
2288
- it('blocks pointer, keyboard, and typeahead changes', async () => {
2289
- const user = userEvent.setup();
2290
- const onChange = vi.fn();
2291
- const changeAction = vi.fn();
2292
- render(
2293
- <MultiSelector
2294
- label="Fruit"
2295
- options={defaultOptions}
2296
- value={['Apple']}
2297
- onChange={onChange}
2298
- changeAction={changeAction}
2299
- isReadOnly
2300
- />,
2301
- );
2302
-
2303
- const trigger = screen.getByRole('combobox');
2304
- await user.click(trigger);
2305
- trigger.focus();
2306
- await user.keyboard('{Enter}{ArrowDown}b{Backspace}');
2307
-
2308
- expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
2309
- expect(onChange).not.toHaveBeenCalled();
2310
- expect(changeAction).not.toHaveBeenCalled();
2311
- });
2312
-
2313
- it.each([false, true])(
2314
- 'restores popover focus when an open selector becomes read-only (search=%s)',
2315
- async hasSearch => {
2316
- const user = userEvent.setup();
2317
- const selector = (isReadOnly: boolean) => (
2318
- <MultiSelector
2319
- label="Fruit"
2320
- options={defaultOptions}
2321
- value={['Apple']}
2322
- onChange={() => {}}
2323
- hasSearch={hasSearch}
2324
- isReadOnly={isReadOnly}
2325
- />
2326
- );
2327
- const {rerender} = render(selector(false));
2328
- const editableTrigger = screen.getByRole(
2329
- hasSearch ? 'button' : 'combobox',
2330
- {name: 'Fruit'},
2331
- );
2332
- await user.click(editableTrigger);
2333
- if (hasSearch) {
2334
- await waitFor(() =>
2335
- expect(screen.getByRole('combobox', h)).toHaveFocus(),
2336
- );
2337
- }
2338
-
2339
- rerender(selector(true));
2340
-
2341
- const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
2342
- await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
2343
- expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
2344
- },
2345
- );
2346
-
2347
- it.each(sheetTransitionCases)(
2348
- 'finishes $presentation close and restores focus when read-only changes (search=$hasSearch)',
2349
- async ({presentation, hasSearch}) => {
2350
- stubCompactTouch(presentation);
2351
- const user = userEvent.setup();
2352
- const selector = (isReadOnly: boolean) => (
2353
- <MultiSelector
2354
- label="Fruit"
2355
- options={defaultOptions}
2356
- value={['Apple']}
2357
- onChange={() => {}}
2358
- hasSearch={hasSearch}
2359
- presentation={presentation}
2360
- isReadOnly={isReadOnly}
2361
- />
2362
- );
2363
- const {rerender} = render(selector(false));
2364
- const editableTrigger = screen.getByRole(
2365
- hasSearch ? 'button' : 'combobox',
2366
- {name: 'Fruit'},
2367
- );
2368
- await user.click(editableTrigger);
2369
- const dialog = await screen.findByRole('dialog', {name: 'Fruit'});
2370
- const panel = dialog.querySelector<HTMLElement>('.astryx-bottom-sheet');
2371
- expect(panel).not.toBeNull();
2372
- await waitFor(() =>
2373
- expect(
2374
- hasSearch
2375
- ? screen.getByRole('combobox', h)
2376
- : screen.getByRole('listbox'),
2377
- ).toHaveFocus(),
2378
- );
2379
-
2380
- rerender(selector(true));
2381
-
2382
- await waitFor(() => expect(dialog).toHaveAttribute('inert'));
2383
- fireEvent.transitionEnd(panel!, {propertyName: 'transform'});
2384
- const readOnlyTrigger = screen.getByRole('combobox', {name: 'Fruit'});
2385
- await waitFor(() => expect(readOnlyTrigger).toHaveFocus());
2386
- expect(screen.queryByRole('dialog', h)).not.toBeInTheDocument();
2387
- expect(screen.queryByRole('listbox', h)).not.toBeInTheDocument();
2388
- },
2389
- );
2390
-
2391
- it.each(disabledPrecedenceCases)(
2392
- 'matches disabled-only semantics for $presentation (search=$hasSearch, reason=$hasDisabledMessage)',
2393
- async ({presentation, hasSearch, hasDisabledMessage}) => {
2394
- if (presentation === 'adaptive') {
2395
- stubCompactTouch(presentation);
2396
- }
2397
- const user = userEvent.setup();
2398
- const onChange = vi.fn();
2399
- const selector = (isReadOnly: boolean) => (
2400
- <form>
2401
- <MultiSelector
2402
- label="Fruit"
2403
- htmlName="fruit"
2404
- options={defaultOptions}
2405
- value={['Apple', 'Banana']}
2406
- onChange={onChange}
2407
- hasClear
2408
- hasSearch={hasSearch}
2409
- presentation={presentation}
2410
- isDisabled
2411
- isReadOnly={isReadOnly}
2412
- disabledMessage={
2413
- hasDisabledMessage ? 'Locked by policy' : undefined
2414
- }
2415
- />
2416
- </form>
2417
- );
2418
- const {container, rerender} = render(selector(false));
2419
- const getTrigger = () =>
2420
- screen.getByRole(hasSearch ? 'button' : 'combobox', {name: 'Fruit'});
2421
- const snapshot = () => {
2422
- const trigger = getTrigger();
2423
- const root = container.querySelector('.astryx-multi-selector');
2424
- return {
2425
- tagName: trigger.tagName,
2426
- role: trigger.getAttribute('role'),
2427
- hasPopup: trigger.getAttribute('aria-haspopup'),
2428
- expanded: trigger.getAttribute('aria-expanded'),
2429
- controls: trigger.getAttribute('aria-controls'),
2430
- readOnly: trigger.getAttribute('aria-readonly'),
2431
- disabled: trigger.matches(':disabled'),
2432
- ariaDisabled: trigger.getAttribute('aria-disabled'),
2433
- tabIndex: trigger.tabIndex,
2434
- rootClassName: root?.className,
2435
- rootDisabled: root?.getAttribute('data-disabled'),
2436
- rootReadOnly: root?.getAttribute('data-readonly'),
2437
- hasIndicator:
2438
- root?.querySelector('.astryx-multi-selector-indicator-icon') !=
2439
- null,
2440
- listboxes: screen.queryAllByRole('listbox', h).length,
2441
- dialogs: screen.queryAllByRole('dialog', h).length,
2442
- formEntries: [
2443
- ...new FormData(container.querySelector('form')!).entries(),
2444
- ],
2445
- };
2446
- };
2447
- const disabledOnly = snapshot();
2448
-
2449
- rerender(selector(true));
2450
-
2451
- expect(snapshot()).toEqual(disabledOnly);
2452
- expect(disabledOnly.rootDisabled).toBe('disabled');
2453
- expect(disabledOnly.rootReadOnly).toBeNull();
2454
- expect(disabledOnly.readOnly).toBeNull();
2455
- expect(disabledOnly.formEntries).toEqual([]);
2456
- const trigger = getTrigger();
2457
- await user.click(trigger);
2458
- trigger.focus();
2459
- await user.keyboard('{Enter}{ArrowDown}b{Backspace}');
2460
- expect(onChange).not.toHaveBeenCalled();
2461
- },
2462
- );
2463
- });
2464
-
2465
- describe('form participation', () => {
2466
- it('submits one entry per selected value under htmlName', () => {
2467
- const {container} = render(
2468
- <form>
2469
- <MultiSelector
2470
- label="Fruit"
2471
- htmlName="fruit"
2472
- options={['Apple', 'Banana', 'Orange']}
2473
- value={['Apple', 'Orange']}
2474
- onChange={() => {}}
2475
- />
2476
- </form>,
2477
- );
2478
- const data = new FormData(container.querySelector('form')!);
2479
- expect(data.getAll('fruit')).toEqual(['Apple', 'Orange']);
2480
- });
2481
-
2482
- it('is excluded from form data when disabled', () => {
2483
- const {container} = render(
2484
- <form>
2485
- <MultiSelector
2486
- label="Fruit"
2487
- htmlName="fruit"
2488
- options={['Apple']}
2489
- value={['Apple']}
2490
- onChange={() => {}}
2491
- isDisabled
2492
- />
2493
- </form>,
2494
- );
2495
- expect([
2496
- ...new FormData(container.querySelector('form')!).keys(),
2497
- ]).toEqual([]);
2498
- });
2499
- });
2500
- });
2501
-
2502
- describe('MultiSelector statusVariant forwarding', () => {
2503
- it('defaults to attached (status renders with data-variant="attached")', () => {
2504
- const {container} = render(
2505
- <MultiSelector
2506
- label="Fruit"
2507
- options={['Apple', 'Banana']}
2508
- value={[]}
2509
- onChange={() => {}}
2510
- status={{type: 'error', message: 'Required'}}
2511
- />,
2512
- );
2513
- expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
2514
- 'data-variant',
2515
- 'attached',
2516
- );
2517
- });
2518
-
2519
- it('forwards statusVariant="detached" to the underlying Field status', () => {
2520
- const {container} = render(
2521
- <MultiSelector
2522
- label="Fruit"
2523
- options={['Apple', 'Banana']}
2524
- value={[]}
2525
- onChange={() => {}}
2526
- status={{type: 'error', message: 'Required'}}
2527
- statusVariant="detached"
2528
- />,
2529
- );
2530
- expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
2531
- 'data-variant',
2532
- 'detached',
2533
- );
2534
- });
2535
-
2536
- it('keeps the on-field status icon for the attached variant', () => {
2537
- const {container} = render(
2538
- <MultiSelector
2539
- label="Fruit"
2540
- options={['Apple', 'Banana']}
2541
- value={[]}
2542
- onChange={() => {}}
2543
- status={{type: 'error', message: 'Required'}}
2544
- />,
2545
- );
2546
- // Attached: the status glyph replaces the chevron indicator on the field.
2547
- expect(
2548
- container.querySelector('.astryx-multi-selector-indicator-icon'),
2549
- ).toBeNull();
2550
- });
2551
-
2552
- it('suppresses the on-field status icon for the detached variant', () => {
2553
- const {container} = render(
2554
- <MultiSelector
2555
- label="Fruit"
2556
- options={['Apple', 'Banana']}
2557
- value={[]}
2558
- onChange={() => {}}
2559
- status={{type: 'error', message: 'Required'}}
2560
- statusVariant="detached"
2561
- />,
2562
- );
2563
- // Detached: the message box below carries its own leading icon, so the
2564
- // field keeps its chevron indicator rather than duplicating the glyph.
2565
- expect(
2566
- container.querySelector('.astryx-multi-selector-indicator-icon'),
2567
- ).not.toBeNull();
2568
- });
2569
-
2570
- it.each(['secondary', 'ghost'] as const)(
2571
- 'detaches attached status by default for the %s variant',
2572
- variant => {
2573
- const {container} = render(
2574
- <MultiSelector
2575
- label="Fruit"
2576
- options={['Apple', 'Banana']}
2577
- value={[]}
2578
- onChange={() => {}}
2579
- variant={variant}
2580
- status={{type: 'error', message: 'Required'}}
2581
- />,
2582
- );
2583
- expect(container.querySelector('.astryx-multi-selector')).toHaveAttribute(
2584
- 'data-variant',
2585
- variant,
2586
- );
2587
- expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
2588
- 'data-variant',
2589
- 'detached',
2590
- );
2591
- },
2592
- );
2593
-
2594
- it('uses a status tooltip for ghost multi-selectors when requested', () => {
2595
- const {container} = render(
2596
- <MultiSelector
2597
- label="Fruit"
2598
- options={['Apple', 'Banana']}
2599
- value={[]}
2600
- onChange={() => {}}
2601
- variant="ghost"
2602
- status={{type: 'warning', message: 'Some rows are hidden'}}
2603
- statusVariant="tooltip"
2604
- />,
2605
- );
2606
- expect(container.querySelector('.astryx-field-status')).toBeNull();
2607
- const statusButton = screen.getByRole('button', {
2608
- name: /warning details/i,
2609
- });
2610
- const tooltip = screen.getByRole('tooltip', h);
2611
- expect(tooltip).toHaveTextContent('Some rows are hidden');
2612
- expect(statusButton.getAttribute('aria-describedby')).toContain(tooltip.id);
2613
- expect(
2614
- screen.getByRole('combobox').getAttribute('aria-describedby'),
2615
- ).toContain(tooltip.id);
2616
- });
2617
- });
2618
-
2619
- describe('MultiSelector empty-state theme target', () => {
2620
- const OPTIONS = ['Apple', 'Banana', 'Cherry'];
2621
-
2622
- it('renders the astryx-multi-selector-empty-state target on the "No results found" element', async () => {
2623
- const user = userEvent.setup();
2624
- const {container} = render(
2625
- <MultiSelector
2626
- label="Fruit"
2627
- options={OPTIONS}
2628
- value={[]}
2629
- onChange={() => {}}
2630
- hasSearch
2631
- />,
2632
- );
2633
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
2634
- await user.type(screen.getByRole('combobox', h), 'xyz');
2635
-
2636
- const empty = container.querySelector('.astryx-multi-selector-empty-state');
2637
- expect(empty).not.toBeNull();
2638
- expect(empty).toHaveTextContent('No results found');
2639
- });
2640
- });
2641
-
2642
- describe('MultiSelector clear icon theme target', () => {
2643
- const ICON_OPTIONS = ['Apple', 'Banana', 'Orange'];
2644
-
2645
- // Resolve the clear glyph span (the astryx-icon element inside the clear
2646
- // button), independent of the theme target class.
2647
- const getClearIcon = (): HTMLElement => {
2648
- const button = screen.getByRole('button', {name: 'Clear all Fruit'});
2649
- const icon = button.querySelector('.astryx-icon');
2650
- if (icon == null) {
2651
- throw new Error('clear icon not found');
2652
- }
2653
- return icon as HTMLElement;
2654
- };
2655
-
2656
- it('renders only the astryx-input-clear-icon target on the clear glyph', () => {
2657
- render(
2658
- <MultiSelector
2659
- label="Fruit"
2660
- options={ICON_OPTIONS}
2661
- value={['Banana']}
2662
- onChange={() => {}}
2663
- hasClear
2664
- />,
2665
- );
2666
- // The canonical target lands on the icon element itself (not the button),
2667
- // so a theme can restyle just this glyph (color, size, hover) via
2668
- // `defineTheme` — a button-level target could not reach the icon's own
2669
- // color/size.
2670
- const icon = getClearIcon();
2671
- expect(icon).toHaveClass('astryx-input-clear-icon');
2672
- expect(icon).not.toHaveClass('astryx-multi-selector-clear-icon');
2673
- expect(icon).toHaveClass('astryx-icon');
2674
- });
2675
-
2676
- it('keeps the clear button functional alongside the target', () => {
2677
- const onChange = vi.fn();
2678
- render(
2679
- <MultiSelector
2680
- label="Fruit"
2681
- options={ICON_OPTIONS}
2682
- value={['Banana']}
2683
- onChange={onChange}
2684
- hasClear
2685
- />,
2686
- );
2687
- const clear = screen.getByRole('button', {name: 'Clear all Fruit'});
2688
- expect(clear.tagName).toBe('BUTTON');
2689
- fireEvent.click(clear);
2690
- expect(onChange).toHaveBeenCalledWith([]);
2691
- });
2692
-
2693
- it('routes the clear glyph through the shared clear button', () => {
2694
- // The clear affordance composes the shared InputClearButton (a ghost Button
2695
- // with a secondary/sm glyph), so the icon carries the canonical
2696
- // `astryx-input-clear-icon` target. Aside from that target class it matches
2697
- // the shared button's own `close`/`sm`/`secondary` glyph exactly, so the
2698
- // default look is defined in one place.
2699
- render(
2700
- <MultiSelector
2701
- label="Fruit"
2702
- options={ICON_OPTIONS}
2703
- value={['Banana']}
2704
- onChange={() => {}}
2705
- hasClear
2706
- />,
2707
- );
2708
- const icon = getClearIcon();
2709
- expect(icon).toHaveClass('astryx-input-clear-icon');
2710
- expect(icon).not.toHaveClass('astryx-multi-selector-clear-icon');
2711
-
2712
- const {container: refContainer} = render(
2713
- <Icon icon="close" size="sm" color="secondary" />,
2714
- );
2715
- const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
2716
-
2717
- const styleClasses = (el: HTMLElement) =>
2718
- el.className
2719
- .split(' ')
2720
- .filter(c => c !== 'astryx-input-clear-icon')
2721
- .sort();
2722
-
2723
- expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
2724
- });
2725
-
2726
- it('uses input-clear-icon so a theme reaches the icon color, size, and hover', () => {
2727
- // jsdom cannot resolve the @layer cascade, so the DOM-class assertion above
2728
- // (target lands on the icon element) plus this generation assertion (the
2729
- // theme emits same-element icon rules in @layer astryx-theme) together
2730
- // prove the seam: a same-element theme rule wins over the icon's own
2731
- // base-layer color/size.
2732
- const theme = defineTheme({
2733
- name: 'multi-selector-shared-clear-icon-test',
2734
- components: {
2735
- 'input-clear-icon': {
2736
- base: {
2737
- width: '12px',
2738
- height: '12px',
2739
- fontSize: '12px',
2740
- color: 'var(--color-icon-secondary)',
2741
- ':hover': {color: 'var(--color-icon-primary)'},
2742
- },
2743
- },
2744
- },
2745
- });
2746
- const css = generateThemeTestCSS(theme);
2747
- expect(css).toContain('.astryx-input-clear-icon {');
2748
- expect(css).toContain('width: 12px');
2749
- expect(css).toContain('height: 12px');
2750
- expect(css).toContain('.astryx-input-clear-icon:hover');
2751
- expect(css).toContain('color: var(--color-icon-primary)');
2752
- });
2753
- });
2754
-
2755
- describe('MultiSelector indicator (chevron) icon theme target', () => {
2756
- const ICON_OPTIONS = ['Apple', 'Banana', 'Orange'];
2757
-
2758
- const getIndicatorIcon = (container: HTMLElement): HTMLElement => {
2759
- // The chevron is the only glyph carrying the indicator target class.
2760
- const icon = container.querySelector(
2761
- '.astryx-multi-selector-indicator-icon',
2762
- );
2763
- if (icon == null) {
2764
- throw new Error('indicator icon not found');
2765
- }
2766
- return icon as HTMLElement;
2767
- };
2768
-
2769
- it('renders the astryx-multi-selector-indicator-icon target on the chevron glyph', () => {
2770
- const {container} = render(
2771
- <MultiSelector
2772
- label="Fruit"
2773
- options={ICON_OPTIONS}
2774
- value={[]}
2775
- onChange={() => {}}
2776
- />,
2777
- );
2778
- // The stable theme target lands on the icon element itself (not the trigger
2779
- // button), so a theme can restyle just this glyph (color, size, hover) —
2780
- // and each open/closed state — via `defineTheme`. A button-level target
2781
- // could not reach the icon's own color/size.
2782
- const icon = getIndicatorIcon(container);
2783
- expect(icon).toHaveClass('astryx-multi-selector-indicator-icon');
2784
- expect(icon).toHaveClass('astryx-icon');
2785
- // Open/closed state is reflected so a theme can target each state alone.
2786
- expect(icon).toHaveAttribute('data-state', 'collapsed');
2787
- });
2788
-
2789
- it('reflects the expanded state on the chevron when the popover is open', async () => {
2790
- const user = userEvent.setup();
2791
- const {container} = render(
2792
- <MultiSelector
2793
- label="Fruit"
2794
- options={ICON_OPTIONS}
2795
- value={[]}
2796
- onChange={() => {}}
2797
- />,
2798
- );
2799
- await user.click(screen.getByRole('combobox'));
2800
- await waitFor(() => {
2801
- expect(getIndicatorIcon(container)).toHaveAttribute(
2802
- 'data-state',
2803
- 'expanded',
2804
- );
2805
- });
2806
- });
2807
-
2808
- it('renders the default icon (secondary color, sm size) byte-identically', () => {
2809
- // Pixel-identical default guard: the chevron glyph must carry the exact
2810
- // same StyleX color/size classes as a standalone secondary/sm icon. The
2811
- // glyph now sets --color-icon-secondary itself rather than inheriting it
2812
- // from a wrapper span that set the same token, so the rendered color is
2813
- // unchanged. The added
2814
- // target class + data-state are purely additive — they change nothing until
2815
- // a theme targets them.
2816
- const {container} = render(
2817
- <MultiSelector
2818
- label="Fruit"
2819
- options={ICON_OPTIONS}
2820
- value={[]}
2821
- onChange={() => {}}
2822
- />,
2823
- );
2824
- const icon = getIndicatorIcon(container);
2825
-
2826
- const {container: refContainer} = render(
2827
- <Icon icon="chevronDown" size="sm" color="secondary" />,
2828
- );
2829
- const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
2830
-
2831
- // Exclude the additive theme-target classes (the stable target + its
2832
- // reflected state class) so only StyleX classes remain.
2833
- const themeTargetClasses = new Set([
2834
- 'astryx-multi-selector-indicator-icon',
2835
- 'collapsed',
2836
- 'expanded',
2837
- ]);
2838
- const styleClasses = (el: HTMLElement) =>
2839
- el.className
2840
- .split(' ')
2841
- .filter(c => !themeTargetClasses.has(c))
2842
- .sort();
2843
-
2844
- // A superset, not an exact match: the chevron additionally carries the
2845
- // rotation styles, which live on the glyph precisely so a theme can reach
2846
- // the transform through the same selector as the color. The guard that
2847
- // matters is that every color/size class of a standalone icon is still
2848
- // present — i.e. the default look has not drifted.
2849
- expect(styleClasses(icon)).toEqual(
2850
- expect.arrayContaining(styleClasses(refIcon)),
2851
- );
2852
- });
2853
-
2854
- it('exposes multi-selector-indicator-icon so a theme reaches the icon size and per-state color', () => {
2855
- // jsdom cannot resolve the @layer cascade, so the DOM-class assertions
2856
- // above (target lands on the icon element) plus this generation assertion
2857
- // (the theme emits same-element icon rules in @layer astryx-theme) together
2858
- // prove the seam: a same-element theme rule wins over the icon's own
2859
- // base-layer color/size.
2860
- const theme = defineTheme({
2861
- name: 'multi-selector-indicator-icon-test',
2862
- components: {
2863
- 'multi-selector-indicator-icon': {
2864
- base: {width: '14px', height: '14px', fontSize: '14px'},
2865
- 'state:expanded': {color: 'var(--color-icon-primary)'},
2866
- },
2867
- },
2868
- });
2869
- const css = generateThemeTestCSS(theme);
2870
- expect(css).toContain('.astryx-multi-selector-indicator-icon {');
2871
- expect(css).toContain('width: 14px');
2872
- expect(css).toContain('height: 14px');
2873
- expect(css).toContain(
2874
- '.astryx-multi-selector-indicator-icon[data-state="expanded"]',
2875
- );
2876
- expect(css).toContain('color: var(--color-icon-primary)');
2877
- });
2878
- });
2879
-
2880
- describe('MultiSelector list structure', () => {
2881
- it('does not draw a divider under select-all', async () => {
2882
- const user = userEvent.setup();
2883
- render(
2884
- <MultiSelector
2885
- label="Fruit"
2886
- options={['Apple', 'Banana', 'Orange']}
2887
- value={[]}
2888
- onChange={() => {}}
2889
- hasSelectAll
2890
- />,
2891
- );
2892
- await user.click(screen.getByRole('combobox'));
2893
- // Select-all is the first row of the list, not a section of its own. No
2894
- // option here declares a divider and there is no search row, so the panel
2895
- // should contain no rule at all.
2896
- expect(document.querySelectorAll('[role="separator"]')).toHaveLength(0);
2897
- const [first] = screen.getAllByRole('option', h);
2898
- expect(first).toHaveTextContent('Select all');
2899
- });
2900
-
2901
- it('renders a section title as a plain heading inside the group, not a divider', async () => {
2902
- const user = userEvent.setup();
2903
- render(
2904
- <MultiSelector
2905
- label="Fruit"
2906
- options={[
2907
- {
2908
- type: 'section',
2909
- title: 'Citrus',
2910
- options: [
2911
- {value: 'orange', label: 'Orange'},
2912
- {value: 'lemon', label: 'Lemon'},
2913
- ],
2914
- },
2915
- ]}
2916
- value={[]}
2917
- onChange={() => {}}
2918
- />,
2919
- );
2920
- await user.click(screen.getByRole('combobox'));
2921
-
2922
- // A labeled Divider used to stand in for the heading; it rendered a
2923
- // role="separator" as a direct child of the listbox and stacked a second
2924
- // rule under the search row's own.
2925
- expect(document.querySelectorAll('[role="separator"]')).toHaveLength(0);
2926
-
2927
- const group = screen.getByRole('group', {name: 'Citrus', ...h});
2928
- const heading = group.querySelector(
2929
- '.astryx-multi-selector-section-heading',
2930
- );
2931
- expect(heading).toBeTruthy();
2932
- expect(heading).toHaveTextContent('Citrus');
2933
- // The group already carries the title as its accessible name, so the
2934
- // visible heading must not announce it a second time.
2935
- expect(heading).toHaveAttribute('aria-hidden', 'true');
2936
- // ...and it precedes the options it heads.
2937
- const [firstOption] = within(group).getAllByRole('option', h);
2938
- expect(
2939
- heading!.compareDocumentPosition(firstOption) &
2940
- Node.DOCUMENT_POSITION_FOLLOWING,
2941
- ).toBeTruthy();
2942
- });
2943
-
2944
- it('hides a standalone divider from the accessibility tree (#4994)', async () => {
2945
- const user = userEvent.setup();
2946
- render(
2947
- <MultiSelector
2948
- label="Fruit"
2949
- options={['Apple', {type: 'divider'}, 'Banana']}
2950
- value={[]}
2951
- onChange={() => {}}
2952
- />,
2953
- );
2954
- await user.click(screen.getByRole('combobox'));
2955
-
2956
- // role="listbox" only permits option/group children. The divider still
2957
- // renders role="separator" (unchanged visual/DOM), but must be excluded
2958
- // from the accessibility tree so it never reaches the listbox's exposed
2959
- // children (axe aria-required-children).
2960
- const divider = document.querySelector('[role="separator"]');
2961
- expect(divider).toBeTruthy();
2962
- expect(divider).toHaveAttribute('aria-hidden', 'true');
2963
- });
2964
- });
2965
-
2966
- describe('MultiSelector search affordances', () => {
2967
- it('renders the search row seamlessly — no nested input box, a divider under it', async () => {
2968
- const user = userEvent.setup();
2969
- render(
2970
- <MultiSelector
2971
- label="Fruit"
2972
- options={['Apple', 'Banana', 'Orange']}
2973
- value={[]}
2974
- onChange={() => {}}
2975
- hasSearch
2976
- />,
2977
- );
2978
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
2979
-
2980
- const search = screen.getByRole('combobox', h);
2981
- // The row is the outer gutter; the input sits inside the rounded field.
2982
- const row = search.closest('.astryx-multi-selector-search');
2983
- const field = search.parentElement;
2984
- if (!row || !field) {
2985
- throw new Error('search row not found');
2986
- }
2987
- // The panel is already a bordered surface: the field inside it must not be
2988
- // a second bordered box (this used to render a TextInput).
2989
- expect(row).not.toHaveClass('astryx-text-input');
2990
- expect(search.closest('.astryx-text-input')).toBeNull();
2991
- // The field is a rounded box inset from the panel edge, shaped like the
2992
- // option rows under it — not a full-bleed header strip.
2993
- expect(field).not.toBe(row);
2994
- // ...and a divider separates it from the options.
2995
- const separator = document.querySelector('[role="separator"]');
2996
- if (!separator) {
2997
- throw new Error('divider not found');
2998
- }
2999
- // Order: row, then divider, then the listbox.
3000
- expect(
3001
- row.compareDocumentPosition(separator) & Node.DOCUMENT_POSITION_FOLLOWING,
3002
- ).toBeTruthy();
3003
- const listbox = screen.getByRole('listbox', h);
3004
- expect(
3005
- separator.compareDocumentPosition(listbox) &
3006
- Node.DOCUMENT_POSITION_FOLLOWING,
3007
- ).toBeTruthy();
3008
- });
3009
-
3010
- it('keeps the search row outside the scrolling option list', async () => {
3011
- const user = userEvent.setup();
3012
- render(
3013
- <MultiSelector
3014
- label="Fruit"
3015
- options={['Apple', 'Banana', 'Orange']}
3016
- value={[]}
3017
- onChange={() => {}}
3018
- hasSearch
3019
- />,
3020
- );
3021
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3022
- // The options scroll under the header rather than carrying it away, so the
3023
- // field stays reachable in a long list.
3024
- const search = screen.getByRole('combobox', h);
3025
- const listbox = screen.getByRole('listbox', h);
3026
- expect(listbox.contains(search)).toBe(false);
3027
- });
3028
-
3029
- it('renders a decorative (aria-hidden) magnifier icon whenever hasSearch is on', async () => {
3030
- const user = userEvent.setup();
3031
- render(
3032
- <MultiSelector
3033
- label="Fruit"
3034
- options={['Apple', 'Banana', 'Orange']}
3035
- value={[]}
3036
- onChange={() => {}}
3037
- hasSearch
3038
- />,
3039
- );
3040
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3041
- const search = screen.getByRole('combobox', h);
3042
- // The magnifier leads the search row, as a sibling of the <input>.
3043
- const wrapper = search.parentElement;
3044
- const magnifier = wrapper?.querySelector('.astryx-icon');
3045
- expect(magnifier).toBeTruthy();
3046
- expect(magnifier?.getAttribute('aria-hidden')).toBe('true');
3047
- expect(magnifier?.getAttribute('aria-label')).toBeNull();
3048
- });
3049
-
3050
- it('renders the clear button once a query is typed and clears + refocuses on click', async () => {
3051
- const user = userEvent.setup();
3052
- render(
3053
- <MultiSelector
3054
- label="Fruit"
3055
- options={['Apple', 'Banana', 'Orange']}
3056
- value={[]}
3057
- onChange={() => {}}
3058
- hasSearch
3059
- />,
3060
- );
3061
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3062
- const search = screen.getByRole('combobox', h);
3063
- await user.type(search, 'ap');
3064
- expect(search).toHaveValue('ap');
3065
-
3066
- // The clear button is TextInput's built-in hasClear affordance; its name is
3067
- // derived from the field label ("Search options").
3068
- const clear = screen.getByRole('button', {
3069
- name: 'Clear Search options',
3070
- hidden: true,
3071
- });
3072
-
3073
- await user.click(clear);
3074
- expect(search).toHaveValue('');
3075
- expect(search).toHaveFocus();
3076
- });
3077
-
3078
- it('does not render the clear button when the query is empty', async () => {
3079
- const user = userEvent.setup();
3080
- render(
3081
- <MultiSelector
3082
- label="Fruit"
3083
- options={['Apple', 'Banana', 'Orange']}
3084
- value={[]}
3085
- onChange={() => {}}
3086
- hasSearch
3087
- />,
3088
- );
3089
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3090
- expect(
3091
- screen.queryByRole('button', {
3092
- name: 'Clear Search options',
3093
- hidden: true,
3094
- }),
3095
- ).not.toBeInTheDocument();
3096
- });
3097
-
3098
- it('keeps the combobox contract on the input, not the affordances', async () => {
3099
- const user = userEvent.setup();
3100
- render(
3101
- <MultiSelector
3102
- label="Fruit"
3103
- options={['Apple', 'Banana', 'Orange']}
3104
- value={[]}
3105
- onChange={() => {}}
3106
- hasSearch
3107
- />,
3108
- );
3109
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3110
- const search = screen.getByRole('combobox', h);
3111
- expect(search.tagName).toBe('INPUT');
3112
- expect(search).toHaveAttribute('aria-autocomplete', 'list');
3113
- });
3114
-
3115
- it('tabs from the search input to the clear button (keeping the popup open) when a query is showing it', async () => {
3116
- const user = userEvent.setup();
3117
- render(
3118
- <MultiSelector
3119
- label="Fruit"
3120
- options={['Apple', 'Banana', 'Orange']}
3121
- value={[]}
3122
- onChange={() => {}}
3123
- hasSearch
3124
- />,
3125
- );
3126
- await user.click(screen.getByRole('button', {name: 'Fruit'}));
3127
- const trigger = screen.getByRole('button', {name: 'Fruit'});
3128
- const search = screen.getByRole('combobox', h);
3129
- await user.type(search, 'ap');
3130
- expect(search).toHaveFocus();
3131
-
3132
- // Forward-tab lands on the clear (✕) button and the popup stays open, so
3133
- // the affordance is keyboard-reachable rather than being skipped when the
3134
- // input's Tab dismisses the popup.
3135
- await user.tab();
3136
- const clear = screen.getByRole('button', {
3137
- name: 'Clear Search options',
3138
- hidden: true,
3139
- });
3140
- expect(clear).toHaveFocus();
3141
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
3142
- });
3143
-
3144
- it('dismisses on Tab from the search input when there is no query (no clear button)', async () => {
3145
- const user = userEvent.setup();
3146
- render(
3147
- <MultiSelector
3148
- label="Fruit"
3149
- options={['Apple', 'Banana', 'Orange']}
3150
- value={[]}
3151
- onChange={() => {}}
3152
- hasSearch
3153
- />,
3154
- );
3155
- const trigger = screen.getByRole('button', {name: 'Fruit'});
3156
- await user.click(trigger);
3157
- const search = screen.getByRole('combobox', h);
3158
- // Focus moves into the search input on open (via rAF).
3159
- await waitFor(() => expect(search).toHaveFocus());
3160
-
3161
- // With no query there is no clear button, so Tab dismisses the popup as a
3162
- // plain combobox does.
3163
- await user.tab();
3164
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
3165
- });
3166
- });
3167
-
3168
- describe('MultiSelector disabled state theme target', () => {
3169
- const getSelectorRoot = (container: HTMLElement): HTMLElement => {
3170
- const root = container.querySelector('.astryx-multi-selector');
3171
- if (root == null) {
3172
- throw new Error('multi-selector root not found');
3173
- }
3174
- return root as HTMLElement;
3175
- };
3176
-
3177
- it('reflects data-state="disabled" on the root when disabled', () => {
3178
- const {container} = render(
3179
- <MultiSelector
3180
- label="Fruit"
3181
- options={['Apple', 'Banana', 'Orange']}
3182
- value={[]}
3183
- onChange={() => {}}
3184
- isDisabled
3185
- />,
3186
- );
3187
- expect(getSelectorRoot(container)).toHaveAttribute(
3188
- 'data-disabled',
3189
- 'disabled',
3190
- );
3191
- });
3192
-
3193
- it('omits the disabled class/attribute when enabled', () => {
3194
- const {container} = render(
3195
- <MultiSelector
3196
- label="Fruit"
3197
- options={['Apple', 'Banana', 'Orange']}
3198
- value={[]}
3199
- onChange={() => {}}
3200
- />,
3201
- );
3202
- const root = getSelectorRoot(container);
3203
- expect(root).not.toHaveAttribute('data-disabled');
3204
- expect(root).not.toHaveAttribute('data-disabled');
3205
- });
3206
-
3207
- it('exposes the disabled state so a theme can key on it', () => {
3208
- const theme = defineTheme({
3209
- name: 'multi-selector-disabled-state-test',
3210
- components: {
3211
- 'multi-selector': {
3212
- 'disabled:disabled': {opacity: '0.4'},
3213
- },
3214
- },
3215
- });
3216
- const css = generateThemeTestCSS(theme);
3217
- expect(css).toContain('.astryx-multi-selector[data-disabled="disabled"]');
3218
- expect(css).toContain('opacity: 0.4');
3219
- });
3220
- });
3221
-
3222
- describe('MultiSelector dropdown option theme target', () => {
3223
- const ROW_OPTIONS = ['Apple', 'Banana', 'Orange'];
3224
-
3225
- it('renders astryx-multi-selector-option, with its size, on every dropdown row', async () => {
3226
- const user = userEvent.setup();
3227
- render(
3228
- <MultiSelector
3229
- label="Fruit"
3230
- options={ROW_OPTIONS}
3231
- value={[]}
3232
- onChange={() => {}}
3233
- size="lg"
3234
- />,
3235
- );
3236
- await user.click(screen.getByRole('combobox'));
3237
- const options = screen.getAllByRole('option', h);
3238
- expect(options).toHaveLength(3);
3239
- for (const option of options) {
3240
- expect(option).toHaveClass('astryx-multi-selector-option');
3241
- expect(option).toHaveAttribute('data-size', 'lg');
3242
- expect(option).toHaveAttribute('data-size', 'lg');
3243
- }
3244
- });
3245
-
3246
- it('carries the selected and disabled states a theme keys on', async () => {
3247
- const user = userEvent.setup();
3248
- render(
3249
- <MultiSelector
3250
- label="Fruit"
3251
- options={[
3252
- {value: 'apple', label: 'Apple'},
3253
- {value: 'banana', label: 'Banana'},
3254
- {value: 'orange', label: 'Orange', disabled: true},
3255
- ]}
3256
- value={['apple']}
3257
- onChange={() => {}}
3258
- />,
3259
- );
3260
- await user.click(screen.getByRole('combobox'));
3261
- const [selected, plain, disabled] = screen.getAllByRole('option', h);
3262
-
3263
- expect(selected).toHaveAttribute('data-selected', 'selected');
3264
- expect(selected).toHaveAttribute('data-selected', 'selected');
3265
- expect(plain).not.toHaveAttribute('data-selected');
3266
- expect(plain).not.toHaveAttribute('data-selected');
3267
-
3268
- expect(disabled).toHaveAttribute('data-disabled', 'disabled');
3269
- expect(disabled).toHaveAttribute('data-disabled', 'disabled');
3270
- expect(plain).not.toHaveAttribute('data-disabled');
3271
- });
3272
-
3273
- it('marks the Select All row with the select-all state, not a separate target', async () => {
3274
- const user = userEvent.setup();
3275
- render(
3276
- <MultiSelector
3277
- label="Fruit"
3278
- options={ROW_OPTIONS}
3279
- value={[]}
3280
- onChange={() => {}}
3281
- hasSelectAll
3282
- />,
3283
- );
3284
- await user.click(screen.getByRole('combobox'));
3285
-
3286
- const [selectAllRow, ...regularRows] = screen.getAllByRole('option', h);
3287
- expect(selectAllRow).toHaveTextContent('Select all');
3288
- expect(selectAllRow).toHaveClass('astryx-multi-selector-option');
3289
- expect(selectAllRow).toHaveAttribute('data-select-all', 'select-all');
3290
- expect(selectAllRow).toHaveAttribute('data-select-all', 'select-all');
3291
-
3292
- for (const row of regularRows) {
3293
- expect(row).toHaveClass('astryx-multi-selector-option');
3294
- expect(row).not.toHaveClass('select-all');
3295
- expect(row).not.toHaveAttribute('data-select-all');
3296
- }
3297
- });
3298
-
3299
- it('keeps the row targetable when renderOption replaces the label', async () => {
3300
- const user = userEvent.setup();
3301
- render(
3302
- <MultiSelector
3303
- label="Fruit"
3304
- options={[{value: 'apple', label: 'Apple'}]}
3305
- value={[]}
3306
- onChange={() => {}}
3307
- renderOption={option => (
3308
- <span data-testid="custom-row">{option.label}</span>
3309
- )}
3310
- />,
3311
- );
3312
- await user.click(screen.getByRole('combobox'));
3313
- const option = screen.getAllByRole('option', h)[0];
3314
- // The row owns the typography, so custom content inherits the same
3315
- // treatment the fallback label gets — and one row override reaches both.
3316
- expect(option).toHaveClass('astryx-multi-selector-option');
3317
- expect(within(option).getByTestId('custom-row')).toHaveTextContent('Apple');
3318
- });
3319
-
3320
- it('exposes the row target, its states and its size to defineTheme', () => {
3321
- const theme = defineTheme({
3322
- name: 'multi-selector-option-target-test',
3323
- components: {
3324
- 'multi-selector-option': {
3325
- base: {borderRadius: '8px', fontWeight: '600'},
3326
- selected: {backgroundColor: 'var(--color-background-muted)'},
3327
- 'select-all': {fontWeight: '700'},
3328
- 'size:lg': {borderRadius: '12px'},
3329
- },
3330
- },
3331
- });
3332
- const css = generateThemeTestCSS(theme);
3333
- expect(css).toContain('.astryx-multi-selector-option {');
3334
- expect(css).toContain(
3335
- '.astryx-multi-selector-option[data-selected="selected"]',
3336
- );
3337
- expect(css).toContain(
3338
- '.astryx-multi-selector-option[data-select-all="select-all"]',
3339
- );
3340
- expect(css).toContain('.astryx-multi-selector-option[data-size="lg"]');
3341
- });
3342
- });
3343
-
3344
- describe('MultiSelector popup theme target', () => {
3345
- it('puts astryx-multi-selector-popup on the surface that paints, not the list inside it', async () => {
3346
- const user = userEvent.setup();
3347
- render(
3348
- <MultiSelector
3349
- label="Fruit"
3350
- options={['Apple', 'Banana']}
3351
- value={[]}
3352
- onChange={() => {}}
3353
- />,
3354
- );
3355
- await user.click(screen.getByRole('combobox', {name: /Fruit/}));
3356
-
3357
- const popup = document.querySelector(
3358
- '.astryx-multi-selector-popup',
3359
- ) as HTMLElement;
3360
- expect(popup).not.toBeNull();
3361
- expect(popup).toHaveClass('astryx-popover');
3362
- // The scrolling list is a descendant, not the target itself.
3363
- expect(popup.querySelector('[role="listbox"]')).not.toBeNull();
3364
- expect(popup.getAttribute('role')).toBeNull();
3365
-
3366
- const layer = document.querySelector('[popover]') as HTMLElement;
3367
- expect(popup).not.toBe(layer);
3368
- expect(layer.contains(popup)).toBe(true);
3369
- });
3370
-
3371
- it('stays closed when the trigger click follows its own light dismiss (#5004)', async () => {
3372
- const user = userEvent.setup();
3373
- render(
3374
- <MultiSelector
3375
- label="Fruit"
3376
- options={['Apple', 'Banana', 'Cherry']}
3377
- value={[]}
3378
- onChange={() => {}}
3379
- />,
3380
- );
3381
- const trigger = screen.getByRole('combobox');
3382
- await user.click(trigger);
3383
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
3384
-
3385
- // The browser dismissed the popup on pointerup and queued the toggle. When
3386
- // that event lands before the click — WebKit, or any engine under load —
3387
- // the click used to read a closed popup and reopen it.
3388
- fireEvent.pointerDown(trigger);
3389
- const popover = document.querySelector('[popover]') as HTMLElement;
3390
- act(() => {
3391
- popover.dispatchEvent(
3392
- Object.assign(new Event('toggle'), {
3393
- oldState: 'open',
3394
- newState: 'closed',
3395
- }),
3396
- );
3397
- });
3398
- // Synchronously: the click falls inside the one gesture the guard covers,
3399
- // as it does in a browser a few milliseconds behind the dismissal.
3400
- fireEvent.click(trigger);
3401
-
3402
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
3403
- expect(trigger.parentElement).toHaveClass(
3404
- stylex.props(selectorPresentationStyles.pointerRestoredFocus).className!,
3405
- );
3406
- });
3407
- });
3408
-
3409
- describe('MultiSelector trigger render prop', () => {
3410
- const OPTIONS = [
3411
- {value: 'bug', label: 'Bug'},
3412
- {value: 'feature', label: 'Feature'},
3413
- {value: 'docs', label: 'Docs'},
3414
- ];
3415
-
3416
- it('renders the caller control instead of the field and button', () => {
3417
- render(
3418
- <MultiSelector
3419
- label="Labels"
3420
- options={OPTIONS}
3421
- value={[]}
3422
- onChange={() => {}}
3423
- renderTrigger={props => (
3424
- <button type="button" {...props}>
3425
- Pick labels
3426
- </button>
3427
- )}
3428
- />,
3429
- );
3430
- const opener = screen.getByRole('button', {name: 'Pick labels'});
3431
- expect(opener).toHaveAttribute('aria-haspopup', 'listbox');
3432
- expect(opener).toHaveAttribute('aria-expanded', 'false');
3433
- expect(opener).toHaveAttribute('aria-controls');
3434
- // No field chrome: exactly one button, and no Field label element.
3435
- expect(screen.getAllByRole('button')).toHaveLength(1);
3436
- expect(screen.queryByText('Labels')).toBeNull();
3437
- });
3438
-
3439
- it('opens against the caller control and names the listbox by label', async () => {
3440
- const user = userEvent.setup();
3441
- render(
3442
- <MultiSelector
3443
- label="Labels"
3444
- options={OPTIONS}
3445
- value={['bug']}
3446
- onChange={() => {}}
3447
- renderTrigger={props => (
3448
- <button type="button" {...props}>
3449
- Pick labels
3450
- </button>
3451
- )}
3452
- />,
3453
- );
3454
- const opener = screen.getByRole('button', {name: 'Pick labels'});
3455
- await user.click(opener);
3456
-
3457
- expect(opener).toHaveAttribute('aria-expanded', 'true');
3458
- const listbox = screen.getByRole('listbox', {name: 'Labels', ...h});
3459
- expect(opener.getAttribute('aria-controls')).toBe(listbox.id);
3460
- expect(screen.getAllByRole('option', h)).toHaveLength(3);
3461
- expect(screen.getByRole('option', {name: 'Bug', ...h})).toHaveAttribute(
3462
- 'aria-selected',
3463
- 'true',
3464
- );
3465
- });
3466
-
3467
- it('the listbox owns the keyboard once open and toggles on Enter', async () => {
3468
- const user = userEvent.setup();
3469
- const onChange = vi.fn();
3470
- render(
3471
- <MultiSelector
3472
- label="Labels"
3473
- options={OPTIONS}
3474
- value={[]}
3475
- onChange={onChange}
3476
- renderTrigger={props => (
3477
- <button type="button" {...props}>
3478
- Pick labels
3479
- </button>
3480
- )}
3481
- />,
3482
- );
3483
- await user.click(screen.getByRole('button', {name: 'Pick labels'}));
3484
- const listbox = screen.getByRole('listbox', h);
3485
- expect(listbox).toHaveAttribute('tabindex', '0');
3486
- await waitFor(() => expect(listbox).toHaveFocus());
3487
-
3488
- // Opening by click highlights the first option; Enter toggles it.
3489
- fireEvent.keyDown(listbox, {key: 'ArrowDown'});
3490
- fireEvent.keyDown(listbox, {key: 'Enter'});
3491
- expect(onChange).toHaveBeenCalledWith(['feature']);
3492
- });
3493
-
3494
- it('handleRef opens and closes the panel and onOpenChange reports it', async () => {
3495
- const onOpenChange = vi.fn();
3496
- function Harness() {
3497
- const handleRef = useRef<MultiSelectorHandle>(null);
3498
- return (
3499
- <>
3500
- <button type="button" onClick={() => handleRef.current?.open()}>
3501
- open from outside
3502
- </button>
3503
- <button type="button" onClick={() => handleRef.current?.close()}>
3504
- close from outside
3505
- </button>
3506
- <MultiSelector
3507
- label="Labels"
3508
- options={OPTIONS}
3509
- value={[]}
3510
- onChange={() => {}}
3511
- handleRef={handleRef}
3512
- onOpenChange={onOpenChange}
3513
- renderTrigger={props => (
3514
- <button type="button" {...props}>
3515
- anchor
3516
- </button>
3517
- )}
3518
- />
3519
- </>
3520
- );
3521
- }
3522
- const user = userEvent.setup();
3523
- render(<Harness />);
3524
- const anchor = screen.getByRole('button', {name: 'anchor'});
3525
- expect(anchor).toHaveAttribute('aria-expanded', 'false');
3526
- expect(onOpenChange).not.toHaveBeenCalled();
3527
-
3528
- await user.click(screen.getByRole('button', {name: 'open from outside'}));
3529
- expect(anchor).toHaveAttribute('aria-expanded', 'true');
3530
- expect(onOpenChange).toHaveBeenLastCalledWith(true);
3531
-
3532
- await user.click(screen.getByRole('button', {name: 'close from outside'}));
3533
- await waitFor(() => {
3534
- expect(anchor).toHaveAttribute('aria-expanded', 'false');
3535
- });
3536
- expect(onOpenChange).toHaveBeenLastCalledWith(false);
3537
- expect(onOpenChange).toHaveBeenCalledTimes(2);
3538
- });
3539
-
3540
- it('the handle does not open a disabled selector', () => {
3541
- const handleRef = {current: null as MultiSelectorHandle | null};
3542
- render(
3543
- <MultiSelector
3544
- label="Labels"
3545
- options={OPTIONS}
3546
- value={[]}
3547
- onChange={() => {}}
3548
- isDisabled
3549
- handleRef={handleRef}
3550
- renderTrigger={props => (
3551
- <button type="button" {...props}>
3552
- anchor
3553
- </button>
3554
- )}
3555
- />,
3556
- );
3557
- act(() => {
3558
- handleRef.current?.open();
3559
- });
3560
- expect(handleRef.current?.isOpen()).toBe(false);
3561
- expect(screen.getByRole('button', {name: 'anchor'})).toHaveAttribute(
3562
- 'aria-expanded',
3563
- 'false',
3564
- );
3565
- });
3566
- });
3567
-
3568
- describe('MultiSelector renderTrigger — read-only', () => {
3569
- it("a read-only selector advertises no panel on the caller's control", () => {
3570
- // spec:AST-011 FR4. A read-only selector has no surface to open, so a
3571
- // control that says "collapsed, has popup" sends a screen-reader user
3572
- // to press Enter on something that cannot respond — with nothing to
3573
- // tell them the value is read-only rather than the control broken.
3574
- const onOpen = vi.fn();
3575
- render(
3576
- <MultiSelector
3577
- label="Labels"
3578
- value={['a']}
3579
- onChange={() => {}}
3580
- isReadOnly
3581
- options={[
3582
- {value: 'a', label: 'Alpha'},
3583
- {value: 'b', label: 'Beta'},
3584
- ]}
3585
- renderTrigger={props => (
3586
- <button type="button" {...props} onClick={onOpen}>
3587
- Labels
3588
- </button>
3589
- )}
3590
- />,
3591
- );
3592
-
3593
- const trigger = screen.getByRole('button', {name: 'Labels'});
3594
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
3595
- expect(trigger).not.toHaveAttribute('aria-haspopup');
3596
- expect(trigger).not.toHaveAttribute('aria-controls');
3597
- // Withholding the disclosure attributes stops the control lying about a
3598
- // panel; this says why it does not open, which the field path conveys
3599
- // through chrome the caller's control does not have.
3600
- expect(trigger).toHaveAttribute('aria-readonly', 'true');
3601
- });
3602
-
3603
- it('still advertises the panel when the selector is not read-only', () => {
3604
- render(
3605
- <MultiSelector
3606
- label="Labels"
3607
- value={[]}
3608
- onChange={() => {}}
3609
- options={[{value: 'a', label: 'Alpha'}]}
3610
- renderTrigger={props => (
3611
- <button type="button" {...props}>
3612
- Labels
3613
- </button>
3614
- )}
3615
- />,
3616
- );
3617
-
3618
- const trigger = screen.getByRole('button', {name: 'Labels'});
3619
- expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');
3620
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
3621
- expect(trigger).toHaveAttribute('aria-controls');
3622
- });
3623
- });
3624
-
3625
- describe('MultiSelector renderTrigger — focus return', () => {
3626
- it('returns focus to a control that takes no focus of its own', async () => {
3627
- // A glyph is not keyboard-openable, but when it IS opened by pointer the
3628
- // panel still has to give focus back somewhere the person recognises.
3629
- // Without the fallback focus lands on the body and they lose their place.
3630
- const user = userEvent.setup();
3631
- render(
3632
- <MultiSelector
3633
- label="Labels"
3634
- value={[]}
3635
- onChange={() => {}}
3636
- options={[
3637
- {value: 'a', label: 'Alpha'},
3638
- {value: 'b', label: 'Beta'},
3639
- ]}
3640
- renderTrigger={props => (
3641
- <span {...props} data-testid="glyph">
3642
- #
3643
- </span>
3644
- )}
3645
- />,
3646
- );
3647
-
3648
- const glyph = screen.getByTestId('glyph');
3649
- expect(glyph).toHaveAttribute('tabindex', '-1');
3650
-
3651
- await user.click(glyph);
3652
- await waitFor(() => expect(glyph).toHaveAttribute('aria-expanded', 'true'));
3653
- await user.keyboard('{Escape}');
3654
- await waitFor(() => expect(glyph).toHaveFocus());
3655
- expect(document.body).not.toHaveFocus();
3656
- });
3657
-
3658
- it('leaves a real control\u2019s own focusability alone', () => {
3659
- render(
3660
- <MultiSelector
3661
- label="Labels"
3662
- value={[]}
3663
- onChange={() => {}}
3664
- options={[{value: 'a', label: 'Alpha'}]}
3665
- renderTrigger={props => (
3666
- <button type="button" {...props}>
3667
- Labels
3668
- </button>
3669
- )}
3670
- />,
3671
- );
3672
-
3673
- expect(screen.getByRole('button', {name: 'Labels'})).not.toHaveAttribute(
3674
- 'tabindex',
3675
- );
3676
- });
3677
- });
3678
-
3679
- describe('MultiSelector hasCreate', () => {
3680
- const OPTIONS = [
3681
- {value: 'bug', label: 'Bug'},
3682
- {value: 'feature', label: 'Feature'},
3683
- {value: 'cafe', label: 'Café'},
3684
- ];
3685
-
3686
- it('offers Create "<query>" first and reports the creation through onChange', async () => {
3687
- const user = userEvent.setup();
3688
- const onChange =
3689
- vi.fn<(value: string[], change?: MultiSelectorChange) => void>();
3690
- render(
3691
- <MultiSelector
3692
- label="Labels"
3693
- options={OPTIONS}
3694
- value={['bug']}
3695
- onChange={onChange}
3696
- hasSearch
3697
- hasCreate
3698
- />,
3699
- );
3700
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3701
- const search = screen.getByRole('combobox', h);
3702
- await user.type(search, 'Urgent');
3703
-
3704
- const options = screen.getAllByRole('option', h);
3705
- expect(options).toHaveLength(1);
3706
- expect(options[0]).toHaveTextContent('Create "Urgent"');
3707
- expect(options[0]).toHaveAttribute('aria-selected', 'false');
3708
- expect(options[0].querySelector('input[type="checkbox"]')).toBeNull();
3709
- expect(screen.queryByText('No results found')).toBeNull();
3710
-
3711
- await user.click(options[0]);
3712
- // One update: the query joins the value and the change says it is a
3713
- // creation, so the caller adds the option and accepts the value together.
3714
- expect(onChange).toHaveBeenCalledTimes(1);
3715
- expect(onChange).toHaveBeenCalledWith(['bug', 'Urgent'], {
3716
- type: 'create',
3717
- query: 'Urgent',
3718
- });
3719
- // The filter is cleared so the option the caller adds is visible.
3720
- expect(search).toHaveValue('');
3721
- });
3722
-
3723
- it('a plain toggle passes no descriptor, so a one-argument handler is unchanged', async () => {
3724
- const user = userEvent.setup();
3725
- const onChange = vi.fn();
3726
- render(
3727
- <MultiSelector
3728
- label="Labels"
3729
- options={OPTIONS}
3730
- value={[]}
3731
- onChange={onChange}
3732
- hasSearch
3733
- hasCreate
3734
- />,
3735
- );
3736
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3737
- await user.click(screen.getByRole('option', {name: 'Bug', ...h}));
3738
- expect(onChange).toHaveBeenCalledWith(['bug']);
3739
- expect(onChange.mock.calls[0]).toHaveLength(1);
3740
- });
3741
-
3742
- it('sits above partial matches and is skipped for a label the filter matches exactly', async () => {
3743
- const user = userEvent.setup();
3744
- render(
3745
- <MultiSelector
3746
- label="Labels"
3747
- options={OPTIONS}
3748
- value={[]}
3749
- onChange={() => {}}
3750
- hasSearch
3751
- hasCreate
3752
- />,
3753
- );
3754
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3755
- const search = screen.getByRole('combobox', h);
3756
-
3757
- await user.type(search, 'Bu');
3758
- expect(screen.getAllByRole('option', h).map(o => o.textContent)).toEqual([
3759
- 'Create "Bu"',
3760
- 'Bug',
3761
- ]);
3762
-
3763
- await user.type(search, 'g');
3764
- expect(screen.getAllByRole('option', h).map(o => o.textContent)).toEqual([
3765
- 'Bug',
3766
- ]);
3767
-
3768
- await user.clear(search);
3769
- await user.type(search, 'bug');
3770
- // Case-insensitive, like the filter: "bug" is Bug, so nothing to create.
3771
- expect(screen.getAllByRole('option', h).map(o => o.textContent)).toEqual([
3772
- 'Bug',
3773
- ]);
3774
-
3775
- await user.clear(search);
3776
- await user.type(search, 'CAFE');
3777
- // The duplicate rule is the filter's rule: the filter cannot surface
3778
- // Café for CAFE, so refusing to create would be a dead end.
3779
- expect(screen.getAllByRole('option', h).map(o => o.textContent)).toEqual([
3780
- 'Create "CAFE"',
3781
- ]);
3782
- });
3783
-
3784
- it('leads the select-all row and is not handed to renderOption', async () => {
3785
- const user = userEvent.setup();
3786
- const renderOption = vi.fn((option: {label?: string}) => (
3787
- <span>{`row:${option.label}`}</span>
3788
- ));
3789
- render(
3790
- <MultiSelector
3791
- label="Labels"
3792
- options={OPTIONS}
3793
- value={[]}
3794
- onChange={() => {}}
3795
- hasSearch
3796
- hasSelectAll
3797
- hasCreate
3798
- renderOption={renderOption}
3799
- />,
3800
- );
3801
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3802
- await user.type(screen.getByRole('combobox', h), 'Bu');
3803
- const options = screen.getAllByRole('option', h);
3804
- expect(options[0]).toHaveTextContent('Create "Bu"');
3805
- expect(options[1]).toHaveTextContent('Select all');
3806
- expect(options[2]).toHaveTextContent('row:Bug');
3807
- expect(
3808
- renderOption.mock.calls.some(([option]) =>
3809
- (option.label ?? '').startsWith('Create'),
3810
- ),
3811
- ).toBe(false);
3812
- });
3813
-
3814
- it('Enter with nothing highlighted commits the create row; ArrowDown reaches it', async () => {
3815
- const user = userEvent.setup();
3816
- const onChange = vi.fn();
3817
- render(
3818
- <MultiSelector
3819
- label="Labels"
3820
- options={OPTIONS}
3821
- value={[]}
3822
- onChange={onChange}
3823
- hasSearch
3824
- hasCreate
3825
- />,
3826
- );
3827
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3828
- const search = screen.getByRole('combobox', h);
3829
- await user.type(search, 'Urgent');
3830
- await user.keyboard('{Enter}');
3831
- expect(onChange).toHaveBeenLastCalledWith(['Urgent'], {
3832
- type: 'create',
3833
- query: 'Urgent',
3834
- });
3835
-
3836
- await user.type(search, 'Later');
3837
- fireEvent.keyDown(search, {key: 'ArrowDown'});
3838
- const createRow = screen.getByRole('option', {
3839
- name: 'Create "Later"',
3840
- ...h,
3841
- });
3842
- expect(search).toHaveAttribute('aria-activedescendant', createRow.id);
3843
- fireEvent.keyDown(search, {key: 'Enter'});
3844
- expect(onChange).toHaveBeenLastCalledWith(
3845
- expect.arrayContaining(['Later']),
3846
- {type: 'create', query: 'Later'},
3847
- );
3848
- });
3849
-
3850
- it('offers nothing while isLoading, and Enter commits nothing', async () => {
3851
- const user = userEvent.setup();
3852
- const onChange = vi.fn();
3853
- const {rerender} = render(
3854
- <MultiSelector
3855
- label="Labels"
3856
- options={[]}
3857
- value={[]}
3858
- onChange={onChange}
3859
- hasSearch
3860
- hasCreate
3861
- isLoading
3862
- />,
3863
- );
3864
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3865
- const search = screen.getByRole('combobox', h);
3866
- await user.type(search, 'Bug');
3867
- expect(screen.queryAllByRole('option', h)).toHaveLength(0);
3868
- expect(politeRegion()?.textContent ?? '').toBe('');
3869
- await user.keyboard('{Enter}');
3870
- expect(onChange).not.toHaveBeenCalled();
3871
-
3872
- // The options land: Bug exists, so still no create row for "Bug" — the
3873
- // duplicate the gate was there to prevent.
3874
- rerender(
3875
- <MultiSelector
3876
- label="Labels"
3877
- options={OPTIONS}
3878
- value={[]}
3879
- onChange={onChange}
3880
- hasSearch
3881
- hasCreate
3882
- />,
3883
- );
3884
- expect(screen.getAllByRole('option', h).map(o => o.textContent)).toEqual([
3885
- 'Bug',
3886
- ]);
3887
- });
3888
-
3889
- it('announces the create row, not "No results found", when it is the only result', async () => {
3890
- const user = userEvent.setup();
3891
- render(
3892
- <MultiSelector
3893
- label="Labels"
3894
- options={OPTIONS}
3895
- value={[]}
3896
- onChange={() => {}}
3897
- hasSearch
3898
- hasCreate
3899
- />,
3900
- );
3901
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3902
- await enterQuery(user, screen.getByRole('combobox', h), 'Urgent');
3903
- // The empty-state message is role="presentation", so the live region is
3904
- // what a screen reader gets — it must say what is on screen: a row.
3905
- await waitFor(() => {
3906
- expect(politeRegion()?.textContent).toBe('Create "Urgent"');
3907
- });
3908
- });
3909
-
3910
- it('warns in development and offers nothing when hasCreate is set without hasSearch', async () => {
3911
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
3912
- try {
3913
- const user = userEvent.setup();
3914
- render(
3915
- <MultiSelector
3916
- label="Labels"
3917
- options={OPTIONS}
3918
- value={[]}
3919
- onChange={() => {}}
3920
- hasCreate
3921
- />,
3922
- );
3923
- expect(warn).toHaveBeenCalledWith(
3924
- expect.stringMatching(/MultiSelector[\s\S]*hasSearch/),
3925
- );
3926
- await user.click(screen.getByRole('combobox', {name: 'Labels'}));
3927
- // No search input, no query, no create row: the three real options only.
3928
- expect(
3929
- screen.queryByRole('combobox', {name: /search/i, ...h}),
3930
- ).toBeNull();
3931
- expect(screen.getAllByRole('option', h)).toHaveLength(3);
3932
- } finally {
3933
- warn.mockRestore();
3934
- }
3935
- });
3936
-
3937
- it('is off by default', async () => {
3938
- const user = userEvent.setup();
3939
- render(
3940
- <MultiSelector
3941
- label="Labels"
3942
- options={OPTIONS}
3943
- value={[]}
3944
- onChange={() => {}}
3945
- hasSearch
3946
- />,
3947
- );
3948
- await user.click(screen.getByRole('button', {name: 'Labels'}));
3949
- await user.type(screen.getByRole('combobox', h), 'Urgent');
3950
- expect(screen.queryAllByRole('option', h)).toHaveLength(0);
3951
- expect(screen.getByText('No results found')).toBeInTheDocument();
3952
- });
3953
- });
3954
-
3955
- describe('MultiSelector option actions (grid)', () => {
3956
- const edit = vi.fn();
3957
- const OPTIONS = [
3958
- {
3959
- value: 'bug',
3960
- label: 'Bug',
3961
- action: (
3962
- <button
3963
- type="button"
3964
- onClick={() => {
3965
- edit('bug');
3966
- }}>
3967
- Edit Bug
3968
- </button>
3969
- ),
3970
- },
3971
- {value: 'feature', label: 'Feature'},
3972
- {
3973
- value: 'docs',
3974
- label: 'Docs',
3975
- disabled: true,
3976
- action: (
3977
- <button
3978
- type="button"
3979
- onClick={() => {
3980
- edit('docs');
3981
- }}>
3982
- Edit Docs
3983
- </button>
3984
- ),
3985
- },
3986
- ];
3987
- beforeEach(() => {
3988
- edit.mockClear();
3989
- });
3990
-
3991
- it('stays a listbox when no option carries an action, and action: null is none', async () => {
3992
- const user = userEvent.setup();
3993
- render(
3994
- <MultiSelector
3995
- label="Labels"
3996
- options={[
3997
- {value: 'bug', label: 'Bug', action: null},
3998
- {value: 'feature', label: 'Feature'},
3999
- ]}
4000
- value={[]}
4001
- onChange={() => {}}
4002
- />,
4003
- );
4004
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4005
- expect(trigger).toHaveAttribute('aria-haspopup', 'listbox');
4006
- await user.click(trigger);
4007
- expect(screen.getByRole('listbox', h)).toBeInTheDocument();
4008
- expect(screen.queryByRole('grid', h)).toBeNull();
4009
- expect(screen.getAllByRole('option', h)).toHaveLength(2);
4010
- });
4011
-
4012
- it('is a grid of rows with exactly two gridcells once an option carries an action', async () => {
4013
- const user = userEvent.setup();
4014
- render(
4015
- <MultiSelector
4016
- label="Labels"
4017
- options={OPTIONS}
4018
- value={['bug']}
4019
- onChange={() => {}}
4020
- hasSelectAll
4021
- />,
4022
- );
4023
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4024
- expect(trigger).toHaveAttribute('aria-haspopup', 'grid');
4025
- await user.click(trigger);
4026
-
4027
- const grid = screen.getByRole('grid', h);
4028
- expect(screen.queryByRole('listbox', h)).toBeNull();
4029
- expect(screen.queryByRole('option', h)).toBeNull();
4030
- expect(grid).toHaveAttribute('aria-multiselectable', 'true');
4031
- expect(trigger.getAttribute('aria-controls')).toBe(grid.id);
4032
-
4033
- const rows = screen.getAllByRole('row', h);
4034
- // select-all + 3 options
4035
- expect(rows).toHaveLength(4);
4036
- for (const row of rows) {
4037
- const cells = Array.from(row.children);
4038
- expect(cells).toHaveLength(2);
4039
- expect(cells[0]).toHaveAttribute('role', 'gridcell');
4040
- expect(cells[1]).toHaveAttribute('role', 'gridcell');
4041
- }
4042
- // Every exposed child of the grid is a row.
4043
- expect(
4044
- Array.from(grid.children).every(c => c.getAttribute('role') === 'row'),
4045
- ).toBe(true);
4046
-
4047
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4048
- expect(bug).toHaveAttribute('aria-selected', 'true');
4049
- expect(bug.children[1]).toContainElement(
4050
- screen.getByRole('button', {name: 'Edit Bug', ...h}),
4051
- );
4052
- // Rows without an action, and the select-all row, have an empty cell.
4053
- expect(
4054
- screen.getByRole('row', {name: 'Feature', ...h}).children[1],
4055
- ).toBeEmptyDOMElement();
4056
- expect(rows[0].children[1]).toBeEmptyDOMElement();
4057
- // The row's name is the option; the control keeps its own.
4058
- expect(bug).toHaveAccessibleName('Bug');
4059
- });
4060
-
4061
- it('latches: a query that filters the actioned option out keeps the grid', async () => {
4062
- const user = userEvent.setup();
4063
- const {rerender} = render(
4064
- <MultiSelector
4065
- label="Labels"
4066
- options={OPTIONS}
4067
- value={[]}
4068
- onChange={() => {}}
4069
- hasSearch
4070
- />,
4071
- );
4072
- await user.click(screen.getByRole('button', {name: 'Labels'}));
4073
- expect(screen.getByRole('grid', h)).toBeInTheDocument();
4074
- await user.type(screen.getByRole('combobox', h), 'Feat');
4075
- expect(screen.getAllByRole('row', h).map(r => r.textContent)).toEqual([
4076
- 'Feature',
4077
- ]);
4078
- expect(screen.getByRole('grid', h)).toBeInTheDocument();
4079
- // The action removed on a later render: still a grid.
4080
- rerender(
4081
- <MultiSelector
4082
- label="Labels"
4083
- options={[{value: 'feature', label: 'Feature'}]}
4084
- value={[]}
4085
- onChange={() => {}}
4086
- hasSearch
4087
- />,
4088
- );
4089
- expect(screen.getByRole('grid', h)).toBeInTheDocument();
4090
- });
4091
-
4092
- it('becomes a grid when options arrive after loading, inside a section too', async () => {
4093
- const user = userEvent.setup();
4094
- const {rerender} = render(
4095
- <MultiSelector
4096
- label="Labels"
4097
- options={[]}
4098
- value={[]}
4099
- onChange={() => {}}
4100
- isLoading
4101
- />,
4102
- );
4103
- expect(screen.getByRole('combobox', {name: 'Labels'})).toHaveAttribute(
4104
- 'aria-haspopup',
4105
- 'listbox',
4106
- );
4107
- rerender(
4108
- <MultiSelector
4109
- label="Labels"
4110
- options={[
4111
- {
4112
- type: 'section',
4113
- title: 'Type',
4114
- options: OPTIONS,
4115
- },
4116
- ]}
4117
- value={[]}
4118
- onChange={() => {}}
4119
- />,
4120
- );
4121
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4122
- expect(trigger).toHaveAttribute('aria-haspopup', 'grid');
4123
- await user.click(trigger);
4124
- const group = screen.getByRole('rowgroup', {name: 'Type', ...h});
4125
- expect(screen.queryByRole('group', h)).toBeNull();
4126
- expect(group.querySelectorAll('[role="row"]')).toHaveLength(3);
4127
- });
4128
-
4129
- it('pressing the option cell toggles; pressing the action fires only the action', async () => {
4130
- const user = userEvent.setup();
4131
- const onChange = vi.fn();
4132
- render(
4133
- <MultiSelector
4134
- label="Labels"
4135
- options={OPTIONS}
4136
- value={[]}
4137
- onChange={onChange}
4138
- />,
4139
- );
4140
- await user.click(screen.getByRole('combobox', {name: 'Labels'}));
4141
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4142
- await user.click(bug.children[0]);
4143
- expect(onChange).toHaveBeenCalledWith(['bug']);
4144
-
4145
- onChange.mockClear();
4146
- await user.click(screen.getByRole('button', {name: 'Edit Bug', ...h}));
4147
- expect(edit).toHaveBeenCalledWith('bug');
4148
- expect(onChange).not.toHaveBeenCalled();
4149
- // The panel stays open.
4150
- expect(screen.getByRole('grid', h)).toBeInTheDocument();
4151
-
4152
- // A disabled option's action is the caller's control, still live.
4153
- await user.click(screen.getByRole('button', {name: 'Edit Docs', ...h}));
4154
- expect(edit).toHaveBeenCalledWith('docs');
4155
- expect(onChange).not.toHaveBeenCalled();
4156
- });
4157
-
4158
- it('draws the standard focus ring on the active action cell, from the focus tokens', async () => {
4159
- // jsdom resolves no custom property, so the ring is read from StyleX
4160
- // classes: a class name hashes one property and its value, and the same
4161
- // declaration yields the same class wherever it is written.
4162
- const ring = stylex.create({
4163
- active: {
4164
- outlineWidth: focusVars['--focus-outline-width'],
4165
- outlineStyle: focusVars['--focus-outline-style'],
4166
- outlineColor: focusVars['--focus-outline-color'],
4167
- outlineOffset: focusVars['--focus-outline-offset'],
4168
- },
4169
- });
4170
- const ringClasses = stylex
4171
- .props(ring.active)
4172
- .className!.split(' ')
4173
- .filter(token => /^x[a-z0-9]+$/.test(token));
4174
- expect(ringClasses).toHaveLength(4);
4175
-
4176
- const user = userEvent.setup();
4177
- render(
4178
- <MultiSelector
4179
- label="Labels"
4180
- options={OPTIONS}
4181
- value={[]}
4182
- onChange={() => {}}
4183
- />,
4184
- );
4185
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4186
- await user.click(trigger);
4187
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4188
- const actionCell = bug.children[1] as HTMLElement;
4189
- for (const token of ringClasses) {
4190
- expect(actionCell).not.toHaveClass(token);
4191
- }
4192
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4193
- expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
4194
- for (const token of ringClasses) {
4195
- expect(actionCell).toHaveClass(token);
4196
- }
4197
- });
4198
-
4199
- it('draws no focus ring on an action cell highlighted after pointer input, until a key moves the highlight', async () => {
4200
- const ring = stylex.create({
4201
- active: {
4202
- outlineWidth: focusVars['--focus-outline-width'],
4203
- outlineStyle: focusVars['--focus-outline-style'],
4204
- outlineColor: focusVars['--focus-outline-color'],
4205
- outlineOffset: focusVars['--focus-outline-offset'],
4206
- },
4207
- });
4208
- const ringClasses = stylex
4209
- .props(ring.active)
4210
- .className!.split(' ')
4211
- .filter(token => /^x[a-z0-9]+$/.test(token));
4212
- expect(ringClasses).toHaveLength(4);
4213
-
4214
- const user = userEvent.setup();
4215
- render(
4216
- <MultiSelector
4217
- label="Labels"
4218
- options={OPTIONS}
4219
- value={[]}
4220
- onChange={() => {}}
4221
- />,
4222
- );
4223
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4224
- await user.click(trigger);
4225
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4226
- const actionCell = bug.children[1] as HTMLElement;
4227
- fireEvent.mouseEnter(actionCell);
4228
- // The hover moved the highlight onto the action cell, without the ring.
4229
- expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
4230
- for (const token of ringClasses) {
4231
- expect(actionCell).not.toHaveClass(token);
4232
- }
4233
- // Keyboard navigation back onto the cell draws it.
4234
- fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
4235
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4236
- expect(trigger).toHaveAttribute('aria-activedescendant', actionCell.id);
4237
- for (const token of ringClasses) {
4238
- expect(actionCell).toHaveClass(token);
4239
- }
4240
- });
4241
-
4242
- describe('ring under mixed input', () => {
4243
- const ringClasses = () => {
4244
- const ring = stylex.create({
4245
- active: {
4246
- outlineWidth: focusVars['--focus-outline-width'],
4247
- outlineStyle: focusVars['--focus-outline-style'],
4248
- outlineColor: focusVars['--focus-outline-color'],
4249
- outlineOffset: focusVars['--focus-outline-offset'],
4250
- },
4251
- });
4252
- return stylex
4253
- .props(ring.active)
4254
- .className!.split(' ')
4255
- .filter(token => /^x[a-z0-9]+$/.test(token));
4256
- };
4257
- const hasRing = (cell: HTMLElement) =>
4258
- ringClasses().every(token => cell.classList.contains(token));
4259
-
4260
- async function openWithKeyboardOnBugAction() {
4261
- const user = userEvent.setup();
4262
- render(
4263
- <MultiSelector
4264
- label="Labels"
4265
- options={OPTIONS}
4266
- value={[]}
4267
- onChange={() => {}}
4268
- />,
4269
- );
4270
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4271
- await user.click(trigger);
4272
- const row = (name: string) => screen.getByRole('row', {name, ...h});
4273
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4274
- const bugAction = row('Bug').children[1] as HTMLElement;
4275
- expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
4276
- expect(hasRing(bugAction)).toBe(true);
4277
- return {trigger, row, bugAction};
4278
- }
4279
-
4280
- it('keeps the ring when the pointer crosses a disabled option the highlight ignores', async () => {
4281
- const {trigger, row, bugAction} = await openWithKeyboardOnBugAction();
4282
- fireEvent.mouseEnter(row('Docs').children[0]);
4283
- expect(trigger).toHaveAttribute('aria-activedescendant', bugAction.id);
4284
- expect(hasRing(bugAction)).toBe(true);
4285
- });
4286
-
4287
- it('follows the keyboard modality when a hover moves the highlight to another action', async () => {
4288
- const {trigger, row} = await openWithKeyboardOnBugAction();
4289
- const docsAction = row('Docs').children[1] as HTMLElement;
4290
- fireEvent.mouseEnter(docsAction);
4291
- expect(trigger).toHaveAttribute('aria-activedescendant', docsAction.id);
4292
- expect(hasRing(docsAction)).toBe(true);
4293
- });
4294
-
4295
- it('draws no ring for a modifier chord after pointer input', async () => {
4296
- const user = userEvent.setup();
4297
- render(
4298
- <MultiSelector
4299
- label="Labels"
4300
- options={OPTIONS}
4301
- value={[]}
4302
- onChange={() => {}}
4303
- />,
4304
- );
4305
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4306
- await user.click(trigger);
4307
- const bugAction = screen.getByRole('row', {name: 'Bug', ...h})
4308
- .children[1] as HTMLElement;
4309
- fireEvent.mouseEnter(bugAction);
4310
- expect(hasRing(bugAction)).toBe(false);
4311
- fireEvent.keyDown(trigger, {key: 'ArrowRight', ctrlKey: true});
4312
- expect(hasRing(bugAction)).toBe(false);
4313
- });
4314
- });
4315
-
4316
- it('the inline-end arrow reaches the action cell, Enter fires it, inline-start returns', async () => {
4317
- const user = userEvent.setup();
4318
- const onChange = vi.fn();
4319
- render(
4320
- <MultiSelector
4321
- label="Labels"
4322
- options={OPTIONS}
4323
- value={[]}
4324
- onChange={onChange}
4325
- />,
4326
- );
4327
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4328
- await user.click(trigger);
4329
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4330
- // Click-open highlights the first row, on its option cell.
4331
- expect(trigger).toHaveAttribute('aria-activedescendant', bug.id);
4332
-
4333
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4334
- expect(trigger).toHaveAttribute(
4335
- 'aria-activedescendant',
4336
- bug.children[1].id,
4337
- );
4338
- fireEvent.keyDown(trigger, {key: 'Enter'});
4339
- expect(edit).toHaveBeenCalledWith('bug');
4340
- expect(onChange).not.toHaveBeenCalled();
4341
- // The highlight stays where it was.
4342
- expect(trigger).toHaveAttribute(
4343
- 'aria-activedescendant',
4344
- bug.children[1].id,
4345
- );
4346
-
4347
- fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
4348
- expect(trigger).toHaveAttribute('aria-activedescendant', bug.id);
4349
- fireEvent.keyDown(trigger, {key: 'Enter'});
4350
- expect(onChange).toHaveBeenCalledWith(['bug']);
4351
-
4352
- // A row without an action: the inline-end arrow is a no-op there, and
4353
- // Down from the action cell lands on the next row's option cell.
4354
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4355
- fireEvent.keyDown(trigger, {key: 'ArrowDown'});
4356
- const feature = screen.getByRole('row', {name: 'Feature', ...h});
4357
- expect(trigger).toHaveAttribute('aria-activedescendant', feature.id);
4358
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4359
- expect(trigger).toHaveAttribute('aria-activedescendant', feature.id);
4360
- });
4361
-
4362
- it('follows visual direction under RTL', async () => {
4363
- const user = userEvent.setup();
4364
- const original = window.getComputedStyle;
4365
- const spy = vi
4366
- .spyOn(window, 'getComputedStyle')
4367
- .mockImplementation((el: Element, pseudo?: string | null) => {
4368
- const style = original.call(window, el, pseudo);
4369
- Object.defineProperty(style, 'direction', {value: 'rtl'});
4370
- return style;
4371
- });
4372
- try {
4373
- render(
4374
- <MultiSelector
4375
- label="Labels"
4376
- options={OPTIONS}
4377
- value={[]}
4378
- onChange={() => {}}
4379
- />,
4380
- );
4381
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4382
- await user.click(trigger);
4383
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4384
- fireEvent.keyDown(trigger, {key: 'ArrowRight'});
4385
- expect(trigger).toHaveAttribute('aria-activedescendant', bug.id);
4386
- fireEvent.keyDown(trigger, {key: 'ArrowLeft'});
4387
- expect(trigger).toHaveAttribute(
4388
- 'aria-activedescendant',
4389
- bug.children[1].id,
4390
- );
4391
- } finally {
4392
- spy.mockRestore();
4393
- }
4394
- });
4395
-
4396
- it('with search, Left/Right move the caret until a row is highlighted', async () => {
4397
- const user = userEvent.setup();
4398
- render(
4399
- <MultiSelector
4400
- label="Labels"
4401
- options={OPTIONS}
4402
- value={[]}
4403
- onChange={() => {}}
4404
- hasSearch
4405
- />,
4406
- );
4407
- await user.click(screen.getByRole('button', {name: 'Labels'}));
4408
- const search = screen.getByRole('combobox', h);
4409
- await user.type(search, 'Bu');
4410
- // No row highlighted: the arrow is the input's.
4411
- const right = fireEvent.keyDown(search, {key: 'ArrowRight'});
4412
- expect(right).toBe(true);
4413
- expect(search).not.toHaveAttribute('aria-activedescendant');
4414
-
4415
- fireEvent.keyDown(search, {key: 'ArrowDown'});
4416
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4417
- expect(search).toHaveAttribute('aria-activedescendant', bug.id);
4418
- const moved = fireEvent.keyDown(search, {key: 'ArrowRight'});
4419
- expect(moved).toBe(false);
4420
- expect(search).toHaveAttribute('aria-activedescendant', bug.children[1].id);
4421
- fireEvent.keyDown(search, {key: 'Enter'});
4422
- expect(edit).toHaveBeenCalledWith('bug');
4423
- expect(search).toHaveValue('Bu');
4424
- });
4425
-
4426
- it('hovering the action cell highlights that row on its action cell', async () => {
4427
- const user = userEvent.setup();
4428
- render(
4429
- <MultiSelector
4430
- label="Labels"
4431
- options={OPTIONS}
4432
- value={[]}
4433
- onChange={() => {}}
4434
- />,
4435
- );
4436
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4437
- await user.click(trigger);
4438
- const bug = screen.getByRole('row', {name: 'Bug', ...h});
4439
- const feature = screen.getByRole('row', {name: 'Feature', ...h});
4440
- fireEvent.mouseEnter(feature.children[0]);
4441
- expect(trigger).toHaveAttribute('aria-activedescendant', feature.id);
4442
- fireEvent.mouseEnter(bug.children[1]);
4443
- expect(trigger).toHaveAttribute(
4444
- 'aria-activedescendant',
4445
- bug.children[1].id,
4446
- );
4447
- fireEvent.mouseEnter(bug.children[0]);
4448
- expect(trigger).toHaveAttribute('aria-activedescendant', bug.id);
4449
- });
4450
-
4451
- it('Tab closes the panel as before: no action control is a stop inside it', async () => {
4452
- const user = userEvent.setup();
4453
- render(
4454
- <MultiSelector
4455
- label="Labels"
4456
- options={OPTIONS}
4457
- value={[]}
4458
- onChange={() => {}}
4459
- />,
4460
- );
4461
- const trigger = screen.getByRole('combobox', {name: 'Labels'});
4462
- await user.click(trigger);
4463
- expect(trigger).toHaveAttribute('aria-expanded', 'true');
4464
- fireEvent.keyDown(trigger, {key: 'Tab'});
4465
- await waitFor(() => {
4466
- expect(trigger).toHaveAttribute('aria-expanded', 'false');
4467
- });
4468
- });
4469
-
4470
- it('warns in development about an action control with no accessible name', async () => {
4471
- const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
4472
- try {
4473
- const user = userEvent.setup();
4474
- render(
4475
- <MultiSelector
4476
- label="Labels"
4477
- options={[
4478
- {value: 'bug', label: 'Bug', action: <button type="button" />},
4479
- ]}
4480
- value={[]}
4481
- onChange={() => {}}
4482
- />,
4483
- );
4484
- await user.click(screen.getByRole('combobox', {name: 'Labels'}));
4485
- expect(warn).toHaveBeenCalledWith(
4486
- expect.stringMatching(/MultiSelector[\s\S]*accessible name/),
4487
- );
4488
- } finally {
4489
- warn.mockRestore();
4490
- }
4491
- });
4492
- });
4493
-
4494
- describe('MultiSelector create row inside a grid', () => {
4495
- it('is a row with exactly two gridcells, the action cell empty, and the arrow stays on the option', async () => {
4496
- // spec:AST-058 FR3: in a grid every row has exactly two cells, and a
4497
- // system-minted row is no exception. The create row has no control of
4498
- // its own, so its action cell is present and empty, and the inline-end
4499
- // arrow has nowhere to go.
4500
- const user = userEvent.setup();
4501
- const onChange =
4502
- vi.fn<(value: string[], change?: MultiSelectorChange) => void>();
4503
- render(
4504
- <MultiSelector
4505
- label="Labels"
4506
- options={[
4507
- {
4508
- value: 'bug',
4509
- label: 'Bug',
4510
- action: <button type="button">Edit Bug</button>,
4511
- },
4512
- {value: 'feature', label: 'Feature'},
4513
- ]}
4514
- value={[]}
4515
- onChange={onChange}
4516
- hasSearch
4517
- hasCreate
4518
- />,
4519
- );
4520
- await user.click(screen.getByRole('button', {name: 'Labels'}));
4521
- const search = screen.getByRole('combobox', {hidden: true});
4522
- await user.type(search, 'Urgent');
4523
-
4524
- const rows = screen.getAllByRole('row', {hidden: true});
4525
- expect(rows).toHaveLength(1);
4526
- const create = rows[0];
4527
- expect(create).toHaveTextContent('Create "Urgent"');
4528
- const cells = Array.from(create.children).filter(
4529
- c => c.getAttribute('role') === 'gridcell',
4530
- );
4531
- expect(cells).toHaveLength(2);
4532
- expect(cells[1]).toBeEmptyDOMElement();
4533
-
4534
- await user.keyboard('{ArrowDown}{ArrowRight}{Enter}');
4535
- expect(onChange).toHaveBeenCalledWith(['Urgent'], {
4536
- type: 'create',
4537
- query: 'Urgent',
4538
- });
4539
- });
4540
- });
4541
-
4542
- describe('MultiSelector in an InputGroup', () => {
4543
- const groupClasses = () =>
4544
- stylex.props(groupStyles.inGroup).className!.split(' ');
4545
- const triggerBox = (name: RegExp) =>
4546
- screen
4547
- .getByRole('combobox', {name})
4548
- .closest('.astryx-multi-selector') as HTMLElement;
4549
-
4550
- it.each(['input', 'secondary'] as const)(
4551
- 'gives a grouped %s trigger the connected group geometry in every position',
4552
- variant => {
4553
- const selector = (label: string) => (
4554
- <MultiSelector
4555
- label={label}
4556
- isLabelHidden
4557
- variant={variant}
4558
- options={['Apple', 'Banana', 'Cherry']}
4559
- value={EMPTY_VALUE}
4560
- onChange={() => {}}
4561
- />
4562
- );
4563
- render(
4564
- <>
4565
- <InputGroup label="First">
4566
- {selector('Fruit first')}
4567
- <InputGroupText>kg</InputGroupText>
4568
- </InputGroup>
4569
- <InputGroup label="Middle">
4570
- <InputGroupText>#</InputGroupText>
4571
- {selector('Fruit middle')}
4572
- <InputGroupText>kg</InputGroupText>
4573
- </InputGroup>
4574
- <InputGroup label="Last">
4575
- <InputGroupText>#</InputGroupText>
4576
- {selector('Fruit last')}
4577
- </InputGroup>
4578
- </>,
4579
- );
4580
-
4581
- // Row height, seam radii, and focus stacking come from these classes;
4582
- // a secondary trigger keeps its fill but not its standalone edges.
4583
- for (const position of ['first', 'middle', 'last']) {
4584
- const classes = new Set(
4585
- triggerBox(new RegExp(`Fruit ${position}`)).className.split(' '),
4586
- );
4587
- expect(groupClasses().every(c => classes.has(c))).toBe(true);
4588
- }
4589
- },
4590
- );
4591
-
4592
- it('keeps a standalone secondary trigger out of the group geometry', () => {
4593
- render(
4594
- <MultiSelector
4595
- label="Fruit"
4596
- variant="secondary"
4597
- options={['Apple', 'Banana', 'Cherry']}
4598
- value={EMPTY_VALUE}
4599
- onChange={() => {}}
4600
- />,
4601
- );
4602
- const classes = new Set(triggerBox(/Fruit/).className.split(' '));
4603
- expect(groupClasses().some(c => classes.has(c))).toBe(false);
4604
- });
4605
- });
4606
-
4607
- describe('MultiSelector caller aria-labelledby', () => {
4608
- const FRUITS = ['Apple', 'Banana', 'Cherry'];
4609
-
4610
- it('names the trigger by its label and a caller aria-labelledby', () => {
4611
- render(
4612
- <>
4613
- <span id="lunch-heading">Lunch</span>
4614
- <MultiSelector
4615
- label="Fruits"
4616
- options={FRUITS}
4617
- value={[]}
4618
- onChange={() => {}}
4619
- aria-labelledby="lunch-heading"
4620
- />
4621
- </>,
4622
- );
4623
- const trigger = screen.getByRole('combobox', {name: 'Fruits Lunch'});
4624
- expect(trigger.getAttribute('aria-labelledby')?.split(' ')).toContain(
4625
- 'lunch-heading',
4626
- );
4627
- });
4628
-
4629
- it('names a grouped trigger by the group, its label, and a caller aria-labelledby', () => {
4630
- render(
4631
- <>
4632
- <span id="lunch-heading">Lunch</span>
4633
- <InputGroup label="Order">
4634
- <MultiSelector
4635
- label="Fruits"
4636
- options={FRUITS}
4637
- value={[]}
4638
- onChange={() => {}}
4639
- aria-labelledby="lunch-heading"
4640
- />
4641
- </InputGroup>
4642
- </>,
4643
- );
4644
- expect(
4645
- screen.getByRole('combobox', {name: 'Order Fruits Lunch'}),
4646
- ).toBeInTheDocument();
4647
- });
4648
-
4649
- it('leaves a standalone trigger named by its label element without a caller aria-labelledby', () => {
4650
- render(
4651
- <MultiSelector
4652
- label="Fruits"
4653
- options={FRUITS}
4654
- value={[]}
4655
- onChange={() => {}}
4656
- />,
4657
- );
4658
- const trigger = screen.getByRole('combobox', {name: 'Fruits'});
4659
- expect(trigger).not.toHaveAttribute('aria-labelledby');
4660
- });
4661
-
4662
- it('still hands a caller aria-labelledby to a renderTrigger control unchanged', () => {
4663
- render(
4664
- <>
4665
- <span id="lunch-heading">Lunch</span>
4666
- <MultiSelector
4667
- label="Fruits"
4668
- options={FRUITS}
4669
- value={[]}
4670
- onChange={() => {}}
4671
- aria-labelledby="lunch-heading"
4672
- renderTrigger={props => (
4673
- <button type="button" {...props}>
4674
- Pick fruits
4675
- </button>
4676
- )}
4677
- />
4678
- </>,
4679
- );
4680
- const opener = screen.getByRole('button', {name: 'Lunch'});
4681
- expect(opener).toHaveAttribute('aria-labelledby', 'lunch-heading');
4682
- });
4683
-
4684
- it("leaves a renderTrigger control's own aria-labelledby in place without a caller value", () => {
4685
- render(
4686
- <>
4687
- <span id="row-title">Lunch fruits</span>
4688
- <MultiSelector
4689
- label="Fruits"
4690
- options={FRUITS}
4691
- value={[]}
4692
- onChange={() => {}}
4693
- renderTrigger={props => (
4694
- <button type="button" aria-labelledby="row-title" {...props} />
4695
- )}
4696
- />
4697
- </>,
4698
- );
4699
- const opener = screen.getByRole('button', {name: 'Lunch fruits'});
4700
- expect(opener).toHaveAttribute('aria-labelledby', 'row-title');
4701
- });
4702
- });