@astryxdesign/core 0.1.9 → 0.2.0-canary.136a00f

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 (560) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/Avatar/Avatar.d.ts +42 -3
  3. package/dist/Avatar/Avatar.d.ts.map +1 -1
  4. package/dist/Avatar/Avatar.js +190 -59
  5. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  6. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  7. package/dist/AvatarGroup/AvatarGroup.d.ts +7 -1
  8. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  9. package/dist/AvatarGroup/AvatarGroup.js +58 -6
  10. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  11. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  12. package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
  13. package/dist/Banner/Banner.js +2 -2
  14. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  15. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  16. package/dist/Breadcrumbs/index.d.ts +1 -0
  17. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  18. package/dist/Breadcrumbs/index.js +2 -1
  19. package/dist/Button/Button.d.ts +1 -1
  20. package/dist/Button/Button.d.ts.map +1 -1
  21. package/dist/Button/Button.js +6 -4
  22. package/dist/Calendar/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar/Calendar.js +50 -9
  24. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  25. package/dist/Calendar/dayCellUtils.js +2 -2
  26. package/dist/Calendar/styles.d.ts +30 -31
  27. package/dist/Calendar/styles.d.ts.map +1 -1
  28. package/dist/Calendar/styles.js +30 -31
  29. package/dist/Card/Card.js +1 -1
  30. package/dist/Carousel/Carousel.d.ts.map +1 -1
  31. package/dist/Carousel/Carousel.js +22 -12
  32. package/dist/Chat/ChatComposerInput.js +1 -1
  33. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  34. package/dist/Chat/ChatDictationButton.js +3 -1
  35. package/dist/Chat/ChatLayout.js +4 -4
  36. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  37. package/dist/Chat/ChatMessageBubble.js +8 -8
  38. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  39. package/dist/Chat/ChatToolCalls.js +10 -3
  40. package/dist/Chat/useTriggerMenu.js +2 -2
  41. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  42. package/dist/CheckboxInput/CheckboxInput.js +6 -6
  43. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  44. package/dist/CheckboxList/CheckboxListItem.js +19 -4
  45. package/dist/Citation/Citation.d.ts +22 -1
  46. package/dist/Citation/Citation.d.ts.map +1 -1
  47. package/dist/Citation/Citation.js +20 -6
  48. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  49. package/dist/CodeBlock/CodeBlock.js +16 -10
  50. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  51. package/dist/Collapsible/Collapsible.js +3 -1
  52. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  53. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  54. package/dist/CommandPalette/CommandPalette.js +42 -3
  55. package/dist/CommandPalette/CommandPaletteItem.js +1 -1
  56. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  57. package/dist/ContextMenu/ContextMenu.js +23 -5
  58. package/dist/ContextMenu/index.d.ts +1 -0
  59. package/dist/ContextMenu/index.d.ts.map +1 -1
  60. package/dist/ContextMenu/index.js +3 -1
  61. package/dist/DateInput/DateInput.d.ts +10 -2
  62. package/dist/DateInput/DateInput.d.ts.map +1 -1
  63. package/dist/DateInput/DateInput.js +33 -19
  64. package/dist/DateRangeInput/DateRangeInput.d.ts +10 -2
  65. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  66. package/dist/DateRangeInput/DateRangeInput.js +23 -20
  67. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  68. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  69. package/dist/Divider/Divider.d.ts +2 -2
  70. package/dist/Divider/Divider.d.ts.map +1 -1
  71. package/dist/Divider/Divider.js +13 -0
  72. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  73. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  74. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  75. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  76. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
  77. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenuItem.js +5 -2
  79. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +8 -11
  80. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -1
  81. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +3 -1
  82. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +1 -1
  83. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuRadioItem.js +5 -2
  85. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  86. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  87. package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
  88. package/dist/DropdownMenu/index.d.ts +10 -0
  89. package/dist/DropdownMenu/index.d.ts.map +1 -1
  90. package/dist/DropdownMenu/index.js +13 -0
  91. package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
  92. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  93. package/dist/DropdownMenu/menuItemHover.js +33 -0
  94. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  95. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  96. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  97. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  98. package/dist/DropdownMenu/renderDropdownItems.js +21 -6
  99. package/dist/Field/Field.d.ts +3 -1
  100. package/dist/Field/Field.d.ts.map +1 -1
  101. package/dist/Field/Field.js +4 -1
  102. package/dist/Field/FieldLabel.d.ts +2 -1
  103. package/dist/Field/FieldLabel.d.ts.map +1 -1
  104. package/dist/Field/FieldLabel.js +9 -5
  105. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  106. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  107. package/dist/Field/inputStyles.stylex.js +1 -0
  108. package/dist/FieldStatus/FieldStatus.d.ts +10 -0
  109. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  110. package/dist/FieldStatus/FieldStatus.js +39 -3
  111. package/dist/FileInput/FileInput.d.ts +10 -2
  112. package/dist/FileInput/FileInput.d.ts.map +1 -1
  113. package/dist/FileInput/FileInput.js +93 -65
  114. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  115. package/dist/HoverCard/useHoverCard.js +10 -4
  116. package/dist/Icon/Icon.d.ts +15 -1
  117. package/dist/Icon/Icon.d.ts.map +1 -1
  118. package/dist/Icon/Icon.js +19 -3
  119. package/dist/Item/Item.d.ts +16 -3
  120. package/dist/Item/Item.d.ts.map +1 -1
  121. package/dist/Item/Item.js +31 -6
  122. package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
  123. package/dist/Layer/layerAnimations.stylex.js +10 -2
  124. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  125. package/dist/Lightbox/Lightbox.js +19 -13
  126. package/dist/List/List.d.ts.map +1 -1
  127. package/dist/List/List.js +6 -3
  128. package/dist/List/ListItem.d.ts +11 -1
  129. package/dist/List/ListItem.d.ts.map +1 -1
  130. package/dist/List/ListItem.js +2 -0
  131. package/dist/Markdown/Markdown.d.ts.map +1 -1
  132. package/dist/Markdown/Markdown.js +7 -0
  133. package/dist/MetadataList/MetadataList.d.ts +1 -1
  134. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  135. package/dist/MetadataList/MetadataList.js +4 -2
  136. package/dist/MultiSelector/MultiSelector.d.ts +9 -1
  137. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  138. package/dist/MultiSelector/MultiSelector.js +80 -42
  139. package/dist/NavMenu/NavHeadingMenuItem.js +1 -1
  140. package/dist/NumberInput/NumberInput.d.ts +10 -2
  141. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  142. package/dist/NumberInput/NumberInput.js +22 -17
  143. package/dist/Outline/Outline.js +2 -2
  144. package/dist/Pagination/Pagination.d.ts.map +1 -1
  145. package/dist/Pagination/Pagination.js +11 -3
  146. package/dist/PowerSearch/PowerSearch.d.ts +9 -2
  147. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  148. package/dist/PowerSearch/PowerSearch.js +2 -0
  149. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  150. package/dist/ProgressBar/ProgressBar.js +6 -1
  151. package/dist/RadioList/RadioList.d.ts +2 -2
  152. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  153. package/dist/RadioList/RadioListItem.js +5 -5
  154. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  155. package/dist/Resizable/ResizeHandle.js +49 -19
  156. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  157. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  158. package/dist/SegmentedControl/SegmentedControlItem.js +5 -3
  159. package/dist/Selector/Selector.d.ts +8 -0
  160. package/dist/Selector/Selector.d.ts.map +1 -1
  161. package/dist/Selector/Selector.js +66 -17
  162. package/dist/SideNav/SideNav.d.ts.map +1 -1
  163. package/dist/SideNav/SideNav.js +5 -5
  164. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  165. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  166. package/dist/SideNav/SideNavItem.d.ts +1 -1
  167. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  168. package/dist/SideNav/SideNavItem.js +12 -10
  169. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  170. package/dist/Skeleton/Skeleton.js +2 -2
  171. package/dist/Slider/Slider.d.ts.map +1 -1
  172. package/dist/Slider/Slider.js +87 -34
  173. package/dist/Spinner/Spinner.d.ts +3 -2
  174. package/dist/Spinner/Spinner.d.ts.map +1 -1
  175. package/dist/Spinner/Spinner.js +11 -2
  176. package/dist/Switch/Switch.d.ts +8 -1
  177. package/dist/Switch/Switch.d.ts.map +1 -1
  178. package/dist/Switch/Switch.js +171 -91
  179. package/dist/TabList/Tab.js +2 -2
  180. package/dist/TabList/TabList.d.ts +2 -11
  181. package/dist/TabList/TabList.d.ts.map +1 -1
  182. package/dist/TabList/TabList.js +5 -9
  183. package/dist/TabList/TabListContext.d.ts +0 -5
  184. package/dist/TabList/TabListContext.d.ts.map +1 -1
  185. package/dist/TabList/TabListContext.js +0 -5
  186. package/dist/TabList/TabMenu.js +1 -1
  187. package/dist/Table/BaseTable.d.ts.map +1 -1
  188. package/dist/Table/BaseTable.js +30 -4
  189. package/dist/Table/TableCell.js +3 -3
  190. package/dist/Table/TableHeaderCell.js +3 -3
  191. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  192. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  193. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  194. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +37 -27
  195. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  196. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  197. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  198. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +15 -7
  199. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  200. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  201. package/dist/Table/types.d.ts +32 -0
  202. package/dist/Table/types.d.ts.map +1 -1
  203. package/dist/TextArea/TextArea.d.ts +10 -1
  204. package/dist/TextArea/TextArea.d.ts.map +1 -1
  205. package/dist/TextArea/TextArea.js +18 -19
  206. package/dist/TextInput/TextInput.d.ts +10 -2
  207. package/dist/TextInput/TextInput.d.ts.map +1 -1
  208. package/dist/TextInput/TextInput.js +20 -17
  209. package/dist/Thumbnail/Thumbnail.js +2 -2
  210. package/dist/TimeInput/TimeInput.d.ts +10 -2
  211. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  212. package/dist/TimeInput/TimeInput.js +17 -17
  213. package/dist/Timestamp/Timestamp.d.ts +31 -3
  214. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  215. package/dist/Timestamp/Timestamp.js +47 -76
  216. package/dist/Timestamp/formatInstant.d.ts +41 -0
  217. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  218. package/dist/Timestamp/formatInstant.js +177 -0
  219. package/dist/Timestamp/index.d.ts +1 -0
  220. package/dist/Timestamp/index.d.ts.map +1 -1
  221. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  222. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  223. package/dist/Timestamp/tooltipEntries.js +163 -0
  224. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  225. package/dist/Toast/ToastViewport.js +61 -9
  226. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  227. package/dist/ToggleButton/ToggleButton.js +3 -1
  228. package/dist/Token/Token.d.ts +34 -4
  229. package/dist/Token/Token.d.ts.map +1 -1
  230. package/dist/Token/Token.js +85 -45
  231. package/dist/Token/TokenLink.d.ts +62 -0
  232. package/dist/Token/TokenLink.d.ts.map +1 -0
  233. package/dist/Token/TokenLink.js +76 -0
  234. package/dist/Token/index.d.ts +1 -1
  235. package/dist/Token/index.d.ts.map +1 -1
  236. package/dist/Tokenizer/Tokenizer.d.ts +9 -2
  237. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  238. package/dist/Tokenizer/Tokenizer.js +4 -2
  239. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  240. package/dist/Tooltip/useTooltip.js +17 -1
  241. package/dist/TopNav/TopNavItem.d.ts +3 -1
  242. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  243. package/dist/TopNav/TopNavItem.js +30 -3
  244. package/dist/TreeList/TreeList.d.ts.map +1 -1
  245. package/dist/TreeList/TreeList.js +1 -0
  246. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  247. package/dist/TreeList/TreeListBranches.js +8 -6
  248. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  249. package/dist/TreeList/TreeListItem.js +32 -14
  250. package/dist/Typeahead/BaseTypeahead.d.ts +9 -0
  251. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  252. package/dist/Typeahead/BaseTypeahead.js +22 -11
  253. package/dist/Typeahead/Typeahead.d.ts +9 -2
  254. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  255. package/dist/Typeahead/Typeahead.js +11 -2
  256. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  257. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  258. package/dist/Typeahead/TypeaheadItem.js +20 -10
  259. package/dist/astryx.css +70 -30
  260. package/dist/astryx.umd.js +51 -51
  261. package/dist/astryx.umd.js.map +4 -4
  262. package/dist/hooks/index.d.ts +2 -0
  263. package/dist/hooks/index.d.ts.map +1 -1
  264. package/dist/hooks/index.js +1 -0
  265. package/dist/hooks/isRtlElement.d.ts +18 -0
  266. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  267. package/dist/hooks/isRtlElement.js +27 -0
  268. package/dist/hooks/useAnnounce.d.ts +3 -0
  269. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  270. package/dist/hooks/useAnnounce.js +39 -1
  271. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  272. package/dist/hooks/useFocusTrap.js +52 -11
  273. package/dist/hooks/useGridFocus.d.ts +6 -1
  274. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  275. package/dist/hooks/useGridFocus.js +12 -9
  276. package/dist/hooks/useHotkeys.d.ts +9 -0
  277. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  278. package/dist/hooks/useHotkeys.js +9 -0
  279. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  280. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  281. package/dist/hooks/useInputStatusIcon.js +158 -0
  282. package/dist/hooks/useKeyboardHint.d.ts +1 -1
  283. package/dist/hooks/useKeyboardHint.d.ts.map +1 -1
  284. package/dist/hooks/useKeyboardHint.js +4 -2
  285. package/dist/hooks/useListFocus.d.ts +39 -1
  286. package/dist/hooks/useListFocus.d.ts.map +1 -1
  287. package/dist/hooks/useListFocus.js +54 -9
  288. package/dist/i18n/InternationalizationContext.d.ts +1 -0
  289. package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
  290. package/dist/i18n/InternationalizationContext.js +3 -0
  291. package/dist/i18n/InternationalizationProvider.d.ts +8 -1
  292. package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
  293. package/dist/i18n/InternationalizationProvider.js +5 -1
  294. package/dist/i18n/getLocaleDirection.d.ts +18 -0
  295. package/dist/i18n/getLocaleDirection.d.ts.map +1 -0
  296. package/dist/i18n/getLocaleDirection.js +29 -0
  297. package/dist/i18n/index.d.ts +6 -0
  298. package/dist/i18n/index.d.ts.map +1 -1
  299. package/dist/i18n/index.js +7 -1
  300. package/dist/i18n/useDirection.d.ts +2 -0
  301. package/dist/i18n/useDirection.d.ts.map +1 -0
  302. package/dist/i18n/useDirection.js +27 -0
  303. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  304. package/dist/theme/derivedVarRegistry.js +13 -0
  305. package/dist/utils/index.d.ts +1 -0
  306. package/dist/utils/index.d.ts.map +1 -1
  307. package/dist/utils/index.js +2 -1
  308. package/dist/utils/plainDate.d.ts +17 -0
  309. package/dist/utils/plainDate.d.ts.map +1 -1
  310. package/dist/utils/plainDate.js +23 -4
  311. package/dist/utils/rtlStyles.d.ts +24 -0
  312. package/dist/utils/rtlStyles.d.ts.map +1 -0
  313. package/dist/utils/rtlStyles.js +27 -0
  314. package/locales/en.json +112 -0
  315. package/locales/pseudo.json +84 -0
  316. package/package.json +6 -1
  317. package/src/Avatar/Avatar.doc.mjs +50 -3
  318. package/src/Avatar/Avatar.test.tsx +218 -0
  319. package/src/Avatar/Avatar.tsx +285 -79
  320. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  321. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  322. package/src/AvatarGroup/AvatarGroup.doc.mjs +2 -0
  323. package/src/AvatarGroup/AvatarGroup.test.tsx +162 -2
  324. package/src/AvatarGroup/AvatarGroup.tsx +58 -3
  325. package/src/AvatarGroup/AvatarGroupOverflow.tsx +5 -2
  326. package/src/Banner/Banner.tsx +6 -6
  327. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  328. package/src/Breadcrumbs/Breadcrumbs.test.tsx +52 -1
  329. package/src/Breadcrumbs/index.ts +4 -0
  330. package/src/Button/Button.doc.mjs +0 -4
  331. package/src/Button/Button.test.tsx +16 -0
  332. package/src/Button/Button.tsx +6 -4
  333. package/src/Calendar/Calendar.test.tsx +170 -20
  334. package/src/Calendar/Calendar.tsx +73 -16
  335. package/src/Calendar/dayCellUtils.test.ts +18 -18
  336. package/src/Calendar/dayCellUtils.ts +2 -2
  337. package/src/Calendar/styles.ts +33 -39
  338. package/src/Card/Card.tsx +1 -1
  339. package/src/Carousel/Carousel.tsx +25 -6
  340. package/src/Chat/ChatComposer.doc.mjs +2 -2
  341. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  342. package/src/Chat/ChatComposerInput.tsx +2 -2
  343. package/src/Chat/ChatDictationButton.tsx +7 -1
  344. package/src/Chat/ChatLayout.tsx +4 -4
  345. package/src/Chat/ChatMessageBubble.tsx +15 -10
  346. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  347. package/src/Chat/ChatToolCalls.tsx +9 -2
  348. package/src/Chat/useTriggerMenu.tsx +1 -1
  349. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  350. package/src/CheckboxInput/CheckboxInput.test.tsx +23 -0
  351. package/src/CheckboxInput/CheckboxInput.tsx +21 -6
  352. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  353. package/src/CheckboxList/CheckboxList.test.tsx +94 -3
  354. package/src/CheckboxList/CheckboxListItem.tsx +17 -10
  355. package/src/Citation/Citation.doc.mjs +4 -3
  356. package/src/Citation/Citation.test.tsx +97 -0
  357. package/src/Citation/Citation.tsx +43 -11
  358. package/src/CodeBlock/CodeBlock.test.tsx +17 -0
  359. package/src/CodeBlock/CodeBlock.tsx +38 -11
  360. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  361. package/src/Collapsible/Collapsible.test.tsx +7 -0
  362. package/src/Collapsible/Collapsible.tsx +16 -6
  363. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  364. package/src/CommandPalette/CommandPalette.tsx +41 -2
  365. package/src/CommandPalette/CommandPaletteItem.tsx +1 -1
  366. package/src/ContextMenu/ContextMenu.test.tsx +59 -2
  367. package/src/ContextMenu/ContextMenu.tsx +22 -11
  368. package/src/ContextMenu/index.ts +5 -0
  369. package/src/DateInput/DateInput.doc.mjs +25 -0
  370. package/src/DateInput/DateInput.test.tsx +230 -0
  371. package/src/DateInput/DateInput.tsx +51 -29
  372. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  373. package/src/DateRangeInput/DateRangeInput.test.tsx +140 -0
  374. package/src/DateRangeInput/DateRangeInput.tsx +42 -30
  375. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  376. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  377. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  378. package/src/Divider/Divider.test.tsx +32 -8
  379. package/src/Divider/Divider.tsx +14 -1
  380. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  381. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  382. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  383. package/src/DropdownMenu/DropdownMenuItem.tsx +9 -3
  384. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +4 -12
  385. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +9 -10
  386. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +9 -3
  387. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +20 -2
  388. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  389. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
  390. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
  391. package/src/DropdownMenu/index.ts +16 -0
  392. package/src/DropdownMenu/menuItemHover.ts +39 -0
  393. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  394. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  395. package/src/Field/Field.tsx +14 -9
  396. package/src/Field/FieldLabel.test.tsx +25 -0
  397. package/src/Field/FieldLabel.tsx +12 -3
  398. package/src/Field/inputStyles.stylex.ts +2 -0
  399. package/src/FieldStatus/FieldStatus.test.tsx +72 -0
  400. package/src/FieldStatus/FieldStatus.tsx +48 -2
  401. package/src/FileInput/FileInput.doc.mjs +23 -2
  402. package/src/FileInput/FileInput.test.tsx +104 -13
  403. package/src/FileInput/FileInput.tsx +106 -79
  404. package/src/HoverCard/useHoverCard.tsx +15 -3
  405. package/src/Icon/Icon.doc.mjs +13 -0
  406. package/src/Icon/Icon.test.tsx +89 -0
  407. package/src/Icon/Icon.tsx +51 -3
  408. package/src/Item/Item.doc.mjs +5 -0
  409. package/src/Item/Item.test.tsx +88 -0
  410. package/src/Item/Item.tsx +57 -11
  411. package/src/Layer/layerAnimations.stylex.ts +23 -0
  412. package/src/Lightbox/Lightbox.tsx +15 -7
  413. package/src/List/List.test.tsx +17 -29
  414. package/src/List/List.tsx +6 -1
  415. package/src/List/ListItem.doc.mjs +12 -0
  416. package/src/List/ListItem.tsx +13 -0
  417. package/src/Markdown/Markdown.test.tsx +21 -0
  418. package/src/Markdown/Markdown.tsx +4 -0
  419. package/src/MetadataList/MetadataList.test.tsx +24 -0
  420. package/src/MetadataList/MetadataList.tsx +6 -2
  421. package/src/MultiSelector/MultiSelector.doc.mjs +20 -0
  422. package/src/MultiSelector/MultiSelector.test.tsx +402 -0
  423. package/src/MultiSelector/MultiSelector.tsx +120 -59
  424. package/src/NavMenu/NavHeadingMenuItem.tsx +1 -1
  425. package/src/NumberInput/NumberInput.doc.mjs +21 -0
  426. package/src/NumberInput/NumberInput.test.tsx +71 -0
  427. package/src/NumberInput/NumberInput.tsx +35 -26
  428. package/src/Outline/Outline.test.tsx +12 -12
  429. package/src/Outline/Outline.tsx +2 -2
  430. package/src/Pagination/Pagination.tsx +11 -3
  431. package/src/PowerSearch/PowerSearch.doc.mjs +15 -0
  432. package/src/PowerSearch/PowerSearch.test.tsx +23 -0
  433. package/src/PowerSearch/PowerSearch.tsx +10 -1
  434. package/src/ProgressBar/ProgressBar.test.tsx +34 -0
  435. package/src/ProgressBar/ProgressBar.tsx +16 -1
  436. package/src/RadioList/RadioList.doc.mjs +6 -0
  437. package/src/RadioList/RadioList.test.tsx +26 -4
  438. package/src/RadioList/RadioList.tsx +2 -2
  439. package/src/RadioList/RadioListItem.tsx +12 -5
  440. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  441. package/src/Resizable/ResizeHandle.tsx +70 -20
  442. package/src/SegmentedControl/SegmentedControl.test.tsx +28 -0
  443. package/src/SegmentedControl/SegmentedControlItem.tsx +22 -2
  444. package/src/Selector/Selector.doc.mjs +15 -0
  445. package/src/Selector/Selector.test.tsx +370 -0
  446. package/src/Selector/Selector.tsx +88 -20
  447. package/src/SideNav/SideNav.tsx +1 -8
  448. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  449. package/src/SideNav/SideNavItem.tsx +5 -3
  450. package/src/Skeleton/Skeleton.test.tsx +16 -0
  451. package/src/Skeleton/Skeleton.tsx +13 -1
  452. package/src/Slider/Slider.doc.mjs +6 -0
  453. package/src/Slider/Slider.test.tsx +240 -12
  454. package/src/Slider/Slider.tsx +96 -23
  455. package/src/Spinner/Spinner.test.tsx +10 -8
  456. package/src/Spinner/Spinner.tsx +15 -5
  457. package/src/Switch/Switch.doc.mjs +142 -42
  458. package/src/Switch/Switch.test.tsx +111 -0
  459. package/src/Switch/Switch.tsx +149 -32
  460. package/src/TabList/Tab.tsx +2 -2
  461. package/src/TabList/TabList.doc.mjs +0 -6
  462. package/src/TabList/TabList.test.tsx +3 -15
  463. package/src/TabList/TabList.tsx +6 -17
  464. package/src/TabList/TabListContext.ts +0 -6
  465. package/src/TabList/TabMenu.tsx +2 -2
  466. package/src/Table/BaseTable.tsx +34 -2
  467. package/src/Table/Table.doc.mjs +11 -0
  468. package/src/Table/Table.test.tsx +85 -0
  469. package/src/Table/TableCell.tsx +3 -3
  470. package/src/Table/TableHeaderCell.tsx +3 -3
  471. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  472. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +19 -0
  473. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +27 -18
  474. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  475. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  476. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +38 -9
  477. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  478. package/src/Table/types.ts +33 -0
  479. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  480. package/src/TextArea/TextArea.doc.mjs +21 -0
  481. package/src/TextArea/TextArea.test.tsx +23 -0
  482. package/src/TextArea/TextArea.tsx +27 -26
  483. package/src/TextInput/TextInput.doc.mjs +21 -0
  484. package/src/TextInput/TextInput.test.tsx +150 -0
  485. package/src/TextInput/TextInput.tsx +28 -25
  486. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  487. package/src/Thumbnail/Thumbnail.tsx +1 -1
  488. package/src/TimeInput/TimeInput.doc.mjs +21 -0
  489. package/src/TimeInput/TimeInput.test.tsx +23 -0
  490. package/src/TimeInput/TimeInput.tsx +24 -25
  491. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  492. package/src/Timestamp/Timestamp.test.tsx +371 -0
  493. package/src/Timestamp/Timestamp.tsx +96 -93
  494. package/src/Timestamp/formatInstant.ts +218 -0
  495. package/src/Timestamp/index.ts +4 -0
  496. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  497. package/src/Timestamp/tooltipEntries.ts +210 -0
  498. package/src/Toast/ToastViewport.test.tsx +218 -8
  499. package/src/Toast/ToastViewport.tsx +71 -12
  500. package/src/ToggleButton/ToggleButton.test.tsx +24 -0
  501. package/src/ToggleButton/ToggleButton.tsx +19 -1
  502. package/src/Token/Token.test.tsx +166 -15
  503. package/src/Token/Token.tsx +104 -47
  504. package/src/Token/TokenLink.tsx +107 -0
  505. package/src/Token/index.ts +1 -1
  506. package/src/Tokenizer/Tokenizer.doc.mjs +23 -1
  507. package/src/Tokenizer/Tokenizer.test.tsx +23 -0
  508. package/src/Tokenizer/Tokenizer.tsx +14 -2
  509. package/src/Tooltip/Tooltip.test.tsx +46 -0
  510. package/src/Tooltip/useTooltip.tsx +20 -0
  511. package/src/TopNav/TopNav.test.tsx +93 -2
  512. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  513. package/src/TopNav/TopNavItem.tsx +35 -6
  514. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  515. package/src/TreeList/TreeList.doc.mjs +6 -0
  516. package/src/TreeList/TreeList.test.tsx +50 -10
  517. package/src/TreeList/TreeList.tsx +5 -0
  518. package/src/TreeList/TreeListBranches.tsx +8 -6
  519. package/src/TreeList/TreeListItem.tsx +33 -11
  520. package/src/Typeahead/BaseTypeahead.tsx +42 -15
  521. package/src/Typeahead/Typeahead.doc.mjs +13 -0
  522. package/src/Typeahead/Typeahead.test.tsx +112 -0
  523. package/src/Typeahead/Typeahead.tsx +17 -1
  524. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  525. package/src/__tests__/forcedColors.ts +60 -0
  526. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  527. package/src/doc-mjs.d.ts +19 -0
  528. package/src/hooks/index.ts +6 -0
  529. package/src/hooks/isRtlElement.ts +27 -0
  530. package/src/hooks/useAnnounce.test.tsx +75 -2
  531. package/src/hooks/useAnnounce.ts +47 -1
  532. package/src/hooks/useFocusTrap.test.tsx +104 -0
  533. package/src/hooks/useFocusTrap.ts +61 -12
  534. package/src/hooks/useGridFocus.doc.mjs +3 -3
  535. package/src/hooks/useGridFocus.test.tsx +41 -0
  536. package/src/hooks/useGridFocus.ts +21 -10
  537. package/src/hooks/useHotkeys.ts +9 -0
  538. package/src/hooks/useInputStatusIcon.test.tsx +162 -0
  539. package/src/hooks/useInputStatusIcon.tsx +224 -0
  540. package/src/hooks/useKeyboardHint.test.tsx +22 -0
  541. package/src/hooks/useKeyboardHint.tsx +6 -2
  542. package/src/hooks/useListFocus.doc.mjs +25 -3
  543. package/src/hooks/useListFocus.test.tsx +153 -1
  544. package/src/hooks/useListFocus.ts +102 -9
  545. package/src/i18n/InternationalizationContext.ts +4 -0
  546. package/src/i18n/InternationalizationProvider.tsx +12 -2
  547. package/src/i18n/__tests__/getLocaleDirection.test.ts +40 -0
  548. package/src/i18n/__tests__/useDirection.test.tsx +66 -0
  549. package/src/i18n/getLocaleDirection.ts +29 -0
  550. package/src/i18n/index.ts +6 -0
  551. package/src/i18n/useDirection.ts +29 -0
  552. package/src/reset.css +0 -26
  553. package/src/reset.test.ts +60 -0
  554. package/src/theme/derivedVarRegistry.test.ts +1 -0
  555. package/src/theme/derivedVarRegistry.ts +6 -0
  556. package/src/theme/generateThemeRules.test.ts +42 -0
  557. package/src/utils/index.ts +2 -0
  558. package/src/utils/plainDate.test.ts +24 -6
  559. package/src/utils/plainDate.ts +18 -4
  560. package/src/utils/rtlStyles.ts +27 -0
