@astryxdesign/core 0.4.4 → 0.4.5-canary.013e728

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 (469) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/AvatarGroup/AvatarGroupOverflow.js +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts +11 -2
  4. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  5. package/dist/BottomSheet/BottomSheet.js +4 -0
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
  7. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheetPanel.js +65 -15
  9. package/dist/BottomSheet/index.d.ts +1 -1
  10. package/dist/BottomSheet/index.d.ts.map +1 -1
  11. package/dist/BottomSheet/snapOffsets.d.ts +57 -20
  12. package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
  13. package/dist/BottomSheet/snapOffsets.js +106 -30
  14. package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
  15. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  16. package/dist/BottomSheet/useSheetGestures.js +160 -64
  17. package/dist/Breadcrumbs/BreadcrumbItem.js +6 -6
  18. package/dist/Button/Button.js +5 -5
  19. package/dist/Calendar/Calendar.d.ts +17 -0
  20. package/dist/Calendar/Calendar.d.ts.map +1 -1
  21. package/dist/Calendar/Calendar.js +55 -15
  22. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
  23. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
  24. package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
  25. package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
  26. package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
  27. package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
  28. package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
  29. package/dist/Calendar/hooks/useCalendarDays.js +14 -10
  30. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  31. package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
  32. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
  33. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
  34. package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
  35. package/dist/Calendar/styles.js +3 -3
  36. package/dist/Center/Center.d.ts +23 -1
  37. package/dist/Center/Center.d.ts.map +1 -1
  38. package/dist/Center/Center.js +12 -6
  39. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  40. package/dist/Chat/ChatComposer.js +2 -2
  41. package/dist/Chat/ChatToolCalls.js +1 -1
  42. package/dist/Chat/useChatDictation.d.ts +1 -1
  43. package/dist/Chat/useChatDictation.d.ts.map +1 -1
  44. package/dist/Chat/useSpeechRecognition.d.ts +1 -1
  45. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  46. package/dist/Chat/useSpeechRecognition.js +4 -3
  47. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  48. package/dist/CheckboxInput/CheckboxInput.js +9 -0
  49. package/dist/Citation/Citation.js +3 -3
  50. package/dist/ClickableCard/ClickableCard.js +2 -2
  51. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  52. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  53. package/dist/ComplexSelector/ComplexSelector.js +8 -3
  54. package/dist/DateInput/DateInput.d.ts.map +1 -1
  55. package/dist/DateInput/DateInput.js +17 -9
  56. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  57. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  58. package/dist/DateRangeInput/DateRangeInput.js +47 -9
  59. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  60. package/dist/DateTimeInput/DateTimeInput.js +18 -10
  61. package/dist/Dialog/Dialog.d.ts.map +1 -1
  62. package/dist/Dialog/Dialog.js +3 -3
  63. package/dist/Field/FieldLabel.d.ts +2 -1
  64. package/dist/Field/FieldLabel.d.ts.map +1 -1
  65. package/dist/Field/FieldLabel.js +21 -3
  66. package/dist/Field/InputClearButton.d.ts +2 -1
  67. package/dist/Field/InputClearButton.d.ts.map +1 -1
  68. package/dist/Field/InputClearButton.js +12 -1
  69. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  70. package/dist/Field/inputStyles.stylex.js +4 -4
  71. package/dist/FileInput/FileInput.d.ts.map +1 -1
  72. package/dist/FileInput/FileInput.js +2 -2
  73. package/dist/FormLayout/FormLayout.d.ts +24 -2
  74. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  75. package/dist/FormLayout/FormLayout.js +7 -2
  76. package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
  77. package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
  78. package/dist/FormLayout/FormLayoutContext.js +19 -4
  79. package/dist/FormLayout/index.d.ts +1 -1
  80. package/dist/FormLayout/index.d.ts.map +1 -1
  81. package/dist/HoverCard/HoverCard.d.ts +13 -3
  82. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  83. package/dist/HoverCard/HoverCard.js +2 -0
  84. package/dist/HoverCard/index.d.ts +1 -1
  85. package/dist/HoverCard/index.d.ts.map +1 -1
  86. package/dist/HoverCard/useHoverCard.d.ts +17 -2
  87. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  88. package/dist/HoverCard/useHoverCard.js +78 -9
  89. package/dist/Item/Item.d.ts +9 -1
  90. package/dist/Item/Item.d.ts.map +1 -1
  91. package/dist/Item/Item.js +46 -8
  92. package/dist/Layer/index.d.ts +4 -2
  93. package/dist/Layer/index.d.ts.map +1 -1
  94. package/dist/Layer/index.js +2 -0
  95. package/dist/Layer/useLayer.d.ts.map +1 -1
  96. package/dist/Layer/useLayer.js +3 -2
  97. package/dist/Layer/useTouchTrigger.d.ts +113 -0
  98. package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
  99. package/dist/Layer/useTouchTrigger.js +241 -0
  100. package/dist/Layout/container.stylex.d.ts +4 -2
  101. package/dist/Layout/container.stylex.d.ts.map +1 -1
  102. package/dist/Layout/container.stylex.js +19 -11
  103. package/dist/Layout/index.d.ts +1 -0
  104. package/dist/Layout/index.d.ts.map +1 -1
  105. package/dist/Layout/index.js +4 -0
  106. package/dist/Layout/padding.stylex.d.ts +315 -12
  107. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  108. package/dist/Layout/padding.stylex.js +389 -12
  109. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  110. package/dist/Lightbox/Lightbox.js +2 -1
  111. package/dist/Link/Link.js +4 -4
  112. package/dist/Markdown/Markdown.d.ts +8 -0
  113. package/dist/Markdown/Markdown.d.ts.map +1 -1
  114. package/dist/Markdown/Markdown.js +31 -3
  115. package/dist/Markdown/parser.d.ts +14 -2
  116. package/dist/Markdown/parser.d.ts.map +1 -1
  117. package/dist/Markdown/parser.js +50 -2
  118. package/dist/MobileNav/MobileNav.d.ts +1 -0
  119. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  120. package/dist/MobileNav/MobileNav.js +24 -6
  121. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  122. package/dist/MultiSelector/MultiSelector.js +8 -3
  123. package/dist/NavItem/navItemStyles.stylex.d.ts +2 -2
  124. package/dist/NavItem/navItemStyles.stylex.js +2 -2
  125. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  126. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  127. package/dist/NumberInput/NumberInput.js +38 -5
  128. package/dist/Outline/Outline.js +2 -2
  129. package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
  130. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  131. package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
  132. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  133. package/dist/PowerSearch/PowerSearch.js +11 -12
  134. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  135. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  136. package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
  137. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  138. package/dist/PowerSearch/formatFilterValue.js +23 -8
  139. package/dist/RadioList/RadioList.d.ts.map +1 -1
  140. package/dist/RadioList/RadioList.js +12 -1
  141. package/dist/RadioList/RadioListItem.d.ts +2 -2
  142. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  143. package/dist/RadioList/RadioListItem.js +68 -38
  144. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  145. package/dist/Resizable/ResizeHandle.js +3 -3
  146. package/dist/Section/Section.d.ts +36 -1
  147. package/dist/Section/Section.d.ts.map +1 -1
  148. package/dist/Section/Section.js +12 -2
  149. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  150. package/dist/SegmentedControl/SegmentedControlItem.js +6 -1
  151. package/dist/SelectableCard/SelectableCard.js +1 -1
  152. package/dist/Selector/Selector.d.ts +26 -1
  153. package/dist/Selector/Selector.d.ts.map +1 -1
  154. package/dist/Selector/Selector.js +95 -17
  155. package/dist/Selector/SelectorOption.d.ts +14 -1
  156. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  157. package/dist/Selector/SelectorOption.js +6 -0
  158. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  159. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  160. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  161. package/dist/Selector/types.d.ts +1 -0
  162. package/dist/Selector/types.d.ts.map +1 -1
  163. package/dist/SideNav/SideNavHeading.js +1 -1
  164. package/dist/SideNav/SideNavItem.js +1 -1
  165. package/dist/Slider/Slider.d.ts.map +1 -1
  166. package/dist/Slider/Slider.js +3 -3
  167. package/dist/Stack/Stack.d.ts +23 -1
  168. package/dist/Stack/Stack.d.ts.map +1 -1
  169. package/dist/Stack/Stack.js +12 -6
  170. package/dist/StatusDot/StatusDot.d.ts +43 -3
  171. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  172. package/dist/StatusDot/StatusDot.js +42 -5
  173. package/dist/Switch/Switch.d.ts.map +1 -1
  174. package/dist/Switch/Switch.js +9 -0
  175. package/dist/TabList/TabMenu.js +1 -1
  176. package/dist/Table/TableRow.js +4 -4
  177. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  178. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  179. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  180. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +1 -1
  181. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  182. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  183. package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
  184. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  185. package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
  186. package/dist/Table/plugins/tree/useTableTreeData.js +2 -2
  187. package/dist/TextArea/TextArea.d.ts.map +1 -1
  188. package/dist/TextArea/TextArea.js +6 -1
  189. package/dist/TextInput/TextInput.d.ts.map +1 -1
  190. package/dist/TextInput/TextInput.js +6 -1
  191. package/dist/Thumbnail/Thumbnail.js +1 -1
  192. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  193. package/dist/TimeInput/TimeInput.js +6 -1
  194. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  195. package/dist/Timestamp/Timestamp.js +6 -4
  196. package/dist/Timestamp/formatInstant.d.ts +3 -2
  197. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  198. package/dist/Timestamp/formatInstant.js +8 -8
  199. package/dist/Timestamp/tooltipEntries.d.ts +2 -1
  200. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  201. package/dist/Timestamp/tooltipEntries.js +5 -3
  202. package/dist/Token/Token.js +1 -1
  203. package/dist/Tooltip/Tooltip.d.ts +16 -3
  204. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  205. package/dist/Tooltip/Tooltip.js +2 -0
  206. package/dist/Tooltip/index.d.ts +1 -1
  207. package/dist/Tooltip/index.d.ts.map +1 -1
  208. package/dist/Tooltip/useTooltip.d.ts +21 -2
  209. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  210. package/dist/Tooltip/useTooltip.js +71 -13
  211. package/dist/TopNav/TopNavHeading.js +2 -2
  212. package/dist/TopNav/TopNavItem.js +2 -2
  213. package/dist/TopNav/TopNavMegaMenu.js +1 -1
  214. package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
  215. package/dist/TopNav/TopNavMenu.js +2 -2
  216. package/dist/TreeList/TreeListItem.js +1 -1
  217. package/dist/astryx.css +85 -60
  218. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  219. package/dist/hooks/containerReveal.stylex.js +18 -18
  220. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  221. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  222. package/dist/hooks/scrollbarGutter.js +103 -0
  223. package/dist/hooks/useResolvedRequired.d.ts +19 -0
  224. package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
  225. package/dist/hooks/useResolvedRequired.js +40 -0
  226. package/dist/hooks/useScrollLock.d.ts +4 -0
  227. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  228. package/dist/hooks/useScrollLock.js +13 -1
  229. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  230. package/dist/i18n/InternationalizationContext.js +2 -0
  231. package/dist/i18n/index.d.ts +6 -0
  232. package/dist/i18n/index.d.ts.map +1 -1
  233. package/dist/i18n/index.js +6 -0
  234. package/dist/i18n/useCollator.d.ts +12 -0
  235. package/dist/i18n/useCollator.d.ts.map +1 -0
  236. package/dist/i18n/useCollator.js +40 -0
  237. package/dist/i18n/useLocale.d.ts +3 -0
  238. package/dist/i18n/useLocale.d.ts.map +1 -0
  239. package/dist/i18n/useLocale.js +31 -0
  240. package/dist/theme/defineTheme.d.ts +5 -0
  241. package/dist/theme/defineTheme.d.ts.map +1 -1
  242. package/dist/theme/defineTheme.js +5 -0
  243. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  244. package/dist/theme/derivedVarRegistry.js +7 -0
  245. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  246. package/dist/theme/generateThemeRules.js +66 -5
  247. package/dist/utils/dateParser.d.ts +4 -3
  248. package/dist/utils/dateParser.d.ts.map +1 -1
  249. package/dist/utils/dateParser.js +8 -8
  250. package/dist/utils/plainDate.d.ts +9 -2
  251. package/dist/utils/plainDate.d.ts.map +1 -1
  252. package/dist/utils/plainDate.js +16 -4
  253. package/locales/af-ZA.json +958 -0
  254. package/locales/ar-SA.json +998 -0
  255. package/locales/ca-ES.json +974 -0
  256. package/locales/cs-CZ.json +990 -0
  257. package/locales/da-DK.json +966 -0
  258. package/locales/de-DE.json +966 -0
  259. package/locales/el-GR.json +990 -0
  260. package/locales/en.json +8 -0
  261. package/locales/es-ES.json +978 -0
  262. package/locales/fi-FI.json +990 -0
  263. package/locales/fr-FR.json +928 -0
  264. package/locales/he-IL.json +990 -0
  265. package/locales/hu-HU.json +990 -0
  266. package/locales/it-IT.json +978 -0
  267. package/locales/ja-JP.json +998 -0
  268. package/locales/ko-KR.json +990 -0
  269. package/locales/nl-NL.json +954 -0
  270. package/locales/no-NO.json +978 -0
  271. package/locales/pl-PL.json +982 -0
  272. package/locales/pseudo.json +6 -0
  273. package/locales/pt-BR.json +982 -0
  274. package/locales/pt-PT.json +982 -0
  275. package/locales/ro-RO.json +982 -0
  276. package/locales/ru-RU.json +990 -0
  277. package/locales/sr-SP.json +990 -0
  278. package/locales/sv-SE.json +986 -0
  279. package/locales/tr-TR.json +990 -0
  280. package/locales/uk-UA.json +990 -0
  281. package/locales/vi-VN.json +986 -0
  282. package/locales/zh-CN.json +998 -0
  283. package/locales/zh-TW.json +998 -0
  284. package/package.json +3 -3
  285. package/src/AvatarGroup/AvatarGroupOverflow.tsx +1 -1
  286. package/src/Banner/Banner.doc.mjs +2 -0
  287. package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
  288. package/src/BottomSheet/BottomSheet.test.tsx +177 -2
  289. package/src/BottomSheet/BottomSheet.tsx +16 -2
  290. package/src/BottomSheet/BottomSheetPanel.test.tsx +41 -0
  291. package/src/BottomSheet/BottomSheetPanel.tsx +95 -15
  292. package/src/BottomSheet/index.ts +5 -1
  293. package/src/BottomSheet/snapOffsets.test.ts +114 -47
  294. package/src/BottomSheet/snapOffsets.ts +119 -33
  295. package/src/BottomSheet/useSheetGestures.test.ts +113 -15
  296. package/src/BottomSheet/useSheetGestures.ts +196 -100
  297. package/src/Breadcrumbs/BreadcrumbItem.tsx +1 -1
  298. package/src/Button/Button.tsx +7 -7
  299. package/src/Calendar/Calendar.doc.mjs +16 -0
  300. package/src/Calendar/Calendar.test.tsx +137 -0
  301. package/src/Calendar/Calendar.tsx +80 -11
  302. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
  303. package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
  304. package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
  305. package/src/Calendar/hooks/useCalendarDays.ts +13 -10
  306. package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
  307. package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
  308. package/src/Calendar/styles.ts +3 -3
  309. package/src/Center/Center.doc.mjs +48 -0
  310. package/src/Center/Center.test.tsx +158 -0
  311. package/src/Center/Center.tsx +50 -9
  312. package/src/Chat/ChatComposer.tsx +7 -4
  313. package/src/Chat/ChatToolCalls.tsx +1 -1
  314. package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
  315. package/src/Chat/useChatDictation.ts +1 -1
  316. package/src/Chat/useSpeechRecognition.ts +5 -3
  317. package/src/CheckboxInput/CheckboxInput.tsx +6 -0
  318. package/src/Citation/Citation.tsx +3 -3
  319. package/src/ClickableCard/ClickableCard.tsx +2 -2
  320. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  321. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
  322. package/src/ComplexSelector/ComplexSelector.tsx +8 -5
  323. package/src/DateInput/DateInput.test.tsx +17 -1
  324. package/src/DateInput/DateInput.tsx +13 -9
  325. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  326. package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
  327. package/src/DateRangeInput/DateRangeInput.tsx +88 -8
  328. package/src/DateTimeInput/DateTimeInput.tsx +23 -8
  329. package/src/Dialog/Dialog.test.tsx +24 -0
  330. package/src/Dialog/Dialog.tsx +3 -0
  331. package/src/Field/Field.doc.mjs +3 -0
  332. package/src/Field/FieldLabel.tsx +20 -4
  333. package/src/Field/InputClearButton.test.tsx +134 -2
  334. package/src/Field/InputClearButton.tsx +42 -1
  335. package/src/Field/inputStyles.stylex.ts +16 -12
  336. package/src/FileInput/FileInput.tsx +5 -4
  337. package/src/FormLayout/FormLayout.doc.mjs +13 -0
  338. package/src/FormLayout/FormLayout.test.tsx +181 -4
  339. package/src/FormLayout/FormLayout.tsx +33 -2
  340. package/src/FormLayout/FormLayoutContext.ts +22 -7
  341. package/src/FormLayout/index.ts +1 -1
  342. package/src/HoverCard/HoverCard.doc.mjs +18 -0
  343. package/src/HoverCard/HoverCard.test.tsx +189 -0
  344. package/src/HoverCard/HoverCard.tsx +22 -2
  345. package/src/HoverCard/index.ts +1 -0
  346. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  347. package/src/HoverCard/useHoverCard.tsx +103 -7
  348. package/src/Item/Item.doc.mjs +1 -0
  349. package/src/Item/Item.test.tsx +50 -0
  350. package/src/Item/Item.tsx +41 -2
  351. package/src/Layer/index.ts +10 -0
  352. package/src/Layer/useLayer.tsx +13 -2
  353. package/src/Layer/useTouchTrigger.test.ts +70 -0
  354. package/src/Layer/useTouchTrigger.ts +366 -0
  355. package/src/Layout/Layout.doc.mjs +18 -0
  356. package/src/Layout/container.stylex.ts +11 -3
  357. package/src/Layout/index.ts +4 -0
  358. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  359. package/src/Layout/padding.stylex.ts +192 -12
  360. package/src/Lightbox/Lightbox.tsx +2 -1
  361. package/src/Link/Link.tsx +4 -4
  362. package/src/Markdown/Markdown.doc.mjs +3 -0
  363. package/src/Markdown/Markdown.test.tsx +78 -0
  364. package/src/Markdown/Markdown.tsx +41 -1
  365. package/src/Markdown/parser.ts +60 -2
  366. package/src/MobileNav/MobileNav.tsx +61 -5
  367. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  368. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  369. package/src/MultiSelector/MultiSelector.test.tsx +1 -1
  370. package/src/MultiSelector/MultiSelector.tsx +8 -5
  371. package/src/NavItem/navItemStyles.stylex.ts +2 -2
  372. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  373. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  374. package/src/NumberInput/NumberInput.test.tsx +138 -0
  375. package/src/NumberInput/NumberInput.tsx +44 -6
  376. package/src/Outline/Outline.doc.mjs +2 -0
  377. package/src/Outline/Outline.tsx +1 -1
  378. package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
  379. package/src/PowerSearch/PowerSearch.tsx +17 -8
  380. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  381. package/src/PowerSearch/formatFilterValue.test.ts +22 -2
  382. package/src/PowerSearch/formatFilterValue.ts +31 -6
  383. package/src/RadioList/RadioList.doc.mjs +1 -1
  384. package/src/RadioList/RadioList.test.tsx +170 -2
  385. package/src/RadioList/RadioList.tsx +9 -1
  386. package/src/RadioList/RadioListItem.tsx +83 -51
  387. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  388. package/src/Resizable/ResizeHandle.tsx +9 -3
  389. package/src/Section/Section.doc.mjs +55 -0
  390. package/src/Section/Section.test.tsx +201 -4
  391. package/src/Section/Section.tsx +69 -0
  392. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  393. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -2
  394. package/src/SelectableCard/SelectableCard.tsx +1 -1
  395. package/src/Selector/Selector.doc.mjs +16 -2
  396. package/src/Selector/Selector.test.tsx +525 -1
  397. package/src/Selector/Selector.tsx +166 -21
  398. package/src/Selector/SelectorOption.doc.mjs +7 -0
  399. package/src/Selector/SelectorOption.tsx +21 -0
  400. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  401. package/src/Selector/types.ts +4 -4
  402. package/src/SideNav/SideNavHeading.tsx +2 -2
  403. package/src/SideNav/SideNavItem.tsx +1 -1
  404. package/src/Slider/Slider.tsx +11 -1
  405. package/src/Stack/Stack.doc.mjs +72 -0
  406. package/src/Stack/Stack.test.tsx +156 -0
  407. package/src/Stack/Stack.tsx +50 -8
  408. package/src/StatusDot/StatusDot.doc.mjs +13 -0
  409. package/src/StatusDot/StatusDot.test.tsx +115 -8
  410. package/src/StatusDot/StatusDot.tsx +74 -9
  411. package/src/Switch/Switch.tsx +6 -0
  412. package/src/TabList/TabMenu.tsx +1 -1
  413. package/src/Table/TableRow.tsx +4 -4
  414. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +2 -1
  415. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +2 -1
  416. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +22 -4
  417. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
  418. package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
  419. package/src/Table/plugins/tree/useTableTreeData.tsx +2 -2
  420. package/src/TextArea/TextArea.doc.mjs +1 -1
  421. package/src/TextArea/TextArea.tsx +3 -1
  422. package/src/TextInput/TextInput.tsx +3 -1
  423. package/src/Thumbnail/Thumbnail.tsx +1 -1
  424. package/src/TimeInput/TimeInput.tsx +3 -1
  425. package/src/Timestamp/Timestamp.test.tsx +27 -7
  426. package/src/Timestamp/Timestamp.tsx +8 -4
  427. package/src/Timestamp/formatInstant.ts +9 -7
  428. package/src/Timestamp/tooltipEntries.test.ts +11 -6
  429. package/src/Timestamp/tooltipEntries.ts +6 -2
  430. package/src/Token/Token.tsx +1 -1
  431. package/src/Tooltip/Tooltip.doc.mjs +18 -0
  432. package/src/Tooltip/Tooltip.test.tsx +254 -1
  433. package/src/Tooltip/Tooltip.tsx +22 -2
  434. package/src/Tooltip/index.ts +1 -0
  435. package/src/Tooltip/useTooltip.doc.mjs +2 -0
  436. package/src/Tooltip/useTooltip.tsx +99 -21
  437. package/src/TopNav/TopNavHeading.tsx +1 -1
  438. package/src/TopNav/TopNavItem.tsx +2 -2
  439. package/src/TopNav/TopNavMegaMenu.tsx +1 -1
  440. package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
  441. package/src/TopNav/TopNavMenu.tsx +2 -2
  442. package/src/TreeList/TreeListItem.tsx +1 -1
  443. package/src/hooks/containerReveal.stylex.ts +54 -18
  444. package/src/hooks/scrollbarGutter.test.ts +167 -0
  445. package/src/hooks/scrollbarGutter.ts +121 -0
  446. package/src/hooks/useResolvedRequired.ts +42 -0
  447. package/src/hooks/useScrollLock.doc.mjs +4 -4
  448. package/src/hooks/useScrollLock.test.ts +73 -0
  449. package/src/hooks/useScrollLock.ts +14 -0
  450. package/src/i18n/InternationalizationContext.ts +2 -0
  451. package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
  452. package/src/i18n/__tests__/useCollator.test.tsx +115 -0
  453. package/src/i18n/__tests__/useLocale.test.tsx +52 -0
  454. package/src/i18n/index.ts +6 -0
  455. package/src/i18n/useCollator.doc.mjs +84 -0
  456. package/src/i18n/useCollator.ts +44 -0
  457. package/src/i18n/useLocale.doc.mjs +70 -0
  458. package/src/i18n/useLocale.ts +34 -0
  459. package/src/i18n/useTranslator.doc.mjs +1 -1
  460. package/src/theme/defineTheme.test.ts +9 -3
  461. package/src/theme/defineTheme.ts +5 -0
  462. package/src/theme/derivedVarRegistry.test.ts +19 -5
  463. package/src/theme/derivedVarRegistry.ts +4 -0
  464. package/src/theme/generateThemeRules.test.ts +118 -0
  465. package/src/theme/generateThemeRules.ts +83 -5
  466. package/src/utils/dateParser.test.ts +20 -0
  467. package/src/utils/dateParser.ts +14 -7
  468. package/src/utils/plainDate.test.ts +56 -0
  469. package/src/utils/plainDate.ts +17 -4
