@astryxdesign/core 0.4.5 → 0.4.6-canary.cea9ecd

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 (538) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/Avatar/Avatar.d.ts +10 -5
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +139 -30
  5. package/dist/Avatar/AvatarStatusDot.d.ts +2 -1
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/Avatar/AvatarStatusDot.js +30 -4
  8. package/dist/Avatar/AvatarStatusLabelContext.d.ts +39 -0
  9. package/dist/Avatar/AvatarStatusLabelContext.d.ts.map +1 -0
  10. package/dist/Avatar/AvatarStatusLabelContext.js +41 -0
  11. package/dist/AvatarGroup/AvatarGroup.d.ts +3 -1
  12. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  13. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  14. package/dist/AvatarGroup/AvatarGroupOverflow.js +15 -9
  15. package/dist/Blockquote/Blockquote.d.ts +4 -3
  16. package/dist/Blockquote/Blockquote.d.ts.map +1 -1
  17. package/dist/Blockquote/Blockquote.js +9 -10
  18. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  19. package/dist/BottomSheet/BottomSheetPanel.js +21 -5
  20. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  21. package/dist/Breadcrumbs/BreadcrumbItem.js +47 -34
  22. package/dist/Button/Button.d.ts.map +1 -1
  23. package/dist/Button/Button.js +7 -7
  24. package/dist/Calendar/Calendar.d.ts.map +1 -1
  25. package/dist/Calendar/Calendar.js +21 -12
  26. package/dist/Calendar/hooks/useCalendarNavigation.d.ts.map +1 -1
  27. package/dist/Calendar/hooks/useCalendarNavigation.js +8 -4
  28. package/dist/Calendar/styles.d.ts +2 -2
  29. package/dist/Calendar/styles.d.ts.map +1 -1
  30. package/dist/Calendar/styles.js +5 -5
  31. package/dist/Center/Center.d.ts +23 -1
  32. package/dist/Center/Center.d.ts.map +1 -1
  33. package/dist/Center/Center.js +12 -6
  34. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  35. package/dist/Chat/ChatComposer.js +3 -3
  36. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposerDrawer.js +2 -2
  38. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  39. package/dist/Chat/ChatToolCalls.js +2 -2
  40. package/dist/Chat/useChatDictation.d.ts +1 -1
  41. package/dist/Chat/useChatDictation.d.ts.map +1 -1
  42. package/dist/Chat/useSpeechRecognition.d.ts +1 -1
  43. package/dist/Chat/useSpeechRecognition.d.ts.map +1 -1
  44. package/dist/Chat/useSpeechRecognition.js +4 -3
  45. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  46. package/dist/Chat/useTriggerMenu.js +2 -2
  47. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  48. package/dist/CheckboxInput/CheckboxInput.js +2 -2
  49. package/dist/Citation/Citation.d.ts.map +1 -1
  50. package/dist/Citation/Citation.js +5 -5
  51. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  52. package/dist/ClickableCard/ClickableCard.js +4 -4
  53. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  54. package/dist/CodeBlock/CodeBlock.js +1 -1
  55. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  56. package/dist/Collapsible/Collapsible.js +2 -2
  57. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  58. package/dist/CommandPalette/CommandPaletteItem.js +3 -3
  59. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  60. package/dist/ComplexSelector/ComplexSelector.js +6 -6
  61. package/dist/DateInput/DateInput.d.ts +31 -2
  62. package/dist/DateInput/DateInput.d.ts.map +1 -1
  63. package/dist/DateInput/DateInput.js +90 -22
  64. package/dist/DateInput/MonthScroller.d.ts +35 -0
  65. package/dist/DateInput/MonthScroller.d.ts.map +1 -0
  66. package/dist/DateInput/MonthScroller.js +658 -0
  67. package/dist/DateInput/MonthYearWheels.d.ts +24 -0
  68. package/dist/DateInput/MonthYearWheels.d.ts.map +1 -0
  69. package/dist/DateInput/MonthYearWheels.js +114 -0
  70. package/dist/DateInput/TouchDateField.d.ts +10 -0
  71. package/dist/DateInput/TouchDateField.d.ts.map +1 -0
  72. package/dist/DateInput/TouchDateField.js +810 -0
  73. package/dist/DateInput/Wheel.d.ts +41 -0
  74. package/dist/DateInput/Wheel.d.ts.map +1 -0
  75. package/dist/DateInput/Wheel.js +315 -0
  76. package/dist/DateInput/monthGeometry.d.ts +65 -0
  77. package/dist/DateInput/monthGeometry.d.ts.map +1 -0
  78. package/dist/DateInput/monthGeometry.js +98 -0
  79. package/dist/DateInput/tokens.stylex.d.ts +63 -0
  80. package/dist/DateInput/tokens.stylex.d.ts.map +1 -0
  81. package/dist/DateInput/tokens.stylex.js +74 -0
  82. package/dist/DateInput/useOwnScrollGesture.d.ts +41 -0
  83. package/dist/DateInput/useOwnScrollGesture.d.ts.map +1 -0
  84. package/dist/DateInput/useOwnScrollGesture.js +202 -0
  85. package/dist/DateInput/usePointerDragScroll.d.ts +15 -0
  86. package/dist/DateInput/usePointerDragScroll.d.ts.map +1 -0
  87. package/dist/DateInput/usePointerDragScroll.js +216 -0
  88. package/dist/DateInput/useScrollSettle.d.ts +26 -0
  89. package/dist/DateInput/useScrollSettle.d.ts.map +1 -0
  90. package/dist/DateInput/useScrollSettle.js +149 -0
  91. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  92. package/dist/DateRangeInput/DateRangeInput.js +17 -14
  93. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  94. package/dist/DateTimeInput/DateTimeInput.js +17 -14
  95. package/dist/Dialog/Dialog.d.ts.map +1 -1
  96. package/dist/Dialog/Dialog.js +3 -3
  97. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  98. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +2 -2
  99. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  100. package/dist/DropdownMenu/DropdownMenuItem.js +2 -2
  101. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  102. package/dist/DropdownMenu/DropdownMenuRadioItem.js +2 -2
  103. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  104. package/dist/DropdownMenu/DropdownMenuSubMenu.js +2 -2
  105. package/dist/Field/FieldLabel.d.ts.map +1 -1
  106. package/dist/Field/FieldLabel.js +4 -4
  107. package/dist/Field/InputClearButton.d.ts.map +1 -1
  108. package/dist/Field/InputClearButton.js +6 -0
  109. package/dist/Field/inputStyles.stylex.d.ts +1 -1
  110. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  111. package/dist/Field/inputStyles.stylex.js +5 -5
  112. package/dist/FileInput/FileInput.d.ts.map +1 -1
  113. package/dist/FileInput/FileInput.js +6 -6
  114. package/dist/HoverCard/HoverCard.d.ts +13 -3
  115. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  116. package/dist/HoverCard/HoverCard.js +2 -0
  117. package/dist/HoverCard/index.d.ts +1 -1
  118. package/dist/HoverCard/index.d.ts.map +1 -1
  119. package/dist/HoverCard/useHoverCard.d.ts +17 -2
  120. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  121. package/dist/HoverCard/useHoverCard.js +78 -9
  122. package/dist/InputGroup/InputGroup.js +1 -1
  123. package/dist/Item/Item.d.ts +9 -1
  124. package/dist/Item/Item.d.ts.map +1 -1
  125. package/dist/Item/Item.js +52 -14
  126. package/dist/Layer/index.d.ts +4 -2
  127. package/dist/Layer/index.d.ts.map +1 -1
  128. package/dist/Layer/index.js +2 -0
  129. package/dist/Layer/useLayer.d.ts.map +1 -1
  130. package/dist/Layer/useLayer.js +3 -2
  131. package/dist/Layer/useTouchTrigger.d.ts +113 -0
  132. package/dist/Layer/useTouchTrigger.d.ts.map +1 -0
  133. package/dist/Layer/useTouchTrigger.js +241 -0
  134. package/dist/Layout/container.stylex.d.ts +4 -2
  135. package/dist/Layout/container.stylex.d.ts.map +1 -1
  136. package/dist/Layout/container.stylex.js +19 -11
  137. package/dist/Layout/index.d.ts +1 -0
  138. package/dist/Layout/index.d.ts.map +1 -1
  139. package/dist/Layout/index.js +4 -0
  140. package/dist/Layout/padding.stylex.d.ts +315 -12
  141. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  142. package/dist/Layout/padding.stylex.js +389 -12
  143. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  144. package/dist/Lightbox/Lightbox.js +5 -4
  145. package/dist/Link/Link.d.ts.map +1 -1
  146. package/dist/Link/Link.js +6 -6
  147. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  148. package/dist/MetadataList/MetadataList.js +1 -1
  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 -8
  154. package/dist/NavItem/navItemStyles.stylex.d.ts +4 -4
  155. package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
  156. package/dist/NavItem/navItemStyles.stylex.js +4 -4
  157. package/dist/NavMenu/NavHeadingMenuItem.d.ts.map +1 -1
  158. package/dist/NavMenu/NavHeadingMenuItem.js +3 -3
  159. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  160. package/dist/NumberInput/NumberInput.js +36 -8
  161. package/dist/Outline/Outline.d.ts.map +1 -1
  162. package/dist/Outline/Outline.js +3 -3
  163. package/dist/Pagination/Pagination.d.ts.map +1 -1
  164. package/dist/Pagination/Pagination.js +2 -2
  165. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  166. package/dist/PowerSearch/PowerSearch.js +11 -12
  167. package/dist/PowerSearch/PowerSearchToken.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchToken.js +3 -1
  169. package/dist/PowerSearch/formatFilterValue.d.ts +11 -2
  170. package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
  171. package/dist/PowerSearch/formatFilterValue.js +23 -8
  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 +70 -40
  175. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  176. package/dist/Resizable/ResizeHandle.js +7 -7
  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 +7 -2
  182. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  183. package/dist/SelectableCard/SelectableCard.js +3 -3
  184. package/dist/Selector/Selector.d.ts +26 -1
  185. package/dist/Selector/Selector.d.ts.map +1 -1
  186. package/dist/Selector/Selector.js +93 -20
  187. package/dist/Selector/SelectorOption.d.ts +14 -1
  188. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  189. package/dist/Selector/SelectorOption.js +6 -0
  190. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  191. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  192. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  193. package/dist/Selector/types.d.ts +1 -0
  194. package/dist/Selector/types.d.ts.map +1 -1
  195. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  196. package/dist/SideNav/SideNavHeading.js +4 -4
  197. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  198. package/dist/SideNav/SideNavItem.js +3 -3
  199. package/dist/Slider/Slider.d.ts.map +1 -1
  200. package/dist/Slider/Slider.js +7 -7
  201. package/dist/Stack/Stack.d.ts +23 -1
  202. package/dist/Stack/Stack.d.ts.map +1 -1
  203. package/dist/Stack/Stack.js +12 -6
  204. package/dist/Switch/Switch.d.ts.map +1 -1
  205. package/dist/Switch/Switch.js +2 -2
  206. package/dist/TabList/Tab.d.ts.map +1 -1
  207. package/dist/TabList/Tab.js +1 -1
  208. package/dist/TabList/TabMenu.d.ts.map +1 -1
  209. package/dist/TabList/TabMenu.js +3 -3
  210. package/dist/Table/TableRow.js +4 -4
  211. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  212. package/dist/Table/plugins/columnResize/useTableColumnResize.js +1 -1
  213. package/dist/Table/plugins/filtering/useTableFiltering.d.ts.map +1 -1
  214. package/dist/Table/plugins/filtering/useTableFiltering.js +2 -2
  215. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  216. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +2 -2
  217. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  218. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +2 -2
  219. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  220. package/dist/Table/plugins/sortable/useTableSortable.js +1 -1
  221. package/dist/Table/plugins/sortable/useTableSortableState.d.ts +5 -3
  222. package/dist/Table/plugins/sortable/useTableSortableState.d.ts.map +1 -1
  223. package/dist/Table/plugins/sortable/useTableSortableState.js +12 -9
  224. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  225. package/dist/Table/plugins/tree/useTableTreeData.js +3 -3
  226. package/dist/TextArea/TextArea.js +1 -1
  227. package/dist/TextInput/TextInput.js +1 -1
  228. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  229. package/dist/Thumbnail/Thumbnail.js +3 -3
  230. package/dist/TimeInput/TimeInput.js +2 -2
  231. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  232. package/dist/Timestamp/Timestamp.js +6 -4
  233. package/dist/Timestamp/formatInstant.d.ts +3 -2
  234. package/dist/Timestamp/formatInstant.d.ts.map +1 -1
  235. package/dist/Timestamp/formatInstant.js +8 -8
  236. package/dist/Timestamp/tooltipEntries.d.ts +2 -1
  237. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -1
  238. package/dist/Timestamp/tooltipEntries.js +5 -3
  239. package/dist/Toast/Toast.d.ts +4 -3
  240. package/dist/Toast/Toast.d.ts.map +1 -1
  241. package/dist/Toast/Toast.js +10 -7
  242. package/dist/Token/Token.d.ts.map +1 -1
  243. package/dist/Token/Token.js +6 -6
  244. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  245. package/dist/Tokenizer/Tokenizer.js +1 -1
  246. package/dist/Tooltip/Tooltip.d.ts +16 -3
  247. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  248. package/dist/Tooltip/Tooltip.js +2 -0
  249. package/dist/Tooltip/index.d.ts +1 -1
  250. package/dist/Tooltip/index.d.ts.map +1 -1
  251. package/dist/Tooltip/useTooltip.d.ts +21 -2
  252. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  253. package/dist/Tooltip/useTooltip.js +71 -13
  254. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  255. package/dist/TopNav/TopNavHeading.js +4 -4
  256. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  257. package/dist/TopNav/TopNavItem.js +3 -3
  258. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  259. package/dist/TopNav/TopNavMegaMenu.js +2 -2
  260. package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
  261. package/dist/TopNav/TopNavMegaMenuItem.js +2 -2
  262. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  263. package/dist/TopNav/TopNavMenu.js +4 -4
  264. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  265. package/dist/TreeList/TreeListItem.js +7 -7
  266. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  267. package/dist/Typeahead/BaseTypeahead.js +2 -2
  268. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  269. package/dist/Typeahead/Typeahead.js +1 -1
  270. package/dist/astryx.css +124 -62
  271. package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
  272. package/dist/hooks/containerReveal.stylex.js +18 -18
  273. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  274. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  275. package/dist/hooks/scrollbarGutter.js +103 -0
  276. package/dist/hooks/useAutoMediaMode.d.ts +31 -0
  277. package/dist/hooks/useAutoMediaMode.d.ts.map +1 -0
  278. package/dist/hooks/useAutoMediaMode.js +201 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
  280. package/dist/hooks/useInputStatusIcon.js +1 -1
  281. package/dist/hooks/useListFocus.d.ts +5 -2
  282. package/dist/hooks/useListFocus.d.ts.map +1 -1
  283. package/dist/hooks/useListFocus.js +12 -6
  284. package/dist/hooks/useScrollLock.d.ts +4 -0
  285. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  286. package/dist/hooks/useScrollLock.js +13 -1
  287. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  288. package/dist/i18n/InternationalizationContext.js +2 -0
  289. package/dist/i18n/index.d.ts +6 -0
  290. package/dist/i18n/index.d.ts.map +1 -1
  291. package/dist/i18n/index.js +6 -0
  292. package/dist/i18n/useCollator.d.ts +12 -0
  293. package/dist/i18n/useCollator.d.ts.map +1 -0
  294. package/dist/i18n/useCollator.js +40 -0
  295. package/dist/i18n/useLocale.d.ts +3 -0
  296. package/dist/i18n/useLocale.d.ts.map +1 -0
  297. package/dist/i18n/useLocale.js +31 -0
  298. package/dist/theme/MediaTheme.d.ts +25 -5
  299. package/dist/theme/MediaTheme.d.ts.map +1 -1
  300. package/dist/theme/MediaTheme.js +31 -6
  301. package/dist/theme/defineTheme.d.ts +5 -0
  302. package/dist/theme/defineTheme.d.ts.map +1 -1
  303. package/dist/theme/defineTheme.js +5 -0
  304. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  305. package/dist/theme/derivedVarRegistry.js +7 -0
  306. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  307. package/dist/theme/generateThemeRules.js +66 -5
  308. package/dist/theme/index.d.ts +1 -1
  309. package/dist/theme/index.d.ts.map +1 -1
  310. package/dist/theme/syntax/index.d.ts +6 -0
  311. package/dist/theme/syntax/index.d.ts.map +1 -1
  312. package/dist/theme/syntax/index.js +7 -2
  313. package/dist/utils/dateParser.d.ts +4 -3
  314. package/dist/utils/dateParser.d.ts.map +1 -1
  315. package/dist/utils/dateParser.js +8 -8
  316. package/dist/utils/index.d.ts +1 -1
  317. package/dist/utils/index.d.ts.map +1 -1
  318. package/dist/utils/index.js +1 -1
  319. package/dist/utils/plainDate.d.ts +5 -2
  320. package/dist/utils/plainDate.d.ts.map +1 -1
  321. package/dist/utils/plainDate.js +20 -4
  322. package/locales/en.json +28 -0
  323. package/locales/pseudo.json +21 -0
  324. package/package.json +3 -3
  325. package/src/Avatar/Avatar.doc.mjs +10 -6
  326. package/src/Avatar/Avatar.test.tsx +219 -1
  327. package/src/Avatar/Avatar.tsx +179 -37
  328. package/src/Avatar/AvatarStatusDot.tsx +30 -4
  329. package/src/Avatar/AvatarStatusLabelContext.ts +47 -0
  330. package/src/AvatarGroup/AvatarGroup.doc.mjs +10 -6
  331. package/src/AvatarGroup/AvatarGroup.test.tsx +62 -0
  332. package/src/AvatarGroup/AvatarGroup.tsx +3 -1
  333. package/src/AvatarGroup/AvatarGroupOverflow.tsx +31 -8
  334. package/src/Banner/Banner.doc.mjs +2 -0
  335. package/src/Blockquote/Blockquote.doc.mjs +30 -7
  336. package/src/Blockquote/Blockquote.test.tsx +34 -3
  337. package/src/Blockquote/Blockquote.tsx +11 -9
  338. package/src/BottomSheet/BottomSheet.doc.mjs +3 -3
  339. package/src/BottomSheet/BottomSheet.test.tsx +24 -0
  340. package/src/BottomSheet/BottomSheetPanel.test.tsx +91 -0
  341. package/src/BottomSheet/BottomSheetPanel.tsx +58 -3
  342. package/src/Breadcrumbs/BreadcrumbItem.tsx +36 -23
  343. package/src/Breadcrumbs/Breadcrumbs.doc.mjs +26 -2
  344. package/src/Breadcrumbs/Breadcrumbs.test.tsx +31 -0
  345. package/src/Button/Button.tsx +12 -9
  346. package/src/Calendar/Calendar.tsx +15 -9
  347. package/src/Calendar/hooks/useCalendarNavigation.ts +6 -4
  348. package/src/Calendar/styles.ts +8 -5
  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 +11 -5
  353. package/src/Chat/ChatComposerDrawer.tsx +4 -1
  354. package/src/Chat/ChatToolCalls.tsx +13 -4
  355. package/src/Chat/{useChatDictation.test.ts → useChatDictation.test.tsx} +29 -14
  356. package/src/Chat/useChatDictation.ts +1 -1
  357. package/src/Chat/useSpeechRecognition.ts +5 -3
  358. package/src/Chat/useTriggerMenu.tsx +4 -1
  359. package/src/CheckboxInput/CheckboxInput.tsx +5 -2
  360. package/src/Citation/Citation.test.tsx +6 -1
  361. package/src/Citation/Citation.tsx +11 -5
  362. package/src/ClickableCard/ClickableCard.tsx +7 -4
  363. package/src/CodeBlock/CodeBlock.tsx +4 -1
  364. package/src/Collapsible/Collapsible.tsx +5 -2
  365. package/src/CommandPalette/CommandPaletteItem.tsx +6 -3
  366. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -0
  367. package/src/ComplexSelector/ComplexSelector.tsx +14 -7
  368. package/src/DateInput/DateInput.test.tsx +17 -1
  369. package/src/DateInput/DateInput.tsx +93 -21
  370. package/src/DateInput/DateInputTouch.test.tsx +2582 -0
  371. package/src/DateInput/MonthScroller.tsx +878 -0
  372. package/src/DateInput/MonthYearWheels.tsx +147 -0
  373. package/src/DateInput/TouchDateField.tsx +1223 -0
  374. package/src/DateInput/Wheel.tsx +473 -0
  375. package/src/DateInput/monthGeometry.ts +118 -0
  376. package/src/DateInput/tokens.stylex.ts +99 -0
  377. package/src/DateInput/useOwnScrollGesture.ts +227 -0
  378. package/src/DateInput/usePointerDragScroll.ts +220 -0
  379. package/src/DateInput/useScrollSettle.ts +164 -0
  380. package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
  381. package/src/DateRangeInput/DateRangeInput.tsx +31 -13
  382. package/src/DateTimeInput/DateTimeInput.tsx +25 -9
  383. package/src/Dialog/Dialog.test.tsx +24 -0
  384. package/src/Dialog/Dialog.tsx +3 -0
  385. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +5 -2
  386. package/src/DropdownMenu/DropdownMenuItem.tsx +5 -2
  387. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +5 -2
  388. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +5 -2
  389. package/src/Field/Field.doc.mjs +2 -0
  390. package/src/Field/FieldLabel.tsx +9 -3
  391. package/src/Field/InputClearButton.test.tsx +110 -3
  392. package/src/Field/InputClearButton.tsx +38 -0
  393. package/src/Field/inputStyles.stylex.ts +17 -13
  394. package/src/FileInput/FileInput.tsx +15 -8
  395. package/src/FormLayout/FormLayout.doc.mjs +1 -1
  396. package/src/HoverCard/HoverCard.doc.mjs +18 -0
  397. package/src/HoverCard/HoverCard.test.tsx +189 -0
  398. package/src/HoverCard/HoverCard.tsx +22 -2
  399. package/src/HoverCard/index.ts +1 -0
  400. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  401. package/src/HoverCard/useHoverCard.tsx +103 -7
  402. package/src/InputGroup/InputGroup.tsx +1 -1
  403. package/src/Item/Item.doc.mjs +1 -0
  404. package/src/Item/Item.test.tsx +50 -0
  405. package/src/Item/Item.tsx +54 -6
  406. package/src/Layer/index.ts +10 -0
  407. package/src/Layer/useLayer.tsx +13 -2
  408. package/src/Layer/useTouchTrigger.test.ts +70 -0
  409. package/src/Layer/useTouchTrigger.ts +366 -0
  410. package/src/Layout/Layout.doc.mjs +18 -0
  411. package/src/Layout/container.stylex.ts +11 -3
  412. package/src/Layout/index.ts +4 -0
  413. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  414. package/src/Layout/padding.stylex.ts +192 -12
  415. package/src/Lightbox/Lightbox.tsx +11 -3
  416. package/src/Link/Link.tsx +9 -6
  417. package/src/MetadataList/MetadataList.tsx +4 -1
  418. package/src/MobileNav/MobileNav.tsx +61 -5
  419. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  420. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  421. package/src/MultiSelector/MultiSelector.test.tsx +1 -1
  422. package/src/MultiSelector/MultiSelector.tsx +26 -10
  423. package/src/NavItem/navItemStyles.stylex.ts +7 -4
  424. package/src/NavMenu/NavHeadingMenuItem.tsx +6 -3
  425. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  426. package/src/NumberInput/NumberInput.test.tsx +138 -0
  427. package/src/NumberInput/NumberInput.tsx +47 -8
  428. package/src/Outline/Outline.tsx +5 -2
  429. package/src/Pagination/Pagination.tsx +5 -2
  430. package/src/Popover/Popover.test.tsx +27 -1
  431. package/src/PowerSearch/PowerSearch.tsx +17 -8
  432. package/src/PowerSearch/PowerSearchToken.tsx +3 -0
  433. package/src/PowerSearch/formatFilterValue.test.ts +22 -2
  434. package/src/PowerSearch/formatFilterValue.ts +31 -6
  435. package/src/RadioList/RadioList.doc.mjs +1 -1
  436. package/src/RadioList/RadioList.test.tsx +170 -2
  437. package/src/RadioList/RadioListItem.tsx +88 -53
  438. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  439. package/src/Resizable/ResizeHandle.tsx +25 -7
  440. package/src/Section/Section.doc.mjs +55 -0
  441. package/src/Section/Section.test.tsx +201 -4
  442. package/src/Section/Section.tsx +69 -0
  443. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  444. package/src/SegmentedControl/SegmentedControlItem.tsx +15 -3
  445. package/src/SelectableCard/SelectableCard.tsx +6 -3
  446. package/src/Selector/Selector.doc.mjs +16 -2
  447. package/src/Selector/Selector.test.tsx +525 -1
  448. package/src/Selector/Selector.tsx +180 -25
  449. package/src/Selector/SelectorOption.doc.mjs +7 -0
  450. package/src/Selector/SelectorOption.tsx +21 -0
  451. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  452. package/src/Selector/types.ts +4 -4
  453. package/src/SideNav/SideNavHeading.tsx +14 -5
  454. package/src/SideNav/SideNavItem.tsx +9 -3
  455. package/src/Slider/Slider.tsx +25 -6
  456. package/src/Stack/Stack.doc.mjs +72 -0
  457. package/src/Stack/Stack.test.tsx +156 -0
  458. package/src/Stack/Stack.tsx +50 -8
  459. package/src/Switch/Switch.tsx +5 -2
  460. package/src/TabList/Tab.tsx +4 -1
  461. package/src/TabList/TabMenu.tsx +9 -3
  462. package/src/Table/TableRow.tsx +4 -4
  463. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +6 -2
  464. package/src/Table/plugins/filtering/useTableFiltering.tsx +4 -1
  465. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +10 -3
  466. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +6 -3
  467. package/src/Table/plugins/sortable/useTableSortable.tsx +4 -1
  468. package/src/Table/plugins/sortable/useTableSortableState.test.tsx +34 -0
  469. package/src/Table/plugins/sortable/useTableSortableState.tsx +26 -23
  470. package/src/Table/plugins/tree/useTableTreeData.tsx +10 -4
  471. package/src/TextArea/TextArea.doc.mjs +1 -1
  472. package/src/TextArea/TextArea.tsx +1 -1
  473. package/src/TextInput/TextInput.tsx +1 -1
  474. package/src/Thumbnail/Thumbnail.tsx +9 -3
  475. package/src/TimeInput/TimeInput.tsx +1 -1
  476. package/src/Timestamp/Timestamp.test.tsx +27 -7
  477. package/src/Timestamp/Timestamp.tsx +8 -4
  478. package/src/Timestamp/formatInstant.ts +9 -7
  479. package/src/Timestamp/tooltipEntries.test.ts +11 -6
  480. package/src/Timestamp/tooltipEntries.ts +6 -2
  481. package/src/Toast/Toast.tsx +9 -7
  482. package/src/Token/Token.tsx +14 -5
  483. package/src/Tokenizer/Tokenizer.tsx +4 -1
  484. package/src/Tooltip/Tooltip.doc.mjs +18 -0
  485. package/src/Tooltip/Tooltip.test.tsx +254 -1
  486. package/src/Tooltip/Tooltip.tsx +22 -2
  487. package/src/Tooltip/index.ts +1 -0
  488. package/src/Tooltip/useTooltip.doc.mjs +2 -0
  489. package/src/Tooltip/useTooltip.tsx +99 -21
  490. package/src/TopNav/TopNavHeading.tsx +13 -4
  491. package/src/TopNav/TopNavItem.tsx +6 -3
  492. package/src/TopNav/TopNavMegaMenu.tsx +5 -2
  493. package/src/TopNav/TopNavMegaMenuItem.tsx +5 -2
  494. package/src/TopNav/TopNavMenu.tsx +10 -4
  495. package/src/TreeList/TreeListItem.tsx +22 -7
  496. package/src/Typeahead/BaseTypeahead.tsx +5 -2
  497. package/src/Typeahead/Typeahead.tsx +4 -1
  498. package/src/hooks/containerReveal.stylex.ts +54 -18
  499. package/src/hooks/scrollbarGutter.test.ts +167 -0
  500. package/src/hooks/scrollbarGutter.ts +121 -0
  501. package/src/hooks/useAutoMediaMode.test.ts +109 -0
  502. package/src/hooks/useAutoMediaMode.ts +233 -0
  503. package/src/hooks/useInputStatusIcon.tsx +4 -1
  504. package/src/hooks/useListFocus.doc.mjs +2 -2
  505. package/src/hooks/useListFocus.test.tsx +65 -3
  506. package/src/hooks/useListFocus.ts +15 -7
  507. package/src/hooks/useScrollLock.doc.mjs +4 -4
  508. package/src/hooks/useScrollLock.test.ts +73 -0
  509. package/src/hooks/useScrollLock.ts +14 -0
  510. package/src/i18n/InternationalizationContext.ts +2 -0
  511. package/src/i18n/__tests__/e2e-powersearch.test.tsx +37 -1
  512. package/src/i18n/__tests__/useCollator.test.tsx +115 -0
  513. package/src/i18n/__tests__/useLocale.test.tsx +52 -0
  514. package/src/i18n/index.ts +6 -0
  515. package/src/i18n/useCollator.doc.mjs +84 -0
  516. package/src/i18n/useCollator.ts +44 -0
  517. package/src/i18n/useLocale.doc.mjs +70 -0
  518. package/src/i18n/useLocale.ts +34 -0
  519. package/src/i18n/useTranslator.doc.mjs +1 -1
  520. package/src/reset.css +9 -2
  521. package/src/reset.test.ts +22 -0
  522. package/src/theme/MediaTheme.doc.mjs +23 -4
  523. package/src/theme/MediaTheme.dom.test.tsx +97 -0
  524. package/src/theme/MediaTheme.tsx +42 -7
  525. package/src/theme/defineTheme.test.ts +9 -3
  526. package/src/theme/defineTheme.ts +5 -0
  527. package/src/theme/derivedVarRegistry.test.ts +19 -5
  528. package/src/theme/derivedVarRegistry.ts +4 -0
  529. package/src/theme/generateThemeRules.test.ts +118 -0
  530. package/src/theme/generateThemeRules.ts +83 -5
  531. package/src/theme/index.ts +1 -1
  532. package/src/theme/syntax/index.ts +6 -2
  533. package/src/theme/syntax/serverSafeSyntax.test.ts +68 -0
  534. package/src/utils/dateParser.test.ts +20 -0
  535. package/src/utils/dateParser.ts +14 -7
  536. package/src/utils/index.ts +2 -0
  537. package/src/utils/plainDate.test.ts +6 -0
  538. package/src/utils/plainDate.ts +21 -4
