@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
@@ -51,10 +51,14 @@ import {
51
51
  plainDateSetFirstOfMonth,
52
52
  plainDateAddMonths,
53
53
  plainDateAddDays,
54
+ plainDateDiffDays,
55
+ getDaysInMonth,
56
+ plainDateIsAfter,
54
57
  plainDateIsBefore,
55
58
  plainDateIsEqual,
56
59
  plainDateGetWeekNumber,
57
60
  plainDateFormat,
61
+ plainDateWeekFieldName,
58
62
  DATE_FORMAT_WITH_WEEKDAY,
59
63
  DATE_FORMAT_MONTH_YEAR,
60
64
  } from '../utils/plainDate';
@@ -91,9 +95,12 @@ import type {
91
95
  import {normalizeDayOfWeek} from '../utils/dateTypes';
92
96
  import {themeProps} from '../utils/themeProps';
93
97
  import {useLocale, useTranslator} from '../i18n';
98
+ import {VisuallyHidden} from '../VisuallyHidden';
94
99
 
95
- /** Imperative handle for Calendar handleRef */
100
+ /** How a month navigation moves the focused day: by its row offset in days, or by whole months. */
101
+ type NavigationUnit = 'days' | 'months';
96
102
 
103
+ /** Imperative handle for Calendar handleRef */
97
104
  export interface CalendarHandle {
98
105
  /** Navigate the calendar to show the month containing the given date */
99
106
  navigateTo: (date: ISODateString) => void;
@@ -214,6 +221,21 @@ interface CalendarRangeProps extends CalendarBaseProps {
214
221
 
215
222
  export type CalendarProps = CalendarSingleProps | CalendarRangeProps;
216
223
 
224
+ /** The first day of `month`'s month that `isDisabled` allows, or null. */
225
+ function firstEnabledDayOfMonth(
226
+ month: PlainDate,
227
+ isDisabled: (pd: PlainDate) => boolean,
228
+ ): PlainDate | null {
229
+ const days = getDaysInMonth(month.year, month.month);
230
+ for (let day = 1; day <= days; day += 1) {
231
+ const pd = {year: month.year, month: month.month, day};
232
+ if (!isDisabled(pd)) {
233
+ return pd;
234
+ }
235
+ }
236
+ return null;
237
+ }
238
+
217
239
  // =============================================================================
218
240
  // Main Component
219
241
  // =============================================================================
@@ -333,6 +355,39 @@ export function Calendar({ref, ...props}: CalendarProps) {
333
355
  });
334
356
  }, [baseMonth, numberOfMonths]);
335
357
 
358
+ // A pending target is claimed by the grid that shows its month, or dropped
359
+ // by the next focus move that is not onto the target day: when a controlled
360
+ // owner refuses the month change, no grid shows the target's month, and a
361
+ // stale target would later steal focus (and Enter) when that month does
362
+ // appear. Only a grid showing the target's month receives it; any other
363
+ // focus change (an arrow to another day, the month header's buttons, or out
364
+ // of the calendar) drops it (see onBlur below).
365
+ const claimablePendingFocus = useMemo(() => {
366
+ if (pendingFocus == null) {
367
+ return null;
368
+ }
369
+ const target = plainDateFromISO(pendingFocus);
370
+ return visibleMonths.some(
371
+ pd => pd.year === target.year && pd.month === target.month,
372
+ )
373
+ ? pendingFocus
374
+ : null;
375
+ }, [pendingFocus, visibleMonths]);
376
+ const handleRootBlur = useCallback(
377
+ (e: React.FocusEvent<HTMLDivElement>) => {
378
+ // Only the move onto the target day itself keeps the target: an arrow
379
+ // to any other day, a move to the header buttons, or focus leaving the
380
+ // calendar all drop it.
381
+ const movesToTarget =
382
+ e.relatedTarget instanceof Element &&
383
+ e.relatedTarget.getAttribute('data-date') === pendingFocus;
384
+ if (pendingFocus != null && !movesToTarget) {
385
+ setPendingFocus(null);
386
+ }
387
+ },
388
+ [pendingFocus],
389
+ );
390
+
336
391
  // Format month header
