@astryxdesign/core 0.6.2 → 0.6.3-canary.0899f21

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 (549) 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/Button/Button.d.ts.map +1 -1
  9. package/dist/Button/Button.js +13 -28
  10. package/dist/Chat/ChatComposer.d.ts.map +1 -1
  11. package/dist/Chat/ChatComposer.js +10 -5
  12. package/dist/Chat/ChatComposerDrawer.d.ts +19 -5
  13. package/dist/Chat/ChatComposerDrawer.d.ts.map +1 -1
  14. package/dist/Chat/ChatComposerDrawer.js +48 -23
  15. package/dist/Chat/ChatComposerInput.d.ts +11 -3
  16. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  17. package/dist/Chat/ChatComposerInput.js +63 -16
  18. package/dist/Chat/ChatDictationButton.d.ts +3 -2
  19. package/dist/Chat/ChatDictationButton.d.ts.map +1 -1
  20. package/dist/Chat/ChatDictationButton.js +6 -4
  21. package/dist/Chat/ChatLayout.d.ts +19 -0
  22. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  23. package/dist/Chat/ChatLayout.js +19 -0
  24. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  25. package/dist/Chat/ChatLayoutScrollButton.js +44 -34
  26. package/dist/Chat/ChatMessageBubble.d.ts +2 -0
  27. package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
  28. package/dist/Chat/ChatMessageBubble.js +5 -3
  29. package/dist/Chat/ChatMessageList.d.ts +2 -1
  30. package/dist/Chat/ChatMessageList.d.ts.map +1 -1
  31. package/dist/Chat/ChatMessageList.js +4 -3
  32. package/dist/Chat/ChatMessageMetadata.d.ts +5 -4
  33. package/dist/Chat/ChatMessageMetadata.d.ts.map +1 -1
  34. package/dist/Chat/ChatMessageMetadata.js +12 -9
  35. package/dist/Chat/ChatSendButton.js +1 -1
  36. package/dist/Chat/index.d.ts +1 -1
  37. package/dist/Chat/index.d.ts.map +1 -1
  38. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  39. package/dist/CheckboxInput/CheckboxInput.js +12 -2
  40. package/dist/Citation/Citation.d.ts +1 -1
  41. package/dist/Citation/Citation.d.ts.map +1 -1
  42. package/dist/Citation/Citation.js +3 -2
  43. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  44. package/dist/CodeBlock/highlightRanges.js +14 -3
  45. package/dist/Collapsible/Collapsible.d.ts.map +1 -1
  46. package/dist/Collapsible/Collapsible.js +6 -1
  47. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  48. package/dist/CommandPalette/CommandPaletteInput.js +1 -1
  49. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  50. package/dist/ComplexSelector/ComplexSelector.js +1 -1
  51. package/dist/DateInput/DateInput.d.ts.map +1 -1
  52. package/dist/DateInput/DateInput.js +4 -4
  53. package/dist/DateInput/NativeDateField.d.ts.map +1 -1
  54. package/dist/DateInput/NativeDateField.js +16 -16
  55. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  56. package/dist/DateInput/TouchDateField.js +2 -2
  57. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  58. package/dist/DateRangeInput/DateRangeInput.js +4 -4
  59. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  60. package/dist/DateTimeInput/DateTimeInput.js +8 -8
  61. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  62. package/dist/DateTimeInput/TouchDateTimeField.js +4 -4
  63. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts +9 -3
  64. package/dist/DateTimeInput/nativePickerSegmentStyles.d.ts.map +1 -1
  65. package/dist/DateTimeInput/nativePickerSegmentStyles.js +3 -3
  66. package/dist/Dialog/DialogHeader.d.ts +8 -1
  67. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  68. package/dist/Dialog/DialogHeader.js +13 -4
  69. package/dist/Field/InputClearButton.d.ts +4 -2
  70. package/dist/Field/InputClearButton.d.ts.map +1 -1
  71. package/dist/Field/InputClearButton.js +4 -2
  72. package/dist/Field/PanelSearchInput.d.ts.map +1 -1
  73. package/dist/Field/PanelSearchInput.js +1 -1
  74. package/dist/FileInput/FileInput.d.ts.map +1 -1
  75. package/dist/FileInput/FileInput.js +7 -7
  76. package/dist/Icon/Icon.d.ts +6 -31
  77. package/dist/Icon/Icon.d.ts.map +1 -1
  78. package/dist/Icon/Icon.js +8 -71
  79. package/dist/Icon/IconDefaultSizeContext.d.ts +4 -0
  80. package/dist/Icon/IconDefaultSizeContext.d.ts.map +1 -0
  81. package/dist/Icon/IconDefaultSizeContext.js +18 -0
  82. package/dist/Icon/IconSize.stylex.d.ts +65 -0
  83. package/dist/Icon/IconSize.stylex.d.ts.map +1 -0
  84. package/dist/Icon/IconSize.stylex.js +75 -0
  85. package/dist/Item/Item.d.ts +1 -1
  86. package/dist/Item/Item.d.ts.map +1 -1
  87. package/dist/Item/Item.js +4 -3
  88. package/dist/Kbd/Kbd.d.ts +5 -3
  89. package/dist/Kbd/Kbd.d.ts.map +1 -1
  90. package/dist/Kbd/Kbd.js +36 -42
  91. package/dist/Link/Link.d.ts.map +1 -1
  92. package/dist/Link/Link.js +6 -2
  93. package/dist/Link/useLinkComponent.d.ts +5 -15
  94. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  95. package/dist/Link/useLinkComponent.js +34 -51
  96. package/dist/List/List.d.ts +19 -1
  97. package/dist/List/List.d.ts.map +1 -1
  98. package/dist/List/List.js +9 -2
  99. package/dist/List/ListContext.d.ts +1 -0
  100. package/dist/List/ListContext.d.ts.map +1 -1
  101. package/dist/List/ListContext.js +2 -1
  102. package/dist/List/ListItem.d.ts +1 -1
  103. package/dist/List/ListItem.d.ts.map +1 -1
  104. package/dist/List/ListItem.js +9 -2
  105. package/dist/Markdown/Markdown.d.ts +8 -2
  106. package/dist/Markdown/Markdown.d.ts.map +1 -1
  107. package/dist/Markdown/Markdown.fr23.bench.d.ts +2 -0
  108. package/dist/Markdown/Markdown.fr23.bench.d.ts.map +1 -0
  109. package/dist/Markdown/Markdown.fr23.bench.js +150 -0
  110. package/dist/Markdown/Markdown.fr23.sampling.d.ts +2 -0
  111. package/dist/Markdown/Markdown.fr23.sampling.d.ts.map +1 -0
  112. package/dist/Markdown/Markdown.fr23.sampling.js +43 -0
  113. package/dist/Markdown/Markdown.js +262 -109
  114. package/dist/Markdown/ast.d.ts +162 -0
  115. package/dist/Markdown/ast.d.ts.map +1 -0
  116. package/dist/Markdown/ast.js +74 -0
  117. package/dist/Markdown/index.d.ts +5 -3
  118. package/dist/Markdown/index.d.ts.map +1 -1
  119. package/dist/Markdown/index.js +3 -2
  120. package/dist/Markdown/parser/index.d.ts +10 -0
  121. package/dist/Markdown/parser/index.d.ts.map +1 -0
  122. package/dist/Markdown/parser/index.js +10 -0
  123. package/dist/Markdown/parser.d.ts +126 -58
  124. package/dist/Markdown/parser.d.ts.map +1 -1
  125. package/dist/Markdown/parser.js +716 -167
  126. package/dist/Markdown/plugins/frontmatter.d.ts +32 -0
  127. package/dist/Markdown/plugins/frontmatter.d.ts.map +1 -0
  128. package/dist/Markdown/plugins/frontmatter.js +132 -0
  129. package/dist/Markdown/plugins/index.d.ts +17 -0
  130. package/dist/Markdown/plugins/index.d.ts.map +1 -0
  131. package/dist/Markdown/plugins/index.js +14 -0
  132. package/dist/Markdown/plugins/protocol.d.ts +191 -0
  133. package/dist/Markdown/plugins/protocol.d.ts.map +1 -0
  134. package/dist/Markdown/plugins/protocol.js +957 -0
  135. package/dist/Markdown/plugins/semanticFence.d.ts +36 -0
  136. package/dist/Markdown/plugins/semanticFence.d.ts.map +1 -0
  137. package/dist/Markdown/plugins/semanticFence.js +213 -0
  138. package/dist/Markdown/plugins/softBreaks.d.ts +3 -0
  139. package/dist/Markdown/plugins/softBreaks.d.ts.map +1 -0
  140. package/dist/Markdown/plugins/softBreaks.js +172 -0
  141. package/dist/Markdown/plugins/sourceDecoration.d.ts +76 -0
  142. package/dist/Markdown/plugins/sourceDecoration.d.ts.map +1 -0
  143. package/dist/Markdown/plugins/sourceDecoration.js +393 -0
  144. package/dist/Markdown/plugins/textTransform.d.ts +30 -0
  145. package/dist/Markdown/plugins/textTransform.d.ts.map +1 -0
  146. package/dist/Markdown/plugins/textTransform.js +374 -0
  147. package/dist/Markdown/remark.d.ts +196 -0
  148. package/dist/Markdown/remark.d.ts.map +1 -0
  149. package/dist/Markdown/remark.js +1062 -0
  150. package/dist/Markdown/url.d.ts +6 -0
  151. package/dist/Markdown/url.d.ts.map +1 -0
  152. package/dist/Markdown/url.js +31 -0
  153. package/dist/Markdown/utils.d.ts +4 -2
  154. package/dist/Markdown/utils.d.ts.map +1 -1
  155. package/dist/Markdown/utils.js +2 -1
  156. package/dist/MetadataList/MetadataList.js +3 -3
  157. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  158. package/dist/NumberInput/NumberInput.js +4 -4
  159. package/dist/Outline/index.d.ts +1 -0
  160. package/dist/Outline/index.d.ts.map +1 -1
  161. package/dist/Outline/parseOutlineFromMarkdown.d.ts +7 -1
  162. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  163. package/dist/Outline/parseOutlineFromMarkdown.js +14 -8
  164. package/dist/Outline/useOutlineFromMarkdown.d.ts +3 -1
  165. package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
  166. package/dist/Outline/useOutlineFromMarkdown.js +8 -3
  167. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  168. package/dist/PowerSearch/PowerSearchEditPopover.js +46 -30
  169. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  170. package/dist/PowerSearch/PowerSearchValueEditor.js +5 -0
  171. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  172. package/dist/ProgressBar/ProgressBar.js +36 -31
  173. package/dist/RadioList/RadioListItem.d.ts.map +1 -1
  174. package/dist/RadioList/RadioListItem.js +13 -1
  175. package/dist/ScrollableArea/ScrollableArea.d.ts +1 -0
  176. package/dist/ScrollableArea/ScrollableArea.d.ts.map +1 -1
  177. package/dist/ScrollableArea/ScrollableArea.js +1 -0
  178. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  179. package/dist/SegmentedControl/SegmentedControlItem.js +5 -5
  180. package/dist/Selector/Selector.d.ts +9 -7
  181. package/dist/Selector/Selector.d.ts.map +1 -1
  182. package/dist/Selector/Selector.js +16 -7
  183. package/dist/SideNav/SideNav.d.ts +2 -1
  184. package/dist/SideNav/SideNav.d.ts.map +1 -1
  185. package/dist/SideNav/SideNav.js +9 -3
  186. package/dist/Slider/Slider.d.ts.map +1 -1
  187. package/dist/Slider/Slider.js +28 -12
  188. package/dist/Spinner/Spinner.d.ts.map +1 -1
  189. package/dist/Spinner/Spinner.js +23 -7
  190. package/dist/Switch/Switch.d.ts.map +1 -1
  191. package/dist/Switch/Switch.js +11 -0
  192. package/dist/TabList/Tab.d.ts +1 -1
  193. package/dist/TabList/Tab.d.ts.map +1 -1
  194. package/dist/TabList/Tab.js +20 -7
  195. package/dist/Table/Table.d.ts +1 -0
  196. package/dist/Table/Table.d.ts.map +1 -1
  197. package/dist/Table/Table.js +29 -13
  198. package/dist/TextArea/TextArea.d.ts.map +1 -1
  199. package/dist/TextArea/TextArea.js +1 -1
  200. package/dist/TextInput/TextInput.d.ts.map +1 -1
  201. package/dist/TextInput/TextInput.js +2 -2
  202. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  203. package/dist/TimeInput/TimeInput.js +4 -4
  204. package/dist/Timer/Timer.d.ts +49 -0
  205. package/dist/Timer/Timer.d.ts.map +1 -0
  206. package/dist/Timer/Timer.js +158 -0
  207. package/dist/Timer/index.d.ts +9 -0
  208. package/dist/Timer/index.d.ts.map +1 -0
  209. package/dist/Timer/index.js +10 -0
  210. package/dist/ToggleButton/ToggleButton.d.ts +9 -6
  211. package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
  212. package/dist/ToggleButton/ToggleButton.js +15 -27
  213. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  214. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  215. package/dist/TopNav/TopNavMenu.js +3 -2
  216. package/dist/TreeList/TreeList.d.ts.map +1 -1
  217. package/dist/TreeList/TreeList.js +3 -0
  218. package/dist/TreeList/TreeListItem.d.ts +6 -1
  219. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  220. package/dist/TreeList/TreeListItem.js +12 -2
  221. package/dist/TreeList/TreeListTypes.d.ts +8 -1
  222. package/dist/TreeList/TreeListTypes.d.ts.map +1 -1
  223. package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
  224. package/dist/Typeahead/BaseTypeahead.js +16 -7
  225. package/dist/astryx.css +27 -11
  226. package/dist/hooks/scrollKeyboardDelegation.d.ts +3 -0
  227. package/dist/hooks/scrollKeyboardDelegation.d.ts.map +1 -0
  228. package/dist/hooks/scrollKeyboardDelegation.js +146 -0
  229. package/dist/hooks/useClickableContainer.d.ts +10 -1
  230. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  231. package/dist/hooks/useClickableContainer.js +15 -4
  232. package/dist/hooks/useScrollableArea.d.ts +6 -2
  233. package/dist/hooks/useScrollableArea.d.ts.map +1 -1
  234. package/dist/hooks/useScrollableArea.js +17 -5
  235. package/dist/index.d.ts +1 -0
  236. package/dist/index.d.ts.map +1 -1
  237. package/dist/index.js +3 -0
  238. package/dist/theme/declarationBoundary.d.ts +17 -0
  239. package/dist/theme/declarationBoundary.d.ts.map +1 -0
  240. package/dist/theme/declarationBoundary.js +343 -0
  241. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  242. package/dist/theme/derivedVarRegistry.js +4 -0
  243. package/dist/theme/generateThemeRules.d.ts +13 -7
  244. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  245. package/dist/theme/generateThemeRules.js +140 -34
  246. package/dist/utils/interactionOverlay.stylex.d.ts +8 -0
  247. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  248. package/dist/utils/interactionOverlay.stylex.js +9 -0
  249. package/dist/utils/safeUrl.d.ts +11 -0
  250. package/dist/utils/safeUrl.d.ts.map +1 -0
  251. package/dist/utils/safeUrl.js +78 -0
  252. package/locales/af-ZA.json +530 -1
  253. package/locales/ar-SA.json +490 -1
  254. package/locales/ca-ES.json +514 -1
  255. package/locales/cs-CZ.json +498 -1
  256. package/locales/da-DK.json +522 -1
  257. package/locales/de-DE.json +522 -1
  258. package/locales/el-GR.json +498 -1
  259. package/locales/es-ES.json +510 -1
  260. package/locales/fi-FI.json +498 -1
  261. package/locales/fr-FR.json +534 -1
  262. package/locales/he-IL.json +498 -1
  263. package/locales/hu-HU.json +498 -1
  264. package/locales/it-IT.json +510 -1
  265. package/locales/ja-JP.json +490 -1
  266. package/locales/ko-KR.json +498 -1
  267. package/locales/nl-NL.json +534 -1
  268. package/locales/no-NO.json +510 -1
  269. package/locales/pl-PL.json +506 -1
  270. package/locales/pt-BR.json +506 -1
  271. package/locales/pt-PT.json +506 -1
  272. package/locales/ro-RO.json +506 -1
  273. package/locales/ru-RU.json +498 -1
  274. package/locales/sr-SP.json +498 -1
  275. package/locales/sv-SE.json +502 -1
  276. package/locales/tr-TR.json +498 -1
  277. package/locales/uk-UA.json +498 -1
  278. package/locales/vi-VN.json +502 -1
  279. package/locales/zh-CN.json +490 -1
  280. package/locales/zh-TW.json +490 -1
  281. package/package.json +24 -4
  282. package/src/BottomSheet/BottomSheet.doc.mjs +8 -1
  283. package/src/BottomSheet/BottomSheet.spec.md +46 -20
  284. package/src/BottomSheet/BottomSheet.test.tsx +6 -3
  285. package/src/BottomSheet/BottomSheet.tsx +3 -1
  286. package/src/BottomSheet/BottomSheetKeyboard.test.tsx +195 -0
  287. package/src/BottomSheet/BottomSheetPanel.test.tsx +11 -1
  288. package/src/BottomSheet/BottomSheetPanel.tsx +49 -16
  289. package/src/BottomSheet/__tests__/BottomSheetKeyboard.a11y.browser.spec.ts +344 -0
  290. package/src/Button/Button.doc.mjs +5 -5
  291. package/src/Button/Button.tsx +15 -13
  292. package/src/Button/__tests__/Button.a11y.chromium.spec.ts +17 -1
  293. package/src/Button/__tests__/Button.a11y.known-failures.ts +0 -29
  294. package/src/Button/__tests__/Button.a11y.renders.tsx +9 -2
  295. package/src/Button/__tests__/Button.a11y.states.ts +9 -0
  296. package/src/Chat/ChatComposer.doc.mjs +14 -0
  297. package/src/Chat/ChatComposer.spec.md +230 -0
  298. package/src/Chat/ChatComposer.test.tsx +57 -0
  299. package/src/Chat/ChatComposer.tsx +21 -9
  300. package/src/Chat/ChatComposerDrawer.doc.mjs +63 -5
  301. package/src/Chat/ChatComposerDrawer.spec.md +267 -0
  302. package/src/Chat/ChatComposerDrawer.test.tsx +81 -12
  303. package/src/Chat/ChatComposerDrawer.tsx +38 -9
  304. package/src/Chat/ChatComposerInput.doc.mjs +114 -26
  305. package/src/Chat/ChatComposerInput.spec.md +185 -0
  306. package/src/Chat/ChatComposerInput.test.tsx +113 -3
  307. package/src/Chat/ChatComposerInput.tsx +78 -13
  308. package/src/Chat/ChatComposerTokenElement.spec.md +152 -0
  309. package/src/Chat/ChatComposerTokenElement.test.tsx +85 -0
  310. package/src/Chat/ChatDictationButton.doc.mjs +3 -3
  311. package/src/Chat/ChatDictationButton.spec.md +160 -0
  312. package/src/Chat/ChatDictationButton.test.tsx +92 -27
  313. package/src/Chat/ChatDictationButton.tsx +8 -6
  314. package/src/Chat/ChatLayout.doc.mjs +12 -4
  315. package/src/Chat/ChatLayout.spec.md +271 -0
  316. package/src/Chat/ChatLayout.test.tsx +28 -0
  317. package/src/Chat/ChatLayout.tsx +19 -0
  318. package/src/Chat/ChatLayoutScrollButton.spec.md +338 -0
  319. package/src/Chat/ChatLayoutScrollButton.test.tsx +31 -0
  320. package/src/Chat/ChatLayoutScrollButton.tsx +31 -14
  321. package/src/Chat/ChatMessageBubble.doc.mjs +28 -15
  322. package/src/Chat/ChatMessageBubble.spec.md +263 -0
  323. package/src/Chat/ChatMessageBubble.test.tsx +93 -0
  324. package/src/Chat/ChatMessageBubble.tsx +8 -3
  325. package/src/Chat/ChatMessageList.spec.md +222 -0
  326. package/src/Chat/ChatMessageList.test.tsx +64 -2
  327. package/src/Chat/ChatMessageList.tsx +4 -3
  328. package/src/Chat/ChatMessageMetadata.doc.mjs +57 -3
  329. package/src/Chat/ChatMessageMetadata.spec.md +220 -0
  330. package/src/Chat/ChatMessageMetadata.test.tsx +34 -0
  331. package/src/Chat/ChatMessageMetadata.tsx +13 -13
  332. package/src/Chat/ChatSendButton.test.tsx +1 -4
  333. package/src/Chat/ChatSendButton.tsx +1 -1
  334. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +514 -0
  335. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +740 -0
  336. package/src/Chat/__tests__/ChatMessageBubble.a11y.chromium.spec.ts +457 -0
  337. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.test.ts +91 -0
  338. package/src/Chat/__tests__/ChatMessageBubble.auditProvenance.ts +91 -0
  339. package/src/Chat/__tests__/ChatMessageList.a11y.chromium.spec.ts +456 -0
  340. package/src/Chat/__tests__/ChatMessageMetadata.a11y.chromium.spec.ts +413 -0
  341. package/src/Chat/index.ts +1 -0
  342. package/src/CheckboxInput/CheckboxInput.doc.mjs +11 -0
  343. package/src/CheckboxInput/CheckboxInput.test.tsx +34 -0
  344. package/src/CheckboxInput/CheckboxInput.tsx +21 -1
  345. package/src/Citation/Citation.doc.mjs +2 -2
  346. package/src/Citation/Citation.test.tsx +17 -0
  347. package/src/Citation/Citation.tsx +4 -2
  348. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  349. package/src/ClickableCard/ClickableCard.test.tsx +102 -5
  350. package/src/CodeBlock/highlightRanges.test.ts +143 -1
  351. package/src/CodeBlock/highlightRanges.ts +15 -5
  352. package/src/Collapsible/Collapsible.doc.mjs +11 -0
  353. package/src/Collapsible/Collapsible.test.tsx +21 -0
  354. package/src/Collapsible/Collapsible.tsx +5 -0
  355. package/src/CommandPalette/CommandPaletteInput.tsx +6 -1
  356. package/src/ComplexSelector/ComplexSelector.tsx +6 -1
  357. package/src/DateInput/DateInput.tsx +6 -1
  358. package/src/DateInput/NativeDateField.tsx +24 -8
  359. package/src/DateInput/TouchDateField.tsx +6 -2
  360. package/src/DateRangeInput/DateRangeInput.tsx +6 -1
  361. package/src/DateTimeInput/DateTimeInput.tsx +6 -1
  362. package/src/DateTimeInput/TouchDateTimeField.tsx +6 -1
  363. package/src/DateTimeInput/nativePickerSegmentStyles.ts +24 -8
  364. package/src/Dialog/Dialog.doc.mjs +2 -0
  365. package/src/Dialog/DialogHeader.doc.mjs +22 -0
  366. package/src/Dialog/DialogHeader.test.tsx +167 -3
  367. package/src/Dialog/DialogHeader.tsx +37 -6
  368. package/src/Dialog/modules/DialogHeader.spec.md +101 -42
  369. package/src/DropdownMenu/DropdownMenu.test.tsx +5 -2
  370. package/src/Field/InputClearButton.test.tsx +17 -3
  371. package/src/Field/InputClearButton.tsx +5 -2
  372. package/src/Field/PanelSearchInput.tsx +5 -2
  373. package/src/FieldStatus/__tests__/StatusMessage.a11y.states.ts +1 -1
  374. package/src/FileInput/FileInput.tsx +11 -2
  375. package/src/Icon/Icon.doc.mjs +7 -5
  376. package/src/Icon/Icon.spec.md +81 -40
  377. package/src/Icon/Icon.test.tsx +6 -3
  378. package/src/Icon/Icon.tsx +20 -68
  379. package/src/Icon/IconDefaultSizeContext.ts +23 -0
  380. package/src/Icon/IconSize.stylex.ts +70 -0
  381. package/src/IconButton/IconButton.doc.mjs +3 -3
  382. package/src/IconButton/IconButton.test.tsx +40 -0
  383. package/src/Item/Item.doc.mjs +6 -2
  384. package/src/Item/Item.tsx +9 -3
  385. package/src/Kbd/Kbd.doc.mjs +3 -3
  386. package/src/Kbd/Kbd.test.tsx +57 -1
  387. package/src/Kbd/Kbd.tsx +57 -37
  388. package/src/Layout/LayoutFooter.doc.mjs +17 -0
  389. package/src/Link/Link.doc.mjs +17 -4
  390. package/src/Link/Link.test.tsx +24 -0
  391. package/src/Link/Link.tsx +5 -0
  392. package/src/Link/LinkProvider.doc.mjs +2 -2
  393. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +261 -0
  394. package/src/Link/useLinkComponent.test.tsx +333 -0
  395. package/src/Link/useLinkComponent.ts +29 -57
  396. package/src/List/List.doc.mjs +6 -0
  397. package/src/List/List.spec.md +200 -0
  398. package/src/List/List.test.tsx +117 -1
  399. package/src/List/List.tsx +22 -2
  400. package/src/List/ListContext.tsx +3 -1
  401. package/src/List/ListItem.doc.mjs +2 -2
  402. package/src/List/ListItem.source-build.test.mjs +41 -0
  403. package/src/List/ListItem.tsx +26 -2
  404. package/src/Markdown/Markdown.doc.mjs +300 -2
  405. package/src/Markdown/Markdown.fr23.bench.ts +174 -0
  406. package/src/Markdown/Markdown.fr23.perf.test.ts +249 -0
  407. package/src/Markdown/Markdown.fr23.sampling.ts +49 -0
  408. package/src/Markdown/Markdown.helpers.perf.test.ts +489 -0
  409. package/src/Markdown/Markdown.public.test.ts +130 -3
  410. package/src/Markdown/Markdown.renderBoundary.test.tsx +72 -0
  411. package/src/Markdown/Markdown.spec.md +203 -45
  412. package/src/Markdown/Markdown.test.tsx +63 -0
  413. package/src/Markdown/Markdown.tsx +414 -135
  414. package/src/Markdown/ast.test.ts +307 -0
  415. package/src/Markdown/ast.ts +337 -0
  416. package/src/Markdown/incremental.test.ts +12 -0
  417. package/src/Markdown/index.ts +37 -1
  418. package/src/Markdown/modules/remark.spec.md +269 -0
  419. package/src/Markdown/modules/softBreaks.spec.md +137 -0
  420. package/src/Markdown/parser/index.ts +64 -0
  421. package/src/Markdown/parser.public.test.ts +103 -0
  422. package/src/Markdown/parser.test.ts +20 -0
  423. package/src/Markdown/parser.ts +1180 -235
  424. package/src/Markdown/plugins/frontmatter.test.tsx +125 -0
  425. package/src/Markdown/plugins/frontmatter.ts +198 -0
  426. package/src/Markdown/plugins/index.ts +54 -0
  427. package/src/Markdown/plugins/protocol.test.tsx +1022 -0
  428. package/src/Markdown/plugins/protocol.ts +1618 -0
  429. package/src/Markdown/plugins/semanticFence.test.tsx +546 -0
  430. package/src/Markdown/plugins/semanticFence.ts +424 -0
  431. package/src/Markdown/plugins/softBreaks.test.tsx +172 -0
  432. package/src/Markdown/plugins/softBreaks.ts +169 -0
  433. package/src/Markdown/plugins/sourceDecoration.test.tsx +799 -0
  434. package/src/Markdown/plugins/sourceDecoration.ts +539 -0
  435. package/src/Markdown/plugins/textTransform.test.ts +467 -0
  436. package/src/Markdown/plugins/textTransform.ts +554 -0
  437. package/src/Markdown/remark.test.tsx +1877 -0
  438. package/src/Markdown/remark.ts +1574 -0
  439. package/src/Markdown/url.ts +36 -0
  440. package/src/Markdown/utils.ts +13 -1
  441. package/src/MetadataList/MetadataList.test.tsx +4 -2
  442. package/src/MetadataList/MetadataList.tsx +3 -3
  443. package/src/MultiSelector/MultiSelector.test.tsx +1 -35
  444. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +128 -0
  445. package/src/NumberInput/NumberInput.tsx +6 -1
  446. package/src/Outline/Outline.doc.mjs +7 -5
  447. package/src/Outline/Outline.spec.md +1 -1
  448. package/src/Outline/index.ts +1 -0
  449. package/src/Outline/modules/parseOutlineFromMarkdown.spec.md +142 -0
  450. package/src/Outline/parseOutlineFromMarkdown.ts +40 -10
  451. package/src/Outline/useOutlineFromMarkdown.ts +15 -3
  452. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  453. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +150 -1
  454. package/src/PowerSearch/PowerSearchEditPopover.tsx +51 -28
  455. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +51 -0
  456. package/src/PowerSearch/PowerSearchValueEditor.tsx +4 -0
  457. package/src/ProgressBar/ProgressBar.test.tsx +29 -11
  458. package/src/ProgressBar/ProgressBar.tsx +59 -72
  459. package/src/RadioList/RadioList.doc.mjs +11 -0
  460. package/src/RadioList/RadioList.test.tsx +32 -0
  461. package/src/RadioList/RadioListItem.tsx +25 -1
  462. package/src/ScrollableArea/ScrollableArea.tsx +1 -0
  463. package/src/ScrollableArea/modules/useScrollableArea.spec.md +50 -22
  464. package/src/SegmentedControl/SegmentedControl.doc.mjs +2 -2
  465. package/src/SegmentedControl/SegmentedControl.test.tsx +31 -0
  466. package/src/SegmentedControl/SegmentedControlItem.tsx +6 -9
  467. package/src/Selector/Selector.doc.mjs +2 -2
  468. package/src/Selector/Selector.spec.md +51 -49
  469. package/src/Selector/Selector.test.tsx +61 -55
  470. package/src/Selector/Selector.tsx +34 -15
  471. package/src/Selector/__tests__/Listbox.a11y.chromium.spec.ts +130 -0
  472. package/src/Selector/__tests__/Listbox.a11y.dom.ts +38 -0
  473. package/src/Selector/__tests__/Listbox.a11y.inventory.test.ts +63 -0
  474. package/src/Selector/__tests__/Listbox.a11y.known-failures.ts +37 -0
  475. package/src/Selector/__tests__/Listbox.a11y.states.ts +232 -0
  476. package/src/Selector/__tests__/Selector.a11y.chromium.spec.ts +45 -0
  477. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +121 -0
  478. package/src/SideNav/SideNav.doc.mjs +1 -1
  479. package/src/SideNav/SideNav.test.tsx +10 -0
  480. package/src/SideNav/SideNav.tsx +14 -2
  481. package/src/Slider/Slider.doc.mjs +17 -3
  482. package/src/Slider/Slider.test.tsx +221 -0
  483. package/src/Slider/Slider.tsx +40 -9
  484. package/src/Spinner/Spinner.test.tsx +4 -0
  485. package/src/Spinner/Spinner.tsx +31 -18
  486. package/src/Switch/Switch.doc.mjs +11 -0
  487. package/src/Switch/Switch.test.tsx +16 -0
  488. package/src/Switch/Switch.tsx +28 -0
  489. package/src/TabList/Tab.tsx +35 -7
  490. package/src/TabList/TabList.doc.mjs +11 -0
  491. package/src/TabList/TabList.spec.md +204 -0
  492. package/src/TabList/TabList.test.tsx +66 -0
  493. package/src/TabList/__tests__/Tabs.a11y.known-failures.ts +1 -34
  494. package/src/Table/Table.doc.mjs +1 -1
  495. package/src/Table/Table.spec.md +54 -40
  496. package/src/Table/Table.test.tsx +83 -10
  497. package/src/Table/Table.tsx +30 -22
  498. package/src/Table/__tests__/TableScroll.a11y.chromium.spec.ts +137 -0
  499. package/src/TextArea/TextArea.tsx +6 -1
  500. package/src/TextInput/TextInput.test.tsx +20 -1
  501. package/src/TextInput/TextInput.tsx +6 -1
  502. package/src/TimeInput/TimeInput.tsx +6 -1
  503. package/src/Timer/Timer.doc.mjs +183 -0
  504. package/src/Timer/Timer.spec.md +215 -0
  505. package/src/Timer/Timer.test.tsx +302 -0
  506. package/src/Timer/Timer.tsx +258 -0
  507. package/src/Timer/index.ts +11 -0
  508. package/src/ToggleButton/ToggleButton.doc.mjs +3 -3
  509. package/src/ToggleButton/ToggleButton.test.tsx +299 -4
  510. package/src/ToggleButton/ToggleButton.tsx +26 -33
  511. package/src/ToggleButton/ToggleButtonGroup.doc.mjs +3 -3
  512. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +209 -0
  513. package/src/Token/Token.doc.mjs +2 -2
  514. package/src/Tokenizer/Tokenizer.spec.md +142 -75
  515. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  516. package/src/TopNav/TopNavMenu.test.tsx +30 -0
  517. package/src/TopNav/TopNavMenu.tsx +7 -2
  518. package/src/TreeList/TreeList.doc.mjs +3 -3
  519. package/src/TreeList/TreeList.spec.md +123 -75
  520. package/src/TreeList/TreeList.test.tsx +29 -0
  521. package/src/TreeList/TreeList.tsx +3 -0
  522. package/src/TreeList/TreeListItem.tsx +15 -1
  523. package/src/TreeList/TreeListTypes.ts +11 -1
  524. package/src/Typeahead/BaseTypeahead.spec.md +4 -3
  525. package/src/Typeahead/BaseTypeahead.tsx +21 -7
  526. package/src/Typeahead/Typeahead.test.tsx +53 -0
  527. package/src/__tests__/PressedState.a11y.chromium.spec.ts +813 -0
  528. package/src/__tests__/pressState.ts +93 -0
  529. package/src/hooks/scrollKeyboardDelegation.test.ts +155 -0
  530. package/src/hooks/scrollKeyboardDelegation.ts +233 -0
  531. package/src/hooks/useClickableContainer.test.tsx +236 -0
  532. package/src/hooks/useClickableContainer.ts +20 -4
  533. package/src/hooks/useScrollableArea.doc.mjs +15 -3
  534. package/src/hooks/useScrollableArea.test.tsx +59 -1
  535. package/src/hooks/useScrollableArea.ts +34 -10
  536. package/src/index.ts +3 -0
  537. package/src/inputFontFloor.test.ts +106 -0
  538. package/src/tailwind-theme.css +0 -6
  539. package/src/theme/Theme.test.tsx +32 -0
  540. package/src/theme/declarationBoundary.test.ts +126 -0
  541. package/src/theme/declarationBoundary.ts +347 -0
  542. package/src/theme/derivedVarRegistry.test.ts +7 -0
  543. package/src/theme/derivedVarRegistry.ts +1 -0
  544. package/src/theme/generateThemeRules.test.ts +526 -1
  545. package/src/theme/generateThemeRules.ts +228 -50
  546. package/src/theme/tokenValueCompat.test.ts +4 -1
  547. package/src/utils/interactionOverlay.stylex.ts +20 -0
  548. package/src/utils/safeUrl.test.ts +88 -0
  549. package/src/utils/safeUrl.ts +85 -0
