@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
@@ -4,14 +4,14 @@
4
4
 
5
5
  /**
6
6
  * @file BottomSheetPanel.tsx
7
- * @input Uses React, StyleX, theme tokens, useSheetGestures
8
- * @output Internal BottomSheetPanel surface and motion-state types
7
+ * @input Uses React, StyleX, theme tokens, sheet gestures, shared scroll behavior, and the host label
8
+ * @output Internal BottomSheetPanel with a keyboard-reachable scrolling body and motion-state types
9
9
  * @position Shared presentation layer for standalone and switcher BottomSheets
10
10
  *
11
11
  * This component owns everything intrinsic to a sheet surface: height budgets,
12
12
  * drag and snap gestures, the handle and scrolling body, motion styles, and
13
- * transition completion. It deliberately does not own a dialog, focus, inert
14
- * state, or switcher registration; those belong to the hosting controller.
13
+ * transition completion. It owns the body's keyboard access; dialog focus
14
+ * entry/return, inert state, and switcher registration belong to the host.
15
15
  *
16
16
  * SYNC: When modified, update these files to stay in sync:
17
17
  * - /packages/core/src/BottomSheet/BottomSheet.tsx
@@ -33,17 +33,20 @@ import {
33
33
  import * as stylex from '@stylexjs/stylex';
34
34
  import type {BaseProps} from '../BaseProps';
35
35
  import {useDevWarning} from '../hooks';
36
+ import {useScrollableArea} from '../hooks/useScrollableArea';
36
37
  import {
37
38
  borderVars,
38
39
  colorVars,
39
40
  durationVars,
40
41
  easeVars,
42
+ focusVars,
41
43
  radiusVars,
42
44
  shadowVars,
43
45
  sizeVars,
44
46
  spacingVars,
45
47
  } from '../theme/tokens.stylex';
46
48
  import {mergeProps, themeProps} from '../utils';
49
+ import {focusOutlineStyles} from '../utils/focusOutline.stylex';
47
50
  import {overlayPaddingReset} from '../Layout/padding.stylex';
48
51
  import {
49
52
  isValidSnapPoint,
@@ -211,9 +214,11 @@ const styles = stylex.create({
211
214
  flexGrow: 1,
212
215
  minHeight: 0,
213
216
  boxSizing: 'border-box',
214
- overflowY: 'auto',
215
- overscrollBehavior: 'none',
216
217
  touchAction: 'pan-y',
218
+ outlineOffset: {
219
+ default: 0,
220
+ ':focus-visible': `calc(-1 * ${focusVars['--focus-outline-width']})`,
221
+ },
217
222
  // The scrolling area paints the surface itself, covering the sheet's whole
218
223
  // inner box. Without it the sheet's edge is not uniform: a theme that packs
219
224
  // an inset ring into --shadow-high (the bundled themes all add one in dark
@@ -229,6 +234,15 @@ const styles = stylex.create({
229
234
  // space a content wrapper's own top padding already provides.
230
235
  paddingBlockEnd: 0,
231
236
  },
237
+ content: {
238
+ // Preserve the body's block formatting (including margin/float isolation)
239
+ // and its definite percentage-height basis. min-content lets this real
240
+ // observed box grow with async content beyond a fixed-height viewport.
241
+ display: 'flow-root',
242
+ boxSizing: 'border-box',
243
+ height: '100%',
244
+ minHeight: 'min-content',
245
+ },
232
246
  tallKeyboardBody: {
233
247
  scrollPaddingBlockEnd: MOBILE_KEYBOARD_BOTTOM_CLEARANCE,
234
248
  '::after': {
@@ -265,6 +279,8 @@ interface BottomSheetPanelProps extends BaseProps<HTMLDivElement> {
265
279
  ref?: React.Ref<HTMLDivElement>;
266
280
  state: BottomSheetPanelState;
267
281
  height: BottomSheetHeight | number | string;
282
+ /** Existing host name, reused for the keyboard scrolling group. */
283
+ label: string;
268
284
  children: ReactNode;
269
285
  snapPoints?: ReadonlyArray<BottomSheetSnapPoint>;
270
286
  isSwipeDismissAllowed?: boolean;
