@hanzogui/kitchen-sink 3.0.5

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 (431) hide show
  1. package/.detoxrc.js +130 -0
  2. package/.env.production +2 -0
  3. package/.maestro/config.yaml +4 -0
  4. package/.maestro/flows/shorthand-variables.yaml +23 -0
  5. package/.watchmanconfig +1 -0
  6. package/LICENSE +21 -0
  7. package/README.md +11 -0
  8. package/app.json +43 -0
  9. package/assets/adaptive-icon.png +0 -0
  10. package/assets/favicon.png +0 -0
  11. package/assets/icon.png +0 -0
  12. package/assets/splash.png +0 -0
  13. package/babel.config.js +25 -0
  14. package/e2e/CompilerExtraction.test.ts +147 -0
  15. package/e2e/GroupPressNative.test.ts +167 -0
  16. package/e2e/MediaQueryGtMd.test.ts +71 -0
  17. package/e2e/NativePortal.test.ts +113 -0
  18. package/e2e/PointerEvents.test.ts +116 -0
  19. package/e2e/PressStyleNative.noRngh.test.ts +191 -0
  20. package/e2e/PressStyleNative.test.ts +231 -0
  21. package/e2e/SafeArea.test.ts +57 -0
  22. package/e2e/SelectAndroidOnPress.test.ts +181 -0
  23. package/e2e/SelectRemount.test.ts +137 -0
  24. package/e2e/SheetDragResist.test.ts +370 -0
  25. package/e2e/SheetKeyboardDrag.test.ts +249 -0
  26. package/e2e/SheetScrollableDrag.test.ts +560 -0
  27. package/e2e/ShorthandVariables.test.ts +53 -0
  28. package/e2e/ThemeChangeBasic.test.ts +123 -0
  29. package/e2e/ThemeMutation.test.ts +80 -0
  30. package/e2e/check-rngh-status.test.ts +31 -0
  31. package/e2e/jest.config.js +19 -0
  32. package/e2e/utils/colors.ts +75 -0
  33. package/e2e/utils/navigation.ts +53 -0
  34. package/eas.json +22 -0
  35. package/flows/AlertDialog.yaml +17 -0
  36. package/flows/OpenApp.yaml +25 -0
  37. package/flows/Select.yaml +13 -0
  38. package/flows/Sheet.yaml +12 -0
  39. package/flows/Tabs.yaml +13 -0
  40. package/flows/Toast.yaml +14 -0
  41. package/flows/WarmUp.yaml +24 -0
  42. package/index.html +21 -0
  43. package/index.js +17 -0
  44. package/metro.config.js +64 -0
  45. package/next-router-shim.ts +9 -0
  46. package/package.json +118 -0
  47. package/plans/toast-2.md +471 -0
  48. package/playwright.config.ts +71 -0
  49. package/plugins/expo-modules-core-swift6.js +76 -0
  50. package/pod-install.sh +7 -0
  51. package/public/favicon.svg +70 -0
  52. package/public/fonts/inter.css +15 -0
  53. package/public/fonts/noto-cn.otf +0 -0
  54. package/public/gui-icon.svg +68 -0
  55. package/run-detox.sh +230 -0
  56. package/run-native-tests.sh +4 -0
  57. package/run-tests-parallel.ts +195 -0
  58. package/screenshots/Screenshotter.test.tsx +48 -0
  59. package/src/AnimationDemos.tsx +131 -0
  60. package/src/App.native.tsx +121 -0
  61. package/src/App.tsx +121 -0
  62. package/src/Navigation.tsx +98 -0
  63. package/src/Sandbox.tsx +87 -0
  64. package/src/TestDynamicEval.tsx +33 -0
  65. package/src/TestNativeSheet.tsx +100 -0
  66. package/src/components/TimedRender.tsx +18 -0
  67. package/src/constants/test-ids.ts +52 -0
  68. package/src/features/demos/demo-screen.tsx +72 -0
  69. package/src/features/home/ColorSchemeListItem.tsx +41 -0
  70. package/src/features/home/TestBuildAButton.tsx +102 -0
  71. package/src/features/home/TestSeparator.tsx +0 -0
  72. package/src/features/home/screen.tsx +285 -0
  73. package/src/features/testcases/screen.tsx +59 -0
  74. package/src/features/testcases/test-screen.tsx +50 -0
  75. package/src/generatedV5Theme.ts +112 -0
  76. package/src/gui.config.ts +411 -0
  77. package/src/guy.png +0 -0
  78. package/src/index.tsx +6 -0
  79. package/src/provider/index.tsx +18 -0
  80. package/src/test-gui-stack.tsx +11 -0
  81. package/src/test.tsx +3 -0
  82. package/src/useKitchenSinkTheme.tsx +15 -0
  83. package/src/usecases/ActionsSheetComparison.tsx +194 -0
  84. package/src/usecases/AnimatePresenceEnterExitCase.tsx +255 -0
  85. package/src/usecases/AnimatePresenceExitTest.tsx +69 -0
  86. package/src/usecases/AnimatedByProp.tsx +39 -0
  87. package/src/usecases/AnimationComprehensiveCase.tsx +2515 -0
  88. package/src/usecases/AnimationValueLoggingCase.tsx +526 -0
  89. package/src/usecases/AnimationsWithMediaQueriesCase.tsx +110 -0
  90. package/src/usecases/Benchmark.tsx +148 -0
  91. package/src/usecases/BenchmarkSelect.tsx +34 -0
  92. package/src/usecases/ButtonCircular.tsx +3 -0
  93. package/src/usecases/ButtonCustom.tsx +33 -0
  94. package/src/usecases/ButtonIconColor.tsx +18 -0
  95. package/src/usecases/ButtonInverse.tsx +30 -0
  96. package/src/usecases/ButtonUnstyled.tsx +31 -0
  97. package/src/usecases/CheckboxDisabledOnPress.tsx +62 -0
  98. package/src/usecases/ClickDuringEnterCase.tsx +59 -0
  99. package/src/usecases/CodeExamplesInput.tsx +9 -0
  100. package/src/usecases/ColorTokenFallback.tsx +52 -0
  101. package/src/usecases/CompilerExtraction.tsx +380 -0
  102. package/src/usecases/ComplexVariants.tsx +164 -0
  103. package/src/usecases/CrashAdaptSheet.tsx +98 -0
  104. package/src/usecases/CustomStyledAnimatedPopover.tsx +42 -0
  105. package/src/usecases/CustomStyledAnimatedTooltip.tsx +72 -0
  106. package/src/usecases/DOMNodeAPIs.tsx +154 -0
  107. package/src/usecases/DialogFocusScopeCase.tsx +277 -0
  108. package/src/usecases/DialogFocusScopeDebug.tsx +85 -0
  109. package/src/usecases/DialogNestedCase.tsx +121 -0
  110. package/src/usecases/DialogOpenControlled.tsx +49 -0
  111. package/src/usecases/DialogPointerEventsCase.tsx +58 -0
  112. package/src/usecases/DialogScopedCase.tsx +106 -0
  113. package/src/usecases/DialogSheetAdaptCase.tsx +178 -0
  114. package/src/usecases/DialogSheetAdaptResizeCase.tsx +98 -0
  115. package/src/usecases/DismissLayerStackingCase.tsx +223 -0
  116. package/src/usecases/DriverDisableAnimationPropsCase.tsx +44 -0
  117. package/src/usecases/Example.tsx +10 -0
  118. package/src/usecases/ExitCompletionCase.tsx +713 -0
  119. package/src/usecases/FocusVisibleButton.tsx +14 -0
  120. package/src/usecases/FocusVisibleButtonPointer.tsx +13 -0
  121. package/src/usecases/FocusVisibleButtonWithFocusStyle.tsx +16 -0
  122. package/src/usecases/FocusWithinCase.tsx +55 -0
  123. package/src/usecases/FontTokensInVariants.tsx +14 -0
  124. package/src/usecases/FormButtonTypeCase.tsx +34 -0
  125. package/src/usecases/GlobalScopedTriggerIsolationCase.tsx +178 -0
  126. package/src/usecases/GroupHoverMobile.tsx +39 -0
  127. package/src/usecases/GroupPressInVariant.tsx +92 -0
  128. package/src/usecases/GroupPressNative.tsx +200 -0
  129. package/src/usecases/GroupProp.tsx +96 -0
  130. package/src/usecases/GroupPseudoVariantOverride.tsx +56 -0
  131. package/src/usecases/GroupUseCases.tsx +94 -0
  132. package/src/usecases/HeightMediaQueryOverrideCase.tsx +183 -0
  133. package/src/usecases/InputAutoFocusAfterMenuCase.tsx +105 -0
  134. package/src/usecases/InputAutoFocusStyledCase.tsx +39 -0
  135. package/src/usecases/KeyboardControllerTest.tsx +146 -0
  136. package/src/usecases/ListItem.tsx +123 -0
  137. package/src/usecases/MediaQueriesV5.tsx +137 -0
  138. package/src/usecases/MediaQueryGtMd.tsx +73 -0
  139. package/src/usecases/MenuAboveDialogCase.tsx +75 -0
  140. package/src/usecases/MenuAccessibilityCase.tsx +133 -0
  141. package/src/usecases/MenuAnimatePositionCase.tsx +41 -0
  142. package/src/usecases/MenuArrowAnimatePresenceCase.tsx +98 -0
  143. package/src/usecases/MenuAsChildPositionCase.tsx +24 -0
  144. package/src/usecases/MenuAutoResizeCase.tsx +57 -0
  145. package/src/usecases/MenuBottomCase.tsx +55 -0
  146. package/src/usecases/MenuFocusLeaveCase.tsx +135 -0
  147. package/src/usecases/MenuHighlightCase.tsx +44 -0
  148. package/src/usecases/MenuItemFocusCase.tsx +79 -0
  149. package/src/usecases/MenuItemPseudoOverrideCase.tsx +270 -0
  150. package/src/usecases/MenuMultiTriggerCase.tsx +47 -0
  151. package/src/usecases/MenuOverflowCase.tsx +60 -0
  152. package/src/usecases/MenuSubCase.tsx +223 -0
  153. package/src/usecases/MenuSubLeftCase.tsx +178 -0
  154. package/src/usecases/MenuSubNestedPositionCase.tsx +171 -0
  155. package/src/usecases/MenuSubStyledCase.tsx +145 -0
  156. package/src/usecases/MenuThemeCase.tsx +50 -0
  157. package/src/usecases/MenuUnstyledCase.tsx +52 -0
  158. package/src/usecases/MultiDriverAnimation.tsx +118 -0
  159. package/src/usecases/NativePortalTest.tsx +179 -0
  160. package/src/usecases/NewInputBasic.tsx +16 -0
  161. package/src/usecases/NewInputEvents.tsx +29 -0
  162. package/src/usecases/NonGuiTextStyledType.tsx +23 -0
  163. package/src/usecases/OnLayoutCase.tsx +134 -0
  164. package/src/usecases/OnLayoutScaleCase.tsx +88 -0
  165. package/src/usecases/OnLayoutStressCase.tsx +353 -0
  166. package/src/usecases/OpacityModifierCase.tsx +113 -0
  167. package/src/usecases/OverlayStyled.tsx +66 -0
  168. package/src/usecases/ParagraphSpanFontInheritance.tsx +53 -0
  169. package/src/usecases/PlaceholderTextColor.tsx +20 -0
  170. package/src/usecases/PointerEventsCase.tsx +100 -0
  171. package/src/usecases/PopoverAndMenuMultiTriggerCase.tsx +138 -0
  172. package/src/usecases/PopoverCase.tsx +222 -0
  173. package/src/usecases/PopoverContentStyledPlusAnimations.tsx +44 -0
  174. package/src/usecases/PopoverFocusScopeCase.tsx +171 -0
  175. package/src/usecases/PopoverHoverableCase.tsx +167 -0
  176. package/src/usecases/PopoverHoverableDisableClickCase.tsx +118 -0
  177. package/src/usecases/PopoverHoverableRapidCase.tsx +103 -0
  178. package/src/usecases/PopoverHoverableScopedCase.tsx +135 -0
  179. package/src/usecases/PopoverScopedCase.tsx +76 -0
  180. package/src/usecases/PopoverTriggerIsolationCase.tsx +80 -0
  181. package/src/usecases/PressStyleNative.tsx +143 -0
  182. package/src/usecases/PseudoStyleMerge.tsx +25 -0
  183. package/src/usecases/PseudoTransitionCase.tsx +174 -0
  184. package/src/usecases/RawAnimatedValueCase.tsx +231 -0
  185. package/src/usecases/RemoveScrollCase.tsx +66 -0
  186. package/src/usecases/RenderPropCase.tsx +263 -0
  187. package/src/usecases/SafeAreaCase.tsx +236 -0
  188. package/src/usecases/ScrollViewRefCase.tsx +88 -0
  189. package/src/usecases/SecondPage.tsx +5 -0
  190. package/src/usecases/SelectAndroidOnPress.tsx +129 -0
  191. package/src/usecases/SelectFocusScopeCase.tsx +270 -0
  192. package/src/usecases/SelectRemount.tsx +136 -0
  193. package/src/usecases/Shadows.tsx +5 -0
  194. package/src/usecases/SheetAnimationCase.tsx +155 -0
  195. package/src/usecases/SheetDragCase.tsx +183 -0
  196. package/src/usecases/SheetDragResistCase.tsx +433 -0
  197. package/src/usecases/SheetDragResistCase.web.tsx +359 -0
  198. package/src/usecases/SheetKeyboardDragCase.tsx +328 -0
  199. package/src/usecases/SheetKeyboardFitContentCase.tsx +165 -0
  200. package/src/usecases/SheetOnAnimationCompleteCase.tsx +54 -0
  201. package/src/usecases/SheetScrollLockCase.tsx +166 -0
  202. package/src/usecases/SheetScrollableDrag.tsx +249 -0
  203. package/src/usecases/SheetSnapPointsFitCase.tsx +393 -0
  204. package/src/usecases/ShorthandVariables.tsx +49 -0
  205. package/src/usecases/SlowThemeReRender.tsx +48 -0
  206. package/src/usecases/SpinnerCustomColors.tsx +34 -0
  207. package/src/usecases/StackZIndex.tsx +82 -0
  208. package/src/usecases/StressPage.tsx +301 -0
  209. package/src/usecases/StylePlatform.tsx +30 -0
  210. package/src/usecases/StyleProp.tsx +29 -0
  211. package/src/usecases/StyledAnchor.tsx +27 -0
  212. package/src/usecases/StyledButtonAnimationAuto.tsx +99 -0
  213. package/src/usecases/StyledButtonTheme.tsx +63 -0
  214. package/src/usecases/StyledButtonVariantPseudo.tsx +25 -0
  215. package/src/usecases/StyledButtonVariantPseudoMerge.tsx +77 -0
  216. package/src/usecases/StyledCheckboxTheme.tsx +23 -0
  217. package/src/usecases/StyledContextColor.tsx +246 -0
  218. package/src/usecases/StyledContextTokens.tsx +147 -0
  219. package/src/usecases/StyledHOCNamed.tsx +20 -0
  220. package/src/usecases/StyledHtmlCase.tsx +144 -0
  221. package/src/usecases/StyledIconColor.tsx +19 -0
  222. package/src/usecases/StyledInputFocusStyle.tsx +21 -0
  223. package/src/usecases/StyledInputOnFocus.tsx +30 -0
  224. package/src/usecases/StyledMediaQueryMerge.tsx +95 -0
  225. package/src/usecases/StyledOverridePsuedo.tsx +26 -0
  226. package/src/usecases/StyledRNW.tsx +61 -0
  227. package/src/usecases/StyledStyleableInputOnFocus.tsx +34 -0
  228. package/src/usecases/StyledStyleableInputVariant.tsx +48 -0
  229. package/src/usecases/StyledStyledStyleableInputOnFocus.tsx +36 -0
  230. package/src/usecases/StyledVariantTextColor.tsx +25 -0
  231. package/src/usecases/StyledViewOnFocus.tsx +32 -0
  232. package/src/usecases/TabHoverAnimationCase.tsx +212 -0
  233. package/src/usecases/TextNestedInheritance.tsx +80 -0
  234. package/src/usecases/ThemeChange.tsx +100 -0
  235. package/src/usecases/ThemeChangeBasic.tsx +52 -0
  236. package/src/usecases/ThemeComponentResolution.tsx +119 -0
  237. package/src/usecases/ThemeConditionalName.tsx +31 -0
  238. package/src/usecases/ThemeMediaAnimationCase.tsx +39 -0
  239. package/src/usecases/ThemeMutation.tsx +86 -0
  240. package/src/usecases/ThemeNested.tsx +103 -0
  241. package/src/usecases/ThemeReset.tsx +62 -0
  242. package/src/usecases/ThemeShallowCase.tsx +83 -0
  243. package/src/usecases/ToastCase.tsx +46 -0
  244. package/src/usecases/ToggleGroupActiveProps.tsx +40 -0
  245. package/src/usecases/ToggleGroupXGroupCase.tsx +104 -0
  246. package/src/usecases/TooltipAnimationCase.tsx +99 -0
  247. package/src/usecases/TooltipCase.tsx +32 -0
  248. package/src/usecases/TooltipGlobalPatternCase.tsx +83 -0
  249. package/src/usecases/TooltipGroupCase.tsx +102 -0
  250. package/src/usecases/TooltipMultiTriggerCase.tsx +88 -0
  251. package/src/usecases/TooltipPositionJumpCase.tsx +91 -0
  252. package/src/usecases/TooltipTriggerInlineCase.tsx +60 -0
  253. package/src/usecases/TransformMediaQueryMerge.tsx +98 -0
  254. package/src/usecases/UseCases.tsx +409 -0
  255. package/src/usecases/UseTheme.tsx +41 -0
  256. package/src/usecases/V5ThemeBuilderOutput.tsx +231 -0
  257. package/src/usecases/VariantFontFamily.tsx +25 -0
  258. package/src/usecases/VariantsOrder.tsx +117 -0
  259. package/src/usecases/ZIndex.tsx +155 -0
  260. package/src/usecases/helpers.tsx +44 -0
  261. package/src/usecases/index.native.ts +122 -0
  262. package/src/usecases/index.ts +3 -0
  263. package/src/usecases/index.web.ts +177 -0
  264. package/tests/AnimatePresenceEnterExit.animated.test.tsx +176 -0
  265. package/tests/AnimatedByProp.animated.test.tsx +138 -0
  266. package/tests/AnimationBehavior.animated.test.tsx +543 -0
  267. package/tests/AnimationTiming.animated.test.tsx +195 -0
  268. package/tests/AnimationsWithMediaQueries.animated.test.tsx +154 -0
  269. package/tests/BuildAButton.test.tsx +87 -0
  270. package/tests/ButtonCircular.test.tsx +17 -0
  271. package/tests/ButtonCustom.test.tsx +17 -0
  272. package/tests/ButtonIconColor.test.tsx +23 -0
  273. package/tests/ButtonUnstyled.test.tsx +56 -0
  274. package/tests/ClickDuringEnter.animated.test.tsx +174 -0
  275. package/tests/ColorTokenFallback.test.tsx +45 -0
  276. package/tests/DOMNodeAPIs.test.tsx +161 -0
  277. package/tests/DialogFocusScope.animated.test.tsx +309 -0
  278. package/tests/DialogNested.test.tsx +128 -0
  279. package/tests/DialogOpenControlled.test.tsx +42 -0
  280. package/tests/DialogPointerEvents.animated.test.tsx +108 -0
  281. package/tests/DialogScoped.test.tsx +137 -0
  282. package/tests/DialogSheetAdapt.test.tsx +68 -0
  283. package/tests/DialogSheetAdaptResize.test.tsx +161 -0
  284. package/tests/DismissLayerStacking.test.tsx +292 -0
  285. package/tests/DriverDisableAnimationProps.animated.test.tsx +157 -0
  286. package/tests/ExitCompletion.animated.test.tsx +425 -0
  287. package/tests/ExitTimingCheck.animated.test.ts +34 -0
  288. package/tests/FocusVisibleButton.test.tsx +41 -0
  289. package/tests/FocusVisibleButtonPointerFocus.test.tsx +23 -0
  290. package/tests/FocusVisibleButtonPointerFocusWithFocusStyle.test.tsx +40 -0
  291. package/tests/FocusWithinStyle.animated.test.tsx +66 -0
  292. package/tests/FocusWithinStyle.test.tsx +60 -0
  293. package/tests/FormButtonType.test.tsx +42 -0
  294. package/tests/GlobalScopedTriggerIsolation.test.tsx +89 -0
  295. package/tests/GroupHoverMobile.test.tsx +52 -0
  296. package/tests/GroupPressInVariant.test.tsx +82 -0
  297. package/tests/GroupProp.test.tsx +30 -0
  298. package/tests/GroupPseudoVariantOverride.test.tsx +57 -0
  299. package/tests/GroupUseCases.test.tsx +111 -0
  300. package/tests/GuiSiteMotion.test.ts +481 -0
  301. package/tests/HeightMediaQueryOverride.test.tsx +112 -0
  302. package/tests/InputAutoFocusAfterMenu.test.tsx +55 -0
  303. package/tests/InputAutoFocusStyled.test.tsx +22 -0
  304. package/tests/ListItem.test.tsx +129 -0
  305. package/tests/MediaQueriesV5.test.tsx +113 -0
  306. package/tests/MediaQueryGtMd.test.tsx +84 -0
  307. package/tests/MenuAboveDialog.test.tsx +108 -0
  308. package/tests/MenuAccessibility.test.tsx +346 -0
  309. package/tests/MenuAnimatePosition.animated.test.tsx +57 -0
  310. package/tests/MenuArrowAnimatePresence.animated.test.tsx +71 -0
  311. package/tests/MenuAsChildPosition.test.tsx +16 -0
  312. package/tests/MenuAutoResize.test.tsx +54 -0
  313. package/tests/MenuFocusLeave.test.tsx +181 -0
  314. package/tests/MenuHighlight.test.tsx +165 -0
  315. package/tests/MenuHoverKeyboardBugs.test.tsx +252 -0
  316. package/tests/MenuItemFocus.test.tsx +59 -0
  317. package/tests/MenuItemPseudoOverride.test.tsx +231 -0
  318. package/tests/MenuMultiTrigger.test.tsx +101 -0
  319. package/tests/MenuOverflow.test.tsx +93 -0
  320. package/tests/MenuStayInFrame.test.tsx +102 -0
  321. package/tests/MenuSubKeyboardFocus.test.tsx +220 -0
  322. package/tests/MenuSubLeftSafePolygon.test.tsx +88 -0
  323. package/tests/MenuSubNestedPosition.test.tsx +48 -0
  324. package/tests/MenuSubSafePolygon.test.tsx +97 -0
  325. package/tests/MenuSubStyled.test.tsx +40 -0
  326. package/tests/MenuTheme.test.tsx +34 -0
  327. package/tests/MenuUnstyled.test.tsx +56 -0
  328. package/tests/MultiDriverAnimation.test.tsx +207 -0
  329. package/tests/NewInputBasic.test.tsx +50 -0
  330. package/tests/NewInputEvents.test.tsx +55 -0
  331. package/tests/OnLayout.test.tsx +163 -0
  332. package/tests/OnLayoutScale.test.tsx +100 -0
  333. package/tests/OnLayoutStress.test.tsx +304 -0
  334. package/tests/ParagraphSpanFontInheritance.test.tsx +73 -0
  335. package/tests/PointerEvents.test.tsx +123 -0
  336. package/tests/Popover.animated.test.tsx +234 -0
  337. package/tests/PopoverAndMenuMultiTrigger.test.tsx +184 -0
  338. package/tests/PopoverAnimatePosition.animated.test.tsx +51 -0
  339. package/tests/PopoverClickDuringEnter.animated.test.tsx +197 -0
  340. package/tests/PopoverFocusScope.test.tsx +242 -0
  341. package/tests/PopoverHoverable.test.tsx +383 -0
  342. package/tests/PopoverHoverableDisableClick.test.tsx +106 -0
  343. package/tests/PopoverHoverableRapid.test.tsx +129 -0
  344. package/tests/PopoverHoverableReposition.test.tsx +111 -0
  345. package/tests/PopoverHoverableScoped.animated.test.tsx +103 -0
  346. package/tests/PopoverHoverableStress.test.tsx +169 -0
  347. package/tests/PopoverInitialPosition.animated.test.tsx +82 -0
  348. package/tests/PopoverMiddlewareSkipRegression.animated.test.tsx +221 -0
  349. package/tests/PopoverScoped.test.tsx +128 -0
  350. package/tests/PopoverScopedPositionGlitch.animated.test.tsx +184 -0
  351. package/tests/PopoverTriggerIsolation.test.tsx +62 -0
  352. package/tests/PseudoTransition.animated.test.tsx +319 -0
  353. package/tests/RawAnimatedValue.test.tsx +147 -0
  354. package/tests/RemoveScroll.test.tsx +223 -0
  355. package/tests/RenderProp.test.tsx +293 -0
  356. package/tests/ScrollViewRef.test.tsx +39 -0
  357. package/tests/SelectClickHold.test.tsx +147 -0
  358. package/tests/SelectFocusScope.test.tsx +176 -0
  359. package/tests/SelectInnerPositioning.test.tsx +82 -0
  360. package/tests/SelectKeyboardNav.test.tsx +173 -0
  361. package/tests/SelectPositioning.test.tsx +56 -0
  362. package/tests/SelectTypeahead.test.tsx +63 -0
  363. package/tests/Shadows.test.tsx +14 -0
  364. package/tests/SheetAnimation.animated.test.tsx +413 -0
  365. package/tests/SheetDrag.animated.test.tsx +223 -0
  366. package/tests/SheetDragResist.animated.test.tsx +393 -0
  367. package/tests/SheetOnAnimationComplete.animated.test.tsx +62 -0
  368. package/tests/SheetScrollLock.animated.test.tsx +287 -0
  369. package/tests/SheetScrollableDrag.animated.test.tsx +1264 -0
  370. package/tests/SheetSnapPointsFit.animated.test.tsx +259 -0
  371. package/tests/ShorthandVariables.test.tsx +44 -0
  372. package/tests/SpinnerCustomColors.test.tsx +67 -0
  373. package/tests/StackZIndex.test.tsx +51 -0
  374. package/tests/StressPagePerf.test.tsx +76 -0
  375. package/tests/StylePlatform.test.tsx +38 -0
  376. package/tests/StyleProp.test.tsx +20 -0
  377. package/tests/StyledAnchor.test.tsx +17 -0
  378. package/tests/StyledButtonTheme.test.tsx +22 -0
  379. package/tests/StyledButtonVariantPseudo.test.tsx +20 -0
  380. package/tests/StyledButtonVariantPseudoMerge.animated.test.tsx +33 -0
  381. package/tests/StyledCheckboxTheme.test.tsx +16 -0
  382. package/tests/StyledContextColor.test.tsx +119 -0
  383. package/tests/StyledContextTokens.test.tsx +56 -0
  384. package/tests/StyledHOCNamed.test.tsx +16 -0
  385. package/tests/StyledHtml.test.tsx +161 -0
  386. package/tests/StyledIconColor.test.tsx +32 -0
  387. package/tests/StyledInputFocusStyle.test.tsx +19 -0
  388. package/tests/StyledInputOnFocus.test.tsx +27 -0
  389. package/tests/StyledMediaQueryMerge.test.tsx +66 -0
  390. package/tests/StyledRNW.test.tsx +17 -0
  391. package/tests/StyledStyleableInputOnFocus.test.tsx +27 -0
  392. package/tests/StyledStyleableInputVariant.test.tsx +22 -0
  393. package/tests/StyledStyledStyleableInputOnFocus.test.tsx +27 -0
  394. package/tests/StyledVariantTextColor.test.tsx +24 -0
  395. package/tests/StyledViewOnFocus.test.tsx +27 -0
  396. package/tests/TabHoverAnimation.animated.test.tsx +468 -0
  397. package/tests/TabHoverPositionSmooth.animated.test.tsx +129 -0
  398. package/tests/TextNestedInheritance.test.tsx +93 -0
  399. package/tests/ThemeChange.test.tsx +70 -0
  400. package/tests/ThemeComponentResolution.test.tsx +82 -0
  401. package/tests/ThemeConditionalName.test.tsx +34 -0
  402. package/tests/ThemeMediaAnimation.test.tsx +65 -0
  403. package/tests/ThemeNested.test.tsx +141 -0
  404. package/tests/ThemeReset.test.tsx +63 -0
  405. package/tests/ThemeShallow.test.tsx +95 -0
  406. package/tests/Toast.test.tsx +106 -0
  407. package/tests/ToggleGroup.test.tsx +61 -0
  408. package/tests/ToggleGroupActiveProps.test.tsx +38 -0
  409. package/tests/ToggleGroupXGroup.test.tsx +172 -0
  410. package/tests/TooltipAnimation.animated.test.tsx +260 -0
  411. package/tests/TooltipEnterInterrupt.animated.test.tsx +76 -0
  412. package/tests/TooltipGlobalPattern.animated.test.tsx +208 -0
  413. package/tests/TooltipGroup.animated.test.tsx +79 -0
  414. package/tests/TooltipMultiTrigger.test.tsx +116 -0
  415. package/tests/TooltipPositionJump.animated.test.tsx +229 -0
  416. package/tests/TooltipPositionJumpNotes.md +219 -0
  417. package/tests/TooltipRapidSwitch.animated.test.tsx +399 -0
  418. package/tests/TooltipTriggerInline.test.tsx +65 -0
  419. package/tests/TransformMediaQueryMerge.test.tsx +104 -0
  420. package/tests/TransitionEnterExit.animated.test.tsx +311 -0
  421. package/tests/UseTheme.test.tsx +16 -0
  422. package/tests/V5ThemeBuilderOutput.test.tsx +164 -0
  423. package/tests/VariantFontFamily.test.tsx +11 -0
  424. package/tests/VariantsOrder.test.tsx +53 -0
  425. package/tests/_debug_position.mjs +52 -0
  426. package/tests/test-utils.ts +106 -0
  427. package/tests/utils.tsx +54 -0
  428. package/tsconfig.json +45 -0
  429. package/vite-env.d.ts +1 -0
  430. package/vite.config.ts +14 -0
  431. package/webpack.config.js +139 -0
