@astryxdesign/core 0.7.1 → 0.7.2-canary.01eab6a

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 (331) hide show
  1. package/dist/AspectRatio/AspectRatio.d.ts +6 -8
  2. package/dist/AspectRatio/AspectRatio.d.ts.map +1 -1
  3. package/dist/AspectRatio/AspectRatio.js +44 -3
  4. package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
  5. package/dist/Breadcrumbs/BreadcrumbItem.js +5 -2
  6. package/dist/Button/Button.d.ts.map +1 -1
  7. package/dist/Button/Button.js +38 -14
  8. package/dist/CheckboxInput/CheckboxInput.d.ts +1 -1
  9. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  10. package/dist/CheckboxInput/CheckboxInput.js +3 -1
  11. package/dist/CheckboxList/CheckboxListItem.d.ts +9 -1
  12. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  13. package/dist/CheckboxList/CheckboxListItem.js +4 -2
  14. package/dist/CommandPalette/CommandPaletteInput.d.ts +1 -1
  15. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  16. package/dist/CommandPalette/CommandPaletteInput.js +37 -5
  17. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  18. package/dist/ContextMenu/ContextMenu.js +5 -2
  19. package/dist/Dialog/Dialog.d.ts.map +1 -1
  20. package/dist/Dialog/Dialog.js +20 -8
  21. package/dist/Drawer/Drawer.d.ts +191 -0
  22. package/dist/Drawer/Drawer.d.ts.map +1 -0
  23. package/dist/Drawer/Drawer.js +382 -0
  24. package/dist/Drawer/DrawerHeader.d.ts +85 -0
  25. package/dist/Drawer/DrawerHeader.d.ts.map +1 -0
  26. package/dist/Drawer/DrawerHeader.js +134 -0
  27. package/dist/Drawer/index.d.ts +11 -0
  28. package/dist/Drawer/index.d.ts.map +1 -0
  29. package/dist/Drawer/index.js +11 -0
  30. package/dist/Drawer/useDrawerDialogPresence.d.ts +42 -0
  31. package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -0
  32. package/dist/Drawer/useDrawerDialogPresence.js +236 -0
  33. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  34. package/dist/DropdownMenu/DropdownMenu.js +47 -4
  35. package/dist/DropdownMenu/DropdownMenuContext.d.ts +17 -0
  36. package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
  37. package/dist/DropdownMenu/DropdownMenuContext.js +21 -0
  38. package/dist/DropdownMenu/DropdownMenuDivider.d.ts +5 -3
  39. package/dist/DropdownMenu/DropdownMenuDivider.d.ts.map +1 -1
  40. package/dist/DropdownMenu/DropdownMenuDivider.js +7 -3
  41. package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
  42. package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -8
  43. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  44. package/dist/EmptyState/EmptyState.js +4 -10
  45. package/dist/Field/Field.d.ts +1 -1
  46. package/dist/Field/Field.d.ts.map +1 -1
  47. package/dist/Field/Field.js +8 -4
  48. package/dist/Grid/GridSpan.d.ts.map +1 -1
  49. package/dist/Grid/GridSpan.js +1 -0
  50. package/dist/Heading/Heading.d.ts.map +1 -1
  51. package/dist/Heading/Heading.js +4 -2
  52. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  53. package/dist/HoverCard/useHoverCard.js +10 -2
  54. package/dist/Item/Item.d.ts +6 -2
  55. package/dist/Item/Item.d.ts.map +1 -1
  56. package/dist/Item/Item.js +19 -4
  57. package/dist/Layer/layerViewportInset.stylex.d.ts +6 -2
  58. package/dist/Layer/layerViewportInset.stylex.d.ts.map +1 -1
  59. package/dist/Layer/layerViewportInset.stylex.js +6 -2
  60. package/dist/Layer/useLayer.d.ts.map +1 -1
  61. package/dist/Layer/useLayer.js +13 -7
  62. package/dist/Layer/useLayerDismissal.d.ts +1 -1
  63. package/dist/Layer/useLayerDismissal.js +1 -1
  64. package/dist/Layout/container.stylex.d.ts +5 -5
  65. package/dist/Layout/container.stylex.js +4 -4
  66. package/dist/List/List.d.ts +46 -19
  67. package/dist/List/List.d.ts.map +1 -1
  68. package/dist/List/List.js +28 -10
  69. package/dist/List/ListContext.d.ts +8 -3
  70. package/dist/List/ListContext.d.ts.map +1 -1
  71. package/dist/List/ListContext.js +7 -2
  72. package/dist/List/ListItem.d.ts +13 -5
  73. package/dist/List/ListItem.d.ts.map +1 -1
  74. package/dist/List/ListItem.js +21 -8
  75. package/dist/List/index.d.ts +1 -1
  76. package/dist/List/index.d.ts.map +1 -1
  77. package/dist/Markdown/Markdown.d.ts.map +1 -1
  78. package/dist/Markdown/Markdown.js +8 -1
  79. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -1
  80. package/dist/Markdown/plugins/frontmatter.js +7 -3
  81. package/dist/MetadataList/MetadataList.d.ts.map +1 -1
  82. package/dist/MetadataList/MetadataList.js +5 -1
  83. package/dist/MetadataList/MetadataListContext.d.ts +1 -1
  84. package/dist/MetadataList/MetadataListContext.d.ts.map +1 -1
  85. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  86. package/dist/MultiSelector/MultiSelector.js +22 -8
  87. package/dist/MultiSelector/OptionActions.a11y.chromium.spec.js +100 -2
  88. package/dist/MultiSelector/hooks.d.ts.map +1 -1
  89. package/dist/MultiSelector/hooks.js +3 -1
  90. package/dist/Pagination/Pagination.d.ts +8 -5
  91. package/dist/Pagination/Pagination.d.ts.map +1 -1
  92. package/dist/Pagination/Pagination.js +21 -8
  93. package/dist/Popover/Popover.js +2 -2
  94. package/dist/PowerSearch/PowerSearch.js +1 -1
  95. package/dist/RadioList/RadioList.d.ts +1 -1
  96. package/dist/RadioList/RadioList.d.ts.map +1 -1
  97. package/dist/RadioList/RadioList.js +6 -2
  98. package/dist/RadioList/RadioListItem.d.ts +9 -1
  99. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  100. package/dist/RadioList/RadioListItem.js +2 -0
  101. package/dist/Selector/hooks.d.ts.map +1 -1
  102. package/dist/Selector/hooks.js +6 -2
  103. package/dist/SideNav/SideNavItem.d.ts +8 -1
  104. package/dist/SideNav/SideNavItem.d.ts.map +1 -1
  105. package/dist/SideNav/SideNavItem.js +71 -9
  106. package/dist/Switch/Switch.d.ts +1 -1
  107. package/dist/Switch/Switch.d.ts.map +1 -1
  108. package/dist/Switch/Switch.js +13 -1
  109. package/dist/TabList/TabMenu.d.ts +2 -2
  110. package/dist/TabList/TabMenu.d.ts.map +1 -1
  111. package/dist/TabList/TabMenu.js +6 -3
  112. package/dist/Text/Text.d.ts.map +1 -1
  113. package/dist/Text/Text.js +4 -2
  114. package/dist/Text/useTruncation.d.ts +7 -5
  115. package/dist/Text/useTruncation.d.ts.map +1 -1
  116. package/dist/Text/useTruncation.js +16 -5
  117. package/dist/ToggleButton/ToggleButton.d.ts +3 -2
  118. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  119. package/dist/ToggleButton/ToggleButton.js +12 -1
  120. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  121. package/dist/Tooltip/useTooltip.js +11 -0
  122. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  123. package/dist/TopNav/TopNavMegaMenu.js +3 -3
  124. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  125. package/dist/TopNav/TopNavMenu.js +3 -3
  126. package/dist/TreeList/TreeList.d.ts.map +1 -1
  127. package/dist/TreeList/TreeList.js +1 -0
  128. package/dist/TreeList/TreeListBranches.d.ts.map +1 -1
  129. package/dist/TreeList/TreeListBranches.js +2 -2
  130. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  131. package/dist/TreeList/TreeListItem.js +57 -9
  132. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  133. package/dist/Typeahead/BaseTypeahead.js +26 -0
  134. package/dist/astryx.css +47 -13
  135. package/dist/hooks/useEntryAnimation.d.ts +2 -1
  136. package/dist/hooks/useEntryAnimation.d.ts.map +1 -1
  137. package/dist/hooks/useEntryAnimation.js +14 -12
  138. package/dist/hooks/useIsHydrating.d.ts +6 -0
  139. package/dist/hooks/useIsHydrating.d.ts.map +1 -0
  140. package/dist/hooks/useIsHydrating.js +30 -0
  141. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  142. package/dist/hooks/useMenuHover.js +18 -13
  143. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  144. package/dist/i18n/generated-locales/en.generated.js +112 -1
  145. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  146. package/dist/i18n/generated-locales/pseudo.generated.js +112 -1
  147. package/dist/index.d.ts +1 -0
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +1 -0
  150. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  151. package/dist/theme/derivedVarRegistry.js +0 -3
  152. package/dist/utils/focusOutline.stylex.d.ts +4 -0
  153. package/dist/utils/focusOutline.stylex.d.ts.map +1 -1
  154. package/dist/utils/focusOutline.stylex.js +4 -0
  155. package/dist/utils/plainDate.d.ts.map +1 -1
  156. package/dist/utils/plainDate.js +15 -9
  157. package/dist/utils/sharedMutationObserver.d.ts +21 -0
  158. package/dist/utils/sharedMutationObserver.d.ts.map +1 -0
  159. package/dist/utils/sharedMutationObserver.js +69 -0
  160. package/locales/en.json +444 -0
  161. package/locales/pseudo.json +333 -0
  162. package/package.json +9 -4
  163. package/src/AspectRatio/AspectRatio.test.tsx +44 -43
  164. package/src/AspectRatio/AspectRatio.tsx +38 -18
  165. package/src/AspectRatio/__tests__/AspectRatioFitLayers.a11y.browser.spec.ts +157 -0
  166. package/src/Avatar/Avatar.doc.mjs +5 -3
  167. package/src/AvatarGroup/AvatarGroup.doc.mjs +3 -2
  168. package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
  169. package/src/Breadcrumbs/Breadcrumbs.test.tsx +30 -1
  170. package/src/Button/Button.doc.mjs +2 -2
  171. package/src/Button/Button.test.tsx +155 -9
  172. package/src/Button/Button.tsx +39 -16
  173. package/src/Button/__tests__/Button.a11y.known-failures.ts +1 -62
  174. package/src/Button/__tests__/Button.a11y.states.ts +0 -10
  175. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +3 -1
  176. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  177. package/src/CheckboxInput/CheckboxInput.tsx +3 -1
  178. package/src/CheckboxList/CheckboxList.test.tsx +22 -0
  179. package/src/CheckboxList/CheckboxListItem.doc.mjs +16 -0
  180. package/src/CheckboxList/CheckboxListItem.tsx +10 -0
  181. package/src/CommandPalette/CommandPaletteInput.test.tsx +53 -4
  182. package/src/CommandPalette/CommandPaletteInput.tsx +33 -3
  183. package/src/CommandPalette/__tests__/CommandPaletteInputAudit.a11y.chromium.spec.ts +335 -0
  184. package/src/CommandPalette/__tests__/CommandPaletteItemAudit.a11y.chromium.spec.ts +292 -0
  185. package/src/ContextMenu/ContextMenu.doc.mjs +2 -3
  186. package/src/ContextMenu/ContextMenu.tsx +4 -1
  187. package/src/ContextMenu/__tests__/ContextMenuAudit.a11y.chromium.spec.ts +438 -0
  188. package/src/Dialog/Dialog.test.tsx +46 -0
  189. package/src/Dialog/Dialog.tsx +20 -7
  190. package/src/Dialog/__tests__/Dialog.a11y.known-failures.ts +0 -14
  191. package/src/Drawer/Drawer.doc.mjs +472 -0
  192. package/src/Drawer/Drawer.spec.md +357 -0
  193. package/src/Drawer/Drawer.test.tsx +1300 -0
  194. package/src/Drawer/Drawer.tsx +613 -0
  195. package/src/Drawer/DrawerDirection.test.tsx +127 -0
  196. package/src/Drawer/DrawerHeader.doc.mjs +293 -0
  197. package/src/Drawer/DrawerHeader.test.tsx +102 -0
  198. package/src/Drawer/DrawerHeader.tsx +235 -0
  199. package/src/Drawer/__tests__/DrawerAudit.a11y.chromium.spec.ts +401 -0
  200. package/src/Drawer/__tests__/DrawerHeaderAudit.a11y.chromium.spec.ts +297 -0
  201. package/src/Drawer/index.ts +13 -0
  202. package/src/Drawer/useDrawerDialogPresence.ts +284 -0
  203. package/src/DropdownMenu/DropdownMenu.test.tsx +62 -13
  204. package/src/DropdownMenu/DropdownMenu.tsx +47 -4
  205. package/src/DropdownMenu/DropdownMenuContext.tsx +24 -0
  206. package/src/DropdownMenu/DropdownMenuDivider.doc.mjs +2 -2
  207. package/src/DropdownMenu/DropdownMenuDivider.spec.md +192 -0
  208. package/src/DropdownMenu/DropdownMenuDivider.tsx +9 -5
  209. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +185 -0
  210. package/src/DropdownMenu/DropdownMenuSubMenu.tsx +19 -5
  211. package/src/DropdownMenu/__tests__/DropdownMenuDividerAudit.a11y.chromium.spec.ts +280 -0
  212. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +145 -0
  213. package/src/DropdownMenu/__tests__/Menu.a11y.known-failures.ts +26 -0
  214. package/src/DropdownMenu/__tests__/Menu.a11y.renders.tsx +65 -0
  215. package/src/DropdownMenu/__tests__/Menu.a11y.states.ts +138 -0
  216. package/src/DropdownMenu/__tests__/Menu.a11y.test.tsx +136 -0
  217. package/src/EmptyState/EmptyState.doc.mjs +3 -2
  218. package/src/EmptyState/EmptyState.spec.md +189 -0
  219. package/src/EmptyState/EmptyState.test.tsx +74 -3
  220. package/src/EmptyState/EmptyState.tsx +4 -8
  221. package/src/Field/Field.doc.mjs +14 -8
  222. package/src/Field/Field.spec.md +217 -0
  223. package/src/Field/Field.test.tsx +60 -2
  224. package/src/Field/Field.tsx +17 -8
  225. package/src/Grid/GridSpan.tsx +3 -0
  226. package/src/Heading/Heading.test.tsx +57 -0
  227. package/src/Heading/Heading.tsx +6 -2
  228. package/src/HoverCard/HoverCard.test.tsx +58 -0
  229. package/src/HoverCard/useHoverCard.tsx +10 -2
  230. package/src/IconButton/IconButton.doc.mjs +2 -2
  231. package/src/IconButton/IconButton.test.tsx +3 -1
  232. package/src/Item/Item.doc.mjs +12 -8
  233. package/src/Item/Item.spec.md +226 -0
  234. package/src/Item/Item.test.tsx +61 -0
  235. package/src/Item/Item.tsx +40 -4
  236. package/src/Layer/layerViewportInset.source-build.test.mjs +90 -0
  237. package/src/Layer/layerViewportInset.stylex.ts +6 -2
  238. package/src/Layer/layerViewportInset.test.ts +1 -0
  239. package/src/Layer/useLayer.tsx +24 -13
  240. package/src/Layer/useLayerDismissal.ts +1 -1
  241. package/src/Layout/container.stylex.ts +6 -6
  242. package/src/List/List.doc.mjs +14 -1
  243. package/src/List/List.spec.md +129 -77
  244. package/src/List/List.test.tsx +163 -94
  245. package/src/List/List.tsx +74 -18
  246. package/src/List/ListContext.tsx +9 -3
  247. package/src/List/ListItem.doc.mjs +24 -5
  248. package/src/List/ListItem.tsx +56 -27
  249. package/src/List/index.ts +1 -1
  250. package/src/Markdown/Markdown.tsx +14 -1
  251. package/src/Markdown/plugins/frontmatter.test.tsx +25 -0
  252. package/src/Markdown/plugins/frontmatter.ts +7 -3
  253. package/src/MetadataList/MetadataList.doc.mjs +2 -2
  254. package/src/MetadataList/MetadataList.test.tsx +38 -0
  255. package/src/MetadataList/MetadataList.tsx +8 -1
  256. package/src/MetadataList/MetadataListContext.tsx +1 -1
  257. package/src/MultiSelector/MultiSelector.test.tsx +159 -0
  258. package/src/MultiSelector/MultiSelector.tsx +40 -7
  259. package/src/MultiSelector/OptionActions.a11y.chromium.spec.ts +112 -2
  260. package/src/MultiSelector/hooks.ts +3 -1
  261. package/src/MultiSelector/useMultiCombobox.doc.mjs +267 -0
  262. package/src/Pagination/Pagination.doc.mjs +7 -7
  263. package/src/Pagination/Pagination.spec.md +9 -9
  264. package/src/Pagination/Pagination.test.tsx +242 -0
  265. package/src/Pagination/Pagination.tsx +27 -11
  266. package/src/Popover/Popover.test.tsx +2 -2
  267. package/src/Popover/Popover.tsx +4 -4
  268. package/src/PowerSearch/PowerSearch.test.tsx +85 -0
  269. package/src/PowerSearch/PowerSearch.tsx +2 -2
  270. package/src/RadioList/RadioList.fr10.test.tsx +45 -2
  271. package/src/RadioList/RadioList.test.tsx +22 -0
  272. package/src/RadioList/RadioList.tsx +4 -1
  273. package/src/RadioList/RadioListItem.doc.mjs +16 -0
  274. package/src/RadioList/RadioListItem.tsx +10 -1
  275. package/src/Selector/hooks.ts +6 -2
  276. package/src/Selector/useCombobox.doc.mjs +237 -0
  277. package/src/SideNav/SideNav.test.tsx +179 -0
  278. package/src/SideNav/SideNavItem.doc.mjs +2 -2
  279. package/src/SideNav/SideNavItem.tsx +96 -17
  280. package/src/Slider/Slider.spec.md +39 -18
  281. package/src/Switch/Switch.test.tsx +49 -0
  282. package/src/Switch/Switch.tsx +11 -0
  283. package/src/TabList/TabList.spec.md +57 -44
  284. package/src/TabList/TabList.test.tsx +67 -0
  285. package/src/TabList/TabMenu.doc.mjs +4 -3
  286. package/src/TabList/TabMenu.tsx +7 -4
  287. package/src/Text/Text.test.tsx +57 -0
  288. package/src/Text/Text.tsx +6 -2
  289. package/src/Text/useTruncation.test.tsx +69 -0
  290. package/src/Text/useTruncation.ts +16 -5
  291. package/src/TimeInput/TimeInput.fr10.test.tsx +15 -0
  292. package/src/ToggleButton/ToggleButton.test.tsx +95 -0
  293. package/src/ToggleButton/ToggleButton.tsx +12 -1
  294. package/src/Tokenizer/Tokenizer.test.tsx +54 -1
  295. package/src/Tooltip/Tooltip.test.tsx +31 -0
  296. package/src/Tooltip/useTooltip.test.tsx +13 -0
  297. package/src/Tooltip/useTooltip.tsx +11 -0
  298. package/src/TopNav/TopNavMegaMenu.test.tsx +22 -0
  299. package/src/TopNav/TopNavMegaMenu.tsx +7 -3
  300. package/src/TopNav/TopNavMenu.test.tsx +19 -0
  301. package/src/TopNav/TopNavMenu.tsx +11 -3
  302. package/src/TreeList/TreeList.doc.mjs +1 -0
  303. package/src/TreeList/TreeList.spec.md +29 -17
  304. package/src/TreeList/TreeList.test.tsx +327 -6
  305. package/src/TreeList/TreeList.tsx +5 -1
  306. package/src/TreeList/TreeListBranches.tsx +6 -0
  307. package/src/TreeList/TreeListItem.tsx +88 -13
  308. package/src/Typeahead/BaseTypeahead.tsx +26 -0
  309. package/src/Typeahead/Typeahead.test.tsx +260 -2
  310. package/src/docPropLiterals.test.ts +36 -10
  311. package/src/hooks/useEntryAnimation.doc.mjs +4 -4
  312. package/src/hooks/useEntryAnimation.ssr.test.tsx +143 -0
  313. package/src/hooks/useEntryAnimation.ts +15 -16
  314. package/src/hooks/useIsHydrating.test.tsx +70 -0
  315. package/src/hooks/useIsHydrating.ts +31 -0
  316. package/src/hooks/useMenuHover.ts +18 -13
  317. package/src/i18n/InternationalizationProvider.doc.mjs +8 -0
  318. package/src/i18n/InternationalizationProvider.spec.md +252 -0
  319. package/src/i18n/__tests__/InternationalizationProvider.test.tsx +128 -0
  320. package/src/i18n/generated-locales/en.generated.ts +121 -0
  321. package/src/i18n/generated-locales/pseudo.generated.ts +121 -0
  322. package/src/i18n/useTranslator.test.tsx +37 -0
  323. package/src/index.ts +1 -0
  324. package/src/reset.css +0 -29
  325. package/src/theme/derivedVarRegistry.test.ts +7 -3
  326. package/src/theme/derivedVarRegistry.ts +0 -3
  327. package/src/utils/focusOutline.stylex.ts +4 -0
  328. package/src/utils/plainDate.test.ts +37 -1
  329. package/src/utils/plainDate.ts +15 -5
  330. package/src/utils/sharedMutationObserver.test.ts +53 -0
  331. package/src/utils/sharedMutationObserver.ts +78 -0
