@astryxdesign/core 0.7.5 → 0.7.6-canary.0ef60dd

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 (559) hide show
  1. package/dist/AlertDialog/AlertDialog.d.ts +1 -1
  2. package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
  3. package/dist/AlertDialog/AlertDialog.js +5 -5
  4. package/dist/Avatar/Avatar.d.ts.map +1 -1
  5. package/dist/Avatar/Avatar.js +12 -3
  6. package/dist/Badge/Badge.d.ts.map +1 -1
  7. package/dist/Badge/Badge.js +7 -1
  8. package/dist/BottomSheet/BottomSheet.js +1 -1
  9. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  10. package/dist/BottomSheet/BottomSheetPanel.js +2 -1
  11. package/dist/BottomSheet/useSheetGestures.d.ts +12 -0
  12. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  13. package/dist/BottomSheet/useSheetGestures.js +30 -3
  14. package/dist/Button/Button.js +4 -4
  15. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  16. package/dist/CheckboxInput/CheckboxInput.js +6 -5
  17. package/dist/ClickableCard/ClickableCard.d.ts.map +1 -1
  18. package/dist/ClickableCard/ClickableCard.js +37 -4
  19. package/dist/DateInput/DateInput.d.ts.map +1 -1
  20. package/dist/DateInput/DateInput.js +12 -4
  21. package/dist/DateInput/MonthScroller.d.ts.map +1 -1
  22. package/dist/DateInput/MonthScroller.js +101 -15
  23. package/dist/DateInput/TouchDateField.d.ts.map +1 -1
  24. package/dist/DateInput/TouchDateField.js +8 -3
  25. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  26. package/dist/DateRangeInput/DateRangeInput.js +1 -1
  27. package/dist/DateTimeInput/DateTimeInput.d.ts +5 -2
  28. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  29. package/dist/DateTimeInput/DateTimeInput.js +23 -12
  30. package/dist/DateTimeInput/NativeTimeSegment.d.ts.map +1 -1
  31. package/dist/DateTimeInput/NativeTimeSegment.js +5 -4
  32. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  33. package/dist/DateTimeInput/TouchDateTimeField.js +4 -3
  34. package/dist/Dialog/Dialog.d.ts.map +1 -1
  35. package/dist/Dialog/Dialog.js +34 -9
  36. package/dist/Dialog/DialogHeader.d.ts.map +1 -1
  37. package/dist/Dialog/DialogHeader.js +5 -4
  38. package/dist/Divider/Divider.d.ts.map +1 -1
  39. package/dist/Divider/Divider.js +2 -1
  40. package/dist/Drawer/Drawer.d.ts +1 -0
  41. package/dist/Drawer/Drawer.d.ts.map +1 -1
  42. package/dist/Drawer/Drawer.js +7 -2
  43. package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
  44. package/dist/DropdownMenu/DropdownMenu.js +7 -3
  45. package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -1
  46. package/dist/DropdownMenu/MenuBottomSheetActionList.js +6 -5
  47. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts +20 -2
  48. package/dist/DropdownMenu/lazyMenuBottomSheet.d.ts.map +1 -1
  49. package/dist/DropdownMenu/lazyMenuBottomSheet.js +31 -3
  50. package/dist/DropdownMenu/renderDropdownItems.d.ts.map +1 -1
  51. package/dist/DropdownMenu/renderDropdownItems.js +21 -16
  52. package/dist/EmptyState/EmptyState.d.ts.map +1 -1
  53. package/dist/EmptyState/EmptyState.js +2 -1
  54. package/dist/Field/FieldLabel.d.ts +1 -1
  55. package/dist/Field/FieldLabel.d.ts.map +1 -1
  56. package/dist/Field/FieldLabel.js +4 -4
  57. package/dist/Field/InputClearButton.js +3 -3
  58. package/dist/Field/PanelSearchInput.js +2 -2
  59. package/dist/FieldStatus/FieldStatus.js +2 -2
  60. package/dist/FileInput/FileInput.d.ts.map +1 -1
  61. package/dist/FileInput/FileInput.js +7 -1
  62. package/dist/Heading/Heading.js +1 -1
  63. package/dist/Icon/Icon.js +1 -1
  64. package/dist/Indicator/CheckIndicator.d.ts.map +1 -1
  65. package/dist/Indicator/CheckIndicator.js +2 -1
  66. package/dist/Indicator/CheckboxIndicator.d.ts.map +1 -1
  67. package/dist/Indicator/CheckboxIndicator.js +3 -1
  68. package/dist/Indicator/RadioIndicator.d.ts.map +1 -1
  69. package/dist/Indicator/RadioIndicator.js +3 -1
  70. package/dist/Item/Item.d.ts.map +1 -1
  71. package/dist/Item/Item.js +2 -1
  72. package/dist/Item/ItemSwipeLayer.js +1 -1
  73. package/dist/Layer/popoverOperations.d.ts +27 -0
  74. package/dist/Layer/popoverOperations.d.ts.map +1 -0
  75. package/dist/Layer/popoverOperations.js +159 -0
  76. package/dist/Layer/useLayer.d.ts +1 -1
  77. package/dist/Layer/useLayer.d.ts.map +1 -1
  78. package/dist/Layer/useLayer.js +93 -59
  79. package/dist/Layout/Layout.js +1 -1
  80. package/dist/Layout/LayoutContent.js +1 -1
  81. package/dist/Layout/LayoutFooter.js +1 -1
  82. package/dist/Layout/LayoutHeader.js +1 -1
  83. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  84. package/dist/Lightbox/Lightbox.js +5 -0
  85. package/dist/Lightbox/LightboxViewport.a11y.browser.spec.js +5 -4
  86. package/dist/List/List.js +1 -1
  87. package/dist/List/ListItem.js +3 -3
  88. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  89. package/dist/MobileNav/MobileNav.js +7 -2
  90. package/dist/MultiSelector/CreateRow.a11y.chromium.spec.js +7 -6
  91. package/dist/MultiSelector/MultiSelector.d.ts +1 -1
  92. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  93. package/dist/MultiSelector/MultiSelector.js +26 -10
  94. package/dist/NumberInput/NumberInput.d.ts +3 -1
  95. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  96. package/dist/NumberInput/NumberInput.js +3 -3
  97. package/dist/NumberInput/numberParser.d.ts +3 -0
  98. package/dist/NumberInput/numberParser.d.ts.map +1 -1
  99. package/dist/NumberInput/numberParser.js +7 -44
  100. package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
  101. package/dist/ProgressBar/ProgressBar.js +16 -12
  102. package/dist/Resizable/useResizable.d.ts.map +1 -1
  103. package/dist/Resizable/useResizable.js +28 -2
  104. package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
  105. package/dist/SegmentedControl/SegmentedControlItem.js +4 -1
  106. package/dist/Selector/useSelectorPresentation.d.ts +8 -1
  107. package/dist/Selector/useSelectorPresentation.d.ts.map +1 -1
  108. package/dist/Selector/useSelectorPresentation.js +20 -4
  109. package/dist/Skeleton/Skeleton.d.ts.map +1 -1
  110. package/dist/Skeleton/Skeleton.js +1 -1
  111. package/dist/Slider/Slider.d.ts.map +1 -1
  112. package/dist/Slider/Slider.js +10 -2
  113. package/dist/Spinner/Spinner.d.ts.map +1 -1
  114. package/dist/Spinner/Spinner.js +3 -3
  115. package/dist/Stack/Stack.js +1 -1
  116. package/dist/Stack/StackItem.js +1 -1
  117. package/dist/TabList/TabList.d.ts.map +1 -1
  118. package/dist/TabList/TabList.js +16 -1
  119. package/dist/Table/plugins/columnResize/useTableColumnResize.d.ts.map +1 -1
  120. package/dist/Table/plugins/columnResize/useTableColumnResize.js +9 -1
  121. package/dist/Text/Text.js +1 -1
  122. package/dist/Text/useTruncation.d.ts.map +1 -1
  123. package/dist/Text/useTruncation.js +23 -5
  124. package/dist/TextArea/TextArea.d.ts +1 -1
  125. package/dist/TextArea/TextArea.d.ts.map +1 -1
  126. package/dist/TextArea/TextArea.js +13 -2
  127. package/dist/TextInput/TextInput.d.ts.map +1 -1
  128. package/dist/TextInput/TextInput.js +3 -3
  129. package/dist/TimeInput/TimeInput.d.ts +6 -2
  130. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  131. package/dist/TimeInput/TimeInput.js +21 -14
  132. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  133. package/dist/TimeInput/TouchTimeField.js +5 -4
  134. package/dist/Timestamp/Timestamp.d.ts.map +1 -1
  135. package/dist/Timestamp/Timestamp.js +2 -2
  136. package/dist/Timestamp/TimestampHoverCard.js +4 -4
  137. package/dist/Toast/Toast.d.ts.map +1 -1
  138. package/dist/Toast/Toast.js +60 -20
  139. package/dist/Toast/ToastViewport.d.ts.map +1 -1
  140. package/dist/Toast/ToastViewport.js +82 -51
  141. package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
  142. package/dist/Tokenizer/Tokenizer.js +13 -2
  143. package/dist/Tokenizer/TokenizerBottomSheet.js +3 -3
  144. package/dist/Toolbar/Toolbar.d.ts.map +1 -1
  145. package/dist/Toolbar/Toolbar.js +2 -1
  146. package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
  147. package/dist/TopNav/TopNavMegaMenu.js +3 -0
  148. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  149. package/dist/TopNav/TopNavMenu.js +1 -0
  150. package/dist/Typeahead/Typeahead.d.ts +1 -1
  151. package/dist/Typeahead/Typeahead.d.ts.map +1 -1
  152. package/dist/Typeahead/Typeahead.js +9 -1
  153. package/dist/astryx.css +20 -4
  154. package/dist/gestures/GestureArena.d.ts +6 -0
  155. package/dist/gestures/GestureArena.d.ts.map +1 -1
  156. package/dist/gestures/GestureArena.js +39 -0
  157. package/dist/gestures/GestureHost.d.ts +352 -0
  158. package/dist/gestures/GestureHost.d.ts.map +1 -0
  159. package/dist/gestures/GestureHost.js +1773 -0
  160. package/dist/gestures/GestureRecognizer.d.ts +10 -0
  161. package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
  162. package/dist/gestures/GestureRecognizer.js +16 -0
  163. package/dist/gestures/LongPressGestureRecognizer.d.ts +1 -0
  164. package/dist/gestures/LongPressGestureRecognizer.d.ts.map +1 -1
  165. package/dist/gestures/LongPressGestureRecognizer.js +3 -0
  166. package/dist/gestures/PanGestureRecognizer.d.ts +1 -0
  167. package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
  168. package/dist/gestures/PanGestureRecognizer.js +3 -0
  169. package/dist/gestures/PinchGestureRecognizer.d.ts +31 -0
  170. package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -0
  171. package/dist/gestures/PinchGestureRecognizer.js +128 -0
  172. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +71 -0
  173. package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -0
  174. package/dist/gestures/ScreenEdgePanGestureRecognizer.js +223 -0
  175. package/dist/gestures/SwipeGestureRecognizer.d.ts +37 -0
  176. package/dist/gestures/SwipeGestureRecognizer.d.ts.map +1 -0
  177. package/dist/gestures/SwipeGestureRecognizer.js +148 -0
  178. package/dist/gestures/TapGestureRecognizer.d.ts +1 -0
  179. package/dist/gestures/TapGestureRecognizer.d.ts.map +1 -1
  180. package/dist/gestures/TapGestureRecognizer.js +3 -0
  181. package/dist/gestures/VelocityTracker.d.ts +6 -2
  182. package/dist/gestures/VelocityTracker.d.ts.map +1 -1
  183. package/dist/gestures/VelocityTracker.js +7 -3
  184. package/dist/gestures/haptics.d.ts +65 -0
  185. package/dist/gestures/haptics.d.ts.map +1 -0
  186. package/dist/gestures/haptics.js +182 -0
  187. package/dist/gestures/nativeScroll.d.ts +42 -0
  188. package/dist/gestures/nativeScroll.d.ts.map +1 -0
  189. package/dist/gestures/nativeScroll.js +228 -0
  190. package/dist/gestures/touchAction.d.ts +60 -0
  191. package/dist/gestures/touchAction.d.ts.map +1 -0
  192. package/dist/gestures/touchAction.js +157 -0
  193. package/dist/gestures/touchActionRule.d.ts +34 -0
  194. package/dist/gestures/touchActionRule.d.ts.map +1 -0
  195. package/dist/gestures/touchActionRule.js +140 -0
  196. package/dist/gestures/types.d.ts +18 -7
  197. package/dist/gestures/types.d.ts.map +1 -1
  198. package/dist/gestures/types.js +22 -2
  199. package/dist/hooks/index.d.ts +1 -1
  200. package/dist/hooks/index.d.ts.map +1 -1
  201. package/dist/hooks/modalAnnounce.d.ts.map +1 -1
  202. package/dist/hooks/modalAnnounce.js +107 -61
  203. package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -1
  204. package/dist/hooks/useAdaptivePresentation.js +4 -1
  205. package/dist/hooks/useAnnounce.d.ts +101 -42
  206. package/dist/hooks/useAnnounce.d.ts.map +1 -1
  207. package/dist/hooks/useAnnounce.js +161 -75
  208. package/dist/hooks/useAnnounceRenderedText.d.ts +11 -3
  209. package/dist/hooks/useAnnounceRenderedText.d.ts.map +1 -1
  210. package/dist/hooks/useAnnounceRenderedText.js +5 -3
  211. package/dist/hooks/useBusyAnnouncement.d.ts.map +1 -1
  212. package/dist/hooks/useBusyAnnouncement.js +6 -12
  213. package/dist/hooks/useMenuHover.d.ts.map +1 -1
  214. package/dist/hooks/useMenuHover.js +12 -1
  215. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  216. package/dist/i18n/generated-locales/en.generated.js +5 -0
  217. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  218. package/dist/i18n/generated-locales/pseudo.generated.js +5 -0
  219. package/dist/motion/animator.d.ts +7 -1
  220. package/dist/motion/animator.d.ts.map +1 -1
  221. package/dist/motion/animator.js +119 -27
  222. package/dist/theme/defineTheme.d.ts +19 -5
  223. package/dist/theme/defineTheme.d.ts.map +1 -1
  224. package/dist/theme/defineTheme.js +64 -4
  225. package/dist/theme/domainTokens/domainTokenDefaults.d.ts +3 -0
  226. package/dist/theme/domainTokens/domainTokenDefaults.d.ts.map +1 -0
  227. package/dist/theme/domainTokens/domainTokenDefaults.js +19 -0
  228. package/dist/theme/domainTokens/index.d.ts +5 -4
  229. package/dist/theme/domainTokens/index.d.ts.map +1 -1
  230. package/dist/theme/domainTokens/index.js +5 -9
  231. package/dist/theme/index.d.ts +1 -0
  232. package/dist/theme/index.d.ts.map +1 -1
  233. package/dist/theme/motionKnobs.d.ts +56 -0
  234. package/dist/theme/motionKnobs.d.ts.map +1 -0
  235. package/dist/theme/motionKnobs.js +48 -0
  236. package/dist/theme/resolveThemeValues.d.ts +11 -4
  237. package/dist/theme/resolveThemeValues.d.ts.map +1 -1
  238. package/dist/theme/resolveThemeValues.js +40 -4
  239. package/dist/theme/themeAdaptations.d.ts +7 -3
  240. package/dist/theme/themeAdaptations.d.ts.map +1 -1
  241. package/dist/theme/themeAdaptations.js +36 -7
  242. package/dist/theme/themeMotion.d.ts +96 -0
  243. package/dist/theme/themeMotion.d.ts.map +1 -0
  244. package/dist/theme/themeMotion.js +177 -0
  245. package/dist/theme/tokenReferences.d.ts +31 -0
  246. package/dist/theme/tokenReferences.d.ts.map +1 -0
  247. package/dist/theme/tokenReferences.js +132 -0
  248. package/dist/theme/tokens.d.ts +1 -0
  249. package/dist/theme/tokens.d.ts.map +1 -1
  250. package/dist/theme/tokens.js +9 -129
  251. package/dist/theme/tokens.stylex.d.ts +12 -0
  252. package/dist/theme/tokens.stylex.d.ts.map +1 -1
  253. package/dist/theme/tokens.stylex.js +12 -0
  254. package/dist/utils/focusReturn.d.ts +17 -1
  255. package/dist/utils/focusReturn.d.ts.map +1 -1
  256. package/dist/utils/focusReturn.js +11 -4
  257. package/dist/utils/getNodeText.d.ts +8 -0
  258. package/dist/utils/getNodeText.d.ts.map +1 -0
  259. package/dist/utils/getNodeText.js +37 -0
  260. package/dist/utils/interactionModality.d.ts +11 -2
  261. package/dist/utils/interactionModality.d.ts.map +1 -1
  262. package/dist/utils/interactionModality.js +58 -5
  263. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  264. package/dist/utils/interactionOverlay.stylex.js +17 -8
  265. package/dist/utils/plainDate.d.ts +2 -0
  266. package/dist/utils/plainDate.d.ts.map +1 -1
  267. package/dist/utils/plainDate.js +46 -3
  268. package/dist/utils/presentation.d.ts +50 -0
  269. package/dist/utils/presentation.d.ts.map +1 -0
  270. package/dist/utils/presentation.js +125 -0
  271. package/dist/utils/pressFeedback.d.ts.map +1 -1
  272. package/dist/utils/pressFeedback.js +20 -1
  273. package/dist/utils/primaryClick.d.ts +11 -0
  274. package/dist/utils/primaryClick.d.ts.map +1 -0
  275. package/dist/utils/primaryClick.js +25 -0
  276. package/dist/utils/timeFormat.d.ts +47 -0
  277. package/dist/utils/timeFormat.d.ts.map +1 -0
  278. package/dist/utils/timeFormat.js +240 -0
  279. package/dist/utils/touchPresentation.d.ts.map +1 -1
  280. package/dist/utils/touchPresentation.js +5 -3
  281. package/dist/utils/touchPresentation.stylex.d.ts +15 -0
  282. package/dist/utils/touchPresentation.stylex.d.ts.map +1 -0
  283. package/dist/utils/touchPresentation.stylex.js +33 -0
  284. package/dist/utils/unicodeDigits.d.ts +20 -0
  285. package/dist/utils/unicodeDigits.d.ts.map +1 -0
  286. package/dist/utils/unicodeDigits.js +70 -0
  287. package/locales/en.json +23 -3
  288. package/locales/pseudo.json +15 -0
  289. package/package.json +5 -4
  290. package/src/AlertDialog/AlertDialog.tsx +6 -5
  291. package/src/AlertDialog/__tests__/AlertDialog.a11y.chromium.spec.ts +18 -6
  292. package/src/Avatar/Avatar.spec.md +22 -22
  293. package/src/Avatar/Avatar.tsx +18 -1
  294. package/src/Avatar/__tests__/AvatarForcedColors.a11y.chromium.spec.ts +126 -0
  295. package/src/Badge/Badge.doc.mjs +1 -1
  296. package/src/Badge/Badge.tsx +19 -2
  297. package/src/Badge/__tests__/BadgeLargerText.a11y.chromium.spec.ts +260 -0
  298. package/src/BottomSheet/BottomSheet.test.tsx +46 -0
  299. package/src/BottomSheet/BottomSheet.tsx +1 -1
  300. package/src/BottomSheet/BottomSheetPanel.tsx +2 -1
  301. package/src/BottomSheet/useSheetGestures.test.ts +52 -0
  302. package/src/BottomSheet/useSheetGestures.ts +37 -6
  303. package/src/Breadcrumbs/__tests__/Breadcrumbs.a11y.chromium.spec.ts +18 -6
  304. package/src/Button/Button.tsx +4 -4
  305. package/src/Calendar/Calendar.spec.md +87 -48
  306. package/src/Calendar/__tests__/Calendar.a11y.chromium.spec.ts +21 -7
  307. package/src/Carousel/__tests__/Carousel.a11y.chromium.spec.ts +21 -7
  308. package/src/CheckboxInput/CheckboxInput.tsx +6 -5
  309. package/src/ClickableCard/ClickableCard.test.tsx +96 -0
  310. package/src/ClickableCard/ClickableCard.tsx +48 -4
  311. package/src/Collapsible/Collapsible.spec.md +2 -0
  312. package/src/Collapsible/CollapsibleGroup.spec.md +57 -29
  313. package/src/Collapsible/__tests__/Collapsible.a11y.chromium.spec.ts +18 -7
  314. package/src/Collapsible/__tests__/CollapsibleGroupAccordion.a11y.chromium.spec.ts +18 -6
  315. package/src/ComplexSelector/ComplexSelector.test.tsx +37 -0
  316. package/src/ContextMenu/ContextMenu.test.tsx +35 -0
  317. package/src/DateInput/DateInput.test.tsx +115 -3
  318. package/src/DateInput/DateInput.tsx +29 -14
  319. package/src/DateInput/DateInputTouch.test.tsx +76 -0
  320. package/src/DateInput/MonthScroller.tsx +106 -20
  321. package/src/DateInput/MonthScrollerSettle.test.tsx +165 -0
  322. package/src/DateInput/TouchDateField.tsx +14 -3
  323. package/src/DateInput/__tests__/MonthScrollerSteps.interaction.browser.spec.ts +312 -0
  324. package/src/DateRangeInput/DateRangeInput.test.tsx +46 -3
  325. package/src/DateRangeInput/DateRangeInput.tsx +16 -12
  326. package/src/DateTimeInput/DateTimeInput.doc.mjs +10 -8
  327. package/src/DateTimeInput/DateTimeInput.spec.md +75 -51
  328. package/src/DateTimeInput/DateTimeInput.test.tsx +81 -0
  329. package/src/DateTimeInput/DateTimeInput.tsx +46 -24
  330. package/src/DateTimeInput/NativeTimeSegment.tsx +4 -6
  331. package/src/DateTimeInput/TouchDateTimeField.tsx +3 -7
  332. package/src/Dialog/Dialog.test.tsx +216 -2
  333. package/src/Dialog/Dialog.tsx +42 -9
  334. package/src/Dialog/DialogHeader.tsx +5 -4
  335. package/src/Dialog/__tests__/DialogSecondDocument.a11y.browser.spec.ts +202 -0
  336. package/src/Divider/Divider.tsx +2 -1
  337. package/src/Drawer/Drawer.spec.md +111 -80
  338. package/src/Drawer/Drawer.test.tsx +35 -0
  339. package/src/Drawer/Drawer.tsx +13 -2
  340. package/src/Drawer/DrawerHeader.spec.md +181 -0
  341. package/src/Drawer/__tests__/DrawerNonModalPageScroll.a11y.chromium.spec.ts +267 -0
  342. package/src/DropdownMenu/DropdownMenu.test.tsx +41 -0
  343. package/src/DropdownMenu/DropdownMenu.tsx +10 -3
  344. package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +34 -0
  345. package/src/DropdownMenu/MenuBottomSheetActionList.tsx +6 -5
  346. package/src/DropdownMenu/__tests__/Menu.a11y.chromium.spec.ts +19 -7
  347. package/src/DropdownMenu/__tests__/MenuButton.a11y.chromium.spec.ts +21 -9
  348. package/src/DropdownMenu/lazyMenuBottomSheet.test.ts +60 -0
  349. package/src/DropdownMenu/lazyMenuBottomSheet.ts +36 -7
  350. package/src/DropdownMenu/renderDropdownItems.tsx +24 -19
  351. package/src/EmptyState/EmptyState.tsx +2 -1
  352. package/src/Field/FieldLabel.tsx +5 -4
  353. package/src/Field/InputClearButton.tsx +3 -3
  354. package/src/Field/PanelSearchInput.tsx +2 -2
  355. package/src/FieldStatus/FieldStatus.tsx +2 -2
  356. package/src/FileInput/FileInput.test.tsx +21 -0
  357. package/src/FileInput/FileInput.tsx +4 -1
  358. package/src/FormLayout/FormLayout.spec.md +7 -8
  359. package/src/Heading/Heading.tsx +1 -1
  360. package/src/Icon/Icon.tsx +1 -1
  361. package/src/Indicator/CheckIndicator.spec.md +47 -42
  362. package/src/Indicator/CheckIndicator.tsx +2 -1
  363. package/src/Indicator/CheckboxIndicator.spec.md +40 -37
  364. package/src/Indicator/CheckboxIndicator.tsx +3 -1
  365. package/src/Indicator/Indicator.spec.md +188 -0
  366. package/src/Indicator/RadioIndicator.tsx +3 -1
  367. package/src/Item/Item.tsx +2 -1
  368. package/src/Item/ItemSwipeLayer.tsx +1 -1
  369. package/src/Kbd/Kbd.spec.md +81 -38
  370. package/src/Layer/__tests__/LayerBrowserHide.a11y.chromium.spec.ts +126 -0
  371. package/src/Layer/popoverOperations.test.ts +205 -0
  372. package/src/Layer/popoverOperations.ts +172 -0
  373. package/src/Layer/useLayer.test.tsx +348 -1
  374. package/src/Layer/useLayer.tsx +118 -71
  375. package/src/Layout/Layout.spec.md +127 -131
  376. package/src/Layout/Layout.tsx +1 -1
  377. package/src/Layout/LayoutContent.spec.md +205 -0
  378. package/src/Layout/LayoutContent.tsx +1 -1
  379. package/src/Layout/LayoutFooter.spec.md +190 -0
  380. package/src/Layout/LayoutFooter.tsx +1 -1
  381. package/src/Layout/LayoutHeader.spec.md +193 -0
  382. package/src/Layout/LayoutHeader.tsx +1 -1
  383. package/src/Layout/LayoutPanel.spec.md +194 -0
  384. package/src/Layout/__tests__/Layout.a11y.chromium.spec.ts +18 -6
  385. package/src/Lightbox/Lightbox.test.tsx +18 -0
  386. package/src/Lightbox/Lightbox.tsx +5 -0
  387. package/src/Lightbox/LightboxViewport.a11y.browser.spec.ts +8 -4
  388. package/src/Link/__tests__/Link.a11y.chromium.spec.ts +18 -8
  389. package/src/List/List.tsx +1 -1
  390. package/src/List/ListItem.spec.md +205 -0
  391. package/src/List/ListItem.tsx +3 -3
  392. package/src/MetadataList/MetadataList.spec.md +187 -0
  393. package/src/MetadataList/MetadataListItem.spec.md +165 -0
  394. package/src/MobileNav/MobileNav.test.tsx +15 -0
  395. package/src/MobileNav/MobileNav.tsx +9 -1
  396. package/src/MobileNav/__tests__/MobileNavHeader.a11y.chromium.spec.ts +89 -0
  397. package/src/MoreMenu/MoreMenu.test.tsx +11 -0
  398. package/src/MultiSelector/CreateRow.a11y.chromium.spec.ts +7 -8
  399. package/src/MultiSelector/MultiSelector.spec.md +7 -4
  400. package/src/MultiSelector/MultiSelector.test.tsx +270 -1
  401. package/src/MultiSelector/MultiSelector.tsx +25 -10
  402. package/src/MultiSelector/__tests__/MultiSelector.listbox.a11y.test.tsx +8 -1
  403. package/src/NavMenu/NavHeadingMenu.spec.md +1 -5
  404. package/src/NumberInput/NumberInput.doc.mjs +13 -7
  405. package/src/NumberInput/NumberInput.spec.md +59 -37
  406. package/src/NumberInput/NumberInput.tsx +13 -3
  407. package/src/NumberInput/__tests__/NumberInput.coarse-pointer.a11y.chromium.spec.ts +102 -0
  408. package/src/NumberInput/numberParser.ts +7 -45
  409. package/src/ProgressBar/ProgressBar.test.tsx +108 -32
  410. package/src/ProgressBar/ProgressBar.tsx +58 -10
  411. package/src/ProgressBar/__tests__/ProgressBar.a11y.chromium.spec.ts +21 -9
  412. package/src/ProgressBar/__tests__/ProgressBarMotion.a11y.chromium.spec.ts +831 -0
  413. package/src/Resizable/__tests__/ResizeHandle.a11y.chromium.spec.ts +21 -14
  414. package/src/Resizable/useResizable.test.ts +43 -0
  415. package/src/Resizable/useResizable.ts +32 -9
  416. package/src/SegmentedControl/SegmentedControl.test.tsx +84 -0
  417. package/src/SegmentedControl/SegmentedControlItem.tsx +11 -5
  418. package/src/Selector/Selector.test.tsx +90 -1
  419. package/src/Selector/__tests__/Selector.combobox.a11y.test.tsx +8 -1
  420. package/src/Selector/__tests__/Selector.listbox.a11y.test.tsx +8 -1
  421. package/src/Selector/useSelectorPresentation.ts +31 -3
  422. package/src/SideNav/SideNav.test.tsx +27 -0
  423. package/src/SideNav/__tests__/SideNavRailMotion.interaction.browser.spec.ts +176 -51
  424. package/src/Skeleton/Skeleton.tsx +3 -1
  425. package/src/Slider/Slider.test.tsx +31 -0
  426. package/src/Slider/Slider.tsx +8 -3
  427. package/src/Slider/__tests__/Slider.a11y.chromium.spec.ts +19 -7
  428. package/src/Spinner/Spinner.tsx +5 -3
  429. package/src/Stack/Stack.tsx +1 -1
  430. package/src/Stack/StackItem.tsx +1 -1
  431. package/src/TabList/TabList.test.tsx +58 -4
  432. package/src/TabList/TabList.tsx +16 -1
  433. package/src/TabList/__tests__/TabPressPointer.interaction.browser.spec.ts +163 -0
  434. package/src/Table/__tests__/Table.a11y.chromium.spec.ts +18 -6
  435. package/src/Table/plugins/columnResize/useTableColumnResize.test.tsx +22 -0
  436. package/src/Table/plugins/columnResize/useTableColumnResize.tsx +8 -4
  437. package/src/Text/Text.tsx +1 -1
  438. package/src/Text/useTruncation.test.tsx +128 -1
  439. package/src/Text/useTruncation.ts +23 -5
  440. package/src/TextArea/TextArea.spec.md +10 -13
  441. package/src/TextArea/TextArea.test.tsx +60 -0
  442. package/src/TextArea/TextArea.tsx +15 -1
  443. package/src/TextInput/TextInput.test.tsx +21 -0
  444. package/src/TextInput/TextInput.tsx +3 -1
  445. package/src/TimeInput/NativeTimeInput.test.tsx +17 -0
  446. package/src/TimeInput/Presentation.test.tsx +21 -0
  447. package/src/TimeInput/TimeInput.doc.mjs +8 -8
  448. package/src/TimeInput/TimeInput.spec.md +45 -48
  449. package/src/TimeInput/TimeInput.test.tsx +76 -0
  450. package/src/TimeInput/TimeInput.tsx +36 -18
  451. package/src/TimeInput/TouchTimeField.tsx +4 -6
  452. package/src/Timestamp/Timestamp.spec.md +6 -12
  453. package/src/Timestamp/Timestamp.test.tsx +30 -5
  454. package/src/Timestamp/Timestamp.tsx +6 -2
  455. package/src/Timestamp/TimestampHoverCard.tsx +4 -4
  456. package/src/Toast/Toast.spec.md +14 -20
  457. package/src/Toast/Toast.tsx +104 -36
  458. package/src/Toast/ToastViewport.test.tsx +719 -1
  459. package/src/Toast/ToastViewport.tsx +100 -50
  460. package/src/Tokenizer/Tokenizer.spec.md +5 -3
  461. package/src/Tokenizer/Tokenizer.test.tsx +68 -0
  462. package/src/Tokenizer/Tokenizer.tsx +14 -2
  463. package/src/Tokenizer/TokenizerBottomSheet.test.tsx +15 -4
  464. package/src/Tokenizer/TokenizerBottomSheet.tsx +3 -3
  465. package/src/Toolbar/Toolbar.spec.md +5 -6
  466. package/src/Toolbar/Toolbar.test.tsx +30 -0
  467. package/src/Toolbar/Toolbar.tsx +2 -1
  468. package/src/Toolbar/__tests__/Toolbar.a11y.chromium.spec.ts +18 -6
  469. package/src/Tooltip/__tests__/Tooltip.a11y.chromium.spec.ts +18 -7
  470. package/src/TopNav/TopNav.test.tsx +27 -0
  471. package/src/TopNav/TopNavMegaMenu.test.tsx +21 -0
  472. package/src/TopNav/TopNavMegaMenu.tsx +4 -1
  473. package/src/TopNav/TopNavMenu.test.tsx +17 -0
  474. package/src/TopNav/TopNavMenu.tsx +1 -0
  475. package/src/Typeahead/Typeahead.test.tsx +40 -0
  476. package/src/Typeahead/Typeahead.tsx +12 -1
  477. package/src/__tests__/pressState.ts +8 -4
  478. package/src/gestures/GestureArena.ts +35 -0
  479. package/src/gestures/GestureHost.ts +2065 -0
  480. package/src/gestures/GestureRecognizer.ts +18 -0
  481. package/src/gestures/LongPressGestureRecognizer.ts +4 -0
  482. package/src/gestures/PanGestureRecognizer.ts +4 -0
  483. package/src/gestures/PinchGestureRecognizer.ts +148 -0
  484. package/src/gestures/ScreenEdgePanGestureRecognizer.ts +256 -0
  485. package/src/gestures/SwipeGestureRecognizer.ts +197 -0
  486. package/src/gestures/TapGestureRecognizer.ts +4 -0
  487. package/src/gestures/VelocityTracker.ts +10 -3
  488. package/src/gestures/__tests__/GestureHost.test.ts +2087 -0
  489. package/src/gestures/__tests__/PanGestureRecognizer.test.ts +4 -4
  490. package/src/gestures/__tests__/PinchGestureRecognizer.test.ts +74 -0
  491. package/src/gestures/__tests__/ScreenEdgePanGestureRecognizer.test.ts +498 -0
  492. package/src/gestures/__tests__/SwipeGestureRecognizer.test.ts +108 -0
  493. package/src/gestures/__tests__/arena.test.ts +5 -0
  494. package/src/gestures/__tests__/arenaPinchSwipeEdge.test.ts +193 -0
  495. package/src/gestures/__tests__/browser/build-fixture.mjs +46 -0
  496. package/src/gestures/__tests__/browser/fixture.ts +307 -0
  497. package/src/gestures/__tests__/browser/gestures.interaction.browser.spec.ts +1137 -0
  498. package/src/gestures/__tests__/browserNegotiation.test.ts +293 -0
  499. package/src/gestures/__tests__/haptics.test.ts +187 -0
  500. package/src/gestures/__tests__/harness.ts +21 -2
  501. package/src/gestures/__tests__/recognizerKind.test.ts +78 -0
  502. package/src/gestures/__tests__/touchActionRule.test.ts +54 -0
  503. package/src/gestures/haptics.ts +203 -0
  504. package/src/gestures/nativeScroll.ts +277 -0
  505. package/src/gestures/touchAction.ts +191 -0
  506. package/src/gestures/touchActionRule.ts +157 -0
  507. package/src/gestures/types.ts +30 -9
  508. package/src/hooks/index.ts +5 -1
  509. package/src/hooks/modalAnnounce.ts +129 -72
  510. package/src/hooks/useAdaptivePresentation.ts +4 -1
  511. package/src/hooks/useAnnounce.doc.mjs +35 -3
  512. package/src/hooks/useAnnounce.replace.test.tsx +516 -0
  513. package/src/hooks/useAnnounce.ts +209 -102
  514. package/src/hooks/useAnnounceRenderedText.adopters.test.tsx +10 -1
  515. package/src/hooks/useAnnounceRenderedText.ts +17 -3
  516. package/src/hooks/useBusyAnnouncement.ts +7 -10
  517. package/src/hooks/useMenuHover.ts +15 -1
  518. package/src/i18n/generated-locales/en.generated.ts +6 -0
  519. package/src/i18n/generated-locales/pseudo.generated.ts +6 -0
  520. package/src/motion/README.md +11 -1
  521. package/src/motion/animator.test.ts +151 -3
  522. package/src/motion/animator.ts +149 -33
  523. package/src/theme/Theme.doc.mjs +1 -1
  524. package/src/theme/defineTheme.ts +117 -8
  525. package/src/theme/derivedVarRegistry.test.ts +4 -0
  526. package/src/theme/domainTokens/domainTokenDefaults.ts +19 -0
  527. package/src/theme/domainTokens/index.ts +6 -10
  528. package/src/theme/expandMotionScale.test.ts +1 -1
  529. package/src/theme/index.ts +5 -0
  530. package/src/theme/motionKnobs.ts +82 -0
  531. package/src/theme/resolveThemeValues.ts +68 -6
  532. package/src/theme/themeAdaptations.ts +52 -12
  533. package/src/theme/themeMotion.test.ts +620 -0
  534. package/src/theme/themeMotion.ts +221 -0
  535. package/src/theme/tokenReferences.ts +149 -0
  536. package/src/theme/tokens.stylex.ts +10 -0
  537. package/src/theme/tokens.ts +15 -158
  538. package/src/utils/focusReturn.ts +17 -6
  539. package/src/utils/getNodeText.test.ts +24 -0
  540. package/src/utils/getNodeText.ts +37 -0
  541. package/src/utils/interactionModality.test.ts +45 -1
  542. package/src/utils/interactionModality.ts +64 -5
  543. package/src/utils/interactionOverlay.stylex.ts +12 -3
  544. package/src/utils/plainDate.test.ts +90 -0
  545. package/src/utils/plainDate.ts +58 -3
  546. package/src/utils/presentation.test.ts +380 -0
  547. package/src/utils/presentation.ts +180 -0
  548. package/src/utils/pressFeedback.test.ts +138 -0
  549. package/src/utils/pressFeedback.ts +34 -11
  550. package/src/utils/pressPaintPriority.test.ts +80 -0
  551. package/src/utils/primaryClick.test.ts +38 -0
  552. package/src/utils/primaryClick.ts +28 -0
  553. package/src/utils/timeFormat.test.ts +204 -0
  554. package/src/utils/timeFormat.ts +314 -0
  555. package/src/utils/touchPresentation.stylex.ts +43 -0
  556. package/src/utils/touchPresentation.test.ts +140 -1
  557. package/src/utils/touchPresentation.ts +5 -3
  558. package/src/utils/unicodeDigits.test.ts +44 -0
  559. package/src/utils/unicodeDigits.ts +71 -0
