@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
@@ -1,43 +1,108 @@
1
1
  // Copyright (c) Meta Platforms, Inc. and affiliates.
2
2
 
3
- import {describe, it, expect} from 'vitest';
4
- import {render, screen} from '@testing-library/react';
3
+ import {createRef} from 'react';
4
+ import {describe, it, expect, vi} from 'vitest';
5
+ import {fireEvent, render, screen} from '@testing-library/react';
6
+ import {colorVars} from '../theme/tokens.stylex';
5
7
  import {ChatDictationButton} from './ChatDictationButton';
8
+ import type {ChatDictationButtonProps} from './ChatDictationButton';
6
9
  import type {UseSpeechRecognitionReturn} from './useSpeechRecognition';
7
10
 
8
- const dictation: UseSpeechRecognitionReturn = {
9
- isSupported: true,
10
- isListening: false,
11
- isSpeaking: false,
12
- volume: 0,
13
- bands: [0, 0, 0, 0, 0],
14
- rawBands: [0, 0, 0, 0, 0],
15
- interimTranscript: '',
16
- start: () => {},
17
- stop: () => {},
18
- abort: () => {},
19
- toggle: () => {},
20
- };
11
+ function makeDictation(
12
+ overrides: Partial<UseSpeechRecognitionReturn> = {},
13
+ ): UseSpeechRecognitionReturn {
14
+ return {
15
+ isSupported: true,
16
+ isListening: false,
17
+ isSpeaking: false,
18
+ volume: 0,
19
+ bands: [0, 0, 0, 0, 0],
20
+ rawBands: [0, 0, 0, 0, 0],
21
+ interimTranscript: '',
22
+ start: vi.fn(),
23
+ stop: vi.fn(),
24
+ abort: vi.fn(),
25
+ toggle: vi.fn(),
26
+ ...overrides,
27
+ };
28
+ }
21
29
 
