@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
@@ -3,8 +3,8 @@
3
3
 
4
4
  /**
5
5
  * @file ContextMenu.tsx
6
- * @input Uses React, StyleX, useLayer, BottomSheet, useListFocus, and the
7
- * shared menu-presentation resolver
6
+ * @input Uses React, StyleX, useLayer, shared layer dismissal, BottomSheet,
7
+ * useListFocus, and the shared menu-presentation resolver
8
8
  * @output Exports ContextMenu with cursor-popover and touch-sheet presentations
9
9
  * @position Core implementation; consumed by index.ts
10
10
  *
@@ -50,6 +50,8 @@ import {Button} from '../Button';
50
50
  import {Heading} from '../Heading';
51
51
  import {Icon} from '../Icon';
52
52
  import {useLayer} from '../Layer/useLayer';
53
+ import {LayerDepthProvider} from '../Layer/LayerDepthContext';
54
+ import {useLayerDismissal} from '../Layer/useLayerDismissal';
53
55
  import {MenuBottomSheetActionList} from '../DropdownMenu/MenuBottomSheetActionList';
54
56
  import {renderDropdownItems} from '../DropdownMenu/renderDropdownItems';
55
57
  import {
@@ -77,7 +79,7 @@ import {
77
79
  easeVars,
78
80
  shadowVars,
79
81
  } from '../theme/tokens.stylex';
80
- import {mergeProps, isImeKeyEvent, rtlStyles} from '../utils';
82
+ import {mergeProps, rtlStyles} from '../utils';
81
83
  import type {BaseProps} from '../BaseProps';
82
84
  import type {StyleXStyles} from '../theme/types';
83
85
  import {themeProps} from '../utils/themeProps';
@@ -313,6 +315,11 @@ export function ContextMenu({
313
315
  style,
314
316
  xstyle,
315
317
  triggerXstyle,
318
+ onContextMenu: onContextMenuProp,
319
+ onTouchStart: onTouchStartProp,
320
+ onTouchMove: onTouchMoveProp,
321
+ onTouchEnd: onTouchEndProp,
322
+ onTouchCancel: onTouchCancelProp,
316
323
  'data-testid': testId,
317
324
  ...rest
318
325
  }: ContextMenuProps) {
@@ -346,7 +353,6 @@ export function ContextMenu({
346
353
  // Element focused before the menu opened, restored when it closes so focus
347
354
  // does not fall to <body> after Escape or outside-click dismissal.
348
355
  const triggerFocusRef = useRef<HTMLElement | null>(null);
349
- const sheetHeadingRef = useRef<HTMLHeadingElement>(null);
350
356
 
351
357
  const [isOpen, setIsOpen] = useState(false);
352
358
  const [submenuPath, setSubmenuPath] = useState<ContextMenuItemData[]>([]);
@@ -406,15 +412,14 @@ export function ContextMenu({
406
412
  [closeMenu],
407
413
  );
408
414
 
409
- useEffect(() => {
410
- if (!isOpen || submenuPath.length === 0) {
411
- return;
412
- }
413
- const frame = requestAnimationFrame(() => {
414
- sheetHeadingRef.current?.focus({preventScroll: true});
415
- });
416
- return () => cancelAnimationFrame(frame);
417
- }, [isOpen, submenuPath.length]);
415
+ const setSheetHeadingRef = useCallback(
416
+ (node: HTMLHeadingElement | null) => {
417
+ if (node && isOpen && submenuPath.length > 0) {
418
+ node.focus({preventScroll: true});
419
+ }
420
+ },
421
+ [isOpen, submenuPath.length],
422
+ );
418
423
 
419
424
  const {
420
425
  listRef,
@@ -429,7 +434,14 @@ export function ContextMenu({
429
434
  // Menus wrap from the last row to the first and back.
430
435
  wrap: true,
431
436
  hasPaging: true,
432
- onEscape: closeMenu,
437
+ });
438
+
439
+ // Join the shared stack so an Escape press dismisses exactly one top-most
440
+ // surface. BottomSheet owns its own dismissal path.
441
+ useLayerDismissal({
442
+ isActive: isOpen && !usesBottomSheet,
443
+ onDismiss: closeMenu,
444
+ getContainer: () => listRef.current,
433
445
  });
434
446
 
435
447
  // Typeahead over the enabled menu items, matched on each row's LABEL alone.
@@ -465,32 +477,6 @@ export function ContextMenu({
465
477
  };
466
478
  }, [isOpen, closeMenu, listRef, usesBottomSheet]);
467
479
 
468
- // Dismiss on Escape from anywhere while open. The menu div's own onKeyDown
469
- // only fires when focus is inside the menu; a document-level listener is
470
- // kept as a reliable fallback Escape path (e.g. if focus has moved out of
471
- // the menu). Guards against IME composition-cancel.
472
- useEffect(() => {
473
- if (!isOpen || usesBottomSheet) {
474
- return;
475
- }
476
- const handleEscape = (e: KeyboardEvent) => {
477
- if (e.key !== 'Escape') {
478
- return;
479
- }
480
- if (isImeKeyEvent(e)) {
481
- // Ignore Escape that is committing/cancelling an IME composition;
482
- // see utils/ime.ts for why.
483
- return;
484
- }
485
- e.preventDefault();
486
- closeMenu();
487
- };
488
- document.addEventListener('keydown', handleEscape);
489
- return () => {
490
- document.removeEventListener('keydown', handleEscape);
491
- };
492
- }, [isOpen, closeMenu, usesBottomSheet]);
493
-
494
480
  const listKeyDown = useCallback(
495
481
  (e: React.KeyboardEvent) => {
496
482
  // A submenu flyout renders inline inside this menu; its key events bubble
@@ -601,7 +587,12 @@ export function ContextMenu({
601
587
  // held long enough (see useLongPress for timer/move-cancel/cleanup logic).
602
588
  // The hook also returns the imperative start/moveTo/cancel API the menu press
603
589
  // model drives; only its four touch handlers belong on the trigger element.
604
- const longPress = useLongPress({
590
+ const {
591
+ onTouchStart: handleLongPressTouchStart,
592
+ onTouchMove: handleLongPressTouchMove,
593
+ onTouchEnd: handleLongPressTouchEnd,
594
+ onTouchCancel: handleLongPressTouchCancel,
595
+ } = useLongPress({
605
596
  disabled: isDisabled,
606
597
  onLongPress: useCallback(
607
598
  (point: {x: number; y: number}) => {
@@ -616,6 +607,45 @@ export function ContextMenu({
616
607
  ),
617
608
  });
618
609
 
610
+ // ContextMenu owns invocation and long-press cleanup. Consumer handlers are
611
+ // notified too, but preventing their default does not cancel component-owned
612
+ // behavior that the public API does not promise as cancelable.
613
+ const handleTriggerContextMenu = useCallback(
614
+ (event: React.MouseEvent<HTMLElement>) => {
615
+ handleContextMenu(event);
616
+ onContextMenuProp?.(event);
617
+ },
618
+ [handleContextMenu, onContextMenuProp],
619
+ );
620
+ const handleTriggerTouchStart = useCallback(
621
+ (event: React.TouchEvent<HTMLElement>) => {
622
+ handleLongPressTouchStart(event);
623
+ onTouchStartProp?.(event);
624
+ },
625
+ [handleLongPressTouchStart, onTouchStartProp],
626
+ );
627
+ const handleTriggerTouchMove = useCallback(
628
+ (event: React.TouchEvent<HTMLElement>) => {
629
+ handleLongPressTouchMove(event);
630
+ onTouchMoveProp?.(event);
631
+ },
632
+ [handleLongPressTouchMove, onTouchMoveProp],
633
+ );
634
+ const handleTriggerTouchEnd = useCallback(
635
+ (event: React.TouchEvent<HTMLElement>) => {
636
+ handleLongPressTouchEnd();
637
+ onTouchEndProp?.(event);
638
+ },
639
+ [handleLongPressTouchEnd, onTouchEndProp],
640
+ );
641
+ const handleTriggerTouchCancel = useCallback(
642
+ (event: React.TouchEvent<HTMLElement>) => {
643
+ handleLongPressTouchCancel();
644
+ onTouchCancelProp?.(event);
645
+ },
646
+ [handleLongPressTouchCancel, onTouchCancelProp],
647
+ );
648
+
619
649
  const popoverXstyle = menuWidth
620
650
  ? styles.popoverCustomWidth(menuWidth)
621
651
  : styles.popover;
@@ -702,7 +732,7 @@ export function ContextMenu({
702
732
  />
703
733
  )}
704
734
  <Heading
705
- ref={sheetHeadingRef}
735
+ ref={setSheetHeadingRef}
706
736
  level={3}
707
737
  tabIndex={-1}
708
738
  xstyle={[
@@ -731,11 +761,11 @@ export function ContextMenu({
731
761
  <TriggerElement
732
762
  ref={useMergedRefs(ref, triggerRef)}
733
763
  {...triggerProps}
734
- onContextMenu={handleContextMenu}
735
- onTouchStart={longPress.onTouchStart}
736
- onTouchMove={longPress.onTouchMove}
737
- onTouchEnd={longPress.onTouchEnd}
738
- onTouchCancel={longPress.onTouchCancel}
764
+ onContextMenu={handleTriggerContextMenu}
765
+ onTouchStart={handleTriggerTouchStart}
766
+ onTouchMove={handleTriggerTouchMove}
767
+ onTouchEnd={handleTriggerTouchEnd}
768
+ onTouchCancel={handleTriggerTouchCancel}
739
769
  data-testid={testId}
740
770
  {...stylex.props(
741
771
  styles.trigger,
@@ -768,7 +798,7 @@ export function ContextMenu({
768
798
  </LazyMenuBottomSheet>
769
799
  </Suspense>
770
800
  ) : (
771
- layer.render(renderedMenu, {
801
+ layer.render(<LayerDepthProvider>{renderedMenu}</LayerDepthProvider>, {
772
802
  placement: 'below',
773
803
  alignment: 'start',
774
804
  xstyle: [popoverXstyle, layerAnimations.below],
@@ -182,12 +182,36 @@ export const docs = {
182
182
  description:
183
183
  'StyleX styles for the Field box around the label, control, and status. containerClassName and containerStyle take a class name and inline styles for the same box. No effect (development warning) where no Field box renders: inside InputGroup (style the group) and in a horizontal-labels FormLayout.',
184
184
  },
185
+ {
186
+ name: 'containerClassName',
187
+ type: 'string',
188
+ description:
189
+ 'Class name for the Field box. Same targeting as containerXstyle.',
190
+ },
191
+ {
192
+ name: 'containerStyle',
193
+ type: 'CSSProperties',
194
+ description:
195
+ 'Inline styles for the Field box. Same targeting as containerXstyle.',
196
+ },
185
197
  {
186
198
  name: 'controlXstyle',
187
199
  type: 'StyleXStyles',
188
200
  description:
189
201
  'StyleX styles for the visual control box: the bordered element that holds the text, icons, and end controls, never the native input. controlClassName and controlStyle take a class name and inline styles for the same box. Replaces root xstyle, className, and style, which this component does not accept.',
190
202
  },
203
+ {
204
+ name: 'controlClassName',
205
+ type: 'string',
206
+ description:
207
+ 'Class name for the visual control box. Same targeting as controlXstyle.',
208
+ },
209
+ {
210
+ name: 'controlStyle',
211
+ type: 'CSSProperties',
212
+ description:
213
+ 'Inline styles for the visual control box. Same targeting as controlXstyle.',
214
+ },
191
215
  ],
192
216
  theming: {
193
217
  targets: [
@@ -503,12 +527,32 @@ export const docsZh = {
503
527
  description:
504
528
  '作用于包含标签、控件和状态的 Field 容器的 StyleX 样式;containerClassName 与 containerStyle 以类名和内联样式作用于同一容器。没有 Field 容器时(InputGroup 内或 horizontal-labels FormLayout 中)不生效,并在开发环境发出警告。',
505
529
  },
530
+ {
531
+ name: 'containerClassName',
532
+ type: 'string',
533
+ description: 'Field 容器的类名,作用目标同 containerXstyle。',
534
+ },
535
+ {
536
+ name: 'containerStyle',
537
+ type: 'CSSProperties',
538
+ description: 'Field 容器的内联样式,作用目标同 containerXstyle。',
539
+ },
506
540
  {
507
541
  name: 'controlXstyle',
508
542
  type: 'StyleXStyles',
509
543
  description:
510
544
  '作用于可见控件框(带边框、包含文本、图标和末端控件的元素,而非原生 input)的 StyleX 样式;controlClassName 与 controlStyle 以类名和内联样式作用于同一元素。本组件不接受根级 xstyle、className 和 style。',
511
545
  },
546
+ {
547
+ name: 'controlClassName',
548
+ type: 'string',
549
+ description: '可见控件框的类名,作用目标同 controlXstyle。',
550
+ },
551
+ {
552
+ name: 'controlStyle',
553
+ type: 'CSSProperties',
554
+ description: '可见控件框的内联样式,作用目标同 controlXstyle。',
555
+ },
512
556
  ],
513
557
  theming: {
514
558
  targets: [
@@ -615,7 +659,15 @@ export const docsDense = {
615
659
  "deprecated, use presentation: 'touch'='adaptive-native', 'always'='native', 'never'='adaptive-bottom-sheet'. still works as released; presentation wins if both set.",
616
660
  containerXstyle:
617
661
  'Field box (label+control+status); also containerClassName/containerStyle. no effect in InputGroup or horizontal-labels FormLayout',
662
+ containerClassName:
663
+ 'class name for the Field box; same target as containerXstyle',
664
+ containerStyle:
665
+ 'inline styles for the Field box; same target as containerXstyle',
618
666
  controlXstyle:
619
667
  'visual control box (border/bg/icons), never the native input; also controlClassName/controlStyle. no root xstyle/className/style',
668
+ controlClassName:
669
+ 'class name for the visual control box; same target as controlXstyle',
670
+ controlStyle:
671
+ 'inline styles for the visual control box; same target as controlXstyle',
620
672
  },
621
673
  };
@@ -13,6 +13,7 @@ review_triggers: [public-api, behavior, layout, theming, accessibility]
13
13
  verified_by:
14
14
  [
15
15
  packages/core/src/DateInput/DateInput.test.tsx,
16
+ packages/core/src/DateInput/DateInput.fr10.test.tsx,
16
17
  packages/core/src/DateInput/DateInputTouch.test.tsx,
17
18
  packages/core/src/DateInput/NativeDateField.test.tsx,
18
19
  packages/core/src/DateInput/Presentation.test.tsx,
@@ -48,7 +49,7 @@ compatibility promise, ownership boundary, or visual direction.
48
49
  | Builder impact | Existing native DateInput `changeAction` handlers now run after `onChange` and receive optimistic busy feedback. No API or migration work is required. |
49
50
  | Compatibility/readiness | Compatible patch correction under the current input-field family rule. Released defaults and controlled ownership are preserved; exact-head browser evidence remains audit evidence rather than product authority. |
50
51
  | Review checks | Reject a surface-mapping change, lost constraint, callback-order change, unlabeled field, unreachable disabled reason, changed close behavior, or a theming target placed on the wrong owner. |
51
- | Governing rules | `spec:AST-043/FR1–FR5`; `family:input-fields/FR1–FR6, FR8–FR9`; `family:overlay-dismissal/FR1–FR7`, including its recorded BottomSheet adoption gap; the three architecture records linked above. |
52
+ | Governing rules | `spec:AST-043/FR1–FR5`; `family:input-fields/FR1–FR6, FR8–FR10`; `family:overlay-dismissal/FR1–FR7`, including its recorded BottomSheet adoption gap; the three architecture records linked above. |
52
53
 
53
54
  This table is a review projection; the body below is authoritative only after this
54
55
  record becomes `current`.
@@ -211,6 +212,15 @@ approved policy.
211
212
  - **AR7 — Directionality.** DateInput's own calendar and clear controls exchange logical
212
213
  inline sides in RTL while preserving their labels and meanings; Calendar navigation
213
214
  remains owned and verified by `component:Calendar`.
215
+ - **AR8 — The month scroller is an APG grid** (`spec:AST-020/FR15`), as one
216
+ composite control: each mounted month pane of the touch calendar's scroller
217
+ is a `role="grid"` named for its month, with rows of `role="gridcell"` days,
218
+ at most one day per pane holding a Tab stop,
219
+ `aria-selected` on the chosen day; ArrowLeft and ArrowRight move by a day
220
+ following the reading direction, ArrowUp and ArrowDown by a week, Home and End
221
+ to the ends of the week, PageUp and PageDown by a month, and Enter or Space
222
+ picks the focused day. Implementation gap: PageUp/PageDown do not page by a
223
+ month.
214
224
 
215
225
  ## Design relationships
216
226
 
@@ -249,14 +259,15 @@ approved policy.
249
259
 
250
260
  The `date-input` target owns the composite field surface. The
251
261
  `date-input-toggle-icon` target reflects collapsed and expanded states on the icon
252
- itself. The deprecated `date-input-clear-icon` alias remains compatibility metadata;
253
- the shared `input-clear-icon` target owns current clear-glyph theming.
262
+ itself. The shared `input-clear-icon` target owns clear-glyph theming; DateInput adds no
263
+ clear-glyph target of its own.
254
264
 
255
265
  ## Family and system relationships
256
266
 
257
267
  - `family:input-fields` owns stable field sizing, end-control space, grouped
258
- adaptation, disabled reasons, loading, Transition Actions, status placement, and
259
- the iOS-only focus-zoom floor; DateInput adopts every listed concept.
268
+ adaptation, disabled reasons, loading, Transition Actions, status placement,
269
+ base-prop and part-styling targets, and the iOS-only focus-zoom floor; DateInput
270
+ adopts every listed concept.
260
271
  - `family:overlay-dismissal` owns topmost Popover and BottomSheet dismissal. DateInput's
261
272
  Popover path uses the shared owner; its bottom-sheet path inherits BottomSheet's
262
273
  recorded adoption gap. DateInput owns only selection, open-state requests, and focus
@@ -271,16 +282,17 @@ the shared `input-clear-icon` target owns current clear-glyph theming.
271
282
 
272
283
  ## Verification map
273
284
 
274
- | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
275
- | ---------------------------------------------------- | ------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------- |
276
- | FR1, FR5–FR8, FR10–FR12; AR1–AR4 | `DateInput.test.tsx` | empty, valued, typing, invalid, constrained, open, disabled, busy, clear, status, grouped | Value, callbacks, ARIA, disabled guard, focus, or shared-field composition drifts | `audit:DateInput/pointer-field` |
277
- | FR2–FR4 | `Presentation.test.tsx`, `NativeDateField.test.tsx`, `DateInputTouch.test.tsx` | every presentation, omitted props, every deprecated mapping, dual props, fine/coarse, touchscreen laptop | A value resolves to the wrong surface, width affects modality, or legacy behavior wins incorrectly | `audit:DateInput/presentation` |
278
- | FR5–FR7; AR1, AR3–AR5 | `NativeDateField.test.tsx` | empty, valued, focused, constrained, disabled, clear, formatted, locale | Native commit, overlay, label, constraint, or focus reconciliation drifts | `audit:DateInput/native` |
279
- | FR1, FR4–FR7, FR9, FR11–FR12; AR1, AR4, AR6; PR1–PR2 | `DateInputTouch.test.tsx` | closed/open, month grid, selection correction, wheels, bounds, keyboard, swipe, mouse drag, clear, busy | Touch value parity, bounded rendering, focus, semantics, or gesture ownership drifts | `audit:DateInput/touch` |
280
- | FR2–FR3, FR6 | `DateInput.doc.mjs`, Storybook controls, `scripts/check-knowledge.mjs` | five presentation values, deprecated prop, named formats, English and Chinese docs | Source, docs, types, or default mappings disagree | `audit:DateInput/docs-api` |
281
- | Theming anatomy | `DateInput.test.tsx`, `themingTargets.test.ts`, generated probe theme | root size/status/disabled, toggle state, shared clear target | Target class or state is missing, undocumented, or placed on the wrong element | `audit:DateInput/theming` |
282
- | FR8; AR2 | `apps/storybook/stories/DateInput.stories.tsx`, component-scoped axe | open Calendar dialog plus closed field states | Important popup semantics stay unreachable to the browser audit | `audit:DateInput/a11y` |
283
- | AR7 | `apps/storybook/rtl-audit/targets.json`, component RTL audit | calendar and clear controls in LTR and RTL | DateInput-owned controls fail to exchange logical inline order | `audit:DateInput/rtl` |
285
+ | Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
286
+ | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- |
287
+ | FR1, FR5–FR8, FR10–FR12; AR1–AR4 | `DateInput.test.tsx` | empty, valued, typing, invalid, constrained, open, disabled, busy, clear, status, grouped | Value, callbacks, ARIA, disabled guard, focus, or shared-field composition drifts | `audit:DateInput/pointer-field` |
288
+ | FR2–FR4 | `Presentation.test.tsx`, `NativeDateField.test.tsx`, `DateInputTouch.test.tsx` | every presentation, omitted props, every deprecated mapping, dual props, fine/coarse, touchscreen laptop | A value resolves to the wrong surface, width affects modality, or legacy behavior wins incorrectly | `audit:DateInput/presentation` |
289
+ | FR5–FR7; AR1, AR3–AR5 | `NativeDateField.test.tsx` | empty, valued, focused, constrained, disabled, clear, formatted, locale | Native commit, overlay, label, constraint, or focus reconciliation drifts | `audit:DateInput/native` |
290
+ | FR1, FR4–FR7, FR9, FR11–FR12; AR1, AR4, AR6; PR1–PR2 | `DateInputTouch.test.tsx` | closed/open, month grid, selection correction, wheels, bounds, keyboard, swipe, mouse drag, clear, busy | Touch value parity, bounded rendering, focus, semantics, or gesture ownership drifts | `audit:DateInput/touch` |
291
+ | FR2–FR3, FR6 | `DateInput.doc.mjs`, Storybook controls, `scripts/check-knowledge.mjs` | five presentation values, deprecated prop, named formats, English and Chinese docs | Source, docs, types, or default mappings disagree | `audit:DateInput/docs-api` |
292
+ | Theming anatomy | `DateInput.test.tsx`, `themingTargets.test.ts`, generated probe theme | root size/status/disabled, toggle state, shared clear target | Target class or state is missing, undocumented, or placed on the wrong element | `audit:DateInput/theming` |
293
+ | FR8; AR2 | `apps/storybook/stories/DateInput.stories.tsx`, component-scoped axe | open Calendar dialog plus closed field states | Important popup semantics stay unreachable to the browser audit | `audit:DateInput/a11y` |
294
+ | AR7 | `apps/storybook/rtl-audit/targets.json`, component RTL audit | calendar and clear controls in LTR and RTL | DateInput-owned controls fail to exchange logical inline order | `audit:DateInput/rtl` |
295
+ | AR8 | `DateInputTouch.test.tsx` calendar-surface suite ("mounts a window of months around the visible one, not the whole century" for the named grids, "moves keyboard focus by date, across the month boundary", "moves a week at a time with the vertical arrows", "leaves exactly one day per month tab-reachable", "gives a spilled date no tab stop in the pane that borrows it", "disables days outside min/max and refuses to commit them", "follows the reading direction under RTL: ArrowLeft moves to the next day", "moves keyboard focus by date, across the month boundary" (the real day, not the spill copy)); ArrowUp, Home/End, Page and activation keys under the Grid accessibility contract (`internal/a11y-spec`, pending) | LTR and RTL; month boundary; selected day; today | A day loses its gridcell role or selection, two days in one pane hold a Tab stop, or an arrow, Home/End, Page or activation key stops moving or picking | `audit:DateInput/accessibility` |
284
296
 
285
297
  ## Decision log
286
298
 
@@ -9,6 +9,7 @@
9
9
  * SYNC: When DateInput.tsx changes, update tests to match new behavior
10
10
  */
