@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
@@ -15,8 +15,11 @@
15
15
  * which is bound to the region's `_size`.
16
16
  */
17
17
 
18
+ import {useRef} from 'react';
18
19
  import {describe, it, expect, vi} from 'vitest';
19
- import {render, screen, fireEvent, act} from '@testing-library/react';
20
+ import {render, screen, fireEvent, act, waitFor} from '@testing-library/react';
21
+ import {hydrateRoot} from 'react-dom/client';
22
+ import {renderToString} from 'react-dom/server';
20
23
  import {ResizeHandle} from './ResizeHandle';
21
24
  import type {ResizeHandleProps} from './ResizeHandle';
22
25
  import {useResizable} from './useResizable';
@@ -130,6 +133,192 @@ describe('ResizeHandle', () => {
130
133
  expect(separator).toHaveAttribute('aria-valuenow', '100');
131
134
  });
132
135
 
136
+ it('reports the window basis as aria-valuemax when no maximum is configured; End leaves an unbounded region alone', () => {
137
+ const previous = window.innerWidth;
138
+ window.innerWidth = 1000;
139
+ try {
140
+ render(<Harness config={{defaultSize: 200, minSize: 100}} />);
141
+ const separator = getSeparator();
142
+ // ARIA needs a maximum on a separator with a value: the basis the hook
143
+ // resolves against stands in for an unbounded one.
144
+ expect(separator).toHaveAttribute('aria-valuemax', '1000');
145
+ // End still moves only to a configured maximum: the stand-in says how
146
+ // far the pane could reach, not where the handle may safely go
147
+ // (component:Resizable records the End gap).
148
+ act(() => separator.focus());
149
+ fireEvent.keyDown(separator, {key: 'End'});
150
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
151
+ } finally {
152
+ window.innerWidth = previous;
153
+ }
154
+ });
155
+
156
+ it('server-renders an unbounded handle with no maximum and reports the window extent after hydration, with no hydration error', async () => {
157
+ const previous = window.innerWidth;
158
+ window.innerWidth = 1000;
159
+ const probe = <Harness config={{defaultSize: 200, minSize: 100}} />;
160
+ const serverHTML = renderToString(probe);
161
+ // The server has no window: no aria-valuemax in the markup.
162
+ expect(serverHTML).not.toContain('aria-valuemax');
163
+ const container = document.createElement('div');
164
+ container.innerHTML = serverHTML;
165
+ document.body.appendChild(container);
166
+ const consoleError = vi
167
+ .spyOn(console, 'error')
168
+ .mockImplementation(() => {});
169
+ const recoverableErrors: unknown[] = [];
170
+ let root: ReturnType<typeof hydrateRoot>;
171
+ try {
172
+ await act(async () => {
173
+ root = hydrateRoot(container, probe, {
174
+ onRecoverableError: error => recoverableErrors.push(error),
175
+ });
176
+ });
177
+ // The first client render reads the window through the store and
178
+ // reports it; a render-time window read would mismatch the server
179
+ // markup (or, read only once, leave the attribute missing).
180
+ await waitFor(() =>
181
+ expect(container.querySelector('[role="separator"]')).toHaveAttribute(
182
+ 'aria-valuemax',
183
+ '1000',
184
+ ),
185
+ );
186
+ expect(recoverableErrors).toEqual([]);
187
+ expect(
188
+ consoleError.mock.calls.filter(call =>
189
+ String(call[0] ?? '')
190
+ .toLowerCase()
191
+ .includes('hydrat'),
192
+ ),
193
+ ).toEqual([]);
194
+ } finally {
195
+ await act(async () => root!.unmount());
196
+ consoleError.mockRestore();
197
+ container.remove();
198
+ window.innerWidth = previous;
199
+ }
200
+ });
201
+
202
+ it('never reports a maximum below the current size, and End does not shrink the region', () => {
203
+ const previous = window.innerWidth;
204
+ window.innerWidth = 500;
205
+ try {
206
+ // A default (or saved, or pre-resize) size larger than the window.
207
+ render(<Harness config={{defaultSize: 800, minSize: 100}} />);
208
+ const separator = getSeparator();
209
+ expect(separator).toHaveAttribute('aria-valuenow', '800');
210
+ expect(separator).toHaveAttribute('aria-valuemax', '800');
211
+ act(() => separator.focus());
212
+ fireEvent.keyDown(separator, {key: 'End'});
213
+ expect(separator).toHaveAttribute('aria-valuenow', '800');
214
+ } finally {
215
+ window.innerWidth = previous;
216
+ }
217
+ });
218
+
219
+ it('never reports aria-valuemax below aria-valuemin while collapsed in a small window', () => {
220
+ const previous = window.innerWidth;
221
+ window.innerWidth = 375;
222
+ try {
223
+ render(
224
+ <Harness
225
+ config={{defaultSize: 500, minSize: 400, collapsible: true}}
226
+ />,
227
+ );
228
+ const separator = getSeparator();
229
+ act(() => separator.focus());
230
+ fireEvent.keyDown(separator, {key: 'Enter'});
231
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
232
+ expect(separator).toHaveAttribute('aria-valuemin', '400');
233
+ expect(
234
+ Number(separator.getAttribute('aria-valuemax')),
235
+ ).toBeGreaterThanOrEqual(400);
236
+ } finally {
237
+ window.innerWidth = previous;
238
+ }
239
+ });
240
+
241
+ it('reports the window height, not the width, for an unbounded vertical region', () => {
242
+ const previousWidth = window.innerWidth;
243
+ const previousHeight = window.innerHeight;
244
+ window.innerWidth = 1000;
245
+ window.innerHeight = 700;
246
+ try {
247
+ render(
248
+ <Harness
249
+ config={{defaultSize: 200, minSize: 100, direction: 'vertical'}}
250
+ handleProps={{direction: 'vertical'}}
251
+ />,
252
+ );
253
+ const separator = getSeparator();
254
+ expect(separator).toHaveAttribute('aria-valuemax', '700');
255
+ } finally {
256
+ window.innerWidth = previousWidth;
257
+ window.innerHeight = previousHeight;
258
+ }
259
+ });
260
+
261
+ it('reports the container basis as aria-valuemax while a percentage bound already observes it', () => {
262
+ function ContainerHarness() {
263
+ const containerRef = useRef<HTMLDivElement>(null);
264
+ const region = useResizable({
265
+ defaultSize: 200,
266
+ minSize: '10%',
267
+ containerRef,
268
+ });
269
+ return (
270
+ <div ref={containerRef}>
271
+ <ResizeHandle resizable={region.props} label="Resize" />
272
+ </div>
273
+ );
274
+ }
275
+ // jsdom has no ResizeObserver; a stub lets the hook subscribe, and the
276
+ // content box is measured through clientWidth minus padding.
277
+ class StubResizeObserver {
278
+ observe() {}
279
+ unobserve() {}
280
+ disconnect() {}
281
+ }
282
+ vi.stubGlobal('ResizeObserver', StubResizeObserver);
283
+ const clientWidth = vi
284
+ .spyOn(HTMLElement.prototype, 'clientWidth', 'get')
285
+ .mockReturnValue(640);
286
+ const computedStyle = vi.spyOn(window, 'getComputedStyle').mockReturnValue({
287
+ paddingTop: '0px',
288
+ paddingBottom: '0px',
289
+ paddingLeft: '0px',
290
+ paddingRight: '0px',
291
+ } as unknown as CSSStyleDeclaration);
292
+ try {
293
+ render(<ContainerHarness />);
294
+ expect(getSeparator()).toHaveAttribute('aria-valuemax', '640');
295
+ } finally {
296
+ clientWidth.mockRestore();
297
+ computedStyle.mockRestore();
298
+ vi.unstubAllGlobals();
299
+ }
300
+ });
301
+
302
+ it('reports no aria-valuemax for an unbounded pixel-only region in a container, which stays unobserved', () => {
303
+ function ContainerHarness() {
304
+ const containerRef = useRef<HTMLDivElement>(null);
305
+ const region = useResizable({
306
+ defaultSize: 200,
307
+ minSize: 100,
308
+ containerRef,
309
+ });
310
+ return (
311
+ <div ref={containerRef}>
312
+ <ResizeHandle resizable={region.props} label="Resize" />
313
+ </div>
314
+ );
315
+ }
316
+ render(<ContainerHarness />);
317
+ // component:Resizable Performance: atomic pixel paths add no observer, so
318
+ // the hook never measures this container and reports no stand-in.
319
+ expect(getSeparator()).not.toHaveAttribute('aria-valuemax');
320
+ });
321
+
133
322
  it('jumps to the maximum on End', () => {
134
323
  render(<Harness />);
135
324
  const separator = getSeparator();
@@ -202,6 +391,379 @@ describe('ResizeHandle', () => {
202
391
  expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
203
392
  });
204
393
 
394
+ it('restores the previous size, not the minimum, when Enter reopens a collapsed pane', () => {
395
+ render(
396
+ <Harness
397
+ config={{
398
+ defaultSize: 300,
399
+ minSize: 100,
400
+ maxSize: 400,
401
+ collapsible: true,
402
+ }}
403
+ />,
404
+ );
405
+ const separator = getSeparator();
406
+ act(() => separator.focus());
407
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
408
+ fireEvent.keyDown(separator, {key: 'Enter'});
409
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
410
+ fireEvent.keyDown(separator, {key: 'Enter'});
411
+ expect(separator).not.toHaveAttribute('aria-valuetext');
412
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
413
+ });
414
+
415
+ it('restores the previous size, not the minimum, when a double-click reopens a collapsed pane', () => {
416
+ render(
417
+ <Harness
418
+ config={{
419
+ defaultSize: 300,
420
+ minSize: 100,
421
+ maxSize: 400,
422
+ collapsible: true,
423
+ }}
424
+ />,
425
+ );
426
+ const separator = getSeparator();
427
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
428
+ fireEvent.doubleClick(separator);
429
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
430
+ fireEvent.doubleClick(separator);
431
+ expect(separator).not.toHaveAttribute('aria-valuetext');
432
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
433
+ });
434
+
435
+ it('reopens on a double-click even when the minimum size sits below the collapse threshold', () => {
436
+ render(
437
+ <Harness
438
+ config={{
439
+ defaultSize: 200,
440
+ minSize: 20,
441
+ maxSize: 400,
442
+ collapsible: true,
443
+ collapsedSize: 50,
444
+ }}
445
+ />,
446
+ );
447
+ const separator = getSeparator();
448
+ fireEvent.doubleClick(separator);
449
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
450
+ fireEvent.doubleClick(separator);
451
+ expect(separator).not.toHaveAttribute('aria-valuetext');
452
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
453
+ });
454
+
455
+ it('a grow arrow opens a collapsed pane one step above its minimum; the shrink arrow leaves it collapsed', () => {
456
+ render(
457
+ <Harness
458
+ config={{
459
+ defaultSize: 200,
460
+ minSize: 100,
461
+ maxSize: 400,
462
+ collapsible: true,
463
+ }}
464
+ />,
465
+ );
466
+ const separator = getSeparator();
467
+ act(() => separator.focus());
468
+ fireEvent.keyDown(separator, {key: 'Enter'});
469
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
470
+ // Shrinking a collapsed pane changes nothing.
471
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
472
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
473
+ // One bare grow step would stay below the collapse threshold (40px) and
474
+ // leave the pane collapsed; instead the arrow opens it one step above the
475
+ // minimum it announced (100 → 110).
476
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
477
+ expect(separator).not.toHaveAttribute('aria-valuetext');
478
+ expect(separator).toHaveAttribute('aria-valuenow', '110');
479
+ });
480
+
481
+ it('a grow arrow opens a collapsed pane at its collapsedSize when the minimum sits below it', () => {
482
+ render(
483
+ <Harness
484
+ config={{
485
+ defaultSize: 200,
486
+ minSize: 20,
487
+ maxSize: 400,
488
+ collapsible: true,
489
+ collapsedSize: 50,
490
+ }}
491
+ />,
492
+ );
493
+ const separator = getSeparator();
494
+ act(() => separator.focus());
495
+ fireEvent.keyDown(separator, {key: 'Enter'});
496
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
497
+ // min + step (30) would still be below the 50px threshold, so the pane
498
+ // opens at the threshold instead.
499
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
500
+ expect(separator).not.toHaveAttribute('aria-valuetext');
501
+ expect(separator).toHaveAttribute('aria-valuenow', '50');
502
+ });
503
+
504
+ it('a grow arrow opens a collapsed pane in the reversed layout and under RTL too', () => {
505
+ const rtl = vi
506
+ .spyOn(window, 'getComputedStyle')
507
+ .mockImplementation(
508
+ () => ({direction: 'rtl'}) as unknown as CSSStyleDeclaration,
509
+ );
510
+ try {
511
+ // Reversed + RTL: the pane sits on the right and grows to the left on
512
+ // screen, which under RTL is ArrowRight again.
513
+ render(
514
+ <Harness
515
+ config={{
516
+ defaultSize: 200,
517
+ minSize: 100,
518
+ maxSize: 400,
519
+ collapsible: true,
520
+ }}
521
+ handleProps={{isReversed: true}}
522
+ />,
523
+ );
524
+ const separator = getSeparator();
525
+ act(() => separator.focus());
526
+ fireEvent.keyDown(separator, {key: 'Enter'});
527
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
528
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
529
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
530
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
531
+ expect(separator).not.toHaveAttribute('aria-valuetext');
532
+ expect(separator).toHaveAttribute('aria-valuenow', '110');
533
+ } finally {
534
+ rtl.mockRestore();
535
+ }
536
+ });
537
+
538
+ it('in a reversed layout, ArrowLeft opens a collapsed pane and ArrowRight leaves it collapsed', () => {
539
+ // Reversed: the pane sits on the right and grows to the left.
540
+ render(
541
+ <Harness
542
+ config={{
543
+ defaultSize: 200,
544
+ minSize: 100,
545
+ maxSize: 400,
546
+ collapsible: true,
547
+ }}
548
+ handleProps={{isReversed: true}}
549
+ />,
550
+ );
551
+ const separator = getSeparator();
552
+ act(() => separator.focus());
553
+ fireEvent.keyDown(separator, {key: 'Enter'});
554
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
555
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
556
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
557
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
558
+ expect(separator).not.toHaveAttribute('aria-valuetext');
559
+ expect(separator).toHaveAttribute('aria-valuenow', '110');
560
+ });
561
+
562
+ it('under RTL, ArrowLeft opens a collapsed pane and ArrowRight leaves it collapsed', () => {
563
+ const rtl = vi
564
+ .spyOn(window, 'getComputedStyle')
565
+ .mockImplementation(
566
+ () => ({direction: 'rtl'}) as unknown as CSSStyleDeclaration,
567
+ );
568
+ try {
569
+ // Not reversed, right-to-left: the pane sits on the right and grows
570
+ // toward the left edge of the screen.
571
+ render(
572
+ <Harness
573
+ config={{
574
+ defaultSize: 200,
575
+ minSize: 100,
576
+ maxSize: 400,
577
+ collapsible: true,
578
+ }}
579
+ />,
580
+ );
581
+ const separator = getSeparator();
582
+ act(() => separator.focus());
583
+ fireEvent.keyDown(separator, {key: 'Enter'});
584
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
585
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
586
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
587
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
588
+ expect(separator).not.toHaveAttribute('aria-valuetext');
589
+ expect(separator).toHaveAttribute('aria-valuenow', '110');
590
+ } finally {
591
+ rtl.mockRestore();
592
+ }
593
+ });
594
+
595
+ it('with snap points, an arrow moves to the adjacent point in its direction and Shift moves two, clamped to the ends', () => {
596
+ render(
597
+ <Harness
598
+ config={{
599
+ defaultSize: 200,
600
+ minSize: 100,
601
+ maxSize: 400,
602
+ snaps: [100, 200, 300, 400],
603
+ }}
604
+ />,
605
+ );
606
+ const separator = getSeparator();
607
+ act(() => separator.focus());
608
+ // A 10px step would snap straight back to 200; the arrow reaches the
609
+ // next point instead.
610
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
611
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
612
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
613
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
614
+ fireEvent.keyDown(separator, {key: 'ArrowRight', shiftKey: true});
615
+ expect(separator).toHaveAttribute('aria-valuenow', '400');
616
+ // Already at the last point: the grow arrow changes nothing.
617
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
618
+ expect(separator).toHaveAttribute('aria-valuenow', '400');
619
+ // Shift past the first point clamps to it.
620
+ fireEvent.keyDown(separator, {key: 'ArrowLeft', shiftKey: true});
621
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
622
+ fireEvent.keyDown(separator, {key: 'ArrowLeft', shiftKey: true});
623
+ expect(separator).toHaveAttribute('aria-valuenow', '100');
624
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
625
+ expect(separator).toHaveAttribute('aria-valuenow', '100');
626
+ });
627
+
628
+ it('with snap points past the bounds, a point beyond maxSize or minSize counts as that bound', () => {
629
+ render(
630
+ <Harness
631
+ config={{
632
+ defaultSize: 200,
633
+ minSize: 100,
634
+ maxSize: 400,
635
+ snaps: [50, 100, 200, 300, 450],
636
+ }}
637
+ />,
638
+ );
639
+ const separator = getSeparator();
640
+ act(() => separator.focus());
641
+ fireEvent.keyDown(separator, {key: 'End'});
642
+ expect(separator).toHaveAttribute('aria-valuenow', '400');
643
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
644
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
645
+ // The 450 point counts as the 400 bound.
646
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
647
+ expect(separator).toHaveAttribute('aria-valuenow', '400');
648
+ fireEvent.keyDown(separator, {key: 'Home'});
649
+ expect(separator).toHaveAttribute('aria-valuenow', '100');
650
+ // The 50 point counts as the 100 bound: nothing below it.
651
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
652
+ expect(separator).toHaveAttribute('aria-valuenow', '100');
653
+ });
654
+
655
+ it('with snap points, the arrows follow the reversed layout and the reading direction', () => {
656
+ const rtl = vi
657
+ .spyOn(window, 'getComputedStyle')
658
+ .mockImplementation(
659
+ () => ({direction: 'rtl'}) as unknown as CSSStyleDeclaration,
660
+ );
661
+ try {
662
+ // Reversed under RTL: the two inversions cancel, so ArrowRight grows.
663
+ render(
664
+ <Harness
665
+ config={{
666
+ defaultSize: 200,
667
+ minSize: 100,
668
+ maxSize: 400,
669
+ snaps: [100, 200, 300, 400],
670
+ }}
671
+ handleProps={{isReversed: true}}
672
+ />,
673
+ );
674
+ const separator = getSeparator();
675
+ act(() => separator.focus());
676
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
677
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
678
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
679
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
680
+ } finally {
681
+ rtl.mockRestore();
682
+ }
683
+ });
684
+
685
+ it('with snap points in a reversed layout, ArrowLeft moves to the next point and ArrowRight to the previous', () => {
686
+ render(
687
+ <Harness
688
+ config={{
689
+ defaultSize: 200,
690
+ minSize: 100,
691
+ maxSize: 400,
692
+ snaps: [100, 200, 300, 400],
693
+ }}
694
+ handleProps={{isReversed: true}}
695
+ />,
696
+ );
697
+ const separator = getSeparator();
698
+ act(() => separator.focus());
699
+ fireEvent.keyDown(separator, {key: 'ArrowLeft'});
700
+ expect(separator).toHaveAttribute('aria-valuenow', '300');
701
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
702
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
703
+ });
704
+
705
+ it('with snap points, the grow arrow opens a collapsed pane at the snap point nearest one step above its minimum', () => {
706
+ render(
707
+ <Harness
708
+ config={{
709
+ defaultSize: 200,
710
+ minSize: 100,
711
+ maxSize: 400,
712
+ collapsible: true,
713
+ snaps: [100, 200, 300, 400],
714
+ }}
715
+ />,
716
+ );
717
+ const separator = getSeparator();
718
+ act(() => separator.focus());
719
+ fireEvent.keyDown(separator, {key: 'Enter'});
720
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
721
+ // min + step is 110; the region can rest only on a point, so it opens
722
+ // on the nearest one (100).
723
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
724
+ expect(separator).not.toHaveAttribute('aria-valuetext');
725
+ expect(separator).toHaveAttribute('aria-valuenow', '100');
726
+ // From there the arrows move between points.
727
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
728
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
729
+ });
730
+
731
+ it('a grow arrow opens a hand-built collapsed region one step above its minimum', () => {
732
+ const props: ResizableProps = {
733
+ ...makeResizable(),
734
+ _isCollapsed: true,
735
+ _size: 0,
736
+ _collapsible: true,
737
+ };
738
+ render(<ResizeHandle resizable={props} label="Resize" />);
739
+ const separator = getSeparator();
740
+ act(() => separator.focus());
741
+ fireEvent.keyDown(separator, {key: 'ArrowRight'});
742
+ // The same move as the hook's own region: minimum plus one step.
743
+ expect(props._onResizeMove).toHaveBeenCalledWith(props._minSizePx + 10);
744
+ });
745
+
746
+ it('reopens on Enter even when the minimum size sits below the collapse threshold', () => {
747
+ render(
748
+ <Harness
749
+ config={{
750
+ defaultSize: 200,
751
+ minSize: 20,
752
+ maxSize: 400,
753
+ collapsible: true,
754
+ collapsedSize: 50,
755
+ }}
756
+ />,
757
+ );
758
+ const separator = getSeparator();
759
+ act(() => separator.focus());
760
+ fireEvent.keyDown(separator, {key: 'Enter'});
761
+ expect(separator).toHaveAttribute('aria-valuetext', 'Collapsed');
762
+ fireEvent.keyDown(separator, {key: 'Enter'});
763
+ expect(separator).not.toHaveAttribute('aria-valuetext');
764
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
765
+ });
766
+
205
767
  it('keeps aria-valuenow >= aria-valuemin and announces "Collapsed" while collapsed', () => {
206
768
  render(
207
769
  <Harness
@@ -243,7 +805,8 @@ describe('ResizeHandle', () => {
243
805
 
244
806
  fireEvent.keyDown(separator, {key: 'Enter'}); // expand again
245
807
  expect(separator).not.toHaveAttribute('aria-valuetext');
246
- expect(separator).toHaveAttribute('aria-valuenow', '100');
808
+ // Back to the size it had before collapsing, not the minimum.
809
+ expect(separator).toHaveAttribute('aria-valuenow', '200');
247
810
  });
248
811
 
249
812
  // --- Disabled guard ---