@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,205 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 7
4
+ kind: component
5
+ id: component:ListItem
6
+ authority: current
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-10-31
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, layout, theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/List/List.test.tsx,
16
+ packages/core/src/theme/themingTargets.test.ts,
17
+ scripts/check-knowledge.mjs,
18
+ ]
19
+ modules: []
20
+ families: [family:navigation-destinations]
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:component-style-authoring,
25
+ architecture:component-theming-surface,
26
+ architecture:public-component-api,
27
+ ]
28
+ contributing: []
29
+ system_specs: [spec:AST-057, spec:AST-103]
30
+ ---
31
+
32
+ # ListItem component contract
33
+
34
+ ## Contract at a glance
35
+
36
+ | Area | Contract |
37
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
38
+ | Public contract | `ListItem` with `label`, `description`, `startContent`, `endContent`, `onClick`, `interactiveRef`, `href`, `target`, `rel`, `align`, `isDisabled`, `isSelected`, `swipeActions`, `swipeBehavior`, `xstyle`, `className`, `style`, and `ref`; the row carries `component:List`'s `list-item` target. `itemKey` (`spec:AST-103` DEC-4) is specified but not shipped. |
39
+ | Behavior | One `<li>` row rendered by `component:Item`, which receives every row prop. Inside a List the row takes the list's density, dividers, edge treatment, and marker; outside a List it uses Item's defaults and draws no marker. |
40
+ | End-user impact | Each list entry reads as one list item, with the list's bullet, number, or task checkbox before it and the same interaction Item gives any row. |
41
+ | Builder impact | None. |
42
+ | Compatibility/readiness | Records shipped behavior; no public change. Two implementation gaps, listed under Current-state impact. |
43
+ | Review checks | Reject a row that is not an `<li>`, a ListItem-level edge, density, or divider prop, a marker drawn outside a marked List, or a dropped Item prop. |
44
+ | Governing rules | `component:Item` (`align` for start and end content); `component:List`; `family:navigation-destinations`; `spec:AST-057`; `spec:AST-103`; `architecture:component-theming-surface`; `architecture:component-style-authoring` INV7. |
45
+
46
+ This table is a review projection; the body below is authoritative.
47
+
48
+ ## Intent
49
+
50
+ ListItem is the row a List renders for each entry. It owns turning a row into
51
+ a list item: the `<li>` element, what it reads from its List, the marker drawn
52
+ before it, and how it applies `component:List`'s `list-item` target. Everything a row does as a row comes
53
+ from `component:Item`. Consumer usage lives in `ListItem.doc.mjs`.
54
+
55
+ ## Compatibility and migration
56
+
57
+ - Released default preserved: `yes`
58
+ - Compatibility class: records shipped behavior; no public change
59
+ - Controlled/uncontrolled behavior: not applicable; `isSelected` and
60
+ `isDisabled` are caller-owned
61
+ - Migration decision: none
62
+
63
+ Consumer migration instructions belong in consumer docs and release notes.
64
+
65
+ ## Current-state impact
66
+
67
+ - **Markers are extra elements.** A bullet marker is drawn as two decorative
68
+ `span` elements, and a number marker as one `span` carrying a counter
69
+ pseudo-element, beside the row's content. `architecture:component-style-authoring`
70
+ INV7 carries decoration with no semantic role on a pseudo-element rather than
71
+ an added element.
72
+ - **ListItem has no `itemKey` yet.** `spec:AST-103` DEC-4 and `component:List`
73
+ FR12 have each reorderable row name its item through an `itemKey` prop;
74
+ ListItem does not accept one yet.
75
+
76
+ ## Ownership boundary
77
+
78
+ **Owns**
79
+
80
+ - Rendering the row as an `<li>` through `component:Item` and passing it every
81
+ row prop (FR1).
82
+ - Reading density, dividers, edge treatment, and marker from the enclosing
83
+ List, and the defaults outside one (FR2, FR3).
84
+ - Drawing the marker or a task item's read-only checkbox (FR4).
85
+ - Applying `component:List`'s `list-item` target to the row (FR5).
86
+
87
+ **Does not own / non-goals**
88
+
89
+ - `align`, the start and end content's alignment — owned by `component:Item`; swipe actions — owned by
90
+ `spec:AST-057`; link destinations — owned by
91
+ `family:navigation-destinations`, of which ListItem is a member.
92
+ - Item's label, description, slots, invisible button, delegated activation,
93
+ and disabled and selected semantics — Item's shipped behavior, uncontracted
94
+ here.
95
+ - What each edge treatment does to row geometry and the row inset, the touch
96
+ target, press paint, row motion, and row menus — owned by `component:List`.
97
+ - The `ListMarkerScope` that sets a marker or task state for Markdown's nested
98
+ lists — internal and uncontracted here.
99
+ - Whole number markers and text alignment for Markdown lists — owned by
100
+ `spec:AST-061` FR11 and `component:List` DEC-5.
101
+
102
+ ## Public concepts
103
+
104
+ No component-local concept beyond the consumer props; row props are
105
+ `component:Item`'s.
106
+
107
+ ## Behavioral and layout contract
108
+
109
+ | ID | Invariant | Basis |
110
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
111
+ | FR1 | ListItem renders `component:Item` as an `<li>` and passes `label`, `description`, `startContent`, `endContent`, `onClick`, `interactiveRef`, `href`, `target`, `rel`, `align`, `isDisabled`, `isSelected`, `swipeActions`, `swipeBehavior`, and `ref` to it unchanged, with other DOM and data props reaching the row. | Current source and tests |
112
+ | FR2 | Inside a List, the row takes the list's `density`, its `hasDividers` setting, which draws a divider between rows with no extra element and square corners, and its `edge` treatment. | Current source and tests |
113
+ | FR3 | Outside a List, the row uses `balanced` density, no dividers, the `padded` treatment, and no marker, keeping Item's inline inset. | Current source and tests |
114
+ | FR4 | In a List whose `listStyle` is `disc`, `circle`, or `decimal`, the row draws that marker before its content: a disc or circle shape, or a counter number. A task state set for the row draws a read-only, labelled checkbox in the marker's place. Each marker and the task checkbox sit on the label's first line. A List whose `listStyle` is `none` draws neither. Other markers, set internally for Markdown's nested lists, are uncontracted here. | Current source and tests |
115
+ | FR5 | The row carries `component:List`'s `list-item` target, merged with the caller's `className` and `style`; `xstyle` applies after ListItem's own styles. | Current source |
116
+
117
+ ### Allowed variation
118
+
119
+ - **AV1 — Row content.** Label, description, and slot content vary.
120
+ - **AV2 — Theme styling.** Themes restyle `list-item`.
121
+
122
+ ### Representative states
123
+
124
+ | State | Required invariant | Allowed variation |
125
+ | -------------------- | ------------------------------------------------ | ----------------- |
126
+ | Static row in a List | One `<li>`; no button or anchor. | Content. |
127
+ | Interactive row | Item's invisible button or anchor; one tab stop. | Content. |
128
+ | Divided list | A divider between rows; square corners. | Density. |
129
+ | Numbered list | A counter number before each row. | Number style. |
130
+ | Task row | A read-only checkbox in the marker's place. | Checked state. |
131
+ | Row outside a List | Item's inline inset; no marker. | Content. |
132
+
133
+ ### Transformation and precedence order
134
+
135
+ - **ORD1 — Marker.** No marker when the List's `listStyle` is `none`; otherwise
136
+ a task checkbox when the row has a task state, then the marker.
137
+ - **ORD2 — Styling.** ListItem's own styles, then the caller's `xstyle`.
138
+
139
+ ### Performance and resources
140
+
141
+ - No resource rule.
142
+
143
+ ## Accessibility contract
144
+
145
+ - **AR1 — List semantics.** Inside a List, each row is an `<li>` in the List's
146
+ `<ul>` or `<ol>` (FR1).
147
+ - **AR2 — Row semantics.** Item's interaction, disabled, and selected semantics
148
+ apply unchanged; they are Item's shipped behavior, uncontracted here.
149
+ - **AR3 — Task state.** A task row's checkbox is read-only and labelled (FR4).
150
+
151
+ ## Design relationships
152
+
153
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
154
+ | ---------------- | ----------------------------------------------- | ------------------------ | -------------- | ------------------ |
155
+ | Row | Presents one list entry through Item. | `component:Item` | Prominent | FR1, FR5 |
156
+ | Marker | Shows the list's bullet, number, or task state. | Current source | Supporting | FR4 |
157
+
158
+ No anatomy-theming map is recorded yet; the block is optional during
159
+ migration, so the map is deferred.
160
+
161
+ ## Family and system relationships
162
+
163
+ - `component:Item` owns `align`, the start and end content's alignment; its
164
+ other row behavior is
165
+ uncontracted. `spec:AST-057` owns swipe actions and
166
+ `family:navigation-destinations` owns link destinations.
167
+ - `component:List` owns the edge treatment and its row geometry and inset;
168
+ its record leaves the density, divider, and marker-style settings
169
+ uncontracted, and FR2 and FR4 here cover what a row does with them.
170
+ - `architecture:component-theming-surface` owns target qualification;
171
+ `component:List` owns the `list-item` target, and FR5 covers how the row
172
+ applies it.
173
+
174
+ ## Verification map
175
+
176
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
177
+ | -------- | -------------------------------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------ | ------------------------------ |
178
+ | FR1, AR1 | `List.test.tsx` row element, align, data attribute, and swipe pass-through tests | Static and interactive rows | A row that is not an `<li>`, or a dropped prop, fails. | `audit:ListItem/behavior` |
179
+ | FR2 | `List.test.tsx` density, divider, radius, and edge treatment tests | Each density; dividers on and off; each edge | A row that ignores its List's settings fails. | `audit:ListItem/layout` |
180
+ | FR3 | `List.test.tsx` "keeps the inset of a ListItem outside a List" | Row outside a List | A row outside a List that loses its inset fails. | `audit:ListItem/layout` |
181
+ | FR4, AR3 | `List.test.tsx` marker and ListMarkerScope tests | Presence of a marker, none, scope marker, task | A missing, extra, or unlabelled marker fails. | `audit:ListItem/anatomy` |
182
+ | AR2 | `List.test.tsx` invisible button, anchor, disabled, and selection tests | Click, href, disabled, selected | Lost row semantics fail. | `audit:ListItem/accessibility` |
183
+
184
+ Read from source, with no focused test yet:
185
+
186
+ - FR1: `interactiveRef`, `rel`, and `ref` reaching the Item row.
187
+ - FR2: the divider border and square corners.
188
+ - FR4: each marker and the task checkbox sitting on the label's first line.
189
+ - FR3: `balanced` density, no dividers, and no marker outside a List.
190
+ - FR4: the shape of each bullet marker (the disc's fill, the circle's ring),
191
+ and the number marker and its counter.
192
+ - FR5: the `list-item` target, and `xstyle` applying after ListItem's styles.
193
+
194
+ ## Decision log
195
+
196
+ No component-local decision is recorded.
197
+
198
+ ## Open questions
199
+
200
+ None.
201
+
202
+ ## Content boundary
203
+
204
+ This file does not duplicate consumer prop tables, Item's row behavior, or
205
+ List's edge and geometry rules. It links to their owners.
@@ -39,9 +39,9 @@ import {
39
39
  } from '../theme/tokens.stylex';
