@astryxdesign/core 0.7.5 → 0.7.6-canary.bb18c81

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/AlertDialog/AlertDialog.js +5 -5
  4. package/dist/Avatar/Avatar.d.ts.map +1 -1
  5. package/dist/Avatar/Avatar.js +12 -3
  6. package/dist/BottomSheet/BottomSheet.js +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +2 -1
  9. package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
  10. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  11. package/dist/BottomSheet/useSheetGestures.js +30 -3
  12. package/dist/Button/Button.js +4 -4
  13. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  14. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  15. package/dist/DateInput/DateInput.d.ts.map +1 -1
  16. package/dist/DateInput/DateInput.js +4 -1
  17. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  18. package/dist/DateInput/MonthScroller.js +101 -15
  19. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  20. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  21. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  22. package/dist/DateTimeInput/DateTimeInput.js +4 -1
  23. package/dist/Dialog/Dialog.d.ts.map +1 -1
  24. package/dist/Dialog/Dialog.js +34 -9
  25. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  26. package/dist/Dialog/DialogHeader.js +5 -4
  27. package/dist/Divider/Divider.d.ts.map +1 -1
  28. package/dist/Divider/Divider.js +2 -1
  29. package/dist/Drawer/Drawer.d.ts.map +1 -1
  30. package/dist/Drawer/Drawer.js +5 -0
  31. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  32. package/dist/DropdownMenu/DropdownMenu.js +4 -1
  33. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  34. package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
  35. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +10 -1
  36. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  37. package/dist/DropdownMenu/lazyMenuBottomSheet.js +16 -2
  38. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  39. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  40. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  41. package/dist/EmptyState/EmptyState.js +2 -1
  42. package/dist/Field/FieldLabel.d.ts +1 -1
  43. package/dist/Field/FieldLabel.d.ts.map +1 -1
  44. package/dist/Field/FieldLabel.js +4 -4
  45. package/dist/Field/InputClearButton.js +3 -3
  46. package/dist/Field/PanelSearchInput.js +2 -2
  47. package/dist/FieldStatus/FieldStatus.js +2 -2
  48. package/dist/FileInput/FileInput.d.ts.map +1 -1
  49. package/dist/FileInput/FileInput.js +7 -1
  50. package/dist/Heading/Heading.js +1 -1
  51. package/dist/Icon/Icon.js +1 -1
  52. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  53. package/dist/Indicator/CheckIndicator.js +2 -1
  54. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  55. package/dist/Indicator/CheckboxIndicator.js +3 -1
  56. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  57. package/dist/Indicator/RadioIndicator.js +3 -1
  58. package/dist/Item/Item.d.ts.map +1 -1
  59. package/dist/Item/Item.js +2 -1
  60. package/dist/Item/ItemSwipeLayer.js +1 -1
  61. package/dist/Layout/Layout.js +1 -1
  62. package/dist/Layout/LayoutContent.js +1 -1
  63. package/dist/Layout/LayoutFooter.js +1 -1
  64. package/dist/Layout/LayoutHeader.js +1 -1
  65. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  66. package/dist/Lightbox/Lightbox.js +5 -0
  67. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
  68. package/dist/List/List.js +1 -1
  69. package/dist/List/ListItem.js +3 -3
  70. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  71. package/dist/MobileNav/MobileNav.js +7 -2
  72. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  73. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  74. package/dist/MultiSelector/MultiSelector.js +5 -2
  75. package/dist/NumberInput/NumberInput.d.ts +3 -1
  76. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  77. package/dist/NumberInput/NumberInput.js +3 -3
  78. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  79. package/dist/ProgressBar/ProgressBar.js +16 -12
  80. package/dist/Resizable/useResizable.d.ts.map +1 -1
  81. package/dist/Resizable/useResizable.js +28 -2
  82. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  83. package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
  84. package/dist/Selector/useSelectorPresentation.d.ts +8 -1
  85. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  86. package/dist/Selector/useSelectorPresentation.js +20 -4
  87. package/dist/Slider/Slider.d.ts.map +1 -1
  88. package/dist/Slider/Slider.js +7 -1
  89. package/dist/Spinner/Spinner.js +2 -2
  90. package/dist/Stack/Stack.js +1 -1
  91. package/dist/Stack/StackItem.js +1 -1
  92. package/dist/TabList/TabList.d.ts.map +1 -1
  93. package/dist/TabList/TabList.js +16 -1
  94. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  95. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  96. package/dist/Text/Text.js +1 -1
  97. package/dist/Text/useTruncation.d.ts.map +1 -1
  98. package/dist/Text/useTruncation.js +23 -5
  99. package/dist/TextArea/TextArea.d.ts +1 -1
  100. package/dist/TextArea/TextArea.d.ts.map +1 -1
  101. package/dist/TextArea/TextArea.js +13 -2
  102. package/dist/TextInput/TextInput.d.ts.map +1 -1
  103. package/dist/TextInput/TextInput.js +3 -3
  104. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  105. package/dist/Timestamp/Timestamp.js +2 -2
  106. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  107. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  108. package/dist/Toast/ToastViewport.js +82 -51
  109. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  110. package/dist/Tokenizer/Tokenizer.js +13 -2
  111. package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
  112. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  113. package/dist/Toolbar/Toolbar.js +2 -1
  114. package/dist/Typeahead/Typeahead.d.ts +1 -1
  115. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  116. package/dist/Typeahead/Typeahead.js +9 -1
  117. package/dist/astryx.css +14 -1
  118. package/dist/gestures/PinchGestureRecognizer.d.ts +30 -0
  119. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  120. package/dist/gestures/PinchGestureRecognizer.js +125 -0
  121. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +70 -0
  122. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  123. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +220 -0
  124. package/dist/gestures/SwipeGestureRecognizer.d.ts +36 -0
  125. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  126. package/dist/gestures/SwipeGestureRecognizer.js +145 -0
  127. package/dist/gestures/VelocityTracker.d.ts +6 -2
  128. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  129. package/dist/gestures/VelocityTracker.js +7 -3
  130. package/dist/gestures/types.d.ts +11 -7
  131. package/dist/gestures/types.d.ts.map +1 -1
  132. package/dist/gestures/types.js +22 -2
  133. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  134. package/dist/hooks/useAdaptivePresentation.js +4 -1
  135. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  136. package/dist/hooks/useMenuHover.js +12 -1
  137. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  138. package/dist/i18n/generated-locales/en.generated.js +3 -0
  139. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  140. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  141. package/dist/motion/animator.d.ts +7 -1
  142. package/dist/motion/animator.d.ts.map +1 -1
  143. package/dist/motion/animator.js +119 -27
  144. package/dist/theme/defineTheme.js +1 -1
  145. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  146. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  147. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  148. package/dist/theme/domainTokens/index.d.ts +5 -4
  149. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  150. package/dist/theme/domainTokens/index.js +5 -9
  151. package/dist/utils/focusReturn.d.ts +17 -1
  152. package/dist/utils/focusReturn.d.ts.map +1 -1
  153. package/dist/utils/focusReturn.js +11 -4
  154. package/dist/utils/getNodeText.d.ts +8 -0
  155. package/dist/utils/getNodeText.d.ts.map +1 -0
  156. package/dist/utils/getNodeText.js +37 -0
  157. package/dist/utils/interactionModality.d.ts +11 -2
  158. package/dist/utils/interactionModality.d.ts.map +1 -1
  159. package/dist/utils/interactionModality.js +58 -5
  160. package/dist/utils/presentation.d.ts +50 -0
  161. package/dist/utils/presentation.d.ts.map +1 -0
  162. package/dist/utils/presentation.js +125 -0
  163. package/dist/utils/touchPresentation.d.ts.map +1 -1
  164. package/dist/utils/touchPresentation.js +5 -3
  165. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  166. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  167. package/dist/utils/touchPresentation.stylex.js +33 -0
  168. package/locales/en.json +13 -1
  169. package/locales/pseudo.json +9 -0
  170. package/package.json +5 -4
  171. package/src/AlertDialog/AlertDialog.tsx +6 -5
  172. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
  173. package/src/Avatar/Avatar.spec.md +22 -22
  174. package/src/Avatar/Avatar.tsx +18 -1
  175. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
  176. package/src/BottomSheet/BottomSheet.test.tsx +46 -0
  177. package/src/BottomSheet/BottomSheet.tsx +1 -1
  178. package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
  179. package/src/BottomSheet/useSheetGestures.test.ts +52 -0
  180. package/src/BottomSheet/useSheetGestures.ts +37 -6
  181. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
  182. package/src/Button/Button.tsx +4 -4
  183. package/src/Calendar/Calendar.spec.md +87 -48
  184. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
  185. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
  186. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  187. package/src/Collapsible/Collapsible.spec.md +2 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
  189. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
  190. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
  191. package/src/ContextMenu/ContextMenu.test.tsx +35 -0
  192. package/src/DateInput/DateInput.test.tsx +53 -2
  193. package/src/DateInput/DateInput.tsx +15 -11
  194. package/src/DateInput/MonthScroller.tsx +106 -20
  195. package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
  196. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
  197. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
  198. package/src/DateRangeInput/DateRangeInput.tsx +16 -12
  199. package/src/DateTimeInput/DateTimeInput.spec.md +71 -48
  200. package/src/DateTimeInput/DateTimeInput.test.tsx +61 -0
  201. package/src/DateTimeInput/DateTimeInput.tsx +15 -11
  202. package/src/Dialog/Dialog.test.tsx +216 -2
  203. package/src/Dialog/Dialog.tsx +42 -9
  204. package/src/Dialog/DialogHeader.tsx +5 -4
  205. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
  206. package/src/Divider/Divider.tsx +2 -1
  207. package/src/Drawer/Drawer.spec.md +111 -80
  208. package/src/Drawer/Drawer.test.tsx +22 -0
  209. package/src/Drawer/Drawer.tsx +5 -0
  210. package/src/Drawer/DrawerHeader.spec.md +181 -0
  211. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
  212. package/src/DropdownMenu/DropdownMenu.tsx +7 -1
  213. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
  214. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
  215. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
  216. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
  217. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +53 -0
  218. package/src/DropdownMenu/lazyMenuBottomSheet.ts +23 -6
  219. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  220. package/src/EmptyState/EmptyState.tsx +2 -1
  221. package/src/Field/FieldLabel.tsx +5 -4
  222. package/src/Field/InputClearButton.tsx +3 -3
  223. package/src/Field/PanelSearchInput.tsx +2 -2
  224. package/src/FieldStatus/FieldStatus.tsx +2 -2
  225. package/src/FileInput/FileInput.test.tsx +21 -0
  226. package/src/FileInput/FileInput.tsx +4 -1
  227. package/src/FormLayout/FormLayout.spec.md +7 -8
  228. package/src/Heading/Heading.tsx +1 -1
  229. package/src/Icon/Icon.tsx +1 -1
  230. package/src/Indicator/CheckIndicator.spec.md +47 -42
  231. package/src/Indicator/CheckIndicator.tsx +2 -1
  232. package/src/Indicator/CheckboxIndicator.spec.md +40 -37
  233. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  234. package/src/Indicator/Indicator.spec.md +188 -0
  235. package/src/Indicator/RadioIndicator.tsx +3 -1
  236. package/src/Item/Item.tsx +2 -1
  237. package/src/Item/ItemSwipeLayer.tsx +1 -1
  238. package/src/Layout/Layout.spec.md +127 -131
  239. package/src/Layout/Layout.tsx +1 -1
  240. package/src/Layout/LayoutContent.spec.md +205 -0
  241. package/src/Layout/LayoutContent.tsx +1 -1
  242. package/src/Layout/LayoutFooter.spec.md +190 -0
  243. package/src/Layout/LayoutFooter.tsx +1 -1
  244. package/src/Layout/LayoutHeader.spec.md +193 -0
  245. package/src/Layout/LayoutHeader.tsx +1 -1
  246. package/src/Layout/LayoutPanel.spec.md +194 -0
  247. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
  248. package/src/Lightbox/Lightbox.test.tsx +18 -0
  249. package/src/Lightbox/Lightbox.tsx +5 -0
  250. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
  251. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
  252. package/src/List/List.tsx +1 -1
  253. package/src/List/ListItem.tsx +3 -3
  254. package/src/MobileNav/MobileNav.test.tsx +15 -0
  255. package/src/MobileNav/MobileNav.tsx +9 -1
  256. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
  257. package/src/MoreMenu/MoreMenu.test.tsx +11 -0
  258. package/src/MultiSelector/MultiSelector.spec.md +7 -4
  259. package/src/MultiSelector/MultiSelector.test.tsx +118 -1
  260. package/src/MultiSelector/MultiSelector.tsx +6 -2
  261. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
  262. package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
  263. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  264. package/src/NumberInput/NumberInput.spec.md +59 -37
  265. package/src/NumberInput/NumberInput.tsx +13 -3
  266. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
  267. package/src/ProgressBar/ProgressBar.test.tsx +108 -32
  268. package/src/ProgressBar/ProgressBar.tsx +58 -10
  269. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
  270. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
  271. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
  272. package/src/Resizable/useResizable.test.ts +43 -0
  273. package/src/Resizable/useResizable.ts +32 -9
  274. package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
  275. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
  276. package/src/Selector/Selector.test.tsx +90 -1
  277. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
  278. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
  279. package/src/Selector/useSelectorPresentation.ts +31 -3
  280. package/src/SideNav/SideNav.test.tsx +27 -0
  281. package/src/Slider/Slider.test.tsx +12 -0
  282. package/src/Slider/Slider.tsx +4 -1
  283. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
  284. package/src/Spinner/Spinner.tsx +2 -2
  285. package/src/Stack/Stack.tsx +1 -1
  286. package/src/Stack/StackItem.tsx +1 -1
  287. package/src/TabList/TabList.test.tsx +52 -1
  288. package/src/TabList/TabList.tsx +16 -1
  289. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
  290. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
  291. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
  292. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  293. package/src/Text/Text.tsx +1 -1
  294. package/src/Text/useTruncation.test.tsx +128 -1
  295. package/src/Text/useTruncation.ts +23 -5
  296. package/src/TextArea/TextArea.spec.md +10 -13
  297. package/src/TextArea/TextArea.test.tsx +60 -0
  298. package/src/TextArea/TextArea.tsx +15 -1
  299. package/src/TextInput/TextInput.test.tsx +21 -0
  300. package/src/TextInput/TextInput.tsx +3 -1
  301. package/src/Timestamp/Timestamp.spec.md +6 -12
  302. package/src/Timestamp/Timestamp.test.tsx +30 -5
  303. package/src/Timestamp/Timestamp.tsx +6 -2
  304. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  305. package/src/Toast/Toast.spec.md +7 -10
  306. package/src/Toast/ToastViewport.test.tsx +575 -1
  307. package/src/Toast/ToastViewport.tsx +100 -50
  308. package/src/Tokenizer/Tokenizer.spec.md +5 -3
  309. package/src/Tokenizer/Tokenizer.test.tsx +68 -0
  310. package/src/Tokenizer/Tokenizer.tsx +14 -2
  311. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
  312. package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
  313. package/src/Toolbar/Toolbar.spec.md +5 -6
  314. package/src/Toolbar/Toolbar.test.tsx +30 -0
  315. package/src/Toolbar/Toolbar.tsx +2 -1
  316. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
  317. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
  318. package/src/TopNav/TopNav.test.tsx +27 -0
  319. package/src/Typeahead/Typeahead.test.tsx +40 -0
  320. package/src/Typeahead/Typeahead.tsx +12 -1
  321. package/src/gestures/PinchGestureRecognizer.ts +144 -0
  322. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +252 -0
  323. package/src/gestures/SwipeGestureRecognizer.ts +193 -0
  324. package/src/gestures/VelocityTracker.ts +10 -3
  325. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
  326. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
  327. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
  328. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
  329. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +188 -0
  330. package/src/gestures/__tests__/harness.ts +21 -2
  331. package/src/gestures/types.ts +23 -9
  332. package/src/hooks/useAdaptivePresentation.ts +4 -1
  333. package/src/hooks/useMenuHover.ts +15 -1
  334. package/src/i18n/generated-locales/en.generated.ts +4 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +4 -0
  336. package/src/motion/README.md +11 -1
  337. package/src/motion/animator.test.ts +151 -3
  338. package/src/motion/animator.ts +149 -33
  339. package/src/theme/defineTheme.ts +1 -1
  340. package/src/theme/derivedVarRegistry.test.ts +4 -0
  341. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  342. package/src/theme/domainTokens/index.ts +6 -10
  343. package/src/utils/focusReturn.ts +17 -6
  344. package/src/utils/getNodeText.test.ts +24 -0
  345. package/src/utils/getNodeText.ts +37 -0
  346. package/src/utils/interactionModality.test.ts +45 -1
  347. package/src/utils/interactionModality.ts +64 -5
  348. package/src/utils/presentation.test.ts +380 -0
  349. package/src/utils/presentation.ts +180 -0
  350. package/src/utils/pressPaintPriority.test.ts +80 -0
  351. package/src/utils/touchPresentation.stylex.ts +43 -0
  352. package/src/utils/touchPresentation.test.ts +140 -1
  353. package/src/utils/touchPresentation.ts +5 -3
