@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

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 (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -0,0 +1,599 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file CommandPalette.a11y.chromium.spec.ts
5
+ * @input Uses the component-owned Storybook fixture and exact-head build stamp
6
+ * @output Chromium screenshots and fail-closed semantic sensor receipts
7
+ * @position Browser evidence for the CommandPalette audit
8
+ */
9
+
10
+ import {createHash} from 'node:crypto';
11
+ import {execFileSync} from 'node:child_process';
12
+ import * as fs from 'node:fs';
13
+ import * as path from 'node:path';
14
+ import {expect, test, type Page} from '@playwright/test';
15
+ import {holdMotionStill} from '@astryxdesign/a11y-spec/chromium';
16
+ import {
17
+ DEFAULT_STORYBOOK_DIR,
18
+ serveStorybook,
19
+ type StaticServer,
20
+ } from '@astryxdesign/a11y-spec/storybook';
21
+
22
+ const OUTPUT = path.resolve('test-results/command-palette-audit-evidence');
23
+ const STORY_ID = 'core-commandpalette--audit-matrix';
24
+ const WIDE = {width: 1024, height: 900};
25
+ const NARROW = {width: 320, height: 900};
26
+
27
+ interface Case {
28
+ state: string;
29
+ mode: 'light' | 'dark';
30
+ direction: 'ltr' | 'rtl';
31
+ viewport: {width: number; height: number};
32
+ }
33
+
34
+ const CASES: Case[] = [
35
+ {state: 'wide-light-ltr', mode: 'light', direction: 'ltr', viewport: WIDE},
36
+ {state: 'wide-dark-ltr', mode: 'dark', direction: 'ltr', viewport: WIDE},
37
+ {state: 'wide-light-rtl', mode: 'light', direction: 'rtl', viewport: WIDE},
38
+ {state: 'wide-dark-rtl', mode: 'dark', direction: 'rtl', viewport: WIDE},
39
+ {
40
+ state: 'narrow-light',
41
+ mode: 'light',
42
+ direction: 'ltr',
43
+ viewport: NARROW,
44
+ },
45
+ {
46
+ state: 'narrow-dark',
47
+ mode: 'dark',
48
+ direction: 'ltr',
49
+ viewport: NARROW,
50
+ },
51
+ ];
52
+
53
+ const frames: Record<string, unknown>[] = [];
54
+ const stateVisualMatrix: Record<string, unknown>[] = [];
55
+ const contrastPairMatrix: Record<string, unknown>[] = [];
56
+ let storybook: StaticServer;
57
+ let checkoutSha: string;
58
+ let storybookSha: string;
59
+ let browserVersion = 'unknown';
60
+
61
+ test.beforeAll(async () => {
62
+ checkoutSha = execFileSync('git', ['rev-parse', 'HEAD'], {
63
+ encoding: 'utf8',
64
+ }).trim();
65
+ const expectedHead = process.env.ASTRYX_HEAD_SHA;
66
+ if (expectedHead && expectedHead !== checkoutSha) {
67
+ throw new Error('The audited checkout does not match the PR head');
68
+ }
69
+ storybook = await serveStorybook(
70
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
71
+ );
72
+ const stamp = await fetch(`${storybook.origin}/astryx-build-sha.txt`);
73
+ if (!stamp.ok) {
74
+ throw new Error(
75
+ `The Storybook build has no source stamp (${stamp.status})`,
76
+ );
77
+ }
78
+ storybookSha = (await stamp.text()).trim();
79
+ if (storybookSha !== checkoutSha) {
80
+ throw new Error(
81
+ 'The served Storybook build does not match the audited checkout',
82
+ );
83
+ }
84
+ fs.mkdirSync(OUTPUT, {recursive: true});
85
+ });
86
+
87
+ test.afterAll(async () => {
88
+ if (checkoutSha && storybookSha) {
89
+ fs.writeFileSync(
90
+ path.join(OUTPUT, 'manifest.json'),
91
+ `${JSON.stringify(
92
+ {
93
+ version: 1,
94
+ component: 'core/CommandPalette',
95
+ headSha: checkoutSha,
96
+ storybookSha,
97
+ browser: browserVersion,
98
+ frames,
99
+ stateVisualMatrix,
100
+ contrastPairMatrix,
101
+ },
102
+ null,
103
+ 2,
104
+ )}\n`,
105
+ );
106
+ }
107
+ await storybook?.close();
108
+ });
109
+
110
+ async function capture(page: Page, scenario: Case) {
111
+ const errors: string[] = [];
112
+ const onPageError = (error: Error) =>
113
+ errors.push(`pageerror: ${String(error)}`);
114
+ const onConsole = (message: {type(): string; text(): string}) => {
115
+ if (message.type() === 'error') {
116
+ errors.push(`console: ${message.text()}`);
117
+ }
118
+ };
119
+ page.on('pageerror', onPageError);
120
+ page.on('console', onConsole);
121
+
122
+ try {
123
+ await page.setViewportSize(scenario.viewport);
124
+ await page.emulateMedia({reducedMotion: 'reduce'});
125
+ browserVersion = page.context().browser()?.version() ?? 'unknown';
126
+ await page.goto(
127
+ `${storybook.origin}/iframe.html?id=${STORY_ID}&viewMode=story&globals=colorMode:${scenario.mode};astryxTheme:neutral;direction:${scenario.direction}`,
128
+ );
129
+
130
+ const canvas = page.locator('#storybook-root');
131
+ const subject = canvas.locator('[data-command-palette-audit-matrix]');
132
+ await subject.waitFor();
133
+ await expect(subject.locator('[role="region"]')).toHaveCount(3);
134
+ await expect(
135
+ subject
136
+ .getByRole('region', {name: 'Pending command palette'})
137
+ .locator('.astryx-spinner[role="status"]'),
138
+ ).toHaveCount(1);
139
+ await page.waitForTimeout(200);
140
+ await holdMotionStill(page);
141
+ await page.evaluate(async () => document.fonts.ready);
142
+ await page.waitForFunction(
143
+ expected =>
144
+ document.documentElement.getAttribute('data-theme') === expected,
145
+ scenario.mode,
146
+ );
147
+ await page.waitForFunction(
148
+ expected =>
149
+ document.querySelector('#storybook-root [dir]')?.getAttribute('dir') ===
150
+ expected,
151
+ scenario.direction,
152
+ );
153
+ await subject.scrollIntoViewIfNeeded();
154
+ await page.evaluate(async () => {
155
+ await new Promise<void>(resolve =>
156
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
157
+ );
158
+ });
159
+
160
+ const initialBox = await subject.boundingBox();
161
+ if (initialBox == null) {
162
+ throw new Error(`${scenario.state}: the audit matrix has no layout box`);
163
+ }
164
+ await page.evaluate(async () => {
165
+ await new Promise<void>(resolve =>
166
+ requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
167
+ );
168
+ });
169
+ const settledBox = await subject.boundingBox();
170
+ if (settledBox == null) {
171
+ throw new Error(
172
+ `${scenario.state}: the audit matrix lost its layout box before capture`,
173
+ );
174
+ }
175
+ const geometryDelta = Math.max(
176
+ Math.abs(settledBox.x - initialBox.x),
177
+ Math.abs(settledBox.y - initialBox.y),
178
+ Math.abs(settledBox.width - initialBox.width),
179
+ Math.abs(settledBox.height - initialBox.height),
180
+ );
181
+
182
+ const observed = await subject.evaluate((node, expectedDirection) => {
183
+ type Color = {r: number; g: number; b: number; a: number};
184
+ const elements = <T extends Element>(selector: string) => [
185
+ ...node.querySelectorAll<T>(selector),
186
+ ];
187
+ const geometry = (element: Element) => {
188
+ const rect = element.getBoundingClientRect();
189
+ return {
190
+ x: rect.x,
191
+ y: rect.y,
192
+ width: rect.width,
193
+ height: rect.height,
194
+ right: rect.right,
195
+ bottom: rect.bottom,
196
+ };
197
+ };
198
+ const parseColor = (value: string): Color => {
199
+ const channels = value.match(/[\d.]+/g)?.map(Number);
200
+ if (!channels || channels.length < 3) {
201
+ throw new Error(`Cannot parse computed color: ${value}`);
202
+ }
203
+ return {
204
+ r: channels[0] ?? 0,
205
+ g: channels[1] ?? 0,
206
+ b: channels[2] ?? 0,
207
+ a: channels[3] ?? 1,
208
+ };
209
+ };
210
+ const composite = (foreground: Color, background: Color): Color => {
211
+ const a = foreground.a + background.a * (1 - foreground.a);
212
+ if (a === 0) {
213
+ return {r: 0, g: 0, b: 0, a: 0};
214
+ }
215
+ return {
216
+ r:
217
+ (foreground.r * foreground.a +
218
+ background.r * background.a * (1 - foreground.a)) /
219
+ a,
220
+ g:
221
+ (foreground.g * foreground.a +
222
+ background.g * background.a * (1 - foreground.a)) /
223
+ a,
224
+ b:
225
+ (foreground.b * foreground.a +
226
+ background.b * background.a * (1 - foreground.a)) /
227
+ a,
228
+ a,
229
+ };
230
+ };
231
+ const renderedBackground = (element: Element): Color => {
232
+ const layers: Color[] = [];
233
+ let current: Element | null = element;
234
+ while (current) {
235
+ const layer = parseColor(getComputedStyle(current).backgroundColor);
236
+ if (layer.a > 0) {
237
+ layers.push(layer);
238
+ }
239
+ if (layer.a >= 0.999) {
240
+ break;
241
+ }
242
+ current = current.parentElement;
243
+ }
244
+ return layers
245
+ .reverse()
246
+ .reduce(
247
+ (background, foreground) => composite(foreground, background),
248
+ {r: 255, g: 255, b: 255, a: 1},
249
+ );
250
+ };
251
+ const formatColor = ({r, g, b, a}: Color) =>
252
+ `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${a.toFixed(3)})`;
253
+ const luminance = ({r, g, b}: Color) => {
254
+ const channel = (value: number) => {
255
+ const normalized = value / 255;
256
+ return normalized <= 0.04045
257
+ ? normalized / 12.92
258
+ : ((normalized + 0.055) / 1.055) ** 2.4;
259
+ };
260
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b);
261
+ };
262
+ const contrast = (left: Color, right: Color) => {
263
+ const leftLuminance = luminance(left);
264
+ const rightLuminance = luminance(right);
265
+ return (
266
+ (Math.max(leftLuminance, rightLuminance) + 0.05) /
267
+ (Math.min(leftLuminance, rightLuminance) + 0.05)
268
+ );
269
+ };
270
+ const isVisible = (element: Element) => {
271
+ const rect = element.getBoundingClientRect();
272
+ const style = getComputedStyle(element);
273
+ return (
274
+ rect.width > 0 &&
275
+ rect.height > 0 &&
276
+ style.display !== 'none' &&
277
+ style.visibility !== 'hidden' &&
278
+ style.opacity !== '0'
279
+ );
280
+ };
281
+ const measure = (
282
+ element: Element,
283
+ part: string,
284
+ type: 'text' | 'non-text',
285
+ colorValue?: string,
286
+ styleOverride?: CSSStyleDeclaration,
287
+ ) => {
288
+ const style = styleOverride ?? getComputedStyle(element);
289
+ const background = renderedBackground(element);
290
+ const foreground = parseColor(colorValue ?? style.color);
291
+ const renderedForeground = composite(foreground, background);
292
+ const ratio = contrast(renderedForeground, background);
293
+ const fontSize = Number.parseFloat(style.fontSize);
294
+ const fontWeight =
295
+ style.fontWeight === 'bold'
296
+ ? 700
297
+ : Number.parseFloat(style.fontWeight) || 400;
298
+ const threshold =
299
+ type === 'non-text' ||
300
+ fontSize >= 24 ||
301
+ (fontSize >= 18.66 && fontWeight >= 700)
302
+ ? 3
303
+ : 4.5;
304
+ return {
305
+ part,
306
+ type,
307
+ state: 'rest',
308
+ meaningful: true,
309
+ foreground: colorValue ?? style.color,
310
+ renderedForeground: formatColor(renderedForeground),
311
+ renderedBackdrop: formatColor(background),
312
+ fontSize,
313
+ fontWeight,
314
+ ratio: Number(ratio.toFixed(2)),
315
+ threshold,
316
+ exception: null,
317
+ passed: ratio + 1e-6 >= threshold,
318
+ };
319
+ };
320
+
321
+ const regions = elements<HTMLElement>('[role="region"]');
322
+ const inputs = elements<HTMLInputElement>('[role="combobox"]');
323
+ const lists = elements<HTMLElement>('[role="listbox"]');
324
+ const options = elements<HTMLElement>('[role="option"]');
325
+ const groups = elements<HTMLElement>('[role="group"]');
326
+ const empties = elements<HTMLElement>('.astryx-command-palette-empty');
327
+ const footers = elements<HTMLElement>('.astryx-command-palette-footer');
328
+ const icons = elements<HTMLElement>(
329
+ '.astryx-command-palette-input .astryx-icon',
330
+ );
331
+ const spinners = elements<HTMLElement>(
332
+ '.astryx-command-palette-input .astryx-spinner',
333
+ );
334
+ const spinnerArcs = elements<SVGCircleElement>(
335
+ '.astryx-command-palette-input .astryx-spinner circle:last-of-type',
336
+ );
337
+ const shortcuts = elements<HTMLElement>(
338
+ '.astryx-command-palette-footer .astryx-kbd',
339
+ );
340
+ const headings = elements<HTMLElement>('h2');
341
+ const groupHeadings = elements<HTMLElement>(
342
+ '.astryx-command-palette-group-heading',
343
+ );
344
+ const targetSelectors = [
345
+ '.astryx-command-palette-input',
346
+ '.astryx-command-palette-list',
347
+ '.astryx-command-palette-item',
348
+ '.astryx-command-palette-group',
349
+ '.astryx-command-palette-group-heading',
350
+ '.astryx-command-palette-empty',
351
+ '.astryx-command-palette-footer',
352
+ ];
353
+ const targets = targetSelectors.flatMap(selector => elements(selector));
354
+ const footerText = footers.flatMap(footer => [
355
+ ...footer.querySelectorAll<HTMLElement>(':scope > span'),
356
+ ]);
357
+
358
+ const contrastPairs = [
359
+ ...headings
360
+ .filter(isVisible)
361
+ .map((element, index) =>
362
+ measure(element, `section-heading-${index + 1}`, 'text'),
363
+ ),
364
+ ...groupHeadings
365
+ .filter(isVisible)
366
+ .map((element, index) =>
367
+ measure(element, `group-heading-${index + 1}`, 'text'),
368
+ ),
369
+ ...inputs.filter(isVisible).map((element, index) => {
370
+ const placeholder = getComputedStyle(element, '::placeholder');
371
+ return measure(
372
+ element,
373
+ `input-placeholder-${index + 1}`,
374
+ 'text',
375
+ placeholder.color,
376
+ placeholder,
377
+ );
378
+ }),
379
+ ...options
380
+ .filter(isVisible)
381
+ .map((element, index) =>
382
+ measure(element, `option-${index + 1}`, 'text'),
383
+ ),
384
+ ...empties
385
+ .filter(isVisible)
386
+ .map((element, index) =>
387
+ measure(element, `empty-${index + 1}`, 'text'),
388
+ ),
389
+ ...footerText
390
+ .filter(isVisible)
391
+ .map((element, index) =>
392
+ measure(element, `footer-hint-${index + 1}`, 'text'),
393
+ ),
394
+ ...shortcuts.flatMap((shortcut, shortcutIndex) =>
395
+ [...shortcut.querySelectorAll<HTMLElement>('kbd')]
396
+ .filter(isVisible)
397
+ .map((element, keyIndex) =>
398
+ measure(
399
+ element,
400
+ `shortcut-${shortcutIndex + 1}-key-${keyIndex + 1}`,
401
+ 'text',
402
+ ),
403
+ ),
404
+ ),
405
+ ...icons
406
+ .filter(isVisible)
407
+ .map((element, index) =>
408
+ measure(element, `search-icon-${index + 1}`, 'non-text'),
409
+ ),
410
+ ...spinnerArcs
411
+ .filter(isVisible)
412
+ .map((element, index) =>
413
+ measure(
414
+ element,
415
+ `loading-spinner-${index + 1}`,
416
+ 'non-text',
417
+ getComputedStyle(element).stroke,
418
+ ),
419
+ ),
420
+ ];
421
+
422
+ return {
423
+ declaredDirection: expectedDirection,
424
+ computedDirections: regions.map(
425
+ region => getComputedStyle(region).direction,
426
+ ),
427
+ text: (node as HTMLElement).innerText,
428
+ counts: {
429
+ regions: regions.length,
430
+ inputs: inputs.length,
431
+ lists: lists.length,
432
+ options: options.length,
433
+ groups: groups.length,
434
+ groupHeadings: groupHeadings.length,
435
+ empties: empties.length,
436
+ footers: footers.length,
437
+ icons: icons.length,
438
+ spinners: spinners.length,
439
+ shortcuts: shortcuts.length,
440
+ targets: targets.length,
441
+ },
442
+ inputLinks: inputs.map(input => ({
443
+ name: input.getAttribute('aria-label'),
444
+ controls: input.getAttribute('aria-controls'),
445
+ controlsExistingList:
446
+ input.getAttribute('aria-controls') != null &&
447
+ lists.some(list => list.id === input.getAttribute('aria-controls')),
448
+ expanded: input.getAttribute('aria-expanded'),
449
+ })),
450
+ selectedValues: options
451
+ .filter(option => option.getAttribute('aria-selected') === 'true')
452
+ .map(option => option.getAttribute('data-value')),
453
+ regions: regions.map(region => ({
454
+ name: region.getAttribute('aria-label'),
455
+ geometry: geometry(region),
456
+ })),
457
+ targetGeometry: targets.map(geometry),
458
+ noHorizontalOverflow:
459
+ document.documentElement.scrollWidth <= window.innerWidth + 1 &&
460
+ targets.every(element => {
461
+ const rect = element.getBoundingClientRect();
462
+ return rect.left >= -1 && rect.right <= window.innerWidth + 1;
463
+ }),
464
+ contrastPairs,
465
+ };
466
+ }, scenario.direction);
467
+
468
+ expect(observed.computedDirections).toEqual([
469
+ scenario.direction,
470
+ scenario.direction,
471
+ scenario.direction,
472
+ ]);
473
+ expect(observed.counts).toEqual({
474
+ regions: 3,
475
+ inputs: 3,
476
+ lists: 1,
477
+ options: 4,
478
+ groups: 2,
479
+ groupHeadings: 2,
480
+ empties: 2,
481
+ footers: 3,
482
+ icons: 3,
483
+ spinners: 1,
484
+ shortcuts: 12,
485
+ targets: 19,
486
+ });
487
+ expect(observed.inputLinks).toEqual([
488
+ expect.objectContaining({
489
+ name: 'Search commands',
490
+ controlsExistingList: true,
491
+ expanded: 'true',
492
+ }),
493
+ expect.objectContaining({
494
+ name: 'Search commands',
495
+ controls: null,
496
+ controlsExistingList: false,
497
+ expanded: 'false',
498
+ }),
499
+ expect.objectContaining({
500
+ name: 'Search commands',
501
+ controls: null,
502
+ controlsExistingList: false,
503
+ expanded: 'false',
504
+ }),
505
+ ]);
506
+ expect(observed.selectedValues).toEqual(['settings']);
507
+ expect(observed.text).toContain('Grouped results');
508
+ expect(observed.text).toContain('Empty bootstrap');
509
+ expect(observed.text).toContain('Pending bootstrap');
510
+ expect(observed.text).toContain('Type to search');
511
+ expect(observed.text).toContain('Create a command');
512
+ expect(observed.text).toContain('Navigate');
513
+ expect(observed.text).toContain('Select');
514
+ expect(observed.text).toContain('Close');
515
+ expect(observed.noHorizontalOverflow).toBe(true);
516
+ expect(geometryDelta).toBeLessThanOrEqual(0.5);
517
+ expect(observed.contrastPairs.length).toBeGreaterThanOrEqual(30);
518
+ expect(observed.contrastPairs.every(pair => pair.passed)).toBe(true);
519
+ expect(errors).toEqual([]);
520
+
521
+ const file = `CommandPalette__${scenario.state}.png`;
522
+ const png = await subject.screenshot({animations: 'disabled'});
523
+ const stateRows = [
524
+ {
525
+ scenario: scenario.state,
526
+ state: 'grouped-results-selected',
527
+ visible: true,
528
+ optionCount: 4,
529
+ groupCount: 2,
530
+ selectedValue: 'settings',
531
+ },
532
+ {
533
+ scenario: scenario.state,
534
+ state: 'empty-bootstrap',
535
+ visible: true,
536
+ emptyCount: 1,
537
+ listboxPresent: false,
538
+ comboboxExpanded: false,
539
+ },
540
+ {
541
+ scenario: scenario.state,
542
+ state: 'pending-bootstrap',
543
+ visible: true,
544
+ spinnerCount: 1,
545
+ emptyStateRemainsMounted: true,
546
+ listboxPresent: false,
547
+ comboboxExpanded: false,
548
+ },
549
+ ];
550
+ const contrastRows = observed.contrastPairs.map(pair => ({
551
+ scenario: scenario.state,
552
+ ...pair,
553
+ }));
554
+ const receipt = {
555
+ state: scenario.state,
556
+ mode: scenario.mode,
557
+ direction: scenario.direction,
558
+ viewport: scenario.viewport,
559
+ headSha: checkoutSha,
560
+ storybookSha,
561
+ image: file,
562
+ imageSha256: createHash('sha256').update(png).digest('hex'),
563
+ imageSize: {
564
+ width: png.readUInt32BE(16),
565
+ height: png.readUInt32BE(20),
566
+ },
567
+ geometryDelta,
568
+ observed,
569
+ stateVisualMatrix: stateRows,
570
+ contrastPairMatrix: contrastRows,
571
+ browser: browserVersion,
572
+ passed: true,
573
+ };
574
+ fs.writeFileSync(path.join(OUTPUT, file), png);
575
+ fs.writeFileSync(
576
+ path.join(OUTPUT, `${file}.sensors.json`),
577
+ `${JSON.stringify(receipt, null, 2)}\n`,
578
+ );
579
+ frames.push({
580
+ state: scenario.state,
581
+ mode: scenario.mode,
582
+ direction: scenario.direction,
583
+ viewport: scenario.viewport,
584
+ passed: true,
585
+ image: receipt.image,
586
+ });
587
+ stateVisualMatrix.push(...stateRows);
588
+ contrastPairMatrix.push(...contrastRows);
589
+ } finally {
590
+ page.off('pageerror', onPageError);
591
+ page.off('console', onConsole);
592
+ }
593
+ }
594
+
595
+ for (const scenario of CASES) {
596
+ test(`captures ${scenario.state}`, async ({page}) => {
597
+ await capture(page, scenario);
598
+ });
599
+ }
@@ -146,8 +146,9 @@ export const docs = {
146
146
  name: 'statusVariant',
147
147
  type: "'attached' | 'detached' | 'tooltip'",
148
148
  description:
149
- 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment; detached when variant is ghost); detached floats below as a separate element with spacing; tooltip reveals the message in an info-tip button.',
150
- default: "'attached'",
149
+ 'How the status message is placed relative to the input. attached overlaps directly below the input (bordered treatment; detached when variant is secondary or ghost); detached floats below as a separate element with spacing; tooltip reveals the message in an info-tip button.',
150
+ default:
151
+ "'attached' for input selectors; 'detached' for secondary and ghost selectors",
151
152
  },