@@ -0,0 +1,227 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useOwnScrollGesture.ts
7
+ * @input A scroll container ref and which axis it owns
8
+ * @output Stops touch gestures on that container from reaching an ancestor
9
+ * @position Internal behavior hook; consumed by Wheel.tsx and MonthScroller.tsx
10
+ *
11
+ * Lets a nested scroller keep the finger that lands on it.
12
+ *
13
+ * ## The conflict
14
+ *
15
+ * `BottomSheet` implements swipe-to-dismiss by watching touches on its
16
+ * scrolling body: at the body's scroll top, a downward pull stops being a
17
+ * scroll and becomes a sheet drag. The test is `body.scrollTop`, and the body
18
+ * of a sheet sized to hug its content never scrolls, so it reads as "at the
19
+ * top" forever — every downward-ish drag anywhere inside promotes to a drag.
20
+ *
21
+ * `touch-action` does NOT protect against this. It governs what the BROWSER
22
+ * will pan natively; the sheet's listener is JavaScript, and its
23
+ * `preventDefault()` cancels the native scroll whatever `touch-action` says.
24
+ * Measured: with `touch-action: pan-x` on the month scroller, a swipe just 9°
25
+ * off horizontal (120px across, 20px down — an ordinary thumb arc) had every
26
+ * `touchmove` cancelled by the sheet, so the calendar did not move at all and
27
+ * the sheet sprang back. The month simply would not change.
28
+ *
29
+ * ## Two ways to own a gesture
30
+ *
31
+ * - `'all'` — the wheels. They scroll vertically, exactly the axis the sheet
32
+ * wants, so there is no way to share: the scroller takes every touch that
33
+ * lands on it and the sheet is dismissed from the handle, the header or the
34
+ * scrim instead.
35
+ *
36
+ * - `'inline'` — the month scroller. It pages sideways, so the two CAN share
37
+ * by direction: lock the axis on the first significant move, claim the
38
+ * gesture only when it is horizontal, and let vertical ones through so
39
+ * swipe-to-dismiss still works on the calendar.
40
+ *
41
+ * ## Where the line falls, and the gestures that fall past it
42
+ *
43
+ * A thumb swiping sideways travels in an arc, not a line — a "horizontal"
44
+ * swipe routinely drifts 10-30° down, and sometimes further. So an `'inline'`
45
+ * scroller claims generously: vertical has to beat horizontal by
46
+ * {@link VERTICAL_DOMINANCE} before the sheet gets the gesture, which puts the
47
+ * split near 63° rather than at the diagonal.
48
+ *
49
+ * That is wider than the browser will pan. `touch-action: pan-x` pans only
50
+ * while `|dx| > |dy|` and refuses the moment a gesture tips past 45°, so the
51
+ * band between the two lines used to be the worst outcome available: measured
52
+ * on an iPhone 15 profile, a 200px swipe at 45-60° neither paged a month nor
53
+ * dismissed the sheet, because we had told the sheet to keep off a gesture the
54
+ * compositor then declined to pan. Nothing moved at all.
55
+ *
56
+ * {@link OwnScrollGestureOptions.onSwipe} closes that band, and it does not
57
+ * need to know where the browser's line is: if the element's scroll offset
58
+ * never changed across a claimed gesture, the browser declined it, and a
59
+ * swipe that cleared {@link SWIPE_DISTANCE} is reported so the caller can page
60
+ * itself. Native pans keep their momentum and snapping and report nothing.
61
+ *
62
+ * The remaining asymmetry is deliberate and in the direction the scroller
63
+ * needs: it is generous about distance too, paging on a 30px flick where the
64
+ * sheet asks for ~200px of pull before it dismisses.
65
+ *
66
+ * ## Why native listeners
67
+ *
68
+ * React delegates its events at the root container, which is an ANCESTOR of
69
+ * the sheet body — so a React `onTouchStart` would run after the body's own
70
+ * listener had already claimed the gesture. `stopPropagation` has to happen on
71
+ * a listener attached to the element itself, during the real bubble phase,
72
+ * before the event ever reaches the body.
73
+ *
74
+ * Nothing here calls `preventDefault`, so the listeners stay passive and the
75
+ * scroller keeps native momentum, snapping and rubber-banding.
76
+ *
77
+ * SYNC: When modified, update DateInputTouch.test.tsx.
78
+ */
79
+
80
+ import {useEffect, useRef} from 'react';
81
+
82
+ /**
83
+ * How much a gesture's vertical travel must exceed its horizontal travel
84
+ * before an `'inline'` scroller gives it up. Above 1, so horizontal wins the
85
+ * ambiguous middle; see the file header for why the split is deliberately
86
+ * unfair, and how the gestures past the browser's own 45° line are handled.
87
+ */
88
+ export const VERTICAL_DOMINANCE = 2;
89
+
90
+ /** Movement below this is noise, not a direction. */
91
+ const AXIS_LOCK_SLOP = 6;
92
+
93
+ /**
94
+ * Horizontal travel a claimed-but-unpanned gesture needs before it counts as
95
+ * a swipe. Roughly the shortest flick the browser itself will page on.
96
+ */
97
+ export const SWIPE_DISTANCE = 32;
98
+
99
+ export type OwnedAxis = 'all' | 'inline';
100
+
101
+ export type OwnScrollGestureOptions = {
102
+ /** Skip while the scroller is hidden; a hidden panel keeps its layout box,
103
+ * so its listeners would otherwise still be live. */
104
+ isEnabled?: boolean;
105
+ /**
106
+ * Called when a claimed gesture ended without the browser having scrolled
107
+ * the element at all — the diagonal swipes `touch-action: pan-x` refuses.
108
+ *
109
+ * `direction` is the sign to move the scroll offset by, so it composes
110
+ * directly: `scrollBy({left: direction * pageWidth})`. It is physical, and
111
+ * therefore correct in both directions of writing.
112
+ *
113
+ * Only fires for `'inline'`. A gesture the browser panned reports nothing,
114
+ * so momentum and snapping are never second-guessed.
115
+ */
116
+ onSwipe?: (direction: 1 | -1) => void;
117
+ };
118
+
119
+ /**
120
+ * Claim touch gestures that start on `ref`'s element, so an ancestor cannot
121
+ * reinterpret them as its own drag.
122
+ *
123
+ * @param ref - the scroll container
124
+ * @param axis - `'all'` to take every touch, `'inline'` to take only the
125
+ * horizontal ones and leave vertical drags to the sheet
126
+ * @param options - see {@link OwnScrollGestureOptions}
127
+ */
128
+ export function useOwnScrollGesture(
129
+ ref: React.RefObject<HTMLElement | null>,
130
+ axis: OwnedAxis = 'all',
131
+ options: OwnScrollGestureOptions = {},
132
+ ): void {
133
+ const {isEnabled = true, onSwipe} = options;
134
+
135
+ // Read through a ref so a caller need not memoize the callback: re-running
136
+ // this effect would tear the listeners off mid-gesture.
137
+ const onSwipeRef = useRef(onSwipe);
138
+ onSwipeRef.current = onSwipe;
139
+
140
+ useEffect(() => {
141
+ const element = ref.current;
142
+ if (element == null || !isEnabled) {
143
+ return;
144
+ }
145
+
146
+ // null = not yet decided; true = ours for the rest of this gesture.
147
+ let claimed: boolean | null = null;
148
+ let originX = 0;
149
+ let originY = 0;
150
+ let lastX = 0;
151
+ let offsetAtStart = 0;
152
+ // Whether the browser actually panned the element during this gesture.
153
+ // Sampled per move rather than compared once at the end, because a short
154
+ // pan that snaps back finishes at the offset it started from.
155
+ let didPan = false;
156
+
157
+ const onStart = (event: TouchEvent) => {
158
+ const touch = event.touches[0];
159
+ originX = touch?.clientX ?? 0;
160
+ originY = touch?.clientY ?? 0;
161
+ lastX = originX;
162
+ offsetAtStart = element.scrollLeft;
163
+ didPan = false;
164
+ // 'all' decides at touchstart, before the sheet's own touchstart
165
+ // listener records an armed edge. 'inline' cannot decide yet — a touch
166
+ // has no direction until it moves — so it lets touchstart through and
167
+ // locks on the first move that clears the slop.
168
+ claimed = axis === 'all' ? true : null;
169
+ if (claimed) {
170
+ event.stopPropagation();
171
+ }
172
+ };
173
+
174
+ const onMove = (event: TouchEvent) => {
175
+ if (claimed === false) {
176
+ return;
177
+ }
178
+ const touch = event.touches[0];
179
+ if (touch == null) {
180
+ return;
181
+ }
182
+ if (claimed == null) {
183
+ const dx = Math.abs(touch.clientX - originX);
184
+ const dy = Math.abs(touch.clientY - originY);
185
+ if (Math.max(dx, dy) < AXIS_LOCK_SLOP) {
186
+ return;
187
+ }
188
+ claimed = dy <= dx * VERTICAL_DOMINANCE;
189
+ if (!claimed) {
190
+ return;
191
+ }
192
+ }
193
+ lastX = touch.clientX;
194
+ didPan ||= Math.abs(element.scrollLeft - offsetAtStart) >= 1;
195
+ event.stopPropagation();
196
+ };
197
+
198
+ const onEnd = () => {
199
+ // A gesture we claimed that the browser then refused to pan: the sheet
200
+ // was told to keep off it, so unless it is acted on here it does
201
+ // nothing at all. See the file header.
202
+ if (
203
+ claimed === true &&
204
+ axis === 'inline' &&
205
+ !didPan &&
206
+ Math.abs(lastX - originX) >= SWIPE_DISTANCE
207
+ ) {
208
+ onSwipeRef.current?.(lastX < originX ? 1 : -1);
209
+ }
210
+ // touchend/touchcancel deliberately propagate: they carry no
211
+ // interpretation, and letting them through is what resets the sheet's
212
+ // bookkeeping.
213
+ claimed = null;
214
+ };
215
+
216
+ element.addEventListener('touchstart', onStart, {passive: true});
217
+ element.addEventListener('touchmove', onMove, {passive: true});
218
+ element.addEventListener('touchend', onEnd, {passive: true});
219
+ element.addEventListener('touchcancel', onEnd, {passive: true});
220
+ return () => {
221
+ element.removeEventListener('touchstart', onStart);
222
+ element.removeEventListener('touchmove', onMove);
223
+ element.removeEventListener('touchend', onEnd);
224
+ element.removeEventListener('touchcancel', onEnd);
225
+ };
226
+ }, [ref, axis, isEnabled]);
227
+ }
@@ -0,0 +1,220 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file usePointerDragScroll.ts
7
+ * @input A scroll container ref
8
+ * @output Lets a mouse drag the container's contents the way a finger does
9
+ * @position Internal behavior hook; consumed by Wheel.tsx
10
+ *
11
+ * ## Why this exists
12
+ *
13
+ * A wheel is a scroll container, which is what makes it feel right under a
14
+ * finger: the platform supplies panning, momentum, rubber-banding and the
15
+ * snap. A mouse gets none of that. Browsers do not drag-scroll an overflow
16
+ * container — press and pull on one and nothing happens at all, which is
17
+ * exactly what a wheel invites you to try.
18
+ *
19
+ * That leaves the mouse with the scroll wheel and a click on a visible row.
20
+ * Both work, and neither is the gesture the control is shaped like. It
21
+ * matters more than "mouse users are not the target": the touch surface is
22
+ * reviewed, themed and screenshotted on desktop browsers, where a wheel that
23
+ * ignores the pointer reads as broken rather than as touch-only.
24
+ *
25
+ * ## Mouse only, deliberately
26
+ *
27
+ * Touch and pen already pan natively, and this hook would be strictly worse
28
+ * than what they get — no momentum, no rubber-banding, no compositor
29
+ * threading. So it ignores every pointer type but `mouse` and leaves those
30
+ * gestures untouched.
31
+ *
32
+ * ## Snap has to be suspended for the duration
33
+ *
34
+ * `scroll-snap-type: y mandatory` re-snaps after every scroll, including a
35
+ * programmatic one, so a drag that assigns `scrollTop` is fighting it on
36
+ * every frame. Measured: dragging 5px at a time with mandatory snap on, 7 of
37
+ * 8 steps were yanked back to a snap position — the wheel sticks to a row and
38
+ * then jumps a whole one. The drag therefore suspends snapping, and restores
39
+ * it once the release has settled somewhere legal.
40
+ *
41
+ * SYNC: When modified, update:
42
+ * - /packages/core/src/DateInput/Wheel.tsx
43
+ * - /packages/core/src/DateInput/DateInputTouch.test.tsx
44
+ */
45
+
46
+ import {useEffect, useRef} from 'react';
47
+
48
+ /**
49
+ * Movement below this stays a click. A mouse shifts a pixel or two under the
50
+ * press of the button itself, and a wheel row is a click target.
51
+ */
52
+ export const DRAG_SLOP = 4;
53
+
54
+ /** How long to wait for a release's settle before restoring snapping. */
55
+ const SETTLE_FALLBACK_MS = 260;
56
+
57
+ /**
58
+ * Let a mouse drag `ref`'s contents, on a container that already scrolls and
59
+ * snaps for touch.
60
+ *
61
+ * @param ref - the scroll container
62
+ * @param isEnabled - skip while the wheel is hidden; a hidden panel keeps its
63
+ * layout box, so its listeners would otherwise still be live
64
+ */
65
+ export function usePointerDragScroll(
66
+ ref: React.RefObject<HTMLElement | null>,
67
+ isEnabled: boolean = true,
68
+ ): void {
69
+ useEffect(() => {
70
+ const element = ref.current;
71
+ if (element == null || !isEnabled) {
72
+ return;
73
+ }
74
+
75
+ let pointerId: number | null = null;
76
+ let originY = 0;
77
+ let originScrollTop = 0;
78
+ let isDragging = false;
79
+ // Survives from the drag's end to the click it produces, which is the
80
+ // whole reason it is not just `isDragging`.
81
+ let wasDragged = false;
82
+ let restoreTimer: number | undefined;
83
+
84
+ const restoreSnap = () => {
85
+ window.clearTimeout(restoreTimer);
86
+ element.removeEventListener('scrollend', restoreSnap);
87
+ // Back to the stylesheet's value rather than a hardcoded one, so this
88
+ // cannot drift from Wheel's own `scrollSnapType`.
89
+ element.style.removeProperty('scroll-snap-type');
90
+ };
91
+
92
+ const suspendSnap = () => {
93
+ window.clearTimeout(restoreTimer);
94
+ element.removeEventListener('scrollend', restoreSnap);
95
+ element.style.scrollSnapType = 'none';
96
+ };
97
+
98
+ const onPointerDown = (event: PointerEvent) => {
99
+ // Touch and pen pan natively, and better. Secondary buttons are for the
100
+ // context menu.
101
+ if (event.pointerType !== 'mouse' || event.button !== 0) {
102
+ return;
103
+ }
104
+ // Keep the press away from BottomSheet, which starts its own
105
+ // drag-to-dismiss from a `pointerdown` on its body and CAPTURES the
106
+ // pointer for it. Two things go wrong if it gets there first: the drag
107
+ // below is fighting the sheet for the same gesture, and — measured on
108
+ // the calendar too, so this is not new — every later pointer event
109
+ // retargets to the sheet body, which means a click that wobbles more
110
+ // than a pixel or two never reaches the row under it and selects
111
+ // nothing at all.
112
+ //
113
+ // A native listener during the real bubble phase is what it takes:
114
+ // the sheet's handler is a React prop, delegated at the root container,
115
+ // which is an ANCESTOR of this element. Same reasoning as
116
+ // useOwnScrollGesture, and the same trade — mouse drag-to-dismiss now
117
+ // starts from the grab handle, the header or the scrim rather than
118
+ // from inside a wheel, which is where a picker sheet puts it anyway.
119
+ event.stopPropagation();
120
+ pointerId = event.pointerId;
121
+ originY = event.clientY;
122
+ originScrollTop = element.scrollTop;
123
+ isDragging = false;
124
+ // Not captured yet: a press that never moves has to stay a click on the
125
+ // row under it, and capturing here would retarget that click.
126
+ };
127
+
128
+ const onPointerMove = (event: PointerEvent) => {
129
+ if (pointerId !== event.pointerId) {
130
+ return;
131
+ }
132
+ const delta = event.clientY - originY;
133
+ if (!isDragging) {
134
+ if (Math.abs(delta) < DRAG_SLOP) {
135
+ return;
136
+ }
137
+ isDragging = true;
138
+ element.setPointerCapture(event.pointerId);
139
+ suspendSnap();
140
+ // Dragging across rows would otherwise select their text, leaving the
141
+ // wheel looking highlighted after the gesture.
142
+ element.style.userSelect = 'none';
143
+ }
144
+ // Content follows the hand: pull up and the list moves up, which means
145
+ // scrolling further down it.
146
+ element.scrollTop = originScrollTop - delta;
147
+ };
148
+
149
+ const onPointerEnd = (event: PointerEvent) => {
150
+ if (pointerId !== event.pointerId) {
151
+ return;
152
+ }
153
+ pointerId = null;
154
+ if (!isDragging) {
155
+ return;
156
+ }
157
+ isDragging = false;
158
+ wasDragged = true;
159
+ element.style.removeProperty('user-select');
160
+ if (element.hasPointerCapture(event.pointerId)) {
161
+ element.releasePointerCapture(event.pointerId);
162
+ }
163
+
164
+ // The release lands mid-row. Glide to the nearest one and only then let
165
+ // snapping back on — restoring it here instead would jump the distance
166
+ // rather than travel it, and a jump is what this hook exists to avoid.
167
+ const rowHeight = firstRowHeight(element);
168
+ if (rowHeight > 0) {
169
+ const nearest = Math.round(element.scrollTop / rowHeight) * rowHeight;
170
+ element.scrollTo({top: nearest, behavior: 'smooth'});
171
+ }
172
+ // `scrollend` is the honest signal; the timer covers browsers that do
173
+ // not fire it, and the case where the glide had nowhere to go.
174
+ // Not `{once: true}`: a listener that never fires is never removed by
175
+ // it, so a component unmounted mid-glide would leave one attached.
176
+ // `restoreSnap` removes it itself, whichever of the two gets there
177
+ // first, and the cleanup below calls `restoreSnap` on unmount.
178
+ //
179
+ // eslint-disable-next-line @eslint-react/web-api-no-leaked-event-listener -- removed by restoreSnap, which the cleanup calls
180
+ element.addEventListener('scrollend', restoreSnap);
181
+ restoreTimer = window.setTimeout(restoreSnap, SETTLE_FALLBACK_MS);
182
+ };
183
+
184
+ // A drag ends over whichever row the mouse happens to be on, and that row
185
+ // would take the click as a selection. Swallow it — but only the one, and
186
+ // only after a real drag, so an ordinary click still selects.
187
+ const onClickCapture = (event: MouseEvent) => {
188
+ if (!wasDragged) {
189
+ return;
190
+ }
191
+ wasDragged = false;
192
+ event.stopPropagation();
193
+ event.preventDefault();
194
+ };
195
+
196
+ element.addEventListener('pointerdown', onPointerDown);
197
+ element.addEventListener('pointermove', onPointerMove);
198
+ element.addEventListener('pointerup', onPointerEnd);
199
+ element.addEventListener('pointercancel', onPointerEnd);
200
+ element.addEventListener('click', onClickCapture, {capture: true});
201
+ return () => {
202
+ element.removeEventListener('pointerdown', onPointerDown);
203
+ element.removeEventListener('pointermove', onPointerMove);
204
+ element.removeEventListener('pointerup', onPointerEnd);
205
+ element.removeEventListener('pointercancel', onPointerEnd);
206
+ element.removeEventListener('click', onClickCapture, {capture: true});
207
+ restoreSnap();
208
+ };
209
+ }, [ref, isEnabled]);
210
+ }
211
+
212
+ /**
213
+ * The pitch of the rows, read off the first one. Taken from layout rather
214
+ * than the token so a themed `--date-input-touch-wheel-item-size` is honoured
215
+ * without this hook having to know the variable exists.
216
+ */
217
+ function firstRowHeight(element: HTMLElement): number {
218
+ const row = element.querySelector('[role="option"]');
219
+ return row instanceof HTMLElement ? row.offsetHeight : 0;
220
+ }
@@ -0,0 +1,164 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useScrollSettle.ts
7
+ * @input A scroll container ref and a settle callback
8
+ * @output Calls back once, after scrolling (including momentum) stops
9
+ * @position Internal behavior hook; consumed by Wheel.tsx and MonthScroller.tsx
10
+ *
11
+ * A snap scroller commits its value when it comes to rest, so it needs a
12
+ * "scrolling stopped" signal. `scrollend` is that signal, but it is recent
13
+ * enough that mobile Safari below 26 does not have it — and it is exactly the
14
+ * browser this component targets. So: listen for `scrollend` where it exists,
15
+ * and fall back to a quiet-period timer everywhere else. The same
16
+ * belt-and-braces pattern is in core's `useScrollSpy`.
17
+ *
18
+ * ## The two things the timer must not mistake for rest
19
+ *
20
+ * A quiet period is a guess, and on iOS it guesses wrong twice unless it is
21
+ * told otherwise. Both mistakes were behind the same bug: opening the
22
+ * month/year wheels, flicking to an earlier month, and watching the month
23
+ * climb on its own afterwards — on real iOS and the simulator, never in
24
+ * Chrome.
25
+ *
26
+ * 1. **A finger resting mid-drag is not rest.** Hold a wheel still for a
27
+ * moment without lifting and the scroll events stop, so the timer fires
28
+ * and the value commits to whatever is under the band. Chrome hides this
29
+ * because it has `scrollend`, which does not fire until the touch is
30
+ * released.
31
+ *
32
+ * 2. **Momentum is not rest either.** iOS momentum runs on the UI thread and
33
+ * keeps going for a second or more after the finger is gone, and its scroll
34
+ * events arrive irregularly — the gaps in the slow tail routinely exceed
35
+ * any sane quiet period. Chrome's compositor-driven momentum fires evenly,
36
+ * so the same timer never trips there.
37
+ *
38
+ * A premature settle is not just a wrong value; it feeds back. The commit
39
+ * moves the value, the value moves the scroller back onto the committed row,
40
+ * that programmatic scroll does not stop the momentum still running
41
+ * underneath, and the fresh scroll events start the next premature settle —
42
+ * each one landing a row further along, which is why the month climbed.
43
+ *
44
+ * So: the settle waits for the touch to end, and then for a quiet period
45
+ * measured from the LAST scroll event, which is what makes it outlast
46
+ * momentum however long it runs. {@link useScrollSettle} also reports whether
47
+ * the scroller is at rest, so a caller can refuse to reposition one that is
48
+ * still moving.
49
+ *
50
+ * SYNC: When modified, update DateInputTouch.test.tsx.
51
+ */
52
+
53
+ import {useEffect, useRef} from 'react';
54
+
55
+ /**
56
+ * How long the scroller must be quiet before a settle is assumed. Long enough
57
+ * to outlast the browser's own snap animation (~150-300ms after the finger
58
+ * lifts), or a wheel would commit to whichever option it was passing rather
59
+ * than the one it settles on.
60
+ */
61
+ export const SCROLL_QUIET_MS = 220;
62
+
63
+ /** What {@link useScrollSettle} reports back about the scroller's state. */
64
+ export type ScrollSettleState = {
65
+ /**
66
+ * False from the first touch until the scroller has genuinely stopped —
67
+ * momentum included. Read it before repositioning the scroller: doing that
68
+ * mid-flight fights the platform and, on iOS, restarts the cycle described
69
+ * in the file header.
70
+ */
71
+ isAtRestRef: React.RefObject<boolean>;
72
+ };
73
+
74
+ /**
75
+ * Run `onSettle` when `ref`'s element stops scrolling.
76
+ *
77
+ * @param ref - the scroll container
78
+ * @param onSettle - called with the settled element; may change every render
79
+ * @param isEnabled - skip attaching while false (e.g. a hidden panel)
80
+ */
81
+ export function useScrollSettle(
82
+ ref: React.RefObject<HTMLElement | null>,
83
+ onSettle: (element: HTMLElement) => void,
84
+ isEnabled: boolean = true,
85
+ ): ScrollSettleState {
86
+ // The callback closes over render-fresh values (the option list, the current
87
+ // value); keeping it in a ref means the listeners attach once instead of
88
+ // re-attaching mid-scroll, which would drop an in-flight settle.
89
+ const onSettleRef = useRef(onSettle);
90
+ onSettleRef.current = onSettle;
91
+
92
+ const isAtRestRef = useRef(true);
93
+
94
+ useEffect(() => {
95
+ const element = ref.current;
96
+ if (element == null || !isEnabled) {
97
+ isAtRestRef.current = true;
98
+ return;
99
+ }
100
+
101
+ let timer: ReturnType<typeof setTimeout> | undefined;
102
+ let hasSettled = false;
103
+ let isTouching = false;
104
+
105
+ const settle = () => {
106
+ // A finger still on the glass is not rest, however quiet the scroller
107
+ // has gone. The release re-arms this.
108
+ if (hasSettled || isTouching) {
109
+ return;
110
+ }
111
+ hasSettled = true;
112
+ isAtRestRef.current = true;
113
+ clearTimeout(timer);
114
+ onSettleRef.current(element);
115
+ };
116
+
117
+ const arm = () => {
118
+ clearTimeout(timer);
119
+ timer = setTimeout(settle, SCROLL_QUIET_MS);
120
+ };
121
+
122
+ const onScroll = () => {
123
+ // A fresh scroll re-opens the window: whatever we settle on now is
124
+ // stale, and the settle that matters is the one after this gesture.
125
+ // Momentum keeps arriving here, so the quiet period is measured from
126
+ // the last of it however long it runs.
127
+ hasSettled = false;
128
+ isAtRestRef.current = false;
129
+ arm();
130
+ };
131
+
132
+ const onTouchStart = () => {
133
+ isTouching = true;
134
+ hasSettled = false;
135
+ isAtRestRef.current = false;
136
+ clearTimeout(timer);
137
+ };
138
+
139
+ const onTouchEnd = () => {
140
+ isTouching = false;
141
+ // Momentum may carry on from here and will re-arm this on its own; the
142
+ // timer covers the case where the finger lifted without a fling, which
143
+ // produces no further scroll events at all.
144
+ arm();
145
+ };
146
+
147
+ element.addEventListener('scroll', onScroll, {passive: true});
148
+ element.addEventListener('scrollend', settle);
149
+ element.addEventListener('touchstart', onTouchStart, {passive: true});
150
+ element.addEventListener('touchend', onTouchEnd, {passive: true});
151
+ element.addEventListener('touchcancel', onTouchEnd, {passive: true});
152
+
153
+ return () => {
154
+ clearTimeout(timer);
155
+ element.removeEventListener('scroll', onScroll);
156
+ element.removeEventListener('scrollend', settle);
157
+ element.removeEventListener('touchstart', onTouchStart);
158
+ element.removeEventListener('touchend', onTouchEnd);
159
+ element.removeEventListener('touchcancel', onTouchEnd);
160
+ };
161
+ }, [ref, isEnabled]);
162
+
163
+ return {isAtRestRef};
164
+ }
@@ -102,7 +102,7 @@ export const docs = {
102
102
  name: 'maxRangeSpan',
103
103
  type: 'number',
104
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`).',
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
106
  },
107
107
  {
108
108
  name: 'minRangeSpan',