@@ -3,7 +3,8 @@
3
3
  /**
4
4
  * @file ToggleButton.test.tsx
5
5
  * @input Uses vitest, @testing-library/react, ToggleButton, ToggleButtonGroup
6
- * @output Unit tests for ToggleButton and ToggleButtonGroup
6
+ * @output Unit tests for ToggleButton callback/Action order, pending state,
7
+ * group-owned selection, and ToggleButtonGroup
7
8
  *
8
9
  * SYNC: When ToggleButton.tsx or ToggleButtonGroup.tsx changes, update tests
9
10
  */
@@ -388,8 +389,94 @@ describe('ToggleButton', () => {
388
389
  expect(pressedChangeAction).toHaveBeenCalledWith(true);
389
390
  });
390
391
 
392
+ it('runs the synchronous callback before the Action with the same next state', async () => {
393
+ const calls: string[] = [];
394
+ const pressedChangeAction = vi.fn(() => {
395
+ calls.push('action');
396
+ });
397
+ const onPressedChange = vi.fn(() => {
398
+ expect(pressedChangeAction).not.toHaveBeenCalled();
399
+ calls.push('change');
400
+ });
401
+ render(
402
+ <ToggleButton
403
+ label="Favorite"
404
+ isPressed={false}
405
+ onPressedChange={onPressedChange}
406
+ pressedChangeAction={pressedChangeAction}
407
+ />,
408
+ );
409
+
410
+ await userEvent.setup().click(screen.getByRole('button'));
411
+
412
+ expect(calls).toEqual(['change', 'action']);
413
+ expect(onPressedChange).toHaveBeenCalledExactlyOnceWith(
414
+ true,
415
+ expect.anything(),
416
+ );
417
+ expect(pressedChangeAction).toHaveBeenCalledExactlyOnceWith(true);
418
+ });
419
+
420
+ it('keeps callback-only controlled toggles synchronous without pending feedback', async () => {
421
+ function LegacyToggle() {
422
+ const [isPressed, setIsPressed] = useState(false);
423
+ return (
424
+ <ToggleButton
425
+ label="Bold"
426
+ isPressed={isPressed}
427
+ onPressedChange={setIsPressed}
428
+ />
429
+ );
430
+ }
431
+ render(<LegacyToggle />);
432
+ const button = screen.getByRole('button');
433
+
434
+ fireEvent.click(button);
435
+ expect(button).toHaveAttribute('aria-pressed', 'true');
436
+ expect(button).not.toHaveAttribute('aria-busy', 'true');
437
+ fireEvent.click(button);
438
+ expect(button).toHaveAttribute('aria-pressed', 'false');
439
+ expect(button).not.toHaveAttribute('aria-busy', 'true');
440
+ await act(async () => {});
441
+ });
442
+
443
+ it('runs an Action without onPressedChange and settles to the controlled value', async () => {
444
+ let resolveAction: (() => void) | undefined;
445
+ const pressedChangeAction = vi.fn(
446
+ async () =>
447
+ new Promise<void>(resolve => {
448
+ resolveAction = resolve;
449
+ }),
450
+ );
451
+ const {rerender} = render(
452
+ <ToggleButton
453
+ label="Favorite"
454
+ isPressed={false}
455
+ pressedChangeAction={pressedChangeAction}
456
+ />,
457
+ );
458
+ const button = screen.getByRole('button');
459
+
460
+ await userEvent.setup().click(button);
461
+ expect(pressedChangeAction).toHaveBeenCalledExactlyOnceWith(true);
462
+ expect(button).toHaveAttribute('aria-pressed', 'true');
463
+ expect(button).toHaveAttribute('aria-busy', 'true');
464
+ await act(async () => resolveAction?.());
465
+ expect(button).toHaveAttribute('aria-pressed', 'false');
466
+ expect(button).not.toHaveAttribute('aria-busy', 'true');
467
+
468
+ rerender(
469
+ <ToggleButton
470
+ label="Favorite"
471
+ isPressed
472
+ pressedChangeAction={pressedChangeAction}
473
+ />,
474
+ );
475
+ expect(button).toHaveAttribute('aria-pressed', 'true');
476
+ expect(pressedChangeAction).toHaveBeenCalledTimes(1);
477
+ });
478
+
391
479
  it('skips pressedChangeAction when onPressedChange calls preventDefault', async () => {
392
- const user = userEvent.setup();
393
480
  const pressedChangeAction = vi.fn();
394
481
  const onPressedChange = vi.fn(
395
482
  (_next: boolean, event: MouseEvent<HTMLButtonElement>) => {
@@ -406,10 +493,17 @@ describe('ToggleButton', () => {
406
493
  />,
407
494
  );
408
495
 
409
- await user.click(screen.getByRole('button', {name: 'Favorite'}));
496
+ const button = screen.getByRole('button', {name: 'Favorite'});
497
+ fireEvent.click(button);
410
498
 
411
- expect(onPressedChange).toHaveBeenCalledWith(true, expect.anything());
499
+ expect(onPressedChange).toHaveBeenCalledExactlyOnceWith(
500
+ true,
501
+ expect.anything(),
502
+ );
412
503
  expect(pressedChangeAction).not.toHaveBeenCalled();
504
+ expect(button).toHaveAttribute('aria-pressed', 'false');
505
+ expect(button).not.toHaveAttribute('aria-busy', 'true');
506
+ await act(async () => {});
413
507
  });
414
508
  });
415
509
 
@@ -570,6 +664,207 @@ describe('ToggleButtonGroup (multiple)', () => {
570
664
  });
571
665
  });
