@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,293 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.describe('Render Prop', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'RenderPropCase', type: 'useCase' })
7
+ })
8
+
9
+ test.describe('styled() render prop', () => {
10
+ test('renders button element when render="button"', async ({ page }) => {
11
+ const element = page.getByTestId('styled-button')
12
+ await expect(element).toBeVisible()
13
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
14
+ expect(tagName).toBe('button')
15
+ })
16
+
17
+ test('renders anchor element when render="a"', async ({ page }) => {
18
+ const element = page.getByTestId('styled-anchor')
19
+ await expect(element).toBeVisible()
20
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
21
+ expect(tagName).toBe('a')
22
+ // Check href attribute is passed through
23
+ const href = await element.getAttribute('href')
24
+ expect(href).toBe('#')
25
+ })
26
+ })
27
+
28
+ test.describe('semantic elements', () => {
29
+ test('renders nav element', async ({ page }) => {
30
+ const element = page.getByTestId('styled-nav')
31
+ await expect(element).toBeVisible()
32
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
33
+ expect(tagName).toBe('nav')
34
+ })
35
+
36
+ test('renders main element', async ({ page }) => {
37
+ const element = page.getByTestId('styled-main')
38
+ await expect(element).toBeVisible()
39
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
40
+ expect(tagName).toBe('main')
41
+ })
42
+
43
+ test('renders section element', async ({ page }) => {
44
+ const element = page.getByTestId('styled-section')
45
+ await expect(element).toBeVisible()
46
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
47
+ expect(tagName).toBe('section')
48
+ })
49
+
50
+ test('renders article element', async ({ page }) => {
51
+ const element = page.getByTestId('styled-article')
52
+ await expect(element).toBeVisible()
53
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
54
+ expect(tagName).toBe('article')
55
+ })
56
+
57
+ test('renders footer element', async ({ page }) => {
58
+ const element = page.getByTestId('styled-footer')
59
+ await expect(element).toBeVisible()
60
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
61
+ expect(tagName).toBe('footer')
62
+ })
63
+ })
64
+
65
+ test.describe('form elements', () => {
66
+ test('renders form element', async ({ page }) => {
67
+ const element = page.getByTestId('styled-form')
68
+ await expect(element).toBeVisible()
69
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
70
+ expect(tagName).toBe('form')
71
+ })
72
+
73
+ test('renders fieldset element', async ({ page }) => {
74
+ const element = page.getByTestId('styled-fieldset')
75
+ await expect(element).toBeVisible()
76
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
77
+ expect(tagName).toBe('fieldset')
78
+ })
79
+
80
+ test('renders label element with htmlFor', async ({ page }) => {
81
+ const element = page.getByTestId('styled-label')
82
+ await expect(element).toBeVisible()
83
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
84
+ expect(tagName).toBe('label')
85
+ const htmlFor = await element.getAttribute('for')
86
+ expect(htmlFor).toBe('test-input')
87
+ })
88
+ })
89
+
90
+ test.describe('heading elements', () => {
91
+ test('renders h1 element', async ({ page }) => {
92
+ const element = page.getByTestId('styled-h1')
93
+ await expect(element).toBeVisible()
94
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
95
+ expect(tagName).toBe('h1')
96
+ })
97
+
98
+ test('renders h2 element', async ({ page }) => {
99
+ const element = page.getByTestId('styled-h2')
100
+ await expect(element).toBeVisible()
101
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
102
+ expect(tagName).toBe('h2')
103
+ })
104
+ })
105
+
106
+ test.describe('list elements', () => {
107
+ test('renders ul element', async ({ page }) => {
108
+ const element = page.getByTestId('styled-ul')
109
+ await expect(element).toBeVisible()
110
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
111
+ expect(tagName).toBe('ul')
112
+ })
113
+
114
+ test('renders li elements', async ({ page }) => {
115
+ const li1 = page.getByTestId('styled-li-1')
116
+ const li2 = page.getByTestId('styled-li-2')
117
+ await expect(li1).toBeVisible()
118
+ await expect(li2).toBeVisible()
119
+ const tagName1 = await li1.evaluate((el) => el.tagName.toLowerCase())
120
+ const tagName2 = await li2.evaluate((el) => el.tagName.toLowerCase())
121
+ expect(tagName1).toBe('li')
122
+ expect(tagName2).toBe('li')
123
+ })
124
+ })
125
+
126
+ test.describe('runtime render prop override', () => {
127
+ test('overrides to button at runtime', async ({ page }) => {
128
+ const element = page.getByTestId('runtime-button')
129
+ await expect(element).toBeVisible()
130
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
131
+ expect(tagName).toBe('button')
132
+ })
133
+
134
+ test('overrides to anchor at runtime', async ({ page }) => {
135
+ const element = page.getByTestId('runtime-anchor')
136
+ await expect(element).toBeVisible()
137
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
138
+ expect(tagName).toBe('a')
139
+ })
140
+ })
141
+
142
+ test.describe('Stack with render prop', () => {
143
+ test('Stack renders as section', async ({ page }) => {
144
+ const element = page.getByTestId('stack-as-section')
145
+ await expect(element).toBeVisible()
146
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
147
+ expect(tagName).toBe('section')
148
+ })
149
+
150
+ test('Stack renders as aside', async ({ page }) => {
151
+ const element = page.getByTestId('stack-as-aside')
152
+ await expect(element).toBeVisible()
153
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
154
+ expect(tagName).toBe('aside')
155
+ })
156
+ })
157
+
158
+ test.describe('Text with render prop', () => {
159
+ test('Text renders as span', async ({ page }) => {
160
+ const element = page.getByTestId('text-as-span')
161
+ await expect(element).toBeVisible()
162
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
163
+ expect(tagName).toBe('span')
164
+ })
165
+
166
+ test('Text renders as strong', async ({ page }) => {
167
+ const element = page.getByTestId('text-as-strong')
168
+ await expect(element).toBeVisible()
169
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
170
+ expect(tagName).toBe('strong')
171
+ })
172
+
173
+ test('Text renders as em', async ({ page }) => {
174
+ const element = page.getByTestId('text-as-em')
175
+ await expect(element).toBeVisible()
176
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
177
+ expect(tagName).toBe('em')
178
+ })
179
+ })
180
+
181
+ test.describe('styles are applied correctly', () => {
182
+ test('styled button has correct styles', async ({ page }) => {
183
+ const element = page.getByTestId('styled-button')
184
+ await expect(element).toBeVisible()
185
+
186
+ // Check cursor style
187
+ const cursor = await element.evaluate((el) => window.getComputedStyle(el).cursor)
188
+ expect(cursor).toBe('pointer')
189
+ })
190
+
191
+ test('styled nav has correct background', async ({ page }) => {
192
+ const element = page.getByTestId('styled-nav')
193
+ await expect(element).toBeVisible()
194
+
195
+ // Just verify the element has some background set
196
+ const bg = await element.evaluate(
197
+ (el) => window.getComputedStyle(el).backgroundColor
198
+ )
199
+ expect(bg).toBeTruthy()
200
+ expect(bg).not.toBe('rgba(0, 0, 0, 0)')
201
+ })
202
+ })
203
+
204
+ test.describe('JSX element render prop', () => {
205
+ test('renders JSX element with merged props', async ({ page }) => {
206
+ const element = page.getByTestId('jsx-element-render')
207
+ await expect(element).toBeVisible()
208
+
209
+ // Should render as anchor (from JSX element)
210
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
211
+ expect(tagName).toBe('a')
212
+
213
+ // Should have href from JSX element
214
+ const href = await element.getAttribute('href')
215
+ expect(href).toBe('/test-link')
216
+
217
+ // Should have custom data attribute from JSX element
218
+ const jsxAttr = await element.getAttribute('data-jsx-element')
219
+ expect(jsxAttr).toBe('true')
220
+ })
221
+
222
+ test('renders JSX button with type attribute', async ({ page }) => {
223
+ const element = page.getByTestId('jsx-element-button')
224
+ await expect(element).toBeVisible()
225
+
226
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
227
+ expect(tagName).toBe('button')
228
+
229
+ const type = await element.getAttribute('type')
230
+ expect(type).toBe('submit')
231
+
232
+ const jsxAttr = await element.getAttribute('data-jsx-button')
233
+ expect(jsxAttr).toBe('true')
234
+ })
235
+
236
+ test('JSX element has styles from Stack', async ({ page }) => {
237
+ const element = page.getByTestId('jsx-element-render')
238
+ await expect(element).toBeVisible()
239
+
240
+ // Should have padding and background from Stack props
241
+ const bg = await element.evaluate(
242
+ (el) => window.getComputedStyle(el).backgroundColor
243
+ )
244
+ expect(bg).toBeTruthy()
245
+ expect(bg).not.toBe('rgba(0, 0, 0, 0)')
246
+ })
247
+ })
248
+
249
+ test.describe('function render prop', () => {
250
+ test('renders with custom component via function', async ({ page }) => {
251
+ const element = page.getByTestId('function-render')
252
+ await expect(element).toBeVisible()
253
+
254
+ // Should render as button (from CustomButton)
255
+ const tagName = await element.evaluate((el) => el.tagName.toLowerCase())
256
+ expect(tagName).toBe('button')
257
+
258
+ // Should have custom data attribute from CustomButton
259
+ const customAttr = await element.getAttribute('data-custom-button')
260
+ expect(customAttr).toBe('true')
261
+ })
262
+
263
+ test('function render receives state object', async ({ page }) => {
264
+ const element = page.getByTestId('function-render-with-state')
265
+ await expect(element).toBeVisible()
266
+
267
+ // Check that state object is passed - initial state should be false
268
+ // Note: CSS driver doesn't track hover/press state - it uses CSS :hover
269
+ // The state object is still passed but hover/press may not update with CSS driver
270
+ const hoverAttr = await element.getAttribute('data-hover')
271
+ const pressAttr = await element.getAttribute('data-press')
272
+
273
+ // Should have the data attributes (state is passed to render function)
274
+ expect(hoverAttr).toBeDefined()
275
+ expect(pressAttr).toBeDefined()
276
+
277
+ // Initial values should be 'false'
278
+ expect(hoverAttr).toBe('false')
279
+ expect(pressAttr).toBe('false')
280
+ })
281
+
282
+ test('function render has styles from Stack', async ({ page }) => {
283
+ const element = page.getByTestId('function-render')
284
+ await expect(element).toBeVisible()
285
+
286
+ const bg = await element.evaluate(
287
+ (el) => window.getComputedStyle(el).backgroundColor
288
+ )
289
+ expect(bg).toBeTruthy()
290
+ expect(bg).not.toBe('rgba(0, 0, 0, 0)')
291
+ })
292
+ })
293
+ })
@@ -0,0 +1,39 @@
1
+ import { expect, test } from '@playwright/test'
2
+
3
+ import { setupPage } from './test-utils'
4
+
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'ScrollViewRefCase', type: 'useCase' })
7
+ })
8
+
9
+ test(`ScrollView ref has scrollTo method`, async ({ page }) => {
10
+ const status = page.locator('#status')
11
+ await expect(status).toHaveText('ready')
12
+
13
+ await page.click('#scroll-to-btn')
14
+ await expect(status).toHaveText('scrolled-to-200')
15
+
16
+ // verify the scroll position changed
17
+ const scrollTop = await page.locator('#test-scrollview').evaluate((el) => el.scrollTop)
18
+ expect(scrollTop).toBe(200)
19
+ })
20
+
21
+ test(`ScrollView ref has scrollToEnd method`, async ({ page }) => {
22
+ const status = page.locator('#status')
23
+ await expect(status).toHaveText('ready')
24
+
25
+ await page.click('#scroll-to-end-btn')
26
+ await expect(status).toHaveText('scrolled-to-end')
27
+
28
+ // verify it scrolled to the end (scrollTop should be > 0)
29
+ const scrollTop = await page.locator('#test-scrollview').evaluate((el) => el.scrollTop)
30
+ expect(scrollTop).toBeGreaterThan(0)
31
+ })
32
+
33
+ test(`ScrollView ref has getScrollableNode method`, async ({ page }) => {
34
+ const status = page.locator('#status')
35
+ await expect(status).toHaveText('ready')
36
+
37
+ await page.click('#get-node-btn')
38
+ await expect(status).toHaveText('got-scrollable-node')
39
+ })
@@ -0,0 +1,147 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ /**
5
+ * Tests for Select click-and-hold-to-select behavior.
6
+ *
7
+ * This behavior allows users to:
8
+ * 1. Press and hold on the select trigger
9
+ * 2. Drag to an item while holding
10
+ * 3. Release to select that item
11
+ *
12
+ * This is a common native select pattern that was broken by Safari fixes.
13
+ */
14
+ test.describe('Select Click and Hold', () => {
15
+ test.beforeEach(async ({ page }) => {
16
+ await setupPage(page, { name: 'SelectFocusScopeCase', type: 'useCase' })
17
+ })
18
+
19
+ test('click-and-hold-to-select works', async ({ page }) => {
20
+ const trigger = page.getByTestId('basic-select-trigger')
21
+ const triggerBox = await trigger.boundingBox()
22
+ expect(triggerBox).toBeTruthy()
23
+
24
+ // Get position of banana item (need to open first to measure)
25
+ await trigger.click()
26
+ await page.waitForTimeout(400) // Wait for menu to open and 300ms guard to pass
27
+
28
+ const bananaItem = page.getByTestId('select-banana')
29
+ await expect(bananaItem).toBeVisible()
30
+ const bananaBox = await bananaItem.boundingBox()
31
+ expect(bananaBox).toBeTruthy()
32
+
33
+ // Close the menu
34
+ await page.keyboard.press('Escape')
35
+ await page.waitForTimeout(200)
36
+
37
+ // Now perform click-and-hold-to-select:
38
+ // 1. Mouse down on trigger
39
+ // 2. Wait for menu to open (> 300ms to pass guard)
40
+ // 3. Move to banana item
41
+ // 4. Release to select
42
+
43
+ await page.mouse.move(
44
+ triggerBox!.x + triggerBox!.width / 2,
45
+ triggerBox!.y + triggerBox!.height / 2
46
+ )
47
+ await page.mouse.down()
48
+
49
+ // Wait for menu to open and the 300ms mouseUp guard to pass
50
+ await page.waitForTimeout(400)
51
+
52
+ // Menu should be open
53
+ await expect(bananaItem).toBeVisible()
54
+
55
+ // Move to banana item
56
+ await page.mouse.move(
57
+ bananaBox!.x + bananaBox!.width / 2,
58
+ bananaBox!.y + bananaBox!.height / 2
59
+ )
60
+ await page.waitForTimeout(50)
61
+
62
+ // Release to select
63
+ await page.mouse.up()
64
+ await page.waitForTimeout(200)
65
+
66
+ // Menu should close and banana should be selected
67
+ const selectViewport = page.getByTestId('basic-select-viewport')
68
+ await expect(selectViewport).not.toBeVisible()
69
+
70
+ // Trigger should show "Banana"
71
+ const triggerText = await trigger.textContent()
72
+ expect(triggerText).toContain('Banana')
73
+ })
74
+
75
+ test('quick click does not select item under cursor (Safari fix)', async ({ page }) => {
76
+ // This tests that the Safari fix still works - a quick click-release
77
+ // should NOT select an item even if the cursor happens to be over one
78
+
79
+ const trigger = page.getByTestId('basic-select-trigger')
80
+ const triggerBox = await trigger.boundingBox()
81
+ expect(triggerBox).toBeTruthy()
82
+
83
+ // Quick click to open
84
+ await trigger.click()
85
+
86
+ // Wait a moment for menu to open but NOT long enough for the guard to pass
87
+ await page.waitForTimeout(100)
88
+
89
+ const selectViewport = page.getByTestId('basic-select-viewport')
90
+ await expect(selectViewport).toBeVisible()
91
+
92
+ // The menu should be open and nothing should be selected yet
93
+ // (The initial click's mouseUp is blocked)
94
+ const triggerText = await trigger.textContent()
95
+ expect(triggerText).not.toContain('Apple')
96
+ expect(triggerText).not.toContain('Banana')
97
+
98
+ // Close menu
99
+ await page.keyboard.press('Escape')
100
+ })
101
+
102
+ test('click-and-release-too-fast does not select', async ({ page }) => {
103
+ // If user clicks and releases before the 300ms guard passes,
104
+ // nothing should be selected even if mouse is over an item
105
+
106
+ const trigger = page.getByTestId('basic-select-trigger')
107
+ const triggerBox = await trigger.boundingBox()
108
+ expect(triggerBox).toBeTruthy()
109
+
110
+ // First, open and get item positions
111
+ await trigger.click()
112
+ await page.waitForTimeout(400)
113
+
114
+ const appleItem = page.getByTestId('select-apple')
115
+ await expect(appleItem).toBeVisible()
116
+ const appleBox = await appleItem.boundingBox()
117
+ expect(appleBox).toBeTruthy()
118
+
119
+ // Close
120
+ await page.keyboard.press('Escape')
121
+ await page.waitForTimeout(200)
122
+
123
+ // Now do a fast click-move-release (under 300ms)
124
+ await page.mouse.move(
125
+ triggerBox!.x + triggerBox!.width / 2,
126
+ triggerBox!.y + triggerBox!.height / 2
127
+ )
128
+ await page.mouse.down()
129
+
130
+ // Wait just a tiny bit - not enough for the guard
131
+ await page.waitForTimeout(50)
132
+
133
+ // Move toward where apple would be and release quickly
134
+ await page.mouse.move(
135
+ appleBox!.x + appleBox!.width / 2,
136
+ appleBox!.y + appleBox!.height / 2
137
+ )
138
+ await page.mouse.up()
139
+
140
+ await page.waitForTimeout(200)
141
+
142
+ // The menu might still be open (that's fine) but nothing should be selected
143
+ const triggerText = await trigger.textContent()
144
+ expect(triggerText).not.toContain('Apple')
145
+ expect(triggerText).not.toContain('Banana')
146
+ })
147
+ })
@@ -0,0 +1,176 @@
1
+ import { expect, test } from '@playwright/test'
2
+ import { setupPage } from './test-utils'
3
+
4
+ test.describe('Select Focus Scope', () => {
5
+ test.beforeEach(async ({ page }) => {
6
+ await setupPage(page, { name: 'SelectFocusScopeCase', type: 'useCase' })
7
+ })
8
+
9
+ test('traps focus within select dropdown when open', async ({ page }) => {
10
+ await page.waitForLoadState('networkidle')
11
+
12
+ // open the basic select
13
+ const trigger = page.getByTestId('basic-select-trigger')
14
+ await trigger.click()
15
+
16
+ // wait for select viewport to be visible
17
+ const selectViewport = page.getByTestId('basic-select-viewport')
18
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
19
+
20
+ // wait for focus trap to settle (rAF-based)
21
+ await page.waitForTimeout(50)
22
+
23
+ // first item should be focused
24
+ const firstItem = page.getByTestId('select-apple')
25
+ await expect(firstItem).toBeFocused()
26
+
27
+ // arrow down to second item
28
+ await page.keyboard.press('ArrowDown')
29
+ await page.waitForTimeout(50)
30
+
31
+ const secondItem = page.getByTestId('select-banana')
32
+ await expect(secondItem).toBeFocused()
33
+
34
+ // enter selects the item and closes the select
35
+ await page.keyboard.press('Enter')
36
+ await page.waitForTimeout(100)
37
+
38
+ // select should be closed after enter
39
+ await expect(selectViewport).not.toBeVisible()
40
+
41
+ // focus should return to the trigger that opened it
42
+ await expect(trigger).toBeFocused()
43
+ })
44
+
45
+ test('allows selection with Enter key', async ({ page }) => {
46
+ await page.waitForLoadState('networkidle')
47
+
48
+ const trigger = page.getByTestId('basic-select-trigger')
49
+ await trigger.click()
50
+
51
+ const selectViewport = page.getByTestId('basic-select-viewport')
52
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
53
+ await page.waitForTimeout(300)
54
+
55
+ // apple is focused initially, navigate to banana
56
+ await page.keyboard.press('ArrowDown')
57
+ await page.waitForTimeout(100)
58
+
59
+ // Select with Enter
60
+ await page.keyboard.press('Enter')
61
+ await page.waitForTimeout(200)
62
+
63
+ // Select should close
64
+ await expect(selectViewport).not.toBeVisible()
65
+
66
+ // Trigger should show selected value
67
+ const triggerText = await trigger.textContent()
68
+ expect(triggerText).toContain('Banana')
69
+ })
70
+
71
+ test('allows selection with click', async ({ page }) => {
72
+ await page.waitForLoadState('networkidle')
73
+
74
+ const trigger = page.getByTestId('custom-select-trigger')
75
+ await trigger.click()
76
+
77
+ // Wait for any select viewport to be visible
78
+ const selectViewport = page.getByRole('listbox').first()
79
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
80
+
81
+ // Click on green option
82
+ const greenOption = page.getByTestId('select-green')
83
+ await greenOption.click()
84
+
85
+ // Select should close
86
+ await expect(selectViewport).not.toBeVisible()
87
+
88
+ // Trigger should show selected value
89
+ const triggerText = await trigger.textContent()
90
+ expect(triggerText).toContain('Green')
91
+ })
92
+
93
+ test('closes on Escape', async ({ page }) => {
94
+ await page.waitForLoadState('networkidle')
95
+
96
+ const trigger = page.getByTestId('small-select-trigger')
97
+ await trigger.click()
98
+
99
+ // Wait for any select viewport to be visible
100
+ const selectViewport = page.getByRole('listbox').first()
101
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
102
+
103
+ // Press Escape to close
104
+ await page.keyboard.press('Escape')
105
+ await page.waitForTimeout(200)
106
+
107
+ // Select should close
108
+ await expect(selectViewport).not.toBeVisible()
109
+
110
+ // Note: Select doesn't automatically restore focus to trigger like Dialog/Popover do
111
+ })
112
+
113
+ test('Escape closes select and returns focus to trigger', async ({ page }) => {
114
+ await page.waitForLoadState('networkidle')
115
+
116
+ // Open the small select
117
+ const trigger = page.getByTestId('small-select-trigger')
118
+ await trigger.click()
119
+
120
+ // Wait for any select viewport to be visible
121
+ const selectViewport = page.getByRole('listbox').first()
122
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
123
+ await page.waitForTimeout(300)
124
+
125
+ // Escape closes the select
126
+ await page.keyboard.press('Escape')
127
+ await page.waitForTimeout(200)
128
+
129
+ // Select should be closed after Escape
130
+ await expect(selectViewport).not.toBeVisible()
131
+
132
+ // focus should return to the trigger
133
+ await expect(trigger).toBeFocused()
134
+ })
135
+
136
+ test('handles arrow key navigation correctly', async ({ page }) => {
137
+ await page.waitForLoadState('networkidle')
138
+
139
+ const trigger = page.getByTestId('basic-select-trigger')
140
+ await trigger.click()
141
+
142
+ const selectViewport = page.getByTestId('basic-select-viewport')
143
+ await expect(selectViewport).toBeVisible({ timeout: 5000 })
144
+ await page.waitForTimeout(300)
145
+
146
+ // first item (apple) is focused on open
147
+ const apple = page.getByTestId('select-apple')
148
+ const isAppleFocused = await apple.evaluate((el) => el === document.activeElement)
149
+ expect(isAppleFocused).toBe(true)
150
+
151
+ // Navigate down through all items
152
+ await page.keyboard.press('ArrowDown') // banana
153
+ await page.waitForTimeout(50)
154
+ await page.keyboard.press('ArrowDown') // orange
155
+ await page.waitForTimeout(50)
156
+ await page.keyboard.press('ArrowDown') // carrot
157
+ await page.waitForTimeout(50)
158
+ await page.keyboard.press('ArrowDown') // broccoli
159
+ await page.waitForTimeout(50)
160
+
161
+ // Should be at broccoli now
162
+ const broccoli = page.getByTestId('select-broccoli')
163
+ const isBroccoliFocused = await broccoli.evaluate(
164
+ (el) => el === document.activeElement
165
+ )
166
+ expect(isBroccoliFocused).toBe(true)
167
+
168
+ // Navigate up
169
+ await page.keyboard.press('ArrowUp')
170
+ await page.waitForTimeout(50)
171
+
172
+ const carrot = page.getByTestId('select-carrot')
173
+ const isCarrotFocused = await carrot.evaluate((el) => el === document.activeElement)
174
+ expect(isCarrotFocused).toBe(true)
175
+ })
176
+ })