@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
@@ -2,7 +2,8 @@
2
2
 
3
3
  /**
4
4
  * @file InputClearButton.test.tsx
5
- * @input Uses vitest, @testing-library/react, InputClearButton, Icon, theme
5
+ * @input Uses node:fs, node:path, vitest, @testing-library/react, InputClearButton, Icon,
6
+ * theme
6
7
  * @output Unit tests for the shared clear-button primitive
7
8
  * @position Testing; validates InputClearButton.tsx — the single home for the
8
9
  * clearable input family's clear (✕) affordance and its theme target.
@@ -10,6 +11,8 @@
10
11
  * SYNC: When InputClearButton.tsx changes, update tests to match new behavior.
11
12
  */
12
13
 
14
+ import {readFileSync} from 'node:fs';
15
+ import path from 'node:path';
13
16
  import {describe, it, expect, vi} from 'vitest';
14
17
  import {render, screen, fireEvent} from '@testing-library/react';
15
18
  import {InputClearButton} from './InputClearButton';
@@ -22,6 +25,39 @@ function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
22
25
  return [prose, component].filter(Boolean).join('\n\n');
23
26
  }
24
27
 
28
+ interface InjectedRule {
29
+ selector: string;
30
+ text: string;
31
+ media: string | null;
32
+ }
33
+
34
+ /**
35
+ * Every style rule StyleX has injected at runtime (`runtimeInjection` in the
36
+ * root vitest config), flattened out of any at-rule wrapper and tagged with
37
+ * that wrapper's condition — so a `@media (pointer: coarse)` rule stays
38
+ * distinguishable from the unconditional one.
39
+ */
40
+ function injectedRules(): InjectedRule[] {
41
+ const walk = (rules: CSSRuleList, condition: string | null) =>
42
+ [...rules].flatMap((rule): InjectedRule[] => {
43
+ const {selectorText} = rule as CSSStyleRule;
44
+ if (typeof selectorText === 'string') {
45
+ return [{selector: selectorText, text: rule.cssText, media: condition}];
46
+ }
47
+ // A grouping rule (@media, @keyframes, ...): descend, carrying the
48
+ // innermost condition down. jsdom hangs an empty `cssRules` off plain
49
+ // style rules too, which is why the leaf check comes first.
50
+ const nested = (rule as CSSGroupingRule).cssRules;
51
+ if (nested == null) {
52
+ return [];
53
+ }
54
+ const own = (rule as CSSMediaRule).media?.mediaText;
55
+ return walk(nested, own != null && own !== '' ? own : condition);
56
+ });
57
+
58
+ return [...document.styleSheets].flatMap(sheet => walk(sheet.cssRules, null));
59
+ }
60
+
25
61
  const getGlyph = (): HTMLElement => {
26
62
  const button = screen.getByRole('button', {name: 'Clear'});
27
63
  const icon = button.querySelector('.astryx-icon');
@@ -111,7 +147,103 @@ describe('InputClearButton', () => {
111
147
  const css = generateThemeTestCSS(theme);
112
148
  expect(css).toContain('.astryx-input-clear-icon {');
113
149
  expect(css).toContain('width: 12px');
114
- expect(css).toContain('.astryx-input-clear-icon:hover {');
150
+ expect(css).toContain('.astryx-input-clear-icon:hover');
115
151
  expect(css).toContain('color: var(--color-icon-primary)');
116
152
  });
153
+
154
+ it('renders the astryx-input-clear-button target on the button wrapper', () => {
155
+ render(<InputClearButton label="Clear" onClick={() => {}} />);
156
+ const button = screen.getByRole('button', {name: 'Clear'});
157
+ expect(button).toHaveClass('astryx-input-clear-button');
158
+ });
159
+
160
+ it('grows the hit area to 24px on a coarse pointer only (WCAG 2.5.8 AA)', () => {
161
+ // The visual glyph stays 20px; an ::after overlay expands only the
162
+ // tappable region, and only under a coarse pointer. Asserted against the
163
+ // CSS StyleX injects at runtime, because jsdom resolves neither media
164
+ // queries nor pseudo-element boxes.
165
+ render(<InputClearButton label="Clear" onClick={() => {}} />);
166
+ const button = screen.getByRole('button', {name: 'Clear'});
167
+ const classes = new Set(button.className.split(' ').filter(Boolean));
168
+
169
+ const css = injectedRules();
170
+ const rulesForButton = css.filter(({selector}) =>
171
+ [...classes].some(c => selector.includes(`.${c}`)),
172
+ );
173
+ // Guards every assertion below against silently passing if StyleX's
174
+ // runtime injection or the class plumbing ever changes shape.
175
+ expect(rulesForButton.length).toBeGreaterThan(0);
176
+
177
+ const decl = (pattern: RegExp, inMedia?: string) =>
178
+ rulesForButton.some(
179
+ ({text, media}) =>
180
+ pattern.test(text) &&
181
+ (inMedia == null ? media == null : (media ?? '').includes(inMedia)),
182
+ );
183
+
184
+ // Fine pointer: hit area == the 20px visual glyph, no expansion.
185
+ expect(decl(/--_input-clear-hit-inset\s*:\s*0px/)).toBe(true);
186
+ // Coarse pointer: 20px + 2px on each side = 24x24, the AA floor.
187
+ expect(decl(/--_input-clear-hit-inset\s*:\s*-2px/, 'pointer: coarse')).toBe(
188
+ true,
189
+ );
190
+ // ...and nothing wider than that, which would reach into the adornment
191
+ // gap and the input's caret area. Scoped to the coarse block, because
192
+ // that is the only place a wider value can appear.
193
+ expect(
194
+ decl(
195
+ /--_input-clear-hit-inset\s*:\s*-(?:[3-9]|\d{2,})px/,
196
+ 'pointer: coarse',
197
+ ),
198
+ ).toBe(false);
199
+ // The overlay exists only on touch. On a fine pointer a generated
200
+ // ::after would cover the button and take hover away from the glyph
201
+ // target, so `content` is gated the same way the inset is.
202
+ expect(decl(/--_input-clear-hit-content\s*:\s*none/)).toBe(true);
203
+ expect(decl(/--_input-clear-hit-content\s*:\s*""/, 'pointer: coarse')).toBe(
204
+ true,
205
+ );
206
+ // The overlay itself is what carries the expansion, and what is gated.
207
+ expect(
208
+ decl(/::after\s*\{[^}]*inset\s*:\s*var\(--_input-clear-hit-inset\)/),
209
+ ).toBe(true);
210
+ expect(
211
+ decl(/::after\s*\{[^}]*content\s*:\s*var\(--_input-clear-hit-content\)/),
212
+ ).toBe(true);
213
+ });
214
+
215
+ it('declares its own containing block for the hit overlay', () => {
216
+ // The ::after overlay must resolve against this button. Button happens to
217
+ // set `position: relative` on itself, so at runtime the overlay is
218
+ // correctly placed either way — and StyleX compiles both declarations to
219
+ // the same atomic class, so the rendered CSS cannot tell the two apart.
220
+ // Assert on the source instead, so a future edit can't quietly leave the
221
+ // overlay depending on another component's internal.
222
+ const source = readFileSync(
223
+ path.resolve(__dirname, './InputClearButton.tsx'),
224
+ 'utf-8',
225
+ );
226
+ const buttonStyle = source.match(/button:\s*\{[\s\S]*?\n {2}\},/)?.[0];
227
+ expect(buttonStyle).toBeDefined();
228
+ expect(buttonStyle).toMatch(/position:\s*'relative'/);
229
+ });
230
+
231
+ it('exposes input-clear-button so a theme controls the button size and hover', () => {
232
+ const theme = defineTheme({
233
+ name: 'input-clear-button-test',
234
+ components: {
235
+ 'input-clear-button': {
236
+ base: {
237
+ height: '28px',
238
+ ':hover': {backgroundImage: 'none'},
239
+ },
240
+ },
241
+ },
242
+ });
243
+ const css = generateThemeTestCSS(theme);
244
+ expect(css).toContain('.astryx-input-clear-button {');
245
+ expect(css).toContain('height: 28px');
246
+ expect(css).toContain('.astryx-input-clear-button:hover');
247
+ expect(css).toContain('background-image: none');
248
+ });
117
249
  });