@@ -0,0 +1,312 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MonthScrollerSteps.interaction.browser.spec.ts
5
+ * @input The built "Default" DateInput story, shown in a bottom sheet, the
6
+ * arrows clicked from the page, real mouse and trackpad input and the
7
+ * interaction harness
8
+ * @output Interaction proofs for the month scroller's steps: after the month
9
+ * and year wheels close, a trackpad scroll still pages to the next month and
10
+ * Reset from four months on still rests on today's month; an arrow step
11
+ * through a stalled frame moves exactly one month; under a finger, a swipe
12
+ * that crosses the middle of a month as it lifts keeps scroll snapping on
13
+ * and rests on the next month.
14
+ * (A second arrow step late in the first one's scroll, which WebKit cuts
15
+ * short, is proven in MonthScrollerSettle.test.tsx: a loaded page's frames
16
+ * are too far apart to aim a click at a share of a 300 ms scroll.)
17
+ * @position Interaction tests of MonthScroller.tsx
18
+ * (`*.interaction.browser.spec.ts`, run by `pnpm test:interaction`)
19
+ */
20
+
21
+ import {expect, test, type Locator, type Page} from '@playwright/test';
22
+ import {
23
+ blockMainThread,
24
+ drag,
25
+ openStory,
26
+ touchDriver,
27
+ withStorybook,
28
+ } from '@astryxdesign/interaction-harness';
29
+
30
+ const storybook = withStorybook(test);
31
+ const STORY = 'core-dateinput--default';
32
+ const ARGS = {presentation: 'bottom-sheet'};
33
+
34
+ /** Waits until the scroller's offset has not moved for ten frames. */
35
+ async function waitForRest(viewport: Locator): Promise<number> {
36
+ return viewport.evaluate(
37
+ async element =>
38
+ new Promise<number>(resolve => {
39
+ let last = element.scrollLeft;
40
+ let still = 0;
41
+ const check = () => {
42
+ const now = element.scrollLeft;
43
+ still = now === last ? still + 1 : 0;
44
+ last = now;
45
+ if (still >= 10) {
46
+ resolve(now);
47
+ } else {
48
+ requestAnimationFrame(check);
49
+ }
50
+ };
51
+ requestAnimationFrame(check);
52
+ }),
53
+ );
54
+ }
55
+
56
+ /** The month the scroller rests on, as the pane's own grid names it. */
57
+ /**
58
+ * Waits until a month's grid sits flush with the scroller's edge, the offset
59
+ * unchanged, for ten frames: the end of the scroller's own settle
60
+ * correction, however long the page takes to run it. (The panes' width can
61
+ * be fractional, so the grid's own box is the measure, not the offset.)
62
+ */
63
+ async function waitForRestOnMonth(viewport: Locator): Promise<number> {
64
+ return viewport.evaluate(
65
+ async element =>
66
+ new Promise<number>(resolve => {
67
+ let last = element.scrollLeft;
68
+ let still = 0;
69
+ const flush = () => {
70
+ const edge = element.getBoundingClientRect().left;
71
+ return [
72
+ ...element.querySelectorAll<HTMLElement>('[role="grid"]'),
73
+ ].some(
74
+ grid => Math.abs(grid.getBoundingClientRect().left - edge) < 1,
75
+ );
76
+ };
77
+ const check = () => {
78
+ const now = element.scrollLeft;
79
+ still = now === last && flush() ? still + 1 : 0;
80
+ last = now;
81
+ if (still >= 10) {
82
+ resolve(now);
83
+ } else {
84
+ requestAnimationFrame(check);
85
+ }
86
+ };
87
+ requestAnimationFrame(check);
88
+ }),
89
+ );
90
+ }
91
+
92
+ async function monthAtRest(viewport: Locator): Promise<string> {
93
+ return viewport.evaluate(element => {
94
+ const box = element.getBoundingClientRect();
95
+ const middle = box.left + box.width / 2;
96
+ for (const grid of element.querySelectorAll<HTMLElement>('[role="grid"]')) {
97
+ const r = grid.getBoundingClientRect();
98
+ if (r.left <= middle && r.right >= middle) {
99
+ return grid.getAttribute('aria-label') ?? '';
100
+ }
101
+ }
102
+ return '';
103
+ });
104
+ }
105
+
106
+ async function openCalendar(page: Page) {
107
+ await openStory(page, storybook(), STORY, ARGS);
108
+ await page
109
+ .getByRole('button', {name: /calendar/i})
110
+ .first()
111
+ .click();
112
+ const viewport = page.locator('[data-scroller="months"]');
113
+ await expect(viewport).toBeVisible();
114
+ await waitForRest(viewport);
115
+ return viewport;
116
+ }
117
+
118
+ test.use({hasTouch: false, isMobile: false});
119
+
120
+ /**
121
+ * Waits until the scroller comes to rest, or until it has gone more than
122
+ * `limit` past `from`: a scroller that runs on by itself never rests, and
123
+ * the proof should fail where it ran, not at the test's timeout.
124
+ */
125
+ async function waitForRestOrRun(
126
+ viewport: Locator,
127
+ from: number,
128
+ limit: number,
129
+ ): Promise<number> {
130
+ return viewport.evaluate(
131
+ async (element, {from, limit}) =>
132
+ new Promise<number>(resolve => {
133
+ let last = element.scrollLeft;
134
+ let still = 0;
135
+ const check = () => {
136
+ const now = element.scrollLeft;
137
+ still = now === last ? still + 1 : 0;
138
+ last = now;
139
+ if (still >= 10 || Math.abs(now - from) > limit) {
140
+ resolve(now);
141
+ } else {
142
+ requestAnimationFrame(check);
143
+ }
144
+ };
145
+ requestAnimationFrame(check);
146
+ }),
147
+ {from, limit},
148
+ );
149
+ }
150
+
151
+ /**
152
+ * A stalled frame right after an arrow (a 300 ms block of the main thread)
153
+ * let the re-center that follows the step land after the scroll had ended,
154
+ * and WebKit's re-snap after it then paged on by itself, a month every few
155
+ * frames. The step moves exactly one month.
156
+ */
157
+ test('an arrow step through a stalled frame moves exactly one month', async ({
158
+ page,
159
+ }) => {
160
+ const viewport = await openCalendar(page);
161
+ const title = page.getByRole('button', {name: /Choose month and year/});
162
+ const from = await viewport.evaluate(element => element.scrollLeft);
163
+ const width = await viewport.evaluate(element => element.clientWidth);
164
+ const box = await page
165
+ .getByRole('button', {name: 'Next month'})
166
+ .boundingBox();
167
+ if (box == null) {
168
+ throw new Error('no Next month button');
169
+ }
170
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
171
+ await blockMainThread(page, 300);
172
+ const at = await waitForRestOrRun(viewport, from, 2 * width);
173
+ expect(Math.round((at - from) / width)).toBe(1);
174
+ await waitForRestOnMonth(viewport);
175
+ expect(
176
+ Math.round(((await viewport.evaluate(e => e.scrollLeft)) - from) / width),
177
+ ).toBe(1);
178
+ await expect(title).toHaveAccessibleName(
179
+ new RegExp(await monthAtRest(viewport)),
180
+ );
181
+ });
182
+
183
+ /**
184
+ * Closing the month and year wheels re-asserts the month shown, a steer that
185
+ * moves nothing. A trackpad scroll after it is the user's: it pages to the
186
+ * next month and stays there, the title following.
187
+ */
188
+ test('after the month and year wheels close, a trackpad scroll to the next month stays there', async ({
189
+ page,
190
+ }) => {
191
+ const viewport = await openCalendar(page);
192
+ const toggle = page.getByRole('button', {name: /Choose month and year/});
193
+ const start = await monthAtRest(viewport);
194
+ expect(start).not.toBe('');
195
+ const toggleBox = await toggle.boundingBox();
196
+ if (toggleBox == null) {
197
+ throw new Error('no month and year toggle');
198
+ }
199
+ const clickToggle = async () =>
200
+ page.mouse.click(
201
+ toggleBox.x + toggleBox.width / 2,
202
+ toggleBox.y + toggleBox.height / 2,
203
+ );
204
+ await clickToggle();
205
+ await expect(toggle).toHaveAttribute('aria-expanded', 'true');
206
+ await clickToggle();
207
+ await expect(toggle).toHaveAttribute('aria-expanded', 'false');
208
+ await waitForRest(viewport);
209
+
210
+ const box = await viewport.boundingBox();
211
+ if (box == null) {
212
+ throw new Error('no month scroller');
213
+ }
214
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
215
+ // A horizontal trackpad scroll of 70 % of a month, as one wheel event: a
216
+ // burst of small ones reaches a loaded page far enough apart to count as
217
+ // separate gestures, each snapping back on its own.
218
+ await page.mouse.wheel(box.width * 0.7, 0);
219
+ // Past the scroller's own settle correction too, which ends on a month's
220
+ // boundary.
221
+ await waitForRestOnMonth(viewport);
222
+ const next = await monthAtRest(viewport);
223
+ expect(next).not.toBe(start);
224
+ await expect(toggle).toHaveAccessibleName(new RegExp(next));
225
+ });
226
+
227
+ /**
228
+ * Reset from four months on is a far jump, mounting today's month before the
229
+ * scroller moves. After the month and year wheels close, whose re-assert is a
230
+ * steer that moved nothing, the jump still rests on today's month, the title
231
+ * with it.
232
+ */
233
+ test('after the month and year wheels close, Reset from four months on rests on today’s month', async ({
234
+ page,
235
+ }) => {
236
+ const viewport = await openCalendar(page);
237
+ const toggle = page.getByRole('button', {name: /Choose month and year/});
238
+ const today = await monthAtRest(viewport);
239
+ expect(today).not.toBe('');
240
+ const clickCentre = async (name: string | RegExp) => {
241
+ const box = await page.getByRole('button', {name}).boundingBox();
242
+ if (box == null) {
243
+ throw new Error(`no ${String(name)} button`);
244
+ }
245
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
246
+ };
247
+ for (let step = 0; step < 4; step++) {
248
+ await clickCentre('Next month');
249
+ await waitForRest(viewport);
250
+ }
251
+ expect(await monthAtRest(viewport)).not.toBe(today);
252
+ await clickCentre(/Choose month and year/);
253
+ await expect(toggle).toHaveAttribute('aria-expanded', 'true');
254
+ await clickCentre(/Choose month and year/);
255
+ await expect(toggle).toHaveAttribute('aria-expanded', 'false');
256
+ await waitForRest(viewport);
257
+
258
+ await clickCentre('Reset');
259
+ // Past the scroller's own settle correction too, which ends on a month's
260
+ // boundary.
261
+ await waitForRestOnMonth(viewport);
262
+ expect(await monthAtRest(viewport)).toBe(today);
263
+ await expect(toggle).toHaveAccessibleName(new RegExp(today));
264
+ });
265
+
266
+ test.describe('under a finger', () => {
267
+ test.use({hasTouch: true, isMobile: true});
268
+
269
+ /**
270
+ * A swipe whose last move crosses the middle of the month makes the
271
+ * scroller re-center as the finger lifts. The finger drives that scroll, so
272
+ * the re-center leaves scroll snapping on (turning it off drops the snap
273
+ * target iOS tracks for the gesture, and the swipe coasted past the month),
274
+ * and the swipe comes to rest on the next month.
275
+ */
276
+ test('a swipe that crosses the middle of a month as it lifts keeps snapping on and rests on the next month', async ({
277
+ page,
278
+ }) => {
279
+ const viewport = await openCalendar(page);
280
+ const start = await monthAtRest(viewport);
281
+ const box = await viewport.boundingBox();
282
+ if (box == null) {
283
+ throw new Error('no month scroller');
284
+ }
285
+ // Every change of the scroller's inline scroll-snap-type from here on.
286
+ await viewport.evaluate(element => {
287
+ const changes: string[] = [];
288
+ (window as unknown as {snapChanges: string[]}).snapChanges = changes;
289
+ new MutationObserver(() => {
290
+ changes.push((element as HTMLElement).style.scrollSnapType || 'on');
291
+ }).observe(element, {attributes: true, attributeFilter: ['style']});
292
+ });
293
+ const driver = await touchDriver(page);
294
+ const y = box.y + box.height / 2;
295
+ const from = {x: box.x + box.width * 0.8, y};
296
+ // 55 % of a month toward the next one, the middle crossed by the last
297
+ // move, lifting a quarter frame after it.
298
+ await driver.play(
299
+ drag(from, {x: from.x - box.width * 0.55, y}, {durationMs: 300}),
300
+ );
301
+ await waitForRestOnMonth(viewport);
302
+ const rest = await monthAtRest(viewport);
303
+ expect(rest).not.toBe('');
304
+ expect(rest).not.toBe(start);
305
+ // Snapping was never turned off while the finger drove the scroll.
306
+ expect(
307
+ await page.evaluate(
308
+ () => (window as unknown as {snapChanges: string[]}).snapChanges,
309
+ ),
310
+ ).not.toContain('none');
311
+ });
312
+ });
@@ -18,9 +18,9 @@ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
18
18
  const {prose, component} = generateThemeCSS(theme);
