@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
@@ -18,6 +18,10 @@
18
18
  *
19
19
  *
20
20
  * SYNC: When modified, update:
21
+ * - /packages/core/src/Chat/ChatComposerInput.test.tsx
22
+ * - /packages/core/src/Chat/ChatComposerInput.doc.mjs
23
+ * - /packages/core/src/Chat/ChatComposerInput.spec.md
24
+ * - /apps/storybook/stories/ChatComposerInput.stories.tsx
21
25
  * - /packages/core/src/Chat/index.ts
22
26
  * - /apps/storybook/stories/ChatComposer.stories.tsx
23
27
  * - /packages/cli/assets/templates/blocks/components/ChatComposerInput/ (block examples)
@@ -32,6 +36,7 @@ import {
32
36
  type ReactNode,
33
37
  type KeyboardEvent,
34
38
  type ClipboardEvent,
39
+ type DragEvent,
35
40
  } from 'react';
36
41
  import {createPortal} from 'react-dom';
37
42
  import type {BaseProps} from '../BaseProps';
@@ -109,6 +114,13 @@ export type ChatComposerTokenCustom = {
109
114
  export type ChatComposerToken =
110
115
  ChatComposerTokenBadge | ChatComposerTokenCustom;
111
116
 
117
+ export interface ChatComposerTokenElementProps extends BaseProps<HTMLSpanElement> {
118
+ /** Ref forwarded to the token wrapper. */
119
+ ref?: React.Ref<HTMLSpanElement>;
120
+ /** Token rendered as a Badge or by its custom render function. */
121
+ token: ChatComposerToken;
122
+ }
123
+
112
124
  export type ChatComposerTriggerItem = SearchableItem;
113
125
 
114
126
  export type ChatComposerTrigger = {
@@ -233,9 +245,14 @@ const styles = stylex.create({
233
245
  whiteSpace: 'pre-wrap',
234
246
  wordBreak: 'break-word',
235
247
  overflowY: 'auto',
248
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
249
+ // control sits under 16px, and only iOS WebKit implements
250
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
236
251
  fontSize: {
237
252
  default: typeScaleVars['--text-body-size'],
238
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
253
+ '@media (pointer: coarse)': {
254
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
255
+ },
239
256
  },
240
257
  lineHeight: `${LINE_HEIGHT_PX}px`,
241
258
  fontFamily: typographyVars['--font-family-body'],
@@ -250,9 +267,12 @@ const styles = stylex.create({
250
267
  insetInlineEnd: 0,
251
268
  pointerEvents: 'none',
252
269
  color: colorVars['--color-text-secondary'],
270
+ // Same iOS-only floor as the editable region it stands in for.
253
271
  fontSize: {
254
272
  default: typeScaleVars['--text-body-size'],
255
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
273
+ '@media (pointer: coarse)': {
274
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
275
+ },
256
276
  },
257
277
  lineHeight: `${LINE_HEIGHT_PX}px`,
258
278
  fontFamily: typographyVars['--font-family-body'],
@@ -508,6 +528,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
508
528
  }, [controlledValue]);
509
529
 
510
530
  const cleanupPortalsRef = useRef<(() => void) | null>(null);
531
+ const emitChangeVersionRef = useRef(0);
511
532
 
512
533
  const emitChange = useCallback(() => {
513
534
  if (!editableRef.current) {
@@ -524,6 +545,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
524
545
  const nextValue = trimmedEmpty ? '' : text;
525
546
  pendingEchoValueRef.current = nextValue;
526
547
  setIsEmpty(trimmedEmpty);
548
+ emitChangeVersionRef.current += 1;
527
549
  onChange?.(nextValue);
528
550
  cleanupPortalsRef.current?.();
529
551
  }, [onChange]);
@@ -542,7 +564,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
542
564
  ? null
543
565
  : (pasteAsTokenProp ?? defaultPasteAsToken);
544
566
 
545
- const insertText = useCallback((text: string) => {
567
+ const insertTextWithoutEmit = useCallback((text: string) => {
546
568
  const editable = editableRef.current;
547
569
  if (!editable) {
548
570
  return;
@@ -550,6 +572,14 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
550
572
  insertTextAtCursor(editable, text);
551
573
  }, []);
552
574
 
575
+ const insertText = useCallback(
576
+ (text: string) => {
577
+ insertTextWithoutEmit(text);
578
+ emitChange();
579
+ },
580
+ [emitChange, insertTextWithoutEmit],
581
+ );
582
+
553
583
  // Keep stable refs in sync for imperative handle
554
584
  insertTokenRef.current = tokens.insertToken;
555
585
  insertTextRef.current = insertText;
@@ -559,7 +589,7 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
559
589
  triggers,
560
590
  editableRef,
561
591
  onInsertToken: tokens.insertToken,
562
- onInsertText: insertText,
592
+ onInsertText: insertTextWithoutEmit,
563
593
  onEmitChange: emitChange,
564
594
  debounceMs,
565
595
  });
@@ -750,15 +780,21 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
750
780
  e.preventDefault();
751
781
  const text = e.clipboardData.getData('text/plain');
752
782
 
753
- // Paste-as-token: convert long pastes to token chips
754
- if (pasteAsToken?.onPaste(e, text)) {
755
- emitChange();
783
+ // Consumer onPaste gets first refusal over plain text. Return true after
784
+ // handling it so the built-in insertion paths do not run. A consumer may
785
+ // use the observable imperative handle while handling the event; emit only
786
+ // if that path did not already publish the change.
787
+ const versionBeforeConsumer = emitChangeVersionRef.current;
788
+ const handled = onPasteProp?.(e, text);
789
+ if (handled) {
790
+ if (emitChangeVersionRef.current === versionBeforeConsumer) {
791
+ emitChange();
792
+ }
756
793
  return;
757
794
  }
758
795
 
759
- // Consumer onPaste — return true to prevent default text insert
760
- const handled = onPasteProp?.(e, text);
761
- if (handled) {
796
+ // Paste-as-token: convert long pastes to token chips.
797
+ if (pasteAsToken?.onPaste(e, text)) {
762
798
  emitChange();
763
799
  return;
764
800
  }
@@ -769,6 +805,28 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
769
805
  [onFiles, onPasteProp, emitChange, tokens, pasteAsToken],
770
806
  );
771
807
 
808
+ const handleDragOver = useCallback((e: DragEvent<HTMLDivElement>) => {
809
+ if (Array.from(e.dataTransfer.types).includes('Files')) {
810
+ e.preventDefault();
811
+ }
812
+ }, []);
813
+
814
+ const handleDrop = useCallback(
815
+ (e: DragEvent<HTMLDivElement>) => {
816
+ const files = Array.from(e.dataTransfer.files);
817
+ if (files.length === 0) {
818
+ return;
819
+ }
820
+ // File drops navigate the page by default. The input owns that drop
821
+ // target even when no callback is supplied, so keep the user in place.
822
+ e.preventDefault();
823
+ if (!isDisabled) {
824
+ onFiles?.(files);
825
+ }
826
+ },
827
+ [isDisabled, onFiles],
828
+ );
829
+
772
830
  const maxHeight = maxRows * LINE_HEIGHT_PX;
773
831
 
774
832
  return (
@@ -794,7 +852,10 @@ export function ChatComposerInput(props: ChatComposerInputProps) {
794
852
  onInput={handleInput}
795
853
  onKeyDown={handleKeyDown}
796
854
  onPaste={handlePaste}
855
+ onDragOver={handleDragOver}
856
+ onDrop={handleDrop}
797
857
  {...triggerMenu.ariaProps}
858
+ aria-disabled={isDisabled || undefined}
798
859
  {...mergeProps(stylex.props(styles.editable), {
799
860
  style: {maxHeight: `${maxHeight}px`},
800
861
  })}
@@ -834,13 +895,17 @@ ChatComposerInput.displayName = 'ChatComposerInput';
834
895
  // Token element helper (for custom rendering in stories/consumers)
835
896
  // =============================================================================
836
897
 
837
- export function ChatComposerTokenElement({token}: {token: ChatComposerToken}) {
898
+ export function ChatComposerTokenElement(props: ChatComposerTokenElementProps) {
899
+ const {token, ref, xstyle, className, style, ...rest} = props;
900
+
838
901
  return (
839
902
  <span
903
+ ref={ref}
904
+ {...mergeProps(stylex.props(styles.tokenSpan, xstyle), className, style)}
905
+ {...rest}
840
906
  data-astryx-token=""
841
907
  data-astryx-token-value={token.value}
842
- contentEditable={false}
843
- {...stylex.props(styles.tokenSpan)}>
908
+ contentEditable={false}>
844
909
  {isCustomToken(token) ? (
845
910
  token.render()
846
911
  ) : (
@@ -0,0 +1,152 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:ChatComposerTokenElement
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, accessibility, theming, testing]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Chat/ChatComposerTokenElement.test.tsx,
16
+ apps/storybook/stories/ChatComposerTokenElement.stories.tsx,
17
+ apps/storybook/rtl-audit/verified-not-applicable.json,
18
+ ]
19
+ modules: []
20
+ families: []
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:public-component-api,
25
+ architecture:component-test-sufficiency,
26
+ architecture:knowledge-contracts,
27
+ ]
28
+ contributing: []
29
+ system_specs: [spec:AST-002, spec:AST-029]
30
+ ---
31
+
32
+ # ChatComposerTokenElement component contract
33
+
34
+ ## Contract at a glance
35
+
36
+ | Area | Contract |
37
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
+ | Public contract | No token shape, default, or rendering change. The public props type and existing span passthrough are directly reachable from the Chat package path. |
39
+ | Behavior | A structured token renders Badge content; a custom token renders caller content. Both retain the serialized value marker and stay non-editable when placed inside a contenteditable composer. |
40
+ | End-user impact | Token labels and caller content remain visible without becoming editable text. |
41
+ | Builder impact | Builders can type the existing component props and attach neutral span attributes, styles, and a ref without recreating its contract. |
42
+ | Compatibility/readiness | Additive patch-compatible contract repair. This observational record adds no token meaning or visual policy. |
43
+ | Review checks | Reject a token wrapper that loses its serialized value, becomes editable, drops accepted span props, or changes the structured/custom rendering split. |
44
+ | Governing rules | `architecture:public-component-api/INV1, INV5–INV6, INV8–INV9, INV12`; `architecture:component-test-sufficiency/INV1–INV8, INV10–INV11`; `spec:AST-029/FR3–FR5, FR7`. |
45
+
46
+ This table is a review projection; the body below is authoritative.
47
+
48
+ ## Intent
49
+
50
+ ChatComposerTokenElement renders one already-defined `ChatComposerToken` outside the
51
+ editor insertion machinery. It supplies the same serialization marker used by
52
+ ChatComposerInput while leaving structured Badge presentation or custom content to
53
+ their existing owners.
54
+
55
+ ## Compatibility and migration
56
+
57
+ - Released default preserved: yes
58
+ - Compatibility class: additive props-type and DOM-passthrough repair
59
+ - Token values and rendering branches: unchanged
60
+ - Migration decision: none
61
+
62
+ ## Ownership boundary
63
+
64
+ **Owns**
65
+
66
+ - The span carrying `data-astryx-token` and the token's serialized value.
67
+ - Keeping that wrapper non-editable when composed inside a contenteditable region.
68
+ - Choosing the existing structured Badge or custom-render branch.
69
+ - Forwarding the wrapper ref and accepted neutral span props.
70
+
71
+ **Does not own / non-goals**
72
+
73
+ - Token value grammar or product semantics.
74
+ - Badge colors, iconography, or sizing.
75
+ - Accessibility semantics and interaction behavior inside caller custom content.
76
+ - Token insertion, deletion, expansion, or composer selection management.
77
+
78
+ ## Public concepts
79
+
80
+ | Concept | Closed values or states | Meaning | Default | Owner | Stability |
81
+ | ---------------- | ------------------------------------------------ | --------------------------------------------------------------------- | -------------- | ------------------------------------ | --------- |
82
+ | token form | structured Badge config; custom render function | Selects caller content without changing serialization. | none; required | `component:ChatComposerTokenElement` | stable |
83
+ | serialized value | non-empty or empty string supplied by caller | Value stored in `data-astryx-token-value` for composer serialization. | none; required | caller value through this component | stable |
84
+ | wrapper surface | ref; DOM/data/ARIA props; className/style/xstyle | Extends the serialization span without replacing owned markers. | omitted | `architecture:public-component-api` | stable |
85
+
86
+ ## Behavioral and layout contract
87
+
88
+ | ID | Candidate invariant | Basis | Draft review state |
89
+ | --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | ------------------ |
90
+ | FR1 | The root MUST expose `data-astryx-token` and the exact supplied `token.value`, and MUST remain `contentEditable={false}`. | shipped implementation, public docs, ChatComposerInput serialization | verify |
91
+ | FR2 | Structured tokens MUST delegate label, variant, and icon rendering to Badge without changing the token value. | shipped implementation and public token type | verify |
92
+ | FR3 | Custom tokens MUST render the caller function result without adding component-owned semantics or interaction. | shipped implementation and public token type | verify |
93
+ | FR4 | Accepted ref, DOM/data/ARIA, className, style, and xstyle inputs MUST reach and compose on the root span while component-owned serialization attributes retain precedence. | `architecture:public-component-api/INV5–INV6, INV8` | settled |
94
+
95
+ ### Allowed variation
96
+
97
+ - Structured Badge labels, variants, and icons remain caller supplied.
98
+ - Custom rendered content and its semantics remain caller owned.
99
+ - The wrapper may be used inside or outside a contenteditable surface.
100
+
101
+ ### Representative states
102
+
103
+ | State | Required invariant | Allowed variation |
104
+ | ---------------- | ------------------------------------------------------------------------- | ------------------------------- |
105
+ | structured token | Visible Badge content and exact serialized value. | Badge label, variant, and icon. |
106
+ | custom token | Caller output and exact serialized value. | Any caller-owned React node. |
107
+ | extended wrapper | Neutral DOM props and ref reach the span without replacing owned markers. | Supported BaseProps values. |
108
+
109
+ ## Accessibility contract
110
+
111
+ - The wrapper owns no interactive role or accessible name beyond accepted caller props.
112
+ - Structured Badge text remains visible content; custom content semantics remain caller owned.
113
+ - `contentEditable={false}` keeps the token atomically non-editable when nested in an editor.
114
+
115
+ ## Design relationships
116
+
117
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
118
+ | ------------------ | ------------------------- | ------------------------ | -------------- | ------------------ |
119
+ | token wrapper | Inline alignment only | observed implementation | supporting | FR1, FR4 |
120
+ | structured content | Badge presentation | `component:Badge` | supporting | FR2 |
121
+ | custom content | caller-owned presentation | caller | supporting | FR3 |
122
+
123
+ The wrapper does not paint a component-owned visual surface or introduce a public theme
124
+ target. Badge theming remains owned by Badge, and custom content remains caller owned.
125
+
126
+ ## Family and system relationships
127
+
128
+ - `component:ChatComposerInput` owns insertion, serialization traversal, deletion, and selection behavior.
129
+ - `architecture:public-component-api` owns the public props type, ref, and BaseProps composition.
130
+ - `architecture:component-test-sufficiency` owns direct evidence for the established public serialization seam.
131
+ - `spec:AST-029` owns this observational audit backfill and its finite evidence inventory.
132
+
133
+ ## Verification map
134
+
135
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
136
+ | --------------- | ------------------------------------------------------------------ | -------------------------------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------- |
137
+ | FR1–FR3 | `ChatComposerTokenElement.test.tsx`; direct Storybook stories | structured and custom tokens | A marker/value disappears, the wrapper becomes editable, or the wrong rendering branch appears. | `audit:ChatComposerTokenElement/behavior` |
138
+ | FR4 | focused ref and DOM-passthrough test; public typecheck | ref, ARIA/data props, styling seams | An accepted input is dropped or an owned serialization marker is replaced. | `audit:ChatComposerTokenElement/api` |
139
+ | rendered states | direct Storybook stories and exact-head visual/accessibility gates | Badge and custom content in supported themes | A story is unreachable or introduces an accessibility/visual regression. | `audit:ChatComposerTokenElement/rendered` |
140
+
141
+ ## Decision log
142
+
143
+ No component-local policy decision is created by this observational draft.
144
+
145
+ ## Open questions
146
+
147
+ None.
148
+
149
+ ## Content boundary
150
+
151
+ This file does not duplicate consumer examples, current audit scores, Badge policy, or
152
+ ChatComposerInput selection mechanics.
@@ -0,0 +1,85 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ChatComposerTokenElement.test.tsx
5
+ * @input Public Chat token element and props contract
6
+ * @output Verifies serialization markers, rendering modes, and DOM passthrough
7
+ * @position Public contract tests for @astryxdesign/core/Chat
8
+ */
9
+
10
+ import {render, screen} from '@testing-library/react';
11
+ import {describe, expect, expectTypeOf, it} from 'vitest';
12
+ import {
13
+ ChatComposerTokenElement,
14
+ type ChatComposerTokenElementProps,
15
+ } from '@astryxdesign/core/Chat';
16
+
17
+ describe('ChatComposerTokenElement', () => {
18
+ it('renders a structured token with its serialized value', () => {
19
+ render(
20
+ <ChatComposerTokenElement
21
+ token={{value: '@ada', label: 'Ada', variant: 'blue'}}
22
+ data-testid="token"
23
+ />,
24
+ );
25
+
26
+ const token = screen.getByTestId('token');
27
+ expect(token).toHaveAttribute('data-astryx-token', '');
28
+ expect(token).toHaveAttribute('data-astryx-token-value', '@ada');
29
+ expect(token).toHaveAttribute('contenteditable', 'false');
30
+ expect(token).toHaveTextContent('Ada');
31
+ });
32
+
33
+ it('renders caller-owned custom token content', () => {
34
+ render(
35
+ <ChatComposerTokenElement
36
+ token={{
37
+ value: '#design',
38
+ render: () => <strong>Design</strong>,
39
+ }}
40
+ />,
41
+ );
42
+
43
+ expect(screen.getByText('Design').tagName).toBe('STRONG');
44
+ });
45
+
46
+ it('forwards the span ref and neutral DOM props', () => {
47
+ const ref = {current: null as HTMLSpanElement | null};
48
+ render(
49
+ <ChatComposerTokenElement
50
+ ref={ref}
51
+ token={{value: '@ada', label: 'Ada'}}
52
+ aria-label="Mention Ada"
53
+ />,
54
+ );
55
+
56
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
57
+ expect(ref.current).toHaveAttribute('aria-label', 'Mention Ada');
58
+ });
59
+
60
+ it('keeps component-owned serialization markers authoritative', () => {
61
+ const conflictingMarkers = {
62
+ 'data-astryx-token': 'consumer-value',
63
+ 'data-astryx-token-value': 'wrong-value',
64
+ };
65
+
66
+ render(
67
+ <ChatComposerTokenElement
68
+ {...conflictingMarkers}
69
+ token={{value: '@ada', label: 'Ada'}}
70
+ data-testid="token"
71
+ />,
72
+ );
73
+
74
+ const token = screen.getByTestId('token');
75
+ expect(token).toHaveAttribute('data-astryx-token', '');
76
+ expect(token).toHaveAttribute('data-astryx-token-value', '@ada');
77
+ });
78
+
79
+ it('publishes the span props contract', () => {
80
+ expectTypeOf<ChatComposerTokenElementProps>().toMatchTypeOf<{
81
+ token: {value: string};
82
+ ref?: React.Ref<HTMLSpanElement>;
83
+ }>();
84
+ });
85
+ });
@@ -47,7 +47,7 @@ export const docs = {
47
47
  name: 'isHiddenWhenUnsupported',
48
48
  type: 'boolean',
49
49
  description:
50
- 'When true, renders nothing if the browser does not support SpeechRecognition.',
50
+ 'When true, renders nothing if the browser does not support SpeechRecognition. When false, keeps the button visible but disabled.',
51
51
  default: 'true',
52
52
  },
53
53
  {
@@ -81,7 +81,7 @@ export const docsZh = {
81
81
  propDescriptions: {
82
82
  dictation: 'The return value from useChatDictation or useSpeechRecognition. Controls all button state.',
83
83
  size: 'Button size.',
84
- isHiddenWhenUnsupported: 'When true, renders nothing if SpeechRecognition is unsupported.',
84
+ isHiddenWhenUnsupported: 'When true, hides unsupported dictation; when false, keeps a disabled button visible.',
85
85
  label: 'Accessible label override.',
86
86
  xstyle: 'Additional StyleX styles.',
87
87
  },
@@ -104,7 +104,7 @@ export const docsDense = {
104
104
  propDescriptions: {
105
105
  dictation: 'return from useChatDictation/useSpeechRecognition; controls state',
106
106
  size: 'btn size',
107
- isHiddenWhenUnsupported: 'hide when SpeechRecognition unsupported',
107
+ isHiddenWhenUnsupported: 'hide unsupported dictation; false keeps disabled btn visible',
108
108
  label: 'a11y label override',
109
109
  xstyle: 'extra StyleX styles',
110
110
  },
@@ -0,0 +1,160 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 5
4
+ kind: component
5
+ id: component:ChatDictationButton
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, accessibility, theming, testing]
13
+ verified_by:
14
+ [
15
+ packages/core/src/Chat/ChatDictationButton.test.tsx,
16
+ apps/storybook/stories/ChatDictation.stories.tsx,
17
+ apps/storybook/rtl-audit/verified-not-applicable.json,
18
+ ]
19
+ modules: []
20
+ families: []
21
+ design_specs: []
22
+ architecture:
23
+ [
24
+ architecture:public-component-api,
25
+ architecture:component-style-authoring,
26
+ architecture:theme-tokens,
27
+ architecture:component-test-sufficiency,
28
+ architecture:knowledge-contracts,
29
+ ]
30
+ contributing: []
31
+ system_specs: [spec:AST-029]
32
+ ---
33
+
34
+ # ChatDictationButton component contract
35
+
36
+ ## Contract at a glance
37
+
38
+ | Area | Contract |
39
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
40
+ | Public contract | `dictation` supplies support, listening, volume, bands, and toggle state; `size`, `label`, root span props, styles, and ref remain additive inputs. |
41
+ | Behavior | The idle button shows the microphone action. Listening replaces the icon with five volume-reactive bars. Unsupported dictation is hidden by default or disabled when explicitly retained. |
42
+ | End-user impact | Voice input remains operable and labelled; unsupported browsers do not expose an action that cannot run. |
43
+ | Builder impact | Builders can place the control in ChatComposer send actions and safely customize its root layout without owning speech state. |
44
+ | Compatibility/readiness | Patch-compatible repair: existing supported behavior is unchanged; the opt-in unsupported-visible state becomes accurately disabled. |
45
+ | Review checks | Reject an enabled unsupported action, a missing listening label, raw theme-inaccessible clipping color, or lost root passthrough. |
46
+ | Governing rules | `architecture:component-style-authoring/INV1–INV3`; `architecture:theme-tokens/INV3`; `architecture:public-component-api/INV1, INV5–INV6, INV8`; `architecture:component-test-sufficiency/INV1–INV8, INV10–INV11`. |
47
+
48
+ This table is a review projection; the body below is authoritative.
49
+
50
+ ## Intent
51
+
52
+ ChatDictationButton renders the visible control for an existing speech-recognition
53
+ state owner. It translates that owner's support and listening states into one
54
+ icon-only Button and a direction-neutral equalizer visualization.
55
+
56
+ ## Compatibility and migration
57
+
58
+ - Released default preserved: yes
59
+ - Compatibility class: behavioral bug fix for the explicit unsupported-visible state
60
+ - Supported idle and listening states: unchanged
61
+ - Migration decision: none
62
+
63
+ ## Ownership boundary
64
+
65
+ **Owns**
66
+
67
+ - The start/stop accessible label and optional caller label override.
68
+ - Hidden-versus-disabled presentation when speech recognition is unsupported.
69
+ - The five-bar visual projection of supplied band and volume values.
70
+ - Theme-aware accent-to-error clipping feedback.
71
+ - Root span ref and accepted neutral span props.
72
+
73
+ **Does not own / non-goals**
74
+
75
+ - Speech recognition support detection, lifecycle, transcripts, or errors.
76
+ - Microphone permission UX or audio capture.
77
+ - ChatComposer placement beyond documented composition guidance.
78
+ - A public color, threshold, or equalizer-shape axis.
79
+
80
+ ## Public concepts
81
+
82
+ | Concept | Closed values or states | Meaning | Default | Owner | Stability |
83
+ | ------------ | -------------------------------------------------- | ------------------------------------------------------------- | --------------------------- | ---------------------------------------- | --------- |
84
+ | support | supported; unsupported-hidden; unsupported-visible | Determines whether the control can render and operate. | unsupported-hidden | speech owner projected by this component | stable |
85
+ | listening | idle; listening | Selects the start/stop label and microphone/equalizer visual. | idle | `dictation` | stable |
86
+ | size | `sm`; `md` | Sizes the Button and equalizer geometry together. | `md` | component | stable |
87
+ | label | translated default; caller override | Names the action for assistive technology. | state-dependent translation | component/caller | stable |
88
+ | root surface | ref; DOM/data/ARIA props; className/style/xstyle | Extends the positioning wrapper. | omitted | public component API | stable |
89
+
90
+ ## Behavioral and layout contract
91
+
92
+ | ID | Candidate invariant | Basis | Draft review state |
93
+ | --- | ------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------- | ------------------ |
94
+ | FR1 | Supported idle state MUST expose an enabled Button named by the start label and invoke `dictation.toggle` on activation. | shipped API and Button contract | verify |
95
+ | FR2 | Supported listening state MUST expose the stop label and five aria-hidden visualizer bars derived from `bands`. | shipped implementation and docs | verify |
96
+ | FR3 | Unsupported state MUST render nothing by default. If explicitly retained, its Button MUST be disabled. | public option plus operability/accessibility repair | settled |
97
+ | FR4 | Clipping feedback MUST derive from portable semantic color tokens so a consumer theme can retint both endpoints. | `architecture:theme-tokens/INV3` and lint enforcement | settled |
98
+ | FR5 | Accepted ref, DOM/data/ARIA, className, style, and xstyle inputs MUST reach and compose on the root span. | `architecture:public-component-api` | settled |
99
+
100
+ ### Allowed variation
101
+
102
+ - Callers may replace the translated start/stop label with `label`.
103
+ - Band amplitudes and clipping strength vary with the supplied dictation owner.
104
+ - Consumer themes may change the accent and error colors used by the bars.
105
+
106
+ ## Accessibility contract
107
+
108
+ - The nested Button owns keyboard and pointer activation, focus, and disabled semantics.
109
+ - The equalizer is decorative and MUST remain hidden from the accessibility tree.
110
+ - The visible control MUST have a state-appropriate accessible label.
111
+ - An unsupported visible control MUST communicate disabled state and MUST NOT activate.
112
+ - Reduced-motion preference removes the equalizer's transition duration.
113
+
114
+ ## Design relationships
115
+
116
+ - Idle uses the shared microphone Icon through Button.
117
+ - Listening bars begin at `--color-accent` and blend toward `--color-error` as clipping increases.
118
+ - The component adds no portable token or public theme target.
119
+ - Equalizer dimensions remain private component geometry.
120
+
121
+ ## Responsive and bidirectional behavior
122
+
123
+ The control has fixed `sm` and `md` sizes and no viewport-owned behavior. Its
124
+ microphone glyph, centered bars, and block-axis scale are direction-neutral; the
125
+ verified-N/A ledger records this with source evidence.
126
+
127
+ ## Family and system relationships
128
+
129
+ - ChatComposer owns placement through its `sendActions` slot.
130
+ - Button owns activation, focus, disabled semantics, hit target, and icon-only presentation.
131
+ - Icon owns the microphone glyph.
132
+ - `useChatDictation` and `useSpeechRecognition` own speech lifecycle and state.
133
+ - Theme token architecture owns the accent and error semantic roles.
134
+
135
+ ## Verification map
136
+
137
+ | Contract | Evidence |
138
+ | ----------------------- | --------------------------------------------------------------------- |
139
+ | FR1, labels, activation | `ChatDictationButton.test.tsx` idle activation and custom-label cases |
140
+ | FR2 | listening structure test and Listening/Speaking stories |
141
+ | FR3 | unsupported hidden and visible-disabled tests/stories |
142
+ | FR4 | clipping semantic-color test, lint, and Clipping story |
143
+ | FR5 | typed ref and root-passthrough test |
144
+ | rendered states | exact-head Storybook, a11y, and visual CI |
145
+ | RTL N/A | `apps/storybook/rtl-audit/verified-not-applicable.json` |
146
+
147
+ ## Decision log
148
+
149
+ | Decision | Reason | Authority |
150
+ | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------ |
151
+ | Keep unsupported dictation hidden by default and disabled when explicitly visible. | An unavailable action must not present as operable. | objective accessibility/operability repair |
152
+ | Blend portable accent and error tokens for clipping. | Preserves the existing progressive clipping cue while keeping both endpoints theme-owned. | `architecture:theme-tokens/INV3` |
153
+
154
+ ## Open questions
155
+
156
+ None for this repair.
157
+
158
+ ## Content boundary
159
+
160
+ Labels may be translated defaults or caller overrides. The component does not own transcript text, permission errors, microphone instructions, or product-specific voice-input guidance.