@astryxdesign/core 0.1.7 → 0.1.8-canary.0c4d8d7

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 (428) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +30 -19
  3. package/dist/AppShell/AppShell.d.ts.map +1 -1
  4. package/dist/AppShell/AppShell.js +6 -2
  5. package/dist/AppShell/AppShellMobileContext.d.ts +8 -0
  6. package/dist/AppShell/AppShellMobileContext.d.ts.map +1 -1
  7. package/dist/Avatar/Avatar.d.ts +10 -5
  8. package/dist/Avatar/Avatar.d.ts.map +1 -1
  9. package/dist/Avatar/Avatar.js +12 -8
  10. package/dist/Avatar/AvatarSizeContext.d.ts +1 -1
  11. package/dist/Avatar/AvatarSizeContext.js +1 -1
  12. package/dist/Avatar/AvatarStatusDot.d.ts +2 -2
  13. package/dist/Avatar/AvatarStatusDot.js +2 -2
  14. package/dist/AvatarGroup/AvatarGroup.d.ts +2 -2
  15. package/dist/AvatarGroup/AvatarGroup.d.ts.map +1 -1
  16. package/dist/AvatarGroup/AvatarGroup.js +2 -2
  17. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
  18. package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
  19. package/dist/AvatarGroup/AvatarGroupOverflow.js +16 -14
  20. package/dist/Banner/Banner.d.ts +9 -1
  21. package/dist/Banner/Banner.d.ts.map +1 -1
  22. package/dist/Banner/Banner.js +31 -2
  23. package/dist/Button/Button.d.ts +8 -2
  24. package/dist/Button/Button.d.ts.map +1 -1
  25. package/dist/Button/Button.js +31 -2
  26. package/dist/ButtonGroup/ButtonGroup.d.ts +9 -1
  27. package/dist/ButtonGroup/ButtonGroup.d.ts.map +1 -1
  28. package/dist/ButtonGroup/ButtonGroup.js +30 -1
  29. package/dist/Card/Card.d.ts +8 -2
  30. package/dist/Card/Card.d.ts.map +1 -1
  31. package/dist/Card/Card.js +28 -2
  32. package/dist/Carousel/Carousel.d.ts +6 -2
  33. package/dist/Carousel/Carousel.d.ts.map +1 -1
  34. package/dist/Carousel/Carousel.js +55 -5
  35. package/dist/Chat/ChatComposer.d.ts +8 -0
  36. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  37. package/dist/Chat/ChatComposer.js +31 -6
  38. package/dist/Chat/ChatComposerDrawer.d.ts +2 -0
  39. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  40. package/dist/Chat/ChatComposerDrawer.js +11 -1
  41. package/dist/Chat/ChatComposerInput.d.ts +20 -3
  42. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  43. package/dist/Chat/ChatComposerInput.js +19 -3
  44. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  45. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  46. package/dist/Chat/ChatMessage.d.ts +1 -1
  47. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  48. package/dist/Chat/ChatMessage.js +1 -1
  49. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  50. package/dist/Chat/ChatSendButton.d.ts.map +1 -1
  51. package/dist/Chat/ChatToolCalls.d.ts +5 -1
  52. package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
  53. package/dist/Chat/ChatToolCalls.js +12 -3
  54. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  55. package/dist/Citation/Citation.d.ts.map +1 -1
  56. package/dist/Citation/Citation.js +10 -4
  57. package/dist/ClickableCard/ClickableCard.d.ts +9 -2
  58. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  59. package/dist/ClickableCard/ClickableCard.js +3 -0
  60. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  61. package/dist/CodeBlock/CodeBlock.js +8 -1
  62. package/dist/Collapsible/Collapsible.d.ts +11 -1
  63. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  64. package/dist/Collapsible/Collapsible.js +36 -4
  65. package/dist/CommandPalette/CommandPaletteInput.d.ts +7 -1
  66. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  67. package/dist/CommandPalette/CommandPaletteInput.js +6 -1
  68. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  69. package/dist/ContextMenu/ContextMenu.js +4 -3
  70. package/dist/ContextMenu/index.d.ts +3 -0
  71. package/dist/ContextMenu/index.d.ts.map +1 -1
  72. package/dist/ContextMenu/index.js +7 -1
  73. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  74. package/dist/DateTimeInput/DateTimeInput.js +10 -1
  75. package/dist/Dialog/Dialog.d.ts.map +1 -1
  76. package/dist/Dialog/Dialog.js +2 -1
  77. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  78. package/dist/DropdownMenu/DropdownMenu.js +8 -4
  79. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +88 -0
  80. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -0
  81. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +169 -0
  82. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -1
  83. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  84. package/dist/DropdownMenu/DropdownMenuContext.js +18 -0
  85. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts +68 -0
  86. package/dist/DropdownMenu/DropdownMenuRadioGroup.d.ts.map +1 -0
  87. package/dist/DropdownMenu/DropdownMenuRadioGroup.js +69 -0
  88. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts +69 -0
  89. package/dist/DropdownMenu/DropdownMenuRadioItem.d.ts.map +1 -0
  90. package/dist/DropdownMenu/DropdownMenuRadioItem.js +178 -0
  91. package/dist/DropdownMenu/index.d.ts +5 -1
  92. package/dist/DropdownMenu/index.d.ts.map +1 -1
  93. package/dist/DropdownMenu/index.js +10 -1
  94. package/dist/DropdownMenu/menuItemRoles.d.ts +13 -0
  95. package/dist/DropdownMenu/menuItemRoles.d.ts.map +1 -0
  96. package/dist/DropdownMenu/menuItemRoles.js +15 -0
  97. package/dist/FieldStatus/FieldStatus.d.ts +8 -0
  98. package/dist/FieldStatus/FieldStatus.d.ts.map +1 -1
  99. package/dist/FieldStatus/FieldStatus.js +22 -4
  100. package/dist/FileInput/FileInput.d.ts.map +1 -1
  101. package/dist/FileInput/FileInput.js +10 -2
  102. package/dist/HoverCard/HoverCard.d.ts +9 -1
  103. package/dist/HoverCard/HoverCard.d.ts.map +1 -1
  104. package/dist/HoverCard/HoverCard.js +2 -0
  105. package/dist/HoverCard/useHoverCard.d.ts +8 -0
  106. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  107. package/dist/HoverCard/useHoverCard.js +7 -2
  108. package/dist/Icon/Icon.d.ts +43 -13
  109. package/dist/Icon/Icon.d.ts.map +1 -1
  110. package/dist/Icon/Icon.js +62 -27
  111. package/dist/Layer/useLayer.d.ts +6 -0
  112. package/dist/Layer/useLayer.d.ts.map +1 -1
  113. package/dist/Layer/useLayer.js +2 -0
  114. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  115. package/dist/MobileNav/MobileNav.js +6 -1
  116. package/dist/MobileNav/MobileNavToggle.d.ts.map +1 -1
  117. package/dist/MobileNav/MobileNavToggle.js +4 -0
  118. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  119. package/dist/MultiSelector/MultiSelector.js +39 -12
  120. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  121. package/dist/MultiSelector/hooks.js +16 -0
  122. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  123. package/dist/Outline/Outline.d.ts +68 -3
  124. package/dist/Outline/Outline.d.ts.map +1 -1
  125. package/dist/Outline/Outline.js +128 -26
  126. package/dist/Outline/useScrollSpy.d.ts +27 -7
  127. package/dist/Outline/useScrollSpy.d.ts.map +1 -1
  128. package/dist/Outline/useScrollSpy.js +202 -70
  129. package/dist/OverflowList/OverflowList.d.ts +20 -3
  130. package/dist/OverflowList/OverflowList.d.ts.map +1 -1
  131. package/dist/OverflowList/OverflowList.js +29 -4
  132. package/dist/PowerSearch/PowerSearchEditPopover.js +1 -1
  133. package/dist/PowerSearch/usePowerSearchSource.d.ts.map +1 -1
  134. package/dist/SelectableCard/SelectableCard.d.ts +9 -2
  135. package/dist/SelectableCard/SelectableCard.d.ts.map +1 -1
  136. package/dist/SelectableCard/SelectableCard.js +14 -11
  137. package/dist/Selector/Selector.d.ts.map +1 -1
  138. package/dist/Selector/Selector.js +4 -2
  139. package/dist/Selector/hooks.d.ts.map +1 -1
  140. package/dist/Selector/hooks.js +16 -0
  141. package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
  142. package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
  143. package/dist/SideNav/SideNavHeading.js +21 -8
  144. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  145. package/dist/Slider/Slider.d.ts.map +1 -1
  146. package/dist/Slider/Slider.js +15 -1
  147. package/dist/TabList/Tab.d.ts.map +1 -1
  148. package/dist/TabList/Tab.js +2 -2
  149. package/dist/TabList/TabList.d.ts.map +1 -1
  150. package/dist/TabList/TabList.js +2 -0
  151. package/dist/TabList/TabMenu.d.ts.map +1 -1
  152. package/dist/TabList/TabMenu.js +1 -1
  153. package/dist/Table/Table.d.ts +1 -1
  154. package/dist/Table/Table.js +1 -1
  155. package/dist/Table/index.d.ts +4 -0
  156. package/dist/Table/index.d.ts.map +1 -1
  157. package/dist/Table/index.js +2 -0
  158. package/dist/Table/plugins/groupedRows/useTableGroupedRows.d.ts.map +1 -1
  159. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  160. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +24 -2
  161. package/dist/Table/plugins/rowStatus/index.d.ts +3 -0
  162. package/dist/Table/plugins/rowStatus/index.d.ts.map +1 -0
  163. package/dist/Table/plugins/rowStatus/index.js +3 -0
  164. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts +60 -0
  165. package/dist/Table/plugins/rowStatus/useTableRowStatus.d.ts.map +1 -0
  166. package/dist/Table/plugins/rowStatus/useTableRowStatus.js +145 -0
  167. package/dist/Table/plugins/selection/useTableSelection.d.ts +12 -0
  168. package/dist/Table/plugins/selection/useTableSelection.d.ts.map +1 -1
  169. package/dist/Table/plugins/selection/useTableSelection.js +4 -1
  170. package/dist/Table/plugins/sortable/useTableSortable.d.ts.map +1 -1
  171. package/dist/Table/plugins/sortable/useTableSortable.js +18 -7
  172. package/dist/Table/plugins/tree/index.d.ts +5 -0
  173. package/dist/Table/plugins/tree/index.d.ts.map +1 -0
  174. package/dist/Table/plugins/tree/index.js +4 -0
  175. package/dist/Table/plugins/tree/useTableTreeData.d.ts +38 -0
  176. package/dist/Table/plugins/tree/useTableTreeData.d.ts.map +1 -0
  177. package/dist/Table/plugins/tree/useTableTreeData.js +358 -0
  178. package/dist/Table/plugins/tree/useTableTreeState.d.ts +51 -0
  179. package/dist/Table/plugins/tree/useTableTreeState.d.ts.map +1 -0
  180. package/dist/Table/plugins/tree/useTableTreeState.js +175 -0
  181. package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
  182. package/dist/Table/useBaseTablePlugins.js +6 -3
  183. package/dist/Thumbnail/Thumbnail.d.ts +8 -1
  184. package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
  185. package/dist/Thumbnail/Thumbnail.js +46 -8
  186. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  187. package/dist/TimeInput/TimeInput.js +15 -4
  188. package/dist/Tokenizer/Tokenizer.d.ts +1 -1
  189. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  190. package/dist/Tokenizer/Tokenizer.js +16 -4
  191. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  192. package/dist/Toolbar/Toolbar.js +8 -1
  193. package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
  194. package/dist/TopNav/TopNavHeading.js +18 -8
  195. package/dist/TopNav/TopNavMegaMenu.js +10 -4
  196. package/dist/TopNav/TopNavMenu.d.ts +2 -1
  197. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  198. package/dist/TopNav/TopNavMenu.js +68 -6
  199. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  200. package/dist/TreeList/TreeListItem.js +5 -14
  201. package/dist/astryx.css +51 -16
  202. package/dist/astryx.umd.js +56 -56
  203. package/dist/astryx.umd.js.map +4 -4
  204. package/dist/authoring/doc.d.ts.map +1 -1
  205. package/dist/hooks/computeOverflow.d.ts +47 -0
  206. package/dist/hooks/computeOverflow.d.ts.map +1 -0
  207. package/dist/hooks/computeOverflow.js +213 -0
  208. package/dist/hooks/useOverflow.d.ts +19 -0
  209. package/dist/hooks/useOverflow.d.ts.map +1 -1
  210. package/dist/hooks/useOverflow.js +37 -25
  211. package/dist/theme/Theme.js +1 -1
  212. package/dist/theme/defineTheme.d.ts +1 -1
  213. package/dist/theme/defineTheme.js +1 -1
  214. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  215. package/dist/theme/generateThemeRules.js +51 -0
  216. package/dist/theme/hct.d.ts +0 -1
  217. package/dist/theme/hct.d.ts.map +1 -1
  218. package/dist/theme/hct.js +0 -3
  219. package/dist/utils/types.d.ts +13 -0
  220. package/dist/utils/types.d.ts.map +1 -1
  221. package/docs.mjs +32 -461
  222. package/locales/en.json +212 -172
  223. package/locales/fr-FR.json +10 -0
  224. package/locales/pseudo.json +30 -0
  225. package/package.json +3 -1
  226. package/scripts/postinstall.mjs +93 -0
  227. package/src/AppShell/AppShell.tsx +6 -0
  228. package/src/AppShell/AppShellMobileContext.tsx +8 -0
  229. package/src/AppShell/useAppShellMobile.doc.mjs +8 -0
  230. package/src/Avatar/Avatar.doc.mjs +6 -6
  231. package/src/Avatar/Avatar.tsx +16 -11
  232. package/src/Avatar/AvatarSizeContext.ts +1 -1
  233. package/src/Avatar/AvatarStatusDot.tsx +2 -2
  234. package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
  235. package/src/AvatarGroup/AvatarGroup.test.tsx +17 -3
  236. package/src/AvatarGroup/AvatarGroup.tsx +3 -3
  237. package/src/AvatarGroup/AvatarGroupOverflow.doc.mjs +4 -4
  238. package/src/AvatarGroup/AvatarGroupOverflow.tsx +21 -5
  239. package/src/Banner/Banner.doc.mjs +9 -0
  240. package/src/Banner/Banner.test.tsx +30 -0
  241. package/src/Banner/Banner.tsx +47 -3
  242. package/src/Button/Button.doc.mjs +9 -0
  243. package/src/Button/Button.test.tsx +49 -0
  244. package/src/Button/Button.tsx +25 -1
  245. package/src/ButtonGroup/ButtonGroup.doc.mjs +3 -0
  246. package/src/ButtonGroup/ButtonGroup.test.tsx +37 -0
  247. package/src/ButtonGroup/ButtonGroup.tsx +32 -0
  248. package/src/Card/Card.doc.mjs +9 -0
  249. package/src/Card/Card.test.tsx +21 -3
  250. package/src/Card/Card.tsx +35 -2
  251. package/src/Carousel/Carousel.doc.mjs +1 -0
  252. package/src/Carousel/Carousel.test.tsx +149 -1
  253. package/src/Carousel/Carousel.tsx +62 -5
  254. package/src/Chat/Chat.doc.mjs +1 -1
  255. package/src/Chat/ChatComposer.doc.mjs +9 -0
  256. package/src/Chat/ChatComposer.test.tsx +35 -0
  257. package/src/Chat/ChatComposer.tsx +58 -9
  258. package/src/Chat/ChatComposerDrawer.test.tsx +61 -0
  259. package/src/Chat/ChatComposerDrawer.tsx +11 -1
  260. package/src/Chat/ChatComposerInput.doc.mjs +7 -0
  261. package/src/Chat/ChatComposerInput.test.tsx +87 -0
  262. package/src/Chat/ChatComposerInput.tsx +35 -3
  263. package/src/Chat/ChatLayout.tsx +3 -1
  264. package/src/Chat/ChatLayoutScrollButton.tsx +3 -1
  265. package/src/Chat/ChatMessage.tsx +4 -2
  266. package/src/Chat/ChatMessageMetadata.tsx +1 -2
  267. package/src/Chat/ChatSendButton.tsx +5 -1
  268. package/src/Chat/ChatToolCalls.test.tsx +64 -0
  269. package/src/Chat/ChatToolCalls.tsx +13 -1
  270. package/src/Chat/useTriggerMenu.tsx +3 -1
  271. package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
  272. package/src/CheckboxInput/CheckboxInput.test.tsx +32 -1
  273. package/src/CheckboxList/CheckboxList.test.tsx +9 -1
  274. package/src/Citation/Citation.test.tsx +51 -0
  275. package/src/Citation/Citation.tsx +8 -2
  276. package/src/ClickableCard/ClickableCard.doc.mjs +2 -0
  277. package/src/ClickableCard/ClickableCard.test.tsx +34 -0
  278. package/src/ClickableCard/ClickableCard.tsx +11 -1
  279. package/src/CodeBlock/CodeBlock.test.tsx +56 -0
  280. package/src/CodeBlock/CodeBlock.tsx +9 -1
  281. package/src/Collapsible/Collapsible.doc.mjs +7 -0
  282. package/src/Collapsible/Collapsible.test.tsx +80 -0
  283. package/src/Collapsible/Collapsible.tsx +58 -5
  284. package/src/CommandPalette/CommandPaletteInput.doc.mjs +26 -9
  285. package/src/CommandPalette/CommandPaletteInput.test.tsx +34 -0
  286. package/src/CommandPalette/CommandPaletteInput.tsx +11 -0
  287. package/src/ContextMenu/ContextMenu.test.tsx +75 -1
  288. package/src/ContextMenu/ContextMenu.tsx +10 -5
  289. package/src/ContextMenu/index.ts +15 -0
  290. package/src/DateTimeInput/DateTimeInput.test.tsx +35 -0
  291. package/src/DateTimeInput/DateTimeInput.tsx +14 -2
  292. package/src/Dialog/Dialog.tsx +12 -2
  293. package/src/DropdownMenu/DropdownMenu.doc.mjs +23 -0
  294. package/src/DropdownMenu/DropdownMenu.tsx +11 -6
  295. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +95 -0
  296. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +236 -0
  297. package/src/DropdownMenu/DropdownMenuContext.tsx +29 -1
  298. package/src/DropdownMenu/DropdownMenuItem.doc.mjs +7 -1
  299. package/src/DropdownMenu/DropdownMenuRadioGroup.doc.mjs +80 -0
  300. package/src/DropdownMenu/DropdownMenuRadioGroup.tsx +118 -0
  301. package/src/DropdownMenu/DropdownMenuRadioItem.doc.mjs +80 -0
  302. package/src/DropdownMenu/DropdownMenuRadioItem.tsx +232 -0
  303. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +163 -0
  304. package/src/DropdownMenu/index.ts +23 -3
  305. package/src/DropdownMenu/menuItemRoles.ts +22 -0
  306. package/src/Field/Field.doc.mjs +1 -1
  307. package/src/Field/Field.test.tsx +57 -12
  308. package/src/Field/FieldLabel.doc.mjs +1 -1
  309. package/src/FieldStatus/FieldStatus.doc.mjs +17 -5
  310. package/src/FieldStatus/FieldStatus.test.tsx +78 -40
  311. package/src/FieldStatus/FieldStatus.tsx +22 -4
  312. package/src/FileInput/FileInput.test.tsx +52 -3
  313. package/src/FileInput/FileInput.tsx +13 -2
  314. package/src/HoverCard/HoverCard.doc.mjs +13 -0
  315. package/src/HoverCard/HoverCard.test.tsx +21 -2
  316. package/src/HoverCard/HoverCard.tsx +11 -0
  317. package/src/HoverCard/useHoverCard.doc.mjs +2 -0
  318. package/src/HoverCard/useHoverCard.tsx +24 -2
  319. package/src/Icon/Icon.doc.mjs +12 -0
  320. package/src/Icon/Icon.test.tsx +142 -0
  321. package/src/Icon/Icon.tsx +98 -30
  322. package/src/IconButton/IconButton.doc.mjs +8 -0
  323. package/src/IconButton/IconButton.test.tsx +15 -18
  324. package/src/Item/Item.doc.mjs +1 -1
  325. package/src/Layer/useLayer.tsx +8 -0
  326. package/src/MobileNav/MobileNav.tsx +12 -1
  327. package/src/MobileNav/MobileNavToggle.test.tsx +116 -0
  328. package/src/MobileNav/MobileNavToggle.tsx +9 -1
  329. package/src/MultiSelector/MultiSelector.doc.mjs +4 -2
  330. package/src/MultiSelector/MultiSelector.test.tsx +162 -1
  331. package/src/MultiSelector/MultiSelector.tsx +55 -12
  332. package/src/MultiSelector/hooks.ts +17 -0
  333. package/src/NumberInput/NumberInput.doc.mjs +2 -2
  334. package/src/NumberInput/NumberInput.test.tsx +11 -7
  335. package/src/NumberInput/NumberInput.tsx +1 -2
  336. package/src/Outline/Outline.doc.mjs +123 -3
  337. package/src/Outline/Outline.test.tsx +735 -0
  338. package/src/Outline/Outline.tsx +183 -33
  339. package/src/Outline/useScrollSpy.ts +248 -72
  340. package/src/OverflowList/OverflowList.doc.mjs +39 -6
  341. package/src/OverflowList/OverflowList.test.tsx +111 -0
  342. package/src/OverflowList/OverflowList.tsx +48 -8
  343. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +67 -0
  344. package/src/PowerSearch/PowerSearchEditPopover.tsx +1 -1
  345. package/src/PowerSearch/usePowerSearchSource.ts +1 -2
  346. package/src/SelectableCard/SelectableCard.doc.mjs +2 -0
  347. package/src/SelectableCard/SelectableCard.test.tsx +46 -4
  348. package/src/SelectableCard/SelectableCard.tsx +27 -14
  349. package/src/Selector/Selector.test.tsx +69 -0
  350. package/src/Selector/Selector.tsx +5 -1
  351. package/src/Selector/SelectorOption.doc.mjs +1 -1
  352. package/src/Selector/hooks.ts +17 -0
  353. package/src/SideNav/SideNav.test.tsx +119 -3
  354. package/src/SideNav/SideNavCollapseButton.tsx +6 -1
  355. package/src/SideNav/SideNavHeading.tsx +24 -8
  356. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  357. package/src/SideNav/SideNavItem.tsx +5 -1
  358. package/src/Slider/Slider.test.tsx +62 -2
  359. package/src/Slider/Slider.tsx +15 -1
  360. package/src/Switch/Switch.doc.mjs +1 -1
  361. package/src/Switch/Switch.test.tsx +32 -1
  362. package/src/TabList/Tab.tsx +5 -1
  363. package/src/TabList/TabList.doc.mjs +3 -3
  364. package/src/TabList/TabList.test.tsx +66 -0
  365. package/src/TabList/TabList.tsx +8 -0
  366. package/src/TabList/TabMenu.tsx +4 -1
  367. package/src/Table/Table.doc.mjs +2 -0
  368. package/src/Table/Table.tsx +1 -1
  369. package/src/Table/index.ts +12 -0
  370. package/src/Table/plugins/groupedRows/useTableGroupedRows.tsx +6 -2
  371. package/src/Table/plugins/rowExpansion/useTableRowExpansion.test.tsx +90 -1
  372. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +37 -5
  373. package/src/Table/plugins/rowStatus/index.ts +7 -0
  374. package/src/Table/plugins/rowStatus/useTableRowStatus.test.tsx +138 -0
  375. package/src/Table/plugins/rowStatus/useTableRowStatus.tsx +189 -0
  376. package/src/Table/plugins/selection/useTableSelection.test.tsx +16 -0
  377. package/src/Table/plugins/selection/useTableSelection.tsx +18 -1
  378. package/src/Table/plugins/sortable/useTableSortable.test.tsx +95 -9
  379. package/src/Table/plugins/sortable/useTableSortable.tsx +23 -7
  380. package/src/Table/plugins/tree/index.ts +12 -0
  381. package/src/Table/plugins/tree/useTableTreeData-perf.test.tsx +163 -0
  382. package/src/Table/plugins/tree/useTableTreeData.test.tsx +594 -0
  383. package/src/Table/plugins/tree/useTableTreeData.tsx +495 -0
  384. package/src/Table/plugins/tree/useTableTreeState.test.tsx +636 -0
  385. package/src/Table/plugins/tree/useTableTreeState.tsx +253 -0
  386. package/src/Table/useBaseTablePlugins.ts +6 -2
  387. package/src/Table/useTableRowStatus.doc.mjs +30 -0
  388. package/src/Table/useTableSelection.doc.mjs +65 -14
  389. package/src/Table/useTableTreeData.doc.mjs +75 -0
  390. package/src/Table/useTableTreeState.doc.mjs +98 -0
  391. package/src/TextArea/TextArea.doc.mjs +1 -1
  392. package/src/TextArea/TextArea.test.tsx +9 -1
  393. package/src/TextInput/TextInput.doc.mjs +1 -1
  394. package/src/Thumbnail/Thumbnail.doc.mjs +9 -0
  395. package/src/Thumbnail/Thumbnail.test.tsx +38 -8
  396. package/src/Thumbnail/Thumbnail.tsx +28 -1
  397. package/src/TimeInput/TimeInput.test.tsx +76 -1
  398. package/src/TimeInput/TimeInput.tsx +19 -6
  399. package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
  400. package/src/Tokenizer/Tokenizer.test.tsx +137 -2
  401. package/src/Tokenizer/Tokenizer.tsx +16 -4
  402. package/src/Toolbar/Toolbar.tsx +10 -3
  403. package/src/TopNav/TopNav.test.tsx +80 -1
  404. package/src/TopNav/TopNavHeading.tsx +24 -8
  405. package/src/TopNav/TopNavMegaMenu.test.tsx +149 -1
  406. package/src/TopNav/TopNavMegaMenu.tsx +10 -4
  407. package/src/TopNav/TopNavMenu.test.tsx +118 -4
  408. package/src/TopNav/TopNavMenu.tsx +83 -5
  409. package/src/TreeList/TreeList.test.tsx +32 -1
  410. package/src/TreeList/TreeListItem.tsx +19 -10
  411. package/src/authoring/doc.ts +1 -3
  412. package/src/docs-script.test.mjs +18 -13
  413. package/src/hooks/computeOverflow.test.ts +422 -0
  414. package/src/hooks/computeOverflow.ts +290 -0
  415. package/src/hooks/useOverflow.test.ts +96 -4
  416. package/src/hooks/useOverflow.ts +77 -33
  417. package/src/postinstall.test.mjs +71 -0
  418. package/src/theme/Theme.doc.mjs +4 -4
  419. package/src/theme/Theme.tsx +1 -1
  420. package/src/theme/defineTheme.ts +1 -1
  421. package/src/theme/generateThemeRules.test.ts +65 -0
  422. package/src/theme/generateThemeRules.ts +56 -0
  423. package/src/theme/hct.ts +0 -4
  424. package/src/utils/types.ts +14 -0
  425. package/dist/TreeList/treeListItem.markers.stylex.d.ts +0 -16
  426. package/dist/TreeList/treeListItem.markers.stylex.d.ts.map +0 -1
  427. package/dist/TreeList/treeListItem.markers.stylex.js +0 -22
  428. package/src/TreeList/treeListItem.markers.stylex.ts +0 -20