19
19
  return [prose, component].filter(Boolean).join('\n\n');
20
20
  }
21
- // getButton/queryButton instead of getByRole('button', {name}): the closed
22
- // popover keeps a two-month Calendar (~85 role=button nodes) mounted, which
23
- // made every role+name query compute ~85 accessible names through jsdom's
21
+ // getButton/queryButton instead of getByRole('button', {name}): the open
22
+ // popover holds a two-month Calendar (~85 role=button nodes), which makes
23
+ // every role+name query compute ~85 accessible names through jsdom's
24
24
  // slow getComputedStyle (~450ms per query). See fastRoleQueries.ts.
25
25
  import {getButton, queryButton} from '../__tests__/fastRoleQueries';
26
26
  import * as stylex from '@stylexjs/stylex';
@@ -767,6 +767,49 @@ describe('DateRangeInput', () => {
767
767
  });
768
768
  });
769
769
 
770
+ describe('DateRangeInput calendar while the popover is closed', () => {
771
+ const calendarGrid = () => document.querySelector('[role="grid"]');
772
+
773
+ it('renders no calendar until the popover opens, and none after it closes', () => {
774
+ render(
775
+ <DateRangeInput
776
+ label="Range"
777
+ value={{start: '2026-01-05', end: '2026-01-09'}}
778
+ onChange={() => {}}
779
+ />,
780
+ );
781
+ const toggle = getButton('Open calendar');
782
+ expect(calendarGrid()).toBeNull();
783
+
784
+ fireEvent.click(toggle);
785
+ expect(calendarGrid()).not.toBeNull();
786
+
787
+ fireEvent.click(toggle);
788
+ expect(calendarGrid()).toBeNull();
789
+ });
790
+
791
+ it('moves focus into the popover on each open from the toggle', async () => {
792
+ render(
793
+ <DateRangeInput
794
+ label="Range"
795
+ value={{start: '2026-01-05', end: '2026-01-09'}}
796
+ onChange={() => {}}
797
+ />,
798
+ );
799
+ const toggle = getButton('Open calendar');
800
+
801
+ for (let i = 0; i < 2; i++) {
802
+ fireEvent.click(toggle);
803
+ // The first calendar control takes focus, not the dialog surface.
804
+ await waitFor(() => {
805
+ expect(getButton('Previous month')).toHaveFocus();
806
+ });
807
+ fireEvent.click(toggle);
808
+ expect(calendarGrid()).toBeNull();
809
+ }
810
+ });
811
+ });
812
+
770
813
  describe('DateRangeInput statusVariant forwarding', () => {
771
814
  it('defaults to attached (status renders with data-variant="attached")', () => {
772
815
  const {container} = render(
@@ -851,18 +851,22 @@ export function DateRangeInput({
851
851
  })}
852
852
  </div>
853
853
  )}
854
- <Calendar
855
- mode="range"
856
- value={optimisticValue}
857
- onChange={handleRangeSelect}
858
- min={min}
859
- max={max}
860
- dateConstraints={dateConstraints}
861
- maxRangeSpan={maxRangeSpan}
862
- minRangeSpan={minRangeSpan}
863
- numberOfMonths={numberOfMonths}
864
- weekStartsOn={weekStartsOn}
865
- />
854
+ {/* The layer renders its children open or closed, so mount the
855
+ calendar only while it is open. */}
856
+ {popover.isOpen && (
857
+ <Calendar
858
+ mode="range"
859
+ value={optimisticValue}
860
+ onChange={handleRangeSelect}
861
+ min={min}
862
+ max={max}
863
+ dateConstraints={dateConstraints}
864
+ maxRangeSpan={maxRangeSpan}
865
+ minRangeSpan={minRangeSpan}
866
+ numberOfMonths={numberOfMonths}
867
+ weekStartsOn={weekStartsOn}
868
+ />
869
+ )}
866
870
  </div>,
867
871
  {placement: 'below', alignment: 'start'},
868
872
  )}
