@astryxdesign/core 0.7.5 → 0.7.6-canary.5cd7a15

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 (463) 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/Badge/Badge.d.ts.map +1 -1
  7. package/dist/Badge/Badge.js +7 -1
  8. package/dist/BottomSheet/BottomSheet.js +1 -1
  9. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  10. package/dist/BottomSheet/BottomSheetPanel.js +2 -1
  11. package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
  12. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  13. package/dist/BottomSheet/useSheetGestures.js +30 -3
  14. package/dist/Button/Button.js +4 -4
  15. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  16. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  17. package/dist/DateInput/DateInput.d.ts.map +1 -1
  18. package/dist/DateInput/DateInput.js +12 -4
  19. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  20. package/dist/DateInput/MonthScroller.js +101 -15
  21. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  22. package/dist/DateInput/TouchDateField.js +8 -3
  23. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  24. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  25. package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
  26. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  27. package/dist/DateTimeInput/DateTimeInput.js +23 -12
  28. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
  29. package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
  30. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  31. package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
  32. package/dist/Dialog/Dialog.d.ts.map +1 -1
  33. package/dist/Dialog/Dialog.js +34 -9
  34. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  35. package/dist/Dialog/DialogHeader.js +5 -4
  36. package/dist/Divider/Divider.d.ts.map +1 -1
  37. package/dist/Divider/Divider.js +2 -1
  38. package/dist/Drawer/Drawer.d.ts +1 -0
  39. package/dist/Drawer/Drawer.d.ts.map +1 -1
  40. package/dist/Drawer/Drawer.js +7 -2
  41. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  42. package/dist/DropdownMenu/DropdownMenu.js +4 -1
  43. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  44. package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
  45. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
  46. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  47. package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
  48. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  49. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  50. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  51. package/dist/EmptyState/EmptyState.js +2 -1
  52. package/dist/Field/FieldLabel.d.ts +1 -1
  53. package/dist/Field/FieldLabel.d.ts.map +1 -1
  54. package/dist/Field/FieldLabel.js +4 -4
  55. package/dist/Field/InputClearButton.js +3 -3
  56. package/dist/Field/PanelSearchInput.js +2 -2
  57. package/dist/FieldStatus/FieldStatus.js +2 -2
  58. package/dist/FileInput/FileInput.d.ts.map +1 -1
  59. package/dist/FileInput/FileInput.js +7 -1
  60. package/dist/Heading/Heading.js +1 -1
  61. package/dist/Icon/Icon.js +1 -1
  62. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  63. package/dist/Indicator/CheckIndicator.js +2 -1
  64. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  65. package/dist/Indicator/CheckboxIndicator.js +3 -1
  66. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  67. package/dist/Indicator/RadioIndicator.js +3 -1
  68. package/dist/Item/Item.d.ts.map +1 -1
  69. package/dist/Item/Item.js +2 -1
  70. package/dist/Item/ItemSwipeLayer.js +1 -1
  71. package/dist/Layout/Layout.js +1 -1
  72. package/dist/Layout/LayoutContent.js +1 -1
  73. package/dist/Layout/LayoutFooter.js +1 -1
  74. package/dist/Layout/LayoutHeader.js +1 -1
  75. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  76. package/dist/Lightbox/Lightbox.js +5 -0
  77. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
  78. package/dist/List/List.js +1 -1
  79. package/dist/List/ListItem.js +3 -3
  80. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  81. package/dist/MobileNav/MobileNav.js +7 -2
  82. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  83. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  84. package/dist/MultiSelector/MultiSelector.js +5 -2
  85. package/dist/NumberInput/NumberInput.d.ts +3 -1
  86. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  87. package/dist/NumberInput/NumberInput.js +3 -3
  88. package/dist/NumberInput/numberParser.d.ts +3 -0
  89. package/dist/NumberInput/numberParser.d.ts.map +1 -1
  90. package/dist/NumberInput/numberParser.js +7 -44
  91. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  92. package/dist/ProgressBar/ProgressBar.js +16 -12
  93. package/dist/Resizable/useResizable.d.ts.map +1 -1
  94. package/dist/Resizable/useResizable.js +28 -2
  95. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  96. package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
  97. package/dist/Selector/useSelectorPresentation.d.ts +8 -1
  98. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  99. package/dist/Selector/useSelectorPresentation.js +20 -4
  100. package/dist/Slider/Slider.d.ts.map +1 -1
  101. package/dist/Slider/Slider.js +7 -1
  102. package/dist/Spinner/Spinner.js +2 -2
  103. package/dist/Stack/Stack.js +1 -1
  104. package/dist/Stack/StackItem.js +1 -1
  105. package/dist/TabList/TabList.d.ts.map +1 -1
  106. package/dist/TabList/TabList.js +16 -1
  107. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  108. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  109. package/dist/Text/Text.js +1 -1
  110. package/dist/Text/useTruncation.d.ts.map +1 -1
  111. package/dist/Text/useTruncation.js +23 -5
  112. package/dist/TextArea/TextArea.d.ts +1 -1
  113. package/dist/TextArea/TextArea.d.ts.map +1 -1
  114. package/dist/TextArea/TextArea.js +13 -2
  115. package/dist/TextInput/TextInput.d.ts.map +1 -1
  116. package/dist/TextInput/TextInput.js +3 -3
  117. package/dist/TimeInput/TimeInput.d.ts +6 -2
  118. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  119. package/dist/TimeInput/TimeInput.js +21 -14
  120. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  121. package/dist/TimeInput/TouchTimeField.js +5 -4
  122. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  123. package/dist/Timestamp/Timestamp.js +2 -2
  124. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  125. package/dist/Toast/Toast.d.ts.map +1 -1
  126. package/dist/Toast/Toast.js +60 -20
  127. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  128. package/dist/Toast/ToastViewport.js +82 -51
  129. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  130. package/dist/Tokenizer/Tokenizer.js +13 -2
  131. package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
  132. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  133. package/dist/Toolbar/Toolbar.js +2 -1
  134. package/dist/Typeahead/Typeahead.d.ts +1 -1
  135. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  136. package/dist/Typeahead/Typeahead.js +9 -1
  137. package/dist/astryx.css +15 -1
  138. package/dist/gestures/GestureArena.d.ts +6 -0
  139. package/dist/gestures/GestureArena.d.ts.map +1 -1
  140. package/dist/gestures/GestureArena.js +39 -0
  141. package/dist/gestures/GestureHost.d.ts +352 -0
  142. package/dist/gestures/GestureHost.d.ts.map +1 -0
  143. package/dist/gestures/GestureHost.js +1773 -0
  144. package/dist/gestures/GestureRecognizer.d.ts +10 -0
  145. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  146. package/dist/gestures/GestureRecognizer.js +16 -0
  147. package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
  148. package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
  149. package/dist/gestures/LongPressGestureRecognizer.js +3 -0
  150. package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
  151. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  152. package/dist/gestures/PanGestureRecognizer.js +3 -0
  153. package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
  154. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  155. package/dist/gestures/PinchGestureRecognizer.js +128 -0
  156. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
  157. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  158. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
  159. package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
  160. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  161. package/dist/gestures/SwipeGestureRecognizer.js +148 -0
  162. package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
  163. package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
  164. package/dist/gestures/TapGestureRecognizer.js +3 -0
  165. package/dist/gestures/VelocityTracker.d.ts +6 -2
  166. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  167. package/dist/gestures/VelocityTracker.js +7 -3
  168. package/dist/gestures/haptics.d.ts +65 -0
  169. package/dist/gestures/haptics.d.ts.map +1 -0
  170. package/dist/gestures/haptics.js +182 -0
  171. package/dist/gestures/nativeScroll.d.ts +42 -0
  172. package/dist/gestures/nativeScroll.d.ts.map +1 -0
  173. package/dist/gestures/nativeScroll.js +228 -0
  174. package/dist/gestures/touchAction.d.ts +60 -0
  175. package/dist/gestures/touchAction.d.ts.map +1 -0
  176. package/dist/gestures/touchAction.js +157 -0
  177. package/dist/gestures/touchActionRule.d.ts +34 -0
  178. package/dist/gestures/touchActionRule.d.ts.map +1 -0
  179. package/dist/gestures/touchActionRule.js +140 -0
  180. package/dist/gestures/types.d.ts +18 -7
  181. package/dist/gestures/types.d.ts.map +1 -1
  182. package/dist/gestures/types.js +22 -2
  183. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  184. package/dist/hooks/useAdaptivePresentation.js +4 -1
  185. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  186. package/dist/hooks/useMenuHover.js +12 -1
  187. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  188. package/dist/i18n/generated-locales/en.generated.js +3 -0
  189. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  190. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  191. package/dist/motion/animator.d.ts +7 -1
  192. package/dist/motion/animator.d.ts.map +1 -1
  193. package/dist/motion/animator.js +119 -27
  194. package/dist/theme/defineTheme.js +1 -1
  195. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  196. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  197. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  198. package/dist/theme/domainTokens/index.d.ts +5 -4
  199. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  200. package/dist/theme/domainTokens/index.js +5 -9
  201. package/dist/utils/focusReturn.d.ts +17 -1
  202. package/dist/utils/focusReturn.d.ts.map +1 -1
  203. package/dist/utils/focusReturn.js +11 -4
  204. package/dist/utils/getNodeText.d.ts +8 -0
  205. package/dist/utils/getNodeText.d.ts.map +1 -0
  206. package/dist/utils/getNodeText.js +37 -0
  207. package/dist/utils/interactionModality.d.ts +11 -2
  208. package/dist/utils/interactionModality.d.ts.map +1 -1
  209. package/dist/utils/interactionModality.js +58 -5
  210. package/dist/utils/plainDate.d.ts +2 -0
  211. package/dist/utils/plainDate.d.ts.map +1 -1
  212. package/dist/utils/plainDate.js +46 -3
  213. package/dist/utils/presentation.d.ts +50 -0
  214. package/dist/utils/presentation.d.ts.map +1 -0
  215. package/dist/utils/presentation.js +125 -0
  216. package/dist/utils/primaryClick.d.ts +11 -0
  217. package/dist/utils/primaryClick.d.ts.map +1 -0
  218. package/dist/utils/primaryClick.js +25 -0
  219. package/dist/utils/timeFormat.d.ts +47 -0
  220. package/dist/utils/timeFormat.d.ts.map +1 -0
  221. package/dist/utils/timeFormat.js +240 -0
  222. package/dist/utils/touchPresentation.d.ts.map +1 -1
  223. package/dist/utils/touchPresentation.js +5 -3
  224. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  225. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  226. package/dist/utils/touchPresentation.stylex.js +33 -0
  227. package/dist/utils/unicodeDigits.d.ts +20 -0
  228. package/dist/utils/unicodeDigits.d.ts.map +1 -0
  229. package/dist/utils/unicodeDigits.js +70 -0
  230. package/locales/en.json +15 -3
  231. package/locales/pseudo.json +9 -0
  232. package/package.json +5 -4
  233. package/src/AlertDialog/AlertDialog.tsx +6 -5
  234. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
  235. package/src/Avatar/Avatar.spec.md +22 -22
  236. package/src/Avatar/Avatar.tsx +18 -1
  237. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
  238. package/src/Badge/Badge.doc.mjs +1 -1
  239. package/src/Badge/Badge.tsx +19 -2
  240. package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
  241. package/src/BottomSheet/BottomSheet.test.tsx +46 -0
  242. package/src/BottomSheet/BottomSheet.tsx +1 -1
  243. package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
  244. package/src/BottomSheet/useSheetGestures.test.ts +52 -0
  245. package/src/BottomSheet/useSheetGestures.ts +37 -6
  246. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
  247. package/src/Button/Button.tsx +4 -4
  248. package/src/Calendar/Calendar.spec.md +87 -48
  249. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
  250. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
  251. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  252. package/src/Collapsible/Collapsible.spec.md +2 -0
  253. package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
  254. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
  255. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
  256. package/src/ComplexSelector/ComplexSelector.test.tsx +37 -0
  257. package/src/ContextMenu/ContextMenu.test.tsx +35 -0
  258. package/src/DateInput/DateInput.test.tsx +115 -3
  259. package/src/DateInput/DateInput.tsx +29 -14
  260. package/src/DateInput/DateInputTouch.test.tsx +76 -0
  261. package/src/DateInput/MonthScroller.tsx +106 -20
  262. package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
  263. package/src/DateInput/TouchDateField.tsx +14 -3
  264. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
  265. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
  266. package/src/DateRangeInput/DateRangeInput.tsx +16 -12
  267. package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
  268. package/src/DateTimeInput/DateTimeInput.spec.md +75 -51
  269. package/src/DateTimeInput/DateTimeInput.test.tsx +81 -0
  270. package/src/DateTimeInput/DateTimeInput.tsx +46 -24
  271. package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
  272. package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
  273. package/src/Dialog/Dialog.test.tsx +216 -2
  274. package/src/Dialog/Dialog.tsx +42 -9
  275. package/src/Dialog/DialogHeader.tsx +5 -4
  276. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
  277. package/src/Divider/Divider.tsx +2 -1
  278. package/src/Drawer/Drawer.spec.md +111 -80
  279. package/src/Drawer/Drawer.test.tsx +35 -0
  280. package/src/Drawer/Drawer.tsx +13 -2
  281. package/src/Drawer/DrawerHeader.spec.md +181 -0
  282. package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +267 -0
  283. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
  284. package/src/DropdownMenu/DropdownMenu.tsx +7 -1
  285. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
  286. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
  287. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
  288. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
  289. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +53 -0
  290. package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
  291. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  292. package/src/EmptyState/EmptyState.tsx +2 -1
  293. package/src/Field/FieldLabel.tsx +5 -4
  294. package/src/Field/InputClearButton.tsx +3 -3
  295. package/src/Field/PanelSearchInput.tsx +2 -2
  296. package/src/FieldStatus/FieldStatus.tsx +2 -2
  297. package/src/FileInput/FileInput.test.tsx +21 -0
  298. package/src/FileInput/FileInput.tsx +4 -1
  299. package/src/FormLayout/FormLayout.spec.md +7 -8
  300. package/src/Heading/Heading.tsx +1 -1
  301. package/src/Icon/Icon.tsx +1 -1
  302. package/src/Indicator/CheckIndicator.spec.md +47 -42
  303. package/src/Indicator/CheckIndicator.tsx +2 -1
  304. package/src/Indicator/CheckboxIndicator.spec.md +40 -37
  305. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  306. package/src/Indicator/Indicator.spec.md +188 -0
  307. package/src/Indicator/RadioIndicator.tsx +3 -1
  308. package/src/Item/Item.tsx +2 -1
  309. package/src/Item/ItemSwipeLayer.tsx +1 -1
  310. package/src/Layout/Layout.spec.md +127 -131
  311. package/src/Layout/Layout.tsx +1 -1
  312. package/src/Layout/LayoutContent.spec.md +205 -0
  313. package/src/Layout/LayoutContent.tsx +1 -1
  314. package/src/Layout/LayoutFooter.spec.md +190 -0
  315. package/src/Layout/LayoutFooter.tsx +1 -1
  316. package/src/Layout/LayoutHeader.spec.md +193 -0
  317. package/src/Layout/LayoutHeader.tsx +1 -1
  318. package/src/Layout/LayoutPanel.spec.md +194 -0
  319. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
  320. package/src/Lightbox/Lightbox.test.tsx +18 -0
  321. package/src/Lightbox/Lightbox.tsx +5 -0
  322. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
  323. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
  324. package/src/List/List.tsx +1 -1
  325. package/src/List/ListItem.spec.md +205 -0
  326. package/src/List/ListItem.tsx +3 -3
  327. package/src/MobileNav/MobileNav.test.tsx +15 -0
  328. package/src/MobileNav/MobileNav.tsx +9 -1
  329. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
  330. package/src/MoreMenu/MoreMenu.test.tsx +11 -0
  331. package/src/MultiSelector/MultiSelector.spec.md +7 -4
  332. package/src/MultiSelector/MultiSelector.test.tsx +118 -1
  333. package/src/MultiSelector/MultiSelector.tsx +6 -2
  334. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
  335. package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
  336. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  337. package/src/NumberInput/NumberInput.spec.md +59 -37
  338. package/src/NumberInput/NumberInput.tsx +13 -3
  339. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
  340. package/src/NumberInput/numberParser.ts +7 -45
  341. package/src/ProgressBar/ProgressBar.test.tsx +108 -32
  342. package/src/ProgressBar/ProgressBar.tsx +58 -10
  343. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
  344. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
  345. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
  346. package/src/Resizable/useResizable.test.ts +43 -0
  347. package/src/Resizable/useResizable.ts +32 -9
  348. package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
  349. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
  350. package/src/Selector/Selector.test.tsx +90 -1
  351. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
  352. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
  353. package/src/Selector/useSelectorPresentation.ts +31 -3
  354. package/src/SideNav/SideNav.test.tsx +27 -0
  355. package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +176 -51
  356. package/src/Slider/Slider.test.tsx +12 -0
  357. package/src/Slider/Slider.tsx +4 -1
  358. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
  359. package/src/Spinner/Spinner.tsx +2 -2
  360. package/src/Stack/Stack.tsx +1 -1
  361. package/src/Stack/StackItem.tsx +1 -1
  362. package/src/TabList/TabList.test.tsx +58 -4
  363. package/src/TabList/TabList.tsx +16 -1
  364. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
  365. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
  366. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
  367. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  368. package/src/Text/Text.tsx +1 -1
  369. package/src/Text/useTruncation.test.tsx +128 -1
  370. package/src/Text/useTruncation.ts +23 -5
  371. package/src/TextArea/TextArea.spec.md +10 -13
  372. package/src/TextArea/TextArea.test.tsx +60 -0
  373. package/src/TextArea/TextArea.tsx +15 -1
  374. package/src/TextInput/TextInput.test.tsx +21 -0
  375. package/src/TextInput/TextInput.tsx +3 -1
  376. package/src/TimeInput/NativeTimeInput.test.tsx +17 -0
  377. package/src/TimeInput/Presentation.test.tsx +21 -0
  378. package/src/TimeInput/TimeInput.doc.mjs +8 -8
  379. package/src/TimeInput/TimeInput.spec.md +45 -48
  380. package/src/TimeInput/TimeInput.test.tsx +76 -0
  381. package/src/TimeInput/TimeInput.tsx +36 -18
  382. package/src/TimeInput/TouchTimeField.tsx +4 -6
  383. package/src/Timestamp/Timestamp.spec.md +6 -12
  384. package/src/Timestamp/Timestamp.test.tsx +30 -5
  385. package/src/Timestamp/Timestamp.tsx +6 -2
  386. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  387. package/src/Toast/Toast.spec.md +14 -20
  388. package/src/Toast/Toast.tsx +104 -36
  389. package/src/Toast/ToastViewport.test.tsx +719 -1
  390. package/src/Toast/ToastViewport.tsx +100 -50
  391. package/src/Tokenizer/Tokenizer.spec.md +5 -3
  392. package/src/Tokenizer/Tokenizer.test.tsx +68 -0
  393. package/src/Tokenizer/Tokenizer.tsx +14 -2
  394. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
  395. package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
  396. package/src/Toolbar/Toolbar.spec.md +5 -6
  397. package/src/Toolbar/Toolbar.test.tsx +30 -0
  398. package/src/Toolbar/Toolbar.tsx +2 -1
  399. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
  400. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
  401. package/src/TopNav/TopNav.test.tsx +27 -0
  402. package/src/Typeahead/Typeahead.test.tsx +40 -0
  403. package/src/Typeahead/Typeahead.tsx +12 -1
  404. package/src/gestures/GestureArena.ts +35 -0
  405. package/src/gestures/GestureHost.ts +2065 -0
  406. package/src/gestures/GestureRecognizer.ts +18 -0
  407. package/src/gestures/LongPressGestureRecognizer.ts +4 -0
  408. package/src/gestures/PanGestureRecognizer.ts +4 -0
  409. package/src/gestures/PinchGestureRecognizer.ts +148 -0
  410. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
  411. package/src/gestures/SwipeGestureRecognizer.ts +197 -0
  412. package/src/gestures/TapGestureRecognizer.ts +4 -0
  413. package/src/gestures/VelocityTracker.ts +10 -3
  414. package/src/gestures/__tests__/GestureHost.test.ts +2087 -0
  415. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
  416. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
  417. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
  418. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
  419. package/src/gestures/__tests__/arena.test.ts +5 -0
  420. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +193 -0
  421. package/src/gestures/__tests__/browser/build-fixture.mjs +46 -0
  422. package/src/gestures/__tests__/browser/fixture.ts +307 -0
  423. package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +1137 -0
  424. package/src/gestures/__tests__/browserNegotiation.test.ts +293 -0
  425. package/src/gestures/__tests__/haptics.test.ts +187 -0
  426. package/src/gestures/__tests__/harness.ts +21 -2
  427. package/src/gestures/__tests__/recognizerKind.test.ts +78 -0
  428. package/src/gestures/__tests__/touchActionRule.test.ts +54 -0
  429. package/src/gestures/haptics.ts +203 -0
  430. package/src/gestures/nativeScroll.ts +277 -0
  431. package/src/gestures/touchAction.ts +191 -0
  432. package/src/gestures/touchActionRule.ts +157 -0
  433. package/src/gestures/types.ts +30 -9
  434. package/src/hooks/useAdaptivePresentation.ts +4 -1
  435. package/src/hooks/useMenuHover.ts +15 -1
  436. package/src/i18n/generated-locales/en.generated.ts +4 -0
  437. package/src/i18n/generated-locales/pseudo.generated.ts +4 -0
  438. package/src/motion/README.md +11 -1
  439. package/src/motion/animator.test.ts +151 -3
  440. package/src/motion/animator.ts +149 -33
  441. package/src/theme/defineTheme.ts +1 -1
  442. package/src/theme/derivedVarRegistry.test.ts +4 -0
  443. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  444. package/src/theme/domainTokens/index.ts +6 -10
  445. package/src/utils/focusReturn.ts +17 -6
  446. package/src/utils/getNodeText.test.ts +24 -0
  447. package/src/utils/getNodeText.ts +37 -0
  448. package/src/utils/interactionModality.test.ts +45 -1
  449. package/src/utils/interactionModality.ts +64 -5
  450. package/src/utils/plainDate.test.ts +90 -0
  451. package/src/utils/plainDate.ts +58 -3
  452. package/src/utils/presentation.test.ts +380 -0
  453. package/src/utils/presentation.ts +180 -0
  454. package/src/utils/pressPaintPriority.test.ts +80 -0
  455. package/src/utils/primaryClick.test.ts +38 -0
  456. package/src/utils/primaryClick.ts +28 -0
  457. package/src/utils/timeFormat.test.ts +204 -0
  458. package/src/utils/timeFormat.ts +314 -0
  459. package/src/utils/touchPresentation.stylex.ts +43 -0
  460. package/src/utils/touchPresentation.test.ts +140 -1
  461. package/src/utils/touchPresentation.ts +5 -3
  462. package/src/utils/unicodeDigits.test.ts +44 -0
  463. package/src/utils/unicodeDigits.ts +71 -0
