@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
@@ -35,7 +35,11 @@ export function plainDateToISO(pd) {
35
35
  return `${y}-${m}-${d}`;
36
36
  }
37
37
  export function plainDateToDate(pd) {
38
- return new Date(pd.year, pd.month - 1, pd.day);
38
+ // Unlike the Date constructor, setFullYear keeps years 0-99 as given
39
+ // instead of reading them as 1900-1999.
40
+ const d = new Date(0, 0, 1);
41
+ d.setFullYear(pd.year, pd.month - 1, pd.day);
42
+ return d;
39
43
  }
40
44
  export function plainDateFromDate(d) {
41
45
  return {
@@ -77,6 +81,17 @@ export function plainDateAddDays(pd, n) {
77
81
  return plainDateFromDate(d);
78
82
  }
79
83
 
84
+ /**
85
+ * `Date.UTC` with years 0-99 kept as given; `Date.UTC` itself reads them as
86
+ * 1900-1999.
87
+ */
88
+ function utcTime(year, monthIndex, day, hour = 0, minute = 0, second = 0) {
89
+ const d = new Date(0);
90
+ d.setUTCFullYear(year, monthIndex, day);
91
+ d.setUTCHours(hour, minute, second);
92
+ return d.getTime();
93
+ }
94
+
80
95
  /**
81
96
  * Whole calendar days from `a` to `b`, ignoring time of day. Positive when
82
97
  * `b` is after `a`, negative when before. Uses UTC midnight so DST shifts
@@ -84,8 +99,8 @@ export function plainDateAddDays(pd, n) {
84
99
  */
85
100
  export function plainDateDiffDays(a, b) {
86
101
  const msPerDay = 24 * 60 * 60 * 1000;
87
- const aUTC = Date.UTC(a.year, a.month - 1, a.day);
88
- const bUTC = Date.UTC(b.year, b.month - 1, b.day);
102
+ const aUTC = utcTime(a.year, a.month - 1, a.day);
103
+ const bUTC = utcTime(b.year, b.month - 1, b.day);
89
104
  return Math.round((bUTC - aUTC) / msPerDay);
90
105
  }
91
106
 
@@ -122,14 +137,23 @@ export function getTimeZoneParts(instant, timezoneID) {
122
137
  }
123
138
  function getTimezoneOffsetMS(timezoneID, instant) {
124
139
  const parts = getTimeZoneParts(instant, timezoneID);
125
- return Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second) - instant;
140
+ return utcTime(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second) - instant;
126
141
  }
142
+ const DAY_MS = 24 * 60 * 60 * 1000;
127
143
  export function plainDateToInstant(date, timezoneID, hour = 0, minute = 0) {
128
- const utcGuess = Date.UTC(date.year, date.month - 1, date.day, hour, minute);
129
- const firstOffset = getTimezoneOffsetMS(timezoneID, utcGuess);
130
- const firstInstant = utcGuess - firstOffset;
131
- const secondOffset = getTimezoneOffsetMS(timezoneID, firstInstant);
132
- return utcGuess - secondOffset;
144
+ // The wall time read as if it were UTC; subtracting an offset gives a
145
+ // candidate instant.
146
+ const wallTime = utcTime(date.year, date.month - 1, date.day, hour, minute);
147
+ // The offsets in effect a day either side cover any transition that can make
148
+ // this wall time skipped or repeated.
149
+ const offsetBefore = getTimezoneOffsetMS(timezoneID, wallTime - DAY_MS);
150
+ const offsetAfter = getTimezoneOffsetMS(timezoneID, wallTime + DAY_MS);
151
+ // AST-067 (Temporal's "compatible" disambiguation): a wall time that happens
152
+ // twice resolves to its earlier occurrence, and one that never happens is
153
+ // read in the offset before the transition, which moves it forward by the
154
+ // length of the gap.
155
+ const candidates = [offsetBefore, offsetAfter].map(offset => wallTime - offset).filter((instant, index) => getTimezoneOffsetMS(timezoneID, instant) === (index === 0 ? offsetBefore : offsetAfter));
156
+ return candidates.length > 0 ? Math.min(...candidates) : wallTime - offsetBefore;
133
157
  }
134
158
  export function plainDateFromInstant(instant, timezoneID) {
135
159
  const parts = getTimeZoneParts(instant, timezoneID);
@@ -181,7 +205,11 @@ export function plainDateGetWeekNumber(pd) {
181
205
  const d = plainDateToDate(pd);
182
206
  const dayNum = d.getDay() || 7;
183
207
  d.setDate(d.getDate() + 4 - dayNum);
184
- const yearStart = new Date(d.getFullYear(), 0, 1);
208
+ const yearStart = plainDateToDate({
209
+ year: d.getFullYear(),
210
+ month: 1,
211
+ day: 1
212
+ });
185
213
  return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
186
214
  }
187
215
 
@@ -262,6 +290,31 @@ export function plainDateFormat(pd, options, locale = 'en') {
262
290
  }).format(plainDateToDate(pd));
263
291
  }
