@astryxdesign/core 0.7.5 → 0.7.6-canary.bb18c81

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 (353) 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/Avatar/Avatar.d.ts.map +1 -1
  5. package/dist/Avatar/Avatar.js +12 -3
  6. package/dist/BottomSheet/BottomSheet.js +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +2 -1
  9. package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
  10. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  11. package/dist/BottomSheet/useSheetGestures.js +30 -3
  12. package/dist/Button/Button.js +4 -4
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  15. package/dist/DateInput/DateInput.d.ts.map +1 -1
  16. package/dist/DateInput/DateInput.js +4 -1
  17. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  18. package/dist/DateInput/MonthScroller.js +101 -15
  19. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  20. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  21. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  22. package/dist/DateTimeInput/DateTimeInput.js +4 -1
  23. package/dist/Dialog/Dialog.d.ts.map +1 -1
  24. package/dist/Dialog/Dialog.js +34 -9
  25. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  26. package/dist/Dialog/DialogHeader.js +5 -4
  27. package/dist/Divider/Divider.d.ts.map +1 -1
  28. package/dist/Divider/Divider.js +2 -1
  29. package/dist/Drawer/Drawer.d.ts.map +1 -1
  30. package/dist/Drawer/Drawer.js +5 -0
  31. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  32. package/dist/DropdownMenu/DropdownMenu.js +4 -1
  33. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
  35. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
  36. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  37. package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
  38. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  39. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  40. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  41. package/dist/EmptyState/EmptyState.js +2 -1
  42. package/dist/Field/FieldLabel.d.ts +1 -1
  43. package/dist/Field/FieldLabel.d.ts.map +1 -1
  44. package/dist/Field/FieldLabel.js +4 -4
  45. package/dist/Field/InputClearButton.js +3 -3
  46. package/dist/Field/PanelSearchInput.js +2 -2
  47. package/dist/FieldStatus/FieldStatus.js +2 -2
  48. package/dist/FileInput/FileInput.d.ts.map +1 -1
  49. package/dist/FileInput/FileInput.js +7 -1
  50. package/dist/Heading/Heading.js +1 -1
  51. package/dist/Icon/Icon.js +1 -1
  52. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  53. package/dist/Indicator/CheckIndicator.js +2 -1
  54. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  55. package/dist/Indicator/CheckboxIndicator.js +3 -1
  56. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  57. package/dist/Indicator/RadioIndicator.js +3 -1
  58. package/dist/Item/Item.d.ts.map +1 -1
  59. package/dist/Item/Item.js +2 -1
  60. package/dist/Item/ItemSwipeLayer.js +1 -1
  61. package/dist/Layout/Layout.js +1 -1
  62. package/dist/Layout/LayoutContent.js +1 -1
  63. package/dist/Layout/LayoutFooter.js +1 -1
  64. package/dist/Layout/LayoutHeader.js +1 -1
  65. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  66. package/dist/Lightbox/Lightbox.js +5 -0
  67. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
  68. package/dist/List/List.js +1 -1
  69. package/dist/List/ListItem.js +3 -3
  70. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  71. package/dist/MobileNav/MobileNav.js +7 -2
  72. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  73. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  74. package/dist/MultiSelector/MultiSelector.js +5 -2
  75. package/dist/NumberInput/NumberInput.d.ts +3 -1
  76. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  77. package/dist/NumberInput/NumberInput.js +3 -3
  78. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  79. package/dist/ProgressBar/ProgressBar.js +16 -12
  80. package/dist/Resizable/useResizable.d.ts.map +1 -1
  81. package/dist/Resizable/useResizable.js +28 -2
  82. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  83. package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
  84. package/dist/Selector/useSelectorPresentation.d.ts +8 -1
  85. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  86. package/dist/Selector/useSelectorPresentation.js +20 -4
  87. package/dist/Slider/Slider.d.ts.map +1 -1
  88. package/dist/Slider/Slider.js +7 -1
  89. package/dist/Spinner/Spinner.js +2 -2
  90. package/dist/Stack/Stack.js +1 -1
  91. package/dist/Stack/StackItem.js +1 -1
  92. package/dist/TabList/TabList.d.ts.map +1 -1
  93. package/dist/TabList/TabList.js +16 -1
  94. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  95. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  96. package/dist/Text/Text.js +1 -1
  97. package/dist/Text/useTruncation.d.ts.map +1 -1
  98. package/dist/Text/useTruncation.js +23 -5
  99. package/dist/TextArea/TextArea.d.ts +1 -1
  100. package/dist/TextArea/TextArea.d.ts.map +1 -1
  101. package/dist/TextArea/TextArea.js +13 -2
  102. package/dist/TextInput/TextInput.d.ts.map +1 -1
  103. package/dist/TextInput/TextInput.js +3 -3
  104. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  105. package/dist/Timestamp/Timestamp.js +2 -2
  106. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  107. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  108. package/dist/Toast/ToastViewport.js +82 -51
  109. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  110. package/dist/Tokenizer/Tokenizer.js +13 -2
  111. package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
  112. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  113. package/dist/Toolbar/Toolbar.js +2 -1
  114. package/dist/Typeahead/Typeahead.d.ts +1 -1
  115. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  116. package/dist/Typeahead/Typeahead.js +9 -1
  117. package/dist/astryx.css +14 -1
  118. package/dist/gestures/PinchGestureRecognizer.d.ts +30 -0
  119. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  120. package/dist/gestures/PinchGestureRecognizer.js +125 -0
  121. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +70 -0
  122. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  123. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +220 -0
  124. package/dist/gestures/SwipeGestureRecognizer.d.ts +36 -0
  125. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  126. package/dist/gestures/SwipeGestureRecognizer.js +145 -0
  127. package/dist/gestures/VelocityTracker.d.ts +6 -2
  128. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  129. package/dist/gestures/VelocityTracker.js +7 -3
  130. package/dist/gestures/types.d.ts +11 -7
  131. package/dist/gestures/types.d.ts.map +1 -1
  132. package/dist/gestures/types.js +22 -2
  133. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  134. package/dist/hooks/useAdaptivePresentation.js +4 -1
  135. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  136. package/dist/hooks/useMenuHover.js +12 -1
  137. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  138. package/dist/i18n/generated-locales/en.generated.js +3 -0
  139. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  140. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  141. package/dist/motion/animator.d.ts +7 -1
  142. package/dist/motion/animator.d.ts.map +1 -1
  143. package/dist/motion/animator.js +119 -27
  144. package/dist/theme/defineTheme.js +1 -1
  145. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  146. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  147. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  148. package/dist/theme/domainTokens/index.d.ts +5 -4
  149. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  150. package/dist/theme/domainTokens/index.js +5 -9
  151. package/dist/utils/focusReturn.d.ts +17 -1
  152. package/dist/utils/focusReturn.d.ts.map +1 -1
  153. package/dist/utils/focusReturn.js +11 -4
  154. package/dist/utils/getNodeText.d.ts +8 -0
  155. package/dist/utils/getNodeText.d.ts.map +1 -0
  156. package/dist/utils/getNodeText.js +37 -0
  157. package/dist/utils/interactionModality.d.ts +11 -2
  158. package/dist/utils/interactionModality.d.ts.map +1 -1
  159. package/dist/utils/interactionModality.js +58 -5
  160. package/dist/utils/presentation.d.ts +50 -0
  161. package/dist/utils/presentation.d.ts.map +1 -0
  162. package/dist/utils/presentation.js +125 -0
  163. package/dist/utils/touchPresentation.d.ts.map +1 -1
  164. package/dist/utils/touchPresentation.js +5 -3
  165. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  166. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  167. package/dist/utils/touchPresentation.stylex.js +33 -0
  168. package/locales/en.json +13 -1
  169. package/locales/pseudo.json +9 -0
  170. package/package.json +5 -4
  171. package/src/AlertDialog/AlertDialog.tsx +6 -5
  172. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
  173. package/src/Avatar/Avatar.spec.md +22 -22
  174. package/src/Avatar/Avatar.tsx +18 -1
  175. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
  176. package/src/BottomSheet/BottomSheet.test.tsx +46 -0
  177. package/src/BottomSheet/BottomSheet.tsx +1 -1
  178. package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
  179. package/src/BottomSheet/useSheetGestures.test.ts +52 -0
  180. package/src/BottomSheet/useSheetGestures.ts +37 -6
  181. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
  182. package/src/Button/Button.tsx +4 -4
  183. package/src/Calendar/Calendar.spec.md +87 -48
  184. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
  185. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
  186. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  187. package/src/Collapsible/Collapsible.spec.md +2 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
  189. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
  190. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
  191. package/src/ContextMenu/ContextMenu.test.tsx +35 -0
  192. package/src/DateInput/DateInput.test.tsx +53 -2
  193. package/src/DateInput/DateInput.tsx +15 -11
  194. package/src/DateInput/MonthScroller.tsx +106 -20
  195. package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
  196. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
  197. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
  198. package/src/DateRangeInput/DateRangeInput.tsx +16 -12
  199. package/src/DateTimeInput/DateTimeInput.spec.md +71 -48
  200. package/src/DateTimeInput/DateTimeInput.test.tsx +61 -0
  201. package/src/DateTimeInput/DateTimeInput.tsx +15 -11
  202. package/src/Dialog/Dialog.test.tsx +216 -2
  203. package/src/Dialog/Dialog.tsx +42 -9
  204. package/src/Dialog/DialogHeader.tsx +5 -4
  205. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
  206. package/src/Divider/Divider.tsx +2 -1
  207. package/src/Drawer/Drawer.spec.md +111 -80
  208. package/src/Drawer/Drawer.test.tsx +22 -0
  209. package/src/Drawer/Drawer.tsx +5 -0
  210. package/src/Drawer/DrawerHeader.spec.md +181 -0
  211. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
  212. package/src/DropdownMenu/DropdownMenu.tsx +7 -1
  213. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
  214. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
  215. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
  216. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
  217. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +53 -0
  218. package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
  219. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  220. package/src/EmptyState/EmptyState.tsx +2 -1
  221. package/src/Field/FieldLabel.tsx +5 -4
  222. package/src/Field/InputClearButton.tsx +3 -3
  223. package/src/Field/PanelSearchInput.tsx +2 -2
  224. package/src/FieldStatus/FieldStatus.tsx +2 -2
  225. package/src/FileInput/FileInput.test.tsx +21 -0
  226. package/src/FileInput/FileInput.tsx +4 -1
  227. package/src/FormLayout/FormLayout.spec.md +7 -8
  228. package/src/Heading/Heading.tsx +1 -1
  229. package/src/Icon/Icon.tsx +1 -1
  230. package/src/Indicator/CheckIndicator.spec.md +47 -42
  231. package/src/Indicator/CheckIndicator.tsx +2 -1
  232. package/src/Indicator/CheckboxIndicator.spec.md +40 -37
  233. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  234. package/src/Indicator/Indicator.spec.md +188 -0
  235. package/src/Indicator/RadioIndicator.tsx +3 -1
  236. package/src/Item/Item.tsx +2 -1
  237. package/src/Item/ItemSwipeLayer.tsx +1 -1
  238. package/src/Layout/Layout.spec.md +127 -131
  239. package/src/Layout/Layout.tsx +1 -1
  240. package/src/Layout/LayoutContent.spec.md +205 -0
  241. package/src/Layout/LayoutContent.tsx +1 -1
  242. package/src/Layout/LayoutFooter.spec.md +190 -0
  243. package/src/Layout/LayoutFooter.tsx +1 -1
  244. package/src/Layout/LayoutHeader.spec.md +193 -0
  245. package/src/Layout/LayoutHeader.tsx +1 -1
  246. package/src/Layout/LayoutPanel.spec.md +194 -0
  247. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
  248. package/src/Lightbox/Lightbox.test.tsx +18 -0
  249. package/src/Lightbox/Lightbox.tsx +5 -0
  250. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
  251. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
  252. package/src/List/List.tsx +1 -1
  253. package/src/List/ListItem.tsx +3 -3
  254. package/src/MobileNav/MobileNav.test.tsx +15 -0
  255. package/src/MobileNav/MobileNav.tsx +9 -1
  256. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
  257. package/src/MoreMenu/MoreMenu.test.tsx +11 -0
  258. package/src/MultiSelector/MultiSelector.spec.md +7 -4
  259. package/src/MultiSelector/MultiSelector.test.tsx +118 -1
  260. package/src/MultiSelector/MultiSelector.tsx +6 -2
  261. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
  262. package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
  263. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  264. package/src/NumberInput/NumberInput.spec.md +59 -37
  265. package/src/NumberInput/NumberInput.tsx +13 -3
  266. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
  267. package/src/ProgressBar/ProgressBar.test.tsx +108 -32
  268. package/src/ProgressBar/ProgressBar.tsx +58 -10
  269. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
  270. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
  271. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
  272. package/src/Resizable/useResizable.test.ts +43 -0
  273. package/src/Resizable/useResizable.ts +32 -9
  274. package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
  275. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
  276. package/src/Selector/Selector.test.tsx +90 -1
  277. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
  278. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
  279. package/src/Selector/useSelectorPresentation.ts +31 -3
  280. package/src/SideNav/SideNav.test.tsx +27 -0
  281. package/src/Slider/Slider.test.tsx +12 -0
  282. package/src/Slider/Slider.tsx +4 -1
  283. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
  284. package/src/Spinner/Spinner.tsx +2 -2
  285. package/src/Stack/Stack.tsx +1 -1
  286. package/src/Stack/StackItem.tsx +1 -1
  287. package/src/TabList/TabList.test.tsx +52 -1
  288. package/src/TabList/TabList.tsx +16 -1
  289. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
  290. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
  291. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
  292. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  293. package/src/Text/Text.tsx +1 -1
  294. package/src/Text/useTruncation.test.tsx +128 -1
  295. package/src/Text/useTruncation.ts +23 -5
  296. package/src/TextArea/TextArea.spec.md +10 -13
  297. package/src/TextArea/TextArea.test.tsx +60 -0
  298. package/src/TextArea/TextArea.tsx +15 -1
  299. package/src/TextInput/TextInput.test.tsx +21 -0
  300. package/src/TextInput/TextInput.tsx +3 -1
  301. package/src/Timestamp/Timestamp.spec.md +6 -12
  302. package/src/Timestamp/Timestamp.test.tsx +30 -5
  303. package/src/Timestamp/Timestamp.tsx +6 -2
  304. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  305. package/src/Toast/Toast.spec.md +7 -10
  306. package/src/Toast/ToastViewport.test.tsx +575 -1
  307. package/src/Toast/ToastViewport.tsx +100 -50
  308. package/src/Tokenizer/Tokenizer.spec.md +5 -3
  309. package/src/Tokenizer/Tokenizer.test.tsx +68 -0
  310. package/src/Tokenizer/Tokenizer.tsx +14 -2
  311. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
  312. package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
  313. package/src/Toolbar/Toolbar.spec.md +5 -6
  314. package/src/Toolbar/Toolbar.test.tsx +30 -0
  315. package/src/Toolbar/Toolbar.tsx +2 -1
  316. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
  317. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
  318. package/src/TopNav/TopNav.test.tsx +27 -0
  319. package/src/Typeahead/Typeahead.test.tsx +40 -0
  320. package/src/Typeahead/Typeahead.tsx +12 -1
  321. package/src/gestures/PinchGestureRecognizer.ts +144 -0
  322. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +252 -0
  323. package/src/gestures/SwipeGestureRecognizer.ts +193 -0
  324. package/src/gestures/VelocityTracker.ts +10 -3
  325. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
  326. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
  327. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
  328. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
  329. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +188 -0
  330. package/src/gestures/__tests__/harness.ts +21 -2
  331. package/src/gestures/types.ts +23 -9
  332. package/src/hooks/useAdaptivePresentation.ts +4 -1
  333. package/src/hooks/useMenuHover.ts +15 -1
  334. package/src/i18n/generated-locales/en.generated.ts +4 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +4 -0
  336. package/src/motion/README.md +11 -1
  337. package/src/motion/animator.test.ts +151 -3
  338. package/src/motion/animator.ts +149 -33
  339. package/src/theme/defineTheme.ts +1 -1
  340. package/src/theme/derivedVarRegistry.test.ts +4 -0
  341. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  342. package/src/theme/domainTokens/index.ts +6 -10
  343. package/src/utils/focusReturn.ts +17 -6
  344. package/src/utils/getNodeText.test.ts +24 -0
  345. package/src/utils/getNodeText.ts +37 -0
  346. package/src/utils/interactionModality.test.ts +45 -1
  347. package/src/utils/interactionModality.ts +64 -5
  348. package/src/utils/presentation.test.ts +380 -0
  349. package/src/utils/presentation.ts +180 -0
  350. package/src/utils/pressPaintPriority.test.ts +80 -0
  351. package/src/utils/touchPresentation.stylex.ts +43 -0
  352. package/src/utils/touchPresentation.test.ts +140 -1
  353. package/src/utils/touchPresentation.ts +5 -3
