@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
@@ -0,0 +1,280 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file DropdownMenuDividerAudit.a11y.chromium.spec.ts
5
+ * @input Exact-head DropdownMenuDivider story across themes, color modes, direction, and viewport widths
6
+ * @output Screenshots and fail-closed sensor receipts for the DropdownMenuDivider component audit
7
+ * @position Component-owned Night Watch browser evidence; CI runs it only for a DropdownMenuDivider audit pull request
8
+ */
9
+
10
+ import {createHash} from 'node:crypto';
11
+ import {execFileSync} from 'node:child_process';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import {expect, test, type Page} from '@playwright/test';
15
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
16
+ import {
17
+ DEFAULT_STORYBOOK_DIR,
18
+ serveStorybook,
19
+ type StaticServer,
20
+ } from '@astryxdesign/a11y-spec/storybook';
21
+
22
+ const OUTPUT = path.resolve('dropdown-menu-divider-audit-evidence');
23
+ const STORY_ID = 'core-dropdownmenudivider--menu-separator';
24
+ const WIDE = {width: 1024, height: 768};
25
+ const NARROW = {width: 320, height: 720};
26
+
27
+ type Mode = 'light' | 'dark';
28
+ type Direction = 'ltr' | 'rtl';
29
+ type Theme = 'neutral' | 'butter';
30
+ type AuditCase = {
31
+ key: string;
32
+ mode: Mode;
33
+ direction: Direction;
34
+ theme: Theme;
35
+ viewport: {width: number; height: number};
36
+ };
37
+
38
+ const cases: AuditCase[] = [
39
+ {
40
+ key: 'neutral-light',
41
+ mode: 'light',
42
+ direction: 'ltr',
43
+ theme: 'neutral',
44
+ viewport: WIDE,
45
+ },
46
+ {
47
+ key: 'neutral-dark',
48
+ mode: 'dark',
49
+ direction: 'ltr',
50
+ theme: 'neutral',
51
+ viewport: WIDE,
52
+ },
53
+ {
54
+ key: 'butter-light',
55
+ mode: 'light',
56
+ direction: 'ltr',
57
+ theme: 'butter',
58
+ viewport: WIDE,
59
+ },
60
+ {
61
+ key: 'butter-dark',
62
+ mode: 'dark',
63
+ direction: 'ltr',
64
+ theme: 'butter',
65
+ viewport: WIDE,
66
+ },
67
+ {
68
+ key: 'rtl-light',
69
+ mode: 'light',
70
+ direction: 'rtl',
71
+ theme: 'neutral',
72
+ viewport: WIDE,
73
+ },
74
+ {
75
+ key: 'rtl-dark',
76
+ mode: 'dark',
77
+ direction: 'rtl',
78
+ theme: 'neutral',
79
+ viewport: WIDE,
80
+ },
81
+ {
82
+ key: 'narrow-light',
83
+ mode: 'light',
84
+ direction: 'ltr',
85
+ theme: 'neutral',
86
+ viewport: NARROW,
87
+ },
88
+ {
89
+ key: 'narrow-dark',
90
+ mode: 'dark',
91
+ direction: 'ltr',
92
+ theme: 'neutral',
93
+ viewport: NARROW,
94
+ },
95
+ ];
96
+
97
+ test.describe.configure({mode: 'serial', retries: 0});
98
+ let server: StaticServer;
99
+ let checkoutSha: string;
100
+ let storybookSha: string;
101
+
102
+ function storyUrl(item: AuditCase) {
103
+ return `${server.origin}/iframe.html?id=${STORY_ID}&viewMode=story&globals=colorMode:${item.mode};astryxTheme:${item.theme};direction:${item.direction}`;
104
+ }
105
+
106
+ async function settle(page: Page) {
107
+ await holdMotionStill(page);
108
+ await page.evaluate(async () => document.fonts.ready);
109
+ await page.waitForTimeout(40);
110
+ }
111
+
112
+ test.beforeAll(async () => {
113
+ fs.rmSync(OUTPUT, {recursive: true, force: true});
114
+ fs.mkdirSync(OUTPUT, {recursive: true});
115
+ checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
116
+ encoding: 'utf8',
117
+ }).trim();
118
+ const expected = process.env.ASTRYX_HEAD_SHA;
119
+ if (expected && expected !== checkoutSha) {
120
+ throw new Error(
121
+ `checkout ${checkoutSha} does not match expected head ${expected}`,
122
+ );
123
+ }
124
+ server = await serveStorybook(
125
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
126
+ );
127
+ const response = await fetch(`${server.origin}/astryx-build-sha.txt`);
128
+ if (!response.ok) {
129
+ throw new Error(`Storybook build has no SHA stamp (${response.status})`);
130
+ }
131
+ storybookSha = (await response.text()).trim();
132
+ if (storybookSha !== checkoutSha) {
133
+ throw new Error(
134
+ `Storybook ${storybookSha} does not match checkout ${checkoutSha}`,
135
+ );
136
+ }
137
+ });
138
+
139
+ test.afterAll(async () => {
140
+ await server?.close();
141
+ });
142
+
143
+ for (const item of cases) {
144
+ test(item.key, async ({browser}) => {
145
+ const context = await browser.newContext({
146
+ viewport: item.viewport,
147
+ colorScheme: item.mode,
148
+ reducedMotion: 'reduce',
149
+ });
150
+ const page = await context.newPage();
151
+ const pageErrors: string[] = [];
152
+ page.on('pageerror', error => pageErrors.push(String(error)));
153
+ await page.goto(storyUrl(item), {waitUntil: 'load'});
154
+ await page.locator('#storybook-root').waitFor();
155
+ const menu = page.getByRole('menu').last();
156
+ await expect(menu).toBeVisible();
157
+ const separator = page.getByRole('separator');
158
+ await expect(separator).toBeVisible();
159
+ await expect(separator).toHaveAttribute('aria-orientation', 'horizontal');
160
+ await settle(page);
161
+
162
+ const menuItems = page.getByRole('menuitem');
163
+ await expect(menuItems).toHaveCount(2);
164
+ await menu.focus();
165
+ await page.keyboard.press('ArrowDown');
166
+ await expect(menuItems.first()).toBeFocused();
167
+ await page.keyboard.press('ArrowDown');
168
+ await expect(menuItems.last()).toBeFocused();
169
+ await expect(separator).not.toBeFocused();
170
+
171
+ const observed = await separator.evaluate(element => {
172
+ const rootBox = element.getBoundingClientRect();
173
+ const rule = element.firstElementChild;
174
+ const ruleBox = rule?.getBoundingClientRect() ?? null;
175
+ const rootStyle = getComputedStyle(element);
176
+ const ruleStyle = rule ? getComputedStyle(rule) : null;
177
+ return {
178
+ theme: document
179
+ .querySelector('[data-astryx-theme]')
180
+ ?.getAttribute('data-astryx-theme'),
181
+ mode: document.documentElement.getAttribute('data-theme'),
182
+ direction: rootStyle.direction,
183
+ viewport: {width: innerWidth, height: innerHeight},
184
+ reducedMotion: matchMedia('(prefers-reduced-motion: reduce)').matches,
185
+ horizontalOverflow:
186
+ document.documentElement.scrollWidth > innerWidth + 1,
187
+ fonts: document.fonts.status,
188
+ role: element.getAttribute('role'),
189
+ orientation: element.getAttribute('aria-orientation'),
190
+ tabIndex: element.getAttribute('tabindex'),
191
+ targetClasses: {
192
+ menuDivider: element.classList.contains(
193
+ 'astryx-dropdown-menu-divider',
194
+ ),
195
+ divider: element.classList.contains('astryx-divider'),
196
+ },
197
+ geometry: {
198
+ root: {
199
+ x: rootBox.x,
200
+ y: rootBox.y,
201
+ width: rootBox.width,
202
+ height: rootBox.height,
203
+ },
204
+ rule:
205
+ ruleBox === null
206
+ ? null
207
+ : {
208
+ x: ruleBox.x,
209
+ y: ruleBox.y,
210
+ width: ruleBox.width,
211
+ height: ruleBox.height,
212
+ },
213
+ },
214
+ ruleBackground: ruleStyle?.backgroundColor ?? null,
215
+ };
216
+ });
217
+
218
+ const geometryPass =
219
+ observed.geometry.root.width > 0 &&
220
+ observed.geometry.root.height > 0 &&
221
+ observed.geometry.rule !== null &&
222
+ observed.geometry.rule.width > 0 &&
223
+ observed.geometry.rule.height > 0;
224
+ const paintPass =
225
+ observed.ruleBackground !== null &&
226
+ observed.ruleBackground !== 'rgba(0, 0, 0, 0)' &&
227
+ observed.ruleBackground !== 'transparent';
228
+ const png = await page.screenshot({animations: 'disabled', fullPage: true});
229
+ const image = `${item.key}.png`;
230
+ const receipt = {
231
+ schemaVersion: 1,
232
+ expected: {
233
+ build: checkoutSha,
234
+ storyId: STORY_ID,
235
+ mode: item.mode,
236
+ direction: item.direction,
237
+ theme: item.theme,
238
+ viewport: item.viewport,
239
+ },
240
+ observed: {
241
+ build: storybookSha,
242
+ ...observed,
243
+ menuItemCount: await menuItems.count(),
244
+ pageErrors,
245
+ },
246
+ image: {
247
+ file: image,
248
+ sha256: createHash('sha256').update(png).digest('hex'),
249
+ width: png.readUInt32BE(16),
250
+ height: png.readUInt32BE(20),
251
+ },
252
+ checks: {geometryPass, paintPass},
253
+ passed:
254
+ storybookSha === checkoutSha &&
255
+ pageErrors.length === 0 &&
256
+ observed.theme === item.theme &&
257
+ observed.mode === item.mode &&
258
+ observed.direction === item.direction &&
259
+ observed.viewport.width === item.viewport.width &&
260
+ observed.viewport.height === item.viewport.height &&
261
+ observed.reducedMotion &&
262
+ !observed.horizontalOverflow &&
263
+ observed.fonts === 'loaded' &&
264
+ observed.role === 'separator' &&
265
+ observed.orientation === 'horizontal' &&
266
+ observed.tabIndex === null &&
267
+ observed.targetClasses.menuDivider &&
268
+ observed.targetClasses.divider &&
269
+ geometryPass &&
270
+ paintPass,
271
+ };
272
+ fs.writeFileSync(path.join(OUTPUT, image), png);
273
+ fs.writeFileSync(
274
+ path.join(OUTPUT, `${item.key}.sensors.json`),
275
+ `${JSON.stringify(receipt, null, 2)}\n`,
276
+ );
277
+ expect(receipt.passed).toBe(true);
278
+ await context.close();
279
+ });
280
+ }
@@ -0,0 +1,145 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Menu.a11y.chromium.spec.ts
5
+ * @input Built menu stories and MENU_PATTERN in real Chromium
6
+ * @output Browser evidence for every menu expectation across the DropdownMenu pointer-menu states
7
+ * @position Chromium lane of the DropdownMenu menu-surface binding
8
+ */
9
+
10
+ import {
11
+ expect,
12
+ test,
13
+ type CDPSession,
14
+ type Locator,
15
+ type Page,
16
+ } from '@playwright/test';
17
+ import {
18
+ MENU_PATTERN,
19
+ blockingResults,
20
+ checkAccessibilitySpec,
21
+ formatFailures,
22
+ neverExercised,
23
+ summarize,
24
+ unmatchedKnownFailures,
25
+ type BindingResult,
26
+ } from '@astryxdesign/a11y-spec';
27
+ import {
28
+ createChromiumHarness,
29
+ holdMotionStill,
30
+ } from '@astryxdesign/a11y-spec/chromium';
31
+ import {
32
+ DEFAULT_STORYBOOK_DIR,
33
+ serveStorybook,
34
+ type StaticServer,
35
+ } from '@astryxdesign/a11y-spec/storybook';
36
+ import {MENU_KNOWN_FAILURES} from './Menu.a11y.known-failures';
37
+ import {MENU_BINDING_STATES, type MenuBindingState} from './Menu.a11y.states';
38
+
39
+ let storybook: StaticServer;
40
+
41
+ test.beforeAll(async () => {
42
+ storybook = await serveStorybook(
43
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
44
+ );
45
+ });
46
+
47
+ test.afterAll(async () => {
48
+ await storybook?.close();
49
+ });
50
+
51
+ function storyUrl(storyId: string): string {
52
+ return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
53
+ }
54
+
55
+ /**
56
+ * Opens the menu the way a keyboard user does — Enter on the focused trigger —
57
+ * and waits until focus is on the first item, so every expectation starts from
58
+ * a displayed menu with focus inside it.
59
+ */
60
+ async function mountState(
61
+ page: Page,
62
+ state: MenuBindingState,
63
+ ): Promise<{subject: Locator; related: Record<string, Locator>}> {
64
+ await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
65
+ await holdMotionStill(page);
66
+ const opener = page
67
+ .locator('#storybook-root')
68
+ .getByRole('button', {name: state.label});
69
+ await opener.focus();
70
+ await page.keyboard.press('Enter');
71
+ const id = await opener.getAttribute('aria-controls');
72
+ if (id == null) {
73
+ throw new Error(`menu state "${state.id}" has no aria-controls`);
74
+ }
75
+ const related: Record<string, Locator> = {opener};
76
+ for (const [relation, [role, name]] of Object.entries(state.items)) {
77
+ related[relation] = page.getByRole(
78
+ role as Parameters<Page['getByRole']>[0],
79
+ {
80
+ name,
81
+ exact: true,
82
+ includeHidden: true,
83
+ },
84
+ );
85
+ }
86
+ await expect(related['first-item']).toBeFocused();
87
+ return {subject: page.locator(`[id="${id}"]`), related};
88
+ }
89
+
90
+ /**
91
+ * How many item actions the story's handlers have run. The story renders the
92
+ * count as an attribute, so this reads a fact the page shows rather than
93
+ * instrumenting it from the test.
94
+ */
95
+ async function activationsOn(page: Page): Promise<number> {
96
+ const raw = await page
97
+ .locator('[data-a11y-activations]')
98
+ .first()
99
+ .getAttribute('data-a11y-activations');
100
+ return Number(raw ?? '0');
101
+ }
102
+
103
+ async function runState(
104
+ page: Page,
105
+ cdp: CDPSession,
106
+ state: MenuBindingState,
107
+ ): Promise<BindingResult> {
108
+ return checkAccessibilitySpec({
109
+ spec: MENU_PATTERN,
110
+ binding: 'DropdownMenu.menu',
111
+ state: state.id,
112
+ facts: state.facts,
113
+ knownFailures: MENU_KNOWN_FAILURES,
114
+ mount: async () => {
115
+ const {subject, related} = await mountState(page, state);
116
+ return createChromiumHarness({page, cdp, subject, related});
117
+ },
118
+ activations: async () => activationsOn(page),
119
+ });
120
+ }
121
+
122
+ test('every menu expectation is exercised by a DropdownMenu state', async ({
123
+ page,
124
+ }) => {
125
+ const cdp = await page.context().newCDPSession(page);
126
+ const results: BindingResult[] = [];
127
+ for (const state of MENU_BINDING_STATES) {
128
+ results.push(await runState(page, cdp, state));
129
+ }
130
+ expect(neverExercised(results)).toEqual([]);
131
+ expect(unmatchedKnownFailures(MENU_KNOWN_FAILURES, results)).toEqual([]);
132
+ });
133
+
134
+ for (const state of MENU_BINDING_STATES) {
135
+ test(`DropdownMenu.menu [${state.id}] — ${state.summary}`, async ({page}) => {
136
+ const cdp = await page.context().newCDPSession(page);
137
+ const result = await runState(page, cdp, state);
138
+ const report = summarize(MENU_PATTERN, [result]);
139
+ // Required rows block (AST-020 FR9); advisory rows and exact known
140
+ // failures report.
141
+ expect(formatFailures(blockingResults([result]))).toBe('');
142
+ expect(report.unrunLayers).toEqual([]);
143
+ expect(report.counts.unexpectedPass).toBe(0);
144
+ });
145
+ }
@@ -0,0 +1,26 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Menu.a11y.known-failures.ts
5
+ * @input The exact menu-contract failures the shipped DropdownMenu produces today
6
+ * @output MENU_KNOWN_FAILURES — records both lanes reconcile against, so a fix shows up as an unexpected pass and any other failure stays a failure
7
+ * @position Known-failure ledger for the DropdownMenu menu-surface binding
8
+ */
9
+
10
+ import type {KnownFailure} from '@astryxdesign/a11y-spec';
11
+
12
+ export const MENU_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
13
+ {
14
+ expectation: 'menu.keys.enter-activates-and-closes',
15
+ binding: 'DropdownMenu.menu',
16
+ state: 'dropdown-selectable-checkbox-row',
17
+ evidenceLayer: 'real-browser',
18
+ failureEquals: 'Enter on an item left the menu displayed',
19
+ standardsReference:
20
+ 'APG Menu and Menubar, Enter: "Otherwise, activates the item and closes the menu" (adopted as a required part by spec:AST-020 FR15); WCAG 2.2 2.1.1 Keyboard',
21
+ userImpact:
22
+ 'A keyboard user who presses Enter on a checkbox row toggles it but stays in the open menu, where the pattern says Enter chooses and closes; Escape or Tab is needed to get back to the page.',
23
+ reason:
24
+ 'DropdownMenuCheckboxItem keeps the menu open by default (hasCloseOnSelect is false), shipped as released behavior under component:DropdownMenu FR16. Whether Enter on a checkbox row closes the menu, or the component record names staying open as an exception, is a behavior decision for the component owner; this test-only binding records it and changes nothing.',
25
+ },
26
+ ];
@@ -0,0 +1,65 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file Menu.a11y.renders.tsx
7
+ * @input One menu binding state from Menu.a11y.states.ts
8
+ * @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook), publishing how many item actions have run as `data-a11y-activations`
9
+ * @position Shared fixture for the DropdownMenu menu-surface binding
10
+ */
11
+
12
+ import {useState, type ReactElement} from 'react';
13
+ import {DropdownMenu} from '../DropdownMenu';
14
+ import {DropdownMenuItem} from '../DropdownMenuItem';
15
+ import {DropdownMenuCheckboxItem} from '../DropdownMenuCheckboxItem';
16
+ import {DropdownMenuRadioGroup} from '../DropdownMenuRadioGroup';
17
+ import {DropdownMenuRadioItem} from '../DropdownMenuRadioItem';
18
+ import {MENU_ACTION_ITEMS, type MenuBindingState} from './Menu.a11y.states';
19
+
20
+ function MenuState({
21
+ content,
22
+ label,
23
+ }: Pick<MenuBindingState, 'content' | 'label'>): ReactElement {
24
+ // An item's action leaves no trace on the menu, so every handler counts
25
+ // here and the count is rendered for the browser lane to read.
26
+ const [activations, setActivations] = useState(0);
27
+ const activate = () => setActivations(count => count + 1);
28
+ const menu =
29
+ content === 'actions' ? (
30
+ <DropdownMenu
31
+ button={{label}}
32
+ items={MENU_ACTION_ITEMS.map(item => ({...item, onClick: activate}))}
33
+ presentation="popover"
34
+ />
35
+ ) : (
36
+ <DropdownMenu button={{label}} presentation="popover">
37
+ <DropdownMenuItem label="Edit" onClick={activate} />
38
+ {/* No hasCloseOnSelect: the row keeps the menu open by default. */}
39
+ <DropdownMenuCheckboxItem
40
+ label="Show archived"
41
+ value={true}
42
+ onChange={activate}
43
+ />
44
+ <DropdownMenuRadioGroup
45
+ value="newest"
46
+ onChange={activate}
47
+ label="Sort by">
48
+ <DropdownMenuRadioItem value="newest" label="Newest" />
49
+ <DropdownMenuRadioItem value="oldest" label="Oldest" />
50
+ </DropdownMenuRadioGroup>
51
+ </DropdownMenu>
52
+ );
53
+ return (
54
+ <div data-a11y-activations={activations}>
55
+ {menu}
56
+ <button type="button">After the menu</button>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ export function renderMenuState(
62
+ state: Pick<MenuBindingState, 'content' | 'label'>,
63
+ ): ReactElement {
64
+ return <MenuState content={state.content} label={state.label} />;
65
+ }
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Menu.a11y.states.ts
5
+ * @input The DropdownMenu pointer menu's representative open states and the facts MENU_PATTERN reads
6
+ * @output MENU_BINDING_STATES and the explicit ownership exclusions for this binding
7
+ * @position Binding inventory shared by the jsdom test and the Chromium spec (component:DropdownMenu menu surface under spec:AST-020 FR15)
8
+ */
9
+
10
+ import type {MenuStateFacts} from '@astryxdesign/a11y-spec';
11
+
12
+ export interface MenuBindingState {
13
+ readonly id: string;
14
+ readonly summary: string;
15
+ readonly storyId: string;
16
+ /** Which menu content the state renders. */
17
+ readonly content: 'actions' | 'selectable';
18
+ /** The trigger's accessible name; the menu is named by it. */
19
+ readonly label: string;
20
+ /** Relation name → [role, accessible name] of the item it designates. */
21
+ readonly items: Readonly<Record<string, readonly [string, string]>>;
22
+ readonly facts: MenuStateFacts;
23
+ }
24
+
25
+ export const MENU_ACTION_ITEMS = [
26
+ {label: 'Rename'},
27
+ {label: 'Duplicate'},
28
+ {label: 'Archive', isDisabled: true},
29
+ {label: 'Delete'},
30
+ ] as const;
31
+
32
+ const story = (id: string) => `a11y-menu--${id}`;
33
+
34
+ export const MENU_BINDING_STATES = [
35
+ {
36
+ id: 'dropdown-actions',
37
+ summary:
38
+ 'a DropdownMenu of four actions, one disabled, opened from its Button trigger',
39
+ storyId: story('dropdown-actions'),
40
+ content: 'actions',
41
+ label: 'Actions',
42
+ items: {
43
+ 'first-item': ['menuitem', 'Rename'],
44
+ 'second-item': ['menuitem', 'Duplicate'],
45
+ 'last-item': ['menuitem', 'Delete'],
46
+ 'disabled-item': ['menuitem', 'Archive'],
47
+ 'typeahead-target': ['menuitem', 'Duplicate'],
48
+ },
49
+ facts: {
50
+ enterTarget: 'first-item',
51
+ wraps: true,
52
+ homeEnd: true,
53
+ disabledItem: true,
54
+ checked: {},
55
+ typeaheadCharacter: 'd',
56
+ },
57
+ },
58
+ {
59
+ id: 'dropdown-selectable',
60
+ summary:
61
+ 'a DropdownMenu holding an action, a checked checkbox row and a titled radio group',
62
+ storyId: story('dropdown-selectable'),
63
+ content: 'selectable',
64
+ label: 'View',
65
+ items: {
66
+ 'first-item': ['menuitem', 'Edit'],
67
+ 'second-item': ['menuitemcheckbox', 'Show archived'],
68
+ 'last-item': ['menuitemradio', 'Oldest'],
69
+ 'checkbox-item': ['menuitemcheckbox', 'Show archived'],
70
+ 'radio-item': ['menuitemradio', 'Newest'],
71
+ 'unchecked-radio-item': ['menuitemradio', 'Oldest'],
72
+ 'typeahead-target': ['menuitemradio', 'Oldest'],
73
+ },
74
+ facts: {
75
+ enterTarget: 'first-item',
76
+ wraps: true,
77
+ homeEnd: true,
78
+ disabledItem: false,
79
+ checked: {
80
+ 'checkbox-item': true,
81
+ 'radio-item': true,
82
+ 'unchecked-radio-item': false,
83
+ },
84
+ typeaheadCharacter: 'o',
85
+ },
86
+ },
87
+ {
88
+ id: 'dropdown-selectable-checkbox-row',
89
+ summary:
90
+ 'the same selectable menu with Enter pressed on its checkbox row, which keeps the menu open (DropdownMenuCheckboxItem hasCloseOnSelect defaults to false)',
91
+ storyId: story('dropdown-selectable-checkbox-row'),
92
+ content: 'selectable',
93
+ label: 'View',
94
+ items: {
95
+ 'first-item': ['menuitem', 'Edit'],
96
+ 'second-item': ['menuitemcheckbox', 'Show archived'],
97
+ 'last-item': ['menuitemradio', 'Oldest'],
98
+ 'checkbox-item': ['menuitemcheckbox', 'Show archived'],
99
+ 'radio-item': ['menuitemradio', 'Newest'],
100
+ 'unchecked-radio-item': ['menuitemradio', 'Oldest'],
101
+ 'typeahead-target': ['menuitemradio', 'Oldest'],
102
+ },
103
+ facts: {
104
+ enterTarget: 'checkbox-item',
105
+ wraps: true,
106
+ homeEnd: true,
107
+ disabledItem: false,
108
+ checked: {
109
+ 'checkbox-item': true,
110
+ 'radio-item': true,
111
+ 'unchecked-radio-item': false,
112
+ },
113
+ typeaheadCharacter: 'o',
114
+ },
115
+ },
116
+ ] as const satisfies ReadonlyArray<MenuBindingState>;
117
+
118
+ export const MENU_EXCLUSIONS = [
119
+ {
120
+ id: 'opening-control',
121
+ owner: 'the menu-button pattern (MENU_BUTTON_PATTERN, MenuButton.a11y.*)',
122
+ reason:
123
+ 'How the trigger exposes itself and where focus lands when it opens the menu are the Menu Button contract. This binding opens the menu from the trigger with Enter only to reach the open state it checks.',
124
+ },
125
+ {
126
+ id: 'submenus',
127
+ owner:
128
+ 'DropdownMenuSubMenu.test.tsx and DropdownMenuDrillIn.a11y.chromium.spec.ts',
129
+ reason:
130
+ 'Right and Left Arrow opening and closing a flyout, a parent row’s haspopup and expanded state, and the compact-touch drill-in are not yet in a shared contract; the menu pattern exempts them to the binding, so none of these states renders a sub-menu.',
131
+ },
132
+ {
133
+ id: 'bottom-sheet-and-header-footer',
134
+ owner: 'component:DropdownMenu AR4/AR5 and the modal-dialog pattern',
135
+ reason:
136
+ 'On a compact touch display the menu is a bottom sheet, and with a header or footer the popover is a modal dialog in which Tab cycles instead of closing; both are dialogs, so every state here uses the plain pointer popover.',
137
+ },
138
+ ] as const;