@astryxdesign/core 0.4.4 → 0.4.5-canary.0472ec1

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 (522) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/AvatarGroup/AvatarGroupOverflow.js +1 -1
  3. package/dist/Blockquote/Blockquote.d.ts +4 -3
  4. package/dist/Blockquote/Blockquote.d.ts.map +1 -1
  5. package/dist/Blockquote/Blockquote.js +9 -10
  6. package/dist/BottomSheet/BottomSheet.d.ts +11 -2
  7. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  8. package/dist/BottomSheet/BottomSheet.js +4 -0
  9. package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
  10. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  11. package/dist/BottomSheet/BottomSheetPanel.js +77 -18
  12. package/dist/BottomSheet/index.d.ts +1 -1
  13. package/dist/BottomSheet/index.d.ts.map +1 -1
  14. package/dist/BottomSheet/snapOffsets.d.ts +57 -20
  15. package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
  16. package/dist/BottomSheet/snapOffsets.js +106 -30
  17. package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
  18. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  19. package/dist/BottomSheet/useSheetGestures.js +160 -64
  20. package/dist/Breadcrumbs/BreadcrumbItem.js +6 -6
  21. package/dist/Button/Button.js +5 -5
  22. package/dist/Calendar/Calendar.d.ts +17 -0
  23. package/dist/Calendar/Calendar.d.ts.map +1 -1
  24. package/dist/Calendar/Calendar.js +55 -15
  25. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
  26. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
  27. package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
  28. package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
  29. package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
  30. package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
  31. package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
  32. package/dist/Calendar/hooks/useCalendarDays.js +14 -10
  33. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  34. package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
  35. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
  36. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
  37. package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
  38. package/dist/Calendar/styles.js +3 -3
  39. package/dist/Center/Center.d.ts +23 -1
  40. package/dist/Center/Center.d.ts.map +1 -1
  41. package/dist/Center/Center.js +12 -6
  42. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposer.js +2 -2
  44. package/dist/Chat/ChatToolCalls.js +1 -1
  45. package/dist/Chat/useChatDictation.d.ts +1 -1
  46. package/dist/Chat/useChatDictation.d.ts.map +1 -1
  47. package/dist/Chat/useSpeechRecognition.d.ts +1 -1
  48. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  49. package/dist/Chat/useSpeechRecognition.js +4 -3
  50. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  51. package/dist/CheckboxInput/CheckboxInput.js +9 -0
  52. package/dist/Citation/Citation.js +3 -3
  53. package/dist/ClickableCard/ClickableCard.js +2 -2
  54. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  55. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  56. package/dist/ComplexSelector/ComplexSelector.js +8 -3
  57. package/dist/DateInput/DateInput.d.ts +31 -2
  58. package/dist/DateInput/DateInput.d.ts.map +1 -1
  59. package/dist/DateInput/DateInput.js +92 -19
  60. package/dist/DateInput/MonthScroller.d.ts +35 -0
  61. package/dist/DateInput/MonthScroller.d.ts.map +1 -0
  62. package/dist/DateInput/MonthScroller.js +573 -0
  63. package/dist/DateInput/MonthYearWheels.d.ts +24 -0
  64. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
  65. package/dist/DateInput/MonthYearWheels.js +114 -0
  66. package/dist/DateInput/TouchDateField.d.ts +10 -0
  67. package/dist/DateInput/TouchDateField.d.ts.map +1 -0
  68. package/dist/DateInput/TouchDateField.js +810 -0
  69. package/dist/DateInput/Wheel.d.ts +41 -0
  70. package/dist/DateInput/Wheel.d.ts.map +1 -0
  71. package/dist/DateInput/Wheel.js +315 -0
  72. package/dist/DateInput/monthGeometry.d.ts +65 -0
  73. package/dist/DateInput/monthGeometry.d.ts.map +1 -0
  74. package/dist/DateInput/monthGeometry.js +98 -0
  75. package/dist/DateInput/tokens.stylex.d.ts +63 -0
  76. package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
  77. package/dist/DateInput/tokens.stylex.js +74 -0
  78. package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
  79. package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
  80. package/dist/DateInput/useOwnScrollGesture.js +202 -0
  81. package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
  82. package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
  83. package/dist/DateInput/usePointerDragScroll.js +216 -0
  84. package/dist/DateInput/useScrollSettle.d.ts +26 -0
  85. package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
  86. package/dist/DateInput/useScrollSettle.js +149 -0
  87. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  88. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  89. package/dist/DateRangeInput/DateRangeInput.js +47 -9
  90. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  91. package/dist/DateTimeInput/DateTimeInput.js +18 -10
  92. package/dist/Dialog/Dialog.d.ts.map +1 -1
  93. package/dist/Dialog/Dialog.js +3 -3
  94. package/dist/Field/FieldLabel.d.ts +2 -1
  95. package/dist/Field/FieldLabel.d.ts.map +1 -1
  96. package/dist/Field/FieldLabel.js +21 -3
  97. package/dist/Field/InputClearButton.d.ts +2 -1
  98. package/dist/Field/InputClearButton.d.ts.map +1 -1
  99. package/dist/Field/InputClearButton.js +12 -1
  100. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  101. package/dist/Field/inputStyles.stylex.js +4 -4
  102. package/dist/FileInput/FileInput.d.ts.map +1 -1
  103. package/dist/FileInput/FileInput.js +2 -2
  104. package/dist/FormLayout/FormLayout.d.ts +24 -2
  105. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  106. package/dist/FormLayout/FormLayout.js +7 -2
  107. package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
  108. package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
  109. package/dist/FormLayout/FormLayoutContext.js +19 -4
  110. package/dist/FormLayout/index.d.ts +1 -1
  111. package/dist/FormLayout/index.d.ts.map +1 -1
  112. package/dist/HoverCard/HoverCard.d.ts +13 -3
  113. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  114. package/dist/HoverCard/HoverCard.js +2 -0
  115. package/dist/HoverCard/index.d.ts +1 -1
  116. package/dist/HoverCard/index.d.ts.map +1 -1
  117. package/dist/HoverCard/useHoverCard.d.ts +17 -2
  118. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  119. package/dist/HoverCard/useHoverCard.js +78 -9
  120. package/dist/Item/Item.d.ts +9 -1
  121. package/dist/Item/Item.d.ts.map +1 -1
  122. package/dist/Item/Item.js +46 -8
  123. package/dist/Layer/index.d.ts +4 -2
  124. package/dist/Layer/index.d.ts.map +1 -1
  125. package/dist/Layer/index.js +2 -0
  126. package/dist/Layer/useLayer.d.ts.map +1 -1
  127. package/dist/Layer/useLayer.js +3 -2
  128. package/dist/Layer/useTouchTrigger.d.ts +113 -0
  129. package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
  130. package/dist/Layer/useTouchTrigger.js +241 -0
  131. package/dist/Layout/container.stylex.d.ts +4 -2
  132. package/dist/Layout/container.stylex.d.ts.map +1 -1
  133. package/dist/Layout/container.stylex.js +19 -11
  134. package/dist/Layout/index.d.ts +1 -0
  135. package/dist/Layout/index.d.ts.map +1 -1
  136. package/dist/Layout/index.js +4 -0
  137. package/dist/Layout/padding.stylex.d.ts +315 -12
  138. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  139. package/dist/Layout/padding.stylex.js +389 -12
  140. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  141. package/dist/Lightbox/Lightbox.js +2 -1
  142. package/dist/Link/Link.js +4 -4
  143. package/dist/Markdown/Markdown.d.ts +8 -0
  144. package/dist/Markdown/Markdown.d.ts.map +1 -1
  145. package/dist/Markdown/Markdown.js +31 -3
  146. package/dist/Markdown/parser.d.ts +14 -2
  147. package/dist/Markdown/parser.d.ts.map +1 -1
  148. package/dist/Markdown/parser.js +50 -2
  149. package/dist/MobileNav/MobileNav.d.ts +1 -0
  150. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  151. package/dist/MobileNav/MobileNav.js +24 -6
  152. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  153. package/dist/MultiSelector/MultiSelector.js +8 -3
  154. package/dist/NavItem/navItemStyles.stylex.d.ts +2 -2
  155. package/dist/NavItem/navItemStyles.stylex.js +2 -2
  156. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  157. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  158. package/dist/NumberInput/NumberInput.js +38 -5
  159. package/dist/Outline/Outline.js +2 -2
  160. package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
  161. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  162. package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
  163. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  164. package/dist/PowerSearch/PowerSearch.js +11 -12
  165. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  166. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  167. package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
  168. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  169. package/dist/PowerSearch/formatFilterValue.js +23 -8
  170. package/dist/RadioList/RadioList.d.ts.map +1 -1
  171. package/dist/RadioList/RadioList.js +12 -1
  172. package/dist/RadioList/RadioListItem.d.ts +2 -2
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +68 -38
  175. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  176. package/dist/Resizable/ResizeHandle.js +3 -3
  177. package/dist/Section/Section.d.ts +36 -1
  178. package/dist/Section/Section.d.ts.map +1 -1
  179. package/dist/Section/Section.js +12 -2
  180. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  181. package/dist/SegmentedControl/SegmentedControlItem.js +6 -1
  182. package/dist/SelectableCard/SelectableCard.js +1 -1
  183. package/dist/Selector/Selector.d.ts +26 -1
  184. package/dist/Selector/Selector.d.ts.map +1 -1
  185. package/dist/Selector/Selector.js +95 -17
  186. package/dist/Selector/SelectorOption.d.ts +14 -1
  187. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  188. package/dist/Selector/SelectorOption.js +6 -0
  189. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  190. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  191. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  192. package/dist/Selector/types.d.ts +1 -0
  193. package/dist/Selector/types.d.ts.map +1 -1
  194. package/dist/SideNav/SideNavHeading.js +1 -1
  195. package/dist/SideNav/SideNavItem.js +1 -1
  196. package/dist/Slider/Slider.d.ts.map +1 -1
  197. package/dist/Slider/Slider.js +3 -3
  198. package/dist/Stack/Stack.d.ts +23 -1
  199. package/dist/Stack/Stack.d.ts.map +1 -1
  200. package/dist/Stack/Stack.js +12 -6
  201. package/dist/StatusDot/StatusDot.d.ts +43 -3
  202. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  203. package/dist/StatusDot/StatusDot.js +42 -5
  204. package/dist/Switch/Switch.d.ts.map +1 -1
  205. package/dist/Switch/Switch.js +9 -0
  206. package/dist/TabList/TabMenu.js +1 -1
  207. package/dist/Table/TableRow.js +4 -4
  208. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  209. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  210. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  211. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +1 -1
  212. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  213. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  214. package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
  215. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  216. package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
  217. package/dist/Table/plugins/tree/useTableTreeData.js +2 -2
  218. package/dist/TextArea/TextArea.d.ts.map +1 -1
  219. package/dist/TextArea/TextArea.js +6 -1
  220. package/dist/TextInput/TextInput.d.ts.map +1 -1
  221. package/dist/TextInput/TextInput.js +6 -1
  222. package/dist/Thumbnail/Thumbnail.js +1 -1
  223. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  224. package/dist/TimeInput/TimeInput.js +6 -1
  225. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  226. package/dist/Timestamp/Timestamp.js +6 -4
  227. package/dist/Timestamp/formatInstant.d.ts +3 -2
  228. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  229. package/dist/Timestamp/formatInstant.js +8 -8
  230. package/dist/Timestamp/tooltipEntries.d.ts +2 -1
  231. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  232. package/dist/Timestamp/tooltipEntries.js +5 -3
  233. package/dist/Token/Token.js +1 -1
  234. package/dist/Tooltip/Tooltip.d.ts +16 -3
  235. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  236. package/dist/Tooltip/Tooltip.js +2 -0
  237. package/dist/Tooltip/index.d.ts +1 -1
  238. package/dist/Tooltip/index.d.ts.map +1 -1
  239. package/dist/Tooltip/useTooltip.d.ts +21 -2
  240. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  241. package/dist/Tooltip/useTooltip.js +71 -13
  242. package/dist/TopNav/TopNavHeading.js +2 -2
  243. package/dist/TopNav/TopNavItem.js +2 -2
  244. package/dist/TopNav/TopNavMegaMenu.js +1 -1
  245. package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
  246. package/dist/TopNav/TopNavMenu.js +2 -2
  247. package/dist/TreeList/TreeListItem.js +1 -1
  248. package/dist/astryx.css +123 -60
  249. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  250. package/dist/hooks/containerReveal.stylex.js +18 -18
  251. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  252. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  253. package/dist/hooks/scrollbarGutter.js +103 -0
  254. package/dist/hooks/useResolvedRequired.d.ts +19 -0
  255. package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
  256. package/dist/hooks/useResolvedRequired.js +40 -0
  257. package/dist/hooks/useScrollLock.d.ts +4 -0
  258. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  259. package/dist/hooks/useScrollLock.js +13 -1
  260. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  261. package/dist/i18n/InternationalizationContext.js +2 -0
  262. package/dist/i18n/index.d.ts +6 -0
  263. package/dist/i18n/index.d.ts.map +1 -1
  264. package/dist/i18n/index.js +6 -0
  265. package/dist/i18n/useCollator.d.ts +12 -0
  266. package/dist/i18n/useCollator.d.ts.map +1 -0
  267. package/dist/i18n/useCollator.js +40 -0
  268. package/dist/i18n/useLocale.d.ts +3 -0
  269. package/dist/i18n/useLocale.d.ts.map +1 -0
  270. package/dist/i18n/useLocale.js +31 -0
  271. package/dist/theme/defineTheme.d.ts +5 -0
  272. package/dist/theme/defineTheme.d.ts.map +1 -1
  273. package/dist/theme/defineTheme.js +5 -0
  274. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  275. package/dist/theme/derivedVarRegistry.js +7 -0
  276. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  277. package/dist/theme/generateThemeRules.js +66 -5
  278. package/dist/theme/syntax/index.d.ts +6 -0
  279. package/dist/theme/syntax/index.d.ts.map +1 -1
  280. package/dist/theme/syntax/index.js +7 -2
  281. package/dist/utils/dateParser.d.ts +4 -3
  282. package/dist/utils/dateParser.d.ts.map +1 -1
  283. package/dist/utils/dateParser.js +8 -8
  284. package/dist/utils/index.d.ts +1 -1
  285. package/dist/utils/index.d.ts.map +1 -1
  286. package/dist/utils/index.js +1 -1
  287. package/dist/utils/plainDate.d.ts +11 -2
  288. package/dist/utils/plainDate.d.ts.map +1 -1
  289. package/dist/utils/plainDate.js +32 -4
  290. package/locales/af-ZA.json +958 -0
  291. package/locales/ar-SA.json +998 -0
  292. package/locales/ca-ES.json +974 -0
  293. package/locales/cs-CZ.json +990 -0
  294. package/locales/da-DK.json +966 -0
  295. package/locales/de-DE.json +966 -0
  296. package/locales/el-GR.json +990 -0
  297. package/locales/en.json +32 -0
  298. package/locales/es-ES.json +978 -0
  299. package/locales/fi-FI.json +990 -0
  300. package/locales/fr-FR.json +928 -0
  301. package/locales/he-IL.json +990 -0
  302. package/locales/hu-HU.json +990 -0
  303. package/locales/it-IT.json +978 -0
  304. package/locales/ja-JP.json +998 -0
  305. package/locales/ko-KR.json +990 -0
  306. package/locales/nl-NL.json +954 -0
  307. package/locales/no-NO.json +978 -0
  308. package/locales/pl-PL.json +982 -0
  309. package/locales/pseudo.json +24 -0
  310. package/locales/pt-BR.json +982 -0
  311. package/locales/pt-PT.json +982 -0
  312. package/locales/ro-RO.json +982 -0
  313. package/locales/ru-RU.json +990 -0
  314. package/locales/sr-SP.json +990 -0
  315. package/locales/sv-SE.json +986 -0
  316. package/locales/tr-TR.json +990 -0
  317. package/locales/uk-UA.json +990 -0
  318. package/locales/vi-VN.json +986 -0
  319. package/locales/zh-CN.json +998 -0
  320. package/locales/zh-TW.json +998 -0
  321. package/package.json +3 -3
  322. package/src/AvatarGroup/AvatarGroupOverflow.tsx +1 -1
  323. package/src/Banner/Banner.doc.mjs +2 -0
  324. package/src/Blockquote/Blockquote.doc.mjs +30 -7
  325. package/src/Blockquote/Blockquote.test.tsx +34 -3
  326. package/src/Blockquote/Blockquote.tsx +11 -9
  327. package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
  328. package/src/BottomSheet/BottomSheet.test.tsx +177 -2
  329. package/src/BottomSheet/BottomSheet.tsx +16 -2
  330. package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
  331. package/src/BottomSheet/BottomSheetPanel.tsx +121 -15
  332. package/src/BottomSheet/index.ts +5 -1
  333. package/src/BottomSheet/snapOffsets.test.ts +114 -47
  334. package/src/BottomSheet/snapOffsets.ts +119 -33
  335. package/src/BottomSheet/useSheetGestures.test.ts +113 -15
  336. package/src/BottomSheet/useSheetGestures.ts +196 -100
  337. package/src/Breadcrumbs/BreadcrumbItem.tsx +1 -1
  338. package/src/Button/Button.tsx +7 -7
  339. package/src/Calendar/Calendar.doc.mjs +16 -0
  340. package/src/Calendar/Calendar.test.tsx +137 -0
  341. package/src/Calendar/Calendar.tsx +80 -11
  342. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
  343. package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
  344. package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
  345. package/src/Calendar/hooks/useCalendarDays.ts +13 -10
  346. package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
  347. package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
  348. package/src/Calendar/styles.ts +3 -3
  349. package/src/Center/Center.doc.mjs +48 -0
  350. package/src/Center/Center.test.tsx +158 -0
  351. package/src/Center/Center.tsx +50 -9
  352. package/src/Chat/ChatComposer.tsx +7 -4
  353. package/src/Chat/ChatToolCalls.tsx +1 -1
  354. package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
  355. package/src/Chat/useChatDictation.ts +1 -1
  356. package/src/Chat/useSpeechRecognition.ts +5 -3
  357. package/src/CheckboxInput/CheckboxInput.tsx +6 -0
  358. package/src/Citation/Citation.tsx +3 -3
  359. package/src/ClickableCard/ClickableCard.tsx +2 -2
  360. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  361. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
  362. package/src/ComplexSelector/ComplexSelector.tsx +8 -5
  363. package/src/DateInput/DateInput.test.tsx +17 -1
  364. package/src/DateInput/DateInput.tsx +90 -19
  365. package/src/DateInput/DateInputTouch.test.tsx +2393 -0
  366. package/src/DateInput/MonthScroller.tsx +784 -0
  367. package/src/DateInput/MonthYearWheels.tsx +147 -0
  368. package/src/DateInput/TouchDateField.tsx +1214 -0
  369. package/src/DateInput/Wheel.tsx +470 -0
  370. package/src/DateInput/monthGeometry.ts +118 -0
  371. package/src/DateInput/tokens.stylex.ts +99 -0
  372. package/src/DateInput/useOwnScrollGesture.ts +227 -0
  373. package/src/DateInput/usePointerDragScroll.ts +220 -0
  374. package/src/DateInput/useScrollSettle.ts +164 -0
  375. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  376. package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
  377. package/src/DateRangeInput/DateRangeInput.tsx +88 -8
  378. package/src/DateTimeInput/DateTimeInput.tsx +23 -8
  379. package/src/Dialog/Dialog.test.tsx +24 -0
  380. package/src/Dialog/Dialog.tsx +3 -0
  381. package/src/Field/Field.doc.mjs +3 -0
  382. package/src/Field/FieldLabel.tsx +20 -4
  383. package/src/Field/InputClearButton.test.tsx +134 -2
  384. package/src/Field/InputClearButton.tsx +42 -1
  385. package/src/Field/inputStyles.stylex.ts +16 -12
  386. package/src/FileInput/FileInput.tsx +5 -4
  387. package/src/FormLayout/FormLayout.doc.mjs +13 -0
  388. package/src/FormLayout/FormLayout.test.tsx +181 -4
  389. package/src/FormLayout/FormLayout.tsx +33 -2
  390. package/src/FormLayout/FormLayoutContext.ts +22 -7
  391. package/src/FormLayout/index.ts +1 -1
  392. package/src/HoverCard/HoverCard.doc.mjs +18 -0
  393. package/src/HoverCard/HoverCard.test.tsx +189 -0
  394. package/src/HoverCard/HoverCard.tsx +22 -2
  395. package/src/HoverCard/index.ts +1 -0
  396. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  397. package/src/HoverCard/useHoverCard.tsx +103 -7
  398. package/src/Item/Item.doc.mjs +1 -0
  399. package/src/Item/Item.test.tsx +50 -0
  400. package/src/Item/Item.tsx +41 -2
  401. package/src/Layer/index.ts +10 -0
  402. package/src/Layer/useLayer.tsx +13 -2
  403. package/src/Layer/useTouchTrigger.test.ts +70 -0
  404. package/src/Layer/useTouchTrigger.ts +366 -0
  405. package/src/Layout/Layout.doc.mjs +18 -0
  406. package/src/Layout/container.stylex.ts +11 -3
  407. package/src/Layout/index.ts +4 -0
  408. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  409. package/src/Layout/padding.stylex.ts +192 -12
  410. package/src/Lightbox/Lightbox.tsx +2 -1
  411. package/src/Link/Link.tsx +4 -4
  412. package/src/Markdown/Markdown.doc.mjs +3 -0
  413. package/src/Markdown/Markdown.test.tsx +78 -0
  414. package/src/Markdown/Markdown.tsx +41 -1
  415. package/src/Markdown/parser.ts +60 -2
  416. package/src/MobileNav/MobileNav.tsx +61 -5
  417. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  418. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  419. package/src/MultiSelector/MultiSelector.test.tsx +1 -1
  420. package/src/MultiSelector/MultiSelector.tsx +8 -5
  421. package/src/NavItem/navItemStyles.stylex.ts +2 -2
  422. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  423. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  424. package/src/NumberInput/NumberInput.test.tsx +138 -0
  425. package/src/NumberInput/NumberInput.tsx +44 -6
  426. package/src/Outline/Outline.doc.mjs +2 -0
  427. package/src/Outline/Outline.tsx +1 -1
  428. package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
  429. package/src/PowerSearch/PowerSearch.tsx +17 -8
  430. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  431. package/src/PowerSearch/formatFilterValue.test.ts +22 -2
  432. package/src/PowerSearch/formatFilterValue.ts +31 -6
  433. package/src/RadioList/RadioList.doc.mjs +1 -1
  434. package/src/RadioList/RadioList.test.tsx +170 -2
  435. package/src/RadioList/RadioList.tsx +9 -1
  436. package/src/RadioList/RadioListItem.tsx +83 -51
  437. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  438. package/src/Resizable/ResizeHandle.tsx +9 -3
  439. package/src/Section/Section.doc.mjs +55 -0
  440. package/src/Section/Section.test.tsx +201 -4
  441. package/src/Section/Section.tsx +69 -0
  442. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  443. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -2
  444. package/src/SelectableCard/SelectableCard.tsx +1 -1
  445. package/src/Selector/Selector.doc.mjs +16 -2
  446. package/src/Selector/Selector.test.tsx +525 -1
  447. package/src/Selector/Selector.tsx +166 -21
  448. package/src/Selector/SelectorOption.doc.mjs +7 -0
  449. package/src/Selector/SelectorOption.tsx +21 -0
  450. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  451. package/src/Selector/types.ts +4 -4
  452. package/src/SideNav/SideNavHeading.tsx +2 -2
  453. package/src/SideNav/SideNavItem.tsx +1 -1
  454. package/src/Slider/Slider.tsx +11 -1
  455. package/src/Stack/Stack.doc.mjs +72 -0
  456. package/src/Stack/Stack.test.tsx +156 -0
  457. package/src/Stack/Stack.tsx +50 -8
  458. package/src/StatusDot/StatusDot.doc.mjs +13 -0
  459. package/src/StatusDot/StatusDot.test.tsx +115 -8
  460. package/src/StatusDot/StatusDot.tsx +74 -9
  461. package/src/Switch/Switch.tsx +6 -0
  462. package/src/TabList/TabMenu.tsx +1 -1
  463. package/src/Table/TableRow.tsx +4 -4
  464. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +2 -1
  465. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +2 -1
  466. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +22 -4
  467. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
  468. package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
  469. package/src/Table/plugins/tree/useTableTreeData.tsx +2 -2
  470. package/src/TextArea/TextArea.doc.mjs +1 -1
  471. package/src/TextArea/TextArea.tsx +3 -1
  472. package/src/TextInput/TextInput.tsx +3 -1
  473. package/src/Thumbnail/Thumbnail.tsx +1 -1
  474. package/src/TimeInput/TimeInput.tsx +3 -1
  475. package/src/Timestamp/Timestamp.test.tsx +27 -7
  476. package/src/Timestamp/Timestamp.tsx +8 -4
  477. package/src/Timestamp/formatInstant.ts +9 -7
  478. package/src/Timestamp/tooltipEntries.test.ts +11 -6
  479. package/src/Timestamp/tooltipEntries.ts +6 -2
  480. package/src/Token/Token.tsx +1 -1
  481. package/src/Tooltip/Tooltip.doc.mjs +18 -0
  482. package/src/Tooltip/Tooltip.test.tsx +254 -1
  483. package/src/Tooltip/Tooltip.tsx +22 -2
  484. package/src/Tooltip/index.ts +1 -0
  485. package/src/Tooltip/useTooltip.doc.mjs +2 -0
  486. package/src/Tooltip/useTooltip.tsx +99 -21
  487. package/src/TopNav/TopNavHeading.tsx +1 -1
  488. package/src/TopNav/TopNavItem.tsx +2 -2
  489. package/src/TopNav/TopNavMegaMenu.tsx +1 -1
  490. package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
  491. package/src/TopNav/TopNavMenu.tsx +2 -2
  492. package/src/TreeList/TreeListItem.tsx +1 -1
  493. package/src/hooks/containerReveal.stylex.ts +54 -18
  494. package/src/hooks/scrollbarGutter.test.ts +167 -0
  495. package/src/hooks/scrollbarGutter.ts +121 -0
  496. package/src/hooks/useResolvedRequired.ts +42 -0
  497. package/src/hooks/useScrollLock.doc.mjs +4 -4
  498. package/src/hooks/useScrollLock.test.ts +73 -0
  499. package/src/hooks/useScrollLock.ts +14 -0
  500. package/src/i18n/InternationalizationContext.ts +2 -0
  501. package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
  502. package/src/i18n/__tests__/useCollator.test.tsx +115 -0
  503. package/src/i18n/__tests__/useLocale.test.tsx +52 -0
  504. package/src/i18n/index.ts +6 -0
  505. package/src/i18n/useCollator.doc.mjs +84 -0
  506. package/src/i18n/useCollator.ts +44 -0
  507. package/src/i18n/useLocale.doc.mjs +70 -0
  508. package/src/i18n/useLocale.ts +34 -0
  509. package/src/i18n/useTranslator.doc.mjs +1 -1
  510. package/src/theme/defineTheme.test.ts +9 -3
  511. package/src/theme/defineTheme.ts +5 -0
  512. package/src/theme/derivedVarRegistry.test.ts +19 -5
  513. package/src/theme/derivedVarRegistry.ts +4 -0
  514. package/src/theme/generateThemeRules.test.ts +118 -0
  515. package/src/theme/generateThemeRules.ts +83 -5
  516. package/src/theme/syntax/index.ts +6 -2
  517. package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
  518. package/src/utils/dateParser.test.ts +20 -0
  519. package/src/utils/dateParser.ts +14 -7
  520. package/src/utils/index.ts +2 -0
  521. package/src/utils/plainDate.test.ts +56 -0
  522. package/src/utils/plainDate.ts +33 -4