@@ -11,7 +11,8 @@
11
11
  * TextInput, NumberInput, TimeInput, DateInput, DateTimeInput,
12
12
  * DateRangeInput, Selector, MultiSelector, Typeahead, Tokenizer, FileInput —
13
13
  * routes it through here, so the glyph is themed in one place via the
14
- * `astryx-input-clear-icon` target.
14
+ * `astryx-input-clear-icon` target and the button wrapper is themed via the
15
+ * `astryx-input-clear-button` target.
15
16
  */
16
17
 
17
18
  import type {ReactNode} from 'react';
@@ -24,6 +25,44 @@ const styles = stylex.create({
24
25
  button: {
25
26
  height: '20px',
26
27
  flexShrink: 0,
28
+ // Containing block for the ::after hit overlay below. Button sets its own
29
+ // `position: relative`, so the overlay would resolve correctly without
30
+ // this — but that is another component's internal, and if it ever changes
31
+ // the overlay silently reattaches to some ancestor and the hit area lands
32
+ // in the wrong place. Declaring it here keeps the containing block
33
+ // colocated with the thing that depends on it. No-op at runtime.
34
+ position: 'relative',
35
+ // Expand the tap target to 24px ONLY on touch (WCAG 2.5.8 AA is a touch
36
+ // requirement, and its floor is 24x24). On a fine pointer the 20px glyph
37
+ // is precise enough, and an unconditional overlay could overlap
38
+ // neighboring controls in dense layouts. The inset is 0 by default (hit
39
+ // area == visual glyph) and grows to -2px (=> 24x24) under a coarse
40
+ // pointer — no further, because at -4px the overlay reaches into the 8px
41
+ // adornment gap and, on the inline-start side, over the input's own caret
42
+ // area. Driven through a custom property because StyleX only allows plain
43
+ // values inside a pseudo-element; the conditional lives on this top-level
44
+ // property instead. Private (--_) because it is an internal implementation
45
+ // detail, not a themeable target.
46
+ '--_input-clear-hit-inset': {
47
+ default: '0px',
48
+ '@media (pointer: coarse)': '-2px',
49
+ },
50
+ // The overlay itself is gated too, not just its size. An ::after that is
51
+ // generated on a fine pointer sits exactly over the button at inset 0,
52
+ // where it adds no hit area but is the topmost hit-test box — so hover
53
+ // stops reaching the descendants and `.astryx-input-clear-icon:hover`, a
54
+ // public theme target, no longer matches. `content: none` means the
55
+ // pseudo-element is not generated at all, so on a fine pointer the
56
+ // overlay does not exist.
57
+ '--_input-clear-hit-content': {
58
+ default: 'none',
59
+ '@media (pointer: coarse)': '""',
60
+ },
61
+ '::after': {
62
+ content: 'var(--_input-clear-hit-content)',
63
+ position: 'absolute',
64
+ inset: 'var(--_input-clear-hit-inset)',
65
+ },
27
66
  },
28
67
  });