@@ -391,6 +407,7 @@ export function BottomSheetPanel({
391
407
  ref,
392
408
  state,
393
409
  height,
410
+ label,
394
411
  children,
395
412
  snapPoints,
396
413
  className,
@@ -490,6 +507,17 @@ export function BottomSheetPanel({
490
507
  onScrimOpacity,
491
508
  });
492
509
 
510
+ const {getViewportProps, getContentProps} = useScrollableArea({
511
+ // The previous overflow-y:auto also computed overflow-x:auto. Keep wide
512
+ // content reachable instead of clipping that existing inline scroll path.
513
+ axis: 'both',
514
+ keyboardAccess: {owner: 'contentOrViewport', label},
515
+ overscroll: 'contain',
516
+ // The sheet body deliberately remains the boundary for sheet-local sticky
517
+ // content while fitting, as it was before shared scroll adoption.
518
+ stickyContainment: 'always',
519
+ });
520
+
493
521
  const setElement = useCallback(
494
522
  (element: HTMLDivElement | null) => {
495
523
  sheetRef(element);
@@ -656,17 +684,22 @@ export function BottomSheetPanel({
656
684
  <div {...stylex.props(styles.handlePill)} />
657
685
  </div>
658
686
  <div
659
- {...mergeProps(
660
- stylex.props(
661
- styles.body,
662
- height === 'tall' && styles.tallKeyboardBody,
687
+ {...getViewportProps<HTMLDivElement>({
688
+ ...bodyProps,
689
+ ...mergeProps(
690
+ stylex.props(
691
+ focusOutlineStyles.focusVisible,
692
+ styles.body,
693
+ height === 'tall' && styles.tallKeyboardBody,
694
+ ),
695
+ scrollPreservationInset > 0
696
+ ? {style: {paddingBlockEnd: `${scrollPreservationInset}px`}}
697
+ : {},
663
698
  ),
664
- scrollPreservationInset > 0
665
- ? {style: {paddingBlockEnd: `${scrollPreservationInset}px`}}
666
- : {},
667
- )}
668
- {...bodyProps}>
669
- {children}
699
+ })}>
700
+ <div {...getContentProps<HTMLDivElement>(stylex.props(styles.content))}>
701
+ {children}
702
+ </div>
670
703
  </div>
671
704
  </div>
672
705
  );
@@ -0,0 +1,344 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file BottomSheetKeyboard.a11y.browser.spec.ts
5
+ * @input Built BottomSheet stories and real Chromium/WebKit keyboard input
6
+ * @output Browser evidence for shared focus delegation, traversal, and native scrolling
7
+ * @position Cross-browser proof for useScrollableArea's contentOrViewport policy
8
+ */
9
+
10
+ import {expect, test, type Page, type Locator} from '@playwright/test';
11
+ import {
12
+ DEFAULT_STORYBOOK_DIR,
13
+ serveStorybook,
14
+ type StaticServer,
15
+ } from '@astryxdesign/a11y-spec/storybook';
16
+
17
+ let storybook: StaticServer;
18
+ test.beforeAll(async () => {
19
+ storybook = await serveStorybook(
20
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
21
+ );
22
+ });
23
+ test.afterAll(async () => {
24
+ await storybook?.close();
25
+ });
26
+
27
+ // macOS WebKit defaults to text-field traversal. Option+Tab selects its native
28
+ // all-controls traversal, equivalent to enabling Safari's keyboard preference.
29
+ function tabKey(browserName: string, reverse = false): string {
30
+ return `${browserName === 'webkit' && process.platform === 'darwin' ? 'Alt+' : ''}${reverse ? 'Shift+' : ''}Tab`;
31
+ }
32
+
33
+ async function mount(page: Page, browserName: string, variant = 'button') {
34
+ await page.goto(
35
+ `${storybook.origin}/iframe.html?id=core-bottomsheet--keyboard-delegation&viewMode=story`,
36
+ );
37
+ const viewport = page.getByRole('group', {
38
+ name: 'Keyboard reading',
39
+ exact: true,
40
+ });
41
+ await expect(viewport).toHaveAttribute('data-scrollable-block', 'true');
42
+ await page.getByLabel('Content case').selectOption(variant);
43
+ await expect(viewport).toHaveAttribute('tabindex', '0');
44
+ // WebKit includes the non-modal dialog host in sequential navigation;
45
+ // Chromium enters its descendants directly. Start immediately before the body.
46
+ const before =
47
+ browserName === 'webkit'
48
+ ? page.getByRole('dialog', {name: 'Keyboard reading', exact: true})
49
+ : page.getByRole('button', {name: 'Toggle modal', exact: true});
50
+ await before.focus();
51
+ return {viewport, before};
52
+ }
53
+
54
+ for (const variant of ['button', 'link']) {
55
+ test(`${variant}: forward entry delegates once and reverse traversal exits`, async ({
56
+ page,
57
+ browserName,
58
+ }) => {
59
+ const {viewport, before} = await mount(page, browserName, variant);
60
+ const first = page.getByRole(variant === 'button' ? 'button' : 'link', {
61
+ name: variant === 'button' ? 'First action' : 'First link',
62
+ exact: true,
63
+ });
64
+ await page.keyboard.press(tabKey(browserName));
65
+ await expect(first).toBeFocused();
66
+ await expect(viewport).toHaveAttribute('tabindex', '0');
67
+ await expect(viewport).toHaveAccessibleName('Keyboard reading');
68
+ await expect(first).toHaveAccessibleName(
69
+ variant === 'button' ? 'First action' : 'First link',
70
+ );
71
+ await page.keyboard.press(tabKey(browserName));
72
+ await expect(page.getByRole('button', {name: 'Last action'})).toBeFocused();
73
+ await page.keyboard.press(tabKey(browserName, true));
74
+ await expect(first).toBeFocused();
75
+ await page.keyboard.press(tabKey(browserName, true));
76
+ await expect(before).toBeFocused();
77
+ await expect(viewport).toHaveAttribute('tabindex', '0');
78
+ await page.keyboard.press(tabKey(browserName));
79
+ await expect(first).toBeFocused();
80
+ });
81
+ }
82
+
83
+ for (const variant of [
84
+ 'text',
85
+ 'input',
86
+ 'disabled',
87
+ 'nested',
88
+ 'native-scroll',
89
+ 'radiogroup',
90
+ 'slider',
91
+ 'combobox',
92
+ 'listbox',
93
+ 'menu',
94
+ 'grid',
95
+ 'tree',
96
+ 'tablist',
97
+ 'toolbar',
98
+ ]) {
99
+ test(`${variant}: retains a named viewport stop`, async ({
100
+ page,
101
+ browserName,
102
+ }) => {
103
+ const {viewport, before} = await mount(page, browserName, variant);
104
+ await page.keyboard.press(tabKey(browserName));
105
+ await expect(viewport).toBeFocused();
106
+ await expect(viewport).toHaveAccessibleName('Keyboard reading');
107
+ await page.keyboard.press(tabKey(browserName, true));
108
+ await expect(before).toBeFocused();
109
+ });
110
+ }
111
+
112
+ async function scrollRange(viewport: Locator) {
113
+ return viewport.evaluate(element => ({
114
+ position: element.scrollTop,
115
+ max: element.scrollHeight - element.clientHeight,
116
+ }));
117
+ }
118
+
119
+ for (const key of ['PageDown', 'ArrowDown']) {
120
+ test(`${key}: reaches both scroll edges after delegation`, async ({
121
+ page,
122
+ browserName,
123
+ }) => {
124
+ const {viewport} = await mount(page, browserName);
125
+ await page.keyboard.press(tabKey(browserName));
126
+ const first = page.getByRole('button', {name: 'First action', exact: true});
127
+ await expect(first).toBeFocused();
128
+ for (let count = 0; count < 300; count++) {
129
+ const {position, max} = await scrollRange(viewport);
130
+ if (position >= max - 1) {
131
+ break;
132
+ }
133
+ await page.keyboard.press(key, {delay: key === 'ArrowDown' ? 50 : 25});
134
+ }
135
+ await expect
136
+ .poll(async () => {
137
+ const {position, max} = await scrollRange(viewport);
138
+ // WebKit can overshoot a native edge while showing rubber-band feedback.
139
+ return max - position;
140
+ })
141
+ .toBeLessThanOrEqual(1);
142
+ for (let count = 0; count < 300; count++) {
143
+ if ((await scrollRange(viewport)).position <= 1) {
144
+ break;
145
+ }
146
+ await page.keyboard.press(key === 'PageDown' ? 'PageUp' : 'ArrowUp', {
147
+ delay: key === 'ArrowDown' ? 50 : 25,
148
+ });
149
+ }
150
+ await expect
151
+ .poll(async () => (await scrollRange(viewport)).position)
152
+ .toBeLessThanOrEqual(1);
153
+ await expect(first).toBeFocused();
154
+ });
155
+ }
156
+
157
+ test('pointer and programmatic focus stay on the viewport, including during keydown', async ({
158
+ page,
159
+ browserName,
160
+ }) => {
161
+ const {viewport, before} = await mount(page, browserName);
162
+ await viewport.focus();
163
+ await expect(viewport).toBeFocused();
164
+ await before.focus();
165
+ await viewport.click({position: {x: 4, y: 40}});
166
+ await expect(viewport).toBeFocused();
167
+ await before.focus();
168
+ await before.evaluate(element => {
169
+ element.addEventListener('keydown', event => {
170
+ if (event instanceof KeyboardEvent && event.key === 'Tab') {
171
+ event.preventDefault();
172
+ document
173
+ .querySelector<HTMLElement>(
174
+ '[role="group"][aria-label="Keyboard reading"]',
175
+ )
176
+ ?.focus();
177
+ }
178
+ });
179
+ });
180
+ await page.keyboard.press(tabKey(browserName));
181
+ await expect(viewport).toBeFocused();
182
+ });
183
+
184
+ test('dynamic eligibility is checked at the next entry without mutation observation', async ({
185
+ page,
186
+ browserName,
187
+ }) => {
188
+ await page.addInitScript(() => {
189
+ const Original = window.MutationObserver;
190
+ const observers: MutationObserver[] = [];
191
+ window.MutationObserver = class extends Original {
192
+ constructor(callback: MutationCallback) {
193
+ super(callback);
194
+ observers.push(this);
195
+ }
196
+ };
197
+ Object.assign(window, {
198
+ stopMutationObservers: () =>
199
+ observers.forEach(observer => observer.disconnect()),
200
+ });
201
+ });
202
+ const {viewport, before} = await mount(page, browserName);
203
+ await page.evaluate(() => {
204
+ (
205
+ window as unknown as {stopMutationObservers: () => void}
206
+ ).stopMutationObservers();
207
+ });
208
+ await viewport.focus();
209
+ const first = page.getByRole('button', {name: 'First action', exact: true});
210
+ await first.evaluate(element => element.setAttribute('disabled', ''));
211
+ await expect(viewport).toBeFocused();
212
+ await before.focus();
213
+ await page.keyboard.press(tabKey(browserName));
214
+ await expect(page.getByRole('button', {name: 'Last action'})).toBeFocused();
215
+ await before.focus();
216
+ await first.evaluate(element => element.removeAttribute('disabled'));
217
+ await page.keyboard.press(tabKey(browserName));
218
+ await expect(first).toBeFocused();
219
+ await before.focus();
220
+ await viewport.locator('[data-scroll-content]').evaluate(element => {
221
+ const input = document.createElement('input');
222
+ input.setAttribute('aria-label', 'Inserted input');
223
+ element.prepend(input);
224
+ });
225
+ await page.keyboard.press(tabKey(browserName));
226
+ await expect(viewport).toBeFocused();
227
+ await page.getByLabel('Inserted input').evaluate(element => element.remove());
228
+ await expect(viewport).toBeFocused();
229
+ await before.focus();
230
+ await page.keyboard.press(tabKey(browserName));
231
+ await expect(first).toBeFocused();
232
+ await before.focus();
233
+ await first.evaluate(element => element.setAttribute('role', 'combobox'));
234
+ await page.keyboard.press(tabKey(browserName));
235
+ await expect(viewport).toBeFocused();
236
+ });
237
+
238
+ test('modal BottomSheet keeps native focus containment without cycling through the viewport', async ({
239
+ page,
240
+ browserName,
241
+ }) => {
242
+ await mount(page, browserName);
243
+ await page.getByRole('button', {name: 'Toggle modal', exact: true}).click();
244
+ const dialog = page.getByRole('dialog', {
245
+ name: 'Keyboard reading',
246
+ exact: true,
247
+ });
248
+ await expect
249
+ .poll(async () => dialog.evaluate(element => element.matches(':modal')))
250
+ .toBe(true);
251
+ const viewport = page.getByRole('group', {
252
+ name: 'Keyboard reading',
253
+ exact: true,
254
+ });
255
+ await expect(viewport).toHaveAttribute('data-scrollable-block', 'true');
256
+ const panel = dialog.locator('.astryx-bottom-sheet');
257
+ await panel.focus();
258
+ await page.keyboard.press(tabKey(browserName));
259
+ const first = page.getByRole('button', {name: 'First action', exact: true});
260
+ await expect(first).toBeFocused();
261
+ await page.keyboard.press(tabKey(browserName, true));
262
+ await expect(viewport).not.toBeFocused();
263
+ await expect(first).not.toBeFocused();
264
+ // Native dialog traversal may wrap to another sheet target or browser chrome;
265
+ // it must never focus the inert background or create a viewport/child loop.
266
+ await expect(
267
+ page.getByRole('button', {name: 'Before sheet', includeHidden: true}),
268
+ ).not.toBeFocused();
269
+ await expect(viewport).toHaveAttribute('tabindex', '0');
270
+ });
271
+
272
+ test('fitting text removes the viewport stop while preserving current focus', async ({
273
+ page,
274
+ browserName,
275
+ }) => {
276
+ const {viewport} = await mount(page, browserName, 'text');
277
+ await viewport.focus();
278
+ await viewport.locator('[data-scroll-content]').evaluate(element => {
279
+ element.textContent = 'Short reading content';
280
+ });
281
+ await expect(viewport).not.toHaveAttribute('data-scrollable-block', 'true');
282
+ await expect(viewport).toHaveAttribute('tabindex', '-1');
283
+ await expect(viewport).toBeFocused();
284
+ });
285
+
286
+ test('horizontal overflow remains keyboard scrollable after delegation', async ({
287
+ page,
288
+ browserName,
289
+ }) => {
290
+ const {viewport, before} = await mount(page, browserName);
291
+ await viewport.evaluate(element => {
292
+ const content = element.querySelector<HTMLElement>('[data-scroll-content]');
293
+ if (content != null) {
294
+ content.style.width = `${element.clientWidth + 320}px`;
295
+ }
296
+ });
297
+ await expect(viewport).toHaveAttribute('data-scrollable-inline', 'true');
298
+ await before.focus();
299
+ await page.keyboard.press(tabKey(browserName));
300
+ await expect(
301
+ page.getByRole('button', {name: 'First action', exact: true}),
302
+ ).toBeFocused();
303
+ for (const key of ['ArrowRight', 'ArrowLeft']) {
304
+ for (let count = 0; count < 100; count++) {
305
+ const atEdge = await viewport.evaluate(
306
+ (element, forward) =>
307
+ forward
308
+ ? element.scrollLeft >=
309
+ element.scrollWidth - element.clientWidth - 1
310
+ : element.scrollLeft <= 1,
311
+ key === 'ArrowRight',
312
+ );
313
+ if (atEdge) {
314
+ break;
315
+ }
316
+ await page.keyboard.press(key, {delay: 50});
317
+ }
318
+ await expect
319
+ .poll(async () =>
320
+ viewport.evaluate(
321
+ (element, forward) =>
322
+ forward
323
+ ? element.scrollWidth - element.clientWidth - element.scrollLeft
324
+ : element.scrollLeft,
325
+ key === 'ArrowRight',
326
+ ),
327
+ )
328
+ .toBeLessThanOrEqual(1);
329
+ }
330
+ });
331
+
332
+ test('does not skip an aria-hidden first tab stop to delegate to a later action', async ({
333
+ page,
334
+ browserName,
335
+ }) => {
336
+ const {viewport} = await mount(page, browserName);
337
+ await page
338
+ .getByRole('button', {name: 'First action', exact: true})
339
+ .evaluate(element => {
340
+ element.setAttribute('aria-hidden', 'true');
341
+ });
342
+ await page.keyboard.press(tabKey(browserName));
343
+ await expect(viewport).toBeFocused();
344
+ });
@@ -21,7 +21,13 @@
21
21
  * ./Button.a11y.known-failures.ts, both shared with the jsdom lane.
22
22
  */
23
23
 
24
- import {expect, test, type CDPSession, type Page} from '@playwright/test';
24
+ import {
25
+ expect,
26
+ test,
27
+ type CDPSession,
28
+ type Locator,
29
+ type Page,
30
+ } from '@playwright/test';
25
31
  import {
26
32
  BUTTON_PATTERN,
27
33
  blockingResults,
@@ -93,6 +99,14 @@ async function activationsOn(page: Page): Promise<number> {
93
99
  return Number(raw ?? '0');
94
100
  }
95
101
 
102
+ function pointerTargetFor(
103
+ page: Page,
104
+ state: ButtonBindingRow,
105
+ ): Locator | undefined {
106
+ const selector = (state as ButtonBindingState).pointerTargetSelector;
107
+ return selector == null ? undefined : page.locator(selector).first();
108
+ }
109
+
96
110
  async function runState(
97
111
  page: Page,
98
112
  cdp: CDPSession,
@@ -111,6 +125,7 @@ async function runState(
111
125
  return createChromiumHarness({
112
126
  page,
113
127
  subject: page.locator('#storybook-root').getByRole('button').first(),
128
+ pointerTarget: pointerTargetFor(page, state),
114
129
  cdp,
115
130
  });
116
131
  },
@@ -306,6 +321,7 @@ test('every expectation is exercised by at least one bound state', async ({
306
321
  .locator('#storybook-root')
307
322
  .getByRole('button')
308
323
  .first(),
324
+ pointerTarget: pointerTargetFor(page, state),
309
325
  cdp,
310
326
  });
311
327
  },
@@ -83,33 +83,4 @@ export const BUTTON_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [
83
83
  userImpact: 'The same, from an icon button.',
84
84
  reason: 'Inherited from Button, as above.',
85
85
  },
86
-
87
- // ---- ClickableCard's role-bearing element cannot be clicked -------------
88
- {
89
- expectation: 'button.action.survives-an-aborted-press',
90
- binding: 'ClickableCard',
91
- state: 'clickable-card',
92
- evidenceLayer: 'real-browser',
93
- failureEquals:
94
- 'a pointer press cannot land on this control: something else is on top of it at its own centre, so there is no press here to abort',
95
- standardsReference: 'WCAG 2.2 2.5.2 Pointer Cancellation (Level A)',
96
- userImpact:
97
- 'The same defect seen from the other side: pointer cancellation cannot be demonstrated on a control no pointer press can land on. Recorded rather than reported green — a serene pass here would claim an outcome nobody observed.',
98
- reason:
99
- 'Same cause as the record above. It is a second record rather than a wider one because a known failure names exactly one outcome, and these two would be fixed and verified separately.',
100
- },
101
- {
102
- expectation: 'button.action.runs-on-pointer',
103
- binding: 'ClickableCard',
104
- state: 'clickable-card',
105
- evidenceLayer: 'real-browser',
106
- failureEquals:
107
- 'a pointer could not reach this control within 2000ms: the browser never found it visible, stable, and able to receive a pointer event. Something is covering it, or it is clipped to nothing.',
108
- standardsReference:
109
- 'Current Astryx buttons family FR2 and WAI-ARIA APG Button operability requirement; supports WCAG 2.2 4.1.2 Name, Role, Value (Level A)',
110
- userImpact:
111
- "Anyone whose tooling acts on the accessible object rather than on pixels — a speech-input user saying the card's name, or assistive technology dispatching a click at the element it is told is the button — aims at a 1×1 control the card's own content paints over. A sighted mouse user is unaffected: they click the card surface, which handles it.",
112
- reason:
113
- 'ClickableCard deliberately splits the surface that takes the click from the hidden button that carries the role and name. Reconciling the two is a design-system decision, not a change this contract should make.',
114
- },
115
86
  ];
@@ -88,12 +88,19 @@ export const BUTTON_STATE_RENDERS: Record<ButtonStateId, StateRender> = {
88
88
  ),
89
89
 
90
90
  'clickable-card': activate => (
91
- <ClickableCard label="Open billing settings" onClick={activate}>
91
+ <ClickableCard
92
+ data-a11y-pointer-target
93
+ label="Open billing settings"
94
+ onClick={activate}>
92
95
  <div>Billing</div>
93
96
  </ClickableCard>
94
97
  ),
95
98
  'clickable-card-disabled': activate => (
96
- <ClickableCard label="Open billing settings" isDisabled onClick={activate}>
99
+ <ClickableCard
100
+ data-a11y-pointer-target
101
+ label="Open billing settings"
102
+ isDisabled
103
+ onClick={activate}>
97
104
  <div>Billing</div>
98
105
  </ClickableCard>
99
106
  ),
@@ -92,6 +92,13 @@ export interface ButtonBindingState {
92
92
  * typo here is reported as a stale inventory, not as a standards failure.
93
93
  */
94
94
  readonly visibleLabel: string | null;
95
+ /**
96
+ * The surface that receives pointer input when it is not the role-bearing
97
+ * element. ClickableCard keeps its button/link hidden for role, name, and
98
+ * keyboard focus, and takes the pointer on the card surface; a binding that
99
+ * aimed the pointer at the hidden control would be testing the wrong surface.
100
+ */
101
+ readonly pointerTargetSelector?: string;
95
102
  /** The checked-in Storybook story the Chromium lane drives. */
96
103
  readonly storyId: string;
97
104
  /**
@@ -220,6 +227,7 @@ export const BUTTON_BINDING_STATES = [
220
227
  "a card whose whole surface performs an action, through the hidden button that carries the card's role and name",
221
228
  facts: facts(),
222
229
  visibleLabel: null,
230
+ pointerTargetSelector: '[data-a11y-pointer-target]',
223
231
  storyId: 'a11y-button-pattern--clickable-card-default',
224
232
  },
225
233
  {
@@ -228,6 +236,7 @@ export const BUTTON_BINDING_STATES = [
228
236
  summary: 'a disabled action card',
229
237
  facts: facts({operable: false, focusable: false, unavailable: true}),
230
238
  visibleLabel: null,
239
+ pointerTargetSelector: '[data-a11y-pointer-target]',
231
240
  storyId: 'a11y-button-pattern--clickable-card-disabled',
232
241
  },
233
242
 
@@ -8,6 +8,20 @@ export const docs = {
8
8
  displayName: 'Chat Composer',
9
9
  description:
10
10
  'Layout shell for a chat composer. Arranges named slots (drawer, header, input, footer, send) with page-radius container, elevation, a keyboard-only editor focus ring, and concentric inner radius for child elements.',
11
+ usage: {
12
+ description:
13
+ 'ChatComposer is the message-entry shell for a chat surface. It coordinates a controlled or uncontrolled draft, provides a default token-capable input and send/stop action, and arranges optional drawer, header, footer, and status content. Custom inputs and send controls can join the same value, submission, disabled, stop, and focus contract through useChatComposerContext().',
14
+ anatomy: [
15
+ {name: 'Composer frame', required: true, description: 'Outer composition root that groups the body with an optional status message above or below it.'},
16
+ {name: 'Composer body', required: true, description: 'Rounded, elevated surface containing the header, input, and footer.'},
17
+ {name: 'Drawer', required: false, description: 'Content before the body for attachments or other expandable context, typically ChatComposerDrawer.'},
18
+ {name: 'Header', required: false, description: 'Row for start-aligned actions and end-aligned contextual information.'},
19
+ {name: 'Input', required: true, description: 'Default ChatComposerInput or a custom editor connected through useChatComposerContext().'},
20
+ {name: 'Footer', required: true, description: 'Row for caller-supplied footer and send actions plus the primary action.'},
21
+ {name: 'Send or stop action', required: true, description: 'Default ChatSendButton or a custom sendButton; it submits the current draft or requests interruption.'},
22
+ {name: 'Status message', required: false, description: 'Error or warning feedback rendered before or after the composer body.'},
23
+ ],
24
+ },
11
25
  playground: {
12
26
  wrapper: {component: 'Stack', props: {width: 480}},
13
27
  },