@astryxdesign/core 0.4.7 → 0.5.0-canary.32a62fd

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 (554) hide show
  1. package/CHANGELOG.md +161 -1
  2. package/dist/AppShell/AppShell.d.ts.map +1 -1
  3. package/dist/AppShell/AppShell.js +3 -2
  4. package/dist/AspectRatio/AspectRatio.d.ts +24 -0
  5. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  6. package/dist/AspectRatio/AspectRatio.js +23 -4
  7. package/dist/Avatar/Avatar.d.ts +25 -1
  8. package/dist/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/Avatar/Avatar.js +58 -19
  10. package/dist/Avatar/index.d.ts +2 -2
  11. package/dist/Avatar/index.d.ts.map +1 -1
  12. package/dist/Avatar/index.js +1 -1
  13. package/dist/AvatarGroup/AvatarGroup.d.ts +9 -2
  14. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  15. package/dist/AvatarGroup/AvatarGroup.js +8 -4
  16. package/dist/AvatarGroup/AvatarGroupContext.d.ts +2 -1
  17. package/dist/AvatarGroup/AvatarGroupContext.d.ts.map +1 -1
  18. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  19. package/dist/AvatarGroup/AvatarGroupOverflow.js +10 -6
  20. package/dist/Banner/Banner.d.ts +40 -15
  21. package/dist/Banner/Banner.d.ts.map +1 -1
  22. package/dist/Banner/Banner.js +51 -18
  23. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  24. package/dist/BottomSheet/BottomSheet.js +17 -4
  25. package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
  26. package/dist/BottomSheet/BottomSheetSwitcher.js +3 -2
  27. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  28. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -10
  29. package/dist/Button/Button.d.ts.map +1 -1
  30. package/dist/Button/Button.js +5 -4
  31. package/dist/ButtonGroup/ButtonGroup.d.ts +5 -1
  32. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  33. package/dist/ButtonGroup/ButtonGroup.js +26 -7
  34. package/dist/Calendar/Calendar.d.ts +3 -1
  35. package/dist/Calendar/Calendar.d.ts.map +1 -1
  36. package/dist/Calendar/Calendar.js +13 -14
  37. package/dist/Calendar/getInitialFocusDate.d.ts +46 -0
  38. package/dist/Calendar/getInitialFocusDate.d.ts.map +1 -0
  39. package/dist/Calendar/getInitialFocusDate.js +62 -0
  40. package/dist/Carousel/Carousel.d.ts.map +1 -1
  41. package/dist/Carousel/Carousel.js +3 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +2 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayout.js +3 -3
  46. package/dist/Chat/ChatMessageList.d.ts +22 -3
  47. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessageList.js +6 -3
  49. package/dist/Chat/ChatTokenizedText.d.ts +2 -0
  50. package/dist/Chat/ChatTokenizedText.d.ts.map +1 -1
  51. package/dist/Chat/ChatTokenizedText.js +11 -6
  52. package/dist/Chat/useChatComposerTokens.js +1 -1
  53. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  54. package/dist/CheckboxInput/CheckboxInput.js +10 -3
  55. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  56. package/dist/ClickableCard/ClickableCard.js +3 -3
  57. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  58. package/dist/CodeBlock/CodeBlock.js +15 -4
  59. package/dist/CodeBlock/highlightRanges.js +1 -1
  60. package/dist/CodeBlock/highlightStyles.js +13 -13
  61. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  62. package/dist/CommandPalette/CommandPaletteInput.js +3 -2
  63. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  64. package/dist/CommandPalette/CommandPaletteItem.js +3 -2
  65. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  66. package/dist/ContextMenu/ContextMenu.js +4 -3
  67. package/dist/DateInput/DateInput.d.ts +43 -0
  68. package/dist/DateInput/DateInput.d.ts.map +1 -1
  69. package/dist/DateInput/DateInput.js +22 -2
  70. package/dist/DateInput/NativeDateField.d.ts +10 -0
  71. package/dist/DateInput/NativeDateField.d.ts.map +1 -0
  72. package/dist/DateInput/NativeDateField.js +477 -0
  73. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  74. package/dist/DateInput/TouchDateField.js +38 -4
  75. package/dist/DateInput/index.d.ts +1 -1
  76. package/dist/DateInput/index.d.ts.map +1 -1
  77. package/dist/DateTimeInput/DateTimeInput.d.ts +21 -1
  78. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  79. package/dist/DateTimeInput/DateTimeInput.js +406 -12
  80. package/dist/DateTimeInput/index.d.ts +1 -1
  81. package/dist/DateTimeInput/index.d.ts.map +1 -1
  82. package/dist/Dialog/Dialog.d.ts +7 -1
  83. package/dist/Dialog/Dialog.d.ts.map +1 -1
  84. package/dist/Dialog/Dialog.js +89 -58
  85. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  86. package/dist/DropdownMenu/DropdownMenuSubMenu.js +6 -5
  87. package/dist/Field/FieldLabel.d.ts.map +1 -1
  88. package/dist/Field/FieldLabel.js +9 -1
  89. package/dist/FileInput/FileInput.d.ts.map +1 -1
  90. package/dist/FileInput/FileInput.js +3 -2
  91. package/dist/Heading/Heading.d.ts.map +1 -1
  92. package/dist/Heading/Heading.js +6 -2
  93. package/dist/HoverCard/HoverCard.d.ts +6 -0
  94. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  95. package/dist/HoverCard/HoverCard.js +52 -3
  96. package/dist/HoverCard/useHoverCard.d.ts +16 -2
  97. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  98. package/dist/HoverCard/useHoverCard.js +74 -26
  99. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  100. package/dist/Indicator/CheckboxIndicator.js +6 -2
  101. package/dist/Item/Item.d.ts.map +1 -1
  102. package/dist/Item/Item.js +4 -2
  103. package/dist/Kbd/Kbd.d.ts.map +1 -1
  104. package/dist/Kbd/Kbd.js +2 -19
  105. package/dist/Layer/LayerDepthContext.d.ts +52 -0
  106. package/dist/Layer/LayerDepthContext.d.ts.map +1 -0
  107. package/dist/Layer/LayerDepthContext.js +66 -0
  108. package/dist/Layer/index.d.ts +3 -0
  109. package/dist/Layer/index.d.ts.map +1 -1
  110. package/dist/Layer/index.js +4 -0
  111. package/dist/Layer/layerStack.d.ts +98 -0
  112. package/dist/Layer/layerStack.d.ts.map +1 -0
  113. package/dist/Layer/layerStack.js +306 -0
  114. package/dist/Layer/useLayerDismissal.d.ts +93 -0
  115. package/dist/Layer/useLayerDismissal.d.ts.map +1 -0
  116. package/dist/Layer/useLayerDismissal.js +91 -0
  117. package/dist/Lightbox/Lightbox.d.ts +1 -0
  118. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  119. package/dist/Lightbox/Lightbox.js +103 -81
  120. package/dist/Markdown/index.d.ts +1 -1
  121. package/dist/Markdown/index.d.ts.map +1 -1
  122. package/dist/Markdown/parser.d.ts +38 -1
  123. package/dist/Markdown/parser.d.ts.map +1 -1
  124. package/dist/Markdown/parser.js +177 -29
  125. package/dist/MobileNav/MobileNav.d.ts +6 -1
  126. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  127. package/dist/MobileNav/MobileNav.js +61 -36
  128. package/dist/MultiSelector/MultiSelector.d.ts +15 -1
  129. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  130. package/dist/MultiSelector/MultiSelector.js +17 -6
  131. package/dist/MultiSelector/index.d.ts +1 -1
  132. package/dist/MultiSelector/index.d.ts.map +1 -1
  133. package/dist/NavIcon/NavIcon.d.ts.map +1 -1
  134. package/dist/NavIcon/NavIcon.js +5 -1
  135. package/dist/NavMenu/NavHeadingMenu.d.ts.map +1 -1
  136. package/dist/NavMenu/NavHeadingMenu.js +3 -2
  137. package/dist/NumberInput/NumberInput.d.ts +1 -0
  138. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  139. package/dist/NumberInput/NumberInput.js +27 -37
  140. package/dist/NumberInput/numberParser.d.ts +43 -0
  141. package/dist/NumberInput/numberParser.d.ts.map +1 -0
  142. package/dist/NumberInput/numberParser.js +293 -0
  143. package/dist/Outline/Outline.d.ts.map +1 -1
  144. package/dist/Outline/Outline.js +3 -2
  145. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  146. package/dist/OverflowList/OverflowList.js +3 -2
  147. package/dist/Overlay/Overlay.d.ts.map +1 -1
  148. package/dist/Overlay/Overlay.js +3 -2
  149. package/dist/Popover/usePopover.d.ts.map +1 -1
  150. package/dist/Popover/usePopover.js +20 -17
  151. package/dist/PowerSearch/PowerSearch.d.ts +8 -2
  152. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  153. package/dist/PowerSearch/PowerSearch.js +19 -6
  154. package/dist/PowerSearch/PowerSearchEditPopover.d.ts +3 -1
  155. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  156. package/dist/PowerSearch/PowerSearchEditPopover.js +28 -6
  157. package/dist/PowerSearch/PowerSearchValueEditor.d.ts +3 -1
  158. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  159. package/dist/PowerSearch/PowerSearchValueEditor.js +19 -9
  160. package/dist/PowerSearch/types.d.ts +2 -0
  161. package/dist/PowerSearch/types.d.ts.map +1 -1
  162. package/dist/PowerSearch/usePowerSearchSource.d.ts +6 -1
  163. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  164. package/dist/PowerSearch/usePowerSearchSource.js +14 -5
  165. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  166. package/dist/ProgressBar/ProgressBar.js +18 -5
  167. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  168. package/dist/Resizable/ResizeHandle.js +3 -2
  169. package/dist/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  170. package/dist/SegmentedControl/SegmentedControl.js +3 -2
  171. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  172. package/dist/SelectableCard/SelectableCard.js +3 -3
  173. package/dist/Selector/Selector.d.ts.map +1 -1
  174. package/dist/Selector/Selector.js +5 -0
  175. package/dist/SideNav/SideNav.d.ts.map +1 -1
  176. package/dist/SideNav/SideNav.js +4 -3
  177. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  178. package/dist/SideNav/SideNavHeading.js +8 -7
  179. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  180. package/dist/SideNav/SideNavItem.js +4 -3
  181. package/dist/Slider/Slider.d.ts.map +1 -1
  182. package/dist/Slider/Slider.js +3 -3
  183. package/dist/Spinner/Spinner.d.ts +3 -3
  184. package/dist/Spinner/Spinner.d.ts.map +1 -1
  185. package/dist/Spinner/Spinner.js +130 -78
  186. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  187. package/dist/StatusDot/StatusDot.js +6 -1
  188. package/dist/Stepper/Step.d.ts +140 -0
  189. package/dist/Stepper/Step.d.ts.map +1 -0
  190. package/dist/Stepper/Step.js +1045 -0
  191. package/dist/Stepper/StepStatus.d.ts +24 -0
  192. package/dist/Stepper/StepStatus.d.ts.map +1 -0
  193. package/dist/Stepper/StepStatus.js +1 -0
  194. package/dist/Stepper/Stepper.d.ts +93 -0
  195. package/dist/Stepper/Stepper.d.ts.map +1 -0
  196. package/dist/Stepper/Stepper.js +184 -0
  197. package/dist/Stepper/StepperContext.d.ts +41 -0
  198. package/dist/Stepper/StepperContext.d.ts.map +1 -0
  199. package/dist/Stepper/StepperContext.js +34 -0
  200. package/dist/Stepper/index.d.ts +8 -0
  201. package/dist/Stepper/index.d.ts.map +1 -0
  202. package/dist/Stepper/index.js +7 -0
  203. package/dist/Stepper/stepper.stylex.d.ts +15 -0
  204. package/dist/Stepper/stepper.stylex.d.ts.map +1 -0
  205. package/dist/Stepper/stepper.stylex.js +20 -0
  206. package/dist/Switch/Switch.d.ts.map +1 -1
  207. package/dist/Switch/Switch.js +8 -3
  208. package/dist/TabList/Tab.d.ts +15 -2
  209. package/dist/TabList/Tab.d.ts.map +1 -1
  210. package/dist/TabList/Tab.js +32 -7
  211. package/dist/TabList/TabList.d.ts +46 -7
  212. package/dist/TabList/TabList.d.ts.map +1 -1
  213. package/dist/TabList/TabList.js +353 -23
  214. package/dist/TabList/TabListContext.d.ts +11 -1
  215. package/dist/TabList/TabListContext.d.ts.map +1 -1
  216. package/dist/TabList/TabListContext.js +11 -2
  217. package/dist/TabList/TabMenu.d.ts.map +1 -1
  218. package/dist/TabList/TabMenu.js +3 -2
  219. package/dist/TabList/index.d.ts +2 -2
  220. package/dist/TabList/index.d.ts.map +1 -1
  221. package/dist/Table/BaseTable.d.ts.map +1 -1
  222. package/dist/Table/BaseTable.js +10 -2
  223. package/dist/Table/Table.d.ts.map +1 -1
  224. package/dist/Table/Table.js +3 -6
  225. package/dist/Table/TableRow.d.ts +16 -5
  226. package/dist/Table/TableRow.d.ts.map +1 -1
  227. package/dist/Table/TableRow.js +16 -5
  228. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  229. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +20 -8
  230. package/dist/Table/plugins/pagination/useTablePagination.d.ts +3 -0
  231. package/dist/Table/plugins/pagination/useTablePagination.d.ts.map +1 -1
  232. package/dist/Table/plugins/pagination/useTablePagination.js +21 -4
  233. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  234. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  235. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -1
  236. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +10 -3
  237. package/dist/Table/plugins/selection/useTableSelection.d.ts +16 -0
  238. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  239. package/dist/Table/plugins/selection/useTableSelection.js +19 -5
  240. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  241. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +3 -4
  242. package/dist/Table/types.d.ts +22 -4
  243. package/dist/Table/types.d.ts.map +1 -1
  244. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  245. package/dist/Table/useBaseTablePlugins.js +5 -0
  246. package/dist/Text/Text.d.ts.map +1 -1
  247. package/dist/Text/Text.js +6 -2
  248. package/dist/TextArea/TextArea.d.ts.map +1 -1
  249. package/dist/TextArea/TextArea.js +9 -3
  250. package/dist/TextInput/TextInput.d.ts.map +1 -1
  251. package/dist/TextInput/TextInput.js +3 -2
  252. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  253. package/dist/TimeInput/TimeInput.js +3 -2
  254. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  255. package/dist/Timestamp/Timestamp.js +5 -3
  256. package/dist/Token/TokenLink.d.ts.map +1 -1
  257. package/dist/Token/TokenLink.js +2 -2
  258. package/dist/Tokenizer/Tokenizer.d.ts +3 -1
  259. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  260. package/dist/Tokenizer/Tokenizer.js +2 -0
  261. package/dist/Tooltip/Tooltip.d.ts +5 -0
  262. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  263. package/dist/Tooltip/useTooltip.d.ts +5 -0
  264. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  265. package/dist/Tooltip/useTooltip.js +39 -24
  266. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  267. package/dist/TopNav/TopNavHeading.js +5 -4
  268. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  269. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  270. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  271. package/dist/TopNav/TopNavMenu.js +4 -3
  272. package/dist/Typeahead/BaseTypeahead.d.ts +2 -0
  273. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  274. package/dist/Typeahead/BaseTypeahead.js +68 -29
  275. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  276. package/dist/Typeahead/Typeahead.js +3 -2
  277. package/dist/astryx.css +94 -7
  278. package/dist/hooks/index.d.ts +1 -0
  279. package/dist/hooks/index.d.ts.map +1 -1
  280. package/dist/hooks/index.js +1 -0
  281. package/dist/hooks/useFocusTrap.d.ts +7 -4
  282. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  283. package/dist/hooks/useFocusTrap.js +60 -95
  284. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  285. package/dist/hooks/useHotkeys.js +3 -18
  286. package/dist/hooks/useMergedRefs.d.ts +18 -0
  287. package/dist/hooks/useMergedRefs.d.ts.map +1 -0
  288. package/dist/hooks/useMergedRefs.js +25 -0
  289. package/dist/index.d.ts +1 -0
  290. package/dist/index.d.ts.map +1 -1
  291. package/dist/index.js +1 -0
  292. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  293. package/dist/theme/derivedVarRegistry.js +24 -4
  294. package/dist/theme/syntax/tokens.d.ts +1 -1
  295. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  296. package/dist/theme/syntax/tokens.js +5 -2
  297. package/dist/utils/groupItems.d.ts +5 -1
  298. package/dist/utils/groupItems.d.ts.map +1 -1
  299. package/dist/utils/groupItems.js +9 -9
  300. package/dist/utils/index.d.ts +1 -1
  301. package/dist/utils/index.d.ts.map +1 -1
  302. package/dist/utils/isApplePlatform.d.ts +18 -0
  303. package/dist/utils/isApplePlatform.d.ts.map +1 -0
  304. package/dist/utils/isApplePlatform.js +37 -0
  305. package/dist/utils/sharedResizeObserver.d.ts.map +1 -1
  306. package/dist/utils/sharedResizeObserver.js +10 -1
  307. package/dist/utils/timeParser.d.ts +1 -1
  308. package/dist/utils/timeParser.d.ts.map +1 -1
  309. package/dist/utils/timeParser.js +26 -12
  310. package/locales/en.json +31 -7
  311. package/locales/pseudo.json +18 -0
  312. package/package.json +8 -3
  313. package/src/AppShell/AppShell.tsx +3 -2
  314. package/src/AspectRatio/AspectRatio.doc.mjs +6 -3
  315. package/src/AspectRatio/AspectRatio.test.tsx +57 -10
  316. package/src/AspectRatio/AspectRatio.tsx +40 -1
  317. package/src/Avatar/Avatar.doc.mjs +14 -3
  318. package/src/Avatar/Avatar.test.tsx +97 -0
  319. package/src/Avatar/Avatar.tsx +125 -52
  320. package/src/Avatar/index.ts +2 -2
  321. package/src/AvatarGroup/AvatarGroup.doc.mjs +8 -2
  322. package/src/AvatarGroup/AvatarGroup.test.tsx +24 -0
  323. package/src/AvatarGroup/AvatarGroup.tsx +15 -6
  324. package/src/AvatarGroup/AvatarGroupContext.ts +5 -3
  325. package/src/AvatarGroup/AvatarGroupOverflow.tsx +10 -4
  326. package/src/Banner/Banner.doc.mjs +13 -11
  327. package/src/Banner/Banner.test.tsx +102 -15
  328. package/src/Banner/Banner.tsx +72 -25
  329. package/src/BottomSheet/BottomSheet.test.tsx +123 -0
  330. package/src/BottomSheet/BottomSheet.tsx +17 -4
  331. package/src/BottomSheet/BottomSheetSwitcher.tsx +3 -2
  332. package/src/Breadcrumbs/BreadcrumbItem.tsx +9 -5
  333. package/src/Button/Button.doc.mjs +22 -0
  334. package/src/Button/Button.test.tsx +13 -0
  335. package/src/Button/Button.tsx +5 -4
  336. package/src/ButtonGroup/ButtonGroup.doc.mjs +10 -1
  337. package/src/ButtonGroup/ButtonGroup.test.tsx +391 -1
  338. package/src/ButtonGroup/ButtonGroup.tsx +23 -5
  339. package/src/Calendar/Calendar.doc.mjs +4 -3
  340. package/src/Calendar/Calendar.test.tsx +52 -0
  341. package/src/Calendar/Calendar.tsx +18 -15
  342. package/src/Calendar/getInitialFocusDate.test.ts +94 -0
  343. package/src/Calendar/getInitialFocusDate.ts +93 -0
  344. package/src/Carousel/Carousel.tsx +3 -2
  345. package/src/Chat/ChatComposerInput.tsx +5 -1
  346. package/src/Chat/ChatLayout.tsx +3 -2
  347. package/src/Chat/ChatMessageList.doc.mjs +9 -1
  348. package/src/Chat/ChatMessageList.test.tsx +46 -0
  349. package/src/Chat/ChatMessageList.tsx +28 -4
  350. package/src/Chat/ChatTokenizedText.tsx +17 -10
  351. package/src/Chat/useChatComposerTokens.ts +1 -1
  352. package/src/CheckboxInput/CheckboxInput.doc.mjs +2 -0
  353. package/src/CheckboxInput/CheckboxInput.test.tsx +15 -0
  354. package/src/CheckboxInput/CheckboxInput.tsx +8 -2
  355. package/src/ClickableCard/ClickableCard.tsx +3 -2
  356. package/src/CodeBlock/CodeBlock.doc.mjs +16 -4
  357. package/src/CodeBlock/CodeBlock.test.tsx +37 -0
  358. package/src/CodeBlock/CodeBlock.tsx +20 -4
  359. package/src/CodeBlock/highlightRanges.ts +1 -1
  360. package/src/CodeBlock/highlightStyles.ts +13 -13
  361. package/src/CommandPalette/CommandPaletteInput.tsx +3 -2
  362. package/src/CommandPalette/CommandPaletteItem.tsx +3 -2
  363. package/src/ContextMenu/ContextMenu.doc.mjs +5 -0
  364. package/src/ContextMenu/ContextMenu.tsx +4 -3
  365. package/src/DateInput/DateInput.doc.mjs +16 -0
  366. package/src/DateInput/DateInput.tsx +55 -2
  367. package/src/DateInput/DateInputTouch.test.tsx +68 -9
  368. package/src/DateInput/NativeDateField.test.tsx +565 -0
  369. package/src/DateInput/NativeDateField.tsx +623 -0
  370. package/src/DateInput/TouchDateField.tsx +38 -4
  371. package/src/DateInput/index.ts +1 -0
  372. package/src/DateTimeInput/DateTimeInput.doc.mjs +18 -0
  373. package/src/DateTimeInput/DateTimeInput.test.tsx +1088 -0
  374. package/src/DateTimeInput/DateTimeInput.tsx +556 -24
  375. package/src/DateTimeInput/index.ts +1 -0
  376. package/src/Dialog/Dialog.doc.mjs +8 -3
  377. package/src/Dialog/Dialog.test.tsx +261 -1
  378. package/src/Dialog/Dialog.tsx +109 -58
  379. package/src/DropdownMenu/DropdownMenu.doc.mjs +6 -0
  380. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +8 -5
  381. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +8 -11
  382. package/src/Field/Field.doc.mjs +13 -2
  383. package/src/Field/FieldLabel.test.tsx +29 -0
  384. package/src/Field/FieldLabel.tsx +7 -0
  385. package/src/FileInput/FileInput.tsx +3 -2
  386. package/src/Heading/Heading.test.tsx +23 -1
  387. package/src/Heading/Heading.tsx +6 -2
  388. package/src/HoverCard/HoverCard.doc.mjs +13 -2
  389. package/src/HoverCard/HoverCard.test.tsx +101 -4
  390. package/src/HoverCard/HoverCard.tsx +71 -3
  391. package/src/HoverCard/useHoverCard.tsx +101 -33
  392. package/src/Indicator/CheckboxIndicator.tsx +14 -8
  393. package/src/Indicator/Indicator.doc.mjs +2 -0
  394. package/src/Item/Item.tsx +5 -4
  395. package/src/Kbd/Kbd.tsx +2 -19
  396. package/src/Layer/LayerDepthContext.tsx +62 -0
  397. package/src/Layer/index.ts +9 -0
  398. package/src/Layer/layerDismissalFamilies.test.tsx +359 -0
  399. package/src/Layer/layerDismissalInvariants.test.tsx +417 -0
  400. package/src/Layer/layerStack.ts +362 -0
  401. package/src/Layer/useLayerDismissal.test.tsx +469 -0
  402. package/src/Layer/useLayerDismissal.ts +173 -0
  403. package/src/Layout/Layout.doc.mjs +2 -2
  404. package/src/Lightbox/Lightbox.doc.mjs +12 -0
  405. package/src/Lightbox/Lightbox.tsx +124 -102
  406. package/src/Link/Link.doc.mjs +39 -0
  407. package/src/Markdown/Markdown.doc.mjs +5 -0
  408. package/src/Markdown/incremental.test.ts +16 -2
  409. package/src/Markdown/index.ts +1 -0
  410. package/src/Markdown/parser.perf.test.ts +96 -1
  411. package/src/Markdown/parser.test.ts +145 -2
  412. package/src/Markdown/parser.ts +244 -29
  413. package/src/MobileNav/MobileNav.tsx +62 -36
  414. package/src/MoreMenu/MoreMenu.doc.mjs +7 -1
  415. package/src/MultiSelector/MultiSelector.doc.mjs +27 -0
  416. package/src/MultiSelector/MultiSelector.test.tsx +106 -0
  417. package/src/MultiSelector/MultiSelector.tsx +50 -6
  418. package/src/MultiSelector/index.ts +1 -0
  419. package/src/NavIcon/NavIcon.doc.mjs +8 -2
  420. package/src/NavIcon/NavIcon.test.tsx +9 -0
  421. package/src/NavIcon/NavIcon.tsx +5 -1
  422. package/src/NavMenu/NavHeadingMenu.tsx +3 -2
  423. package/src/NumberInput/NumberInput.doc.mjs +20 -0
  424. package/src/NumberInput/NumberInput.tsx +28 -29
  425. package/src/NumberInput/numberParser.test.ts +137 -0
  426. package/src/NumberInput/numberParser.ts +346 -0
  427. package/src/Outline/Outline.tsx +3 -2
  428. package/src/OverflowList/OverflowList.tsx +3 -2
  429. package/src/Overlay/Overlay.tsx +3 -2
  430. package/src/Popover/usePopover.tsx +31 -28
  431. package/src/PowerSearch/PowerSearch.doc.mjs +160 -28
  432. package/src/PowerSearch/PowerSearch.test.tsx +240 -1
  433. package/src/PowerSearch/PowerSearch.tsx +34 -5
  434. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +201 -0
  435. package/src/PowerSearch/PowerSearchEditPopover.tsx +29 -2
  436. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +47 -1
  437. package/src/PowerSearch/PowerSearchValueEditor.tsx +15 -0
  438. package/src/PowerSearch/types.ts +2 -0
  439. package/src/PowerSearch/usePowerSearchSource.test.ts +82 -2
  440. package/src/PowerSearch/usePowerSearchSource.ts +13 -3
  441. package/src/ProgressBar/ProgressBar.doc.mjs +42 -6
  442. package/src/ProgressBar/ProgressBar.test.tsx +24 -0
  443. package/src/ProgressBar/ProgressBar.tsx +28 -8
  444. package/src/Resizable/ResizeHandle.tsx +3 -2
  445. package/src/SegmentedControl/SegmentedControl.tsx +3 -2
  446. package/src/SelectableCard/SelectableCard.tsx +3 -2
  447. package/src/Selector/Selector.doc.mjs +12 -1
  448. package/src/Selector/Selector.test.tsx +45 -1
  449. package/src/Selector/Selector.tsx +11 -1
  450. package/src/SideNav/SideNav.doc.mjs +6 -6
  451. package/src/SideNav/SideNav.tsx +4 -2
  452. package/src/SideNav/SideNavHeading.tsx +12 -12
  453. package/src/SideNav/SideNavItem.tsx +4 -2
  454. package/src/Slider/Slider.tsx +3 -2
  455. package/src/Spinner/Spinner.test.tsx +147 -1
  456. package/src/Spinner/Spinner.tsx +120 -101
  457. package/src/StatusDot/StatusDot.doc.mjs +8 -2
  458. package/src/StatusDot/StatusDot.test.tsx +9 -0
  459. package/src/StatusDot/StatusDot.tsx +7 -1
  460. package/src/Stepper/Step.doc.mjs +98 -0
  461. package/src/Stepper/Step.tsx +1638 -0
  462. package/src/Stepper/StepStatus.ts +26 -0
  463. package/src/Stepper/Stepper.doc.mjs +370 -0
  464. package/src/Stepper/Stepper.test.tsx +1024 -0
  465. package/src/Stepper/Stepper.tsx +258 -0
  466. package/src/Stepper/StepperContext.ts +71 -0
  467. package/src/Stepper/index.ts +18 -0
  468. package/src/Stepper/stepper.stylex.ts +19 -0
  469. package/src/Switch/Switch.doc.mjs +2 -0
  470. package/src/Switch/Switch.test.tsx +10 -0
  471. package/src/Switch/Switch.tsx +6 -2
  472. package/src/TabList/Tab.doc.mjs +13 -1
  473. package/src/TabList/Tab.tsx +67 -7
  474. package/src/TabList/TabList.doc.mjs +22 -6
  475. package/src/TabList/TabList.test.tsx +559 -0
  476. package/src/TabList/TabList.tsx +490 -22
  477. package/src/TabList/TabListContext.ts +13 -2
  478. package/src/TabList/TabMenu.tsx +3 -2
  479. package/src/TabList/index.ts +6 -2
  480. package/src/Table/BaseTable.tsx +12 -4
  481. package/src/Table/Table.doc.mjs +9 -2
  482. package/src/Table/Table.test.tsx +132 -14
  483. package/src/Table/Table.tsx +3 -8
  484. package/src/Table/TableBody.doc.mjs +40 -0
  485. package/src/Table/TableFooter.doc.mjs +40 -0
  486. package/src/Table/TableHeader.doc.mjs +40 -0
  487. package/src/Table/TableRow.tsx +16 -5
  488. package/src/Table/plugins/groupedRows/useTableGroupedRows-perf.test.tsx +112 -0
  489. package/src/Table/plugins/groupedRows/useTableGroupedRows.test.tsx +100 -0
  490. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +17 -8
  491. package/src/Table/plugins/pagination/useTablePagination.test.tsx +34 -4
  492. package/src/Table/plugins/pagination/useTablePagination.tsx +35 -3
  493. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +9 -2
  494. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +13 -7
  495. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +12 -3
  496. package/src/Table/plugins/selection/useTableSelection.test.tsx +76 -0
  497. package/src/Table/plugins/selection/useTableSelection.tsx +40 -7
  498. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +1 -2
  499. package/src/Table/types.ts +22 -4
  500. package/src/Table/useBaseTablePlugins.ts +5 -0
  501. package/src/Table/useTableGroupedRows.doc.mjs +5 -4
  502. package/src/Table/useTablePagination.doc.mjs +7 -0
  503. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  504. package/src/Table/useTableSelection.doc.mjs +31 -0
  505. package/src/Text/Text.test.tsx +23 -1
  506. package/src/Text/Text.tsx +6 -2
  507. package/src/TextArea/TextArea.doc.mjs +8 -2
  508. package/src/TextArea/TextArea.test.tsx +11 -0
  509. package/src/TextArea/TextArea.tsx +15 -8
  510. package/src/TextInput/TextInput.doc.mjs +10 -0
  511. package/src/TextInput/TextInput.tsx +3 -2
  512. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  513. package/src/TimeInput/TimeInput.tsx +2 -2
  514. package/src/Timestamp/Timestamp.tsx +4 -2
  515. package/src/Toast/Toast.doc.mjs +6 -0
  516. package/src/Token/TokenLink.tsx +2 -2
  517. package/src/Tokenizer/Tokenizer.doc.mjs +27 -0
  518. package/src/Tokenizer/Tokenizer.tsx +4 -0
  519. package/src/Toolbar/Toolbar.doc.mjs +5 -0
  520. package/src/Tooltip/Tooltip.doc.mjs +5 -0
  521. package/src/Tooltip/Tooltip.test.tsx +32 -0
  522. package/src/Tooltip/Tooltip.tsx +5 -0
  523. package/src/Tooltip/useTooltip.tsx +47 -24
  524. package/src/TopNav/TopNavHeading.tsx +5 -4
  525. package/src/TopNav/TopNavMegaMenu.tsx +3 -2
  526. package/src/TopNav/TopNavMenu.tsx +4 -3
  527. package/src/Typeahead/BaseTypeahead.doc.mjs +11 -0
  528. package/src/Typeahead/BaseTypeahead.tsx +85 -33
  529. package/src/Typeahead/Typeahead.doc.mjs +5 -0
  530. package/src/Typeahead/Typeahead.test.tsx +49 -0
  531. package/src/Typeahead/Typeahead.tsx +3 -2
  532. package/src/hooks/index.ts +2 -0
  533. package/src/hooks/useContainerReveal.doc.mjs +2 -2
  534. package/src/hooks/useFocusTrap.test.tsx +14 -0
  535. package/src/hooks/useFocusTrap.ts +62 -111
  536. package/src/hooks/useFocusTrapEscapeShim.test.tsx +165 -0
  537. package/src/hooks/useHotkeys.ts +3 -18
  538. package/src/hooks/useMergedRefs.doc.mjs +79 -0
  539. package/src/hooks/useMergedRefs.test.tsx +62 -0
  540. package/src/hooks/useMergedRefs.ts +36 -0
  541. package/src/index.ts +1 -0
  542. package/src/theme/MediaTheme.doc.mjs +5 -5
  543. package/src/theme/derivedVarRegistry.test.ts +19 -3
  544. package/src/theme/derivedVarRegistry.ts +23 -4
  545. package/src/theme/generateThemeRules.test.ts +37 -0
  546. package/src/theme/syntax/tokens.ts +5 -2
  547. package/src/utils/groupItems.ts +12 -6
  548. package/src/utils/index.ts +1 -1
  549. package/src/utils/isApplePlatform.test.ts +61 -0
  550. package/src/utils/isApplePlatform.ts +37 -0
  551. package/src/utils/sharedResizeObserver.ts +10 -2
  552. package/src/utils/timeParser.test.ts +6 -1
  553. package/src/utils/timeParser.ts +35 -13
  554. package/src/__tests__/inputWidthContract.test.tsx +0 -69