@@ -3,11 +3,11 @@ schema_version: 3
3
3
  template_version: 7
4
4
  kind: component
5
5
  id: component:CollapsibleGroup
6
- authority: draft
6
+ authority: current
7
7
  archive_reason: null
8
8
  superseded_by: null
9
- approved_by: null
10
- approved_at: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-10-31
11
11
  owners: [cixzhang, imdreamrunner]
12
12
  review_triggers: [public-api, behavior, layout, accessibility, theming]
13
13
  verified_by:
@@ -17,6 +17,8 @@ verified_by:
17
17
  packages/core/src/Collapsible/__tests__/Collapsible.a11y.test.tsx,
18
18
  packages/core/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts,
19
19
  packages/core/src/Collapsible/__tests__/CollapsibleGroup.a11y.chromium.spec.ts,
20
+ packages/core/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx,
21
+ packages/core/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts,
20
22
  packages/core/src/theme/themingTargets.test.ts,
21
23
  apps/storybook/stories/CollapsibleGroup.stories.tsx,
22
24
  ]
@@ -31,7 +33,7 @@ architecture:
31
33
  architecture:component-test-sufficiency,
32
34
  ]
33
35
  contributing: [contributing:api-conventions]
34
- system_specs: [spec:AST-029]
36
+ system_specs: [spec:AST-029, spec:AST-085]
35
37
  ---