@@ -3,11 +3,11 @@ schema_version: 3
3
3
  template_version: 7
4
4
  kind: component
5
5
  id: component:DateTimeInput
6
- authority: draft
6
+ authority: current
7
7
  archive_reason: null
8
8
  superseded_by: null
9
- approved_by: null
10
- approved_at: null
9
+ approved_by: cixzhang
10
+ approved_at: 2026-10-30
11
11
  owners: [cixzhang]
12
12
  review_triggers: [public-api, behavior, layout, theming, accessibility]
13
13
  verified_by:
@@ -16,30 +16,37 @@ verified_by:
16
16
  packages/core/src/DateTimeInput/DateTimeInputTouch.test.tsx,
17
17
  packages/core/src/DateTimeInput/NativePickerSegments.test.tsx,
18
18
  packages/core/src/DateTimeInput/Presentation.test.tsx,
19
+ packages/core/src/DateTimeInput/DateTimeInput.fr10.test.tsx,
20
+ packages/core/src/__tests__/BusyOwnerAnnouncement.test.tsx,
19
21
  packages/core/src/theme/themingTargets.test.ts,
20
22
  scripts/check-knowledge.mjs,
21
23
  ]
22
24
  modules: []
23
25
  families: [family:input-fields, family:overlay-dismissal]