264
292
 
293
+ /**
294
+ * The locale's name for the "week of year" date field ("week", "Woche", "週"),
295
+ * from CLDR through `Intl.DisplayNames`. Calendar names its week-number
296
+ * column header with it, so no catalog string is needed for a word the
297
+ * platform already carries in every locale.
298
+ *
299
+ * `Intl.DisplayNames` with `type: 'dateTimeField'` is inside the Baseline − 2
300
+ * support tier (Chrome 106, Safari 15.4, Firefox 110). Should a host lack it
301
+ * or return nothing, the field's English CLDR name stands in rather than an
302
+ * empty header.
303
+ */
304
+ export function plainDateWeekFieldName(locale = 'en') {
305
+ try {
306
+ const name = new Intl.DisplayNames(locale, {
307
+ type: 'dateTimeField'
308
+ }).of('weekOfYear');
309
+ if (name != null && name !== '') {
310
+ return name;
311
+ }
312
+ } catch {
313
+ // Fall through to the English CLDR value.
314
+ }
315
+ return 'week';
316
+ }
317
+
265
318
  // =============================================================================
266
319
  // Shared date-only format vocabulary
267
320
  // =============================================================================
package/locales/en.json CHANGED
@@ -409,7 +409,7 @@
409
409
  },
410
410
  "@astryx.commandPalette.noResultsFor": {
411
411
  "defaultMessage": "No results for {query}",
412
- "description": "Screen-reader-only announcement when a CommandPalette query matches nothing. `{query}` is the user's verbatim search text; keep it last if your language allows so truncation-by-AT still conveys the outcome."
412
+ "description": "Not read by any component: CommandPalette announces its rendered empty-search message (`emptySearchText`) instead. Formerly the screen-reader-only announcement when a query matched nothing; `{query}` was the user's verbatim search text."
413
413
  },
414
414
  "@astryx.commandPalette.loading": {
415
415
  "defaultMessage": "Loading",
@@ -863,6 +863,10 @@
863
863
  "defaultMessage": "Search…",
864
864
  "description": "Grey placeholder inside the CommandPalette's search input. Imperative verb; trailing `…` is one character."
865
865
  },
866
+ "@astryx.commandPalette.input.label": {
867
+ "defaultMessage": "Search commands",
868
+ "description": "Screen-reader-only accessible name for the CommandPalette query input. Distinguish it from the visible placeholder; verb followed by the app actions being searched."
869
+ },
866
870
  "@astryx.commandPalette.list.label": {
867
871
  "defaultMessage": "Commands",
868
872
  "description": "\"Command\" = an app action a user can invoke (metaphor from CLI), not a Unix command. Screen-reader-only name for the matching-commands list."
@@ -1287,6 +1291,10 @@
1287
1291
  "defaultMessage": "Increment {label}",
1288
1292
  "description": "Accessible label for the NumberInput button that increases the value by one configured step. Example: `Increment Quantity`."
1289
1293
  },