36
38
 
37
39
  # CollapsibleGroup component contract
@@ -44,11 +46,11 @@ system_specs: [spec:AST-029]
44
46
  | Behavior | Direct children with a `value` read group state; opening an item replaces or extends the open-value set according to mode. The group renders no wrapper unless divider chrome is enabled. |
45
47
  | End-user impact | People can reveal one section or several sections without disclosure state drifting between sibling rows; divided groups keep one coherent visual and directional treatment. |
46
48
  | Builder impact | Builders choose state ownership and selection mode, give participating items stable values, and may opt into divided-row presentation. |
47
- | Compatibility/readiness | Released DOM output, props, defaults, targets, and migration promises are unchanged. An unchanged controlled string preserves the coordination broadcast identity instead of rerendering every consumer. |
49
+ | Compatibility/readiness | Records shipped behavior; no public change. Three implementation gaps, listed under Current-state impact. |
48
50
  | Review checks | Reject lost single/multiple coordination, controlled state that mutates internally, uncontrolled state that stops updating, presentation leakage into nested disclosures, or a wrapper appearing when dividers are absent. |
49
51
  | Governing rules | `component:Collapsible/FR7–FR9,DEC-2`; `architecture:public-component-api/INV1,INV5–INV9`; `architecture:react-component-runtime/INV1,INV8–INV9`; `architecture:component-theming-surface/INV3–INV7`. |