@@ -0,0 +1,623 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file NativeDateField.tsx
7
+ * @input Uses React, Field, Icon, InputClearButton, Spinner, useCalendarConstraints
8
+ * @output Exports NativeDateField — the OS-picker touch surface
9
+ * @position One of DateInput's three surfaces. `DateInput` picks the pointer
10
+ * field on a mouse and, on touch, either `TouchDateField` (the default) or
11
+ * this one when the consumer opts in with `nativePicker`.
12
+ *
13
+ * Hands date picking to the platform: a real `<input type="date">`, whose
14
+ * picker the OS draws — the iOS wheel, the Android calendar dialog. The field
15
+ * itself still looks like every other Astryx input, and DateInput paints its
16
+ * text so `format` and `placeholder` keep applying.
17
+ *
18
+ * Everything unusual in here was measured on a real iOS device; the comments
19
+ * say which behaviour forced which decision, because none of them are
20
+ * reproducible in a desktop browser.
21
+ *
22
+ * SYNC: When modified, update these files to stay in sync:
23
+ * - /packages/core/src/DateInput/DateInput.tsx (the `nativePicker` prop)
24
+ * - /packages/core/src/DateInput/DateInput.doc.mjs (prop table)
25
+ * - /packages/core/src/DateInput/DateInputNative.test.tsx (tests)
26
+ */
27
+
28
+ import {useCallback, useEffect, useId, useRef, useState} from 'react';
29
+ import * as stylex from '@stylexjs/stylex';
30
+ import {useCalendarConstraints} from '../Calendar';
31
+ import type {DateInputProps} from './DateInput';
32
+ import {
33
+ Field,
34
+ InputClearButton,
35
+ inputStatusBorderStyles,
36
+ inputStatusFocusWithinStyles,
37
+ inputStatusHoverShadowStyles,
38
+ inputWrapperStyles,
39
+ } from '../Field';
40
+ import {useInputStatusIcon, useMergedRefs} from '../hooks';
41
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
42
+ import {Icon} from '../Icon';
43
+ import {useLocale, useTranslator} from '../i18n';
44
+ import {useInputGroup} from '../InputGroup';
45
+ import {groupStyles} from '../InputGroup/groupStyles';
46
+ import {stableClassName} from '../naming';
47
+ import {useSize} from '../SizeContext';
48
+ import {Spinner} from '../Spinner';
49
+ import {
50
+ colorVars,
51
+ radiusVars,
52
+ sizeVars,
53
+ typeScaleVars,
54
+ typographyVars,
55
+ } from '../theme/tokens.stylex';
56
+ import {useTooltip} from '../Tooltip';
57
+ import {VisuallyHidden} from '../VisuallyHidden';
58
+ import {
59
+ focusOutlineStyles,
60
+ formatSharedDate,
61
+ getInputARIA,
62
+ mergeProps,
63
+ parseDateInput,
64
+ plainDateFromISO,
65
+ plainDateToISO,
66
+ themeProps,
67
+ type ISODateString,
68
+ } from '../utils';
69
+
70
+ const styles = stylex.create({
71
+ wrapper: {
72
+ gap: 8,
73
+ },
74
+ iconButton: {
75
+ display: 'flex',
76
+ alignItems: 'center',
77
+ justifyContent: 'center',
78
+ padding: 0,
79
+ margin: 0,
80
+ borderWidth: 0,
81
+ borderStyle: 'none',
82
+ backgroundColor: 'transparent',
83
+ cursor: {
84
+ default: 'pointer',
85
+ ':is(:disabled,[aria-disabled="true"])': 'default',
86
+ },
87
+ borderRadius: radiusVars['--radius-element'],
88
+ },
89
+ iconButtonDisabled: {
90
+ cursor: 'default',
91
+ },
92
+ input: {
93
+ display: 'block',
94
+ flex: 1,
95
+ minWidth: 0,
96
+ borderWidth: 0,
97
+ borderStyle: 'none',
98
+ padding: 0,
99
+ fontFamily: typographyVars['--font-family-body'],
100
+ // Below 16px iOS zooms the page when the field takes focus.
101
+ fontSize: {
102
+ default: typeScaleVars['--text-body-size'],
103
+ '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
104
+ },
105
+ lineHeight: typeScaleVars['--text-body-leading'],
106
+ color: colorVars['--color-text-primary'],
107
+ backgroundColor: 'transparent',
108
+ outline: 'none',
109
+ // A date control's intrinsic height comes from its inner edit fields, not
110
+ // from `line-height`, so it renders ~2px taller than a text input and its
111
+ // value sits off the shared baseline inside the same flex row. One line
112
+ // box is exactly what the text field occupies.
113
+ height: stylex.firstThatWorks(
114
+ '1lh',
115
+ `calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
116
+ ),
117
+ // iOS gives date controls their own button-like chrome, with inner
118
+ // spacing and a centred value that no reset of ours can reach.
119
+ WebkitAppearance: 'none',
120
+ appearance: 'none',
121
+ // Chromium paints a second calendar glyph inside the field; this surface
122
+ // already ships a toggle button, so drop the duplicate.
123
+ '::-webkit-calendar-picker-indicator': {
124
+ display: 'none',
125
+ },
126
+ '::-webkit-date-and-time-value': {
127
+ textAlign: 'start',
128
+ marginBlock: 0,
129
+ marginInline: 0,
130
+ paddingBlock: 0,
131
+ paddingInline: 0,
132
+ lineHeight: 'inherit',
133
+ minHeight: 0,
134
+ },
135
+ '::-webkit-datetime-edit': {
136
+ paddingBlock: 0,
137
+ paddingInline: 0,
138
+ lineHeight: 'inherit',
139
+ },
140
+ },
141
+ inputDisabled: {
142
+ cursor: 'default',
143
+ },
144
+ inputInvalid: {
145
+ color: colorVars['--color-text-secondary'],
146
+ },
147
+ // Hides whatever the engine paints inside the control so this field's own
148
+ // text can take that space. WebKit renders the value into a single
149
+ // `::-webkit-date-and-time-value` run which the UA stylesheet gives no
150
+ // colour of its own (the iOS UA colour sits on the INPUT), so it inherits
151
+ // this; Chromium's `::-webkit-datetime-edit` fields inherit it too.
152
+ // `-webkit-text-fill-color` is what actually wins inside a WebKit date
153
+ // control.
154
+ inputTextHidden: {
155
+ color: 'transparent',
156
+ WebkitTextFillColor: 'transparent',
157
+ },
158
+ // Positioning context for the overlay, standing in for the input's own box
159
+ // in the field's flex row.
160
+ slot: {
161
+ position: 'relative',
162
+ display: 'flex',
163
+ alignItems: 'center',
164
+ flex: 1,
165
+ minWidth: 0,
166
+ },
167
+ // This field's own text, laid over the control. Decorative: the input still
168
+ // holds the value and keeps its label, description, and status wiring, so
169
+ // announcing this too would just double-speak.
170
+ overlay: {
171
+ position: 'absolute',
172
+ insetInlineStart: 0,
173
+ // Both insets, so the overlay is bounded by the slot rather than
174
+ // shrink-to-fit. Without the end inset a long formatted date paints past
175
+ // the slot and over whatever follows it in the field — measured running
176
+ // 24px across the clear button.
177
+ insetInlineEnd: 0,
178
+ insetBlock: 0,
179
+ // A BLOCK box, not a flex one: `text-overflow` only applies to a block
180
+ // container, so on a flex container a too-long date hard-clips mid-glyph
181
+ // instead of ellipsising (measured identical to `text-overflow: clip` in
182
+ // both WebKit and Chromium). Centring then comes from the line box, so
183
+ // the overlay carries the same font size and leading as the input it
184
+ // covers — one line of that leading fills its height exactly, which puts
185
+ // the glyphs on the input's own baseline.
186
+ display: 'block',
187
+ fontSize: {
188
+ default: typeScaleVars['--text-body-size'],
189
+ '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
190
+ },
191
+ lineHeight: typeScaleVars['--text-body-leading'],
192
+ // A tap has to reach the control underneath — that is what raises the
193
+ // picker.
194
+ pointerEvents: 'none',
195
+ overflow: 'hidden',
196
+ whiteSpace: 'nowrap',
197
+ textOverflow: 'ellipsis',
198
+ },
199
+ overlayValue: {
200
+ color: colorVars['--color-text-primary'],
201
+ },
202
+ overlayPlaceholder: {
203
+ color: colorVars['--color-text-secondary'],
204
+ },
205
+ });
206
+
207
+ const sizeStyles = stylex.create({
208
+ sm: {height: sizeVars['--size-element-sm'], minWidth: 180},
209
+ md: {height: sizeVars['--size-element-md'], minWidth: 180},
210
+ lg: {height: sizeVars['--size-element-lg'], minWidth: 180},
211
+ });
212
+
213
+ const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
214
+
215
+ /**
216
+ * The OS-picker surface. Takes `DateInput`'s props verbatim; see
217
+ * {@link DateInput} for when it is chosen over the other two.
218
+ */
219
+ export function NativeDateField({
220
+ label,
221
+ isLabelHidden = false,
222
+ description,
223
+ isOptional = false,
224
+ isRequired = false,
225
+ isDisabled = false,
226
+ disabledMessage,
227
+ value,
228
+ onChange,
229
+ isLoading = false,
230
+ min,
231
+ max,
232
+ dateConstraints,
233
+ placeholder: placeholderFromProps,
234
+ size: sizeProp,
235
+ status,
236
+ statusVariant = 'attached',
237
+ labelTooltip,
238
+ hasClear = false,
239
+ // The OS draws the picker, so neither reaches it: both describe a calendar
240
+ // grid it does not have. Accepted (the prop types are shared) and ignored.
241
+ numberOfMonths: _numberOfMonths,
242
+ weekStartsOn: _weekStartsOn,
243
+ format = 'date_long',
244
+ width,
245
+ xstyle,
246
+ className,
247
+ style,
248
+ ref,
249
+ ...rest
250
+ }: DateInputProps) {
251
+ const t = useTranslator();
252
+ const locale = useLocale();
253
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
254
+ const placeholder =
255
+ placeholderFromProps ?? t('@astryx.dateInput.placeholder');
256
+ const size = useSize(sizeProp, 'md');
257
+
258
+ const id = useId();
259
+ const inputLabelID = useId();
260
+ const descriptionID = useId();
261
+ const statusMessageID = useId();
262
+ const inputRef = useRef<HTMLInputElement | null>(null);
263
+ const mergedInputRef = useMergedRefs(ref, inputRef);
264
+ const inputGroup = useInputGroup();
265
+
266
+ const isEffectivelyDisabled = isDisabled || isLoading;
267
+
268
+ // Disabled-reason tooltip, same contract as the other two surfaces: a
269
+ // disabled control swallows pointer events, so the listeners attach to the
270
+ // wrapper and the input stays focusable via aria-disabled.
271
+ const showsDisabledMessage = isDisabled && !!disabledMessage;
272
+ const disabledMessageTooltip = useTooltip({
273
+ placement: 'above',
274
+ focusTrigger: 'always',
275
+ isEnabled: showsDisabledMessage,
276
+ });
277
+
278
+ const {isDateDisabled} = useCalendarConstraints({min, max, dateConstraints});
279
+
280
+ const {statusIcon, describedBy: statusTooltipDescribedBy} =
281
+ useInputStatusIcon({
282
+ status,
283
+ statusVariant,
284
+ isInGroup: !!inputGroup,
285
+ });
286
+
287
+ const {ariaLabelledBy, ariaDescribedBy} = getInputARIA(
288
+ inputLabelID,
289
+ [
290
+ description ? descriptionID : null,
291
+ statusVariant !== 'tooltip' && status?.message ? statusMessageID : null,
292
+ statusTooltipDescribedBy,
293
+ showsDisabledMessage ? disabledMessageTooltip.describedBy : null,
294
+ ],
295
+ inputGroup,
296
+ );
297
+
298
+ // A date the picker produced that `dateConstraints` refuses, held so the
299
+ // refusal can be announced instead of looking like a dead tap.
300
+ const [rejectedValue, setRejectedValue] = useState<string | null>(null);
301
+ // Whether the control has focus — which, on a touch device, means its
302
+ // picker is open.
303
+ const [isFocused, setIsFocused] = useState(false);
304
+ // The raw value the control last reported and we acted on, so the same edit
305
+ // arriving through both commit paths only fires one change.
306
+ const lastCommitRef = useRef<string | null>(null);
307
+
308
+ const prevValueRef = useRef(value);
309
+ if (value !== prevValueRef.current) {
310
+ prevValueRef.current = value;
311
+ lastCommitRef.current = null;
312
+ if (rejectedValue !== null) {
313
+ setRejectedValue(null);
314
+ }
315
+ }
316
+
317
+ // The control's own value is always ISO — the only form it accepts, and
318
+ // what the picker reads and writes. `format` rides on the overlay instead.
319
+ const nativeValue = value && ISO_DATE.test(value) ? value : '';
320
+ const isInputValid = rejectedValue === null;
321
+
322
+ const formatValue = useCallback(
323
+ (iso: ISODateString): string =>
324
+ typeof format === 'function'
325
+ ? format(iso)
326
+ : formatSharedDate(plainDateFromISO(iso), format, locale),
327
+ [format, locale],
328
+ );
329
+
330
+ // This field paints the closed control's text itself, which is what keeps
331
+ // `format` and `placeholder` applying: the OS picker has no segments to
332
+ // edit, so our text holds even while the picker is open, and tracks it live.
333
+ const overlayText = nativeValue ? formatValue(nativeValue) : placeholder;
334
+ const showsOverlay = !!overlayText;
335
+
336
+ const commitValue = useCallback(
337
+ (newValue: string) => {
338
+ if (isEffectivelyDisabled) {
339
+ return;
340
+ }
341
+ // The same edit can arrive twice — React's synthetic change and the
342
+ // native listener below both report it — so act on a raw value once.
343
+ if (lastCommitRef.current === newValue) {
344
+ return;
345
+ }
346
+ lastCommitRef.current = newValue;
347
+
348
+ if (!newValue) {
349
+ setRejectedValue(null);
350
+ if (value !== undefined) {
351
+ onChange?.(undefined);
352
+ }
353
+ return;
354
+ }
355
+
356
+ const parsed = parseDateInput(newValue, locale);
357
+ if (!parsed) {
358
+ return;
359
+ }
360
+ if (isDateDisabled(parsed)) {
361
+ // iOS does not enforce min/max in its picker — those attributes are
362
+ // constraint-validation flags, not clamps, and the sheet lets the
363
+ // user land on any date. Refuse it here and let the sync effect snap
364
+ // the control back; the live region below announces the refusal.
365
+ setRejectedValue(newValue);
366
+ return;
367
+ }
368
+
369
+ setRejectedValue(null);
370
+ const parsedISO = plainDateToISO(parsed);
371
+ if (parsedISO !== value) {
372
+ onChange?.(parsedISO);
373
+ }
374
+ },
375
+ [value, onChange, isDateDisabled, isEffectivelyDisabled, locale],
376
+ );
377
+
378
+ const handleChange = useCallback(
379
+ (e: React.ChangeEvent<HTMLInputElement>) => {
380
+ commitValue(e.target.value);
381
+ },
382
+ [commitValue],
383
+ );
384
+
385
+ // React's synthetic change system does not reliably observe the iOS
386
+ // picker's edits. Measured on an iPhone: picking a date fired a native
387
+ // `input` event carrying the new date while React's `onChange` never ran —
388
+ // so React re-rendered and wrote its own stale value straight back over the
389
+ // picker's, and the user's pick (and their Reset) silently reverted. A
390
+ // native listener reads what the control actually holds, whatever React's
391
+ // synthetic layer made of it.
392
+ const commitRef = useRef(commitValue);
393
+ useEffect(() => {
394
+ commitRef.current = commitValue;
395
+ });
396
+ useEffect(() => {
397
+ const input = inputRef.current;
398
+ if (!input) {
399
+ return;
400
+ }
401
+ const handleNative = () => commitRef.current(input.value);
402
+ input.addEventListener('input', handleNative);
403
+ input.addEventListener('change', handleNative);
404
+ return () => {
405
+ input.removeEventListener('input', handleNative);
406
+ input.removeEventListener('change', handleNative);
407
+ };
408
+ }, []);
409
+
410
+ // The value the control mounts with. Deliberately captured once: React
411
+ // writes to the element whenever a `value` OR `defaultValue` prop changes,
412
+ // and on iOS ANY write while the picker sheet is open detaches the sheet
413
+ // from the field — the wheel and Reset keep moving the sheet's own
414
+ // highlight, but nothing they do reaches the input and no event fires, so
415
+ // the user's pick appears to do nothing. Holding this constant means React
416
+ // touches the element exactly once, at mount; the effect below owns every
417
+ // later update and only writes while the field is unfocused.
418
+ const initialValueRef = useRef<string | null>(null);
419
+ if (initialValueRef.current === null) {
420
+ initialValueRef.current = nativeValue;
421
+ }
422
+
423
+ // Push an externally-changed value in — but never while the control has
424
+ // focus, for the reason above. Blur flips `isFocused`, so this doubles as
425
+ // the reconcile once the picker closes. (Self-heals a stale
426
+ // `initialValueRef` too, on the commit right after a remount.)
427
+ useEffect(() => {
428
+ if (isFocused) {
429
+ return;
430
+ }
431
+ const input = inputRef.current;
432
+ if (input && input.value !== nativeValue) {
433
+ input.value = nativeValue;
434
+ }
435
+ }, [isFocused, nativeValue]);
436
+
437
+ const handleBlur = useCallback(() => {
438
+ const domValue = inputRef.current?.value;
439
+ setIsFocused(false);
440
+ // A refused date is reverted by the sync effect the moment focus leaves,
441
+ // so the field is once again showing a date that IS valid. Keeping the
442
+ // rejection past that point would mark good data invalid, with no way
443
+ // back except changing the field again. The live region announced the
444
+ // refusal while it happened; that is the feedback.
445
+ setRejectedValue(null);
446
+ if (domValue !== undefined && domValue !== nativeValue) {
447
+ commitValue(domValue);
448
+ }
449
+ }, [commitValue, nativeValue]);
450
+
451
+ // Focusing a date control is what raises the OS picker, so the usual
452
+ // focus-restore after a clear would pop the picker the tap just dismissed —
453
+ // and on iOS that reads as the clear having done nothing.
454
+ const handleClear = useCallback(() => {
455
+ onChange?.(undefined);
456
+ }, [onChange]);
457
+
458
+ const openPicker = useCallback(() => {
459
+ if (isEffectivelyDisabled) {
460
+ return;
461
+ }
462
+ const input = inputRef.current;
463
+ if (!input) {
464
+ return;
465
+ }
466
+ // Focus first: on touch browsers focusing the control is itself what
467
+ // raises the picker, and iOS implements no `showPicker()` for type=date
468
+ // (WebKit bug 261703), so focus is the whole mechanism there.
469
+ input.focus();
470
+ if (typeof input.showPicker === 'function') {
471
+ try {
472
+ input.showPicker();
473
+ } catch {
474
+ // showPicker throws without transient user activation and inside a
475
+ // cross-origin iframe. The focus above is the fallback.
476
+ }
477
+ }
478
+ }, [isEffectivelyDisabled]);
479
+
480
+ const inputWrapper = (
481
+ <div
482
+ ref={el => {
483
+ disabledMessageTooltip.ref(el);
484
+ }}
485
+ {...rest}
486
+ {...mergeProps(
487
+ themeProps('date-input', {
488
+ size,
489
+ status: status?.type ?? null,
490
+ disabled: isDisabled ? 'disabled' : null,
491
+ }),
492
+ stylex.props(
493
+ inputWrapperStyles.base,
494
+ sizeStyles[size],
495
+ styles.wrapper,
496
+ isEffectivelyDisabled && inputWrapperStyles.disabled,
497
+ status && inputStatusBorderStyles[status.type],
498
+ status &&
499
+ !isEffectivelyDisabled &&
500
+ inputStatusHoverShadowStyles[status.type],
501
+ status && inputStatusFocusWithinStyles[status.type],
502
+ inputGroup && groupStyles.inGroup,
503
+ xstyle,
504
+ ),
505
+ className,
506
+ style,
507
+ )}>
508
+ {inputGroup && <VisuallyHidden id={inputLabelID}>{label}</VisuallyHidden>}
509
+ <button
510
+ type="button"
511
+ onClick={openPicker}
512
+ disabled={isEffectivelyDisabled}
513
+ aria-label={t('@astryx.dateInput.openCalendar')}
514
+ tabIndex={-1}
515
+ {...stylex.props(
516
+ focusOutlineStyles.focusVisible,
517
+ styles.iconButton,
518
+ isEffectivelyDisabled && styles.iconButtonDisabled,
519
+ )}>
520
+ <Icon
521
+ icon="calendar"
522
+ size="sm"
523
+ color="secondary"
524
+ {...themeProps('date-input-toggle-icon', {state: 'collapsed'})}
525
+ />
526
+ </button>
527
+ <span {...stylex.props(styles.slot)}>
528
+ <input
529
+ ref={mergedInputRef}
530
+ id={id}
531
+ type="date"
532
+ // UNCONTROLLED on purpose, with a value React never rewrites after
533
+ // mount — see `initialValueRef` and the sync effect above.
534
+ defaultValue={initialValueRef.current ?? ''}
535
+ onChange={handleChange}
536
+ onFocus={() => setIsFocused(true)}
537
+ onBlur={handleBlur}
538
+ min={min}
539
+ max={max}
540
+ disabled={isEffectivelyDisabled && !showsDisabledMessage}
541
+ aria-disabled={showsDisabledMessage ? 'true' : undefined}
542
+ readOnly={showsDisabledMessage || undefined}
543
+ aria-labelledby={ariaLabelledBy}
544
+ aria-describedby={ariaDescribedBy}
545
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
546
+ aria-invalid={
547
+ status?.type === 'error' || !isInputValid ? 'true' : undefined
548
+ }
549
+ aria-busy={isLoading || undefined}
550
+ {...stylex.props(
551
+ styles.input,
552
+ showsOverlay && styles.inputTextHidden,
553
+ isEffectivelyDisabled && styles.inputDisabled,
554
+ !isInputValid && styles.inputInvalid,
555
+ )}
556
+ />
557
+ {showsOverlay && (
558
+ <span
559
+ aria-hidden="true"
560
+ {...stylex.props(
561
+ styles.overlay,
562
+ nativeValue ? styles.overlayValue : styles.overlayPlaceholder,
563
+ isEffectivelyDisabled && styles.inputDisabled,
564
+ !isInputValid && !!nativeValue && styles.inputInvalid,
565
+ )}>
566
+ {overlayText}
567
+ </span>
568
+ )}
569
+ </span>
570
+ {/*
571
+ Live region announcing a refused date. The value snaps back on its
572
+ own, so without this a screen-reader user would get no feedback that
573
+ their pick was rejected (WCAG 3.3.1).
574
+ */}
575
+ <VisuallyHidden as="div" role="alert" aria-live="assertive">
576
+ {!isInputValid ? t('@astryx.dateInput.invalidDate') : ''}
577
+ </VisuallyHidden>
578
+ {hasClear && value !== undefined && !isEffectivelyDisabled && (
579
+ <InputClearButton
580
+ label={t('@astryx.dateInput.clear', {label})}
581
+ onClick={handleClear}
582
+ iconClassName={stableClassName('date-input-clear-icon')}
583
+ />
584
+ )}
585
+ {isLoading && <Spinner size="sm" />}
586
+ {statusIcon}
587
+ {showsDisabledMessage &&
588
+ disabledMessageTooltip.renderTooltip(disabledMessage)}
589
+ </div>
590
+ );
591
+
592
+ if (inputGroup) {
593
+ return inputWrapper;
594
+ }
595
+
596
+ return (
597
+ <Field
598
+ label={label}
599
+ isLabelHidden={isLabelHidden}
600
+ description={description}
601
+ inputID={id}
602
+ descriptionID={description ? descriptionID : undefined}
603
+ isOptional={isOptional}
604
+ isRequired={isRequired}
605
+ isDisabled={isDisabled}
606
+ status={
607
+ status
608
+ ? {
609
+ type: status.type,
610
+ message: status.message,
611
+ messageID: status.message ? statusMessageID : undefined,
612
+ }
613
+ : undefined
614
+ }
615
+ statusVariant={statusVariant}
616
+ labelTooltip={labelTooltip}
617
+ width={width}>
618
+ {inputWrapper}
619
+ </Field>
620
+ );
621
+ }
622
+
623
+ NativeDateField.displayName = 'NativeDateField';
@@ -67,7 +67,7 @@ import {
67
67
  inputStatusHoverShadowStyles,
68
68
  inputStatusFocusWithinStyles,
69
69
  } from '../Field';
70
- import {useInputStatusIcon} from '../hooks';
70
+ import {useInputStatusIcon, useMergedRefs} from '../hooks';
71
71
  import {useResolvedRequired} from '../hooks/useResolvedRequired';
72
72
  import {Icon} from '../Icon';
73
73
  import {IconButton} from '../IconButton';
@@ -96,7 +96,6 @@ import {
96
96
  getInputARIA,
97
97
  isImeKeyEvent,
98
98
  mergeProps,
99
- mergeRefs,
100
99
  rtlStyles,
101
100
  themeProps,
102
101
  formatSharedDate,
@@ -557,6 +556,7 @@ export function TouchDateField({
557
556
  const descriptionID = useId();
558
557
  const statusMessageID = useId();
559
558
  const inputRef = useRef<HTMLInputElement | null>(null);
559
+ const mergedInputRef = useMergedRefs(ref, inputRef);
560
560
  const inputGroup = useInputGroup();
561
561
 
562
562
  const [, startTransition] = useTransition();
@@ -599,6 +599,16 @@ export function TouchDateField({
599
599
  const [isSheetOpen, setIsSheetOpen] = useState(false);
600
600
  const [isWheelOpen, setIsWheelOpen] = useState(false);
601
601
  const scrollerHandleRef = useRef<MonthScrollerHandle | null>(null);
602
+ // Pending focus handoff from the clear button; see handleClear.
603
+ const clearFocusTimerRef = useRef<number | null>(null);
604
+ useEffect(
605
+ () => () => {
606
+ if (clearFocusTimerRef.current != null) {
607
+ clearTimeout(clearFocusTimerRef.current);
608
+ }
609
+ },
610
+ [],
611
+ );
602
612
 
603
613
  const today = useMemo(() => plainDateToday(), []);
604
614
  const selectedDate = useMemo(
@@ -708,7 +718,31 @@ export function TouchDateField({
708
718
 
709
719
  const handleClear = useCallback(() => {
710
720
  fireChange(undefined);
711
- inputRef.current?.focus();
721
+ // Focus goes back to the field on the NEXT task, not synchronously.
722
+ //
723
+ // Clearing unmounts this button (it only renders while there is a value),
724
+ // and focusing another element in the same task as that unmount makes iOS
725
+ // Safari scroll the whole document to the top — the user is thrown from
726
+ // wherever the field sat to the start of the page. Measured on the iOS 26
727
+ // simulator against the live docsite, field at scrollY 2055: synchronous
728
+ // focus lands at 0, deferred focus stays at 2055.
729
+ //
730
+ // `preventScroll` alone does NOT fix it (verified: still 0) — this is not
731
+ // the browser's ordinary scroll-the-focused-element-into-view step, so the
732
+ // deferral is the load-bearing half. It is kept because the reveal scroll
733
+ // is real too, and unwanted for the same reason: the field the user just
734
+ // tapped is already on screen (+12px on a plain page without it).
735
+ //
736
+ // Skipping the focus entirely would also stop the scroll, but then focus
737
+ // dies with the unmounting button and lands on <body>.
738
+ const field = inputRef.current;
739
+ if (field == null) {
740
+ return;
741
+ }
742
+ clearFocusTimerRef.current = window.setTimeout(() => {
743
+ clearFocusTimerRef.current = null;
744
+ field.focus({preventScroll: true});
745
+ }, 0);
712
746
  }, [fireChange]);
713
747
 
714
748
  /**
@@ -1135,7 +1169,7 @@ export function TouchDateField({
1135
1169
  />
1136
1170
  </button>
1137
1171
  <input
1138
- ref={mergeRefs(ref, inputRef)}
1172
+ ref={mergedInputRef}
1139
1173
  id={id}
1140
1174
  type="text"
1141
1175
  role="combobox"