@astryxdesign/core 0.7.1-canary.ff40b78 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/dist/Button/Button.d.ts +1 -1
  3. package/dist/Button/Button.d.ts.map +1 -1
  4. package/dist/Button/Button.js +5 -2
  5. package/dist/Calendar/Calendar.d.ts.map +1 -1
  6. package/dist/Calendar/Calendar.js +191 -32
  7. package/dist/Chat/ChatComposerInput.d.ts +9 -0
  8. package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
  9. package/dist/Chat/ChatComposerInput.js +5 -0
  10. package/dist/Chat/ChatMessage.d.ts +11 -0
  11. package/dist/Chat/ChatMessage.d.ts.map +1 -1
  12. package/dist/Chat/ChatMessage.js +18 -9
  13. package/dist/Chat/useTriggerMenu.d.ts +9 -2
  14. package/dist/Chat/useTriggerMenu.d.ts.map +1 -1
  15. package/dist/Chat/useTriggerMenu.js +222 -50
  16. package/dist/CodeBlock/CodeBlock.d.ts +10 -1
  17. package/dist/CodeBlock/CodeBlock.d.ts.map +1 -1
  18. package/dist/CodeBlock/CodeBlock.js +158 -38
  19. package/dist/CodeBlock/tokenizer.d.ts +39 -0
  20. package/dist/CodeBlock/tokenizer.d.ts.map +1 -1
  21. package/dist/CodeBlock/tokenizer.js +108 -0
  22. package/dist/CommandPalette/CommandPalette.d.ts +9 -5
  23. package/dist/CommandPalette/CommandPalette.d.ts.map +1 -1
  24. package/dist/CommandPalette/CommandPalette.js +38 -20
  25. package/dist/CommandPalette/CommandPaletteEmpty.d.ts +4 -3
  26. package/dist/CommandPalette/CommandPaletteEmpty.d.ts.map +1 -1
  27. package/dist/CommandPalette/CommandPaletteEmpty.js +4 -3
  28. package/dist/CommandPalette/CommandPaletteInput.d.ts +5 -4
  29. package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
  30. package/dist/CommandPalette/CommandPaletteInput.js +35 -18
  31. package/dist/CommandPalette/CommandPaletteItem.d.ts +11 -6
  32. package/dist/CommandPalette/CommandPaletteItem.d.ts.map +1 -1
  33. package/dist/CommandPalette/CommandPaletteItem.js +8 -9
  34. package/dist/CommandPalette/CommandPaletteList.d.ts +4 -2
  35. package/dist/CommandPalette/CommandPaletteList.d.ts.map +1 -1
  36. package/dist/CommandPalette/CommandPaletteList.js +10 -5
  37. package/dist/ComplexSelector/ComplexSelector.d.ts +8 -5
  38. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  39. package/dist/ComplexSelector/ComplexSelector.js +12 -3
  40. package/dist/ContextMenu/ContextMenu.d.ts +3 -3
  41. package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
  42. package/dist/ContextMenu/ContextMenu.js +62 -48
  43. package/dist/DateInput/DateInput.js +1 -1
  44. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  45. package/dist/DateInput/MonthScroller.js +12 -4
  46. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  47. package/dist/DateRangeInput/DateRangeInput.js +12 -2
  48. package/dist/Dialog/Dialog.d.ts +1 -1
  49. package/dist/Dialog/Dialog.d.ts.map +1 -1
  50. package/dist/Dialog/Dialog.js +16 -5
  51. package/dist/Dialog/DialogHeader.d.ts +1 -1
  52. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  53. package/dist/Dialog/DialogHeader.js +7 -7
  54. package/dist/Divider/Divider.d.ts +1 -0
  55. package/dist/Divider/Divider.d.ts.map +1 -1
  56. package/dist/Divider/Divider.js +5 -4
  57. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  58. package/dist/DropdownMenu/DropdownMenu.js +161 -65
  59. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts +7 -5
  60. package/dist/DropdownMenu/DropdownMenuCheckboxItem.d.ts.map +1 -1
  61. package/dist/DropdownMenu/DropdownMenuCheckboxItem.js +12 -5
  62. package/dist/Field/InputClearButton.d.ts +8 -4
  63. package/dist/Field/InputClearButton.d.ts.map +1 -1
  64. package/dist/Field/InputClearButton.js +23 -7
  65. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  66. package/dist/FormLayout/FormLayout.js +1 -0
  67. package/dist/Grid/Grid.d.ts +13 -4
  68. package/dist/Grid/Grid.d.ts.map +1 -1
  69. package/dist/Grid/Grid.js +7 -1
  70. package/dist/HoverCard/useHoverCard.d.ts +1 -1
  71. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  72. package/dist/HoverCard/useHoverCard.js +67 -49
  73. package/dist/InputGroup/InputGroup.d.ts +2 -4
  74. package/dist/InputGroup/InputGroup.d.ts.map +1 -1
  75. package/dist/InputGroup/InputGroup.js +2 -4
  76. package/dist/InputGroup/groupStyles.d.ts +10 -2
  77. package/dist/InputGroup/groupStyles.d.ts.map +1 -1
  78. package/dist/InputGroup/groupStyles.js +19 -2
  79. package/dist/Layer/useLayerDismissal.d.ts +9 -9
  80. package/dist/Layer/useLayerDismissal.js +9 -9
  81. package/dist/Markdown/Markdown.d.ts.map +1 -1
  82. package/dist/Markdown/Markdown.js +25 -4
  83. package/dist/MobileNav/MobileNav.d.ts +7 -0
  84. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  85. package/dist/MobileNav/MobileNav.js +45 -2
  86. package/dist/MultiSelector/MultiSelector.d.ts +7 -4
  87. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  88. package/dist/MultiSelector/MultiSelector.js +24 -3
  89. package/dist/NumberInput/NumberInput.js +1 -1
  90. package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
  91. package/dist/PowerSearch/PowerSearchEditPopover.js +6 -0
  92. package/dist/Resizable/ResizeHandle.d.ts +1 -1
  93. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  94. package/dist/Resizable/ResizeHandle.js +83 -15
  95. package/dist/Resizable/useResizable.d.ts +23 -0
  96. package/dist/Resizable/useResizable.d.ts.map +1 -1
  97. package/dist/Resizable/useResizable.js +78 -0
  98. package/dist/Selector/Selector.d.ts +6 -3
  99. package/dist/Selector/Selector.d.ts.map +1 -1
  100. package/dist/Selector/Selector.js +28 -6
  101. package/dist/SideNav/SideNav.d.ts.map +1 -1
  102. package/dist/SideNav/SideNav.js +10 -2
  103. package/dist/Slider/Slider.d.ts.map +1 -1
  104. package/dist/Slider/Slider.js +18 -7
  105. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  106. package/dist/Tokenizer/Tokenizer.js +16 -4
  107. package/dist/Toolbar/Toolbar.d.ts +2 -1
  108. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  109. package/dist/Toolbar/Toolbar.js +3 -1
  110. package/dist/TreeList/TreeList.d.ts.map +1 -1
  111. package/dist/TreeList/TreeList.js +6 -3
  112. package/dist/TreeList/TreeListItem.d.ts.map +1 -1
  113. package/dist/TreeList/TreeListItem.js +7 -1
  114. package/dist/astryx.css +22 -0
  115. package/dist/hooks/useScrollOverflow.d.ts.map +1 -1
  116. package/dist/hooks/useScrollOverflow.js +55 -12
  117. package/dist/hooks/useTreeFocus.d.ts +21 -0
  118. package/dist/hooks/useTreeFocus.d.ts.map +1 -1
  119. package/dist/hooks/useTreeFocus.js +40 -2
  120. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  121. package/dist/i18n/generated-locales/en.generated.js +3 -0
  122. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  123. package/dist/i18n/generated-locales/pseudo.generated.js +3 -0
  124. package/dist/reset.a11y.chromium.spec.js +6 -1
  125. package/dist/reset.until-found.a11y.browser.spec.d.ts +2 -0
  126. package/dist/reset.until-found.a11y.browser.spec.d.ts.map +1 -0
  127. package/dist/reset.until-found.a11y.browser.spec.js +71 -0
  128. package/dist/theme/syntax/SyntaxTheme.d.ts.map +1 -1
  129. package/dist/theme/syntax/SyntaxTheme.js +43 -2
  130. package/dist/theme/syntax/defineSyntaxTheme.d.ts +18 -5
  131. package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
  132. package/dist/theme/syntax/defineSyntaxTheme.js +38 -9
  133. package/dist/theme/syntax/index.d.ts +1 -1
  134. package/dist/theme/syntax/index.d.ts.map +1 -1
  135. package/dist/theme/syntax/tokens.d.ts +12 -2
  136. package/dist/theme/syntax/tokens.d.ts.map +1 -1
  137. package/dist/theme/syntax/tokens.js +25 -4
  138. package/dist/utils/plainDate.d.ts +12 -0
  139. package/dist/utils/plainDate.d.ts.map +1 -1
  140. package/dist/utils/plainDate.js +63 -10
  141. package/locales/en.json +13 -1
  142. package/locales/pseudo.json +9 -0
  143. package/package.json +4 -4
  144. package/src/AlertDialog/AlertDialog.doc.mjs +6 -0
  145. package/src/AlertDialog/AlertDialog.spec.md +205 -0
  146. package/src/AlertDialog/AlertDialog.test.tsx +3 -14
  147. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +115 -0
  148. package/src/AlertDialog/__tests__/AlertDialog.a11y.renders.tsx +52 -0
  149. package/src/AlertDialog/__tests__/AlertDialog.a11y.states.ts +68 -0
  150. package/src/AlertDialog/__tests__/AlertDialog.a11y.test.tsx +144 -0
  151. package/src/Button/Button.test.tsx +37 -0
  152. package/src/Button/Button.tsx +5 -1
  153. package/src/Calendar/Calendar.doc.mjs +234 -60
  154. package/src/Calendar/Calendar.spec.md +209 -0
  155. package/src/Calendar/Calendar.test.tsx +559 -20
  156. package/src/Calendar/Calendar.tsx +258 -41
  157. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +126 -0
  158. package/src/Calendar/__tests__/Calendar.a11y.known-failures.ts +12 -0
  159. package/src/Calendar/__tests__/Calendar.a11y.renders.tsx +57 -0
  160. package/src/Calendar/__tests__/Calendar.a11y.states.ts +139 -0
  161. package/src/Calendar/__tests__/Calendar.a11y.test.tsx +137 -0
  162. package/src/Carousel/Carousel.test.tsx +54 -71
  163. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +192 -0
  164. package/src/Carousel/__tests__/Carousel.a11y.known-failures.ts +27 -0
  165. package/src/Carousel/__tests__/Carousel.a11y.renders.tsx +39 -0
  166. package/src/Carousel/__tests__/Carousel.a11y.states.ts +139 -0
  167. package/src/Carousel/__tests__/Carousel.a11y.test.tsx +127 -0
  168. package/src/Chat/ChatComposerInput.doc.mjs +1 -1
  169. package/src/Chat/ChatComposerInput.spec.md +94 -55
  170. package/src/Chat/ChatComposerInput.test.tsx +202 -1
  171. package/src/Chat/ChatComposerInput.tsx +13 -0
  172. package/src/Chat/ChatMessage.doc.mjs +31 -16
  173. package/src/Chat/ChatMessage.spec.md +265 -0
  174. package/src/Chat/ChatMessage.test.tsx +246 -0
  175. package/src/Chat/ChatMessage.tsx +30 -7
  176. package/src/Chat/ChatSystemMessage.spec.md +36 -37
  177. package/src/Chat/ChatSystemMessage.test.tsx +4 -4
  178. package/src/Chat/useTriggerMenu.test.tsx +994 -0
  179. package/src/Chat/useTriggerMenu.tsx +278 -46
  180. package/src/CodeBlock/CodeBlock.doc.mjs +7 -0
  181. package/src/CodeBlock/CodeBlock.spec.md +40 -41
  182. package/src/CodeBlock/CodeBlock.test.tsx +89 -9
  183. package/src/CodeBlock/CodeBlock.tsx +205 -25
  184. package/src/CodeBlock/__tests__/CodeBlockOverflowStop.a11y.chromium.spec.ts +125 -0
  185. package/src/CodeBlock/__tests__/DiffRowPaint.a11y.chromium.spec.ts +93 -0
  186. package/src/CodeBlock/tokenizer.test.ts +112 -0
  187. package/src/CodeBlock/tokenizer.ts +106 -0
  188. package/src/Collapsible/CollapsibleGroup.spec.md +17 -8
  189. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.renders.tsx +45 -0
  190. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.states.ts +139 -0
  191. package/src/Collapsible/__tests__/CollapsibleGroup.a11y.test.tsx +135 -0
  192. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +128 -0
  193. package/src/CommandPalette/CommandPalette.doc.mjs +126 -23
  194. package/src/CommandPalette/CommandPalette.spec.md +100 -49
  195. package/src/CommandPalette/CommandPalette.test.tsx +241 -2
  196. package/src/CommandPalette/CommandPalette.tsx +47 -21
  197. package/src/CommandPalette/CommandPaletteEmpty.tsx +4 -3
  198. package/src/CommandPalette/CommandPaletteInput.doc.mjs +61 -4
  199. package/src/CommandPalette/CommandPaletteInput.spec.md +215 -0
  200. package/src/CommandPalette/CommandPaletteInput.test.tsx +375 -51
  201. package/src/CommandPalette/CommandPaletteInput.tsx +74 -21
  202. package/src/CommandPalette/CommandPaletteItem.doc.mjs +73 -22
  203. package/src/CommandPalette/CommandPaletteItem.spec.md +223 -0
  204. package/src/CommandPalette/CommandPaletteItem.test.tsx +200 -5
  205. package/src/CommandPalette/CommandPaletteItem.tsx +16 -11
  206. package/src/CommandPalette/CommandPaletteList.doc.mjs +3 -3
  207. package/src/CommandPalette/CommandPaletteList.spec.md +41 -36
  208. package/src/CommandPalette/CommandPaletteList.test.tsx +80 -0
  209. package/src/CommandPalette/CommandPaletteList.tsx +10 -5
  210. package/src/CommandPalette/__tests__/CommandPalette.a11y.chromium.spec.ts +599 -0
  211. package/src/ComplexSelector/ComplexSelector.doc.mjs +10 -8
  212. package/src/ComplexSelector/ComplexSelector.spec.md +14 -9
  213. package/src/ComplexSelector/ComplexSelector.test.tsx +64 -16
  214. package/src/ComplexSelector/ComplexSelector.tsx +37 -8
  215. package/src/ContextMenu/ContextMenu.doc.mjs +6 -0
  216. package/src/ContextMenu/ContextMenu.spec.md +51 -53
  217. package/src/ContextMenu/ContextMenu.test.tsx +132 -1
  218. package/src/ContextMenu/ContextMenu.tsx +78 -48
  219. package/src/DateInput/DateInput.doc.mjs +52 -0
  220. package/src/DateInput/DateInput.spec.md +27 -15
  221. package/src/DateInput/DateInput.test.tsx +38 -0
  222. package/src/DateInput/DateInput.tsx +1 -1
  223. package/src/DateInput/DateInputTouch.test.tsx +22 -0
  224. package/src/DateInput/MonthScroller.tsx +12 -4
  225. package/src/DateRangeInput/DateRangeInput.test.tsx +49 -0
  226. package/src/DateRangeInput/DateRangeInput.tsx +20 -2
  227. package/src/Dialog/Dialog.test.tsx +27 -1
  228. package/src/Dialog/Dialog.tsx +16 -5
  229. package/src/Dialog/DialogHeader.test.tsx +27 -2
  230. package/src/Dialog/DialogHeader.tsx +7 -8
  231. package/src/Dialog/__tests__/Dialog.a11y.chromium.spec.ts +21 -14
  232. package/src/Divider/Divider.doc.mjs +5 -3
  233. package/src/Divider/Divider.spec.md +140 -54
  234. package/src/Divider/Divider.test.tsx +15 -0
  235. package/src/Divider/Divider.tsx +6 -4
  236. package/src/DropdownMenu/DropdownMenu.doc.mjs +2 -2
  237. package/src/DropdownMenu/DropdownMenu.spec.md +121 -45
  238. package/src/DropdownMenu/DropdownMenu.test.tsx +288 -23
  239. package/src/DropdownMenu/DropdownMenu.tsx +212 -81
  240. package/src/DropdownMenu/DropdownMenuCheckboxItem.doc.mjs +38 -2
  241. package/src/DropdownMenu/DropdownMenuCheckboxItem.tsx +32 -18
  242. package/src/DropdownMenu/DropdownMenuSelectable.test.tsx +146 -1
  243. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +136 -0
  244. package/src/DropdownMenu/__tests__/MenuButton.a11y.known-failures.ts +12 -0
  245. package/src/DropdownMenu/__tests__/MenuButton.a11y.renders.tsx +67 -0
  246. package/src/DropdownMenu/__tests__/MenuButton.a11y.states.ts +141 -0
  247. package/src/DropdownMenu/__tests__/MenuButton.a11y.test.tsx +138 -0
  248. package/src/DropdownMenu/__tests__/MenuHeaderFooter.a11y.chromium.spec.ts +40 -5
  249. package/src/Field/InputClearButton.test.tsx +29 -4
  250. package/src/Field/InputClearButton.tsx +26 -8
  251. package/src/FieldStatus/__tests__/StatusMessage.a11y.chromium.spec.ts +21 -14
  252. package/src/FormLayout/FormLayout.tsx +1 -0
  253. package/src/FormLayout/__snapshots__/FormLayout.test.tsx.snap +1 -1
  254. package/src/Grid/Grid.doc.mjs +14 -11
  255. package/src/Grid/Grid.spec.md +50 -32
  256. package/src/Grid/Grid.tsx +13 -4
  257. package/src/HoverCard/HoverCard.test.tsx +46 -1
  258. package/src/HoverCard/useHoverCard.tsx +68 -48
  259. package/src/InputGroup/InputGroup.doc.mjs +11 -1
  260. package/src/InputGroup/InputGroup.tsx +2 -4
  261. package/src/InputGroup/groupStyles.ts +40 -2
  262. package/src/Layer/useLayerDismissal.ts +9 -9
  263. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +142 -0
  264. package/src/Link/__tests__/Link.a11y.renders.tsx +97 -0
  265. package/src/Link/__tests__/Link.a11y.states.ts +104 -0
  266. package/src/Link/__tests__/Link.a11y.test.tsx +127 -0
  267. package/src/Markdown/Markdown.doc.mjs +1 -1
  268. package/src/Markdown/Markdown.fr23.perf.test.ts +161 -40
  269. package/src/Markdown/Markdown.spec.md +45 -23
  270. package/src/Markdown/Markdown.test.tsx +38 -0
  271. package/src/Markdown/Markdown.tsx +24 -2
  272. package/src/Markdown/plugins/semanticFence.test.tsx +3 -2
  273. package/src/MobileNav/MobileNav.tsx +50 -2
  274. package/src/MobileNav/MobileNavFocus.test.tsx +553 -0
  275. package/src/MoreMenu/MoreMenu.spec.md +18 -7
  276. package/src/MoreMenu/MoreMenu.test.tsx +38 -0
  277. package/src/MultiSelector/MultiSelector.doc.mjs +8 -8
  278. package/src/MultiSelector/MultiSelector.spec.md +1 -1
  279. package/src/MultiSelector/MultiSelector.test.tsx +188 -21
  280. package/src/MultiSelector/MultiSelector.tsx +50 -11
  281. package/src/NumberInput/NumberInput.test.tsx +24 -0
  282. package/src/NumberInput/NumberInput.tsx +1 -1
  283. package/src/NumberInput/__tests__/NumberInput.a11y.chromium.spec.ts +22 -15
  284. package/src/PowerSearch/PowerSearchEditPopover.test.tsx +78 -0
  285. package/src/PowerSearch/PowerSearchEditPopover.tsx +6 -0
  286. package/src/Resizable/Resizable.spec.md +41 -9
  287. package/src/Resizable/ResizeHandle.test.tsx +565 -2
  288. package/src/Resizable/ResizeHandle.tsx +126 -21
  289. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +119 -0
  290. package/src/Resizable/__tests__/ResizeHandle.a11y.known-failures.ts +39 -0
  291. package/src/Resizable/__tests__/ResizeHandle.a11y.renders.tsx +80 -0
  292. package/src/Resizable/__tests__/ResizeHandle.a11y.states.ts +197 -0
  293. package/src/Resizable/__tests__/ResizeHandle.a11y.test.tsx +123 -0
  294. package/src/Resizable/useResizable.ts +95 -0
  295. package/src/Selector/Selector.doc.mjs +7 -6
  296. package/src/Selector/Selector.spec.md +37 -24
  297. package/src/Selector/Selector.test.tsx +176 -10
  298. package/src/Selector/Selector.tsx +59 -14
  299. package/src/Selector/__tests__/Combobox.a11y.known-failures.ts +27 -0
  300. package/src/Selector/__tests__/Combobox.a11y.states.ts +145 -0
  301. package/src/Selector/__tests__/Selector.combobox.a11y.chromium.spec.ts +108 -0
  302. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +126 -0
  303. package/src/SideNav/SideNav.test.tsx +35 -0
  304. package/src/SideNav/SideNav.tsx +16 -2
  305. package/src/Slider/Slider.spec.md +43 -17
  306. package/src/Slider/Slider.test.tsx +134 -0
  307. package/src/Slider/Slider.tsx +29 -6
  308. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +112 -0
  309. package/src/Slider/__tests__/Slider.a11y.known-failures.ts +12 -0
  310. package/src/Slider/__tests__/Slider.a11y.renders.tsx +42 -0
  311. package/src/Slider/__tests__/Slider.a11y.states.ts +106 -0
  312. package/src/Slider/__tests__/Slider.a11y.test.tsx +105 -0
  313. package/src/Switch/__tests__/Switch.a11y.chromium.spec.ts +25 -18
  314. package/src/TabList/__tests__/Tabs.a11y.chromium.spec.ts +19 -12
  315. package/src/TextInput/__tests__/TextInput.a11y.chromium.spec.ts +21 -14
  316. package/src/ToggleButton/__tests__/ToggleButton.a11y.chromium.spec.ts +18 -11
  317. package/src/Tokenizer/Tokenizer.test.tsx +87 -0
  318. package/src/Tokenizer/Tokenizer.tsx +31 -3
  319. package/src/Toolbar/Toolbar.test.tsx +131 -4
  320. package/src/Toolbar/Toolbar.tsx +3 -1
  321. package/src/TreeList/TreeList.spec.md +33 -8
  322. package/src/TreeList/TreeList.test.tsx +72 -0
  323. package/src/TreeList/TreeList.tsx +10 -6
  324. package/src/TreeList/TreeListItem.tsx +5 -0
  325. package/src/TreeList/__tests__/TreeList.a11y.chromium.spec.ts +125 -0
  326. package/src/TreeList/__tests__/TreeList.a11y.renders.tsx +44 -0
  327. package/src/TreeList/__tests__/TreeList.a11y.states.ts +164 -0
  328. package/src/TreeList/__tests__/TreeList.a11y.test.tsx +138 -0
  329. package/src/TreeList/__tests__/TreeListRowNames.a11y.chromium.spec.ts +82 -0
  330. package/src/hooks/useScrollOverflow.test.tsx +222 -0
  331. package/src/hooks/useScrollOverflow.ts +55 -13
  332. package/src/hooks/useTreeFocus.test.tsx +37 -0
  333. package/src/hooks/useTreeFocus.ts +55 -2
  334. package/src/i18n/generated-locales/en.generated.ts +3 -0
  335. package/src/i18n/generated-locales/pseudo.generated.ts +3 -0
  336. package/src/reset.a11y.chromium.spec.ts +9 -1
  337. package/src/reset.css +8 -3
  338. package/src/reset.until-found.a11y.browser.spec.ts +75 -0
  339. package/src/reset.until-found.test.ts +32 -0
  340. package/src/stylexValidation.source-build.test.mjs +42 -0
  341. package/src/theme/syntax/SyntaxTheme.tsx +46 -5
  342. package/src/theme/syntax/defineSyntaxTheme.diff.test.ts +91 -0
  343. package/src/theme/syntax/defineSyntaxTheme.ts +75 -15
  344. package/src/theme/syntax/index.ts +5 -1
  345. package/src/theme/syntax/tokens.ts +32 -2
  346. package/src/theme/syntax/useSyntaxTheme.diff.test.tsx +40 -0
  347. package/src/utils/plainDate.test.ts +147 -0
  348. package/src/utils/plainDate.ts +75 -10