@@ -0,0 +1,181 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 7
4
+ kind: component
5
+ id: component:DrawerHeader
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-10-30
11
+ owners: [cixzhang, imdreamrunner]
12
+ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Drawer/DrawerHeader.test.tsx,
16
+ apps/storybook/stories/DrawerHeader.stories.tsx,
17
+ scripts/check-knowledge.mjs,
18
+ ]
19
+ modules: []
20
+ families: [family:layout-regions]
21
+ design_specs: []
22
+ architecture:
23
+ [architecture:component-theming-surface, architecture:container-padding]
24
+ contributing: []
25
+ system_specs: []
26
+ ---
27
+
28
+ # DrawerHeader component contract
29
+
30
+ ## Contract at a glance
31
+
32
+ | Area | Contract |
33
+ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | Public contract | `DrawerHeader` with `title`, `subtitle`, `onOpenChange`, `startContent`, `endContent`, `endContentEdgeCompensation` (`inline`, `block`, `all`), and `hasDivider`; five `drawer-header` targets. |
35
+ | Behavior | A Layout header row for a Drawer: optional start content, an h2 title with optional subtitle, optional end content, and a close button only when `onOpenChange` is given. |
36
+ | End-user impact | Users see the drawer's title and, when the caller provides one, a named close button; focus is not moved when the header mounts. |
37
+ | Builder impact | None. |
38
+ | Compatibility/readiness | Records shipped behavior; no public change. |
39
+ | Review checks | Reject a close button rendered without `onOpenChange`, a header that names the Drawer or moves focus on mount, or a title that is not an h2. |
40
+ | Governing rules | `component:Drawer` (the panel and naming); `family:layout-regions`; `architecture:component-theming-surface`. |
41
+
42
+ This table is a review projection; the body below is authoritative.
43
+
44
+ ## Intent
45
+
46
+ DrawerHeader is the optional title row a caller composes in a Drawer's Layout
47
+ header slot. It owns the row's parts, its close button, and its targets.
48
+ Consumer usage lives in `DrawerHeader.doc.mjs`.
49
+
50
+ ## Compatibility and migration
51
+
52
+ - Released default preserved: `yes`
53
+ - Compatibility class: records shipped behavior; no public change
54
+ - Controlled/uncontrolled behavior: the close button calls the caller's
55
+ `onOpenChange(false)` and owns no state
56
+ - Migration decision: none
57
+
58
+ Consumer migration instructions belong in consumer docs and release notes.
59
+
60
+ ## Ownership boundary
61
+
62
+ **Owns**
63
+
64
+ - The header row, its slots, title semantics, close button, end-content edge
65
+ compensation, and five targets (FR1–FR5).
66
+
67
+ **Does not own / non-goals**
68
+
69
+ - The Drawer's name, focus entry, and open state — owned by `component:Drawer`.
70
+ - The header region's padding, divider default, and place in the Layout — owned
71
+ by `family:layout-regions` through `LayoutHeader`.
72
+ - The close Button — owned by `component:Button`.
73
+
74
+ ## Public concepts
75
+
76
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
77
+ | ------------------------ | --------------------------------- | --------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------------------------------------------------- | ------------------------ | --------- | -------------------------------- |
78
+ | End-content compensation | `inline`, `block`, `all`, omitted | Which edges of the end slot pull outward to align a trailing control with the container edge. | End slot. | Both axes when `onOpenChange` is set, none otherwise | `component:DrawerHeader` | Stable | TypeScript rejects other values. |
79
+
80
+ ## Behavioral and layout contract
81
+
82
+ | ID | Invariant | Basis |
83
+ | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
84
+ | FR1 | DrawerHeader renders inside a LayoutHeader that receives `ref`, `hasDivider`, `xstyle`, `className`, `style`, and other DOM props. Its row carries `drawer-header` and shows, in order, start content, the title block, and the end slot. | Current source and tests |
85
+ | FR2 | The title renders as an h2; a renderable `subtitle` (including `0`) renders below it as secondary small body text, and an empty one renders nothing. Start and end slots render when their content is renderable, including `0`. | Current source and tests |
86
+ | FR3 | With `onOpenChange`, the end slot ends with an icon-only ghost Button named and tooltipped by `@astryx.dialog.close` that calls `onOpenChange(false)`; without it there is no close button. | Current source and tests |
87
+ | FR4 | The end slot pulls outward by `--spacing-2` on the block axis, the inline-end edge, or both, per `endContentEdgeCompensation`; when omitted, both apply only if there is a close button. | Current source |
88
+ | FR5 | The start slot, title block, end slot, and close glyph carry `drawer-header-start-content`, `drawer-header-title-block`, `drawer-header-end-content`, and `drawer-header-close-icon`. | Current source and tests |
89
+ | FR6 | Mounting the header does not move focus, and the header does not name the Drawer. | Current source and tests |
90
+
91
+ ### Allowed variation
92
+
93
+ - **AV1 — Slots.** Start, end, and subtitle content vary or are absent.
94
+
95
+ ### Representative states
96
+
97
+ | State | Required invariant | Allowed variation |
98
+ | ------------------- | ------------------------------------------------- | ----------------- |
99
+ | Title only | h2 title; no close button. | — |
100
+ | With `onOpenChange` | Named close button at the end; edges compensated. | End content. |
101
+ | Start and end slots | Order: start, title block, end. | Content. |
102
+
103
+ ### Transformation and precedence order
104
+
105
+ - **ORD1 — Compensation.** `endContentEdgeCompensation`, then the close-button
106
+ default (FR4).
107
+
108
+ ### Performance and resources
109
+
110
+ - No resource rule.
111
+
112
+ ## Accessibility contract
113
+
114
+ - **AR1 — Heading.** The title is an h2.
115
+ - **AR2 — Close name.** FR3's translated name and tooltip.
116
+ - **AR3 — Focus.** FR6.
117
+
118
+ ## Design relationships
119
+
120
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
121
+ | ---------------- | --------------------------------------------- | ------------------------------ | -------------- | ------------------ |
122
+ | Header row | Start, title block, end in one row. | Current source and public docs | Supporting | FR1 |
123
+ | Start content | Leading caller content. | Current source and public docs | Supporting | FR2, FR5 |
124
+ | Title block | Title and optional subtitle. | Current source and public docs | Prominent | FR2, FR5 |
125
+ | End content | Trailing caller content and the close button. | Current source and public docs | Supporting | FR3–FR5 |
126
+ | Close icon | Close glyph inside the button. | Current source and public docs | Supporting | FR3, FR5 |
127
+
128
+ ### Theming anatomy
129
+
130
+ <!-- anatomy-theming:v1 -->
131
+
132
+ ```json
133
+ {
134
+ "Header row": {"target": "drawer-header"},
135
+ "Start content": {"target": "drawer-header-start-content"},
136
+ "Title block": {"target": "drawer-header-title-block"},
137
+ "End content": {"target": "drawer-header-end-content"},
138
+ "Close icon": {"target": "drawer-header-close-icon"}
139
+ }
140
+ ```
141
+
142
+ ## Family and system relationships
143
+
144
+ - `component:Drawer` hosts the header in its Layout and owns naming and focus.
145
+ - `family:layout-regions` owns the header region it renders into.
146
+
147
+ ## Verification map
148
+
149
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
150
+ | ------------------- | -------------------------------------------- | ---------------------------------------- | -------------------------------------------------- | ---------------------------------- |
151
+ | FR1, FR2, AR1 | `DrawerHeader.test.tsx` title and slot tests | Title, subtitle, `0` values, empty slots | Wrong heading level or dropped content fails. | `audit:DrawerHeader/anatomy` |
152
+ | FR3, AR2 | `DrawerHeader.test.tsx` close-button tests | With and without `onOpenChange` | A button without a callback, or no name, fails. | `audit:DrawerHeader/accessibility` |
153
+ | FR5 | `DrawerHeader.test.tsx` theming target test | All slots | A missing target fails. | `audit:DrawerHeader/theming` |
154
+ | FR6, AR3 | `DrawerHeader.test.tsx` focus test | Mount | Focus moved on mount fails. | `audit:DrawerHeader/accessibility` |
155
+ | Theming anatomy map | `scripts/check-knowledge.mjs` | Consumer anatomy and current targets | Missing, extra, or stale mappings fail validation. | `audit:DrawerHeader/theming` |
156
+
157
+ FR4 is read from source; no focused test checks it yet.
158
+
159
+ Read from source, with no focused test yet:
160
+
161
+ - FR1: `ref`, `hasDivider`, `xstyle`, `className`, `style`, and other DOM
162
+ props reaching the LayoutHeader.
163
+ - FR3: the close button's tooltip.
164
+ - FR4: the end slot's edge compensation.
165
+ - FR6: the header not naming the Drawer.
166
+
167
+ ## Decision log
168
+
169
+ No component-local decision is recorded.
170
+
171
+ ## Open questions
172
+
173
+ - How do `endContentEdgeCompensation`'s `inline`, `block`, and `all` axis
174
+ values map onto `endContentEdge` treatment values
175
+ (`architecture:container-padding`), and when does the old prop enter its
176
+ compatibility window? Today the prop stays supported as released.
177
+
178
+ ## Content boundary
179
+
180
+ This file does not duplicate consumer prop tables, examples, or Drawer
181
+ behavior. It links to their owners.
@@ -0,0 +1,267 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file DrawerNonModalPageScroll.a11y.chromium.spec.ts
5
+ * @input The built Drawer stories "Row Inspector" (non-modal) and "Showcase"
6
+ * (modal), real Chromium touch input (CDP `Input.dispatchTouchEvent`) in a
7
+ * phone context
8
+ * @output Browser evidence that under a finger the page beside an open
9
+ * non-modal drawer still scrolls, because the panel is no scroll container
10
+ * whose overscroll containment could hold the drag, while a modal drawer
11
+ * still holds the page behind its scrim. With `overflow: hidden` on the
12
+ * panel, Chromium held every such drag while the drawer was open, whether it
13
+ * had opened from code (a selection, a deep link, an assistive
14
+ * technology's click) or from a tap.
15
+ * @position Chromium proof for Drawer.tsx's `overflow: clip`; the declaration
16
+ * itself is pinned in Drawer.test.tsx
17
+ *
18
+ * Each check plays one drag, proves the page received it (a capture-phase
19
+ * touch probe), and measures the page by the visual viewport's place in the
20
+ * document, so a pan that moves only the visual viewport counts too.
21
+ */
22
+
23
+ import {expect, test, type Page} from '@playwright/test';
24
+ import {
25
+ DEFAULT_STORYBOOK_DIR,
26
+ serveStorybook,
27
+ type StaticServer,
28
+ } from '@astryxdesign/a11y-spec/storybook';
29
+
30
+ let storybook: StaticServer;
31
+ test.beforeAll(async () => {
32
+ storybook = await serveStorybook(
33
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
34
+ );
35
+ });
36
+ test.afterAll(async () => {
37
+ await storybook?.close();
38
+ });
39
+
40
+ test.use({
41
+ hasTouch: true,
42
+ isMobile: true,
43
+ viewport: {width: 402, height: 714},
44
+ deviceScaleFactor: 1,
45
+ });
46
+
47
+ /** In the strip the drawer leaves free on the phone. */
48
+ const STRIP_X = 20;
49
+
50
+ async function frames(page: Page, count = 2): Promise<void> {
51
+ await page.evaluate(async n => {
52
+ for (let i = 0; i < n; i++) {
53
+ await new Promise(resolve => requestAnimationFrame(resolve));
54
+ }
55
+ }, count);
56
+ }
57
+
58
+ const sleep = async (ms: number) =>
59
+ new Promise(resolve => {
60
+ setTimeout(resolve, ms);
61
+ });
62
+
63
+ /**
64
+ * A finger dragging up the strip, 350 px in 200 ms at 60 Hz, then resting
65
+ * 150 ms before it lifts so no fling outlives the pan. Paced in real time, as
66
+ * a finger is: each event goes out at its own time, stamped with it.
67
+ */
68
+ async function dragUp(page: Page): Promise<void> {
69
+ const cdp = await page.context().newCDPSession(page);
70
+ const from = 600;
71
+ const to = 250;
72
+ const steps = 12;
73
+ const at = (y: number) => [
74
+ {x: STRIP_X, y, id: 0, radiusX: 1, radiusY: 1, force: 1},
75
+ ];
76
+ type Point = {
77
+ x: number;
78
+ y: number;
79
+ id: number;
80
+ radiusX: number;
81
+ radiusY: number;
82
+ force: number;
83
+ };
84
+ const send = async (
85
+ type: 'touchStart' | 'touchMove' | 'touchEnd',
86
+ touchPoints: Point[],
87
+ ) =>
88
+ cdp.send('Input.dispatchTouchEvent', {
89
+ type,
90
+ touchPoints,
91
+ timestamp: Date.now() / 1000,
92
+ });
93
+ await send('touchStart', at(from));
94
+ for (let i = 1; i <= steps; i++) {
95
+ await sleep(200 / steps);
96
+ await send('touchMove', at(from + ((to - from) * i) / steps));
97
+ }
98
+ await sleep(150);
99
+ await send('touchEnd', []);
100
+ await cdp.detach();
101
+ }
102
+
103
+ /**
104
+ * Records every touch the page receives, and whether the browser took one to
105
+ * pan something (a `pointercancel`): a window capture listener added before
106
+ * the page's own scripts. Install it before opening the story.
107
+ */
108
+ async function installTouchProbe(page: Page): Promise<void> {
109
+ await page.addInitScript(() => {
110
+ const seen: string[] = [];
111
+ (window as unknown as {__touchProbe: string[]}).__touchProbe = seen;
112
+ for (const type of [
113
+ 'touchstart',
114
+ 'touchmove',
115
+ 'touchend',
116
+ 'touchcancel',
117
+ 'pointercancel',
118
+ ]) {
119
+ window.addEventListener(type, () => seen.push(type), {
120
+ capture: true,
121
+ passive: true,
122
+ });
123
+ }
124
+ });
125
+ }
126
+
127
+ /**
128
+ * Plays one drag up the strip and returns what the page received: its touch
129
+ * events, and whether the browser took the touch to pan something. A drag the
130
+ * page never received shows here, so it cannot pass as one that scrolled
131
+ * nothing.
132
+ */
133
+ async function playDrag(
134
+ page: Page,
135
+ ): Promise<{received: string[]; panned: boolean}> {
136
+ const take = async () =>
137
+ page.evaluate(() =>
138
+ (window as unknown as {__touchProbe: string[]}).__touchProbe.splice(0),
139
+ );
140
+ await take();
141
+ await dragUp(page);
142
+ await frames(page, 4);
143
+ const seen = await take();
144
+ return {
145
+ received: seen.filter(type => type.startsWith('touch')),
146
+ panned: seen.includes('pointercancel'),
147
+ };
148
+ }
149
+
150
+ /** The whole drag reached the page: a touchstart, its moves and its touchend. */
151
+ function expectDelivered(played: {received: string[]}): void {
152
+ expect(played.received[0], 'the drag reached the page').toBe('touchstart');
153
+ expect(played.received).toContain('touchmove');
154
+ expect(played.received.at(-1)).toBe('touchend');
155
+ }
156
+
157
+ /**
158
+ * Where the visual viewport is in the document. A page wider than the phone
159
+ * (main's story stage) lets a pan first move the visual viewport inside the
160
+ * layout viewport, which `window.scrollY` does not report.
161
+ */
162
+ const pageTop = async (page: Page) =>
163
+ page.evaluate(() => {
164
+ if (window.visualViewport == null) {
165
+ throw new Error('the page has no visual viewport');
166
+ }
167
+ return window.visualViewport.pageTop;
168
+ });
169
+
170
+ /**
171
+ * Opens the story on a page taller than the screen, checks that a drag
172
+ * scrolls it, then opens the drawer (with a tap, or from code: a click with no
173
+ * input behind it) and waits until it is in.
174
+ */
175
+ async function openOnTallPage(
176
+ page: Page,
177
+ story: string,
178
+ opener: string,
179
+ how: 'tap' | 'code' = 'tap',
180
+ ) {
181
+ await installTouchProbe(page);
182
+ await page.goto(`${storybook.origin}/iframe.html?id=${story}&viewMode=story`);
183
+ const button = page.getByRole('button', {name: opener});
184
+ await expect(button).toBeVisible();
185
+ await page.evaluate(() => {
186
+ const spacer = document.createElement('div');
187
+ spacer.style.height = '300vh';
188
+ document.body.append(spacer);
189
+ });
190
+ const before = await playDrag(page);
191
+ expectDelivered(before);
192
+ expect(before.panned, 'the browser took the drag to scroll').toBe(true);
193
+ expect(
194
+ await pageTop(page),
195
+ 'the page scrolls before the drawer opens',
196
+ ).toBeGreaterThan(0);
197
+ await button.evaluate(element => element.scrollIntoView({block: 'center'}));
198
+ await frames(page);
199
+
200
+ if (how === 'tap') {
201
+ await button.tap();
202
+ } else {
203
+ await button.evaluate(element => (element as HTMLElement).click());
204
+ }
205
+ const drawer = page.locator('dialog[open], dialog:popover-open');
206
+ await expect(drawer).toHaveCount(1);
207
+ await drawer.evaluate(async element => {
208
+ await Promise.all(
209
+ element.getAnimations().map(async animation =>
210
+ animation.finished.catch((error: unknown) => {
211
+ // A canceled animation's `finished` rejects with an AbortError:
212
+ // it ended. Anything else is a failure, and it surfaces.
213
+ if (!(error instanceof DOMException && error.name === 'AbortError')) {
214
+ throw error;
215
+ }
216
+ }),
217
+ ),
218
+ );
219
+ });
220
+ await frames(page);
221
+ }
222
+
223
+ for (const how of ['code', 'tap'] as const) {
224
+ test(`a drag on the page beside a non-modal drawer opened from ${how === 'tap' ? 'a tap' : 'code'} scrolls the page`, async ({
225
+ page,
226
+ }) => {
227
+ await openOnTallPage(
228
+ page,
229
+ 'core-drawer--row-inspector',
230
+ 'Open drawer',
231
+ how,
232
+ );
233
+
234
+ // Measured from where the open left the page: a tap scrolls the opener
235
+ // into view first.
236
+ const start = await pageTop(page);
237
+ const played = await playDrag(page);
238
+ expectDelivered(played);
239
+ expect(played.panned, 'the browser took the drag to scroll').toBe(true);
240
+ expect(await pageTop(page)).toBeGreaterThan(start);
241
+ });
242
+ }
243
+
244
+ test('a modal drawer still holds the page behind its scrim', async ({page}) => {
245
+ await openOnTallPage(page, 'core-drawer--showcase', 'Open inspector');
246
+
247
+ // Measured from where the open left the page, whatever the scroll lock
248
+ // reports there.
249
+ const start = await pageTop(page);
250
+ expectDelivered(await playDrag(page));
251
+ expect(await pageTop(page)).toBe(start);
252
+ await expect(
253
+ page.getByRole('dialog', {name: 'Deployment details'}),
254
+ ).toBeVisible();
255
+ });
256
+
257
+ test('the story fits the phone: the page does not scroll sideways', async ({
258
+ page,
259
+ }) => {
260
+ await page.goto(
261
+ `${storybook.origin}/iframe.html?id=core-drawer--row-inspector&viewMode=story`,
262
+ );
263
+ await expect(page.getByRole('button', {name: 'Open drawer'})).toBeVisible();
264
+ expect(
265
+ await page.evaluate(() => document.documentElement.scrollWidth),
266
+ ).toBeLessThanOrEqual(402);
267
+ });
@@ -127,6 +127,47 @@ describe('DropdownMenu', () => {
127
127
  ).not.toBeInTheDocument();
128
128
  });