572
666
 
667
+ // =============================================================================
668
+ // Group ownership — member Actions must not compete with controlled selection
669
+ // =============================================================================
670
+
671
+ describe.each(['single', 'multiple'] as const)(
672
+ 'ToggleButtonGroup (%s) ownership',
673
+ type => {
674
+ it('keeps selection group-owned even when a member has standalone handlers', async () => {
675
+ const onChange = vi.fn();
676
+ const onPressedChange = vi.fn();
677
+ const pressedChangeAction = vi.fn();
678
+ const groupProps =
679
+ type === 'single'
680
+ ? {type, value: 'list', onChange}
681
+ : {type, value: ['list'], onChange};
682
+ const children = (
683
+ <>
684
+ <ToggleButton value="list" label="List" isPressed={false} />
685
+ <ToggleButton
686
+ value="grid"
687
+ label="Grid"
688
+ isPressed
689
+ onPressedChange={onPressedChange}
690
+ pressedChangeAction={pressedChangeAction}
691
+ />
692
+ </>
693
+ );
694
+ const {rerender} = render(
695
+ <ToggleButtonGroup {...groupProps} label="View">
696
+ {children}
697
+ </ToggleButtonGroup>,
698
+ );
699
+ const grid = screen.getByRole('button', {name: 'Grid'});
700
+ expect(grid).toHaveAttribute('aria-pressed', 'false');
701
+ expect(screen.getByRole('button', {name: 'List'})).toHaveAttribute(
702
+ 'aria-pressed',
703
+ 'true',
704
+ );
705
+
706
+ await userEvent.setup().click(grid);
707
+ expect(onChange).toHaveBeenCalledExactlyOnceWith(
708
+ type === 'single' ? 'grid' : ['list', 'grid'],
709
+ );
710
+ expect(onPressedChange).not.toHaveBeenCalled();
711
+ expect(pressedChangeAction).not.toHaveBeenCalled();
712
+ expect(grid).toHaveAttribute('aria-pressed', 'false');
713
+ expect(grid).not.toHaveAttribute('aria-busy', 'true');
714
+
715
+ const acceptedProps =
716
+ type === 'single'
717
+ ? {type, value: 'grid', onChange}
718
+ : {type, value: ['list', 'grid'], onChange};
719
+ rerender(
720
+ <ToggleButtonGroup {...acceptedProps} label="View">
721
+ {children}
722
+ </ToggleButtonGroup>,
723
+ );
724
+ expect(grid).toHaveAttribute('aria-pressed', 'true');
725
+ await userEvent.setup().click(grid);
726
+ expect(onChange).toHaveBeenLastCalledWith(
727
+ type === 'single' ? null : ['list'],
728
+ );
729
+ expect(onPressedChange).not.toHaveBeenCalled();
730
+ expect(pressedChangeAction).not.toHaveBeenCalled();
731
+ });
732
+ },
733
+ );
734
+
735
+ // =============================================================================
736
+ // Disabled state — family:buttons FR3 (disabled means non-operable)
737
+ // =============================================================================
738
+
739
+ /**
740
+ * Two ways a ToggleButton becomes unavailable, and the rule that binds them:
741
+ * a group disables everything it contains, and a member can disable itself
742
+ * while the group stays enabled. Neither source may cancel the other out.
743
+ *
744
+ * The tooltip cases are here at the attribute and callback level only. A
745
+ * tooltip'd disabled toggle carries `aria-disabled` instead of the native
746
+ * `disabled` attribute, so whether a REAL mouse press is refused is an engine
747
+ * fact that jsdom's synthetic click cannot settle — that half lives in
748
+ * ./__tests__/ToggleButton.a11y.chromium.spec.ts.
749
+ */
750
+ describe('disabled state', () => {
751
+ it('keeps a member disabled when the group disables nothing', async () => {
752
+ const user = userEvent.setup();
753
+ const handleChange = vi.fn();
754
+ render(
755
+ <ToggleButtonGroup value={null} onChange={handleChange} label="View mode">
756
+ <ToggleButton value="list" label="List" isDisabled />
757
+ <ToggleButton value="grid" label="Grid" />
758
+ </ToggleButtonGroup>,
759
+ );
760
+
761
+ expect(screen.getByRole('button', {name: 'List'})).toBeDisabled();
762
+
763
+ await user.click(screen.getByRole('button', {name: 'List'}));
764
+ expect(handleChange).not.toHaveBeenCalled();
765
+ });
766
+
767
+ it('leaves the rest of an enabled group selectable', async () => {
768
+ const user = userEvent.setup();
769
+ const handleChange = vi.fn();
770
+ render(
771
+ <ToggleButtonGroup value={null} onChange={handleChange} label="View mode">
772
+ <ToggleButton value="list" label="List" isDisabled />
773
+ <ToggleButton value="grid" label="Grid" />
774
+ </ToggleButtonGroup>,
775
+ );
776
+
777
+ expect(screen.getByRole('button', {name: 'Grid'})).toBeEnabled();
778
+
779
+ await user.click(screen.getByRole('button', {name: 'Grid'}));
780
+ expect(handleChange).toHaveBeenCalledWith('grid');
781
+ });
782
+
783
+ it('still disables a member that says nothing when the group is disabled', async () => {
784
+ const user = userEvent.setup();
785
+ const handleChange = vi.fn();
786
+ render(
787
+ <ToggleButtonGroup
788
+ value={null}
789
+ onChange={handleChange}
790
+ label="View mode"
791
+ isDisabled>
792
+ <ToggleButton value="list" label="List" />
793
+ </ToggleButtonGroup>,
794
+ );
795
+
796
+ expect(screen.getByRole('button', {name: 'List'})).toBeDisabled();
797
+
798
+ await user.click(screen.getByRole('button', {name: 'List'}));
799
+ expect(handleChange).not.toHaveBeenCalled();
800
+ });
801
+
802
+ it('keeps a tooltip-bearing disabled member focusable but inert', async () => {
803
+ const user = userEvent.setup();
804
+ const handleChange = vi.fn();
805
+ render(
806
+ <ToggleButtonGroup value={null} onChange={handleChange} label="View mode">
807
+ <ToggleButton
808
+ value="list"
809
+ label="List"
810
+ tooltip="List view is unavailable for this dataset"
811
+ isDisabled
812
+ />
813
+ </ToggleButtonGroup>,
814
+ );
815
+
816
+ const member = screen.getByRole('button', {name: 'List'});
817
+ // The tooltip is the disabled reason, so the control stays reachable to
818
+ // read it: aria-disabled rather than the native attribute, which would
819
+ // take it out of the tab order along with its own explanation.
820
+ expect(member).toHaveAttribute('aria-disabled', 'true');
821
+ expect(member).not.toBeDisabled();
822
+
823
+ await user.click(member);
824
+ expect(handleChange).not.toHaveBeenCalled();
825
+ });
826
+
827
+ it('does not toggle a standalone disabled toggle that carries a tooltip', async () => {
828
+ const user = userEvent.setup();
829
+ const handleChange = vi.fn();
830
+ render(
831
+ <ToggleButton
832
+ label="Bold"
833
+ tooltip="Formatting is locked for this document"
834
+ isPressed={false}
835
+ onPressedChange={handleChange}
836
+ isDisabled
837
+ />,
838
+ );
839
+
840
+ const toggle = screen.getByRole('button', {name: 'Bold'});
841
+ expect(toggle).toHaveAttribute('aria-disabled', 'true');
842
+
843
+ await user.click(toggle);
844
+ expect(handleChange).not.toHaveBeenCalled();
845
+ expect(toggle).toHaveAttribute('aria-pressed', 'false');
846
+ });
847
+
848
+ it('still toggles an enabled toggle that carries the same tooltip', async () => {
849
+ const user = userEvent.setup();
850
+ const handleChange = vi.fn();
851
+ render(
852
+ <ToggleButton
853
+ label="Bold"
854
+ tooltip="Bold the selected text"
855
+ isPressed={false}
856
+ onPressedChange={handleChange}
857
+ />,
858
+ );
859
+
860
+ const toggle = screen.getByRole('button', {name: 'Bold'});
861
+ expect(toggle).not.toHaveAttribute('aria-disabled');
862
+
863
+ await user.click(toggle);
864
+ expect(handleChange).toHaveBeenCalledWith(true, expect.anything());
865
+ });
866
+ });
867
+
573
868
  // jsdom cannot emulate forced-colors rendering, so these assert that the