24
26
  design_specs: []
25
- architecture: [architecture:component-theming-surface]
27
+ architecture:
28
+ [
29
+ architecture:component-theming-surface,
30
+ architecture:internationalization,
31
+ architecture:public-component-api,
32
+ ]
26
33
  contributing: []
27
- system_specs: [spec:AST-043]
34
+ system_specs: [spec:AST-043, spec:AST-085, spec:AST-086, spec:AST-102]
28
35
  ---
29
36
 
30
37
  # DateTimeInput component contract
31
38
 
32
39
  ## Contract at a glance
33
40
 
34
- | Area | Contract |
35
- | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
36
- | Public contract | One controlled ISO date-time value presented through coordinated date and time segments; `presentation` selects Astryx, native, or adaptive picker surfaces. |
37
- | Behavior | Date and time changes emit one combined value; clear emits `undefined`; controlled empty state remains empty in every rendered picker. |
38
- | End-user impact | People selecting a date and time see one value reflected consistently in the closed field and the active picker, including after clearing. |
39
- | Builder impact | None. |
40
- | Compatibility/readiness | Released defaults and deprecated `nativePicker` behavior remain unchanged. The presentation matrix is governed by current `spec:AST-043`; component-local observations remain draft pending owner review. |
41
- | Review checks | Reject competing picker selection, date/time output that is not one valid combined value, presentation precedence drift, lost per-segment fallback, or broken segment accessibility. |
42
- | Governing rules | `spec:AST-043`, `family:input-fields`, `family:overlay-dismissal`, and objective platform accessibility standards. |
41
+ | Area | Contract |
42
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
43
+ | Public contract | One controlled ISO date-time value presented through coordinated date and time segments; `presentation` selects Astryx, native, or adaptive picker surfaces. |
44
+ | Behavior | Date and time changes emit one combined value; clear emits `undefined`; controlled empty state remains empty in every rendered picker. |
45
+ | End-user impact | People selecting a date and time see one value reflected consistently in the closed field and the active picker, including after clearing. |
46
+ | Builder impact | None. |
47
+ | Compatibility/readiness | Records shipped behavior; no API, default, or DOM change. The presentation matrix and deprecated `nativePicker` mapping follow `spec:AST-043`. Fixed segment height, locale time text and parsing, busy paint, and caller handlers while disabled are listed under Current-state impact. |
48
+ | Review checks | Reject competing picker selection, date/time output that is not one valid combined value, presentation precedence drift, lost per-segment fallback, or broken segment accessibility. |
49
+ | Governing rules | `spec:AST-043`, `family:input-fields`, `family:overlay-dismissal`, `spec:AST-085`, `spec:AST-086` FR8, and objective platform accessibility standards. |
43
50
 