@@ -56,14 +56,27 @@ function resolveEffectiveSide(
56
56
  }
57
57
 
58
58
  /**
59
- * Hit area bias percentage. When the pill is off-center, the grab zone
60
- * shifts ~2:1 toward the pill so users can reach the visible grip easily.
59
+ * Hit-area inline bias, expressed as a fraction of the pill's per-side offset.
60
+ *
61
+ * The visible pill is placed by `pillOffsetX`/`pillOffsetY`: anchored at the
62
+ * divider's inline-start edge and pushed one grip-width-plus-gap toward the
63
+ * panel side via a PHYSICAL translate (dir −1 = start, +1 = end). Because that
64
+ * offset is physical, the pill sits on the same physical side of the divider in
65
+ * both LTR and RTL. The grab zone must sit OVER that pill, so it has to use the
66
+ * exact same anchor + physical-offset construction as the pill (just wider) —
67
+ * anchoring the hit area at the divider centre (`insetInlineStart: 50%`) and
68
+ * biasing with a percentage translate mixes a logical anchor (which flips under
69
+ * RTL) with a physical translate (which doesn't), leaving the grab zone stranded
70
+ * to one side under RTL. Returning the pill's `dir` here lets the hit area reuse
71
+ * `pillOffsetX`/`pillOffsetY` so the two elements are positioned identically.
61
72
  */
