@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,1437 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file TouchDateTimeField.tsx
7
+ * @input Uses React, Field, BottomSheet, SegmentedControl, DateInput month picker primitives, Wheel, StyleX intrinsic flex layout
8
+ * @output Exports TouchDateTimeField — the coarse-pointer surface behind DateTimeInput
9
+ * @position Internal component; consumed by DateTimeInput.tsx
10
+ *
11
+ * The touch half of `DateTimeInput`, holding `DateTimeInput`'s whole prop
12
+ * contract so the desktop and touch surfaces are interchangeable. The desktop
13
+ * only props `timeIncrement` and `timeOptionInterval` remain accepted but are
14
+ * intentionally ignored here: time is selected with wheels instead of a typed
15
+ * field or preset list.
16
+ *
17
+ * SYNC: When modified, update these files to stay in sync:
18
+ * - /packages/core/src/DateTimeInput/DateTimeInput.tsx
19
+ * - /packages/core/src/DateTimeInput/DateTimeInput.doc.mjs
20
+ * - /packages/core/src/DateTimeInput/DateTimeInputTouch.test.tsx
21
+ * - /apps/storybook/stories/DateTimeInput.stories.tsx
22
+ * - /packages/cli/assets/templates/blocks/components/DateTimeInput/
23
+ */
24
+
25
+ import {
26
+ use,
27
+ useCallback,
28
+ useEffect,
29
+ useId,
30
+ useMemo,
31
+ useOptimistic,
32
+ useRef,
33
+ useState,
34
+ useTransition,
35
+ } from 'react';
36
+ import * as stylex from '@stylexjs/stylex';
37
+ import {BottomSheet} from '../BottomSheet';
38
+ import {Button} from '../Button';
39
+ import {useCalendarConstraints, type ISODateString} from '../Calendar';
40
+ import {
41
+ Field,
42
+ InputClearButton,
43
+ inputWrapperStyles,
44
+ inputStatusBorderStyles,
45
+ inputStatusHoverShadowStyles,
46
+ inputStatusFocusWithinStyles,
47
+ } from '../Field';
48
+ import {useInputStatusIcon, useMergedRefs} from '../hooks';
49
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
50
+ import {Icon} from '../Icon';
51
+ import {IconButton} from '../IconButton';
52
+ import {useTranslator, InternationalizationContext} from '../i18n';
53
+ import {SegmentedControl, SegmentedControlItem} from '../SegmentedControl';
54
+ import {useSize} from '../SizeContext/SizeContext';
55
+ import {Spinner} from '../Spinner';
56
+ import {useTooltip} from '../Tooltip';
57
+ import {
58
+ colorVars,
59
+ fontWeightVars,
60
+ radiusVars,
61
+ sizeVars,
62
+ spacingVars,
63
+ typeScaleVars,
64
+ typographyVars,
65
+ } from '../theme/tokens.stylex';
66
+ import {focusOutlineStyles} from '../utils/focusOutline.stylex';
67
+ import {rtlStyles} from '../utils/rtlStyles';
68
+ import {themeProps} from '../utils/themeProps';
69
+ import {normalizeDayOfWeek} from '../utils/dateTypes';
70
+ import {
71
+ formatDisplayTime12h,
72
+ formatDisplayTime24h,
73
+ formatISOTime,
74
+ isImeKeyEvent,
75
+ isRenderable,
76
+ isTimeInRange,
77
+ clampTime,
78
+ mergeProps,
79
+ parseISOTime,
80
+ type ISOTimeString,
81
+ } from '../utils';
82
+ import {
83
+ DATE_FORMAT_LONG,
84
+ DATE_FORMAT_MONTH_YEAR,
85
+ DATE_FORMAT_WEEKDAY_ONLY,
86
+ plainDateFormat,
87
+ plainDateFromISO,
88
+ plainDateToday,
89
+ type PlainDate,
90
+ } from '../utils/plainDate';
91
+ import {
92
+ MonthScroller,
93
+ type MonthScrollerHandle,
94
+ } from '../DateInput/MonthScroller';
95
+ import {MonthYearWheels} from '../DateInput/MonthYearWheels';
96
+ import {Wheel, type WheelOption} from '../DateInput/Wheel';
97
+ import {
98
+ dateInputTouchGeometry,
99
+ dateInputTouchSizes,
100
+ } from '../DateInput/tokens.stylex';
101
+ import {
102
+ DEFAULT_MONTH_REACH,
103
+ clampIndex,
104
+ fromMonthIndex,
105
+ monthIndexOf,
106
+ } from '../DateInput/monthGeometry';
107
+ import type {DateTimeInputProps, ISODateTimeString} from './DateTimeInput';
108
+
109
+ function splitDateTime(dt: ISODateTimeString | undefined): {
110
+ date: ISODateString | undefined;
111
+ time: ISOTimeString | undefined;
112
+ } {
113
+ if (!dt) {
114
+ return {date: undefined, time: undefined};
115
+ }
116
+ const tIndex = dt.indexOf('T');
117
+ if (tIndex === -1) {
118
+ return {date: dt as unknown as ISODateString, time: undefined};
119
+ }
120
+ return {
121
+ date: dt.slice(0, tIndex) as ISODateString,
122
+ time: dt.slice(tIndex + 1) as ISOTimeString,
123
+ };
124
+ }
125
+
126
+ function combineDateTime(
127
+ date: ISODateString | undefined,
128
+ time: ISOTimeString | undefined,
129
+ ): ISODateTimeString | undefined {
130
+ if (!date || !time) {
131
+ return undefined;
132
+ }
133
+ return `${date}T${time}` as ISODateTimeString;
134
+ }
135
+
136
+ function getDefaultTime(hasSeconds: boolean): ISOTimeString {
137
+ const now = new Date();
138
+ return formatISOTime(
139
+ {hour: now.getHours(), minute: now.getMinutes(), second: now.getSeconds()},
140
+ hasSeconds,
141
+ );
142
+ }
143
+
144
+ const sizeStyles = stylex.create({
145
+ sm: {
146
+ height: sizeVars['--size-element-sm'],
147
+ },
148
+ md: {
149
+ height: sizeVars['--size-element-md'],
150
+ },
151
+ lg: {
152
+ height: sizeVars['--size-element-lg'],
153
+ },
154
+ });
155
+
156
+ const HORIZONTAL_SEGMENT_BASIS = 196;
157
+
158
+ const styles = stylex.create({
159
+ iconButton: {
160
+ display: 'flex',
161
+ alignItems: 'center',
162
+ justifyContent: 'center',
163
+ padding: 0,
164
+ margin: 0,
165
+ borderWidth: 0,
166
+ borderStyle: 'none',
167
+ backgroundColor: 'transparent',
168
+ cursor: {
169
+ default: 'pointer',
170
+ ':is(:disabled,[aria-disabled="true"])': 'default',
171
+ },
172
+ borderRadius: radiusVars['--radius-element'],
173
+ },
174
+ iconButtonDisabled: {
175
+ cursor: 'default',
176
+ },
177
+ input: {
178
+ display: 'block',
179
+ flex: 1,
180
+ minWidth: 0,
181
+ borderWidth: 0,
182
+ borderStyle: 'none',
183
+ padding: 0,
184
+ fontFamily: typographyVars['--font-family-body'],
185
+ fontSize: {
186
+ default: typeScaleVars['--text-body-size'],
187
+ '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
188
+ },
189
+ lineHeight: typeScaleVars['--text-body-leading'],
190
+ color: colorVars['--color-text-primary'],
191
+ backgroundColor: 'transparent',
192
+ outline: 'none',
193
+ '::placeholder': {
194
+ color: colorVars['--color-text-secondary'],
195
+ },
196
+ },
197
+ inputDisabled: {
198
+ cursor: 'default',
199
+ },
200
+ touchRow: {
201
+ display: 'flex',
202
+ flexWrap: 'wrap',
203
+ inlineSize: '100%',
204
+ minInlineSize: 0,
205
+ gap: spacingVars['--spacing-2'],
206
+ },
207
+ touchDateWrapper: {
208
+ flex: 1,
209
+ flexBasis: HORIZONTAL_SEGMENT_BASIS,
210
+ minInlineSize: 0,
211
+ },
212
+ touchTimeWrapper: {
213
+ flex: 1,
214
+ flexBasis: HORIZONTAL_SEGMENT_BASIS,
215
+ minInlineSize: 0,
216
+ },
217
+ touchInput: {
218
+ caretColor: 'transparent',
219
+ cursor: {
220
+ default: 'pointer',
221
+ ':is(:disabled,[aria-disabled="true"])': 'default',
222
+ },
223
+ userSelect: 'none',
224
+ },
225
+ touchSheetBody: {
226
+ boxSizing: 'border-box',
227
+ inlineSize: '100%',
228
+ minInlineSize: 0,
229
+ paddingInline: spacingVars['--spacing-4'],
230
+ paddingBlockStart: spacingVars['--spacing-6'],
231
+ paddingBlockEnd: spacingVars['--spacing-4'],
232
+ },
233
+ touchSurface: {
234
+ display: 'flex',
235
+ flexDirection: 'column',
236
+ inlineSize: '100%',
237
+ minInlineSize: 0,
238
+ gap: spacingVars['--spacing-3'],
239
+ },
240
+ touchPanelStack: {
241
+ display: 'grid',
242
+ inlineSize: '100%',
243
+ minInlineSize: 0,
244
+ },
245
+ touchPanel: {
246
+ gridArea: '1 / 1',
247
+ display: 'flex',
248
+ inlineSize: '100%',
249
+ minInlineSize: 0,
250
+ flexDirection: 'column',
251
+ gap: spacingVars['--spacing-2'],
252
+ },
253
+ touchPanelHidden: {
254
+ visibility: 'hidden',
255
+ opacity: 0,
256
+ pointerEvents: 'none',
257
+ },
258
+ touchHeader: {
259
+ display: 'flex',
260
+ alignItems: 'center',
261
+ gap: spacingVars['--spacing-1'],
262
+ blockSize: sizeVars['--size-element-lg'],
263
+ },
264
+ touchTitle: {
265
+ display: 'flex',
266
+ alignItems: 'center',
267
+ gap: spacingVars['--spacing-1'],
268
+ minInlineSize: 0,
269
+ paddingInline: spacingVars['--spacing-2'],
270
+ borderWidth: 0,
271
+ borderStyle: 'none',
272
+ borderRadius: radiusVars['--radius-element'],
273
+ backgroundColor: 'transparent',
274
+ color: colorVars['--color-text-primary'],
275
+ fontFamily: typographyVars['--font-family-body'],
276
+ fontSize: typeScaleVars['--text-large-size'],
277
+ fontWeight: fontWeightVars['--font-weight-semibold'],
278
+ cursor: {
279
+ default: 'pointer',
280
+ ':is(:disabled,[aria-disabled="true"])': 'default',
281
+ },
282
+ },
283
+ touchTitleText: {
284
+ minInlineSize: 0,
285
+ overflow: 'hidden',
286
+ textOverflow: 'ellipsis',
287
+ whiteSpace: 'nowrap',
288
+ },
289
+ touchHeaderActions: {
290
+ display: 'flex',
291
+ alignItems: 'center',
292
+ gap: spacingVars['--spacing-0-5'],
293
+ marginInlineStart: 'auto',
294
+ },
295
+ touchHeaderActionsHidden: {
296
+ visibility: 'hidden',
297
+ opacity: 0,
298
+ pointerEvents: 'none',
299
+ },
300
+ touchArrow: {
301
+ minBlockSize: {
302
+ default: null,
303
+ '@media (pointer: coarse)': dateInputTouchSizes.daySize,
304
+ },
305
+ minInlineSize: {
306
+ default: null,
307
+ '@media (pointer: coarse)': dateInputTouchSizes.daySize,
308
+ },
309
+ },
310
+ touchArrowUnavailable: {
311
+ visibility: 'hidden',
312
+ },
313
+ touchArrowIcon: {
314
+ display: 'inline-flex',
315
+ },
316
+ touchResetButton: {
317
+ minBlockSize: {
318
+ default: null,
319
+ '@media (pointer: coarse)': dateInputTouchSizes.daySize,
320
+ },
321
+ },
322
+ touchDateSurfaceStack: {
323
+ display: 'grid',
324
+ inlineSize: '100%',
325
+ minInlineSize: 0,
326
+ },
327
+ touchDateSurface: {
328
+ gridArea: '1 / 1',
329
+ display: 'flex',
330
+ inlineSize: '100%',
331
+ minInlineSize: 0,
332
+ flexDirection: 'column',
333
+ gap: spacingVars['--spacing-2'],
334
+ },
335
+ touchDateSurfaceHidden: {
336
+ visibility: 'hidden',
337
+ opacity: 0,
338
+ pointerEvents: 'none',
339
+ },
340
+ touchWeekdays: {
341
+ display: 'grid',
342
+ gridTemplateColumns: 'repeat(7, 1fr)',
343
+ blockSize: sizeVars['--size-element-sm'],
344
+ alignItems: 'center',
345
+ },
346
+ touchWeekday: {
347
+ textAlign: 'center',
348
+ fontSize: typeScaleVars['--text-supporting-size'],
349
+ fontWeight: fontWeightVars['--font-weight-normal'],
350
+ color: colorVars['--color-text-secondary'],
351
+ },
352
+ touchWheelSpacer: {
353
+ blockSize: sizeVars['--size-element-sm'],
354
+ },
355
+ touchTimeWheels: {
356
+ display: 'flex',
357
+ inlineSize: '100%',
358
+ minInlineSize: 0,
359
+ blockSize: dateInputTouchGeometry.paneBlockSize,
360
+ gap: spacingVars['--spacing-2'],
361
+ },
362
+ touchFooter: {
363
+ display: 'flex',
364
+ marginBlockStart: 'auto',
365
+ paddingBlockStart: spacingVars['--spacing-1'],
366
+ },
367
+ });
368
+
369
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
370
+
371
+ function parseValidISODate(date: ISODateString | undefined): PlainDate | null {
372
+ if (date == null || !ISO_DATE.test(date)) {
373
+ return null;
374
+ }
375
+ try {
376
+ return plainDateFromISO(date);
377
+ } catch {
378
+ return null;
379
+ }
380
+ }
381
+
382
+ function normalizeISOTime(
383
+ time: ISOTimeString | undefined,
384
+ hasSeconds: boolean,
385
+ ): ISOTimeString | undefined {
386
+ if (time === undefined) {
387
+ return undefined;
388
+ }
389
+ const parsed = parseISOTime(time);
390
+ return parsed ? formatISOTime(parsed, hasSeconds) : undefined;
391
+ }
392
+
393
+ function timeToSeconds(time: ISOTimeString | undefined): number | null {
394
+ if (time === undefined) {
395
+ return null;
396
+ }
397
+ const parsed = parseISOTime(time);
398
+ return parsed == null
399
+ ? null
400
+ : parsed.hour * 3600 + parsed.minute * 60 + parsed.second;
401
+ }
402
+
403
+ function twoDigits(value: number): string {
404
+ return String(value).padStart(2, '0');
405
+ }
406
+
407
+ function hour12From24(hour: number): number {
408
+ return hour === 0 ? 12 : hour > 12 ? hour - 12 : hour;
409
+ }
410
+
411
+ function hour24From12(hour: number, meridiem: number): number {
412
+ if (meridiem === 0) {
413
+ return hour === 12 ? 0 : hour;
414
+ }
415
+ return hour === 12 ? 12 : hour + 12;
416
+ }
417
+
418
+ function rangeOverlaps(
419
+ startSecond: number,
420
+ endSecond: number,
421
+ min: ISOTimeString | undefined,
422
+ max: ISOTimeString | undefined,
423
+ ): boolean {
424
+ const minSecond = timeToSeconds(min);
425
+ const maxSecond = timeToSeconds(max);
426
+ return (
427
+ (minSecond == null || endSecond >= minSecond) &&
428
+ (maxSecond == null || startSecond <= maxSecond)
429
+ );
430
+ }
431
+
432
+ type TouchDateTimePanel = 'date' | 'time';
433
+
434
+ export function TouchDateTimeField({
435
+ label,
436
+ isLabelHidden = false,
437
+ description,
438
+ isOptional = false,
439
+ isRequired = false,
440
+ isDisabled = false,
441
+ disabledMessage,
442
+ value,
443
+ onChange,
444
+ changeAction,
445
+ isLoading = false,
446
+ min,
447
+ max,
448
+ dateConstraints,
449
+ hasSeconds = false,
450
+ hourFormat = '12h',
451
+ // Desktop-only: mobile uses wheels instead of typed arrow stepping or a preset list.
452
+ timeIncrement: _timeIncrement,
453
+ timeOptionInterval: _timeOptionInterval,
454
+ hasClear = false,
455
+ placeholder: placeholderFromProps,
456
+ timePlaceholder: timePlaceholderFromProps,
457
+ timeLabel,
458
+ size: sizeProp,
459
+ status,
460
+ labelTooltip,
461
+ // Desktop-only: the touch Date panel shows one swipe-paged month at a time.
462
+ numberOfMonths: _numberOfMonths,
463
+ weekStartsOn: weekStartsOnProp = 0,
464
+ width,
465
+ xstyle,
466
+ className,
467
+ style,
468
+ ref,
469
+ ...rest
470
+ }: DateTimeInputProps) {
471
+ const t = useTranslator();
472
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
473
+ const {locale} = use(InternationalizationContext);
474
+ const placeholder =
475
+ placeholderFromProps ?? t('@astryx.dateTimeInput.placeholder');
476
+ const timePlaceholder =
477
+ timePlaceholderFromProps ?? t('@astryx.dateTimeInput.timePlaceholder');
478
+ const resolvedTimeLabel =
479
+ timeLabel ?? t('@astryx.dateTimeInput.timeSuffix', {label});
480
+ const size = useSize(sizeProp, 'md');
481
+ const dateInputId = useId();
482
+ const timeInputId = useId();
483
+ const descriptionID = useId();
484
+ const statusMessageID = useId();
485
+ const dateInputRef = useRef<HTMLInputElement | null>(null);
486
+ const timeInputRef = useRef<HTMLInputElement | null>(null);
487
+ const timeSegmentRef = useRef<HTMLButtonElement | null>(null);
488
+ const mergedDateInputRef = useMergedRefs(ref, dateInputRef);
489
+ const scrollerHandleRef = useRef<MonthScrollerHandle | null>(null);
490
+
491
+ const [, startTransition] = useTransition();
492
+ const [optimisticValue, setOptimisticValue] = useOptimistic(value);
493
+ const isPendingChange = optimisticValue !== value;
494
+ const isBusy = isLoading || isPendingChange;
495
+ const isEffectivelyDisabled = isDisabled || isLoading;
496
+
497
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
498
+ const disabledMessageTooltip = useTooltip({
499
+ placement: 'above',
500
+ focusTrigger: 'always',
501
+ isEnabled: showsDisabledMessage,
502
+ });
503
+
504
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
505
+ useInputStatusIcon({
506
+ status,
507
+ statusVariant: 'detached',
508
+ });
509
+
510
+ const ariaDescribedBy =
511
+ [
512
+ description ? descriptionID : null,
513
+ status?.message ? statusMessageID : null,
514
+ statusTooltipDescribedBy,
515
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
516
+ ]
517
+ .filter(Boolean)
518
+ .join(' ') || undefined;
519
+
520
+ const minParts = useMemo(() => splitDateTime(min), [min]);
521
+ const maxParts = useMemo(() => splitDateTime(max), [max]);
522
+ const valueParts = useMemo(
523
+ () => splitDateTime(optimisticValue),
524
+ [optimisticValue],
525
+ );
526
+ const calendarMin = minParts.date;
527
+ const calendarMax = maxParts.date;
528
+ const {isDateDisabled} = useCalendarConstraints({
529
+ min: calendarMin,
530
+ max: calendarMax,
531
+ dateConstraints,
532
+ });
533
+
534
+ const [isSheetOpen, setIsSheetOpen] = useState(false);
535
+ const [activePanel, setActivePanel] = useState<TouchDateTimePanel>('date');
536
+ const [isWheelOpen, setIsWheelOpen] = useState(false);
537
+ const [draftTime, setDraftTime] = useState<ISOTimeString | undefined>(() =>
538
+ normalizeISOTime(splitDateTime(value).time, hasSeconds),
539
+ );
540
+
541
+ const today = useMemo(() => plainDateToday(), []);
542
+ const selectedDate = useMemo(
543
+ () => parseValidISODate(valueParts.date),
544
+ [valueParts.date],
545
+ );
546
+ const fallbackTime = useMemo(() => getDefaultTime(hasSeconds), [hasSeconds]);
547
+ const normalizedValueTime = normalizeISOTime(valueParts.time, hasSeconds);
548
+ const timeForWheels = normalizedValueTime ?? draftTime ?? fallbackTime;
549
+ const parsedWheelTime = useMemo(
550
+ () =>
551
+ parseISOTime(timeForWheels) ?? {
552
+ hour: 0,
553
+ minute: 0,
554
+ second: 0,
555
+ },
556
+ [timeForWheels],
557
+ );
558
+ const weekStartsOn = normalizeDayOfWeek(weekStartsOnProp);
559
+
560
+ useEffect(() => {
561
+ if (normalizedValueTime !== undefined) {
562
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- mirrors an externally controlled time into the sheet-local draft
563
+ setDraftTime(normalizedValueTime);
564
+ } else if (optimisticValue === undefined) {
565
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- external clear resets the sheet-local draft too
566
+ setDraftTime(undefined);
567
+ }
568
+ }, [normalizedValueTime, optimisticValue]);
569
+
570
+ const [anchorMonthIndex] = useState(() =>
571
+ monthIndexOf(selectedDate ?? plainDateToday()),
572
+ );
573
+ const minMonthIndex =
574
+ calendarMin != null
575
+ ? monthIndexOf(plainDateFromISO(calendarMin))
576
+ : anchorMonthIndex - DEFAULT_MONTH_REACH;
577
+ const maxMonthIndex =
578
+ calendarMax != null
579
+ ? monthIndexOf(plainDateFromISO(calendarMax))
580
+ : anchorMonthIndex + DEFAULT_MONTH_REACH;
581
+ const [monthIndex, setMonthIndex] = useState(() =>
582
+ clampIndex(anchorMonthIndex, minMonthIndex, maxMonthIndex),
583
+ );
584
+ const {year, month} = fromMonthIndex(monthIndex);
585
+ const canStepBack = monthIndex > minMonthIndex;
586
+ const canStepForward = monthIndex < maxMonthIndex;
587
+
588
+ useEffect(() => {
589
+ const clamped = clampIndex(monthIndex, minMonthIndex, maxMonthIndex);
590
+ if (clamped !== monthIndex) {
591
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- min/max can change underneath the mounted sheet; keep the visible month in range
592
+ setMonthIndex(clamped);
593
+ scrollerHandleRef.current?.scrollToMonth(clamped, 'auto');
594
+ }
595
+ }, [monthIndex, minMonthIndex, maxMonthIndex]);
596
+
597
+ const dayNames = useMemo(
598
+ () =>
599
+ Array.from({length: 7}, (_, offset) =>
600
+ plainDateFormat(
601
+ {year: 1970, month: 1, day: 4 + ((weekStartsOn + offset) % 7)},
602
+ DATE_FORMAT_WEEKDAY_ONLY,
603
+ locale,
604
+ ),
605
+ ),
606
+ [weekStartsOn, locale],
607
+ );
608
+ const monthYearLabel = plainDateFormat(
609
+ {year, month, day: 1},
610
+ DATE_FORMAT_MONTH_YEAR,
611
+ locale,
612
+ );
613
+
614
+ const dateDisplayValue = useMemo(() => {
615
+ if (selectedDate == null) {
616
+ return '';
617
+ }
618
+ return plainDateFormat(selectedDate, DATE_FORMAT_LONG, locale);
619
+ }, [selectedDate, locale]);
620
+
621
+ const timeDisplayValue = useMemo(() => {
622
+ const displayTime = normalizedValueTime ?? draftTime;
623
+ return displayTime
624
+ ? (hourFormat === '12h' ? formatDisplayTime12h : formatDisplayTime24h)(
625
+ displayTime,
626
+ hasSeconds,
627
+ )
628
+ : '';
629
+ }, [draftTime, normalizedValueTime, hourFormat, hasSeconds]);
630
+
631
+ const timeBoundsForDate = useCallback(
632
+ (date: ISODateString | undefined) => ({
633
+ min:
634
+ date != null && minParts.date === date && minParts.time
635
+ ? minParts.time
636
+ : undefined,
637
+ max:
638
+ date != null && maxParts.date === date && maxParts.time
639
+ ? maxParts.time
640
+ : undefined,
641
+ }),
642
+ [minParts.date, minParts.time, maxParts.date, maxParts.time],
643
+ );
644
+
645
+ const clampTimeForDate = useCallback(
646
+ (date: ISODateString | undefined, time: ISOTimeString): ISOTimeString => {
647
+ const bounds = timeBoundsForDate(date);
648
+ return clampTime(time, bounds.min, bounds.max, hasSeconds);
649
+ },
650
+ [hasSeconds, timeBoundsForDate],
651
+ );
652
+
653
+ const fireChange = useCallback(
654
+ (newValue: ISODateTimeString | undefined) => {
655
+ if (isEffectivelyDisabled) {
656
+ return;
657
+ }
658
+ onChange(newValue);
659
+ if (changeAction) {
660
+ startTransition(async () => {
661
+ setOptimisticValue(newValue);
662
+ await changeAction(newValue);
663
+ });
664
+ }
665
+ },
666
+ [
667
+ isEffectivelyDisabled,
668
+ onChange,
669
+ changeAction,
670
+ startTransition,
671
+ setOptimisticValue,
672
+ ],
673
+ );
674
+
675
+ const openSheet = useCallback(
676
+ (panel: TouchDateTimePanel) => {
677
+ if (!isEffectivelyDisabled) {
678
+ setIsWheelOpen(false);
679
+ setActivePanel(panel);
680
+ setIsSheetOpen(true);
681
+ }
682
+ },
683
+ [isEffectivelyDisabled],
684
+ );
685
+
686
+ const closeSheet = useCallback(() => {
687
+ if (selectedDate == null) {
688
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- The shared close path is also used by the disabled/loading effect.
689
+ setDraftTime(undefined);
690
+ }
691
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- The shared close path is also used by the disabled/loading effect.
692
+ setIsSheetOpen(false);
693
+ }, [selectedDate]);
694
+
695
+ const handleSheetOpenChange = useCallback(
696
+ (nextOpen: boolean) => {
697
+ if (nextOpen) {
698
+ setIsSheetOpen(true);
699
+ } else {
700
+ closeSheet();
701
+ }
702
+ },
703
+ [closeSheet],
704
+ );
705
+
706
+ useEffect(() => {
707
+ if (isEffectivelyDisabled && isSheetOpen) {
708
+ closeSheet();
709
+ }
710
+ }, [closeSheet, isEffectivelyDisabled, isSheetOpen]);
711
+
712
+ const stepMonth = useCallback(
713
+ (delta: number) => {
714
+ const target = clampIndex(
715
+ monthIndex + delta,
716
+ minMonthIndex,
717
+ maxMonthIndex,
718
+ );
719
+ if (target === monthIndex) {
720
+ return;
721
+ }
722
+ setMonthIndex(target);
723
+ scrollerHandleRef.current?.scrollToMonth(target, 'smooth');
724
+ },
725
+ [monthIndex, minMonthIndex, maxMonthIndex],
726
+ );
727
+
728
+ const handleResetInSheet = useCallback(() => {
729
+ fireChange(undefined);
730
+ setDraftTime(undefined);
731
+ const currentMonth = monthIndexOf(today);
732
+ if (currentMonth < minMonthIndex || currentMonth > maxMonthIndex) {
733
+ return;
734
+ }
735
+ setMonthIndex(currentMonth);
736
+ scrollerHandleRef.current?.scrollToMonth(currentMonth, 'smooth');
737
+ }, [fireChange, today, minMonthIndex, maxMonthIndex]);
738
+
739
+ const handleDateSelect = useCallback(
740
+ (nextDate: ISODateString) => {
741
+ const nextTime = clampTimeForDate(nextDate, timeForWheels);
742
+ setDraftTime(nextTime);
743
+ const combined = combineDateTime(nextDate, nextTime);
744
+ if (combined && combined !== optimisticValue) {
745
+ fireChange(combined);
746
+ }
747
+ },
748
+ [clampTimeForDate, fireChange, optimisticValue, timeForWheels],
749
+ );
750
+
751
+ const commitWheelTime = useCallback(
752
+ (rawTime: ISOTimeString) => {
753
+ const nextTime = clampTimeForDate(valueParts.date, rawTime);
754
+ setDraftTime(nextTime);
755
+ if (valueParts.date != null) {
756
+ const combined = combineDateTime(valueParts.date, nextTime);
757
+ if (combined && combined !== optimisticValue) {
758
+ fireChange(combined);
759
+ }
760
+ }
761
+ },
762
+ [clampTimeForDate, fireChange, optimisticValue, valueParts.date],
763
+ );
764
+
765
+ const composeWheelTime = useCallback(
766
+ ({
767
+ hour24,
768
+ hour12,
769
+ minute,
770
+ second,
771
+ meridiem,
772
+ }: {
773
+ hour24?: number;
774
+ hour12?: number;
775
+ minute?: number;
776
+ second?: number;
777
+ meridiem?: number;
778
+ }): ISOTimeString => {
779
+ const currentMeridiem = parsedWheelTime.hour < 12 ? 0 : 1;
780
+ let hour = parsedWheelTime.hour;
781
+ if (hourFormat === '24h') {
782
+ hour = hour24 ?? hour;
783
+ } else {
784
+ hour = hour24From12(
785
+ hour12 ?? hour12From24(hour),
786
+ meridiem ?? currentMeridiem,
787
+ );
788
+ }
789
+ return formatISOTime(
790
+ {
791
+ hour,
792
+ minute: minute ?? parsedWheelTime.minute,
793
+ second: hasSeconds ? (second ?? parsedWheelTime.second) : 0,
794
+ },
795
+ hasSeconds,
796
+ );
797
+ },
798
+ [hasSeconds, hourFormat, parsedWheelTime],
799
+ );
800
+
801
+ const timeBounds = timeBoundsForDate(valueParts.date);
802
+ const hourOptions: WheelOption[] = useMemo(() => {
803
+ if (hourFormat === '24h') {
804
+ return Array.from({length: 24}, (_, hour) => ({
805
+ value: hour,
806
+ label: twoDigits(hour),
807
+ isDisabled: !rangeOverlaps(
808
+ hour * 3600,
809
+ hour * 3600 + 3599,
810
+ timeBounds.min,
811
+ timeBounds.max,
812
+ ),
813
+ }));
814
+ }
815
+ const meridiem = parsedWheelTime.hour < 12 ? 0 : 1;
816
+ // In 12-hour mode the hour column is interpreted inside the active AM/PM
817
+ // half, so disabled rows describe reachability within that half only.
818
+ return Array.from({length: 12}, (_, index) => {
819
+ const hour = index + 1;
820
+ const hour24 = hour24From12(hour, meridiem);
821
+ return {
822
+ value: hour,
823
+ label: String(hour),
824
+ isDisabled: !rangeOverlaps(
825
+ hour24 * 3600,
826
+ hour24 * 3600 + 3599,
827
+ timeBounds.min,
828
+ timeBounds.max,
829
+ ),
830
+ };
831
+ });
832
+ }, [hourFormat, parsedWheelTime.hour, timeBounds.min, timeBounds.max]);
833
+
834
+ const minuteOptions: WheelOption[] = useMemo(
835
+ () =>
836
+ Array.from({length: 60}, (_, minute) => {
837
+ const start = parsedWheelTime.hour * 3600 + minute * 60;
838
+ return {
839
+ value: minute,
840
+ label: twoDigits(minute),
841
+ isDisabled: !rangeOverlaps(
842
+ start,
843
+ start + (hasSeconds ? 59 : 0),
844
+ timeBounds.min,
845
+ timeBounds.max,
846
+ ),
847
+ };
848
+ }),
849
+ [hasSeconds, parsedWheelTime.hour, timeBounds.min, timeBounds.max],
850
+ );
851
+
852
+ const secondOptions: WheelOption[] = useMemo(
853
+ () =>
854
+ Array.from({length: 60}, (_, second) => ({
855
+ value: second,
856
+ label: twoDigits(second),
857
+ isDisabled: !isTimeInRange(
858
+ formatISOTime(
859
+ {
860
+ hour: parsedWheelTime.hour,
861
+ minute: parsedWheelTime.minute,
862
+ second,
863
+ },
864
+ true,
865
+ ),
866
+ timeBounds.min,
867
+ timeBounds.max,
868
+ ),
869
+ })),
870
+ [
871
+ parsedWheelTime.hour,
872
+ parsedWheelTime.minute,
873
+ timeBounds.min,
874
+ timeBounds.max,
875
+ ],
876
+ );
877
+
878
+ const meridiemOptions: WheelOption[] = useMemo(
879
+ () => [
880
+ {
881
+ value: 0,
882
+ label: t('@astryx.dateTimeInput.meridiemAM'),
883
+ isDisabled: !rangeOverlaps(
884
+ 0,
885
+ 11 * 3600 + 3599,
886
+ timeBounds.min,
887
+ timeBounds.max,
888
+ ),
889
+ },
890
+ {
891
+ value: 1,
892
+ label: t('@astryx.dateTimeInput.meridiemPM'),
893
+ isDisabled: !rangeOverlaps(
894
+ 12 * 3600,
895
+ 23 * 3600 + 3599,
896
+ timeBounds.min,
897
+ timeBounds.max,
898
+ ),
899
+ },
900
+ ],
901
+ [t, timeBounds.min, timeBounds.max],
902
+ );
903
+
904
+ const handleVisibleMonthChange = useCallback(
905
+ (next: number) => {
906
+ if (activePanel === 'date' && !isWheelOpen) {
907
+ setMonthIndex(next);
908
+ }
909
+ },
910
+ [activePanel, isWheelOpen],
911
+ );
912
+
913
+ const monthIndexRef = useRef(monthIndex);
914
+ monthIndexRef.current = monthIndex;
915
+ useEffect(() => {
916
+ if (activePanel === 'date' && !isWheelOpen) {
917
+ scrollerHandleRef.current?.scrollToMonth(monthIndexRef.current, 'auto');
918
+ }
919
+ }, [activePanel, isWheelOpen]);
920
+
921
+ const handleInputKeyDown = useCallback(
922
+ (event: React.KeyboardEvent, panel: TouchDateTimePanel) => {
923
+ if (isImeKeyEvent(event.nativeEvent)) {
924
+ return;
925
+ }
926
+ if (
927
+ event.key === 'ArrowDown' ||
928
+ event.key === 'Enter' ||
929
+ event.key === ' ' ||
930
+ event.key === 'Spacebar'
931
+ ) {
932
+ event.preventDefault();
933
+ openSheet(panel);
934
+ }
935
+ },
936
+ [openSheet],
937
+ );
938
+
939
+ const clearFocusTimerRef = useRef<number | null>(null);
940
+ useEffect(
941
+ () => () => {
942
+ if (clearFocusTimerRef.current != null) {
943
+ clearTimeout(clearFocusTimerRef.current);
944
+ }
945
+ },
946
+ [],
947
+ );
948
+
949
+ const timeSegmentFocusTimerRef = useRef<number | null>(null);
950
+ useEffect(
951
+ () => () => {
952
+ if (timeSegmentFocusTimerRef.current != null) {
953
+ clearTimeout(timeSegmentFocusTimerRef.current);
954
+ }
955
+ },
956
+ [],
957
+ );
958
+
959
+ const handleClear = useCallback(
960
+ (event: React.MouseEvent<HTMLButtonElement>) => {
961
+ event.stopPropagation();
962
+ fireChange(undefined);
963
+ setDraftTime(undefined);
964
+ setIsSheetOpen(false);
965
+ const field = dateInputRef.current;
966
+ if (field == null) {
967
+ return;
968
+ }
969
+ clearFocusTimerRef.current = window.setTimeout(() => {
970
+ clearFocusTimerRef.current = null;
971
+ field.focus({preventScroll: true});
972
+ }, 0);
973
+ },
974
+ [fireChange],
975
+ );
976
+
977
+ const surface = (
978
+ <div {...stylex.props(styles.touchSurface)}>
979
+ <SegmentedControl
980
+ value={activePanel}
981
+ onChange={nextPanel => {
982
+ setActivePanel(nextPanel as TouchDateTimePanel);
983
+ setIsWheelOpen(false);
984
+ }}
985
+ label={t('@astryx.dateTimeInput.pickerMode')}
986
+ layout="fill">
987
+ <SegmentedControlItem
988
+ value="date"
989
+ label={t('@astryx.dateTimeInput.dateTab')}
990
+ />
991
+ <SegmentedControlItem
992
+ ref={timeSegmentRef}
993
+ value="time"
994
+ label={t('@astryx.dateTimeInput.timeTab')}
995
+ />
996
+ </SegmentedControl>
997
+
998
+ <div {...stylex.props(styles.touchPanelStack)}>
999
+ <div
1000
+ data-panel="date"
1001
+ aria-hidden={activePanel !== 'date' ? 'true' : undefined}
1002
+ inert={activePanel !== 'date' ? true : undefined}
1003
+ {...stylex.props(
1004
+ styles.touchPanel,
1005
+ activePanel !== 'date' && styles.touchPanelHidden,
1006
+ )}>
1007
+ <div {...stylex.props(styles.touchHeader)}>
1008
+ <button
1009
+ type="button"
1010
+ onClick={() => setIsWheelOpen(open => !open)}
1011
+ aria-expanded={isWheelOpen}
1012
+ aria-label={t('@astryx.dateInput.chooseMonthYear', {
1013
+ monthYear: monthYearLabel,
1014
+ })}
1015
+ {...stylex.props(
1016
+ styles.touchTitle,
1017
+ focusOutlineStyles.focusVisible,
1018
+ )}>
1019
+ <span {...stylex.props(styles.touchTitleText)}>
1020
+ {monthYearLabel}
1021
+ </span>
1022
+ <Icon icon="chevronDown" size="sm" color="secondary" />
1023
+ </button>
1024
+ <span
1025
+ inert={isWheelOpen ? true : undefined}
1026
+ {...stylex.props(
1027
+ styles.touchHeaderActions,
1028
+ isWheelOpen && styles.touchHeaderActionsHidden,
1029
+ )}>
1030
+ <IconButton
1031
+ variant="ghost"
1032
+ size="sm"
1033
+ xstyle={[
1034
+ styles.touchArrow,
1035
+ !canStepBack && styles.touchArrowUnavailable,
1036
+ ]}
1037
+ isDisabled={!canStepBack}
1038
+ onClick={() => stepMonth(-1)}
1039
+ label={t('@astryx.calendar.previousMonth')}
1040
+ icon={
1041
+ <span
1042
+ {...stylex.props(styles.touchArrowIcon, rtlStyles.mirror)}>
1043
+ <Icon icon="chevronLeft" size="sm" color="inherit" />
1044
+ </span>
1045
+ }
1046
+ />
1047
+ <IconButton
1048
+ variant="ghost"
1049
+ size="sm"
1050
+ xstyle={[
1051
+ styles.touchArrow,
1052
+ !canStepForward && styles.touchArrowUnavailable,
1053
+ ]}
1054
+ isDisabled={!canStepForward}
1055
+ onClick={() => stepMonth(1)}
1056
+ label={t('@astryx.calendar.nextMonth')}
1057
+ icon={
1058
+ <span
1059
+ {...stylex.props(styles.touchArrowIcon, rtlStyles.mirror)}>
1060
+ <Icon icon="chevronRight" size="sm" color="inherit" />
1061
+ </span>
1062
+ }
1063
+ />
1064
+ <Button
1065
+ variant="ghost"
1066
+ size="sm"
1067
+ xstyle={styles.touchResetButton}
1068
+ label={t('@astryx.dateInput.resetPicking')}
1069
+ onClick={handleResetInSheet}
1070
+ />
1071
+ </span>
1072
+ </div>
1073
+
1074
+ <div {...stylex.props(styles.touchDateSurfaceStack)}>
1075
+ <div
1076
+ data-date-surface="calendar"
1077
+ aria-hidden={isWheelOpen ? 'true' : undefined}
1078
+ inert={isWheelOpen ? true : undefined}
1079
+ {...stylex.props(
1080
+ styles.touchDateSurface,
1081
+ isWheelOpen && styles.touchDateSurfaceHidden,
1082
+ )}>
1083
+ <div aria-hidden="true" {...stylex.props(styles.touchWeekdays)}>
1084
+ {dayNames.map(name => (
1085
+ <div key={name} {...stylex.props(styles.touchWeekday)}>
1086
+ {name}
1087
+ </div>
1088
+ ))}
1089
+ </div>
1090
+ <MonthScroller
1091
+ key={`${minMonthIndex}:${maxMonthIndex}`}
1092
+ handleRef={scrollerHandleRef}
1093
+ minMonthIndex={minMonthIndex}
1094
+ maxMonthIndex={maxMonthIndex}
1095
+ initialMonthIndex={monthIndex}
1096
+ onVisibleMonthChange={handleVisibleMonthChange}
1097
+ selectedDate={selectedDate}
1098
+ today={today}
1099
+ isDateDisabled={isDateDisabled}
1100
+ weekStartsOn={weekStartsOn}
1101
+ onSelect={handleDateSelect}
1102
+ />
1103
+ <div {...stylex.props(styles.touchFooter)}>
1104
+ <Button
1105
+ variant="primary"
1106
+ size="md"
1107
+ width="100%"
1108
+ label={t('@astryx.dateTimeInput.saveDatePicking')}
1109
+ isDisabled={selectedDate == null}
1110
+ onClick={() => {
1111
+ setIsWheelOpen(false);
1112
+ setActivePanel('time');
1113
+ if (timeSegmentFocusTimerRef.current != null) {
1114
+ clearTimeout(timeSegmentFocusTimerRef.current);
1115
+ }
1116
+ timeSegmentFocusTimerRef.current = window.setTimeout(() => {
1117
+ timeSegmentFocusTimerRef.current = null;
1118
+ timeSegmentRef.current?.focus({preventScroll: true});
1119
+ }, 0);
1120
+ }}
1121
+ />
1122
+ </div>
1123
+ </div>
1124
+
1125
+ <div
1126
+ data-date-surface="wheels"
1127
+ aria-hidden={!isWheelOpen ? 'true' : undefined}
1128
+ inert={!isWheelOpen ? true : undefined}
1129
+ {...stylex.props(
1130
+ styles.touchDateSurface,
1131
+ !isWheelOpen && styles.touchDateSurfaceHidden,
1132
+ )}>
1133
+ <div
1134
+ aria-hidden="true"
1135
+ {...stylex.props(styles.touchWheelSpacer)}
1136
+ />
1137
+ <MonthYearWheels
1138
+ monthIndex={monthIndex}
1139
+ minMonthIndex={minMonthIndex}
1140
+ maxMonthIndex={maxMonthIndex}
1141
+ onChange={next => {
1142
+ setMonthIndex(next);
1143
+ scrollerHandleRef.current?.scrollToMonth(next, 'auto');
1144
+ }}
1145
+ monthLabel={t('@astryx.dateInput.monthWheel')}
1146
+ yearLabel={t('@astryx.dateInput.yearWheel')}
1147
+ isActive={activePanel === 'date' && isWheelOpen}
1148
+ />
1149
+ <div {...stylex.props(styles.touchFooter)}>
1150
+ <Button
1151
+ variant="secondary"
1152
+ size="md"
1153
+ width="100%"
1154
+ label={t('@astryx.dateInput.doneChoosingMonth')}
1155
+ onClick={() => setIsWheelOpen(false)}
1156
+ />
1157
+ </div>
1158
+ </div>
1159
+ </div>
1160
+ </div>
1161
+
1162
+ <div
1163
+ data-panel="time"
1164
+ role="group"
1165
+ aria-label={resolvedTimeLabel}
1166
+ aria-hidden={activePanel !== 'time' ? 'true' : undefined}
1167
+ inert={activePanel !== 'time' ? true : undefined}
1168
+ {...stylex.props(
1169
+ styles.touchPanel,
1170
+ activePanel !== 'time' && styles.touchPanelHidden,
1171
+ )}>
1172
+ <div {...stylex.props(styles.touchTimeWheels)}>
1173
+ <Wheel
1174
+ label={t('@astryx.dateTimeInput.hourWheel')}
1175
+ options={hourOptions}
1176
+ value={
1177
+ hourFormat === '24h'
1178
+ ? parsedWheelTime.hour
1179
+ : hour12From24(parsedWheelTime.hour)
1180
+ }
1181
+ isActive={activePanel === 'time'}
1182
+ onChange={hour =>
1183
+ commitWheelTime(
1184
+ hourFormat === '24h'
1185
+ ? composeWheelTime({hour24: hour})
1186
+ : composeWheelTime({hour12: hour}),
1187
+ )
1188
+ }
1189
+ />
1190
+ <Wheel
1191
+ label={t('@astryx.dateTimeInput.minuteWheel')}
1192
+ options={minuteOptions}
1193
+ value={parsedWheelTime.minute}
1194
+ isActive={activePanel === 'time'}
1195
+ onChange={minute => commitWheelTime(composeWheelTime({minute}))}
1196
+ />
1197
+ {hasSeconds && (
1198
+ <Wheel
1199
+ label={t('@astryx.dateTimeInput.secondWheel')}
1200
+ options={secondOptions}
1201
+ value={parsedWheelTime.second}
1202
+ isActive={activePanel === 'time'}
1203
+ onChange={second => commitWheelTime(composeWheelTime({second}))}
1204
+ />
1205
+ )}
1206
+ {hourFormat === '12h' && (
1207
+ <Wheel
1208
+ label={t('@astryx.dateTimeInput.meridiemWheel')}
1209
+ options={meridiemOptions}
1210
+ value={parsedWheelTime.hour < 12 ? 0 : 1}
1211
+ isActive={activePanel === 'time'}
1212
+ onChange={meridiem =>
1213
+ commitWheelTime(composeWheelTime({meridiem}))
1214
+ }
1215
+ />
1216
+ )}
1217
+ </div>
1218
+ <div {...stylex.props(styles.touchFooter)}>
1219
+ <Button
1220
+ variant="primary"
1221
+ size="md"
1222
+ width="100%"
1223
+ label={t('@astryx.dateInput.savePicking')}
1224
+ onClick={closeSheet}
1225
+ />
1226
+ </div>
1227
+ </div>
1228
+ </div>
1229
+ </div>
1230
+ );
1231
+
1232
+ return (
1233
+ <Field
1234
+ label={label}
1235
+ isLabelHidden={isLabelHidden}
1236
+ description={description}
1237
+ inputID={dateInputId}
1238
+ descriptionID={description ? descriptionID : undefined}
1239
+ isOptional={isOptional}
1240
+ isRequired={isRequired}
1241
+ isDisabled={isDisabled}
1242
+ status={
1243
+ status
1244
+ ? {
1245
+ type: status.type,
1246
+ message: status.message,
1247
+ messageID: status.message ? statusMessageID : undefined,
1248
+ }
1249
+ : undefined
1250
+ }
1251
+ labelTooltip={labelTooltip}
1252
+ statusVariant="detached"
1253
+ width={width}>
1254
+ <div
1255
+ ref={el => {
1256
+ disabledMessageTooltip.ref(el);
1257
+ }}
1258
+ {...rest}
1259
+ {...mergeProps(
1260
+ themeProps('date-time-input', {
1261
+ size,
1262
+ status: status?.type ?? null,
1263
+ disabled: isDisabled ? 'disabled' : null,
1264
+ }),
1265
+ stylex.props(styles.touchRow, xstyle),
1266
+ className,
1267
+ style,
1268
+ )}>
1269
+ <div
1270
+ onClick={event => {
1271
+ if (event.target === event.currentTarget) {
1272
+ openSheet('date');
1273
+ }
1274
+ }}
1275
+ {...mergeProps(
1276
+ themeProps('date-time-input-date-segment', {
1277
+ size,
1278
+ status: status?.type ?? null,
1279
+ }),
1280
+ stylex.props(
1281
+ inputWrapperStyles.base,
1282
+ sizeStyles[size],
1283
+ styles.touchDateWrapper,
1284
+ isEffectivelyDisabled && inputWrapperStyles.disabled,
1285
+ status && inputStatusBorderStyles[status.type],
1286
+ status &&
1287
+ !isEffectivelyDisabled &&
1288
+ inputStatusHoverShadowStyles[status.type],
1289
+ status && inputStatusFocusWithinStyles[status.type],
1290
+ ),
1291
+ )}>
1292
+ <button
1293
+ type="button"
1294
+ onClick={() => openSheet('date')}
1295
+ disabled={isEffectivelyDisabled}
1296
+ aria-label={t('@astryx.dateInput.openCalendar')}
1297
+ tabIndex={-1}
1298
+ {...stylex.props(
1299
+ focusOutlineStyles.focusVisible,
1300
+ styles.iconButton,
1301
+ isEffectivelyDisabled && styles.iconButtonDisabled,
1302
+ )}>
1303
+ <Icon
1304
+ icon="calendar"
1305
+ size="sm"
1306
+ color="secondary"
1307
+ {...themeProps('date-time-input-toggle-icon', {
1308
+ state: isSheetOpen ? 'expanded' : 'collapsed',
1309
+ })}
1310
+ />
1311
+ </button>
1312
+ <input
1313
+ ref={mergedDateInputRef}
1314
+ id={dateInputId}
1315
+ type="text"
1316
+ role="combobox"
1317
+ value={dateDisplayValue}
1318
+ readOnly
1319
+ inputMode="none"
1320
+ onChange={() => {}}
1321
+ onClick={() => openSheet('date')}
1322
+ onKeyDown={event => handleInputKeyDown(event, 'date')}
1323
+ placeholder={placeholder}
1324
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
1325
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
1326
+ aria-describedby={ariaDescribedBy}
1327
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
1328
+ aria-invalid={status?.type === 'error' ? 'true' : undefined}
1329
+ aria-busy={isBusy || undefined}
1330
+ aria-expanded={isSheetOpen && activePanel === 'date'}
1331
+ aria-haspopup="dialog"
1332
+ aria-autocomplete="none"
1333
+ autoComplete="off"
1334
+ {...stylex.props(
1335
+ styles.input,
1336
+ styles.touchInput,
1337
+ isEffectivelyDisabled && styles.inputDisabled,
1338
+ )}
1339
+ />
1340
+ {hasClear && value !== undefined && !isEffectivelyDisabled && (
1341
+ <InputClearButton
1342
+ label={t('@astryx.dateInput.clear', {label})}
1343
+ onClick={handleClear}
1344
+ />
1345
+ )}
1346
+ {isBusy && <Spinner size="sm" />}
1347
+ {isRenderable(statusIcon) && statusIcon}
1348
+ </div>
1349
+
1350
+ <div
1351
+ onClick={event => {
1352
+ if (event.target === event.currentTarget) {
1353
+ openSheet('time');
1354
+ }
1355
+ }}
1356
+ {...mergeProps(
1357
+ themeProps('date-time-input-time-segment', {
1358
+ size,
1359
+ status: status?.type ?? null,
1360
+ }),
1361
+ stylex.props(
1362
+ inputWrapperStyles.base,
1363
+ sizeStyles[size],
1364
+ styles.touchTimeWrapper,
1365
+ isEffectivelyDisabled && inputWrapperStyles.disabled,
1366
+ status && inputStatusBorderStyles[status.type],
1367
+ status &&
1368
+ !isEffectivelyDisabled &&
1369
+ inputStatusHoverShadowStyles[status.type],
1370
+ status && inputStatusFocusWithinStyles[status.type],
1371
+ ),
1372
+ )}>
1373
+ <button
1374
+ type="button"
1375
+ onClick={() => openSheet('time')}
1376
+ disabled={isEffectivelyDisabled}
1377
+ aria-label={t('@astryx.dateTimeInput.openTimePicker', {
1378
+ label: resolvedTimeLabel,
1379
+ })}
1380
+ tabIndex={-1}
1381
+ {...stylex.props(
1382
+ focusOutlineStyles.focusVisible,
1383
+ styles.iconButton,
1384
+ isEffectivelyDisabled && styles.iconButtonDisabled,
1385
+ )}>
1386
+ <Icon
1387
+ icon="clock"
1388
+ size="sm"
1389
+ color="secondary"
1390
+ {...themeProps('date-time-input-clock-icon')}
1391
+ />
1392
+ </button>
1393
+ <input
1394
+ ref={timeInputRef}
1395
+ id={timeInputId}
1396
+ type="text"
1397
+ role="combobox"
1398
+ value={timeDisplayValue}
1399
+ readOnly
1400
+ inputMode="none"
1401
+ onChange={() => {}}
1402
+ onClick={() => openSheet('time')}
1403
+ onKeyDown={event => handleInputKeyDown(event, 'time')}
1404
+ placeholder={timePlaceholder}
1405
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
1406
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
1407
+ aria-label={resolvedTimeLabel}
1408
+ aria-describedby={ariaDescribedBy}
1409
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
1410
+ aria-invalid={status?.type === 'error' ? 'true' : undefined}
1411
+ aria-busy={isBusy || undefined}
1412
+ aria-expanded={isSheetOpen && activePanel === 'time'}
1413
+ aria-haspopup="dialog"
1414
+ aria-autocomplete="none"
1415
+ autoComplete="off"
1416
+ {...stylex.props(
1417
+ styles.input,
1418
+ styles.touchInput,
1419
+ isEffectivelyDisabled && styles.inputDisabled,
1420
+ )}
1421
+ />
1422
+ </div>
1423
+ <BottomSheet
1424
+ isOpen={isSheetOpen}
1425
+ onOpenChange={handleSheetOpenChange}
1426
+ label={t('@astryx.dateTimeInput.dialogLabel')}
1427
+ height="hug">
1428
+ <div {...stylex.props(styles.touchSheetBody)}>{surface}</div>
1429
+ </BottomSheet>
1430
+ {showsDisabledMessage &&
1431
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
1432
+ </div>
1433
+ </Field>
1434
+ );
1435
+ }
1436
+
1437
+ TouchDateTimeField.displayName = 'TouchDateTimeField';