50
52
 
51
- This table is a review projection; the body below is authoritative only after owner approval.
53
+ This table is a review projection; the body below is authoritative.
52
54
 
53
55
  ## Intent
54
56
 
@@ -57,12 +59,29 @@ Coordinate a set of disclosure items so their open state follows one group owner
57
59
  ## Compatibility and migration
58
60
 
59
61
  - Released default preserved: yes
60
- - Compatibility class: compatible runtime efficiency correction; no DOM output, public type, default, target, export, or migration change
62
+ - Compatibility class: records shipped behavior; no public change
61
63
  - Controlled/uncontrolled behavior: unchanged
62
64
  - Migration decision: none
63
65
 
64
66
  Consumer migration instructions belong in consumer docs and release notes.
65
67
 
68
+ ## Current-state impact
69
+
70
+ - **A disabled header leaves the Tab order.** A disabled direct item's header
71
+ carries `aria-disabled` but drops out of the Tab order, so a keyboard user
72
+ cannot reach it to learn why it is unavailable. `spec:AST-085` FR1 keeps a
73
+ disabled control focusable; the header is owned by `component:Collapsible`.
74
+ - **Equal values can rebuild the coordination context.** An equal new
75
+ controlled array, or a new `onChange` such as an inline handler, rebuilds the
76
+ coordination context on every parent render, so memoized direct items
77
+ re-render. `architecture:react-component-runtime` INV9 keeps an unchanged
78
+ payload from broadcasting a new identity (FR7).
79
+ - **The accordion binding runs adopted rows as advisory.** The binding runs
80
+ the header-to-header Tab step, single-mode sibling collapse, and no
81
+ arrow-key movement as advisory rows, so
82
+ a regression there does not fail the binding lane. `spec:AST-020` FR9 makes
83
+ an adopted expectation required once this record adopts it (AR4).
84
+
66
85
  ## Ownership boundary
67
86
 
68
87
  **Owns**
@@ -90,15 +109,15 @@ Consumer migration instructions belong in consumer docs and release notes.
90
109
 
91
110
  ## Behavioral and layout contract
92
111
 