@@ -0,0 +1,831 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file ProgressBarMotion.a11y.chromium.spec.ts
5
+ * @input Built ProgressBar stories in real Chromium (CDP screencast, tracing)
6
+ * @output Browser evidence that a determinate value change moves the fill on
7
+ * the compositor: it keeps moving while a script holds the main thread,
8
+ * lays out and paints nothing per frame, and its leading edge lands on the
9
+ * value (mirrored in RTL). Writes a storyboard (contact sheet + JSON trace)
10
+ * to test-results/progress-bar-motion/.
11
+ * @position Chromium proof for ProgressBar's sliding fill; the iOS Safari side
12
+ * of the same proof is a simulator recording attached to the pull request.
13
+ *
14
+ * SYNC: Keep in step with ProgressBar.tsx (`styles.fill`, `styles.track`).
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import {expect, test, type Page} from '@playwright/test';
20
+ import {
21
+ DEFAULT_STORYBOOK_DIR,
22
+ serveStorybook,
23
+ type StaticServer,
24
+ } from '@astryxdesign/a11y-spec/storybook';
25
+
26
+ let storybook: StaticServer;
27
+ test.beforeAll(async () => {
28
+ storybook = await serveStorybook(
29
+ process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
30
+ );
31
+ });
32
+ test.afterAll(async () => {
33
+ await storybook?.close();
34
+ });
35
+
36
+ // Playwright empties test-results/ at the start of every run, so evidence
37
+ // meant to outlive the run goes where ASTRYX_MOTION_EVIDENCE points.
38
+ const EVIDENCE = path.resolve(
39
+ process.env.ASTRYX_MOTION_EVIDENCE ??
40
+ path.join('test-results', 'progress-bar-motion'),
41
+ );
42
+ const STORY = 'core-progressbar--default';
43
+ const BLOCK_MS = 700;
44
+ const DPR = 2;
45
+
46
+ test.use({viewport: {width: 402, height: 200}, deviceScaleFactor: DPR});
47
+
48
+ async function open(page: Page, value: number, globals = '') {
49
+ await page.goto(
50
+ `${storybook.origin}/iframe.html?id=${STORY}&viewMode=story${globals ? `&globals=${globals}` : ''}`,
51
+ );
52
+ await expect(page.getByRole('progressbar')).toBeVisible();
53
+ await setValue(page, value);
54
+ // Let any transition from the story's own value finish.
55
+ await settle(page);
56
+ }
57
+
58
+ /** Re-renders the story with a new value and resolves once it rendered. */
59
+ async function setValue(page: Page, value: number) {
60
+ await page.evaluate(
61
+ async ({storyId, updatedArgs}) =>
62
+ new Promise<void>(resolve => {
63
+ const channel = (
64
+ window as unknown as {
65
+ __STORYBOOK_ADDONS_CHANNEL__: {
66
+ emit: (event: string, payload: unknown) => void;
67
+ once: (event: string, handler: () => void) => void;
68
+ };
69
+ }
70
+ ).__STORYBOOK_ADDONS_CHANNEL__;
71
+ channel.once('storyRendered', () => resolve());
72
+ channel.emit('updateStoryArgs', {storyId, updatedArgs});
73
+ setTimeout(resolve, 1500);
74
+ }),
75
+ {storyId: STORY, updatedArgs: {value}},
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Sets a new value and, once the fill's transition has started and one more
81
+ * frame has been presented, holds the main thread: the pattern of a progress
82
+ * update followed by the work it reports on. Returns the block's start and
83
+ * end (epoch ms).
84
+ */
85
+ async function setValueThenBlock(page: Page, value: number, ms: number) {
86
+ return page.evaluate(
87
+ async ({storyId, updatedArgs, duration}) =>
88
+ new Promise<{from: number; to: number}>(resolve => {
89
+ const channel = (
90
+ window as unknown as {
91
+ __STORYBOOK_ADDONS_CHANNEL__: {
92
+ emit: (event: string, payload: unknown) => void;
93
+ once: (event: string, handler: () => void) => void;
94
+ };
95
+ }
96
+ ).__STORYBOOK_ADDONS_CHANNEL__;
97
+ const onStart = (event: Event) => {
98
+ const bar = document.querySelector('[role="progressbar"]');
99
+ if (bar == null || !bar.contains(event.target as Node)) {
100
+ return;
101
+ }
102
+ document.removeEventListener('transitionstart', onStart, true);
103
+ requestAnimationFrame(() =>
104
+ setTimeout(() => {
105
+ const from = performance.timeOrigin + performance.now();
106
+ const end = performance.now() + duration;
107
+ while (performance.now() < end) {
108
+ // hold
109
+ }
110
+ resolve({from, to: performance.timeOrigin + performance.now()});
111
+ }),
112
+ );
113
+ };
114
+ document.addEventListener('transitionstart', onStart, true);
115
+ channel.emit('updateStoryArgs', {storyId, updatedArgs});
116
+ }),
117
+ {storyId: STORY, updatedArgs: {value}, duration: ms},
118
+ );
119
+ }
120
+
121
+ interface Frame {
122
+ /** Presentation time, epoch ms. */
123
+ readonly t: number;
124
+ readonly data: string;
125
+ }
126
+
127
+ /** Compositor frames, captured off the main thread. */
128
+ async function startScreencast(page: Page) {
129
+ const cdp = await page.context().newCDPSession(page);
130
+ const frames: Frame[] = [];
131
+ const acks: Promise<unknown>[] = [];
132
+ const ackFailures: string[] = [];
133
+ cdp.on('Page.screencastFrame', frame => {
134
+ frames.push({
135
+ t: (frame.metadata.timestamp ?? Date.now() / 1000) * 1000,
136
+ data: frame.data,
137
+ });
138
+ acks.push(
139
+ cdp
140
+ .send('Page.screencastFrameAck', {sessionId: frame.sessionId})
141
+ .catch((error: unknown) => {
142
+ ackFailures.push(String(error));
143
+ }),
144
+ );
145
+ });
146
+ const viewport = page.viewportSize() ?? {width: 402, height: 200};
147
+ await cdp.send('Page.startScreencast', {
148
+ format: 'png',
149
+ maxWidth: viewport.width * DPR,
150
+ maxHeight: viewport.height * DPR,
151
+ everyNthFrame: 1,
152
+ });
153
+ return {
154
+ async stop() {
155
+ await cdp.send('Page.stopScreencast');
156
+ await Promise.all(acks);
157
+ await cdp.detach();
158
+ // A lost acknowledgement stalls the screencast and thins the frames.
159
+ expect(ackFailures).toEqual([]);
160
+ return frames;
161
+ },
162
+ };
163
+ }
164
+
165
+ interface Box {
166
+ x: number;
167
+ y: number;
168
+ width: number;
169
+ height: number;
170
+ }
171
+
172
+ async function trackBox(page: Page): Promise<Box> {
173
+ const box = await page.getByRole('progressbar').boundingBox();
174
+ if (box == null) {
175
+ throw new Error('no track box');
176
+ }
177
+ return box;
178
+ }
179
+
180
+ /**
181
+ * The fill's leading edge in each frame, in CSS px from the track's start:
182
+ * along the track's middle row, the last pixel that still has the fill's
183
+ * color (sampled at the track's start, where the fill always is once p > 0).
184
+ */
185
+ async function leadingEdges(
186
+ page: Page,
187
+ frames: ReadonlyArray<Frame>,
188
+ track: Box,
189
+ ): Promise<number[]> {
190
+ const scratch = await page.context().newPage();
191
+ const viewportWidth = page.viewportSize()?.width ?? 402;
192
+ try {
193
+ const out: number[] = [];
194
+ for (let i = 0; i < frames.length; i += 10) {
195
+ const batch = frames.slice(i, i + 10).map(f => f.data);
196
+ out.push(
197
+ ...(await scratch.evaluate(
198
+ async ({items, box, vw}) => {
199
+ const result: number[] = [];
200
+ for (const item of items) {
201
+ const bitmap = await createImageBitmap(
202
+ await (await fetch(`data:image/png;base64,${item}`)).blob(),
203
+ );
204
+ const scale = bitmap.width / vw;
205
+ const w = Math.round(box.width * scale);
206
+ const canvas = new OffscreenCanvas(w, 1);
207
+ const context = canvas.getContext('2d');
208
+ if (context == null) {
209
+ throw new Error('no 2d context');
210
+ }
211
+ const y = Math.round((box.y + box.height / 2) * scale);
212
+ context.drawImage(
213
+ bitmap,
214
+ Math.round(box.x * scale),
215
+ y,
216
+ w,
217
+ 1,
218
+ 0,
219
+ 0,
220
+ w,
221
+ 1,
222
+ );
223
+ const {data} = context.getImageData(0, 0, w, 1);
224
+ // The fill's color, a few pixels in from the start; the track's
225
+ // color at the far end.
226
+ const at = (x: number) => [
227
+ data[x * 4],
228
+ data[x * 4 + 1],
229
+ data[x * 4 + 2],
230
+ ];
231
+ const fill = at(Math.round(6 * scale));
232
+ const rest = at(w - Math.round(6 * scale));
233
+ const near = (c: number[], d: number[]) =>
234
+ Math.abs(c[0] - d[0]) +
235
+ Math.abs(c[1] - d[1]) +
236
+ Math.abs(c[2] - d[2]) <
237
+ Math.abs(c[0] - rest[0]) +
238
+ Math.abs(c[1] - rest[1]) +
239
+ Math.abs(c[2] - rest[2]);
240
+ let edge = 0;
241
+ for (let x = 0; x < w; x += 1) {
242
+ if (near(at(x), fill)) {
243
+ edge = x + 1;
244
+ }
245
+ }
246
+ result.push(edge / scale);
247
+ }
248
+ return result;
249
+ },
250
+ {items: batch, box: track, vw: viewportWidth},
251
+ )),
252
+ );
253
+ }
254
+ return out;
255
+ } finally {
256
+ await scratch.close();
257
+ }
258
+ }
259
+
260
+ test('the fill keeps moving to a new value while a script holds the main thread', async ({
261
+ page,
262
+ }) => {
263
+ await open(page, 10);
264
+ const track = await trackBox(page);
265
+ const cast = await startScreencast(page);
266
+ await page.waitForTimeout(300);
267
+ const busy = await setValueThenBlock(page, 90, BLOCK_MS);
268
+ await page.waitForTimeout(300);
269
+ const frames = await cast.stop();
270
+ const edges = await leadingEdges(page, frames, track);
271
+ const start = track.width * 0.1;
272
+ const target = track.width * 0.9;
273
+ // Inside the block, a frame clear of each edge.
274
+ const inside = frames
275
+ .map((frame, i) => ({t: frame.t, edge: edges[i]}))
276
+ .filter(f => f.t >= busy.from + 20 && f.t <= busy.to - 20);
277
+ // The transition (the theme's `--duration-medium`) is shorter than the
278
+ // block, so the fill must make its whole trip inside it: frame after frame
279
+ // through distinct positions…
280
+ const positions = new Set(inside.map(f => Math.round(f.edge)));
281
+ expect(positions.size).toBeGreaterThanOrEqual(5);
282
+ // …and arrives before the block ends (the transition is shorter than it).
283
+ const last = inside[inside.length - 1].edge;
284
+ expect(Math.abs(last - target)).toBeLessThan(2);
285
+ // It never runs backwards.
286
+ for (let i = 1; i < inside.length; i += 1) {
287
+ expect(inside[i].edge).toBeGreaterThanOrEqual(inside[i - 1].edge - 1);
288
+ }
289
+ expect(inside[0].edge).toBeLessThan(target - (target - start) * 0.2);
290
+ });
291
+
292
+ test('moving the fill lays out and paints nothing per frame', async ({
293
+ page,
294
+ browser,
295
+ }) => {
296
+ await open(page, 20);
297
+ await browser.startTracing(page, {
298
+ categories: ['devtools.timeline', 'disabled-by-default-devtools.timeline'],
299
+ });
300
+ await setValue(page, 80);
301
+ // Traced until the change has finished, however long the machine takes.
302
+ await settle(page);
303
+ const trace = JSON.parse((await browser.stopTracing()).toString()) as {
304
+ traceEvents: {
305
+ name: string;
306
+ ph: string;
307
+ pid: number;
308
+ tid: number;
309
+ args?: {name?: string};
310
+ }[];
311
+ };
312
+ const mainThreads = new Set(
313
+ trace.traceEvents
314
+ .filter(e => e.ph === 'M' && e.name === 'thread_name')
315
+ .filter(e => e.args?.name === 'CrRendererMain')
316
+ .map(e => `${e.pid}:${e.tid}`),
317
+ );
318
+ const count = (name: string) =>
319
+ trace.traceEvents.filter(
320
+ e => e.name === name && mainThreads.has(`${e.pid}:${e.tid}`),
321
+ ).length;
322
+ // The update itself may lay out and paint once; a width transition then
323
+ // lays out and repaints every frame until it ends (about 18 times here).
324
+ expect(count('Layout')).toBeLessThanOrEqual(2);
325
+ expect(count('Paint')).toBeLessThanOrEqual(2);
326
+ });
327
+
328
+ /** Resolves once the fill's transitions, if any, have finished. */
329
+ async function settle(page: Page) {
330
+ await page.evaluate(async () => {
331
+ const fill = document.querySelector(
332
+ '[role="progressbar"]',
333
+ )?.firstElementChild;
334
+ if (fill == null) {
335
+ throw new Error('no progressbar');
336
+ }
337
+ // A new value starts its transition on the frame that applies it.
338
+ await new Promise(requestAnimationFrame);
339
+ await new Promise(requestAnimationFrame);
340
+ await Promise.all(
341
+ fill.getAnimations().map(async animation => animation.finished),
342
+ );
343
+ });
344
+ }
345
+
346
+ // The story's own direction makes a real RTL region (Storybook keeps the
347
+ // story inside its own `dir`); an LTR region inside an RTL page fills from
348
+ // the left, like its label and marks.
349
+ for (const {name, globals, nested, fromLeft} of [
350
+ {name: 'ltr', globals: '', nested: false, fromLeft: true},
351
+ {name: 'rtl', globals: 'direction:rtl', nested: false, fromLeft: false},
352
+ {
353
+ name: 'ltr inside rtl',
354
+ globals: 'direction:rtl',
355
+ nested: true,
356
+ fromLeft: true,
357
+ },
358
+ ]) {
359
+ test(`the leading edge sits at the value, mirrored in an RTL region: ${name}`, async ({
360
+ page,
361
+ }) => {
362
+ await open(page, 0, globals);
363
+ if (nested) {
364
+ await page.evaluate(() => {
365
+ const bar = document.querySelector('[role="progressbar"]');
366
+ bar?.parentElement?.setAttribute('dir', 'ltr');
367
+ });
368
+ }
369
+ for (const value of [0, 25, 60, 100]) {
370
+ await setValue(page, value);
371
+ await settle(page);
372
+ const {track, fill, direction} = await page.evaluate(() => {
373
+ const bar = document.querySelector('[role="progressbar"]');
374
+ const child = bar?.firstElementChild;
375
+ if (bar == null || child == null) {
376
+ throw new Error('no progressbar');
377
+ }
378
+ const r = (e: Element) => {
379
+ const b = e.getBoundingClientRect();
380
+ return {left: b.left, right: b.right, width: b.width};
381
+ };
382
+ return {
383
+ track: r(bar),
384
+ fill: r(child),
385
+ direction: getComputedStyle(bar).direction,
386
+ };
387
+ });
388
+ expect(direction, name).toBe(fromLeft ? 'ltr' : 'rtl');
389
+ // The fill spans the track and is moved back by the unfilled share.
390
+ expect(fill.width).toBeCloseTo(track.width, 1);
391
+ const filled = (track.width * value) / 100;
392
+ if (fromLeft) {
393
+ expect(fill.right - track.left, `${name} ${value}`).toBeCloseTo(
394
+ filled,
395
+ 0,
396
+ );
397
+ } else {
398
+ expect(track.right - fill.left, `${name} ${value}`).toBeCloseTo(
399
+ filled,
400
+ 0,
401
+ );
402
+ }
403
+ }
404
+ });
405
+ }
406
+
407
+ test('an ordinary change keeps the theme\u2019s duration and easing', async ({
408
+ page,
409
+ }) => {
410
+ await open(page, 20);
411
+ const box = await trackBox(page);
412
+ // The transition the width used to run: the same tokens, now on transform.
413
+ const timing = await page.evaluate(() => {
414
+ const fill = document.querySelector('[role="progressbar"]')
415
+ ?.firstElementChild as HTMLElement;
416
+ const root = getComputedStyle(document.documentElement);
417
+ const style = getComputedStyle(fill);
418
+ return {
419
+ property: style.transitionProperty,
420
+ duration: style.transitionDuration,
421
+ easing: style.transitionTimingFunction,
422
+ tokenDuration:
423
+ getComputedStyle(fill).getPropertyValue('--duration-medium').trim() ||
424
+ root.getPropertyValue('--duration-medium').trim(),
425
+ };
426
+ });
427
+ expect(timing.property).toBe('transform');
428
+ // Sample the change itself: 20% → 80%, held at five points of its time.
429
+ await page.evaluate(
430
+ async ({storyId}) =>
431
+ new Promise<void>(resolve => {
432
+ const onStart = () => {
433
+ document.removeEventListener('transitionstart', onStart, true);
434
+ const fill = document.querySelector('[role="progressbar"]')
435
+ ?.firstElementChild as HTMLElement;
436
+ for (const animation of fill.getAnimations()) {
437
+ animation.pause();
438
+ }
439
+ resolve();
440
+ };
441
+ document.addEventListener('transitionstart', onStart, true);
442
+ (
443
+ window as unknown as {
444
+ __STORYBOOK_ADDONS_CHANNEL__: {
445
+ emit: (event: string, payload: unknown) => void;
446
+ };
447
+ }
448
+ ).__STORYBOOK_ADDONS_CHANNEL__.emit('updateStoryArgs', {
449
+ storyId,
450
+ updatedArgs: {value: 80},
451
+ });
452
+ }),
453
+ {storyId: STORY},
454
+ );
455
+ const samples = await page.evaluate(() => {
456
+ const fill = document.querySelector('[role="progressbar"]')
457
+ ?.firstElementChild as HTMLElement;
458
+ const [transition] = fill.getAnimations();
459
+ const duration = Number(transition.effect?.getTiming().duration);
460
+ const easing = String(transition.effect?.getTiming().easing);
461
+ // The easing, evaluated by the browser through a detached animation.
462
+ const probe = new Animation(
463
+ new KeyframeEffect(null, null, {duration: 1000, easing, fill: 'both'}),
464
+ );
465
+ const out: {t: number; eased: number; right: number}[] = [];
466
+ for (const t of [0, 0.25, 0.5, 0.75, 1]) {
467
+ transition.currentTime = Math.min(t * duration, duration - 0.01);
468
+ probe.currentTime = t * 1000;
469
+ out.push({
470
+ t,
471
+ eased: probe.effect?.getComputedTiming().progress ?? t,
472
+ right: fill.getBoundingClientRect().right,
473
+ });
474
+ }
475
+ return {duration, easing, out};
476
+ });
477
+ // The duration is the theme's `--duration-medium`, the easing its
478
+ // `--ease-standard`, as before.
479
+ expect(samples.duration).toBeCloseTo(
480
+ Number.parseFloat(timing.duration) *
481
+ (timing.duration.endsWith('ms') ? 1 : 1000),
482
+ 0,
483
+ );
484
+ expect(samples.easing).toBe(timing.easing);
485
+ for (const {t, eased, right} of samples.out) {
486
+ const expected = box.x + box.width * (0.2 + 0.6 * eased);
487
+ expect(Math.abs(right - expected), `edge at ${t}`).toBeLessThan(1);
488
+ }
489
+ });
490
+
491
+ for (const pointer of ['a mouse', 'a finger'] as const) {
492
+ test.describe(`with ${pointer}`, () => {
493
+ test.use(
494
+ pointer === 'a finger'
495
+ ? {hasTouch: true, isMobile: true}
496
+ : {hasTouch: false, isMobile: false},
497
+ );
498
+
499
+ test('under reduced motion a new value lands at once', async ({page}) => {
500
+ // Both shared reduced-motion queries, a fine pointer's and a coarse
501
+ // pointer's, set the fill's duration to 0 s.
502
+ await page.emulateMedia({reducedMotion: 'reduce'});
503
+ await open(page, 20);
504
+ expect(
505
+ await page.evaluate(() => matchMedia('(pointer: coarse)').matches),
506
+ ).toBe(pointer === 'a finger');
507
+ // Read the fill in the same task React commits the new value: a running
508
+ // transition shows up there, before any frame could finish it.
509
+ const state = await page.evaluate(
510
+ async ({storyId}) =>
511
+ new Promise<{running: number; edge: number}>(resolve => {
512
+ const track = document.querySelector(
513
+ '[role="progressbar"]',
514
+ ) as Element;
515
+ const fill = track.firstElementChild as HTMLElement;
516
+ const observer = new MutationObserver(() => {
517
+ observer.disconnect();
518
+ const t = track.getBoundingClientRect();
519
+ resolve({
520
+ running: fill.getAnimations().length,
521
+ edge: (fill.getBoundingClientRect().right - t.left) / t.width,
522
+ });
523
+ });
524
+ observer.observe(fill, {
525
+ attributes: true,
526
+ attributeFilter: ['style'],
527
+ });
528
+ (
529
+ window as unknown as {
530
+ __STORYBOOK_ADDONS_CHANNEL__: {
531
+ emit: (event: string, payload: unknown) => void;
532
+ };
533
+ }
534
+ ).__STORYBOOK_ADDONS_CHANNEL__.emit('updateStoryArgs', {
535
+ storyId,
536
+ updatedArgs: {value: 80},
537
+ });
538
+ }),
539
+ {storyId: STORY},
540
+ );
541
+ // No transition, and the fill is already at its new value: nothing travels.
542
+ expect(state.running).toBe(0);
543
+ expect(state.edge).toBeCloseTo(0.8, 2);
544
+ });
545
+ });
546
+ }
547
+
548
+ test('below one track height of fill, the leading end follows the track\u2019s curve', async ({
549
+ page,
550
+ }) => {
551
+ await open(page, 0);
552
+ await setValue(page, 1);
553
+ await settle(page);
554
+ const box = await trackBox(page);
555
+ const colors = await page.evaluate(() => {
556
+ const bar = document.querySelector('[role="progressbar"]') as HTMLElement;
557
+ const parse = (value: string) =>
558
+ (value.match(/[\d.]+/g) ?? []).slice(0, 3).map(Number);
559
+ return {
560
+ fill: parse(
561
+ getComputedStyle(bar.firstElementChild as Element).backgroundColor,
562
+ ),
563
+ track: parse(getComputedStyle(bar).backgroundColor),
564
+ };
565
+ });
566
+ const shot = await page.screenshot({
567
+ clip: {x: box.x, y: box.y, width: 24, height: box.height},
568
+ });
569
+ const ink = await page.evaluate(
570
+ async ({png, h, fill, track}) => {
571
+ const bitmap = await createImageBitmap(
572
+ await (await fetch(`data:image/png;base64,${png}`)).blob(),
573
+ );
574
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
575
+ const context = canvas.getContext('2d');
576
+ if (context == null) {
577
+ throw new Error('no 2d context');
578
+ }
579
+ context.drawImage(bitmap, 0, 0);
580
+ const {data, width, height} = context.getImageData(
581
+ 0,
582
+ 0,
583
+ bitmap.width,
584
+ bitmap.height,
585
+ );
586
+ // How much of each pixel is the fill: its color projected on the line
587
+ // from the track's color to the fill's (the page around the track's
588
+ // rounded end projects below zero).
589
+ const axis = [0, 1, 2].map(k => fill[k] - track[k]);
590
+ const norm = axis.reduce((sum, v) => sum + v * v, 0);
591
+ const coverage = (x: number, y: number) => {
592
+ const i = (y * width + x) * 4;
593
+ const dot = [0, 1, 2].reduce(
594
+ (sum, k) => sum + (data[i + k] - track[k]) * axis[k],
595
+ 0,
596
+ );
597
+ return Math.min(1, Math.max(0, dot / norm));
598
+ };
599
+ const columns: number[] = [];
600
+ for (let x = 0; x < width; x += 1) {
601
+ let count = 0;
602
+ for (let y = 0; y < height; y += 1) {
603
+ count += coverage(x, y);
604
+ }
605
+ columns.push(count);
606
+ }
607
+ const scale = bitmap.height / h;
608
+ return {
609
+ scale,
610
+ width: columns.filter(c => c > 0.5).length / scale,
611
+ heights: columns.map(c => c / scale),
612
+ };
613
+ },
614
+ {png: shot.toString('base64'), h: box.height, ...colors},
615
+ );
616
+ // About 1% of the track wide...
617
+ expect(ink.width).toBeGreaterThan(1);
618
+ expect(ink.width).toBeLessThanOrEqual((box.width * 1) / 100 + 1.5);
619
+ // ...and shaped by the track's own rounded end (radius half its height),
620
+ // column by column, where a squeezed capsule (main) rounds off with half of
621
+ // its own 3.7 px width and stands nearly full height at once.
622
+ const radius = box.height / 2;
623
+ for (let column = 0; column < 4; column += 1) {
624
+ const x = (column + 0.5) / ink.scale;
625
+ const expected = 2 * Math.sqrt(radius ** 2 - (radius - x) ** 2);
626
+ expect(
627
+ Math.abs(ink.heights[column] - expected),
628
+ `height at ${x.toFixed(2)} px`,
629
+ ).toBeLessThanOrEqual(1);
630
+ }
631
+ });
632
+
633
+ test('in forced colors the fill keeps its whole outline, a closed capsule inside the track', async ({
634
+ page,
635
+ }) => {
636
+ // architecture:theme-tokens INV11: forced colors paint the fill with
637
+ // Canvas, and its CanvasText outline keeps its shape visible. The track
638
+ // clips what it holds, so the outline stays whole only when the fill ends
639
+ // where it is drawn: under forced colors the fill is sized to the value, not
640
+ // slid along the track.
641
+ await page.emulateMedia({forcedColors: 'active'});
642
+ for (const {globals, fromLeft} of [
643
+ {globals: '', fromLeft: true},
644
+ {globals: 'direction:rtl', fromLeft: false},
645
+ ]) {
646
+ await open(page, 0, globals);
647
+ for (const value of [2, 50]) {
648
+ await setValue(page, value);
649
+ await settle(page);
650
+ const box = await trackBox(page);
651
+ const {track, fill, outline} = await page.evaluate(() => {
652
+ const bar = document.querySelector(
653
+ '[role="progressbar"]',
654
+ ) as HTMLElement;
655
+ const child = bar.firstElementChild as HTMLElement;
656
+ const r = (e: Element) => {
657
+ const b = e.getBoundingClientRect();
658
+ return {left: b.left, right: b.right};
659
+ };
660
+ return {
661
+ track: r(bar),
662
+ fill: r(child),
663
+ outline: (getComputedStyle(child).outlineColor.match(/[\d.]+/g) ?? [])
664
+ .slice(0, 3)
665
+ .map(Number),
666
+ };
667
+ });
668
+ const label = `${globals || 'ltr'} ${value}%`;
669
+ // The fill's box lies inside the track and ends at the value.
670
+ const filled = ((track.right - track.left) * value) / 100;
671
+ if (fromLeft) {
672
+ expect(fill.left - track.left, label).toBeCloseTo(0, 0);
673
+ expect(fill.right - track.left, label).toBeCloseTo(filled, 0);
674
+ } else {
675
+ expect(track.right - fill.right, label).toBeCloseTo(0, 0);
676
+ expect(track.right - fill.left, label).toBeCloseTo(filled, 0);
677
+ }
678
+ // The outline's cap at the track's inline-start edge is drawn: at mid
679
+ // height the edge's first pixels are the outline's color.
680
+ const shot = await page.screenshot({
681
+ clip: {
682
+ x: fromLeft ? box.x : box.x + box.width - 2,
683
+ y: box.y,
684
+ width: 2,
685
+ height: box.height,
686
+ },
687
+ });
688
+ const nearest = await page.evaluate(
689
+ async ({png, outline}) => {
690
+ const bitmap = await createImageBitmap(
691
+ await (await fetch(`data:image/png;base64,${png}`)).blob(),
692
+ );
693
+ const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
694
+ const context = canvas.getContext('2d');
695
+ if (context == null) {
696
+ throw new Error('no 2d context');
697
+ }
698
+ context.drawImage(bitmap, 0, 0);
699
+ const {data, width, height} = context.getImageData(
700
+ 0,
701
+ 0,
702
+ bitmap.width,
703
+ bitmap.height,
704
+ );
705
+ const y = Math.floor(height / 2);
706
+ let best = Infinity;
707
+ for (let x = 0; x < width; x += 1) {
708
+ const i = (y * width + x) * 4;
709
+ const distance = Math.max(
710
+ ...[0, 1, 2].map(k => Math.abs(data[i + k] - outline[k])),
711
+ );
712
+ best = Math.min(best, distance);
713
+ }
714
+ return best;
715
+ },
716
+ {png: shot.toString('base64'), outline},
717
+ );
718
+ expect(nearest, `${label} start cap`).toBeLessThanOrEqual(64);
719
+ }
720
+ }
721
+ });
722
+
723
+ test('storyboard: the fill moving from 20% to 80% @storyboard', async ({
724
+ page,
725
+ }, testInfo) => {
726
+ await open(page, 20);
727
+ const track = await trackBox(page);
728
+ const cast = await startScreencast(page);
729
+ await page.waitForTimeout(200);
730
+ await setValue(page, 80);
731
+ // Recorded until the change has finished, however long the machine takes.
732
+ await settle(page);
733
+ const frames = await cast.stop();
734
+ const edges = await leadingEdges(page, frames, track);
735
+ // The trace is the assertion: the edge moves forward only, from 20% to
736
+ // 80%, with no jump bigger than a quarter of the travel between frames.
737
+ const travel = track.width * 0.6;
738
+ expect(edges[0]).toBeCloseTo(track.width * 0.2, 0);
739
+ expect(edges[edges.length - 1]).toBeCloseTo(track.width * 0.8, 0);
740
+ for (let i = 1; i < edges.length; i += 1) {
741
+ expect(edges[i]).toBeGreaterThanOrEqual(edges[i - 1] - 1);
742
+ expect(edges[i] - edges[i - 1]).toBeLessThan(travel / 4);
743
+ }
744
+ const moving = frames
745
+ .map((frame, i) => ({t: frame.t, edge: edges[i], data: frame.data}))
746
+ .filter(
747
+ (f, i) =>
748
+ i === 0 ||
749
+ Math.abs(f.edge - edges[i - 1]) > 0.25 ||
750
+ i === edges.length - 1,
751
+ );
752
+ fs.mkdirSync(EVIDENCE, {recursive: true});
753
+ fs.writeFileSync(
754
+ path.join(EVIDENCE, 'progress-bar-fill-move.json'),
755
+ JSON.stringify(
756
+ {
757
+ story: STORY,
758
+ from: 20,
759
+ to: 80,
760
+ trackWidth: track.width,
761
+ frames: moving.map(f => ({
762
+ t: Math.round(f.t - moving[0].t),
763
+ edge: Math.round(f.edge * 10) / 10,
764
+ })),
765
+ },
766
+ null,
767
+ 1,
768
+ ),
769
+ );
770
+ // The contact sheet: the track in every frame where the fill moved.
771
+ const sheet = await page.context().newPage();
772
+ try {
773
+ const png = await sheet.evaluate(
774
+ async ({items, box, vw}) => {
775
+ const decoded = await Promise.all(
776
+ items.map(async item =>
777
+ createImageBitmap(
778
+ await (await fetch(`data:image/png;base64,${item}`)).blob(),
779
+ ),
780
+ ),
781
+ );
782
+ const scale = decoded[0].width / vw;
783
+ const pad = 6 * scale;
784
+ const w = Math.round(box.width * scale + pad * 2);
785
+ const h = Math.round(box.height * scale + pad * 2);
786
+ const canvas = new OffscreenCanvas(w, decoded.length * h);
787
+ const context = canvas.getContext('2d');
788
+ if (context == null) {
789
+ throw new Error('no 2d context');
790
+ }
791
+ context.fillStyle = '#fff';
792
+ context.fillRect(0, 0, canvas.width, canvas.height);
793
+ decoded.forEach((bitmap, i) => {
794
+ context.drawImage(
795
+ bitmap,
796
+ Math.round(box.x * scale - pad),
797
+ Math.round(box.y * scale - pad),
798
+ w,
799
+ h,
800
+ 0,
801
+ i * h,
802
+ w,
803
+ h,
804
+ );
805
+ });
806
+ const blob = await canvas.convertToBlob({type: 'image/png'});
807
+ const bytes = new Uint8Array(await blob.arrayBuffer());
808
+ let binary = '';
809
+ for (const byte of bytes) {
810
+ binary += String.fromCharCode(byte);
811
+ }
812
+ return btoa(binary);
813
+ },
814
+ {
815
+ items: moving.map(f => f.data),
816
+ box: track,
817
+ vw: page.viewportSize()?.width ?? 402,
818
+ },
819
+ );
820
+ fs.writeFileSync(
821
+ path.join(EVIDENCE, 'progress-bar-fill-move.png'),
822
+ Buffer.from(png, 'base64'),
823
+ );
824
+ await testInfo.attach('progress-bar-fill-move.png', {
825
+ path: path.join(EVIDENCE, 'progress-bar-fill-move.png'),
826
+ contentType: 'image/png',
827
+ });
828
+ } finally {
829
+ await sheet.close();
830
+ }
831
+ });