@@ -14,14 +14,14 @@
14
14
  */
15
15
 
16
16
  import {useState, useCallback, useEffect, useMemo, useRef} from 'react';
17
- import {useTranslator} from '../i18n';
17
+ import {useLocale, useTranslator} from '../i18n';
18
18
 
19
19
  // =============================================================================
20
20
  // Types
21
21
  // =============================================================================
22
22
 
23
23
  export interface UseSpeechRecognitionOptions {
24
- /** BCP-47 language tag. @default navigator.language */
24
+ /** BCP-47 language tag. @default InternationalizationProvider locale */
25
25
  lang?: string;
26
26
  /** Whether recognition continues until explicitly stopped. @default true */
27
27
  continuous?: boolean;
@@ -260,6 +260,7 @@ export function useSpeechRecognition(
260
260
  options: UseSpeechRecognitionOptions = {},
261
261
  ): UseSpeechRecognitionReturn {
262
262
  const t = useTranslator();
263
+ const providerLocale = useLocale();
263
264
  const {
264
265
  lang,
265
266
  continuous = true,
@@ -348,7 +349,7 @@ export function useSpeechRecognition(
348
349
  }
349
350
  recognitionRef.current?.abort();
350
351
  const recognition = new SR();
351
- recognition.lang = lang ?? navigator.language;
352
+ recognition.lang = lang ?? providerLocale;
352
353
  recognition.continuous = continuous;
353
354
  recognition.interimResults = interimResults;
354
355
 
@@ -418,6 +419,7 @@ export function useSpeechRecognition(
418
419
  recognition.start();
419
420
  }, [
420
421
  lang,
422
+ providerLocale,
421
423
  continuous,
422
424
  interimResults,
423
425
  startVolumePolling,
@@ -47,6 +47,7 @@ import {useTooltip} from '../Tooltip';
47
47
  import {mergeProps, mergeRefs} from '../utils';
48
48
  import {indicatorScope} from '../Indicator/indicator.markers.stylex';
49
49
  import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
50
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
50
51
  import {useIndicator} from '../Indicator';
51
52
  import {themeProps} from '../utils/themeProps';
52
53
  import {CheckboxListContext} from '../CheckboxList/CheckboxListContext';
@@ -301,6 +302,10 @@ export function CheckboxInput({
301
302
  const id = useId();
302
303
  const descriptionID = useId();
303
304
  const statusMessageID = useId();
305
+ // Announce the effective required state (form default included) while the
306
+ // native `required` stays bound to the explicit `isRequired` so a layout
307
+ // default never switches on browser validation.
308
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
304
309
 
305
310
  const [, startTransition] = useTransition();
306
311
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
@@ -421,6 +426,7 @@ export function CheckboxInput({
421
426
  form={isFocusableDisabled ? '' : undefined}
422
427
  readOnly={isReadOnly}
423
428
  required={isRequired}
429
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
424
430
  onChange={e => {
425
431
  if (isDisabled || isBusy || isReadOnly) {
426
432
  return;
@@ -103,7 +103,7 @@ const styles = stylex.create({
103
103
  },
104
104
  labelHover: {
105
105
  backgroundColor: {
106
- ':hover': {
106
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
107
107
  '@media (hover: hover)': colorVars['--color-overlay-hover'],
108
108
  },
109
109
  },
@@ -112,7 +112,7 @@ const styles = stylex.create({
112
112
  // the base secondary color from `label` (last-wins property merge),
113
113
  // leaving linked citations to inherit the surrounding text color.
114
114
  default: colorVars['--color-text-secondary'],
115
- ':hover': {
115
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
116
116
  '@media (hover: hover)': colorVars['--color-text-primary'],
117
117
  },
118
118
  },
@@ -145,7 +145,7 @@ const styles = stylex.create({
145
145
  // hover-only conditional replaces the base accent-muted pill from
146
146
  // `number` on merge, leaving linked badges transparent.
147
147
  default: colorVars['--color-accent-muted'],
148
- ':hover': {
148
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
149
149
  '@media (hover: hover)': colorVars['--color-overlay-hover'],
150
150
  },
151
151
  },
@@ -78,7 +78,7 @@ const styles = stylex.create({
78
78
  },
79
79
  hoverOnPointer: {
80
80
  '@media (hover: hover)': {
81
- ':hover::after': {
81
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))::after': {
82
82
  backgroundColor: colorVars['--color-overlay-hover'],
83
83
  },
84
84
  },
@@ -110,7 +110,7 @@ const styles = stylex.create({
110
110
  // @media (hover: hover) so touch devices don't get a stuck hover state.
111
111
  borderedHoverOnPointer: {
112
112
  '@media (hover: hover)': {
113
- ':hover': {
113
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
114
114
  borderColor: colorVars['--color-border-emphasized'],
115
115
  },
116
116
  },
@@ -49,7 +49,7 @@ const styles = stylex.create({
49
49
  userSelect: 'none',
50
50
  },
51
51
  itemHover: {
52
- ':hover': {
52
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
53
53
  [HOVER_HOVER]: {
54
54
  backgroundColor: colorVars['--color-overlay-hover'],
55
55
  },
@@ -215,6 +215,16 @@ export const docsDense = {
215
215
  description:
216
216
  'Use when a selection needs richer custom content than a Selector row. One component: it owns field, trigger, popover, focus restore, and changeAction; the render prop owns the selector-specific accessible structure.',
217
217
  bestPractices: [
218
+ {
219
+ guidance: true,
220
+ description:
221
+ 'Use variant="ghost" with a startIcon for a toolbar trigger. Use alignment="end" when a wide surface should align its end edge to the trigger.',
222
+ },
223
+ {
224
+ guidance: true,
225
+ description:
226
+ 'For staged editors, keep draft state in the composed content and call the provided onChange helper only from Apply. Cancel or dismiss without committing.',
227
+ },
218
228
  {
219
229
  guidance: true,
220
230
  description:
@@ -35,6 +35,7 @@ import {useTranslator} from '../i18n';
35
35
  import {layerAnimations} from '../Layer/layerAnimations.stylex';
36
36
  import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
37
37
  import {usePopover} from '../Popover/usePopover';
38
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
38
39
  import {
39
40
  colorVars,
40
41
  durationVars,
@@ -110,16 +111,17 @@ const styles = stylex.create({
110
111
  backgroundColor: 'transparent',
111
112
  backgroundImage: {
112
113
  default: null,
113
- ':hover': {
114
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
114
115
  '@media (hover: hover)': `linear-gradient(${colorVars['--color-overlay-hover']}, ${colorVars['--color-overlay-hover']})`,
115
116
  },
116
117
  ':active': `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`,
117
118
  },
118
119
  boxShadow: {
119
120
  default: 'none',
120
- ':hover:not(:focus-within)': {
121
- '@media (hover: hover)': 'none',
122
- },
121
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
122
+ {
123
+ '@media (hover: hover)': 'none',
124
+ },
123
125
  ':focus-within': 'none',
124
126
  },
125
127
  fontWeight: fontWeightVars['--font-weight-medium'],
@@ -344,6 +346,7 @@ export function ComplexSelector<Value>({
344
346
  ...props
345
347
  }: ComplexSelectorProps<Value>) {
346
348
  const t = useTranslator();
349
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
347
350
  const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
348
351
  const effectiveStatusVariant =
349
352
  variant === 'ghost' && statusVariant === 'attached'
@@ -497,7 +500,7 @@ export function ComplexSelector<Value>({
497
500
  aria-controls={contentId}
498
501
  aria-describedby={ariaDescribedBy}
499
502
  aria-labelledby={labelId}
500
- aria-required={isRequired ? 'true' : undefined}
503
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
501
504
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
502
505
  aria-busy={isBusy || undefined}
503
506
  disabled={isDisabled}
@@ -917,6 +917,22 @@ describe('DateInput', () => {
917
917
  expect(screen.getByDisplayValue('Jan 25, 2026')).toBeInTheDocument();
918
918
  });
919
919
 
920
+ it('follows the InternationalizationProvider locale (#5074)', () => {
921
+ render(
922
+ <InternationalizationProvider locale="es-ES">
923
+ <DateInput
924
+ label="Date"
925
+ value="2026-01-25"
926
+ onChange={() => {}}
927
+ format="date_long"
928
+ />
929
+ </InternationalizationProvider>,
930
+ );
931
+ expect(
932
+ screen.getByDisplayValue('25 de enero de 2026'),
933
+ ).toBeInTheDocument();
934
+ });
935
+
920
936
  it('renders the ISO shape for format="system_date"', () => {
921
937
  render(
922
938
  <DateInput
@@ -1186,7 +1202,7 @@ describe('DateInput clear icon theme target', () => {
1186
1202
  expect(css).toContain('.astryx-date-input-clear-icon {');
1187
1203
  expect(css).toContain('width: 12px');
1188
1204
  expect(css).toContain('height: 12px');
1189
- expect(css).toContain('.astryx-date-input-clear-icon:hover {');
1205
+ expect(css).toContain('.astryx-date-input-clear-icon:hover');
1190
1206
  expect(css).toContain('color: var(--color-icon-primary)');
1191
1207
  });
1192
1208
  });
@@ -17,6 +17,7 @@
17
17
  */
18
18
 
19
19
  import {
20
+ use,
20
21
  useId,
21
22
  useState,
22
23
  useCallback,
@@ -58,6 +59,7 @@ import {
58
59
  } from '../Calendar';
59
60
  import {useCalendarConstraints} from '../Calendar/hooks';
60
61
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
62
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
61
63
  import {usePopover} from '../Popover';
62
64
  import {useTooltip} from '../Tooltip';
63
65
  import {getInputARIA, isImeKeyEvent, parseDateInput} from '../utils';
@@ -160,7 +162,7 @@ import type {SizeValue} from '../utils/types';
160
162
  import {themeProps} from '../utils/themeProps';
161
163
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
162
164
  import {stableClassName} from '../naming';
163
- import {useTranslator} from '../i18n';
165
+ import {useTranslator, InternationalizationContext} from '../i18n';
164
166
 
165
167
  export interface DateInputProps extends Omit<
166
168
  BaseProps,
@@ -399,6 +401,8 @@ export function DateInput({
399
401
  ...rest
400
402
  }: DateInputProps) {
401
403
  const t = useTranslator();
404
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
405
+ const {locale} = use(InternationalizationContext);
402
406
  const placeholder =
403
407
  placeholderFromProps ?? t('@astryx.dateInput.placeholder');
404
408
  const size = useSize(sizeProp, 'md');
@@ -481,8 +485,8 @@ export function DateInput({
481
485
  (iso: ISODateString): string =>
482
486
  typeof format === 'function'
483
487
  ? format(iso)
484
- : formatSharedDate(plainDateFromISO(iso), format),
485
- [format],
488
+ : formatSharedDate(plainDateFromISO(iso), format, locale),
489
+ [format, locale],
486
490
  );
487
491
 
488
492
  // Display value: pending input if typing, otherwise formatted value
@@ -497,7 +501,7 @@ export function DateInput({
497
501
  const isInputValid =
498
502
  pendingInput === null || !pendingInput.trim()
499
503
  ? true
500
- : parseDateInput(pendingInput) !== null;
504
+ : parseDateInput(pendingInput, locale) !== null;
501
505
 
502
506
  const popover = usePopover({
503
507
  dialogLabel: t('@astryx.dateInput.dialogLabel'),
@@ -579,7 +583,7 @@ export function DateInput({
579
583
  setPendingInput(newValue);
580
584
 
581
585
  // If the input is valid and passes constraints, update immediately
582
- const parsed = parseDateInput(newValue);
586
+ const parsed = parseDateInput(newValue, locale);
583
587
  if (
584
588
  parsed &&
585
589
  plainDateToISO(parsed) !== value &&
@@ -592,7 +596,7 @@ export function DateInput({
592
596
  calendarRef.current?.navigateTo(parsedISO);
593
597
  }
594
598
  },
595
- [value, fireChange, isDateDisabled, isEffectivelyDisabled],
599
+ [value, fireChange, isDateDisabled, isEffectivelyDisabled, locale],
596
600
  );
597
601
 
598
602
  // Commit pending input (shared by blur and Enter key)
@@ -609,7 +613,7 @@ export function DateInput({
609
613
  return;
610
614
  }
611
615
 
612
- const parsed = parseDateInput(pendingInput);
616
+ const parsed = parseDateInput(pendingInput, locale);
613
617
  if (parsed && !isDateDisabled(parsed)) {
614
618
  const parsedISO = plainDateToISO(parsed);
615
619
  if (parsedISO !== value) {
@@ -617,7 +621,7 @@ export function DateInput({
617
621
  }
618
622
  }
619
623
  setPendingInput(null);
620
- }, [pendingInput, value, fireChange, isDateDisabled]);
624
+ }, [pendingInput, value, fireChange, isDateDisabled, locale]);
621
625
 
622
626
  // Handle blur - validate, check constraints, and clear pending input
623
627
  const handleBlur = useCallback(() => {
@@ -737,7 +741,7 @@ export function DateInput({
737
741
  readOnly={showsDisabledMessage || undefined}
738
742
  aria-labelledby={ariaLabelledBy}
739
743
  aria-describedby={ariaDescribedBy}
740
- aria-required={isRequired === true ? 'true' : undefined}
744
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
741
745
  aria-invalid={
742
746
  status?.type === 'error' || !isInputValid ? 'true' : undefined
743
747
  }
@@ -98,6 +98,18 @@ export const docs = {
98
98
  type: 'Array<(date: Date) => boolean>',
99
99
  description: 'Custom constraint functions to disable specific dates.',
100
100
  },
101
+ {
102
+ name: 'maxRangeSpan',
103
+ type: 'number',
104
+ description:
105
+ 'Maximum days a selected range may span, counting both endpoints (`7` = a 7-day window, start + 6). Once a start is picked, days beyond this distance are disabled so the range cannot stretch past the cap. Rolling window relative to the start; for fixed calendar bounds use `min`/`max`. Constrains selection only; it never rewrites a `value` already wider than the cap (flag that with `status`).',
106
+ },
107
+ {
108
+ name: 'minRangeSpan',
109
+ type: 'number',
110
+ description:
111
+ 'Minimum days a selected range must span, counting both endpoints (`2` forbids a single-day range). Once a start is picked, days closer than this are disabled. Defaults to 1 (same-day start and end allowed).',
112
+ },
101
113
  {
102
114
  name: 'presets',
103
115
  type: 'Array<DateRangePreset>',
@@ -313,6 +325,10 @@ export const docsDense = {
313
325
  min: 'min selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
314
326
  max: 'max selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
315
327
  dateConstraints: 'custom constraint fns to disable dates',
328
+ maxRangeSpan:
329
+ 'max days a range may span, both endpoints counted (7 = a 7-day window); caps the window from the picked start. Selection-only; does not rewrite an over-wide value',
330
+ minRangeSpan:
331
+ 'min days a range must span, both endpoints counted (2 forbids a single-day range); default 1',
316
332
  presets: 'preset ranges as quick-select options',
317
333
  hasClear: 'clear button when range is set (default true)',
318
334
  placeholder: 'placeholder when empty',
@@ -9,7 +9,7 @@
9
9
  * SYNC: When DateRangeInput.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeEach} from 'vitest';
12
+ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
 
@@ -712,3 +712,83 @@ describe('DateRangeInput disabled theme state', () => {
712
712
  expect(root).not.toHaveAttribute('data-disabled');
713
713
  });
714
714
  });
715
+
716
+ describe('DateRangeInput range-span forwarding', () => {
717
+ // Pin "today" so the popover opens on a known month and the day buttons we
718
+ // query are guaranteed to render.
719
+ beforeEach(() => {
720
+ vi.useFakeTimers();
721
+ vi.setSystemTime(new Date('2026-01-05T12:00:00Z'));
722
+ });
723
+ afterEach(() => {
724
+ vi.useRealTimers();
725
+ });
726
+
727
+ // The calendar renders in the top layer; jsdom keeps day buttons in the DOM
728
+ // but role queries skip them, so reach them by their machine-readable
729
+ // data-date (ISO) attribute — the same approach Calendar's own tests use.
730
+ const dayButton = (iso: string): HTMLButtonElement | null =>
731
+ document.querySelector<HTMLButtonElement>(`button[data-date="${iso}"]`);
732
+
733
+ it('forwards maxRangeSpan so the window caps after a start is picked', () => {
734
+ render(
735
+ <DateRangeInput
736
+ label="Reporting period"
737
+ value={null}
738
+ onChange={() => {}}
739
+ maxRangeSpan={7}
740
+ />,
741
+ );
742
+
743
+ fireEvent.click(getButton('Open calendar'));
744
+
745
+ // Before a start is picked, a far-off day is selectable.
746
+ expect(dayButton('2026-01-20')).not.toBeDisabled();
747
+
748
+ fireEvent.click(dayButton('2026-01-10') as HTMLButtonElement);
749
+
750
+ // A 7-day window spans start ± 6 days: Jan 16 is the edge, Jan 17 is out.
751
+ expect(dayButton('2026-01-16')).not.toBeDisabled();
752
+ expect(dayButton('2026-01-17')).toBeDisabled();
753
+ });
754
+
755
+ it('disables a preset whose range violates the span cap', () => {
756
+ const presets = [
757
+ {
758
+ label: 'Last 3 days',
759
+ getRange: (): DateRange => ({
760
+ start: '2026-01-08',
761
+ end: '2026-01-10',
762
+ }),
763
+ },
764
+ {
765
+ label: 'Last 30 days',
766
+ getRange: (): DateRange => ({
767
+ start: '2025-12-12',
768
+ end: '2026-01-10',
769
+ }),
770
+ },
771
+ ];
772
+ const handleChange = vi.fn();
773
+ render(
774
+ <DateRangeInput
775
+ label="Reporting period"
776
+ value={null}
777
+ onChange={handleChange}
778
+ maxRangeSpan={7}
779
+ presets={presets}
780
+ />,
781
+ );
782
+
783
+ fireEvent.click(getButton('Open calendar'));
784
+
785
+ // The 3-day preset fits the 7-day cap; the 30-day preset can't be committed.
786
+ const withinCap = getButton('Last 3 days');
787
+ const overCap = getButton('Last 30 days');
788
+ expect(withinCap).not.toBeDisabled();
789
+ expect(overCap).toBeDisabled();
790
+
791
+ fireEvent.click(overCap);
792
+ expect(handleChange).not.toHaveBeenCalled();
793
+ });
794
+ });
@@ -16,12 +16,20 @@
16
16
  * - /packages/cli/assets/templates/blocks/components/DateRangeInput/ (showcase blocks)
17
17
  */
18
18
 
19
- import {useId, useCallback, useMemo, useOptimistic, useTransition} from 'react';
19
+ import {
20
+ use,
21
+ useId,
22
+ useCallback,
23
+ useMemo,
24
+ useOptimistic,
25
+ useTransition,
26
+ } from 'react';
20
27
  import * as stylex from '@stylexjs/stylex';
21
28
  import {
22
29
  plainDateFromISO,
23
30
  plainDateToday,
24
31
  plainDateFormat,
32
+ plainDateDiffDays,
25
33
  DATE_FORMAT_SHORT,
26
34
  DATE_FORMAT_SHORT_WITH_YEAR,
27
35
  } from '../utils/plainDate';
@@ -60,10 +68,12 @@ import type {BaseProps} from '../BaseProps';
60
68
  import type {SizeValue} from '../utils/types';
61
69
  import {useSize} from '../SizeContext/SizeContext';
62
70
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
71
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
63
72
  import {themeProps} from '../utils/themeProps';
64
73
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
65
74
  import {stableClassName} from '../naming';
66
- import {useTranslator} from '../i18n';
75
+ import {useTranslator, InternationalizationContext} from '../i18n';
76
+ import type {Locale} from '../i18n/types';
67
77
 
68
78
  export type {DateRange} from '../Calendar';
69
79
 
@@ -146,7 +156,7 @@ const styles = stylex.create({
146
156
  borderRadius: radiusVars['--radius-element'],
147
157
  backgroundColor: {
148
158
  default: 'transparent',
149
- ':hover': {
159
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
150
160
  '@media (hover: hover)': colorVars['--color-overlay-hover'],
151
161
  },
152
162
  },
@@ -161,6 +171,11 @@ const styles = stylex.create({
161
171
  backgroundColor: colorVars['--color-accent-muted'],
162
172
  color: colorVars['--color-accent'],
163
173
  },
174
+ presetButtonDisabled: {
175
+ color: colorVars['--color-text-disabled'],
176
+ cursor: 'not-allowed',
177
+ backgroundColor: 'transparent',
178
+ },
164
179
  });
165
180
 
166
181
  const sizeStyles = stylex.create({
@@ -178,7 +193,7 @@ const sizeStyles = stylex.create({
178
193
  },
179
194
  });
180
195
 
181
- function formatRangeDisplay(range: DateRange | null): string {
196
+ function formatRangeDisplay(range: DateRange | null, locale?: Locale): string {
182
197
  if (!range) {
183
198
  return '';
184
199
  }
@@ -188,7 +203,7 @@ function formatRangeDisplay(range: DateRange | null): string {
188
203
  const sameYear = start.year === end.year && start.year === currentYear;
189
204
 
190
205
  const fmt = sameYear ? DATE_FORMAT_SHORT : DATE_FORMAT_SHORT_WITH_YEAR;
191
- return `${plainDateFormat(start, fmt)} – ${plainDateFormat(end, fmt)}`;
206
+ return `${plainDateFormat(start, fmt, locale)} – ${plainDateFormat(end, fmt, locale)}`;
192
207
  }
193
208
 
194
209
  function isRangeEqual(a: DateRange | null, b: DateRange | null): boolean {
@@ -201,6 +216,34 @@ function isRangeEqual(a: DateRange | null, b: DateRange | null): boolean {
201
216
  return a.start === b.start && a.end === b.end;
202
217
  }
203
218
 
219
+ // A preset that would land outside the span bounds is disabled rather than
220
+ // allowed to override them: the cap is authoritative, so an out-of-window
221
+ // preset stays visible (discoverable) but non-committable, mirroring how the
222
+ // calendar disables out-of-window days. Spans count both endpoints.
223
+ function isRangeWithinSpan(
224
+ range: DateRange,
225
+ maxRangeSpan: number | undefined,
226
+ minRangeSpan: number | undefined,
227
+ ): boolean {
228
+ if (maxRangeSpan == null && minRangeSpan == null) {
229
+ return true;
230
+ }
231
+ const span =
232
+ Math.abs(
233
+ plainDateDiffDays(
234
+ plainDateFromISO(range.start),
235
+ plainDateFromISO(range.end),
236
+ ),
237
+ ) + 1;
238
+ if (maxRangeSpan != null && span > maxRangeSpan) {
239
+ return false;
240
+ }
241
+ if (minRangeSpan != null && span < minRangeSpan) {
242
+ return false;
243
+ }
244
+ return true;
245
+ }
246
+
204
247
  export interface DateRangeInputProps extends Omit<
205
248
  BaseProps,
206
249
  'onChange' | 'defaultValue'
@@ -303,6 +346,30 @@ export interface DateRangeInputProps extends Omit<
303
346
  */
304
347
  dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
305
348
 
349
+ /**
350
+ * Maximum number of days the selected range may span, counting both
351
+ * endpoints — `maxRangeSpan={7}` allows a 7-day window (start + 6 days).
352
+ * Once a start date is picked, days beyond this distance from it are
353
+ * disabled, so the user can't stretch the range past the cap. Use for
354
+ * rolling windows like "at most a week from the chosen day"; for fixed
355
+ * calendar bounds use `min`/`max`.
356
+ *
357
+ * This constrains selection only — it never rewrites a `value` that is
358
+ * already wider than the cap. Surface such a value with `status` if you
359
+ * need to flag it. A `preset` whose range violates the cap is disabled
360
+ * (shown but not committable) rather than allowed to override it.
361
+ */
362
+ maxRangeSpan?: number;
363
+
364
+ /**
365
+ * Minimum number of days the selected range must span, counting both
366
+ * endpoints — `minRangeSpan={2}` forbids a single-day range. Once a start
367
+ * date is picked, days closer than this to it are disabled — except the
368
+ * start itself, which stays selectable as the active anchor. Defaults to 1
369
+ * (a same-day start and end is allowed).
370
+ */
371
+ minRangeSpan?: number;
372
+
306
373
  /**
307
374
  * Preset date ranges shown as quick-select options beside the calendar.
308
375
  */
@@ -396,6 +463,8 @@ export function DateRangeInput({
396
463
  min,
397
464
  max,
398
465
  dateConstraints,
466
+ maxRangeSpan,
467
+ minRangeSpan,
399
468
  presets,
400
469
  hasClear = true,
401
470
  placeholder: placeholderFromProps,
@@ -413,6 +482,8 @@ export function DateRangeInput({
413
482
  ...rest
414
483
  }: DateRangeInputProps) {
415
484
  const t = useTranslator();
485
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
486
+ const {locale} = use(InternationalizationContext);
416
487
  const placeholder =
417
488
  placeholderFromProps ?? t('@astryx.dateRangeInput.placeholder');
418
489
  const size = useSize(sizeProp, 'md');
@@ -459,8 +530,8 @@ export function DateRangeInput({
459
530
  .join(' ') || undefined;
460
531
 
461
532
  const displayValue = useMemo(
462
- () => formatRangeDisplay(optimisticValue),
463
- [optimisticValue],
533
+ () => formatRangeDisplay(optimisticValue, locale),
534
+ [optimisticValue, locale],
464
535
  );
465
536
 
466
537
  const popover = usePopover({
@@ -609,7 +680,7 @@ export function DateRangeInput({
609
680
  aria-disabled={showsDisabledMessage ? 'true' : undefined}
610
681
  aria-label={triggerAriaLabel}
611
682
  aria-describedby={ariaDescribedBy}
612
- aria-required={isRequired === true ? 'true' : undefined}
683
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
613
684
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
614
685
  aria-busy={isBusy || undefined}
615
686
  aria-expanded={popover.isOpen}
@@ -642,6 +713,11 @@ export function DateRangeInput({
642
713
  {presets.map(preset => {
643
714
  const presetRange = preset.getRange();
644
715
  const isActive = isRangeEqual(value, presetRange);
716
+ const isPresetDisabled = !isRangeWithinSpan(
717
+ presetRange,
718
+ maxRangeSpan,
719
+ minRangeSpan,
720
+ );
645
721
  return (
646
722
  <button
647
723
  key={preset.label}
@@ -652,11 +728,13 @@ export function DateRangeInput({
652
728
  // marked with aria-current (not aria-selected, a listbox
653
729
  // concept that contradicted the Tab interaction) (forms-5).
654
730
  aria-current={isActive ? 'true' : undefined}
731
+ disabled={isPresetDisabled}
655
732
  onClick={() => handlePresetClick(preset)}
656
733
  {...stylex.props(
657
734
  focusOutlineStyles.focusVisible,
658
735
  styles.presetButton,
659
736
  isActive && styles.presetButtonActive,
737
+ isPresetDisabled && styles.presetButtonDisabled,
660
738
  )}>
661
739
  {preset.label}
662
740
  </button>
@@ -671,6 +749,8 @@ export function DateRangeInput({
671
749
  min={min}
672
750
  max={max}
673
751
  dateConstraints={dateConstraints}
752
+ maxRangeSpan={maxRangeSpan}
753
+ minRangeSpan={minRangeSpan}
674
754
  numberOfMonths={numberOfMonths}
675
755
  weekStartsOn={weekStartsOn}
676
756
  />