1294
+ "@astryx.numberInput.invalidNumber": {
1295
+ "defaultMessage": "Invalid number",
1296
+ "description": "Screen-reader-only live-region alert in NumberInput when the typed text is not a valid number for the field. The field silently reverts on blur, so this is the only rejection feedback a screen-reader user gets."
1297
+ },
1290
1298
  "@astryx.selector.clearLabel": {
1291
1299
  "defaultMessage": "Clear {label}",
1292
1300
  "description": "Screen-reader-only label on the X that clears the selected value from a Selector. `{label}` is the selector's visible label — example: `Clear Country`."
@@ -1407,6 +1415,10 @@
1407
1415
  "defaultMessage": "{count, number} {count, plural, one {character} other {characters}} over the limit",
1408
1416
  "description": "Screen-reader-only assertive announcement when the user has typed past a TextArea's maxLength. `{count}` is how many characters over — example: `3 characters over the limit`."
1409
1417
  },
1418
+ "@astryx.inputClearButton.tooltip": {
1419
+ "defaultMessage": "Clear",
1420
+ "description": "Tooltip shown on hover or keyboard focus over the X button inside an input that clears its value. Verb, imperative. Shown alone beside the field, so it names only the action; the screen-reader label names the field separately."
1421
+ },
1410
1422
  "@astryx.textInput.clearLabel": {
1411
1423
  "defaultMessage": "Clear {label}",
1412
1424
  "description": "Screen-reader-only label on the X that clears the value from a TextInput. Example: `Clear Email`, `Clear Name`."
@@ -647,6 +647,9 @@
647
647
  "@astryx.commandPalette.input.placeholder": {
648
648
  "defaultMessage": "⟦Šéàřçĥ…⟧"
649
649
  },
650
+ "@astryx.commandPalette.input.label": {
651
+ "defaultMessage": "⟦Šéàřçĥ çóɱɱàñðš⟧"
652
+ },
650
653
  "@astryx.commandPalette.list.label": {
651
654
  "defaultMessage": "⟦Çóɱɱàñðš⟧"
652
655
  },
@@ -965,6 +968,9 @@
965
968
  "@astryx.numberInput.incrementLabel": {
966
969
  "defaultMessage": "⟦Íñçřéɱéñţ {label}⟧"
967
970
  },
971
+ "@astryx.numberInput.invalidNumber": {
972
+ "defaultMessage": "⟦Íñṽàłíð ñúɱƀéř⟧"
973
+ },
968
974
  "@astryx.selector.clearLabel": {
969
975
  "defaultMessage": "⟦Çłéàř {label}⟧"
970
976
  },
@@ -1055,6 +1061,9 @@
1055
1061
  "@astryx.textArea.charactersOverLimit": {
1056
1062
  "defaultMessage": "⟦{count, number} {count, plural, one {character} other {characters}} óṽéř ţĥé łíɱíţ⟧"
1057
1063
  },
1064
+ "@astryx.inputClearButton.tooltip": {
1065
+ "defaultMessage": "⟦Çłéàř⟧"
1066
+ },
1058
1067
  "@astryx.textInput.clearLabel": {
1059
1068
  "defaultMessage": "⟦Çłéàř {label}⟧"
1060
1069
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astryxdesign/core",
3
- "version": "0.7.1-canary.ff40b78",
3
+ "version": "0.7.1",
4
4
  "displayName": "Astryx Core",
5
5
  "description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
6
6
  "author": "Meta Open Source",
@@ -787,8 +787,6 @@
787
787
  "react-dom": ">=19.0.0"
788
788
  },
789
789
  "devDependencies": {
790
- "@astryxdesign/a11y-spec": "0.7.1-canary.ff40b78",
791
- "@astryxdesign/cli": "0.7.1-canary.ff40b78",
792
790
  "@babel/cli": "^7.29.7",
793
791
  "@babel/core": "^7.29.7",
794
792
  "@babel/preset-react": "^7.29.7",
@@ -799,7 +797,9 @@
799
797
  "@testing-library/jest-dom": "^6.6.0",
800
798
  "@testing-library/react": "^16.3.2",
801
799
  "@types/babel__core": "^7.20.5",
802
- "tailwindcss": "^4.3.2"
800
+ "tailwindcss": "^4.3.2",
801
+ "@astryxdesign/a11y-spec": "0.0.0",
802
+ "@astryxdesign/cli": "0.7.1"
803
803
  },
804
804
  "dependencies": {
805
805
  "intl-messageformat": "^11.2.9"
@@ -90,6 +90,12 @@ export const docs = {
90
90
  description:
91
91
  'Overlay behind the dialog that blocks page interaction. Clicking it does not dismiss.',
92
92
  },
93
+ {
94
+ name: 'Dialog surface',
95
+ required: true,
96
+ description:
97
+ 'The modal panel that holds the title, description, and buttons.',
98
+ },
93
99
  ],
94
100
  },
