@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
@@ -4,16 +4,20 @@
4
4
 
5
5
  /**
6
6
  * @file useListFocus.ts
7
- * @input Uses React useCallback, useRef, useIsomorphicLayoutEffect
7
+ * @input Uses React useCallback, useRef, useIsomorphicLayoutEffect,
8
+ * isRtlElement
8
9
  * @output Exports useListFocus hook for linear list keyboard navigation
9
10
  * @position Core hook; used by TabMenu for dropdown menu navigation, Toolbar
10
11
  * for roving tabindex, ButtonGroup, ContextMenu, NavHeadingMenu, and more.
12
+ * Auto-detects RTL from the container's computed direction so horizontal
13
+ * arrow keys follow visual direction (WCAG 1.3.2).
11
14
  *
12
15
  * SYNC: When modified, update:
13
16
  * - /packages/core/src/hooks/index.ts
14
17
  */
15
18
 
16
19
  import {useCallback, useRef} from 'react';
20
+ import {isRtlElement} from './isRtlElement';
17
21
  import {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect';
18
22
 
19
23
  /**
@@ -34,6 +38,25 @@ export interface UseListFocusOptions {
34
38
  */
35
39
  itemSelector?: string;
36
40
 
41
+ /**
42
+ * Selector identifying a list boundary — used to scope a list that contains
43
+ * *nested* lists of the same kind (e.g. a menu with submenu flyouts).
44
+ *
45
+ * Overlays like submenu flyouts render inline (native popover, not a
46
+ * portal), so a nested list's items are DOM descendants of the parent list.
47
+ * Without scoping, the parent's `querySelectorAll` sweeps the nested items
48
+ * into its roving order (leaving hidden items `.focus()` can't land on), and
49
+ * key events from the nested list bubble into the parent's handler (moving
50
+ * focus twice). When set, `useListFocus`:
51
+ * - counts an item as its own only when the item's nearest
52
+ * `boundarySelector` ancestor is this list's container, and
53
+ * - ignores key events whose nearest `boundarySelector` ancestor is not
54
+ * this list's container (i.e. they originated in a nested list).
55
+ *
56
+ * Typically `'[role="menu"]'` for menus. Omit for flat lists.
57
+ */
58
+ boundarySelector?: string;
59
+
37
60
  /**
38
61
  * Whether arrow navigation wraps around at the ends.
39
62
  * @default true
@@ -62,7 +85,12 @@ export interface UseListFocusOptions {
62
85
  * Whether the list is in a right-to-left context. When true, ArrowLeft and
63
86
  * ArrowRight are swapped so horizontal navigation follows visual direction.
64
87
  * Only affects horizontal (`'horizontal'`/`'both'`) navigation.
65
- * @default false
88
+ *
89
+ * When omitted, the direction is auto-detected from the container's computed
90
+ * `direction` (read lazily on keydown, only for horizontal arrow keys), so
91
+ * lists inside `dir="rtl"` subtrees flip automatically. Pass an explicit
92
+ * boolean to override detection.
93
+ * @default undefined (auto-detect from the container)
66
94
  */
67
95
  isRtl?: boolean;
68
96
 
@@ -128,6 +156,23 @@ export interface UseListFocusReturn<T extends HTMLElement = HTMLElement> {
128
156
  * Focus the last enabled item. Returns true when an item was focused.
129
157
  */
130
158
  focusLast: () => boolean;
159
+
160
+ /**
161
+ * Whether a key event belongs to this list level (vs. a nested list that
162
+ * shares the same `boundarySelector`). Useful when a consumer wraps
163
+ * `handleKeyDown` with extra behavior (Enter/Space activation, typeahead)
164
+ * and must apply it only to events this level owns. Always true when no
165
+ * `boundarySelector` is configured.
166
+ */
167
+ ownsEvent: (e: React.KeyboardEvent) => boolean;
168
+
169
+ /**
170
+ * The current, in-DOM-order list of this level's focusable items (already
171
+ * scoped by `boundarySelector`). Exposed so consumers can build typeahead
172
+ * targets from the same source of truth as roving focus, rather than
173
+ * re-querying and re-filtering.
174
+ */
175
+ getItems: () => HTMLElement[];
131
176
  }
132
177
 
133
178
  // Text-editing inputs whose caret must not be hijacked by arrow navigation.
@@ -271,11 +316,12 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
271
316
  ): UseListFocusReturn<T> {
272
317
  const {
273
318
  itemSelector = '[role="menuitem"]',
319
+ boundarySelector,
274
320
  wrap = true,
275
321
  onEscape,
276
322
  orientation = 'vertical',
277
323
  hasHomeEnd = true,
278
- isRtl = false,
324
+ isRtl,
279
325
  hasRovingTabIndex = false,
280
326
  hasCaretGuard = false,
281
327
  } = options;
@@ -299,13 +345,43 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
299
345
  * Get all focusable items in the list.
300
346
  */
301
347
  const getItems = useCallback((): HTMLElement[] => {
302
- if (!listRef.current) {
348
+ const listEl = listRef.current;
349
+ if (!listEl) {
303
350
  return [];
304
351
  }
305
- return Array.from(
306
- listRef.current.querySelectorAll<HTMLElement>(itemSelector),
352
+ const matched = Array.from(
353
+ listEl.querySelectorAll<HTMLElement>(itemSelector),
307
354
  );
308
- }, [itemSelector]);
355
+ // When a boundary is set, keep only items that belong to THIS list level —
356
+ // i.e. whose nearest boundary ancestor is our own container. This excludes
357
+ // items inside nested lists (e.g. inline submenu flyouts) that would
358
+ // otherwise be swept in by querySelectorAll.
359
+ if (!boundarySelector) {
360
+ return matched;
361
+ }
362
+ return matched.filter(el => el.closest(boundarySelector) === listEl);
363
+ }, [itemSelector, boundarySelector]);
364
+
365
+ /**
366
+ * Whether a key event belongs to this list level rather than a nested list.
367
+ * With a boundary set, an event that originated inside a nested boundary
368
+ * (e.g. a submenu flyout) has bubbled up to us and must be ignored so we
369
+ * don't double-handle navigation. Without a boundary, every event is ours.
370
+ */
371
+ const ownsEvent = useCallback(
372
+ (e: React.KeyboardEvent): boolean => {
373
+ const listEl = listRef.current;
374
+ if (!listEl || !boundarySelector) {
375
+ return true;
376
+ }
377
+ const target = e.target as HTMLElement | null;
378
+ if (!target) {
379
+ return true;
380
+ }
381
+ return target.closest(boundarySelector) === listEl;
382
+ },
383
+ [boundarySelector],
384
+ );
309
385
 
310
386
  /**
311
387
  * Find the next enabled item index from `start`, moving by `step`, optionally
@@ -473,6 +549,13 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
473
549
  return;
474
550
  }
475
551
 
552
+ // Ignore events that bubbled up from a nested list (e.g. a submenu
553
+ // flyout, which renders inline inside this container). That level owns
554
+ // and already handled them; re-handling here would move focus twice.
555
+ if (!ownsEvent(e)) {
556
+ return;
557
+ }
558
+
476
559
  // Escape is handled regardless of orientation. Preserve the historical
477
560
  // behavior of always consuming Escape here (preventDefault) so consumers
478
561
  // that relied on it are unaffected.
@@ -486,11 +569,18 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
486
569
  const vertical = orientation === 'vertical' || orientation === 'both';
487
570
 
488
571
  // Resolve which keys advance vs retreat, honoring RTL for horizontal.
572
+ // Direction is resolved lazily — getComputedStyle runs only when a
573
+ // horizontal arrow key is actually pressed (SSR-safe, no layout thrash
574
+ // on unrelated keys) — and an explicit `isRtl` always wins.
489
575
  const nextKeys: string[] = [];
490
576
  const prevKeys: string[] = [];
491
577
  if (horizontal) {
492
- nextKeys.push(isRtl ? 'ArrowLeft' : 'ArrowRight');
493
- prevKeys.push(isRtl ? 'ArrowRight' : 'ArrowLeft');
578
+ const rtl =
579
+ e.key === 'ArrowLeft' || e.key === 'ArrowRight'
580
+ ? (isRtl ?? isRtlElement(listRef.current))
581
+ : false;
582
+ nextKeys.push(rtl ? 'ArrowLeft' : 'ArrowRight');
583
+ prevKeys.push(rtl ? 'ArrowRight' : 'ArrowLeft');
494
584
  }
495
585
  if (vertical) {
496
586
  nextKeys.push('ArrowDown');
@@ -553,6 +643,7 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
553
643
  focusFirst,
554
644
  focusLast,
555
645
  onEscape,
646
+ ownsEvent,
556
647
  ],
557
648
  );
558
649
 
@@ -563,5 +654,7 @@ export function useListFocus<T extends HTMLElement = HTMLElement>(
563
654
  focusItem,
564
655
  focusFirst,
565
656
  focusLast,
657
+ ownsEvent,
658
+ getItems,
566
659
  };
567
660
  }
@@ -15,6 +15,8 @@
15
15
  * SYNC: When modified, update these files to stay in sync:
16
16
  * - /packages/core/src/i18n/InternationalizationProvider.tsx
17
17
  * - /packages/core/src/i18n/t.client.ts
18
+ * - /packages/core/src/i18n/useDirection.ts
19
+ * - /packages/core/src/i18n/getLocaleDirection.ts
18
20
  * - /packages/core/src/i18n/index.ts
19
21
  */
20
22
 
@@ -23,6 +25,7 @@ import type {Locale, MessagesByLocale, Overrides} from './types';
23
25
 
24
26
  export interface InternationalizationContextValue {
25
27
  locale: Locale;
28
+ direction: 'ltr' | 'rtl';
26
29
  messages: MessagesByLocale;
27
30
  overrides?: Overrides;
28
31
  }
@@ -34,6 +37,7 @@ export interface InternationalizationContextValue {
34
37
  export const InternationalizationContext =
35
38
  createContext<InternationalizationContextValue>({
36
39
  locale: 'en',
40
+ direction: 'ltr',
37
41
  messages: {},
38
42
  });
39
43
  InternationalizationContext.displayName = 'InternationalizationContext';
@@ -21,6 +21,7 @@
21
21
 
22
22
  import {useMemo, type ReactNode} from 'react';
23
23
  import {InternationalizationContext} from './InternationalizationContext';
24
+ import {getLocaleDirection} from './getLocaleDirection';
24
25
  import type {Locale, MessagesByLocale, Overrides} from './types';
25
26
 
26
27
  export interface InternationalizationProviderProps {
@@ -55,6 +56,13 @@ export interface InternationalizationProviderProps {
55
56
  * ```
56
57
  */
57
58
  overrides?: Overrides;
59
+ /**
60
+ * Optional explicit text direction override. When omitted, direction is derived
61
+ * from `locale` via `Intl.Locale.getTextInfo()`. Provide this to force a
62
+ * direction (e.g. RTL layout testing under an English catalog) or to skip the
63
+ * derivation when you already know the direction.
64
+ */
65
+ dir?: 'ltr' | 'rtl';
58
66
  children: ReactNode;
59
67
  }
60
68
 
@@ -66,11 +74,13 @@ export function InternationalizationProvider({
66
74
  locale,
67
75
  messages,
68
76
  overrides,
77
+ dir,
69
78
  children,
70
79
  }: InternationalizationProviderProps) {
80
+ const direction = dir ?? getLocaleDirection(locale);
71
81
  const value = useMemo(
72
- () => ({locale, messages: messages ?? {}, overrides}),
73
- [locale, messages, overrides],
82
+ () => ({locale, direction, messages: messages ?? {}, overrides}),
83
+ [locale, direction, messages, overrides],
74
84
  );
75
85
  return (
76
86
  <InternationalizationContext value={value}>
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file getLocaleDirection.test.ts
5
+ * @input packages/core/src/i18n/getLocaleDirection.ts
6
+ * @output Unit tests for the server-safe locale → direction helper
7
+ * @position Colocated tests; targets LTR/RTL classification and the
8
+ * try/catch fallback for malformed input.
9
+ */
10
+
11
+ import {describe, expect, test} from 'vitest';
12
+ import {getLocaleDirection} from '../getLocaleDirection';
13
+
14
+ describe('getLocaleDirection', () => {
15
+ test('returns ltr for English', () => {
16
+ expect(getLocaleDirection('en')).toBe('ltr');
17
+ });
18
+
19
+ test('returns rtl for RTL languages', () => {
20
+ expect(getLocaleDirection('ar')).toBe('rtl');
21
+ expect(getLocaleDirection('he')).toBe('rtl');
22
+ expect(getLocaleDirection('fa')).toBe('rtl');
23
+ expect(getLocaleDirection('ur')).toBe('rtl');
24
+ });
25
+
26
+ test('returns ltr for regional LTR tags', () => {
27
+ expect(getLocaleDirection('pt-BR')).toBe('ltr');
28
+ expect(getLocaleDirection('zh-CN')).toBe('ltr');
29
+ });
30
+
31
+ test('falls back to ltr for a malformed locale without throwing', () => {
32
+ expect(() => getLocaleDirection('not_a_locale')).not.toThrow();
33
+ expect(getLocaleDirection('not_a_locale')).toBe('ltr');
34
+ });
35
+
36
+ test('falls back to ltr for an empty string without throwing', () => {
37
+ expect(() => getLocaleDirection('')).not.toThrow();
38
+ expect(getLocaleDirection('')).toBe('ltr');
39
+ });
40
+ });
@@ -0,0 +1,66 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useDirection.test.tsx
5
+ * @input packages/core/src/i18n/useDirection.ts, InternationalizationProvider
6
+ * @output Unit tests for the ambient text-direction hook
7
+ * @position Colocated tests; targets the default (no-provider) fallback,
8
+ * locale-derived direction, and the explicit `dir` override.
9
+ */
10
+
11
+ import type {ReactNode} from 'react';
12
+ import {describe, expect, test} from 'vitest';
13
+ import {renderHook} from '@testing-library/react';
14
+ import {useDirection} from '../useDirection';
15
+ import {InternationalizationProvider} from '../InternationalizationProvider';
16
+
17
+ describe('useDirection', () => {
18
+ test('returns ltr when rendered without a provider', () => {
19
+ const {result} = renderHook(() => useDirection());
20
+ expect(result.current).toBe('ltr');
21
+ });
22
+
23
+ test('returns ltr under an English provider', () => {
24
+ const {result} = renderHook(() => useDirection(), {
25
+ wrapper: ({children}: {children: ReactNode}) => (
26
+ <InternationalizationProvider locale="en">
27
+ {children}
28
+ </InternationalizationProvider>
29
+ ),
30
+ });
31
+ expect(result.current).toBe('ltr');
32
+ });
33
+
34
+ test('returns rtl under an Arabic provider', () => {
35
+ const {result} = renderHook(() => useDirection(), {
36
+ wrapper: ({children}: {children: ReactNode}) => (
37
+ <InternationalizationProvider locale="ar">
38
+ {children}
39
+ </InternationalizationProvider>
40
+ ),
41
+ });
42
+ expect(result.current).toBe('rtl');
43
+ });
44
+
45
+ test('explicit dir="rtl" wins over an LTR locale', () => {
46
+ const {result} = renderHook(() => useDirection(), {
47
+ wrapper: ({children}: {children: ReactNode}) => (
48
+ <InternationalizationProvider locale="en" dir="rtl">
49
+ {children}
50
+ </InternationalizationProvider>
51
+ ),
52
+ });
53
+ expect(result.current).toBe('rtl');
54
+ });
55
+
56
+ test('explicit dir="ltr" wins over an RTL locale', () => {
57
+ const {result} = renderHook(() => useDirection(), {
58
+ wrapper: ({children}: {children: ReactNode}) => (
59
+ <InternationalizationProvider locale="ar" dir="ltr">
60
+ {children}
61
+ </InternationalizationProvider>
62
+ ),
63
+ });
64
+ expect(result.current).toBe('ltr');
65
+ });
66
+ });
@@ -0,0 +1,29 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file getLocaleDirection.ts
5
+ * @input BCP 47 locale tag
6
+ * @output 'ltr' | 'rtl'
7
+ * @position Server-safe helper for computing text direction from a locale.
8
+ * Callable in React Server Components / Next.js layouts to set <html dir>.
9
+ *
10
+ * Uses Intl.Locale.getTextInfo() (CLDR-backed) with a try/catch fallback to
11
+ * 'ltr'. React 19 baseline covers all target browsers + Node ≥20; the older
12
+ * accessor form (`.textInfo`) is handled as a fallback for defensive parity.
13
+ *
14
+ * SYNC: When modified, update these files to stay in sync:
15
+ * - /packages/core/src/i18n/InternationalizationProvider.tsx (uses this to derive default)
16
+ * - /packages/core/src/i18n/useDirection.ts (client-side hook counterpart)
17
+ * - /packages/core/src/i18n/index.ts (public export)
18
+ */
19
+
20
+ export function getLocaleDirection(locale: string): 'ltr' | 'rtl' {
21
+ try {
22
+ const loc = new Intl.Locale(locale);
23
+ // @ts-expect-error — older engines expose accessor form `.textInfo`
24
+ const info = typeof loc.getTextInfo === 'function' ? loc.getTextInfo() : loc.textInfo;
25
+ return info?.direction === 'rtl' ? 'rtl' : 'ltr';
26
+ } catch {
27
+ return 'ltr';
28
+ }
29
+ }
package/src/i18n/index.ts CHANGED
@@ -10,10 +10,14 @@
10
10
  * The public API is deliberately small:
11
11
  * - InternationalizationProvider — provider component
12
12
  * - useTranslator — hook returning a translator function
13
+ * - useDirection — hook returning the ambient text direction
14
+ * - getLocaleDirection — server-safe locale → direction helper
13
15
  * - Translator — interface for consumer-injected runtimes
14
16
  *
15
17
  * SYNC: When modified, update these files to stay in sync:
16
18
  * - /packages/core/src/index.ts
19
+ * - /packages/core/src/i18n/useDirection.ts
20
+ * - /packages/core/src/i18n/getLocaleDirection.ts
17
21
  */
18
22
 
19
23
  export {InternationalizationProvider} from './InternationalizationProvider';
@@ -22,6 +26,8 @@ export {InternationalizationContext} from './InternationalizationContext';
22
26
  export type {InternationalizationContextValue} from './InternationalizationContext';
23
27
  export {useTranslator} from './useTranslator';
24
28
  export type {TranslatorFn} from './useTranslator';
29
+ export {useDirection} from './useDirection';
30
+ export {getLocaleDirection} from './getLocaleDirection';
25
31
  export type {Translator} from './translator';
26
32
  export type {
27
33
  Locale,
@@ -0,0 +1,29 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useDirection.ts
7
+ * @input InternationalizationContext (via use())
8
+ * @output 'ltr' | 'rtl'
9
+ * @position Client hook for reading the ambient text direction. Returns 'ltr'
10
+ * when called outside a provider (matches the silent-en-fallback pattern of
11
+ * useTranslator).
12
+ *
13
+ * Use this in components that need to swap directional icons, mirror
14
+ * behavioral logic (slider math, keyboard nav), or apply direction-specific
15
+ * styling that CSS logical properties can't express.
16
+ *
17
+ * SYNC: When modified, update these files to stay in sync:
18
+ * - /packages/core/src/i18n/InternationalizationContext.ts
19
+ * - /packages/core/src/i18n/getLocaleDirection.ts (server-safe counterpart)
20
+ * - /packages/core/src/i18n/index.ts
21
+ */
22
+
23
+ import {use} from 'react';
24
+ import {InternationalizationContext} from './InternationalizationContext';
25
+
26
+ export function useDirection(): 'ltr' | 'rtl' {
27
+ const ctx = use(InternationalizationContext);
28
+ return ctx.direction;
29
+ }
package/src/reset.css CHANGED
@@ -360,32 +360,6 @@
360
360
  -webkit-tap-highlight-color: transparent;
361
361
  }
362
362
 
363
- /* ===========================================================================
364
- Touch Devices
365
- =========================================================================== */
366
-
367
- /*
368
- * Suppress focus-visible outlines on touch-only devices.
369
- *
370
- * Focus rings exist to show keyboard users where focus is. On devices
371
- * with no hover capability and a coarse pointer (phones, tablets),
372
- * there's no keyboard navigation — focus rings are visual noise that
373
- * can flash on tap, on dialog open (showModal auto-focus), or on
374
- * programmatic focus.
375
- *
376
- * This targets touch-only devices specifically:
377
- * - (hover: none) — no hover capability (not a mouse)
378
- * - (pointer: coarse) — imprecise pointer (finger, not stylus)
379
- *
380
- * Hybrid devices (laptop with touchscreen) are NOT affected because
381
- * their primary pointer is fine (trackpad/mouse) and hover is available.
382
- */
383
- @media (hover: none) and (pointer: coarse) {
384
- :where(:focus-visible) {
385
- outline: none;
386
- }
387
- }
388
-
389
363
  /* ===========================================================================
390
364
  Color Scheme
391
365
  =========================================================================== */
@@ -0,0 +1,60 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * Tests for reset.css invariants that components rely on.
5
+ *
6
+ * reset.css is a plain stylesheet, so these tests assert on its source
7
+ * text (same approach as the reset.css baseline checks in
8
+ * theme/onMediaTokens.test.ts and AspectRatio/AspectRatio.test.tsx).
9
+ */
10
+
11
+ import fs from 'fs';
12
+ import path from 'path';
13
+
14
+ import {beforeAll, describe, expect, it} from 'vitest';
15
+
16
+ describe('reset.css accessibility invariants', () => {
17
+ let ruleBlocks: {selector: string; body: string}[];
18
+
19
+ beforeAll(() => {
20
+ const resetCSS = fs.readFileSync(
21
+ path.resolve(__dirname, './reset.css'),
22
+ 'utf-8',
23
+ );
24
+ // Strip comments, then collect every innermost `selector { body }`
25
+ // block. `[^{}]` keeps at-rule preludes (@media, @layer) out of the
26
+ // selector capture, since their own `{` terminates any earlier match.
27
+ const stripped = resetCSS.replace(/\/\*[\s\S]*?\*\//g, '');
28
+ ruleBlocks = [...stripped.matchAll(/([^{}]+)\{([^{}]*)\}/g)].map(
29
+ ([, selector, body]) => ({selector: selector.trim(), body}),
30
+ );
31
+ });
32
+
33
+ it('parses rule blocks out of reset.css', () => {
34
+ // Guards the WCAG assertion below against silently passing if the
35
+ // block parsing ever breaks.
36
+ expect(ruleBlocks.length).toBeGreaterThan(20);
37
+ expect(
38
+ ruleBlocks.some(({body}) => body.includes('box-sizing: border-box')),
39
+ ).toBe(true);
40
+ });
41
+
42
+ /**
43
+ * WCAG 2.4.7 (Focus Visible, Level AA): keyboard focus indicators must
44
+ * not be suppressed. `:focus-visible` only matches keyboard-style focus,
45
+ * so any `outline: none` on it removes the focus indicator for keyboard
46
+ * users — and a `@media (hover: none) and (pointer: coarse)` guard does
47
+ * not exempt keyboard users on coarse-pointer devices (iPad/phone with a
48
+ * Bluetooth keyboard, switch-control users).
49
+ */
50
+ it('does not suppress :focus-visible outlines (WCAG 2.4.7)', () => {
51
+ const focusVisibleBlocks = ruleBlocks.filter(({selector}) =>
52
+ selector.includes(':focus-visible'),
53
+ );
54
+ for (const {selector, body} of focusVisibleBlocks) {
55
+ expect
56
+ .soft(body, `":focus-visible" rule "${selector}" must keep the outline`)
57
+ .not.toMatch(/outline(?:-style|-width)?\s*:\s*(?:none|0)/);
58
+ }
59
+ });
60
+ });
@@ -186,6 +186,7 @@ function discoverComponents(): ComponentInfo[] {
186
186
  // ---------------------------------------------------------------------------
187
187
 
188
188
  const DIR_TO_REGISTRY_KEY: Record<string, string> = {
189
+ Avatar: 'avatar',
189
190
  Banner: 'banner',
190
191
  Button: 'button',
191
192
  Card: 'card',
@@ -35,6 +35,12 @@ export interface DerivedVarEntry {
35
35
  * entries share the same property.
36
36
  */
37
37
  export const derivedVarRegistry: Record<string, DerivedVarEntry[]> = {
38
+ avatar: [
39
+ {property: 'fontSize', vars: ['--_avatar-fallback-font-size']},
40
+ {property: 'fontWeight', vars: ['--_avatar-fallback-font-weight']},
41
+ {property: 'color', vars: ['--_avatar-fallback-color']},
42
+ {property: 'backgroundColor', vars: ['--_avatar-fallback-background']},
43
+ ],
38
44
  banner: [{property: 'borderRadius', vars: ['--_banner-radius']}],
39
45
  button: [{property: 'borderRadius', vars: ['--_button-radius']}],
40
46
  card: [
@@ -385,6 +385,48 @@ describe('derived var expansion', () => {
385
385
  expect(rule).toContain('--_button-radius: 8px');
386
386
  });
387
387
 
388
+ it('emits internal vars for avatar fallback typography (base)', () => {
389
+ const theme = defineTheme({
390
+ name: 'test-derived-avatar',
391
+ components: {
392
+ avatar: {
393
+ base: {
394
+ fontWeight: 'var(--font-weight-normal)',
395
+ color: 'var(--color-text-secondary)',
396
+ backgroundColor: 'var(--color-accent-muted)',
397
+ },
398
+ },
399
+ },
400
+ });
401
+ const rules = generateThemeRules(theme);
402
+ const rule = rules.find(r => r.includes('.astryx-avatar'));
403
+ expect(rule).toBeDefined();
404
+ expect(rule).toContain(
405
+ '--_avatar-fallback-font-weight: var(--font-weight-normal)',
406
+ );
407
+ expect(rule).toContain(
408
+ '--_avatar-fallback-color: var(--color-text-secondary)',
409
+ );
410
+ expect(rule).toContain(
411
+ '--_avatar-fallback-background: var(--color-accent-muted)',
412
+ );
413
+ });
414
+
415
+ it('emits a per-size fallback font-size for avatar (size:sm)', () => {
416
+ const theme = defineTheme({
417
+ name: 'test-derived-avatar-size',
418
+ components: {
419
+ avatar: {
420
+ 'size:sm': {fontSize: '9px'},
421
+ },
422
+ },
423
+ });
424
+ const rules = generateThemeRules(theme);
425
+ const rule = rules.find(r => r.includes('.astryx-avatar.sm'));
426
+ expect(rule).toBeDefined();
427
+ expect(rule).toContain('--_avatar-fallback-font-size: 9px');
428
+ });
429
+
388
430
  it('does not emit derived vars for components without registry entries', () => {
389
431
  const theme = defineTheme({
390
432
  name: 'test-no-derived',
@@ -98,3 +98,5 @@ export {
98
98
  export type {RGBA} from './color';
99
99
 
100
100
  export {devWarn, devError, warnOnce, formatDevMessage} from './devWarning';
101
+
102
+ export {rtlStyles} from './rtlStyles';
@@ -235,19 +235,37 @@ describe('plainDateAddMonths', () => {
235
235
  });
236
236
  });
237
237
 
238
- it('overflows day into next month when target month is shorter', () => {
238
+ it('clamps to the last day when the target month is shorter', () => {
239
+ // Jan 31 + 1 month lands in February (clamped), not overflowed into
240
+ // March — matching Temporal.PlainDate.add and date-fns addMonths.
239
241
  expect(plainDateAddMonths({year: 2026, month: 1, day: 31}, 1)).toEqual({
240
242
  year: 2026,
241
- month: 3,
242
- day: 3,
243
+ month: 2,
244
+ day: 28,
243
245
  });
244
246
  });
245
247
 
246
- it('overflows Jan 31 + 1 month in leap year', () => {
248
+ it('clamps to Feb 29 in a leap year', () => {
247
249
  expect(plainDateAddMonths({year: 2024, month: 1, day: 31}, 1)).toEqual({
248
250
  year: 2024,
249
- month: 3,
250
- day: 2,
251
+ month: 2,
252
+ day: 29,
253
+ });
254
+ });
255
+
256
+ it('clamps when subtracting into a shorter month', () => {
257
+ expect(plainDateAddMonths({year: 2026, month: 3, day: 31}, -1)).toEqual({
258
+ year: 2026,
259
+ month: 2,
260
+ day: 28,
261
+ });
262
+ });
263
+
264
+ it('clamps a 31st onto a 30-day month', () => {
265
+ expect(plainDateAddMonths({year: 2026, month: 5, day: 31}, 1)).toEqual({
266
+ year: 2026,
267
+ month: 6,
268
+ day: 30,
251
269
  });
252
270
  });
253
271
  });