44
51
  This table is a review projection; the body below is authoritative.
45
52
 
@@ -71,13 +78,16 @@ Consumer migration instructions belong in consumer docs and release notes.
71
78
  - Applying component-local date-time bounds, date constraints, time precision,
72
79
  typed-time stepping, and optional preset-time behavior.
73
80
  - Emitting `onChange` before an optional optimistic `changeAction`.
81
+ - The disabled-reason Tooltip composition, which `family:input-fields` assigns
82
+ to the member.
74
83
 
75
84
  **Does not own / non-goals**
76
85
 
77
- - Field label, description, status, disabled-reason, and clear-button presentation —
78
- owned by `component:Field` and `component:FieldStatus`.
86
+ - Field label, description, status, and clear-button presentation —
87
+ owned by Field under `family:input-fields` (shared owner section) and
88
+ `component:FieldStatus`.
79
89
  - Calendar-grid rendering and date-cell interaction — owned by
80
- `component:Calendar`.
90
+ Calendar.
81
91
  - Popover and sheet hosting or dismissal — owned by the composed layer components
82
92
  and `family:overlay-dismissal`.
83
93
  - Cross-component picker-presentation vocabulary and deprecated-prop mapping —
@@ -95,18 +105,17 @@ Consumer migration instructions belong in consumer docs and release notes.
95
105
 
