@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,219 @@
1
+ # Motion Driver Tooltip Position Jump Bug
2
+
3
+ ## Problem Summary
4
+
5
+ When using `animatePosition` with a shared tooltip (scope pattern) in the motion driver, rapidly moving between triggers causes the tooltip to JUMP to a wrong position (often far left or near origin) before animating back.
6
+
7
+ ## Reproduction Pattern
8
+
9
+ 1. Hover on rightmost button (HIRE US), wait ~1 second for tooltip to fully appear
10
+ 2. Move mouse FAST to the left, briefly hitting the middle button (BENTO)
11
+ 3. Within ~33ms (1 frame at 30fps), continue to the leftmost button (TAKEOUT)
12
+ 4. The tooltip JUMPS to wrong position then animates back
13
+
14
+ ## Video Analysis (CleanShot 2026-01-22 at 22.46.02.mp4)
15
+
16
+ - **Frame rate**: 30fps (~33ms per frame)
17
+ - **Frame 50**: User leaves the left button (TAKEOUT), moving right
18
+ - **Frame 56**: Tooltip correctly positioned below middle button (COPY-PASTE UI / BENTO), shows "Bento — Free + paid pre-made UI"
19
+ - **Frame 57**: **THE JUMP** - Tooltip jumps to wrong position overlapping the buttons, text changes to "Takeout — universal RN starter kit"
20
+ - **Jump characteristics**: ~100px UP in Y, shifted left in X, happens in exactly 1 frame
21
+
22
+ ## Timing Constraints
23
+
24
+ - Total movement time: ~50-150ms for the bug to trigger
25
+ - Key factor: Brief hover on middle button (~33ms) then immediately to left button
26
+ - The bug seems to occur when animation is interrupted mid-flight by rapid position changes
27
+
28
+ ## Affected Code
29
+
30
+ - `/code/core/animations-motion/src/createAnimations.tsx` - Motion driver implementation
31
+ - `/code/ui/popper/src/Popper.tsx` - PopperContent handles position with floating-ui
32
+ - `flushAnimation()` - handles animation updates
33
+ - `getDiff()` - computes what properties changed
34
+
35
+ ## Files for Reproduction
36
+
37
+ - `/code/gui.hanzo.ai/features/site/home/PromoLinksRow.tsx` - Original component with the bug
38
+ - `/code/kitchen-sink/src/usecases/TooltipPositionJumpCase.tsx` - Test case
39
+ - `/code/kitchen-sink/scripts/repro-tooltip-jump.ts` - Playwright reproduction script
40
+
41
+ ## Jump Detection Criteria
42
+
43
+ - dx > 300px or dy > 300px in a single frame
44
+ - Jump from reasonable position (x > 100, y > 100) to near origin (x < 50 or y < 50)
45
+
46
+ ## Previous Fix Attempts
47
+
48
+ 1. Parsed matrix transform to find X/Y - broke other animations (immediate movement instead of animated)
49
+ 2. Issue: Hard to distinguish between legitimate position changes and bug jumps
50
+
51
+ ## Hypothesis
52
+
53
+ The motion driver's animation state gets out of sync when:
54
+
55
+ 1. Animation A starts (moving to middle button position)
56
+ 2. Before A completes, animation B starts (moving to left button position)
57
+ 3. The animation system calculates diff from wrong base position (possibly 0,0 or stale value)
58
+ 4. Result: Large jump because diff is computed against wrong starting point
59
+
60
+ ## Testing Notes
61
+
62
+ - Bug is **motion-driver specific** - other drivers (css, native, reanimated) may not have this issue
63
+ - Run kitchen-sink on port 9000: `yarn start:web`
64
+ - Run gui.hanzo.ai on port 8282: `yarn dev --port 8282` (v2 branch)
65
+ - Animation driver can be changed via URL param or config
66
+
67
+ ## Playwright Reproduction Results (SUCCESS!)
68
+
69
+ Script: `/code/kitchen-sink/scripts/repro-tooltip-jump.ts`
70
+
71
+ **Attempt 2 detected 2 jumps:**
72
+
73
+ 1. `(0,-4) -> (609,162)` delta:(609,166) - Tooltip appearing (expected, from initial position)
74
+ 2. `(609,166) -> (106,36)` delta:(503,130) - **THE BUG!** Jump from correct position to near-origin
75
+
76
+ The second jump is the bug - tooltip goes from reasonable position (609,166) to near-origin (106,36) in one frame.
77
+
78
+ ## Analysis So Far
79
+
80
+ ### The Jump Pattern
81
+
82
+ - Consistent reproduction: `(609,166) -> (~127-195, ~42-65)`
83
+ - Jump goes TO LEFT and UP (toward origin direction)
84
+ - Target X (~127-195) is LESS than leftmost button X (393)
85
+ - This suggests animation is computing from WRONG starting position
86
+
87
+ ### Code Flow
88
+
89
+ 1. `flushAnimation()` is called when position changes
90
+ 2. `getDiff()` computes changed properties (e.g., `{ x: newValue }`)
91
+ 3. `animate(scope.current, diff, animationOptions)` animates element
92
+ 4. Motion library reads current transform and animates TO the diff target
93
+
94
+ ### Hypothesis: Transform State Corruption
95
+
96
+ When rapidly changing triggers:
97
+
98
+ 1. Animation A starts: x 609 → 537 (HIRE → BENTO)
99
+ 2. Before A completes, Animation B starts: x → 393 (to TAKEOUT)
100
+ 3. Motion reads current transform which may be in corrupted/transitional state
101
+ 4. Animation animates from wrong value causing visual jump
102
+
103
+ ### Attempted Fixes
104
+
105
+ 1. `controls.current.complete()` before new animation - didn't help (bundled server issue)
106
+ 2. Need to verify fix actually reaches browser
107
+
108
+ ### Debugging Challenge
109
+
110
+ - gui.hanzo.ai on 8282 uses bundled code, doesn't pick up local changes
111
+ - kitchen-sink on 9000 doesn't trigger tooltips in headless mode
112
+ - Need to either fix bundling or find different test approach
113
+
114
+ ## Key Discovery - Transform Jump Pattern
115
+
116
+ From Playwright reproduction, the transform jumps like this:
117
+
118
+ ```
119
+ Before: matrix(1, 0, 0, 1, 609, 166)
120
+ After: matrix(1, 0, 0, 1, 128, 43)
121
+ ```
122
+
123
+ This happens in ONE FRAME, not animated. The motion library's `animate()` is supposed to animate from the current position to the target, but it's snapping instantly.
124
+
125
+ ## Root Cause Analysis
126
+
127
+ The jump happens when:
128
+
129
+ 1. Animation A is running (tooltip moving from trigger 1 to trigger 2)
130
+ 2. Animation B starts immediately (tooltip should move from trigger 2 to trigger 3)
131
+ 3. Motion's animate() gets called with just the DIFF (new target values)
132
+ 4. BUT the animation starts from the WRONG position (possibly 0,0 or stale value)
133
+
134
+ Potential causes:
135
+
136
+ 1. Motion library reads stale transform when animation is in flight
137
+ 2. Something clears/resets the element's transform before motion reads it
138
+ 3. The `controls.current.stop()` or animation interruption isn't working correctly
139
+ 4. Race condition between animation frames and new animation start
140
+
141
+ ## Files Involved
142
+
143
+ - `/code/core/animations-motion/src/createAnimations.tsx` - Motion driver
144
+ - `flushAnimation()` - Called when position changes
145
+ - `getDiff()` - Calculates what properties changed
146
+ - `animate(scope.current, diff, animationOptions)` - Starts animation
147
+
148
+ - `/code/ui/popper/src/Popper.tsx` - PopperContent
149
+ - `animatePosition` prop enables x/y animation
150
+ - Passes x/y from floating-ui to the animated element
151
+
152
+ ## TODO
153
+
154
+ - [x] Reliably reproduce with Playwright script (1 iteration = 1 jump)
155
+ - [ ] Verify other drivers don't have this issue
156
+ - [x] Add debug logging to trace values during jump
157
+ - [x] Test with `controls.current.stop()` before new animation (implemented)
158
+ - [ ] Consider using motion's `useMotionValue` for x/y instead of animate()
159
+ - [ ] Fix without breaking normal animations
160
+ - [ ] **FINAL STEP**: Create standalone reproduction repo and open PR on https://github.com/motiondivision/motion (n8 knows matt the author, highly responsive to thorough repros)
161
+
162
+ ## FINAL FIX (in createAnimations.tsx)
163
+
164
+ The fix handles position animation interruptions by:
165
+
166
+ 1. Detecting when a transform with translate is changing
167
+ 2. Reading the current animated position BEFORE calling stop() (important - stop() resets it!)
168
+ 3. Using keyframes to explicitly animate FROM current position TO target
169
+
170
+ ```typescript
171
+ const hasTransformWithTranslate =
172
+ typeof diff.transform === 'string' && diff.transform.includes('translate')
173
+
174
+ if (hasTransformWithTranslate && controls.current) {
175
+ // IMPORTANT: Read transform BEFORE stopping - stop() may reset it!
176
+ const computedStyle = getComputedStyle(node)
177
+ const currentTransform = computedStyle.transform
178
+
179
+ controls.current.stop()
180
+
181
+ // set transform to captured value to prevent flicker
182
+ node.style.transform = currentTransform
183
+ node.offsetHeight // force reflow
184
+
185
+ if (currentTransform && currentTransform !== 'none') {
186
+ // extract current position from matrix(1, 0, 0, 1, tx, ty)
187
+ const matrixMatch = currentTransform.match(
188
+ /matrix\([^,]+,\s*[^,]+,\s*[^,]+,\s*[^,]+,\s*([^,]+),\s*([^)]+)\)/
189
+ )
190
+ if (matrixMatch) {
191
+ const currentX = parseFloat(matrixMatch[1])
192
+ const currentY = parseFloat(matrixMatch[2])
193
+
194
+ // build start transform from current animated position
195
+ const startTransform = `translateX(${currentX}px) translateY(${currentY}px)`
196
+ const targetTransform = diff.transform as string
197
+
198
+ // use keyframes to animate from current position to target
199
+ const keyframeDiff = { ...diff }
200
+ keyframeDiff.transform = [startTransform, targetTransform]
201
+
202
+ controls.current = animate(scope.current, keyframeDiff, animationOptions)
203
+ return
204
+ }
205
+ }
206
+ }
207
+ ```
208
+
209
+ ## Key Insights
210
+
211
+ 1. **Gui converts x/y props to CSS transform**: When you use `x={100}`, Hanzo GUI converts it to `transform: translateX(100px)` before passing to the motion driver.
212
+
213
+ 2. **motion's stop() resets the transform**: When you call `controls.current.stop()`, the element's transform may reset to `matrix(1, 0, 0, 1, 0, 0)`. You MUST read the current position BEFORE calling stop().
214
+
215
+ 3. **Keyframes work for smooth interruption**: By using `transform: [startTransform, targetTransform]` as keyframes, motion knows exactly where to start from and animate to, preventing jumps.
216
+
217
+ ## Motion Version
218
+
219
+ - Upgraded to motion 12.29.0 (from 12.28.1) on 2026-01-22
@@ -0,0 +1,399 @@
1
+ import { expect, test, type CDPSession, type Page } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ // helper: use CDP Input.dispatchMouseEvent to simulate rapid mouse movement
5
+ // this generates real browser-level input events at high frequency,
6
+ // unlike Playwright's mouse.move which uses a synthetic approach
7
+ async function cdpMouseMove(
8
+ cdp: CDPSession,
9
+ fromX: number,
10
+ fromY: number,
11
+ toX: number,
12
+ toY: number,
13
+ steps: number,
14
+ delayMs = 2
15
+ ) {
16
+ for (let i = 0; i <= steps; i++) {
17
+ const t = i / steps
18
+ const x = Math.round(fromX + (toX - fromX) * t)
19
+ const y = Math.round(fromY + (toY - fromY) * t)
20
+ await cdp.send('Input.dispatchMouseEvent', {
21
+ type: 'mouseMoved',
22
+ x,
23
+ y,
24
+ button: 'none',
25
+ modifiers: 0,
26
+ })
27
+ if (delayMs > 0 && i < steps) {
28
+ await new Promise((r) => setTimeout(r, delayMs))
29
+ }
30
+ }
31
+ }
32
+
33
+ async function getTriggerCenter(page: Page, id: string) {
34
+ const box = await page.locator(`#${id}`).boundingBox()
35
+ if (!box) throw new Error(`trigger #${id} not found`)
36
+ return { x: box.x + box.width / 2, y: box.y + box.height / 2 }
37
+ }
38
+
39
+ test.describe('Tooltip rapid trigger switch bugs', () => {
40
+ test.beforeEach(async ({ page }) => {
41
+ await setupPage(page, {
42
+ name: 'TooltipMultiTriggerCase',
43
+ type: 'useCase',
44
+ })
45
+ await page.waitForLoadState('networkidle')
46
+ })
47
+
48
+ test('enter animation completes to full opacity after rapid CDP trigger switch', async ({
49
+ page,
50
+ }) => {
51
+ const content = page.locator('#tip-content')
52
+ const cdp = await page.context().newCDPSession(page)
53
+
54
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
55
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
56
+
57
+ // move into trigger A to start tooltip enter animation
58
+ await cdpMouseMove(cdp, a.x - 50, a.y, a.x, a.y, 10, 2)
59
+
60
+ // wait just enough for tooltip to mount and enter animation to start
61
+ // but NOT enough for it to complete (~medium transition is 200-300ms)
62
+ await page.waitForTimeout(60)
63
+
64
+ // rapidly sweep to trigger C — this should NOT interrupt the enter animation
65
+ // use many small steps to pass through the gap between triggers,
66
+ // which may briefly trigger mouseleave on A before mouseenter on C
67
+ await cdpMouseMove(cdp, a.x, a.y, c.x, c.y, 30, 1)
68
+
69
+ // wait for all animations to settle
70
+ await page.waitForTimeout(800)
71
+
72
+ await expect(content).toBeVisible({ timeout: 3000 })
73
+
74
+ const opacity = await content.evaluate((el) => {
75
+ return parseFloat(getComputedStyle(el).opacity)
76
+ })
77
+ expect(
78
+ opacity,
79
+ `tooltip should be fully opaque after trigger switch, got ${opacity}`
80
+ ).toBeGreaterThan(0.95)
81
+ })
82
+
83
+ test('enter animation completes when trigger switch happens during first 50ms', async ({
84
+ page,
85
+ }) => {
86
+ const content = page.locator('#tip-content')
87
+ const cdp = await page.context().newCDPSession(page)
88
+
89
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
90
+ const b = await getTriggerCenter(page, 'tip-trigger-b')
91
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
92
+
93
+ // rapidly sweep across all three triggers without pausing
94
+ // this creates the pattern: enter A → leave A → enter B → leave B → enter C
95
+ // all within ~100ms total, well within the enter animation duration
96
+ await cdpMouseMove(cdp, a.x - 30, a.y, a.x, a.y, 5, 1)
97
+ await cdpMouseMove(cdp, a.x, a.y, b.x, b.y, 8, 1)
98
+ await cdpMouseMove(cdp, b.x, b.y, c.x, c.y, 8, 1)
99
+
100
+ // wait for animations to settle
101
+ await page.waitForTimeout(800)
102
+
103
+ await expect(content).toBeVisible({ timeout: 3000 })
104
+
105
+ const opacity = await content.evaluate((el) =>
106
+ parseFloat(getComputedStyle(el).opacity)
107
+ )
108
+ expect(
109
+ opacity,
110
+ `tooltip should be fully opaque after rapid 3-trigger sweep, got ${opacity}`
111
+ ).toBeGreaterThan(0.95)
112
+
113
+ // also check the y transform isn't stuck at an intermediate value
114
+ const transform = await content.evaluate((el) => getComputedStyle(el).transform)
115
+ if (transform && transform !== 'none') {
116
+ // extract translateY from matrix — matrix(a,b,c,d,tx,ty)
117
+ const match = transform.match(/matrix\(([^)]+)\)/)
118
+ if (match) {
119
+ const values = match[1].split(',').map(Number)
120
+ const ty = values[5] // translateY component
121
+ // the enter animation goes from y:-4 to y:0, so ty should be close
122
+ // to the final popper-calculated position, not stuck at an offset
123
+ // we can't know the exact value, but it should NOT be NaN
124
+ expect(Number.isNaN(ty), 'transform should not be NaN').toBe(false)
125
+ }
126
+ }
127
+ })
128
+
129
+ test('enter animation completes after rapid back-and-forth CDP sweep', async ({
130
+ page,
131
+ }) => {
132
+ const content = page.locator('#tip-content')
133
+ const cdp = await page.context().newCDPSession(page)
134
+
135
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
136
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
137
+
138
+ // move into trigger A
139
+ await cdpMouseMove(cdp, a.x - 50, a.y, a.x, a.y, 8, 2)
140
+ await page.waitForTimeout(40)
141
+
142
+ // rapid sweep: A → C → A → C (mimics real fast back-and-forth)
143
+ await cdpMouseMove(cdp, a.x, a.y, c.x, c.y, 12, 1)
144
+ await page.waitForTimeout(20)
145
+ await cdpMouseMove(cdp, c.x, c.y, a.x, a.y, 12, 1)
146
+ await page.waitForTimeout(20)
147
+ await cdpMouseMove(cdp, a.x, a.y, c.x, c.y, 12, 1)
148
+
149
+ // wait for animations to settle
150
+ await page.waitForTimeout(800)
151
+
152
+ await expect(content).toBeVisible({ timeout: 3000 })
153
+
154
+ const opacity = await content.evaluate((el) =>
155
+ parseFloat(getComputedStyle(el).opacity)
156
+ )
157
+ expect(
158
+ opacity,
159
+ `tooltip should be fully opaque after rapid sweep, got ${opacity}`
160
+ ).toBeGreaterThan(0.95)
161
+ })
162
+
163
+ test('enter animation completes after zero-delay CDP trigger switch', async ({
164
+ page,
165
+ }) => {
166
+ // the stuck-opacity bug requires switching triggers during the very
167
+ // first frames of the enter animation — use zero delay between events
168
+ const content = page.locator('#tip-content')
169
+ const cdp = await page.context().newCDPSession(page)
170
+
171
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
172
+ const b = await getTriggerCenter(page, 'tip-trigger-b')
173
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
174
+
175
+ // move into trigger A with zero-delay CDP events (maximum speed)
176
+ await cdpMouseMove(cdp, a.x - 30, a.y, a.x, a.y, 5, 0)
177
+
178
+ // no wait at all — switch to C immediately while tooltip is still mounting
179
+ await cdpMouseMove(cdp, a.x, a.y, c.x, c.y, 20, 0)
180
+
181
+ // wait for enter animation to complete
182
+ await page.waitForTimeout(800)
183
+
184
+ // check content visibility and opacity
185
+ const isVisible = await content.isVisible().catch(() => false)
186
+ if (!isVisible) {
187
+ // tooltip might have closed — re-hover trigger C to reopen
188
+ await cdpMouseMove(cdp, c.x - 20, c.y, c.x, c.y, 5, 1)
189
+ await page.waitForTimeout(800)
190
+ }
191
+
192
+ await expect(content).toBeVisible({ timeout: 3000 })
193
+ const opacity = await content.evaluate((el) =>
194
+ parseFloat(getComputedStyle(el).opacity)
195
+ )
196
+ expect(
197
+ opacity,
198
+ `tooltip should be fully opaque after zero-delay switch, got ${opacity}`
199
+ ).toBeGreaterThan(0.95)
200
+ })
201
+
202
+ test('enter animation completes after immediate A→B→C sweep with no pauses', async ({
203
+ page,
204
+ }) => {
205
+ // simulate "as fast as possible" trigger switching — all events
206
+ // dispatched with 0ms delay between steps
207
+ const content = page.locator('#tip-content')
208
+ const cdp = await page.context().newCDPSession(page)
209
+
210
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
211
+ const b = await getTriggerCenter(page, 'tip-trigger-b')
212
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
213
+
214
+ // blast through all triggers at maximum CDP speed
215
+ await cdpMouseMove(cdp, a.x - 20, a.y, a.x, a.y, 3, 0)
216
+ await cdpMouseMove(cdp, a.x, a.y, b.x, b.y, 5, 0)
217
+ await cdpMouseMove(cdp, b.x, b.y, c.x, c.y, 5, 0)
218
+
219
+ // wait for animations
220
+ await page.waitForTimeout(800)
221
+
222
+ await expect(content).toBeVisible({ timeout: 3000 })
223
+ const opacity = await content.evaluate((el) =>
224
+ parseFloat(getComputedStyle(el).opacity)
225
+ )
226
+ expect(
227
+ opacity,
228
+ `tooltip should be fully opaque after instant A→B→C sweep, got ${opacity}`
229
+ ).toBeGreaterThan(0.95)
230
+ })
231
+
232
+ test('tooltip does not flash to position (0,0) during rapid trigger switching', async ({
233
+ page,
234
+ }) => {
235
+ // capture motion driver debug logs
236
+ const motionLogs: string[] = []
237
+ page.on('console', (msg) => {
238
+ const text = msg.text()
239
+ if (text.includes('[motion-debug-popper]')) {
240
+ motionLogs.push(text)
241
+ }
242
+ })
243
+
244
+ const content = page.locator('#tip-content')
245
+ const cdp = await page.context().newCDPSession(page)
246
+
247
+ const a = await getTriggerCenter(page, 'tip-trigger-a')
248
+ const c = await getTriggerCenter(page, 'tip-trigger-c')
249
+
250
+ // start by hovering trigger A to open tooltip
251
+ await cdpMouseMove(cdp, a.x - 50, a.y, a.x, a.y, 10, 2)
252
+ await page.waitForTimeout(300) // let tooltip fully open and enter animation complete
253
+
254
+ // enable animation driver debug logging
255
+ await page.evaluate(() => {
256
+ ;(window as any).__motionDebug = true
257
+ ;(window as any).__motionDebugLog = []
258
+ })
259
+
260
+ // intercept style.transform writes on the outer position element
261
+ await page.evaluate(() => {
262
+ const inner = document.getElementById('tip-content')
263
+ if (!inner) return
264
+ const outer = (inner.closest('[data-popper-animate-position]') ||
265
+ inner.parentElement) as HTMLElement | null
266
+ if (!outer) return
267
+ ;(window as any).__transformWriteLog = []
268
+ const log = (window as any).__transformWriteLog
269
+ const realDescriptor = Object.getOwnPropertyDescriptor(
270
+ CSSStyleDeclaration.prototype,
271
+ 'transform'
272
+ )
273
+ if (!realDescriptor) return
274
+ Object.defineProperty(outer.style, 'transform', {
275
+ get() {
276
+ return realDescriptor.get!.call(this)
277
+ },
278
+ set(value: string) {
279
+ log.push({
280
+ value: String(value).substring(0, 100),
281
+ t: performance.now(),
282
+ stack: new Error().stack?.split('\n').slice(1, 5).join(' | '),
283
+ })
284
+ return realDescriptor.set!.call(this, value)
285
+ },
286
+ configurable: true,
287
+ })
288
+ })
289
+
290
+ // set up in-browser rAF monitoring for highest fidelity
291
+ // monitor BOTH the inner content element AND the outer position wrapper
292
+ await page.evaluate(() => {
293
+ const inner = document.getElementById('tip-content')
294
+ if (!inner) return
295
+ // the outer position wrapper has data-popper-animate-position
296
+ const outer = (inner.closest('[data-popper-animate-position]') ||
297
+ inner.parentElement) as HTMLElement | null
298
+ ;(window as any).__posLog = []
299
+ ;(window as any).__transformLog = []
300
+ ;(window as any).__outerLog = []
301
+ const log = (window as any).__posLog
302
+ const tLog = (window as any).__transformLog
303
+ const oLog = (window as any).__outerLog
304
+ function sample() {
305
+ if (!inner || !inner.isConnected) return
306
+ const rect = inner.getBoundingClientRect()
307
+ log.push({ x: rect.x, y: rect.y, t: performance.now() })
308
+ tLog.push({
309
+ inline: inner.style.transform,
310
+ computed: getComputedStyle(inner).transform,
311
+ t: performance.now(),
312
+ })
313
+ if (outer && outer.isConnected) {
314
+ const outerRect = outer.getBoundingClientRect()
315
+ oLog.push({
316
+ x: outerRect.x,
317
+ y: outerRect.y,
318
+ inlineTransform: outer.style.transform,
319
+ computedTransform: getComputedStyle(outer).transform,
320
+ popperX: outer.getAttribute('data-popper-x'),
321
+ popperY: outer.getAttribute('data-popper-y'),
322
+ effX: outer.getAttribute('data-popper-eff-x'),
323
+ effY: outer.getAttribute('data-popper-eff-y'),
324
+ positioned: outer.getAttribute('data-popper-positioned'),
325
+ t: performance.now(),
326
+ })
327
+ }
328
+ requestAnimationFrame(sample)
329
+ }
330
+ requestAnimationFrame(sample)
331
+ })
332
+
333
+ // rapid back-and-forth sweeps
334
+ for (let i = 0; i < 4; i++) {
335
+ await cdpMouseMove(cdp, a.x, a.y, c.x, c.y, 10, 1)
336
+ await page.waitForTimeout(10)
337
+ await cdpMouseMove(cdp, c.x, c.y, a.x, a.y, 10, 1)
338
+ await page.waitForTimeout(10)
339
+ }
340
+
341
+ // wait for animations to settle
342
+ await page.waitForTimeout(500)
343
+
344
+ // collect results
345
+ const { posLog, transformLog, outerLog } = await page.evaluate(() => ({
346
+ posLog: (window as any).__posLog as { x: number; y: number; t: number }[],
347
+ transformLog: (window as any).__transformLog as {
348
+ inline: string
349
+ computed: string
350
+ t: number
351
+ }[],
352
+ outerLog: (window as any).__outerLog as {
353
+ x: number
354
+ y: number
355
+ inlineTransform: string
356
+ computedTransform: string
357
+ classList: string
358
+ t: number
359
+ }[],
360
+ }))
361
+
362
+ // check for (0,0) flashes — tooltip triggers are far from origin
363
+ const flashToOrigin = posLog.filter(
364
+ (p) => p.x >= 0 && p.x < 20 && p.y >= 0 && p.y < 20
365
+ )
366
+
367
+ // log diagnostic info for debugging
368
+ if (flashToOrigin.length > 0) {
369
+ const firstFlash = flashToOrigin[0]
370
+ const flashIdx = posLog.indexOf(firstFlash)
371
+ const context = posLog.slice(Math.max(0, flashIdx - 2), flashIdx + 3)
372
+ const tContext = transformLog.slice(Math.max(0, flashIdx - 2), flashIdx + 3)
373
+ console.log('flash context (positions):', JSON.stringify(context))
374
+ console.log('flash context (transforms):', JSON.stringify(tContext))
375
+ // show outer element state around the flash
376
+ const oContext = outerLog.slice(Math.max(0, flashIdx - 2), flashIdx + 3)
377
+ console.log('flash context (outer element):', JSON.stringify(oContext))
378
+ // dump transform write logs — these show WHO writes to style.transform
379
+ const writeLogs = await page.evaluate(
380
+ () => (window as any).__transformWriteLog || []
381
+ )
382
+ // show writes around the flash time
383
+ const flashT = firstFlash.t
384
+ const nearFlash = writeLogs.filter((w: any) => Math.abs(w.t - flashT) < 50)
385
+ console.log(
386
+ `transform writes near flash (${nearFlash.length} of ${writeLogs.length}):`
387
+ )
388
+ for (const w of nearFlash.slice(0, 15)) {
389
+ console.log(` t=${w.t.toFixed(1)} val=${w.value}`)
390
+ console.log(` ${w.stack}`)
391
+ }
392
+ }
393
+
394
+ expect(
395
+ flashToOrigin.length,
396
+ `tooltip should never flash to (0,0), found ${flashToOrigin.length} occurrences`
397
+ ).toBe(0)
398
+ })
399
+ })
@@ -0,0 +1,65 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.describe('TooltipTrigger display inline', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'TooltipTriggerInlineCase', type: 'useCase' })
7
+ })
8
+
9
+ test('tooltip trigger with display="inline" renders inline', async ({ page }) => {
10
+ await page.waitForLoadState('networkidle')
11
+
12
+ const trigger = page.getByTestId('inline-tooltip-trigger')
13
+ await expect(trigger).toBeVisible()
14
+
15
+ // Check that the trigger has inline display
16
+ const display = await trigger.evaluate((el) => {
17
+ return window.getComputedStyle(el).display
18
+ })
19
+
20
+ // Should be inline, inline-block, or inline-flex - not block/flex
21
+ expect(display).toMatch(/^inline/)
22
+ })
23
+
24
+ test('inline tooltip triggers appear within text flow', async ({ page }) => {
25
+ await page.waitForLoadState('networkidle')
26
+
27
+ const trigger = page.getByTestId('inline-tooltip-trigger')
28
+ const text = page.getByTestId('inline-tooltip-text')
29
+
30
+ // The trigger and its text should be visible
31
+ await expect(trigger).toBeVisible()
32
+ await expect(text).toBeVisible()
33
+
34
+ // The text content should be inline within the paragraph
35
+ const triggerBox = await trigger.boundingBox()
36
+ const textBox = await text.boundingBox()
37
+
38
+ // Text should be contained within or equal to trigger bounds
39
+ expect(triggerBox).toBeTruthy()
40
+ expect(textBox).toBeTruthy()
41
+ })
42
+
43
+ test('tooltip still works with inline display', async ({ page }) => {
44
+ await page.waitForLoadState('networkidle')
45
+
46
+ const trigger = page.getByTestId('inline-tooltip-trigger')
47
+ const content = page.getByTestId('inline-tooltip-content')
48
+
49
+ // Initially content should not be visible
50
+ await expect(content).not.toBeVisible()
51
+
52
+ // Hover over trigger to show tooltip
53
+ await trigger.hover()
54
+
55
+ // Wait for tooltip to appear
56
+ await expect(content).toBeVisible({ timeout: 5000 })
57
+
58
+ // Move away from trigger
59
+ await page.mouse.move(0, 0)
60
+
61
+ // Wait for tooltip to hide
62
+ await page.waitForTimeout(500)
63
+ await expect(content).not.toBeVisible()
64
+ })
65
+ })