29
68
 
@@ -48,11 +87,13 @@ export function InputClearButton({
48
87
  iconClassName,
49
88
  }: InputClearButtonProps): ReactNode {
50
89
  const {className: iconTargetClassName} = themeProps('input-clear-icon');
90
+ const {className: buttonTargetClassName} = themeProps('input-clear-button');
51
91
  return (
52
92
  <Button
53
93
  variant="ghost"
54
94
  size="sm"
55
95
  label={label}
96
+ className={buttonTargetClassName}
56
97
  icon={
57
98
  <Icon
58
99
  icon="close"
@@ -56,9 +56,10 @@ export const inputWrapperStyles = stylex.create({
56
56
  transitionTimingFunction: easeVars['--ease-standard'],
57
57
  boxShadow: {
58
58
  default: 'none',
59
- ':hover:not(:focus-within)': {
60
- '@media (hover: hover)': `inset 0px 0px 0px 2px color-mix(in srgb, ${colorVars['--color-border-emphasized']} 30%, transparent)`,
61
- },
59
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
60
+ {
61
+ '@media (hover: hover)': `inset 0px 0px 0px 2px color-mix(in srgb, ${colorVars['--color-border-emphasized']} 30%, transparent)`,
62
+ },
62
63
  ':focus-within': `inset 0px 0px 0px 2px ${colorVars['--color-accent-muted']}`,
63
64
  },
64
65
  outline: 'none',
@@ -96,25 +97,28 @@ export const inputStatusHoverShadowStyles = stylex.create({
96
97
  warning: {
97
98
  boxShadow: {
98
99
  default: 'none',
99
- ':hover:not(:focus-within)': {
100
- '@media (hover: hover)': shadowVars['--shadow-inset-warning'],
101
- },
100
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
101
+ {
102
+ '@media (hover: hover)': shadowVars['--shadow-inset-warning'],
103
+ },
102
104
  },
103
105
  },
104
106
  error: {
105
107
  boxShadow: {
106
108
  default: 'none',
107
- ':hover:not(:focus-within)': {
108
- '@media (hover: hover)': shadowVars['--shadow-inset-error'],
109
- },
109
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
110
+ {
111
+ '@media (hover: hover)': shadowVars['--shadow-inset-error'],
112
+ },
110
113
  },
111
114
  },
112
115
  success: {
113
116
  boxShadow: {
114
117
  default: 'none',
115
- ':hover:not(:focus-within)': {
116
- '@media (hover: hover)': shadowVars['--shadow-inset-success'],
117
- },
118
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
119
+ {
120
+ '@media (hover: hover)': shadowVars['--shadow-inset-success'],
121
+ },
118
122
  },
119
123
  },
120
124
  });
@@ -158,7 +158,7 @@ const styles = stylex.create({
158
158
  dropzoneHover: {
159
159
  boxShadow: {
160
160
  default: null,
161
- ':hover': {
161
+ ':hover:where(:not(:disabled,[aria-disabled="true"]))': {
162
162
  '@media (hover: hover)': `inset 0 0 0 2px color-mix(in srgb, ${colorVars['--color-accent']} 20%, transparent)`,
163
163
  },
164
164
  },
@@ -197,9 +197,10 @@ const styles = stylex.create({
197
197
  transitionTimingFunction: easeVars['--ease-standard'],
198
198
  boxShadow: {
199
199
  default: 'none',
200
- ':hover:not(:focus-within)': {
201
- '@media (hover: hover)': `inset 0 0 0 2px color-mix(in srgb, ${colorVars['--color-accent']} 20%, transparent)`,
202
- },
200
+ ':hover:not(:focus-within):where(:not(:disabled,[aria-disabled="true"]))':
201
+ {
202
+ '@media (hover: hover)': `inset 0 0 0 2px color-mix(in srgb, ${colorVars['--color-accent']} 20%, transparent)`,
203
+ },
203
204
  },
204
205
  cursor: 'pointer',
205
206
  height: sizeVars['--size-element-md'],
@@ -16,6 +16,12 @@ export const docs = {
16
16
  'Controls field arrangement. Vertical stacks top-to-bottom, horizontal arranges left-to-right with equal flex-grow, and horizontal-labels uses CSS Grid with labels to the left of inputs (collapses to vertical on narrow viewports <=480px).',
17
17
  default: "'vertical'",
18
18
  },
19
+ {
20
+ name: 'defaultOptionality',
21
+ type: "'optional' | 'required'",
22
+ description:
23
+ 'The state the form treats as its default, so only the exception shows an optional/required indicator. With "optional", only fields marked isRequired show an indicator; with "required", only fields marked isOptional do. A field that restates the default shows nothing. Under "required" the unmarked fields also expose aria-required so screen readers match the visual default; aria-required only, never the native required attribute. Leave unset for today\'s per-field behavior.',
24
+ },
19
25
  {
20
26
  name: 'children',
21
27
  type: 'ReactNode',
@@ -64,6 +70,12 @@ export const docsZh = {
64
70
  '控制字段排列方式。vertical 从上到下堆叠,horizontal 从左到右排列且等比弹性增长,horizontal-labels 使用 CSS Grid 将标签放在输入框左侧(在窄视口 <=480px 时折叠为垂直布局)。',
65
71
  default: "'vertical'",
66
72
  },
73
+ {
74
+ name: 'defaultOptionality',
75
+ type: "'optional' | 'required'",
76
+ description:
77
+ '表单视为默认的状态,因此仅例外字段显示可选/必填指示器。设为 "optional" 时,仅标记 isRequired 的字段显示指示器;设为 "required" 时,仅标记 isOptional 的字段显示。与默认一致的字段不显示任何内容。设为 "required" 时,未标记的字段仍会暴露 aria-required,使屏幕阅读器与视觉默认一致——仅作用于 aria-required,不改变原生 required 属性。不设置则保持当前逐字段行为。',
78
+ },
67
79
  {
68
80
  name: 'children',
69
81
  type: 'ReactNode',
@@ -122,6 +134,7 @@ export const docsDense = {
122
134
  },
123
135
  propDescriptions: {
124
136
  direction: 'Field arrangement. Vertical stacks top-to-bottom, horizontal arranges left-to-right w/ equal flex-grow, horizontal-labels uses CSS Grid w/ labels left of inputs (collapses <=480px).',
137
+ defaultOptionality: 'State the form treats as default, so only the exception is marked. "optional" → only isRequired fields show an indicator; "required" → only isOptional fields do. Under "required" unmarked fields also expose aria-required (aria only, not native required). Unset = per-field behavior.',
125
138
  children: 'Form fields to arrange. Accepts Astryx inputs + Field-wrapped custom controls.',
126
139
  xstyle: 'StyleX styles for layout customization. Must be stylex.create() value.',
127
140
  },
@@ -16,6 +16,8 @@ import {FormLayout} from './FormLayout';
16
16
  import {FormLayoutContext} from './FormLayoutContext';
17
17
  import type {FormLayoutDirection} from './FormLayoutContext';
18
18
  import {Field} from '../Field';
19
+ import {TextInput} from '../TextInput';
20
+ import {CheckboxInput} from '../CheckboxInput';
19
21
 
20
22
  // Helper component to read context
21
23
  function DirectionReader() {
@@ -23,6 +25,12 @@ function DirectionReader() {
23
25
  return <span data-testid="direction">{direction}</span>;
24
26
  }
25
27
 
28
+ // Helper component to read the defaultOptionality context value
29
+ function OptionalityReader() {
30
+ const {defaultOptionality} = use(FormLayoutContext);
31
+ return <span data-testid="optionality">{defaultOptionality ?? 'unset'}</span>;
32
+ }
33
+
26
34
  describe('FormLayout', () => {
27
35
  // ─── Basic rendering ────────────────────────────────────────────────────
28
36
 
@@ -154,6 +162,178 @@ describe('FormLayout', () => {
154
162
  expect(screen.getByTestId('inner-child-2')).toBeInTheDocument();
155
163
  });
156
164
 
165
+ // ─── defaultOptionality context propagation ─────────────────────────────
166
+
167
+ it('leaves defaultOptionality unset by default', () => {
168
+ render(
169
+ <FormLayout>
170
+ <OptionalityReader />
171
+ </FormLayout>,
172
+ );
173
+ expect(screen.getByTestId('optionality')).toHaveTextContent('unset');
174
+ });
175
+
176
+ it('provides defaultOptionality="optional" to children', () => {
177
+ render(
178
+ <FormLayout defaultOptionality="optional">
179
+ <OptionalityReader />
180
+ </FormLayout>,
181
+ );
182
+ expect(screen.getByTestId('optionality')).toHaveTextContent('optional');
183
+ });
184
+
185
+ it('provides defaultOptionality="required" to children', () => {
186
+ render(
187
+ <FormLayout defaultOptionality="required">
188
+ <OptionalityReader />
189
+ </FormLayout>,
190
+ );
191
+ expect(screen.getByTestId('optionality')).toHaveTextContent('required');
192
+ });
193
+
194
+ it('an inner layout shadows the outer defaultOptionality', () => {
195
+ render(
196
+ <FormLayout defaultOptionality="optional">
197
+ <FormLayout defaultOptionality="required">
198
+ <OptionalityReader />
199
+ </FormLayout>
200
+ </FormLayout>,
201
+ );
202
+ expect(screen.getByTestId('optionality')).toHaveTextContent('required');
203
+ });
204
+
205
+ // ─── defaultOptionality indicator behavior (through Field) ───────────────
206
+ //
207
+ // The rule: only the *exception* is marked. A field that restates the form
208
+ // default shows nothing; a deviation shows its indicator.
209
+
210
+ it('optional default: only isRequired fields show an indicator', () => {
211
+ render(
212
+ <FormLayout defaultOptionality="optional">
213
+ <Field label="Bio" inputID="bio">
214
+ <input id="bio" />
215
+ </Field>
216
+ <Field label="Nickname" inputID="nick" isOptional>
217
+ <input id="nick" />
218
+ </Field>
219
+ <Field label="Email" inputID="email" isRequired>
220
+ <input id="email" />
221
+ </Field>
222
+ </FormLayout>,
223
+ );
224
+ // Plain + isOptional match the default → nothing shown.
225
+ expect(screen.queryByText(/Optional/)).not.toBeInTheDocument();
226
+ // isRequired deviates → the required indicator shows.
227
+ expect(screen.getByText(/Required/)).toBeInTheDocument();
228
+ });
229
+
230
+ it('required default: only isOptional fields show an indicator', () => {
231
+ render(
232
+ <FormLayout defaultOptionality="required">
233
+ <Field label="Name" inputID="name">
234
+ <input id="name" />
235
+ </Field>
236
+ <Field label="Email" inputID="email" isRequired>
237
+ <input id="email" />
238
+ </Field>
239
+ <Field label="Nickname" inputID="nick" isOptional>
240
+ <input id="nick" />
241
+ </Field>
242
+ </FormLayout>,
243
+ );
244
+ // Plain + isRequired match the default → nothing shown.
245
+ expect(screen.queryByText(/Required/)).not.toBeInTheDocument();
246
+ // isOptional deviates → the optional indicator shows.
247
+ expect(screen.getByText(/Optional/)).toBeInTheDocument();
248
+ });
249
+
250
+ it('unset default preserves per-field indicators (backwards compatible)', () => {
251
+ render(
252
+ <FormLayout>
253
+ <Field label="Email" inputID="email" isRequired>
254
+ <input id="email" />
255
+ </Field>
256
+ <Field label="Nickname" inputID="nick" isOptional>
257
+ <input id="nick" />
258
+ </Field>
259
+ </FormLayout>,
260
+ );
261
+ expect(screen.getByText(/Required/)).toBeInTheDocument();
262
+ expect(screen.getByText(/Optional/)).toBeInTheDocument();
263
+ });
264
+
265
+ // ─── defaultOptionality aria-required resolution ─────────────────────────
266
+ //
267
+ // The indicator is suppressed for the unmarked majority, so the matching
268
+ // `aria-required` must still be exposed — otherwise a sighted user reads a
269
+ // field as required (form default, no indicator) while a screen reader hears
270
+ // "not required". Native `required` stays bound to the explicit prop so a
271
+ // layout default never switches on browser validation.
272
+
273
+ it('required default: an unmarked input still exposes aria-required', () => {
274
+ render(
275
+ <FormLayout defaultOptionality="required">
276
+ <TextInput label="Name" value="" onChange={() => {}} />
277
+ </FormLayout>,
278
+ );
279
+ expect(screen.getByLabelText('Name')).toHaveAttribute(
280
+ 'aria-required',
281
+ 'true',
282
+ );
283
+ });
284
+
285
+ it('required default: an isOptional input is not aria-required', () => {
286
+ render(
287
+ <FormLayout defaultOptionality="required">
288
+ <TextInput label="Nickname" value="" onChange={() => {}} isOptional />
289
+ </FormLayout>,
290
+ );
291
+ expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-required');
292
+ });
293
+
294
+ it('optional default: an unmarked input is not aria-required', () => {
295
+ render(
296
+ <FormLayout defaultOptionality="optional">
297
+ <TextInput label="Bio" value="" onChange={() => {}} />
298
+ </FormLayout>,
299
+ );
300
+ expect(screen.getByLabelText('Bio')).not.toHaveAttribute('aria-required');
301
+ });
302
+
303
+ it('no layout: an unmarked input is not aria-required (backwards compatible)', () => {
304
+ render(<TextInput label="Solo" value="" onChange={() => {}} />);
305
+ expect(screen.getByLabelText('Solo')).not.toHaveAttribute('aria-required');
306
+ });
307
+
308
+ it('required default resolves aria-required without native required', () => {
309
+ render(
310
+ <FormLayout defaultOptionality="required">
311
+ <CheckboxInput label="Terms" value={false} onChange={() => {}} />
312
+ </FormLayout>,
313
+ );
314
+ const checkbox = screen.getByRole('checkbox', {name: 'Terms'});
315
+ // Announced as required (form default)…
316
+ expect(checkbox).toHaveAttribute('aria-required', 'true');
317
+ // …but the native `required` attribute is not switched on by the layout.
318
+ expect(checkbox).not.toHaveAttribute('required');
319
+ });
320
+
321
+ it('explicit isRequired still drives native required under a layout', () => {
322
+ render(
323
+ <FormLayout defaultOptionality="required">
324
+ <CheckboxInput
325
+ label="Consent"
326
+ value={false}
327
+ onChange={() => {}}
328
+ isRequired
329
+ />
330
+ </FormLayout>,
331
+ );
332
+ const checkbox = screen.getByRole('checkbox', {name: 'Consent'});
333
+ expect(checkbox).toHaveAttribute('aria-required', 'true');
334
+ expect(checkbox).toHaveAttribute('required');
335
+ });
336
+
157
337
  // ─── Snapshot tests ─────────────────────────────────────────────────────
158
338
 
159
339
  it('matches snapshot for vertical direction', () => {
@@ -223,10 +403,7 @@ describe('FormLayout', () => {
223
403
  it('horizontal-labels with Field: label and input wrapper are siblings under display:contents', () => {
224
404
  render(
225
405
  <FormLayout direction="horizontal-labels" data-testid="layout">
226
- <Field
227
- label="Username"
228
- inputID="username"
229
- data-testid="username-field">
406
+ <Field label="Username" inputID="username" data-testid="username-field">
230
407
  <input id="username" data-testid="username-input" />
231
408
  </Field>
232
409
  </FormLayout>,
@@ -10,7 +10,10 @@
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
12
12
  * - /packages/core/src/FormLayout/FormLayout.test.tsx (tests for new/changed behavior)
13
+ * - /packages/core/src/FormLayout/FormLayoutContext.ts (context value shape)
14
+ * - /packages/core/src/FormLayout/FormLayout.doc.mjs (props table)
13
15
  * - /packages/core/src/FormLayout/index.ts (exports if types change)
16
+ * - /packages/core/src/Field/FieldLabel.tsx (defaultOptionality indicator resolution)
14
17
  * - /apps/storybook/stories/FormLayout.stories.tsx (storybook stories)
15
18
  * - /packages/cli/assets/templates/blocks/components/FormLayout/ (showcase blocks)
16
19
  */
@@ -19,7 +22,11 @@ import {useMemo, type ReactNode} from 'react';
19
22
  import type {BaseProps} from '../BaseProps';
20
23
  import * as stylex from '@stylexjs/stylex';
21
24
  import {spacingVars} from '../theme/tokens.stylex';
22
- import {FormLayoutContext, type FormLayoutDirection} from './FormLayoutContext';
25
+ import {
26
+ FormLayoutContext,
27
+ type FormLayoutDirection,
28
+ type FormOptionality,
29
+ } from './FormLayoutContext';
23
30
  import {mergeProps} from '../utils';
24
31
  import {themeProps} from '../utils/themeProps';
25
32
 
@@ -82,6 +89,26 @@ export interface FormLayoutProps extends BaseProps<HTMLDivElement> {
82
89
  * @default 'vertical'
83
90
  */
84
91
  direction?: FormLayoutDirection;
92
+
93
+ /**
94
+ * Which state the form treats as its default, so only the *exception*
95
+ * carries a visible optional/required indicator. It also resolves each
96
+ * field's `aria-required` so the unmarked majority is still announced
97
+ * correctly — but only `aria-required`, never the native `required`
98
+ * attribute, so a layout default can't switch on browser validation.
99
+ *
100
+ * - `'optional'` — fields read as optional; only a field with `isRequired`
101
+ * shows an indicator (the "required" one).
102
+ * - `'required'` — fields read as required; only a field with `isOptional`
103
+ * shows an indicator (the "optional" one). Fields without `isOptional`
104
+ * expose `aria-required` even though they show no indicator.
105
+ * - unset — today's behavior: `isRequired` and `isOptional` each show their
106
+ * own indicator independently.
107
+ *
108
+ * A field that merely restates the default (e.g. `isOptional` under
109
+ * `'optional'`) shows nothing. An inner `FormLayout` shadows an outer one.
110
+ */
111
+ defaultOptionality?: FormOptionality;
85
112
  }
86
113
 
87
114
  // =============================================================================
@@ -109,13 +136,17 @@ export interface FormLayoutProps extends BaseProps<HTMLDivElement> {
109
136
  export function FormLayout({
110
137
  children,
111
138
  direction = 'vertical',
139
+ defaultOptionality,
112
140
  xstyle,
113
141
  className,
114
142
  style,
115
143
  ref,
116
144
  ...props
117
145
  }: FormLayoutProps) {
118
- const contextValue = useMemo(() => ({direction}), [direction]);
146
+ const contextValue = useMemo(
147
+ () => ({direction, defaultOptionality}),
148
+ [direction, defaultOptionality],
149
+ );
119
150
 
120
151
  return (
121
152
  <FormLayoutContext value={contextValue}>
@@ -5,10 +5,13 @@
5
5
  /**
6
6
  * @file FormLayoutContext.ts
7
7
  * @input Uses React createContext
8
- * @output Exports FormLayoutContext and FormLayoutDirection type
9
- * @position Context for form layout direction detection
8
+ * @output Exports FormLayoutContext, FormLayoutDirection, and FormOptionality types
9
+ * @position Context for form layout direction + default-optionality detection
10
10
  *
11
11
  * SYNC: When modified, update these files to stay in sync:
12
+ * - /packages/core/src/FormLayout/FormLayout.tsx (prop + context value)
13
+ * - /packages/core/src/Field/FieldLabel.tsx (indicator resolution)
14
+ * - /packages/core/src/FormLayout/index.ts (exports if types change)
12
15
  */
13
16
 
14
17
  import {createContext} from 'react';
@@ -22,15 +25,27 @@ import {createContext} from 'react';
22
25
  * of their inputs (settings/admin panel pattern).
23
26
  */
24
27
  export type FormLayoutDirection =
25
- | 'vertical'
26
- | 'horizontal'
27
- | 'horizontal-labels';
28
+ 'vertical' | 'horizontal' | 'horizontal-labels';
28
29
 
29
30
  /**
30
- * Context for detecting which form layout direction a component is rendered in.
31
- * Children can use this to adapt their rendering based on the parent layout.
31
+ * Which state a form treats as its default, so only the *exception* carries a
32
+ * visible optional/required indicator.
33
+ *
34
+ * - `'optional'` — fields are optional unless a field opts into `isRequired`;
35
+ * only required fields show an indicator.
36
+ * - `'required'` — fields are required unless a field opts into `isOptional`;
37
+ * only optional fields show an indicator.
38
+ */
39
+ export type FormOptionality = 'optional' | 'required';
40
+
41
+ /**
42
+ * Context for detecting which form layout a component is rendered in. Children
43
+ * can use this to adapt their rendering based on the parent layout — direction
44
+ * for spatial arrangement, and `defaultOptionality` so a field can suppress the
45
+ * indicator that merely restates the form-wide default.
32
46
  */
33
47
  export const FormLayoutContext = createContext<{
34
48
  direction: FormLayoutDirection;
49
+ defaultOptionality?: FormOptionality;
35
50
  }>({direction: 'vertical'});
36
51
  FormLayoutContext.displayName = 'FormLayoutContext';