@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,242 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.describe('Popover Focus Scope', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'PopoverFocusScopeCase', type: 'useCase' })
7
+ })
8
+
9
+ test('traps focus within popover when trapFocus is true', async ({ page }) => {
10
+ await page.waitForLoadState('networkidle')
11
+
12
+ // Open the basic popover
13
+ const trigger = page.getByTestId('basic-popover-trigger')
14
+ await trigger.click()
15
+
16
+ // Wait for popover to be visible
17
+ const popoverContent = page.getByTestId('basic-popover-content')
18
+ await expect(popoverContent).toBeVisible({ timeout: 5000 })
19
+
20
+ // Wait for auto-focus
21
+ await page.waitForTimeout(300)
22
+
23
+ const nameInput = popoverContent.getByTestId('popover-name-input')
24
+ await expect(nameInput).toBeFocused()
25
+
26
+ // Tab through all focusable elements
27
+ await page.keyboard.press('Tab')
28
+ const emailInput = popoverContent.getByTestId('popover-email-input')
29
+ await expect(emailInput).toBeFocused()
30
+
31
+ await page.keyboard.press('Tab')
32
+ const notesTextarea = popoverContent.getByTestId('popover-notes-textarea')
33
+ await expect(notesTextarea).toBeFocused()
34
+
35
+ await page.keyboard.press('Tab')
36
+ const cancelButton = popoverContent.getByTestId('popover-cancel-button')
37
+ await expect(cancelButton).toBeFocused()
38
+
39
+ await page.keyboard.press('Tab')
40
+ const saveButton = popoverContent.getByTestId('popover-save-button')
41
+ await expect(saveButton).toBeFocused()
42
+
43
+ // Tab should wrap back to first input (loop)
44
+ await page.keyboard.press('Tab')
45
+ await expect(nameInput).toBeFocused()
46
+
47
+ // Shift+Tab should go backwards and loop
48
+ await page.keyboard.press('Shift+Tab')
49
+ await expect(saveButton).toBeFocused()
50
+
51
+ // Close popover
52
+ await cancelButton.click()
53
+ await expect(popoverContent).not.toBeVisible()
54
+
55
+ // Focus should return to trigger
56
+ await expect(trigger).toBeFocused()
57
+ })
58
+
59
+ test('does not trap focus when trapFocus is false', async ({ page }) => {
60
+ await page.waitForLoadState('networkidle')
61
+
62
+ // Open the non-trap popover
63
+ const trigger = page.getByTestId('no-trap-popover-trigger')
64
+ await trigger.click()
65
+
66
+ // Wait for popover to be visible
67
+ const popoverContent = page.getByTestId('no-trap-popover-content')
68
+ await expect(popoverContent).toBeVisible({ timeout: 5000 })
69
+
70
+ // Focus on the input
71
+ const input = popoverContent.getByTestId('no-trap-input')
72
+ await input.click()
73
+ await page.waitForTimeout(100)
74
+ await expect(input).toBeFocused({ timeout: 5000 })
75
+
76
+ // Tab to the close button
77
+ await page.keyboard.press('Tab')
78
+ await page.waitForTimeout(100)
79
+
80
+ const closeButton = popoverContent.getByTestId('no-trap-close-button')
81
+ await expect(closeButton).toBeFocused()
82
+
83
+ // Tab again - when trapFocus is false, focus can leave the popover
84
+ // Focus might go to browser chrome or other page elements
85
+ await page.keyboard.press('Tab')
86
+ await page.waitForTimeout(200)
87
+
88
+ // When trapFocus is false, focus is NOT trapped in the popover
89
+ // We can't reliably predict where focus will go as it depends on page structure
90
+ // The important thing is that FocusScope is not preventing the Tab key
91
+
92
+ // Let's verify that trapFocus=false is working by checking that
93
+ // we can focus back on the trigger button (outside the popover)
94
+ await page.getByTestId('no-trap-popover-trigger').focus()
95
+ await expect(page.getByTestId('no-trap-popover-trigger')).toBeFocused()
96
+
97
+ // And we can still focus elements inside the popover
98
+ await input.focus()
99
+ await expect(input).toBeFocused()
100
+
101
+ // The key difference from trapFocus=true is that FocusScope is not
102
+ // handling the Tab key - it's the browser's default behavior.
103
+ // With trapFocus=false and loop=false, FocusScope doesn't interfere.
104
+
105
+ // Verify that the popover has the correct setup
106
+ const focusScopeInfo = await page.evaluate(() => {
107
+ const popover = document.querySelector('[data-testid="no-trap-popover-content"]')
108
+ const focusScope = popover?.closest('[data-focus-scope]') || popover?.parentElement
109
+
110
+ // Check if there are any event handlers that would indicate focus trapping
111
+ const hasKeydownHandler = !!(focusScope as any)?._keydownHandler
112
+
113
+ return {
114
+ popoverFound: !!popover,
115
+ // If FocusScope was trapping, it would have special attributes or handlers
116
+ hasFocusScopeAttributes:
117
+ focusScope?.hasAttribute?.('data-focus-scope-trapped') || false,
118
+ hasKeydownHandler,
119
+ }
120
+ })
121
+
122
+ // FocusScope should not be actively trapping focus
123
+ expect(focusScopeInfo.hasFocusScopeAttributes).toBe(false)
124
+ })
125
+
126
+ test('handles nested popovers focus correctly', async ({ page }) => {
127
+ await page.waitForLoadState('networkidle')
128
+
129
+ // Open parent popover
130
+ const parentTrigger = page.getByTestId('parent-popover-trigger')
131
+ await parentTrigger.click()
132
+
133
+ const parentPopover = page.getByTestId('parent-popover-content')
134
+ await expect(parentPopover).toBeVisible({ timeout: 5000 })
135
+
136
+ await page.waitForTimeout(300)
137
+ const parentInput = parentPopover.getByTestId('parent-popover-input')
138
+ await expect(parentInput).toBeFocused()
139
+
140
+ // Open nested popover
141
+ const nestedTrigger = parentPopover.getByTestId('nested-popover-trigger')
142
+ await nestedTrigger.click()
143
+
144
+ const nestedPopover = page.getByTestId('nested-popover-content')
145
+ await expect(nestedPopover).toBeVisible({ timeout: 5000 })
146
+
147
+ // Focus should be trapped in nested popover
148
+ await page.waitForTimeout(300)
149
+ const nestedInput = nestedPopover.getByTestId('nested-popover-input')
150
+ await expect(nestedInput).toBeFocused()
151
+
152
+ // Tab should stay within nested popover
153
+ await page.keyboard.press('Tab')
154
+ const nestedClose = nestedPopover.getByTestId('nested-popover-close')
155
+ await expect(nestedClose).toBeFocused()
156
+
157
+ await page.keyboard.press('Tab')
158
+ await expect(nestedInput).toBeFocused() // Should loop back
159
+
160
+ // Close nested popover
161
+ await nestedClose.click()
162
+ await expect(nestedPopover).not.toBeVisible()
163
+
164
+ // Focus should return to nested trigger in parent popover
165
+ await expect(nestedTrigger).toBeFocused()
166
+
167
+ // Close parent popover
168
+ const parentClose = parentPopover.getByTestId('parent-popover-close')
169
+ await parentClose.click()
170
+ await expect(parentPopover).not.toBeVisible()
171
+
172
+ // Focus should return to parent trigger
173
+ await expect(parentTrigger).toBeFocused()
174
+ })
175
+
176
+ test('auto-focuses first element on mount', async ({ page }) => {
177
+ await page.waitForLoadState('networkidle')
178
+
179
+ // Check if popover content is already visible (might be open by default)
180
+ let popoverContent = page.locator('[data-testid="basic-popover-content"]')
181
+ const isAlreadyVisible = await popoverContent.isVisible()
182
+
183
+ if (!isAlreadyVisible) {
184
+ // If not visible, click the trigger
185
+ const trigger = page.getByTestId('basic-popover-trigger')
186
+ await expect(trigger).toBeVisible({ timeout: 5000 })
187
+ await trigger.click()
188
+
189
+ // Wait a bit for popover animation
190
+ await page.waitForTimeout(500)
191
+ }
192
+
193
+ await expect(popoverContent).toBeVisible({ timeout: 5000 })
194
+
195
+ // Wait for auto-focus
196
+ await page.waitForTimeout(300)
197
+
198
+ // First input should be focused
199
+ const nameInput = popoverContent.getByTestId('popover-name-input')
200
+ await expect(nameInput).toBeFocused()
201
+ })
202
+
203
+ test('closes on escape key and returns focus', async ({ page }) => {
204
+ await page.waitForLoadState('networkidle')
205
+
206
+ const trigger = page.getByTestId('basic-popover-trigger')
207
+ await trigger.click()
208
+
209
+ const popoverContent = page.getByTestId('basic-popover-content')
210
+ await expect(popoverContent).toBeVisible({ timeout: 5000 })
211
+
212
+ // Focus needs to be inside the popover for escape to work
213
+ const nameInput = popoverContent.getByTestId('popover-name-input')
214
+ await expect(nameInput).toBeFocused({ timeout: 3000 })
215
+
216
+ // Press escape to close
217
+ await page.keyboard.press('Escape')
218
+
219
+ // Wait for popover to close (animation may take time)
220
+ await page.waitForTimeout(600)
221
+ await expect(popoverContent).not.toBeVisible({ timeout: 5000 })
222
+
223
+ // Focus should return to trigger
224
+ await expect(trigger).toBeFocused({ timeout: 5000 })
225
+ })
226
+
227
+ test('closes when clicking outside', async ({ page }) => {
228
+ await page.waitForLoadState('networkidle')
229
+
230
+ const trigger = page.getByTestId('basic-popover-trigger')
231
+ await trigger.click()
232
+
233
+ const popoverContent = page.getByTestId('basic-popover-content')
234
+ await expect(popoverContent).toBeVisible({ timeout: 5000 })
235
+
236
+ // Click outside to close
237
+ await page.click('body', { position: { x: 10, y: 10 } })
238
+
239
+ // Wait for popover to close (Playwright auto-retries until timeout)
240
+ await expect(popoverContent).not.toBeVisible({ timeout: 5000 })
241
+ })
242
+ })
@@ -0,0 +1,383 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { getBoundingRect, setupPage } from './test-utils'
3
+
4
+ // helper: get opacity of element by id
5
+ async function getOpacity(page: any, id: string) {
6
+ return page.evaluate(
7
+ (sel: string) => parseFloat(getComputedStyle(document.getElementById(sel)!).opacity),
8
+ id
9
+ )
10
+ }
11
+
12
+ // Bug 1: delay should apply to both enter AND exit
13
+ test.describe('Popover hoverable delay', () => {
14
+ test.beforeEach(async ({ page }) => {
15
+ await setupPage(page, { name: 'PopoverHoverableDelayCase', type: 'useCase' })
16
+ await page.waitForLoadState('networkidle')
17
+ })
18
+
19
+ test('delay applies to enter: popover should not open before delay elapses', async ({
20
+ page,
21
+ }) => {
22
+ const trigger = page.locator('#delay-trigger')
23
+ const content = page.locator('#delay-content')
24
+
25
+ await expect(content).not.toBeVisible()
26
+
27
+ // hover over trigger
28
+ await trigger.hover()
29
+
30
+ // check immediately after hover (should NOT be open yet - delay is 400ms)
31
+ await expect(content).not.toBeVisible()
32
+
33
+ // wait less than delay - still should not open
34
+ await page.waitForTimeout(200)
35
+ await expect(content).not.toBeVisible()
36
+
37
+ // wait full delay time - should now be open
38
+ await page.waitForTimeout(300)
39
+ await expect(content).toBeVisible({ timeout: 3000 })
40
+ })
41
+
42
+ test('delay applies to exit: popover should not close before delay elapses', async ({
43
+ page,
44
+ }) => {
45
+ const trigger = page.locator('#delay-trigger')
46
+ const content = page.locator('#delay-content')
47
+
48
+ // hover to open (wait for delay)
49
+ await trigger.hover()
50
+ await page.waitForTimeout(800)
51
+ await expect(content).toBeVisible({ timeout: 3000 })
52
+
53
+ // move mouse far away (outside safe zone)
54
+ await page.mouse.move(10, 10)
55
+
56
+ // should still be visible right after (delay is 400ms)
57
+ await expect(content).toBeVisible()
58
+
59
+ // wait less than delay - still should be visible
60
+ await page.waitForTimeout(200)
61
+ await expect(content).toBeVisible()
62
+
63
+ // wait for delay to elapse + animation - should now be closing
64
+ await page.waitForTimeout(600)
65
+ await expect(content).not.toBeVisible({ timeout: 3000 })
66
+ })
67
+ })
68
+
69
+ // Bug 2: restMs should only apply to enter (not exit)
70
+ test.describe('Popover hoverable restMs', () => {
71
+ test.beforeEach(async ({ page }) => {
72
+ await setupPage(page, { name: 'PopoverHoverableRestMsCase', type: 'useCase' })
73
+ await page.waitForLoadState('networkidle')
74
+ })
75
+
76
+ test('restMs delays enter: popover should open after mouse rests', async ({ page }) => {
77
+ const trigger = page.locator('#restms-trigger')
78
+ const content = page.locator('#restms-content')
79
+
80
+ await expect(content).not.toBeVisible()
81
+
82
+ // hover over trigger
83
+ await trigger.hover()
84
+
85
+ // should not open immediately
86
+ await expect(content).not.toBeVisible()
87
+
88
+ // wait for restMs to elapse
89
+ await page.waitForTimeout(500)
90
+ await expect(content).toBeVisible({ timeout: 2000 })
91
+ })
92
+
93
+ test('exit without restMs: popover should close quickly after mouse leaves', async ({
94
+ page,
95
+ }) => {
96
+ const trigger = page.locator('#restms-trigger')
97
+ const content = page.locator('#restms-content')
98
+
99
+ // open it
100
+ await trigger.hover()
101
+ await page.waitForTimeout(500)
102
+ await expect(content).toBeVisible({ timeout: 2000 })
103
+
104
+ // move mouse away - with restMs but no delay, exit should be handled by safePolygon (quick)
105
+ await page.mouse.move(10, 10)
106
+
107
+ // exit should happen quickly (no restMs delay on exit, only animation duration ~200ms)
108
+ await expect(content).not.toBeVisible({ timeout: 1000 })
109
+ })
110
+ })
111
+
112
+ // Bug 3: exit animation should play when hoverable closes popover
113
+ test.describe('Popover hoverable exit animation', () => {
114
+ test.beforeEach(async ({ page }) => {
115
+ await setupPage(page, { name: 'PopoverHoverableExitAnimCase', type: 'useCase' })
116
+ await page.waitForLoadState('networkidle')
117
+ })
118
+
119
+ test('exit animation plays: content should still be visible briefly after mouse leaves', async ({
120
+ page,
121
+ }) => {
122
+ const trigger = page.locator('#exitanim-trigger')
123
+ const content = page.locator('#exitanim-content')
124
+
125
+ // hover to open
126
+ await trigger.hover()
127
+ await expect(content).toBeVisible({ timeout: 3000 })
128
+
129
+ // wait for enter animation to complete (500ms transition + buffer)
130
+ await page.waitForTimeout(700)
131
+
132
+ // confirm fully visible now
133
+ const opacityBefore = await content.evaluate((el) =>
134
+ parseFloat(getComputedStyle(el).opacity)
135
+ )
136
+ expect(opacityBefore).toBeGreaterThan(0.9)
137
+
138
+ // move mouse far away (outside safe zone) to trigger close
139
+ await page.mouse.move(10, 10)
140
+
141
+ // immediately after leaving - content should still be visible (animation in progress)
142
+ // the transition is 500ms so it should still be partially visible
143
+ await page.waitForTimeout(50)
144
+
145
+ const opacityDuring = await content.evaluate((el) =>
146
+ parseFloat(getComputedStyle(el).opacity)
147
+ )
148
+ // should be animating - opacity should be > 0 (not instantly gone to 0)
149
+ expect(opacityDuring).toBeGreaterThan(0)
150
+
151
+ // wait for animation to complete (500ms + buffer)
152
+ await page.waitForTimeout(600)
153
+ await expect(content).not.toBeVisible({ timeout: 2000 })
154
+ })
155
+ })
156
+
157
+ // Bug: safePolygon should allow hovering from trigger to content through the gap
158
+ test.describe('Popover hoverable safePolygon', () => {
159
+ test.beforeEach(async ({ page }) => {
160
+ await setupPage(page, { name: 'PopoverHoverableSafePolygonCase', type: 'useCase' })
161
+ await page.waitForLoadState('networkidle')
162
+ })
163
+
164
+ test('can hover from trigger to content through the gap', async ({ page }) => {
165
+ const trigger = page.locator('#safepoly-trigger')
166
+ const content = page.locator('#safepoly-content')
167
+
168
+ // hover trigger to open (restMs is 260ms)
169
+ await trigger.hover()
170
+ await page.waitForTimeout(350)
171
+ await expect(content).toBeVisible({ timeout: 200 })
172
+ // wait for enter animation to settle
173
+ await page.waitForTimeout(100)
174
+
175
+ // slowly move from trigger down to content (through the 80px offset gap)
176
+ const triggerBox = await trigger.boundingBox()
177
+ const contentBox = await content.boundingBox()
178
+ if (triggerBox && contentBox) {
179
+ const startY = triggerBox.y + triggerBox.height
180
+ const endY = contentBox.y + 10
181
+ const x = triggerBox.x + triggerBox.width / 2
182
+ for (let y = startY; y <= endY; y += 4) {
183
+ await page.mouse.move(x, y)
184
+ await page.waitForTimeout(10)
185
+ }
186
+ }
187
+
188
+ // wait well past any grace period or animation - if the popover closed
189
+ // during the gap crossing, it won't be visible after this
190
+ await page.waitForTimeout(500)
191
+ await expect(content).toBeVisible({ timeout: 1000 })
192
+
193
+ // verify content is fully opaque (not mid-exit-animation)
194
+ const opacity = await content.evaluate((el) =>
195
+ parseFloat(getComputedStyle(el).opacity)
196
+ )
197
+ expect(opacity).toBeGreaterThan(0.9)
198
+ })
199
+
200
+ test('restMs applies on re-hover (not just first hover)', async ({ page }) => {
201
+ const trigger = page.locator('#safepoly-trigger')
202
+ const content = page.locator('#safepoly-content')
203
+
204
+ // first hover: should respect restMs (260ms)
205
+ await trigger.hover()
206
+ // should NOT be open before restMs elapses
207
+ await page.waitForTimeout(100)
208
+ await expect(content).not.toBeVisible()
209
+ // wait for restMs
210
+ await page.waitForTimeout(250)
211
+ await expect(content).toBeVisible({ timeout: 2000 })
212
+
213
+ // move far away to close
214
+ await page.mouse.move(10, 10)
215
+ await page.waitForTimeout(500)
216
+ await expect(content).not.toBeVisible({ timeout: 2000 })
217
+
218
+ // re-hover: restMs should still apply (not open instantly)
219
+ await trigger.hover()
220
+ // check after 100ms - should NOT be open yet (restMs is 260ms)
221
+ await page.waitForTimeout(100)
222
+ await expect(content).not.toBeVisible()
223
+
224
+ // wait for restMs to elapse
225
+ await page.waitForTimeout(250)
226
+ await expect(content).toBeVisible({ timeout: 2000 })
227
+ })
228
+ })
229
+
230
+ // Bug: scoped multi-trigger hoverable - mimics WebsiteHeader.tsx pattern
231
+ // uses CSS driver since animatePosition needs a driver that supports classNames
232
+ test.describe('Popover hoverable scoped multi-trigger', () => {
233
+ test.beforeEach(async ({ page }) => {
234
+ await setupPage(page, {
235
+ name: 'PopoverHoverableScopedCase',
236
+ type: 'useCase',
237
+ searchParams: { animationDriver: 'css' },
238
+ })
239
+ await page.waitForLoadState('networkidle')
240
+ })
241
+
242
+ test('scoped: delay applies to enter - should not open immediately', async ({
243
+ page,
244
+ }) => {
245
+ const trigger = page.locator('#nav-trigger-about')
246
+ const content = page.locator('#nav-content')
247
+
248
+ await trigger.hover()
249
+
250
+ // should NOT be open right away (300ms delay)
251
+ await expect(content).not.toBeVisible()
252
+
253
+ // wait for delay
254
+ await page.waitForTimeout(450)
255
+ await expect(content).toBeVisible({ timeout: 2000 })
256
+ })
257
+
258
+ test('scoped: exit animation plays when hovering away', async ({ page }) => {
259
+ const trigger = page.locator('#nav-trigger-about')
260
+ const content = page.locator('#nav-content')
261
+
262
+ // open it
263
+ await trigger.hover()
264
+ await page.waitForTimeout(450)
265
+ await expect(content).toBeVisible({ timeout: 2000 })
266
+
267
+ // wait for enter animation to finish (500ms transition)
268
+ await page.waitForTimeout(700)
269
+
270
+ // verify content is correctly positioned below trigger (not stuck at y=0)
271
+ const triggerRect = await getBoundingRect(page, '#nav-trigger-about')
272
+ const contentRect = await getBoundingRect(page, '#nav-content')
273
+
274
+ expect(contentRect!.y).toBeGreaterThan(triggerRect!.y)
275
+
276
+ const opacityBefore = await getOpacity(page, 'nav-content')
277
+ expect(opacityBefore).toBeGreaterThan(0.9)
278
+
279
+ // move far away to trigger close
280
+ await page.mouse.move(10, 10)
281
+
282
+ // check opacity shortly after - should still be > 0 (animating out with 500ms transition)
283
+ await page.waitForTimeout(50)
284
+ const opacityDuring = await getOpacity(page, 'nav-content')
285
+ expect(opacityDuring).toBeGreaterThan(0)
286
+
287
+ // wait for animation to complete (500ms + buffer)
288
+ await page.waitForTimeout(600)
289
+ await expect(content).not.toBeVisible({ timeout: 2000 })
290
+ })
291
+
292
+ test('scoped: switching between triggers keeps popover open', async ({ page }) => {
293
+ const aboutTrigger = page.locator('#nav-trigger-about')
294
+ const blogTrigger = page.locator('#nav-trigger-blog')
295
+ const content = page.locator('#nav-content')
296
+
297
+ // open about
298
+ await aboutTrigger.hover()
299
+ await page.waitForTimeout(450)
300
+ await expect(content).toBeVisible({ timeout: 2000 })
301
+
302
+ // switch to blog trigger - popover should stay open
303
+ await blogTrigger.hover()
304
+ await page.waitForTimeout(100)
305
+ await expect(content).toBeVisible()
306
+ })
307
+
308
+ test('scoped: content → different trigger repositions popover', async ({ page }) => {
309
+ const aboutTrigger = page.locator('#nav-trigger-about')
310
+ const contactTrigger = page.locator('#nav-trigger-contact')
311
+ const content = page.locator('#nav-content')
312
+
313
+ // open at about
314
+ await aboutTrigger.hover()
315
+ await page.waitForTimeout(450)
316
+ await expect(content).toBeVisible({ timeout: 2000 })
317
+ await page.waitForTimeout(300)
318
+
319
+ const contentRectAtAbout = await getBoundingRect(page, '#nav-content')
320
+
321
+ // move into the popover content
322
+ await content.hover()
323
+ await page.waitForTimeout(100)
324
+ await expect(content).toBeVisible()
325
+
326
+ // move from content to "contact" trigger (rightmost)
327
+ await contactTrigger.hover()
328
+ await page.waitForTimeout(600)
329
+
330
+ // popover should still be visible and repositioned
331
+ await expect(content).toBeVisible()
332
+ const contentRectAtContact = await getBoundingRect(page, '#nav-content')
333
+
334
+ // contact is to the right of about, so content x should shift right
335
+ expect(contentRectAtContact!.x).toBeGreaterThan(contentRectAtAbout!.x + 20)
336
+ })
337
+
338
+ test('scoped: content → gap → different trigger repositions popover', async ({
339
+ page,
340
+ }) => {
341
+ const aboutTrigger = page.locator('#nav-trigger-about')
342
+ const contactTrigger = page.locator('#nav-trigger-contact')
343
+ const content = page.locator('#nav-content')
344
+
345
+ // open at about
346
+ await aboutTrigger.hover()
347
+ await page.waitForTimeout(450)
348
+ await expect(content).toBeVisible({ timeout: 2000 })
349
+ await page.waitForTimeout(300)
350
+
351
+ const contentRectAtAbout = await getBoundingRect(page, '#nav-content')
352
+
353
+ // move into the popover content
354
+ await content.hover()
355
+ await page.waitForTimeout(100)
356
+ await expect(content).toBeVisible()
357
+
358
+ // move mouse to empty space BELOW the content (not to a trigger)
359
+ // this simulates the recording where mouse exits content into the page gap
360
+ const contentBox = await content.boundingBox()
361
+ if (contentBox) {
362
+ await page.mouse.move(
363
+ contentBox.x + contentBox.width / 2,
364
+ contentBox.y + contentBox.height + 50
365
+ )
366
+ }
367
+
368
+ // wait a bit for the popover to close (safePolygon should eventually close it)
369
+ await page.waitForTimeout(300)
370
+
371
+ // now move to a different trigger (contact)
372
+ await contactTrigger.hover()
373
+ // wait for restMs (300ms) + buffer
374
+ await page.waitForTimeout(500)
375
+
376
+ // popover should be visible and repositioned at the new trigger
377
+ await expect(content).toBeVisible({ timeout: 2000 })
378
+ const contentRectAtContact = await getBoundingRect(page, '#nav-content')
379
+
380
+ // contact is to the right of about, so content x should shift right
381
+ expect(contentRectAtContact!.x).toBeGreaterThan(contentRectAtAbout!.x + 20)
382
+ })
383
+ })