96
106
  ## Behavioral and layout contract
97
107
 
98
- Draft requirements identify their basis so observed code is not mistaken for an
99
- intentional decision.
100
-
101
- | ID | Candidate invariant | Basis | Draft review state |
102
- | --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------ |
103
- | FR1 | DateTimeInput MUST reflect one controlled date-time value across its visible segments and active picker, and MUST NOT reveal a competing selected date after the parent accepts a clear. | Public docs, controlled component seam, current source, focused tests | Verified observation; owner approval pending |
104
- | FR2 | Every committed date or time change MUST emit one valid combined ISO date-time value; clearing MUST emit `undefined`; changing time while no date is committed MUST emit nothing. | Public types, docs, current source, focused tests | Verified observation; owner approval pending |
105
- | FR3 | `presentation`, deprecated `nativePicker`, precedence, pointer adaptation, forced surfaces, and per-segment native fallbacks MUST follow `spec:AST-043` FR1–FR4. | `spec:AST-043` | Inherited current authority |
106
- | FR4 | When `changeAction` is present, every value-change path MUST call `onChange` first, show the proposed controlled value optimistically, and contribute to one busy state until the controlled value accepts or replaces it. | `family:input-fields/FR6` | Inherited current authority |
107
- | FR5 | The two pointer segments share one row while each retains its intrinsic basis; when the container is narrower, flex wrapping moves them into separate full-width rows. | Public docs, current source, focused layout tests | Verify rendered geometry; class-level behavior covered |
108
- | FR6 | Date-time bounds and date constraints MUST prevent invalid commits. Native controls MAY receive platform hints, but JavaScript validation remains the final commit boundary; calendar-only options MAY be unavailable on native surfaces. | Public docs, current source, focused presentation tests | Verified observation; owner approval pending |
109
- | FR7 | A preset-time listbox MUST remain optional, preserve typed entry, expose one selected option independently from keyboard highlight, and limit offered values to the selected date's effective minimum and maximum. | Public docs, current source, focused interaction tests | Verified observation; owner approval pending |
108
+ | ID | Invariant | Basis |
109
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
110
+ | FR1 | DateTimeInput MUST reflect one controlled date-time value across its visible segments and active picker, and MUST NOT reveal a competing selected date after the parent accepts a clear. | Public docs, controlled component seam, current source, focused tests |
111
+ | FR2 | Every committed date or time change MUST emit one valid combined ISO date-time value; clearing MUST emit `undefined`; changing time while no date is committed MUST emit nothing. | Public types, docs, current source, focused tests |
112
+ | FR3 | `presentation`, deprecated `nativePicker`, precedence, pointer adaptation, forced surfaces, and per-segment native fallbacks MUST follow `spec:AST-043` FR1–FR4. | `spec:AST-043` |
113
+ | FR4 | When `changeAction` is present, every value-change path MUST call `onChange` first, show the proposed controlled value optimistically, and contribute to one busy state until the controlled value accepts or replaces it. | `family:input-fields/FR6` |
114
+ | FR5 | The two pointer segments share one row while each retains its intrinsic basis; when the container is narrower, flex wrapping moves them into separate full-width rows. Each segment is the shared element height for its size, as a minimum that grows with a set text size under `spec:AST-102` FR4. Implementation gap: the height is fixed at that size. | Public docs, current source, focused layout tests; `spec:AST-102` FR4 |
115
+ | FR6 | Date-time bounds and date constraints MUST prevent invalid commits. Native controls MAY receive platform hints, but JavaScript validation remains the final commit boundary; calendar-only options MAY be unavailable on native surfaces. | Public docs, current source, focused presentation tests |
116
+ | FR7 | A preset-time listbox MUST remain optional, preserve typed entry, expose one selected option independently from keyboard highlight, and limit offered values to the selected date's effective minimum and maximum. Time text and typed times MUST follow the active locale's digits, separator, and day-period words (`architecture:internationalization` INV1a, INV8). Implementation gap: time text, preset options, and the stepped-time announcement use ASCII digits, a `:` separator, and a fixed English "AM"/"PM", and the typed time parser takes no locale. | Public docs, current source, focused interaction tests; `architecture:internationalization` INV1a, INV8 |
117
+ | FR8 | Base props and `ref` MUST reach the date segment's input; a caller `aria-describedby` joins the component's own descriptions, and a caller DOM handler runs after the field's own handler. `container*` reaches the Field box and `control*` the segment row; root `xstyle`, `className`, and `style` are not accepted. | `family:input-fields` FR10; `DateTimeInput.fr10.test.tsx` |
118
+ | FR9 | A disabled field MUST keep both segments focusable with `aria-disabled` and no native `disabled`, block every value-changing path, keep the calendar button's tab stop, and paint the keyboard focus ring. Explicit `isLoading` makes every surface inert the same way; a pending `changeAction` does so only on the popover surface, and the bottom sheet and any field with a native date segment keep accepting edits. The busy start is announced once. Implementation gap: a caller `onKeyDown` and other DOM handlers on the date segment still run while disabled (`spec:AST-085` FR2). Implementation gap: a busy field that is not disabled takes the disabled paint (half opacity), on every surface while `isLoading` and on the popover while a `changeAction` is pending, against `spec:AST-085` FR6. | `spec:AST-085` FR1, FR2, FR6; `spec:AST-086` FR8; current source and tests |
110
119
 
111
120
  ### Allowed variation
112
121
 
@@ -119,13 +128,13 @@ intentional decision.
119
128
 
120
129
  ### Representative states
121
130
 