@@ -45,6 +45,13 @@ export const docs = {
45
45
  description: 'Whether the thumbnail is disabled.',
46
46
  default: 'false',
47
47
  },
48
+ {
49
+ name: 'elevation',
50
+ type: "'none' | 'low' | 'med' | 'high'",
51
+ description:
52
+ 'Resting shadow depth. `none` (the default) keeps the existing hover-only shadow; set a level to raise the tile at rest.',
53
+ default: "'none'",
54
+ },
48
55
  {
49
56
  name: 'xstyle',
50
57
  type: 'StyleXStyles',
@@ -101,6 +108,7 @@ export const docsZh = {
101
108
  onClick: '\u70B9\u51FB\u5904\u7406\u5668\u3002\u6DFB\u52A0\u6309\u94AE\u8BED\u4E49\u548C\u60AC\u505C\u9634\u5F71\u3002',
102
109
  isLoading: '\u663E\u793A\u9AA8\u67B6\u5C4F\uFF08\u65E0 src\uFF09\u6216\u4E0A\u4F20\u8986\u76D6\u5C42\uFF08\u6709 src\uFF09\u3002',
103
110
  isDisabled: '\u662F\u5426\u7981\u7528\u7F29\u7565\u56FE\u3002',
111
+ elevation: '\u9759\u6B62\u9634\u5F71\u6DF1\u5EA6\uFF1Anone\uFF08\u9ED8\u8BA4\uFF0C\u4FDD\u7559\u4EC5\u60AC\u505C\u9634\u5F71\uFF09| low | med | high\u3002',
104
112
  xstyle: '\u7528\u4E8E\u5E03\u5C40\u81EA\u5B9A\u4E49\u7684 StyleX \u6837\u5F0F\u3002\u5FC5\u987B\u662F stylex.create() \u7684\u503C\uFF0C\u800C\u975E\u5185\u8054\u6837\u5F0F\u5BF9\u8C61\u3002',
105
113
  className: '\u6839\u5143\u7D20\u7684 CSS \u7C7B\u540D\u3002\u5EFA\u8BAE\u4F7F\u7528 xstyle\u3002',
106
114
  style: '\u6839\u5143\u7D20\u7684\u5185\u8054\u6837\u5F0F\u3002\u5EFA\u8BAE\u4F7F\u7528 xstyle\u3002',
@@ -141,6 +149,7 @@ export const docsDense = {
141
149
  onClick: '(e) => void. Adds button semantics + hover shadow.',
142
150
  isLoading: 'Skeleton (no src) or upload overlay (with src). Default: false.',
143
151
  isDisabled: 'Disabled state. Default: false.',
152
+ elevation: 'Resting shadow depth: none (keeps hover-only shadow) | low | med | high.',
144
153
  xstyle: 'stylex.create() for layout.',
145
154
  className: 'CSS class. Prefer xstyle.',
146
155
  style: 'Inline styles. Prefer xstyle.',
@@ -21,16 +21,19 @@ describe('Thumbnail', () => {
21
21
  });
22
22
 
23
23
  it('shows skeleton when isLoading with no src', () => {
24
- const {container} = render(
25
- <Thumbnail isLoading data-testid="thumb" />,
26
- );
24
+ const {container} = render(<Thumbnail isLoading data-testid="thumb" />);
27
25
  expect(container.querySelector('.astryx-skeleton')).toBeInTheDocument();
28
26
  expect(screen.queryByRole('img')).toBeNull();
29
27
  });
30
28
 
31
29
  it('shows image with upload overlay when isLoading with src', () => {
32
30
  render(
33
- <Thumbnail src="/local.jpg" alt="Uploading" isLoading data-testid="thumb" />,
31
+ <Thumbnail
32
+ src="/local.jpg"
33
+ alt="Uploading"
34
+ isLoading
35
+ data-testid="thumb"
36
+ />,
34
37
  );
35
38
  const img = screen.getByRole('img');
36
39
  expect(img).toHaveAttribute('src', '/local.jpg');
@@ -75,7 +78,6 @@ describe('Thumbnail', () => {
75
78
  ).toBeInTheDocument();
76
79
  });
77
80
 
78
-
79
81
  it('label is shown via tooltip, not as inline text', () => {
80
82
  render(<Thumbnail label="photo.png" data-testid="thumb" />);
81
83
  // Label should exist in DOM (tooltip) but not as a direct child text node
@@ -117,9 +119,7 @@ describe('Thumbnail', () => {
117
119
 
118
120
  it('is not interactive when isLoading', () => {
119
121
  const onClick = vi.fn();
120
- render(
121
- <Thumbnail src="/img.jpg" alt="Test" onClick={onClick} isLoading />,
122
- );
122
+ render(<Thumbnail src="/img.jpg" alt="Test" onClick={onClick} isLoading />);
123
123
  expect(screen.queryByRole('button')).toBeNull();
124
124
  });
125
125
 
@@ -128,4 +128,34 @@ describe('Thumbnail', () => {
128
128
  render(<Thumbnail ref={ref} data-testid="thumb" />);
129
129
  expect(ref).toHaveBeenCalled();
130
130
  });
131
+
132
+ describe('elevation', () => {
133
+ // The elevation class lives on the image container (the shadow surface),
134
+ // which is the root's first element child.
135
+ const imageContainerClass = (
136
+ elevation: 'none' | 'low' | 'med' | 'high',
137
+ ) => {
138
+ const {container} = render(
139
+ <Thumbnail src="/x.png" alt="x" elevation={elevation} />,
140
+ );
141
+ return container.firstElementChild!.firstElementChild!.className;
142
+ };
143
+
144
+ it('renders a distinct class for each elevation level', () => {
145
+ const classes = new Set([
146
+ imageContainerClass('none'),
147
+ imageContainerClass('low'),
148
+ imageContainerClass('med'),
149
+ imageContainerClass('high'),
150
+ ]);
151
+ expect(classes.size).toBe(4);
152
+ });
153
+
154
+ it('defaults to flat (elevation none)', () => {
155
+ const {container: def} = render(<Thumbnail src="/x.png" alt="x" />);
156
+ expect(def.firstElementChild!.firstElementChild!.className).toBe(
157
+ imageContainerClass('none'),
158
+ );
159
+ });
160
+ });
131
161
  });
@@ -38,6 +38,7 @@ import {Tooltip} from '../Tooltip/Tooltip';
38
38
  import {MediaTheme} from '../theme/MediaTheme';
39
39
  import {useImageMode} from '../hooks/useImageMode';
40
40
  import type {BaseProps} from '../BaseProps';
41
+ import type {Elevation} from '../utils/types';
41
42
  import {mergeProps} from '../utils';
42
43
  import {themeProps} from '../utils/themeProps';
43
44
  import {useTranslator} from '../i18n';
@@ -90,6 +91,12 @@ export interface ThumbnailProps extends BaseProps<HTMLDivElement> {
90
91
  * @default false
91
92
  */
92
93
  isDisabled?: boolean;
94
+ /**
95
+ * Resting elevation — the shadow depth the tile sits at. `none` (the default)
96
+ * keeps the existing hover-only shadow; a set level raises the tile at rest.
97
+ * @default 'none'
98
+ */
99
+ elevation?: Elevation;
93
100
  /**
94
101
  * Test ID for testing frameworks.
95
102
  */
@@ -116,6 +123,10 @@ const styles = stylex.create({
116
123
  borderRadius: radiusVars['--radius-element'],
117
124
  overflow: 'hidden',
118
125
  backgroundColor: colorVars['--color-neutral'],
126
+ // Resting elevation is set via --_thumbnail-elevation (see elevationStyles).
127
+ // Routing it through a var lets the interactive hover bump (below) override
128
+ // just the resting value without clobbering the elevation prop.
129
+ boxShadow: 'var(--_thumbnail-elevation, none)',
119
130
  },
120
131
  image: {
121
132
  width: '100%',
@@ -144,7 +155,10 @@ const styles = stylex.create({
144
155
  transitionDuration: durationVars['--duration-fast'],
145
156
  transitionTimingFunction: easeVars['--ease-standard'],
146
157
  boxShadow: {
147
- default: 'none',
158
+ // Rest at the elevation prop's level (default none); bump to med on hover
159
+ // as before. Reading the var here keeps a set elevation visible at rest
160
+ // instead of being reset to flat.
161
+ default: 'var(--_thumbnail-elevation, none)',
148
162
  ':hover': {
149
163
  '@media (hover: hover)': shadowVars['--shadow-med'],
150
164
  },
@@ -201,6 +215,17 @@ const styles = stylex.create({
201
215
  },
202
216
  });
203
217
 
218
+ // Resting elevation → shadow token. Sets --_thumbnail-elevation (read by the
219
+ // image container and the interactive hover style) rather than box-shadow
220
+ // directly, so a set resting elevation survives the interactive hover bump.
221
+ // 'none' is the default — the existing hover-only shadow is unchanged.
222
+ const elevationStyles = stylex.create({
223
+ none: {'--_thumbnail-elevation': 'none'},
224
+ low: {'--_thumbnail-elevation': shadowVars['--shadow-low']},
225
+ med: {'--_thumbnail-elevation': shadowVars['--shadow-med']},
226
+ high: {'--_thumbnail-elevation': shadowVars['--shadow-high']},
227
+ });
228
+
204
229
  // =============================================================================
205
230
  // Placeholder icon — a simple image silhouette
206
231
  // =============================================================================
@@ -246,6 +271,7 @@ export function Thumbnail({
246
271
  onClick,
247
272
  isLoading = false,
248
273
  isDisabled = false,
274
+ elevation = 'none',
249
275
  xstyle,
250
276
  className,
251
277
  style,
@@ -311,6 +337,7 @@ export function Thumbnail({
311
337
  <div
312
338
  {...stylex.props(
313
339
  styles.imageContainer,
340
+ elevationStyles[elevation],
314
341
  isInteractive && styles.interactive,
315
342
  )}>
316
343
  {isInteractive ? (
@@ -9,12 +9,24 @@
9
9
  * SYNC: When TimeInput.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeEach} from 'vitest';
12
+ import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
13
13
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {TimeInput} from './TimeInput';
16
16
  import {InputGroup, InputGroupText} from '../InputGroup';
17
17
  import type {ISOTimeString} from '../utils';
18
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
19
+
20
+ function politeRegion(): HTMLElement | null {
21
+ return document.querySelector('[data-astryx-live-region="polite"]');
22
+ }
23
+ function assertiveRegion(): HTMLElement | null {
24
+ return document.querySelector('[data-astryx-live-region="assertive"]');
25
+ }
26
+
27
+ afterEach(() => {
28
+ __resetLiveRegionsForTest();
29
+ });
18
30
 
19
31
  describe('TimeInput', () => {
20
32
  it('renders with label', () => {
@@ -352,6 +364,69 @@ describe('TimeInput', () => {
352
364
  // duplicated.
353
365
  expect(container.querySelectorAll('svg')).toHaveLength(1);
354
366
  });
367
+
368
+ // The grouped status node exists only for aria-describedby; announcing
369
+ // happens through the persistent useAnnounce regions because a live
370
+ // region mounted together with its content is not reliably announced.
371
+ it('keeps the grouped status node role-free while announcing via the persistent region', async () => {
372
+ render(
373
+ <InputGroup label="Schedule">
374
+ <InputGroupText>Starts</InputGroupText>
375
+ <TimeInput
376
+ label="Start time"
377
+ isLabelHidden
378
+ value={'09:00' as ISOTimeString}
379
+ onChange={() => {}}
380
+ status={{type: 'error', message: 'Start time is required'}}
381
+ />
382
+ </InputGroup>,
383
+ );
384
+
385
+ const input = screen.getByRole('textbox', {
386
+ name: 'Schedule Start time',
387
+ });
388
+ const describedByIDs =
389
+ input.getAttribute('aria-describedby')?.split(' ') ?? [];
390
+ const statusNode = describedByIDs
391
+ .map(id => document.getElementById(id))
392
+ .find(el => el?.textContent === 'Start time is required');
393
+ expect(statusNode).toBeTruthy();
394
+ expect(statusNode).not.toHaveAttribute('role');
395
+ expect(statusNode).not.toHaveAttribute('aria-live');
396
+
397
+ await waitFor(() => {
398
+ expect(assertiveRegion()).toHaveTextContent('Start time is required');
399
+ });
400
+ });
401
+
402
+ // Regression: a grouped status message appearing after mount (the common
403
+ // validation flow) must land in the persistent announce region.
404
+ it('announces a grouped status message that appears after mount', async () => {
405
+ const grouped = (status?: {
406
+ type: 'error' | 'warning';
407
+ message: string;
408
+ }) => (
409
+ <InputGroup label="Schedule">
410
+ <InputGroupText>Starts</InputGroupText>
411
+ <TimeInput
412
+ label="Start time"
413
+ isLabelHidden
414
+ value={'09:00' as ISOTimeString}
415
+ onChange={() => {}}
416
+ status={status}
417
+ />
418
+ </InputGroup>
419
+ );
420
+ const {rerender} = render(grouped());
421
+ expect(politeRegion()).toBeNull();
422
+
423
+ rerender(grouped({type: 'warning', message: 'Schedule is unusual'}));
424
+ await waitFor(() => {
425
+ expect(politeRegion()).toHaveTextContent('Schedule is unusual');
426
+ });
427
+ // Non-error statuses stay on the polite channel.
428
+ expect(assertiveRegion()).toHaveTextContent('');
429
+ });
355
430
  });
356
431
 
357
432
  describe('disabledMessage', () => {
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file TimeInput.tsx
7
- * @input Uses React, useId, useState, useCallback, useRef, Field, Icon, InputGroupContext
7
+ * @input Uses React, useId, useState, useCallback, useRef, Field, Icon, InputGroupContext, useAnnounce
8
8
  * @output Exports TimeInput component, TimeInputProps
9
9
  * @position Core implementation; consumed by index.ts, tested by TimeInput.test.tsx
10
10
  *
@@ -20,6 +20,7 @@ import {
20
20
  useId,
21
21
  useState,
22
22
  useCallback,
23
+ useEffect,
23
24
  useRef,
24
25
  useMemo,
25
26
  useOptimistic,
@@ -64,6 +65,7 @@ import {
64
65
  import type {BaseProps} from '../BaseProps';
65
66
  import type {SizeValue} from '../utils/types';
66
67
  import {useSize} from '../SizeContext/SizeContext';
68
+ import {useAnnounce} from '../hooks/useAnnounce';
67
69
  import {useInputContainer} from '../hooks/useInputContainer';
68
70
  import {useInputGroup} from '../InputGroup/InputGroupContext';
69
71
  import {groupStyles} from '../InputGroup/groupStyles';
@@ -369,6 +371,21 @@ export function TimeInput({
369
371
  const [optimisticValue, setOptimisticValue] = useOptimistic(value);
370
372
  const isBusy = isLoading || optimisticValue !== value;
371
373
 
374
+ // In grouped mode the status message renders as a visually-hidden node that
375
+ // exists only for aria-describedby. Announce it through the persistent
376
+ // useAnnounce live regions instead of role/aria-live on that node — a live
377
+ // region mounted together with its content is not reliably announced.
378
+ // Ungrouped mode delegates to Field -> FieldStatus, which announces itself.
379
+ const announce = useAnnounce();
380
+ useEffect(() => {
381
+ if (inputGroup && status?.message) {
382
+ announce(
383
+ status.message,
384
+ status.type === 'error' ? 'assertive' : 'polite',
385
+ );
386
+ }
387
+ }, [announce, inputGroup, status?.message, status?.type]);
388
+
372
389
  // Disabled-reason tooltip. Disabled controls swallow pointer events, so the
373
390
  // tooltip listeners attach to the input container (which already exists) and
374
391
  // the input stays perceivable via aria-disabled instead of the disabled
@@ -611,11 +628,7 @@ export function TimeInput({
611
628
  </VisuallyHidden>
612
629
  )}
613
630
  {inputGroup && status?.message && (
614
- <VisuallyHidden
615
- as="div"
616
- id={statusMessageID}
617
- role={status.type === 'error' ? 'alert' : 'status'}
618
- aria-live={status.type === 'error' ? 'assertive' : 'polite'}>
631
+ <VisuallyHidden as="div" id={statusMessageID}>
619
632
  {status.message}
620
633
  </VisuallyHidden>
621
634
  )}
@@ -31,7 +31,7 @@ export const docs = {
31
31
  name: 'onChange',
32
32
  type: '(items: T[], change: TokenizerChange<T>) => void',
33
33
  description:
34
- "Called when selection changes. The change argument includes the affected item and type ('add' | 'create' | 'remove' | 'reorder').",
34
+ "Called when selection changes. The change argument includes the affected item and type ('add' | 'create' | 'remove' | 'reorder'). Additions and removals (including Backspace on an empty input) are announced to screen readers via a polite live region.",
35
35
  required: true,
36
36
  },
37
37
  {
@@ -9,12 +9,17 @@
9
9
  * SYNC: When Tokenizer.tsx changes, update tests to match
10
10
  */
11
11
 
12
- import {describe, it, expect, vi, beforeAll, afterAll} from 'vitest';
12
+ import {describe, it, expect, vi, beforeAll, afterAll, afterEach} from 'vitest';
13
13
  import {render, screen, fireEvent, act, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {Tokenizer} from './Tokenizer';
16
+ import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
16
17
  import type {SearchSource, SearchableItem} from '../Typeahead/types';
17
18
 
19
+ function politeRegion(): HTMLElement | null {
20
+ return document.querySelector('[data-astryx-live-region="polite"]');
21
+ }
22
+
18
23
  // Store original matches to restore later
19
24
  const originalMatches = HTMLElement.prototype.matches;
20
25
 
@@ -60,6 +65,10 @@ afterAll(() => {
60
65
  (HTMLElement.prototype as any).matches = originalMatches;
61
66
  });
62
67
 
68
+ afterEach(() => {
69
+ __resetLiveRegionsForTest();
70
+ });
71
+
63
72
  // Test data
64
73
  const users: SearchableItem[] = [
65
74
  {id: '1', label: 'Alice'},
@@ -921,6 +930,130 @@ describe('Tokenizer', () => {
921
930
  expect(screen.getByRole('combobox')).toBeDisabled();
922
931
  });
923
932
  });
933
+ describe('announcements', () => {
934
+ it('announces removal politely on Backspace with an empty input', async () => {
935
+ const onChange = vi.fn();
936
+ render(
937
+ <Tokenizer
938
+ label="Members"
939
+ searchSource={userSource}
940
+ value={[users[0], users[1]]}
941
+ onChange={onChange}
942
+ />,
943
+ );
944
+ const input = screen.getByRole('combobox');
945
+ fireEvent.keyDown(input, {key: 'Backspace'});
946
+ expect(onChange).toHaveBeenCalledWith([users[0]], {
947
+ item: users[1],
948
+ type: 'remove',
949
+ });
950
+ await waitFor(() => {
951
+ expect(politeRegion()).toHaveTextContent('Removed Bob');
952
+ });
953
+ });
954
+
955
+ it("announces removal politely when clicking a token's remove button", async () => {
956
+ render(
957
+ <Tokenizer
958
+ label="Members"
959
+ searchSource={userSource}
960
+ value={[users[0], users[1]]}
961
+ onChange={() => {}}
962
+ />,
963
+ );
964
+ fireEvent.click(screen.getByRole('button', {name: 'Remove Alice'}));
965
+ await waitFor(() => {
966
+ expect(politeRegion()).toHaveTextContent('Removed Alice');
967
+ });
968
+ });
969
+
970
+ it('announces addition politely when selecting a search result', async () => {
971
+ render(
972
+ <Tokenizer
973
+ label="Members"
974
+ searchSource={userSource}
975
+ value={[]}
976
+ onChange={() => {}}
977
+ hasEntriesOnFocus
978
+ debounceMs={0}
979
+ />,
980
+ );
981
+ const input = screen.getByRole('combobox');
982
+ fireEvent.focus(input);
983
+ await act(async () => {
984
+ await new Promise(r => setTimeout(r, 50));
985
+ });
986
+ fireEvent.click(screen.getByText('Alice'));
987
+ await waitFor(() => {
988
+ expect(politeRegion()).toHaveTextContent('Added Alice');
989
+ });
990
+ });
991
+
992
+ it('announces addition politely when creating a token with hasCreate', async () => {
993
+ const emptySource: SearchSource = {
994
+ search: () => [],
995
+ bootstrap: () => [],
996
+ };
997
+ render(
998
+ <Tokenizer
999
+ label="Tags"
1000
+ searchSource={emptySource}
1001
+ value={[]}
1002
+ onChange={() => {}}
1003
+ hasCreate
1004
+ debounceMs={0}
1005
+ />,
1006
+ );
1007
+ const input = screen.getByRole('combobox');
1008
+ await act(async () => {
1009
+ fireEvent.change(input, {target: {value: 'new-tag'}});
1010
+ });
1011
+ await act(async () => {
1012
+ await new Promise(r => setTimeout(r, 50));
1013
+ });
1014
+ fireEvent.click(screen.getByText('Create "new-tag"'));
1015
+ await waitFor(() => {
1016
+ expect(politeRegion()).toHaveTextContent('Added new-tag');
1017
+ });
1018
+ });
1019
+
1020
+ it('does not announce on mount', () => {
1021
+ render(
1022
+ <Tokenizer
1023
+ label="Members"
1024
+ searchSource={userSource}
1025
+ value={[users[0]]}
1026
+ onChange={() => {}}
1027
+ />,
1028
+ );
1029
+ // The live regions are created lazily on first announce, so a mount
1030
+ // with pre-selected tokens must not create (or speak through) one.
1031
+ expect(politeRegion()).toBeNull();
1032
+ });
1033
+
1034
+ it('does not announce add/remove while typing', async () => {
1035
+ render(
1036
+ <Tokenizer
1037
+ label="Members"
1038
+ searchSource={userSource}
1039
+ value={[]}
1040
+ onChange={() => {}}
1041
+ debounceMs={0}
1042
+ />,
1043
+ );
1044
+ const input = screen.getByRole('combobox');
1045
+ await act(async () => {
1046
+ fireEvent.change(input, {target: {value: 'Ali'}});
1047
+ });
1048
+ await act(async () => {
1049
+ await new Promise(r => setTimeout(r, 50));
1050
+ });
1051
+ // BaseTypeahead announces result counts while typing (existing
1052
+ // behavior); typing alone must not produce add/remove announcements.
1053
+ expect(politeRegion()?.textContent ?? '').not.toMatch(/Added|Removed/);
1054
+ });
1055
+ });
1056
+
924
1057
  describe('form participation', () => {
925
1058
  it('submits one entry per token id under htmlName', () => {
926
1059
  const {container} = render(
@@ -951,7 +1084,9 @@ describe('Tokenizer', () => {
951
1084
  />
952
1085
  </form>,
953
1086
  );
954
- expect([...new FormData(container.querySelector('form')!).keys()]).toEqual([]);
1087
+ expect([
1088
+ ...new FormData(container.querySelector('form')!).keys(),
1089
+ ]).toEqual([]);
955
1090
  });
956
1091
  });
957
1092
  });
@@ -4,7 +4,7 @@
4
4
 
5
5
  /**
6
6
  * @file Tokenizer.tsx
7
- * @input Uses React, BaseTypeahead, Field, Token
7
+ * @input Uses React, BaseTypeahead, Field, Token, useAnnounce
8
8
  * @output Exports Tokenizer multi-select typeahead component
9
9
  * @position Composed component; forwards DOM ref and exposes focus control via
10
10
  * handleRef
@@ -43,6 +43,7 @@ import {renderIconSlot, type IconType} from '../Icon';
43
43
  import {OverflowList} from '../OverflowList';
44
44
  import {useLayer} from '../Layer/useLayer';
45
45
  import {useTooltip} from '../Tooltip';
46
+ import {useAnnounce} from '../hooks/useAnnounce';
46
47
  import {
47
48
  colorVars,
48
49
  spacingVars,
@@ -560,6 +561,12 @@ export function Tokenizer<T extends SearchableItem>({
560
561
  [],
561
562
  );
562
563
 
564
+ // Announce token add/remove politely via the persistent live region.
565
+ // Tokens previously appeared and disappeared silently — Backspace on an
566
+ // empty input removes the trailing token, and the per-token remove buttons
567
+ // gave no audible feedback either.
568
+ const announce = useAnnounce();
569
+
563
570
  // Handle adding an item — detect creatable synthetic items
564
571
  const handleAdd = useCallback(
565
572
  (item: T | null) => {
@@ -584,6 +591,7 @@ export function Tokenizer<T extends SearchableItem>({
584
591
  const realItem = base as T;
585
592
  const newItems = [...value, realItem];
586
593
  onChange(newItems, {item: realItem, type: 'create'});
594
+ announce(`Added ${createdValue}`);
587
595
  return;
588
596
  }
589
597
 
@@ -592,18 +600,22 @@ export function Tokenizer<T extends SearchableItem>({
592
600
  }
593
601
  const newItems = [...value, item];
594
602
  onChange(newItems, {item, type: 'add'});
603
+ announce(`Added ${item.label}`);
595
604
  },
596
- [value, onChange, isAtMax, selectedIds, hasCreate],
605
+ [value, onChange, isAtMax, selectedIds, hasCreate, announce],
597
606
  );
598
607
 
599
- // Handle removing an item
608
+ // Handle removing an item. Single removal path: both Backspace on an empty
609
+ // input and the per-token remove buttons route through here, so the
610
+ // announcement covers both.
600
611
  const handleRemove = useCallback(
601
612
  (item: T) => {
602
613
  const newItems = value.filter(v => v.id !== item.id);
603
614
  onChange(newItems, {item, type: 'remove'});
615
+ announce(`Removed ${item.label}`);
604
616
  inputRef.current?.focus();
605
617
  },
606
- [value, onChange],
618
+ [value, onChange, announce],
607
619
  );
608
620
 
609
621
  // Handle clearing all items
@@ -239,6 +239,7 @@ export function Toolbar({
239
239
  const hasCenterContent = centerContent != null;
240
240
  const hasStartContent = startContent != null;
241
241
  const hasEndContent = endContent != null;
242
+ const hasBottomDivider = dividers?.includes('bottom') ?? false;
242
243
 
243
244
  const gapVar = spacingVars[spacingStepToVar[gap]] as string;
244
245
 
@@ -316,9 +317,15 @@ export function Toolbar({
316
317
  orientation === 'vertical' && styles.vertical,
317
318
  sizeStyles.base,
318
319
  dynamicStyles.gap(gapVar),
319
- dynamicStyles.tabIndicatorBottom(
320
- `calc(-1 * (${blockPaddingVarForSize[size]}${dividers?.includes('bottom') ? ' + 1px' : ''}))`,
321
- ),
320
+ // Only drop the tab indicator through the toolbar's block padding
321
+ // onto the rail when a bottom divider is actually present.
322
+ // Without a divider, leave `--_tab-indicator-bottom` unset so a
323
+ // nested TabList's indicator keeps its own default (hugging the
324
+ // tab) instead of floating down into the toolbar's padding.
325
+ hasBottomDivider &&
326
+ dynamicStyles.tabIndicatorBottom(
327
+ `calc(-1 * (${blockPaddingVarForSize[size]} + 1px))`,
328
+ ),
322
329
  ),
323
330
  )}
324
331
  {...props}>