@@ -0,0 +1,2515 @@
1
+ import { useRef, useState, useEffect, useCallback } from 'react'
2
+ import { AnimatePresence } from '@hanzogui/animate-presence'
3
+ import { Button, Paragraph, Square, XStack, YStack, View, Text } from '@hanzo/gui'
4
+
5
+ /**
6
+ * COMPREHENSIVE ANIMATION TEST SUITE
7
+ *
8
+ * 30+ test scenarios covering all animation types, configurations, and edge cases.
9
+ * Each test logs EVERY frame of animation with full details for analysis.
10
+ *
11
+ * Log format: [ANIM_FRAME] scenario:<id> frame:<n> prop:<property> value:<value> time:<ms> delta:<ms>
12
+ */
13
+
14
+ // Global frame logging system
15
+ const useAnimationLogger = (
16
+ scenarioId: string,
17
+ elementRef: React.RefObject<HTMLElement | null>,
18
+ properties: string[]
19
+ ) => {
20
+ const frameCountRef = useRef(0)
21
+ const lastTimeRef = useRef(0)
22
+ const lastValuesRef = useRef<Record<string, string>>({})
23
+ const isLoggingRef = useRef(false)
24
+ const rafIdRef = useRef<number | null>(null)
25
+
26
+ const startLogging = useCallback(() => {
27
+ if (isLoggingRef.current) return
28
+ isLoggingRef.current = true
29
+ frameCountRef.current = 0
30
+ lastTimeRef.current = performance.now()
31
+ lastValuesRef.current = {}
32
+
33
+ console.log(`[ANIM_START] scenario:${scenarioId} time:${Date.now()}`)
34
+
35
+ const logFrame = () => {
36
+ if (!isLoggingRef.current || !elementRef.current) return
37
+
38
+ const now = performance.now()
39
+ const delta = now - lastTimeRef.current
40
+ const style = getComputedStyle(elementRef.current)
41
+
42
+ let hasChanges = false
43
+
44
+ for (const prop of properties) {
45
+ let value: string
46
+
47
+ if (prop === 'transform') {
48
+ value = style.transform
49
+ } else if (prop === 'opacity') {
50
+ value = style.opacity
51
+ } else if (prop === 'backgroundColor') {
52
+ value = style.backgroundColor
53
+ } else if (prop === 'width') {
54
+ value = style.width
55
+ } else if (prop === 'height') {
56
+ value = style.height
57
+ } else if (prop === 'borderRadius') {
58
+ value = style.borderRadius
59
+ } else if (prop === 'color') {
60
+ value = style.color
61
+ } else if (prop === 'left') {
62
+ value = style.left
63
+ } else if (prop === 'top') {
64
+ value = style.top
65
+ } else {
66
+ value = (style as any)[prop] || ''
67
+ }
68
+
69
+ if (lastValuesRef.current[prop] !== value) {
70
+ hasChanges = true
71
+ console.log(
72
+ `[ANIM_FRAME] scenario:${scenarioId} frame:${frameCountRef.current} prop:${prop} value:${value} time:${Math.round(now)} delta:${Math.round(delta)}`
73
+ )
74
+ lastValuesRef.current[prop] = value
75
+ }
76
+ }
77
+
78
+ if (hasChanges) {
79
+ frameCountRef.current++
80
+ lastTimeRef.current = now
81
+ }
82
+
83
+ rafIdRef.current = requestAnimationFrame(logFrame)
84
+ }
85
+
86
+ rafIdRef.current = requestAnimationFrame(logFrame)
87
+ }, [scenarioId, elementRef, properties])
88
+
89
+ const stopLogging = useCallback(() => {
90
+ isLoggingRef.current = false
91
+ if (rafIdRef.current) {
92
+ cancelAnimationFrame(rafIdRef.current)
93
+ }
94
+ console.log(
95
+ `[ANIM_END] scenario:${scenarioId} totalFrames:${frameCountRef.current} time:${Date.now()}`
96
+ )
97
+ }, [scenarioId])
98
+
99
+ useEffect(() => {
100
+ return () => {
101
+ if (rafIdRef.current) {
102
+ cancelAnimationFrame(rafIdRef.current)
103
+ }
104
+ }
105
+ }, [])
106
+
107
+ return { startLogging, stopLogging, frameCount: frameCountRef.current }
108
+ }
109
+
110
+ // ============================================================================
111
+ // MAIN COMPONENT
112
+ // ============================================================================
113
+
114
+ export function AnimationComprehensiveCase() {
115
+ return (
116
+ <YStack gap="$2" padding="$2" flex={1} overflow="scroll">
117
+ <Paragraph fontWeight="bold" fontSize="$5">
118
+ Comprehensive Animation Test Suite (30+ Scenarios)
119
+ </Paragraph>
120
+ <Paragraph size="$2" color="$color10">
121
+ Open console to see [ANIM_FRAME] logs
122
+ </Paragraph>
123
+
124
+ {/* SECTION 1: Basic Property Animations */}
125
+ <SectionHeader>1. Basic Property Animations</SectionHeader>
126
+ <Scenario01_OpacityBasic />
127
+ <Scenario02_ScaleBasic />
128
+ <Scenario03_TranslateX />
129
+ <Scenario04_TranslateY />
130
+ <Scenario05_Rotate />
131
+ <Scenario06_MultipleTransforms />
132
+
133
+ {/* SECTION 2: Dimension Animations */}
134
+ <SectionHeader>2. Dimension Animations</SectionHeader>
135
+ <Scenario07_Width />
136
+ <Scenario08_Height />
137
+ <Scenario09_WidthAndHeight />
138
+ <Scenario10_BorderRadius />
139
+
140
+ {/* SECTION 3: Color Animations */}
141
+ <SectionHeader>3. Color Animations</SectionHeader>
142
+ <Scenario11_BackgroundColor />
143
+ <Scenario12_TextColor />
144
+ <Scenario13_BorderColor />
145
+
146
+ {/* SECTION 4: Spring Physics Variations */}
147
+ <SectionHeader>4. Spring Physics Variations</SectionHeader>
148
+ <Scenario14_SpringBouncy />
149
+ <Scenario15_SpringLazy />
150
+ <Scenario16_SpringQuick />
151
+ <Scenario17_SpringCustom />
152
+
153
+ {/* SECTION 5: Timing Animations */}
154
+ <SectionHeader>5. Timing Animations</SectionHeader>
155
+ <Scenario18_Timing100ms />
156
+ <Scenario19_Timing200ms />
157
+ <Scenario20_TimingWithDelay />
158
+
159
+ {/* SECTION 6: Enter/Exit Animations */}
160
+ <SectionHeader>6. Enter/Exit Animations</SectionHeader>
161
+ <Scenario21_EnterStyle />
162
+ <Scenario22_ExitStyle />
163
+ <Scenario23_EnterExitCombined />
164
+
165
+ {/* SECTION 7: Edge Cases */}
166
+ <SectionHeader>7. Edge Cases</SectionHeader>
167
+ <Scenario24_RapidToggle />
168
+ <Scenario25_Interruption />
169
+ <Scenario26_AnimateOnly />
170
+ <Scenario27_AnimationConfig />
171
+
172
+ {/* SECTION 8: Complex Animations */}
173
+ <SectionHeader>8. Complex Animations</SectionHeader>
174
+ <Scenario28_MultiProperty />
175
+ <Scenario29_NestedAnimations />
176
+ <Scenario30_HoverAnimation />
177
+
178
+ {/* SECTION 9: Per-Property Animation Configs (Critical Tests) */}
179
+ <SectionHeader>9. Per-Property Animation Configs</SectionHeader>
180
+ <Scenario31_PerPropertyConfigs />
181
+ <Scenario32_PerPropertyWithInterruption />
182
+ <Scenario33_MixedSpringTiming />
183
+ <Scenario34_ComplexObjectManyProps />
184
+ <Scenario35_RapidPerPropertyChanges />
185
+
186
+ {/* SECTION 10: Timing-based Tests */}
187
+ <SectionHeader>10. Timing-based Tests</SectionHeader>
188
+ <Scenario36_TimingTest />
189
+
190
+ {/* SECTION 11: scaleX EnterStyle (BenchmarkChart reproduction) */}
191
+ <SectionHeader>11. scaleX EnterStyle</SectionHeader>
192
+ <Scenario37_EnterStyleScaleX />
193
+
194
+ {/* SECTION 12: Per-Property with Transform (animationClamped fix) */}
195
+ <SectionHeader>12. Per-Property with Transform</SectionHeader>
196
+ <Scenario38_PerPropertyWithTransform />
197
+ <Scenario39_ObjectFormatPerProperty />
198
+ <Scenario40_ObjectFormatNoDefault />
199
+ <Scenario41_PerPropertyWithDelay />
200
+
201
+ {/* SECTION 13: Enter/Exit Transition Props */}
202
+ <SectionHeader>13. Enter/Exit Transition Props</SectionHeader>
203
+ <Scenario42_TransitionEnterExit />
204
+ <Scenario43_TransitionEnterOnly />
205
+ <Scenario44_TransitionExitOnly />
206
+ <Scenario45_TransitionEnterExitWithDefault />
207
+ <Scenario46_TransitionEnterExitPerProperty />
208
+ <Scenario47_TransitionEnterExitWithDelay />
209
+
210
+ {/* SECTION 14: animateOnly with Exit/Enter Styles */}
211
+ <SectionHeader>14. animateOnly with Exit/Enter Styles</SectionHeader>
212
+ <Scenario48_AnimateOnlyWithExitStyle />
213
+ <Scenario49_AnimateOnlyWithEnterExitStyle />
214
+
215
+ {/* SECTION 15: Enter/Exit Timing Verification (Bug fixes) */}
216
+ <SectionHeader>15. Enter/Exit Timing Verification</SectionHeader>
217
+ <Scenario50_EnterTimingVerification />
218
+ <Scenario51_DurationNormalization />
219
+ <Scenario52_DurationNormalizationInlineConfig />
220
+ </YStack>
221
+ )
222
+ }
223
+
224
+ const SectionHeader = ({ children }: { children: string }) => (
225
+ <Paragraph fontWeight="bold" fontSize="$3" marginTop="$3" color="$blue10">
226
+ {children}
227
+ </Paragraph>
228
+ )
229
+
230
+ // ============================================================================
231
+ // SCENARIO 1: Basic Opacity Animation
232
+ // ============================================================================
233
+ function Scenario01_OpacityBasic() {
234
+ const [active, setActive] = useState(false)
235
+ const ref = useRef<HTMLDivElement>(null)
236
+ const { startLogging, stopLogging } = useAnimationLogger('01-opacity-basic', ref, [
237
+ 'opacity',
238
+ ])
239
+
240
+ return (
241
+ <XStack gap="$2" alignItems="center">
242
+ <Button
243
+ size="$2"
244
+ onPress={() => {
245
+ startLogging()
246
+ setActive(!active)
247
+ setTimeout(stopLogging, 1000)
248
+ }}
249
+ testID="scenario-01-trigger"
250
+ data-testid="scenario-01-trigger"
251
+ >
252
+ 01: Opacity
253
+ </Button>
254
+ <Square
255
+ ref={ref as any}
256
+ transition="quick"
257
+ size={40}
258
+ bg="$blue10"
259
+ opacity={active ? 0.2 : 1}
260
+ testID="scenario-01-target"
261
+ data-testid="scenario-01-target"
262
+ />
263
+ <Paragraph size="$1">{active ? '0.2' : '1'}</Paragraph>
264
+ </XStack>
265
+ )
266
+ }
267
+
268
+ // ============================================================================
269
+ // SCENARIO 2: Basic Scale Animation
270
+ // ============================================================================
271
+ function Scenario02_ScaleBasic() {
272
+ const [active, setActive] = useState(false)
273
+ const ref = useRef<HTMLDivElement>(null)
274
+ const { startLogging, stopLogging } = useAnimationLogger('02-scale-basic', ref, [
275
+ 'transform',
276
+ ])
277
+
278
+ return (
279
+ <XStack gap="$2" alignItems="center">
280
+ <Button
281
+ size="$2"
282
+ onPress={() => {
283
+ startLogging()
284
+ setActive(!active)
285
+ setTimeout(stopLogging, 1000)
286
+ }}
287
+ testID="scenario-02-trigger"
288
+ data-testid="scenario-02-trigger"
289
+ >
290
+ 02: Scale
291
+ </Button>
292
+ <Square
293
+ ref={ref as any}
294
+ transition="quick"
295
+ size={40}
296
+ bg="$green10"
297
+ scale={active ? 1.5 : 1}
298
+ testID="scenario-02-target"
299
+ data-testid="scenario-02-target"
300
+ />
301
+ <Paragraph size="$1">{active ? '1.5' : '1'}</Paragraph>
302
+ </XStack>
303
+ )
304
+ }
305
+
306
+ // ============================================================================
307
+ // SCENARIO 3: TranslateX Animation
308
+ // ============================================================================
309
+ function Scenario03_TranslateX() {
310
+ const [active, setActive] = useState(false)
311
+ const ref = useRef<HTMLDivElement>(null)
312
+ const { startLogging, stopLogging } = useAnimationLogger('03-translateX', ref, [
313
+ 'transform',
314
+ ])
315
+
316
+ return (
317
+ <XStack gap="$2" alignItems="center">
318
+ <Button
319
+ size="$2"
320
+ onPress={() => {
321
+ startLogging()
322
+ setActive(!active)
323
+ setTimeout(stopLogging, 1000)
324
+ }}
325
+ testID="scenario-03-trigger"
326
+ data-testid="scenario-03-trigger"
327
+ >
328
+ 03: TranslateX
329
+ </Button>
330
+ <Square
331
+ ref={ref as any}
332
+ transition="quick"
333
+ size={40}
334
+ bg="$blue10"
335
+ x={active ? 50 : 0}
336
+ testID="scenario-03-target"
337
+ data-testid="scenario-03-target"
338
+ />
339
+ <Paragraph size="$1">{active ? '50px' : '0'}</Paragraph>
340
+ </XStack>
341
+ )
342
+ }
343
+
344
+ // ============================================================================
345
+ // SCENARIO 4: TranslateY Animation
346
+ // ============================================================================
347
+ function Scenario04_TranslateY() {
348
+ const [active, setActive] = useState(false)
349
+ const ref = useRef<HTMLDivElement>(null)
350
+ const { startLogging, stopLogging } = useAnimationLogger('04-translateY', ref, [
351
+ 'transform',
352
+ ])
353
+
354
+ return (
355
+ <XStack gap="$2" alignItems="center">
356
+ <Button
357
+ size="$2"
358
+ onPress={() => {
359
+ startLogging()
360
+ setActive(!active)
361
+ setTimeout(stopLogging, 1000)
362
+ }}
363
+ testID="scenario-04-trigger"
364
+ data-testid="scenario-04-trigger"
365
+ >
366
+ 04: TranslateY
367
+ </Button>
368
+ <Square
369
+ ref={ref as any}
370
+ transition="quick"
371
+ size={40}
372
+ bg="$yellow10"
373
+ y={active ? -30 : 0}
374
+ testID="scenario-04-target"
375
+ data-testid="scenario-04-target"
376
+ />
377
+ <Paragraph size="$1">{active ? '-30px' : '0'}</Paragraph>
378
+ </XStack>
379
+ )
380
+ }
381
+
382
+ // ============================================================================
383
+ // SCENARIO 5: Rotate Animation
384
+ // ============================================================================
385
+ function Scenario05_Rotate() {
386
+ const [active, setActive] = useState(false)
387
+ const ref = useRef<HTMLDivElement>(null)
388
+ const { startLogging, stopLogging } = useAnimationLogger('05-rotate', ref, [
389
+ 'transform',
390
+ ])
391
+
392
+ return (
393
+ <XStack gap="$2" alignItems="center">
394
+ <Button
395
+ size="$2"
396
+ onPress={() => {
397
+ startLogging()
398
+ setActive(!active)
399
+ setTimeout(stopLogging, 1000)
400
+ }}
401
+ testID="scenario-05-trigger"
402
+ data-testid="scenario-05-trigger"
403
+ >
404
+ 05: Rotate
405
+ </Button>
406
+ <Square
407
+ ref={ref as any}
408
+ transition="quick"
409
+ size={40}
410
+ bg="$red10"
411
+ rotate={active ? '45deg' : '0deg'}
412
+ testID="scenario-05-target"
413
+ data-testid="scenario-05-target"
414
+ />
415
+ <Paragraph size="$1">{active ? '45deg' : '0'}</Paragraph>
416
+ </XStack>
417
+ )
418
+ }
419
+
420
+ // ============================================================================
421
+ // SCENARIO 6: Multiple Transforms Combined
422
+ // ============================================================================
423
+ function Scenario06_MultipleTransforms() {
424
+ const [active, setActive] = useState(false)
425
+ const ref = useRef<HTMLDivElement>(null)
426
+ const { startLogging, stopLogging } = useAnimationLogger('06-multi-transform', ref, [
427
+ 'transform',
428
+ 'opacity',
429
+ ])
430
+
431
+ return (
432
+ <XStack gap="$2" alignItems="center">
433
+ <Button
434
+ size="$2"
435
+ onPress={() => {
436
+ startLogging()
437
+ setActive(!active)
438
+ setTimeout(stopLogging, 1000)
439
+ }}
440
+ testID="scenario-06-trigger"
441
+ data-testid="scenario-06-trigger"
442
+ >
443
+ 06: Multi-Transform
444
+ </Button>
445
+ <Square
446
+ ref={ref as any}
447
+ transition="quick"
448
+ size={40}
449
+ bg="$red10"
450
+ scale={active ? 1.2 : 1}
451
+ x={active ? 20 : 0}
452
+ rotate={active ? '15deg' : '0deg'}
453
+ opacity={active ? 0.7 : 1}
454
+ testID="scenario-06-target"
455
+ data-testid="scenario-06-target"
456
+ />
457
+ <Paragraph size="$1">{active ? 'active' : 'default'}</Paragraph>
458
+ </XStack>
459
+ )
460
+ }
461
+
462
+ // ============================================================================
463
+ // SCENARIO 7: Width Animation
464
+ // ============================================================================
465
+ function Scenario07_Width() {
466
+ const [active, setActive] = useState(false)
467
+ const ref = useRef<HTMLDivElement>(null)
468
+ const { startLogging, stopLogging } = useAnimationLogger('07-width', ref, ['width'])
469
+
470
+ return (
471
+ <XStack gap="$2" alignItems="center">
472
+ <Button
473
+ size="$2"
474
+ onPress={() => {
475
+ startLogging()
476
+ setActive(!active)
477
+ setTimeout(stopLogging, 1500)
478
+ }}
479
+ testID="scenario-07-trigger"
480
+ data-testid="scenario-07-trigger"
481
+ >
482
+ 07: Width
483
+ </Button>
484
+ <View
485
+ ref={ref as any}
486
+ transition="quick"
487
+ height={40}
488
+ width={active ? 150 : 40}
489
+ bg="$blue10"
490
+ testID="scenario-07-target"
491
+ data-testid="scenario-07-target"
492
+ />
493
+ <Paragraph size="$1">{active ? '150px' : '40px'}</Paragraph>
494
+ </XStack>
495
+ )
496
+ }
497
+
498
+ // ============================================================================
499
+ // SCENARIO 8: Height Animation
500
+ // ============================================================================
501
+ function Scenario08_Height() {
502
+ const [active, setActive] = useState(false)
503
+ const ref = useRef<HTMLDivElement>(null)
504
+ const { startLogging, stopLogging } = useAnimationLogger('08-height', ref, ['height'])
505
+
506
+ return (
507
+ <XStack gap="$2" alignItems="center">
508
+ <Button
509
+ size="$2"
510
+ onPress={() => {
511
+ startLogging()
512
+ setActive(!active)
513
+ setTimeout(stopLogging, 1500)
514
+ }}
515
+ testID="scenario-08-trigger"
516
+ data-testid="scenario-08-trigger"
517
+ >
518
+ 08: Height
519
+ </Button>
520
+ <View
521
+ ref={ref as any}
522
+ transition="quick"
523
+ width={40}
524
+ height={active ? 80 : 40}
525
+ bg="$green10"
526
+ testID="scenario-08-target"
527
+ data-testid="scenario-08-target"
528
+ />
529
+ <Paragraph size="$1">{active ? '80px' : '40px'}</Paragraph>
530
+ </XStack>
531
+ )
532
+ }
533
+
534
+ // ============================================================================
535
+ // SCENARIO 9: Width AND Height Animation
536
+ // ============================================================================
537
+ function Scenario09_WidthAndHeight() {
538
+ const [active, setActive] = useState(false)
539
+ const ref = useRef<HTMLDivElement>(null)
540
+ const { startLogging, stopLogging } = useAnimationLogger('09-width-height', ref, [
541
+ 'width',
542
+ 'height',
543
+ ])
544
+
545
+ return (
546
+ <XStack gap="$2" alignItems="center">
547
+ <Button
548
+ size="$2"
549
+ onPress={() => {
550
+ startLogging()
551
+ setActive(!active)
552
+ setTimeout(stopLogging, 1500)
553
+ }}
554
+ testID="scenario-09-trigger"
555
+ data-testid="scenario-09-trigger"
556
+ >
557
+ 09: W+H
558
+ </Button>
559
+ <View
560
+ ref={ref as any}
561
+ transition="quick"
562
+ width={active ? 100 : 40}
563
+ height={active ? 60 : 40}
564
+ bg="$blue10"
565
+ testID="scenario-09-target"
566
+ data-testid="scenario-09-target"
567
+ />
568
+ <Paragraph size="$1">{active ? '100x60' : '40x40'}</Paragraph>
569
+ </XStack>
570
+ )
571
+ }
572
+
573
+ // ============================================================================
574
+ // SCENARIO 10: Border Radius Animation
575
+ // ============================================================================
576
+ function Scenario10_BorderRadius() {
577
+ const [active, setActive] = useState(false)
578
+ const ref = useRef<HTMLDivElement>(null)
579
+ const { startLogging, stopLogging } = useAnimationLogger('10-border-radius', ref, [
580
+ 'borderRadius',
581
+ ])
582
+
583
+ return (
584
+ <XStack gap="$2" alignItems="center">
585
+ <Button
586
+ size="$2"
587
+ onPress={() => {
588
+ startLogging()
589
+ setActive(!active)
590
+ setTimeout(stopLogging, 1000)
591
+ }}
592
+ testID="scenario-10-trigger"
593
+ data-testid="scenario-10-trigger"
594
+ >
595
+ 10: BorderRadius
596
+ </Button>
597
+ <View
598
+ ref={ref as any}
599
+ transition="quick"
600
+ width={40}
601
+ height={40}
602
+ bg="$yellow10"
603
+ borderRadius={active ? 20 : 0}
604
+ testID="scenario-10-target"
605
+ data-testid="scenario-10-target"
606
+ />
607
+ <Paragraph size="$1">{active ? '20px' : '0'}</Paragraph>
608
+ </XStack>
609
+ )
610
+ }
611
+
612
+ // ============================================================================
613
+ // SCENARIO 11: Background Color Animation
614
+ // ============================================================================
615
+ function Scenario11_BackgroundColor() {
616
+ const [active, setActive] = useState(false)
617
+ const ref = useRef<HTMLDivElement>(null)
618
+ const { startLogging, stopLogging } = useAnimationLogger('11-bg-color', ref, [
619
+ 'backgroundColor',
620
+ ])
621
+
622
+ return (
623
+ <XStack gap="$2" alignItems="center">
624
+ <Button
625
+ size="$2"
626
+ onPress={() => {
627
+ startLogging()
628
+ setActive(!active)
629
+ setTimeout(stopLogging, 1000)
630
+ }}
631
+ testID="scenario-11-trigger"
632
+ data-testid="scenario-11-trigger"
633
+ >
634
+ 11: BgColor
635
+ </Button>
636
+ <Square
637
+ ref={ref as any}
638
+ transition="quick"
639
+ size={40}
640
+ backgroundColor={active ? '$red10' : '$blue10'}
641
+ testID="scenario-11-target"
642
+ data-testid="scenario-11-target"
643
+ />
644
+ <Paragraph size="$1">{active ? 'red' : 'blue'}</Paragraph>
645
+ </XStack>
646
+ )
647
+ }
648
+
649
+ // ============================================================================
650
+ // SCENARIO 12: Text Color Animation
651
+ // ============================================================================
652
+ function Scenario12_TextColor() {
653
+ const [active, setActive] = useState(false)
654
+ const ref = useRef<HTMLDivElement>(null)
655
+ const { startLogging, stopLogging } = useAnimationLogger('12-text-color', ref, [
656
+ 'color',
657
+ ])
658
+
659
+ return (
660
+ <XStack gap="$2" alignItems="center">
661
+ <Button
662
+ size="$2"
663
+ onPress={() => {
664
+ startLogging()
665
+ setActive(!active)
666
+ setTimeout(stopLogging, 1000)
667
+ }}
668
+ testID="scenario-12-trigger"
669
+ data-testid="scenario-12-trigger"
670
+ >
671
+ 12: TextColor
672
+ </Button>
673
+ <Text
674
+ ref={ref as any}
675
+ transition="quick"
676
+ fontSize="$5"
677
+ fontWeight="bold"
678
+ color={active ? '$red10' : '$blue10'}
679
+ testID="scenario-12-target"
680
+ data-testid="scenario-12-target"
681
+ >
682
+ ABC
683
+ </Text>
684
+ <Paragraph size="$1">{active ? 'red' : 'blue'}</Paragraph>
685
+ </XStack>
686
+ )
687
+ }
688
+
689
+ // ============================================================================
690
+ // SCENARIO 13: Border Color Animation
691
+ // ============================================================================
692
+ function Scenario13_BorderColor() {
693
+ const [active, setActive] = useState(false)
694
+ const ref = useRef<HTMLDivElement>(null)
695
+ const { startLogging, stopLogging } = useAnimationLogger('13-border-color', ref, [
696
+ 'borderColor',
697
+ ])
698
+
699
+ return (
700
+ <XStack gap="$2" alignItems="center">
701
+ <Button
702
+ size="$2"
703
+ onPress={() => {
704
+ startLogging()
705
+ setActive(!active)
706
+ setTimeout(stopLogging, 1000)
707
+ }}
708
+ testID="scenario-13-trigger"
709
+ data-testid="scenario-13-trigger"
710
+ >
711
+ 13: BorderColor
712
+ </Button>
713
+ <Square
714
+ ref={ref as any}
715
+ transition="quick"
716
+ size={40}
717
+ bg="transparent"
718
+ borderWidth={3}
719
+ borderColor={active ? '$red10' : '$blue10'}
720
+ testID="scenario-13-target"
721
+ data-testid="scenario-13-target"
722
+ />
723
+ <Paragraph size="$1">{active ? 'red' : 'blue'}</Paragraph>
724
+ </XStack>
725
+ )
726
+ }
727
+
728
+ // ============================================================================
729
+ // SCENARIO 14: Spring Bouncy
730
+ // ============================================================================
731
+ function Scenario14_SpringBouncy() {
732
+ const [active, setActive] = useState(false)
733
+ const ref = useRef<HTMLDivElement>(null)
734
+ const { startLogging, stopLogging } = useAnimationLogger('14-spring-bouncy', ref, [
735
+ 'transform',
736
+ ])
737
+
738
+ return (
739
+ <XStack gap="$2" alignItems="center">
740
+ <Button
741
+ size="$2"
742
+ onPress={() => {
743
+ startLogging()
744
+ setActive(!active)
745
+ setTimeout(stopLogging, 1500)
746
+ }}
747
+ testID="scenario-14-trigger"
748
+ data-testid="scenario-14-trigger"
749
+ >
750
+ 14: Bouncy
751
+ </Button>
752
+ <Square
753
+ ref={ref as any}
754
+ transition="bouncy"
755
+ size={40}
756
+ bg="$blue10"
757
+ scale={active ? 1.5 : 1}
758
+ testID="scenario-14-target"
759
+ data-testid="scenario-14-target"
760
+ />
761
+ <Paragraph size="$1">bouncy spring</Paragraph>
762
+ </XStack>
763
+ )
764
+ }
765
+
766
+ // ============================================================================
767
+ // SCENARIO 15: Spring Lazy
768
+ // ============================================================================
769
+ function Scenario15_SpringLazy() {
770
+ const [active, setActive] = useState(false)
771
+ const ref = useRef<HTMLDivElement>(null)
772
+ const { startLogging, stopLogging } = useAnimationLogger('15-spring-lazy', ref, [
773
+ 'transform',
774
+ ])
775
+
776
+ return (
777
+ <XStack gap="$2" alignItems="center">
778
+ <Button
779
+ size="$2"
780
+ onPress={() => {
781
+ startLogging()
782
+ setActive(!active)
783
+ setTimeout(stopLogging, 2000)
784
+ }}
785
+ testID="scenario-15-trigger"
786
+ data-testid="scenario-15-trigger"
787
+ >
788
+ 15: Lazy
789
+ </Button>
790
+ <Square
791
+ ref={ref as any}
792
+ transition="lazy"
793
+ size={40}
794
+ bg="$green10"
795
+ scale={active ? 1.5 : 1}
796
+ testID="scenario-15-target"
797
+ data-testid="scenario-15-target"
798
+ />
799
+ <Paragraph size="$1">lazy spring</Paragraph>
800
+ </XStack>
801
+ )
802
+ }
803
+
804
+ // ============================================================================
805
+ // SCENARIO 16: Spring Quick
806
+ // ============================================================================
807
+ function Scenario16_SpringQuick() {
808
+ const [active, setActive] = useState(false)
809
+ const ref = useRef<HTMLDivElement>(null)
810
+ const { startLogging, stopLogging } = useAnimationLogger('16-spring-quick', ref, [
811
+ 'transform',
812
+ ])
813
+
814
+ return (
815
+ <XStack gap="$2" alignItems="center">
816
+ <Button
817
+ size="$2"
818
+ onPress={() => {
819
+ startLogging()
820
+ setActive(!active)
821
+ setTimeout(stopLogging, 800)
822
+ }}
823
+ testID="scenario-16-trigger"
824
+ data-testid="scenario-16-trigger"
825
+ >
826
+ 16: Quick
827
+ </Button>
828
+ <Square
829
+ ref={ref as any}
830
+ transition="quick"
831
+ size={40}
832
+ bg="$blue10"
833
+ scale={active ? 1.5 : 1}
834
+ testID="scenario-16-target"
835
+ data-testid="scenario-16-target"
836
+ />
837
+ <Paragraph size="$1">quick spring</Paragraph>
838
+ </XStack>
839
+ )
840
+ }
841
+
842
+ // ============================================================================
843
+ // SCENARIO 17: Custom Spring Config
844
+ // ============================================================================
845
+ function Scenario17_SpringCustom() {
846
+ const [active, setActive] = useState(false)
847
+ const ref = useRef<HTMLDivElement>(null)
848
+ const { startLogging, stopLogging } = useAnimationLogger('17-spring-custom', ref, [
849
+ 'transform',
850
+ ])
851
+
852
+ return (
853
+ <XStack gap="$2" alignItems="center">
854
+ <Button
855
+ size="$2"
856
+ onPress={() => {
857
+ startLogging()
858
+ setActive(!active)
859
+ setTimeout(stopLogging, 1500)
860
+ }}
861
+ testID="scenario-17-trigger"
862
+ data-testid="scenario-17-trigger"
863
+ >
864
+ 17: Custom
865
+ </Button>
866
+ <Square
867
+ ref={ref as any}
868
+ transition="quick"
869
+ // @ts-ignore
870
+ animationConfig={{ type: 'spring', damping: 5, stiffness: 100, mass: 0.5 }}
871
+ size={40}
872
+ bg="$yellow10"
873
+ scale={active ? 1.5 : 1}
874
+ testID="scenario-17-target"
875
+ data-testid="scenario-17-target"
876
+ />
877
+ <Paragraph size="$1">custom spring</Paragraph>
878
+ </XStack>
879
+ )
880
+ }
881
+
882
+ // ============================================================================
883
+ // SCENARIO 18: Timing 100ms
884
+ // ============================================================================
885
+ function Scenario18_Timing100ms() {
886
+ const [active, setActive] = useState(false)
887
+ const ref = useRef<HTMLDivElement>(null)
888
+ const { startLogging, stopLogging } = useAnimationLogger('18-timing-100ms', ref, [
889
+ 'opacity',
890
+ ])
891
+
892
+ return (
893
+ <XStack gap="$2" alignItems="center">
894
+ <Button
895
+ size="$2"
896
+ onPress={() => {
897
+ startLogging()
898
+ setActive(!active)
899
+ setTimeout(stopLogging, 500)
900
+ }}
901
+ testID="scenario-18-trigger"
902
+ data-testid="scenario-18-trigger"
903
+ >
904
+ 18: 100ms
905
+ </Button>
906
+ <Square
907
+ ref={ref as any}
908
+ transition="100ms"
909
+ size={40}
910
+ bg="$blue10"
911
+ opacity={active ? 0.3 : 1}
912
+ testID="scenario-18-target"
913
+ data-testid="scenario-18-target"
914
+ />
915
+ <Paragraph size="$1">timing 100ms</Paragraph>
916
+ </XStack>
917
+ )
918
+ }
919
+
920
+ // ============================================================================
921
+ // SCENARIO 19: Timing 200ms
922
+ // ============================================================================
923
+ function Scenario19_Timing200ms() {
924
+ const [active, setActive] = useState(false)
925
+ const ref = useRef<HTMLDivElement>(null)
926
+ const { startLogging, stopLogging } = useAnimationLogger('19-timing-200ms', ref, [
927
+ 'opacity',
928
+ ])
929
+
930
+ return (
931
+ <XStack gap="$2" alignItems="center">
932
+ <Button
933
+ size="$2"
934
+ onPress={() => {
935
+ startLogging()
936
+ setActive(!active)
937
+ setTimeout(stopLogging, 600)
938
+ }}
939
+ testID="scenario-19-trigger"
940
+ data-testid="scenario-19-trigger"
941
+ >
942
+ 19: 200ms
943
+ </Button>
944
+ <Square
945
+ ref={ref as any}
946
+ transition="quick"
947
+ size={40}
948
+ bg="$green10"
949
+ opacity={active ? 0.3 : 1}
950
+ testID="scenario-19-target"
951
+ data-testid="scenario-19-target"
952
+ />
953
+ <Paragraph size="$1">quick spring</Paragraph>
954
+ </XStack>
955
+ )
956
+ }
957
+
958
+ // ============================================================================
959
+ // SCENARIO 20: Timing with Delay
960
+ // ============================================================================
961
+ function Scenario20_TimingWithDelay() {
962
+ const [active, setActive] = useState(false)
963
+ const ref = useRef<HTMLDivElement>(null)
964
+ const { startLogging, stopLogging } = useAnimationLogger('20-timing-delay', ref, [
965
+ 'opacity',
966
+ ])
967
+
968
+ return (
969
+ <XStack gap="$2" alignItems="center">
970
+ <Button
971
+ size="$2"
972
+ onPress={() => {
973
+ startLogging()
974
+ setActive(!active)
975
+ setTimeout(stopLogging, 1000)
976
+ }}
977
+ testID="scenario-20-trigger"
978
+ data-testid="scenario-20-trigger"
979
+ >
980
+ 20: Delay
981
+ </Button>
982
+ <Square
983
+ ref={ref as any}
984
+ transition={['quick', { delay: 300 }]}
985
+ size={40}
986
+ bg="$blue10"
987
+ opacity={active ? 0.3 : 1}
988
+ testID="scenario-20-target"
989
+ data-testid="scenario-20-target"
990
+ />
991
+ <Paragraph size="$1">300ms delay</Paragraph>
992
+ </XStack>
993
+ )
994
+ }
995
+
996
+ // ============================================================================
997
+ // SCENARIO 21: Enter Style
998
+ // ============================================================================
999
+ function Scenario21_EnterStyle() {
1000
+ const [visible, setVisible] = useState(true)
1001
+ const ref = useRef<HTMLDivElement>(null)
1002
+ const { startLogging, stopLogging } = useAnimationLogger('21-enter-style', ref, [
1003
+ 'opacity',
1004
+ 'transform',
1005
+ ])
1006
+
1007
+ return (
1008
+ <XStack gap="$2" alignItems="center" minHeight={50}>
1009
+ <Button
1010
+ size="$2"
1011
+ onPress={() => {
1012
+ if (!visible) startLogging()
1013
+ setVisible(!visible)
1014
+ setTimeout(stopLogging, 1000)
1015
+ }}
1016
+ testID="scenario-21-trigger"
1017
+ data-testid="scenario-21-trigger"
1018
+ >
1019
+ 21: EnterStyle
1020
+ </Button>
1021
+ {visible && (
1022
+ <Square
1023
+ ref={ref as any}
1024
+ transition="bouncy"
1025
+ size={40}
1026
+ bg="$blue10"
1027
+ enterStyle={{ opacity: 0, scale: 0.5 }}
1028
+ testID="scenario-21-target"
1029
+ data-testid="scenario-21-target"
1030
+ />
1031
+ )}
1032
+ <Paragraph size="$1">{visible ? 'visible' : 'hidden'}</Paragraph>
1033
+ </XStack>
1034
+ )
1035
+ }
1036
+
1037
+ // ============================================================================
1038
+ // SCENARIO 22: Exit Style
1039
+ // ============================================================================
1040
+ function Scenario22_ExitStyle() {
1041
+ const [visible, setVisible] = useState(true)
1042
+ const ref = useRef<HTMLDivElement>(null)
1043
+ const { startLogging, stopLogging } = useAnimationLogger('22-exit-style', ref, [
1044
+ 'opacity',
1045
+ 'transform',
1046
+ ])
1047
+
1048
+ return (
1049
+ <XStack gap="$2" alignItems="center" minHeight={50}>
1050
+ <Button
1051
+ size="$2"
1052
+ onPress={() => {
1053
+ if (visible) startLogging()
1054
+ setVisible(!visible)
1055
+ setTimeout(stopLogging, 1000)
1056
+ }}
1057
+ testID="scenario-22-trigger"
1058
+ data-testid="scenario-22-trigger"
1059
+ >
1060
+ 22: ExitStyle
1061
+ </Button>
1062
+ <AnimatePresence>
1063
+ {visible && (
1064
+ <Square
1065
+ key="exit-square"
1066
+ ref={ref as any}
1067
+ transition="bouncy"
1068
+ size={40}
1069
+ bg="$green10"
1070
+ exitStyle={{ opacity: 0, scale: 0.5 }}
1071
+ testID="scenario-22-target"
1072
+ data-testid="scenario-22-target"
1073
+ />
1074
+ )}
1075
+ </AnimatePresence>
1076
+ <Paragraph size="$1">{visible ? 'visible' : 'hidden'}</Paragraph>
1077
+ </XStack>
1078
+ )
1079
+ }
1080
+
1081
+ // ============================================================================
1082
+ // SCENARIO 23: Enter + Exit Combined
1083
+ // ============================================================================
1084
+ function Scenario23_EnterExitCombined() {
1085
+ const [visible, setVisible] = useState(true)
1086
+ const ref = useRef<HTMLDivElement>(null)
1087
+ const { startLogging, stopLogging } = useAnimationLogger('23-enter-exit', ref, [
1088
+ 'opacity',
1089
+ 'transform',
1090
+ ])
1091
+
1092
+ return (
1093
+ <XStack gap="$2" alignItems="center" minHeight={50}>
1094
+ <Button
1095
+ size="$2"
1096
+ onPress={() => {
1097
+ startLogging()
1098
+ setVisible(!visible)
1099
+ setTimeout(stopLogging, 1000)
1100
+ }}
1101
+ testID="scenario-23-trigger"
1102
+ data-testid="scenario-23-trigger"
1103
+ >
1104
+ 23: Enter+Exit
1105
+ </Button>
1106
+ <AnimatePresence>
1107
+ {visible && (
1108
+ <Square
1109
+ key="enter-exit-square"
1110
+ ref={ref as any}
1111
+ transition="bouncy"
1112
+ size={40}
1113
+ bg="$blue10"
1114
+ enterStyle={{ opacity: 0, scale: 0.5, y: -20 }}
1115
+ exitStyle={{ opacity: 0, scale: 0.5, y: 20 }}
1116
+ testID="scenario-23-target"
1117
+ data-testid="scenario-23-target"
1118
+ />
1119
+ )}
1120
+ </AnimatePresence>
1121
+ <Paragraph size="$1">{visible ? 'visible' : 'hidden'}</Paragraph>
1122
+ </XStack>
1123
+ )
1124
+ }
1125
+
1126
+ // ============================================================================
1127
+ // SCENARIO 24: Rapid Toggle (Interruption stress test)
1128
+ // ============================================================================
1129
+ function Scenario24_RapidToggle() {
1130
+ const [active, setActive] = useState(false)
1131
+ const ref = useRef<HTMLDivElement>(null)
1132
+ const { startLogging, stopLogging } = useAnimationLogger('24-rapid-toggle', ref, [
1133
+ 'transform',
1134
+ ])
1135
+ const toggleCountRef = useRef(0)
1136
+
1137
+ const handleRapidToggle = () => {
1138
+ startLogging()
1139
+ toggleCountRef.current = 0
1140
+ const interval = setInterval(() => {
1141
+ setActive((v) => !v)
1142
+ toggleCountRef.current++
1143
+ if (toggleCountRef.current >= 6) {
1144
+ clearInterval(interval)
1145
+ setTimeout(stopLogging, 500)
1146
+ }
1147
+ }, 100)
1148
+ }
1149
+
1150
+ return (
1151
+ <XStack gap="$2" alignItems="center">
1152
+ <Button
1153
+ size="$2"
1154
+ onPress={handleRapidToggle}
1155
+ testID="scenario-24-trigger"
1156
+ data-testid="scenario-24-trigger"
1157
+ >
1158
+ 24: Rapid
1159
+ </Button>
1160
+ <Square
1161
+ ref={ref as any}
1162
+ transition="quick"
1163
+ size={40}
1164
+ bg="$yellow10"
1165
+ scale={active ? 1.5 : 1}
1166
+ testID="scenario-24-target"
1167
+ data-testid="scenario-24-target"
1168
+ />
1169
+ <Paragraph size="$1">6 toggles @ 100ms</Paragraph>
1170
+ </XStack>
1171
+ )
1172
+ }
1173
+
1174
+ // ============================================================================
1175
+ // SCENARIO 25: Mid-Animation Interruption
1176
+ // ============================================================================
1177
+ function Scenario25_Interruption() {
1178
+ const [position, setPosition] = useState(0) // 0, 1, 2
1179
+ const ref = useRef<HTMLDivElement>(null)
1180
+ const { startLogging, stopLogging } = useAnimationLogger('25-interruption', ref, [
1181
+ 'transform',
1182
+ ])
1183
+
1184
+ const handleInterrupt = () => {
1185
+ startLogging()
1186
+ setPosition(1)
1187
+ setTimeout(() => setPosition(2), 150) // Interrupt mid-animation
1188
+ setTimeout(stopLogging, 1500)
1189
+ }
1190
+
1191
+ return (
1192
+ <XStack gap="$2" alignItems="center">
1193
+ <Button
1194
+ size="$2"
1195
+ onPress={handleInterrupt}
1196
+ testID="scenario-25-trigger"
1197
+ data-testid="scenario-25-trigger"
1198
+ >
1199
+ 25: Interrupt
1200
+ </Button>
1201
+ <Square
1202
+ ref={ref as any}
1203
+ transition="lazy"
1204
+ size={40}
1205
+ bg="$red10"
1206
+ x={position === 0 ? 0 : position === 1 ? 50 : 100}
1207
+ testID="scenario-25-target"
1208
+ data-testid="scenario-25-target"
1209
+ />
1210
+ <Button size="$2" onPress={() => setPosition(0)}>
1211
+ Reset
1212
+ </Button>
1213
+ </XStack>
1214
+ )
1215
+ }
1216
+
1217
+ // ============================================================================
1218
+ // SCENARIO 26: animateOnly prop
1219
+ // ============================================================================
1220
+ function Scenario26_AnimateOnly() {
1221
+ const [active, setActive] = useState(false)
1222
+ const ref = useRef<HTMLDivElement>(null)
1223
+ const { startLogging, stopLogging } = useAnimationLogger('26-animate-only', ref, [
1224
+ 'opacity',
1225
+ 'transform',
1226
+ ])
1227
+
1228
+ return (
1229
+ <XStack gap="$2" alignItems="center">
1230
+ <Button
1231
+ size="$2"
1232
+ onPress={() => {
1233
+ startLogging()
1234
+ setActive(!active)
1235
+ setTimeout(stopLogging, 1000)
1236
+ }}
1237
+ testID="scenario-26-trigger"
1238
+ data-testid="scenario-26-trigger"
1239
+ >
1240
+ 26: AnimateOnly
1241
+ </Button>
1242
+ <Square
1243
+ ref={ref as any}
1244
+ transition="quick"
1245
+ animateOnly={['opacity']}
1246
+ size={40}
1247
+ bg="$blue10"
1248
+ opacity={active ? 0.3 : 1}
1249
+ scale={active ? 1.5 : 1}
1250
+ testID="scenario-26-target"
1251
+ data-testid="scenario-26-target"
1252
+ />
1253
+ <Paragraph size="$1">only opacity</Paragraph>
1254
+ </XStack>
1255
+ )
1256
+ }
1257
+
1258
+ // ============================================================================
1259
+ // SCENARIO 27: animationConfig override
1260
+ // ============================================================================
1261
+ function Scenario27_AnimationConfig() {
1262
+ const [active, setActive] = useState(false)
1263
+ const ref = useRef<HTMLDivElement>(null)
1264
+ const { startLogging, stopLogging } = useAnimationLogger('27-animation-config', ref, [
1265
+ 'transform',
1266
+ ])
1267
+
1268
+ return (
1269
+ <XStack gap="$2" alignItems="center">
1270
+ <Button
1271
+ size="$2"
1272
+ onPress={() => {
1273
+ startLogging()
1274
+ setActive(!active)
1275
+ setTimeout(stopLogging, 2000)
1276
+ }}
1277
+ testID="scenario-27-trigger"
1278
+ data-testid="scenario-27-trigger"
1279
+ >
1280
+ 27: Config
1281
+ </Button>
1282
+ <Square
1283
+ ref={ref as any}
1284
+ transition="quick"
1285
+ // @ts-ignore
1286
+ animationConfig={{ type: 'spring', damping: 8, stiffness: 80 }}
1287
+ size={40}
1288
+ bg="$green10"
1289
+ scale={active ? 1.5 : 1}
1290
+ testID="scenario-27-target"
1291
+ data-testid="scenario-27-target"
1292
+ />
1293
+ <Paragraph size="$1">config override</Paragraph>
1294
+ </XStack>
1295
+ )
1296
+ }
1297
+
1298
+ // ============================================================================
1299
+ // SCENARIO 28: Multiple Properties Simultaneously
1300
+ // ============================================================================
1301
+ function Scenario28_MultiProperty() {
1302
+ const [active, setActive] = useState(false)
1303
+ const ref = useRef<HTMLDivElement>(null)
1304
+ const { startLogging, stopLogging } = useAnimationLogger('28-multi-property', ref, [
1305
+ 'opacity',
1306
+ 'transform',
1307
+ 'borderRadius',
1308
+ ])
1309
+
1310
+ return (
1311
+ <XStack gap="$2" alignItems="center">
1312
+ <Button
1313
+ size="$2"
1314
+ onPress={() => {
1315
+ startLogging()
1316
+ setActive(!active)
1317
+ setTimeout(stopLogging, 1000)
1318
+ }}
1319
+ testID="scenario-28-trigger"
1320
+ data-testid="scenario-28-trigger"
1321
+ >
1322
+ 28: Multi
1323
+ </Button>
1324
+ <View
1325
+ ref={ref as any}
1326
+ transition="bouncy"
1327
+ width={40}
1328
+ height={40}
1329
+ bg="$blue10"
1330
+ opacity={active ? 0.5 : 1}
1331
+ scale={active ? 1.3 : 1}
1332
+ rotate={active ? '30deg' : '0deg'}
1333
+ borderRadius={active ? 20 : 4}
1334
+ testID="scenario-28-target"
1335
+ data-testid="scenario-28-target"
1336
+ />
1337
+ <Paragraph size="$1">4 props</Paragraph>
1338
+ </XStack>
1339
+ )
1340
+ }
1341
+
1342
+ // ============================================================================
1343
+ // SCENARIO 29: Nested Animated Elements
1344
+ // ============================================================================
1345
+ function Scenario29_NestedAnimations() {
1346
+ const [active, setActive] = useState(false)
1347
+ const outerRef = useRef<HTMLDivElement>(null)
1348
+ const innerRef = useRef<HTMLDivElement>(null)
1349
+ const { startLogging: startOuter, stopLogging: stopOuter } = useAnimationLogger(
1350
+ '29-nested-outer',
1351
+ outerRef,
1352
+ ['transform']
1353
+ )
1354
+ const { startLogging: startInner, stopLogging: stopInner } = useAnimationLogger(
1355
+ '29-nested-inner',
1356
+ innerRef,
1357
+ ['opacity']
1358
+ )
1359
+
1360
+ return (
1361
+ <XStack gap="$2" alignItems="center">
1362
+ <Button
1363
+ size="$2"
1364
+ onPress={() => {
1365
+ startOuter()
1366
+ startInner()
1367
+ setActive(!active)
1368
+ setTimeout(() => {
1369
+ stopOuter()
1370
+ stopInner()
1371
+ }, 1000)
1372
+ }}
1373
+ testID="scenario-29-trigger"
1374
+ data-testid="scenario-29-trigger"
1375
+ >
1376
+ 29: Nested
1377
+ </Button>
1378
+ <View
1379
+ ref={outerRef as any}
1380
+ transition="quick"
1381
+ scale={active ? 1.2 : 1}
1382
+ padding="$1"
1383
+ bg="$blue5"
1384
+ testID="scenario-29-outer"
1385
+ data-testid="scenario-29-outer"
1386
+ >
1387
+ <Square
1388
+ ref={innerRef as any}
1389
+ transition="bouncy"
1390
+ size={30}
1391
+ bg="$blue10"
1392
+ opacity={active ? 0.5 : 1}
1393
+ testID="scenario-29-inner"
1394
+ data-testid="scenario-29-inner"
1395
+ />
1396
+ </View>
1397
+ <Paragraph size="$1">parent+child</Paragraph>
1398
+ </XStack>
1399
+ )
1400
+ }
1401
+
1402
+ // ============================================================================
1403
+ // SCENARIO 30: Hover Animation (pseudo state)
1404
+ // ============================================================================
1405
+ function Scenario30_HoverAnimation() {
1406
+ const ref = useRef<HTMLDivElement>(null)
1407
+ const { startLogging, stopLogging } = useAnimationLogger('30-hover', ref, [
1408
+ 'transform',
1409
+ 'backgroundColor',
1410
+ ])
1411
+
1412
+ return (
1413
+ <XStack gap="$2" alignItems="center">
1414
+ <Paragraph size="$1">30: Hover →</Paragraph>
1415
+ <View
1416
+ ref={ref as any}
1417
+ transition="quick"
1418
+ width={40}
1419
+ height={40}
1420
+ bg="$blue10"
1421
+ hoverStyle={{ scale: 1.2, backgroundColor: '$green10' }}
1422
+ onMouseEnter={startLogging}
1423
+ onMouseLeave={() => setTimeout(stopLogging, 500)}
1424
+ testID="scenario-30-target"
1425
+ data-testid="scenario-30-target"
1426
+ cursor="pointer"
1427
+ />
1428
+ <Paragraph size="$1">hover me</Paragraph>
1429
+ </XStack>
1430
+ )
1431
+ }
1432
+
1433
+ // ============================================================================
1434
+ // SCENARIO 31: Per-Property Animation Configs
1435
+ // Tests: transition={['quick', { opacity: 'lazy', scale: 'bouncy' }]}
1436
+ // Each property should animate with its own timing/spring config
1437
+ // ============================================================================
1438
+ function Scenario31_PerPropertyConfigs() {
1439
+ const [active, setActive] = useState(false)
1440
+ const ref = useRef<HTMLDivElement>(null)
1441
+ const { startLogging, stopLogging } = useAnimationLogger('31-per-prop-configs', ref, [
1442
+ 'opacity',
1443
+ 'transform',
1444
+ ])
1445
+
1446
+ return (
1447
+ <XStack gap="$2" alignItems="center">
1448
+ <Button
1449
+ size="$2"
1450
+ onPress={() => {
1451
+ startLogging()
1452
+ setActive(!active)
1453
+ setTimeout(stopLogging, 2000)
1454
+ }}
1455
+ testID="scenario-31-trigger"
1456
+ data-testid="scenario-31-trigger"
1457
+ >
1458
+ 31: PerProp
1459
+ </Button>
1460
+ <Square
1461
+ ref={ref as any}
1462
+ transition={['quick', { opacity: 'lazy', scale: 'bouncy' }] as any}
1463
+ size={40}
1464
+ bg="$blue10"
1465
+ opacity={active ? 0.3 : 1}
1466
+ scale={active ? 1.5 : 1}
1467
+ testID="scenario-31-target"
1468
+ data-testid="scenario-31-target"
1469
+ />
1470
+ <Paragraph size="$1">opacity=lazy, scale=bouncy</Paragraph>
1471
+ </XStack>
1472
+ )
1473
+ }
1474
+
1475
+ // ============================================================================
1476
+ // SCENARIO 32: Per-Property Configs with Mid-Animation Interruption
1477
+ // Critical: Tests that per-property configs survive interruptions correctly
1478
+ // ============================================================================
1479
+ function Scenario32_PerPropertyWithInterruption() {
1480
+ const [state, setState] = useState<0 | 1 | 2>(0)
1481
+ const ref = useRef<HTMLDivElement>(null)
1482
+ const { startLogging, stopLogging } = useAnimationLogger('32-per-prop-interrupt', ref, [
1483
+ 'opacity',
1484
+ 'transform',
1485
+ ])
1486
+
1487
+ const handleInterrupt = () => {
1488
+ startLogging()
1489
+ setState(1)
1490
+ // Interrupt at 200ms - well before lazy animation completes
1491
+ setTimeout(() => setState(2), 200)
1492
+ setTimeout(stopLogging, 3000)
1493
+ }
1494
+
1495
+ return (
1496
+ <XStack gap="$2" alignItems="center">
1497
+ <Button
1498
+ size="$2"
1499
+ onPress={handleInterrupt}
1500
+ testID="scenario-32-trigger"
1501
+ data-testid="scenario-32-trigger"
1502
+ >
1503
+ 32: Interrupt
1504
+ </Button>
1505
+ <Square
1506
+ ref={ref as any}
1507
+ transition={['quick', { opacity: 'lazy', scale: 'bouncy' }] as any}
1508
+ size={40}
1509
+ bg="$green10"
1510
+ opacity={state === 0 ? 1 : state === 1 ? 0.5 : 0.2}
1511
+ scale={state === 0 ? 1 : state === 1 ? 1.3 : 1.6}
1512
+ x={state === 0 ? 0 : state === 1 ? 30 : 60}
1513
+ testID="scenario-32-target"
1514
+ data-testid="scenario-32-target"
1515
+ />
1516
+ <Button size="$2" onPress={() => setState(0)}>
1517
+ Reset
1518
+ </Button>
1519
+ <Paragraph size="$1">state={state}</Paragraph>
1520
+ </XStack>
1521
+ )
1522
+ }
1523
+
1524
+ // ============================================================================
1525
+ // SCENARIO 33: Mixed Spring and Timing per Property
1526
+ // Some properties use spring, others use timing duration
1527
+ // ============================================================================
1528
+ function Scenario33_MixedSpringTiming() {
1529
+ const [active, setActive] = useState(false)
1530
+ const ref = useRef<HTMLDivElement>(null)
1531
+ const { startLogging, stopLogging } = useAnimationLogger(
1532
+ '33-mixed-spring-timing',
1533
+ ref,
1534
+ ['opacity', 'transform', 'borderRadius']
1535
+ )
1536
+
1537
+ return (
1538
+ <XStack gap="$2" alignItems="center">
1539
+ <Button
1540
+ size="$2"
1541
+ onPress={() => {
1542
+ startLogging()
1543
+ setActive(!active)
1544
+ setTimeout(stopLogging, 2000)
1545
+ }}
1546
+ testID="scenario-33-trigger"
1547
+ data-testid="scenario-33-trigger"
1548
+ >
1549
+ 33: Mixed
1550
+ </Button>
1551
+ <View
1552
+ ref={ref as any}
1553
+ transition={['bouncy', { opacity: 'quick', borderRadius: 'lazy' }] as any}
1554
+ width={40}
1555
+ height={40}
1556
+ bg="$blue10"
1557
+ opacity={active ? 0.4 : 1}
1558
+ scale={active ? 1.4 : 1}
1559
+ borderRadius={active ? 20 : 4}
1560
+ testID="scenario-33-target"
1561
+ data-testid="scenario-33-target"
1562
+ />
1563
+ <Paragraph size="$1">scale=bouncy, opacity=quick, radius=lazy</Paragraph>
1564
+ </XStack>
1565
+ )
1566
+ }
1567
+
1568
+ // ============================================================================
1569
+ // SCENARIO 34: Complex Object with Many Properties
1570
+ // Tests driver handling of large style objects with varied configs
1571
+ // ============================================================================
1572
+ function Scenario34_ComplexObjectManyProps() {
1573
+ const [active, setActive] = useState(false)
1574
+ const ref = useRef<HTMLDivElement>(null)
1575
+ const { startLogging, stopLogging } = useAnimationLogger('34-complex-many-props', ref, [
1576
+ 'opacity',
1577
+ 'transform',
1578
+ 'width',
1579
+ 'height',
1580
+ 'borderRadius',
1581
+ 'backgroundColor',
1582
+ ])
1583
+
1584
+ return (
1585
+ <XStack gap="$2" alignItems="center">
1586
+ <Button
1587
+ size="$2"
1588
+ onPress={() => {
1589
+ startLogging()
1590
+ setActive(!active)
1591
+ setTimeout(stopLogging, 2500)
1592
+ }}
1593
+ testID="scenario-34-trigger"
1594
+ data-testid="scenario-34-trigger"
1595
+ >
1596
+ 34: Complex
1597
+ </Button>
1598
+ <View
1599
+ ref={ref as any}
1600
+ transition={
1601
+ [
1602
+ 'quick',
1603
+ {
1604
+ opacity: 'lazy',
1605
+ scale: 'bouncy',
1606
+ width: 'lazy',
1607
+ height: 'lazy',
1608
+ borderRadius: 'bouncy',
1609
+ backgroundColor: '200ms',
1610
+ },
1611
+ ] as any
1612
+ }
1613
+ width={active ? 80 : 40}
1614
+ height={active ? 60 : 40}
1615
+ backgroundColor={active ? '$red10' : '$blue10'}
1616
+ opacity={active ? 0.7 : 1}
1617
+ scale={active ? 1.2 : 1}
1618
+ rotate={active ? '15deg' : '0deg'}
1619
+ borderRadius={active ? 16 : 4}
1620
+ testID="scenario-34-target"
1621
+ data-testid="scenario-34-target"
1622
+ />
1623
+ <Paragraph size="$1">7 props, 4 configs</Paragraph>
1624
+ </XStack>
1625
+ )
1626
+ }
1627
+
1628
+ // ============================================================================
1629
+ // SCENARIO 35: Rapid State Changes with Per-Property Configs
1630
+ // Stress test: rapid toggles while different properties have different timings
1631
+ // ============================================================================
1632
+ function Scenario35_RapidPerPropertyChanges() {
1633
+ const [active, setActive] = useState(false)
1634
+ const ref = useRef<HTMLDivElement>(null)
1635
+ const { startLogging, stopLogging } = useAnimationLogger('35-rapid-per-prop', ref, [
1636
+ 'opacity',
1637
+ 'transform',
1638
+ ])
1639
+ const toggleCountRef = useRef(0)
1640
+
1641
+ const handleRapid = () => {
1642
+ startLogging()
1643
+ toggleCountRef.current = 0
1644
+ // Toggle every 150ms - fast enough to interrupt lazy animations
1645
+ const interval = setInterval(() => {
1646
+ setActive((v) => !v)
1647
+ toggleCountRef.current++
1648
+ if (toggleCountRef.current >= 8) {
1649
+ clearInterval(interval)
1650
+ setTimeout(stopLogging, 2000)
1651
+ }
1652
+ }, 150)
1653
+ }
1654
+
1655
+ return (
1656
+ <XStack gap="$2" alignItems="center">
1657
+ <Button
1658
+ size="$2"
1659
+ onPress={handleRapid}
1660
+ testID="scenario-35-trigger"
1661
+ data-testid="scenario-35-trigger"
1662
+ >
1663
+ 35: Rapid
1664
+ </Button>
1665
+ <Square
1666
+ ref={ref as any}
1667
+ transition={['quick', { opacity: 'lazy', scale: 'bouncy' }] as any}
1668
+ size={40}
1669
+ bg="$yellow10"
1670
+ opacity={active ? 0.3 : 1}
1671
+ scale={active ? 1.5 : 1}
1672
+ x={active ? 40 : 0}
1673
+ testID="scenario-35-target"
1674
+ data-testid="scenario-35-target"
1675
+ />
1676
+ <Paragraph size="$1">8 toggles @ 150ms</Paragraph>
1677
+ </XStack>
1678
+ )
1679
+ }
1680
+
1681
+ // ============================================================================
1682
+ // SCENARIO 36: 1000ms timing animation for reliable intermediate testing
1683
+ // Long duration ensures we can capture intermediate values
1684
+ // ============================================================================
1685
+ function Scenario36_TimingTest() {
1686
+ const [active, setActive] = useState(false)
1687
+ const ref = useRef<HTMLDivElement>(null)
1688
+ const { startLogging, stopLogging } = useAnimationLogger('36-timing-test', ref, [
1689
+ 'opacity',
1690
+ 'transform',
1691
+ ])
1692
+
1693
+ return (
1694
+ <XStack gap="$2" alignItems="center">
1695
+ <Button
1696
+ size="$2"
1697
+ onPress={() => {
1698
+ startLogging()
1699
+ setActive(!active)
1700
+ setTimeout(stopLogging, 1500)
1701
+ }}
1702
+ testID="scenario-36-trigger"
1703
+ data-testid="scenario-36-trigger"
1704
+ >
1705
+ 36: 1000ms
1706
+ </Button>
1707
+ <Square
1708
+ ref={ref as any}
1709
+ transition="500ms"
1710
+ size={40}
1711
+ bg="$blue10"
1712
+ opacity={active ? 0.2 : 1}
1713
+ scale={active ? 1.5 : 1}
1714
+ testID="scenario-36-target"
1715
+ data-testid="scenario-36-target"
1716
+ />
1717
+ <Paragraph size="$1">1000ms timing</Paragraph>
1718
+ </XStack>
1719
+ )
1720
+ }
1721
+
1722
+ // ============================================================================
1723
+ // SCENARIO 37: EnterStyle with scaleX (like BenchmarkChart)
1724
+ // Tests animation from scaleX: 0 to scaleX: 1
1725
+ // ============================================================================
1726
+ function Scenario37_EnterStyleScaleX() {
1727
+ const [visible, setVisible] = useState(false)
1728
+ const ref = useRef<HTMLDivElement>(null)
1729
+ const { startLogging, stopLogging } = useAnimationLogger('37-enter-scaleX', ref, [
1730
+ 'opacity',
1731
+ 'transform',
1732
+ ])
1733
+
1734
+ return (
1735
+ <XStack gap="$2" alignItems="center" minHeight={50}>
1736
+ <Button
1737
+ size="$2"
1738
+ onPress={() => {
1739
+ if (!visible) startLogging()
1740
+ setVisible(!visible)
1741
+ setTimeout(stopLogging, 2000)
1742
+ }}
1743
+ testID="scenario-37-trigger"
1744
+ data-testid="scenario-37-trigger"
1745
+ >
1746
+ 37: EnterScaleX
1747
+ </Button>
1748
+ {visible && (
1749
+ <View
1750
+ ref={ref as any}
1751
+ transition="lazy"
1752
+ width={100}
1753
+ height={20}
1754
+ bg="$red10"
1755
+ enterStyle={{ opacity: 0, scaleX: 0 }}
1756
+ testID="scenario-37-target"
1757
+ data-testid="scenario-37-target"
1758
+ />
1759
+ )}
1760
+ <Paragraph size="$1">{visible ? 'visible' : 'hidden'}</Paragraph>
1761
+ </XStack>
1762
+ )
1763
+ }
1764
+
1765
+ // ============================================================================
1766
+ // SCENARIO 38: Per-Property Config with Transform (animationClamped pattern)
1767
+ // Tests: transition={['quick', { opacity: '200ms', backgroundColor: '200ms' }]}
1768
+ // The key test: scale/y should STILL animate with the default 'quick' animation
1769
+ // even though they're not explicitly listed in the per-property config
1770
+ // ============================================================================
1771
+ function Scenario38_PerPropertyWithTransform() {
1772
+ const [active, setActive] = useState(false)
1773
+ const ref = useRef<HTMLDivElement>(null)
1774
+ const { startLogging, stopLogging } = useAnimationLogger('38-per-prop-transform', ref, [
1775
+ 'opacity',
1776
+ 'transform',
1777
+ 'backgroundColor',
1778
+ ])
1779
+
1780
+ return (
1781
+ <XStack gap="$2" alignItems="center">
1782
+ <Button
1783
+ size="$2"
1784
+ onPress={() => {
1785
+ startLogging()
1786
+ setActive(!active)
1787
+ setTimeout(stopLogging, 2000)
1788
+ }}
1789
+ testID="scenario-38-trigger"
1790
+ data-testid="scenario-38-trigger"
1791
+ >
1792
+ 38: PerProp+Transform
1793
+ </Button>
1794
+ <Square
1795
+ ref={ref as any}
1796
+ // this is the "animationClamped" pattern - opacity/backgroundColor have specific timing
1797
+ // but scale/y should STILL animate with the default 'quick' animation
1798
+ transition={['quick', { opacity: '200ms', backgroundColor: '200ms' }] as any}
1799
+ size={40}
1800
+ bg={active ? '$red10' : '$blue10'}
1801
+ opacity={active ? 0.5 : 1}
1802
+ scale={active ? 1.3 : 1}
1803
+ y={active ? -10 : 0}
1804
+ testID="scenario-38-target"
1805
+ data-testid="scenario-38-target"
1806
+ />
1807
+ <Paragraph size="$1">opacity/bg=200ms, scale/y=quick</Paragraph>
1808
+ </XStack>
1809
+ )
1810
+ }
1811
+
1812
+ // ============================================================================
1813
+ // SCENARIO 39: Object Format Per-Property with Transform
1814
+ // Tests: transition={{ opacity: '200ms', backgroundColor: '200ms', default: 'quick' }}
1815
+ // Same as 38 but using object format instead of array format
1816
+ // ============================================================================
1817
+ function Scenario39_ObjectFormatPerProperty() {
1818
+ const [active, setActive] = useState(false)
1819
+ const ref = useRef<HTMLDivElement>(null)
1820
+ const { startLogging, stopLogging } = useAnimationLogger('39-object-per-prop', ref, [
1821
+ 'opacity',
1822
+ 'transform',
1823
+ 'backgroundColor',
1824
+ ])
1825
+
1826
+ return (
1827
+ <XStack gap="$2" alignItems="center">
1828
+ <Button
1829
+ size="$2"
1830
+ onPress={() => {
1831
+ startLogging()
1832
+ setActive(!active)
1833
+ setTimeout(stopLogging, 2000)
1834
+ }}
1835
+ testID="scenario-39-trigger"
1836
+ data-testid="scenario-39-trigger"
1837
+ >
1838
+ 39: Object Format
1839
+ </Button>
1840
+ <Square
1841
+ ref={ref as any}
1842
+ // object format - same as array but different syntax
1843
+ transition={
1844
+ { opacity: '200ms', backgroundColor: '200ms', default: 'quick' } as any
1845
+ }
1846
+ size={40}
1847
+ bg={active ? '$red10' : '$blue10'}
1848
+ opacity={active ? 0.5 : 1}
1849
+ scale={active ? 1.3 : 1}
1850
+ y={active ? -10 : 0}
1851
+ testID="scenario-39-target"
1852
+ data-testid="scenario-39-target"
1853
+ />
1854
+ <Paragraph size="$1">object: opacity/bg=200ms, default=quick</Paragraph>
1855
+ </XStack>
1856
+ )
1857
+ }
1858
+
1859
+ // ============================================================================
1860
+ // SCENARIO 40: Object Format WITHOUT Default (only specified properties animate)
1861
+ // Tests: transition={{ opacity: '200ms' }} - scale should NOT animate
1862
+ // ============================================================================
1863
+ function Scenario40_ObjectFormatNoDefault() {
1864
+ const [active, setActive] = useState(false)
1865
+ const ref = useRef<HTMLDivElement>(null)
1866
+ const { startLogging, stopLogging } = useAnimationLogger('40-object-no-default', ref, [
1867
+ 'opacity',
1868
+ 'transform',
1869
+ ])
1870
+
1871
+ return (
1872
+ <XStack gap="$2" alignItems="center">
1873
+ <Button
1874
+ size="$2"
1875
+ onPress={() => {
1876
+ startLogging()
1877
+ setActive(!active)
1878
+ setTimeout(stopLogging, 1000)
1879
+ }}
1880
+ testID="scenario-40-trigger"
1881
+ data-testid="scenario-40-trigger"
1882
+ >
1883
+ 40: No Default
1884
+ </Button>
1885
+ <Square
1886
+ ref={ref as any}
1887
+ // NO default key - only opacity should animate, scale should snap instantly
1888
+ transition={{ opacity: '500ms' } as any}
1889
+ size={40}
1890
+ bg="$blue10"
1891
+ opacity={active ? 0.5 : 1}
1892
+ scale={active ? 1.3 : 1}
1893
+ testID="scenario-40-target"
1894
+ data-testid="scenario-40-target"
1895
+ />
1896
+ <Paragraph size="$1">only opacity animates (500ms)</Paragraph>
1897
+ </XStack>
1898
+ )
1899
+ }
1900
+
1901
+ // ============================================================================
1902
+ // SCENARIO 41: Per-Property Config with Delay
1903
+ // Tests: transition={['quick', { delay: 300, opacity: '500ms' }]}
1904
+ // Delay should apply to all properties, opacity uses custom timing
1905
+ // ============================================================================
1906
+ function Scenario41_PerPropertyWithDelay() {
1907
+ const [active, setActive] = useState(false)
1908
+ const ref = useRef<HTMLDivElement>(null)
1909
+ const { startLogging, stopLogging } = useAnimationLogger('41-per-prop-delay', ref, [
1910
+ 'opacity',
1911
+ 'transform',
1912
+ ])
1913
+
1914
+ return (
1915
+ <XStack gap="$2" alignItems="center">
1916
+ <Button
1917
+ size="$2"
1918
+ onPress={() => {
1919
+ startLogging()
1920
+ setActive(!active)
1921
+ setTimeout(stopLogging, 2000)
1922
+ }}
1923
+ testID="scenario-41-trigger"
1924
+ data-testid="scenario-41-trigger"
1925
+ >
1926
+ 41: PerProp+Delay
1927
+ </Button>
1928
+ <Square
1929
+ ref={ref as any}
1930
+ // delay + per-property: 300ms delay, then opacity=500ms, scale=quick
1931
+ transition={['quick', { delay: 300, opacity: '500ms' }] as any}
1932
+ size={40}
1933
+ bg="$blue10"
1934
+ opacity={active ? 0.5 : 1}
1935
+ scale={active ? 1.3 : 1}
1936
+ testID="scenario-41-target"
1937
+ data-testid="scenario-41-target"
1938
+ />
1939
+ <Paragraph size="$1">300ms delay, opacity=500ms, scale=quick</Paragraph>
1940
+ </XStack>
1941
+ )
1942
+ }
1943
+
1944
+ // ============================================================================
1945
+ // SCENARIO 42: Different Enter/Exit Transitions
1946
+ // Tests: transition={{ enter: '500ms', exit: '100ms' }}
1947
+ // Enter animation should be slow (500ms), exit should be fast (100ms)
1948
+ // Using timing animations for predictable test behavior across all drivers
1949
+ // ============================================================================
1950
+ function Scenario42_TransitionEnterExit() {
1951
+ const [visible, setVisible] = useState(true)
1952
+ const ref = useRef<HTMLDivElement>(null)
1953
+ const { startLogging, stopLogging } = useAnimationLogger('42-enter-exit', ref, [
1954
+ 'opacity',
1955
+ 'transform',
1956
+ ])
1957
+
1958
+ return (
1959
+ <XStack gap="$2" alignItems="center" minHeight={50}>
1960
+ <Button
1961
+ size="$2"
1962
+ onPress={() => {
1963
+ startLogging()
1964
+ setVisible(!visible)
1965
+ setTimeout(stopLogging, 2000)
1966
+ }}
1967
+ testID="scenario-42-trigger"
1968
+ data-testid="scenario-42-trigger"
1969
+ >
1970
+ 42: Enter/Exit
1971
+ </Button>
1972
+ <AnimatePresence>
1973
+ {visible && (
1974
+ <Square
1975
+ key="enter-exit-42"
1976
+ ref={ref as any}
1977
+ // enter=500ms (slow), exit=100ms (fast)
1978
+ transition={{ enter: '500ms', exit: '100ms' } as any}
1979
+ size={40}
1980
+ bg="$blue10"
1981
+ enterStyle={{ opacity: 0, scale: 0.5 }}
1982
+ exitStyle={{ opacity: 0, scale: 0.5 }}
1983
+ testID="scenario-42-target"
1984
+ data-testid="scenario-42-target"
1985
+ />
1986
+ )}
1987
+ </AnimatePresence>
1988
+ <Paragraph size="$1">
1989
+ {visible ? 'visible' : 'hidden'} (enter=500ms, exit=100ms)
1990
+ </Paragraph>
1991
+ </XStack>
1992
+ )
1993
+ }
1994
+
1995
+ // ============================================================================
1996
+ // SCENARIO 43: Enter Transition Only (exit uses default)
1997
+ // Tests: transition={{ enter: '500ms', default: '100ms' }}
1998
+ // Enter uses 500ms (slow), exit/other uses default 100ms (fast)
1999
+ // Using timing animations for predictable test behavior across all drivers
2000
+ // ============================================================================
2001
+ function Scenario43_TransitionEnterOnly() {
2002
+ const [visible, setVisible] = useState(true)
2003
+ const ref = useRef<HTMLDivElement>(null)
2004
+ const { startLogging, stopLogging } = useAnimationLogger('43-enter-only', ref, [
2005
+ 'opacity',
2006
+ 'transform',
2007
+ ])
2008
+
2009
+ return (
2010
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2011
+ <Button
2012
+ size="$2"
2013
+ onPress={() => {
2014
+ startLogging()
2015
+ setVisible(!visible)
2016
+ setTimeout(stopLogging, 2000)
2017
+ }}
2018
+ testID="scenario-43-trigger"
2019
+ data-testid="scenario-43-trigger"
2020
+ >
2021
+ 43: Enter Only
2022
+ </Button>
2023
+ <AnimatePresence>
2024
+ {visible && (
2025
+ <Square
2026
+ key="enter-only-43"
2027
+ ref={ref as any}
2028
+ // enter=500ms, exit falls back to default=100ms
2029
+ transition={{ enter: '500ms', default: '100ms' } as any}
2030
+ size={40}
2031
+ bg="$green10"
2032
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2033
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2034
+ testID="scenario-43-target"
2035
+ data-testid="scenario-43-target"
2036
+ />
2037
+ )}
2038
+ </AnimatePresence>
2039
+ <Paragraph size="$1">
2040
+ {visible ? 'visible' : 'hidden'} (enter=500ms, default=100ms)
2041
+ </Paragraph>
2042
+ </XStack>
2043
+ )
2044
+ }
2045
+
2046
+ // ============================================================================
2047
+ // SCENARIO 44: Exit Transition Only (enter uses default)
2048
+ // Tests: transition={{ exit: '500ms', default: '100ms' }}
2049
+ // Exit uses 500ms (slow), enter/other uses default 100ms (fast)
2050
+ // Using timing animations for predictable test behavior across all drivers
2051
+ // ============================================================================
2052
+ function Scenario44_TransitionExitOnly() {
2053
+ const [visible, setVisible] = useState(true)
2054
+ const ref = useRef<HTMLDivElement>(null)
2055
+ const { startLogging, stopLogging } = useAnimationLogger('44-exit-only', ref, [
2056
+ 'opacity',
2057
+ 'transform',
2058
+ ])
2059
+
2060
+ return (
2061
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2062
+ <Button
2063
+ size="$2"
2064
+ onPress={() => {
2065
+ startLogging()
2066
+ setVisible(!visible)
2067
+ setTimeout(stopLogging, 2000)
2068
+ }}
2069
+ testID="scenario-44-trigger"
2070
+ data-testid="scenario-44-trigger"
2071
+ >
2072
+ 44: Exit Only
2073
+ </Button>
2074
+ <AnimatePresence>
2075
+ {visible && (
2076
+ <Square
2077
+ key="exit-only-44"
2078
+ ref={ref as any}
2079
+ // exit=500ms (slow), enter falls back to default=100ms (fast)
2080
+ transition={{ exit: '500ms', default: '100ms' } as any}
2081
+ size={40}
2082
+ bg="$yellow10"
2083
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2084
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2085
+ testID="scenario-44-target"
2086
+ data-testid="scenario-44-target"
2087
+ />
2088
+ )}
2089
+ </AnimatePresence>
2090
+ <Paragraph size="$1">
2091
+ {visible ? 'visible' : 'hidden'} (exit=500ms, default=100ms)
2092
+ </Paragraph>
2093
+ </XStack>
2094
+ )
2095
+ }
2096
+
2097
+ // ============================================================================
2098
+ // SCENARIO 45: Enter/Exit with Default Fallback
2099
+ // Tests: transition={{ enter: '300ms', exit: '100ms', default: '500ms' }}
2100
+ // Enter=300ms, exit=100ms, property changes while visible use 500ms
2101
+ // Using timing animations for predictable test behavior across all drivers
2102
+ // ============================================================================
2103
+ function Scenario45_TransitionEnterExitWithDefault() {
2104
+ const [visible, setVisible] = useState(true)
2105
+ const [active, setActive] = useState(false)
2106
+ const ref = useRef<HTMLDivElement>(null)
2107
+ const { startLogging, stopLogging } = useAnimationLogger('45-enter-exit-default', ref, [
2108
+ 'opacity',
2109
+ 'transform',
2110
+ ])
2111
+
2112
+ return (
2113
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2114
+ <Button
2115
+ size="$2"
2116
+ onPress={() => {
2117
+ startLogging()
2118
+ setVisible(!visible)
2119
+ setTimeout(stopLogging, 2000)
2120
+ }}
2121
+ testID="scenario-45-trigger"
2122
+ data-testid="scenario-45-trigger"
2123
+ >
2124
+ 45: Toggle
2125
+ </Button>
2126
+ <Button
2127
+ size="$2"
2128
+ onPress={() => {
2129
+ startLogging()
2130
+ setActive(!active)
2131
+ setTimeout(stopLogging, 2000)
2132
+ }}
2133
+ testID="scenario-45-trigger-prop"
2134
+ data-testid="scenario-45-trigger-prop"
2135
+ >
2136
+ Prop
2137
+ </Button>
2138
+ <AnimatePresence>
2139
+ {visible && (
2140
+ <Square
2141
+ key="enter-exit-default-45"
2142
+ ref={ref as any}
2143
+ // enter=300ms, exit=100ms, property changes while visible=500ms
2144
+ transition={{ enter: '300ms', exit: '100ms', default: '500ms' } as any}
2145
+ size={40}
2146
+ bg="$red10"
2147
+ opacity={active ? 0.5 : 1}
2148
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2149
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2150
+ testID="scenario-45-target"
2151
+ data-testid="scenario-45-target"
2152
+ />
2153
+ )}
2154
+ </AnimatePresence>
2155
+ <Paragraph size="$1">
2156
+ {visible ? 'visible' : 'hidden'} (enter=300ms, exit=100ms, default=500ms)
2157
+ </Paragraph>
2158
+ </XStack>
2159
+ )
2160
+ }
2161
+
2162
+ // ============================================================================
2163
+ // SCENARIO 46: Enter/Exit with Per-Property Configs
2164
+ // Tests: transition={{ enter: '300ms', exit: '100ms', opacity: '500ms' }}
2165
+ // Enter=300ms for scale, exit=100ms for scale, but opacity always uses 500ms
2166
+ // Using timing animations for predictable test behavior across all drivers
2167
+ // ============================================================================
2168
+ function Scenario46_TransitionEnterExitPerProperty() {
2169
+ const [visible, setVisible] = useState(true)
2170
+ const ref = useRef<HTMLDivElement>(null)
2171
+ const { startLogging, stopLogging } = useAnimationLogger(
2172
+ '46-enter-exit-per-prop',
2173
+ ref,
2174
+ ['opacity', 'transform']
2175
+ )
2176
+
2177
+ return (
2178
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2179
+ <Button
2180
+ size="$2"
2181
+ onPress={() => {
2182
+ startLogging()
2183
+ setVisible(!visible)
2184
+ setTimeout(stopLogging, 2000)
2185
+ }}
2186
+ testID="scenario-46-trigger"
2187
+ data-testid="scenario-46-trigger"
2188
+ >
2189
+ 46: PerProp
2190
+ </Button>
2191
+ <AnimatePresence>
2192
+ {visible && (
2193
+ <Square
2194
+ key="enter-exit-per-prop-46"
2195
+ ref={ref as any}
2196
+ // enter=300ms for scale, exit=100ms for scale, but opacity always=500ms
2197
+ transition={{ enter: '300ms', exit: '100ms', opacity: '500ms' } as any}
2198
+ size={40}
2199
+ bg="$blue10"
2200
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2201
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2202
+ testID="scenario-46-target"
2203
+ data-testid="scenario-46-target"
2204
+ />
2205
+ )}
2206
+ </AnimatePresence>
2207
+ <Paragraph size="$1">
2208
+ {visible ? 'visible' : 'hidden'} (enter=300ms, exit=100ms, opacity=500ms)
2209
+ </Paragraph>
2210
+ </XStack>
2211
+ )
2212
+ }
2213
+
2214
+ // ============================================================================
2215
+ // SCENARIO 47: Enter/Exit Transitions with Delay
2216
+ // Tests: transition={{ enter: '300ms', exit: '100ms', delay: 200 }}
2217
+ // Both enter and exit have 200ms delay before starting
2218
+ // Using timing animations for predictable test behavior across all drivers
2219
+ // ============================================================================
2220
+ function Scenario47_TransitionEnterExitWithDelay() {
2221
+ const [visible, setVisible] = useState(true)
2222
+ const ref = useRef<HTMLDivElement>(null)
2223
+ const { startLogging, stopLogging } = useAnimationLogger('47-enter-exit-delay', ref, [
2224
+ 'opacity',
2225
+ 'transform',
2226
+ ])
2227
+
2228
+ return (
2229
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2230
+ <Button
2231
+ size="$2"
2232
+ onPress={() => {
2233
+ startLogging()
2234
+ setVisible(!visible)
2235
+ setTimeout(stopLogging, 2000)
2236
+ }}
2237
+ testID="scenario-47-trigger"
2238
+ data-testid="scenario-47-trigger"
2239
+ >
2240
+ 47: Delay
2241
+ </Button>
2242
+ <AnimatePresence>
2243
+ {visible && (
2244
+ <Square
2245
+ key="enter-exit-delay-47"
2246
+ ref={ref as any}
2247
+ transition={{ enter: '300ms', exit: '100ms', delay: 200 } as any}
2248
+ size={40}
2249
+ bg="$color10"
2250
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2251
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2252
+ testID="scenario-47-target"
2253
+ data-testid="scenario-47-target"
2254
+ />
2255
+ )}
2256
+ </AnimatePresence>
2257
+ <Paragraph size="$1">
2258
+ {visible ? 'visible' : 'hidden'} (enter=300ms, exit=100ms, delay=200ms)
2259
+ </Paragraph>
2260
+ </XStack>
2261
+ )
2262
+ }
2263
+
2264
+ // ============================================================================
2265
+ // SCENARIO 48: animateOnly with exitStyle
2266
+ // Tests: animateOnly={['opacity', 'transform']} combined with exitStyle
2267
+ // Exit animation should work correctly when animateOnly includes the exit properties
2268
+ // ============================================================================
2269
+ function Scenario48_AnimateOnlyWithExitStyle() {
2270
+ const [visible, setVisible] = useState(true)
2271
+ const ref = useRef<HTMLDivElement>(null)
2272
+ const { startLogging, stopLogging } = useAnimationLogger('48-animate-only-exit', ref, [
2273
+ 'opacity',
2274
+ 'transform',
2275
+ ])
2276
+
2277
+ return (
2278
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2279
+ <Button
2280
+ size="$2"
2281
+ onPress={() => {
2282
+ if (visible) startLogging()
2283
+ setVisible(!visible)
2284
+ setTimeout(stopLogging, 2000)
2285
+ }}
2286
+ testID="scenario-48-trigger"
2287
+ data-testid="scenario-48-trigger"
2288
+ >
2289
+ 48: AnimateOnly+Exit
2290
+ </Button>
2291
+ <AnimatePresence>
2292
+ {visible && (
2293
+ <Square
2294
+ key="animate-only-exit-48"
2295
+ ref={ref as any}
2296
+ transition="500ms"
2297
+ animateOnly={['opacity', 'transform']}
2298
+ size={40}
2299
+ bg="$blue10"
2300
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2301
+ testID="scenario-48-target"
2302
+ data-testid="scenario-48-target"
2303
+ />
2304
+ )}
2305
+ </AnimatePresence>
2306
+ <Paragraph size="$1">
2307
+ {visible ? 'visible' : 'hidden'} (animateOnly=['opacity', 'transform'])
2308
+ </Paragraph>
2309
+ </XStack>
2310
+ )
2311
+ }
2312
+
2313
+ // ============================================================================
2314
+ // SCENARIO 49: animateOnly with enterStyle and exitStyle
2315
+ // Tests: animateOnly combined with both enter and exit animations
2316
+ // Both should animate smoothly using only the specified properties
2317
+ // ============================================================================
2318
+ function Scenario49_AnimateOnlyWithEnterExitStyle() {
2319
+ const [visible, setVisible] = useState(true)
2320
+ const ref = useRef<HTMLDivElement>(null)
2321
+ const { startLogging, stopLogging } = useAnimationLogger(
2322
+ '49-animate-only-enter-exit',
2323
+ ref,
2324
+ ['opacity', 'transform']
2325
+ )
2326
+
2327
+ return (
2328
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2329
+ <Button
2330
+ size="$2"
2331
+ onPress={() => {
2332
+ startLogging()
2333
+ setVisible(!visible)
2334
+ setTimeout(stopLogging, 2000)
2335
+ }}
2336
+ testID="scenario-49-trigger"
2337
+ data-testid="scenario-49-trigger"
2338
+ >
2339
+ 49: AnimateOnly+Enter+Exit
2340
+ </Button>
2341
+ <AnimatePresence>
2342
+ {visible && (
2343
+ <Square
2344
+ key="animate-only-enter-exit-49"
2345
+ ref={ref as any}
2346
+ transition="500ms"
2347
+ animateOnly={['opacity', 'transform']}
2348
+ size={40}
2349
+ bg="$green10"
2350
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2351
+ exitStyle={{ opacity: 0, scale: 0.5, y: 20 }}
2352
+ testID="scenario-49-target"
2353
+ data-testid="scenario-49-target"
2354
+ />
2355
+ )}
2356
+ </AnimatePresence>
2357
+ <Paragraph size="$1">
2358
+ {visible ? 'visible' : 'hidden'} (enter+exit with animateOnly)
2359
+ </Paragraph>
2360
+ </XStack>
2361
+ )
2362
+ }
2363
+
2364
+ // ============================================================================
2365
+ // SCENARIO 50: Enter Timing Verification
2366
+ // Tests: transition={{ enter: '200ms', exit: '1000ms' }}
2367
+ // BUG FIX TEST: Verifies that enter ACTUALLY uses 200ms, not exit/default timing
2368
+ // The enter animation should complete in ~200ms, not 1000ms
2369
+ // ============================================================================
2370
+ function Scenario50_EnterTimingVerification() {
2371
+ const [visible, setVisible] = useState(true)
2372
+ const ref = useRef<HTMLDivElement>(null)
2373
+ const { startLogging, stopLogging } = useAnimationLogger('50-enter-timing', ref, [
2374
+ 'opacity',
2375
+ 'transform',
2376
+ ])
2377
+
2378
+ return (
2379
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2380
+ <Button
2381
+ size="$2"
2382
+ onPress={() => {
2383
+ startLogging()
2384
+ setVisible(!visible)
2385
+ setTimeout(stopLogging, 2000)
2386
+ }}
2387
+ testID="scenario-50-trigger"
2388
+ data-testid="scenario-50-trigger"
2389
+ >
2390
+ 50: Enter 200ms
2391
+ </Button>
2392
+ <AnimatePresence>
2393
+ {visible && (
2394
+ <Square
2395
+ key="enter-timing-50"
2396
+ ref={ref as any}
2397
+ // enter should be 200ms (fast), exit should be 1000ms (slow)
2398
+ transition={{ enter: '200ms', exit: '1000ms' } as any}
2399
+ size={40}
2400
+ bg="$red10"
2401
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2402
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2403
+ testID="scenario-50-target"
2404
+ data-testid="scenario-50-target"
2405
+ />
2406
+ )}
2407
+ </AnimatePresence>
2408
+ <Paragraph size="$1">
2409
+ {visible ? 'visible' : 'hidden'} (enter=200ms, exit=1000ms)
2410
+ </Paragraph>
2411
+ </XStack>
2412
+ )
2413
+ }
2414
+
2415
+ // ============================================================================
2416
+ // SCENARIO 51: Duration Normalization
2417
+ // Tests: transition={{ default: { duration: 1 } }}
2418
+ // BUG FIX TEST: duration: 1 should be 1ms, not 1 second
2419
+ // The animation should be instant (1ms), not slow (1 second)
2420
+ // ============================================================================
2421
+ function Scenario51_DurationNormalization() {
2422
+ const [visible, setVisible] = useState(true)
2423
+ const ref = useRef<HTMLDivElement>(null)
2424
+ const { startLogging, stopLogging } = useAnimationLogger('51-duration-norm', ref, [
2425
+ 'opacity',
2426
+ 'transform',
2427
+ ])
2428
+
2429
+ return (
2430
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2431
+ <Button
2432
+ size="$2"
2433
+ onPress={() => {
2434
+ startLogging()
2435
+ setVisible(!visible)
2436
+ setTimeout(stopLogging, 2000)
2437
+ }}
2438
+ testID="scenario-51-trigger"
2439
+ data-testid="scenario-51-trigger"
2440
+ >
2441
+ 51: Duration 1
2442
+ </Button>
2443
+ <AnimatePresence>
2444
+ {visible && (
2445
+ <Square
2446
+ key="duration-norm-51"
2447
+ ref={ref as any}
2448
+ // BUG: duration: 1 should mean 1ms (instant)
2449
+ // but it's being treated as 1 second
2450
+ transition={{ duration: 1 } as any}
2451
+ size={40}
2452
+ bg="$orange10"
2453
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2454
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2455
+ testID="scenario-51-target"
2456
+ data-testid="scenario-51-target"
2457
+ />
2458
+ )}
2459
+ </AnimatePresence>
2460
+ <Paragraph size="$1">
2461
+ {visible ? 'visible' : 'hidden'} (duration=1 should be 1ms)
2462
+ </Paragraph>
2463
+ </XStack>
2464
+ )
2465
+ }
2466
+
2467
+ // ============================================================================
2468
+ // SCENARIO 52: Duration Normalization with Inline Config
2469
+ // Tests: transition={{ default: '100ms', duration: 50 }}
2470
+ // BUG FIX TEST: Inline duration override should also be in milliseconds
2471
+ // ============================================================================
2472
+ function Scenario52_DurationNormalizationInlineConfig() {
2473
+ const [visible, setVisible] = useState(true)
2474
+ const ref = useRef<HTMLDivElement>(null)
2475
+ const { startLogging, stopLogging } = useAnimationLogger('52-duration-inline', ref, [
2476
+ 'opacity',
2477
+ 'transform',
2478
+ ])
2479
+
2480
+ return (
2481
+ <XStack gap="$2" alignItems="center" minHeight={50}>
2482
+ <Button
2483
+ size="$2"
2484
+ onPress={() => {
2485
+ startLogging()
2486
+ setVisible(!visible)
2487
+ setTimeout(stopLogging, 2000)
2488
+ }}
2489
+ testID="scenario-52-trigger"
2490
+ data-testid="scenario-52-trigger"
2491
+ >
2492
+ 52: Inline Duration
2493
+ </Button>
2494
+ <AnimatePresence>
2495
+ {visible && (
2496
+ <Square
2497
+ key="duration-inline-52"
2498
+ ref={ref as any}
2499
+ // uses '100ms' base but overrides with duration: 50 (should be 50ms)
2500
+ transition={{ default: '100ms', duration: 50 } as any}
2501
+ size={40}
2502
+ bg="$purple10"
2503
+ enterStyle={{ opacity: 0, scale: 0.5 }}
2504
+ exitStyle={{ opacity: 0, scale: 0.5 }}
2505
+ testID="scenario-52-target"
2506
+ data-testid="scenario-52-target"
2507
+ />
2508
+ )}
2509
+ </AnimatePresence>
2510
+ <Paragraph size="$1">
2511
+ {visible ? 'visible' : 'hidden'} (base=100ms, override=50ms)
2512
+ </Paragraph>
2513
+ </XStack>
2514
+ )
2515
+ }