@@ -6,8 +6,10 @@
6
6
  * built Storybook.
7
7
  * @output Real-engine proof for option actions: the action is visible at rest
8
8
  * beside its row (`spec:AST-058` FR8), the keyboard reaches it through the
9
- * inline-end arrow in both directions and fires it with Enter, and the
10
- * highlight follows a real mouse across rows and actions.
9
+ * inline-end arrow in both directions and fires it with Enter, the active
10
+ * action cell draws the standard focus ring when the shared input modality
11
+ * is keyboard and not after pointer input, and the highlight follows a real
12
+ * mouse across rows and actions.
11
13
  * @position Run with `pnpm test:a11y-contract`; paint, pointer paths and the
12
14
  * active descendant as an engine exposes them are not jsdom claims.
13
15
  */
@@ -143,6 +145,114 @@ test('the keyboard reaches an action through the inline-end arrow and fires it w
143
145
  await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
144
146
  });
145
147
 
148
+ test('the active action cell draws the standard focus ring', async ({page}) => {
149
+ await page.goto(
150
+ `${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
151
+ );
152
+ const search = page.getByRole('combobox');
153
+ await expect(search).toBeFocused();
154
+ await search.press('ArrowDown'); // select-all
155
+ await search.press('ArrowDown'); // Feature (no action)
156
+ await search.press('ArrowDown'); // Bug
157
+ await search.press('ArrowRight');
158
+ await expect
159
+ .poll(async () => activeDescendant(page))
160
+ .toBe('gridcell:Edit Bug');
161
+ const ring = await page.evaluate(() => {
162
+ const id = document.activeElement?.getAttribute('aria-activedescendant');
163
+ const cell = id ? document.getElementById(id) : null;
164
+ if (!cell) {
165
+ return null;
166
+ }
167
+ const cs = getComputedStyle(cell);
168
+ // The ring the focus tokens describe, resolved by the browser on a probe.
169
+ const probe = document.createElement('div');
170
+ probe.style.cssText =
171
+ 'outline-width:var(--focus-outline-width);outline-style:var(--focus-outline-style);outline-color:var(--focus-outline-color);outline-offset:var(--focus-outline-offset)';
172
+ document.body.appendChild(probe);
173
+ const want = getComputedStyle(probe);
174
+ const result = {
175
+ style: cs.outlineStyle,
176
+ width: cs.outlineWidth,
177
+ color: cs.outlineColor,
178
+ offset: cs.outlineOffset,
179
+ wantStyle: want.outlineStyle,
180
+ wantWidth: want.outlineWidth,
181
+ wantColor: want.outlineColor,
182
+ wantOffset: want.outlineOffset,
183
+ };
184
+ probe.remove();
185
+ return result;
186
+ });
187
+ expect(ring).not.toBeNull();
188
+ expect(ring!.style).not.toBe('none');
189
+ expect(ring!.width).not.toBe('0px');
190
+ expect(ring!.style).toBe(ring!.wantStyle);
191
+ expect(ring!.width).toBe(ring!.wantWidth);
192
+ expect(ring!.color).toBe(ring!.wantColor);
193
+ expect(ring!.offset).toBe(ring!.wantOffset);
194
+ // Back on the option cell, the action cell drops the ring.
195
+ await search.press('ArrowLeft');
196
+ await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
197
+ const after = await page
198
+ .getByRole('row', {name: 'Bug'})
199
+ .locator('[data-multi-selector-action-cell]')
200
+ .evaluate(cell => getComputedStyle(cell).outlineStyle);
201
+ expect(after).toBe('none');
202
+ });
203
+
204
+ test('the action-cell ring follows the shared input modality', async ({
205
+ page,
206
+ }) => {
207
+ await page.goto(
208
+ `${storybook.origin}/iframe.html?id=core-multiselector--row-actions&viewMode=story`,
209
+ );
210
+ const search = page.getByRole('combobox');
211
+ await expect(search).toBeFocused();
212
+ const actionCell = (name: string) =>
213
+ page.getByRole('row', {name}).locator('[data-multi-selector-action-cell]');
214
+ const outlineStyle = async (name: string) =>
215
+ actionCell(name).evaluate(cell => getComputedStyle(cell).outlineStyle);
216
+ const hover = async (name: string) => {
217
+ const cell = await box(actionCell(name));
218
+ await page.mouse.move(cell.x + cell.width / 2, cell.y + cell.height / 2, {
219
+ steps: 6,
220
+ });
221
+ };
222
+
223
+ // Pointer input: a click into the search field, then a hover onto an
224
+ // action, moves the highlight without the ring, and leaves none behind.
225
+ await search.click();
226
+ await hover('Bug');
227
+ await expect
228
+ .poll(async () => activeDescendant(page))
229
+ .toBe('gridcell:Edit Bug');
230
+ expect(await outlineStyle('Bug')).toBe('none');
231
+ // A modifier chord is not keyboard navigation for the shared tracker.
232
+ await search.press('Control+ArrowRight');
233
+ expect(await outlineStyle('Bug')).toBe('none');
234
+ await page.mouse.move(1, 1, {steps: 6});
235
+ expect(await outlineStyle('Bug')).toBe('none');
236
+
237
+ // Keyboard input: back onto the same cell, the ring is drawn.
238
+ await search.press('ArrowLeft');
239
+ await expect.poll(async () => activeDescendant(page)).toBe('row:Bug');
240
+ await search.press('ArrowRight');
241
+ await expect
242
+ .poll(async () => activeDescendant(page))
243
+ .toBe('gridcell:Edit Bug');
244
+ expect(await outlineStyle('Bug')).not.toBe('none');
245
+
246
+ // Still keyboard-driven, a hover that moves the highlight to another
247
+ // row's action carries the ring with it.
248
+ await hover('P0');
249
+ await expect
250
+ .poll(async () => activeDescendant(page))
251
+ .toBe('gridcell:Edit P0');
252
+ expect(await outlineStyle('P0')).not.toBe('none');
253
+ expect(await outlineStyle('Bug')).toBe('none');
254
+ });
255
+
146
256
  test('under RTL the inline-end arrow is ArrowLeft', async ({page}) => {
147
257
  await page.goto(
148
258
  `${storybook.origin}/iframe.html?id=core-multiselector--row-actions-rtl&viewMode=story`,
@@ -6,10 +6,12 @@
6
6
  * @file hooks.ts
7
7
  * @input Uses React hooks, MultiSelectorOptionData type
8
8
  * @output Exports useMultiCombobox hook
9
- * @position Internal hook; used by MultiSelector.tsx
9
+ * @position Public hook, exported from the MultiSelector entry point; used by
10
+ * MultiSelector.tsx
10
11
  *
11
12
  * SYNC: When modified, update:
12
13
  * - /packages/core/src/MultiSelector/index.ts
14
+ * - /packages/core/src/MultiSelector/useMultiCombobox.doc.mjs
13
15
  */
14
16
 
15
17
  import {useCallback, useRef, useState} from 'react';
@@ -0,0 +1,267 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /** @type {import('@astryxdesign/cli/authoring').HookDoc} */
4
+ export const docs = {
5
+ name: 'useMultiCombobox',
6
+ displayName: 'useMultiCombobox',
7
+ group: 'MultiSelector',
8
+ keywords: [
9
+ 'combobox',
10
+ 'multi-select',
11
+ 'multiselect',
12
+ 'listbox',
13
+ 'grid',
14
+ 'toggle',
15
+ 'highlight',
16
+ 'active option',
17
+ 'aria-activedescendant',
18
+ 'keyboard',
19
+ 'headless',
20
+ 'apg',
21
+ 'accessibility',
22
+ 'a11y',
23
+ ],
24
+ params: [
25
+ {
26
+ name: 'options',
27
+ type: 'UseMultiComboboxOptions',
28
+ description: 'Configuration object.',
29
+ required: true,
30
+ },
31
+ {
32
+ name: 'options.selectableItems',
33
+ type: 'MultiSelectorOptionData[]',
34
+ description:
35
+ 'The options in the same order as they render. Options with `disabled` cannot be toggled and are skipped while moving through the open list; ArrowDown or ArrowUp that opens the list still lands on the first or last option.',
36
+ required: true,
37
+ },
38
+ {
39
+ name: 'options.isOpen',
40
+ type: 'boolean',
41
+ description: 'Whether the list is shown. The caller owns this state.',
42
+ required: true,
43
+ },
44
+ {
45
+ name: 'options.onOpen',
46
+ type: '() => unknown',
47
+ description:
48
+ 'Called to show the list. Return `false` to refuse; the hook then leaves the highlight alone.',
49
+ required: true,
50
+ },
51
+ {
52
+ name: 'options.onClose',
53
+ type: '() => void',
54
+ description: 'Called to hide the list. The highlight resets first.',
55
+ required: true,
56
+ },
57
+ {
58
+ name: 'options.onToggle',
59
+ type: '(itemValue: string) => void',
60
+ description:
61
+ 'Called with the value of the option to add or remove. Toggling keeps the list open.',
62
+ required: true,
63
+ },
64
+ {
65
+ name: 'options.listboxId',
66
+ type: 'string',
67
+ description:
68
+ "The listbox (or grid) element's id. Option and action-cell ids are derived from it.",
69
+ required: true,
70
+ },
71
+ {
72
+ name: 'options.hasSearch',
73
+ type: 'boolean',
74
+ description:
75
+ 'Whether the list has its own search field. Space then types instead of toggling, opening by a press or Enter does not pre-highlight (ArrowDown and ArrowUp still open on the first and last option), Delete and Backspace edit the query, and type-to-select is off. Home and End still jump to the first and last option, so a search input that keeps them for the caret should stop them before this handler and rely on PageUp and PageDown.',
76
+ default: 'false',
77
+ },
78
+ {
79
+ name: 'options.onClear',
80
+ type: '() => void',
81
+ description:
82
+ 'Clears every selected value. When provided, Delete or Backspace on the closed control clears them, so clearing is not pointer-only. Ignored while the list is open or with `hasSearch`.',
83
+ },
84
+ {
85
+ name: 'options.hasValue',
86
+ type: 'boolean',
87
+ description:
88
+ 'Whether anything is selected. The Delete and Backspace clear is skipped when false.',
89
+ default: 'false',
90
+ },
91
+ {
92
+ name: 'options.isGrid',
93
+ type: 'boolean',
94
+ description:
95
+ "Whether each row pairs an option cell with an action cell. The inline-end arrow then moves the highlight to the row's action and the inline-start arrow back, following visual direction in right-to-left layouts, and Enter on the action runs `onActivateAction` instead of toggling.",
96
+ default: 'false',
97
+ },
98
+ {
99
+ name: 'options.rowHasAction',
100
+ type: '(index: number) => boolean',
101
+ description:
102
+ 'In a grid, whether the row at `index` has an action cell to move to.',
103
+ },
104
+ {
105
+ name: 'options.onActivateAction',
106
+ type: '(index: number) => void',
107
+ description:
108
+ "In a grid, runs the control in the highlighted row's action cell. The selection does not change.",
109
+ },
110
+ {
111
+ name: 'options.isDisabled',
112
+ type: 'boolean',
113
+ description: 'Whether the control ignores clicks and keys.',
114
+ default: 'false',
115
+ },
116
+ ],
117
+ returns: [
118
+ {
119
+ name: 'highlightedIndex',
120
+ type: 'number',
121
+ description: 'Index of the active row, or -1 when none is active.',
122
+ },
123
+ {
124
+ name: 'setHighlightedIndex',
125
+ type: '(index: number) => void',
126
+ description:
127
+ "Sets the active row, for example after filtering. The highlight lands on the row's option cell.",
128
+ },
129
+ {
130
+ name: 'highlightedCell',
131
+ type: "'option' | 'action'",
132
+ description:
133
+ "Which cell of the active row is highlighted; `'action'` only in a grid.",
134
+ },
135
+ {
136
+ name: 'activeDescendantId',
137
+ type: 'string | undefined',
138
+ description:
139
+ "The id to pass as the control's `aria-activedescendant`: the active option, or its action cell in a grid. Undefined while the list is closed or nothing is active.",
140
+ },
141
+ {
142
+ name: 'getItemId',
143
+ type: '(index: number) => string',
144
+ description: 'The id to render on the option at `index`.',
145
+ },
146
+ {
147
+ name: 'getActionCellId',
148
+ type: '(index: number) => string',
149
+ description:
150
+ 'In a grid, the id to render on the action cell of row `index`.',
151
+ },
152
+ {
153
+ name: 'onTriggerClick',
154
+ type: '() => void',
155
+ description: 'Toggles the list from a press on the control.',
156
+ },
157
+ {
158
+ name: 'onKeyDown',
159
+ type: '(e: React.KeyboardEvent) => void',
160
+ description:
161
+ 'Key handler for the control that keeps focus. ArrowDown and ArrowUp open the list or move the active row; Enter (and Space without search) toggles the active option and keeps the list open; Escape and Tab close it; Home, End, PageUp and PageDown jump to the first or last option; without search, typed characters move to the first option whose label starts with them.',
162
+ },
163
+ {
164
+ name: 'onItemMouseEnter',
165
+ type: "(item: MultiSelectorOptionData, index: number, cell?: 'option' | 'action') => void",
166
+ description:
167
+ 'Makes the hovered option, or its action cell, active without scrolling the list or moving focus.',
168
+ },
169
+ ],
170
+ usage: {
171
+ description:
172
+ 'Tracks the active option of a multi-select combobox and handles its keys, while DOM focus stays on the control. It powers MultiSelector; unlike useCombobox, toggling an option keeps the list open, and rows can carry an action cell. It renders nothing and sets no attributes: the caller renders the control and the list, and wires the ARIA attributes from what it returns.',
173
+ bestPractices: [
174
+ {
175
+ guidance: true,
176
+ description:
177
+ "Render each option with `id={getItemId(index)}` and pass `activeDescendantId` as the control's `aria-activedescendant`.",
178
+ },
179
+ {
180
+ guidance: true,
181
+ description:
182
+ 'Use `isGrid` only when rows carry a control next to the option; give each action cell `id={getActionCellId(index)}`.',
183
+ },
184
+ {
185
+ guidance: true,
186
+ description:
187
+ 'Announce the number of results through useAnnounce when the options change for a new query.',
188
+ },
189
+ {
190
+ guidance: false,
191
+ description:
192
+ 'Close the list after each toggle; a multi-select stays open so several options can be picked in a row.',
193
+ },
194
+ ],
195
+ },
196
+ relatedComponents: ['MultiSelector'],
197
+ relatedHooks: ['useCombobox', 'useAnnounce'],
198
+ importPath: '@astryxdesign/core/MultiSelector',
199
+ category: 'focus',
200
+ };
201
+
202
+ /** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
203
+ export const docsDense = {
204
+ description:
205
+ 'Multi-select combobox state + keys; focus stays on control. Powers MultiSelector. Toggle keeps list open; rows may carry an action cell (grid). Renders nothing, sets no attrs: caller renders + wires ARIA.',
206
+ paramDescriptions: {
207
+ options: 'config.',
208
+ 'options.selectableItems':
209
+ 'options in render order; disabled ones not toggleable, skipped while moving in open list (ArrowDown/Up opening still lands first / last).',
210
+ 'options.isOpen': 'list shown; caller owns state.',
211
+ 'options.onOpen':
212
+ 'show list; return false to refuse (highlight untouched).',
213
+ 'options.onClose': 'hide list; highlight resets first.',
214
+ 'options.onToggle': 'add / remove value; list stays open.',
215
+ 'options.listboxId':
216
+ 'listbox / grid id; option + action-cell ids derive from it.',
217
+ 'options.hasSearch':
218
+ 'own search field: Space types, press / Enter opening does not pre-highlight (ArrowDown/Up still first / last), Delete / Backspace edit query, no type-to-select. Home / End still jump; stop them first to keep them for the caret, use PageUp / PageDown.',
219
+ 'options.onClear':
220
+ 'clear all; Delete / Backspace on closed control. ignored when open or hasSearch.',
221
+ 'options.hasValue': 'anything selected; clear skipped when false.',
222
+ 'options.isGrid':
223
+ 'rows = option + action cell; inline-end / inline-start arrows move between them (RTL-aware); Enter on action -> onActivateAction.',
224
+ 'options.rowHasAction': 'grid: row at index has an action cell.',
225
+ 'options.onActivateAction':
226
+ "grid: run highlighted row's action; selection unchanged.",
227
+ 'options.isDisabled': 'ignore clicks + keys.',
228
+ },
229
+ returnDescriptions: {
230
+ highlightedIndex: 'active row index, -1 = none.',
231
+ setHighlightedIndex: 'set active row (lands on option cell).',
232
+ highlightedCell: "'option' | 'action' (action only in grid).",
233
+ activeDescendantId:
234
+ 'control aria-activedescendant: active option or its action cell; undefined when closed / none.',
235
+ getItemId: 'id for option at index.',
236
+ getActionCellId: 'grid: id for action cell of row index.',
237
+ onTriggerClick: 'toggle list from a press on control.',
238
+ onKeyDown:
239
+ 'control keydown: ArrowDown/Up open or move; Enter (Space w/o search) toggles, stays open; Escape / Tab close; Home / End / PageUp / PageDown jump; typed chars jump by label prefix (w/o search).',
240
+ onItemMouseEnter:
241
+ 'hover makes option / action cell active; no scroll, no focus move.',
242
+ },
243
+ usage: {
244
+ description:
245
+ 'Headless multi-select combobox: active option + keys, focus stays on control; toggle keeps list open. Caller renders + wires ARIA.',
246
+ bestPractices: [
247
+ {
248
+ guidance: true,
249
+ description:
250
+ 'Option id = getItemId(index); control aria-activedescendant = activeDescendantId.',
251
+ },
252
+ {
253
+ guidance: true,
254
+ description:
255
+ 'isGrid only when rows carry a control; action cell id = getActionCellId(index).',
256
+ },
257
+ {
258
+ guidance: true,
259
+ description: 'Announce result count via useAnnounce on new query.',
260
+ },
261
+ {
262
+ guidance: false,
263
+ description: 'Close after each toggle; multi-select stays open.',
264
+ },
265
+ ],
266
+ },
267
+ };
@@ -137,7 +137,7 @@ export const docs = {
137
137
  name: 'variant',
138
138
  type: "'pages' | 'count' | 'compact' | 'dots' | 'input' | 'none'",
139
139
  description:
140
- "Visual variant controlling what appears between prev/next buttons. 'pages' shows page number buttons with ellipsis, 'count' shows 'X-Y of Z' text, 'compact' shows 'Page X of Y', 'dots' shows dot indicators, 'input' shows an editable page-number box with a leading label ('Page [ n ] / N') flanked by first/last buttons by default (the box needs a known total to clamp against, so it is disabled in cursor/hasMore mode), 'none' shows just prev/next buttons.",
140
+ "Visual variant controlling what appears between prev/next buttons. 'pages' shows page number buttons with ellipsis, 'count' shows 'X-Y of Z' text, 'compact' shows 'Page X of Y' (add first/last buttons with hasFirstLast), 'dots' shows dot indicators, 'input' shows an editable page-number box with a leading label ('Page [ n ] / N') flanked by first/last buttons by default (the box needs a known total to clamp against, so it is disabled in cursor/hasMore mode), 'none' shows just prev/next buttons.",
141
141
  default: "'pages'",
142
142
  },
143
143
  {
@@ -150,8 +150,8 @@ export const docs = {
150
150
  name: 'hasFirstLast',
151
151
  type: 'boolean',
152
152
  description:
153
- "Whether to show first/last («/») double-chevron buttons flanking prev/next. Only applies to the 'input' variant; omitted when the page count is unknown (cursor/hasMore pagination).",
154
- default: 'true',
153
+ "Whether to show first/last («/») double-chevron buttons flanking prev/next. Applies to the 'input' variant (shown unless false) and the 'compact' variant (shown only when true); other variants ignore it. Omitted when the page count is unknown (cursor/hasMore pagination).",
154
+ default: "true for 'input'; false for 'compact'",
155
155
  },
156
156
  {
157
157
  name: 'step',
@@ -288,7 +288,7 @@ export const docsZh = {
288
288
  name: 'variant',
289
289
  type: "'pages' | 'count' | 'compact' | 'dots' | 'input' | 'none'",
290
290
  description:
291
- "控制上一页/下一页按钮之间显示内容的视觉变体。'pages' 显示带省略号的页码按钮,'count' 显示 'X-Y of Z' 文本,'compact' 显示 'Page X of Y','dots' 显示点指示器,'input' 显示带前置标签的可编辑页码框——'Page [ n ] / N'——默认两侧带首页/末页按钮,'none' 仅显示上一页/下一页按钮。",
291
+ "控制上一页/下一页按钮之间显示内容的视觉变体。'pages' 显示带省略号的页码按钮,'count' 显示 'X-Y of Z' 文本,'compact' 显示 'Page X of Y'(可通过 hasFirstLast 添加首页/末页按钮),'dots' 显示点指示器,'input' 显示带前置标签的可编辑页码框——'Page [ n ] / N'——默认两侧带首页/末页按钮,'none' 仅显示上一页/下一页按钮。",
292
292
  default: "'pages'",
293
293
  },
294
294
  {
@@ -301,8 +301,8 @@ export const docsZh = {
301
301
  name: 'hasFirstLast',
302
302
  type: 'boolean',
303
303
  description:
304
- "是否显示首页/末页(«/»)双箭头按钮,位于上一页/下一页两侧。仅适用于 'input' 变体;当页数未知(游标/hasMore 分页)时省略。",
305
- default: 'true',
304
+ "是否显示首页/末页(«/»)双箭头按钮,位于上一页/下一页两侧。适用于 'input' 变体(除非设为 false 否则显示)和 'compact' 变体(仅在设为 true 时显示);其他变体忽略此项。当页数未知(游标/hasMore 分页)时省略。",
305
+ default: "'input' 为 true;'compact' 为 false",
306
306
  },
307
307
  {
308
308
  name: 'step',
@@ -400,7 +400,7 @@ export const docsDense = {
400
400
  onPageSizeChange: 'Called on page size change. Auto resets to page 1.',
401
401
  variant: "Display between prev/next. 'input' = 'Page [n] / N' + first/last.",
402
402
  pageLabel: "Noun before the 'input' box (e.g. 'Page' or 'Row'). Navigation stays page-based. Default localized 'Page'.",
403
- hasFirstLast: "Show first/last («/») buttons in the 'input' variant. Omitted when total unknown.",
403
+ hasFirstLast: "Show first/last («/») buttons. 'input' default true; 'compact' default false (opt in). Other variants ignore. Omitted when total unknown.",
404
404
  step: 'Pages the prev/next buttons advance per click. Clamped to range; < 1 or non-integer falls back to 1.',
405
405
  siblingCount: "Page buttons each side of current; only variant='pages'.",
406
406
  size: 'Control size.',
@@ -92,15 +92,15 @@ and usage remain documented in `Pagination.doc.mjs`.
92
92
 
93
93
  ### Representative states
94
94
 
95
- | State | Required invariant | Allowed variation |
96
- | ----------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------- |
97
- | Numbered, known page count | Page number buttons render; Ellipsis appears only where pages are omitted. | Page count, sibling range, and active page. |
98
- | Count with known item total | Count readout renders between Previous/next buttons. | Range and total text. |
99
- | Compact with known page count | Compact readout renders between Previous/next buttons. | Current and total page text. |
100
- | Dots with known page count | One Dot renders for each page and active state stays on the Dot target. | Dot count and active Dot. |
101
- | Input with known page count | Page input label, Page input, and Page input total render; First/last may render. | Label text and first/last opt-out. |
102
- | Input with unknown page count | Page input label and disabled Page input remain; total and First/last are absent. | Cursor-backed current page. |
103
- | None | Previous/next buttons remain without a presentation-specific part between them. | Enabled state follows current paging availability. |
95
+ | State | Required invariant | Allowed variation |
96
+ | ----------------------------- | -------------------------------------------------------------------------------------------- | -------------------------------------------------- |
97
+ | Numbered, known page count | Page number buttons render; Ellipsis appears only where pages are omitted. | Page count, sibling range, and active page. |
98
+ | Count with known item total | Count readout renders between Previous/next buttons. | Range and total text. |
99
+ | Compact with known page count | Compact readout renders between Previous/next buttons; First/last render only when opted in. | Current and total page text and first/last opt-in. |
100
+ | Dots with known page count | One Dot renders for each page and active state stays on the Dot target. | Dot count and active Dot. |
101
+ | Input with known page count | Page input label, Page input, and Page input total render; First/last may render. | Label text and first/last opt-out. |
102
+ | Input with unknown page count | Page input label and disabled Page input remain; total and First/last are absent. | Cursor-backed current page. |
103
+ | None | Previous/next buttons remain without a presentation-specific part between them. | Enabled state follows current paging availability. |
104
104
 
105
105
  The optional Page-size selector may precede the paging controls in any current
106
106
  presentation when page-size options are provided.