@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

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 (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import {describe, it, expect, vi, beforeEach} from 'vitest';
13
- import {render, screen} from '@testing-library/react';
13
+ import {render, screen, fireEvent, waitFor} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
15
  import {DropdownMenu} from './DropdownMenu';
16
16
  import {DropdownMenuCheckboxItem} from './DropdownMenuCheckboxItem';
@@ -63,6 +63,151 @@ describe('DropdownMenuCheckboxItem', () => {
63
63
  }),
64
64
  );
65
65
  expect(onChangeSpy).toHaveBeenCalledWith(true);
66
+ expect(HTMLElement.prototype.hidePopover).not.toHaveBeenCalled();
67
+ });
68
+
69
+ it('composes the caller click handler with the checked-state change', async () => {
70
+ const user = userEvent.setup();
71
+ const calls: string[] = [];
72
+ const onChange = vi.fn(() => calls.push('change'));
73
+ const onClick = vi.fn(() => calls.push('click'));
74
+ render(
75
+ <DropdownMenu button={{label: 'View'}}>
76
+ <DropdownMenuCheckboxItem
77
+ label="Show archived"
78
+ value={false}
79
+ onChange={onChange}
80
+ onClick={onClick}
81
+ />
82
+ </DropdownMenu>,
83
+ );
84
+ await user.click(screen.getByRole('button', {name: /View/}));
85
+ await user.click(
86
+ screen.getByRole('menuitemcheckbox', {
87
+ name: /Show archived/,
88
+ hidden: true,
89
+ }),
90
+ );
91
+ expect(onChange).toHaveBeenCalledWith(true);
92
+ expect(onClick).toHaveBeenCalledOnce();
93
+ expect(calls).toEqual(['change', 'click']);
94
+ });
95
+
96
+ it('composes the caller click handler on keyboard activation', async () => {
97
+ const user = userEvent.setup();
98
+ const onChange = vi.fn();
99
+ const onClick = vi.fn();
100
+ render(
101
+ <DropdownMenu button={{label: 'View'}}>
102
+ <DropdownMenuCheckboxItem
103
+ label="Show archived"
104
+ value={false}
105
+ onChange={onChange}
106
+ onClick={onClick}
107
+ />
108
+ </DropdownMenu>,
109
+ );
110
+ const trigger = screen.getByRole('button', {name: /View/});
111
+ trigger.focus();
112
+ await user.keyboard('{Enter}');
113
+ const item = screen.getByRole('menuitemcheckbox', {
114
+ name: /Show archived/,
115
+ hidden: true,
116
+ });
117
+ await waitFor(() => expect(item).toHaveFocus());
118
+ await user.keyboard('{Enter}');
119
+ expect(onChange).toHaveBeenCalledWith(true);
120
+ expect(onClick).toHaveBeenCalledOnce();
121
+ });
122
+
123
+ it('composes the caller pointer-move handler with menu hover focus', async () => {
124
+ const user = userEvent.setup();
125
+ const onPointerMove = vi.fn(() => document.activeElement);
126
+ render(
127
+ <DropdownMenu button={{label: 'View'}}>
128
+ <DropdownMenuCheckboxItem
129
+ label="Show archived"
130
+ value={false}
131
+ onPointerMove={onPointerMove}
132
+ />
133
+ </DropdownMenu>,
134
+ );
135
+ await user.click(screen.getByRole('button', {name: /View/}));
136
+ const item = screen.getByRole('menuitemcheckbox', {
137
+ name: /Show archived/,
138
+ hidden: true,
139
+ });
140
+ fireEvent.pointerMove(item, {pointerType: 'mouse'});
141
+ expect(item).toHaveFocus();
142
+ expect(onPointerMove).toHaveBeenCalledOnce();
143
+ expect(onPointerMove).toHaveReturnedWith(item);
144
+ });
145
+
146
+ it('does not call consumer activation handlers when disabled', async () => {
147
+ const user = userEvent.setup();
148
+ const onChange = vi.fn();
149
+ const onClick = vi.fn();
150
+ render(
151
+ <DropdownMenu button={{label: 'View'}}>
152
+ <DropdownMenuCheckboxItem
153
+ label="Show archived"
154
+ value={false}
155
+ onChange={onChange}
156
+ onClick={onClick}
157
+ isDisabled
158
+ />
159
+ </DropdownMenu>,
160
+ );
161
+ await user.click(screen.getByRole('button', {name: /View/}));
162
+ await user.click(
163
+ screen.getByRole('menuitemcheckbox', {
164
+ name: /Show archived/,
165
+ hidden: true,
166
+ }),
167
+ );
168
+ expect(onChange).not.toHaveBeenCalled();
169
+ expect(onClick).not.toHaveBeenCalled();
170
+ });
171
+
172
+ it('closes after a successful toggle only when requested', async () => {
173
+ const user = userEvent.setup();
174
+ render(
175
+ <DropdownMenu button={{label: 'View'}}>
176
+ <DropdownMenuCheckboxItem
177
+ label="Show archived"
178
+ value={false}
179
+ onChange={() => {}}
180
+ hasCloseOnSelect
181
+ />
182
+ </DropdownMenu>,
183
+ );
184
+ await user.click(screen.getByRole('button', {name: /View/}));
185
+ await user.click(
186
+ screen.getByRole('menuitemcheckbox', {
187
+ name: /Show archived/,
188
+ hidden: true,
189
+ }),
190
+ );
191
+ expect(HTMLElement.prototype.hidePopover).toHaveBeenCalledOnce();
192
+ });
193
+
194
+ it('maps a small menu to the small shared checkbox marker', async () => {
195
+ const user = userEvent.setup();
196
+ render(
197
+ <DropdownMenu button={{label: 'View', size: 'sm'}}>
198
+ <DropdownMenuCheckboxItem label="Show archived" value={true} />
199
+ </DropdownMenu>,
200
+ );
201
+ await user.click(screen.getByRole('button', {name: /View/}));
202
+ const row = screen.getByRole('menuitemcheckbox', {
203
+ name: /Show archived/,
204
+ hidden: true,
205
+ });
206
+ expect(row).toHaveAttribute('aria-checked', 'true');
207
+ expect(row.querySelector('.astryx-checkbox-indicator')).toHaveAttribute(
208
+ 'data-size',
209
+ 'sm',
210
+ );
66
211
  });
