@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
@@ -680,7 +680,7 @@ function PointerDateField({
680
680
  // Unified change handler that fires both onChange and changeAction
681
681
  const fireChange = useCallback(
682
682
  (newValue: ISODateString | undefined) => {
683
- if (isBusy) {
683
+ if (isEffectivelyDisabled) {
684
684
  return;
685
685
  }
686
686
  onChange?.(newValue);
@@ -691,7 +691,13 @@ function PointerDateField({
691
691
  });
692
692
  }
693
693
  },
694
- [isBusy, onChange, changeAction, startTransition, setOptimisticValue],
694
+ [
695
+ isEffectivelyDisabled,
696
+ onChange,
697
+ changeAction,
698
+ startTransition,
699
+ setOptimisticValue,
700
+ ],
695
701
  );
696
702
 
697
703
  // Handle clear button click
@@ -714,11 +720,16 @@ function PointerDateField({
714
720
  // Handle date selection from calendar
715
721
  const handleDateSelect = useCallback(
716
722
  (selectedDate: ISODateString) => {
723
+ // An already-open calendar can outlive an enabled owner. Reject the
724
+ // whole selection, including dismissal, so its focused day stays put.
725
+ if (isDisabled) {
726
+ return;
727
+ }
717
728
  fireChange(selectedDate);
718
729
  setPendingInput(null);
719
730
  popover.hide();
720
731
  },
721
- [fireChange, popover],
732
+ [isDisabled, fireChange, popover],
722
733
  );
723
734
 
724
735
  // Handle input text change - update immediately if valid and allowed
@@ -948,17 +959,21 @@ function PointerDateField({
948
959
  {isBusy && <Spinner size="sm" aria-hidden="true" />}
949
960
  {statusIcon}
950
961
  {popover.render(
951
- <Calendar
952
- handleRef={calendarRef}
953
- mode="single"
954
- value={optimisticValue ?? null}
955
- onChange={handleDateSelect}
956
- min={min}
957
- max={max}
958
- dateConstraints={dateConstraints}
959
- numberOfMonths={numberOfMonths}
960
- weekStartsOn={weekStartsOn}
961
- />,
962
+ // The layer renders its children open or closed, so mount the
963
+ // calendar only while it is open; it opens on the current value.
964
+ !popover.isOpen ? null : (
965
+ <Calendar
966
+ handleRef={calendarRef}
967
+ mode="single"
968
+ value={optimisticValue ?? null}
969
+ onChange={handleDateSelect}
970
+ min={min}
971
+ max={max}
972
+ dateConstraints={dateConstraints}
973
+ numberOfMonths={numberOfMonths}
974
+ weekStartsOn={weekStartsOn}
975
+ />
976
+ ),
962
977
  {placement: 'below', alignment: 'start'},
963
978
  )}
964
979
  {showsDisabledMessage &&
@@ -29,6 +29,7 @@ import {
29
29
  afterEach,
30
30
  } from 'vitest';
31
31
  import {
32
+ act,
32
33
  render,
33
34
  screen,
34
35
  fireEvent,
@@ -276,6 +277,81 @@ function weekdayRow(): HTMLElement {
276
277
  return row as HTMLElement;
277
278
  }
278
279
 
280
+ describe('DateInput — open-sheet disablement', () => {
281
+ it('blocks selection after the open sheet becomes disabled, retaining value and focus', () => {
282
+ const onChange = vi.fn();
283
+ const changeAction = vi.fn();
284
+ const props = {initial: '2026-02-21' as const, onChange, changeAction};
285
+ withLayout(() => {
286
+ const {rerender} = render(<Controlled {...props} />);
287
+ fireEvent.click(field());
288
+ const day = within(pane('February 2026')).getByRole('button', {
289
+ name: /February 22, 2026/,
290
+ });
291
+ act(() => day.focus());
292
+ rerender(<Controlled {...props} isDisabled />);
293
+ expect(day).toHaveFocus();
294
+ fireEvent.click(day);
295
+
296
+ expect(onChange).not.toHaveBeenCalled();
297
+ expect(changeAction).not.toHaveBeenCalled();
298
+ expect(field()).toHaveValue('February 21, 2026');
299
+ expect(field()).toHaveAttribute('aria-expanded', 'true');
300
+ expect(day).toHaveFocus();
301
+ expect(
302
+ document.querySelector('[data-title="month-year"]'),
303
+ ).toHaveTextContent('February 2026');
304
+
305
+ rerender(<Controlled {...props} />);
306
+ fireEvent.click(day);
307
+ expect(onChange).toHaveBeenCalledExactlyOnceWith('2026-02-22');
308
+ expect(changeAction).toHaveBeenCalledExactlyOnceWith('2026-02-22');
309
+ expect(field()).toHaveAttribute('aria-expanded', 'true');
310
+ });
311
+ });
312
+
313
+ it('blocks disabled reset but preserves busy-only month navigation', () => {
314
+ const onChange = vi.fn();
315
+ const changeAction = vi.fn();
316
+ const props = {initial: '2026-02-21' as const, onChange, changeAction};
317
+ withLayout(() => {
318
+ const {rerender} = render(<Controlled {...props} />);
319
+ fireEvent.click(field());
320
+ const reset = screen.getByRole('button', {name: 'Reset'});
321
+ act(() => reset.focus());
322
+ rerender(<Controlled {...props} isDisabled />);
323
+ expect(reset).toHaveFocus();
324
+ fireEvent.click(reset);
325
+
326
+ expect(onChange).not.toHaveBeenCalled();
327
+ expect(changeAction).not.toHaveBeenCalled();
328
+ expect(field()).toHaveValue('February 21, 2026');
329
+ expect(field()).toHaveAttribute('aria-expanded', 'true');
330
+ expect(reset).toHaveFocus();
331
+ expect(
332
+ document.querySelector('[data-title="month-year"]'),
333
+ ).toHaveTextContent('February 2026');
334
+
335
+ rerender(<Controlled {...props} isLoading />);
336
+ fireEvent.click(reset);
337
+ expect(onChange).not.toHaveBeenCalled();
338
+ expect(changeAction).not.toHaveBeenCalled();
339
+ expect(field()).toHaveValue('February 21, 2026');
340
+ expect(field()).toHaveAttribute('aria-expanded', 'true');
341
+ expect(reset).toHaveFocus();
342
+ expect(
343
+ document.querySelector('[data-title="month-year"]'),
344
+ ).toHaveTextContent('March 2026');
345
+
346
+ rerender(<Controlled {...props} />);
347
+ fireEvent.click(reset);
348
+ expect(onChange).toHaveBeenCalledExactlyOnceWith(undefined);
349
+ expect(changeAction).toHaveBeenCalledExactlyOnceWith(undefined);
350
+ expect(field()).toHaveAttribute('aria-expanded', 'true');
351
+ });
352
+ });
353
+ });
354
+
279
355
  // ---------------------------------------------------------------------------
280
356
  // Which surface, and why
281
357
  // ---------------------------------------------------------------------------
@@ -85,6 +85,14 @@ const DAY_SIZE = dateInputTouchSizes.daySize;
85
85
  */
86
86
  const OVERSCAN = 3;
87
87
 
88
+ /** Input on the scroller that ends a steer: the user's from here. */
89
+ const USER_INPUT_EVENTS = [
90
+ 'touchstart',
91
+ 'wheel',
92
+ 'pointerdown',
93
+ 'keydown',
94
+ ] as const;
95
+
88
96
  const styles = stylex.create({
89
97
  scroller: {
90
98
  position: 'relative',
@@ -326,13 +334,59 @@ export function MonthScroller({
326
334
  // updater — React runs updaters during render, where another component's
327
335
  // setState is illegal.
328
336
  const centerRowRef = useRef(centerRow);
337
+
338
+ /**
339
+ * Scroll snapping is off from a re-center until the frame after its
340
+ * commit. WebKit keeps the position it snapped to as an index into the
341
+ * container's snap offsets, and re-snaps to that index after a layout: a
342
+ * re-center that unmounts panes before the snapped one shifts the index,
343
+ * so the re-snap lands a month further on, and the scroll that makes
344
+ * re-centers again. One stalled frame right after an arrow (300 ms,
345
+ * measured) was enough to set the calendar paging on by itself, a month
346
+ * every few frames. With snapping off across the commit nothing re-snaps,
347
+ * and switching it back on a frame later snaps to the pane in place.
348
+ *
349
+ * Each pause counts itself, so a batch of re-centers that ends on the
350
+ * center it began from, which commits no new center, still restores
351
+ * snapping.
352
+ *
353
+ * Not while a finger drives the scroll, from its touch until the scroll it
354
+ * started comes to rest: iOS tracks a snap target for the gesture, and
355
+ * turning snapping off drops it, so the swipe coasted past the month for
356
+ * about two seconds and was corrected late (iOS 26.2 simulator, where the
357
+ * same swipe snaps in about 0.6 s with snapping left on).
358
+ */
359
+ const [snapPauses, setSnapPauses] = useState(0);
360
+ const isFingerScrollRef = useRef(false);
361
+ const recenter = useCallback((scroller: HTMLElement, row: number) => {
362
+ if (!isFingerScrollRef.current) {
363
+ scroller.style.scrollSnapType = 'none';
364
+ setSnapPauses(count => count + 1);
365
+ }
366
+ setCenterRow(row);
367
+ }, []);
368
+ // Synchronizes the scroller's snapping with the browser's frame loop: it
369
+ // comes back on in the frame after the pause's commit, and a restore still
370
+ // pending at the next pause or at unmount is dropped.
371
+ useLayoutEffect(() => {
372
+ const scroller = scrollerRef.current;
373
+ if (snapPauses === 0 || scroller == null) {
374
+ return undefined;
375
+ }
376
+ const frame = requestAnimationFrame(() => {
377
+ scroller.style.scrollSnapType = '';
378
+ });
379
+ return () => cancelAnimationFrame(frame);
380
+ }, [snapPauses]);
329
381
  // A row to scroll to once its pane is mounted; see scrollToMonth.
330
382
  const pendingScrollRef = useRef<number | null>(null);
331
383
 
332
384
  /**
333
385
  * The row a programmatic scroll is heading for, so its own scroll events
334
- * are not reported back as if the user had gone there. Null once a finger
335
- * touches the scroller, or once the steered scroll has arrived.
386
+ * are not reported back as if the user had gone there, and so the settle
387
+ * correction finishes the steer instead of rounding it back. Null once the
388
+ * user's own input reaches the scroller (a finger, a wheel or trackpad, a
389
+ * press, a key), or once the steered scroll has arrived.
336
390
  */
337
391
  const steeredRowRef = useRef<number | null>(null);
338
392
 
@@ -401,7 +455,12 @@ export function MonthScroller({
401
455
  // below); a jump that far is never worth animating either.
402
456
  pendingScrollRef.current = row;
403
457
  centerRowRef.current = row;
404
- setCenterRow(row);
458
+ // The jump is a steer too. Recording it replaces a steer left behind
459
+ // by a scroll that moved nothing (the re-assert when the month and
460
+ // year wheels close), which the settle would otherwise finish,
461
+ // pulling the scroller back off the jump's month.
462
+ steeredRowRef.current = row;
463
+ recenter(scroller, row);
405
464
  return;
406
465
  }
407
466
  steeredRowRef.current = row;
@@ -410,7 +469,7 @@ export function MonthScroller({
410
469
  behavior,
411
470
  });
412
471
  },
413
- [paneSize, minMonthIndex, rowCount, isRTL],
472
+ [paneSize, minMonthIndex, rowCount, isRTL, recenter],
414
473
  );
415
474
 
416
475
  // Deliberately un-keyed: it runs after every render, costs a null check, and
@@ -476,8 +535,11 @@ export function MonthScroller({
476
535
  * A tail crawling slower than half a pixel a frame has effectively arrived,
477
536
  * so treating it as arrived is right anyway.
478
537
  *
479
- * The correction is at most half a pane by construction, and its own scroll
480
- * settles onto the boundary it just aimed at, so it cannot oscillate.
538
+ * After the user's own scroll the correction goes to the nearest boundary,
539
+ * at most half a pane away. After a steer it goes on to the steered month,
540
+ * however early the browser cut the steer short. Either way its own scroll
541
+ * settles onto the boundary it aimed at, where a steer counts as arrived, so
542
+ * it cannot oscillate.
481
543
  */
482
544
  const settleFrameRef = useRef<number | undefined>(undefined);
483
545
  useEffect(
@@ -490,6 +552,8 @@ export function MonthScroller({
490
552
  );
491
553
 
492
554
  useScrollSettle(scrollerRef, scroller => {
555
+ // At rest, momentum included: the finger no longer drives the scroll.
556
+ isFingerScrollRef.current = false;
493
557
  if (paneSize === 0) {
494
558
  return;
495
559
  }
@@ -505,15 +569,22 @@ export function MonthScroller({
505
569
  if (Math.abs(scroller.scrollLeft - offsetBefore) >= 0.5) {
506
570
  return;
507
571
  }
508
- const row = rowAtScrollOffset(
509
- scroller.scrollLeft,
510
- paneSize,
511
- rowCount,
512
- isRTL,
513
- );
572
+ // A steer the browser cut short is finished, not rounded back. WebKit
573
+ // ends a smooth scroll that a second one retargeted once the first
574
+ // one's time is up, firing `scrollend` part-way: an arrow tapped late
575
+ // in the previous arrow's scroll stopped near that month, and rounding
576
+ // to the nearest pane put the scroller back on it while the title named
577
+ // the new one.
578
+ const steered = steeredRowRef.current;
579
+ const row =
580
+ steered ??
581
+ rowAtScrollOffset(scroller.scrollLeft, paneSize, rowCount, isRTL);
514
582
  const target = scrollOffsetForRow(row, paneSize, isRTL);
515
583
  // Sub-pixel drift is the browser's own rounding, not a failed snap.
516
584
  if (Math.abs(scroller.scrollLeft - target) < 1) {
585
+ // At rest on its target, a steer has arrived even if no scroll
586
+ // crossed into its month.
587
+ steeredRowRef.current = null;
517
588
  return;
518
589
  }
519
590
  scroller.scrollTo({left: target, behavior: 'smooth'});
@@ -554,11 +625,20 @@ export function MonthScroller({
554
625
  if (scroller == null || paneSize === 0) {
555
626
  return;
556
627
  }
557
- // A finger on the scroller ends any steering: from here the months it
558
- // passes are the user's doing and every one of them is worth reporting.
559
- const onTouchStart = () => {
628
+ // A finger, a wheel or trackpad, a press or a key on the scroller ends any
629
+ // steering: from here the months it passes are the user's doing, every
630
+ // one of them is worth reporting, and the settle rounds to the nearest
631
+ // month instead of finishing a steer. A steer that moved nothing (the
632
+ // re-assert when the month and year wheels close on the month shown)
633
+ // would otherwise pull the user's next scroll back to its month.
634
+ const endSteer = () => {
560
635
  steeredRowRef.current = null;
561
636
  };
637
+ // From a touch until its scroll comes to rest (the settle above), the
638
+ // finger drives the scroll and a re-center leaves snapping on.
639
+ const onTouchStart = () => {
640
+ isFingerScrollRef.current = true;
641
+ };
562
642
 
563
643
  const onScroll = () => {
564
644
  if (frameRef.current != null) {
@@ -576,7 +656,7 @@ export function MonthScroller({
576
656
  return;
577
657
  }
578
658
  centerRowRef.current = row;
579
- setCenterRow(row);
659
+ recenter(scroller, row);
580
660
  // Report nothing while a steer is in flight. `scrollToMonth` is only
581
661
  // ever called by something that already knows the month — a wheel
582
662
  // commit, a header arrow, the re-assert when the wheels close — so
@@ -590,9 +670,9 @@ export function MonthScroller({
590
670
  // the exact moment reports start being trusted again. The month drifted
591
671
  // on the way back to the calendar.
592
672
  //
593
- // Cleared on arrival, or by a touch below — once a finger is on the
594
- // scroller the user owns it, and every month it passes is worth
595
- // reporting.
673
+ // Cleared on arrival, or by the user's input below (a finger, a wheel
674
+ // or trackpad, a press, a key) — from then the user owns the scroller,
675
+ // and every month it passes is worth reporting.
596
676
  if (steeredRowRef.current != null) {
597
677
  if (steeredRowRef.current === row) {
598
678
  steeredRowRef.current = null;
@@ -603,16 +683,22 @@ export function MonthScroller({
603
683
  });
604
684
  };
605
685
  scroller.addEventListener('scroll', onScroll, {passive: true});
686
+ for (const type of USER_INPUT_EVENTS) {
687
+ scroller.addEventListener(type, endSteer, {passive: true});
688
+ }
606
689
  scroller.addEventListener('touchstart', onTouchStart, {passive: true});
607
690
  return () => {
608
691
  scroller.removeEventListener('scroll', onScroll);
692
+ for (const type of USER_INPUT_EVENTS) {
693
+ scroller.removeEventListener(type, endSteer);
694
+ }
609
695
  scroller.removeEventListener('touchstart', onTouchStart);
610
696
  if (frameRef.current != null) {
611
697
  cancelAnimationFrame(frameRef.current);
612
698
  frameRef.current = undefined;
613
699
  }
614
700
  };
615
- }, [paneSize, rowCount, minMonthIndex, isRTL]);
701
+ }, [paneSize, rowCount, minMonthIndex, isRTL, recenter]);
616
702
 
617
703
  // Move the keyboard focus by whole days and let the scroller follow. Paging
618
704
  // by month is PageUp/PageDown; everything else is the APG grid vocabulary.
@@ -0,0 +1,165 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ import {act, render} from '@testing-library/react';
4
+ import {createRef} from 'react';
5
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
6
+ import {MonthScroller, type MonthScrollerHandle} from './MonthScroller';
7
+ import {SCROLL_QUIET_MS} from './useScrollSettle';
8
+
9
+ class FakeResizeObserver {
10
+ constructor(private callback: ResizeObserverCallback) {}
11
+ observe() {
12
+ this.callback([], this);
13
+ }
14
+ unobserve() {}
15
+ disconnect() {}
16
+ }
17
+
18
+ const PANE = 300;
19
+
20
+ /**
21
+ * The month scroller's settle, with the browser's scrolling played by hand:
22
+ * `scrollTo` is recorded, and the offset is whatever the test sets.
23
+ */
24
+ describe('MonthScroller settle', () => {
25
+ const scrollTo = vi.fn();
26
+ beforeEach(() => {
27
+ vi.useFakeTimers({
28
+ toFake: [
29
+ 'setTimeout',
30
+ 'clearTimeout',
31
+ 'requestAnimationFrame',
32
+ 'cancelAnimationFrame',
33
+ 'Date',
34
+ 'performance',
35
+ ],
36
+ });
37
+ vi.stubGlobal('ResizeObserver', FakeResizeObserver);
38
+ vi.spyOn(HTMLElement.prototype, 'clientWidth', 'get').mockReturnValue(PANE);
39
+ vi.spyOn(HTMLElement.prototype, 'clientHeight', 'get').mockReturnValue(
40
+ PANE,
41
+ );
42
+ Element.prototype.scrollTo = scrollTo;
43
+ scrollTo.mockClear();
44
+ });
45
+ afterEach(() => {
46
+ vi.useRealTimers();
47
+ vi.unstubAllGlobals();
48
+ vi.restoreAllMocks();
49
+ });
50
+
51
+ function renderScroller() {
52
+ const handleRef = createRef<MonthScrollerHandle>();
53
+ const view = render(
54
+ <MonthScroller
55
+ handleRef={handleRef}
56
+ minMonthIndex={0}
57
+ maxMonthIndex={23}
58
+ initialMonthIndex={9}
59
+ onVisibleMonthChange={() => {}}
60
+ selectedDate={null}
61
+ today={{year: 2026, month: 10, day: 15}}
62
+ isDateDisabled={() => false}
63
+ weekStartsOn={0}
64
+ onSelect={() => {}}
65
+ />,
66
+ );
67
+ const scroller = view.container.querySelector<HTMLElement>(
68
+ '[data-scroller="months"]',
69
+ );
70
+ if (scroller == null) {
71
+ throw new Error('no month scroller');
72
+ }
73
+ scrollTo.mockClear();
74
+ return {handleRef, scroller};
75
+ }
76
+
77
+ /** The browser moving the scroller to `left`, as a scroll event reports. */
78
+ /**
79
+ * A touch event carrying the lists a real one always has: jsdom has no
80
+ * constructible TouchEvent, and the gesture hook reads `touches[0]`.
81
+ */
82
+ function touchEvent(type: 'touchstart' | 'touchend') {
83
+ const event = new Event(type, {bubbles: true});
84
+ const point = {identifier: 1, clientX: 100, clientY: 100};
85
+ Object.defineProperties(event, {
86
+ touches: {value: type === 'touchend' ? [] : [point]},
87
+ targetTouches: {value: type === 'touchend' ? [] : [point]},
88
+ changedTouches: {value: [point]},
89
+ });
90
+ return event;
91
+ }
92
+
93
+ function scrollBy(scroller: HTMLElement, left: number) {
94
+ scroller.scrollLeft = left;
95
+ scroller.dispatchEvent(new Event('scroll'));
96
+ }
97
+
98
+ it('turns scroll snapping back on after re-centers that end on the month they began on', () => {
99
+ const {handleRef, scroller} = renderScroller();
100
+ // Two far jumps in one batch, out and back: each re-centers the mounted
101
+ // months, pausing scroll snapping, and the second puts the center back
102
+ // where it was, so the render that follows changes no center.
103
+ act(() => {
104
+ handleRef.current?.scrollToMonth(20);
105
+ handleRef.current?.scrollToMonth(9);
106
+ });
107
+ act(() => {
108
+ vi.advanceTimersByTime(50);
109
+ });
110
+ expect(scroller.style.scrollSnapType).toBe('');
111
+ });
112
+
113
+ it('leaves snapping on for a re-center while a finger drives the scroll', () => {
114
+ const {scroller} = renderScroller();
115
+ // A finger drags the next month in: the scroller crosses the middle of
116
+ // October's pane and re-centers its months with the finger still down.
117
+ act(() => {
118
+ scroller.dispatchEvent(touchEvent('touchstart'));
119
+ });
120
+ act(() => scrollBy(scroller, 9 * PANE + 0.6 * PANE));
121
+ act(() => {
122
+ vi.advanceTimersByTime(16);
123
+ });
124
+ // Snapping stays on: iOS tracks a snap target for the gesture.
125
+ expect(scroller.style.scrollSnapType).toBe('');
126
+ });
127
+
128
+ it('pauses snapping for a re-center again once the finger’s scroll has come to rest', () => {
129
+ const {handleRef, scroller} = renderScroller();
130
+ act(() => {
131
+ scroller.dispatchEvent(touchEvent('touchstart'));
132
+ scroller.dispatchEvent(touchEvent('touchend'));
133
+ });
134
+ act(() => {
135
+ vi.advanceTimersByTime(SCROLL_QUIET_MS + 50);
136
+ });
137
+ // An arrow's far jump after the swipe re-centers with snapping paused.
138
+ act(() => handleRef.current?.scrollToMonth(20));
139
+ expect(scroller.style.scrollSnapType).toBe('none');
140
+ act(() => {
141
+ vi.advanceTimersByTime(50);
142
+ });
143
+ expect(scroller.style.scrollSnapType).toBe('');
144
+ });
145
+
146
+ it('finishes a steer the browser cut short instead of rounding it back', () => {
147
+ const {handleRef, scroller} = renderScroller();
148
+ // An arrow toward month 10, its scroll 95 % of the way there.
149
+ act(() => handleRef.current?.scrollToMonth(10));
150
+ act(() => scrollBy(scroller, 9 * PANE + 0.95 * PANE));
151
+ // The other arrow, late in that scroll: back toward month 9.
152
+ act(() => handleRef.current?.scrollToMonth(9));
153
+ scrollTo.mockClear();
154
+ // WebKit ends the retargeted scroll where the first one's time ran out,
155
+ // near month 10, and the scroller goes quiet there.
156
+ act(() => scrollBy(scroller, 9 * PANE + 0.95 * PANE));
157
+ act(() => {
158
+ vi.advanceTimersByTime(SCROLL_QUIET_MS + 50);
159
+ });
160
+ // The settle finishes the steer onto month 9, not the nearest pane (10).
161
+ expect(
162
+ scrollTo.mock.calls.map(([options]) => (options as ScrollToOptions).left),
163
+ ).toEqual([9 * PANE]);
164
+ });
165
+ });
@@ -772,7 +772,7 @@ export function TouchDateField({
772
772
 
773
773
  const fireChange = useCallback(
774
774
  (newValue: ISODateString | undefined) => {
775
- if (isBusy) {
775
+ if (isEffectivelyDisabled) {
776
776
  return;
777
777
  }
778
778
  onChange?.(newValue);
@@ -783,7 +783,13 @@ export function TouchDateField({
783
783
  });
784
784
  }
785
785
  },
786
- [isBusy, onChange, changeAction, startTransition, setOptimisticValue],
786
+ [
787
+ isEffectivelyDisabled,
788
+ onChange,
789
+ changeAction,
790
+ startTransition,
791
+ setOptimisticValue,
792
+ ],
787
793
  );
788
794
 
789
795
  const openSheet = useCallback(() => {
@@ -841,6 +847,11 @@ export function TouchDateField({
841
847
  * value is still cleared either way — that half never depends on the range.
842
848
  */
843
849
  const handleResetInSheet = useCallback(() => {
850
+ // Disabled: reject the entire reset without closing or moving focus.
851
+ // Busy-only fields keep month navigation; fireChange blocks the clear.
852
+ if (isDisabled) {
853
+ return;
854
+ }
844
855
  fireChange(undefined);
845
856
  const currentMonth = monthIndexOf(today);
846
857
  if (currentMonth < minMonthIndex || currentMonth > maxMonthIndex) {
@@ -851,7 +862,7 @@ export function TouchDateField({
851
862
  }
852
863
  setMonthIndex(currentMonth);
853
864
  scrollerHandleRef.current?.scrollToMonth(currentMonth, 'smooth');
854
- }, [fireChange, today, monthIndex, minMonthIndex, maxMonthIndex]);
865
+ }, [isDisabled, fireChange, today, monthIndex, minMonthIndex, maxMonthIndex]);
855
866
 
856
867
  // Selection commits on the tap and leaves the sheet up, so a mistake can be
857
868
  // corrected in place and a nearby date reconsidered without reopening.