@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
@@ -53,6 +53,9 @@ import {
53
53
  parseMarkdownIncremental,
54
54
  createIncrementalState,
55
55
  trimStreamingArtifacts,
56
+ inlineText,
57
+ slugify,
58
+ uniqueSlug,
56
59
  } from './parser';
57
60
  import type {BlockNode, InlineNode, IncrementalState} from './parser';
58
61
  import {themeProps} from '../utils/themeProps';
@@ -108,6 +111,14 @@ export interface MarkdownComponents {
108
111
  heading?: React.ComponentType<{
109
112
  level: 1 | 2 | 3 | 4 | 5 | 6;
110
113
  children: React.ReactNode;
114
+ /**
115
+ * Generated slug for this heading, matching the ids produced by
116
+ * useOutlineFromMarkdown / parseOutlineFromMarkdown. Render it as the
117
+ * element's `id` to keep Outline hash navigation working. Undefined for
118
+ * headings nested inside blockquotes or list items (the outline only
119
+ * lists top-level headings).
120
+ */
121
+ id?: string;
111
122
  }>;
112
123
  paragraph?: React.ComponentType<{children: React.ReactNode}>;
113
124
  image?: React.ComponentType<{src: string; alt: string}>;
@@ -1045,6 +1056,7 @@ function renderBlock(
1045
1056
  inlinePlugins: MarkdownInlinePlugin[] | undefined,
1046
1057
  components: Partial<MarkdownComponents> | undefined,
1047
1058
  t: TranslatorFn,
1059
+ headingIdMap?: ReadonlyMap<BlockNode, string>,
1048
1060
  ): SyncReactNode {
1049
1061
  const blockAlignMargin = BLOCK_ALIGN_MARGIN[contentAlign];
1050
1062
  const blockAlignStyle =
@@ -1071,10 +1083,15 @@ function renderBlock(
1071
1083
  components,
1072
1084
  ),
1073
1085
  );
1086
+ // Only top-level headings get an id: the map is built from the same
1087
+ // traversal parseOutlineFromMarkdown uses (which skips headings nested
1088
+ // in blockquotes / list items), so rendered ids and outline ids stay
1089
+ // identical — including duplicate-slug numbering.
1090
+ const headingId = headingIdMap?.get(node);
1074
1091
  const HeadingComp = components?.heading;
1075
1092
  if (HeadingComp) {
1076
1093
  return (
1077
- <HeadingComp key={index} level={level}>
1094
+ <HeadingComp key={index} level={level} id={headingId}>
1078
1095
  {headingChildren}
1079
1096
  </HeadingComp>
1080
1097
  );
@@ -1083,6 +1100,7 @@ function renderBlock(
1083
1100
  return (
1084
1101
  <Tag
1085
1102
  key={index}
1103
+ id={headingId}
1086
1104
  {...mergeProps(
1087
1105
  themeProps('markdown-heading', {density, level}),
1088
1106
  stylex.props(
@@ -1652,6 +1670,27 @@ export function Markdown({
1652
1670
  return parseMarkdown(children, parseOptions);
1653
1671
  }, [display, smoothedText, children, isStreaming, parseOptions]);
1654
1672
 
1673
+ // Assign each top-level heading the slug that parseOutlineFromMarkdown
1674
+ // would derive for it, so Outline hash links built from the same source
1675
+ // always find a matching DOM id. Mirrors that function's traversal exactly:
1676
+ // top-level blocks only, one shared duplicate-numbering sequence.
1677
+ // NOTE: must stay above the `display === 'inline'` early return below —
1678
+ // hooks cannot be conditional.
1679
+ const headingIdMap = useMemo(() => {
1680
+ if (display === 'inline' || blocks.length === 0) {
1681
+ return undefined;
1682
+ }
1683
+ const map = new Map<BlockNode, string>();
1684
+ const counts = new Map<string, number>();
1685
+ for (const block of blocks) {
1686
+ if (block.type === 'heading') {
1687
+ const label = inlineText(block.children).trim();
1688
+ map.set(block, uniqueSlug(slugify(label), counts));
1689
+ }
1690
+ }
1691
+ return map;
1692
+ }, [display, blocks]);
1693
+
1655
1694
  const inlineNodes = useMemo(() => {
1656
1695
  if (display !== 'inline') {
1657
1696
  return [];
@@ -1765,6 +1804,7 @@ export function Markdown({
1765
1804
  inlinePlugins,
1766
1805
  components,
1767
1806
  t,
1807
+ headingIdMap,
1768
1808
  ),
1769
1809
  )}
1770
1810
  </div>
@@ -3,8 +3,10 @@
3
3
  /**
4
4
  * @file parser.ts
5
5
  * @input Markdown string
6
- * @output Array of MarkdownNode AST nodes
7
- * @position Core parser; consumed by Markdown.tsx
6
+ * @output Array of MarkdownNode AST nodes; heading slug helpers
7
+ * (inlineText, slugify, uniqueSlug) shared by Markdown rendering and
8
+ * Outline's parseOutlineFromMarkdown
9
+ * @position Core parser; consumed by Markdown.tsx and Outline
8
10
  */
9
11
 
10
12
  // ---------------------------------------------------------------------------
@@ -1815,3 +1817,59 @@ export function parseMarkdownIncremental(
1815
1817
 
1816
1818
  return mergeSettledBlocks(settledBlocks, unsettledBlocks);
1817
1819
  }
1820
+
1821
+ // ---------------------------------------------------------------------------
1822
+ // Heading slugs
1823
+ // ---------------------------------------------------------------------------
1824
+ // Single source of truth for the heading id contract: Markdown renders these
1825
+ // slugs as `id` attributes on h1–h6, and Outline's parseOutlineFromMarkdown
1826
+ // derives its item ids from the same functions, so outline hash links always
1827
+ // resolve to a rendered heading by construction.
1828
+
1829
+ /** Flatten inline nodes into their plain text content. */
1830
+ export function inlineText(nodes: InlineNode[]): string {
1831
+ return nodes
1832
+ .map(node => {
1833
+ switch (node.type) {
1834
+ case 'text':
1835
+ case 'code':
1836
+ return node.content;
1837
+ case 'bold':
1838
+ case 'italic':
1839
+ case 'strikethrough':
1840
+ case 'link':
1841
+ return inlineText(node.children);
1842
+ case 'image':
1843
+ return node.alt;
1844
+ case 'citation':
1845
+ case 'break':
1846
+ return '';
1847
+ }
1848
+ })
1849
+ .join('');
1850
+ }
1851
+
1852
+ /** Turn heading text into a URL-safe slug (lowercase, hyphen-separated). */
1853
+ export function slugify(value: string): string {
1854
+ return value
1855
+ .trim()
1856
+ .toLowerCase()
1857
+ .replace(/['"]/g, '')
1858
+ .replace(/[^a-z0-9]+/g, '-')
1859
+ .replace(/^-+|-+$/g, '');
1860
+ }
1861
+
1862
+ /**
1863
+ * Disambiguate repeated slugs with a numeric suffix (`setup`, `setup-1`, …).
1864
+ * Empty slugs fall back to `section`. The caller owns the counts map so one
1865
+ * document shares a single numbering sequence.
1866
+ */
1867
+ export function uniqueSlug(
1868
+ baseSlug: string,
1869
+ counts: Map<string, number>,
1870
+ ): string {
1871
+ const fallbackSlug = baseSlug || 'section';
1872
+ const count = counts.get(fallbackSlug) ?? 0;
1873
+ counts.set(fallbackSlug, count + 1);
1874
+ return count === 0 ? fallbackSlug : `${fallbackSlug}-${count}`;
1875
+ }
@@ -23,6 +23,7 @@
23
23
  *
24
24
  * SYNC: When modified, update these files to stay in sync:
25
25
  * - /packages/core/src/MobileNav/index.ts (exports if types change)
26
+ * - /packages/core/src/hooks/scrollbarGutter.ts (shared scroll-lock gutter)
26
27
  * - /packages/cli/assets/templates/blocks/components/MobileNav/ (showcase blocks)
27
28
  */
28
29
 
@@ -47,7 +48,12 @@ import {Button} from '../Button';
47
48
  import {Icon} from '../Icon';
48
49
  import {Heading} from '../Heading/Heading';
49
50
  import {useAppShellMobile} from '../AppShell/AppShellMobileContext';
51
+ import {
52
+ holdScrollbarGutter,
53
+ type ScrollbarGutterHold,
54
+ } from '../hooks/scrollbarGutter';
50
55
  import {mergeProps, mergeRefs, composeEventHandlers} from '../utils';
56
+ import {overlayPaddingReset} from '../Layout/padding.stylex';
51
57
  import type {BaseProps} from '../BaseProps';
52
58
  import {themeProps} from '../utils/themeProps';
53
59
  import {useTranslator} from '../i18n';
@@ -70,7 +76,16 @@ const styles = stylex.create({
70
76
  width: '100vw',
71
77
  height: '100dvh',
72
78
  backgroundColor: 'transparent',
73
- overflow: 'hidden',
79
+ // `clip`, not `hidden`. Both clip the off-screen drawer, but `hidden` makes
80
+ // the dialog a SCROLL CONTAINER, and a scroll container in the top layer
81
+ // whose subtree holds another scroller (the drawer's content area) does not
82
+ // paint a @starting-style entry transition for its descendants in Chromium:
83
+ // the transition ticks in the CSSOM while every painted frame shows the
84
+ // end value, so the drawer appears fully open. `clip` clips without
85
+ // creating a scroll container and the slide-in paints normally. The dialog
86
+ // never scrolls anyway — its child is absolutely positioned — so nothing
87
+ // depended on it being a scroll container.
88
+ overflow: 'clip',
74
89
  overscrollBehavior: 'contain',
75
90
  // Prevent touch gestures (pull-to-refresh, background scroll) passing through
76
91
  touchAction: 'none',
@@ -112,7 +127,14 @@ const styles = stylex.create({
112
127
  },
113
128
  backdropOpen: {
114
129
  '::backdrop': {
115
- opacity: 1,
130
+ // The ::backdrop only exists once showModal() has put the dialog in the
131
+ // top layer, so its first rendered frame already has the open opacity.
132
+ // Without a starting style there is no earlier value to transition from
133
+ // and the scrim snaps in — @starting-style supplies that value.
134
+ opacity: {
135
+ default: 1,
136
+ '@starting-style': 0,
137
+ },
116
138
  },
117
139
  },
118
140
  drawer: {
@@ -143,7 +165,17 @@ const styles = stylex.create({
143
165
  },
144
166
  },
145
167
  drawerStartOpen: {
146
- transform: 'translateX(0)',
168
+ // The whole dialog is `display: none` while closed, so the drawer is not
169
+ // rendered and the open transform is the only value it has ever had — a
170
+ // transition needs a previous value to run from. @starting-style gives the
171
+ // first rendered frame the off-screen transform, so the slide-in plays.
172
+ transform: {
173
+ default: 'translateX(0)',
174
+ '@starting-style': {
175
+ default: 'translateX(-100%)',
176
+ ':is([dir="rtl"] *)': 'translateX(100%)',
177
+ },
178
+ },
147
179
  },
148
180
  drawerEnd: {
149
181
  insetInlineEnd: 0,
@@ -156,7 +188,14 @@ const styles = stylex.create({
156
188
  },
157
189
  },
158
190
  drawerEndOpen: {
159
- transform: 'translateX(0)',
191
+ // See drawerStartOpen — same starting style, mirrored edge.
192
+ transform: {
193
+ default: 'translateX(0)',
194
+ '@starting-style': {
195
+ default: 'translateX(100%)',
196
+ ':is([dir="rtl"] *)': 'translateX(-100%)',
197
+ },
198
+ },
160
199
  },
161
200
  header: {
162
201
  display: 'flex',
@@ -396,6 +435,15 @@ export function MobileNav({
396
435
 
397
436
  const dialogRef = useRef<HTMLDialogElement>(null);
398
437
  const closeTimeoutRef = useRef<ReturnType<typeof setTimeout>>(null);
438
+ const gutterRef = useRef<ScrollbarGutterHold | null>(null);
439
+
440
+ // Gives back the gutter held open in place of the hidden scrollbar.
441
+ const releaseGutter = useCallback(() => {
442
+ if (gutterRef.current) {
443
+ gutterRef.current.release();
444
+ gutterRef.current = null;
445
+ }
446
+ }, []);
399
447
  // Resolved side — computed from trigger position when side='auto'
400
448
  const [resolvedSide, setResolvedSide] = useState<'start' | 'end'>(
401
449
  side === 'auto' ? 'end' : side,
@@ -438,6 +486,10 @@ export function MobileNav({
438
486
  }
439
487
 
440
488
  if (isOpen) {
489
+ // Taken first: every mutation below is one that can hide the scrollbar,
490
+ // and the gutter has to be measured while it is still there.
491
+ gutterRef.current ??= holdScrollbarGutter(document.documentElement);
492
+
441
493
  if (!dialog.open) {
442
494
  dialog.showModal();
443
495
  }
@@ -445,8 +497,10 @@ export function MobileNav({
445
497
  // overflow: clip avoids creating a scroll container (unlike hidden),
446
498
  // so there's no scroll bounce and no need to save/restore scroll position.
447
499
  document.documentElement.style.overflow = 'clip';
500
+ gutterRef.current.settle();
448
501
  } else if (dialog.open) {
449
502
  document.documentElement.style.overflow = '';
503
+ releaseGutter();
450
504
 
451
505
  closeTimeoutRef.current = setTimeout(() => {
452
506
  dialog.close();
@@ -459,8 +513,9 @@ export function MobileNav({
459
513
  closeTimeoutRef.current = null;
460
514
  }
461
515
  document.documentElement.style.overflow = '';
516
+ releaseGutter();
462
517
  };
463
- }, [isOpen]);
518
+ }, [isOpen, releaseGutter]);
464
519
 
465
520
  // Close the native dialog on unmount if it's still open. Inside AppShell the
466
521
  // drawer is mounted in an <Activity> that switches to mode="hidden" when the
@@ -512,6 +567,7 @@ export function MobileNav({
512
567
  themeProps('mobile-nav', {side: resolvedSide}),
513
568
  stylex.props(
514
569
  styles.dialog,
570
+ overlayPaddingReset.reset,
515
571
  isOpen && styles.open,
516
572
  styles.backdrop,
517
573
  isOpen && styles.backdropOpen,
@@ -0,0 +1,212 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MobileNavEntryAnimation.test.tsx
5
+ * @input Uses vitest, @testing-library/react, the TypeScript compiler API
6
+ * @output Regression tests keeping the drawer's slide-in and scrim fade-in
7
+ * @position Testing; guards the open path of MobileNav.tsx
8
+ *
9
+ * The drawer used to open with no animation at all while closing smoothly.
10
+ *
11
+ * The dialog is `display: none` until `isOpen`, so nothing inside it is
12
+ * rendered while closed. React commits the open `display` and the open
13
+ * `transform` in the same pass, which means the first frame the drawer is ever
14
+ * rendered in already holds the open transform: a transition has no earlier
15
+ * value to run from and the drawer simply appears. Closing looked fine because
16
+ * both values exist by then — `display` stays in the transition with
17
+ * `allow-discrete`, so the element is still rendered as the transform animates
18
+ * out.
19
+ *
20
+ * `@starting-style` supplies that before-change style — the off-screen
21
+ * transform for the drawer, transparent for the `::backdrop`.
22
+ *
23
+ * That alone is NOT enough, and the second half is the part that is easy to
24
+ * undo by accident. The dialog used `overflow: hidden`, which makes it a
25
+ * SCROLL CONTAINER. A scroll container in the top layer whose subtree holds
26
+ * another scroller — here the drawer's own content area — does not paint a
27
+ * `@starting-style` entry transition for its descendants in Chromium: the
28
+ * transition ticks in the CSSOM (`getComputedStyle` interpolates perfectly)
29
+ * while every painted frame shows the end value. Measuring the CSSOM says
30
+ * "animating"; the screen says "snapped". `overflow: clip` clips the
31
+ * off-screen drawer exactly as `hidden` did without creating a scroll
32
+ * container, and the slide-in paints.
33
+ *
34
+ * Reproduced minimally: dialog `overflow: hidden` + a scrolling child inside
35
+ * the drawer snaps; either one alone animates.
36
+ *
37
+ * Note on scope: jsdom has no top layer, no transitions, no `@starting-style`
38
+ * evaluation and no compositor, so none of this is observable here. These
39
+ * tests pin the three declarations the behaviour rests on. Verified in
40
+ * Chromium by screencasting painted frames (not computed style) at both edges,
41
+ * LTR and RTL: open and close each paint ~60-100 distinct intermediate
42
+ * positions, and reduced motion collapses to a 10ms transition.
43
+ */
44
+
45
+ import {describe, it, expect, beforeAll} from 'vitest';
46
+ import {render, screen} from '@testing-library/react';
47
+ import {readFileSync} from 'node:fs';
48
+ import {join} from 'node:path';
49
+ import ts from 'typescript';
50
+ import {MobileNav} from './MobileNav';
51
+
52
+ // jsdom doesn't implement showModal/close on <dialog>, so we mock them
53
+ beforeAll(() => {
54
+ HTMLDialogElement.prototype.showModal =
55
+ HTMLDialogElement.prototype.showModal ||
56
+ function (this: HTMLDialogElement) {
57
+ this.setAttribute('open', '');
58
+ };
59
+ HTMLDialogElement.prototype.close =
60
+ HTMLDialogElement.prototype.close ||
61
+ function (this: HTMLDialogElement) {
62
+ this.removeAttribute('open');
63
+ };
64
+ });
65
+
66
+ /** Every CSS rule StyleX has injected into the document, as text. */
67
+ function injectedRules(): string[] {
68
+ const rules: string[] = [];
69
+ for (const sheet of Array.from(document.styleSheets)) {
70
+ let cssRules: CSSRuleList;
71
+ try {
72
+ cssRules = sheet.cssRules;
73
+ } catch {
74
+ continue;
75
+ }
76
+ for (const rule of Array.from(cssRules)) {
77
+ rules.push(rule.cssText);
78
+ }
79
+ }
80
+ return rules;
81
+ }
82
+
83
+ describe('MobileNav scrim fades in', () => {
84
+ it('gives the ::backdrop a transparent starting style', () => {
85
+ render(
86
+ <MobileNav isOpen onOpenChange={() => {}} data-testid="mobile-nav">
87
+ <span>Nav content</span>
88
+ </MobileNav>,
89
+ );
90
+
91
+ const classes = Array.from(screen.getByTestId('mobile-nav').classList);
92
+ // Without this the scrim is opaque in the frame the dialog enters the top
93
+ // layer, so it has nothing to fade from and snaps in behind the drawer.
94
+ const startingStyleForThisDialog = injectedRules().filter(
95
+ rule =>
96
+ rule.startsWith('@starting-style') &&
97
+ rule.includes('::backdrop') &&
98
+ classes.some(cls => rule.includes(`.${cls}`)),
99
+ );
100
+
101
+ expect(startingStyleForThisDialog).toHaveLength(1);
102
+ expect(startingStyleForThisDialog[0]).toMatch(/opacity:\s*0/);
103
+ });
104
+ });
105
+
106
+ // =============================================================================
107
+ // Drawer slide-in — asserted on the style definition
108
+ // =============================================================================
109
+
110
+ const SOURCE = readFileSync(join(__dirname, 'MobileNav.tsx'), 'utf8');
111
+
112
+ /**
113
+ * The object literal a `styles.<name>` key is defined with, e.g. the value of
114
+ * `drawerStartOpen` inside the file's `stylex.create({...})` call.
115
+ */
116
+ function styleDefinition(name: string): ts.ObjectLiteralExpression {
117
+ const file = ts.createSourceFile(
118
+ 'MobileNav.tsx',
119
+ SOURCE,
120
+ ts.ScriptTarget.Latest,
121
+ true,
122
+ ts.ScriptKind.TSX,
123
+ );
124
+
125
+ let found: ts.ObjectLiteralExpression | undefined;
126
+ const visit = (node: ts.Node) => {
127
+ if (
128
+ ts.isPropertyAssignment(node) &&
129
+ ts.isIdentifier(node.name) &&
130
+ node.name.text === name &&
131
+ ts.isObjectLiteralExpression(node.initializer)
132
+ ) {
133
+ found = node.initializer;
134
+ }
135
+ ts.forEachChild(node, visit);
136
+ };
137
+ visit(file);
138
+
139
+ if (!found) {
140
+ throw new Error(`No style named "${name}" in MobileNav.tsx`);
141
+ }
142
+ return found;
143
+ }
144
+
145
+ /** The value of one property of a style definition, as source text. */
146
+ function property(style: ts.ObjectLiteralExpression, key: string): string {
147
+ const prop = style.properties.find(
148
+ p =>
149
+ ts.isPropertyAssignment(p) &&
150
+ (ts.isIdentifier(p.name) || ts.isStringLiteral(p.name)) &&
151
+ p.name.text === key,
152
+ );
153
+
154
+ if (!prop) {
155
+ throw new Error(`No "${key}" in ${style.getText().slice(0, 40)}…`);
156
+ }
157
+ return (prop as ts.PropertyAssignment).initializer.getText();
158
+ }
159
+
160
+ describe('MobileNav dialog does not become a scroll container', () => {
161
+ it('clips the off-screen drawer with `clip`, not `hidden`', () => {
162
+ // This is the half of the fix with no visible declaration of its own
163
+ // purpose: `hidden` looks like a pure clipping choice and clips exactly as
164
+ // well, so it is an easy "harmless tidy-up" to make. It is not harmless —
165
+ // it makes the dialog a scroll container, and the entry animation then
166
+ // ticks in the CSSOM without ever painting. See the file header.
167
+ expect(property(styleDefinition('dialog'), 'overflow')).toBe("'clip'");
168
+ });
169
+ });
170
+
171
+ describe.each([
172
+ {
173
+ name: 'drawerStartOpen',
174
+ offscreen: 'translateX(-100%)',
175
+ offscreenRtl: 'translateX(100%)',
176
+ },
177
+ {
178
+ name: 'drawerEndOpen',
179
+ offscreen: 'translateX(100%)',
180
+ offscreenRtl: 'translateX(-100%)',
181
+ },
182
+ ])('MobileNav drawer slides in ($name)', ({name, offscreen, offscreenRtl}) => {
183
+ it('opens to the on-screen transform', () => {
184
+ expect(property(styleDefinition(name), 'transform')).toContain(
185
+ 'translateX(0)',
186
+ );
187
+ });
188
+
189
+ it('starts off-screen so the open transform has something to run from', () => {
190
+ const transform = property(styleDefinition(name), 'transform');
191
+
192
+ // The whole point: the first rendered frame needs the closed transform.
193
+ // Drop this and the drawer is simply there, fully open, on frame one —
194
+ // while the close still animates, which is what made the bug look like a
195
+ // missing entry animation rather than a missing starting style.
196
+ expect(transform).toContain('@starting-style');
197
+ expect(transform).toContain(offscreen);
198
+ // Mirrored, like the closed styles it has to match: sliding in from the
199
+ // wrong edge in RTL is as broken as not sliding at all.
200
+ expect(transform).toContain(offscreenRtl);
201
+ });
202
+
203
+ it('starts from the same edge the closed style parks it at', () => {
204
+ const closed = property(
205
+ styleDefinition(name.replace('Open', '')),
206
+ 'transform',
207
+ );
208
+
209
+ expect(closed).toContain(offscreen);
210
+ expect(closed).toContain(offscreenRtl);
211
+ });
212
+ });
@@ -0,0 +1,109 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MobileNavScrollbarGutter.test.tsx
5
+ * @input Uses vitest, @testing-library/react, MobileNav, a stubbed viewport
6
+ * @output Unit tests for the scrollbar gutter MobileNav reserves while open
7
+ * @position Testing; guards MobileNav.tsx against the open-drawer page shift
8
+ *
9
+ * MobileNav clips the document to lock background scroll, which hides a
10
+ * classic scrollbar and widens the layout viewport by its width — the page
11
+ * behind the drawer jumps sideways. The clip has to be paired with holding
12
+ * that gutter open.
13
+ *
14
+ * SYNC: When MobileNav.tsx changes, update tests to match new behavior
15
+ */
16
+
17
+ import {describe, it, expect, beforeAll, afterEach} from 'vitest';
18
+ import {render, cleanup} from '@testing-library/react';
19
+ import {MobileNav} from './MobileNav';
20
+
21
+ beforeAll(() => {
22
+ // jsdom doesn't implement showModal/close on <dialog>
23
+ HTMLDialogElement.prototype.showModal =
24
+ HTMLDialogElement.prototype.showModal ||
25
+ function (this: HTMLDialogElement) {
26
+ this.setAttribute('open', '');
27
+ };
28
+ HTMLDialogElement.prototype.close =
29
+ HTMLDialogElement.prototype.close ||
30
+ function (this: HTMLDialogElement) {
31
+ this.removeAttribute('open');
32
+ };
33
+ });
34
+
35
+ /** jsdom does no layout, so both halves of the measurement are stubbed. */
36
+ function stubViewport(innerWidth: number, clientWidth: number) {
37
+ Object.defineProperty(window, 'innerWidth', {
38
+ value: innerWidth,
39
+ configurable: true,
40
+ writable: true,
41
+ });
42
+ Object.defineProperty(document.documentElement, 'clientWidth', {
43
+ value: clientWidth,
44
+ configurable: true,
45
+ });
46
+ }
47
+
48
+ const rootGutter = () => document.documentElement.style.scrollbarGutter;
49
+
50
+ describe('MobileNav scrollbar gutter', () => {
51
+ afterEach(() => {
52
+ cleanup();
53
+ document.documentElement.style.cssText = '';
54
+ // @ts-expect-error -- drop the viewport stub so jsdom's own value comes back
55
+ delete document.documentElement.clientWidth;
56
+ });
57
+
58
+ it('holds the gutter open while the drawer is open', () => {
59
+ stubViewport(1024, 1009);
60
+
61
+ const view = render(
62
+ <MobileNav isOpen={true} onOpenChange={() => {}}>
63
+ <span>Nav content</span>
64
+ </MobileNav>,
65
+ );
66
+
67
+ expect(document.documentElement.style.overflow).toBe('clip');
68
+ expect(rootGutter()).toBe('stable');
69
+
70
+ view.unmount();
71
+
72
+ expect(document.documentElement.style.overflow).toBe('');
73
+ expect(rootGutter()).toBe('');
74
+ });
75
+
76
+ it('gives the gutter back when the drawer closes', () => {
77
+ stubViewport(1024, 1009);
78
+
79
+ const view = render(
80
+ <MobileNav isOpen={true} onOpenChange={() => {}}>
81
+ <span>Nav content</span>
82
+ </MobileNav>,
83
+ );
84
+
85
+ expect(rootGutter()).toBe('stable');
86
+
87
+ view.rerender(
88
+ <MobileNav isOpen={false} onOpenChange={() => {}}>
89
+ <span>Nav content</span>
90
+ </MobileNav>,
91
+ );
92
+
93
+ expect(rootGutter()).toBe('');
94
+ });
95
+
96
+ it('holds nothing when the scrollbar is an overlay one', () => {
97
+ stubViewport(1024, 1024);
98
+
99
+ render(
100
+ <MobileNav isOpen={true} onOpenChange={() => {}}>
101
+ <span>Nav content</span>
102
+ </MobileNav>,
103
+ );
104
+
105
+ expect(document.documentElement.style.overflow).toBe('clip');
106
+ expect(rootGutter()).toBe('');
107
+ expect(document.documentElement.style.paddingRight).toBe('');
108
+ });
109
+ });
@@ -1790,7 +1790,7 @@ describe('MultiSelector clear icon theme target', () => {
1790
1790
  expect(css).toContain('.astryx-multi-selector-clear-icon {');
1791
1791
  expect(css).toContain('width: 12px');
1792
1792
  expect(css).toContain('height: 12px');
1793
- expect(css).toContain('.astryx-multi-selector-clear-icon:hover {');
1793
+ expect(css).toContain('.astryx-multi-selector-clear-icon:hover');
1794
1794
  expect(css).toContain('color: var(--color-icon-primary)');
1795
1795
  });
1796
1796
  });
@@ -73,6 +73,7 @@ import {
73
73
  import {useMultiCombobox} from './hooks';
74
74
  import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
75
75
  import {useAnnounce} from '../hooks/useAnnounce';
76
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
76
77
  import type {BaseProps} from '../BaseProps';
77
78
  import type {SizeValue} from '../utils/types';
78
79
  import {useSize} from '../SizeContext/SizeContext';
@@ -189,16 +190,17 @@ const styles = stylex.create({
189
190
  backgroundColor: 'transparent',
190
191
  backgroundImage: {
191
192
  default: null,
192
- ':hover': {
193
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
193
194
  '@media (hover: hover)': `linear-gradient(${colorVars['--color-overlay-hover']}, ${colorVars['--color-overlay-hover']})`,
194
195
  },
195
196
  ':active': `linear-gradient(${colorVars['--color-overlay-pressed']}, ${colorVars['--color-overlay-pressed']})`,
196
197
  },
197
198
  boxShadow: {
198
199
  default: 'none',
199
- ':hover:not(:focus-within)': {
200
- '@media (hover: hover)': 'none',
201
- },
200
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
201
+ {
202
+ '@media (hover: hover)': 'none',
203
+ },
202
204
  ':focus-within': 'none',
203
205
  },
204
206
  fontWeight: fontWeightVars['--font-weight-medium'],
@@ -706,6 +708,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
706
708
  style,
707
709
  }: MultiSelectorProps<T>) {
708
710
  const t = useTranslator();
711
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
709
712
  const placeholder =
710
713
  placeholderFromProps ?? t('@astryx.multiSelector.selectPlaceholder');
711
714
  const selectAllLabel =
@@ -1526,7 +1529,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
1526
1529
  }
1527
1530
  aria-describedby={ariaDescribedBy}
1528
1531
  aria-labelledby={ariaLabelledBy}
1529
- aria-required={isRequired ? 'true' : undefined}
1532
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
1530
1533
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
1531
1534
  aria-busy={isBusy || undefined}
1532
1535
  // With a disabledMessage the trigger keeps focusability via