337
392
  const monthYearLabel = useMemo(() => {
338
393
  if (numberOfMonths === 1) {
@@ -363,6 +418,15 @@ export function Calendar({ref, ...props}: CalendarProps) {
363
418
  }, [monthYearLabel, announce]);
364
419
 
365
420
  // Determine if prev/next navigation is possible based on min/max
421
+ const minDateForKeys = useMemo(
422
+ () => (min ? plainDateFromISO(min) : null),
423
+ [min],
424
+ );
425
+ const maxDateForKeys = useMemo(
426
+ () => (max ? plainDateFromISO(max) : null),
427
+ [max],
428
+ );
429
+
366
430
  const canNavigatePrevious = useMemo(() => {
367
431
  if (!min) {
368
432
  return true;
@@ -390,26 +454,137 @@ export function Calendar({ref, ...props}: CalendarProps) {
390
454
  }, [max, baseMonth, numberOfMonths]);
391
455
 
392
456
  // Navigation handlers
457
+ // The same constraint view the grids use, so a key can resolve its target
458
+ // before the month changes.
459
+ const rootRangeAnchor = useMemo(
460
+ () =>
461
+ mode === 'range' && rangeSelectionStart
462
+ ? plainDateFromISO(rangeSelectionStart)
463
+ : null,
464
+ [mode, rangeSelectionStart],
465
+ );
466
+ const {isDateDisabled: isRootDateDisabled} = useCalendarConstraints({
467
+ min,
468
+ max,
469
+ dateConstraints,
470
+ maxRangeSpan,
471
+ minRangeSpan,
472
+ rangeAnchor: rootRangeAnchor,
473
+ });
474
+
393
475
  const navigateMonth = useCallback(
394
- (delta: number, focusedDate?: ISODateString, offset?: number) => {
395
- const newPd = plainDateAddMonths(baseMonth, delta);
396
- const newISO = plainDateToISO(newPd);
476
+ (
477
+ delta: number,
478
+ focusedDate?: ISODateString,
479
+ offset?: number,
480
+ unit: NavigationUnit = 'days',
481
+ ) => {
482
+ const monthIndex = (pd: PlainDate) => pd.year * 12 + pd.month;
483
+ const firstShown = monthIndex(baseMonth);
484
+ const lastShown = firstShown + numberOfMonths - 1;
485
+
486
+ // The Previous/Next buttons move the month with no focused day.
487
+ if (!focusedDate) {
488
+ if (
489
+ (delta < 0 && !canNavigatePrevious) ||
490
+ (delta > 0 && !canNavigateNext)
491
+ ) {
492
+ return;
493
+ }
494
+ const newPd = plainDateAddMonths(baseMonth, delta);
495
+ if (onFocusDateChange) {
496
+ onFocusDateChange(plainDateToISO(newPd));
497
+ } else {
498
+ setInternalFocusDate(newPd);
499
+ }
500
+ return;
501
+ }
397
502
 
398
- // Calculate target focus date if a focused date was provided
399
- if (focusedDate) {
400
- const currentPd = plainDateFromISO(focusedDate);
401
- const daysToMove = offset ?? 7;
402
- const targetPd = plainDateAddDays(currentPd, delta * daysToMove);
403
- setPendingFocus(plainDateToISO(targetPd));
503
+ // A key resolves its TARGET day first and decides by that, so a
504
+ // boundary blocks only out-of-range targets, never the month movement
505
+ // itself (ArrowUp from February 3 reaches January 27 at a January
506
+ // minimum), and a direction with no enabled day left changes nothing
507
+ // rather than dropping focus.
508
+ const currentPd = plainDateFromISO(focusedDate);
509
+ const inRange = (pd: PlainDate) =>
510
+ !(minDateForKeys && plainDateIsBefore(pd, minDateForKeys)) &&
511
+ !(maxDateForKeys && plainDateIsAfter(pd, maxDateForKeys));
512
+ let targetPd: PlainDate;
513
+ if (unit === 'months') {
514
+ // A page targets the same day of the month a month away (clamped to
515
+ // that month's length). Past `min`/`max` nothing moves; a month with
516
+ // no enabled day is no destination either, the same as an arrow with
517
+ // no enabled day in its direction; a target disabled by a constraint
518
+ // lands on that month's first enabled day instead.
519
+ const candidate = plainDateAddMonths(currentPd, delta);
520
+ if (!inRange(candidate)) {
521
+ return;
522
+ }
523
+ if (isRootDateDisabled(candidate)) {
524
+ const firstEnabled = firstEnabledDayOfMonth(
525
+ candidate,
526
+ isRootDateDisabled,
527
+ );
528
+ if (firstEnabled == null) {
529
+ return;
530
+ }
531
+ targetPd = firstEnabled;
532
+ } else {
533
+ targetPd = candidate;
534
+ }
535
+ } else {
536
+ // Walk the arrow's direction to the first enabled day, up to a year
537
+ // away; past `min`/`max` or with every day disabled, nothing moves.
538
+ const step = delta * (offset ?? 7);
539
+ let candidate = plainDateAddDays(currentPd, step);
540
+ let found: PlainDate | null = null;
541
+ while (Math.abs(plainDateDiffDays(currentPd, candidate)) <= 366) {
542
+ if (!inRange(candidate)) {
543
+ return;
544
+ }
545
+ if (!isRootDateDisabled(candidate)) {
546
+ found = candidate;
547
+ break;
548
+ }
549
+ candidate = plainDateAddDays(candidate, step);
550
+ }
551
+ if (found == null) {
552
+ return;
553
+ }
554
+ targetPd = found;
404
555
  }
405
556
 
557
+ // Focus moves to the target; the months change only when the target's
558
+ // month is not already shown. The view shifts the least it can in the
559
+ // key's direction, so the target month becomes the first (backwards)
560
+ // or last (forwards) month shown.
561
+ const resolved: PlainDate = targetPd;
562
+ const targetMonth = monthIndex(resolved);
563
+ setPendingFocus(plainDateToISO(resolved));
564
+ if (targetMonth >= firstShown && targetMonth <= lastShown) {
565
+ return;
566
+ }
567
+ const targetFirst = {year: resolved.year, month: resolved.month, day: 1};
568
+ const newPd =
569
+ targetMonth < firstShown
570
+ ? targetFirst
571
+ : plainDateAddMonths(targetFirst, -(numberOfMonths - 1));
406
572
  if (onFocusDateChange) {
407
- onFocusDateChange(newISO);
573
+ onFocusDateChange(plainDateToISO(newPd));
408
574
  } else {
409
575
  setInternalFocusDate(newPd);
410
576
  }
411
577
  },
412
- [baseMonth, onFocusDateChange],
578
+ [
579
+ baseMonth,
580
+ canNavigateNext,
581
+ canNavigatePrevious,
582
+ isRootDateDisabled,
583
+ maxDateForKeys,
584
+ minDateForKeys,
585
+ numberOfMonths,
586
+ onFocusDateChange,
587
+ ],
413
588
  );
414
589
 
415
590
  // Escape key handler to cancel range selection
@@ -513,6 +688,7 @@ export function Calendar({ref, ...props}: CalendarProps) {
513
688
  <div
514
689
  ref={ref}
515
690
  {...rest}
691
+ onBlur={composeEventHandlers(rest.onBlur, handleRootBlur)}
516
692
  {...mergeProps(
517
693
  themeProps('calendar', {mode}),
518
694
  stylex.props(calendarStyles.calendar, xstyle),
@@ -587,13 +763,13 @@ export function Calendar({ref, ...props}: CalendarProps) {
587
763
  setHoveredDate(date ? plainDateToISO(date) : null)
588
764
  }
589
765
  today={today}
590
- onNavigatePrevious={(focusedDate, offset) =>
591
- navigateMonth(-1, focusedDate, offset)
766
+ onNavigatePrevious={(focusedDate, offset, unit) =>
767
+ navigateMonth(-1, focusedDate, offset, unit)
592
768
  }
593
- onNavigateNext={(focusedDate, offset) =>
594
- navigateMonth(1, focusedDate, offset)
769
+ onNavigateNext={(focusedDate, offset, unit) =>
770
+ navigateMonth(1, focusedDate, offset, unit)
595
771
  }
596
- pendingFocus={pendingFocus}
772
+ pendingFocus={claimablePendingFocus}
597
773
  onPendingFocusHandled={() => setPendingFocus(null)}
598
774
  />
599
775
  ))}
@@ -626,8 +802,16 @@ interface MonthGridProps {
626
802
  onDayClick: (date: PlainDate) => void;
627
803
  onDayHover: (date: PlainDate | null) => void;
628
804
  today: PlainDate;
629
- onNavigatePrevious?: (focusedDate: ISODateString, offset: number) => void;
630
- onNavigateNext?: (focusedDate: ISODateString, offset: number) => void;
805
+ onNavigatePrevious?: (
806
+ focusedDate: ISODateString,
807
+ offset: number,
808
+ unit?: NavigationUnit,
809
+ ) => void;
810
+ onNavigateNext?: (
811
+ focusedDate: ISODateString,
812
+ offset: number,
813
+ unit?: NavigationUnit,
814
+ ) => void;
631
815
  pendingFocus?: ISODateString | null;
632
816
  onPendingFocusHandled?: () => void;
633
817
  }
@@ -773,14 +957,14 @@ function MonthGrid({
773
957
  const handlePageUp = useCallback(() => {
774
958
  const focusedDate = getFocusedDate();
775
959
  if (focusedDate) {
776
- onNavigatePrevious?.(focusedDate, 7);
960
+ onNavigatePrevious?.(focusedDate, 1, 'months');
777
961
  }
778
962
  }, [getFocusedDate, onNavigatePrevious]);
779
963
 
780
964
  const handlePageDown = useCallback(() => {
781
965
  const focusedDate = getFocusedDate();
782
966
  if (focusedDate) {
783
- onNavigateNext?.(focusedDate, 7);
967
+ onNavigateNext?.(focusedDate, 1, 'months');
784
968
  }
785
969
  }, [getFocusedDate, onNavigateNext]);
786
970
 
@@ -800,8 +984,15 @@ function MonthGrid({
800
984
  } = useGridFocus<HTMLDivElement>({
801
985
  columns: 7,
802
986
  cellSelector: '[role="gridcell"]',
987
+ // A borrowed adjacent-month day is exposed aria-disabled (its click does
988
+ // nothing, but it can still take pointer focus) and may also be natively
989
+ // disabled by bounds or a constraint; the keyboard skips it either way
990
+ // and crosses into that month through the grid edge instead, so focus
991
+ // lands on the real day in the month now shown (FR5).
803
992
  isCellFocusable: cell =>
804
- cell.querySelector('button:not([disabled])') !== null,
993
+ cell.querySelector(
994
+ 'button:not([disabled]):not([aria-disabled="true"])',
995
+ ) !== null,
805
996
  getFocusTarget: cell => cell.querySelector<HTMLElement>('button'),
806
997
  hasRovingTabIndex: true,
807
998
  onNavigateBefore: handleNavigatePrevious,
@@ -810,34 +1001,53 @@ function MonthGrid({
810
1001
  onPageDown: handlePageDown,
811
1002
  });
812
1003
 
813
- // Handle pending focus after month navigation
1004
+ // Handle pending focus after month navigation. Every mounted month grid
1005
+ // sees the same pending date; only the grid showing that date's month acts,
1006
+ // so a neighbouring grid's fallback never overrides the right target. The
1007
+ // target is the real day in this month, never an outside-day copy; when it
1008
+ // is disabled, the month's first enabled day takes focus instead.
814
1009
  useEffect(() => {
815
1010
  if (!pendingFocus || !gridRef.current) {
816
1011
  return;
817
1012
  }
818
1013
 
819
- const buttons = gridRef.current.querySelectorAll<HTMLElement>(
820
- 'button:not([disabled])',
821
- );
822
-
823
1014
  const targetPd = plainDateFromISO(pendingFocus);
1015
+ if (targetPd.year !== year || targetPd.month !== month.month) {
1016
+ return;
1017
+ }
824
1018
  const targetIso = plainDateToISO(targetPd);
825
1019
 
826
- let targetButton: HTMLElement | null = null;
827
- for (const button of buttons) {
828
- if (button.getAttribute('data-date') === targetIso) {
829
- targetButton = button;
830
- break;
831
- }
832
- }
1020
+ const buttons = Array.from(
1021
+ gridRef.current.querySelectorAll<HTMLElement>(
1022
+ 'button[data-date]:not([disabled]):not([aria-disabled="true"])',
1023
+ ),
1024
+ );
833
1025
 
834
- if (!targetButton && buttons.length > 0) {
835
- targetButton = buttons[0];
1026
+ // The real target day, else the month's first enabled day, else — in a
1027
+ // month with no enabled day — the first day button that can still take
1028
+ // focus (a borrowed adjacent-month day), so focus stays in the grid
1029
+ // rather than dropping to the page.
1030
+ const targetButton =
1031
+ buttons.find(button => button.getAttribute('data-date') === targetIso) ??
1032
+ buttons[0] ??
1033
+ gridRef.current.querySelector<HTMLElement>(
1034
+ 'button[data-date]:not([disabled])',
1035
+ ) ??
1036
+ null;
1037
+
1038
+ if (targetButton != null) {
1039
+ // The target becomes this grid's one Tab stop before it takes focus,
1040
+ // the way the hook's own arrow moves do (FR2): a programmatic focus
1041
+ // alone would leave the stop on the day the grid mounted with.
1042
+ for (const button of gridRef.current.querySelectorAll<HTMLElement>(
1043
+ 'button[data-date]',
1044
+ )) {
1045
+ button.tabIndex = button === targetButton ? 0 : -1;
1046
+ }
1047
+ targetButton.focus();
836
1048
  }
837
-
838
- targetButton?.focus();
839
1049
  onPendingFocusHandled?.();
840
- }, [pendingFocus, gridRef, onPendingFocusHandled]);
1050
+ }, [pendingFocus, gridRef, month.month, onPendingFocusHandled, year]);
841
1051
 
842
1052
  // Parse selection
843
1053
  let selectedDate: PlainDate | null = null;
@@ -900,12 +1110,19 @@ function MonthGrid({
900
1110
  same display:contents row so its cells align to the grid columns. */}
901
1111
  <div role="row" {...stylex.props(monthGridStyles.weekRow)}>
902
1112
  {hasWeekNumbers && (
1113
+ // The corner above the week-number column is a header cell like
1114
+ // the weekday names beside it (APG grid: every cell in a row is a
1115
+ // gridcell, columnheader or rowheader). Its name is the locale's
1116
+ // own word for the week field, as visually hidden text so the
1117
+ // column stays blank and the header is not empty to axe.
903
1118
  <div
1119
+ role="columnheader"
904
1120
  {...stylex.props(
905
1121
  monthGridStyles.dayName,
906
1122
  monthGridStyles.weekNumberHeader,
907
- )}
908
- />
1123
+ )}>
1124
+ <VisuallyHidden>{plainDateWeekFieldName(locale)}</VisuallyHidden>
1125
+ </div>
909
1126
  )}
910
1127
  {dayNameHeaders.map(({dayOfWeek, name}) => (
911
1128
  <div
@@ -0,0 +1,126 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Calendar.a11y.chromium.spec.ts
5
+ * @input Built Calendar stories and GRID_PATTERN in real Chromium
6
+ * @output Browser evidence for every grid expectation across the binding's states (role and name, arrows in reading order, Home/End, Enter)
7
+ * @position Chromium lane of the Calendar grid binding (component:Calendar AR1)
8
+ */
9
+
10
+ import {
11
+ expect,
12
+ test,
13
+ type CDPSession,
14
+ type Locator,
15
+ type Page,
16
+ } from '@playwright/test';
17
+ import {
18
+ GRID_PATTERN,
19
+ blockingResults,
20
+ checkAccessibilitySpec,
21
+ formatFailures,
22
+ neverExercised,
23
+ unmatchedKnownFailures,
24
+ summarize,
25
+ type BindingResult,
26
+ } from '@astryxdesign/a11y-spec';
27
+ import {
28
+ createChromiumHarness,
29
+ holdMotionStill,
30
+ } from '@astryxdesign/a11y-spec/chromium';
31
+ import {
32
+ DEFAULT_STORYBOOK_DIR,
33
+ serveStorybook,
34
+ type StaticServer,
35
+ } from '@astryxdesign/a11y-spec/storybook';
36
+ import {
37
+ CALENDAR_BINDING_STATES,
38
+ NEIGHBOURHOOD,
39
+ START_ISO,
40
+ type CalendarBindingState,
41
+ } from './Calendar.a11y.states';
42
+ import {CALENDAR_KNOWN_FAILURES} from './Calendar.a11y.known-failures';
43
+
44
+ let storybook: StaticServer;
45
+
46
+ test.beforeAll(async () => {
47
+ storybook = await serveStorybook(
48
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
49
+ );
50
+ });
51
+
52
+ test.afterAll(async () => {
53
+ await storybook?.close();
54
+ });
55
+
56
+ function storyUrl(storyId: string): string {
57
+ return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
58
+ }
59
+
60
+ async function mountState(
61
+ page: Page,
62
+ state: CalendarBindingState,
63
+ ): Promise<{subject: Locator; related: Record<string, Locator>}> {
64
+ await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
65
+ await holdMotionStill(page);
66
+ const root = page.locator('#storybook-root');
67
+ const subject = root.getByRole('grid', {name: state.gridName});
68
+ await subject.waitFor({state: 'attached'});
69
+ const day = (iso: string) => root.locator(`button[data-date="${iso}"]`);
70
+ const start = day(START_ISO);
71
+ return {
72
+ subject,
73
+ related: {
74
+ start,
75
+ startCell: root.locator(
76
+ `[role="gridcell"]:has(> button[data-date="${START_ISO}"])`,
77
+ ),
78
+ ...Object.fromEntries(
79
+ Object.entries(NEIGHBOURHOOD).map(([name, iso]) => [name, day(iso)]),
80
+ ),
81
+ },
82
+ };
83
+ }
84
+
85
+ async function runState(
86
+ page: Page,
87
+ cdp: CDPSession,
88
+ state: CalendarBindingState,
89
+ ): Promise<BindingResult> {
90
+ return checkAccessibilitySpec({
91
+ spec: GRID_PATTERN,
92
+ binding: 'Calendar',
93
+ state: state.id,
94
+ facts: state.facts,
95
+ knownFailures: CALENDAR_KNOWN_FAILURES,
96
+ mount: async () => {
97
+ const {subject, related} = await mountState(page, state);
98
+ return createChromiumHarness({page, cdp, subject, related});
99
+ },
100
+ });
101
+ }
102
+
103
+ test('every grid expectation is exercised by a Calendar state', async ({
104
+ page,
105
+ }) => {
106
+ const cdp = await page.context().newCDPSession(page);
107
+ const results: BindingResult[] = [];
108
+ for (const state of CALENDAR_BINDING_STATES) {
109
+ results.push(await runState(page, cdp, state));
110
+ }
111
+ expect(neverExercised(results)).toEqual([]);
112
+ expect(unmatchedKnownFailures(CALENDAR_KNOWN_FAILURES, results)).toEqual([]);
113
+ });
114
+
115
+ for (const state of CALENDAR_BINDING_STATES) {
116
+ test(`Calendar [${state.id}] — ${state.summary}`, async ({page}) => {
117
+ const cdp = await page.context().newCDPSession(page);
118
+ const result = await runState(page, cdp, state);
119
+ const report = summarize(GRID_PATTERN, [result]);
120
+ // Required rows block (AST-020 FR9); advisory rows and exact known
121
+ // failures report.
122
+ expect(formatFailures(blockingResults([result]))).toBe('');
123
+ expect(report.unrunLayers).toEqual([]);
124
+ expect(report.counts.unexpectedPass).toBe(0);
125
+ });
126
+ }
@@ -0,0 +1,12 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file Calendar.a11y.known-failures.ts
5
+ * @input The exact grid-contract failures the shipped Calendar produces today
6
+ * @output CALENDAR_KNOWN_FAILURES — records both lanes reconcile against, so a fix shows up as an unexpected pass and any other failure stays a failure
7
+ * @position Known-failure ledger for the Calendar binding
8
+ */
9
+
10
+ import type {KnownFailure} from '@astryxdesign/a11y-spec';
11
+
12
+ export const CALENDAR_KNOWN_FAILURES: ReadonlyArray<KnownFailure> = [];
@@ -0,0 +1,57 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file Calendar.a11y.renders.tsx
7
+ * @input One Calendar binding state
8
+ * @output The React tree every lane renders for that state (the jsdom test directly, the story through Storybook)
9
+ * @position Shared fixture for the Calendar grid binding
10
+ */
11
+
12
+ import {useState, type ReactElement} from 'react';
13
+ import {Calendar} from '../Calendar';
14
+ import type {DateRange, ISODateString} from '../Calendar';
15
+ import {START_ISO, type CalendarBindingState} from './Calendar.a11y.states';
16
+
17
+ function BoundCalendar({state}: {state: CalendarBindingState}) {
18
+ const [single, setSingle] = useState<ISODateString | null>(
19
+ state.mode === 'single' && typeof state.value === 'string'
20
+ ? state.value
21
+ : null,
22
+ );
23
+ const [range, setRange] = useState<DateRange | null>(
24
+ state.mode === 'range' && typeof state.value === 'object'
25
+ ? state.value
26
+ : null,
27
+ );
28
+ if (state.mode === 'range') {
29
+ return (
30
+ <Calendar
31
+ mode="range"
32
+ value={range}
33
+ onChange={setRange}
34
+ focusDate={START_ISO}
35
+ numberOfMonths={state.numberOfMonths ?? 1}
36
+ />
37
+ );
38
+ }
39
+ return (
40
+ <Calendar
41
+ mode="single"
42
+ value={single}
43
+ onChange={setSingle}
44
+ focusDate={START_ISO}
45
+ numberOfMonths={state.numberOfMonths ?? 1}
46
+ hasWeekNumbers={state.hasWeekNumbers}
47
+ />
48
+ );
49
+ }
50
+
51
+ export function renderCalendarState(state: CalendarBindingState): ReactElement {
52
+ return (
53
+ <div dir={state.rtl ? 'rtl' : 'ltr'} style={{padding: 16}}>
54
+ <BoundCalendar state={state} />
55
+ </div>
56
+ );
57
+ }