@astryxdesign/core 0.5.0 → 0.5.1-canary.046f6ea

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 (622) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  3. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  4. package/dist/AlertDialog/AlertDialog.js +45 -20
  5. package/dist/AspectRatio/AspectRatio.d.ts +6 -6
  6. package/dist/Avatar/Avatar.d.ts +25 -1
  7. package/dist/Avatar/Avatar.d.ts.map +1 -1
  8. package/dist/Avatar/Avatar.js +43 -9
  9. package/dist/Avatar/index.d.ts +2 -2
  10. package/dist/Avatar/index.d.ts.map +1 -1
  11. package/dist/Avatar/index.js +1 -1
  12. package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
  13. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroup.js +5 -2
  15. package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
  16. package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
  17. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  18. package/dist/AvatarGroup/AvatarGroupOverflow.js +11 -7
  19. package/dist/Badge/Badge.d.ts.map +1 -1
  20. package/dist/Badge/Badge.js +26 -2
  21. package/dist/Banner/Banner.d.ts +10 -1
  22. package/dist/Banner/Banner.d.ts.map +1 -1
  23. package/dist/Banner/Banner.js +11 -2
  24. package/dist/Button/Button.d.ts.map +1 -1
  25. package/dist/Button/Button.js +8 -7
  26. package/dist/Calendar/Calendar.d.ts +4 -2
  27. package/dist/Calendar/Calendar.d.ts.map +1 -1
  28. package/dist/Calendar/Calendar.js +15 -21
  29. package/dist/Calendar/hooks/useCalendarDays.js +4 -6
  30. package/dist/Calendar/hooks/useCalendarNavigation.js +4 -6
  31. package/dist/Calendar/styles.d.ts +5 -20
  32. package/dist/Calendar/styles.d.ts.map +1 -1
  33. package/dist/Calendar/styles.js +15 -17
  34. package/dist/Card/Card.d.ts +12 -12
  35. package/dist/Card/Card.d.ts.map +1 -1
  36. package/dist/Card/Card.js +17 -9
  37. package/dist/Card/index.d.ts +35 -1
  38. package/dist/Card/index.d.ts.map +1 -1
  39. package/dist/Card/index.js +21 -1
  40. package/dist/Carousel/Carousel.d.ts.map +1 -1
  41. package/dist/Carousel/Carousel.js +108 -7
  42. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  43. package/dist/CheckboxInput/CheckboxInput.js +7 -1
  44. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  45. package/dist/CodeBlock/CodeBlock.js +15 -4
  46. package/dist/CodeBlock/highlightRanges.js +1 -1
  47. package/dist/CodeBlock/highlightStyles.js +13 -13
  48. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  49. package/dist/CommandPalette/CommandPaletteItem.js +2 -7
  50. package/dist/ComplexSelector/ComplexSelector.d.ts +10 -2
  51. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  52. package/dist/ComplexSelector/ComplexSelector.js +14 -7
  53. package/dist/DateInput/DateInput.d.ts +43 -0
  54. package/dist/DateInput/DateInput.d.ts.map +1 -1
  55. package/dist/DateInput/DateInput.js +22 -5
  56. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  57. package/dist/DateInput/MonthScroller.js +5 -3
  58. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -1
  59. package/dist/DateInput/MonthYearWheels.js +6 -4
  60. package/dist/DateInput/NativeDateField.d.ts +10 -0
  61. package/dist/DateInput/NativeDateField.d.ts.map +1 -0
  62. package/dist/DateInput/NativeDateField.js +494 -0
  63. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  64. package/dist/DateInput/TouchDateField.js +52 -25
  65. package/dist/DateInput/Wheel.d.ts.map +1 -1
  66. package/dist/DateInput/Wheel.js +1 -0
  67. package/dist/DateInput/index.d.ts +1 -1
  68. package/dist/DateInput/index.d.ts.map +1 -1
  69. package/dist/DateInput/nativeDateSegments.d.ts +22 -0
  70. package/dist/DateInput/nativeDateSegments.d.ts.map +1 -0
  71. package/dist/DateInput/nativeDateSegments.js +111 -0
  72. package/dist/DateRangeInput/DateRangeInput.d.ts +4 -2
  73. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  74. package/dist/DateRangeInput/DateRangeInput.js +3 -5
  75. package/dist/DateTimeInput/DateTimeInput.d.ts +23 -24
  76. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  77. package/dist/DateTimeInput/DateTimeInput.js +35 -10
  78. package/dist/DateTimeInput/TouchDateTimeField.d.ts +6 -0
  79. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -0
  80. package/dist/DateTimeInput/TouchDateTimeField.js +1021 -0
  81. package/dist/Dialog/Dialog.d.ts +16 -2
  82. package/dist/Dialog/Dialog.d.ts.map +1 -1
  83. package/dist/Dialog/Dialog.js +28 -7
  84. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  85. package/dist/DropdownMenu/DropdownMenu.js +4 -13
  86. package/dist/Field/FieldLabel.d.ts.map +1 -1
  87. package/dist/Field/FieldLabel.js +9 -1
  88. package/dist/Field/InputClearButton.d.ts +9 -3
  89. package/dist/Field/InputClearButton.d.ts.map +1 -1
  90. package/dist/Field/InputClearButton.js +14 -3
  91. package/dist/Heading/Heading.d.ts.map +1 -1
  92. package/dist/Heading/Heading.js +0 -1
  93. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  94. package/dist/HoverCard/HoverCard.js +104 -3
  95. package/dist/HoverCard/useHoverCard.d.ts +14 -2
  96. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  97. package/dist/HoverCard/useHoverCard.js +7 -1
  98. package/dist/Icon/Icon.d.ts +4 -2
  99. package/dist/Icon/Icon.d.ts.map +1 -1
  100. package/dist/Icon/defaultIcons.d.ts +3 -2
  101. package/dist/Icon/defaultIcons.d.ts.map +1 -1
  102. package/dist/Icon/defaultIcons.js +7 -0
  103. package/dist/Icon/globalIconRegistry.d.ts +13 -0
  104. package/dist/Icon/globalIconRegistry.d.ts.map +1 -1
  105. package/dist/Icon/globalIconRegistry.js +34 -6
  106. package/dist/Icon/index.d.ts +1 -1
  107. package/dist/Icon/index.d.ts.map +1 -1
  108. package/dist/Icon/useIcon.d.ts +5 -2
  109. package/dist/Icon/useIcon.d.ts.map +1 -1
  110. package/dist/Icon/useIcon.js +3 -0
  111. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  112. package/dist/Indicator/CheckboxIndicator.js +6 -2
  113. package/dist/Item/Item.d.ts.map +1 -1
  114. package/dist/Item/Item.js +2 -2
  115. package/dist/Kbd/Kbd.d.ts.map +1 -1
  116. package/dist/Kbd/Kbd.js +2 -26
  117. package/dist/Layer/gestureCounter.d.ts +8 -0
  118. package/dist/Layer/gestureCounter.d.ts.map +1 -0
  119. package/dist/Layer/gestureCounter.js +53 -0
  120. package/dist/Layer/useLayer.d.ts +23 -13
  121. package/dist/Layer/useLayer.d.ts.map +1 -1
  122. package/dist/Layer/useLayer.js +146 -24
  123. package/dist/List/List.d.ts +1 -1
  124. package/dist/List/List.d.ts.map +1 -1
  125. package/dist/List/List.js +15 -4
  126. package/dist/Markdown/Markdown.d.ts +1 -1
  127. package/dist/Markdown/Markdown.d.ts.map +1 -1
  128. package/dist/Markdown/Markdown.js +20 -7
  129. package/dist/Markdown/parser.d.ts.map +1 -1
  130. package/dist/Markdown/parser.js +5 -3
  131. package/dist/MetadataList/MetadataListItem.d.ts +1 -1
  132. package/dist/MetadataList/MetadataListItem.d.ts.map +1 -1
  133. package/dist/MetadataList/MetadataListItem.js +4 -1
  134. package/dist/MultiSelector/MultiSelector.d.ts +18 -1
  135. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  136. package/dist/MultiSelector/MultiSelector.js +58 -11
  137. package/dist/MultiSelector/hooks.d.ts +7 -1
  138. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  139. package/dist/MultiSelector/hooks.js +3 -2
  140. package/dist/NavIcon/NavIcon.d.ts.map +1 -1
  141. package/dist/NavIcon/NavIcon.js +5 -1
  142. package/dist/NavItem/navItemStyles.stylex.d.ts +0 -8
  143. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  144. package/dist/NavItem/navItemStyles.stylex.js +0 -2
  145. package/dist/NavMenu/NavHeadingMenu.d.ts +1 -1
  146. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  147. package/dist/NavMenu/NavHeadingMenu.js +7 -4
  148. package/dist/NumberInput/NumberInput.d.ts +6 -1
  149. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  150. package/dist/NumberInput/NumberInput.js +95 -126
  151. package/dist/NumberInput/numberInputCommit.d.ts +34 -0
  152. package/dist/NumberInput/numberInputCommit.d.ts.map +1 -0
  153. package/dist/NumberInput/numberInputCommit.js +71 -0
  154. package/dist/NumberInput/numberParser.d.ts +64 -0
  155. package/dist/NumberInput/numberParser.d.ts.map +1 -0
  156. package/dist/NumberInput/numberParser.js +350 -0
  157. package/dist/Outline/Outline.d.ts.map +1 -1
  158. package/dist/Outline/Outline.js +2 -3
  159. package/dist/OverflowList/OverflowList.d.ts +41 -1
  160. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  161. package/dist/OverflowList/OverflowList.js +43 -2
  162. package/dist/Popover/Popover.d.ts.map +1 -1
  163. package/dist/Popover/Popover.js +8 -14
  164. package/dist/Popover/usePopover.d.ts +15 -41
  165. package/dist/Popover/usePopover.d.ts.map +1 -1
  166. package/dist/Popover/usePopover.js +26 -7
  167. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchValueEditor.js +91 -18
  169. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  170. package/dist/PowerSearch/formatFilterValue.js +4 -2
  171. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  172. package/dist/ProgressBar/ProgressBar.js +18 -5
  173. package/dist/Resizable/useResizable.d.ts +19 -1
  174. package/dist/Resizable/useResizable.d.ts.map +1 -1
  175. package/dist/Resizable/useResizable.js +104 -38
  176. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  177. package/dist/SelectableCard/SelectableCard.js +9 -0
  178. package/dist/Selector/Selector.d.ts +18 -1
  179. package/dist/Selector/Selector.d.ts.map +1 -1
  180. package/dist/Selector/Selector.js +92 -21
  181. package/dist/Selector/hooks.d.ts +7 -1
  182. package/dist/Selector/hooks.d.ts.map +1 -1
  183. package/dist/Selector/hooks.js +3 -2
  184. package/dist/SideNav/SideNav.d.ts +6 -1
  185. package/dist/SideNav/SideNav.d.ts.map +1 -1
  186. package/dist/SideNav/SideNav.js +57 -27
  187. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  188. package/dist/SideNav/SideNavHeading.js +5 -4
  189. package/dist/SideNav/SideNavItem.d.ts +29 -4
  190. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  191. package/dist/SideNav/SideNavItem.js +75 -20
  192. package/dist/Slider/Slider.d.ts.map +1 -1
  193. package/dist/Slider/Slider.js +34 -2
  194. package/dist/Spinner/Spinner.d.ts +3 -1
  195. package/dist/Spinner/Spinner.d.ts.map +1 -1
  196. package/dist/Spinner/Spinner.js +199 -20
  197. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  198. package/dist/StatusDot/StatusDot.js +6 -1
  199. package/dist/Stepper/Step.d.ts.map +1 -1
  200. package/dist/Stepper/Step.js +7 -6
  201. package/dist/Switch/Switch.d.ts.map +1 -1
  202. package/dist/Switch/Switch.js +5 -1
  203. package/dist/Table/BaseTable.d.ts.map +1 -1
  204. package/dist/Table/BaseTable.js +6 -1
  205. package/dist/Table/Table.d.ts.map +1 -1
  206. package/dist/Table/Table.js +3 -6
  207. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  208. package/dist/Table/plugins/filtering/useTableFiltering.js +73 -4
  209. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts +26 -0
  210. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  211. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +34 -3
  212. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  213. package/dist/Table/plugins/selection/useTableSelection.js +25 -1
  214. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  215. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
  216. package/dist/Text/Text.d.ts.map +1 -1
  217. package/dist/Text/Text.js +0 -1
  218. package/dist/TextArea/TextArea.d.ts.map +1 -1
  219. package/dist/TextArea/TextArea.js +13 -8
  220. package/dist/Timestamp/Timestamp.d.ts +1 -1
  221. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  222. package/dist/Timestamp/Timestamp.js +10 -18
  223. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  224. package/dist/Timestamp/formatInstant.js +10 -5
  225. package/dist/Toast/Toast.d.ts +17 -2
  226. package/dist/Toast/Toast.d.ts.map +1 -1
  227. package/dist/Toast/Toast.js +77 -11
  228. package/dist/Toast/ToastViewport.d.ts +1 -0
  229. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  230. package/dist/Toast/ToastViewport.js +76 -21
  231. package/dist/Toast/index.d.ts +1 -1
  232. package/dist/Toast/index.d.ts.map +1 -1
  233. package/dist/Toast/types.d.ts +38 -0
  234. package/dist/Toast/types.d.ts.map +1 -1
  235. package/dist/Toast/useToastGesture.d.ts +26 -0
  236. package/dist/Toast/useToastGesture.d.ts.map +1 -0
  237. package/dist/Toast/useToastGesture.js +223 -0
  238. package/dist/Token/Token.d.ts +1 -1
  239. package/dist/Token/Token.d.ts.map +1 -1
  240. package/dist/Token/Token.js +23 -12
  241. package/dist/Token/TokenLink.d.ts +1 -1
  242. package/dist/Token/TokenLink.d.ts.map +1 -1
  243. package/dist/Token/TokenLink.js +5 -2
  244. package/dist/Tokenizer/Tokenizer.d.ts +12 -1
  245. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  246. package/dist/Tokenizer/Tokenizer.js +31 -21
  247. package/dist/TopNav/TopNavHeading.js +2 -2
  248. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  249. package/dist/TopNav/TopNavItem.js +3 -3
  250. package/dist/TopNav/TopNavMegaMenu.d.ts +1 -1
  251. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  252. package/dist/TopNav/TopNavMegaMenu.js +30 -9
  253. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  254. package/dist/TopNav/TopNavMegaMenuItem.js +3 -3
  255. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  256. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  257. package/dist/TopNav/TopNavMenu.js +21 -6
  258. package/dist/TreeList/TreeList.d.ts +1 -1
  259. package/dist/TreeList/TreeList.d.ts.map +1 -1
  260. package/dist/TreeList/TreeList.js +7 -2
  261. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  262. package/dist/TreeList/TreeListItem.js +2 -2
  263. package/dist/Typeahead/BaseTypeahead.d.ts +33 -0
  264. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  265. package/dist/Typeahead/BaseTypeahead.js +51 -8
  266. package/dist/Typeahead/Typeahead.d.ts +8 -1
  267. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  268. package/dist/Typeahead/Typeahead.js +2 -0
  269. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  270. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  271. package/dist/Typeahead/TypeaheadItem.js +7 -2
  272. package/dist/astryx.css +87 -22
  273. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  274. package/dist/hooks/useHotkeys.js +3 -25
  275. package/dist/hooks/useOverflow.d.ts.map +1 -1
  276. package/dist/hooks/useOverflow.js +14 -4
  277. package/dist/i18n/InternationalizationContext.d.ts +2 -0
  278. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  279. package/dist/i18n/InternationalizationContext.js +3 -1
  280. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  281. package/dist/i18n/InternationalizationProvider.js +3 -1
  282. package/dist/i18n/resolve.d.ts +1 -1
  283. package/dist/i18n/resolve.d.ts.map +1 -1
  284. package/dist/i18n/resolve.js +37 -38
  285. package/dist/i18n/useTranslator.d.ts.map +1 -1
  286. package/dist/i18n/useTranslator.js +2 -3
  287. package/dist/index.d.ts +1 -1
  288. package/dist/index.d.ts.map +1 -1
  289. package/dist/theme/Theme.d.ts.map +1 -1
  290. package/dist/theme/Theme.js +46 -10
  291. package/dist/theme/defineTheme.d.ts +9 -3
  292. package/dist/theme/defineTheme.d.ts.map +1 -1
  293. package/dist/theme/defineTheme.js +5 -0
  294. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  295. package/dist/theme/derivedVarRegistry.js +24 -4
  296. package/dist/theme/generateThemeRules.d.ts +21 -0
  297. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  298. package/dist/theme/generateThemeRules.js +26 -0
  299. package/dist/theme/syntax/tokens.d.ts +1 -1
  300. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  301. package/dist/theme/syntax/tokens.js +5 -2
  302. package/dist/theme/useTheme.d.ts.map +1 -1
  303. package/dist/theme/useTheme.js +6 -7
  304. package/dist/utils/dateParser.d.ts +5 -8
  305. package/dist/utils/dateParser.d.ts.map +1 -1
  306. package/dist/utils/dateParser.js +10 -11
  307. package/dist/utils/focusOutline.stylex.d.ts +25 -0
  308. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  309. package/dist/utils/focusOutline.stylex.js +15 -0
  310. package/dist/utils/interactionOverlay.stylex.d.ts +26 -0
  311. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -0
  312. package/dist/utils/interactionOverlay.stylex.js +42 -0
  313. package/dist/utils/isApplePlatform.d.ts +18 -0
  314. package/dist/utils/isApplePlatform.d.ts.map +1 -0
  315. package/dist/utils/isApplePlatform.js +37 -0
  316. package/dist/utils/plainDate.d.ts +11 -0
  317. package/dist/utils/plainDate.d.ts.map +1 -1
  318. package/dist/utils/plainDate.js +20 -4
  319. package/locales/af-ZA.json +7 -7
  320. package/locales/ar-SA.json +7 -7
  321. package/locales/ca-ES.json +6 -6
  322. package/locales/cs-CZ.json +7 -7
  323. package/locales/da-DK.json +7 -7
  324. package/locales/de-DE.json +7 -7
  325. package/locales/el-GR.json +7 -7
  326. package/locales/en.json +178 -6
  327. package/locales/es-ES.json +6 -6
  328. package/locales/fi-FI.json +7 -7
  329. package/locales/fr-FR.json +7 -7
  330. package/locales/he-IL.json +7 -7
  331. package/locales/hu-HU.json +7 -7
  332. package/locales/it-IT.json +7 -7
  333. package/locales/ja-JP.json +7 -7
  334. package/locales/ko-KR.json +7 -7
  335. package/locales/nl-NL.json +7 -7
  336. package/locales/no-NO.json +7 -7
  337. package/locales/pl-PL.json +7 -7
  338. package/locales/pseudo.json +130 -1
  339. package/locales/pt-BR.json +7 -7
  340. package/locales/pt-PT.json +7 -7
  341. package/locales/ro-RO.json +7 -7
  342. package/locales/ru-RU.json +7 -7
  343. package/locales/sr-SP.json +7 -7
  344. package/locales/sv-SE.json +7 -7
  345. package/locales/tr-TR.json +7 -7
  346. package/locales/uk-UA.json +7 -7
  347. package/locales/vi-VN.json +7 -7
  348. package/locales/zh-CN.json +7 -7
  349. package/locales/zh-TW.json +7 -7
  350. package/package.json +4 -5
  351. package/src/AlertDialog/AlertDialog.doc.mjs +114 -30
  352. package/src/AlertDialog/AlertDialog.test.tsx +211 -2
  353. package/src/AlertDialog/AlertDialog.tsx +62 -20
  354. package/src/AspectRatio/AspectRatio.doc.mjs +3 -3
  355. package/src/AspectRatio/AspectRatio.tsx +6 -6
  356. package/src/Avatar/Avatar.doc.mjs +12 -2
  357. package/src/Avatar/Avatar.test.tsx +80 -0
  358. package/src/Avatar/Avatar.tsx +64 -14
  359. package/src/Avatar/index.ts +2 -2
  360. package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
  361. package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
  362. package/src/AvatarGroup/AvatarGroup.tsx +12 -4
  363. package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
  364. package/src/AvatarGroup/AvatarGroupOverflow.tsx +12 -12
  365. package/src/Badge/Badge.doc.mjs +3 -2
  366. package/src/Badge/Badge.test.tsx +32 -3
  367. package/src/Badge/Badge.tsx +42 -1
  368. package/src/Banner/Banner.doc.mjs +9 -0
  369. package/src/Banner/Banner.test.tsx +76 -11
  370. package/src/Banner/Banner.tsx +21 -2
  371. package/src/Button/Button.doc.mjs +2 -1
  372. package/src/Button/Button.test.tsx +19 -4
  373. package/src/Button/Button.tsx +9 -29
  374. package/src/ButtonGroup/ButtonGroup.doc.mjs +2 -2
  375. package/src/ButtonGroup/ButtonGroup.test.tsx +13 -0
  376. package/src/Calendar/Calendar.doc.mjs +4 -4
  377. package/src/Calendar/Calendar.test.tsx +112 -0
  378. package/src/Calendar/Calendar.tsx +18 -16
  379. package/src/Calendar/hooks/useCalendarDays.ts +4 -4
  380. package/src/Calendar/hooks/useCalendarNavigation.ts +4 -4
  381. package/src/Calendar/styles.ts +48 -42
  382. package/src/Card/Card.doc.mjs +10 -7
  383. package/src/Card/Card.test.tsx +113 -19
  384. package/src/Card/Card.tsx +67 -89
  385. package/src/Card/index.ts +36 -1
  386. package/src/Carousel/Carousel.doc.mjs +3 -0
  387. package/src/Carousel/Carousel.test.tsx +360 -0
  388. package/src/Carousel/Carousel.tsx +116 -15
  389. package/src/CheckboxInput/CheckboxInput.doc.mjs +3 -1
  390. package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
  391. package/src/CheckboxInput/CheckboxInput.tsx +5 -0
  392. package/src/CheckboxList/CheckboxList.doc.mjs +1 -1
  393. package/src/CodeBlock/CodeBlock.doc.mjs +10 -4
  394. package/src/CodeBlock/CodeBlock.test.tsx +37 -0
  395. package/src/CodeBlock/CodeBlock.tsx +20 -4
  396. package/src/CodeBlock/highlightRanges.ts +1 -1
  397. package/src/CodeBlock/highlightStyles.ts +13 -13
  398. package/src/CommandPalette/CommandPaletteItem.tsx +2 -12
  399. package/src/ComplexSelector/ComplexSelector.doc.mjs +9 -2
  400. package/src/ComplexSelector/ComplexSelector.test.tsx +183 -0
  401. package/src/ComplexSelector/ComplexSelector.tsx +22 -12
  402. package/src/DateInput/DateInput.doc.mjs +17 -1
  403. package/src/DateInput/DateInput.test.tsx +9 -4
  404. package/src/DateInput/DateInput.tsx +54 -3
  405. package/src/DateInput/DateInputTouch.test.tsx +146 -32
  406. package/src/DateInput/MonthScroller.tsx +5 -1
  407. package/src/DateInput/MonthYearWheels.tsx +7 -4
  408. package/src/DateInput/NativeDateField.test.tsx +635 -0
  409. package/src/DateInput/NativeDateField.tsx +641 -0
  410. package/src/DateInput/TouchDateField.tsx +83 -33
  411. package/src/DateInput/Wheel.tsx +1 -0
  412. package/src/DateInput/index.ts +1 -0
  413. package/src/DateInput/nativeDateSegments.ts +127 -0
  414. package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -3
  415. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -0
  416. package/src/DateRangeInput/DateRangeInput.tsx +8 -13
  417. package/src/DateTimeInput/DateTimeInput.doc.mjs +39 -22
  418. package/src/DateTimeInput/DateTimeInput.test.tsx +76 -0
  419. package/src/DateTimeInput/DateTimeInput.tsx +54 -20
  420. package/src/DateTimeInput/DateTimeInputTouch.test.tsx +980 -0
  421. package/src/DateTimeInput/TouchDateTimeField.tsx +1437 -0
  422. package/src/Dialog/Dialog.test.tsx +185 -18
  423. package/src/Dialog/Dialog.tsx +35 -11
  424. package/src/DropdownMenu/DropdownMenu.test.tsx +44 -10
  425. package/src/DropdownMenu/DropdownMenu.tsx +4 -13
  426. package/src/Field/Field.doc.mjs +1 -1
  427. package/src/Field/FieldLabel.test.tsx +29 -0
  428. package/src/Field/FieldLabel.tsx +7 -0
  429. package/src/Field/InputClearButton.test.tsx +15 -1
  430. package/src/Field/InputClearButton.tsx +28 -4
  431. package/src/FieldStatus/FieldStatus.doc.mjs +1 -1
  432. package/src/FileInput/FileInput.doc.mjs +1 -1
  433. package/src/Heading/Heading.tsx +0 -1
  434. package/src/HoverCard/HoverCard.doc.mjs +8 -2
  435. package/src/HoverCard/HoverCard.test.tsx +154 -4
  436. package/src/HoverCard/HoverCard.tsx +137 -3
  437. package/src/HoverCard/useHoverCard.tsx +23 -3
  438. package/src/Icon/Icon.test.tsx +55 -1
  439. package/src/Icon/Icon.tsx +5 -3
  440. package/src/Icon/defaultIcons.tsx +11 -2
  441. package/src/Icon/globalIconRegistry.test.tsx +16 -3
  442. package/src/Icon/globalIconRegistry.tsx +37 -6
  443. package/src/Icon/index.ts +1 -0
  444. package/src/Icon/useIcon.ts +9 -2
  445. package/src/Indicator/CheckboxIndicator.tsx +14 -8
  446. package/src/Indicator/Indicator.doc.mjs +3 -1
  447. package/src/InputGroup/InputGroup.doc.mjs +1 -1
  448. package/src/Item/Item.tsx +2 -7
  449. package/src/Kbd/Kbd.test.tsx +0 -17
  450. package/src/Kbd/Kbd.tsx +2 -26
  451. package/src/Layer/gestureCounter.ts +57 -0
  452. package/src/Layer/useLayer.test.tsx +368 -16
  453. package/src/Layer/useLayer.tsx +208 -35
  454. package/src/Layout/__tests__/edgeCompensation.test.tsx +1 -1
  455. package/src/List/List.test.tsx +38 -0
  456. package/src/List/List.tsx +8 -1
  457. package/src/Markdown/Markdown.test.tsx +37 -0
  458. package/src/Markdown/Markdown.tsx +16 -5
  459. package/src/Markdown/parser.test.ts +33 -0
  460. package/src/Markdown/parser.ts +9 -3
  461. package/src/MetadataList/MetadataList.test.tsx +75 -2
  462. package/src/MetadataList/MetadataListItem.tsx +5 -2
  463. package/src/MultiSelector/MultiSelector.doc.mjs +23 -2
  464. package/src/MultiSelector/MultiSelector.test.tsx +205 -0
  465. package/src/MultiSelector/MultiSelector.tsx +103 -16
  466. package/src/MultiSelector/hooks.ts +9 -2
  467. package/src/NavIcon/NavIcon.doc.mjs +8 -2
  468. package/src/NavIcon/NavIcon.test.tsx +9 -0
  469. package/src/NavIcon/NavIcon.tsx +5 -1
  470. package/src/NavItem/navItemStyles.stylex.ts +0 -8
  471. package/src/NavMenu/NavHeadingMenu.test.tsx +30 -0
  472. package/src/NavMenu/NavHeadingMenu.tsx +7 -4
  473. package/src/NumberInput/NumberInput.doc.mjs +27 -8
  474. package/src/NumberInput/NumberInput.test.tsx +563 -8
  475. package/src/NumberInput/NumberInput.tsx +77 -131
  476. package/src/NumberInput/numberInputCommit.test.ts +72 -0
  477. package/src/NumberInput/numberInputCommit.ts +103 -0
  478. package/src/NumberInput/numberParser.docblock.test.ts +234 -0
  479. package/src/NumberInput/numberParser.test.ts +222 -0
  480. package/src/NumberInput/numberParser.ts +403 -0
  481. package/src/Outline/Outline.tsx +2 -4
  482. package/src/OverflowList/OverflowList.doc.mjs +102 -21
  483. package/src/OverflowList/OverflowList.test.tsx +550 -4
  484. package/src/OverflowList/OverflowList.tsx +100 -2
  485. package/src/Pagination/Pagination.test.tsx +3 -2
  486. package/src/Popover/Popover.doc.mjs +2 -2
  487. package/src/Popover/Popover.test.tsx +38 -0
  488. package/src/Popover/Popover.tsx +28 -66
  489. package/src/Popover/usePopover.tsx +44 -5
  490. package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
  491. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +87 -0
  492. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +64 -0
  493. package/src/PowerSearch/PowerSearchValueEditor.tsx +109 -10
  494. package/src/PowerSearch/formatFilterValue.test.ts +16 -1
  495. package/src/PowerSearch/formatFilterValue.ts +2 -0
  496. package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
  497. package/src/ProgressBar/ProgressBar.test.tsx +24 -0
  498. package/src/ProgressBar/ProgressBar.tsx +28 -8
  499. package/src/RadioList/RadioList.doc.mjs +1 -1
  500. package/src/Resizable/Resizable.doc.mjs +2 -2
  501. package/src/Resizable/useResizable.doc.mjs +21 -2
  502. package/src/Resizable/useResizable.test.ts +421 -0
  503. package/src/Resizable/useResizable.ts +133 -48
  504. package/src/SelectableCard/SelectableCard.doc.mjs +3 -0
  505. package/src/SelectableCard/SelectableCard.tsx +16 -0
  506. package/src/Selector/Selector.doc.mjs +15 -1
  507. package/src/Selector/Selector.source-build.test.mjs +65 -0
  508. package/src/Selector/Selector.test.tsx +301 -0
  509. package/src/Selector/Selector.tsx +145 -32
  510. package/src/Selector/hooks.ts +17 -2
  511. package/src/SideNav/SideNav.doc.mjs +2 -2
  512. package/src/SideNav/SideNav.test.tsx +1069 -6
  513. package/src/SideNav/SideNav.tsx +109 -38
  514. package/src/SideNav/SideNavHeading.tsx +5 -2
  515. package/src/SideNav/SideNavItem.doc.mjs +24 -3
  516. package/src/SideNav/SideNavItem.tsx +134 -44
  517. package/src/Slider/Slider.doc.mjs +2 -2
  518. package/src/Slider/Slider.test.tsx +106 -1
  519. package/src/Slider/Slider.tsx +52 -1
  520. package/src/Spinner/Spinner.doc.mjs +17 -5
  521. package/src/Spinner/Spinner.test.tsx +164 -0
  522. package/src/Spinner/Spinner.tsx +252 -22
  523. package/src/StatusDot/StatusDot.doc.mjs +8 -2
  524. package/src/StatusDot/StatusDot.test.tsx +9 -0
  525. package/src/StatusDot/StatusDot.tsx +7 -1
  526. package/src/Stepper/Step.doc.mjs +1 -1
  527. package/src/Stepper/Step.tsx +7 -14
  528. package/src/Switch/Switch.doc.mjs +3 -1
  529. package/src/Switch/Switch.test.tsx +10 -0
  530. package/src/Switch/Switch.tsx +3 -0
  531. package/src/Table/BaseTable.tsx +6 -1
  532. package/src/Table/Table.doc.mjs +5 -3
  533. package/src/Table/Table.tsx +3 -8
  534. package/src/Table/TableBody.doc.mjs +2 -2
  535. package/src/Table/TableFooter.doc.mjs +1 -1
  536. package/src/Table/TableHeader.doc.mjs +1 -1
  537. package/src/Table/plugins/filtering/useTableFiltering.test.tsx +44 -6
  538. package/src/Table/plugins/filtering/useTableFiltering.tsx +100 -4
  539. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +50 -4
  540. package/src/Table/plugins/selection/useTableSelection.test.tsx +62 -0
  541. package/src/Table/plugins/selection/useTableSelection.tsx +25 -2
  542. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
  543. package/src/Table/useTableGroupedRows.doc.mjs +3 -3
  544. package/src/Table/useTablePagination.doc.mjs +1 -1
  545. package/src/Table/useTableSelection.doc.mjs +1 -1
  546. package/src/Text/Text.test.tsx +44 -0
  547. package/src/Text/Text.tsx +0 -1
  548. package/src/TextArea/TextArea.doc.mjs +13 -3
  549. package/src/TextArea/TextArea.test.tsx +11 -0
  550. package/src/TextArea/TextArea.tsx +34 -22
  551. package/src/TextInput/TextInput.doc.mjs +1 -1
  552. package/src/TimeInput/TimeInput.doc.mjs +1 -1
  553. package/src/Timestamp/Timestamp.test.tsx +89 -0
  554. package/src/Timestamp/Timestamp.tsx +8 -8
  555. package/src/Timestamp/formatInstant.ts +5 -0
  556. package/src/Toast/Toast.doc.mjs +213 -44
  557. package/src/Toast/Toast.tsx +118 -23
  558. package/src/Toast/ToastViewport.test.tsx +1434 -8
  559. package/src/Toast/ToastViewport.tsx +85 -14
  560. package/src/Toast/index.ts +2 -0
  561. package/src/Toast/types.ts +43 -5
  562. package/src/Toast/useToastGesture.ts +361 -0
  563. package/src/Token/Token.test.tsx +95 -0
  564. package/src/Token/Token.tsx +19 -17
  565. package/src/Token/TokenLink.tsx +12 -3
  566. package/src/Tokenizer/Tokenizer.doc.mjs +17 -4
  567. package/src/Tokenizer/Tokenizer.test.tsx +161 -0
  568. package/src/Tokenizer/Tokenizer.tsx +49 -24
  569. package/src/TopNav/TopNavHeading.tsx +2 -2
  570. package/src/TopNav/TopNavItem.tsx +3 -7
  571. package/src/TopNav/TopNavMegaMenu.test.tsx +104 -0
  572. package/src/TopNav/TopNavMegaMenu.tsx +47 -4
  573. package/src/TopNav/TopNavMegaMenuItem.tsx +10 -9
  574. package/src/TopNav/TopNavMenu.test.tsx +133 -1
  575. package/src/TopNav/TopNavMenu.tsx +38 -5
  576. package/src/TreeList/TreeList.test.tsx +49 -0
  577. package/src/TreeList/TreeList.tsx +7 -2
  578. package/src/TreeList/TreeListItem.tsx +2 -7
  579. package/src/Typeahead/BaseTypeahead.doc.mjs +13 -0
  580. package/src/Typeahead/BaseTypeahead.tsx +96 -6
  581. package/src/Typeahead/Typeahead.doc.mjs +7 -1
  582. package/src/Typeahead/Typeahead.test.tsx +154 -0
  583. package/src/Typeahead/Typeahead.tsx +9 -0
  584. package/src/Typeahead/TypeaheadItem.test.tsx +43 -0
  585. package/src/Typeahead/TypeaheadItem.tsx +8 -1
  586. package/src/hooks/useAnnounce.doc.mjs +96 -0
  587. package/src/hooks/useDevWarning.doc.mjs +96 -0
  588. package/src/hooks/useHotkeys.test.ts +0 -18
  589. package/src/hooks/useHotkeys.ts +3 -25
  590. package/src/hooks/useIndicatorFocusRing.doc.mjs +104 -0
  591. package/src/hooks/useInputStatusIcon.doc.mjs +142 -0
  592. package/src/hooks/useInteractiveRole.doc.mjs +119 -0
  593. package/src/hooks/useLongPress.doc.mjs +123 -0
  594. package/src/hooks/useOverflow.doc.mjs +58 -17
  595. package/src/hooks/useOverflow.ts +16 -4
  596. package/src/hooks/useTypeahead.doc.mjs +153 -0
  597. package/src/i18n/InternationalizationContext.ts +3 -0
  598. package/src/i18n/InternationalizationProvider.tsx +8 -1
  599. package/src/i18n/__tests__/resolve.test.ts +23 -1
  600. package/src/i18n/resolve.ts +46 -49
  601. package/src/i18n/useTranslator.ts +2 -7
  602. package/src/index.ts +2 -0
  603. package/src/raw-import.d.ts +14 -0
  604. package/src/theme/Theme.test.tsx +104 -2
  605. package/src/theme/Theme.tsx +49 -14
  606. package/src/theme/defineTheme.test.ts +23 -0
  607. package/src/theme/defineTheme.ts +12 -3
  608. package/src/theme/derivedVarRegistry.test.ts +87 -3
  609. package/src/theme/derivedVarRegistry.ts +23 -4
  610. package/src/theme/generateThemeRules.test.ts +99 -1
  611. package/src/theme/generateThemeRules.ts +28 -0
  612. package/src/theme/syntax/tokens.ts +5 -2
  613. package/src/theme/useTheme.test.tsx +10 -0
  614. package/src/theme/useTheme.ts +16 -10
  615. package/src/utils/dateParser.test.ts +34 -2
  616. package/src/utils/dateParser.ts +11 -14
  617. package/src/utils/focusOutline.stylex.ts +50 -0
  618. package/src/utils/interactionOverlay.stylex.ts +74 -0
  619. package/src/utils/isApplePlatform.test.ts +61 -0
  620. package/src/utils/isApplePlatform.ts +37 -0
  621. package/src/utils/plainDate.test.ts +46 -0
  622. package/src/utils/plainDate.ts +18 -3
