@astryxdesign/core 0.1.9-canary.d2d84f8 → 0.1.9-canary.e00a241

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 (414) hide show
  1. package/dist/Avatar/Avatar.d.ts +6 -1
  2. package/dist/Avatar/Avatar.d.ts.map +1 -1
  3. package/dist/Avatar/Avatar.js +46 -12
  4. package/dist/Avatar/AvatarStatusDot.d.ts +6 -4
  5. package/dist/Avatar/AvatarStatusDot.d.ts.map +1 -1
  6. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  7. package/dist/Breadcrumbs/BreadcrumbItem.js +11 -4
  8. package/dist/Breadcrumbs/index.d.ts +1 -0
  9. package/dist/Breadcrumbs/index.d.ts.map +1 -1
  10. package/dist/Breadcrumbs/index.js +2 -1
  11. package/dist/Calendar/Calendar.d.ts.map +1 -1
  12. package/dist/Calendar/Calendar.js +50 -9
  13. package/dist/Calendar/dayCellUtils.d.ts +4 -4
  14. package/dist/Calendar/dayCellUtils.js +2 -2
  15. package/dist/Calendar/styles.d.ts +30 -31
  16. package/dist/Calendar/styles.d.ts.map +1 -1
  17. package/dist/Calendar/styles.js +30 -31
  18. package/dist/Card/Card.js +1 -1
  19. package/dist/Carousel/Carousel.d.ts.map +1 -1
  20. package/dist/Carousel/Carousel.js +22 -12
  21. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  22. package/dist/Chat/ChatDictationButton.js +3 -1
  23. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  24. package/dist/Chat/ChatMessageBubble.js +8 -8
  25. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  26. package/dist/Chat/ChatToolCalls.js +10 -3
  27. package/dist/CheckboxInput/CheckboxInput.js +4 -4
  28. package/dist/Citation/Citation.d.ts +22 -1
  29. package/dist/Citation/Citation.d.ts.map +1 -1
  30. package/dist/Citation/Citation.js +20 -6
  31. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  32. package/dist/CodeBlock/CodeBlock.js +13 -7
  33. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  34. package/dist/Collapsible/Collapsible.js +3 -1
  35. package/dist/CommandPalette/CommandPalette.d.ts +1 -1
  36. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  37. package/dist/CommandPalette/CommandPalette.js +42 -3
  38. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  39. package/dist/ContextMenu/ContextMenu.js +14 -5
  40. package/dist/ContextMenu/index.d.ts +1 -0
  41. package/dist/ContextMenu/index.d.ts.map +1 -1
  42. package/dist/ContextMenu/index.js +3 -1
  43. package/dist/DateInput/DateInput.d.ts +1 -0
  44. package/dist/DateInput/DateInput.d.ts.map +1 -1
  45. package/dist/DateInput/DateInput.js +31 -19
  46. package/dist/DateRangeInput/DateRangeInput.d.ts +1 -0
  47. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  48. package/dist/DateRangeInput/DateRangeInput.js +20 -19
  49. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  50. package/dist/DateTimeInput/DateTimeInput.js +23 -18
  51. package/dist/Divider/Divider.d.ts +2 -2
  52. package/dist/Divider/Divider.d.ts.map +1 -1
  53. package/dist/Divider/Divider.js +13 -0
  54. package/dist/DropdownMenu/DropdownMenu.d.ts +6 -0
  55. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  56. package/dist/DropdownMenu/DropdownMenu.js +22 -5
  57. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +4 -1
  59. package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
  60. package/dist/DropdownMenu/DropdownMenuItem.js +4 -1
  61. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -1
  62. package/dist/DropdownMenu/DropdownMenuRadioItem.js +4 -1
  63. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +104 -0
  64. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -0
  65. package/dist/DropdownMenu/DropdownMenuSubMenu.js +427 -0
  66. package/dist/DropdownMenu/index.d.ts +10 -0
  67. package/dist/DropdownMenu/index.d.ts.map +1 -1
  68. package/dist/DropdownMenu/index.js +13 -0
  69. package/dist/DropdownMenu/menuItemHover.d.ts +23 -0
  70. package/dist/DropdownMenu/menuItemHover.d.ts.map +1 -0
  71. package/dist/DropdownMenu/menuItemHover.js +33 -0
  72. package/dist/DropdownMenu/menuItemRoles.d.ts +8 -0
  73. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -1
  74. package/dist/DropdownMenu/menuItemRoles.js +10 -1
  75. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  76. package/dist/DropdownMenu/renderDropdownItems.js +21 -6
  77. package/dist/Field/Field.d.ts +3 -1
  78. package/dist/Field/Field.d.ts.map +1 -1
  79. package/dist/Field/Field.js +4 -1
  80. package/dist/Field/inputStyles.stylex.d.ts +1 -0
  81. package/dist/Field/inputStyles.stylex.d.ts.map +1 -1
  82. package/dist/Field/inputStyles.stylex.js +1 -0
  83. package/dist/FieldStatus/FieldStatus.d.ts +4 -1
  84. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  85. package/dist/FieldStatus/FieldStatus.js +4 -2
  86. package/dist/FileInput/FileInput.d.ts +1 -0
  87. package/dist/FileInput/FileInput.d.ts.map +1 -1
  88. package/dist/FileInput/FileInput.js +91 -65
  89. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  90. package/dist/HoverCard/useHoverCard.js +10 -4
  91. package/dist/Icon/Icon.d.ts +15 -1
  92. package/dist/Icon/Icon.d.ts.map +1 -1
  93. package/dist/Icon/Icon.js +19 -3
  94. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  95. package/dist/Lightbox/Lightbox.js +15 -9
  96. package/dist/List/List.d.ts.map +1 -1
  97. package/dist/List/List.js +6 -3
  98. package/dist/Markdown/Markdown.d.ts.map +1 -1
  99. package/dist/Markdown/Markdown.js +7 -0
  100. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  101. package/dist/MultiSelector/MultiSelector.js +65 -40
  102. package/dist/NumberInput/NumberInput.d.ts +1 -0
  103. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  104. package/dist/NumberInput/NumberInput.js +20 -17
  105. package/dist/Outline/Outline.js +2 -2
  106. package/dist/RadioList/RadioList.d.ts +2 -2
  107. package/dist/RadioList/RadioListItem.js +4 -4
  108. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  109. package/dist/Resizable/ResizeHandle.js +49 -19
  110. package/dist/SegmentedControl/SegmentedControlItem.d.ts +1 -1
  111. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  112. package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
  113. package/dist/Selector/Selector.d.ts.map +1 -1
  114. package/dist/Selector/Selector.js +58 -16
  115. package/dist/SideNav/SideNav.d.ts.map +1 -1
  116. package/dist/SideNav/SideNav.js +5 -5
  117. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  118. package/dist/SideNav/SideNavCollapseButton.js +13 -10
  119. package/dist/SideNav/SideNavItem.d.ts +1 -1
  120. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  121. package/dist/SideNav/SideNavItem.js +12 -10
  122. package/dist/Slider/Slider.d.ts.map +1 -1
  123. package/dist/Slider/Slider.js +87 -34
  124. package/dist/Spinner/Spinner.d.ts +3 -2
  125. package/dist/Spinner/Spinner.d.ts.map +1 -1
  126. package/dist/Spinner/Spinner.js +11 -2
  127. package/dist/Switch/Switch.d.ts +8 -1
  128. package/dist/Switch/Switch.d.ts.map +1 -1
  129. package/dist/Switch/Switch.js +160 -91
  130. package/dist/TabList/TabList.d.ts +2 -11
  131. package/dist/TabList/TabList.d.ts.map +1 -1
  132. package/dist/TabList/TabList.js +5 -9
  133. package/dist/TabList/TabListContext.d.ts +0 -5
  134. package/dist/TabList/TabListContext.d.ts.map +1 -1
  135. package/dist/TabList/TabListContext.js +0 -5
  136. package/dist/Table/BaseTable.d.ts.map +1 -1
  137. package/dist/Table/BaseTable.js +30 -4
  138. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  139. package/dist/Table/plugins/groupedRows/useTableGroupedRows.js +15 -11
  140. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  141. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +36 -26
  142. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  143. package/dist/Table/plugins/selection/useTableSelection.js +3 -0
  144. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -1
  145. package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +14 -6
  146. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -1
  147. package/dist/Table/plugins/tree/useTableTreeData.js +29 -23
  148. package/dist/Table/types.d.ts +32 -0
  149. package/dist/Table/types.d.ts.map +1 -1
  150. package/dist/TextArea/TextArea.d.ts +1 -0
  151. package/dist/TextArea/TextArea.d.ts.map +1 -1
  152. package/dist/TextArea/TextArea.js +16 -19
  153. package/dist/TextInput/TextInput.d.ts +1 -0
  154. package/dist/TextInput/TextInput.d.ts.map +1 -1
  155. package/dist/TextInput/TextInput.js +18 -17
  156. package/dist/TimeInput/TimeInput.d.ts +1 -0
  157. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  158. package/dist/TimeInput/TimeInput.js +15 -17
  159. package/dist/Timestamp/Timestamp.d.ts +31 -3
  160. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  161. package/dist/Timestamp/Timestamp.js +47 -76
  162. package/dist/Timestamp/formatInstant.d.ts +41 -0
  163. package/dist/Timestamp/formatInstant.d.ts.map +1 -0
  164. package/dist/Timestamp/formatInstant.js +177 -0
  165. package/dist/Timestamp/index.d.ts +1 -0
  166. package/dist/Timestamp/index.d.ts.map +1 -1
  167. package/dist/Timestamp/tooltipEntries.d.ts +56 -0
  168. package/dist/Timestamp/tooltipEntries.d.ts.map +1 -0
  169. package/dist/Timestamp/tooltipEntries.js +163 -0
  170. package/dist/Token/Token.d.ts +30 -3
  171. package/dist/Token/Token.d.ts.map +1 -1
  172. package/dist/Token/Token.js +17 -2
  173. package/dist/Token/index.d.ts +1 -1
  174. package/dist/Token/index.d.ts.map +1 -1
  175. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  176. package/dist/Tokenizer/Tokenizer.js +2 -2
  177. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  178. package/dist/Tooltip/useTooltip.js +17 -1
  179. package/dist/TopNav/TopNavItem.d.ts +3 -1
  180. package/dist/TopNav/TopNavItem.d.ts.map +1 -1
  181. package/dist/TopNav/TopNavItem.js +30 -3
  182. package/dist/TreeList/TreeList.d.ts.map +1 -1
  183. package/dist/TreeList/TreeList.js +1 -0
  184. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  185. package/dist/TreeList/TreeListBranches.js +8 -6
  186. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  187. package/dist/TreeList/TreeListItem.js +32 -14
  188. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  189. package/dist/Typeahead/BaseTypeahead.js +19 -10
  190. package/dist/Typeahead/Typeahead.js +1 -1
  191. package/dist/Typeahead/TypeaheadItem.d.ts +1 -1
  192. package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
  193. package/dist/Typeahead/TypeaheadItem.js +20 -10
  194. package/dist/astryx.css +38 -14
  195. package/dist/astryx.umd.js +52 -52
  196. package/dist/astryx.umd.js.map +4 -4
  197. package/dist/hooks/index.d.ts +2 -0
  198. package/dist/hooks/index.d.ts.map +1 -1
  199. package/dist/hooks/index.js +1 -0
  200. package/dist/hooks/isRtlElement.d.ts +18 -0
  201. package/dist/hooks/isRtlElement.d.ts.map +1 -0
  202. package/dist/hooks/isRtlElement.js +27 -0
  203. package/dist/hooks/useAnnounce.d.ts +3 -0
  204. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  205. package/dist/hooks/useAnnounce.js +39 -1
  206. package/dist/hooks/useFocusTrap.d.ts.map +1 -1
  207. package/dist/hooks/useFocusTrap.js +52 -11
  208. package/dist/hooks/useGridFocus.d.ts +6 -1
  209. package/dist/hooks/useGridFocus.d.ts.map +1 -1
  210. package/dist/hooks/useGridFocus.js +12 -9
  211. package/dist/hooks/useHotkeys.d.ts +9 -0
  212. package/dist/hooks/useHotkeys.d.ts.map +1 -1
  213. package/dist/hooks/useHotkeys.js +9 -0
  214. package/dist/hooks/useInputStatusIcon.d.ts +38 -0
  215. package/dist/hooks/useInputStatusIcon.d.ts.map +1 -0
  216. package/dist/hooks/useInputStatusIcon.js +158 -0
  217. package/dist/hooks/useListFocus.d.ts +39 -1
  218. package/dist/hooks/useListFocus.d.ts.map +1 -1
  219. package/dist/hooks/useListFocus.js +54 -9
  220. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  221. package/dist/theme/derivedVarRegistry.js +13 -0
  222. package/dist/utils/index.d.ts +1 -0
  223. package/dist/utils/index.d.ts.map +1 -1
  224. package/dist/utils/index.js +2 -1
  225. package/dist/utils/plainDate.d.ts +17 -0
  226. package/dist/utils/plainDate.d.ts.map +1 -1
  227. package/dist/utils/plainDate.js +23 -4
  228. package/dist/utils/rtlStyles.d.ts +24 -0
  229. package/dist/utils/rtlStyles.d.ts.map +1 -0
  230. package/dist/utils/rtlStyles.js +27 -0
  231. package/locales/en.json +76 -0
  232. package/locales/pseudo.json +57 -0
  233. package/package.json +6 -1
  234. package/src/Avatar/Avatar.doc.mjs +18 -4
  235. package/src/Avatar/Avatar.test.tsx +106 -0
  236. package/src/Avatar/Avatar.tsx +62 -10
  237. package/src/Avatar/AvatarStatusDot.doc.mjs +4 -4
  238. package/src/Avatar/AvatarStatusDot.tsx +6 -4
  239. package/src/AvatarGroup/AvatarGroup.test.tsx +16 -1
  240. package/src/Breadcrumbs/BreadcrumbItem.tsx +10 -10
  241. package/src/Breadcrumbs/Breadcrumbs.test.tsx +51 -0
  242. package/src/Breadcrumbs/index.ts +4 -0
  243. package/src/Button/Button.doc.mjs +0 -4
  244. package/src/Calendar/Calendar.test.tsx +170 -20
  245. package/src/Calendar/Calendar.tsx +73 -16
  246. package/src/Calendar/dayCellUtils.test.ts +18 -18
  247. package/src/Calendar/dayCellUtils.ts +2 -2
  248. package/src/Calendar/styles.ts +33 -39
  249. package/src/Card/Card.tsx +1 -1
  250. package/src/Carousel/Carousel.tsx +25 -6
  251. package/src/Chat/ChatComposer.doc.mjs +2 -2
  252. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  253. package/src/Chat/ChatDictationButton.tsx +7 -1
  254. package/src/Chat/ChatMessageBubble.tsx +15 -10
  255. package/src/Chat/ChatSystemMessage.test.tsx +5 -0
  256. package/src/Chat/ChatToolCalls.tsx +9 -2
  257. package/src/CheckboxInput/CheckboxInput.doc.mjs +6 -0
  258. package/src/CheckboxInput/CheckboxInput.tsx +4 -4
  259. package/src/CheckboxList/CheckboxList.doc.mjs +6 -0
  260. package/src/Citation/Citation.doc.mjs +4 -3
  261. package/src/Citation/Citation.test.tsx +97 -0
  262. package/src/Citation/Citation.tsx +43 -11
  263. package/src/CodeBlock/CodeBlock.tsx +35 -8
  264. package/src/Collapsible/Collapsible.doc.mjs +1 -0
  265. package/src/Collapsible/Collapsible.test.tsx +7 -0
  266. package/src/Collapsible/Collapsible.tsx +16 -6
  267. package/src/CommandPalette/CommandPalette.test.tsx +95 -1
  268. package/src/CommandPalette/CommandPalette.tsx +41 -2
  269. package/src/ContextMenu/ContextMenu.test.tsx +42 -0
  270. package/src/ContextMenu/ContextMenu.tsx +13 -11
  271. package/src/ContextMenu/index.ts +5 -0
  272. package/src/DateInput/DateInput.doc.mjs +15 -5
  273. package/src/DateInput/DateInput.test.tsx +210 -3
  274. package/src/DateInput/DateInput.tsx +41 -29
  275. package/src/DateRangeInput/DateRangeInput.doc.mjs +11 -3
  276. package/src/DateRangeInput/DateRangeInput.test.tsx +120 -3
  277. package/src/DateRangeInput/DateRangeInput.tsx +29 -27
  278. package/src/DateTimeInput/DateTimeInput.doc.mjs +22 -1
  279. package/src/DateTimeInput/DateTimeInput.test.tsx +112 -0
  280. package/src/DateTimeInput/DateTimeInput.tsx +44 -39
  281. package/src/Divider/Divider.test.tsx +32 -8
  282. package/src/Divider/Divider.tsx +14 -1
  283. package/src/DropdownMenu/DropdownMenu.test.tsx +60 -0
  284. package/src/DropdownMenu/DropdownMenu.tsx +31 -12
  285. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +8 -2
  286. package/src/DropdownMenu/DropdownMenuItem.tsx +8 -2
  287. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +8 -2
  288. package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +88 -0
  289. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +635 -0
  290. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +553 -0
  291. package/src/DropdownMenu/index.ts +16 -0
  292. package/src/DropdownMenu/menuItemHover.ts +39 -0
  293. package/src/DropdownMenu/menuItemRoles.ts +9 -0
  294. package/src/DropdownMenu/renderDropdownItems.tsx +29 -15
  295. package/src/Field/Field.tsx +14 -9
  296. package/src/Field/inputStyles.stylex.ts +2 -0
  297. package/src/FieldStatus/FieldStatus.tsx +10 -4
  298. package/src/FileInput/FileInput.doc.mjs +13 -7
  299. package/src/FileInput/FileInput.test.tsx +84 -16
  300. package/src/FileInput/FileInput.tsx +96 -79
  301. package/src/HoverCard/useHoverCard.tsx +15 -3
  302. package/src/Icon/Icon.doc.mjs +13 -0
  303. package/src/Icon/Icon.test.tsx +89 -0
  304. package/src/Icon/Icon.tsx +51 -3
  305. package/src/Lightbox/Lightbox.tsx +11 -3
  306. package/src/List/List.test.tsx +17 -29
  307. package/src/List/List.tsx +6 -1
  308. package/src/Markdown/Markdown.test.tsx +21 -0
  309. package/src/Markdown/Markdown.tsx +4 -0
  310. package/src/MultiSelector/MultiSelector.doc.mjs +11 -0
  311. package/src/MultiSelector/MultiSelector.test.tsx +306 -3
  312. package/src/MultiSelector/MultiSelector.tsx +88 -57
  313. package/src/NumberInput/NumberInput.doc.mjs +11 -5
  314. package/src/NumberInput/NumberInput.test.tsx +48 -0
  315. package/src/NumberInput/NumberInput.tsx +25 -26
  316. package/src/Outline/Outline.test.tsx +12 -12
  317. package/src/Outline/Outline.tsx +2 -2
  318. package/src/RadioList/RadioList.doc.mjs +6 -0
  319. package/src/RadioList/RadioList.tsx +2 -2
  320. package/src/RadioList/RadioListItem.tsx +4 -4
  321. package/src/Resizable/ResizeHandle.test.tsx +94 -0
  322. package/src/Resizable/ResizeHandle.tsx +70 -20
  323. package/src/SegmentedControl/SegmentedControlItem.tsx +2 -0
  324. package/src/Selector/Selector.doc.mjs +8 -0
  325. package/src/Selector/Selector.test.tsx +328 -3
  326. package/src/Selector/Selector.tsx +70 -19
  327. package/src/SideNav/SideNav.tsx +1 -8
  328. package/src/SideNav/SideNavCollapseButton.tsx +9 -7
  329. package/src/SideNav/SideNavItem.tsx +5 -3
  330. package/src/Slider/Slider.doc.mjs +6 -0
  331. package/src/Slider/Slider.test.tsx +240 -12
  332. package/src/Slider/Slider.tsx +96 -23
  333. package/src/Spinner/Spinner.test.tsx +10 -8
  334. package/src/Spinner/Spinner.tsx +15 -5
  335. package/src/Switch/Switch.doc.mjs +142 -42
  336. package/src/Switch/Switch.test.tsx +22 -0
  337. package/src/Switch/Switch.tsx +90 -33
  338. package/src/TabList/TabList.doc.mjs +0 -6
  339. package/src/TabList/TabList.test.tsx +3 -15
  340. package/src/TabList/TabList.tsx +6 -17
  341. package/src/TabList/TabListContext.ts +0 -6
  342. package/src/Table/BaseTable.tsx +34 -2
  343. package/src/Table/Table.doc.mjs +11 -0
  344. package/src/Table/Table.test.tsx +85 -0
  345. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +9 -6
  346. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +24 -17
  347. package/src/Table/plugins/selection/useTableSelection.test.tsx +61 -3
  348. package/src/Table/plugins/selection/useTableSelection.tsx +3 -0
  349. package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +37 -8
  350. package/src/Table/plugins/tree/useTableTreeData.tsx +17 -13
  351. package/src/Table/types.ts +33 -0
  352. package/src/Table/useTableRowStatus.doc.mjs +1 -1
  353. package/src/TextArea/TextArea.doc.mjs +11 -5
  354. package/src/TextArea/TextArea.tsx +17 -26
  355. package/src/TextInput/TextInput.doc.mjs +11 -5
  356. package/src/TextInput/TextInput.test.tsx +130 -3
  357. package/src/TextInput/TextInput.tsx +18 -25
  358. package/src/Thumbnail/Thumbnail.doc.mjs +1 -1
  359. package/src/TimeInput/TimeInput.doc.mjs +11 -5
  360. package/src/TimeInput/TimeInput.tsx +14 -25
  361. package/src/Timestamp/Timestamp.doc.mjs +32 -6
  362. package/src/Timestamp/Timestamp.test.tsx +371 -0
  363. package/src/Timestamp/Timestamp.tsx +96 -93
  364. package/src/Timestamp/formatInstant.ts +218 -0
  365. package/src/Timestamp/index.ts +4 -0
  366. package/src/Timestamp/tooltipEntries.test.ts +552 -0
  367. package/src/Timestamp/tooltipEntries.ts +210 -0
  368. package/src/Token/Token.test.tsx +21 -15
  369. package/src/Token/Token.tsx +31 -14
  370. package/src/Token/index.ts +1 -1
  371. package/src/Tokenizer/Tokenizer.doc.mjs +8 -1
  372. package/src/Tokenizer/Tokenizer.tsx +4 -2
  373. package/src/Tooltip/Tooltip.test.tsx +46 -0
  374. package/src/Tooltip/useTooltip.tsx +20 -0
  375. package/src/TopNav/TopNav.test.tsx +93 -2
  376. package/src/TopNav/TopNavItem.doc.mjs +3 -3
  377. package/src/TopNav/TopNavItem.tsx +35 -6
  378. package/src/TopNav/TopNavMegaMenuFeaturedCard.doc.mjs +1 -1
  379. package/src/TreeList/TreeList.doc.mjs +6 -0
  380. package/src/TreeList/TreeList.test.tsx +50 -10
  381. package/src/TreeList/TreeList.tsx +5 -0
  382. package/src/TreeList/TreeListBranches.tsx +8 -6
  383. package/src/TreeList/TreeListItem.tsx +33 -11
  384. package/src/Typeahead/BaseTypeahead.tsx +29 -14
  385. package/src/Typeahead/Typeahead.doc.mjs +6 -0
  386. package/src/Typeahead/Typeahead.test.tsx +27 -0
  387. package/src/Typeahead/Typeahead.tsx +1 -1
  388. package/src/Typeahead/TypeaheadItem.tsx +2 -1
  389. package/src/__tests__/inputWidthContract.test.tsx +62 -0
  390. package/src/doc-mjs.d.ts +19 -0
  391. package/src/hooks/index.ts +6 -0
  392. package/src/hooks/isRtlElement.ts +27 -0
  393. package/src/hooks/useAnnounce.test.tsx +75 -2
  394. package/src/hooks/useAnnounce.ts +47 -1
  395. package/src/hooks/useFocusTrap.test.tsx +104 -0
  396. package/src/hooks/useFocusTrap.ts +61 -12
  397. package/src/hooks/useGridFocus.doc.mjs +3 -3
  398. package/src/hooks/useGridFocus.test.tsx +41 -0
  399. package/src/hooks/useGridFocus.ts +21 -10
  400. package/src/hooks/useHotkeys.ts +9 -0
  401. package/src/hooks/useInputStatusIcon.test.tsx +162 -0
  402. package/src/hooks/useInputStatusIcon.tsx +224 -0
  403. package/src/hooks/useListFocus.doc.mjs +25 -3
  404. package/src/hooks/useListFocus.test.tsx +153 -1
  405. package/src/hooks/useListFocus.ts +102 -9
  406. package/src/reset.css +0 -26
  407. package/src/reset.test.ts +60 -0
  408. package/src/theme/derivedVarRegistry.test.ts +1 -0
  409. package/src/theme/derivedVarRegistry.ts +6 -0
  410. package/src/theme/generateThemeRules.test.ts +42 -0
  411. package/src/utils/index.ts +2 -0
  412. package/src/utils/plainDate.test.ts +24 -6
  413. package/src/utils/plainDate.ts +18 -4
  414. package/src/utils/rtlStyles.ts +27 -0