22
30
  describe('ChatDictationButton', () => {
23
- it('renders a dictation button', () => {
24
- render(
25
- <ChatDictationButton dictation={dictation} data-testid="dictation" />,
31
+ it('labels and toggles the idle button', () => {
32
+ const dictation = makeDictation();
33
+ render(<ChatDictationButton dictation={dictation} />);
34
+
35
+ fireEvent.click(screen.getByRole('button', {name: 'Start dictation'}));
36
+ expect(dictation.toggle).toHaveBeenCalledTimes(1);
37
+ });
38
+
39
+ it('shows five hidden visualizer bars and the stop label while listening', () => {
40
+ const dictation = makeDictation({
41
+ isListening: true,
42
+ bands: [0.02, 0.04, 0.08, 0.12, 0.18],
43
+ });
44
+ const {container} = render(<ChatDictationButton dictation={dictation} />);
45
+
46
+ expect(screen.getByRole('button', {name: 'Stop dictation'})).toBeEnabled();
47
+ const visualizer = container.querySelector('[aria-hidden="true"]');
48
+ expect(visualizer).not.toBeNull();
49
+ expect(visualizer?.children).toHaveLength(5);
50
+ });
51
+
52
+ it('uses theme-owned colors for clipping feedback', () => {
53
+ const dictation = makeDictation({
54
+ isListening: true,
55
+ volume: 0.3,
56
+ bands: [0.2, 0.2, 0.2, 0.2, 0.2],
57
+ });
58
+ const {container} = render(<ChatDictationButton dictation={dictation} />);
59
+
60
+ const barStyle = container
61
+ .querySelector('[aria-hidden="true"] > span')
62
+ ?.getAttribute('style');
63
+ expect(barStyle).toContain('color-mix(in srgb');
64
+ expect(barStyle).toContain(colorVars['--color-accent']);
65
+ expect(barStyle).toContain(colorVars['--color-error']);
66
+ expect(barStyle).not.toContain('hsl(');
67
+ });
68
+
69
+ it('hides unsupported dictation by default', () => {
70
+ const {container} = render(
71
+ <ChatDictationButton dictation={makeDictation({isSupported: false})} />,
26
72
  );
27
- expect(screen.getByTestId('dictation')).toBeTruthy();
73
+
74
+ expect(container).toBeEmptyDOMElement();
28
75
  });
29
76
 
30
- it('forwards rest props (data-*, aria-*, id) to the root element', () => {
77
+ it('disables an unsupported button when explicitly kept visible', () => {
31
78
  render(
32
79
  <ChatDictationButton
33
- dictation={dictation}
34
- data-testid="dictation"
35
- data-custom="x"
36
- id="dictate-1"
80
+ dictation={makeDictation({isSupported: false})}
81
+ isHiddenWhenUnsupported={false}
37
82
  />,
38
83
  );
39
- const root = screen.getByTestId('dictation');
40
- expect(root).toHaveAttribute('data-custom', 'x');
41
- expect(root).toHaveAttribute('id', 'dictate-1');
84
+
85
+ expect(
86
+ screen.getByRole('button', {name: 'Start dictation'}),
87
+ ).toBeDisabled();
88
+ });
89
+
90
+ it('forwards the root ref and accepted span props', () => {
91
+ const ref = createRef<HTMLSpanElement>();
92
+ const props = {
93
+ dictation: makeDictation(),
94
+ 'data-testid': 'dictation',
95
+ 'data-custom': 'x',
96
+ id: 'dictate-1',
97
+ label: 'Use voice input',
98
+ ref,
99
+ } satisfies ChatDictationButtonProps;
100
+
101
+ render(<ChatDictationButton {...props} />);
102
+
103
+ expect(ref.current).toBe(screen.getByTestId('dictation'));
104
+ expect(ref.current).toHaveAttribute('data-custom', 'x');
105
+ expect(ref.current).toHaveAttribute('id', 'dictate-1');
106
+ expect(screen.getByRole('button', {name: 'Use voice input'})).toBeEnabled();
42
107
  });
43
108
  });
@@ -11,7 +11,8 @@
11
11
  * A toggle button that connects to useChatDictation. Shows a microphone
12
12
  * icon when idle. When listening, replaces the icon with volume-reactive
13
13
  * frequency bars (equalizer style) that respond to real mic input.
14
- * Bars use the accent color and hue-shift when volume clips past 10%.
14
+ * Bars use the accent color and blend toward the semantic error color when
15
+ * volume clips past 20%.
15
16
  *
16
17
  * SYNC: When modified, update:
17
18
  * - /packages/core/src/Chat/index.ts (exports)
@@ -39,7 +40,7 @@ export interface ChatDictationButtonProps extends BaseProps<HTMLSpanElement> {
39
40
  dictation: UseSpeechRecognitionReturn;
40
41
  /** Button size. @default "md" */
41
42
  size?: 'sm' | 'md';
42
- /** Hide the button when SpeechRecognition is not supported. @default true */
43
+ /** Hide the button when SpeechRecognition is unsupported. When false, the unsupported button remains visible but disabled. @default true */
43
44
  isHiddenWhenUnsupported?: boolean;
44
45
  /** Accessible label override. */
45
46
  label?: string;
@@ -128,13 +129,13 @@ export function ChatDictationButton({
128
129
  // Boost each band for visibility — quiet speech (0-10%) maps to full visual range
129
130
  const boostedBands = bands.map(b => Math.min(Math.pow(b / 0.2, 0.5), 1));
130
131
 
131
- // Hue shift from accent color when volume clips past 10%
132
+ // Blend toward the theme's semantic error color as clipping increases.
132
133
  const isClipping = rawVolume >= 0.2;
133
- const hueShift = isClipping ? Math.min((rawVolume - 0.2) / 0.1, 1) * 60 : 0;
134
+ const clippingStrength = Math.min((rawVolume - 0.2) / 0.1, 1) * 100;
134
135
 
135
136
  const barColor = isClipping
136
- ? `hsl(calc(var(--accent-hue, 210) + ${hueShift}), 80%, 50%)`
137
- : `var(--color-accent, ${colorVars['--color-accent']})`;
137
+ ? `color-mix(in srgb, ${colorVars['--color-accent']}, ${colorVars['--color-error']} ${clippingStrength}%)`
138
+ : colorVars['--color-accent'];
138
139
 
139
140
  const {barWidth, barGap, barMaxHeight} = SIZE_CONFIG[size];
140
141
 
@@ -180,6 +181,7 @@ export function ChatDictationButton({
180
181
  size={size}
181
182
  icon={isListening ? undefined : <Icon icon="microphone" size={size} />}
182
183
  isIconOnly
184
+ isDisabled={!dictation.isSupported}
183
185
  onClick={dictation.toggle}
184
186
  />
185
187
  </span>
@@ -180,10 +180,7 @@ describe('ChatSendButton', () => {
180
180
  expect(send).toBeEnabled();
181
181
 
182
182
  fireEvent.click(send);
183
- // Only that the click reaches the composer's submit path is this
184
- // button's behavior. The submitted value comes from the composer's own
185
- // state, not from here, so asserting it would pin ChatComposer instead.
186
- expect(onSubmit).toHaveBeenCalledTimes(1);
183
+ expect(onSubmit).toHaveBeenCalledWith('hello');
187
184
  });
188
185
  });
189
186
  });
@@ -95,7 +95,7 @@ export function ChatSendButton(props: ChatSendButtonProps): ReactNode {
95
95
  ...rest
96
96
  } = props;
97
97
 
98
- const handleSend = onSend ?? (() => context?.onSubmit(''));
98
+ const handleSend = onSend ?? (() => context?.onSubmit(context.value));
99
99
  const defaultStopIcon = useIcon('stop');
100
100
  const defaultSendIcon = useIcon('arrowUp');
101
101
 
package/src/Chat/index.ts CHANGED
@@ -25,6 +25,7 @@ export {ChatComposerInput, ChatComposerTokenElement} from './ChatComposerInput';
25
25
  export type {
26
26
  ChatComposerInputProps,
27
27
  ChatComposerInputHandle,
28
+ ChatComposerTokenElementProps,
28
29
  ChatComposerToken,
29
30
  ChatComposerTrigger,
30
31
  ChatComposerTriggerItem,
@@ -137,6 +137,17 @@ export const docs = {
137
137
  ],
138
138
  },
139
139
  usage: {
140
+ accessibility: [
141
+ {
142
+ name: 'Checkbox box',
143
+ category: 'Color contrast',
144
+ criterion: '1.4.11 Non-text Contrast',
145
+ requirement: '3:1',
146
+ states: ['Rest', 'Hover', 'Pointer down', 'Checked'],
147
+ description:
148
+ 'The box edge (unchecked) and fill (checked) must have at least 3:1 contrast with the surface behind them. For Hover and Pointer down, measure the final colors after the tint and the pressed overlay are applied.',
149
+ },
150
+ ],
140
151
  description: 'CheckboxInput toggles a single on/off value. Use it for settings like "Enable notifications", terms acceptance, or opt-in choices. For multiple checkboxes in a group, use CheckboxList instead.',
141
152
  bestPractices: [
142
153
  { guidance: true, description: 'Always provide a visible label so the user knows what they are toggling. Use isLabelHidden only when surrounding context makes it obvious.' },
@@ -15,6 +15,7 @@
15
15
  import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
16
16
  import {render, screen, fireEvent, waitFor} from '@testing-library/react';
17
17
  import userEvent from '@testing-library/user-event';
18
+ import {hasPressedArm} from '../__tests__/pressState';
18
19
  import {CheckboxInput} from './CheckboxInput';
19
20
  import {Theme} from '../theme/Theme';
20
21
  import {defineTheme} from '../theme/defineTheme';
@@ -763,3 +764,36 @@ describe('label theme target', () => {
763
764
  expect(label).toHaveClass('astryx-checkbox-label');
764
765
  });
765
766
  });
767
+
768
+ describe('pressed state', () => {
769
+ it('paints the pressed overlay over the indicator while the row is pressed', () => {
770
+ const {container} = render(
771
+ <CheckboxInput label="Accept terms" value={false} onChange={() => {}} />,
772
+ );
773
+ const box = container.querySelector('.astryx-checkbox-indicator');
774
+ const wrapper = box?.parentElement?.parentElement;
775
+ if (wrapper == null) {
776
+ throw new Error('the checkbox has no indicator wrapper to press');
777
+ }
778
+ // The owner paints over the resolved indicator, so the treatment survives a
779
+ // theme replacement that does not forward style props.
780
+ expect(hasPressedArm(wrapper)).toBe(true);
781
+ });
782
+
783
+ it('does not expose a pressed arm on a disabled checkbox', () => {
784
+ const {container} = render(
785
+ <CheckboxInput
786
+ label="Unavailable"
787
+ value={false}
788
+ onChange={() => {}}
789
+ isDisabled
790
+ />,
791
+ );
792
+ const box = container.querySelector('.astryx-checkbox-indicator');
793
+ const wrapper = box?.parentElement?.parentElement;
794
+ if (wrapper == null) {
795
+ throw new Error('the checkbox has no indicator wrapper');
796
+ }
797
+ expect(hasPressedArm(wrapper)).toBe(false);
798
+ });
799
+ });
@@ -70,6 +70,22 @@ const styles = stylex.create({
70
70
  flexShrink: 0,
71
71
  isolation: 'isolate',
72
72
  },
73
+ // The owner paints this layer over the resolved indicator, so a theme
74
+ // replacement cannot accidentally drop the component's pressed contract.
75
+ indicatorPressOverlay: {
76
+ '::after': {
77
+ content: '""',
78
+ position: 'absolute',
79
+ inset: 0,
80
+ borderRadius: radiusVars['--radius-inner'],
81
+ pointerEvents: 'none',
82
+ backgroundColor: {
83
+ default: 'transparent',
84
+ [stylex.when.ancestor(':active', indicatorScope)]:
85
+ colorVars['--color-overlay-pressed'],
86
+ },
87
+ },
88
+ },
73
89
  // Holds only the indicator, so the focus ring has one unambiguous target.
74
90
  // `display: contents` adds no box of its own — the indicator keeps whatever
75
91
  // layout relationship it already had with the wrapper.
@@ -399,7 +415,11 @@ export function CheckboxInput({
399
415
  !isDisabled && indicatorScope,
400
416
  )}>
401
417
  <div
402
- {...stylex.props(styles.checkboxWrapper, wrapperSizeStyles[size])}
418
+ {...stylex.props(
419
+ styles.checkboxWrapper,
420
+ wrapperSizeStyles[size],
421
+ !isDisabled && styles.indicatorPressOverlay,
422
+ )}
403
423
  {...focusProps}>
404
424
  <input
405
425
  {...rest}
@@ -2,6 +2,7 @@
2
2
 
3
3
  import {describe, it, expect, vi} from 'vitest';
4
4
  import {render, screen, fireEvent} from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
5
6
  import {ClickableCard} from './ClickableCard';
6
7
 
7
8
  describe('ClickableCard', () => {
@@ -47,6 +48,47 @@ describe('ClickableCard', () => {
47
48
  expect(handleClick).toHaveBeenCalledTimes(1);
48
49
  });
49
50
 
51
+ it('calls onClick once when the accessible control itself is clicked', () => {
52
+ const handleClick = vi.fn();
53
+ render(
54
+ <ClickableCard label="Test card" onClick={handleClick}>
55
+ <span>Content</span>
56
+ </ClickableCard>,
57
+ );
58
+ // Pointer activation aimed at the element carrying the role — what
59
+ // speech input, assistive technology, and automation dispatch.
60
+ fireEvent.click(screen.getByRole('button', {name: 'Test card'}));
61
+ expect(handleClick).toHaveBeenCalledTimes(1);
62
+ });
63
+
64
+ it('passes the card surface as currentTarget for surface clicks', () => {
65
+ const seen: EventTarget[] = [];
66
+ render(
67
+ <ClickableCard
68
+ label="Test card"
69
+ onClick={e => seen.push(e.currentTarget)}>
70
+ <span>Content</span>
71
+ </ClickableCard>,
72
+ );
73
+ fireEvent.click(screen.getByText('Content'));
74
+ expect(seen).toHaveLength(1);
75
+ expect(seen[0]).toBe(screen.getByText('Content').parentElement);
76
+ });
77
+
78
+ it('calls onClick exactly once when the surface of an href card is clicked without preventDefault', () => {
79
+ const handleClick = vi.fn();
80
+ render(
81
+ <ClickableCard label="Nav card" href="/settings" onClick={handleClick}>
82
+ <span>Content</span>
83
+ </ClickableCard>,
84
+ );
85
+ // The container hook proxies the surface click to the link with
86
+ // `link.click()`; that synthetic click bubbles back through the card and
87
+ // must not run the consumer callback a second time.
88
+ fireEvent.click(screen.getByText('Content'));
89
+ expect(handleClick).toHaveBeenCalledTimes(1);
90
+ });
91
+
50
92
  it('does NOT call onClick when a nested button is clicked', () => {
51
93
  const handleCardClick = vi.fn();
52
94
  const handleButtonClick = vi.fn();
@@ -86,25 +128,80 @@ describe('ClickableCard', () => {
86
128
  expect(link).toHaveAttribute('target', '_blank');
87
129
  });
88
130
 
89
- it('disabled button is disabled', () => {
131
+ it('disabled button is disabled and the surface runs nothing', () => {
90
132
  const handleClick = vi.fn();
91
133
  render(
92
134
  <ClickableCard label="Disabled" onClick={handleClick} isDisabled>
93
- Content
135
+ <span>Content</span>
94
136
  </ClickableCard>,
95
137
  );
96
138
  const button = screen.getByRole('button', {name: 'Disabled'});
97
139
  expect(button).toBeDisabled();
140
+ fireEvent.click(screen.getByText('Content'));
141
+ fireEvent.click(button);
142
+ expect(handleClick).not.toHaveBeenCalled();
98
143
  });
99
144
 
100
- it('disabled link has aria-disabled', () => {
145
+ it('disabled link has aria-disabled, leaves the tab order, and the surface runs nothing', () => {
146
+ const handleClick = vi.fn();
101
147
  render(
102
- <ClickableCard label="Disabled link" href="/settings" isDisabled>
103
- Content
148
+ <ClickableCard
149
+ label="Disabled link"
150
+ href="/settings"
151
+ onClick={handleClick}
152
+ isDisabled>
153
+ <span>Content</span>
104
154
  </ClickableCard>,
105
155
  );
106
156
  const link = screen.getByRole('link', {name: 'Disabled link'});
107
157
  expect(link).toHaveAttribute('aria-disabled', 'true');
158
+ expect(link).toHaveAttribute('tabindex', '-1');
159
+ fireEvent.click(screen.getByText('Content'));
160
+ expect(handleClick).not.toHaveBeenCalled();
161
+ });
162
+
163
+ it('runs onClick once from the keyboard via the hidden button', async () => {
164
+ const user = userEvent.setup();
165
+ const handleClick = vi.fn();
166
+ render(
167
+ <ClickableCard label="Test card" onClick={handleClick}>
168
+ <span>Content</span>
169
+ </ClickableCard>,
170
+ );
171
+ await user.tab();
172
+ expect(screen.getByRole('button', {name: 'Test card'})).toHaveFocus();
173
+ await user.keyboard('{Enter}');
174
+ await user.keyboard(' ');
175
+ expect(handleClick).toHaveBeenCalledTimes(2);
176
+ });
177
+
178
+ it('does NOT call onClick when a nested input is clicked', () => {
179
+ const handleCardClick = vi.fn();
180
+ render(
181
+ <ClickableCard label="Test card" onClick={handleCardClick}>
182
+ <input aria-label="Quantity" readOnly value="1" />
183
+ </ClickableCard>,
184
+ );
185
+ fireEvent.click(screen.getByRole('textbox', {name: 'Quantity'}));
186
+ expect(handleCardClick).not.toHaveBeenCalled();
187
+ });
188
+
189
+ it('does NOT call onClick when text inside the card is selected', () => {
190
+ const handleClick = vi.fn();
191
+ render(
192
+ <ClickableCard label="Test card" onClick={handleClick}>
193
+ <span>Selectable body text</span>
194
+ </ClickableCard>,
195
+ );
196
+ const text = screen.getByText('Selectable body text');
197
+ const range = document.createRange();
198
+ range.selectNodeContents(text);
199
+ const selection = document.getSelection()!;
200
+ selection.removeAllRanges();
201
+ selection.addRange(range);
202
+ fireEvent.click(text);
203
+ expect(handleClick).not.toHaveBeenCalled();
204
+ selection.removeAllRanges();
108
205
  });
109
206
 
110
207
  describe('elevation', () => {
@@ -82,6 +82,17 @@ export const docs = {
82
82
  {name: 'CollapsibleGroup'},
83
83
  ],
84
84
  usage: {
85
+ accessibility: [
86
+ {
87
+ name: 'Trigger label',
88
+ category: 'Color contrast',
89
+ criterion: '1.4.3 Contrast (Minimum)',
90
+ requirement: '4.5:1',
91
+ states: ['Rest', 'Pointer down'],
92
+ description:
93
+ 'The trigger text must have at least 4.5:1 contrast with the surface behind it. For Pointer down, measure against the pressed overlay the trigger row paints while it is pressed.',
94
+ },
95
+ ],
85
96
  description: 'Collapsible hides and reveals content behind a trigger button. Use it in settings panels, FAQ pages, or detail views to keep the page scannable while letting users drill into sections they care about. Wrap multiple collapsibles in CollapsibleGroup for accordion behavior. For custom collapsible components, use the `useCollapsible` hook directly (`astryx hook useCollapsible`).',
86
97
  bestPractices: [
87
98
  { guidance: true, description: 'Use hasDividers on CollapsibleGroup for FAQ-style lists: built-in row hairlines with themed border tokens, no hand-rolled borders.' },
@@ -12,6 +12,7 @@
12
12
  import {describe, it, expect, vi} from 'vitest';
13
13
  import {render, screen, within} from '@testing-library/react';
14
14
  import userEvent from '@testing-library/user-event';
15
+ import {hasPressedArm} from '../__tests__/pressState';
15
16
  import {Collapsible} from './Collapsible';
16
17
  import {CollapsibleGroup} from './CollapsibleGroup';
17
18
 
@@ -615,3 +616,23 @@ describe('Collapsible', () => {
615
616
  });
616
617
  });
617
618
  });
619
+
620
+ describe('pressed state', () => {
621
+ it('paints the pressed overlay on the trigger row while it is pressed', () => {
622
+ render(
623
+ <Collapsible trigger="Details">
624
+ <p>Body</p>
625
+ </Collapsible>,
626
+ );
627
+ expect(hasPressedArm(screen.getByRole('button'))).toBe(true);
628
+ });
629
+
630
+ it('does not press a disabled trigger', () => {
631
+ render(
632
+ <Collapsible trigger="Details" isDisabled>
633
+ <p>Body</p>
634
+ </Collapsible>,
635
+ );
636
+ expect(hasPressedArm(screen.getByRole('button'))).toBe(false);
637
+ });
638
+ });
@@ -48,6 +48,7 @@ import {mergeProps} from '../utils';
48
48
  import type {BaseProps} from '../BaseProps';
49
49
  import {themeProps} from '../utils/themeProps';
50
50
  import {focusOutlineProps} from '../utils/focusOutline.stylex';
51
+ import {interactionOverlayStyles} from '../utils/interactionOverlay.stylex';
51
52
 
52
53
  const styles = stylex.create({
53
54
  root: {
@@ -411,6 +412,10 @@ export function Collapsible({
411
412
  focusOutlineProps.focusVisible(
412
413
  styles.trigger,
413
414
  density != null && triggerDensity[density],
415
+ // The system's pressed overlay on the disclosure row. The trigger
416
+ // has no hover surface of its own, so this is the one background
417
+ // it paints, and only while it is pressed.
418
+ !isDisabled && interactionOverlayStyles.pressedBackgroundColor,
414
419
  isDisabled && styles.triggerDisabled,
415
420
  ),
416
421
  )}>
@@ -73,9 +73,14 @@ const styles = stylex.create({
73
73
  backgroundColor: 'transparent',
74
74
  color: colorVars['--color-text-primary'],
75
75
  fontFamily: typographyVars['--font-family-body'],
76
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
77
+ // control sits under 16px, and only iOS WebKit implements
78
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
76
79
  fontSize: {
77
80
  default: typeScaleVars['--text-body-size'],
78
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
81
+ '@media (pointer: coarse)': {
82
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
83
+ },
79
84
  },
80
85
  lineHeight: typeScaleVars['--text-body-leading'],
81
86
  padding: 0,
@@ -65,9 +65,14 @@ const styles = stylex.create({
65
65
  paddingBlock: spacingVars['--spacing-2'],
66
66
  paddingInline: spacingVars['--spacing-3'],
67
67
  fontFamily: typographyVars['--font-family-body'],
68
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
69
+ // control sits under 16px, and only iOS WebKit implements
70
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
68
71
  fontSize: {
69
72
  default: typeScaleVars['--text-label-size'],
70
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
73
+ '@media (pointer: coarse)': {
74
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-label-size']})`,
75
+ },
71
76
  },
72
77
  lineHeight: typeScaleVars['--text-label-leading'],
73
78
  color: colorVars['--color-text-primary'],
@@ -99,9 +99,14 @@ const styles = stylex.create({
99
99
  borderStyle: 'none',
100
100
  padding: 0,
101
101
  fontFamily: typographyVars['--font-family-body'],
102
+ // The 16px floor is iOS-only: iOS Safari zooms the page when a focused
103
+ // control sits under 16px, and only iOS WebKit implements
104
+ // -webkit-touch-callout to key the coarse-pointer floor to it.
102
105
  fontSize: {
103
106
  default: typeScaleVars['--text-body-size'],
104
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
107
+ '@media (pointer: coarse)': {
108
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
109
+ },
105
110
  },
106
111
  lineHeight: typeScaleVars['--text-body-leading'],
107
112
  color: colorVars['--color-text-primary'],
@@ -100,10 +100,14 @@ const styles = stylex.create({
100
100
  borderStyle: 'none',
101
101
  padding: 0,
102
102
  fontFamily: typographyVars['--font-family-body'],
103
- // Below 16px iOS zooms the page when the field takes focus.
103
+ // Below 16px iOS zooms the page when the field takes focus. Only iOS
104
+ // WebKit implements -webkit-touch-callout, so the coarse-pointer floor is
105
+ // keyed to iOS alone.
104
106
  fontSize: {
105
107
  default: typeScaleVars['--text-body-size'],
106
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
108
+ '@media (pointer: coarse)': {
109
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
110
+ },
107
111
  },
108
112
  lineHeight: typeScaleVars['--text-body-leading'],
109
113
  color: colorVars['--color-text-primary'],
@@ -112,11 +116,20 @@ const styles = stylex.create({
112
116
  // A date control's intrinsic height comes from its inner edit fields, not
113
117
  // from `line-height`, so it renders ~2px taller than a text input and its
114
118
  // value sits off the shared baseline inside the same flex row. One line
115
- // box is exactly what the text field occupies.
116
- height: stylex.firstThatWorks(
117
- '1lh',
118
- `calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
119
- ),
119
+ // box is exactly what the text field occupies. The calc fallback mirrors
120
+ // the iOS-only floor above; browsers with `1lh` track it for free.
121
+ height: {
122
+ default: stylex.firstThatWorks(
123
+ '1lh',
124
+ `calc(${typeScaleVars['--text-body-size']} * ${typeScaleVars['--text-body-leading']})`,
125
+ ),
126
+ '@media (pointer: coarse)': {
127
+ '@supports (-webkit-touch-callout: none)': stylex.firstThatWorks(
128
+ '1lh',
129
+ `calc(max(1rem, ${typeScaleVars['--text-body-size']}) * ${typeScaleVars['--text-body-leading']})`,
130
+ ),
131
+ },
132
+ },
120
133
  // iOS gives date controls their own button-like chrome, with inner
121
134
  // spacing and a centred value that no reset of ours can reach.
122
135
  WebkitAppearance: 'none',
@@ -187,9 +200,12 @@ const styles = stylex.create({
187
200
  // covers — one line of that leading fills its height exactly, which puts
188
201
  // the glyphs on the input's own baseline.
189
202
  display: 'block',
203
+ // ...with the input's own iOS floor, so the two baselines stay shared.
190
204
  fontSize: {
191
205
  default: typeScaleVars['--text-body-size'],
192
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
206
+ '@media (pointer: coarse)': {
207
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
208
+ },
193
209
  },
194
210
  lineHeight: typeScaleVars['--text-body-leading'],
195
211
  // A tap has to reach the control underneath — that is what raises the
@@ -194,10 +194,14 @@ const styles = stylex.create({
194
194
  padding: 0,
195
195
  fontFamily: typographyVars['--font-family-body'],
196
196
  // Below 16px iOS zooms the page on focus. The field is focusable even
197
- // though it is not typable, so it needs the same floor DateInput has.
197
+ // though it is not typable, so it needs the same floor DateInput has —
198
+ // keyed to iOS alone, since only iOS WebKit implements
199
+ // -webkit-touch-callout.
198
200
  fontSize: {
199
201
  default: typeScaleVars['--text-body-size'],
200
- '@media (pointer: coarse)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
202
+ '@media (pointer: coarse)': {
203
+ '@supports (-webkit-touch-callout: none)': `max(1rem, ${typeScaleVars['--text-body-size']})`,
204
+ },
201
205
  },
202
206
  lineHeight: typeScaleVars['--text-body-leading'],
203
207
  color: colorVars['--color-text-primary'],