40
40
  import type {BaseProps} from '../BaseProps';
41
41
  import {ListContext, type ListMarker} from './ListContext';
42
- import {CheckboxInput} from '../CheckboxInput';
43
- import {mergeProps} from '../utils';
44
- import {Item} from '../Item';
42
+ import {CheckboxInput} from '../CheckboxInput/CheckboxInput';
43
+ import {mergeProps} from '../utils/mergeProps';
44
+ import {Item} from '../Item/Item';
45
45
  import {ItemMarkerColumnsContext} from '../Item/ItemMarkerColumnsContext';
46
46
  import type {ItemAlign, ItemSwipeActions, ItemSwipeBehavior} from '../Item';
47
47
  import {themeProps} from '../utils/themeProps';
@@ -54,6 +54,21 @@ describe('MobileNav', () => {
54
54
  expect(dialog).not.toHaveAttribute('open');
55
55
  });
56
56
 
57
+ it('stays open when a file picker inside it is dismissed', () => {
58
+ const handleClose = vi.fn();
59
+ render(
60
+ <MobileNav isOpen={true} onOpenChange={handleClose}>
61
+ <input type="file" aria-label="Picture" />
62
+ </MobileNav>,
63
+ );
64
+ // A dismissed picker fires a bubbling `cancel` at the input; it is not a
65
+ // close request for the navigation.
66
+ const fromPicker = new Event('cancel', {bubbles: true, cancelable: true});
67
+ screen.getByLabelText('Picture').dispatchEvent(fromPicker);
68
+ expect(handleClose).not.toHaveBeenCalled();
69
+ expect(fromPicker.defaultPrevented).toBe(false);
70
+ });
71
+
57
72
  it('calls onOpenChange(false) on native cancel event (Escape)', () => {
58
73
  const handleClose = vi.fn();
59
74
  render(
@@ -225,7 +225,10 @@ const styles = stylex.create({
225
225
  display: 'flex',
226
226
  alignItems: 'center',
227
227
  justifyContent: 'space-between',
228
- height: spacingVars['--spacing-12'],
228
+ // At least one bar high, and taller for header content that needs it (a
229
+ // stacked title and search, or a title wrapped at a larger text size), so
230
+ // it never runs into the first item below.
231
+ minHeight: spacingVars['--spacing-12'],
229
232
  paddingInline: spacingVars['--spacing-2'],
230
233
  flexShrink: 0,
231
234
  borderBlockEndWidth: borderVars['--border-width'],
@@ -648,6 +651,11 @@ export function MobileNav({
648
651
  // only, and never while an IME composition is in progress.
649
652
  const handleCancel = useCallback(
650
653
  (event: React.SyntheticEvent<HTMLDialogElement>) => {
654
+ // A cancel that bubbles up from inside (a dismissed file picker) is not
655
+ // a close request; the platform's is fired at the dialog itself.
656
+ if (event.target !== event.currentTarget) {
657
+ return;
658
+ }
651
659
  event.preventDefault();
652
660
  if (!shouldDismissOnCloseRequest()) {
653
661
  return;
@@ -0,0 +1,89 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MobileNavHeader.a11y.chromium.spec.ts
5
+ * @input Built MobileNav stories in a phone context in real Chromium
6
+ * @output Browser evidence that the header row is at least one bar high and
7
+ * grows with taller header content: a stacked title and search field stay
8
+ * inside the row, and the first navigation item starts below it
9
+ * @position Chromium proof for MobileNav's header row (`styles.header` in
10
+ * MobileNav.tsx, `minHeight`); jsdom has no layout to measure it
11
+ */
12
+
13
+ import {expect, test, type Page} from '@playwright/test';
14
+ import {
15
+ DEFAULT_STORYBOOK_DIR,
16
+ serveStorybook,
17
+ type StaticServer,
18
+ } from '@astryxdesign/a11y-spec/storybook';
19
+
20
+ let storybook: StaticServer;
21
+ test.beforeAll(async () => {
22
+ storybook = await serveStorybook(
23
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
24
+ );
25
+ });
26
+ test.afterAll(async () => {
27
+ await storybook?.close();
28
+ });
29
+
30
+ test.use({
31
+ hasTouch: true,
32
+ isMobile: true,
33
+ viewport: {width: 390, height: 844},
34
+ deviceScaleFactor: 1,
35
+ });
36
+
37
+ /** Opens a story's drawer and returns the boxes of its header row and first item. */
38
+ async function openAndMeasure(page: Page, story: string, firstItem: string) {
39
+ await page.goto(
40
+ `${storybook.origin}/iframe.html?id=core-mobilenav--${story}&viewMode=story`,
41
+ );
42
+ await page.getByRole('button', {name: 'Open Navigation'}).click();
43
+ const nav = page.getByRole('dialog');
44
+ await expect(nav).toBeVisible();
45
+ const close = nav.getByRole('button', {name: 'Close navigation'});
46
+ // The Close button sits in the header row.
47
+ const header = close.locator('xpath=..');
48
+ const headerBox = await header.boundingBox();
49
+ const itemBox = await nav
50
+ .getByRole('link', {name: firstItem})
51
+ .first()
52
+ .boundingBox();
53
+ if (headerBox == null || itemBox == null) {
54
+ throw new Error('the drawer did not render');
55
+ }
56
+ return {nav, header, headerBox, itemBox};
57
+ }
58
+
59
+ test('a one-line header keeps the one-bar row', async ({page}) => {
60
+ const {headerBox, itemBox} = await openAndMeasure(
61
+ page,
62
+ 'default',
63
+ 'Dashboard',
64
+ );
65
+ // --spacing-12 at the default root size, as before.
66
+ expect(headerBox.height).toBeCloseTo(48, 0);
67
+ expect(itemBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height);
68
+ });
69
+
70
+ test('a taller header grows its row, and the first item starts below it', async ({
71
+ page,
72
+ }) => {
73
+ const {nav, headerBox, itemBox} = await openAndMeasure(
74
+ page,
75
+ 'tall-header',
76
+ 'Dashboard',
77
+ );
78
+ expect(headerBox.height).toBeGreaterThan(48);
79
+ const search = await nav.getByRole('textbox', {name: 'Search'}).boundingBox();
80
+ if (search == null) {
81
+ throw new Error('the search field did not render');
82
+ }
83
+ // The search field is inside the row, and nothing overlaps the first item.
84
+ expect(search.y).toBeGreaterThanOrEqual(headerBox.y);
85
+ expect(search.y + search.height).toBeLessThanOrEqual(
86
+ headerBox.y + headerBox.height,
87
+ );
88
+ expect(itemBox.y).toBeGreaterThanOrEqual(headerBox.y + headerBox.height);
89
+ });
@@ -9,6 +9,7 @@
9
9
  * SYNC: When MoreMenu.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
+ import {createRef} from 'react';
12
13
  import {describe, it, expect, vi, beforeAll, beforeEach} from 'vitest';
13
14
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
15
  import userEvent from '@testing-library/user-event';
@@ -256,6 +257,16 @@ describe('MoreMenu', () => {
256
257
  expect(handleEdit).not.toHaveBeenCalled();
257
258
  });
258
259
 
260
+ it('forwards its ref to the bottom-sheet trigger', () => {
261
+ const ref = createRef<HTMLButtonElement>();
262
+ render(
263
+ <MoreMenu items={defaultItems} ref={ref} presentation="bottom-sheet" />,
264
+ );
265
+ expect(ref.current).toBe(
266
+ screen.getByRole('button', {name: 'More options'}),
267
+ );
268
+ });
269
+
259
270
  it('supports forwardRef', () => {
260
271
  const ref = vi.fn();
261
272
  render(<MoreMenu items={defaultItems} ref={ref} />);
@@ -182,10 +182,13 @@ and `aria-controls` for the listbox, the listbox carries `aria-label` from
182
182
  focused element that owns arrow, Enter, Escape, and typeahead keys while open,
183
183
  so an anchor that cannot take focus still leaves a keyboard path.
184
184
 
185
- The create row is a `role="option"` with `aria-selected="false"` and no
186
- checkbox indicator. When it is the only result, the polite live region
187
- announces its label rather than the empty-search message, so what is announced
188
- matches what is on screen; picking it announces the creation.
185
+ The create row is a `role="option"` with `aria-selected="false"` and no checkbox
186
+ indicator. When it is the only result, the polite live region announces its
187
+ label rather than the empty-search message, so what is announced matches what is
188
+ on screen; picking it announces the creation. The result count and the create
189
+ row's label come from the search's own `useAnnounce({hasReplace: true})`
190
+ instance, so a newer one replaces one not yet spoken (`spec:AST-086` FR9), and
191
+ selection messages keep their own instance.
189
192
 
190
193
  ## Design relationships
191
194
 
@@ -9,7 +9,15 @@
9
9
  * SYNC: When MultiSelector.tsx API changes, update these tests.
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
12
+ import {
13
+ describe,
14
+ it,
15
+ expect,
16
+ vi,
17
+ beforeAll,
18
+ beforeEach,
19
+ afterEach,
20
+ } from 'vitest';
13
21
  import {
14
22
  act,
15
23
  render,
@@ -65,6 +73,15 @@ function politeRegion(): HTMLElement | null {
65
73
  }
66
74
 
67
75
  // Mock showPopover and hidePopover methods since they're not implemented in jsdom
76
+ // The bottom-sheet presentation renders BottomSheet through React.lazy. Its
77
+ // first import transforms the BottomSheet module tree, which on a loaded
78
+ // runner takes longer than a query's 1 s wait, so the first sheet test timed
79
+ // out before the dialog existed. Load the module once up front: lazy() then
80
+ // resolves from the module cache and every sheet test waits only on the open.
81
+ beforeAll(async () => {
82
+ await import('../BottomSheet/BottomSheet');
83
+ });
84
+
68
85
  beforeEach(() => {
69
86
  __resetInteractionModalityForTest();
70
87
  HTMLDialogElement.prototype.showModal = vi.fn(function (
@@ -128,6 +145,13 @@ function expectSilentBusySpinner() {
128
145
  expect(screen.queryByRole('status', {name: 'Loading'})).toBeNull();
129
146
  }
130
147
 
148
+ // The bottom sheet is loaded on demand (SelectorBottomSheet's lazy import).
149
+ // Loading its module is not part of an open, so the file waits for it once
150
+ // here, and each test's open waits only for the open.
151
+ beforeAll(async () => {
152
+ await import('../BottomSheet/BottomSheet');
153
+ });
154
+
131
155
  describe('MultiSelector', () => {
132
156
  const defaultOptions = ['Apple', 'Banana', 'Orange'];
133
157
 
@@ -1388,6 +1412,58 @@ describe('MultiSelector', () => {
1388
1412
  expect(within(listbox).getByText('Add a fruit first')).toBeInTheDocument();
1389
1413
  });
1390
1414
 
1415
+ describe('count trigger text', () => {
1416
+ it('shows the default count in the catalog message', () => {
1417
+ render(
1418
+ <MultiSelector
1419
+ label="Fruit"
1420
+ options={defaultOptions}
1421
+ value={['Apple', 'Banana']}
1422
+ onChange={() => {}}
1423
+ triggerDisplay="count"
1424
+ />,
1425
+ );
1426
+ expect(screen.getByText('2 selected')).toBeInTheDocument();
1427
+ });
1428
+
1429
+ it('translates the default count, with plural forms, under another locale', () => {
1430
+ render(
1431
+ <InternationalizationProvider
1432
+ locale="fr"
1433
+ overrides={{
1434
+ fr: {
1435
+ '@astryx.multiSelector.selectedCount':
1436
+ '{count, number} {count, plural, one {sélectionné} other {sélectionnés}}',
1437
+ },
1438
+ }}>
1439
+ <MultiSelector
1440
+ label="Fruit"
1441
+ options={defaultOptions}
1442
+ value={['Apple', 'Banana']}
1443
+ onChange={() => {}}
1444
+ triggerDisplay="count"
1445
+ />
1446
+ </InternationalizationProvider>,
1447
+ );
1448
+ expect(screen.getByText('2 sélectionnés')).toBeInTheDocument();
1449
+ expect(screen.queryByText('2 selected')).not.toBeInTheDocument();
1450
+ });
1451
+
1452
+ it('still lets formatValue replace the count text', () => {
1453
+ render(
1454
+ <MultiSelector
1455
+ label="Fruit"
1456
+ options={defaultOptions}
1457
+ value={['Apple', 'Banana']}
1458
+ onChange={() => {}}
1459
+ triggerDisplay="count"
1460
+ formatValue={items => `${items.length} fruits`}
1461
+ />,
1462
+ );
1463
+ expect(screen.getByText('2 fruits')).toBeInTheDocument();
1464
+ });
1465
+ });
1466
+
1391
1467
  describe('result announcements', () => {
1392
1468
  it('announces the match count politely while searching', async () => {
1393
1469
  const user = userEvent.setup();
@@ -3436,6 +3512,47 @@ describe('MultiSelector trigger render prop', () => {
3436
3512
  expect(screen.queryByText('Labels')).toBeNull();
3437
3513
  });
3438
3514
 
3515
+ // spec:AST-055 AR3: a light dismiss returns focus to the caller's control,
3516
+ // even when the press that closed the list focused another field.
3517
+ it('returns focus to the caller control after a light dismiss onto another field', async () => {
3518
+ const user = userEvent.setup();
3519
+ render(
3520
+ <>
3521
+ <MultiSelector
3522
+ label="Labels"
3523
+ options={OPTIONS}
3524
+ value={[]}
3525
+ onChange={() => {}}
3526
+ renderTrigger={props => (
3527
+ <button type="button" {...props}>
3528
+ Pick labels
3529
+ </button>
3530
+ )}
3531
+ />
3532
+ <input aria-label="Notes" />
3533
+ </>,
3534
+ );
3535
+ const opener = screen.getByRole('button', {name: 'Pick labels'});
3536
+ await user.click(opener);
3537
+ expect(opener).toHaveAttribute('aria-expanded', 'true');
3538
+
3539
+ act(() => {
3540
+ screen.getByRole('textbox', {name: 'Notes'}).focus();
3541
+ });
3542
+ const popover = document.querySelector('[popover]') as HTMLElement;
3543
+ act(() => {
3544
+ popover.dispatchEvent(
3545
+ Object.assign(new Event('toggle'), {
3546
+ oldState: 'open',
3547
+ newState: 'closed',
3548
+ }),
3549
+ );
3550
+ });
3551
+
3552
+ expect(opener).toHaveAttribute('aria-expanded', 'false');
3553
+ expect(opener).toHaveFocus();
3554
+ });
3555
+
3439
3556
  it('opens against the caller control and names the listbox by label', async () => {
3440
3557
  const user = userEvent.setup();
3441
3558
  render(
@@ -905,7 +905,7 @@ export interface MultiSelectorProps<
905
905
  * Not called when nothing is selected — the placeholder shows instead — and
906
906
  * not called for triggerDisplay 'badges', which renders Badge elements
907
907
  * rather than text.
908
- * @default items => `${items.length} selected` for 'count', "A, B, C, +N" for 'labels'
908
+ * @default the translated `@astryx.multiSelector.selectedCount` message ("2 selected") for 'count', "A, B, C, +N" for 'labels'
909
909
  */
910
910
  formatValue?: (items: MultiSelectorSelectedItem[]) => string;
911
911
 
@@ -1450,6 +1450,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1450
1450
  onHide: handleLayerHide,
1451
1451
  onShow: handleLayerShow,
1452
1452
  triggerRef,
1453
+ isFocusReturnAlways: hasExternalTrigger,
1453
1454
  popoverOptions: {
1454
1455
  hasLightDismiss: true,
1455
1456
  hasCloseButton: false,
@@ -1890,7 +1891,9 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1890
1891
  return (
1891
1892
  <span {...stylex.props(styles.triggerText)}>
1892
1893
  {formatValue?.(selectedItems) ??
1893
- `${optimisticValue.length} selected`}
1894
+ t('@astryx.multiSelector.selectedCount', {
1895
+ count: optimisticValue.length,
1896
+ })}
1894
1897
  </span>
1895
1898
  );
1896
1899
 
@@ -1933,6 +1936,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1933
1936
  placeholder,
1934
1937
  formatValue,
1935
1938
  maxBadges,
1939
+ t,
1936
1940
  ]);
1937
1941
 
1938
1942
  // Render search input
@@ -9,7 +9,7 @@
9
9
 
10
10
  import {cleanup, render, screen, within} from '@testing-library/react';
11
11
  import userEvent from '@testing-library/user-event';
12
- import {describe, expect, it} from 'vitest';
12
+ import {beforeAll, describe, expect, it} from 'vitest';
13
13
  import {
14
14
  expectAccessibilitySpec,
15
15
  LISTBOX_PATTERN,
@@ -23,6 +23,13 @@ import {installListboxDialogStubs} from '../../Selector/__tests__/Listbox.a11y.d
23
23
 
24
24
  installListboxDialogStubs();
25
25
 
26
+ // The bottom sheet is loaded on demand (SelectorBottomSheet's lazy import).
27
+ // Loading its module is not part of an open, so the file waits for it once
28
+ // here, and each scenario's open waits only for the open.
29
+ beforeAll(async () => {
30
+ await import('../../BottomSheet/BottomSheet');
31
+ });
32
+
26
33
  const scenarios = LISTBOX_SCENARIOS.filter(
27
34
  scenario => scenario.component === 'MultiSelector',
28
35
  );
@@ -44,7 +44,7 @@ system_specs: [spec:AST-005/DEC-1, spec:AST-020, spec:AST-085]
44
44
  | Behavior | A `role="menu"` list of `menuitem` rows inside a heading popover, with arrow, Home, End, typeahead, Enter, Space, and Escape support. Selecting an enabled item runs it and, where the host heading supplies a close action, closes the heading menu. |
45
45
  | End-user impact | Users switch products or accounts from the heading menu by pointer or keyboard; disabled rows are skipped by keyboard and typeahead. |
46
46
  | Builder impact | None. |
47
- | Compatibility/readiness | Records shipped behavior; no public change. Six implementation gaps (see Current-state impact). |
47
+ | Compatibility/readiness | Records shipped behavior; no public change. Five implementation gaps (see Current-state impact). |
48
48
  | Review checks | Reject losing `menu`/`menuitem` roles to caller props, keyboard focus landing on a disabled item, or a caller `onKeyDown` that silently disables the keyboard model. |
49
49
  | Governing rules | `family:navigation-destinations` FR7; `architecture:interaction-modality`; `architecture:component-theming-surface`; `architecture:layer-runtime`. |
50
50
 
@@ -81,10 +81,6 @@ Consumer migration instructions belong in consumer docs and release notes.
81
81
  `role="menu"` sits directly inside the heading's own `role="menu"` wrapper.
82
82
  WAI-ARIA 1.2 allows a `menu` to own only `menuitem`-family items and groups,
83
83
  so assistive technology meets a menu nested in a menu.
84
- - **Inside a heading, arrow keys move twice.** Inside SideNavHeading or
85
- TopNavHeading, the heading's own list handler also moves focus over every
86
- `menuitem`, so one ArrowDown moves two items, and in a two-item menu ArrowDown
87
- never moves focus. The APG menu pattern moves focus one item per arrow press.
88
84
  - **The menu has no name of its own.** NavHeadingMenu's `menu` carries no
89
85
  accessible name unless the caller passes `aria-label`; inside a heading only
90
86
  the heading's wrapper `menu` is named. The APG menu pattern, which