67
212
 
68
213
  it('keeps the checkbox indicator decorative (row is the only announced control)', async () => {
@@ -0,0 +1,136 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MenuButton.a11y.chromium.spec.ts
5
+ * @input Built menu-button stories and MENU_BUTTON_PATTERN in real Chromium
6
+ * @output Browser evidence for every menu-button expectation across the DropdownMenu and MoreMenu trigger states
7
+ * @position Chromium lane of the menu-button binding (component:DropdownMenu AR4, component:MoreMenu AR1)
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_BUTTON_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_BUTTON_KNOWN_FAILURES} from './MenuButton.a11y.known-failures';
37
+ import {
38
+ MENU_BUTTON_BINDING_STATES,
39
+ type MenuButtonBindingState,
40
+ } from './MenuButton.a11y.states';
41
+
42
+ let storybook: StaticServer;
43
+
44
+ test.beforeAll(async () => {
45
+ storybook = await serveStorybook(
46
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
47
+ );
48
+ });
49
+
50
+ test.afterAll(async () => {
51
+ await storybook?.close();
52
+ });
53
+
54
+ function storyUrl(storyId: string): string {
55
+ return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
56
+ }
57
+
58
+ function trigger(page: Page, label: string): Locator {
59
+ return page
60
+ .locator('#storybook-root')
61
+ .getByRole('button', {name: label, includeHidden: true});
62
+ }
63
+
64
+ async function mountState(
65
+ page: Page,
66
+ state: MenuButtonBindingState,
67
+ ): Promise<{subject: Locator; related: Record<string, Locator>}> {
68
+ await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
69
+ await holdMotionStill(page);
70
+ const subject = trigger(page, state.label);
71
+ await subject.waitFor({state: 'attached'});
72
+ const id = await subject.getAttribute('aria-controls');
73
+ if (id == null) {
74
+ throw new Error(`menu-button state "${state.id}" has no aria-controls`);
75
+ }
76
+ const menu = page.locator(`[id="${id}"]`);
77
+ const related: Record<string, Locator> = {
78
+ menu,
79
+ 'first-item': page.getByRole('menuitem', {
80
+ name: state.firstItem,
81
+ includeHidden: true,
82
+ }),
83
+ 'last-item': page.getByRole('menuitem', {
84
+ name: state.lastItem,
85
+ includeHidden: true,
86
+ }),
87
+ };
88
+ return {subject, related};
89
+ }
90
+
91
+ async function runState(
92
+ page: Page,
93
+ cdp: CDPSession,
94
+ state: MenuButtonBindingState,
95
+ ): Promise<BindingResult> {
96
+ return checkAccessibilitySpec({
97
+ spec: MENU_BUTTON_PATTERN,
98
+ binding: 'DropdownMenu.trigger',
99
+ state: state.id,
100
+ facts: state.facts,
101
+ knownFailures: MENU_BUTTON_KNOWN_FAILURES,
102
+ mount: async () => {
103
+ const {subject, related} = await mountState(page, state);
104
+ return createChromiumHarness({page, cdp, subject, related});
105
+ },
106
+ });
107
+ }
108
+
109
+ test('every menu-button expectation is exercised by a DropdownMenu or MoreMenu state', async ({
110
+ page,
111
+ }) => {
112
+ const cdp = await page.context().newCDPSession(page);
113
+ const results: BindingResult[] = [];
114
+ for (const state of MENU_BUTTON_BINDING_STATES) {
115
+ results.push(await runState(page, cdp, state));
116
+ }
117
+ expect(neverExercised(results)).toEqual([]);
118
+ expect(unmatchedKnownFailures(MENU_BUTTON_KNOWN_FAILURES, results)).toEqual(
119
+ [],
120
+ );
121
+ });
122
+
123
+ for (const state of MENU_BUTTON_BINDING_STATES) {
124
+ test(`DropdownMenu.trigger [${state.id}] — ${state.summary}`, async ({
125
+ page,
126
+ }) => {
127
+ const cdp = await page.context().newCDPSession(page);
128
+ const result = await runState(page, cdp, state);
129
+ const report = summarize(MENU_BUTTON_PATTERN, [result]);
130
+ // Required rows block (AST-020 FR9); advisory rows and exact known
131
+ // failures report.
132
+ expect(formatFailures(blockingResults([result]))).toBe('');
133
+ expect(report.unrunLayers).toEqual([]);
134
+ expect(report.counts.unexpectedPass).toBe(0);
135
+ });
136
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MenuButton.a11y.known-failures.ts
5
+ * @input Uses KnownFailure from @astryxdesign/a11y-spec
6
+ * @output MENU_BUTTON_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/MoreMenu menu-button binding
8
+ */
9
+
10
+ import type {KnownFailure} from '@astryxdesign/a11y-spec';
11
+
12
+ export const MENU_BUTTON_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
@@ -0,0 +1,67 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MenuButton.a11y.renders.tsx
5
+ * @input One menu-button binding state from MenuButton.a11y.states.ts
6
+ * @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook)
7
+ * @position Shared fixture for the DropdownMenu and MoreMenu menu-button binding
8
+ */
9
+
10
+ import type {ReactElement} from 'react';
11
+ import {DropdownMenu} from '../DropdownMenu';
12
+ import {MoreMenu} from '../../MoreMenu';
13
+ import {
14
+ MENU_BUTTON_ITEMS,
15
+ type MenuButtonBindingState,
16
+ } from './MenuButton.a11y.states';
17
+
18
+ const items = MENU_BUTTON_ITEMS.map(item => ({...item}));
19
+
20
+ export function renderMenuButtonState(
21
+ state: Pick<
22
+ MenuButtonBindingState,
23
+ 'component' | 'label' | 'open' | 'disabled'
24
+ >,
25
+ ): ReactElement {
26
+ let menu: ReactElement;
27
+ switch (state.component) {
28
+ case 'MoreMenu':
29
+ menu = (
30
+ <MoreMenu
31
+ label={state.label}
32
+ items={items}
33
+ presentation="popover"
34
+ isDisabled={state.disabled}
35
+ />
36
+ );
37
+ break;
38
+ case 'DropdownMenu.renderTrigger':
39
+ menu = (
40
+ <DropdownMenu
41
+ items={items}
42
+ presentation="popover"
43
+ renderTrigger={({ref, ...props}) => (
44
+ <button type="button" ref={ref} {...props}>
45
+ {state.label}
46
+ </button>
47
+ )}
48
+ />
49
+ );
50
+ break;
51
+ case 'DropdownMenu':
52
+ menu = (
53
+ <DropdownMenu
54
+ button={{label: state.label, isDisabled: state.disabled}}
55
+ items={items}
56
+ presentation="popover"
57
+ {...(state.open ? {isMenuOpen: true, onOpenChange: () => {}} : {})}
58
+ />
59
+ );
60
+ }
61
+ return (
62
+ <div>
63
+ {menu}
64
+ <button type="button">After the menu</button>
65
+ </div>
66
+ );
67
+ }
@@ -0,0 +1,141 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file MenuButton.a11y.states.ts
5
+ * @input The anchored DropdownMenu and MoreMenu triggers' representative states and the facts MENU_BUTTON_PATTERN reads
6
+ * @output MENU_BUTTON_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 AR4, component:MoreMenu AR1)
8
+ */
9
+
10
+ import type {MenuButtonStateFacts} from '@astryxdesign/a11y-spec';
11
+
12
+ export interface MenuButtonBindingState {
13
+ readonly id: string;
14
+ readonly summary: string;
15
+ readonly storyId: string;
16
+ /** Which component renders the trigger. */
17
+ readonly component:
18
+ 'DropdownMenu' | 'DropdownMenu.renderTrigger' | 'MoreMenu';
19
+ /** The trigger's accessible name. */
20
+ readonly label: string;
21
+ /** Labels of the menu's first and last enabled items. */
22
+ readonly firstItem: string;
23
+ readonly lastItem: string;
24
+ readonly facts: MenuButtonStateFacts;
25
+ /** Whether the menu is mounted open (controlled `isMenuOpen`). */
26
+ readonly open?: boolean;
27
+ /** Whether the trigger is disabled. */
28
+ readonly disabled?: boolean;
29
+ }
30
+
31
+ export const MENU_BUTTON_ITEMS = [
32
+ {label: 'Rename'},
33
+ {label: 'Duplicate'},
34
+ {label: 'Archive', isDisabled: true},
35
+ {label: 'Delete'},
36
+ ] as const;
37
+
38
+ function facts(
39
+ overrides: Partial<MenuButtonStateFacts> = {},
40
+ ): MenuButtonStateFacts {
41
+ return {
42
+ open: false,
43
+ operable: true,
44
+ focusable: true,
45
+ controls: true,
46
+ arrowKeysOpen: true,
47
+ ...overrides,
48
+ };
49
+ }
50
+
51
+ const story = (id: string) => `a11y-menubutton--${id}`;
52
+
53
+ export const MENU_BUTTON_BINDING_STATES = [
54
+ {
55
+ id: 'dropdown-button-closed',
56
+ summary: 'the default DropdownMenu Button trigger with its menu hidden',
57
+ storyId: story('dropdown-button-closed'),
58
+ component: 'DropdownMenu',
59
+ label: 'Actions',
60
+ firstItem: 'Rename',
61
+ lastItem: 'Delete',
62
+ facts: facts(),
63
+ },
64
+ {
65
+ id: 'dropdown-button-open',
66
+ summary: 'the default DropdownMenu Button trigger with its menu displayed',
67
+ storyId: story('dropdown-button-open'),
68
+ component: 'DropdownMenu',
69
+ label: 'Actions',
70
+ firstItem: 'Rename',
71
+ lastItem: 'Delete',
72
+ facts: facts({open: true}),
73
+ open: true,
74
+ },
75
+ {
76
+ id: 'dropdown-render-trigger-closed',
77
+ summary:
78
+ 'a caller-rendered trigger that spreads the DropdownMenu trigger props onto its own button',
79
+ storyId: story('dropdown-render-trigger-closed'),
80
+ component: 'DropdownMenu.renderTrigger',
81
+ label: 'Row actions',
82
+ firstItem: 'Rename',
83
+ lastItem: 'Delete',
84
+ facts: facts(),
85
+ },
86
+ {
87
+ id: 'dropdown-button-disabled',
88
+ summary: 'a disabled DropdownMenu trigger that keeps its menu hidden',
89
+ storyId: story('dropdown-button-disabled'),
90
+ component: 'DropdownMenu',
91
+ label: 'Actions',
92
+ firstItem: 'Rename',
93
+ lastItem: 'Delete',
94
+ facts: facts({operable: false, focusable: false}),
95
+ disabled: true,
96
+ },
97
+ {
98
+ id: 'more-menu-closed',
99
+ summary: 'the MoreMenu icon-only trigger with its menu hidden',
100
+ storyId: story('more-menu-closed'),
101
+ component: 'MoreMenu',
102
+ label: 'More options',
103
+ firstItem: 'Rename',
104
+ lastItem: 'Delete',
105
+ facts: facts(),
106
+ },
107
+ {
108
+ id: 'more-menu-disabled',
109
+ summary:
110
+ 'a disabled MoreMenu trigger that stays focusable for its tooltip and must keep its menu closed',
111
+ storyId: story('more-menu-disabled'),
112
+ component: 'MoreMenu',
113
+ label: 'More options',
114
+ firstItem: 'Rename',
115
+ lastItem: 'Delete',
116
+ facts: facts({operable: false, focusable: true}),
117
+ disabled: true,
118
+ },
119
+ ] as const satisfies ReadonlyArray<MenuButtonBindingState>;
120
+
121
+ export const MENU_BUTTON_EXCLUSIONS = [
122
+ {
123
+ id: 'menu-surface-semantics',
124
+ owner: 'the menu pattern (queued) and DropdownMenu.test.tsx',
125
+ reason:
126
+ 'Item roles, arrow navigation inside the open menu, typeahead, Escape, selection, and focus return on close are the menu surface contract. This binding proves only how the trigger exposes itself and where focus lands when it opens the menu.',
127
+ },
128
+ {
129
+ id: 'bottom-sheet-presentation',
130
+ owner: 'component:DropdownMenu AR4 and the modal-dialog pattern',
131
+ reason:
132
+ 'On a compact touch display the trigger opens a bottom sheet and says aria-haspopup="dialog"; that presentation is a dialog, not a menu button, and AR4 places it outside this pattern (MoreMenu AR1 follows DropdownMenu).',
133
+ },
134
+ {
135
+ id: 'header-footer-popover',
136
+ owner:
137
+ 'component:DropdownMenu AR4 and AR5 (the popover adopts the modal-dialog pattern)',
138
+ reason:
139
+ 'A pointer menu with a header or footer opens a popover that holds the menu beside caller regions, so its trigger says aria-haspopup="dialog" and controls the popover; AR4 places it outside this pattern and AR5 gives it the dialog pattern, so none of these menu-button states renders one.',
140
+ },
141
+ ] as const;
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ /** @vitest-environment jsdom */
3
+
4
+ /**
5
+ * @file MenuButton.a11y.test.tsx
6
+ * @input MENU_BUTTON_PATTERN bound to the anchored DropdownMenu and MoreMenu triggers
7
+ * @output jsdom-lane proof of the DOM expectations and an honest "unrun" report for the browser layers
8
+ * @position Binding test for component:DropdownMenu AR4 and component:MoreMenu AR1 (the Chromium spec covers the rest)
9
+ */
10
+
11
+ import {cleanup, render, screen} from '@testing-library/react';
12
+ import {describe, expect, it} from 'vitest';
13
+ import {
14
+ MENU_BUTTON_PATTERN,
15
+ checkAccessibilitySpec,
16
+ createJsdomHarness,
17
+ expectAccessibilitySpec,
18
+ summarize,
19
+ type BindingResult,
20
+ } from '@astryxdesign/a11y-spec';
21
+ import {MENU_BUTTON_KNOWN_FAILURES} from './MenuButton.a11y.known-failures';
22
+ import {renderMenuButtonState} from './MenuButton.a11y.renders';
23
+ import {
24
+ MENU_BUTTON_BINDING_STATES,
25
+ MENU_BUTTON_EXCLUSIONS,
26
+ type MenuButtonBindingState,
27
+ } from './MenuButton.a11y.states';
28
+
29
+ function trigger(state: MenuButtonBindingState): HTMLElement {
30
+ return screen.getByRole('button', {name: state.label});
31
+ }
32
+
33
+ function relatedFor(
34
+ state: MenuButtonBindingState,
35
+ ): Readonly<Record<string, Element>> {
36
+ const subject = trigger(state);
37
+ const controls = subject.getAttribute('aria-controls');
38
+ const menu = controls == null ? null : document.getElementById(controls);
39
+ if (menu == null) {
40
+ throw new Error(`menu-button state "${state.id}" has no controlled menu`);
41
+ }
42
+ return {
43
+ menu,
44
+ 'first-item': screen.getByRole('menuitem', {
45
+ name: state.firstItem,
46
+ hidden: true,
47
+ }),
48
+ 'last-item': screen.getByRole('menuitem', {
49
+ name: state.lastItem,
50
+ hidden: true,
51
+ }),
52
+ };
53
+ }
54
+
55
+ async function checkState(
56
+ state: MenuButtonBindingState,
57
+ ): Promise<BindingResult> {
58
+ return checkAccessibilitySpec({
59
+ spec: MENU_BUTTON_PATTERN,
60
+ binding: 'DropdownMenu.trigger',
61
+ state: state.id,
62
+ facts: state.facts,
63
+ knownFailures: MENU_BUTTON_KNOWN_FAILURES,
64
+ mount: async () => {
65
+ render(renderMenuButtonState(state));
66
+ return createJsdomHarness({
67
+ subject: trigger(state),
68
+ related: relatedFor(state),
69
+ });
70
+ },
71
+ unmount: cleanup,
72
+ });
73
+ }
74
+
75
+ describe('DropdownMenu and MoreMenu triggers — menu-button pattern, jsdom lane', () => {
76
+ it.each(
77
+ MENU_BUTTON_BINDING_STATES.map(
78
+ state => [state.id, state.summary, state] as const,
79
+ ),
80
+ )('%s — %s', async (_id, _summary, state) => {
81
+ await expectAccessibilitySpec({
82
+ spec: MENU_BUTTON_PATTERN,
83
+ binding: 'DropdownMenu.trigger',
84
+ state: state.id,
85
+ facts: state.facts,
86
+ knownFailures: MENU_BUTTON_KNOWN_FAILURES,
87
+ render: () => {
88
+ render(renderMenuButtonState(state));
89
+ },
90
+ subject: () => trigger(state),
91
+ related: () => relatedFor(state),
92
+ cleanup,
93
+ });
94
+ });
95
+
96
+ it('runs the DOM layer and reports the browser-owned outcomes as unrun', async () => {
97
+ const results: BindingResult[] = [];
98
+ for (const state of MENU_BUTTON_BINDING_STATES) {
99
+ results.push(await checkState(state));
100
+ }
101
+ const report = summarize(MENU_BUTTON_PATTERN, results);
102
+ expect(report.counts.pass).toBeGreaterThan(0);
103
+ expect(report.unrunLayers).toEqual(['accessibility-tree', 'real-browser']);
104
+ expect(report.counts.unexpectedPass).toBe(0);
105
+ });
106
+ });
107
+
108
+ describe('menu-button binding inventory', () => {
109
+ it('gives every representative state a distinct checked-in story', () => {
110
+ const stories = MENU_BUTTON_BINDING_STATES.map(state => state.storyId);
111
+ expect(new Set(stories).size).toBe(stories.length);
112
+ });
113
+
114
+ it('keeps menu-surface, bottom-sheet and header/footer ownership explicit', () => {
115
+ expect(MENU_BUTTON_EXCLUSIONS.map(row => row.id)).toEqual([
116
+ 'menu-surface-semantics',
117
+ 'bottom-sheet-presentation',
118
+ 'header-footer-popover',
119
+ ]);
120
+ for (const exclusion of MENU_BUTTON_EXCLUSIONS) {
121
+ expect(exclusion.owner.length, exclusion.id).toBeGreaterThan(20);
122
+ expect(exclusion.reason.length, exclusion.id).toBeGreaterThan(80);
123
+ }
124
+ });
125
+
126
+ it('renders exactly one trigger and its menu relations for every state', () => {
127
+ for (const state of MENU_BUTTON_BINDING_STATES) {
128
+ render(renderMenuButtonState(state));
129
+ expect(trigger(state), state.id).toBeTruthy();
130
+ expect(Object.keys(relatedFor(state)), state.id).toEqual([
131
+ 'menu',
132
+ 'first-item',
133
+ 'last-item',
134
+ ]);
135
+ cleanup();
136
+ }
137
+ });
138
+ });