@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,121 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file scrollbarGutter.ts
5
+ * @input The element a scroll lock pins, plus live layout measurements
6
+ * @output Exports holdScrollbarGutter and the ScrollbarGutterHold it returns
7
+ * @position Internal hook utility; used by useScrollLock and MobileNav so that
8
+ * locking background scroll does not resize the layout viewport.
9
+ *
10
+ * Locking scroll hides the document's scrollbar. Where that scrollbar is a
11
+ * classic (space-taking) one — Windows/Linux desktop, and macOS set to
12
+ * "Always" show scroll bars — hiding it widens the layout viewport by the
13
+ * scrollbar's width and the whole page jumps sideways behind the overlay.
14
+ *
15
+ * The fix is `scrollbar-gutter: stable`, which holds the gutter open once the
16
+ * scrollbar goes away. Being a real layout change it holds `position: fixed`
17
+ * chrome (sticky headers, toast viewports) as well as in-flow content, which
18
+ * padding fundamentally cannot: fixed elements resolve against the viewport,
19
+ * not against the padded element.
20
+ *
21
+ * Padding is kept only as a fallback for engines without `scrollbar-gutter`
22
+ * (Safari shipped it in 18.2), and it is applied by measuring whether the
23
+ * element actually moved rather than by assuming it did.
24
+ */
25
+
26
+ const NOOP: ScrollbarGutterHold = {
27
+ settle() {},
28
+ release() {},
29
+ };
30
+
31
+ export interface ScrollbarGutterHold {
32
+ /**
33
+ * Call once the lock's own styles are applied: measures whether the element
34
+ * grew anyway and, only then, pads the difference away.
35
+ */
36
+ settle(): void;
37
+ /** Call on unlock: restores everything this hold changed. */
38
+ release(): void;
39
+ }
40
+
41
+ /**
42
+ * Keeps `element`'s content box the width it is right now, across a scroll
43
+ * lock that is about to hide the document's scrollbar.
44
+ *
45
+ * Call it *before* applying the lock's styles, then {@link
46
+ * ScrollbarGutterHold.settle} immediately after, and {@link
47
+ * ScrollbarGutterHold.release} on unlock:
48
+ *
49
+ * ```
50
+ * const hold = holdScrollbarGutter(document.body);
51
+ * body.style.position = 'fixed';
52
+ * hold.settle();
53
+ * ```
54
+ *
55
+ * Overlay scrollbars (mobile, default macOS) take no layout space, so there is
56
+ * nothing to hold and nothing is touched.
57
+ */
58
+ export function holdScrollbarGutter(element: HTMLElement): ScrollbarGutterHold {
59
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
60
+ return NOOP;
61
+ }
62
+
63
+ const root = document.documentElement;
64
+ const viewportWidth = root.clientWidth;
65
+
66
+ // 0 means "no layout to measure" (jsdom, and any non-visual environment),
67
+ // not "the scrollbar is as wide as the window".
68
+ if (viewportWidth === 0) {
69
+ return NOOP;
70
+ }
71
+
72
+ const previousGutter = root.style.scrollbarGutter;
73
+ const previousPadding = element.style.paddingRight;
74
+ const widthBefore = element.getBoundingClientRect().width;
75
+
76
+ let heldGutter = false;
77
+ let padded = false;
78
+ let settled = false;
79
+
80
+ // Only when a space-taking scrollbar is actually there. Holding the gutter
81
+ // open on a page that never had one would carve out 15px that was never
82
+ // reserved — the same shift, in the other direction.
83
+ if (window.innerWidth > viewportWidth) {
84
+ root.style.scrollbarGutter = 'stable';
85
+ heldGutter = true;
86
+ }
87
+
88
+ return {
89
+ settle() {
90
+ if (settled) {
91
+ return;
92
+ }
93
+ settled = true;
94
+
95
+ // What the lock actually did to this element, rather than what we
96
+ // assumed it would do. 0 on any engine where the gutter held — and on a
97
+ // page that already sets `scrollbar-gutter: stable` itself, where the
98
+ // scrollbar was never taking the space back.
99
+ const grew = element.getBoundingClientRect().width - widthBefore;
100
+ if (grew <= 0) {
101
+ return;
102
+ }
103
+
104
+ const existing =
105
+ Number.parseFloat(window.getComputedStyle(element).paddingRight) || 0;
106
+ element.style.paddingRight = `${existing + grew}px`;
107
+ padded = true;
108
+ },
109
+
110
+ release() {
111
+ if (padded) {
112
+ element.style.paddingRight = previousPadding;
113
+ padded = false;
114
+ }
115
+ if (heldGutter) {
116
+ root.style.scrollbarGutter = previousGutter;
117
+ heldGutter = false;
118
+ }
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,42 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useResolvedRequired.ts
7
+ * @input Uses React, FormLayoutContext
8
+ * @output Exports useResolvedRequired
9
+ * @position Resolves a field's effective aria-required against the form default
10
+ *
11
+ * SYNC: When modified, update these files to stay in sync:
12
+ * - /packages/core/src/FormLayout/FormLayoutContext.ts (defaultOptionality source)
13
+ * - /packages/core/src/FormLayout/FormLayout.tsx (prop doc: aria-required behavior)
14
+ */
15
+
16
+ import {use} from 'react';
17
+ import {FormLayoutContext} from '../FormLayout/FormLayoutContext';
18
+
19
+ /**
20
+ * Resolve a field's effective *required* state for `aria-required`, honoring a
21
+ * surrounding `FormLayout`'s `defaultOptionality`.
22
+ *
23
+ * Under `defaultOptionality="required"` a field is required unless it opts out
24
+ * with `isOptional`. The visible indicator is suppressed for the unmarked
25
+ * majority, so those fields must still expose `aria-required` — otherwise a
26
+ * sighted user reads them as required (form-wide default, no indicator) while a
27
+ * screen reader hears "not required". This closes that mismatch.
28
+ *
29
+ * Semantics only: this drives `aria-required`, never the native `required`
30
+ * attribute, so a layout-level default can't silently switch on browser
31
+ * validation bubbles. `isOptional` takes precedence, matching `FieldLabel`.
32
+ */
33
+ export function useResolvedRequired({
34
+ isRequired = false,
35
+ isOptional = false,
36
+ }: {
37
+ isRequired?: boolean;
38
+ isOptional?: boolean;
39
+ }): boolean {
40
+ const {defaultOptionality} = use(FormLayoutContext);
41
+ return !isOptional && (isRequired || defaultOptionality === 'required');
42
+ }
@@ -4,7 +4,7 @@
4
4
  export const docs = {
5
5
  name: 'useScrollLock',
6
6
  displayName: 'useScrollLock',
7
- keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed'],
7
+ keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed', 'scrollbar', 'gutter', 'layout shift', 'reflow'],
8
8
  params: [
9
9
  {
10
10
  name: 'isLocked',
@@ -16,7 +16,7 @@ export const docs = {
16
16
  returns: [],
17
17
  usage: {
18
18
  description:
19
- 'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked.',
19
+ 'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked. Pinning hides the document scrollbar, so where that scrollbar takes layout space (desktop) the hook holds its gutter open with scrollbar-gutter: stable for the duration of the lock. The page, including any position: fixed chrome, does not shift sideways.',
20
20
  bestPractices: [
21
21
  { guidance: true, description: 'Use when opening full-screen modals or dialogs to prevent background content from scrolling.' },
22
22
  { guidance: true, description: 'Pass the same boolean that controls dialog visibility (e.g., isOpen) as the isLocked parameter.' },
@@ -32,13 +32,13 @@ export const docs = {
32
32
  /** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
33
33
  export const docsDense = {
34
34
  description:
35
- 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
35
+ 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page (incl. position: fixed chrome) does not shift sideways.',
36
36
  paramDescriptions: {
37
37
  isLocked: 'whether body scroll should be locked.',
38
38
  },
39
39
  usage: {
40
40
  description:
41
- 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
41
+ 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page (incl. position: fixed chrome) does not shift sideways.',
42
42
  bestPractices: [
43
43
  { guidance: true, description: 'Use when opening full-screen modals / dialogs to prevent background content from scrolling.' },
44
44
  { guidance: true, description: 'Pass same boolean that controls dialog visibility (e.g. isOpen) as isLocked parameter.' },
@@ -17,6 +17,9 @@ describe('useScrollLock', () => {
17
17
  afterEach(() => {
18
18
  cleanup();
19
19
  document.body.style.cssText = '';
20
+ document.documentElement.style.cssText = '';
21
+ // @ts-expect-error -- drop the viewport stub so jsdom's own value comes back
22
+ delete document.documentElement.clientWidth;
20
23
  vi.restoreAllMocks();
21
24
  });
22
25
 
@@ -69,4 +72,74 @@ describe('useScrollLock', () => {
69
72
  expect(document.body.style.left).toBe('');
70
73
  expect(document.body.style.right).toBe('');
71
74
  });
75
+
76
+ it('holds the page still across the scrollbar it hides', () => {
77
+ // A 1024px window over a 1009px layout viewport = a 15px classic
78
+ // scrollbar. Pinning the body hides it, which would widen the page by
79
+ // those 15px and reflow everything sideways.
80
+ Object.defineProperty(window, 'innerWidth', {
81
+ value: 1024,
82
+ configurable: true,
83
+ writable: true,
84
+ });
85
+ Object.defineProperty(document.documentElement, 'clientWidth', {
86
+ value: 1009,
87
+ configurable: true,
88
+ });
89
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
90
+
91
+ const lock = renderHook(() => useScrollLock(true));
92
+
93
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
94
+
95
+ lock.unmount();
96
+
97
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
98
+ });
99
+
100
+ it('leaves the page alone when the scrollbar is an overlay one', () => {
101
+ Object.defineProperty(window, 'innerWidth', {
102
+ value: 1024,
103
+ configurable: true,
104
+ writable: true,
105
+ });
106
+ Object.defineProperty(document.documentElement, 'clientWidth', {
107
+ value: 1024,
108
+ configurable: true,
109
+ });
110
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
111
+
112
+ const lock = renderHook(() => useScrollLock(true));
113
+
114
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
115
+ expect(document.body.style.paddingRight).toBe('');
116
+
117
+ lock.unmount();
118
+ });
119
+
120
+ it('holds the gutter for the outermost overlay only, and gives it back once', () => {
121
+ Object.defineProperty(window, 'innerWidth', {
122
+ value: 1024,
123
+ configurable: true,
124
+ writable: true,
125
+ });
126
+ Object.defineProperty(document.documentElement, 'clientWidth', {
127
+ value: 1009,
128
+ configurable: true,
129
+ });
130
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
131
+
132
+ const first = renderHook(() => useScrollLock(true));
133
+ const second = renderHook(() => useScrollLock(true));
134
+
135
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
136
+
137
+ first.unmount();
138
+
139
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
140
+
141
+ second.unmount();
142
+
143
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
144
+ });
72
145
  });
@@ -13,9 +13,11 @@
13
13
  *
14
14
  * SYNC: When modified, update:
15
15
  * - /packages/core/src/hooks/index.ts
16
+ * - /packages/core/src/hooks/scrollbarGutter.ts
16
17
  */
17
18
 
18
19
  import {useEffect} from 'react';
20
+ import {holdScrollbarGutter, type ScrollbarGutterHold} from './scrollbarGutter';
19
21
 
20
22
  interface ScrollLockSnapshot {
21
23
  scrollX: number;
@@ -25,6 +27,7 @@ interface ScrollLockSnapshot {
25
27
  top: string;
26
28
  left: string;
27
29
  right: string;
30
+ gutter: ScrollbarGutterHold;
28
31
  }
29
32
 
30
33
  let lockCount = 0;
@@ -38,6 +41,10 @@ let originalBodyState: ScrollLockSnapshot | null = null;
38
41
  * does not prevent body scroll behind modals. Restores scroll position
39
42
  * on unlock.
40
43
  *
44
+ * Pinning also hides the document's scrollbar, so the gutter that scrollbar
45
+ * occupied is held open for the duration of the lock — without it the page
46
+ * reflows sideways by ~15px the moment an overlay opens.
47
+ *
41
48
  * @example
42
49
  * ```
43
50
  * useScrollLock(isOpen);
@@ -55,6 +62,9 @@ export function useScrollLock(isLocked: boolean): void {
55
62
  const scrollX = window.scrollX;
56
63
  const scrollY = window.scrollY;
57
64
 
65
+ // Taken before the pinning styles below hide the scrollbar.
66
+ const gutter = holdScrollbarGutter(body);
67
+
58
68
  originalBodyState = {
59
69
  scrollX,
60
70
  scrollY,
@@ -63,6 +73,7 @@ export function useScrollLock(isLocked: boolean): void {
63
73
  top: body.style.top,
64
74
  left: body.style.left,
65
75
  right: body.style.right,
76
+ gutter,
66
77
  };
67
78
 
68
79
  body.style.overflow = 'hidden';
@@ -70,6 +81,8 @@ export function useScrollLock(isLocked: boolean): void {
70
81
  body.style.top = `-${scrollY}px`;
71
82
  body.style.left = '0';
72
83
  body.style.right = '0';
84
+
85
+ gutter.settle();
73
86
  }
74
87
 
75
88
  lockCount += 1;
@@ -89,6 +102,7 @@ export function useScrollLock(isLocked: boolean): void {
89
102
  body.style.top = state.top;
90
103
  body.style.left = state.left;
91
104
  body.style.right = state.right;
105
+ state.gutter.release();
92
106
  window.scrollTo(state.scrollX, state.scrollY);
93
107
  };
94
108
  }, [isLocked]);
@@ -16,6 +16,8 @@
16
16
  * - /packages/core/src/i18n/InternationalizationProvider.tsx
17
17
  * - /packages/core/src/i18n/t.client.ts
18
18
  * - /packages/core/src/i18n/useDirection.ts
19
+ * - /packages/core/src/i18n/useLocale.ts
20
+ * - /packages/core/src/i18n/useCollator.ts
19
21
  * - /packages/core/src/i18n/getLocaleDirection.ts
20
22
  * - /packages/core/src/i18n/index.ts
21
23
  */
@@ -23,7 +23,11 @@ import {describe, test, expect} from 'vitest';
23
23
  import {render, screen} from '@testing-library/react';
24
24
  import React, {useMemo} from 'react';
25
25
  import {PowerSearch, usePowerSearchConfig} from '../../PowerSearch';
26
- import type {FieldDefinition, PowerSearchFilter} from '../../PowerSearch';
26
+ import type {
27
+ FieldDefinition,
28
+ PowerSearchConfig,
29
+ PowerSearchFilter,
30
+ } from '../../PowerSearch';
27
31
  import {InternationalizationProvider} from '../InternationalizationProvider';
28
32
  import pseudoCatalog from '../../../locales/pseudo.json';
29
33
 
@@ -112,6 +116,38 @@ describe('PowerSearch × i18n — end to end', () => {
112
116
  expect(screen.getByText(/Name: contient/)).toBeInTheDocument();
113
117
  });
114
118
 
119
+ test('date tokens update when the provider locale changes', () => {
120
+ const config: PowerSearchConfig = {
121
+ name: 'Dates',
122
+ fields: [
123
+ {
124
+ key: 'created',
125
+ label: 'Created',
126
+ defaultOperator: 'is',
127
+ operators: [{key: 'is', label: 'is', value: {type: 'date_absolute'}}],
128
+ },
129
+ ],
130
+ };
131
+ const filters: PowerSearchFilter[] = [
132
+ {
133
+ field: 'created',
134
+ operator: 'is',
135
+ value: {type: 'date_absolute', unixSeconds: 1769342400},
136
+ },
137
+ ];
138
+ const search = (locale: string) => (
139
+ <InternationalizationProvider locale={locale}>
140
+ <PowerSearch config={config} filters={filters} onChange={() => {}} />
141
+ </InternationalizationProvider>
142
+ );
143
+
144
+ const {rerender} = render(search('en-US'));
145
+ expect(screen.getByText('Jan 25, 2026')).toBeInTheDocument();
146
+
147
+ rerender(search('de-DE'));
148
+ expect(screen.getByText('25. Jan. 2026')).toBeInTheDocument();
149
+ });
150
+
115
151
  test('resultCount ICU plural swaps under provider overrides', () => {
116
152
  render(
117
153
  <InternationalizationProvider
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useCollator.test.tsx
5
+ * @input packages/core/src/i18n/useCollator.ts, InternationalizationProvider
6
+ * @output Unit tests for the internal provider-aware Intl.Collator hook
7
+ * @position Colocated tests; targets the default (no-provider) fallback,
8
+ * locale-driven comparison order, option threading, and memoization.
9
+ */
10
+
11
+ import type {ReactNode} from 'react';
12
+ import {describe, expect, test} from 'vitest';
13
+ import {render, renderHook} from '@testing-library/react';
14
+ import {useCollator} from '../useCollator';
15
+ import {InternationalizationProvider} from '../InternationalizationProvider';
16
+
17
+ function wrapperFor(locale: string) {
18
+ return ({children}: {children: ReactNode}) => (
19
+ <InternationalizationProvider locale={locale}>
20
+ {children}
21
+ </InternationalizationProvider>
22
+ );
23
+ }
24
+
25
+ describe('useCollator', () => {
26
+ test('compares using the en fallback when rendered without a provider', () => {
27
+ const {result} = renderHook(() => useCollator());
28
+ expect(result.current.compare('a', 'b')).toBeLessThan(0);
29
+ });
30
+
31
+ test('orders "ä" before "z" under Swedish, and after "z" under German', () => {
32
+ const {result: sv} = renderHook(() => useCollator(), {
33
+ wrapper: wrapperFor('sv-SE'),
34
+ });
35
+ expect(['z', 'ä'].sort((a, b) => sv.current.compare(a, b))).toEqual([
36
+ 'z',
37
+ 'ä',
38
+ ]);
39
+
40
+ const {result: de} = renderHook(() => useCollator(), {
41
+ wrapper: wrapperFor('de-DE'),
42
+ });
43
+ expect(['z', 'ä'].sort((a, b) => de.current.compare(a, b))).toEqual([
44
+ 'ä',
45
+ 'z',
46
+ ]);
47
+ });
48
+
49
+ test('threads options through to the underlying Intl.Collator (numeric)', () => {
50
+ const {result} = renderHook(() => useCollator({numeric: true}));
51
+ expect(
52
+ ['item2', 'item10'].sort((a, b) => result.current.compare(a, b)),
53
+ ).toEqual(['item2', 'item10']);
54
+ });
55
+
56
+ test('without numeric, orders "item10" before "item2" lexicographically', () => {
57
+ const {result} = renderHook(() => useCollator());
58
+ expect(
59
+ ['item2', 'item10'].sort((a, b) => result.current.compare(a, b)),
60
+ ).toEqual(['item10', 'item2']);
61
+ });
62
+
63
+ test('memoizes the collator when locale and options identity are unchanged', () => {
64
+ const options = {numeric: true} satisfies Intl.CollatorOptions;
65
+ const {result, rerender} = renderHook(
66
+ ({collatorOptions}) => useCollator(collatorOptions),
67
+ {
68
+ initialProps: {collatorOptions: options},
69
+ wrapper: wrapperFor('en'),
70
+ },
71
+ );
72
+ const first = result.current;
73
+ rerender({collatorOptions: options});
74
+ expect(result.current).toBe(first);
75
+ });
76
+
77
+ test('rebuilds the collator when options identity changes', () => {
78
+ const {result, rerender} = renderHook(
79
+ ({collatorOptions}) => useCollator(collatorOptions),
80
+ {
81
+ initialProps: {
82
+ collatorOptions: {numeric: true} satisfies Intl.CollatorOptions,
83
+ },
84
+ wrapper: wrapperFor('en'),
85
+ },
86
+ );
87
+ const first = result.current;
88
+ rerender({collatorOptions: {numeric: true}});
89
+ expect(result.current).not.toBe(first);
90
+ });
91
+
92
+ test('re-renders with a new collator when the provider locale changes', () => {
93
+ let latestCollator: Intl.Collator | undefined;
94
+ function Probe() {
95
+ latestCollator = useCollator();
96
+ const order = ['z', 'ä'].sort((a, b) => latestCollator!.compare(a, b));
97
+ return <span data-testid="order">{order.join(',')}</span>;
98
+ }
99
+ const {getByTestId, rerender} = render(
100
+ <InternationalizationProvider locale="sv-SE">
101
+ <Probe />
102
+ </InternationalizationProvider>,
103
+ );
104
+ expect(getByTestId('order').textContent).toBe('z,ä');
105
+ const svCollator = latestCollator;
106
+
107
+ rerender(
108
+ <InternationalizationProvider locale="de-DE">
109
+ <Probe />
110
+ </InternationalizationProvider>,
111
+ );
112
+ expect(getByTestId('order').textContent).toBe('ä,z');
113
+ expect(latestCollator).not.toBe(svCollator);
114
+ });
115
+ });
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useLocale.test.tsx
5
+ * @input packages/core/src/i18n/useLocale.ts, InternationalizationProvider
6
+ * @output Unit tests for the internal provider-aware locale accessor
7
+ * @position Colocated tests; targets the default (no-provider) fallback, the
8
+ * provider locale, and re-rendering when the provider locale changes.
9
+ */
10
+
11
+ import type {ReactNode} from 'react';
12
+ import {describe, expect, test} from 'vitest';
13
+ import {render, renderHook} from '@testing-library/react';
14
+ import {useLocale} from '../useLocale';
15
+ import {InternationalizationProvider} from '../InternationalizationProvider';
16
+
17
+ describe('useLocale', () => {
18
+ test('returns en when rendered without a provider', () => {
19
+ const {result} = renderHook(() => useLocale());
20
+ expect(result.current).toBe('en');
21
+ });
22
+
23
+ test('returns the provider locale', () => {
24
+ const {result} = renderHook(() => useLocale(), {
25
+ wrapper: ({children}: {children: ReactNode}) => (
26
+ <InternationalizationProvider locale="fr">
27
+ {children}
28
+ </InternationalizationProvider>
29
+ ),
30
+ });
31
+ expect(result.current).toBe('fr');
32
+ });
33
+
34
+ test('re-renders with the new locale when the provider locale changes', () => {
35
+ function Probe() {
36
+ return <span data-testid="locale">{useLocale()}</span>;
37
+ }
38
+ const {getByTestId, rerender} = render(
39
+ <InternationalizationProvider locale="en">
40
+ <Probe />
41
+ </InternationalizationProvider>,
42
+ );
43
+ expect(getByTestId('locale').textContent).toBe('en');
44
+
45
+ rerender(
46
+ <InternationalizationProvider locale="ja-JP">
47
+ <Probe />
48
+ </InternationalizationProvider>,
49
+ );
50
+ expect(getByTestId('locale').textContent).toBe('ja-JP');
51
+ });
52
+ });
package/src/i18n/index.ts CHANGED
@@ -10,12 +10,16 @@
10
10
  * The public API is deliberately small:
11
11
  * - InternationalizationProvider — provider component
12
12
  * - useTranslator — hook returning a translator function
13
+ * - useLocale — hook returning the provider locale
14
+ * - useCollator — provider-bound locale-aware comparison
13
15
  * - useDirection — hook returning the ambient text direction
14
16
  * - getLocaleDirection — server-safe locale → direction helper
15
17
  * - Translator — interface for consumer-injected runtimes
16
18
  *
17
19
  * SYNC: When modified, update these files to stay in sync:
18
20
  * - /packages/core/src/index.ts
21
+ * - /packages/core/src/i18n/useLocale.ts
22
+ * - /packages/core/src/i18n/useCollator.ts
19
23
  * - /packages/core/src/i18n/useDirection.ts
20
24
  * - /packages/core/src/i18n/getLocaleDirection.ts
21
25
  */
@@ -26,6 +30,8 @@ export {InternationalizationContext} from './InternationalizationContext';
26
30
  export type {InternationalizationContextValue} from './InternationalizationContext';
27
31
  export {useTranslator} from './useTranslator';
28
32
  export type {TranslatorFn} from './useTranslator';
33
+ export {useLocale} from './useLocale';
34
+ export {useCollator} from './useCollator';
29
35
  export {useDirection} from './useDirection';
30
36
  export {getLocaleDirection} from './getLocaleDirection';
31
37
  export type {Translator} from './translator';