@astryxdesign/core 0.6.2 → 0.6.3-canary.01fa439

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 (384) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +3 -1
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +7 -5
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +56 -19
  8. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposer.js +10 -5
  10. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  11. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  12. package/dist/Chat/ChatComposerDrawer.js +48 -23
  13. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  14. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  15. package/dist/Chat/ChatComposerInput.js +63 -16
  16. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  17. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  18. package/dist/Chat/ChatDictationButton.js +6 -4
  19. package/dist/Chat/ChatSendButton.js +1 -1
  20. package/dist/Chat/index.d.ts +1 -1
  21. package/dist/Chat/index.d.ts.map +1 -1
  22. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  23. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  24. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  25. package/dist/Collapsible/Collapsible.js +6 -1
  26. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  28. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  29. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  30. package/dist/DateInput/DateInput.d.ts.map +1 -1
  31. package/dist/DateInput/DateInput.js +4 -4
  32. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  33. package/dist/DateInput/NativeDateField.js +16 -16
  34. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  35. package/dist/DateInput/TouchDateField.js +2 -2
  36. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  37. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  38. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  39. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  40. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  41. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  42. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  43. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  44. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  45. package/dist/Dialog/DialogHeader.d.ts +8 -1
  46. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  47. package/dist/Dialog/DialogHeader.js +10 -1
  48. package/dist/Field/InputClearButton.d.ts +4 -2
  49. package/dist/Field/InputClearButton.d.ts.map +1 -1
  50. package/dist/Field/InputClearButton.js +4 -2
  51. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  52. package/dist/Field/PanelSearchInput.js +1 -1
  53. package/dist/FileInput/FileInput.d.ts.map +1 -1
  54. package/dist/FileInput/FileInput.js +7 -7
  55. package/dist/Kbd/Kbd.d.ts +5 -3
  56. package/dist/Kbd/Kbd.d.ts.map +1 -1
  57. package/dist/Kbd/Kbd.js +36 -42
  58. package/dist/Link/Link.d.ts.map +1 -1
  59. package/dist/Link/Link.js +6 -2
  60. package/dist/Markdown/Markdown.d.ts +8 -2
  61. package/dist/Markdown/Markdown.d.ts.map +1 -1
  62. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  63. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  64. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  65. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  66. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  67. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  68. package/dist/Markdown/Markdown.js +262 -109
  69. package/dist/Markdown/ast.d.ts +162 -0
  70. package/dist/Markdown/ast.d.ts.map +1 -0
  71. package/dist/Markdown/ast.js +74 -0
  72. package/dist/Markdown/index.d.ts +5 -3
  73. package/dist/Markdown/index.d.ts.map +1 -1
  74. package/dist/Markdown/index.js +3 -2
  75. package/dist/Markdown/parser/index.d.ts +10 -0
  76. package/dist/Markdown/parser/index.d.ts.map +1 -0
  77. package/dist/Markdown/parser/index.js +10 -0
  78. package/dist/Markdown/parser.d.ts +126 -58
  79. package/dist/Markdown/parser.d.ts.map +1 -1
  80. package/dist/Markdown/parser.js +716 -167
  81. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  82. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  83. package/dist/Markdown/plugins/frontmatter.js +132 -0
  84. package/dist/Markdown/plugins/index.d.ts +16 -0
  85. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  86. package/dist/Markdown/plugins/index.js +13 -0
  87. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  88. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  89. package/dist/Markdown/plugins/protocol.js +957 -0
  90. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  91. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  92. package/dist/Markdown/plugins/semanticFence.js +213 -0
  93. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  94. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  95. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  96. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  97. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  98. package/dist/Markdown/plugins/textTransform.js +374 -0
  99. package/dist/Markdown/remark.d.ts +196 -0
  100. package/dist/Markdown/remark.d.ts.map +1 -0
  101. package/dist/Markdown/remark.js +1062 -0
  102. package/dist/Markdown/url.d.ts +4 -0
  103. package/dist/Markdown/url.d.ts.map +1 -0
  104. package/dist/Markdown/url.js +25 -0
  105. package/dist/Markdown/utils.d.ts +4 -2
  106. package/dist/Markdown/utils.d.ts.map +1 -1
  107. package/dist/Markdown/utils.js +2 -1
  108. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  109. package/dist/NumberInput/NumberInput.js +4 -4
  110. package/dist/Outline/index.d.ts +1 -0
  111. package/dist/Outline/index.d.ts.map +1 -1
  112. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  113. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  114. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  115. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  116. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  117. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  118. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  119. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  120. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  121. package/dist/ProgressBar/ProgressBar.js +36 -31
  122. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  123. package/dist/RadioList/RadioListItem.js +13 -1
  124. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  125. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  126. package/dist/Selector/Selector.d.ts +9 -7
  127. package/dist/Selector/Selector.d.ts.map +1 -1
  128. package/dist/Selector/Selector.js +7 -5
  129. package/dist/SideNav/SideNav.d.ts +2 -1
  130. package/dist/SideNav/SideNav.d.ts.map +1 -1
  131. package/dist/SideNav/SideNav.js +9 -3
  132. package/dist/Slider/Slider.d.ts.map +1 -1
  133. package/dist/Slider/Slider.js +28 -12
  134. package/dist/Spinner/Spinner.d.ts.map +1 -1
  135. package/dist/Spinner/Spinner.js +23 -7
  136. package/dist/Switch/Switch.d.ts.map +1 -1
  137. package/dist/Switch/Switch.js +11 -0
  138. package/dist/TabList/Tab.d.ts +1 -1
  139. package/dist/TabList/Tab.d.ts.map +1 -1
  140. package/dist/TabList/Tab.js +20 -7
  141. package/dist/Table/Table.d.ts +1 -0
  142. package/dist/Table/Table.d.ts.map +1 -1
  143. package/dist/Table/Table.js +29 -13
  144. package/dist/TextArea/TextArea.d.ts.map +1 -1
  145. package/dist/TextArea/TextArea.js +1 -1
  146. package/dist/TextInput/TextInput.d.ts.map +1 -1
  147. package/dist/TextInput/TextInput.js +2 -2
  148. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  149. package/dist/TimeInput/TimeInput.js +4 -4
  150. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  151. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  152. package/dist/ToggleButton/ToggleButton.js +15 -27
  153. package/dist/TreeList/TreeList.d.ts.map +1 -1
  154. package/dist/TreeList/TreeList.js +3 -0
  155. package/dist/TreeList/TreeListItem.d.ts +6 -1
  156. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  157. package/dist/TreeList/TreeListItem.js +12 -2
  158. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  159. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  160. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  161. package/dist/Typeahead/BaseTypeahead.js +16 -7
  162. package/dist/astryx.css +18 -7
  163. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  164. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  165. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  166. package/dist/hooks/useScrollableArea.d.ts +6 -2
  167. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  168. package/dist/hooks/useScrollableArea.js +17 -5
  169. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  170. package/dist/theme/generateThemeRules.js +10 -0
  171. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  172. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  173. package/dist/utils/interactionOverlay.stylex.js +9 -0
  174. package/locales/af-ZA.json +530 -1
  175. package/locales/ar-SA.json +490 -1
  176. package/locales/ca-ES.json +514 -1
  177. package/locales/cs-CZ.json +498 -1
  178. package/locales/da-DK.json +522 -1
  179. package/locales/de-DE.json +522 -1
  180. package/locales/el-GR.json +498 -1
  181. package/locales/es-ES.json +510 -1
  182. package/locales/fi-FI.json +498 -1
  183. package/locales/fr-FR.json +534 -1
  184. package/locales/he-IL.json +498 -1
  185. package/locales/hu-HU.json +498 -1
  186. package/locales/it-IT.json +510 -1
  187. package/locales/ja-JP.json +490 -1
  188. package/locales/ko-KR.json +498 -1
  189. package/locales/nl-NL.json +534 -1
  190. package/locales/no-NO.json +510 -1
  191. package/locales/pl-PL.json +506 -1
  192. package/locales/pt-BR.json +506 -1
  193. package/locales/pt-PT.json +506 -1
  194. package/locales/ro-RO.json +506 -1
  195. package/locales/ru-RU.json +498 -1
  196. package/locales/sr-SP.json +498 -1
  197. package/locales/sv-SE.json +502 -1
  198. package/locales/tr-TR.json +498 -1
  199. package/locales/uk-UA.json +498 -1
  200. package/locales/vi-VN.json +502 -1
  201. package/locales/zh-CN.json +490 -1
  202. package/locales/zh-TW.json +490 -1
  203. package/package.json +19 -4
  204. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  205. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  206. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  207. package/src/BottomSheet/BottomSheet.tsx +3 -1
  208. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  209. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  210. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  211. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  212. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  213. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  214. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  215. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  216. package/src/Chat/ChatComposer.doc.mjs +14 -0
  217. package/src/Chat/ChatComposer.spec.md +230 -0
  218. package/src/Chat/ChatComposer.test.tsx +57 -0
  219. package/src/Chat/ChatComposer.tsx +21 -9
  220. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  221. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  222. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  223. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  224. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  225. package/src/Chat/ChatComposerInput.spec.md +185 -0
  226. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  227. package/src/Chat/ChatComposerInput.tsx +78 -13
  228. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  229. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  230. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  231. package/src/Chat/ChatDictationButton.spec.md +160 -0
  232. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  233. package/src/Chat/ChatDictationButton.tsx +8 -6
  234. package/src/Chat/ChatSendButton.test.tsx +1 -4
  235. package/src/Chat/ChatSendButton.tsx +1 -1
  236. package/src/Chat/index.ts +1 -0
  237. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  238. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  239. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  240. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  241. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  242. package/src/Collapsible/Collapsible.test.tsx +21 -0
  243. package/src/Collapsible/Collapsible.tsx +5 -0
  244. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  245. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  246. package/src/DateInput/DateInput.tsx +6 -1
  247. package/src/DateInput/NativeDateField.tsx +24 -8
  248. package/src/DateInput/TouchDateField.tsx +6 -2
  249. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  250. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  251. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  252. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  253. package/src/Dialog/DialogHeader.doc.mjs +12 -0
  254. package/src/Dialog/DialogHeader.test.tsx +97 -0
  255. package/src/Dialog/DialogHeader.tsx +25 -3
  256. package/src/Dialog/modules/DialogHeader.spec.md +75 -38
  257. package/src/Field/InputClearButton.test.tsx +17 -3
  258. package/src/Field/InputClearButton.tsx +5 -2
  259. package/src/Field/PanelSearchInput.tsx +5 -2
  260. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  261. package/src/FileInput/FileInput.tsx +11 -2
  262. package/src/Kbd/Kbd.doc.mjs +3 -3
  263. package/src/Kbd/Kbd.test.tsx +57 -1
  264. package/src/Kbd/Kbd.tsx +57 -37
  265. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  266. package/src/Link/Link.doc.mjs +11 -0
  267. package/src/Link/Link.test.tsx +24 -0
  268. package/src/Link/Link.tsx +5 -0
  269. package/src/List/List.spec.md +200 -0
  270. package/src/Markdown/Markdown.doc.mjs +272 -0
  271. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  272. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  273. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  274. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  275. package/src/Markdown/Markdown.public.test.ts +126 -3
  276. package/src/Markdown/Markdown.spec.md +197 -44
  277. package/src/Markdown/Markdown.test.tsx +11 -0
  278. package/src/Markdown/Markdown.tsx +414 -135
  279. package/src/Markdown/ast.test.ts +307 -0
  280. package/src/Markdown/ast.ts +337 -0
  281. package/src/Markdown/incremental.test.ts +12 -0
  282. package/src/Markdown/index.ts +37 -1
  283. package/src/Markdown/modules/remark.spec.md +269 -0
  284. package/src/Markdown/parser/index.ts +64 -0
  285. package/src/Markdown/parser.public.test.ts +103 -0
  286. package/src/Markdown/parser.ts +1180 -235
  287. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  288. package/src/Markdown/plugins/frontmatter.ts +198 -0
  289. package/src/Markdown/plugins/index.ts +53 -0
  290. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  291. package/src/Markdown/plugins/protocol.ts +1618 -0
  292. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  293. package/src/Markdown/plugins/semanticFence.ts +424 -0
  294. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  295. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  296. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  297. package/src/Markdown/plugins/textTransform.ts +554 -0
  298. package/src/Markdown/remark.test.tsx +1877 -0
  299. package/src/Markdown/remark.ts +1574 -0
  300. package/src/Markdown/url.ts +30 -0
  301. package/src/Markdown/utils.ts +13 -1
  302. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  303. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  304. package/src/NumberInput/NumberInput.tsx +6 -1
  305. package/src/Outline/Outline.doc.mjs +7 -5
  306. package/src/Outline/Outline.spec.md +1 -1
  307. package/src/Outline/index.ts +1 -0
  308. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  309. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  310. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  311. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  312. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  313. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  314. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  315. package/src/ProgressBar/ProgressBar.tsx +59 -72
  316. package/src/RadioList/RadioList.doc.mjs +11 -0
  317. package/src/RadioList/RadioList.test.tsx +32 -0
  318. package/src/RadioList/RadioListItem.tsx +25 -1
  319. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  320. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  321. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  322. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  323. package/src/Selector/Selector.doc.mjs +2 -2
  324. package/src/Selector/Selector.spec.md +51 -49
  325. package/src/Selector/Selector.test.tsx +61 -55
  326. package/src/Selector/Selector.tsx +23 -14
  327. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  328. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  329. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  330. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  331. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  332. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  333. package/src/SideNav/SideNav.doc.mjs +1 -1
  334. package/src/SideNav/SideNav.test.tsx +10 -0
  335. package/src/SideNav/SideNav.tsx +14 -2
  336. package/src/Slider/Slider.doc.mjs +17 -3
  337. package/src/Slider/Slider.test.tsx +221 -0
  338. package/src/Slider/Slider.tsx +40 -9
  339. package/src/Spinner/Spinner.test.tsx +4 -0
  340. package/src/Spinner/Spinner.tsx +31 -18
  341. package/src/Switch/Switch.doc.mjs +11 -0
  342. package/src/Switch/Switch.test.tsx +16 -0
  343. package/src/Switch/Switch.tsx +28 -0
  344. package/src/TabList/Tab.tsx +35 -7
  345. package/src/TabList/TabList.doc.mjs +11 -0
  346. package/src/TabList/TabList.spec.md +204 -0
  347. package/src/TabList/TabList.test.tsx +66 -0
  348. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  349. package/src/Table/Table.doc.mjs +1 -1
  350. package/src/Table/Table.spec.md +54 -40
  351. package/src/Table/Table.test.tsx +83 -10
  352. package/src/Table/Table.tsx +30 -22
  353. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  354. package/src/TextArea/TextArea.tsx +6 -1
  355. package/src/TextInput/TextInput.test.tsx +20 -1
  356. package/src/TextInput/TextInput.tsx +6 -1
  357. package/src/TimeInput/TimeInput.tsx +6 -1
  358. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  359. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  360. package/src/ToggleButton/ToggleButton.tsx +26 -33
  361. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  362. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  363. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  364. package/src/TreeList/TreeList.doc.mjs +3 -3
  365. package/src/TreeList/TreeList.spec.md +123 -75
  366. package/src/TreeList/TreeList.test.tsx +29 -0
  367. package/src/TreeList/TreeList.tsx +3 -0
  368. package/src/TreeList/TreeListItem.tsx +15 -1
  369. package/src/TreeList/TreeListTypes.ts +11 -1
  370. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  371. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  372. package/src/Typeahead/Typeahead.test.tsx +53 -0
  373. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  374. package/src/__tests__/pressState.ts +93 -0
  375. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  376. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  377. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  378. package/src/hooks/useScrollableArea.test.tsx +59 -1
  379. package/src/hooks/useScrollableArea.ts +34 -10
  380. package/src/inputFontFloor.test.ts +106 -0
  381. package/src/tailwind-theme.css +0 -6
  382. package/src/theme/generateThemeRules.test.ts +30 -0
  383. package/src/theme/generateThemeRules.ts +10 -0
  384. package/src/utils/interactionOverlay.stylex.ts +20 -0