11
11
 
12
+ import {useState} from 'react';
12
13
  import {describe, it, expect, vi, beforeEach} from 'vitest';
13
14
  import {
14
15
  render,
@@ -27,6 +28,7 @@ import {InputGroupText} from '../InputGroup/InputGroupText';
27
28
  import {defineTheme} from '../theme/defineTheme';
28
29
  import {generateThemeCSS} from '../theme/generateThemeRules';
29
30
  import {InternationalizationProvider} from '../i18n';
31
+ import type {ISODateString} from '../utils';
30
32
 
31
33
  function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
32
34
  const {prose, component} = generateThemeCSS(theme);
@@ -593,6 +595,42 @@ describe('DateInput', () => {
593
595
  // Full popover interaction is tested in the browser via Storybook.
594
596
 
595
597
  describe('hasClear', () => {
598
+ it('shows no selected calendar days after clearing', async () => {
599
+ const user = userEvent.setup();
600
+
601
+ function ControlledDateInput() {
602
+ const [value, setValue] = useState<ISODateString | undefined>(
603
+ '2026-01-01',
604
+ );
605
+ return (
606
+ <DateInput
607
+ label="Date"
608
+ value={value}
609
+ onChange={setValue}
610
+ min="2026-01-01"
611
+ max="2026-01-31"
612
+ presentation="popover"
613
+ hasClear
614
+ />
615
+ );
616
+ }
617
+
618
+ render(<ControlledDateInput />);
619
+ await user.click(getButton('Open calendar'));
620
+ await user.click(getButton(/January 15, 2026/));
621
+ expect(
622
+ screen.getAllByRole('gridcell', {selected: true, hidden: true}),
623
+ ).toHaveLength(1);
624
+
625
+ await user.click(getButton('Clear Date'));
626
+ expect(screen.getByRole('combobox')).toHaveValue('');
627
+ await user.click(getButton('Open calendar'));
628
+
629
+ expect(
630
+ screen.queryAllByRole('gridcell', {selected: true, hidden: true}),
631
+ ).toHaveLength(0);
632
+ });
633
+
596
634
  it('shows clear button when hasClear is true and value exists', () => {
597
635
  render(
598
636
  <DateInput
@@ -910,7 +910,7 @@ function PointerDateField({
910
910
  <Calendar
911
911
  handleRef={calendarRef}
912
912
  mode="single"
913
- value={optimisticValue}
913
+ value={optimisticValue ?? null}
914
914
  onChange={handleDateSelect}
915
915
  min={min}
916
916
  max={max}
@@ -1118,6 +1118,28 @@ describe('DateInput — calendar surface', () => {
1118
1118
  last.focus();
1119
1119
  fireEvent.keyDown(last, {key: 'ArrowRight'});
1120
1120
  expect(document.activeElement).toHaveAttribute('data-date', '2026-04-01');
1121
+ // The real April 1, not its dimmed spill copy at the foot of the March
1122
+ // pane (which is disabled and selects nothing).
1123
+ expect(pane('April 2026')).toContainElement(
1124
+ document.activeElement as HTMLElement,
1125
+ );
1126
+ expect(document.activeElement).not.toHaveAttribute('aria-disabled');
1127
+ });
1128
+
1129
+ it('follows the reading direction under RTL: ArrowLeft moves to the next day', () => {
1130
+ renderAndOpen(
1131
+ <InternationalizationProvider locale="en-US" dir="rtl">
1132
+ <Controlled initial="2026-03-10" />
1133
+ </InternationalizationProvider>,
1134
+ );
1135
+ const day = within(pane('March 2026')).getByRole('button', {
1136
+ name: /March 10, 2026/,
1137
+ });
1138
+ day.focus();
1139
+ fireEvent.keyDown(day, {key: 'ArrowLeft'});
1140
+ expect(document.activeElement).toHaveAttribute('data-date', '2026-03-11');
1141
+ fireEvent.keyDown(document.activeElement!, {key: 'ArrowRight'});
1142
+ expect(document.activeElement).toHaveAttribute('data-date', '2026-03-10');
1121
1143
  });
1122
1144
 
1123
1145
  it('moves a week at a time with the vertical arrows', () => {
@@ -637,12 +637,16 @@ export function MonthScroller({
637
637
  // Column within the displayed week, which is what Home/End move to —
638
638
  // not the day of the month.
639
639
  const column = (plainDateDayOfWeek(date) - weekStartsOn + 7) % 7;
640
+ // The horizontal arrows follow the reading direction (APG grid): under
641
+ // RTL the next day sits to the left.
642
+ const forward = isRTL ? 'ArrowLeft' : 'ArrowRight';
643
+ const backward = isRTL ? 'ArrowRight' : 'ArrowLeft';
640
644
  switch (event.key) {
641
- case 'ArrowLeft':
645
+ case backward:
642
646
  event.preventDefault();
643
647
  moveFocus(date, -1);
644
648
  break;
645
- case 'ArrowRight':
649
+ case forward:
646
650
  event.preventDefault();
647
651
  moveFocus(date, 1);
648
652
  break;
@@ -666,7 +670,7 @@ export function MonthScroller({
666
670
  break;
667
671
  }
668
672
  },
669
- [moveFocus, weekStartsOn],
673
+ [isRTL, moveFocus, weekStartsOn],
670
674
  );
671
675
 
672
676
  // The focused day may have been in an unmounted pane a frame ago; focus it
@@ -677,8 +681,11 @@ export function MonthScroller({
677
681
  if (!shouldRestoreFocusRef.current || focusedISO == null) {
678
682
  return;
679
683
  }
684
+ // A date can be mounted twice: as a real day in its own pane and as a
685
+ // dimmed, disabled spill copy at the edge of a neighbouring pane. Only
686
+ // the real day may take focus, or Enter and Space would select nothing.
680
687
  const target = scrollerRef.current?.querySelector<HTMLElement>(
681
- `[data-date="${focusedISO}"]`,
688
+ `[data-date="${focusedISO}"]:not([data-outside])`,
682
689
  );
683
690
  if (target != null) {
684
691
  shouldRestoreFocusRef.current = false;
@@ -835,6 +842,7 @@ function MonthPane({
835
842
  <button
836
843
  type="button"
837
844
  data-date={day.iso}
845
+ data-outside={day.isOutside ? '' : undefined}
838
846
  tabIndex={day.iso === tabbableISO ? 0 : -1}
839
847
  aria-label={plainDateFormat(
840
848
  day.date,
@@ -320,6 +320,55 @@ describe('DateRangeInput', () => {
320
320
  ).toHaveLength(0);
321
321
  });
322
322
 
323
+ it('returns focus to the trigger when the clear button is activated by keyboard', async () => {
324
+ const user = userEvent.setup();
325
+ function ControlledDateRangeInput() {
326
+ const [controlledValue, setControlledValue] =
327
+ useState<DateRange | null>({
328
+ start: '2026-01-01',
329
+ end: '2026-01-03',
330
+ });
331
+ return (
332
+ <DateRangeInput
333
+ label="Range"
334
+ value={controlledValue}
335
+ onChange={setControlledValue}
336
+ />
337
+ );
338
+ }
339
+ render(<ControlledDateRangeInput />);
340
+
341
+ getButton('Clear Range').focus();
342
+ await user.keyboard('{Enter}');
343
+
344
+ expect(queryButton('Clear Range')).not.toBeInTheDocument();
345
+ expect(getButton(/^Range:/)).toHaveFocus();
346
+ });
347
+
348
+ it('does not move focus to the trigger when the clear button is clicked', async () => {
349
+ const user = userEvent.setup();
350
+ function ControlledDateRangeInput() {
351
+ const [controlledValue, setControlledValue] =
352
+ useState<DateRange | null>({
353
+ start: '2026-01-01',
354
+ end: '2026-01-03',
355
+ });
356
+ return (
357
+ <DateRangeInput
358
+ label="Range"
359
+ value={controlledValue}
360
+ onChange={setControlledValue}
361
+ />
362
+ );
363
+ }
364
+ render(<ControlledDateRangeInput />);
365
+
366
+ await user.click(getButton('Clear Range'));
367
+
368
+ expect(queryButton('Clear Range')).not.toBeInTheDocument();
369
+ expect(getButton(/^Range:/)).not.toHaveFocus();
370
+ });
371
+
323
372
  it('shows clear button when hasClear is true and value exists', () => {
324
373
  const range: DateRange = {
325
374
  start: '2026-03-15',
@@ -16,7 +16,14 @@
16
16
  * - /packages/cli/assets/templates/blocks/components/DateRangeInput/ (showcase blocks)
17
17
  */
18
18
 
19
- import {useId, useCallback, useMemo, useOptimistic, useTransition} from 'react';
19
+ import {
20
+ useId,
21
+ useCallback,
22
+ useMemo,
23
+ useOptimistic,
24
+ useRef,
25
+ useTransition,
26
+ } from 'react';
20
27
  import * as stylex from '@stylexjs/stylex';
21
28
  import {
22
29
  plainDateFromISO,
@@ -68,6 +75,7 @@ import {
68
75
  import type {SizeValue} from '../utils/types';
69
76
  import {useSize} from '../SizeContext/SizeContext';
70
77
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
78
+ import {useMergedRefs} from '../hooks/useMergedRefs';
71
79
  import {useResolvedRequired} from '../hooks/useResolvedRequired';
72
80
  import {themeProps} from '../utils/themeProps';
73
81
  import {focusOutlineStyles} from '../utils/focusOutline.stylex';
@@ -624,10 +632,20 @@ export function DateRangeInput({
624
632
  [fireChange, popover],
625
633
  );
626
634
 
635
+ const triggerRef = useRef<HTMLButtonElement>(null);
636
+ const triggerButtonRef = useMergedRefs<HTMLButtonElement>(triggerRef, ref);
637
+
627
638
  const handleClear = useCallback(
628
639
  (e: React.MouseEvent) => {
629
640
  e.stopPropagation();
630
641
  fireChange(null);
642
+ // A keyboard activation (detail 0) leaves focus on the clear button,
643
+ // which unmounts once the range is null and would drop focus to <body>;
644
+ // hand it to the trigger. A pointer press never focuses the button, so
645
+ // there is no focus to repair.
646
+ if (e.detail === 0) {
647
+ triggerRef.current?.focus();
648
+ }
631
649
  },
632
650
  [fireChange],
633
651
  );
@@ -713,7 +731,7 @@ export function DateRangeInput({
713
731
  </button>
714
732
  <button
715
733
  {...controlProps}
716
- ref={ref}
734
+ ref={triggerButtonRef}
717
735
  id={id}
718
736
  type="button"
719
737
  onClick={chainEventHandlers(handleToggle, onClickProp)}