62
- function hitAreaBias(effectiveSide: 'start' | 'end' | 'center'): string {
73
+ function hitAreaBiasDir(
74
+ effectiveSide: 'start' | 'end' | 'center',
75
+ ): number | null {
63
76
  if (effectiveSide === 'center') {
64
- return '50%';
77
+ return null;
65
78
  }
66
- return effectiveSide === 'start' ? '66.67%' : '33.33%';
79
+ return effectiveSide === 'start' ? -1 : 1;
67
80
  }
68
81
 
69
82
  const styles = stylex.create({
@@ -102,8 +115,8 @@ const styles = stylex.create({
102
115
  },
103
116
  overlayVertical: {
104
117
  insetBlockEnd: 0,
105
- left: 0,
106
- right: 0,
118
+ insetInlineStart: 0,
119
+ insetInlineEnd: 0,
107
120
  height: 'var(--resize-handle-hit-area, 16px)',
108
121
  },
109
122
  horizontal: {
@@ -145,16 +158,24 @@ const styles = stylex.create({
145
158
  width: spacingVars['--spacing-4'],
146
159
  top: 0,
147
160
  bottom: 0,
148
- left: '50%',
149
161
  cursor: 'col-resize',
150
162
  },
151
163
  hitAreaVertical: {
152
164
  height: spacingVars['--spacing-4'],
153
- left: 0,
154
- right: 0,
155
- top: '50%',
165
+ insetInlineStart: 0,
166
+ insetInlineEnd: 0,
156
167
  cursor: 'row-resize',
157
168
  },
169
+ // Centered grab zone (pillPlacement 'center' / no bias): sit the hit area on
170
+ // the divider itself. Anchored logically so it centers in both directions.
171
+ hitAreaCenteredX: {
172
+ insetInlineStart: '50%',
173
+ transform: 'translateX(-50%)',
174
+ },
175
+ hitAreaCenteredY: {
176
+ insetBlockStart: '50%',
177
+ transform: 'translateY(-50%)',
178
+ },
158
179
 
159
180
  // Pill base — themes target .astryx-resize-handle-pill for size/shape.
160
181
  pill: {
@@ -167,7 +188,7 @@ const styles = stylex.create({
167
188
  transitionDuration: durationVars['--duration-fast'],
168
189
  transitionTimingFunction: easeVars['--ease-standard'],
169
190
  top: '50%',
170
- left: '50%',
191
+ insetInlineStart: '50%',
171
192
  transform: 'translate(-50%, -50%)',
172
193
  },
173
194
  pillHorizontal: {
@@ -193,14 +214,32 @@ const styles = stylex.create({
193
214
  // Dynamic styles — avoids inline style overrides.
194
215
  // Each axis gets its own function since StyleX requires static structure.
195
216
  const dynamicStyles = stylex.create({
196
- hitAreaBiasX: (pct: string) => ({
197
- transform: `translateX(-${pct})`,
217
+ // Hit-area inline offset — mirrors the pill's `pillOffsetX`/`pillOffsetY`
218
+ // construction so the grab zone sits centred on the visible pill in BOTH
219
+ // directions. Both anchor at the divider's inline-start edge
220
+ // (`insetInlineStart: 0`) and travel toward the panel side with the SAME
221
+ // physical translate `dir * (gripWidth + gap)`, so they share the pill's
222
+ // near edge. The hit area is wider, so it also shifts by half the width
223
+ // difference `(16px − 3px) / 2 = 6.5px` to align the two CENTRES. That
224
+ // centring shift is along the inline axis (a box grows from its inline-start
225
+ // edge toward inline-end), so it must flip physical sign under RTL — unlike
226
+ // the `dir * (...)` travel, which is physical and identical in both
227
+ // directions because the pill's own offset is physical. Mixing the previous
228
+ // divider-relative `50%` anchor with a percentage translate stranded the grab
229
+ // zone to one side under RTL; this construction keeps it on the pill.
230
+ hitAreaOffsetX: (dir: number) => ({
231
+ insetInlineStart: 0,
232
+ transform: {
233
+ default: `translate(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px), -50%)`,
234
+ ':is([dir="rtl"] *)': `translate(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) + 6.5px), -50%)`,
235
+ },
198
236
  }),
199
- hitAreaBiasY: (pct: string) => ({
200
- transform: `translateY(-${pct})`,
237
+ hitAreaOffsetY: (dir: number) => ({
238
+ insetBlockStart: 0,
239
+ transform: `translate(-50%, calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
201
240
  }),
202
241
  pillOffsetX: (dir: number) => ({
203
- left: 0,
242
+ insetInlineStart: 0,
204
243
  transform: `translate(calc(${dir} * (100% + ${spacingVars['--spacing-1']})), -50%)`,
205
244
  }),
206
245
  // Vertical offset: no rotation — use explicit landscape dimensions.
@@ -333,6 +372,9 @@ export function ResizeHandle({
333
372
  isReversed,
334
373
  resizable?._isCollapsed ?? false,
335
374
  );
375
+ // Physical offset direction shared by the pill and the grab zone (null =
376
+ // centered / no bias). Keeps the two positioned identically in LTR and RTL.
377
+ const hitBiasDir = hitAreaBiasDir(effectiveSide);
336
378
 
337
379
  const getRTLMultiplier = useCallback((): number => {
338
380
  const el = handleRef.current;
@@ -538,9 +580,17 @@ export function ResizeHandle({
538
580
  {...stylex.props(
539
581
  styles.hitArea,
540
582
  isHorizontal ? styles.hitAreaHorizontal : styles.hitAreaVertical,
541
- isHorizontal
542
- ? dynamicStyles.hitAreaBiasX(hitAreaBias(effectiveSide))
543
- : dynamicStyles.hitAreaBiasY(hitAreaBias(effectiveSide)),
583
+ // Bias the grab zone to sit over the visible pill. When the pill is
584
+ // centered (no bias) just center the hit area on the divider; when
585
+ // it's offset to a panel side, reuse the pill's physical-offset
586
+ // construction so the two stay aligned in LTR and RTL alike.
587
+ hitBiasDir == null
588
+ ? isHorizontal
589
+ ? styles.hitAreaCenteredX
590
+ : styles.hitAreaCenteredY
591
+ : isHorizontal
592
+ ? dynamicStyles.hitAreaOffsetX(hitBiasDir)
593
+ : dynamicStyles.hitAreaOffsetY(hitBiasDir),
544
594
  isDisabled && styles.disabled,
545
595
  )}
546
596
  onPointerDown={handlePointerDown}
@@ -14,6 +14,10 @@ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {SegmentedControl} from './SegmentedControl';
16
16
  import {SegmentedControlItem} from './SegmentedControlItem';
17
+ import {
18
+ getAllInjectedCss,
19
+ getForcedColorsRules,
20
+ } from '../__tests__/forcedColors';
17
21
 
18
22
  // Mock showPopover/hidePopover (not implemented in jsdom) so the tooltip layer
19
23
  // reflects its open state via a `popover-open` attribute the tests can assert.
@@ -780,3 +784,27 @@ describe('SegmentedControl container handler forwarding', () => {
780
784
  expect(onChange).not.toHaveBeenCalled();
781
785
  });
782
786
  });
787
+
788
+ // jsdom cannot emulate forced-colors rendering, so these assert that the
789
+ // compiled output includes the forced-colors rules; visual behavior needs
790
+ // manual verification under Windows High Contrast.
791
+ describe('forced colors (WCAG 1.4.11)', () => {
792
+ it('compiles forced-colors overrides so the selected segment survives Windows High Contrast', () => {
793
+ render(
794
+ <SegmentedControl value="grid" onChange={() => {}} label="View mode">
795
+ <SegmentedControlItem value="grid" label="Grid" />
796
+ <SegmentedControlItem value="list" label="List" />
797
+ </SegmentedControl>,
798
+ );
799
+ const css = getForcedColorsRules();
800
+ // The painted surface fill and shadow are stripped; Highlight/
801
+ // HighlightText marks the selected segment.
802
+ expect(css).toContain('background-color: highlight;');
803
+ expect(css).toContain('color: highlighttext;');
804
+ // The segment is a <button>; without opting out of UA remapping it keeps
805
+ // the native ButtonFace surface and ignores the Highlight fill, leaving
806
+ // HighlightText text on a white surface. forced-color-adjust: none makes
807
+ // both render as authored.
808
+ expect(getAllInjectedCss()).toContain('forced-color-adjust: none;');
809
+ });
810
+ });
@@ -103,9 +103,27 @@ const styles = stylex.create({
103
103
  },
104
104
  },
105
105
  selected: {
106
- color: colorVars['--color-text-primary'],
106
+ // Forced colors (Windows High Contrast) strips the painted surface fill
107
+ // and box shadow, which would leave the selected segment with no state
108
+ // indication beyond font weight. Highlight/HighlightText is the platform
109
+ // convention for a selected item (WCAG 1.4.11).
110
+ //
111
+ // forced-color-adjust must be `none` here: the segment is a <button>, and
112
+ // the UA keeps native form-control colors (ButtonFace surface) for it under
113
+ // forced colors, ignoring the authored Highlight fill — the label kept its
114
+ // HighlightText color, giving white text on a white surface. Opting the
115
+ // selected segment out of UA remapping makes both the Highlight surface and
116
+ // the HighlightText label render as authored, restoring figure-ground.
117
+ forcedColorAdjust: 'none',
118
+ color: {
119
+ default: colorVars['--color-text-primary'],
120
+ '@media (forced-colors: active)': 'HighlightText',
121
+ },
107
122
  fontWeight: fontWeightVars['--font-weight-semibold'],
108
- backgroundColor: colorVars['--color-background-surface'],
123
+ backgroundColor: {
124
+ default: colorVars['--color-background-surface'],
125
+ '@media (forced-colors: active)': 'Highlight',
126
+ },
109
127
  boxShadow: shadowVars['--shadow-low'],
110
128
  },
111
129
  disabled: {
@@ -169,6 +187,7 @@ export function SegmentedControlItem({
169
187
  icon,
170
188
  isDisabled = false,
171
189
  onClick: onClickProp,
190
+ xstyle,
172
191
  ...rest
173
192
  }: SegmentedControlItemProps) {
174
193
  const ctx = useSegmentedControlContext();
@@ -228,6 +247,7 @@ export function SegmentedControlItem({
228
247
  isSelected && styles.selected,
229
248
  !isSelected && !isItemDisabled && styles.hover,
230
249
  isItemDisabled && styles.disabled,
250
+ xstyle,
231
251
  ),
232
252
  )}>
233
253
  {iconElement}
@@ -23,6 +23,8 @@ export const docs = {
23
23
  targets: [
24
24
  {className: 'astryx-selector', visualProps: ['size', 'status']},
25
25
  {className: 'astryx-selector-option'},
26
+ {className: 'astryx-selector-clear-icon'},
27
+ {className: 'astryx-selector-indicator-icon', states: ['state']},
26
28
  ],
27
29
  },
28
30
  description: 'Dropdown selector for choosing from a list of options.',
@@ -128,12 +130,25 @@ export const docs = {
128
130
  type: "{type: 'error' | 'warning' | 'success', message?: string}",
129
131
  description: 'Validation status with an optional message.',
130
132
  },
133
+ {
134
+ name: 'statusVariant',
135
+ type: "'attached' | 'detached'",
136
+ description:
137
+ 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment); detached floats below as a separate element with spacing.',
138
+ default: "'attached'",
139
+ },
131
140
  {
132
141
  name: 'renderOption',
133
142
  type: '(option: SelectorOptionData) => ReactNode',
134
143
  description:
135
144
  'Custom render function for each selectable option in the dropdown. Use this instead of JSX children; dividers and sections are rendered by the selector.',
136
145
  },
146
+ {
147
+ name: 'width',
148
+ type: 'SizeValue',
149
+ description:
150
+ 'Width of the field (number = pixels, string used as-is, e.g. "100%"). Sizes the whole field (label, control, and status) so they stay aligned.',
151
+ },
137
152
  {
138
153
  name: 'xstyle',
139
154
  type: 'StyleXStyles',
@@ -20,8 +20,11 @@ import {
20
20
  import userEvent from '@testing-library/user-event';
21
21
  import {Selector} from './Selector';
22
22
  import {SelectorOption} from './SelectorOption';
23
+ import {Icon} from '../Icon';
23
24
  import {InputGroup, InputGroupText} from '../InputGroup';
24
25
  import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
26
+ import {defineTheme} from '../theme/defineTheme';
27
+ import {generateThemeCSSFlat} from '../theme/generateThemeRules';
25
28
 
26
29
  function politeRegion(): HTMLElement | null {
27
30
  return document.querySelector('[data-astryx-live-region="polite"]');
@@ -610,6 +613,28 @@ describe('Selector', () => {
610
613
  expect(within(listbox).getByText('No results found')).toBeInTheDocument();
611
614
  });
612
615
 
616
+ it('empty-state message is not exposed as a listbox child', async () => {
617
+ const user = userEvent.setup();
618
+ render(
619
+ <Selector
620
+ label="Fruit"
621
+ options={OPTIONS}
622
+ value="Apple"
623
+ onChange={() => {}}
624
+ hasSearch
625
+ />,
626
+ );
627
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
628
+ await user.type(screen.getByRole('combobox', h), 'xyz');
629
+
630
+ // role="listbox" only permits option/group children — the visual
631
+ // empty-state message must be presentational (it is announced through
632
+ // the result-count live region instead).
633
+ const listbox = screen.getByRole('listbox', h);
634
+ const empty = within(listbox).getByText('No results found');
635
+ expect(empty).toHaveAttribute('role', 'presentation');
636
+ });
637
+
613
638
  it('calls onChange when selecting a filtered option', async () => {
614
639
  const user = userEvent.setup();
615
640
  const onChange = vi.fn();
@@ -746,6 +771,119 @@ describe('Selector', () => {
746
771
  expect(politeRegion()?.textContent ?? '').toBe('');
747
772
  });
748
773
  });
774
+
775
+ describe('grouped search', () => {
776
+ const GROUPED = [
777
+ {
778
+ type: 'section' as const,
779
+ title: 'Citrus',
780
+ options: [
781
+ {value: 'orange', label: 'Orange'},
782
+ {value: 'lemon', label: 'Lemon'},
783
+ ],
784
+ },
785
+ {
786
+ type: 'section' as const,
787
+ title: 'Berries',
788
+ options: [
789
+ {value: 'strawberry', label: 'Strawberry'},
790
+ {value: 'blueberry', label: 'Blueberry'},
791
+ ],
792
+ },
793
+ ];
794
+
795
+ it('keeps the group header above matching items while searching', async () => {
796
+ const user = userEvent.setup();
797
+ render(
798
+ <Selector
799
+ label="Fruit"
800
+ options={GROUPED}
801
+ onChange={() => {}}
802
+ hasSearch
803
+ />,
804
+ );
805
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
806
+ // "orange" only matches within the Citrus group.
807
+ await user.type(screen.getByRole('combobox', h), 'orange');
808
+
809
+ expect(
810
+ screen.getByRole('group', {name: 'Citrus', ...h}),
811
+ ).toBeInTheDocument();
812
+ const options = screen.getAllByRole('option', h);
813
+ expect(options).toHaveLength(1);
814
+ expect(options[0]).toHaveTextContent('Orange');
815
+ });
816
+
817
+ it('hides a group whose items have no match', async () => {
818
+ const user = userEvent.setup();
819
+ render(
820
+ <Selector
821
+ label="Fruit"
822
+ options={GROUPED}
823
+ onChange={() => {}}
824
+ hasSearch
825
+ />,
826
+ );
827
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
828
+ // "berry" only matches items inside the Berries group.
829
+ await user.type(screen.getByRole('combobox', h), 'berry');
830
+
831
+ expect(
832
+ screen.getByRole('group', {name: 'Berries', ...h}),
833
+ ).toBeInTheDocument();
834
+ expect(
835
+ screen.queryByRole('group', {name: 'Citrus', ...h}),
836
+ ).not.toBeInTheDocument();
837
+ expect(screen.getAllByRole('option', h)).toHaveLength(2);
838
+ });
839
+
840
+ it('lands keyboard focus on the correct option after filtering', async () => {
841
+ const user = userEvent.setup();
842
+ render(
843
+ <Selector
844
+ label="Fruit"
845
+ options={GROUPED}
846
+ onChange={() => {}}
847
+ hasSearch
848
+ />,
849
+ );
850
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
851
+ const search = screen.getByRole('combobox', h);
852
+ // "berry" leaves Strawberry, Blueberry (in that document order).
853
+ await user.type(search, 'berry');
854
+ const options = screen.getAllByRole('option', h);
855
+ expect(options.map(o => o.textContent)).toEqual([
856
+ 'Strawberry',
857
+ 'Blueberry',
858
+ ]);
859
+ await user.keyboard('{ArrowDown}');
860
+ expect(search).toHaveAttribute('aria-activedescendant', options[0].id);
861
+ await user.keyboard('{ArrowDown}');
862
+ expect(search).toHaveAttribute('aria-activedescendant', options[1].id);
863
+ });
864
+
865
+ it('shows the empty state when no group has a match', async () => {
866
+ const user = userEvent.setup();
867
+ render(
868
+ <Selector
869
+ label="Fruit"
870
+ options={GROUPED}
871
+ onChange={() => {}}
872
+ hasSearch
873
+ />,
874
+ );
875
+ await user.click(screen.getByRole('button', {name: 'Fruit'}));
876
+ await user.type(screen.getByRole('combobox', h), 'zzz');
877
+ expect(screen.queryAllByRole('option', h)).toHaveLength(0);
878
+ expect(
879
+ screen.queryByRole('group', {name: 'Citrus', ...h}),
880
+ ).not.toBeInTheDocument();
881
+ const listbox = screen.getByRole('listbox', h);
882
+ expect(
883
+ within(listbox).getByText('No results found'),
884
+ ).toBeInTheDocument();
885
+ });
886
+ });
749
887
  });
750
888
 
751
889
  describe('keyboard accessibility', () => {
@@ -1073,3 +1211,235 @@ describe('Selector', () => {
1073
1211
  });
1074
1212
  });
1075
1213
  });
1214
+
1215
+ describe('Selector statusVariant forwarding', () => {
1216
+ it('defaults to attached (status renders with data-variant="attached")', () => {
1217
+ const {container} = render(
1218
+ <Selector
1219
+ label="Fruit"
1220
+ options={['Apple', 'Banana']}
1221
+ status={{type: 'error', message: 'Required'}}
1222
+ />,
1223
+ );
1224
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
1225
+ 'data-variant',
1226
+ 'attached',
1227
+ );
1228
+ });
1229
+
1230
+ it('forwards statusVariant="detached" to the underlying Field status', () => {
1231
+ const {container} = render(
1232
+ <Selector
1233
+ label="Fruit"
1234
+ options={['Apple', 'Banana']}
1235
+ status={{type: 'error', message: 'Required'}}
1236
+ statusVariant="detached"
1237
+ />,
1238
+ );
1239
+ expect(container.querySelector('.astryx-field-status')).toHaveAttribute(
1240
+ 'data-variant',
1241
+ 'detached',
1242
+ );
1243
+ });
1244
+ });
1245
+
1246
+ describe('Selector clear icon theme target', () => {
1247
+ // Resolve the clear glyph span (the astryx-icon element inside the clear
1248
+ // button), independent of the theme target class.
1249
+ const getClearIcon = (): HTMLElement => {
1250
+ const button = screen.getByRole('button', {name: 'Clear Fruit'});
1251
+ const icon = button.querySelector('.astryx-icon');
1252
+ if (icon == null) {
1253
+ throw new Error('clear icon not found');
1254
+ }
1255
+ return icon as HTMLElement;
1256
+ };
1257
+
1258
+ it('renders the astryx-selector-clear-icon target on the clear glyph', () => {
1259
+ render(
1260
+ <Selector
1261
+ label="Fruit"
1262
+ options={OPTIONS}
1263
+ value="Banana"
1264
+ onChange={() => {}}
1265
+ hasClear
1266
+ />,
1267
+ );
1268
+ // The stable theme target lands on the icon element itself (not the
1269
+ // button), so a theme can restyle just this glyph (color, size, hover)
1270
+ // via `defineTheme` — a button-level target could not reach the icon's
1271
+ // own color/size.
1272
+ const icon = getClearIcon();
1273
+ expect(icon).toHaveClass('astryx-selector-clear-icon');
1274
+ expect(icon).toHaveClass('astryx-icon');
1275
+ });
1276
+
1277
+ it('keeps the clear button functional alongside the target', () => {
1278
+ const onChange = vi.fn();
1279
+ render(
1280
+ <Selector
1281
+ label="Fruit"
1282
+ options={OPTIONS}
1283
+ value="Banana"
1284
+ onChange={onChange}
1285
+ hasClear
1286
+ />,
1287
+ );
1288
+ const clear = screen.getByRole('button', {name: 'Clear Fruit'});
1289
+ expect(clear.tagName).toBe('BUTTON');
1290
+ fireEvent.click(clear);
1291
+ expect(onChange).toHaveBeenCalledWith(null);
1292
+ });
1293
+
1294
+ it('renders the default icon (secondary color, sm size) byte-identically', () => {
1295
+ // Pixel-identical default guard: the clear glyph must carry the exact same
1296
+ // StyleX color/size classes as a standalone secondary/sm icon. The added
1297
+ // target class is purely additive — it changes nothing until a theme
1298
+ // targets it.
1299
+ render(
1300
+ <Selector
1301
+ label="Fruit"
1302
+ options={OPTIONS}
1303
+ value="Banana"
1304
+ onChange={() => {}}
1305
+ hasClear
1306
+ />,
1307
+ );
1308
+ const icon = getClearIcon();
1309
+
1310
+ const {container: refContainer} = render(
1311
+ <Icon icon="close" size="sm" color="secondary" />,
1312
+ );
1313
+ const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
1314
+
1315
+ const styleClasses = (el: HTMLElement) =>
1316
+ el.className
1317
+ .split(' ')
1318
+ .filter(c => c !== 'astryx-selector-clear-icon')
1319
+ .sort();
1320
+
1321
+ expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
1322
+ });
1323
+
1324
+ it('exposes selector-clear-icon so a theme reaches the icon color, size, and hover', () => {
1325
+ // jsdom cannot resolve the @layer cascade, so the DOM-class assertion above
1326
+ // (target lands on the icon element) plus this generation assertion (the
1327
+ // theme emits same-element icon rules in @layer astryx-theme) together
1328
+ // prove the seam: a same-element theme rule wins over the icon's own
1329
+ // base-layer color/size.
1330
+ const theme = defineTheme({
1331
+ name: 'selector-clear-icon-test',
1332
+ components: {
1333
+ 'selector-clear-icon': {
1334
+ base: {
1335
+ width: '12px',
1336
+ height: '12px',
1337
+ fontSize: '12px',
1338
+ color: 'var(--color-icon-secondary)',
1339
+ ':hover': {color: 'var(--color-icon-primary)'},
1340
+ },
1341
+ },
1342
+ },
1343
+ });
1344
+ const css = generateThemeCSSFlat(theme);
1345
+ expect(css).toContain('.astryx-selector-clear-icon {');
1346
+ expect(css).toContain('width: 12px');
1347
+ expect(css).toContain('height: 12px');
1348
+ expect(css).toContain('.astryx-selector-clear-icon:hover {');
1349
+ expect(css).toContain('color: var(--color-icon-primary)');
1350
+ });
1351
+ });
1352
+
1353
+ describe('Selector indicator (chevron) icon theme target', () => {
1354
+ const getIndicatorIcon = (container: HTMLElement): HTMLElement => {
1355
+ // The chevron is the only glyph carrying the indicator target class.
1356
+ const icon = container.querySelector('.astryx-selector-indicator-icon');
1357
+ if (icon == null) {
1358
+ throw new Error('indicator icon not found');
1359
+ }
1360
+ return icon as HTMLElement;
1361
+ };
1362
+
1363
+ it('renders the astryx-selector-indicator-icon target on the chevron glyph', () => {
1364
+ const {container} = render(
1365
+ <Selector label="Fruit" options={OPTIONS} onChange={() => {}} />,
1366
+ );
1367
+ // The stable theme target lands on the icon element itself (not the trigger
1368
+ // button), so a theme can restyle just this glyph (color, size, hover) —
1369
+ // and each open/closed state — via `defineTheme`. A button-level target
1370
+ // could not reach the icon's own color/size.
1371
+ const icon = getIndicatorIcon(container);
1372
+ expect(icon).toHaveClass('astryx-selector-indicator-icon');
1373
+ expect(icon).toHaveClass('astryx-icon');
1374
+ // Open/closed state is reflected so a theme can target each state alone.
1375
+ expect(icon).toHaveAttribute('data-state', 'collapsed');
1376
+ });
1377
+
1378
+ it('reflects the expanded state on the chevron when the popover is open', async () => {
1379
+ const user = userEvent.setup();
1380
+ const {container} = render(
1381
+ <Selector label="Fruit" options={OPTIONS} onChange={() => {}} />,
1382
+ );
1383
+ await user.click(screen.getByRole('combobox'));
1384
+ await waitFor(() => {
1385
+ expect(getIndicatorIcon(container)).toHaveAttribute(
1386
+ 'data-state',
1387
+ 'expanded',
1388
+ );
1389
+ });
1390
+ });
1391
+
1392
+ it('renders the default icon (inherit color, sm size) byte-identically', () => {
1393
+ // Pixel-identical default guard: the chevron glyph must carry the exact
1394
+ // same StyleX color/size classes as a standalone inherit/sm icon. The added
1395
+ // target class + data-state are purely additive — they change nothing until
1396
+ // a theme targets them.
1397
+ const {container} = render(
1398
+ <Selector label="Fruit" options={OPTIONS} onChange={() => {}} />,
1399
+ );
1400
+ const icon = getIndicatorIcon(container);
1401
+
1402
+ const {container: refContainer} = render(
1403
+ <Icon icon="chevronDown" size="sm" color="inherit" />,
1404
+ );
1405
+ const refIcon = refContainer.querySelector('.astryx-icon') as HTMLElement;
1406
+
1407
+ // Exclude the additive theme-target classes (the stable target + its
1408
+ // reflected state class) so only the StyleX color/size classes remain.
1409
+ const themeTargetClasses = new Set([
1410
+ 'astryx-selector-indicator-icon',
1411
+ 'collapsed',
1412
+ 'expanded',
1413
+ ]);
1414
+ const styleClasses = (el: HTMLElement) =>
1415
+ el.className
1416
+ .split(' ')
1417
+ .filter(c => !themeTargetClasses.has(c))
1418
+ .sort();
1419
+
1420
+ expect(styleClasses(icon)).toEqual(styleClasses(refIcon));
1421
+ });
1422
+
1423
+ it('exposes selector-indicator-icon so a theme reaches the icon size and per-state color', () => {
1424
+ // jsdom cannot resolve the @layer cascade, so the DOM-class assertions
1425
+ // above (target lands on the icon element) plus this generation assertion
1426
+ // (the theme emits same-element icon rules in @layer astryx-theme) together
1427
+ // prove the seam: a same-element theme rule wins over the icon's own
1428
+ // base-layer color/size.
1429
+ const theme = defineTheme({
1430
+ name: 'selector-indicator-icon-test',
1431
+ components: {
1432
+ 'selector-indicator-icon': {
1433
+ base: {width: '14px', height: '14px', fontSize: '14px'},
1434
+ 'state:expanded': {color: 'var(--color-icon-primary)'},
1435
+ },
1436
+ },
1437
+ });
1438
+ const css = generateThemeCSSFlat(theme);
1439
+ expect(css).toContain('.astryx-selector-indicator-icon {');
1440
+ expect(css).toContain('width: 14px');
1441
+ expect(css).toContain('height: 14px');
1442
+ expect(css).toContain('.astryx-selector-indicator-icon.expanded');
1443
+ expect(css).toContain('color: var(--color-icon-primary)');
1444
+ });
1445
+ });