95
101
  // Intentionally a contained isInline preview, not playground.overlay: the
@@ -0,0 +1,205 @@
1
+ ---
2
+ schema_version: 3
3
+ template_version: 7
4
+ kind: component
5
+ id: component:AlertDialog
6
+ authority: draft
7
+ archive_reason: null
8
+ superseded_by: null
9
+ approved_by: null
10
+ approved_at: null
11
+ owners: [cixzhang]
12
+ review_triggers: [public-api, behavior, accessibility, theming]
13
+ verified_by:
14
+ [
15
+ packages/core/src/AlertDialog/AlertDialog.test.tsx,
16
+ packages/core/src/AlertDialog/useImperativeAlertDialog.test.tsx,
17
+ packages/core/src/Dialog/Dialog.test.tsx,
18
+ scripts/check-knowledge.mjs,
19
+ ]
20
+ modules: []
21
+ families: [family:overlay-dismissal]
22
+ design_specs: []
23
+ architecture:
24
+ [
25
+ architecture:public-component-api,
26
+ architecture:component-theming-surface,
27
+ architecture:layer-runtime,
28
+ ]
29
+ contributing: []
30
+ system_specs: [spec:AST-020/FR15]
31
+ ---
32
+
33
+ # AlertDialog component contract
34
+
35
+ ## Contract at a glance
36
+
37
+ | Area | Contract |
38
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | Public contract | None changes. `AlertDialog`, `AlertDialogProps`, and `useImperativeAlertDialog` are exported from `@astryxdesign/core/AlertDialog`; `title`, `description`, `actionLabel`, `onAction`, and `onOpenChange` are required with `isOpen`, plus cancel label, action variant, loading, width, and an inline preview mode. |
40
+ | Behavior | A modal confirmation that interrupts: it opens through the native dialog, cannot be dismissed by a click outside, closes on Escape or Cancel through `onOpenChange(false)`, and runs `onAction` on the action button. |
41
+ | End-user impact | A keyboard or screen-reader user is told a decision is needed (the title and description are announced), lands on Cancel, and cannot lose the dialog by clicking past it. |
42
+ | Builder impact | None. |
43
+ | Compatibility/readiness | Released behavior as observed; this draft records it and adopts the APG Alert Dialog pattern (AR1) under `spec:AST-020/FR15`. |
44
+ | Review checks | Reject a modal AlertDialog without `role="alertdialog"`, a title or description not linked by id, initial focus on the destructive action, or a backdrop click that dismisses it. |
45
+ | Governing rules | `architecture:public-component-api/INV1,INV5–INV8`; `architecture:component-theming-surface/INV3–INV7`; `architecture:layer-runtime`; `component:Dialog/AR1–AR3`; `spec:AST-020/FR15`. |
46
+
47
+ This table is a review projection; the body below is authoritative.
48
+
49
+ ## Intent
50
+
51
+ AlertDialog asks a person to confirm or cancel one consequential action. It
52
+ composes Dialog's modal, focus, and dismissal runtime with a fixed anatomy —
53
+ title, description, Cancel, and one action — so a product gets an interrupting
54
+ confirmation without assembling it.
55
+
56
+ ## Compatibility and migration
57
+
58
+ - Released default preserved: `yes`
59
+ - Compatibility class: additive documentation; runtime, DOM, styling, targets, and
60
+ public API are unchanged
61
+ - Controlled/uncontrolled behavior: unchanged — `isOpen` with `onOpenChange` is
62
+ controlled; `useImperativeAlertDialog` owns that pair for a caller that wants
63
+ `show`/`hide`
64
+ - Migration decision: none
65
+
66
+ Consumer migration instructions belong in consumer docs and release notes.
67
+
68
+ ## Ownership boundary
69
+
70
+ **Owns**
71
+
72
+ - The confirmation anatomy and its order: title, description, then Cancel and
73
+ action in the order FR3 states.
74
+ - The alert-dialog role, its name and description links, and the least-destructive
75
+ initial focus.
76
+ - The imperative wrapper hook.
77
+
78
+ **Does not own / non-goals**
79
+
80
+ - Modal presentation, focus return, backdrop, and dismissal mechanics — owned by
81
+ `component:Dialog` and `architecture:layer-runtime`.
82
+ - Button variants and loading — owned by `component:Button`.
83
+
84
+ ## Public concepts
85
+
86
+ | Concept | Closed values or states | Meaning | Availability by variant/orientation/state | Default | Owner | Stability | Invalid-value behavior |
87
+ | --------------- | ----------------------- | ------------------------------------------------------------------- | ----------------------------------------- | --------------- | ----------------------- | --------- | ---------------------- |
88
+ | `isInline` | `true`, `false` | Renders the content inline as a non-modal preview (`role="group"`). | Always | `false` | `component:AlertDialog` | stable | Type-rejected |
89
+ | `actionVariant` | Button variants | The action button's emphasis. | Always | `'destructive'` | `component:Button` | stable | Button's behavior |
90
+
91
+ ## Behavioral and layout contract
92
+
93
+ Draft requirements identify their basis so observed code is not mistaken for an
94
+ intentional decision. A `current` contract contains no unresolved rows.
95
+
96
+ | ID | Candidate invariant | Basis | Draft review state |
97
+ | --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- | ------------------ |
98
+ | FR1 | A modal AlertDialog opens through Dialog's native modal path and a click on the backdrop does not dismiss it. | shipped source (`purpose="form"`); Alert Dialog accessibility contract (pending) | verify |
99
+ | FR2 | Escape and the Cancel button both call `onOpenChange(false)`; the action button calls `onAction` and leaves closing to the caller. | shipped source and focused tests | verify |
100
+ | FR3 | The anatomy is title, description, then the buttons: Cancel then action on wider screens, the action stacked above Cancel on small screens, Cancel keeping initial focus either way; the action may show a loading state while `isActionLoading`. | shipped source and focused tests | verify |
101
+ | FR4 | `isInline` renders the same anatomy without modality and without the alert-dialog role. | shipped source and public docs | verify |
102
+ | FR5 | `useImperativeAlertDialog` returns `show`, `hide`, and the element to render, and owns `isOpen`/`onOpenChange` for its caller. | shipped source and focused tests | verify |
103
+
104
+ ### Allowed variation
105
+
106
+ - **AV1 — Layout.** Spacing and button width follow Dialog and Layout; the
107
+ small-screen stacking and order are FR3's.
108
+
109
+ ### Representative states
110
+
111
+ | State | Required invariant | Allowed variation |
112
+ | -------------- | ----------------------------------------------------------- | ---------------------- |
113
+ | open, modal | `role="alertdialog"`, named and described, focus on Cancel. | Width, action variant. |
114
+ | action loading | The action shows loading; Cancel stays available. | Loading affordance. |
115
+ | inline preview | `role="group"`, no modality, same content. | Surrounding page. |
116
+ | imperative | `show()` opens and `hide()` closes the same dialog. | Caller's trigger. |
117
+
118
+ ### Transformation and precedence order
119
+
120
+ - **ORD1 — Dismissal.** Escape or Cancel → `onOpenChange(false)`; backdrop click →
121
+ nothing; action → `onAction` only.
122
+
123
+ ### Performance and resources
124
+
125
+ - **PR1 — Dialog-owned resources.** AlertDialog's only listener is the
126
+ small-screen media query behind the button order; Dialog owns the modal
127
+ lifecycle.
128
+
129
+ ## Accessibility contract
130
+
131
+ - **AR1 — AlertDialog adopts the APG Alert Dialog pattern** (`spec:AST-020/FR15`):
132
+ the modal surface is `role="alertdialog"`, named by its title through
133
+ `aria-labelledby` and described by its description through `aria-describedby`;
134
+ it is modal through Dialog (focus enters it and returns to the opener on close
135
+ per `component:Dialog/AR1–AR2`; Tab containment follows Dialog, whose record
136
+ leaves it open); initial
137
+ focus lands on Cancel, the least destructive action; Escape cancels; no click
138
+ outside dismisses it. The inline preview is not an alert dialog and carries
139
+ `role="group"`.
140
+
141
+ ## Design relationships
142
+
143
+ | Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
144
+ | ---------------- | ---------------------------------------------------------------------------------------------- | ------------------------ | -------------- | ------------------ |
145
+ | Title | Names the decision. | `component:Heading` | Prominent | FR3, AR1 |
146
+ | Description | States the consequence. | `component:Text` | Supporting | FR3, AR1 |
147
+ | Cancel button | The safe way out; receives initial focus. | `component:Button` | Supporting | FR2, AR1 |
148
+ | Action button | The consequential choice, with its variant and loading; stacked above Cancel on small screens. | `component:Button` | Prominent | FR2, FR3 |
149
+ | Backdrop | Separates the dialog from the page; does not dismiss it. | `component:Dialog` | Supporting | FR1 |
150
+
151
+ ### Theming anatomy
152
+
153
+ <!-- anatomy-theming:v1 -->
154
+
155
+ ```json
156
+ {
157
+ "Title": {"delegatesTo": {"owner": "component:Text", "target": "heading"}},
158
+ "Description": {"delegatesTo": {"owner": "component:Text", "target": "text"}},
159
+ "Cancel button": {
160
+ "delegatesTo": {"owner": "component:Button", "target": "button"}
161
+ },
162
+ "Action button": {
163
+ "delegatesTo": {"owner": "component:Button", "target": "button"}
164
+ },
165
+ "Backdrop": {
166
+ "delegatesTo": {"owner": "component:Dialog", "target": "dialog"}
167
+ },
168
+ "Dialog surface": {"target": "alert-dialog"}
169
+ }
170
+ ```
171
+
172
+ The dialog surface (the Dialog element AlertDialog renders) carries `alert-dialog`.
173
+
174
+ ## Family and system relationships
175
+
176
+ - `component:Dialog` owns modality, focus return, and dismissal; AlertDialog
177
+ composes it with `purpose="form"` semantics and the alert-dialog role.
178
+ - `family:overlay-dismissal` lists AlertDialog as a member: it dismisses through
179
+ Dialog's shared owner with no deviation of its own; Escape cancels it and a
180
+ backdrop click does not (FR1).
181
+ - `spec:AST-020/FR15` decides which APG parts a component adopts; AlertDialog
182
+ adopts the Alert Dialog pattern's required parts because it already plays that
183
+ role.
184
+
185
+ ## Verification map
186
+
187
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
188
+ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------- |
189
+ | FR1–FR5 | `AlertDialog.test.tsx` ("calls onOpenChange(false) on Escape", "does not call onOpenChange when action is clicked", "does not call onAction on Escape", "does not disable the cancel button while the action is loading", "exposes a named group instead, with no aria-modal") and `useImperativeAlertDialog.test.tsx` ("opens on show()", "closes on hide()") | open, loading, inline, imperative | Escape not cancelling, the action closing by itself, or the inline path claiming the role fails. | `audit:AlertDialog/behavior` |
190
+ | AR1 | `AlertDialog.test.tsx` ("renders with alertdialog role", "links title via aria-labelledby", "links description via aria-describedby", "marks cancel as the initial focus target", "keeps the initial focus target on the least destructive action", "calls onOpenChange(false) on Escape", "stacks long destructive action labels above cancel on a narrow fine-pointer screen", "reaches both buttons by Tab, cancel first", "is a modal alertdialog", "does not claim the alertdialog role") and `useImperativeAlertDialog.test.tsx`; the no-backdrop-dismiss outcome and focus delegation under the Alert Dialog accessibility contract (`internal/a11y-spec`, pending) | open modal; after Escape; after Cancel | The role, name link, description link, or Cancel initial focus is lost, or a click outside dismisses the dialog. | `audit:AlertDialog/accessibility` |
191
+
192
+ ## Decision log
193
+
194
+ None. This draft records shipped behavior; the Alert Dialog adoption follows the
195
+ system rule rather than a component-local decision.
196
+
197
+ ## Open questions
198
+
199
+ None.
200
+
201
+ ## Content boundary
202
+
203
+ This file does not duplicate consumer prop tables or examples
204
+ (`AlertDialog.doc.mjs`), Dialog's modal contract, or shared theming rules. It
205
+ links to their owners.
@@ -93,11 +93,9 @@ describe('AlertDialog', () => {
93
93
  onAction: vi.fn(),
94
94
  };
95
95
 
96
- it('renders with alertdialog role', () => {
97
- render(<AlertDialog {...defaultProps} />);
98
- expect(screen.getByRole('alertdialog')).toBeInTheDocument();
99
- });
100
-
96
+ // The alertdialog role and the aria-describedby link are asserted by the
97
+ // Alert Dialog accessibility contract binding (AlertDialog.a11y.test.tsx /
98
+ // .chromium.spec.ts), not here.
101
99
  it('renders title and description', () => {
102
100
  render(<AlertDialog {...defaultProps} />);
103
101
  expect(screen.getByText('Delete item?')).toBeInTheDocument();
@@ -115,15 +113,6 @@ describe('AlertDialog', () => {
115
113
  expect(titleEl).toHaveTextContent('Delete item?');
116
114
  });
117
115
 
118
- it('links description via aria-describedby', () => {
119
- render(<AlertDialog {...defaultProps} />);
120
- const dialog = screen.getByRole('alertdialog');
121
- const describedBy = dialog.getAttribute('aria-describedby');
122
- expect(describedBy).toBeTruthy();
123
- const descEl = document.getElementById(describedBy!);
124
- expect(descEl).toHaveTextContent('This action cannot be undone.');
125
- });
126
-
127
116
  it('renders cancel and action buttons', () => {
128
117
  render(<AlertDialog {...defaultProps} />);
129
118
  expect(screen.getByText('Cancel')).toBeInTheDocument();
@@ -0,0 +1,115 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file AlertDialog.a11y.chromium.spec.ts
5
+ * @input Built AlertDialog stories and ALERT_DIALOG_PATTERN in real Chromium
6
+ * @output Browser evidence for every alert-dialog expectation across the binding's states, the surface opened through its opener the way a person reaches it
7
+ * @position Chromium lane of the AlertDialog binding (component:AlertDialog 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
+ ALERT_DIALOG_PATTERN,
19
+ blockingResults,
20
+ checkAccessibilitySpec,
21
+ formatFailures,
22
+ neverExercised,
23
+ summarize,
24
+ type BindingResult,
25
+ } from '@astryxdesign/a11y-spec';
26
+ import {
27
+ createChromiumHarness,
28
+ holdMotionStill,
29
+ } from '@astryxdesign/a11y-spec/chromium';
30
+ import {
31
+ DEFAULT_STORYBOOK_DIR,
32
+ serveStorybook,
33
+ type StaticServer,
34
+ } from '@astryxdesign/a11y-spec/storybook';
35
+ import {
36
+ ALERT_DIALOG_BINDING_STATES,
37
+ ALERT_DIALOG_CANCEL_LABEL,
38
+ ALERT_DIALOG_MESSAGE,
39
+ ALERT_DIALOG_OPEN_LABEL,
40
+ type AlertDialogBindingState,
41
+ } from './AlertDialog.a11y.states';
42
+
43
+ let storybook: StaticServer;
44
+
45
+ test.beforeAll(async () => {
46
+ storybook = await serveStorybook(
47
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
48
+ );
49
+ });
50
+
51
+ test.afterAll(async () => {
52
+ await storybook?.close();
53
+ });
54
+
55
+ function storyUrl(storyId: string): string {
56
+ return `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`;
57
+ }
58
+
59
+ async function mountState(
60
+ page: Page,
61
+ state: AlertDialogBindingState,
62
+ ): Promise<{subject: Locator; related: Record<string, Locator>}> {
63
+ await page.goto(storyUrl(state.storyId), {waitUntil: 'load'});
64
+ await holdMotionStill(page);
65
+ const root = page.locator('#storybook-root');
66
+ const subject = root.locator('dialog');
67
+ await root.getByRole('button', {name: ALERT_DIALOG_OPEN_LABEL}).click();
68
+ await subject.waitFor({state: 'visible'});
69
+ const message = subject.getByText(ALERT_DIALOG_MESSAGE);
70
+ const cancel = subject.getByRole('button', {
71
+ name: ALERT_DIALOG_CANCEL_LABEL,
72
+ });
73
+ return {subject, related: {message, cancel}};
74
+ }
75
+
76
+ async function runState(
77
+ page: Page,
78
+ cdp: CDPSession,
79
+ state: AlertDialogBindingState,
80
+ ): Promise<BindingResult> {
81
+ return checkAccessibilitySpec({
82
+ spec: ALERT_DIALOG_PATTERN,
83
+ binding: 'AlertDialog',
84
+ state: state.id,
85
+ facts: state.facts,
86
+ mount: async () => {
87
+ const {subject, related} = await mountState(page, state);
88
+ return createChromiumHarness({page, cdp, subject, related});
89
+ },
90
+ });
91
+ }
92
+
93
+ test('every alert-dialog expectation is exercised by an AlertDialog state', async ({
94
+ page,
95
+ }) => {
96
+ const cdp = await page.context().newCDPSession(page);
97
+ const results: BindingResult[] = [];
98
+ for (const state of ALERT_DIALOG_BINDING_STATES) {
99
+ results.push(await runState(page, cdp, state));
100
+ }
101
+ expect(neverExercised(results)).toEqual([]);
102
+ });
103
+
104
+ for (const state of ALERT_DIALOG_BINDING_STATES) {
105
+ test(`AlertDialog [${state.id}] — ${state.summary}`, async ({page}) => {
106
+ const cdp = await page.context().newCDPSession(page);
107
+ const result = await runState(page, cdp, state);
108
+ const report = summarize(ALERT_DIALOG_PATTERN, [result]);
109
+ // Required rows block (AST-020 FR9); advisory rows and exact known
110
+ // failures report.
111
+ expect(formatFailures(blockingResults([result]))).toBe('');
112
+ expect(report.unrunLayers).toEqual([]);
113
+ expect(report.counts.unexpectedPass).toBe(0);
114
+ });
115
+ }
@@ -0,0 +1,52 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file AlertDialog.a11y.renders.tsx
7
+ * @input One AlertDialog binding state
8
+ * @output The React tree every lane renders for that state: an opener button and the AlertDialog it opens (the jsdom test directly, the story through Storybook)
9
+ * @position Shared fixture for the AlertDialog binding
10
+ */
11
+
12
+ import {useState, type ReactElement} from 'react';
13
+ import {AlertDialog} from '../AlertDialog';
14
+ import {
15
+ ALERT_DIALOG_ACTION_LABEL,
16
+ ALERT_DIALOG_CANCEL_LABEL,
17
+ ALERT_DIALOG_MESSAGE,
18
+ ALERT_DIALOG_OPEN_LABEL,
19
+ ALERT_DIALOG_TITLE,
20
+ type AlertDialogBindingState,
21
+ } from './AlertDialog.a11y.states';
22
+
23
+ function BoundAlertDialog({state}: {state: AlertDialogBindingState}) {
24
+ const [isOpen, setIsOpen] = useState(false);
25
+ return (
26
+ <>
27
+ <button type="button" onClick={() => setIsOpen(true)}>
28
+ {ALERT_DIALOG_OPEN_LABEL}
29
+ </button>
30
+ <AlertDialog
31
+ isOpen={isOpen}
32
+ onOpenChange={setIsOpen}
33
+ title={ALERT_DIALOG_TITLE}
34
+ description={ALERT_DIALOG_MESSAGE}
35
+ cancelLabel={ALERT_DIALOG_CANCEL_LABEL}
36
+ actionLabel={ALERT_DIALOG_ACTION_LABEL}
37
+ isActionLoading={state.isActionLoading}
38
+ onAction={() => setIsOpen(false)}
39
+ />
40
+ </>
41
+ );
42
+ }
43
+
44
+ export function renderAlertDialogState(
45
+ state: AlertDialogBindingState,
46
+ ): ReactElement {
47
+ return (
48
+ <div style={{padding: 24}}>
49
+ <BoundAlertDialog state={state} />
50
+ </div>
51
+ );
52
+ }