574
869
  // compiled output includes the forced-colors rules; visual behavior needs
575
870
  // manual verification under Windows High Contrast.
@@ -9,7 +9,7 @@
9
9
  * @position Thin wrapper over Button; adds controlled toggle pattern
10
10
  *
11
11
  * ToggleButton wraps Button with `isPressed` and adds:
12
- * - `onPressedChange` controlled toggle callback
12
+ * - `onPressedChange` from internal `onClick`; `pressedChangeAction` via Button
13
13
  * - `pressedIcon` for outline-to-filled icon swap
14
14
  * - Font weight shift on press with width reservation to prevent layout shift
15
15
  * - Group integration via ToggleButtonGroupContext
@@ -116,10 +116,12 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
116
116
  ) => void;
117
117
 
118
118
  /**
119
- * Action handler for API- or navigation-backed toggles, run inside a
120
- * transition by Button. The button shows a loading spinner while the action
121
- * is pending — whether it returns a promise or synchronously triggers a
122
- * suspending update (e.g. a router navigation that suspends on data).
119
+ * Action handler for API- or navigation-backed toggles, run through Button's
120
+ * clickAction transition after onPressedChange unless that callback prevents
121
+ * default. It also runs without a callback. Omit it for callback-only toggles
122
+ * that need no Action transition. The button shows a loading spinner while
123
+ * the action is pending, including synchronous actions that suspend.
124
+ * Ignored when a value identifies this button as a ToggleButtonGroup member.
123
125
  *
124
126
  * @example
125
127
  * ```
@@ -154,7 +156,8 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
154
156
 
155
157
  /**
156
158
  * Whether the button is disabled.
157
- * When used inside ToggleButtonGroup, the group's isDisabled overrides this.
159
+ * When used inside ToggleButtonGroup, a disabled group disables this button
160
+ * too, but an enabled group does not re-enable a button that disables itself.
158
161
  * @default false
159
162
  */