@@ -0,0 +1,813 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file PressedState.a11y.chromium.spec.ts
5
+ * @input Uses the eight public Pressed state stories and a built Storybook
6
+ * @output Pixel proof for enabled press feedback, release recovery, disabled
7
+ * non-response, and the selected SegmentedControl exception
8
+ * @position Real-Chromium evidence for design:user-states; DOM emulation and
9
+ * generated CSS inspection cannot prove which pixels a held pointer paints.
10
+ */
11
+
12
+ import {createHash} from 'node:crypto';
13
+ import * as fs from 'node:fs';
14
+ import * as path from 'node:path';
15
+ import {expect, test, type Locator, type Page} from '@playwright/test';
16
+ import pixelmatch from 'pixelmatch';
17
+ // @ts-expect-error -- pngjs ships no declarations; runtime support is pinned.
18
+ import {PNG} from 'pngjs';
19
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
20
+ import {
21
+ DEFAULT_STORYBOOK_DIR,
22
+ serveStorybook,
23
+ type StaticServer,
24
+ } from '@astryxdesign/a11y-spec/storybook';
25
+
26
+ const OUTPUT = path.resolve('test-results/pressed-state-evidence');
27
+
28
+ type MountedCase = {
29
+ target: Locator;
30
+ surfaces: Record<string, Locator>;
31
+ state: () => Promise<string>;
32
+ disabledTarget: Locator;
33
+ disabledSurfaces: Record<string, Locator>;
34
+ disabledState: () => Promise<string>;
35
+ independentTarget?: Locator;
36
+ independentSurfaces?: Record<string, Locator>;
37
+ independentState?: () => Promise<string>;
38
+ independentActivated?: () => Promise<boolean>;
39
+ selectedTarget?: Locator;
40
+ selectedSurfaces?: Record<string, Locator>;
41
+ selectedState?: () => Promise<string>;
42
+ };
43
+
44
+ type MatrixCase = {
45
+ component: string;
46
+ storyId: string;
47
+ releaseInPlace?: boolean;
48
+ mount: (root: Locator) => MountedCase;
49
+ };
50
+
51
+ type Shot = {
52
+ file: string;
53
+ sha256: string;
54
+ width: number;
55
+ height: number;
56
+ };
57
+
58
+ type Result = {
59
+ component: string;
60
+ storyId: string;
61
+ shots: Record<string, Shot>;
62
+ comparisons: {
63
+ surface: string;
64
+ pressedDiffPixels: number;
65
+ releasedDiffPixels: number;
66
+ disabledDiffPixels: number;
67
+ independentDiffPixels?: number;
68
+ selectedDiffPixels?: number;
69
+ }[];
70
+ };
71
+
72
+ const results: Result[] = [];
73
+ let storybook: StaticServer;
74
+ let browserVersion = 'unknown';
75
+ let viewport: {width: number; height: number} | null = null;
76
+
77
+ test.beforeAll(async () => {
78
+ fs.rmSync(OUTPUT, {recursive: true, force: true});
79
+ fs.mkdirSync(OUTPUT, {recursive: true});
80
+ storybook = await serveStorybook(
81
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
82
+ );
83
+ });
84
+
85
+ test.afterAll(async () => {
86
+ fs.writeFileSync(
87
+ path.join(OUTPUT, 'manifest.json'),
88
+ `${JSON.stringify(
89
+ {
90
+ version: 1,
91
+ headSha:
92
+ process.env.ASTRYX_HEAD_SHA ??
93
+ process.env.GITHUB_SHA ??
94
+ 'local-working-copy',
95
+ checkoutSha: process.env.GITHUB_SHA ?? 'local-working-copy',
96
+ browser: browserVersion,
97
+ viewport,
98
+ stories: results,
99
+ },
100
+ null,
101
+ 2,
102
+ )}\n`,
103
+ );
104
+ await storybook?.close();
105
+ });
106
+
107
+ const checkedState = (locator: Locator) => async () =>
108
+ JSON.stringify({
109
+ ariaChecked: await locator.getAttribute('aria-checked'),
110
+ nativeChecked: await locator.evaluate(element =>
111
+ element instanceof HTMLInputElement ? element.checked : null,
112
+ ),
113
+ });
114
+
115
+ const attributeState = (locator: Locator, name: string) => async () =>
116
+ JSON.stringify({value: await locator.getAttribute(name)});
117
+
118
+ const MATRIX: MatrixCase[] = [
119
+ {
120
+ component: 'checkbox-input',
121
+ storyId: 'core-checkboxinput--pressed-state',
122
+ mount: root => {
123
+ const target = root.getByRole('checkbox', {
124
+ name: 'Unchecked — press and hold',
125
+ });
126
+ const disabledTarget = root.getByRole('checkbox', {
127
+ name: 'Disabled — no pressed state',
128
+ });
129
+ const surfaces = root.locator('.astryx-checkbox-indicator');
130
+ return {
131
+ target,
132
+ surfaces: {indicator: surfaces.nth(0)},
133
+ state: checkedState(target),
134
+ disabledTarget,
135
+ disabledSurfaces: {indicator: surfaces.nth(2)},
136
+ disabledState: checkedState(disabledTarget),
137
+ };
138
+ },
139
+ },
140
+ {
141
+ component: 'collapsible',
142
+ storyId: 'core-collapsible--pressed-state',
143
+ mount: root => {
144
+ const target = root.getByRole('button', {
145
+ name: 'Details — press and hold',
146
+ });
147
+ const disabledTarget = root.getByRole('button', {
148
+ name: 'Unavailable — no pressed state',
149
+ });
150
+ return {
151
+ target,
152
+ surfaces: {trigger: target},
153
+ state: attributeState(target, 'aria-expanded'),
154
+ disabledTarget,
155
+ disabledSurfaces: {trigger: disabledTarget},
156
+ disabledState: attributeState(disabledTarget, 'aria-expanded'),
157
+ };
158
+ },
159
+ },
160
+ {
161
+ component: 'link',
162
+ storyId: 'core-link--pressed-state',
163
+ mount: root => {
164
+ const target = root.getByRole('link', {
165
+ name: 'documentation — press and hold',
166
+ });
167
+ const disabledTarget = root.locator('a[aria-disabled="true"]', {
168
+ hasText: 'Unavailable — no pressed state',
169
+ });
170
+ return {
171
+ target,
172
+ surfaces: {text: target},
173
+ state: attributeState(target, 'href'),
174
+ disabledTarget,
175
+ disabledSurfaces: {text: disabledTarget},
176
+ disabledState: attributeState(disabledTarget, 'href'),
177
+ };
178
+ },
179
+ },
180
+ {
181
+ component: 'slider',
182
+ storyId: 'core-slider--pressed-state',
183
+ releaseInPlace: true,
184
+ mount: root => {
185
+ const target = root.getByRole('slider', {
186
+ name: 'Volume — press and drag',
187
+ });
188
+ const disabledTarget = root.getByRole('slider', {
189
+ name: 'Unavailable — no pressed state',
190
+ });
191
+ return {
192
+ target,
193
+ surfaces: {thumb: target},
194
+ state: attributeState(target, 'aria-valuenow'),
195
+ disabledTarget,
196
+ disabledSurfaces: {thumb: disabledTarget},
197
+ disabledState: attributeState(disabledTarget, 'aria-valuenow'),
198
+ };
199
+ },
200
+ },
201
+ {
202
+ component: 'switch',
203
+ storyId: 'core-switch--pressed-state',
204
+ mount: root => {
205
+ const target = root.getByRole('switch', {name: 'Off — press and hold'});
206
+ const disabledTarget = root.getByRole('switch', {
207
+ name: 'Disabled — no pressed state',
208
+ });
209
+ const tracks = root.locator('.astryx-switch');
210
+ const thumbs = root.locator('.astryx-switch-thumb');
211
+ return {
212
+ target,
213
+ surfaces: {track: tracks.nth(0), thumb: thumbs.nth(0)},
214
+ state: checkedState(target),
215
+ disabledTarget,
216
+ disabledSurfaces: {track: tracks.nth(2), thumb: thumbs.nth(2)},
217
+ disabledState: checkedState(disabledTarget),
218
+ };
219
+ },
220
+ },
221
+ {
222
+ component: 'tab',
223
+ storyId: 'core-tablist--pressed-state',
224
+ mount: root => {
225
+ const target = root.getByRole('button', {
226
+ name: 'Projects — press and hold',
227
+ });
228
+ const disabledTarget = root.getByRole('button', {
229
+ name: 'Unavailable — no pressed state',
230
+ });
231
+ return {
232
+ target,
233
+ surfaces: {
234
+ surface: target.locator('span[aria-hidden="true"]').first(),
235
+ },
236
+ state: attributeState(target, 'aria-current'),
237
+ disabledTarget,
238
+ disabledSurfaces: {
239
+ surface: disabledTarget.locator('span[aria-hidden="true"]').first(),
240
+ },
241
+ disabledState: attributeState(disabledTarget, 'aria-current'),
242
+ };
243
+ },
244
+ },
245
+ {
246
+ component: 'radio-list',
247
+ storyId: 'core-radiolist--pressed-state',
248
+ mount: root => {
249
+ const radio = root.getByRole('radio', {name: 'SMS — press and hold'});
250
+ const target = root.getByTestId('radio-row-press-target');
251
+ const disabledTarget = root.getByRole('radio', {
252
+ name: 'Push — disabled, no pressed state',
253
+ });
254
+ const independentTarget = root.getByRole('link', {
255
+ name: 'Details — independent link',
256
+ });
257
+ const surfaces = root.locator('.astryx-radio-indicator');
258
+ return {
259
+ target,
260
+ surfaces: {indicator: surfaces.nth(1)},
261
+ state: checkedState(radio),
262
+ disabledTarget,
263
+ disabledSurfaces: {indicator: surfaces.nth(2)},
264
+ disabledState: checkedState(disabledTarget),
265
+ independentTarget,
266
+ independentSurfaces: {indicator: surfaces.nth(1)},
267
+ independentState: checkedState(radio),
268
+ independentActivated: async () =>
269
+ root.page().url().endsWith('#details'),
270
+ };
271
+ },
272
+ },
273
+ {
274
+ component: 'segmented-control',
275
+ storyId: 'core-segmentedcontrol--pressed-state',
276
+ mount: root => {
277
+ const target = root.getByRole('radio', {
278
+ name: 'List — press and hold',
279
+ });
280
+ const disabledTarget = root.getByRole('radio', {
281
+ name: 'Unavailable — no pressed state',
282
+ });
283
+ const selectedTarget = root.getByRole('radio', {
284
+ name: 'Grid — selected',
285
+ });
286
+ return {
287
+ target,
288
+ surfaces: {item: target},
289
+ state: checkedState(target),
290
+ disabledTarget,
291
+ disabledSurfaces: {item: disabledTarget},
292
+ disabledState: checkedState(disabledTarget),
293
+ selectedTarget,
294
+ selectedSurfaces: {item: selectedTarget},
295
+ selectedState: checkedState(selectedTarget),
296
+ };
297
+ },
298
+ },
299
+ ];
300
+
301
+ async function settle(page: Page): Promise<void> {
302
+ await page.evaluate(
303
+ async () =>
304
+ new Promise<void>(resolve => requestAnimationFrame(() => resolve())),
305
+ );
306
+ }
307
+
308
+ async function focusForComparison(target: Locator): Promise<void> {
309
+ await target.evaluate(element => {
310
+ if (element instanceof HTMLElement) {
311
+ element.focus({preventScroll: true});
312
+ }
313
+ });
314
+ }
315
+
316
+ async function pointerCenter(
317
+ locator: Locator,
318
+ ): Promise<{x: number; y: number}> {
319
+ await locator.scrollIntoViewIfNeeded();
320
+ const box = await locator.boundingBox();
321
+ if (box == null) {
322
+ throw new Error('pointer target has no rendered bounds');
323
+ }
324
+ return {x: box.x + box.width / 2, y: box.y + box.height / 2};
325
+ }
326
+
327
+ async function capture(
328
+ locator: Locator,
329
+ component: string,
330
+ surface: string,
331
+ state: string,
332
+ ): Promise<{bytes: Buffer; shot: Shot}> {
333
+ await locator.waitFor({state: 'visible'});
334
+ const bytes = await locator.screenshot({animations: 'disabled'});
335
+ const png = PNG.sync.read(bytes);
336
+ const file = `${component}-${surface}-${state}.png`;
337
+ fs.writeFileSync(path.join(OUTPUT, file), bytes);
338
+ return {
339
+ bytes,
340
+ shot: {
341
+ file,
342
+ sha256: createHash('sha256').update(bytes).digest('hex'),
343
+ width: png.width,
344
+ height: png.height,
345
+ },
346
+ };
347
+ }
348
+
349
+ async function captureSurfaces(
350
+ locators: Record<string, Locator>,
351
+ component: string,
352
+ state: string,
353
+ shots: Record<string, Shot>,
354
+ ): Promise<Record<string, Buffer>> {
355
+ const buffers: Record<string, Buffer> = {};
356
+ for (const [surface, locator] of Object.entries(locators)) {
357
+ const captured = await capture(locator, component, surface, state);
358
+ buffers[surface] = captured.bytes;
359
+ shots[`${surface}-${state}`] = captured.shot;
360
+ }
361
+ return buffers;
362
+ }
363
+
364
+ function diffPixels(before: Buffer, after: Buffer, file: string): number {
365
+ const beforePng = PNG.sync.read(before);
366
+ const afterPng = PNG.sync.read(after);
367
+ if (
368
+ beforePng.width !== afterPng.width ||
369
+ beforePng.height !== afterPng.height
370
+ ) {
371
+ throw new Error(`${file}: surface dimensions changed`);
372
+ }
373
+ const diff = new PNG({width: beforePng.width, height: beforePng.height});
374
+ const count = pixelmatch(
375
+ beforePng.data,
376
+ afterPng.data,
377
+ diff.data,
378
+ beforePng.width,
379
+ beforePng.height,
380
+ {threshold: 0.01, includeAA: true, alpha: 0.2},
381
+ );
382
+ fs.writeFileSync(path.join(OUTPUT, file), PNG.sync.write(diff));
383
+ return count;
384
+ }
385
+
386
+ async function press(page: Page, target: Locator): Promise<void> {
387
+ const point = await pointerCenter(target);
388
+ await page.mouse.move(point.x, point.y);
389
+ await settle(page);
390
+ await page.mouse.down();
391
+ await settle(page);
392
+ }
393
+
394
+ async function releaseAway(page: Page): Promise<void> {
395
+ await page.mouse.move(1, 1);
396
+ await page.mouse.up();
397
+ await settle(page);
398
+ }
399
+
400
+ test('a range Slider keeps the pressed overlay on only the track-selected thumb throughout a drag', async ({
401
+ page,
402
+ }) => {
403
+ test.setTimeout(2 * 60 * 1000);
404
+ await page.goto(
405
+ `${storybook.origin}/iframe.html?id=core-slider--pressed-state&viewMode=story`,
406
+ {waitUntil: 'load'},
407
+ );
408
+ await holdMotionStill(page);
409
+
410
+ const root = page.locator('#storybook-root');
411
+ const range = root.getByRole('group', {
412
+ name: 'Price range — only the dragged thumb presses',
413
+ });
414
+ const thumbs = range.getByRole('slider');
415
+ const start = thumbs.nth(0);
416
+ const end = thumbs.nth(1);
417
+ await end.waitFor({state: 'visible'});
418
+
419
+ const result: Result = {
420
+ component: 'slider-range',
421
+ storyId: 'core-slider--pressed-state',
422
+ shots: {},
423
+ comparisons: [],
424
+ };
425
+ results.push(result);
426
+
427
+ await page.mouse.move(1, 1);
428
+ await settle(page);
429
+ const rest = await captureSurfaces(
430
+ {start, end},
431
+ 'slider-range',
432
+ 'rest',
433
+ result.shots,
434
+ );
435
+ const startValue = await start.getAttribute('aria-valuenow');
436
+ const endValue = await end.getAttribute('aria-valuenow');
437
+
438
+ const box = await range.boundingBox();
439
+ const startBox = await start.boundingBox();
440
+ const endBox = await end.boundingBox();
441
+ if (box == null || startBox == null || endBox == null) {
442
+ throw new Error('range Slider has no rendered drag geometry');
443
+ }
444
+ const dragStart = {
445
+ x: box.x + box.width * 0.65,
446
+ y: box.y + box.height / 2,
447
+ };
448
+ const isInside = (
449
+ point: {x: number; y: number},
450
+ rect: {x: number; y: number; width: number; height: number},
451
+ ) =>
452
+ point.x >= rect.x &&
453
+ point.x <= rect.x + rect.width &&
454
+ point.y >= rect.y &&
455
+ point.y <= rect.y + rect.height;
456
+ expect(isInside(dragStart, startBox)).toBe(false);
457
+ expect(isInside(dragStart, endBox)).toBe(false);
458
+
459
+ await page.mouse.move(dragStart.x, dragStart.y);
460
+ await page.mouse.down();
461
+ await settle(page);
462
+ await page.mouse.move(box.x + box.width * 0.6, dragStart.y);
463
+ await settle(page);
464
+ const dragged = await captureSurfaces(
465
+ {start, end},
466
+ 'slider-range',
467
+ 'dragged',
468
+ result.shots,
469
+ );
470
+
471
+ const startDiff = diffPixels(
472
+ rest.start,
473
+ dragged.start,
474
+ 'slider-range-start-stable-diff.png',
475
+ );
476
+ expect(startDiff).toBe(0);
477
+ expect(await start.getAttribute('aria-valuenow')).toBe(startValue);
478
+ expect(await end.getAttribute('aria-valuenow')).not.toBe(endValue);
479
+ const draggedEndValue = await end.getAttribute('aria-valuenow');
480
+
481
+ // Release without moving the pointer so the comparison keeps the same thumb
482
+ // value, geometry, hover, and focus. The remaining changed pixels are the
483
+ // pressed overlay that persisted through the drag.
484
+ await page.mouse.up();
485
+ await settle(page);
486
+ const hoverAfterDrag = await captureSurfaces(
487
+ {end},
488
+ 'slider-range',
489
+ 'hover-after-drag',
490
+ result.shots,
491
+ );
492
+ const endDiff = diffPixels(
493
+ hoverAfterDrag.end,
494
+ dragged.end,
495
+ 'slider-range-end-pressed-diff.png',
496
+ );
497
+ expect(endDiff).toBeGreaterThan(0);
498
+ expect(await end.getAttribute('aria-valuenow')).toBe(draggedEndValue);
499
+
500
+ await page.mouse.move(1, 1);
501
+ await settle(page);
502
+ const released = await captureSurfaces(
503
+ {end},
504
+ 'slider-range',
505
+ 'released',
506
+ result.shots,
507
+ );
508
+ await page.mouse.move(box.x + box.width * 0.6, dragStart.y);
509
+ await settle(page);
510
+ await page.mouse.move(1, 1);
511
+ await settle(page);
512
+ const settled = await captureSurfaces(
513
+ {end},
514
+ 'slider-range',
515
+ 'rest-after-release',
516
+ result.shots,
517
+ );
518
+ const releasedDiff = diffPixels(
519
+ settled.end,
520
+ released.end,
521
+ 'slider-range-end-released-diff.png',
522
+ );
523
+ expect(releasedDiff).toBe(0);
524
+
525
+ result.comparisons.push(
526
+ {
527
+ surface: 'dragged-end-thumb',
528
+ pressedDiffPixels: endDiff,
529
+ releasedDiffPixels: releasedDiff,
530
+ disabledDiffPixels: 0,
531
+ },
532
+ {
533
+ surface: 'stationary-start-thumb',
534
+ pressedDiffPixels: startDiff,
535
+ releasedDiffPixels: 0,
536
+ disabledDiffPixels: 0,
537
+ },
538
+ );
539
+ });
540
+
541
+ test('the bounded pressed matrix paints only enabled transient surfaces', async ({
542
+ page,
543
+ }) => {
544
+ test.setTimeout(5 * 60 * 1000);
545
+ browserVersion = page.context().browser()?.version() ?? 'unknown';
546
+ viewport = page.viewportSize();
547
+ const failures: string[] = [];
548
+
549
+ for (const entry of MATRIX) {
550
+ await page.goto(
551
+ `${storybook.origin}/iframe.html?id=${entry.storyId}&viewMode=story`,
552
+ {waitUntil: 'load'},
553
+ );
554
+ await holdMotionStill(page);
555
+ const mounted = entry.mount(page.locator('#storybook-root'));
556
+ await mounted.target.waitFor({state: 'visible'});
557
+
558
+ const result: Result = {
559
+ component: entry.component,
560
+ storyId: entry.storyId,
561
+ shots: {},
562
+ comparisons: [],
563
+ };
564
+ results.push(result);
565
+
566
+ const initialState = await mounted.state();
567
+
568
+ // Prime each target with a canceled pointer cycle before collecting the
569
+ // baseline. That leaves focus and input modality in the same pointer-owned
570
+ // state used by the measured press, so focus pixels cannot masquerade as
571
+ // pressed feedback.
572
+ await press(page, mounted.target);
573
+ if (entry.releaseInPlace) {
574
+ await page.mouse.up();
575
+ await page.mouse.move(1, 1);
576
+ await settle(page);
577
+ } else {
578
+ await releaseAway(page);
579
+ }
580
+ if ((await mounted.state()) !== initialState) {
581
+ failures.push(`${entry.component}: priming press activated the control`);
582
+ }
583
+
584
+ await captureSurfaces(
585
+ mounted.surfaces,
586
+ entry.component,
587
+ 'rest',
588
+ result.shots,
589
+ );
590
+
591
+ const point = await pointerCenter(mounted.target);
592
+ await page.mouse.move(point.x, point.y);
593
+ await settle(page);
594
+ const hover = await captureSurfaces(
595
+ mounted.surfaces,
596
+ entry.component,
597
+ 'hover',
598
+ result.shots,
599
+ );
600
+ await page.mouse.down();
601
+ await settle(page);
602
+ const pressed = await captureSurfaces(
603
+ mounted.surfaces,
604
+ entry.component,
605
+ 'pressed',
606
+ result.shots,
607
+ );
608
+ if (entry.releaseInPlace) {
609
+ await page.mouse.up();
610
+ await page.mouse.move(1, 1);
611
+ await settle(page);
612
+ } else {
613
+ await releaseAway(page);
614
+ }
615
+ const released = await captureSurfaces(
616
+ mounted.surfaces,
617
+ entry.component,
618
+ 'released',
619
+ result.shots,
620
+ );
621
+ // Revisit hover, then rest, without another press. This makes the release
622
+ // comparison share the same pointer-owned focus state as the released shot.
623
+ await page.mouse.move(point.x, point.y);
624
+ await settle(page);
625
+ await page.mouse.move(1, 1);
626
+ await settle(page);
627
+ const settledRest = await captureSurfaces(
628
+ mounted.surfaces,
629
+ entry.component,
630
+ 'rest-after-release',
631
+ result.shots,
632
+ );
633
+
634
+ for (const surface of Object.keys(mounted.surfaces)) {
635
+ const pressedCount = diffPixels(
636
+ hover[surface],
637
+ pressed[surface],
638
+ `${entry.component}-${surface}-pressed-diff.png`,
639
+ );
640
+ const releasedCount = diffPixels(
641
+ settledRest[surface],
642
+ released[surface],
643
+ `${entry.component}-${surface}-released-diff.png`,
644
+ );
645
+ if (pressedCount === 0) {
646
+ failures.push(
647
+ `${entry.component} ${surface}: held pointer changed no pixels beyond hover`,
648
+ );
649
+ }
650
+ if (releasedCount !== 0) {
651
+ failures.push(
652
+ `${entry.component} ${surface}: release left ${releasedCount} pixels changed`,
653
+ );
654
+ }
655
+ result.comparisons.push({
656
+ surface,
657
+ pressedDiffPixels: pressedCount,
658
+ releasedDiffPixels: releasedCount,
659
+ disabledDiffPixels: 0,
660
+ });
661
+ }
662
+ if ((await mounted.state()) !== initialState) {
663
+ failures.push(`${entry.component}: canceled press activated the control`);
664
+ }
665
+
666
+ await page.mouse.move(1, 1);
667
+ await focusForComparison(mounted.disabledTarget);
668
+ await settle(page);
669
+ const disabledRest = await captureSurfaces(
670
+ mounted.disabledSurfaces,
671
+ entry.component,
672
+ 'disabled-rest',
673
+ result.shots,
674
+ );
675
+ const disabledState = await mounted.disabledState();
676
+ await press(page, mounted.disabledTarget);
677
+ const disabledPressed = await captureSurfaces(
678
+ mounted.disabledSurfaces,
679
+ entry.component,
680
+ 'disabled-pressed',
681
+ result.shots,
682
+ );
683
+ await page.mouse.up();
684
+ await settle(page);
685
+ for (const surface of Object.keys(mounted.disabledSurfaces)) {
686
+ const count = diffPixels(
687
+ disabledRest[surface],
688
+ disabledPressed[surface],
689
+ `${entry.component}-${surface}-disabled-diff.png`,
690
+ );
691
+ if (count !== 0) {
692
+ failures.push(
693
+ `${entry.component} ${surface}: disabled hold changed ${count} pixels`,
694
+ );
695
+ }
696
+ const comparison = result.comparisons.find(
697
+ item => item.surface === surface,
698
+ );
699
+ if (comparison != null) {
700
+ comparison.disabledDiffPixels = count;
701
+ }
702
+ }
703
+ if ((await mounted.disabledState()) !== disabledState) {
704
+ failures.push(`${entry.component}: disabled press activated the control`);
705
+ }
706
+
707
+ if (
708
+ mounted.independentTarget != null &&
709
+ mounted.independentSurfaces != null &&
710
+ mounted.independentState != null
711
+ ) {
712
+ await page.mouse.move(1, 1);
713
+ await focusForComparison(mounted.independentTarget);
714
+ const independentPoint = await pointerCenter(mounted.independentTarget);
715
+ await page.mouse.move(independentPoint.x, independentPoint.y);
716
+ await settle(page);
717
+ const independentHover = await captureSurfaces(
718
+ mounted.independentSurfaces,
719
+ entry.component,
720
+ 'independent-hover',
721
+ result.shots,
722
+ );
723
+ const independentState = await mounted.independentState();
724
+ await page.mouse.down();
725
+ await settle(page);
726
+ const independentPressed = await captureSurfaces(
727
+ mounted.independentSurfaces,
728
+ entry.component,
729
+ 'independent-pressed',
730
+ result.shots,
731
+ );
732
+ await page.mouse.up();
733
+ await settle(page);
734
+ for (const surface of Object.keys(mounted.independentSurfaces)) {
735
+ const count = diffPixels(
736
+ independentHover[surface],
737
+ independentPressed[surface],
738
+ `${entry.component}-${surface}-independent-diff.png`,
739
+ );
740
+ if (count !== 0) {
741
+ failures.push(
742
+ `${entry.component} ${surface}: nested action hold changed ${count} pixels`,
743
+ );
744
+ }
745
+ const comparison = result.comparisons.find(
746
+ item => item.surface === surface,
747
+ );
748
+ if (comparison != null) {
749
+ comparison.independentDiffPixels = count;
750
+ }
751
+ }
752
+ if ((await mounted.independentState()) !== independentState) {
753
+ failures.push(
754
+ `${entry.component}: nested action changed control state`,
755
+ );
756
+ }
757
+ if (
758
+ mounted.independentActivated != null &&
759
+ !(await mounted.independentActivated())
760
+ ) {
761
+ failures.push(`${entry.component}: nested action did not activate`);
762
+ }
763
+ }
764
+
765
+ if (
766
+ mounted.selectedTarget != null &&
767
+ mounted.selectedSurfaces != null &&
768
+ mounted.selectedState != null
769
+ ) {
770
+ await page.mouse.move(1, 1);
771
+ await focusForComparison(mounted.selectedTarget);
772
+ await settle(page);
773
+ const selectedRest = await captureSurfaces(
774
+ mounted.selectedSurfaces,
775
+ entry.component,
776
+ 'selected-rest',
777
+ result.shots,
778
+ );
779
+ const selectedState = await mounted.selectedState();
780
+ await press(page, mounted.selectedTarget);
781
+ const selectedPressed = await captureSurfaces(
782
+ mounted.selectedSurfaces,
783
+ entry.component,
784
+ 'selected-pressed',
785
+ result.shots,
786
+ );
787
+ await releaseAway(page);
788
+ for (const surface of Object.keys(mounted.selectedSurfaces)) {
789
+ const count = diffPixels(
790
+ selectedRest[surface],
791
+ selectedPressed[surface],
792
+ `${entry.component}-${surface}-selected-diff.png`,
793
+ );
794
+ if (count !== 0) {
795
+ failures.push(
796
+ `${entry.component} ${surface}: selected hold changed ${count} pixels`,
797
+ );
798
+ }
799
+ const comparison = result.comparisons.find(
800
+ item => item.surface === surface,
801
+ );
802
+ if (comparison != null) {
803
+ comparison.selectedDiffPixels = count;
804
+ }
805
+ }
806
+ if ((await mounted.selectedState()) !== selectedState) {
807
+ failures.push(`${entry.component}: selected exception changed state`);
808
+ }
809
+ }
810
+ }
811
+
812
+ expect(failures).toEqual([]);
813
+ });