@@ -32,6 +32,18 @@ export const docs = {
32
32
  {className: 'astryx-avatar-status-dot', visualProps: ['variant']},
33
33
  {className: 'astryx-avatar-status-dot-glyph', visualProps: ['shape']},
34
34
  ],
35
+ vars: [
36
+ {name: '--_avatar-fallback-font-size', description: 'Initials font size; default is proportional to the avatar size (size × 0.4). Override per size tier (e.g. size:sm) for a custom type scale.', default: 'calc(avatar-size × 0.4)', private: true},
37
+ {name: '--_avatar-fallback-font-weight', description: 'Initials font weight', default: 'var(--font-weight-medium)', private: true},
38
+ {name: '--_avatar-fallback-color', description: 'Fallback text and default-icon color', default: 'var(--color-text-secondary)', private: true},
39
+ {name: '--_avatar-fallback-background', description: 'Fallback wash background fill', default: 'var(--color-neutral)', private: true},
40
+ ],
41
+ derived: [
42
+ {property: 'fontSize', vars: ['--_avatar-fallback-font-size']},
43
+ {property: 'fontWeight', vars: ['--_avatar-fallback-font-weight']},
44
+ {property: 'color', vars: ['--_avatar-fallback-color']},
45
+ {property: 'backgroundColor', vars: ['--_avatar-fallback-background']},
46
+ ],
35
47
  },
