@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.
- package/.detoxrc.js +130 -0
- package/.env.production +2 -0
- package/.maestro/config.yaml +4 -0
- package/.maestro/flows/shorthand-variables.yaml +23 -0
- package/.watchmanconfig +1 -0
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/app.json +43 -0
- package/assets/adaptive-icon.png +0 -0
- package/assets/favicon.png +0 -0
- package/assets/icon.png +0 -0
- package/assets/splash.png +0 -0
- package/babel.config.js +25 -0
- package/e2e/CompilerExtraction.test.ts +147 -0
- package/e2e/GroupPressNative.test.ts +167 -0
- package/e2e/MediaQueryGtMd.test.ts +71 -0
- package/e2e/NativePortal.test.ts +113 -0
- package/e2e/PointerEvents.test.ts +116 -0
- package/e2e/PressStyleNative.noRngh.test.ts +191 -0
- package/e2e/PressStyleNative.test.ts +231 -0
- package/e2e/SafeArea.test.ts +57 -0
- package/e2e/SelectAndroidOnPress.test.ts +181 -0
- package/e2e/SelectRemount.test.ts +137 -0
- package/e2e/SheetDragResist.test.ts +370 -0
- package/e2e/SheetKeyboardDrag.test.ts +249 -0
- package/e2e/SheetScrollableDrag.test.ts +560 -0
- package/e2e/ShorthandVariables.test.ts +53 -0
- package/e2e/ThemeChangeBasic.test.ts +123 -0
- package/e2e/ThemeMutation.test.ts +80 -0
- package/e2e/check-rngh-status.test.ts +31 -0
- package/e2e/jest.config.js +19 -0
- package/e2e/utils/colors.ts +75 -0
- package/e2e/utils/navigation.ts +53 -0
- package/eas.json +22 -0
- package/flows/AlertDialog.yaml +17 -0
- package/flows/OpenApp.yaml +25 -0
- package/flows/Select.yaml +13 -0
- package/flows/Sheet.yaml +12 -0
- package/flows/Tabs.yaml +13 -0
- package/flows/Toast.yaml +14 -0
- package/flows/WarmUp.yaml +24 -0
- package/index.html +21 -0
- package/index.js +17 -0
- package/metro.config.js +64 -0
- package/next-router-shim.ts +9 -0
- package/package.json +118 -0
- package/plans/toast-2.md +471 -0
- package/playwright.config.ts +71 -0
- package/plugins/expo-modules-core-swift6.js +76 -0
- package/pod-install.sh +7 -0
- package/public/favicon.svg +70 -0
- package/public/fonts/inter.css +15 -0
- package/public/fonts/noto-cn.otf +0 -0
- package/public/gui-icon.svg +68 -0
- package/run-detox.sh +230 -0
- package/run-native-tests.sh +4 -0
- package/run-tests-parallel.ts +195 -0
- package/screenshots/Screenshotter.test.tsx +48 -0
- package/src/AnimationDemos.tsx +131 -0
- package/src/App.native.tsx +121 -0
- package/src/App.tsx +121 -0
- package/src/Navigation.tsx +98 -0
- package/src/Sandbox.tsx +87 -0
- package/src/TestDynamicEval.tsx +33 -0
- package/src/TestNativeSheet.tsx +100 -0
- package/src/components/TimedRender.tsx +18 -0
- package/src/constants/test-ids.ts +52 -0
- package/src/features/demos/demo-screen.tsx +72 -0
- package/src/features/home/ColorSchemeListItem.tsx +41 -0
- package/src/features/home/TestBuildAButton.tsx +102 -0
- package/src/features/home/TestSeparator.tsx +0 -0
- package/src/features/home/screen.tsx +285 -0
- package/src/features/testcases/screen.tsx +59 -0
- package/src/features/testcases/test-screen.tsx +50 -0
- package/src/generatedV5Theme.ts +112 -0
- package/src/gui.config.ts +411 -0
- package/src/guy.png +0 -0
- package/src/index.tsx +6 -0
- package/src/provider/index.tsx +18 -0
- package/src/test-gui-stack.tsx +11 -0
- package/src/test.tsx +3 -0
- package/src/useKitchenSinkTheme.tsx +15 -0
- package/src/usecases/ActionsSheetComparison.tsx +194 -0
- package/src/usecases/AnimatePresenceEnterExitCase.tsx +255 -0
- package/src/usecases/AnimatePresenceExitTest.tsx +69 -0
- package/src/usecases/AnimatedByProp.tsx +39 -0
- package/src/usecases/AnimationComprehensiveCase.tsx +2515 -0
- package/src/usecases/AnimationValueLoggingCase.tsx +526 -0
- package/src/usecases/AnimationsWithMediaQueriesCase.tsx +110 -0
- package/src/usecases/Benchmark.tsx +148 -0
- package/src/usecases/BenchmarkSelect.tsx +34 -0
- package/src/usecases/ButtonCircular.tsx +3 -0
- package/src/usecases/ButtonCustom.tsx +33 -0
- package/src/usecases/ButtonIconColor.tsx +18 -0
- package/src/usecases/ButtonInverse.tsx +30 -0
- package/src/usecases/ButtonUnstyled.tsx +31 -0
- package/src/usecases/CheckboxDisabledOnPress.tsx +62 -0
- package/src/usecases/ClickDuringEnterCase.tsx +59 -0
- package/src/usecases/CodeExamplesInput.tsx +9 -0
- package/src/usecases/ColorTokenFallback.tsx +52 -0
- package/src/usecases/CompilerExtraction.tsx +380 -0
- package/src/usecases/ComplexVariants.tsx +164 -0
- package/src/usecases/CrashAdaptSheet.tsx +98 -0
- package/src/usecases/CustomStyledAnimatedPopover.tsx +42 -0
- package/src/usecases/CustomStyledAnimatedTooltip.tsx +72 -0
- package/src/usecases/DOMNodeAPIs.tsx +154 -0
- package/src/usecases/DialogFocusScopeCase.tsx +277 -0
- package/src/usecases/DialogFocusScopeDebug.tsx +85 -0
- package/src/usecases/DialogNestedCase.tsx +121 -0
- package/src/usecases/DialogOpenControlled.tsx +49 -0
- package/src/usecases/DialogPointerEventsCase.tsx +58 -0
- package/src/usecases/DialogScopedCase.tsx +106 -0
- package/src/usecases/DialogSheetAdaptCase.tsx +178 -0
- package/src/usecases/DialogSheetAdaptResizeCase.tsx +98 -0
- package/src/usecases/DismissLayerStackingCase.tsx +223 -0
- package/src/usecases/DriverDisableAnimationPropsCase.tsx +44 -0
- package/src/usecases/Example.tsx +10 -0
- package/src/usecases/ExitCompletionCase.tsx +713 -0
- package/src/usecases/FocusVisibleButton.tsx +14 -0
- package/src/usecases/FocusVisibleButtonPointer.tsx +13 -0
- package/src/usecases/FocusVisibleButtonWithFocusStyle.tsx +16 -0
- package/src/usecases/FocusWithinCase.tsx +55 -0
- package/src/usecases/FontTokensInVariants.tsx +14 -0
- package/src/usecases/FormButtonTypeCase.tsx +34 -0
- package/src/usecases/GlobalScopedTriggerIsolationCase.tsx +178 -0
- package/src/usecases/GroupHoverMobile.tsx +39 -0
- package/src/usecases/GroupPressInVariant.tsx +92 -0
- package/src/usecases/GroupPressNative.tsx +200 -0
- package/src/usecases/GroupProp.tsx +96 -0
- package/src/usecases/GroupPseudoVariantOverride.tsx +56 -0
- package/src/usecases/GroupUseCases.tsx +94 -0
- package/src/usecases/HeightMediaQueryOverrideCase.tsx +183 -0
- package/src/usecases/InputAutoFocusAfterMenuCase.tsx +105 -0
- package/src/usecases/InputAutoFocusStyledCase.tsx +39 -0
- package/src/usecases/KeyboardControllerTest.tsx +146 -0
- package/src/usecases/ListItem.tsx +123 -0
- package/src/usecases/MediaQueriesV5.tsx +137 -0
- package/src/usecases/MediaQueryGtMd.tsx +73 -0
- package/src/usecases/MenuAboveDialogCase.tsx +75 -0
- package/src/usecases/MenuAccessibilityCase.tsx +133 -0
- package/src/usecases/MenuAnimatePositionCase.tsx +41 -0
- package/src/usecases/MenuArrowAnimatePresenceCase.tsx +98 -0
- package/src/usecases/MenuAsChildPositionCase.tsx +24 -0
- package/src/usecases/MenuAutoResizeCase.tsx +57 -0
- package/src/usecases/MenuBottomCase.tsx +55 -0
- package/src/usecases/MenuFocusLeaveCase.tsx +135 -0
- package/src/usecases/MenuHighlightCase.tsx +44 -0
- package/src/usecases/MenuItemFocusCase.tsx +79 -0
- package/src/usecases/MenuItemPseudoOverrideCase.tsx +270 -0
- package/src/usecases/MenuMultiTriggerCase.tsx +47 -0
- package/src/usecases/MenuOverflowCase.tsx +60 -0
- package/src/usecases/MenuSubCase.tsx +223 -0
- package/src/usecases/MenuSubLeftCase.tsx +178 -0
- package/src/usecases/MenuSubNestedPositionCase.tsx +171 -0
- package/src/usecases/MenuSubStyledCase.tsx +145 -0
- package/src/usecases/MenuThemeCase.tsx +50 -0
- package/src/usecases/MenuUnstyledCase.tsx +52 -0
- package/src/usecases/MultiDriverAnimation.tsx +118 -0
- package/src/usecases/NativePortalTest.tsx +179 -0
- package/src/usecases/NewInputBasic.tsx +16 -0
- package/src/usecases/NewInputEvents.tsx +29 -0
- package/src/usecases/NonGuiTextStyledType.tsx +23 -0
- package/src/usecases/OnLayoutCase.tsx +134 -0
- package/src/usecases/OnLayoutScaleCase.tsx +88 -0
- package/src/usecases/OnLayoutStressCase.tsx +353 -0
- package/src/usecases/OpacityModifierCase.tsx +113 -0
- package/src/usecases/OverlayStyled.tsx +66 -0
- package/src/usecases/ParagraphSpanFontInheritance.tsx +53 -0
- package/src/usecases/PlaceholderTextColor.tsx +20 -0
- package/src/usecases/PointerEventsCase.tsx +100 -0
- package/src/usecases/PopoverAndMenuMultiTriggerCase.tsx +138 -0
- package/src/usecases/PopoverCase.tsx +222 -0
- package/src/usecases/PopoverContentStyledPlusAnimations.tsx +44 -0
- package/src/usecases/PopoverFocusScopeCase.tsx +171 -0
- package/src/usecases/PopoverHoverableCase.tsx +167 -0
- package/src/usecases/PopoverHoverableDisableClickCase.tsx +118 -0
- package/src/usecases/PopoverHoverableRapidCase.tsx +103 -0
- package/src/usecases/PopoverHoverableScopedCase.tsx +135 -0
- package/src/usecases/PopoverScopedCase.tsx +76 -0
- package/src/usecases/PopoverTriggerIsolationCase.tsx +80 -0
- package/src/usecases/PressStyleNative.tsx +143 -0
- package/src/usecases/PseudoStyleMerge.tsx +25 -0
- package/src/usecases/PseudoTransitionCase.tsx +174 -0
- package/src/usecases/RawAnimatedValueCase.tsx +231 -0
- package/src/usecases/RemoveScrollCase.tsx +66 -0
- package/src/usecases/RenderPropCase.tsx +263 -0
- package/src/usecases/SafeAreaCase.tsx +236 -0
- package/src/usecases/ScrollViewRefCase.tsx +88 -0
- package/src/usecases/SecondPage.tsx +5 -0
- package/src/usecases/SelectAndroidOnPress.tsx +129 -0
- package/src/usecases/SelectFocusScopeCase.tsx +270 -0
- package/src/usecases/SelectRemount.tsx +136 -0
- package/src/usecases/Shadows.tsx +5 -0
- package/src/usecases/SheetAnimationCase.tsx +155 -0
- package/src/usecases/SheetDragCase.tsx +183 -0
- package/src/usecases/SheetDragResistCase.tsx +433 -0
- package/src/usecases/SheetDragResistCase.web.tsx +359 -0
- package/src/usecases/SheetKeyboardDragCase.tsx +328 -0
- package/src/usecases/SheetKeyboardFitContentCase.tsx +165 -0
- package/src/usecases/SheetOnAnimationCompleteCase.tsx +54 -0
- package/src/usecases/SheetScrollLockCase.tsx +166 -0
- package/src/usecases/SheetScrollableDrag.tsx +249 -0
- package/src/usecases/SheetSnapPointsFitCase.tsx +393 -0
- package/src/usecases/ShorthandVariables.tsx +49 -0
- package/src/usecases/SlowThemeReRender.tsx +48 -0
- package/src/usecases/SpinnerCustomColors.tsx +34 -0
- package/src/usecases/StackZIndex.tsx +82 -0
- package/src/usecases/StressPage.tsx +301 -0
- package/src/usecases/StylePlatform.tsx +30 -0
- package/src/usecases/StyleProp.tsx +29 -0
- package/src/usecases/StyledAnchor.tsx +27 -0
- package/src/usecases/StyledButtonAnimationAuto.tsx +99 -0
- package/src/usecases/StyledButtonTheme.tsx +63 -0
- package/src/usecases/StyledButtonVariantPseudo.tsx +25 -0
- package/src/usecases/StyledButtonVariantPseudoMerge.tsx +77 -0
- package/src/usecases/StyledCheckboxTheme.tsx +23 -0
- package/src/usecases/StyledContextColor.tsx +246 -0
- package/src/usecases/StyledContextTokens.tsx +147 -0
- package/src/usecases/StyledHOCNamed.tsx +20 -0
- package/src/usecases/StyledHtmlCase.tsx +144 -0
- package/src/usecases/StyledIconColor.tsx +19 -0
- package/src/usecases/StyledInputFocusStyle.tsx +21 -0
- package/src/usecases/StyledInputOnFocus.tsx +30 -0
- package/src/usecases/StyledMediaQueryMerge.tsx +95 -0
- package/src/usecases/StyledOverridePsuedo.tsx +26 -0
- package/src/usecases/StyledRNW.tsx +61 -0
- package/src/usecases/StyledStyleableInputOnFocus.tsx +34 -0
- package/src/usecases/StyledStyleableInputVariant.tsx +48 -0
- package/src/usecases/StyledStyledStyleableInputOnFocus.tsx +36 -0
- package/src/usecases/StyledVariantTextColor.tsx +25 -0
- package/src/usecases/StyledViewOnFocus.tsx +32 -0
- package/src/usecases/TabHoverAnimationCase.tsx +212 -0
- package/src/usecases/TextNestedInheritance.tsx +80 -0
- package/src/usecases/ThemeChange.tsx +100 -0
- package/src/usecases/ThemeChangeBasic.tsx +52 -0
- package/src/usecases/ThemeComponentResolution.tsx +119 -0
- package/src/usecases/ThemeConditionalName.tsx +31 -0
- package/src/usecases/ThemeMediaAnimationCase.tsx +39 -0
- package/src/usecases/ThemeMutation.tsx +86 -0
- package/src/usecases/ThemeNested.tsx +103 -0
- package/src/usecases/ThemeReset.tsx +62 -0
- package/src/usecases/ThemeShallowCase.tsx +83 -0
- package/src/usecases/ToastCase.tsx +46 -0
- package/src/usecases/ToggleGroupActiveProps.tsx +40 -0
- package/src/usecases/ToggleGroupXGroupCase.tsx +104 -0
- package/src/usecases/TooltipAnimationCase.tsx +99 -0
- package/src/usecases/TooltipCase.tsx +32 -0
- package/src/usecases/TooltipGlobalPatternCase.tsx +83 -0
- package/src/usecases/TooltipGroupCase.tsx +102 -0
- package/src/usecases/TooltipMultiTriggerCase.tsx +88 -0
- package/src/usecases/TooltipPositionJumpCase.tsx +91 -0
- package/src/usecases/TooltipTriggerInlineCase.tsx +60 -0
- package/src/usecases/TransformMediaQueryMerge.tsx +98 -0
- package/src/usecases/UseCases.tsx +409 -0
- package/src/usecases/UseTheme.tsx +41 -0
- package/src/usecases/V5ThemeBuilderOutput.tsx +231 -0
- package/src/usecases/VariantFontFamily.tsx +25 -0
- package/src/usecases/VariantsOrder.tsx +117 -0
- package/src/usecases/ZIndex.tsx +155 -0
- package/src/usecases/helpers.tsx +44 -0
- package/src/usecases/index.native.ts +122 -0
- package/src/usecases/index.ts +3 -0
- package/src/usecases/index.web.ts +177 -0
- package/tests/AnimatePresenceEnterExit.animated.test.tsx +176 -0
- package/tests/AnimatedByProp.animated.test.tsx +138 -0
- package/tests/AnimationBehavior.animated.test.tsx +543 -0
- package/tests/AnimationTiming.animated.test.tsx +195 -0
- package/tests/AnimationsWithMediaQueries.animated.test.tsx +154 -0
- package/tests/BuildAButton.test.tsx +87 -0
- package/tests/ButtonCircular.test.tsx +17 -0
- package/tests/ButtonCustom.test.tsx +17 -0
- package/tests/ButtonIconColor.test.tsx +23 -0
- package/tests/ButtonUnstyled.test.tsx +56 -0
- package/tests/ClickDuringEnter.animated.test.tsx +174 -0
- package/tests/ColorTokenFallback.test.tsx +45 -0
- package/tests/DOMNodeAPIs.test.tsx +161 -0
- package/tests/DialogFocusScope.animated.test.tsx +309 -0
- package/tests/DialogNested.test.tsx +128 -0
- package/tests/DialogOpenControlled.test.tsx +42 -0
- package/tests/DialogPointerEvents.animated.test.tsx +108 -0
- package/tests/DialogScoped.test.tsx +137 -0
- package/tests/DialogSheetAdapt.test.tsx +68 -0
- package/tests/DialogSheetAdaptResize.test.tsx +161 -0
- package/tests/DismissLayerStacking.test.tsx +292 -0
- package/tests/DriverDisableAnimationProps.animated.test.tsx +157 -0
- package/tests/ExitCompletion.animated.test.tsx +425 -0
- package/tests/ExitTimingCheck.animated.test.ts +34 -0
- package/tests/FocusVisibleButton.test.tsx +41 -0
- package/tests/FocusVisibleButtonPointerFocus.test.tsx +23 -0
- package/tests/FocusVisibleButtonPointerFocusWithFocusStyle.test.tsx +40 -0
- package/tests/FocusWithinStyle.animated.test.tsx +66 -0
- package/tests/FocusWithinStyle.test.tsx +60 -0
- package/tests/FormButtonType.test.tsx +42 -0
- package/tests/GlobalScopedTriggerIsolation.test.tsx +89 -0
- package/tests/GroupHoverMobile.test.tsx +52 -0
- package/tests/GroupPressInVariant.test.tsx +82 -0
- package/tests/GroupProp.test.tsx +30 -0
- package/tests/GroupPseudoVariantOverride.test.tsx +57 -0
- package/tests/GroupUseCases.test.tsx +111 -0
- package/tests/GuiSiteMotion.test.ts +481 -0
- package/tests/HeightMediaQueryOverride.test.tsx +112 -0
- package/tests/InputAutoFocusAfterMenu.test.tsx +55 -0
- package/tests/InputAutoFocusStyled.test.tsx +22 -0
- package/tests/ListItem.test.tsx +129 -0
- package/tests/MediaQueriesV5.test.tsx +113 -0
- package/tests/MediaQueryGtMd.test.tsx +84 -0
- package/tests/MenuAboveDialog.test.tsx +108 -0
- package/tests/MenuAccessibility.test.tsx +346 -0
- package/tests/MenuAnimatePosition.animated.test.tsx +57 -0
- package/tests/MenuArrowAnimatePresence.animated.test.tsx +71 -0
- package/tests/MenuAsChildPosition.test.tsx +16 -0
- package/tests/MenuAutoResize.test.tsx +54 -0
- package/tests/MenuFocusLeave.test.tsx +181 -0
- package/tests/MenuHighlight.test.tsx +165 -0
- package/tests/MenuHoverKeyboardBugs.test.tsx +252 -0
- package/tests/MenuItemFocus.test.tsx +59 -0
- package/tests/MenuItemPseudoOverride.test.tsx +231 -0
- package/tests/MenuMultiTrigger.test.tsx +101 -0
- package/tests/MenuOverflow.test.tsx +93 -0
- package/tests/MenuStayInFrame.test.tsx +102 -0
- package/tests/MenuSubKeyboardFocus.test.tsx +220 -0
- package/tests/MenuSubLeftSafePolygon.test.tsx +88 -0
- package/tests/MenuSubNestedPosition.test.tsx +48 -0
- package/tests/MenuSubSafePolygon.test.tsx +97 -0
- package/tests/MenuSubStyled.test.tsx +40 -0
- package/tests/MenuTheme.test.tsx +34 -0
- package/tests/MenuUnstyled.test.tsx +56 -0
- package/tests/MultiDriverAnimation.test.tsx +207 -0
- package/tests/NewInputBasic.test.tsx +50 -0
- package/tests/NewInputEvents.test.tsx +55 -0
- package/tests/OnLayout.test.tsx +163 -0
- package/tests/OnLayoutScale.test.tsx +100 -0
- package/tests/OnLayoutStress.test.tsx +304 -0
- package/tests/ParagraphSpanFontInheritance.test.tsx +73 -0
- package/tests/PointerEvents.test.tsx +123 -0
- package/tests/Popover.animated.test.tsx +234 -0
- package/tests/PopoverAndMenuMultiTrigger.test.tsx +184 -0
- package/tests/PopoverAnimatePosition.animated.test.tsx +51 -0
- package/tests/PopoverClickDuringEnter.animated.test.tsx +197 -0
- package/tests/PopoverFocusScope.test.tsx +242 -0
- package/tests/PopoverHoverable.test.tsx +383 -0
- package/tests/PopoverHoverableDisableClick.test.tsx +106 -0
- package/tests/PopoverHoverableRapid.test.tsx +129 -0
- package/tests/PopoverHoverableReposition.test.tsx +111 -0
- package/tests/PopoverHoverableScoped.animated.test.tsx +103 -0
- package/tests/PopoverHoverableStress.test.tsx +169 -0
- package/tests/PopoverInitialPosition.animated.test.tsx +82 -0
- package/tests/PopoverMiddlewareSkipRegression.animated.test.tsx +221 -0
- package/tests/PopoverScoped.test.tsx +128 -0
- package/tests/PopoverScopedPositionGlitch.animated.test.tsx +184 -0
- package/tests/PopoverTriggerIsolation.test.tsx +62 -0
- package/tests/PseudoTransition.animated.test.tsx +319 -0
- package/tests/RawAnimatedValue.test.tsx +147 -0
- package/tests/RemoveScroll.test.tsx +223 -0
- package/tests/RenderProp.test.tsx +293 -0
- package/tests/ScrollViewRef.test.tsx +39 -0
- package/tests/SelectClickHold.test.tsx +147 -0
- package/tests/SelectFocusScope.test.tsx +176 -0
- package/tests/SelectInnerPositioning.test.tsx +82 -0
- package/tests/SelectKeyboardNav.test.tsx +173 -0
- package/tests/SelectPositioning.test.tsx +56 -0
- package/tests/SelectTypeahead.test.tsx +63 -0
- package/tests/Shadows.test.tsx +14 -0
- package/tests/SheetAnimation.animated.test.tsx +413 -0
- package/tests/SheetDrag.animated.test.tsx +223 -0
- package/tests/SheetDragResist.animated.test.tsx +393 -0
- package/tests/SheetOnAnimationComplete.animated.test.tsx +62 -0
- package/tests/SheetScrollLock.animated.test.tsx +287 -0
- package/tests/SheetScrollableDrag.animated.test.tsx +1264 -0
- package/tests/SheetSnapPointsFit.animated.test.tsx +259 -0
- package/tests/ShorthandVariables.test.tsx +44 -0
- package/tests/SpinnerCustomColors.test.tsx +67 -0
- package/tests/StackZIndex.test.tsx +51 -0
- package/tests/StressPagePerf.test.tsx +76 -0
- package/tests/StylePlatform.test.tsx +38 -0
- package/tests/StyleProp.test.tsx +20 -0
- package/tests/StyledAnchor.test.tsx +17 -0
- package/tests/StyledButtonTheme.test.tsx +22 -0
- package/tests/StyledButtonVariantPseudo.test.tsx +20 -0
- package/tests/StyledButtonVariantPseudoMerge.animated.test.tsx +33 -0
- package/tests/StyledCheckboxTheme.test.tsx +16 -0
- package/tests/StyledContextColor.test.tsx +119 -0
- package/tests/StyledContextTokens.test.tsx +56 -0
- package/tests/StyledHOCNamed.test.tsx +16 -0
- package/tests/StyledHtml.test.tsx +161 -0
- package/tests/StyledIconColor.test.tsx +32 -0
- package/tests/StyledInputFocusStyle.test.tsx +19 -0
- package/tests/StyledInputOnFocus.test.tsx +27 -0
- package/tests/StyledMediaQueryMerge.test.tsx +66 -0
- package/tests/StyledRNW.test.tsx +17 -0
- package/tests/StyledStyleableInputOnFocus.test.tsx +27 -0
- package/tests/StyledStyleableInputVariant.test.tsx +22 -0
- package/tests/StyledStyledStyleableInputOnFocus.test.tsx +27 -0
- package/tests/StyledVariantTextColor.test.tsx +24 -0
- package/tests/StyledViewOnFocus.test.tsx +27 -0
- package/tests/TabHoverAnimation.animated.test.tsx +468 -0
- package/tests/TabHoverPositionSmooth.animated.test.tsx +129 -0
- package/tests/TextNestedInheritance.test.tsx +93 -0
- package/tests/ThemeChange.test.tsx +70 -0
- package/tests/ThemeComponentResolution.test.tsx +82 -0
- package/tests/ThemeConditionalName.test.tsx +34 -0
- package/tests/ThemeMediaAnimation.test.tsx +65 -0
- package/tests/ThemeNested.test.tsx +141 -0
- package/tests/ThemeReset.test.tsx +63 -0
- package/tests/ThemeShallow.test.tsx +95 -0
- package/tests/Toast.test.tsx +106 -0
- package/tests/ToggleGroup.test.tsx +61 -0
- package/tests/ToggleGroupActiveProps.test.tsx +38 -0
- package/tests/ToggleGroupXGroup.test.tsx +172 -0
- package/tests/TooltipAnimation.animated.test.tsx +260 -0
- package/tests/TooltipEnterInterrupt.animated.test.tsx +76 -0
- package/tests/TooltipGlobalPattern.animated.test.tsx +208 -0
- package/tests/TooltipGroup.animated.test.tsx +79 -0
- package/tests/TooltipMultiTrigger.test.tsx +116 -0
- package/tests/TooltipPositionJump.animated.test.tsx +229 -0
- package/tests/TooltipPositionJumpNotes.md +219 -0
- package/tests/TooltipRapidSwitch.animated.test.tsx +399 -0
- package/tests/TooltipTriggerInline.test.tsx +65 -0
- package/tests/TransformMediaQueryMerge.test.tsx +104 -0
- package/tests/TransitionEnterExit.animated.test.tsx +311 -0
- package/tests/UseTheme.test.tsx +16 -0
- package/tests/V5ThemeBuilderOutput.test.tsx +164 -0
- package/tests/VariantFontFamily.test.tsx +11 -0
- package/tests/VariantsOrder.test.tsx +53 -0
- package/tests/_debug_position.mjs +52 -0
- package/tests/test-utils.ts +106 -0
- package/tests/utils.tsx +54 -0
- package/tsconfig.json +45 -0
- package/vite-env.d.ts +1 -0
- package/vite.config.ts +14 -0
- 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
|
+
})
|