@@ -0,0 +1,784 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file MonthScroller.tsx
7
+ * @input Month bounds, selected value, constraints, week start
8
+ * @output Exports MonthScroller and MonthScrollerHandle
9
+ * @position Internal component; consumed by TouchDateField.tsx
10
+ *
11
+ * The continuous surface: months stacked vertically in one scroller, each pane
12
+ * exactly as tall as the scrollport and snapped to its start. Two consequences
13
+ * fall out of that single geometric choice — the picker never changes height,
14
+ * and there is no resting position that shows half of two months.
15
+ *
16
+ * Every pane is a fixed six-row grid, including the months that only need
17
+ * four or five, because a pane whose height depended on its contents would
18
+ * make snap offsets vary from month to month and the scroll position drift.
19
+ *
20
+ * The list is long (a century by default) but only a few panes are ever
21
+ * mounted: a spacer holds the full scroll height and the visible panes are
22
+ * positioned into it absolutely. Nothing is stitched or recycled during a
23
+ * scroll, so snap offsets are constant and momentum is never interrupted —
24
+ * which is the failure mode of the usual "append months at the edge" approach.
25
+ *
26
+ * SYNC: When modified, update:
27
+ * - /packages/core/src/DateInput/TouchDateField.tsx
28
+ * - /packages/core/src/DateInput/DateInput.doc.mjs
29
+ * - /packages/core/src/DateInput/DateInputTouch.test.tsx
30
+ */
31
+
32
+ import {
33
+ useCallback,
34
+ useEffect,
35
+ useImperativeHandle,
36
+ useLayoutEffect,
37
+ useRef,
38
+ useState,
39
+ } from 'react';
40
+ import * as stylex from '@stylexjs/stylex';
41
+ import {useCalendarDays} from '../Calendar';
42
+ import {useDirection} from '../i18n';
43
+ import {
44
+ colorVars,
45
+ radiusVars,
46
+ fontWeightVars,
47
+ typeScaleVars,
48
+ durationVars,
49
+ borderVars,
50
+ } from '../theme/tokens.stylex';
51
+ import {focusOutlineStyles} from '../utils';
52
+ import {
53
+ type DayOfWeek,
54
+ type ISODateString,
55
+ type PlainDate,
56
+ plainDateAddDays,
57
+ plainDateDayOfWeek,
58
+ plainDateFormat,
59
+ plainDateIsEqual,
60
+ plainDateToISO,
61
+ DATE_FORMAT_MONTH_YEAR,
62
+ DATE_FORMAT_WITH_WEEKDAY,
63
+ } from '../utils';
64
+ import {dateInputTouchSizes, dateInputTouchGeometry} from './tokens.stylex';
65
+ import {useOwnScrollGesture} from './useOwnScrollGesture';
66
+ import {
67
+ fromMonthIndex,
68
+ monthIndexOf,
69
+ paneWindow,
70
+ rowAtScrollOffset,
71
+ rowsIn,
72
+ scrollOffsetForRow,
73
+ } from './monthGeometry';
74
+
75
+ const DAY_SIZE = dateInputTouchSizes.daySize;
76
+
77
+ /**
78
+ * Panes mounted on each side of the visible one. Sized against a fast fling:
79
+ * the window widens once per animation frame, so it only fails if a single
80
+ * frame travels further than this — three months, about 800px at the default
81
+ * day size. Under `scroll-snap-type: mandatory`, landing on an unmounted pane
82
+ * would not merely show a gap, it would re-snap to the nearest mounted one.
83
+ */
84
+ const OVERSCAN = 3;
85
+
86
+ const styles = stylex.create({
87
+ scroller: {
88
+ position: 'relative',
89
+ blockSize: dateInputTouchGeometry.paneBlockSize,
90
+ // Stated, not inherited from the reset: the pane size, the snap offsets
91
+ // and the virtualization all key off the measured box, so a consumer
92
+ // without reset.css (or with a stray box-sizing rule — the reset's is
93
+ // zero-specificity `:where`) must not be able to change what it means.
94
+ boxSizing: 'border-box',
95
+ overflowX: 'auto',
96
+ overflowY: 'hidden',
97
+ // The paging behaviour, now along the inline axis. Every snap area is a
98
+ // full pane, so there is exactly one resting position per month.
99
+ scrollSnapType: 'x mandatory',
100
+ // Keeps a fling inside the picker rather than handing it to the page.
101
+ overscrollBehavior: 'contain',
102
+ scrollbarWidth: 'none',
103
+ // pan-x, so the browser keeps horizontal pans here and hands VERTICAL
104
+ // ones straight to the sheet. That is why this scroller no longer claims
105
+ // the gesture the way the wheels do: with the axes separated there is
106
+ // nothing to fight over, and a downward drag on the calendar can go back
107
+ // to meaning swipe-to-dismiss.
108
+ touchAction: 'pan-x',
109
+ },
110
+ spacer: {
111
+ position: 'relative',
112
+ blockSize: '100%',
113
+ },
114
+ pane: {
115
+ position: 'absolute',
116
+ insetBlock: 0,
117
+ blockSize: dateInputTouchGeometry.paneBlockSize,
118
+ scrollSnapAlign: 'start',
119
+ // No `scroll-snap-stop: always`. It would cap every fling at one month,
120
+ // which is tidy but stops the surface being continuous — "three months
121
+ // ahead" should be one throw, not three flicks. Momentum carrying past
122
+ // several panes is why OVERSCAN is what it is.
123
+ display: 'grid',
124
+ gridTemplateColumns: 'repeat(7, 1fr)',
125
+ gridTemplateRows: dateInputTouchGeometry.paneRows,
126
+ },
127
+ row: {
128
+ display: 'contents',
129
+ },
130
+ cell: {
131
+ position: 'relative',
132
+ display: 'flex',
133
+ alignItems: 'center',
134
+ justifyContent: 'center',
135
+ },
136
+ day: {
137
+ display: 'flex',
138
+ alignItems: 'center',
139
+ justifyContent: 'center',
140
+ inlineSize: '100%',
141
+ blockSize: '100%',
142
+ minInlineSize: DAY_SIZE,
143
+ minBlockSize: DAY_SIZE,
144
+ padding: 0,
145
+ borderWidth: 0,
146
+ borderStyle: 'none',
147
+ backgroundColor: 'transparent',
148
+ color: colorVars['--color-text-primary'],
149
+ fontSize: typeScaleVars['--text-body-size'],
150
+ fontWeight: fontWeightVars['--font-weight-normal'],
151
+ cursor: 'pointer',
152
+ // A tap on a 44px target should not also select the number inside it.
153
+ userSelect: 'none',
154
+ WebkitTapHighlightColor: 'transparent',
155
+ },
156
+ /**
157
+ * The circular hit/selection puck inside the tap target. Sizing the puck
158
+ * rather than the button keeps the touch target at the full cell while the
159
+ * visual stays a tidy circle.
160
+ */
161
+ puck: {
162
+ display: 'flex',
163
+ alignItems: 'center',
164
+ justifyContent: 'center',
165
+ inlineSize: `calc(${DAY_SIZE} - 8px)`,
166
+ blockSize: `calc(${DAY_SIZE} - 8px)`,
167
+ borderRadius: radiusVars['--radius-full'],
168
+ borderWidth: borderVars['--border-width'],
169
+ borderStyle: 'solid',
170
+ borderColor: 'transparent',
171
+ backgroundColor: 'transparent',
172
+ transitionProperty: 'background-color, color, border-color',
173
+ transitionDuration: durationVars['--duration-fast'],
174
+ },
175
+ puckHoverable: {
176
+ // Two guards, for two different failure modes.
177
+ //
178
+ // `@media (hover: hover)`: on a touch screen a :hover tint sticks to the
179
+ // last tapped day until something else is tapped.
180
+ //
181
+ // `:where(:not(:disabled,[aria-disabled="true"]))`: a browser suppresses a
182
+ // disabled control's EVENTS, not its hover styling, so an unguarded
183
+ // :hover tints a day you cannot pick. The day's disabled state lives on
184
+ // the button (as `aria-disabled`, which keeps it focusable) and this puck
185
+ // is the span inside it, so today the working mechanism is the JS gate at
186
+ // the call site — this guard is what keeps the rule true if the styles
187
+ // ever move onto the button itself.
188
+ backgroundColor: {
189
+ default: 'transparent',
190
+ '@media (hover: hover)': {
191
+ default: 'transparent',
192
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))':
193
+ colorVars['--color-overlay-hover'],
194
+ },
195
+ },
196
+ },
197
+ puckToday: {
198
+ borderColor: colorVars['--color-border-emphasized'],
199
+ fontWeight: fontWeightVars['--font-weight-semibold'],
200
+ },
201
+ puckSelected: {
202
+ backgroundColor: {
203
+ default: colorVars['--color-accent'],
204
+ // Holds the accent through hover, overriding puckHoverable's tint.
205
+ // Same guarded selector, so the two have equal specificity and
206
+ // application order decides — which is what puts this one on top.
207
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))':
208
+ colorVars['--color-accent'],
209
+ },
210
+ borderColor: colorVars['--color-accent'],
211
+ color: colorVars['--color-on-accent'],
212
+ fontWeight: fontWeightVars['--font-weight-semibold'],
213
+ },
214
+ /**
215
+ * A day belonging to the month either side of this pane. Muted the way
216
+ * Calendar mutes its own, and unselectable for the same reason it is
217
+ * there: it exists to show where the month ends, not to offer a date. A
218
+ * pane is one month, and tapping out of it would move the calendar under
219
+ * your thumb; the swipe or the arrow says "next month" without ambiguity.
220
+ *
221
+ * It needs no special handling for the roving tab order: `tabbableISO` is
222
+ * resolved per pane and only ever names a date in THAT pane's own month,
223
+ * so an outside cell can never match it. Which is the property that keeps
224
+ * a date from having two tab stops — the pane that owns it, and the
225
+ * neighbour that merely shows it.
226
+ */
227
+ dayOutside: {
228
+ // Exactly what the desktop calendar does to its own spill days: the
229
+ // secondary text colour AND half opacity, which is what makes them read
230
+ // as background rather than as dimmer choices. (Calendar splits these
231
+ // across two style objects — `dayOutside` in its theme layer carries the
232
+ // colour, the one in its structural layer the opacity — so applying only
233
+ // the colour, as this did at first, was half the treatment.)
234
+ color: colorVars['--color-text-secondary'],
235
+ opacity: 0.5,
236
+ },
237
+ dayDisabled: {
238
+ // The desktop's disabled treatment, and deliberately not a disabled
239
+ // COLOUR: it fades whatever colour the day already had (0.3), so an
240
+ // in-month day and a spilled one stay a step apart while disabled, and
241
+ // both land LIGHTER than an enabled spill day rather than darker.
242
+ //
243
+ // A flat `--color-text-disabled` is what this had, and it put an enabled
244
+ // adjacent day at ~168 against a disabled in-month day at ~163 on white:
245
+ // five levels apart, which is no difference at all. That is the whole
246
+ // reason the two were hard to tell apart.
247
+ opacity: 0.3,
248
+ cursor: 'not-allowed',
249
+ },
250
+ });
251
+
252
+ const dynamic = stylex.create({
253
+ spacer: (inlineSize: number) => ({
254
+ inlineSize: `${inlineSize}px`,
255
+ }),
256
+ // insetInlineStart, not left: under RTL the panes have to lay out from the
257
+ // right, and the scroll math mirrors with them (see scrollOffsetForRow).
258
+ pane: (insetInlineStart: number, inlineSize: number) => ({
259
+ insetInlineStart: `${insetInlineStart}px`,
260
+ inlineSize: `${inlineSize}px`,
261
+ }),
262
+ });
263
+
264
+ export interface MonthScrollerHandle {
265
+ /** Bring a month to rest at the top of the scrollport. */
266
+ scrollToMonth: (monthIndex: number, behavior?: ScrollBehavior) => void;
267
+ }
268
+
269
+ export interface MonthScrollerProps {
270
+ /** Imperative handle for programmatic navigation (Today, the wheels). */
271
+ handleRef?: React.RefObject<MonthScrollerHandle | null>;
272
+ /** First reachable month, as a month index. */
273
+ minMonthIndex: number;
274
+ /** Last reachable month, as a month index. */
275
+ maxMonthIndex: number;
276
+ /** Month to rest on at mount. */
277
+ initialMonthIndex: number;
278
+ /** Fires as the scroller passes each month, settled or not. */
279
+ onVisibleMonthChange: (monthIndex: number) => void;
280
+ /** Currently selected date. */
281
+ selectedDate: PlainDate | null;
282
+ /** Today, for the `aria-current` marker. */
283
+ today: PlainDate;
284
+ /** Whether a date fails min/max or a custom constraint. */
285
+ isDateDisabled: (date: PlainDate) => boolean;
286
+ /** First column of the week. */
287
+ weekStartsOn: DayOfWeek;
288
+ /** Fired when a day is tapped. */
289
+ onSelect: (value: ISODateString) => void;
290
+ }
291
+
292
+ /**
293
+ * A vertically continuous, snap-paged run of month grids.
294
+ */
295
+ export function MonthScroller({
296
+ handleRef,
297
+ minMonthIndex,
298
+ maxMonthIndex,
299
+ initialMonthIndex,
300
+ onVisibleMonthChange,
301
+ selectedDate,
302
+ today,
303
+ isDateDisabled,
304
+ weekStartsOn,
305
+ onSelect,
306
+ }: MonthScrollerProps) {
307
+ const scrollerRef = useRef<HTMLDivElement | null>(null);
308
+ const rowCount = maxMonthIndex - minMonthIndex + 1;
309
+
310
+ // Pane WIDTH is read from layout, never assumed: it is whatever the CSS
311
+ // says the scrollport is, so the snap offsets and the virtualization follow
312
+ // a resize (rotation, a split-screen change) without re-deriving anything.
313
+ const [paneSize, setPaneSize] = useState(0);
314
+ // Which way the inline axis runs. Under RTL the panes lay out from the
315
+ // right and scrollLeft counts DOWN from 0, so every conversion between a
316
+ // row and a scroll offset has to know.
317
+ const isRTL = useDirection() === 'rtl';
318
+ const [centerRow, setCenterRow] = useState(initialMonthIndex - minMonthIndex);
319
+ // The visible row, mirrored so the scroll handler can tell "changed" from
320
+ // "same" without reading state, and notify the parent OUTSIDE a state
321
+ // updater — React runs updaters during render, where another component's
322
+ // setState is illegal.
323
+ const centerRowRef = useRef(centerRow);
324
+ // A row to scroll to once its pane is mounted; see scrollToMonth.
325
+ const pendingScrollRef = useRef<number | null>(null);
326
+
327
+ /**
328
+ * The row a programmatic scroll is heading for, so its own scroll events
329
+ * are not reported back as if the user had gone there. Null once a finger
330
+ * touches the scroller, or once the steered scroll has arrived.
331
+ */
332
+ const steeredRowRef = useRef<number | null>(null);
333
+
334
+ // Keyboard focus moves by date, not by cell index, so it crosses month
335
+ // boundaries the way a calendar should. Null until the user takes the grid
336
+ // with the keyboard.
337
+ const [focusedDate, setFocusedDate] = useState<PlainDate | null>(null);
338
+ const shouldRestoreFocusRef = useRef(false);
339
+
340
+ const onVisibleMonthChangeRef = useRef(onVisibleMonthChange);
341
+ onVisibleMonthChangeRef.current = onVisibleMonthChange;
342
+
343
+ useLayoutEffect(() => {
344
+ const scroller = scrollerRef.current;
345
+ if (scroller == null) {
346
+ return;
347
+ }
348
+ const measure = () => {
349
+ // Ignore zero: the picker can be mounted inside something not yet
350
+ // displayed (a closed BottomSheet), and a zero width would unmount
351
+ // every pane and lose the scroll position.
352
+ const measured = scroller.clientWidth;
353
+ if (measured > 0) {
354
+ // eslint-disable-next-line @eslint-react/set-state-in-effect -- pane width is a measurement; layout is the only place it exists
355
+ setPaneSize(measured);
356
+ }
357
+ };
358
+ measure();
359
+ const observer = new ResizeObserver(measure);
360
+ observer.observe(scroller);
361
+ return () => observer.disconnect();
362
+ }, []);
363
+
364
+ // Land on the initial month before the first paint. Runs once the height is
365
+ // known; `hasPositioned` keeps a later resize from yanking the user back.
366
+ const hasPositionedRef = useRef(false);
367
+ useLayoutEffect(() => {
368
+ const scroller = scrollerRef.current;
369
+ if (scroller == null || paneSize === 0 || hasPositionedRef.current) {
370
+ return;
371
+ }
372
+ hasPositionedRef.current = true;
373
+ scroller.scrollLeft = scrollOffsetForRow(
374
+ initialMonthIndex - minMonthIndex,
375
+ paneSize,
376
+ isRTL,
377
+ );
378
+ }, [paneSize, initialMonthIndex, minMonthIndex, isRTL]);
379
+
380
+ const scrollToMonth = useCallback(
381
+ (monthIndex: number, behavior: ScrollBehavior = 'smooth') => {
382
+ const scroller = scrollerRef.current;
383
+ if (scroller == null || paneSize === 0) {
384
+ return;
385
+ }
386
+ const row = Math.min(
387
+ rowCount - 1,
388
+ Math.max(0, monthIndex - minMonthIndex),
389
+ );
390
+ if (Math.abs(row - centerRowRef.current) > OVERSCAN) {
391
+ // Beyond the mounted window there is no pane at the target offset, and
392
+ // `scroll-snap-type: mandatory` will not leave the scroller resting
393
+ // where no snap area is — it re-snaps to the nearest mounted pane, so
394
+ // a jump to another year lands one month away instead. Mount the
395
+ // target first and scroll once it exists (see the layout effect
396
+ // below); a jump that far is never worth animating either.
397
+ pendingScrollRef.current = row;
398
+ centerRowRef.current = row;
399
+ setCenterRow(row);
400
+ return;
401
+ }
402
+ steeredRowRef.current = row;
403
+ scroller.scrollTo({
404
+ left: scrollOffsetForRow(row, paneSize, isRTL),
405
+ behavior,
406
+ });
407
+ },
408
+ [paneSize, minMonthIndex, rowCount, isRTL],
409
+ );
410
+
411
+ // Deliberately un-keyed: it runs after every render, costs a null check, and
412
+ // must fire on whichever render finally mounts the pending row's pane.
413
+ useLayoutEffect(() => {
414
+ const row = pendingScrollRef.current;
415
+ const scroller = scrollerRef.current;
416
+ if (row == null || scroller == null || paneSize === 0) {
417
+ return;
418
+ }
419
+ pendingScrollRef.current = null;
420
+ scroller.scrollTo({
421
+ left: scrollOffsetForRow(row, paneSize, isRTL),
422
+ behavior: 'auto',
423
+ });
424
+ });
425
+
426
+ useImperativeHandle(handleRef, () => ({scrollToMonth}), [scrollToMonth]);
427
+
428
+ // Claim horizontal gestures, leave vertical ones to the sheet.
429
+ //
430
+ // `touch-action: pan-x` alone is NOT enough: it governs what the browser
431
+ // pans natively, and the sheet's listener is JavaScript whose
432
+ // preventDefault() cancels the scroll regardless. Measured — a swipe 9° off
433
+ // horizontal had every touchmove cancelled by the sheet and the month never
434
+ // changed. The axis lock is biased toward horizontal, because a thumb arcs
435
+ // as it swipes; `onSwipe` then pages the diagonals the browser itself
436
+ // declines to pan, so no angle is left doing nothing. See
437
+ // useOwnScrollGesture.
438
+ useOwnScrollGesture(scrollerRef, 'inline', {
439
+ onSwipe: direction => {
440
+ const scroller = scrollerRef.current;
441
+ if (scroller == null || paneSize === 0) {
442
+ return;
443
+ }
444
+ scroller.scrollBy({left: direction * paneSize, behavior: 'smooth'});
445
+ },
446
+ });
447
+
448
+ // rAF-throttled: a touch scroll fires far more scroll events than frames,
449
+ // and all this does is move a label and widen a window.
450
+ const frameRef = useRef<number | undefined>(undefined);
451
+ useEffect(() => {
452
+ const scroller = scrollerRef.current;
453
+ // Not before the height is known. A listener attached while it was zero
454
+ // would close over that zero and report row 0 for any scroll — and the
455
+ // scroll event fired by the initial positioning arrives just late enough
456
+ // to be handled by that stale listener. Every pane then unmounts except
457
+ // rows 0-2, and `scroll-snap-type: mandatory` yanks the scroller to the
458
+ // nearest surviving snap area, a century away from where it just landed.
459
+ if (scroller == null || paneSize === 0) {
460
+ return;
461
+ }
462
+ // A finger on the scroller ends any steering: from here the months it
463
+ // passes are the user's doing and every one of them is worth reporting.
464
+ const onTouchStart = () => {
465
+ steeredRowRef.current = null;
466
+ };
467
+
468
+ const onScroll = () => {
469
+ if (frameRef.current != null) {
470
+ return;
471
+ }
472
+ frameRef.current = requestAnimationFrame(() => {
473
+ frameRef.current = undefined;
474
+ const row = rowAtScrollOffset(
475
+ scroller.scrollLeft,
476
+ paneSize,
477
+ rowCount,
478
+ isRTL,
479
+ );
480
+ if (row === centerRowRef.current) {
481
+ return;
482
+ }
483
+ centerRowRef.current = row;
484
+ setCenterRow(row);
485
+ // Report nothing while a steer is in flight. `scrollToMonth` is only
486
+ // ever called by something that already knows the month — a wheel
487
+ // commit, a header arrow, the re-assert when the wheels close — so
488
+ // none of the scrolling it causes is news, including whatever the
489
+ // scroller passes through on the way.
490
+ //
491
+ // Reporting it is a feedback cycle, and worse on iOS. A wheel commit
492
+ // steers this scroller while it is hidden behind the wheels, and a
493
+ // hidden scroller does not reliably stay where it was put: iOS
494
+ // re-snaps it when the panel becomes visible again, firing scrolls at
495
+ // the exact moment reports start being trusted again. The month drifted
496
+ // on the way back to the calendar.
497
+ //
498
+ // Cleared on arrival, or by a touch below — once a finger is on the
499
+ // scroller the user owns it, and every month it passes is worth
500
+ // reporting.
501
+ if (steeredRowRef.current != null) {
502
+ if (steeredRowRef.current === row) {
503
+ steeredRowRef.current = null;
504
+ }
505
+ return;
506
+ }
507
+ onVisibleMonthChangeRef.current(minMonthIndex + row);
508
+ });
509
+ };
510
+ scroller.addEventListener('scroll', onScroll, {passive: true});
511
+ scroller.addEventListener('touchstart', onTouchStart, {passive: true});
512
+ return () => {
513
+ scroller.removeEventListener('scroll', onScroll);
514
+ scroller.removeEventListener('touchstart', onTouchStart);
515
+ if (frameRef.current != null) {
516
+ cancelAnimationFrame(frameRef.current);
517
+ frameRef.current = undefined;
518
+ }
519
+ };
520
+ }, [paneSize, rowCount, minMonthIndex, isRTL]);
521
+
522
+ // Move the keyboard focus by whole days and let the scroller follow. Paging
523
+ // by month is PageUp/PageDown; everything else is the APG grid vocabulary.
524
+ const moveFocus = useCallback(
525
+ (from: PlainDate, deltaDays: number) => {
526
+ const next = plainDateAddDays(from, deltaDays);
527
+ const nextIndex = monthIndexOf(next);
528
+ if (nextIndex < minMonthIndex || nextIndex > maxMonthIndex) {
529
+ return;
530
+ }
531
+ shouldRestoreFocusRef.current = true;
532
+ setFocusedDate(next);
533
+ if (nextIndex !== monthIndexOf(from)) {
534
+ scrollToMonth(nextIndex, 'smooth');
535
+ }
536
+ },
537
+ [minMonthIndex, maxMonthIndex, scrollToMonth],
538
+ );
539
+
540
+ const handleKeyDown = useCallback(
541
+ (event: React.KeyboardEvent, date: PlainDate) => {
542
+ // Column within the displayed week, which is what Home/End move to —
543
+ // not the day of the month.
544
+ const column = (plainDateDayOfWeek(date) - weekStartsOn + 7) % 7;
545
+ switch (event.key) {
546
+ case 'ArrowLeft':
547
+ event.preventDefault();
548
+ moveFocus(date, -1);
549
+ break;
550
+ case 'ArrowRight':
551
+ event.preventDefault();
552
+ moveFocus(date, 1);
553
+ break;
554
+ case 'ArrowUp':
555
+ event.preventDefault();
556
+ moveFocus(date, -7);
557
+ break;
558
+ case 'ArrowDown':
559
+ event.preventDefault();
560
+ moveFocus(date, 7);
561
+ break;
562
+ case 'Home':
563
+ event.preventDefault();
564
+ moveFocus(date, -column);
565
+ break;
566
+ case 'End':
567
+ event.preventDefault();
568
+ moveFocus(date, 6 - column);
569
+ break;
570
+ default:
571
+ break;
572
+ }
573
+ },
574
+ [moveFocus, weekStartsOn],
575
+ );
576
+
577
+ // The focused day may have been in an unmounted pane a frame ago; focus it
578
+ // once it exists, and only in response to a key press so a scroll never
579
+ // steals focus.
580
+ const focusedISO = focusedDate == null ? null : plainDateToISO(focusedDate);
581
+ useEffect(() => {
582
+ if (!shouldRestoreFocusRef.current || focusedISO == null) {
583
+ return;
584
+ }
585
+ const target = scrollerRef.current?.querySelector<HTMLElement>(
586
+ `[data-date="${focusedISO}"]`,
587
+ );
588
+ if (target != null) {
589
+ shouldRestoreFocusRef.current = false;
590
+ // preventScroll: the scroller is already snapping to this month; a
591
+ // browser scroll-into-view here would fight the snap and land between
592
+ // two panes.
593
+ target.focus({preventScroll: true});
594
+ }
595
+ }, [focusedISO]);
596
+
597
+ const visibleRows = paneWindow(centerRow, rowCount, OVERSCAN);
598
+
599
+ // A focus event fires for the programmatic focus above too; only take the
600
+ // date when it is actually a different day.
601
+ const handleDayFocus = useCallback((date: PlainDate) => {
602
+ setFocusedDate(previous =>
603
+ previous != null && plainDateIsEqual(previous, date) ? previous : date,
604
+ );
605
+ }, []);
606
+
607
+ return (
608
+ <div
609
+ ref={scrollerRef}
610
+ data-scroller="months"
611
+ {...stylex.props(styles.scroller)}>
612
+ <div
613
+ {...stylex.props(styles.spacer, dynamic.spacer(rowCount * paneSize))}>
614
+ {paneSize > 0 &&
615
+ rowsIn(visibleRows).map(row => (
616
+ <MonthPane
617
+ key={row}
618
+ monthIndex={minMonthIndex + row}
619
+ insetInlineStart={row * paneSize}
620
+ inlineSize={paneSize}
621
+ selectedDate={selectedDate}
622
+ focusedDate={focusedDate}
623
+ today={today}
624
+ isDateDisabled={isDateDisabled}
625
+ weekStartsOn={weekStartsOn}
626
+ onSelect={onSelect}
627
+ onDayKeyDown={handleKeyDown}
628
+ onDayFocus={handleDayFocus}
629
+ />
630
+ ))}
631
+ </div>
632
+ </div>
633
+ );
634
+ }
635
+
636
+ MonthScroller.displayName = 'MonthScroller';
637
+
638
+ interface MonthPaneProps {
639
+ monthIndex: number;
640
+ insetInlineStart: number;
641
+ inlineSize: number;
642
+ selectedDate: PlainDate | null;
643
+ focusedDate: PlainDate | null;
644
+ today: PlainDate;
645
+ isDateDisabled: (date: PlainDate) => boolean;
646
+ weekStartsOn: DayOfWeek;
647
+ onSelect: (value: ISODateString) => void;
648
+ onDayKeyDown: (event: React.KeyboardEvent, date: PlainDate) => void;
649
+ onDayFocus: (date: PlainDate) => void;
650
+ }
651
+
652
+ /**
653
+ * One month, as a six-row grid.
654
+ *
655
+ * There is no `role="columnheader"` row in here: the weekday names are a
656
+ * single sticky row outside the scroller (they would otherwise scroll away),
657
+ * so each day instead carries its weekday in its accessible name.
658
+ */
659
+ function MonthPane({
660
+ monthIndex,
661
+ insetInlineStart,
662
+ inlineSize,
663
+ selectedDate,
664
+ focusedDate,
665
+ today,
666
+ isDateDisabled,
667
+ weekStartsOn,
668
+ onSelect,
669
+ onDayKeyDown,
670
+ onDayFocus,
671
+ }: MonthPaneProps) {
672
+ const {year, month} = fromMonthIndex(monthIndex);
673
+ const {weeks} = useCalendarDays({
674
+ year,
675
+ month,
676
+ weekStartsOn,
677
+ // Always six rows: a variable grid would make pane heights differ, and
678
+ // with them every snap offset below this month.
679
+ hasVariableRowCount: false,
680
+ });
681
+
682
+ const monthLabel = plainDateFormat(
683
+ {year, month, day: 1},
684
+ DATE_FORMAT_MONTH_YEAR,
685
+ );
686
+
687
+ // Exactly one day per pane is tab-reachable, so Tab moves through the
688
+ // picker rather than through 42 buttons: the focused day if the keyboard
689
+ // owns one, else the selection, else the first of the month.
690
+ const tabbableISO = (() => {
691
+ if (
692
+ focusedDate != null &&
693
+ focusedDate.year === year &&
694
+ focusedDate.month === month
695
+ ) {
696
+ return plainDateToISO(focusedDate);
697
+ }
698
+ if (
699
+ selectedDate != null &&
700
+ selectedDate.year === year &&
701
+ selectedDate.month === month
702
+ ) {
703
+ return plainDateToISO(selectedDate);
704
+ }
705
+ return plainDateToISO({year, month, day: 1});
706
+ })();
707
+
708
+ return (
709
+ <div
710
+ role="grid"
711
+ aria-label={monthLabel}
712
+ data-month={monthLabel}
713
+ {...stylex.props(
714
+ styles.pane,
715
+ dynamic.pane(insetInlineStart, inlineSize),
716
+ )}>
717
+ {weeks.map(week => (
718
+ <div key={week[0].iso} role="row" {...stylex.props(styles.row)}>
719
+ {week.map(day => {
720
+ // A spilled day is context, not a choice. Calendar computes the
721
+ // same thing as `effectivelyDisabled: isDisabled || isOutside`,
722
+ // and guards today/selected on `!isOutside` beside it, so a date
723
+ // borrowed from a neighbouring month never carries a ring or a
724
+ // puck in the pane that is only showing it.
725
+ const isDisabled = day.isOutside || isDateDisabled(day.date);
726
+ const isSelected =
727
+ !day.isOutside &&
728
+ selectedDate != null &&
729
+ plainDateIsEqual(day.date, selectedDate);
730
+ const isToday = !day.isOutside && plainDateIsEqual(day.date, today);
731
+
732
+ return (
733
+ <div
734
+ key={day.iso}
735
+ role="gridcell"
736
+ aria-selected={isSelected || undefined}
737
+ {...stylex.props(styles.cell)}>
738
+ <button
739
+ type="button"
740
+ data-date={day.iso}
741
+ tabIndex={day.iso === tabbableISO ? 0 : -1}
742
+ aria-label={plainDateFormat(
743
+ day.date,
744
+ DATE_FORMAT_WITH_WEEKDAY,
745
+ )}
746
+ aria-disabled={isDisabled || undefined}
747
+ aria-current={isToday ? 'date' : undefined}
748
+ onClick={() => {
749
+ if (!isDisabled) {
750
+ onSelect(day.iso);
751
+ }
752
+ }}
753
+ onFocus={() => onDayFocus(day.date)}
754
+ onKeyDown={event => onDayKeyDown(event, day.date)}
755
+ {...stylex.props(
756
+ styles.day,
757
+ focusOutlineStyles.focusVisible,
758
+ // Outside FIRST, so disabled wins where both apply: a
759
+ // spilled day past the range is unselectable, and must
760
+ // not be painted more available than the in-month days
761
+ // beside it.
762
+ day.isOutside && styles.dayOutside,
763
+ isDisabled && styles.dayDisabled,
764
+ )}>
765
+ <span
766
+ {...stylex.props(
767
+ styles.puck,
768
+ !isDisabled && styles.puckHoverable,
769
+ isToday && styles.puckToday,
770
+ isSelected && styles.puckSelected,
771
+ )}>
772
+ {day.dayNumber}
773
+ </span>
774
+ </button>
775
+ </div>
776
+ );
777
+ })}
778
+ </div>
779
+ ))}
780
+ </div>
781
+ );
782
+ }
783
+
784
+ MonthPane.displayName = 'MonthPane';