160
163
  isDisabled?: boolean;
@@ -263,54 +266,44 @@ export function ToggleButton({
263
266
  ? group.selectedValues.has(value)
264
267
  : (isPressedProp ?? false);
265
268
  const size = sizeProp ?? group?.size ?? 'md';
266
- const isDisabled = group?.isDisabled ?? isDisabledProp;
269
+ // Either source disabling this button is enough. `??` could not express that:
270
+ // the group always supplies a boolean (its own prop defaults to false), so the
271
+ // fallback never ran and an enabled group handed a member that had disabled
272
+ // itself its availability back. A group still disables everything it contains
273
+ // — that is the half `??` got right — but it cannot re-enable a member
274
+ // (family:buttons FR3).
275
+ const isDisabled = (group?.isDisabled ?? false) || isDisabledProp;
267
276
 
268
- // Track the pressed state optimistically so the button reflects the intended
269
- // state immediately while an async action is pending. The optimistic update
270
- // is applied inside Button's clickAction transition (below), satisfying
271
- // React's "useOptimistic must run in a transition" rule.
272
277
  const [optimisticPressed, setOptimisticPressed] =
273
278
  useOptimistic(committedPressed);
274
279
  const isPressed = optimisticPressed;
275
-
276
- // Next state derives from the *optimistic* value, not the committed one, so a
277
- // re-click while an action is pending reads the in-flight intent and toggles
278
- // (true -> false -> true) rather than recomputing from the stale committed
279
- // state. handleClick and clickAction share this so the callback and the
280
- // optimistic update always agree within a single click.
280
+ // Both paths use the same in-flight intent, so re-clicks reverse a pending
281
+ // toggle rather than deriving from the stale controlled value.
281
282
  const nextPressed = !isPressed;
282
-
283
283
  const resolvedIcon = isPressed && pressedIcon ? pressedIcon : icon;
284
284
 
285
- // Synchronous part of the toggle. Button calls onClick before clickAction and
286
- // skips clickAction when the event is defaultPrevented, so calling
287
- // preventDefault() inside onPressedChange opts out of pressedChangeAction.
288
285
  const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
289
286
  if (isDisabled) {
290
287
  return;
291
288
  }
292
289
  if (group && value != null) {
293
- // Group mode delegates selection to the group; no async-action path.
294
290
  group.toggle(value);
295
291
  event.preventDefault();
296
292
  return;
297
293
  }
294
+
298
295
  onPressedChangeProp?.(nextPressed, event);
299
296
  };
300
297
 
301
- // Async part of the toggle, run inside Button's transition (so the optimistic
302
- // update is valid and Button's isPending drives the pending spinner). Skipped
303
- // automatically by Button when handleClick called preventDefault, and never
304
- // wired in group mode (where there is no async action). Button is rendered
305
- // interruptible, so it stays clickable while pending and a re-click starts a
306
- // fresh transition that interrupts the previous one.
298
+ // Button runs onClick first and skips clickAction when the callback prevents
299
+ // default. Its transition owns both the optimistic update and pending UI.
307
300
  const clickAction =
308
- group && value != null
309
- ? undefined
310
- : async () => {
301
+ pressedChangeAction && !(group && value != null)
302
+ ? async () => {
311
303
  setOptimisticPressed(nextPressed);
312
- await pressedChangeAction?.(nextPressed);
313
- };
304
+ await pressedChangeAction(nextPressed);
305
+ }
306
+ : undefined;
314
307
 
315
308
  // isIconOnly prop is the source of truth for icon-only rendering.
316
309
  const labelContent =
@@ -11,7 +11,7 @@ export const docs = {
11
11
  {
12
12
  name: 'children',
13
13
  type: 'ReactNode',
14
- description: 'ToggleButton children.',
14
+ description: 'ToggleButton children. Give each member a value; the group owns its pressed state and ignores its isPressed, onPressedChange, and pressedChangeAction props. Member activation calls the group’s onChange without starting a member Action transition.',
15
15
  required: true,
16
16
  slotElements: [
17
17
  {
@@ -83,7 +83,7 @@ export const docsZh = {
83
83
  displayName: 'Toggle Button Group',
84
84
  description: '将切换按钮分组,支持单选或多选行为。通过 type 判别联合类型实现类型安全。',
85
85
  propDescriptions: {
86
- children: 'ToggleButton 子元素。',
86
+ children: 'ToggleButton 子元素;每个成员都需要 value。分组控制按下状态,并忽略成员的 isPressed、onPressedChange 和 pressedChangeAction。激活成员只调用分组的 onChange,不启动成员的 Action 过渡。',
87
87
  label: '分组的无障碍标签 (aria-label)。',
88
88
  type: '选择模式。single 允许单个激活,multiple 允许多个。',
89
89
  value: '当前选中的值。类型取决于选择模式。',
@@ -101,7 +101,7 @@ export const docsDense = {
101
101
  displayName: 'Toggle Button Group',
102
102
  description: 'groups toggle btns for exclusive/multi-select; discriminated union on type',
103
103
  propDescriptions: {
104
- children: 'ToggleButton children',
104
+ children: 'ToggleButton children; each member needs a value. Group owns pressed state; member isPressed/onPressedChange/pressedChangeAction are ignored. Selection uses group onChange, with no member Action transition.',
105
105
  label: 'a11y label (aria-label)',
106
106
  type: 'selection mode: single or multiple',
107
107
  value: 'selected value(s); type depends on mode',
@@ -0,0 +1,209 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ToggleButton.a11y.chromium.spec.ts
5
+ * @input Uses @playwright/test, the a11y-spec static Storybook server, and the
6
+ * checked-in ToggleButtonDisabledA11y stories
7
+ * @output Real-browser evidence for `docs/families/buttons.md` FR3 — disabled
8
+ * means non-operable — across the two ways a ToggleButton becomes disabled,
9
+ * plus the enabled control that catches a fix which disables too much.
10
+ * @position The half of FR3 that jsdom cannot honestly reach. A disabled toggle
11
+ * kept focusable by a tooltip carries `aria-disabled` instead of the native
12
+ * `disabled` attribute, so whether a real mouse press is refused is an engine
13
+ * fact — jsdom's synthetic click does not settle it
14
+ * (`docs/specs/AST-013/spec.md`: a browser claim is made in a browser).
15
+ *
16
+ * Each state drives a checked-in story rather than a page this file builds, so
17
+ * the reproduction path for any failure is a URL a person can open
18
+ * (`docs/specs/AST-009/spec.md` FR30). Each story counts its own activations,
19
+ * which is what `activationsOn` reads — a toggle that refuses a press leaves no
20
+ * trace on itself.
21
+ *
22
+ * Presses go through `page.mouse` at the control's own centre rather than
23
+ * `locator.click()`. Playwright's actionability wait would refuse to press a
24
+ * natively disabled control at all, which would prove nothing about what the
25
+ * engine does with a press that really lands.
26
+ *
27
+ * SYNC: Fixtures live in
28
+ * /apps/storybook/stories/ToggleButtonDisabledA11y.stories.tsx.
29
+ */
30
+
31
+ import {expect, test, type Page} from '@playwright/test';
32
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
33
+ import {
34
+ DEFAULT_STORYBOOK_DIR,
35
+ serveStorybook,
36
+ type StaticServer,
37
+ } from '@astryxdesign/a11y-spec/storybook';
38
+
39
+ const STORY_BASE = 'a11y-togglebutton-disabled-state';
40
+
41
+ let storybook: StaticServer;
42
+
43
+ test.beforeAll(async () => {
44
+ storybook = await serveStorybook(
45
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
46
+ );
47
+ });
48
+
49
+ test.afterAll(async () => {
50
+ await storybook?.close();
51
+ });
52
+
53
+ /** Load a story and wait for the control the assertions press. */
54
+ async function mountStory(page: Page, storyId: string): Promise<void> {
55
+ await page.goto(
56
+ `${storybook.origin}/iframe.html?id=${STORY_BASE}--${storyId}&viewMode=story`,
57
+ {waitUntil: 'load'},
58
+ );
59
+ await holdMotionStill(page);
60
+ await page
61
+ .locator('#storybook-root')
62
+ .getByRole('button')
63
+ .first()
64
+ .waitFor({state: 'attached'});
65
+ }
66
+
67
+ /**
68
+ * How many times this story's own handler has run.
69
+ *
70
+ * The story publishes the count as an attribute, so this reads a fact the story
71
+ * renders rather than instrumenting the page from the test — a person opening
72
+ * the story sees the same number the spec does.
73
+ */
74
+ async function activationsOn(page: Page): Promise<number> {
75
+ const raw = await page
76
+ .locator('[data-a11y-activations]')
77
+ .first()
78
+ .getAttribute('data-a11y-activations');
79
+ return Number(raw ?? '0');
80
+ }
81
+
82
+ /**
83
+ * A real mouse press at the control's own centre, landing whether or not
84
+ * Playwright would consider the control actionable. This is the press the
85
+ * expectations are about: a user aiming at a control that looks unavailable.
86
+ */
87
+ async function pressWithMouse(page: Page, name: string): Promise<void> {
88
+ const control = page
89
+ .locator('#storybook-root')
90
+ .getByRole('button', {name, includeHidden: true})
91
+ .first();
92
+ const box = await control.boundingBox();
93
+ if (box == null) {
94
+ throw new Error(
95
+ `"${name}" has no layout box, so no mouse press could land on it`,
96
+ );
97
+ }
98
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
99
+ }
100
+
101
+ /**
102
+ * Whether the engine reports this control as unavailable, by either of the two
103
+ * spellings FR3 permits: the native attribute, or focusable `aria-disabled`.
104
+ */
105
+ async function isUnavailable(page: Page, name: string): Promise<boolean> {
106
+ return page
107
+ .locator('#storybook-root')
108
+ .getByRole('button', {name, includeHidden: true})
109
+ .first()
110
+ .evaluate(
111
+ element =>
112
+ (element as HTMLButtonElement).disabled ||
113
+ element.getAttribute('aria-disabled') === 'true',
114
+ );
115
+ }
116
+
117
+ test('a member that disables itself stays disabled inside an enabled group', async ({
118
+ page,
119
+ }) => {
120
+ await mountStory(page, 'group-enabled-member-disabled');
121
+
122
+ expect(
123
+ await isUnavailable(page, 'List'),
124
+ 'the member sets isDisabled, so it must be exposed as unavailable even though the group disables nothing',
125
+ ).toBe(true);
126
+
127
+ await pressWithMouse(page, 'List');
128
+ expect(
129
+ await activationsOn(page),
130
+ 'a disabled member must not invoke the group selection callback (family:buttons FR3)',
131
+ ).toBe(0);
132
+ });
133
+
134
+ test('an enabled member of the same group still toggles', async ({page}) => {
135
+ await mountStory(page, 'group-enabled-member-disabled');
136
+
137
+ expect(await isUnavailable(page, 'Grid')).toBe(false);
138
+
139
+ await pressWithMouse(page, 'Grid');
140
+ expect(
141
+ await activationsOn(page),
142
+ 'the sibling says nothing about its own availability and the group disables nothing, so it must still select',
143
+ ).toBe(1);
144
+ });
145
+
146
+ test('a group disabled state still disables a member that says nothing', async ({
147
+ page,
148
+ }) => {
149
+ await mountStory(page, 'group-disabled-member-silent');
150
+
151
+ expect(await isUnavailable(page, 'List')).toBe(true);
152
+
153
+ await pressWithMouse(page, 'List');
154
+ expect(
155
+ await activationsOn(page),
156
+ 'a group disabled state overrides member availability (family:buttons FR3)',
157
+ ).toBe(0);
158
+ });
159
+
160
+ test('a disabled toggle kept focusable by its tooltip refuses a mouse press', async ({
161
+ page,
162
+ }) => {
163
+ await mountStory(page, 'disabled-with-tooltip');
164
+
165
+ const control = page.locator('#storybook-root').getByRole('button').first();
166
+
167
+ // The tooltip is why this control is focusable rather than natively disabled,
168
+ // so the press has to be made the way a user reaches it: hover first, let the
169
+ // tip come up, then press. Pressing cold would skip the hover state the
170
+ // tooltip's own pointer listeners run in.
171
+ await control.hover();
172
+ await expect(page.getByRole('tooltip')).toBeVisible();
173
+
174
+ expect(
175
+ await control.evaluate(element => element.getAttribute('aria-disabled')),
176
+ 'a tooltip keeps the reason reachable by making the control focusable rather than natively disabled',
177
+ ).toBe('true');
178
+ expect(await control.getAttribute('aria-pressed')).toBe('false');
179
+
180
+ await pressWithMouse(page, 'Bold');
181
+
182
+ expect(
183
+ await activationsOn(page),
184
+ 'focusable is not operable: a disabled toggle must not invoke onPressedChange from a mouse press (family:buttons FR3)',
185
+ ).toBe(0);
186
+ expect(
187
+ await control.getAttribute('aria-pressed'),
188
+ 'the pressed state must not flip',
189
+ ).toBe('false');
190
+ });
191
+
192
+ test('an enabled toggle with the same tooltip still toggles on a mouse press', async ({
193
+ page,
194
+ }) => {
195
+ await mountStory(page, 'enabled-with-tooltip');
196
+
197
+ const control = page.locator('#storybook-root').getByRole('button').first();
198
+ await control.hover();
199
+ await expect(page.getByRole('tooltip')).toBeVisible();
200
+ expect(await control.getAttribute('aria-pressed')).toBe('false');
201
+
202
+ await pressWithMouse(page, 'Bold');
203
+
204
+ expect(
205
+ await activationsOn(page),
206
+ 'the enabled path must be untouched by the disabled-state fix',
207
+ ).toBe(1);
208
+ expect(await control.getAttribute('aria-pressed')).toBe('true');
209
+ });