93
- | ID | Candidate invariant | Basis | Draft review state |
94
- | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------ |
95
- | FR1 | In `single` mode, toggling a closed direct item MUST replace the open-value set with that item, and toggling the open item MUST clear the set. | shipped source, consumer docs, and focused interaction tests | verify |
96
- | FR2 | In `multiple` mode, toggling a direct item MUST add or remove only that value while preserving the state of other values. | shipped source, consumer docs, and focused interaction tests | verify |
97
- | FR3 | Supplying `value` MUST keep state caller-owned and report requested changes through `onChange`; omitting it MUST keep state internal after initialization from `defaultValue`. | `architecture:react-component-runtime/INV1,INV8`; shipped controlled and uncontrolled tests | verify |
98
- | FR4 | A direct `Collapsible` with a `value` MUST use group coordination; an item without a value MUST retain standalone ownership. | shipped hook behavior and focused group/standalone tests | verify |
99
- | FR5 | With `hasDividers=false`, the group MUST add no DOM wrapper. With dividers enabled it MUST render one wrapper that receives documented root props and the `collapsible-group` target. | `architecture:public-component-api/INV5–INV8`; `architecture:component-theming-surface/INV3–INV7` | verify |
100
- | FR6 | Density, divider, and chevron defaults MUST reach direct items without leaking through a direct item's revealed content into nested independent disclosures. | `component:Collapsible/FR7–FR8,DEC-2`; focused nesting tests | settled |
101
- | FR7 | Presentation context identity MUST remain stable while divider, density, and chevron inputs are unchanged; coordination identity MUST change only when the open-value behavior it broadcasts changes. | `architecture:react-component-runtime/INV9`; memoized source and interaction tests | verify |
112
+ | ID | Invariant | Basis |
113
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
114
+ | FR1 | In `single` mode, toggling a closed direct item MUST replace the open-value set with that item, and toggling the open item MUST clear the set. | shipped source, consumer docs, and focused interaction tests |
115
+ | FR2 | In `multiple` mode, toggling a direct item MUST add or remove only that value while preserving the state of other values. | shipped source, consumer docs, and focused interaction tests |
116
+ | FR3 | Supplying `value` MUST keep state caller-owned and report requested changes through `onChange`; omitting it MUST keep state internal after initialization from `defaultValue`. | `architecture:react-component-runtime/INV1,INV8`; shipped controlled and uncontrolled tests |
117
+ | FR4 | A direct `Collapsible` with a `value` MUST use group coordination; an item without a value MUST retain standalone ownership. | shipped hook behavior and focused group/standalone tests |
118
+ | FR5 | With `hasDividers=false`, the group MUST add no DOM wrapper. With dividers enabled it MUST render one wrapper that receives documented root props and the `collapsible-group` target. | `architecture:public-component-api/INV5–INV8`; `architecture:component-theming-surface/INV3–INV7` |
119
+ | FR6 | Density, divider, and chevron defaults MUST reach direct items without leaking through a direct item's revealed content into nested independent disclosures. | `component:Collapsible/FR7–FR8,DEC-2`; focused nesting tests |
120
+ | FR7 | Presentation context identity MUST remain stable while divider, density, and chevron inputs are unchanged; coordination identity MUST stay stable across a rerender with an unchanged controlled string, the same controlled array reference, or unchanged internal state, with the same `type` and `onChange`. Implementation gap: an equal new controlled array or a new `onChange` rebuilds it (Current-state impact). | `architecture:react-component-runtime/INV9`; memoized source and interaction tests |
102
121
 
103
122
  ### Allowed variation
104
123
 
@@ -126,14 +145,14 @@ Consumer migration instructions belong in consumer docs and release notes.
126
145
  ### Performance and resources
127
146
 
128
147
  - **PR1 — Render-owned coordination.** The group derives output from props, state, and context and owns no Effect, listener, observer, timer, request, subscription, or imperative browser resource.
129
- - **PR2 — Stable broadcasts.** Memoized context values prevent unchanged semantic payloads from broadcasting new identities to direct consumers.
148
+ - **PR2 — Stable broadcasts.** Memoized context values keep the identities FR7 names stable for direct consumers.
130
149
 
131
150
  ## Accessibility contract
132
151
 
133
152
  - **AR1 — Child semantics remain primary.** Group coordination MUST preserve each Collapsible button's accessible name, `aria-expanded`, `aria-controls`, controlled region, and keyboard activation.
134
153
  - **AR2 — Presentation adds no competing interaction.** The divider wrapper MUST NOT add a role, focus target, accessible name, or keyboard behavior.
135
154
  - **AR3 — Direction stays with Collapsible.** Group chevron defaults MUST preserve `component:Collapsible/AR1–AR3`, including the inward leading cue under LTR and RTL.
136
- - **AR4 — The group adopts the APG Accordion pattern's required parts** (`spec:AST-020/FR15`): each direct item's header is a `button` whose accessible name is the item's title, with `aria-expanded` reflecting its panel, `aria-controls` naming the panel it reveals, and `aria-disabled` when it cannot be toggled; every enabled header sits in the page Tab order and Enter or Space toggles its panel, in `single` mode collapsing the sibling that was open; a disabled header carries `aria-disabled`, drops out of the Tab order, and cannot be toggled. Not adopted: a heading element around each header (this record requires none; the caller's document structure supplies headings), the optional `region` role on panels, and arrow-key or Home/End movement between headers — the group is a set of independent disclosures, not one composite control.
155
+ - **AR4 — The group adopts the APG Accordion pattern's required parts** (`spec:AST-020/FR15`): each direct item's header is a `button` whose accessible name is the item's title, with `aria-expanded` reflecting its panel, `aria-controls` naming the panel it reveals, and `aria-disabled` when it cannot be toggled; every enabled header sits in the page Tab order and Enter or Space toggles its panel, in `single` mode collapsing the sibling that was open; a disabled header carries `aria-disabled` and cannot be toggled, apart from the Tab-order gap (Current-state impact). Not adopted: a heading element around each header (this record requires none; the caller's document structure supplies headings), the optional `region` role on panels, and arrow-key or Home/End movement between headers — the group is a set of independent disclosures, not one composite control.
137
156
 
138
157
  ## Design relationships
139
158
 
@@ -142,7 +161,7 @@ Consumer migration instructions belong in consumer docs and release notes.
142
161
  | Group container | Optional divider chrome visually associates sibling disclosures as a set. | observed shipped representation | aggregate container | FR5–FR6, AR2 |