122
- | State | Required invariant | Allowed variation |
123
- | --------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
124
- | Empty | Both segments and every active picker expose no committed selection. | Placeholder and selected surface. |
125
- | Date-time value | Date segment, time segment, and active picker reflect the same committed value. | Locale, hour format, and time precision. |
126
- | Cleared after picker change | Closed field is empty and reopening the picker reveals no selected date or retained committed time. | Picker focus date may remain navigational. |
127
- | Busy | Proposed value remains visible; mutation paths are unavailable; both editable segments expose busy. | Explicit loading or pending action source. |
128
- | Focusable disabled | Both segments remain focusable enough to expose the reason while every value-changing path stays blocked. | Disabled-reason text. |
131
+ | State | Required invariant | Allowed variation |
132
+ | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
133
+ | Empty | Both segments and every active picker expose no committed selection. | Placeholder and selected surface. |
134
+ | Date-time value | Date segment, time segment, and active picker reflect the same committed value. | Locale, hour format, and time precision. |
135
+ | Cleared after picker change | Closed field is empty and reopening the picker reveals no selected date or retained committed time. | Picker focus date may remain navigational. |
136
+ | Busy | Proposed value remains visible; both editable segments expose busy. With explicit `isLoading`, mutation paths are unavailable on every surface; with a pending `changeAction`, only on the popover surface. | Explicit loading or pending action source. |
137
+ | Focusable disabled | Both segments stay focusable with `aria-disabled` while every value-changing path stays blocked. | Disabled-reason text. |
129
138
 
130
139
  ### Transformation and precedence order
131
140
 
@@ -220,24 +229,38 @@ leading clock glyph uses `date-time-input-clock-icon`.
220
229
 
221
230
  ## Verification map
222
231
 
223
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
224
- | ------------------- | --------------------------------------------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- |
225
- | FR1–FR2 | `DateTimeInput.test.tsx` | empty, calendar selection, typed date/time, clear | A stale Calendar selection, partial output, or invalid mutation makes a public-state test fail. | `audit:DateTimeInput/behavior` |
226
- | FR3, FR6 | `Presentation.test.tsx`, `NativePickerSegments.test.tsx`, `DateTimeInputTouch.test.tsx` | forced, adaptive, legacy, native fallback, constrained commit | A substituted surface, precedence drift, or invalid commit makes a presentation test fail. | `audit:DateTimeInput/surfaces` |
227
- | FR4, AR4 | `DateTimeInput.test.tsx`, `DateTimeInputTouch.test.tsx` | pending, loading, disabled reason | Mutation during busy/disabled state or stale optimistic output makes a state test fail. | `audit:DateTimeInput/state` |
228
- | FR5 | `DateTimeInput.test.tsx` | intrinsic row, narrow-container wrap classes | Removing the intrinsic basis or wrap styles makes the class-level layout assertions fail; rendered geometry remains review evidence. | `audit:DateTimeInput/layout` |
229
- | FR7, AR1–AR3 | `DateTimeInput.test.tsx`, `DateTimeInputTouch.test.tsx` | popovers, time options, keyboard/focus | Wrong role, name, state, keyboard behavior, or focus behavior makes a focused interaction test fail. | `audit:DateTimeInput/interaction` |
230
- | Theming anatomy map | `scripts/check-knowledge.mjs`, `themingTargets.test.ts` | root, segments, icons, listbox, options | Missing, undocumented, or wrongly placed targets fail knowledge or target checks. | `audit:DateTimeInput/theming` |
232
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
233
+ | ------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
234
+ | FR1–FR2 | `DateTimeInput.test.tsx` | empty, calendar selection, typed date/time, clear | A stale Calendar selection, partial output, or invalid mutation makes a public-state test fail. | `audit:DateTimeInput/behavior` |
235
+ | FR3, FR6 | `Presentation.test.tsx`, `NativePickerSegments.test.tsx`, `DateTimeInputTouch.test.tsx` | forced, adaptive, legacy, native fallback, constrained commit | A substituted surface, precedence drift, or invalid commit makes a presentation test fail. | `audit:DateTimeInput/surfaces` |
236
+ | FR4, FR9, AR4 | `DateTimeInput.test.tsx`, `DateTimeInputTouch.test.tsx`; `BusyOwnerAnnouncement.test.tsx` DateTimeInput case | pending, loading, disabled reason | Mutation during busy/disabled state or stale optimistic output makes a state test fail. Evidence gap: no test asserts that caller handlers still run while disabled. | `audit:DateTimeInput/state` |
237
+ | FR5 | `DateTimeInput.test.tsx` | intrinsic row, narrow-container wrap classes | Removing the intrinsic basis or wrap styles makes the class-level layout assertions fail; rendered geometry remains review evidence. Evidence gap: no test asserts segment heights or text growth. | `audit:DateTimeInput/layout` |
238
+ | FR7, AR1–AR3 | `DateTimeInput.test.tsx`, `DateTimeInputTouch.test.tsx` | popovers, time options, keyboard/focus | Wrong role, name, state, keyboard behavior, or focus behavior makes a focused interaction test fail. Evidence gap: no test asserts locale-specific time text or parsing. | `audit:DateTimeInput/interaction` |
239
+ | FR8 | `DateTimeInput.fr10.test.tsx` | each surface; caller id, describedby, onClick, onKeyDown | A base prop off the date input, a dropped caller description, or styling on the wrong part fails. | `audit:DateTimeInput/api` |
240
+ | Theming anatomy map | `scripts/check-knowledge.mjs`, `themingTargets.test.ts` | root, segments, icons, listbox, options | Missing, undocumented, or wrongly placed targets fail knowledge or target checks. | `audit:DateTimeInput/theming` |
241
+
242
+ ## Current-state impact
243
+
244
+ - **Fixed height (FR5).** Segments hold the fixed shared element height instead
245
+ of growing with a set text size, against `spec:AST-102` FR4.
246
+ - **Time text and parsing (FR7).** Time text, preset options, and the stepped
247
+ announcement use ASCII digits, a `:` separator, and English "AM"/"PM", and
248
+ typed times parse without the locale, against
249
+ `architecture:internationalization` INV1a and INV8.
250
+ - **Busy paint (FR9).** A busy field that is not disabled takes the disabled
251
+ half-opacity paint, against `spec:AST-085` FR6.
252
+ - **Caller handlers while disabled (FR9).** Caller DOM handlers on the date
253
+ segment, such as `onKeyDown`, still run while disabled, against
254
+ `spec:AST-085` FR2.
231
255
 
232
256
  ## Decision log
233
257
 
234
- No component-local decision is asserted by this draft. Current cross-component
235
- picker decisions remain in `spec:AST-043`.
258
+ No component-local decision. Cross-component picker decisions live in
259
+ `spec:AST-043`.
236
260
 
237
261
  ## Open questions
238
262
 
239
- None. Owner review may accept, amend, or reject the observational component-local
240
- rows without changing inherited current authority.
263
+ None.
241
264
 
242
265
  ## Content boundary
243
266