@@ -0,0 +1,164 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file TreeList.a11y.states.ts
5
+ * @input TreeList's representative tree-view states and the facts TREE_VIEW_PATTERN reads
6
+ * @output TREE_LIST_BINDING_STATES, the shared item data, and the explicit ownership exclusions for this binding
7
+ * @position Binding inventory shared by the jsdom test, the Chromium spec, and the story (component:TreeList AR1)
8
+ */
9
+
10
+ import type {TreeViewStateFacts} from '@astryxdesign/a11y-spec';
11
+
12
+ export interface TreeListBindingState {
13
+ readonly id: string;
14
+ readonly summary: string;
15
+ readonly storyId: string;
16
+ /** `data-tree-id` of the subject row. */
17
+ readonly subject: string;
18
+ /** `data-tree-id` of each relation row the facts say exists. */
19
+ readonly relations: Readonly<
20
+ Partial<
21
+ Record<
22
+ 'first-child' | 'parent' | 'next' | 'previous' | 'typeahead-target',
23
+ string
24
+ >
25
+ >
26
+ > & {readonly first: string; readonly last: string};
27
+ readonly facts: TreeViewStateFacts;
28
+ /** Which parent rows start expanded. */
29
+ readonly expanded: ReadonlyArray<string>;
30
+ /** Which rows are disabled. */
31
+ readonly disabled?: ReadonlyArray<string>;
32
+ readonly direction?: 'ltr' | 'rtl';
33
+ }
34
+
35
+ export const TREE_LIST_LABEL = 'Files';
36
+
37
+ function facts(
38
+ overrides: Partial<TreeViewStateFacts> = {},
39
+ ): TreeViewStateFacts {
40
+ return {
41
+ hasChildren: true,
42
+ expanded: false,
43
+ disabled: false,
44
+ tabbable: true,
45
+ level: 1,
46
+ position: 1,
47
+ setSize: 3,
48
+ hasNext: true,
49
+ hasPrevious: false,
50
+ hasParent: false,
51
+ rtl: false,
52
+ typeaheadCharacter: 't',
53
+ enterToggles: true,
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ const story = (id: string) => `a11y-treelist-tree-view--${id}`;
59
+
60
+ export const TREE_LIST_BINDING_STATES = [
61
+ {
62
+ id: 'closed-parent-tab-stop',
63
+ summary: "the closed Documents parent, the tree's one Tab stop",
64
+ storyId: story('closed-parent-tab-stop'),
65
+ subject: 'docs',
66
+ relations: {
67
+ next: 'photos',
68
+ first: 'docs',
69
+ last: 'trash',
70
+ 'first-child': 'annual',
71
+ 'typeahead-target': 'trash',
72
+ },
73
+ facts: facts(),
74
+ expanded: [],
75
+ },
76
+ {
77
+ id: 'open-parent-tab-stop',
78
+ summary: 'the open Documents parent with its child group',
79
+ storyId: story('open-parent-tab-stop'),
80
+ subject: 'docs',
81
+ relations: {
82
+ next: 'annual',
83
+ first: 'docs',
84
+ last: 'trash',
85
+ 'first-child': 'annual',
86
+ 'typeahead-target': 'trash',
87
+ },
88
+ facts: facts({expanded: true}),
89
+ expanded: ['docs'],
90
+ },
91
+ {
92
+ id: 'end-node',
93
+ summary: 'the Photos end node, which is not the Tab stop',
94
+ storyId: story('end-node'),
95
+ subject: 'photos',
96
+ relations: {
97
+ next: 'trash',
98
+ previous: 'docs',
99
+ first: 'docs',
100
+ last: 'trash',
101
+ 'typeahead-target': 'docs',
102
+ },
103
+ facts: facts({
104
+ hasChildren: false,
105
+ tabbable: false,
106
+ position: 2,
107
+ hasPrevious: true,
108
+ typeaheadCharacter: 'd',
109
+ enterToggles: false,
110
+ }),
111
+ expanded: [],
112
+ },
113
+ {
114
+ id: 'closed-parent-rtl',
115
+ summary: 'the closed Documents parent in a right-to-left tree',
116
+ storyId: story('closed-parent-rtl'),
117
+ subject: 'docs',
118
+ relations: {
119
+ next: 'photos',
120
+ first: 'docs',
121
+ last: 'trash',
122
+ 'first-child': 'annual',
123
+ 'typeahead-target': 'trash',
124
+ },
125
+ facts: facts({rtl: true}),
126
+ expanded: [],
127
+ direction: 'rtl',
128
+ },
129
+ {
130
+ id: 'disabled-end-node',
131
+ summary:
132
+ 'the disabled Trash end node: exposed and positioned, not operable',
133
+ storyId: story('disabled-end-node'),
134
+ subject: 'trash',
135
+ relations: {previous: 'photos', first: 'docs', last: 'trash'},
136
+ facts: facts({
137
+ hasChildren: false,
138
+ disabled: true,
139
+ tabbable: false,
140
+ position: 3,
141
+ hasNext: false,
142
+ hasPrevious: true,
143
+ typeaheadCharacter: null,
144
+ enterToggles: false,
145
+ }),
146
+ expanded: [],
147
+ disabled: ['trash'],
148
+ },
149
+ ] as const satisfies ReadonlyArray<TreeListBindingState>;
150
+
151
+ export const TREE_LIST_EXCLUSIONS = [
152
+ {
153
+ id: 'row-name-composition',
154
+ owner: 'component:TreeList AR1 and TreeList.test.tsx',
155
+ reason:
156
+ "This binding proves each row has a treeitem role and a non-empty name. Which text makes up that name — the label alone, without the chevron toggle, description, or start and end content — is the record's row-naming requirement, proven by TreeList's own name tests and browser evidence rather than by the shared pattern.",
157
+ },
158
+ {
159
+ id: 'selection-keyboard',
160
+ owner: 'component:TreeList AR1 and DEC-2',
161
+ reason:
162
+ 'Selection is caller-owned (isSelected) with at most one row selected; the tree adopts no selection keyboard (Shift or Control combinations; Space is activation, covered by the contract) and not the optional * key, so the contract asserts only that the tree makes no multiselect claim.',
163
+ },
164
+ ] as const;
@@ -0,0 +1,138 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ /** @vitest-environment jsdom */
3
+
4
+ /**
5
+ * @file TreeList.a11y.test.tsx
6
+ * @input TREE_VIEW_PATTERN bound to TreeList's representative states
7
+ * @output jsdom-lane proof of the DOM expectations and an honest "unrun" report for the browser layers
8
+ * @position Binding test for component:TreeList AR1 (the Chromium spec covers the rest)
9
+ */
10
+
11
+ import {cleanup, render, screen} from '@testing-library/react';
12
+ import {describe, expect, it} from 'vitest';
13
+ import {
14
+ TREE_VIEW_PATTERN,
15
+ checkAccessibilitySpec,
16
+ createJsdomHarness,
17
+ expectAccessibilitySpec,
18
+ summarize,
19
+ type BindingResult,
20
+ } from '@astryxdesign/a11y-spec';
21
+ import {renderTreeListState} from './TreeList.a11y.renders';
22
+ import {
23
+ TREE_LIST_BINDING_STATES,
24
+ TREE_LIST_EXCLUSIONS,
25
+ type TreeListBindingState,
26
+ } from './TreeList.a11y.states';
27
+
28
+ function row(id: string): Element {
29
+ const element = document.querySelector(
30
+ `[role="treeitem"][data-tree-id="${id}"]`,
31
+ );
32
+ if (element == null) {
33
+ throw new Error(`no treeitem row "${id}"`);
34
+ }
35
+ return element;
36
+ }
37
+
38
+ function relatedFor(
39
+ state: TreeListBindingState,
40
+ ): Readonly<Record<string, Element>> {
41
+ const related: Record<string, Element> = {
42
+ tree: screen.getByRole('tree'),
43
+ before: screen.getByRole('button', {name: 'Before the tree'}),
44
+ after: screen.getByRole('button', {name: 'After the tree'}),
45
+ };
46
+ for (const [name, id] of Object.entries(state.relations)) {
47
+ const element = document.querySelector(
48
+ `[role="treeitem"][data-tree-id="${id}"]`,
49
+ );
50
+ if (element != null) {
51
+ related[name] = element;
52
+ }
53
+ }
54
+ const group = row(state.subject).querySelector('[role="group"]');
55
+ if (group != null) {
56
+ related.group = group;
57
+ }
58
+ return related;
59
+ }
60
+
61
+ async function checkState(state: TreeListBindingState): Promise<BindingResult> {
62
+ return checkAccessibilitySpec({
63
+ spec: TREE_VIEW_PATTERN,
64
+ binding: 'TreeList',
65
+ state: state.id,
66
+ facts: state.facts,
67
+ mount: async () => {
68
+ render(renderTreeListState(state));
69
+ return createJsdomHarness({
70
+ subject: row(state.subject),
71
+ related: relatedFor(state),
72
+ });
73
+ },
74
+ unmount: cleanup,
75
+ });
76
+ }
77
+
78
+ describe('TreeList — tree-view pattern, jsdom lane', () => {
79
+ it.each(
80
+ TREE_LIST_BINDING_STATES.map(
81
+ state => [state.id, state.summary, state] as const,
82
+ ),
83
+ )('%s — %s', async (_id, _summary, state) => {
84
+ await expectAccessibilitySpec({
85
+ spec: TREE_VIEW_PATTERN,
86
+ binding: 'TreeList',
87
+ state: state.id,
88
+ facts: state.facts,
89
+ render: () => {
90
+ render(renderTreeListState(state));
91
+ },
92
+ subject: () => row(state.subject),
93
+ related: () => relatedFor(state),
94
+ cleanup,
95
+ });
96
+ });
97
+
98
+ it('runs the DOM layer and reports the browser-owned outcomes as unrun', async () => {
99
+ const results: BindingResult[] = [];
100
+ for (const state of TREE_LIST_BINDING_STATES) {
101
+ results.push(await checkState(state));
102
+ }
103
+ const report = summarize(TREE_VIEW_PATTERN, results);
104
+ expect(report.counts.pass).toBeGreaterThan(0);
105
+ expect(report.unrunLayers).toEqual(['accessibility-tree', 'real-browser']);
106
+ expect(report.counts.unexpectedPass).toBe(0);
107
+ });
108
+ });
109
+
110
+ describe('TreeList tree-view binding inventory', () => {
111
+ it('gives every representative state a distinct checked-in story', () => {
112
+ const stories = TREE_LIST_BINDING_STATES.map(state => state.storyId);
113
+ expect(new Set(stories).size).toBe(stories.length);
114
+ });
115
+
116
+ it('keeps row-name composition and selection ownership explicit', () => {
117
+ expect(TREE_LIST_EXCLUSIONS.map(exclusion => exclusion.id)).toEqual([
118
+ 'row-name-composition',
119
+ 'selection-keyboard',
120
+ ]);
121
+ for (const exclusion of TREE_LIST_EXCLUSIONS) {
122
+ expect(exclusion.owner.length, exclusion.id).toBeGreaterThan(20);
123
+ expect(exclusion.reason.length, exclusion.id).toBeGreaterThan(80);
124
+ }
125
+ });
126
+
127
+ it('renders the subject row, the tree, and the first/last rows for every state', () => {
128
+ for (const state of TREE_LIST_BINDING_STATES) {
129
+ render(renderTreeListState(state));
130
+ expect(row(state.subject), state.id).toBeTruthy();
131
+ const related = relatedFor(state);
132
+ expect(Object.keys(related), state.id).toEqual(
133
+ expect.arrayContaining(['tree', 'before', 'after', 'first', 'last']),
134
+ );
135
+ cleanup();
136
+ }
137
+ });
138
+ });
@@ -0,0 +1,82 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file TreeListRowNames.a11y.chromium.spec.ts
5
+ * @input The built TreeList row-names story in real Chromium
6
+ * @output Proof that the browser names every treeitem by its label alone and describes it by its description — the chevron toggle and inner link/button do not leak into the row's name
7
+ * @position Chromium evidence for component:TreeList AR1 (component:TreeList's row-naming implementation gap, closed)
8
+ */
9
+
10
+ import {expect, test, type Page} from '@playwright/test';
11
+ import {
12
+ createChromiumHarness,
13
+ holdMotionStill,
14
+ } from '@astryxdesign/a11y-spec/chromium';
15
+ import {
16
+ DEFAULT_STORYBOOK_DIR,
17
+ serveStorybook,
18
+ type StaticServer,
19
+ } from '@astryxdesign/a11y-spec/storybook';
20
+
21
+ const STORY_ID = 'a11y-treelist-row-names--row-names';
22
+
23
+ const ROWS = [
24
+ {id: 'docs', name: 'Documents', description: 'Shared with the team'},
25
+ // Carries start content and an end-content button: neither joins the name.
26
+ {id: 'budget', name: 'Budget', description: 'Updated yesterday'},
27
+ {id: 'report', name: 'Report', description: 'Opens the report'},
28
+ {id: 'export', name: 'Export', description: 'Downloads a file'},
29
+ ] as const;
30
+
31
+ let storybook: StaticServer;
32
+
33
+ test.beforeAll(async () => {
34
+ storybook = await serveStorybook(
35
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
36
+ );
37
+ });
38
+
39
+ test.afterAll(async () => {
40
+ await storybook?.close();
41
+ });
42
+
43
+ async function mount(page: Page): Promise<void> {
44
+ await page.goto(
45
+ `${storybook.origin}/iframe.html?id=${STORY_ID}&viewMode=story`,
46
+ {waitUntil: 'load'},
47
+ );
48
+ await holdMotionStill(page);
49
+ await page.locator('[role="tree"]').waitFor({state: 'attached'});
50
+ }
51
+
52
+ for (const row of ROWS) {
53
+ test(`the browser names the "${row.name}" row by its label and describes it by its description`, async ({
54
+ page,
55
+ }) => {
56
+ await mount(page);
57
+ const cdp = await page.context().newCDPSession(page);
58
+ const harness = createChromiumHarness({
59
+ page,
60
+ cdp,
61
+ subject: page.locator(`[role="treeitem"][data-tree-id="${row.id}"]`),
62
+ });
63
+ const computed = await (await harness.subject()).computed();
64
+ expect(computed.role).toBe('treeitem');
65
+ expect(computed.name).toBe(row.name);
66
+ expect(computed.description).toBe(row.description);
67
+ });
68
+ }
69
+
70
+ test('typing a letter matches the label, so a row whose text begins with start content is reachable', async ({
71
+ page,
72
+ }) => {
73
+ await mount(page);
74
+ const docsRow = page.locator('[role="treeitem"][data-tree-id="docs"]');
75
+ await docsRow.focus();
76
+ // The Budget row's text begins with its start content ("3"), so a
77
+ // whole-text match never finds it under "b"; the label match does.
78
+ await page.keyboard.press('b');
79
+ await expect(
80
+ page.locator('[role="treeitem"][data-tree-id="budget"]'),
81
+ ).toBeFocused();
82
+ });
@@ -0,0 +1,222 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file useScrollOverflow.test.tsx
5
+ * @input Uses vitest, @testing-library/react, useScrollOverflow
6
+ * @output Unit tests for the hook's measurement and its rendered-only gate
7
+ * @position Testing; jsdom lays nothing out, so the scroll box is faked and
8
+ * `checkVisibility` is stubbed per element.
9
+ */
10
+
11
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
12
+ import {act, renderHook} from '@testing-library/react';
13
+ import {useScrollOverflow} from './useScrollOverflow';
14
+
15
+ // jsdom has no ResizeObserver. This one hands the test the shared observer's
16
+ // callback so a resize can be delivered on demand; the registration-time
17
+ // measurement comes from `observeResize` itself.
18
+ let deliverResize: ((entries: ResizeObserverEntry[]) => void) | null = null;
19
+
20
+ class FakeResizeObserver implements ResizeObserver {
21
+ constructor(callback: ResizeObserverCallback) {
22
+ deliverResize = entries => callback(entries, this);
23
+ }
24
+ observe = vi.fn();
25
+ unobserve = vi.fn();
26
+ disconnect = vi.fn();
27
+ }
28
+
29
+ // The shared observer reads only `target` off an entry.
30
+ function resizeEntry(target: Element): ResizeObserverEntry {
31
+ return {target} as unknown as ResizeObserverEntry;
32
+ }
33
+
34
+ type Reads = {scrollWidth: number; clientWidth: number; scrollLeft: number};
35
+
36
+ // Fakes an overflowing scroll box and counts every layout read on it.
37
+ function fakeScrollBox(
38
+ el: HTMLElement,
39
+ box: {scrollWidth: number; clientWidth: number; scrollLeft?: number},
40
+ ): Reads {
41
+ const reads: Reads = {scrollWidth: 0, clientWidth: 0, scrollLeft: 0};
42
+ const values: Reads = {scrollLeft: 0, ...box};
43
+ for (const name of Object.keys(reads) as (keyof Reads)[]) {
44
+ Object.defineProperty(el, name, {
45
+ configurable: true,
46
+ get() {
47
+ reads[name] += 1;
48
+ return values[name];
49
+ },
50
+ });
51
+ }
52
+ return reads;
53
+ }
54
+
55
+ function totalReads(reads: Reads): number {
56
+ return reads.scrollWidth + reads.clientWidth + reads.scrollLeft;
57
+ }
58
+
59
+ function stubRendered(el: HTMLElement, rendered: boolean | undefined) {
60
+ Object.defineProperty(el, 'checkVisibility', {
61
+ configurable: true,
62
+ value: rendered === undefined ? undefined : () => rendered,
63
+ });
64
+ }
65
+
66
+ beforeEach(() => {
67
+ vi.stubGlobal('ResizeObserver', FakeResizeObserver);
68
+ });
69
+
70
+ afterEach(() => {
71
+ vi.unstubAllGlobals();
72
+ deliverResize = null;
73
+ });
74
+
75
+ describe('useScrollOverflow', () => {
76
+ it('measures a rendered container when the ref attaches', () => {
77
+ const el = document.createElement('div');
78
+ const reads = fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300});
79
+ stubRendered(el, true);
80
+ const {result, unmount} = renderHook(() => useScrollOverflow());
81
+
82
+ act(() => {
83
+ result.current.scrollRef(el);
84
+ });
85
+
86
+ expect(result.current.hasOverflow).toBe(true);
87
+ expect(result.current.overflowStart).toBe(false);
88
+ expect(result.current.overflowEnd).toBe(true);
89
+ expect(totalReads(reads)).toBeGreaterThan(0);
90
+ unmount();
91
+ });
92
+
93
+ it('reads no layout while the container is not rendered', () => {
94
+ const el = document.createElement('div');
95
+ const reads = fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300});
96
+ stubRendered(el, false);
97
+ const {result, unmount} = renderHook(() => useScrollOverflow());
98
+
99
+ act(() => {
100
+ result.current.scrollRef(el);
101
+ });
102
+ // A resize delivered for the hidden box (a `display: none` or a
103
+ // `content-visibility: hidden` ancestor reports a zero box) is skipped
104
+ // too; a scroll is not (the next case).
105
+ act(() => {
106
+ deliverResize?.([resizeEntry(el)]);
107
+ });
108
+
109
+ expect(totalReads(reads)).toBe(0);
110
+ expect(result.current.hasOverflow).toBe(false);
111
+ expect(result.current.overflowEnd).toBe(false);
112
+ unmount();
113
+ });
114
+
115
+ it('keeps the last state while hidden and measures again on the reveal', () => {
116
+ const el = document.createElement('div');
117
+ const reads = fakeScrollBox(el, {
118
+ scrollWidth: 600,
119
+ clientWidth: 300,
120
+ scrollLeft: 150,
121
+ });
122
+ stubRendered(el, true);
123
+ const {result, unmount} = renderHook(() => useScrollOverflow());
124
+
125
+ act(() => {
126
+ result.current.scrollRef(el);
127
+ });
128
+ expect(result.current.overflowStart).toBe(true);
129
+ expect(result.current.overflowEnd).toBe(true);
130
+
131
+ stubRendered(el, false);
132
+ const readsBeforeHide = totalReads(reads);
133
+ act(() => {
134
+ deliverResize?.([resizeEntry(el)]);
135
+ });
136
+ expect(totalReads(reads)).toBe(readsBeforeHide);
137
+ expect(result.current.overflowStart).toBe(true);
138
+ expect(result.current.overflowEnd).toBe(true);
139
+
140
+ // Revealed at the end edge with a new size: the resize that follows the
141
+ // reveal measures (a reveal at the same size has no resize, and relies on
142
+ // the scroll having been measured: the next case).
143
+ stubRendered(el, true);
144
+ fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300, scrollLeft: 300});
145
+ act(() => {
146
+ deliverResize?.([resizeEntry(el)]);
147
+ });
148
+ expect(result.current.overflowStart).toBe(true);
149
+ expect(result.current.overflowEnd).toBe(false);
150
+ unmount();
151
+ });
152
+
153
+ it('measures a scroll while hidden, so a reveal at the same size is already right', () => {
154
+ const el = document.createElement('div');
155
+ fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300});
156
+ stubRendered(el, true);
157
+ const {result, unmount} = renderHook(() => useScrollOverflow());
158
+
159
+ act(() => {
160
+ result.current.scrollRef(el);
161
+ });
162
+ expect(result.current.overflowStart).toBe(false);
163
+ expect(result.current.overflowEnd).toBe(true);
164
+
165
+ // Hidden, then scrolled by script to the end edge (a selected tab's
166
+ // reveal under a hidden panel): the scroll is measured.
167
+ stubRendered(el, false);
168
+ const scrolled = fakeScrollBox(el, {
169
+ scrollWidth: 600,
170
+ clientWidth: 300,
171
+ scrollLeft: 300,
172
+ });
173
+ act(() => {
174
+ el.dispatchEvent(new Event('scroll'));
175
+ });
176
+ expect(totalReads(scrolled)).toBeGreaterThan(0);
177
+ expect(result.current.overflowStart).toBe(true);
178
+ expect(result.current.overflowEnd).toBe(false);
179
+
180
+ // Revealed at the same size: no resize observation comes, and none is
181
+ // needed.
182
+ stubRendered(el, true);
183
+ expect(result.current.overflowStart).toBe(true);
184
+ expect(result.current.overflowEnd).toBe(false);
185
+ unmount();
186
+ });
187
+
188
+ it('measures in a browser without checkVisibility', () => {
189
+ const el = document.createElement('div');
190
+ const reads = fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300});
191
+ stubRendered(el, undefined);
192
+ const {result, unmount} = renderHook(() => useScrollOverflow());
193
+
194
+ act(() => {
195
+ result.current.scrollRef(el);
196
+ });
197
+
198
+ expect(result.current.hasOverflow).toBe(true);
199
+ expect(totalReads(reads)).toBeGreaterThan(0);
200
+ unmount();
201
+ });
202
+
203
+ it('follows a scroll on a rendered container', () => {
204
+ const el = document.createElement('div');
205
+ fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300});
206
+ stubRendered(el, true);
207
+ const {result, unmount} = renderHook(() => useScrollOverflow());
208
+
209
+ act(() => {
210
+ result.current.scrollRef(el);
211
+ });
212
+ expect(result.current.overflowStart).toBe(false);
213
+
214
+ fakeScrollBox(el, {scrollWidth: 600, clientWidth: 300, scrollLeft: 300});
215
+ act(() => {
216
+ el.dispatchEvent(new Event('scroll'));
217
+ });
218
+ expect(result.current.overflowStart).toBe(true);
219
+ expect(result.current.overflowEnd).toBe(false);
220
+ unmount();
221
+ });
222
+ });