143
162
  | Direct item | Density and chevron defaults remain consistent across sibling rows. | `component:Collapsible/FR7–FR8` | disclosure row | FR6, AR3 |
144
163
 
145
- No current design record prescribes exact CollapsibleGroup pixels. This observational draft records the shipped divided and plain relationships without deciding new visual direction.
164
+ No current design record prescribes exact CollapsibleGroup pixels. This record covers the shipped divided and plain relationships without deciding new visual direction.
146
165
 
147
166
  ### Theming anatomy
148
167
 
@@ -160,7 +179,7 @@ No current design record prescribes exact CollapsibleGroup pixels. This observat
160
179
  }
161
180
  ```
162
181
 
163
- The Group container is CollapsibleGroup's conditional aggregate wrapper. The other anatomy rows remain owned by `component:Collapsible`; they are repeated in this observational subcomponent map only because the released consumer anatomy and target inventory are shared. Trigger, content, chevron, open state, disabled state, density padding, and per-item dividers remain with Collapsible's contract.
182
+ The Group container is CollapsibleGroup's conditional aggregate wrapper. The other anatomy rows remain owned by `component:Collapsible`; they are repeated in this subcomponent map only because the released consumer anatomy and target inventory are shared. Trigger, content, chevron, open state, disabled state, density padding, and per-item dividers remain with Collapsible's contract.
164
183
 
165
184
  ## Family and system relationships
166
185
 
@@ -172,14 +191,23 @@ The Group container is CollapsibleGroup's conditional aggregate wrapper. The oth
172
191
 
173
192
  ## Verification map
174
193
 
175
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
176
- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
177
- | FR1–FR4, ORD1 | `CollapsibleGroup.test.tsx`; `useCollapsible.test.tsx` | single/multiple; controlled/uncontrolled; valued/unvalued | A sibling stays open in single mode, closes in multiple mode, controlled state mutates internally, or an unvalued item loses local state. | `audit:CollapsibleGroup/behavior` |
178
- | FR5–FR6, ORD2–ORD3 | `CollapsibleGroup.test.tsx`; `Collapsible.test.tsx`; `themingTargets.test.ts` | plain/divided; each density; group/item/nested position | Wrapper/target reachability changes, defaults stop reaching direct items, or presentation leaks into nested content. | `audit:CollapsibleGroup/theming` |
179
- | AR4 | `Collapsible.test.tsx` (disabled trigger: `aria-disabled`, out of the Tab order, no toggle); `CollapsibleGroup.test.tsx` (single/multiple coordination, `aria-expanded`, Enter activation); `Collapsible.a11y.test.tsx` and `Collapsible.a11y.chromium.spec.ts` (disclosure semantics, Space, Tab reachability for a lone item); grouped Tab order, Space in a group, and no arrow-key movement under the Accordion contract (`internal/a11y-spec`, pending) | single/multiple; open/closed; a disabled item, open or closed; Tab order; Enter/Space round trip | A header loses its button role, name, `aria-expanded`, or `aria-controls`; Enter or Space stops toggling; arrow keys move focus between headers | `audit:CollapsibleGroup/accessibility` |
180
- | AR1–AR3 | DOM and Chromium accessibility suites; component-owned Storybook fixture; RTL audit | open/closed; single/multiple; leading/trailing; LTR/RTL | Disclosure semantics diverge from state, the wrapper becomes interactive, or leading direction stops mirroring. | `audit:CollapsibleGroup/accessibility` |
181
- | PR1–PR2, FR7 | source review plus context and interaction tests | unchanged and changed state/presentation inputs | An Effect/resource appears, unchanged payload broadcasts a new identity, or changed group behavior fails to reach consumers. | `audit:CollapsibleGroup/code-health` |
182
- | Consumer documentation | CLI component projection, docs checks, and `CollapsibleGroup.stories.tsx` | every public concept and representative composition | A released prop/default is undiscoverable or the owned browser route loses representative state coverage. | `audit:CollapsibleGroup/docs` |
194
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
195
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- |
196
+ | FR1–FR4, ORD1 | `CollapsibleGroup.test.tsx`; `useCollapsible.test.tsx` | single/multiple; controlled/uncontrolled; valued/unvalued | A sibling stays open in single mode, closes in multiple mode, controlled state mutates internally, or an unvalued item loses local state. | `audit:CollapsibleGroup/behavior` |
197
+ | FR5–FR6, ORD2–ORD3 | `CollapsibleGroup.test.tsx`; `Collapsible.test.tsx`; `themingTargets.test.ts` | plain/divided; each density; group/item/nested position | Wrapper/target reachability changes, defaults stop reaching direct items, or presentation leaks into nested content. | `audit:CollapsibleGroup/theming` |
198
+ | AR4 | `Collapsible.test.tsx` (disabled trigger: `aria-disabled` and no toggle; its out-of-Tab-order assertion pins the gap, pending conformance); `CollapsibleGroup.test.tsx` (single/multiple coordination, `aria-expanded`, Enter activation); `Collapsible.a11y.test.tsx` and `Collapsible.a11y.chromium.spec.ts` (disclosure semantics, Space, Tab reachability for a lone item); `CollapsibleGroup.a11y.test.tsx` and `CollapsibleGroupAccordion.a11y.chromium.spec.ts` (Space on grouped headers; the header-to-header Tab step, single-mode sibling collapse, and no arrow-key movement run as advisory rows) | single/multiple; open/closed; a disabled item, open or closed; Tab order; Enter/Space round trip | A header loses its button role, name, `aria-expanded`, or `aria-controls`; Enter or Space stops toggling; arrow keys move focus between headers | `audit:CollapsibleGroup/accessibility` |
199
+ | AR1–AR3 | DOM and Chromium accessibility suites; component-owned Storybook fixture; RTL audit | open/closed; single/multiple; leading/trailing; LTR/RTL | Disclosure semantics diverge from state, the wrapper becomes interactive, or leading direction stops mirroring. | `audit:CollapsibleGroup/accessibility` |
200
+ | PR1–PR2, FR7 | source review plus context and interaction tests | unchanged and changed state/presentation inputs | An Effect/resource appears, unchanged payload broadcasts a new identity, or changed group behavior fails to reach consumers. | `audit:CollapsibleGroup/code-health` |
201
+ | Consumer documentation | CLI component projection, docs checks, and `CollapsibleGroup.stories.tsx` | every public concept and representative composition | A released prop/default is undiscoverable or the owned browser route loses representative state coverage. | `audit:CollapsibleGroup/docs` |
202
+
203
+ Read from source, with no focused test yet:
204
+
205
+ - AR4: no Home/End movement between headers.
206
+ - FR7: presentation context identity staying stable while divider, density,
207
+ and chevron inputs are unchanged, and coordination identity across the same
208
+ array reference, unchanged internal state, and an equal new array; only the
209
+ controlled-string case is tested.
210
+ - PR1: the group owning no Effect or browser resource.
183
211
 
184
212
  ## Decision log
185
213
 
@@ -194,7 +222,7 @@ Rejected: arrow-key movement between headers; a required heading element around
194
222
 
195
223
  ## Open questions
196
224
 
197
- None. This observational draft does not broaden the released coordination modes or decide new presentation.
225
+ None. This record does not broaden the released coordination modes or decide new presentation.
198
226
 
199
227
  ## Content boundary
200
228
 
@@ -155,16 +155,27 @@ test('the state inventory matches rendered disclosure facts', async ({
155
155
  expect(wrong).toEqual([]);
156
156
  });
157
157
 
158
- test('every disclosure expectation is exercised by a Collapsible state', async ({
159
- page,
160
- }) => {
161
- test.setTimeout(3 * 60 * 1000);
162
- const cdp = await page.context().newCDPSession(page);
158
+ // Checking coverage runs every state again, which on a loaded runner nears
159
+ // the per-test timeout as one test. It runs as one serial part per state,
160
+ // sharing their results, then one check over all of them.
161
+ test.describe('every disclosure expectation is exercised by a Collapsible state', () => {
162
+ test.describe.configure({mode: 'serial'});
163
163
  const results: BindingResult[] = [];
164
+
164
165
  for (const state of COLLAPSIBLE_DISCLOSURE_STATES) {
165
- results.push(await runState(page, cdp, state));
166
+ test(`runs ${state.id}`, async ({page}) => {
167
+ const cdp = await page.context().newCDPSession(page);
168
+ results.push(await runState(page, cdp, state));
169
+ });
166
170
  }
167
- expect(neverExercised(results)).toEqual([]);
171
+
172
+ test('no expectation is left unexercised', () => {
173
+ // Every part ran in this worker; this test alone proves nothing.
174
+ expect(results.map(result => result.state).sort()).toEqual(
175
+ COLLAPSIBLE_DISCLOSURE_STATES.map(state => state.id).sort(),
176
+ );
177
+ expect(neverExercised(results)).toEqual([]);
178
+ });
168
179
  });
169
180
 
170
181
  for (const state of COLLAPSIBLE_DISCLOSURE_STATES) {
@@ -103,15 +103,27 @@ async function runState(
103
103
  });
104
104
  }
105
105
 
106
- test('every accordion expectation is exercised by a CollapsibleGroup state', async ({
107
- page,
108
- }) => {
109
- const cdp = await page.context().newCDPSession(page);
106
+ // Checking coverage runs every state again, which on a loaded runner nears
107
+ // the per-test timeout as one test. It runs as one serial part per state,
108
+ // sharing their results, then one check over all of them.
109
+ test.describe('every accordion expectation is exercised by a CollapsibleGroup state', () => {
110
+ test.describe.configure({mode: 'serial'});
110
111
  const results: BindingResult[] = [];
112
+
111
113
  for (const state of COLLAPSIBLE_GROUP_ACCORDION_STATES) {
112
- results.push(await runState(page, cdp, state));
114
+ test(`runs ${state.id}`, async ({page}) => {
115
+ const cdp = await page.context().newCDPSession(page);
116
+ results.push(await runState(page, cdp, state));
117
+ });
113
118
  }
114
- expect(neverExercised(results)).toEqual([]);
119
+
120
+ test('no expectation is left unexercised', () => {
121
+ // Every part ran in this worker; this test alone proves nothing.
122
+ expect(results.map(result => result.state).sort()).toEqual(
123
+ COLLAPSIBLE_GROUP_ACCORDION_STATES.map(state => state.id).sort(),
124
+ );
125
+ expect(neverExercised(results)).toEqual([]);
126
+ });
115
127
  });
116
128
 
117
129
  for (const state of COLLAPSIBLE_GROUP_ACCORDION_STATES) {
@@ -1320,6 +1320,41 @@ describe('ContextMenu keyboard access for menuitemradio/menuitemcheckbox (#3829)
1320
1320
  });
1321
1321
  });
1322
1322
 
1323
+ describe('ContextMenu data-driven submenu inside a section', () => {
1324
+ it('renders a submenu row for an item with nested items inside a section', () => {
1325
+ render(
1326
+ <ContextMenu
1327
+ items={[
1328
+ {
1329
+ type: 'section',
1330
+ title: 'Organize',
1331
+ items: [
1332
+ {label: 'Rename', onClick: () => {}},
1333
+ {
1334
+ label: 'Move to',
1335
+ items: [
1336
+ {label: 'Folder A', onClick: () => {}},
1337
+ {label: 'Folder B', onClick: () => {}},
1338
+ ],
1339
+ },
1340
+ ],
1341
+ },
1342
+ ]}>
1343
+ <div>Right-click me</div>
1344
+ </ContextMenu>,
1345
+ );
1346
+ fireEvent.contextMenu(screen.getByText('Right-click me'));
1347
+ const trigger = screen.getByRole('menuitem', {
1348
+ name: /Move to/,
1349
+ hidden: true,
1350
+ });
1351
+ expect(trigger).toHaveAttribute('aria-haspopup', 'menu');
1352
+ expect(
1353
+ screen.getByRole('menuitem', {name: 'Folder A', hidden: true}),
1354
+ ).toBeInTheDocument();
1355
+ });
1356
+ });
1357
+
1323
1358
  describe('ContextMenu press model', () => {
1324
1359
  const touch = {pointerType: 'touch', pointerId: 1};
1325
1360
  const item = (name: string) =>
@@ -633,15 +633,19 @@ describe('DateInput', () => {
633
633
  }
634
634
 
635
635
  render(<ControlledDateInput />);
636
- await user.click(getButton('Open calendar'));
636
+ const toggle = getButton('Open calendar');
637
+ await user.click(toggle);
637
638
  await user.click(getButton(/January 15, 2026/));
639
+ // The calendar mounts only while open; reopen it to read the selection.
640
+ await user.click(toggle);
638
641
  expect(
639
642
  screen.getAllByRole('gridcell', {selected: true, hidden: true}),
640
643
  ).toHaveLength(1);
644
+ await user.click(toggle);
641
645
 
642
646
  await user.click(getButton('Clear Date'));
643
647
  expect(screen.getByRole('combobox')).toHaveValue('');
644
- await user.click(getButton('Open calendar'));
648
+ await user.click(toggle);
645
649
 
646
650
  expect(
647
651
  screen.queryAllByRole('gridcell', {selected: true, hidden: true}),
@@ -1152,6 +1156,53 @@ describe('DateInput', () => {
1152
1156
  expect(screen.getByDisplayValue('Mar 10, 2026')).toBeInTheDocument();
1153
1157
  });
1154
1158
  });
1159
+ describe('calendar while the popover is closed', () => {
1160
+ const calendarGrid = () => document.querySelector('[role="grid"]');
1161
+
1162
+ it('renders no calendar until the popover opens, and none after it closes', () => {
1163
+ render(<DateInput label="Date" value="2026-01-25" onChange={() => {}} />);
1164
+ const toggle = getButton('Open calendar');
1165
+ expect(calendarGrid()).toBeNull();
1166
+
1167
+ fireEvent.click(toggle);
1168
+ expect(calendarGrid()).not.toBeNull();
1169
+
1170
+ fireEvent.click(toggle);
1171
+ expect(calendarGrid()).toBeNull();
1172
+ });
1173
+
1174
+ it('opens on the month of a date typed while closed', () => {
1175
+ function Controlled() {
1176
+ const [value, setValue] = useState<ISODateString | undefined>(
1177
+ '2026-01-25',
1178
+ );
1179
+ return <DateInput label="Date" value={value} onChange={setValue} />;
1180
+ }
1181
+ render(<Controlled />);
1182
+ const input = screen.getByRole('combobox');
1183
+
1184
+ fireEvent.change(input, {target: {value: 'March 5, 2027'}});
1185
+ fireEvent.keyDown(input, {key: 'ArrowDown'});
1186
+
1187
+ expect(calendarGrid()).toHaveAccessibleName('March 2027');
1188
+ });
1189
+
1190
+ it('moves focus into the calendar on each open from the toggle', async () => {
1191
+ render(<DateInput label="Date" value="2026-01-25" onChange={() => {}} />);
1192
+ const toggle = getButton('Open calendar');
1193
+
1194
+ for (let i = 0; i < 2; i++) {
1195
+ fireEvent.click(toggle);
1196
+ // The first calendar control takes focus, not the dialog surface.
1197
+ await waitFor(() => {
1198
+ expect(getButton('Previous month')).toHaveFocus();
1199
+ });
1200
+ fireEvent.click(toggle);
1201
+ expect(calendarGrid()).toBeNull();
1202
+ }
1203
+ });
1204
+ });
1205
+
1155
1206
  describe('weekStartsOn', () => {
1156
1207
  // The calendar popover renders in the top layer; jsdom keeps the content in
1157
1208
  // the DOM but role queries skip it, so read the columnheaders directly.
@@ -948,17 +948,21 @@ function PointerDateField({
948
948
  {isBusy && <Spinner size="sm" aria-hidden="true" />}
949
949
  {statusIcon}
950
950
  {popover.render(
951
- <Calendar
952
- handleRef={calendarRef}
953
- mode="single"
954
- value={optimisticValue ?? null}
955
- onChange={handleDateSelect}
956
- min={min}
957
- max={max}
958
- dateConstraints={dateConstraints}
959
- numberOfMonths={numberOfMonths}
960
- weekStartsOn={weekStartsOn}
961
- />,
951
+ // The layer renders its children open or closed, so mount the
952
+ // calendar only while it is open; it opens on the current value.
953
+ !popover.isOpen ? null : (
954
+ <Calendar
955
+ handleRef={calendarRef}
956
+ mode="single"
957
+ value={optimisticValue ?? null}
958
+ onChange={handleDateSelect}
959
+ min={min}
960
+ max={max}
961
+ dateConstraints={dateConstraints}
962
+ numberOfMonths={numberOfMonths}
963
+ weekStartsOn={weekStartsOn}
964
+ />
965
+ ),
962
966
  {placement: 'below', alignment: 'start'},
963
967
  )}
964
968
  {showsDisabledMessage &&