129
129
 
130
+ it("forwards the button's ref object to the popover trigger", () => {
131
+ const ref = createRef<HTMLButtonElement>();
132
+ render(
133
+ <DropdownMenu
134
+ button={{label: 'Project actions', ref}}
135
+ items={[{label: 'Edit project', onClick: vi.fn()}]}
136
+ />,
137
+ );
138
+ expect(ref.current).toBe(
139
+ screen.getByRole('button', {name: /Project actions/}),
140
+ );
141
+ });
142
+
143
+ it("forwards the button's ref object to the bottom-sheet trigger", () => {
144
+ const ref = createRef<HTMLButtonElement>();
145
+ render(
146
+ <DropdownMenu
147
+ button={{label: 'Project actions', ref}}
148
+ presentation="bottom-sheet"
149
+ items={[{label: 'Edit project', onClick: vi.fn()}]}
150
+ />,
151
+ );
152
+ expect(ref.current).toBe(
153
+ screen.getByRole('button', {name: /Project actions/}),
154
+ );
155
+ });
156
+
157
+ it("forwards the button's callback ref to the bottom-sheet trigger", () => {
158
+ const ref = vi.fn();
159
+ render(
160
+ <DropdownMenu
161
+ button={{label: 'Project actions', ref}}
162
+ presentation="bottom-sheet"
163
+ items={[{label: 'Edit project', onClick: vi.fn()}]}
164
+ />,
165
+ );
166
+ expect(ref).toHaveBeenCalledWith(
167
+ screen.getByRole('button', {name: /Project actions/}),
168
+ );
169
+ });
170
+
130
171
  it('renders data-driven actions in a bottom sheet when requested', async () => {
131
172
  const user = userEvent.setup();
132
173
  const onEdit = vi.fn();
@@ -67,6 +67,7 @@ import {
67
67
  type DropdownMenuSurfaceValue,
68
68
  } from './DropdownMenuContext';
69
69
  import {useIsomorphicLayoutEffect} from '../hooks/useIsomorphicLayoutEffect';
70
+ import {useMergedRefs} from '../hooks/useMergedRefs';
70
71
  import {useListFocus} from '../hooks/useListFocus';
71
72
  import {useMenuPress} from '../hooks/useMenuPress';
72
73
  import type {MenuPressPointerType} from '../hooks/menuPressGesture';
@@ -621,6 +622,11 @@ function DropdownMenuBottomSheet({
621
622
  const button = buttonFromProps ?? {label: t(DEFAULT_BUTTON_I18N_KEY)};
622
623
  const backLabel = t('@astryx.dropdownMenu.back');
623
624
  const buttonRef = useRef<HTMLElement>(null);
625
+ // The caller's `button.ref` rides along with the sheet's own trigger ref.
626
+ const triggerRef = useMergedRefs<HTMLButtonElement>(
627
+ buttonRef as React.Ref<HTMLButtonElement>,
628
+ button.ref,
629
+ );
624
630
  const triggerId = useId();
625
631
  const sheetId = useId();
626
632
  const actionListRef = useRef<HTMLDivElement>(null);
@@ -806,7 +812,7 @@ function DropdownMenuBottomSheet({
806
812
  ) : (
807
813
  <Button
808
814
  {...button}
809
- ref={buttonRef as React.Ref<HTMLButtonElement>}
815
+ ref={triggerRef}
810
816
  xstyle={[
811
817
  isOpen && styles.triggerOpen,
812
818
  button.xstyle,
@@ -647,6 +647,40 @@ describe('DropdownMenu data-driven submenus', () => {
647
647
  expect(onMove).toHaveBeenCalledWith('b');
648
648
  });
649
649
 
650
+ it('renders a submenu for an item with nested items inside a section', async () => {
651
+ const user = userEvent.setup();
652
+ const onWork = vi.fn();
653
+ render(
654
+ <DropdownMenu
655
+ button={{label: 'Share'}}
656
+ items={[
657
+ {
658
+ type: 'section',
659
+ title: 'Send',
660
+ items: [
661
+ {label: 'Copy link', onClick: () => {}},
662
+ {
663
+ label: 'Email',
664
+ items: [
665
+ {label: 'Work', onClick: onWork},
666
+ {label: 'Personal', onClick: () => {}},
667
+ ],
668
+ },
669
+ ],
670
+ },
671
+ ]}
672
+ />,
673
+ );
674
+ await user.click(screen.getByRole('button', {name: /Share/}));
675
+ const trigger = screen.getByRole('menuitem', {name: /Email/, hidden: true});
676
+ expect(trigger).toHaveAttribute('aria-haspopup', 'menu');
677
+ await user.click(trigger);
678
+ await user.click(
679
+ await screen.findByRole('menuitem', {name: 'Work', hidden: true}),
680
+ );
681
+ expect(onWork).toHaveBeenCalled();
682
+ });
683
+
650
684
  it('keyboard-reaches an item positioned after a submenu row', async () => {
651
685
  // Regression: the submenu flyout renders inline inside the root menu, so
652
686
  // the root's item query swept in the (hidden) flyout items. Arrow nav then
@@ -11,13 +11,14 @@
11
11
 
12
12
  import React, {useRef, type ReactElement} from 'react';
13
13
  import * as stylex from '@stylexjs/stylex';
14
- import {Divider} from '../Divider';
15
- import {Heading} from '../Heading';
16
- import {Icon, renderIconSlot} from '../Icon';
17
- import {List, ListItem} from '../List';
14
+ import {Divider} from '../Divider/Divider';
15
+ import {Heading} from '../Heading/Heading';
16
+ import {Icon, renderIconSlot} from '../Icon/Icon';
17
+ import {List} from '../List/List';
18
+ import {ListItem} from '../List/ListItem';
18
19
  import {useMenuPress} from '../hooks/useMenuPress';
19
20
  import {colorVars, spacingVars} from '../theme/tokens.stylex';
20
- import {rtlStyles} from '../utils';
21
+ import {rtlStyles} from '../utils/rtlStyles';
21
22
  import {getInteractionModality} from '../utils/interactionModality';
22
23
  import type {DropdownMenuItemData, DropdownMenuOption} from './DropdownMenu';
23
24
  import {isModifiedClick} from './menuItemRoles';
@@ -120,16 +120,28 @@ async function runState(
120
120
  });
121
121
  }
122
122
 
123
- test('every menu expectation is exercised by a DropdownMenu state', async ({
124
- page,
125
- }) => {
126
- const cdp = await page.context().newCDPSession(page);
123
+ // Checking coverage runs every state again, which on a loaded runner nears
124
+ // the per-test timeout as one test. It runs as one serial part per state,
125
+ // sharing their results, then one check over all of them.
126
+ test.describe('every menu expectation is exercised by a DropdownMenu state', () => {
127
+ test.describe.configure({mode: 'serial'});
127
128
  const results: BindingResult[] = [];
129
+
128
130
  for (const state of MENU_BINDING_STATES) {
129
- results.push(await runState(page, cdp, state));
131
+ test(`runs ${state.id}`, async ({page}) => {
132
+ const cdp = await page.context().newCDPSession(page);
133
+ results.push(await runState(page, cdp, state));
134
+ });
130
135
  }
131
- expect(neverExercised(results)).toEqual([]);
132
- expect(unmatchedKnownFailures(MENU_KNOWN_FAILURES, results)).toEqual([]);
136
+
137
+ test('no expectation is left unexercised', () => {
138
+ // Every part ran in this worker; this test alone proves nothing.
139
+ expect(results.map(result => result.state).sort()).toEqual(
140
+ MENU_BINDING_STATES.map(state => state.id).sort(),
141
+ );
142
+ expect(neverExercised(results)).toEqual([]);
143
+ expect(unmatchedKnownFailures(MENU_KNOWN_FAILURES, results)).toEqual([]);
144
+ });
133
145
  });
134
146
 
135
147
  for (const state of MENU_BINDING_STATES) {
@@ -107,18 +107,30 @@ async function runState(
107
107
  });
108
108
  }
109
109
 
110
- test('every menu-button expectation is exercised by a DropdownMenu or MoreMenu state', async ({
111
- page,
112
- }) => {
113
- const cdp = await page.context().newCDPSession(page);
110
+ // Checking coverage runs every state again, which on a loaded runner nears
111
+ // the per-test timeout as one test. It runs as one serial part per state,
112
+ // sharing their results, then one check over all of them.
113
+ test.describe('every menu-button expectation is exercised by a DropdownMenu or MoreMenu state', () => {
114
+ test.describe.configure({mode: 'serial'});
114
115
  const results: BindingResult[] = [];
116
+
115
117
  for (const state of MENU_BUTTON_BINDING_STATES) {
116
- results.push(await runState(page, cdp, state));
118
+ test(`runs ${state.id}`, async ({page}) => {
119
+ const cdp = await page.context().newCDPSession(page);
120
+ results.push(await runState(page, cdp, state));
121
+ });
117
122
  }
118
- expect(neverExercised(results)).toEqual([]);
119
- expect(unmatchedKnownFailures(MENU_BUTTON_KNOWN_FAILURES, results)).toEqual(
120
- [],
121
- );
123
+
124
+ test('no expectation is left unexercised', () => {
125
+ // Every part ran in this worker; this test alone proves nothing.
126
+ expect(results.map(result => result.state).sort()).toEqual(
127
+ MENU_BUTTON_BINDING_STATES.map(state => state.id).sort(),
128
+ );
129
+ expect(neverExercised(results)).toEqual([]);
130
+ expect(unmatchedKnownFailures(MENU_BUTTON_KNOWN_FAILURES, results)).toEqual(
131
+ [],
132
+ );
133
+ });
122
134
  });
123
135
 
124
136
  for (const state of MENU_BUTTON_BINDING_STATES) {