@@ -0,0 +1,980 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file DateTimeInputTouch.test.tsx
5
+ * @input Uses vitest, @testing-library/react, DateTimeInput touch surface
6
+ * @output Focused coverage for DateTimeInput's coarse-pointer bottom-sheet picker
7
+ * @position Test file for /packages/core/src/DateTimeInput/
8
+ *
9
+ * The touch surface reuses DateInput's sheet/calendar primitives, so this file
10
+ * keeps coverage at the DateTimeInput integration seams: surface selection,
11
+ * segmented switching, date/time combination, constraints, and field parity.
12
+ */
13
+
14
+ import {
15
+ describe,
16
+ it,
17
+ expect,
18
+ vi,
19
+ beforeAll,
20
+ afterAll,
21
+ beforeEach,
22
+ afterEach,
23
+ } from 'vitest';
24
+ import {useState} from 'react';
25
+ import * as stylex from '@stylexjs/stylex';
26
+ import {readFileSync} from 'node:fs';
27
+ import {render, screen, fireEvent, within} from '@testing-library/react';
28
+ import {DateTimeInput} from './DateTimeInput';
29
+ import type {ISODateTimeString} from './DateTimeInput';
30
+
31
+ class MockResizeObserver {
32
+ observe() {}
33
+ unobserve() {}
34
+ disconnect() {}
35
+ }
36
+
37
+ const HOVER_CAPABLE = /\(\s*hover\s*:\s*hover\s*\)/;
38
+ const SCROLLPORT_WIDTH = 360;
39
+
40
+ const responsiveLayoutProbe = stylex.create({
41
+ row: {flexWrap: 'wrap'},
42
+ segment: {flexBasis: 196, minInlineSize: 0},
43
+ });
44
+
45
+ function expectResponsiveProbeClasses(
46
+ element: HTMLElement,
47
+ style: (typeof responsiveLayoutProbe)[keyof typeof responsiveLayoutProbe],
48
+ ): void {
49
+ const classes = (stylex.props(style).className ?? '')
50
+ .split(' ')
51
+ .filter(className => className !== '' && !className.includes('__'));
52
+ expect(classes.length).toBeGreaterThan(0);
53
+ for (const className of classes) {
54
+ expect(element).toHaveClass(className);
55
+ }
56
+ }
57
+
58
+ function stubMedia(pointer: 'coarse' | 'fine', anyPointer = pointer): void {
59
+ vi.stubGlobal('matchMedia', (query: string) => ({
60
+ matches: /any-pointer:\s*coarse/.test(query)
61
+ ? anyPointer === 'coarse'
62
+ : /pointer:\s*coarse/.test(query)
63
+ ? pointer === 'coarse'
64
+ : /pointer:\s*fine/.test(query)
65
+ ? pointer === 'fine'
66
+ : HOVER_CAPABLE.test(query),
67
+ media: query,
68
+ onchange: null,
69
+ addListener: () => {},
70
+ removeListener: () => {},
71
+ addEventListener: () => {},
72
+ removeEventListener: () => {},
73
+ dispatchEvent: () => false,
74
+ }));
75
+ }
76
+
77
+ function withMonthLayout<T>(fn: () => T): T {
78
+ Object.defineProperty(HTMLElement.prototype, 'clientWidth', {
79
+ configurable: true,
80
+ get(this: HTMLElement) {
81
+ return this.dataset?.scroller === 'months' ? SCROLLPORT_WIDTH : 0;
82
+ },
83
+ });
84
+ try {
85
+ return fn();
86
+ } finally {
87
+ delete (HTMLElement.prototype as {clientWidth?: unknown}).clientWidth;
88
+ }
89
+ }
90
+
91
+ function dateField(label = 'Meeting'): HTMLElement {
92
+ return screen.getByRole('combobox', {name: label});
93
+ }
94
+
95
+ function timeField(label = 'Meeting time'): HTMLElement {
96
+ return screen.getByRole('combobox', {name: label});
97
+ }
98
+
99
+ function dateSegment(): HTMLElement {
100
+ const segment = document.querySelector(
101
+ '.astryx-date-time-input-date-segment',
102
+ );
103
+ if (!(segment instanceof HTMLElement)) {
104
+ throw new Error('date segment not found');
105
+ }
106
+ return segment;
107
+ }
108
+
109
+ function timeSegment(): HTMLElement {
110
+ const segment = document.querySelector(
111
+ '.astryx-date-time-input-time-segment',
112
+ );
113
+ if (!(segment instanceof HTMLElement)) {
114
+ throw new Error('time segment not found');
115
+ }
116
+ return segment;
117
+ }
118
+
119
+ function openDateSheet(label = 'Meeting'): HTMLElement {
120
+ const field = dateField(label);
121
+ fireEvent.click(field);
122
+ return field;
123
+ }
124
+
125
+ function openTimeSheet(label = 'Meeting time'): HTMLElement {
126
+ const field = timeField(label);
127
+ fireEvent.click(field);
128
+ return field;
129
+ }
130
+
131
+ function switchToTimePanel(): void {
132
+ fireEvent.click(screen.getByRole('radio', {name: 'Time'}));
133
+ }
134
+
135
+ function optionIn(listName: string, optionName: string): HTMLElement {
136
+ return within(screen.getByRole('listbox', {name: listName})).getByRole(
137
+ 'option',
138
+ {name: optionName},
139
+ );
140
+ }
141
+
142
+ function ControlledDateTimeInput({
143
+ initialValue,
144
+ }: {
145
+ initialValue?: ISODateTimeString;
146
+ }) {
147
+ const [value, setValue] = useState<ISODateTimeString | undefined>(
148
+ initialValue,
149
+ );
150
+ return <DateTimeInput label="Meeting" value={value} onChange={setValue} />;
151
+ }
152
+
153
+ beforeAll(() => {
154
+ window.scrollTo = vi.fn();
155
+ Element.prototype.scrollTo = vi.fn();
156
+ Element.prototype.scrollIntoView = vi.fn();
157
+ HTMLDialogElement.prototype.showModal = vi.fn(function (
158
+ this: HTMLDialogElement,
159
+ ) {
160
+ this.open = true;
161
+ });
162
+ HTMLDialogElement.prototype.show = vi.fn(function (this: HTMLDialogElement) {
163
+ this.open = true;
164
+ });
165
+ HTMLDialogElement.prototype.close = vi.fn(function (this: HTMLDialogElement) {
166
+ this.open = false;
167
+ });
168
+ });
169
+
170
+ afterAll(() => {
171
+ vi.useRealTimers();
172
+ });
173
+
174
+ beforeEach(() => {
175
+ vi.setSystemTime(new Date(2026, 2, 15, 9, 30, 45));
176
+ vi.stubGlobal('ResizeObserver', MockResizeObserver);
177
+ stubMedia('coarse');
178
+ });
179
+
180
+ afterEach(() => {
181
+ vi.unstubAllGlobals();
182
+ });
183
+
184
+ describe('DateTimeInput touch surface', () => {
185
+ it('uses the bottom-sheet picker on a coarse pointer', () => {
186
+ withMonthLayout(() => {
187
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
188
+ const field = dateField();
189
+
190
+ expect(field).toHaveAttribute('readonly');
191
+ expect(field).toHaveAttribute('inputmode', 'none');
192
+ expect(field).toHaveAttribute('aria-expanded', 'false');
193
+
194
+ fireEvent.click(field);
195
+ expect(field).toHaveAttribute('aria-expanded', 'true');
196
+ expect(
197
+ screen.getByRole('radiogroup', {name: 'Date/time section'}),
198
+ ).toBeInTheDocument();
199
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
200
+ 'aria-checked',
201
+ 'true',
202
+ );
203
+ });
204
+ });
205
+
206
+ it('uses intrinsic wrapping for the closed touch segments', () => {
207
+ const {container} = render(
208
+ <DateTimeInput label="Meeting" onChange={() => {}} />,
209
+ );
210
+ const row = container.querySelector(
211
+ '.astryx-date-time-input',
212
+ ) as HTMLElement;
213
+ expectResponsiveProbeClasses(row, responsiveLayoutProbe.row);
214
+ expectResponsiveProbeClasses(dateSegment(), responsiveLayoutProbe.segment);
215
+ expectResponsiveProbeClasses(timeSegment(), responsiveLayoutProbe.segment);
216
+ });
217
+
218
+ it('keeps the desktop two-field surface on a fine primary pointer', () => {
219
+ stubMedia('fine', 'coarse');
220
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
221
+
222
+ expect(screen.getByRole('combobox')).not.toHaveAttribute('readonly');
223
+ expect(screen.getByLabelText('Meeting time')).toBeInTheDocument();
224
+ });
225
+
226
+ it('closed touch fields display separate values and placeholders', () => {
227
+ const {rerender} = render(
228
+ <DateTimeInput label="Meeting" onChange={() => {}} />,
229
+ );
230
+ expect(dateField()).toHaveAttribute('placeholder', 'Select a date');
231
+ expect(timeField()).toHaveAttribute('placeholder', 'Select a time');
232
+ expect(dateField()).toHaveValue('');
233
+ expect(timeField()).toHaveValue('');
234
+
235
+ rerender(
236
+ <DateTimeInput
237
+ label="Meeting"
238
+ value={'2026-03-15T14:30' as ISODateTimeString}
239
+ onChange={() => {}}
240
+ />,
241
+ );
242
+ expect(dateField()).toHaveValue('March 15, 2026');
243
+ expect(timeField()).toHaveValue('2:30 PM');
244
+ expect(dateField()).not.toHaveValue('March 15, 2026, 2:30 PM');
245
+ });
246
+
247
+ it('uses a custom timePlaceholder on the touch time segment', () => {
248
+ render(
249
+ <DateTimeInput
250
+ label="Meeting"
251
+ timePlaceholder="Pick a time"
252
+ onChange={() => {}}
253
+ />,
254
+ );
255
+ expect(timeField()).toHaveAttribute('placeholder', 'Pick a time');
256
+ });
257
+
258
+ it('does not wire focus to reopen the sheet after dismissal', () => {
259
+ const source = readFileSync(
260
+ 'packages/core/src/DateTimeInput/TouchDateTimeField.tsx',
261
+ 'utf8',
262
+ );
263
+ expect(source).not.toContain('onFocus=');
264
+ expect(source).not.toContain('handleDateFocus');
265
+ expect(source).not.toContain('handleTimeFocus');
266
+ });
267
+
268
+ it('opens directly to Date from the date segment and calendar icon', () => {
269
+ render(
270
+ <DateTimeInput
271
+ label="Meeting"
272
+ value={'2026-03-15T09:30' as ISODateTimeString}
273
+ onChange={() => {}}
274
+ />,
275
+ );
276
+ fireEvent.click(dateField());
277
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
278
+ 'aria-checked',
279
+ 'true',
280
+ );
281
+ expect(dateField()).toHaveAttribute('aria-expanded', 'true');
282
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
283
+
284
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
285
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
286
+ 'aria-checked',
287
+ 'true',
288
+ );
289
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
290
+ fireEvent.click(screen.getByRole('button', {name: 'Open calendar'}));
291
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
292
+ 'aria-checked',
293
+ 'true',
294
+ );
295
+ });
296
+
297
+ it('opens directly to Time from the time segment and clock affordance with no date', () => {
298
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
299
+ fireEvent.click(timeField());
300
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
301
+ 'aria-checked',
302
+ 'true',
303
+ );
304
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
305
+ expect(timeField()).toHaveAttribute('aria-expanded', 'true');
306
+
307
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
308
+ fireEvent.click(screen.getByRole('button', {name: 'Open Meeting time'}));
309
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
310
+ 'aria-checked',
311
+ 'true',
312
+ );
313
+ });
314
+
315
+ it('opens from date and time segment padding clicks', () => {
316
+ render(
317
+ <DateTimeInput
318
+ label="Meeting"
319
+ value={'2026-03-15T09:30' as ISODateTimeString}
320
+ onChange={() => {}}
321
+ />,
322
+ );
323
+
324
+ fireEvent.click(dateSegment());
325
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
326
+ 'aria-checked',
327
+ 'true',
328
+ );
329
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
330
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
331
+
332
+ fireEvent.click(timeSegment());
333
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
334
+ 'aria-checked',
335
+ 'true',
336
+ );
337
+ });
338
+
339
+ it('keyboard activation opens the corresponding touch panel', () => {
340
+ render(
341
+ <DateTimeInput
342
+ label="Meeting"
343
+ value={'2026-03-15T09:30' as ISODateTimeString}
344
+ onChange={() => {}}
345
+ />,
346
+ );
347
+ fireEvent.keyDown(dateField(), {key: 'Enter'});
348
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
349
+ 'aria-checked',
350
+ 'true',
351
+ );
352
+
353
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
354
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
355
+ fireEvent.keyDown(timeField(), {key: ' '});
356
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
357
+ 'aria-checked',
358
+ 'true',
359
+ );
360
+ });
361
+
362
+ it('switches between inert date and time panels with the pill control', () => {
363
+ withMonthLayout(() => {
364
+ const {container} = render(
365
+ <DateTimeInput label="Meeting" onChange={() => {}} />,
366
+ );
367
+ openDateSheet();
368
+
369
+ const datePanel = container.querySelector(
370
+ '[data-panel="date"]',
371
+ ) as HTMLElement;
372
+ const timePanel = container.querySelector(
373
+ '[data-panel="time"]',
374
+ ) as HTMLElement;
375
+ expect(datePanel).not.toHaveAttribute('aria-hidden');
376
+ expect(timePanel).toHaveAttribute('aria-hidden', 'true');
377
+
378
+ switchToTimePanel();
379
+ expect(datePanel).toHaveAttribute('aria-hidden', 'true');
380
+ expect(timePanel).not.toHaveAttribute('aria-hidden');
381
+ expect(screen.getByRole('radio', {name: 'Time'})).toHaveAttribute(
382
+ 'aria-checked',
383
+ 'true',
384
+ );
385
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
386
+ expect(timeField()).toHaveAttribute('aria-expanded', 'true');
387
+ });
388
+ });
389
+
390
+ it('selects a date with the drafted/default time and leaves the Date panel active', () => {
391
+ const onChange = vi.fn();
392
+ withMonthLayout(() => {
393
+ render(<DateTimeInput label="Meeting" onChange={onChange} />);
394
+ openDateSheet();
395
+
396
+ fireEvent.click(
397
+ screen.getByRole('button', {name: 'Saturday, March 21, 2026'}),
398
+ );
399
+
400
+ expect(onChange).toHaveBeenCalledWith('2026-03-21T09:30');
401
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
402
+ 'aria-checked',
403
+ 'true',
404
+ );
405
+ });
406
+ });
407
+
408
+ it('does not lose a time chosen before the date exists', () => {
409
+ const onChange = vi.fn();
410
+ withMonthLayout(() => {
411
+ render(<DateTimeInput label="Meeting" onChange={onChange} />);
412
+ openTimeSheet();
413
+
414
+ fireEvent.click(optionIn('AM/PM', 'PM'));
415
+ fireEvent.click(optionIn('Hour', '3'));
416
+ fireEvent.click(optionIn('Minute', '45'));
417
+ expect(timeField()).toHaveValue('3:45 PM');
418
+ expect(dateField()).toHaveValue('');
419
+ expect(onChange).not.toHaveBeenCalled();
420
+
421
+ fireEvent.click(screen.getByRole('radio', {name: 'Date'}));
422
+ fireEvent.click(
423
+ screen.getByRole('button', {name: 'Saturday, March 21, 2026'}),
424
+ );
425
+ expect(onChange).toHaveBeenCalledWith('2026-03-21T15:45');
426
+ });
427
+ });
428
+
429
+ it('keeps an uncommitted time draft across Date and Time tab switches', () => {
430
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
431
+ openTimeSheet();
432
+
433
+ fireEvent.click(optionIn('AM/PM', 'PM'));
434
+ fireEvent.click(optionIn('Hour', '3'));
435
+ fireEvent.click(optionIn('Minute', '45'));
436
+ fireEvent.click(screen.getByRole('radio', {name: 'Date'}));
437
+ expect(timeField()).toHaveValue('3:45 PM');
438
+
439
+ switchToTimePanel();
440
+ expect(timeField()).toHaveValue('3:45 PM');
441
+ });
442
+
443
+ it('clears an uncommitted time draft when Time Save closes without a date', () => {
444
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
445
+ openTimeSheet();
446
+
447
+ fireEvent.click(optionIn('AM/PM', 'PM'));
448
+ fireEvent.click(optionIn('Hour', '3'));
449
+ fireEvent.click(optionIn('Minute', '45'));
450
+ expect(timeField()).toHaveValue('3:45 PM');
451
+
452
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
453
+ expect(timeField()).toHaveValue('');
454
+ });
455
+
456
+ it('clears an uncommitted time draft when dismissed with Escape', () => {
457
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
458
+ openTimeSheet();
459
+
460
+ fireEvent.click(optionIn('AM/PM', 'PM'));
461
+ fireEvent.click(optionIn('Hour', '3'));
462
+ fireEvent.click(optionIn('Minute', '45'));
463
+ expect(timeField()).toHaveValue('3:45 PM');
464
+
465
+ fireEvent.keyDown(screen.getByRole('dialog'), {key: 'Escape'});
466
+ expect(timeField()).toHaveValue('');
467
+ });
468
+
469
+ it('clears an uncommitted time draft when dismissed from the scrim', () => {
470
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
471
+ openTimeSheet();
472
+
473
+ fireEvent.click(optionIn('AM/PM', 'PM'));
474
+ fireEvent.click(optionIn('Hour', '3'));
475
+ fireEvent.click(optionIn('Minute', '45'));
476
+ expect(timeField()).toHaveValue('3:45 PM');
477
+
478
+ fireEvent.click(screen.getByRole('dialog'));
479
+ expect(timeField()).toHaveValue('');
480
+ });
481
+
482
+ it('commits 12-hour wheel changes against the controlled date', () => {
483
+ const onChange = vi.fn();
484
+ render(
485
+ <DateTimeInput
486
+ label="Meeting"
487
+ value={'2026-03-15T14:30' as ISODateTimeString}
488
+ onChange={onChange}
489
+ />,
490
+ );
491
+ openDateSheet();
492
+ switchToTimePanel();
493
+
494
+ fireEvent.click(optionIn('Hour', '3'));
495
+ expect(onChange).toHaveBeenCalledWith('2026-03-15T15:30');
496
+ });
497
+
498
+ it('commits 24-hour wheel changes', () => {
499
+ const onChange = vi.fn();
500
+ render(
501
+ <DateTimeInput
502
+ label="Meeting"
503
+ value={'2026-03-15T14:30' as ISODateTimeString}
504
+ hourFormat="24h"
505
+ onChange={onChange}
506
+ />,
507
+ );
508
+ openDateSheet();
509
+ switchToTimePanel();
510
+
511
+ fireEvent.click(optionIn('Hour', '09'));
512
+ expect(onChange).toHaveBeenCalledWith('2026-03-15T09:30');
513
+ });
514
+
515
+ it('commits seconds when hasSeconds is set', () => {
516
+ const onChange = vi.fn();
517
+ render(
518
+ <DateTimeInput
519
+ label="Meeting"
520
+ value={'2026-03-15T14:30:00' as ISODateTimeString}
521
+ hasSeconds
522
+ onChange={onChange}
523
+ />,
524
+ );
525
+ openDateSheet();
526
+ switchToTimePanel();
527
+
528
+ fireEvent.click(optionIn('Second', '45'));
529
+ expect(onChange).toHaveBeenCalledWith('2026-03-15T14:30:45');
530
+ });
531
+
532
+ it('disables out-of-range hours on the min/max boundary date', () => {
533
+ render(
534
+ <DateTimeInput
535
+ label="Meeting"
536
+ value={'2026-03-15T10:00' as ISODateTimeString}
537
+ min={'2026-03-15T09:00' as ISODateTimeString}
538
+ max={'2026-03-15T17:00' as ISODateTimeString}
539
+ hourFormat="24h"
540
+ onChange={() => {}}
541
+ />,
542
+ );
543
+ openDateSheet();
544
+ switchToTimePanel();
545
+
546
+ expect(optionIn('Hour', '08')).toHaveAttribute('aria-disabled', 'true');
547
+ expect(optionIn('Hour', '09')).not.toHaveAttribute('aria-disabled');
548
+ expect(optionIn('Hour', '18')).toHaveAttribute('aria-disabled', 'true');
549
+ });
550
+
551
+ it('preserves the browsed month when switching Date to Time and back', () => {
552
+ withMonthLayout(() => {
553
+ render(<DateTimeInput label="Meeting" onChange={() => {}} />);
554
+ openDateSheet();
555
+
556
+ fireEvent.click(screen.getByRole('button', {name: 'Next month'}));
557
+ expect(screen.getByText('April 2026')).toBeInTheDocument();
558
+
559
+ switchToTimePanel();
560
+ fireEvent.click(screen.getByRole('radio', {name: 'Date'}));
561
+ expect(screen.getByText('April 2026')).toBeInTheDocument();
562
+ expect(document.querySelector('[data-scroller="months"]')).not.toBeNull();
563
+ });
564
+ });
565
+
566
+ it('bounds grid panel min-content width so MonthScroller cannot feed back through its spacer', () => {
567
+ const source = readFileSync(
568
+ 'packages/core/src/DateTimeInput/TouchDateTimeField.tsx',
569
+ 'utf8',
570
+ );
571
+ const styleBlock = (name: string): string => {
572
+ const match = new RegExp(`${name}: \\{([\\s\\S]*?)\\n \\},`).exec(
573
+ source,
574
+ );
575
+ if (match == null) {
576
+ throw new Error(`missing style block ${name}`);
577
+ }
578
+ return match[1];
579
+ };
580
+
581
+ for (const name of [
582
+ 'touchRow',
583
+ 'touchSheetBody',
584
+ 'touchSurface',
585
+ 'touchPanelStack',
586
+ 'touchPanel',
587
+ 'touchDateSurfaceStack',
588
+ 'touchDateSurface',
589
+ 'touchTimeWheels',
590
+ ]) {
591
+ expect(styleBlock(name)).toContain("inlineSize: '100%'");
592
+ expect(styleBlock(name)).toContain('minInlineSize: 0');
593
+ }
594
+ for (const name of ['touchDateWrapper', 'touchTimeWrapper']) {
595
+ expect(styleBlock(name)).toContain('minInlineSize: 0');
596
+ }
597
+ });
598
+
599
+ it('keeps accepting in-sheet edits while a changeAction is pending', () => {
600
+ const onChange = vi.fn();
601
+ const changeAction = vi.fn(async () => new Promise<void>(() => {}));
602
+ render(
603
+ <DateTimeInput
604
+ label="Meeting"
605
+ value={'2026-03-15T14:30' as ISODateTimeString}
606
+ onChange={onChange}
607
+ changeAction={changeAction}
608
+ />,
609
+ );
610
+ openDateSheet();
611
+ switchToTimePanel();
612
+
613
+ fireEvent.click(optionIn('Hour', '3'));
614
+ expect(onChange).toHaveBeenCalledWith('2026-03-15T15:30');
615
+ expect(dateField()).toHaveAttribute('aria-busy', 'true');
616
+
617
+ fireEvent.click(optionIn('Minute', '45'));
618
+ expect(onChange).toHaveBeenCalledWith('2026-03-15T15:45');
619
+ expect(changeAction).toHaveBeenCalledTimes(2);
620
+ });
621
+
622
+ it('clamps drafted time to the selected date boundary', () => {
623
+ const onChange = vi.fn();
624
+ withMonthLayout(() => {
625
+ render(
626
+ <DateTimeInput
627
+ label="Meeting"
628
+ min={'2026-03-21T09:00' as ISODateTimeString}
629
+ onChange={onChange}
630
+ />,
631
+ );
632
+ openDateSheet();
633
+ switchToTimePanel();
634
+ fireEvent.click(optionIn('Hour', '3'));
635
+
636
+ fireEvent.click(screen.getByRole('radio', {name: 'Date'}));
637
+ fireEvent.click(
638
+ screen.getByRole('button', {name: 'Saturday, March 21, 2026'}),
639
+ );
640
+
641
+ expect(onChange).toHaveBeenCalledWith('2026-03-21T09:00');
642
+ });
643
+ });
644
+
645
+ it('honors dateConstraints in the Date panel', () => {
646
+ const onChange = vi.fn();
647
+ withMonthLayout(() => {
648
+ render(
649
+ <DateTimeInput
650
+ label="Meeting"
651
+ dateConstraints={[date => date.getDate() !== 21]}
652
+ onChange={onChange}
653
+ />,
654
+ );
655
+ openDateSheet();
656
+ const disabledDay = screen.getByRole('button', {
657
+ name: 'Saturday, March 21, 2026',
658
+ });
659
+
660
+ expect(disabledDay).toHaveAttribute('aria-disabled', 'true');
661
+ fireEvent.click(disabledDay);
662
+ expect(onChange).not.toHaveBeenCalled();
663
+ });
664
+ });
665
+
666
+ it('Reset clears the value and leaves the sheet open on the Date panel', () => {
667
+ const onChange = vi.fn();
668
+ withMonthLayout(() => {
669
+ render(
670
+ <DateTimeInput
671
+ label="Meeting"
672
+ value={'2026-03-15T10:00' as ISODateTimeString}
673
+ onChange={onChange}
674
+ />,
675
+ );
676
+ const field = openDateSheet();
677
+
678
+ fireEvent.click(screen.getByRole('button', {name: 'Reset'}));
679
+ expect(onChange).toHaveBeenCalledWith(undefined);
680
+ expect(field).toHaveAttribute('aria-expanded', 'true');
681
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
682
+ 'aria-checked',
683
+ 'true',
684
+ );
685
+ });
686
+ });
687
+
688
+ it('keeps Save date disabled until a valid date exists', () => {
689
+ withMonthLayout(() => {
690
+ render(<ControlledDateTimeInput />);
691
+ openDateSheet();
692
+ const saveDateButton = screen.getByRole('button', {name: 'Save date'});
693
+
694
+ expect(saveDateButton).toBeDisabled();
695
+ fireEvent.click(saveDateButton);
696
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
697
+ 'aria-checked',
698
+ 'true',
699
+ );
700
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
701
+ });
702
+ });
703
+
704
+ it('enables Save date after a controlled date selection updates value', () => {
705
+ vi.useFakeTimers();
706
+ withMonthLayout(() => {
707
+ render(<ControlledDateTimeInput />);
708
+ openDateSheet();
709
+ const saveDateButton = screen.getByRole('button', {name: 'Save date'});
710
+ expect(saveDateButton).toBeDisabled();
711
+
712
+ fireEvent.click(
713
+ screen.getByRole('button', {name: 'Saturday, March 21, 2026'}),
714
+ );
715
+ expect(dateField()).toHaveValue('March 21, 2026');
716
+ expect(saveDateButton).not.toBeDisabled();
717
+
718
+ fireEvent.click(saveDateButton);
719
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
720
+ expect(timeField()).toHaveAttribute('aria-expanded', 'true');
721
+ const timeTab = screen.getByRole('radio', {name: 'Time'});
722
+ expect(timeTab).toHaveAttribute('aria-checked', 'true');
723
+
724
+ vi.runAllTimers();
725
+ expect(timeTab).toHaveFocus();
726
+ });
727
+ vi.useRealTimers();
728
+ });
729
+
730
+ it('disables Save date after Reset clears the controlled date', () => {
731
+ withMonthLayout(() => {
732
+ render(
733
+ <ControlledDateTimeInput
734
+ initialValue={'2026-03-15T10:00' as ISODateTimeString}
735
+ />,
736
+ );
737
+ openDateSheet();
738
+ const saveDateButton = screen.getByRole('button', {name: 'Save date'});
739
+ expect(saveDateButton).not.toBeDisabled();
740
+
741
+ fireEvent.click(screen.getByRole('button', {name: 'Reset'}));
742
+ expect(dateField()).toHaveValue('');
743
+ expect(saveDateButton).toBeDisabled();
744
+
745
+ fireEvent.click(saveDateButton);
746
+ expect(screen.getByRole('radio', {name: 'Date'})).toHaveAttribute(
747
+ 'aria-checked',
748
+ 'true',
749
+ );
750
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
751
+ });
752
+ });
753
+
754
+ it('Save date advances to Time, focuses the Time tab, and does not change the value', () => {
755
+ vi.useFakeTimers();
756
+ const onChange = vi.fn();
757
+ withMonthLayout(() => {
758
+ render(
759
+ <DateTimeInput
760
+ label="Meeting"
761
+ value={'2026-03-15T10:00' as ISODateTimeString}
762
+ onChange={onChange}
763
+ />,
764
+ );
765
+ openDateSheet();
766
+ const timeTab = screen.getByRole('radio', {name: 'Time'});
767
+
768
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
769
+
770
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
771
+ expect(timeField()).toHaveAttribute('aria-expanded', 'true');
772
+ expect(timeTab).toHaveAttribute('aria-checked', 'true');
773
+ expect(document.querySelector('[data-panel="time"]')).not.toHaveAttribute(
774
+ 'aria-hidden',
775
+ );
776
+ expect(onChange).not.toHaveBeenCalled();
777
+
778
+ vi.runAllTimers();
779
+ expect(timeTab).toHaveFocus();
780
+ });
781
+ vi.useRealTimers();
782
+ });
783
+
784
+ it('Save after keyboard opening does not refocus-reopen the sheet', () => {
785
+ withMonthLayout(() => {
786
+ render(
787
+ <DateTimeInput
788
+ label="Meeting"
789
+ value={'2026-03-15T10:00' as ISODateTimeString}
790
+ onChange={() => {}}
791
+ />,
792
+ );
793
+ const field = dateField();
794
+ field.focus();
795
+ fireEvent.keyDown(field, {key: 'Enter'});
796
+ expect(field).toHaveAttribute('aria-expanded', 'true');
797
+
798
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
799
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
800
+ expect(field).toHaveAttribute('aria-expanded', 'false');
801
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
802
+ });
803
+ });
804
+
805
+ it('Time Save closes the sheet without changing the value', () => {
806
+ const onChange = vi.fn();
807
+ withMonthLayout(() => {
808
+ render(
809
+ <DateTimeInput
810
+ label="Meeting"
811
+ value={'2026-03-15T10:00' as ISODateTimeString}
812
+ onChange={onChange}
813
+ />,
814
+ );
815
+ openDateSheet();
816
+ fireEvent.click(screen.getByRole('button', {name: 'Save date'}));
817
+
818
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
819
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
820
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
821
+ expect(onChange).not.toHaveBeenCalled();
822
+ });
823
+ });
824
+
825
+ it('disables out-of-range seconds on the min/max boundary time', () => {
826
+ render(
827
+ <DateTimeInput
828
+ label="Meeting"
829
+ value={'2026-03-15T14:30:30' as ISODateTimeString}
830
+ min={'2026-03-15T14:30:30' as ISODateTimeString}
831
+ max={'2026-03-15T14:30:30' as ISODateTimeString}
832
+ hasSeconds
833
+ onChange={() => {}}
834
+ />,
835
+ );
836
+ openDateSheet();
837
+ switchToTimePanel();
838
+
839
+ expect(optionIn('Second', '29')).toHaveAttribute('aria-disabled', 'true');
840
+ expect(optionIn('Second', '30')).not.toHaveAttribute('aria-disabled');
841
+ expect(optionIn('Second', '31')).toHaveAttribute('aria-disabled', 'true');
842
+ });
843
+
844
+ it('reflects controlled value changes in the closed field', () => {
845
+ const {rerender} = render(
846
+ <DateTimeInput
847
+ label="Meeting"
848
+ value={'2026-03-15T10:00' as ISODateTimeString}
849
+ onChange={() => {}}
850
+ />,
851
+ );
852
+ expect(dateField()).toHaveValue('March 15, 2026');
853
+ expect(timeField()).toHaveValue('10:00 AM');
854
+
855
+ rerender(
856
+ <DateTimeInput
857
+ label="Meeting"
858
+ value={'2026-03-16T18:45' as ISODateTimeString}
859
+ onChange={() => {}}
860
+ />,
861
+ );
862
+ expect(dateField()).toHaveValue('March 16, 2026');
863
+ expect(timeField()).toHaveValue('6:45 PM');
864
+ });
865
+
866
+ it('clears the whole datetime and restores focus on the next task', () => {
867
+ vi.useFakeTimers();
868
+ const onChange = vi.fn();
869
+ render(
870
+ <DateTimeInput
871
+ label="Meeting"
872
+ value={'2026-03-15T10:00' as ISODateTimeString}
873
+ hasClear
874
+ onChange={onChange}
875
+ />,
876
+ );
877
+ const field = dateField();
878
+ const focusSpy = vi.spyOn(field, 'focus');
879
+
880
+ fireEvent.click(screen.getByRole('button', {name: 'Clear Meeting'}));
881
+ expect(onChange).toHaveBeenCalledWith(undefined);
882
+ expect(field).toHaveAttribute('aria-expanded', 'false');
883
+ expect(focusSpy).not.toHaveBeenCalled();
884
+
885
+ vi.runAllTimers();
886
+ expect(focusSpy).toHaveBeenCalledWith({preventScroll: true});
887
+ vi.useRealTimers();
888
+ });
889
+
890
+ it('blocks opening, clearing and in-sheet edits while loading', () => {
891
+ const onChange = vi.fn();
892
+ const {rerender} = render(
893
+ <DateTimeInput
894
+ label="Meeting"
895
+ value={'2026-03-15T14:30' as ISODateTimeString}
896
+ isLoading
897
+ hasClear
898
+ onChange={onChange}
899
+ />,
900
+ );
901
+ const loadingField = dateField();
902
+ fireEvent.click(loadingField);
903
+ expect(loadingField).toHaveAttribute('aria-expanded', 'false');
904
+ expect(
905
+ screen.queryByRole('button', {name: 'Clear Meeting'}),
906
+ ).not.toBeInTheDocument();
907
+
908
+ rerender(
909
+ <DateTimeInput
910
+ label="Meeting"
911
+ value={'2026-03-15T14:30' as ISODateTimeString}
912
+ hasClear
913
+ onChange={onChange}
914
+ />,
915
+ );
916
+ const field = dateField();
917
+ fireEvent.click(field);
918
+ switchToTimePanel();
919
+ expect(field).toHaveAttribute('aria-expanded', 'false');
920
+ expect(timeField()).toHaveAttribute('aria-expanded', 'true');
921
+
922
+ rerender(
923
+ <DateTimeInput
924
+ label="Meeting"
925
+ value={'2026-03-15T14:30' as ISODateTimeString}
926
+ isLoading
927
+ hasClear
928
+ onChange={onChange}
929
+ />,
930
+ );
931
+ expect(field).toHaveAttribute('aria-expanded', 'false');
932
+ expect(onChange).not.toHaveBeenCalled();
933
+ });
934
+
935
+ it('clicking the pending spinner inside an enabled date segment does not open the sheet', () => {
936
+ const onChange = vi.fn();
937
+ const changeAction = vi.fn(async () => new Promise<void>(() => {}));
938
+ const {container} = render(
939
+ <DateTimeInput
940
+ label="Meeting"
941
+ value={'2026-03-15T14:30' as ISODateTimeString}
942
+ changeAction={changeAction}
943
+ onChange={onChange}
944
+ />,
945
+ );
946
+ openTimeSheet();
947
+ fireEvent.click(optionIn('Minute', '45'));
948
+ fireEvent.click(screen.getByRole('button', {name: 'Save'}));
949
+ const spinner = container.querySelector(
950
+ '.astryx-date-time-input-date-segment .astryx-spinner',
951
+ ) as HTMLElement;
952
+ expect(spinner).not.toBeNull();
953
+ expect(dateField()).not.toBeDisabled();
954
+
955
+ fireEvent.click(spinner);
956
+
957
+ expect(dateField()).toHaveAttribute('aria-expanded', 'false');
958
+ expect(timeField()).toHaveAttribute('aria-expanded', 'false');
959
+ });
960
+
961
+ it('keeps a disabled reason reachable while blocking activation', () => {
962
+ render(
963
+ <DateTimeInput
964
+ label="Meeting"
965
+ isDisabled
966
+ disabledMessage="Choose a project first"
967
+ onChange={() => {}}
968
+ />,
969
+ );
970
+ const field = dateField();
971
+
972
+ expect(field).not.toBeDisabled();
973
+ expect(field).toHaveAttribute('aria-disabled', 'true');
974
+ expect(field.getAttribute('aria-describedby')).toContain(
975
+ screen.getByRole('tooltip', {hidden: true}).id,
976
+ );
977
+ fireEvent.click(field);
978
+ expect(field).toHaveAttribute('aria-expanded', 'false');
979
+ });
980
+ });