36
48
  description: 'Displays a user avatar with image, initials fallback, and optional status indicator.',
37
49
  props: [
@@ -64,7 +76,7 @@ export const docs = {
64
76
  {
65
77
  name: 'status',
66
78
  type: 'ReactNode',
67
- description: 'Corner content for status indicators.',
79
+ description: 'Corner content for status indicators. A string `label` on the element (as on AvatarStatusDot) is composed into the avatar\'s accessible name (e.g. "Jane Doe, Online") so screen readers announce the status.',
68
80
  slotElements: [
69
81
  {
70
82
  __element: 'AvatarStatusDot',
@@ -79,14 +91,14 @@ export const docs = {
79
91
  name: 'tooltip',
80
92
  type: 'string | boolean',
81
93
  description:
82
- "Tooltip shown on hover and keyboard focus. Omitted or true shows the avatar's name; a string shows that text instead; false shows no tooltip. Not auto-disabled when wrapped in your own Tooltip/HoverCard — set tooltip={false} if you supply your own overlay. No tooltip is shown when tooltip is true/omitted and there is no name.",
94
+ "Tooltip shown on hover and keyboard focus. Omitted or true shows the avatar's name; a string shows that text instead; false shows no tooltip. Not auto-disabled when wrapped in your own Tooltip/HoverCard. Set tooltip={false} if you supply your own overlay. No tooltip is shown when tooltip is true/omitted and there is no name.",
83
95
  default: 'true',
84
96
  },
85
97
  {
86
98
  name: 'href',
87
99
  type: 'string',
88
100
  description:
89
- 'When set, the avatar renders as an interactive link (`<a>` or a custom link component) pointing here — the same element-swap rule as Button. Requires a meaningful accessible name via `alt` or `name`. Inside an AvatarGroup, interactive avatars share a single Tab stop and are reached with arrow keys.',
101
+ 'When set, the avatar renders as an interactive link (`<a>` or a custom link component) pointing here. This follows the same element-swap rule as Button. Requires a meaningful accessible name via `alt` or `name`. Inside an AvatarGroup, interactive avatars share a single Tab stop and are reached with arrow keys.',
90
102
  },
91
103
  {
92
104
  name: 'as',
@@ -140,6 +152,7 @@ export const docsDense = {
140
152
  {guidance: true, description: 'Always pass a name for initials fallback and screen reader alt text.'},
141
153
  {guidance: true, description: 'Match size to context: xsm/sm inline, md/lg in lists, xl for profiles.'},
142
154
  {guidance: true, description: 'Add a status dot in chat or team views where availability matters.'},
155
+ {guidance: true, description: 'When wrapping Avatar in your own Tooltip or HoverCard, set tooltip={false} so the built-in name tooltip does not overlap yours.'},
143
156
  {guidance: false, description: 'Use for logos or product images. Use an image or icon instead.'},
144
157
  {guidance: false, description: 'Force a square or custom shape. Avatars are always circular.'},
145
158
  ],
@@ -150,7 +163,8 @@ export const docsDense = {
150
163
  name: 'user name for initials and alt text',
151
164
  alt: 'alt text; falls back to name',
152
165
  size: "avatar size. Named ('xsm' 20px, 'sm' 24px, 'md' 36px, 'lg' 48px, 'xl' 128px) or numeric px.",
153
- status: 'corner content for status indicators',
166
+ status:
167
+ 'corner content for status indicators; its `label` is composed into the avatar accessible name ("Jane Doe, Online")',
154
168
  tooltip:
155
169
  "hover/focus tooltip. true/omitted → name; string → that text; false → none. Owns its tooltip; set false when wrapping in your own Tooltip/HoverCard. Default true.",
156
170
  href: 'renders avatar as a link (<a>/custom). Needs alt/name. Button-style element swap.',
@@ -4,6 +4,7 @@ import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
4
4
  import {render, screen, fireEvent} from '@testing-library/react';
5
5
  import userEvent from '@testing-library/user-event';
6
6
  import {Avatar} from './Avatar';
7
+ import {AvatarStatusDot} from './AvatarStatusDot';
7
8
 
8
9
  describe('Avatar', () => {
9
10
  it('exposes role="img" with the name as accessible name', () => {
@@ -36,6 +37,19 @@ describe('Avatar', () => {
36
37
  expect(innerImg).toHaveAttribute('alt', '');
37
38
  });
38
39
 
40
+ it('renders fallback initials through the themeable font-size var, not a bare px literal', () => {
41
+ render(<Avatar name="Ada Lovelace" size="sm" data-testid="a" />);
42
+ const initials = screen.getByText('AL');
43
+ // The seam: the dynamic font size resolves to the Avatar-scoped var (with
44
+ // the proportional `size × 0.4` default baked in as the fallback), so a
45
+ // theme can re-scope it per size. A regression to a bare px literal would
46
+ // break theming.
47
+ const style = initials.getAttribute('style') ?? '';
48
+ expect(style).toContain('var(--_avatar-fallback-font-size,');
49
+ // Default still reproduces the proportional scale (sm = 24 × 0.4 = 9.6px).
50
+ expect(style).toMatch(/var\(--_avatar-fallback-font-size,\s*9\.6\d*px\)/);
51
+ });
52
+
39
53
  it('retries a new src after a previous src failed to load', () => {
40
54
  const {rerender} = render(
41
55
  <Avatar name="Ada" src="https://example.com/broken.jpg" />,
@@ -53,6 +67,98 @@ describe('Avatar', () => {
53
67
  expect(img).toHaveAttribute('src', 'https://example.com/ada.jpg');
54
68
  });
55
69
 
70
+ describe('status in the accessible name (WCAG 4.1.2)', () => {
71
+ // The avatar root is role="img", which prunes descendant semantics —
72
+ // a label inside the status subtree is never announced on its own.
73
+ // Avatar composes the status element's `label` into its own name.
74
+ it('composes the status label into the accessible name for image avatars', () => {
75
+ render(
76
+ <Avatar
77
+ name="Ada Lovelace"
78
+ src="https://example.com/ada.jpg"
79
+ status={<AvatarStatusDot variant="success" label="Online" />}
80
+ />,
81
+ );
82
+ expect(
83
+ screen.getByRole('img', {name: 'Ada Lovelace, Online'}),
84
+ ).toBeInTheDocument();
85
+ });
86
+
87
+ it('composes the status label into the accessible name for initials avatars', () => {
88
+ render(
89
+ <Avatar
90
+ name="Ada Lovelace"
91
+ status={<AvatarStatusDot variant="error" label="Busy" />}
92
+ />,
93
+ );
94
+ const avatar = screen.getByRole('img', {name: 'Ada Lovelace, Busy'});
95
+ expect(avatar).toBeInTheDocument();
96
+ expect(avatar).toHaveTextContent('AL');
97
+ });
98
+
99
+ it('composes the status label with alt when alt overrides name', () => {
100
+ render(
101
+ <Avatar
102
+ name="Ada"
103
+ alt="Ada Lovelace, profile photo"
104
+ status={<AvatarStatusDot label="Online" />}
105
+ />,
106
+ );
107
+ expect(
108
+ screen.getByRole('img', {name: 'Ada Lovelace, profile photo, Online'}),
109
+ ).toBeInTheDocument();
110
+ });
111
+
112
+ it('keeps the plain name when there is no status', () => {
113
+ render(<Avatar name="Ada Lovelace" data-testid="a" />);
114
+ expect(screen.getByTestId('a')).toHaveAttribute(
115
+ 'aria-label',
116
+ 'Ada Lovelace',
117
+ );
118
+ });
119
+
120
+ it('keeps the plain name when the status dot has no label', () => {
121
+ render(
122
+ <Avatar
123
+ name="Ada Lovelace"
124
+ data-testid="a"
125
+ status={<AvatarStatusDot variant="success" />}
126
+ />,
127
+ );
128
+ expect(screen.getByTestId('a')).toHaveAttribute(
129
+ 'aria-label',
130
+ 'Ada Lovelace',
131
+ );
132
+ });
133
+
134
+ it('keeps the plain name for custom status nodes without a label prop', () => {
135
+ render(<Avatar name="Ada Lovelace" data-testid="a" status={<span />} />);
136
+ expect(screen.getByTestId('a')).toHaveAttribute(
137
+ 'aria-label',
138
+ 'Ada Lovelace',
139
+ );
140
+ });
141
+
142
+ it('announces a labelled status even on an otherwise decorative avatar', () => {
143
+ // A labelled status is meaningful information on its own — the avatar
144
+ // must not stay aria-hidden and swallow it.
145
+ render(
146
+ <Avatar data-testid="a" status={<AvatarStatusDot label="Online" />} />,
147
+ );
148
+ const el = screen.getByTestId('a');
149
+ expect(el).toHaveAttribute('role', 'img');
150
+ expect(el).toHaveAttribute('aria-label', 'Online');
151
+ expect(el).not.toHaveAttribute('aria-hidden');
152
+ });
153
+
154
+ it('stays decorative with an unlabelled status and no name', () => {
155
+ render(<Avatar data-testid="a" status={<AvatarStatusDot />} />);
156
+ const el = screen.getByTestId('a');
157
+ expect(el).toHaveAttribute('aria-hidden', 'true');
158
+ expect(el).not.toHaveAttribute('aria-label');
159
+ });
160
+ });
161
+
56
162
  it('retries a new fallbackSrc after a previous fallbackSrc failed to load', () => {
57
163
  const {rerender} = render(
58
164
  <Avatar name="Ada" fallbackSrc="https://example.com/broken.jpg" />,
@@ -5,7 +5,7 @@
5
5
  /**
6
6
  * @file Avatar.tsx
7
7
  * @input Uses React, HTMLAttributes, ReactNode, useState; useTooltip
8
- * (Tooltip hook) for the optional name-on-hover tooltip
8
+ * (Tooltip hook) for the optional name-on-hover tooltip; useTranslator (i18n)
9
9
  * @output Exports Avatar component, AvatarProps, AvatarSize types
10
10
  * @position Core implementation; consumed by index.ts
11
11
  *
@@ -18,7 +18,7 @@
18
18
  * Last synced props: alt, fallbackSrc, name, size, src, status, href, as, target, rel, onClick
19
19
  */
20
20
 
21
- import {useMemo, useState, type ReactNode} from 'react';
21
+ import {isValidElement, useMemo, useState, type ReactNode} from 'react';
22
22
  import type {BaseProps} from '../BaseProps';
23
23
  import * as stylex from '@stylexjs/stylex';
24
24
  import {
@@ -34,6 +34,7 @@ import {themeProps} from '../utils/themeProps';
34
34
  import {useTooltip} from '../Tooltip/useTooltip';
35
35
  import {useLinkComponent} from '../Link/useLinkComponent';
36
36
  import type {LinkComponentType} from '../Link/types';
37
+ import {useTranslator} from '../i18n';
37
38
 
38
39
  /**
39
40
  * The offset ratio for positioning elements on a circle's edge at 45°.
@@ -110,6 +111,11 @@ const styles = stylex.create({
110
111
  position: 'relative',
111
112
  display: 'inline-flex',
112
113
  flexShrink: 0,
114
+ // The wrapper is not clipped (so the status dot can overflow), so it must be
115
+ // rounded itself: a themed fallback background lands on `.astryx-avatar` (the
116
+ // class-bearing wrapper) as well as the internal var, and an unrounded
117
+ // wrapper would show that fill as square corners behind the circular content.
118
+ borderRadius: radiusVars['--radius-full'],
113
119
  },
114
120
  content: {
115
121
  display: 'flex',
@@ -130,10 +136,14 @@ const styles = stylex.create({
130
136
  justifyContent: 'center',
131
137
  width: '100%',
132
138
  height: '100%',
133
- backgroundColor: colorVars['--color-neutral'],
134
- color: colorVars['--color-text-secondary'],
139
+ // Fallback surface (initials + default icon). Each property reads an
140
+ // Avatar-scoped internal var so a theme can re-scope the fallback wash and
141
+ // initials weight/color without forking; the defaults reproduce today's
142
+ // exact output. See derivedVarRegistry (avatar) + Avatar.doc.mjs theming.
143
+ backgroundColor: `var(--_avatar-fallback-background, ${colorVars['--color-neutral']})`,
144
+ color: `var(--_avatar-fallback-color, ${colorVars['--color-text-secondary']})`,
135
145
  fontFamily: typographyVars['--font-family-body'],
136
- fontWeight: fontWeightVars['--font-weight-medium'],
146
+ fontWeight: `var(--_avatar-fallback-font-weight, ${fontWeightVars['--font-weight-medium']})`,
137
147
  textTransform: 'uppercase',
138
148
  },
139
149
  status: {
@@ -195,8 +205,13 @@ const dynamicStyles = stylex.create({
195
205
  width: size,
196
206
  height: size,
197
207
  }),
208
+ // Initials font size defaults to the proportional `size × ratio` scale but is
209
+ // reachable via the `--_avatar-fallback-font-size` derived var, so a theme can
210
+ // set a per-size type scale (e.g. `components.avatar['size:sm']`).
198
211
  fontSize: (size: number) => ({
199
- fontSize: size * INITIALS_FONT_SIZE_RATIO,
212
+ fontSize: `var(--_avatar-fallback-font-size, ${
213
+ size * INITIALS_FONT_SIZE_RATIO
214
+ }px)`,
200
215
  }),
201
216
  statusPosition: (size: number) => ({
202
217
  bottom: size * CIRCLE_EDGE_OFFSET_RATIO,
@@ -266,6 +281,11 @@ export interface AvatarProps extends BaseProps<HTMLDivElement> {
266
281
  /**
267
282
  * Content displayed in the corner of the avatar.
268
283
  * Typically used for status indicators or badges.
284
+ *
285
+ * When the element carries a string `label` prop (as `AvatarStatusDot`
286
+ * does), the label is composed into the avatar's accessible name
287
+ * (e.g. "Jane Doe, Online") so assistive tech can reach the status —
288
+ * the `role="img"` root prunes descendant semantics (WCAG 4.1.2).
269
289
  */
270
290
  status?: ReactNode;
271
291
  /**
@@ -332,6 +352,24 @@ function getInitials(name: string): string {
332
352
  return (words[0].charAt(0) + words[words.length - 1].charAt(0)).toUpperCase();
333
353
  }
334
354
 
355
+ /**
356
+ * Reads the accessible status label off the `status` element, when it
357
+ * exposes one. `AvatarStatusDot`'s `label` prop is the canonical source,
358
+ * but any custom status element with a string `label` prop participates.
359
+ *
360
+ * The avatar root is `role="img"`, which prunes ALL descendant semantics
361
+ * from the accessibility tree — a label inside the status subtree is never
362
+ * announced on its own. Composing it into the avatar's own accessible name
363
+ * is the only way the status reaches assistive tech (WCAG 4.1.2).
364
+ */
365
+ function getStatusLabel(status: ReactNode): string | undefined {
366
+ if (!isValidElement(status)) {
367
+ return undefined;
368
+ }
369
+ const {label} = status.props as {label?: unknown};
370
+ return typeof label === 'string' && label !== '' ? label : undefined;
371
+ }
372
+
335
373
  /**
336
374
  * Default person icon SVG for when no image or name is provided
337
375
  */
@@ -398,10 +436,24 @@ export function Avatar({
398
436
  const showInitials = !showImage && !showFallbackImage && name;
399
437
  const showIcon = !showImage && !showFallbackImage && !name;
400
438
 
401
- // A meaningful accessible name comes from `alt` or `name`. With neither, the
402
- // avatar is decorative — expose it as `presentation`/`aria-hidden` rather than
403
- // announcing a meaningless generic "Avatar" (obs-9).
404
- const accessibleName = alt || name;
439
+ // A meaningful accessible name comes from `alt`/`name`, composed with the
440
+ // status element's `label` when one is present ("Jane Doe, Online") — the
441
+ // `role="img"` root prunes descendant semantics, so surfacing the label in
442
+ // the avatar's own name is the only way assistive tech can reach the
443
+ // status (WCAG 4.1.2). A labelled status alone is also meaningful. With
444
+ // neither a name nor a labelled status, the avatar is decorative — expose
445
+ // it as `presentation`/`aria-hidden` rather than announcing a meaningless
446
+ // generic "Avatar" (obs-9).
447
+ const t = useTranslator();
448
+ const nameLabel = alt || name;
449
+ const statusLabel = getStatusLabel(status);
450
+ const accessibleName =
451
+ nameLabel && statusLabel
452
+ ? t('@astryx.avatar.nameWithStatus', {
453
+ name: nameLabel,
454
+ status: statusLabel,
455
+ })
456
+ : nameLabel || statusLabel;
405
457
  const isDecorative = !accessibleName;
406
458
  const avatarGroup = useAvatarGroup();
407
459
  const resolvedSize = avatarGroup?.size ?? size;
@@ -21,7 +21,7 @@ export const docs = {
21
21
  name: 'label',
22
22
  type: 'string',
23
23
  description:
24
- 'Accessible label describing the status. Not announced inside an Avatar today (the Avatar root is role="img", which prunes child semantics) — pass it anyway; an Avatar-level name-composition fix is planned.',
24
+ 'Accessible label describing the status. Inside an Avatar it is composed into the avatar\'s accessible name (e.g. "Jane Doe, Online"); the Avatar root is role="img", which prunes child semantics, so the composed name is how the status reaches assistive tech. Standalone dots expose role="img" with this label directly.',
25
25
  },
26
26
  {
27
27
  name: 'icon',
@@ -59,7 +59,7 @@ export const docsZh = {
59
59
  name: 'label',
60
60
  type: 'string',
61
61
  description:
62
- '描述状态的无障碍标签。目前在 Avatar 内不会被朗读(Avatar 根元素为 role="img",会裁剪子元素语义)——仍请传入;Avatar 层面的名称合成修复已在计划中。',
62
+ '描述状态的无障碍标签。在 Avatar 内会被合成进头像自身的无障碍名称(如 "Jane Doe, Online")——Avatar 根元素为 role="img",会裁剪子元素语义,因此合成名称是辅助技术获知状态的途径。独立使用时状态点直接以 role="img" 暴露该标签。',
63
63
  },
64
64
  {
65
65
  name: 'icon',
@@ -80,7 +80,7 @@ export const docsDense = {
80
80
  variant:
81
81
  'colour + shape variant: success filled, neutral ring, error minus',
82
82
  label:
83
- 'accessible status label (not yet announced inside Avatar — role="img" prunes children)',
84
- icon: 'icon centered in dot (hidden at tiny sizes); replaces the built-in shape glyph — differ it per status',
83
+ 'accessible status label; composed into the Avatar accessible name ("Jane Doe, Online"); standalone dots expose role="img" with it directly',
84
+ icon: 'icon centered in dot (hidden at tiny sizes); replaces the built-in shape glyph, so differ it per status',
85
85
  },
86
86
  };
@@ -115,10 +115,12 @@ export interface AvatarStatusDotProps extends BaseProps<HTMLDivElement> {
115
115
  * Describes the meaning of the indicator for screen readers
116
116
  * (e.g. "Online", "Accepted", "John Doe is busy").
117
117
  *
118
- * Note: inside an Avatar the label is currently not announced — the
119
- * Avatar root is `role="img"`, which prunes descendant semantics.
120
- * Pass it anyway; composing status into the avatar's accessible name
121
- * is a planned Avatar-level fix.
118
+ * Note: inside an Avatar the dot sits in the avatar's `role="img"`
119
+ * subtree, where descendant semantics are pruned — the dot is never its
120
+ * own stop there. Instead, Avatar reads this `label` and composes it into
121
+ * its own accessible name (e.g. "Jane Doe, Online"), which is how the
122
+ * status reaches assistive tech (WCAG 4.1.2). Standalone dots (outside an
123
+ * Avatar) expose `role="img"` with this label directly.
122
124
  */
123
125
  label?: string;
124
126
  /**
@@ -4,7 +4,7 @@ import {render, screen} from '@testing-library/react';
4
4
  import userEvent from '@testing-library/user-event';
5
5
  import {AvatarGroup} from './AvatarGroup';
6
6
  import {AvatarGroupOverflow} from './AvatarGroupOverflow';
7
- import {Avatar} from '../Avatar';
7
+ import {Avatar, AvatarStatusDot} from '../Avatar';
8
8
 
9
9
  describe('AvatarGroup', () => {
10
10
  it('renders all avatar children', () => {
@@ -21,6 +21,21 @@ describe('AvatarGroup', () => {
21
21
  expect(screen.getByLabelText('Charlie')).toBeInTheDocument();
22
22
  });
23
23
 
24
+ it('composes a labelled status into a grouped avatar accessible name (WCAG 4.1.2)', () => {
25
+ render(
26
+ <AvatarGroup>
27
+ <Avatar
28
+ name="Alice"
29
+ status={<AvatarStatusDot variant="success" label="Online" />}
30
+ />
31
+ <Avatar name="Bob" />
32
+ </AvatarGroup>,
33
+ );
34
+
35
+ expect(screen.getByLabelText('Alice, Online')).toBeInTheDocument();
36
+ expect(screen.getByLabelText('Bob')).toBeInTheDocument();
37
+ });
38
+
24
39
  it('renders with role="group" and default aria-label', () => {
25
40
  render(
26
41
  <AvatarGroup>
@@ -59,6 +59,7 @@ import {
59
59
  import {
60
60
  MENU_ITEM_ROLES,
61
61
  MENU_ITEM_SELECTOR,
62
+ MENU_BOUNDARY_SELECTOR,
62
63
  } from '../DropdownMenu/menuItemRoles';
63
64
  import type {DropdownMenuOption} from '../DropdownMenu/DropdownMenu';
64
65
 
@@ -515,21 +516,15 @@ function BreadcrumbMenuTrigger({
515
516
  handleKeyDown: listNavKeyDown,
516
517
  focusFirst,
517
518
  focusItem,
519
+ ownsEvent,
520
+ getItems: getMenuItems,
518
521
  } = useListFocus<HTMLDivElement>({
519
522
  itemSelector: MENU_ITEM_SELECTOR,
523
+ boundarySelector: MENU_BOUNDARY_SELECTOR,
520
524
  wrap: false,
521
525
  onEscape: closeMenu,
522
526
  });
523
527
 
524
- const getMenuItems = useCallback(
525
- (): HTMLElement[] =>
526
- listRef.current
527
- ? Array.from(
528
- listRef.current.querySelectorAll<HTMLElement>(MENU_ITEM_SELECTOR),
529
- )
530
- : [],
531
- [listRef],
532
- );
533
528
  const typeahead = useTypeahead({
534
529
  getItemLabels: () => getMenuItems().map(el => el.textContent),
535
530
  onMatch: focusItem,
@@ -542,6 +537,11 @@ function BreadcrumbMenuTrigger({
542
537
 
543
538
  const listKeyDown = useCallback(
544
539
  (e: React.KeyboardEvent) => {
540
+ // A submenu flyout renders inline inside this menu; its key events bubble
541
+ // up here. Let that level own them — only handle events from this level.
542
+ if (!ownsEvent(e)) {
543
+ return;
544
+ }
545
545
  if (e.key === 'Enter' || e.key === ' ') {
546
546
  e.preventDefault();
547
547
  const focused = document.activeElement as HTMLElement | null;
@@ -564,7 +564,7 @@ function BreadcrumbMenuTrigger({
564
564
  }
565
565
  listNavKeyDown(e);
566
566
  },
567
- [listNavKeyDown, closeMenu, typeahead],
567
+ [listNavKeyDown, closeMenu, typeahead, ownsEvent],
568
568
  );
569
569
 
570
570
  const openAndFocus = useCallback(() => {
@@ -589,6 +589,57 @@ describe('BreadcrumbItem menu', () => {
589
589
  ).toHaveFocus();
590
590
  });
591
591
 
592
+ it('renders a submenu from a nested items array and keyboard-reaches an item after it', async () => {
593
+ // Breadcrumb menus reuse the DropdownMenu item pipeline, so a nested
594
+ // `items` array becomes a submenu. The inline flyout must not pollute the
595
+ // breadcrumb menu's roving order — an item after the submenu row stays
596
+ // keyboard-reachable.
597
+ const onDelete = vi.fn();
598
+ render(
599
+ <Breadcrumbs>
600
+ <BreadcrumbItem
601
+ menu={[
602
+ {label: 'Rename', onClick: vi.fn()},
603
+ {
604
+ label: 'Move to',
605
+ items: [
606
+ {label: 'Folder A', onClick: vi.fn()},
607
+ {label: 'Folder B', onClick: vi.fn()},
608
+ ],
609
+ },
610
+ {type: 'divider'},
611
+ {label: 'Delete', onClick: onDelete},
612
+ ]}>
613
+ Teams
614
+ </BreadcrumbItem>
615
+ <BreadcrumbItem isCurrent>Overview</BreadcrumbItem>
616
+ </Breadcrumbs>,
617
+ );
618
+ const trigger = screen.getByRole('button', {name: 'Teams'});
619
+ trigger.focus();
620
+ fireEvent.keyDown(trigger, {key: 'ArrowDown'});
621
+ // Two role="menu" exist (the breadcrumb menu + the inline submenu flyout);
622
+ // the breadcrumb menu is the first in DOM order.
623
+ const menu = screen.getAllByRole('menu', {hidden: true})[0];
624
+ const submenuTrigger = screen.getByRole('menuitem', {
625
+ name: /Move to/,
626
+ hidden: true,
627
+ });
628
+ expect(submenuTrigger).toHaveAttribute('aria-haspopup', 'menu');
629
+ await waitFor(() => {
630
+ expect(
631
+ screen.getByRole('menuitem', {name: 'Rename', hidden: true}),
632
+ ).toHaveFocus();
633
+ });
634
+ // Rename → Move to → Delete, one step per press.
635
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
636
+ expect(submenuTrigger).toHaveFocus();
637
+ fireEvent.keyDown(menu, {key: 'ArrowDown'});
638
+ expect(
639
+ screen.getByRole('menuitem', {name: 'Delete', hidden: true}),
640
+ ).toHaveFocus();
641
+ });
642
+
592
643
  it('allows menu together with isCurrent (both aria-current and aria-haspopup)', () => {
593
644
  render(
594
645
  <Breadcrumbs>
@@ -35,6 +35,10 @@ export {
35
35
  DropdownMenuRadioItem as BreadcrumbMenuRadioItem,
36
36
  type DropdownMenuRadioItemProps as BreadcrumbMenuRadioItemProps,
37
37
  } from '../DropdownMenu/DropdownMenuRadioItem';
38
+ export {
39
+ DropdownMenuSubMenu as BreadcrumbMenuSubMenu,
40
+ type DropdownMenuSubMenuProps as BreadcrumbMenuSubMenuProps,
41
+ } from '../DropdownMenu/DropdownMenuSubMenu';
38
42
  export type {
39
43
  DropdownMenuOption as BreadcrumbMenuOption,
40
44
  DropdownMenuItemData as BreadcrumbMenuItemData,
@@ -163,8 +163,6 @@ export const docs = {
163
163
  ],
164
164
  vars: [
165
165
  {name: '--_button-radius', description: 'Border radius', default: 'var(--radius-element)', private: true},
166
- {name: '--button-press-scale', description: 'Active press transform', default: 'scale(0.98)'},
167
- {name: '--button-disabled-opacity', description: 'Opacity when disabled', default: '0.5'},
168
166
  {name: '--button-focus-offset', description: 'Focus ring outline offset', default: '3px'},
169
167
  {name: '--button-icon-only-aspect', description: 'Aspect ratio for icon-only buttons', default: '1 / 1'},
170
168
  ],
@@ -247,8 +245,6 @@ export const docsZh = {
247
245
  ],
248
246
  vars: [
249
247
  {name: '--_button-radius', description: '圆角半径', default: 'var(--radius-element)', private: true},
250
- {name: '--button-press-scale', description: '按下时的变换', default: 'scale(0.98)'},
251
- {name: '--button-disabled-opacity', description: '禁用时的不透明度', default: '0.5'},
252
248
  {name: '--button-focus-offset', description: '焦点环轮廓偏移', default: '3px'},
253
249
  {name: '--button-icon-only-aspect', description: '纯图标按钮的宽高比', default: '1 / 1'},
254
250
  ],