152
153
  {
153
154
  name: 'size',
@@ -157,9 +158,9 @@ export const docs = {
157
158
  },
158
159
  {
159
160
  name: 'variant',
160
- type: "'input' | 'ghost'",
161
+ type: "'input' | 'secondary' | 'ghost'",
161
162
  description:
162
- 'Visual trigger style. Input is the bordered form treatment; ghost matches toolbar buttons.',
163
+ 'Visual trigger style. Input is the bordered form treatment; secondary matches the secondary buttons of the active theme (borderless and neutral-filled by default); ghost is borderless and transparent to match ghost buttons.',
163
164
  default: "'input'",
164
165
  },
165
166
  {
@@ -264,7 +265,7 @@ export const docs = {
264
265
  {
265
266
  guidance: true,
266
267
  description:
267
- 'Use variant="ghost" with a startIcon when the selector is triggered from a toolbar. Use alignment="end" when a wide surface should align its end edge to the trigger.',
268
+ 'Use variant="secondary" beside secondary buttons, or variant="ghost" beside ghost buttons; add a startIcon when it helps identify the trigger. Use alignment="end" when a wide surface should align its end edge to the trigger.',
268
269
  },
269
270
  {
270
271
  guidance: true,
@@ -316,7 +317,7 @@ export const docsDense = {
316
317
  group: 'Selector',
317
318
  category: 'Form Controls',
318
319
  description:
319
- 'Input/ghost trigger + dialog-popover shell for rich custom selectors. Content gets value/onChange/close/state; content owns semantics. Use focus hooks and evaluate custom content against WCAG 2.2.',
320
+ 'Input/secondary/ghost trigger + dialog-popover shell for rich custom selectors. Content gets value/onChange/close/state; content owns semantics. Use focus hooks and evaluate custom content against WCAG 2.2.',
320
321
  usage: {
321
322
  anatomy,
322
323
  description:
@@ -376,7 +377,8 @@ export const docsDense = {
376
377
  changeAction: 'Async action after onChange; drives optimistic value/busy.',
377
378
  children: 'Render custom dialog content from (value,onChange,close,state).',
378
379
  triggerLabel: 'Closed trigger label/content.',
379
- variant: 'input for forms; ghost for toolbar triggers.',
380
+ variant:
381
+ 'input for forms; secondary beside secondary buttons; ghost beside ghost buttons.',
380
382
  startIcon: 'Leading trigger icon.',
381
383
  placement: 'Popup placement.',
382
384
  alignment: 'Popup alignment.',
@@ -397,7 +399,7 @@ export const docsDense = {
397
399
  controlClassName: 'Class name for the visual control box.',
398
400
  controlStyle: 'Inline styles for the visual control box.',
399
401
  statusVariant:
400
- 'How status message is placed: attached overlaps below input (detached for ghost); detached floats below w/ spacing; tooltip shows on status icon.',
402
+ 'How status message is placed: attached overlaps below input (detached for secondary and ghost); detached floats below w/ spacing; tooltip shows on status icon.',
401
403
  accessibility:
402
404
  'Custom content must provide its own accessible structure. Use focus hooks and evaluate against WCAG 2.2.',
403
405
  },
@@ -23,7 +23,7 @@ design_specs: []
23
23
  architecture:
24
24
  [architecture:component-theming-surface, architecture:layer-runtime]
25
25
  contributing: []
26
- system_specs: []
26
+ system_specs: [spec:AST-079/DEC-1]
27
27
  ---
28
28
 
29
29
  # ComplexSelector component contract
@@ -98,7 +98,8 @@ remain documented in `ComplexSelector.doc.mjs`.
98
98
 
99
99
  ### Representative states
100
100
 
101
- - Input and ghost variants use the same six-part anatomy and current targets.
101
+ - Input, secondary, and ghost variants use the same six-part anatomy and current
102
+ targets.
102
103
  - The start slot is absent when `startIcon` is omitted. When present, exactly
103
104
  one of Icon-rendered start icon or Caller-rendered start content applies.
104
105
  - The Popup remains mounted in both states: it is hidden while closed and shown
@@ -188,13 +189,17 @@ caller-owned ReactNode content.
188
189
 
189
190
  ## Decision log
190
191
 
191
- - **DEC-1 — Anchored mode through `renderTrigger`.** A picker anchored in a row the
192
- caller owns cannot host the Field shell or a nested button. The caller
193
- renders the control and spreads the given props; the component keeps owning
194
- open state through the existing `handleRef`/`onOpenChange`. Rejected: an
195
- `anchorRef` plus a controlled `isOpen`, per the rule already written on
196
- `ComplexSelectorHandle`. Proposed in a pull request; FR5 becomes policy only
197
- when an owner approves this record.
192
+ - **DEC-1 — Anchored mode through `renderTrigger`.**
193
+
194
+ **Decider:** pending owner review
195
+
196
+ A picker anchored in a row the caller owns cannot host the Field shell or a
197
+ nested button. The caller renders the control and spreads the given props; the
198
+ component keeps owning open state through the existing
199
+ `handleRef`/`onOpenChange`.
200
+
201
+ Rejected: an `anchorRef` plus a controlled `isOpen`, per the rule already
202
+ written on `ComplexSelectorHandle`.
198
203
 
199
204
  ## Open questions
200
205