@userflow-tech/react-native 0.4.4 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -34
- package/docs/anchoring.md +6 -1
- package/docs/deep-linking.md +26 -0
- package/docs/troubleshooting.md +50 -23
- package/expo-plugin/hostDependencyWarnings.d.ts +6 -4
- package/expo-plugin/hostDependencyWarnings.d.ts.map +1 -1
- package/expo-plugin/hostDependencyWarnings.js +8 -6
- package/expo-plugin/hostDependencyWarnings.js.map +1 -1
- package/lib/commonjs/anchors/AutoAnchorRegistry.js +144 -34
- package/lib/commonjs/anchors/AutoAnchorRegistry.js.map +1 -1
- package/lib/commonjs/anchors/animationFrame.js +29 -1
- package/lib/commonjs/anchors/animationFrame.js.map +1 -1
- package/lib/commonjs/anchors/appActive.js +61 -0
- package/lib/commonjs/anchors/appActive.js.map +1 -0
- package/lib/commonjs/anchors/elementConditionEvaluator.js +25 -8
- package/lib/commonjs/anchors/elementConditionEvaluator.js.map +1 -1
- package/lib/commonjs/anchors/elementTapMonitor.js +67 -12
- package/lib/commonjs/anchors/elementTapMonitor.js.map +1 -1
- package/lib/commonjs/anchors/elementTargetMatch.js +33 -7
- package/lib/commonjs/anchors/elementTargetMatch.js.map +1 -1
- package/lib/commonjs/anchors/fiber/fiberInternals.js +66 -0
- package/lib/commonjs/anchors/fiber/fiberInternals.js.map +1 -1
- package/lib/commonjs/anchors/fiber/indexHostFibers.js +4 -3
- package/lib/commonjs/anchors/fiber/indexHostFibers.js.map +1 -1
- package/lib/commonjs/anchors/fiber/resolveFiberPath.js +30 -15
- package/lib/commonjs/anchors/fiber/resolveFiberPath.js.map +1 -1
- package/lib/commonjs/anchors/fiberAnchorCache.js +20 -0
- package/lib/commonjs/anchors/fiberAnchorCache.js.map +1 -1
- package/lib/commonjs/anchors/gestureHandlerHandoff.js +304 -0
- package/lib/commonjs/anchors/gestureHandlerHandoff.js.map +1 -0
- package/lib/commonjs/anchors/resolveAnchorHost.js +6 -0
- package/lib/commonjs/anchors/resolveAnchorHost.js.map +1 -1
- package/lib/commonjs/anchors/resolveAutoAnchorHost.js +11 -6
- package/lib/commonjs/anchors/resolveAutoAnchorHost.js.map +1 -1
- package/lib/commonjs/anchors/resolveElementTargets.js +63 -8
- package/lib/commonjs/anchors/resolveElementTargets.js.map +1 -1
- package/lib/commonjs/anchors/semanticMatcher.js +43 -21
- package/lib/commonjs/anchors/semanticMatcher.js.map +1 -1
- package/lib/commonjs/anchors/tapSlop.js +16 -0
- package/lib/commonjs/anchors/tapSlop.js.map +1 -0
- package/lib/commonjs/anchors/targetPressMonitor.js +66 -12
- package/lib/commonjs/anchors/targetPressMonitor.js.map +1 -1
- package/lib/commonjs/authoring/fingerprintFromInstance.js +5 -9
- package/lib/commonjs/authoring/fingerprintFromInstance.js.map +1 -1
- package/lib/commonjs/content/RichContentRenderer.js +3 -3
- package/lib/commonjs/content/RichContentRenderer.js.map +1 -1
- package/lib/commonjs/content/openSafeUrl.js +3 -1
- package/lib/commonjs/content/openSafeUrl.js.map +1 -1
- package/lib/commonjs/expo-router/useUserflowExpoRouter.js +6 -0
- package/lib/commonjs/expo-router/useUserflowExpoRouter.js.map +1 -1
- package/lib/commonjs/index.js +19 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/installCheck.js +2 -2
- package/lib/commonjs/installCheck.js.map +1 -1
- package/lib/commonjs/navigation/navigateUrlUtils.js +105 -0
- package/lib/commonjs/navigation/navigateUrlUtils.js.map +1 -0
- package/lib/commonjs/navigation/openExternalNavigateUrl.js +14 -0
- package/lib/commonjs/navigation/openExternalNavigateUrl.js.map +1 -0
- package/lib/commonjs/navigation/openFlowNavigateUrl.js +47 -41
- package/lib/commonjs/navigation/openFlowNavigateUrl.js.map +1 -1
- package/lib/commonjs/pairing/PairingController.js +3 -1
- package/lib/commonjs/pairing/PairingController.js.map +1 -1
- package/lib/commonjs/runtime/BannerHost.js +38 -29
- package/lib/commonjs/runtime/BannerHost.js.map +1 -1
- package/lib/commonjs/runtime/TargetPressBridge.js +55 -7
- package/lib/commonjs/runtime/TargetPressBridge.js.map +1 -1
- package/lib/commonjs/runtime/UserflowOverlay.js +5 -11
- package/lib/commonjs/runtime/UserflowOverlay.js.map +1 -1
- package/lib/commonjs/runtime/UserflowProvider.js +66 -40
- package/lib/commonjs/runtime/UserflowProvider.js.map +1 -1
- package/lib/commonjs/runtime/bannerEngine.js +8 -2
- package/lib/commonjs/runtime/bannerEngine.js.map +1 -1
- package/lib/commonjs/runtime/bannerFlowSpacer.js +71 -0
- package/lib/commonjs/runtime/bannerFlowSpacer.js.map +1 -0
- package/lib/commonjs/runtime/flowEngine.js +11 -0
- package/lib/commonjs/runtime/flowEngine.js.map +1 -1
- package/lib/commonjs/runtime/previewStart.js +39 -0
- package/lib/commonjs/runtime/previewStart.js.map +1 -0
- package/lib/commonjs/runtime/pushBannerLayoutContext.js +98 -0
- package/lib/commonjs/runtime/pushBannerLayoutContext.js.map +1 -0
- package/lib/commonjs/screenTracker.js +12 -0
- package/lib/commonjs/screenTracker.js.map +1 -1
- package/lib/commonjs/surfaces/BannerBar.js +79 -61
- package/lib/commonjs/surfaces/BannerBar.js.map +1 -1
- package/lib/commonjs/surfaces/BannerSurface.js +26 -0
- package/lib/commonjs/surfaces/BannerSurface.js.map +1 -1
- package/lib/commonjs/surfaces/TooltipSurface.js +43 -41
- package/lib/commonjs/surfaces/TooltipSurface.js.map +1 -1
- package/lib/commonjs/surfaces/bannerBarLayout.js +77 -10
- package/lib/commonjs/surfaces/bannerBarLayout.js.map +1 -1
- package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js +112 -63
- package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
- package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js +59 -11
- package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
- package/lib/commonjs/surfaces/hostAccessibilityTrap.js +28 -0
- package/lib/commonjs/surfaces/hostAccessibilityTrap.js.map +1 -0
- package/lib/commonjs/surfaces/resolveBannerTheme.js +6 -3
- package/lib/commonjs/surfaces/resolveBannerTheme.js.map +1 -1
- package/lib/commonjs/surfaces/tooltip/TooltipBubble.js +3 -2
- package/lib/commonjs/surfaces/tooltip/TooltipBubble.js.map +1 -1
- package/lib/commonjs/surfaces/tooltip/targetSearchSchedule.js +134 -0
- package/lib/commonjs/surfaces/tooltip/targetSearchSchedule.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/module/anchors/AutoAnchorRegistry.js +145 -35
- package/lib/module/anchors/AutoAnchorRegistry.js.map +1 -1
- package/lib/module/anchors/animationFrame.js +29 -2
- package/lib/module/anchors/animationFrame.js.map +1 -1
- package/lib/module/anchors/appActive.js +57 -0
- package/lib/module/anchors/appActive.js.map +1 -0
- package/lib/module/anchors/elementConditionEvaluator.js +26 -9
- package/lib/module/anchors/elementConditionEvaluator.js.map +1 -1
- package/lib/module/anchors/elementTapMonitor.js +67 -12
- package/lib/module/anchors/elementTapMonitor.js.map +1 -1
- package/lib/module/anchors/elementTargetMatch.js +32 -7
- package/lib/module/anchors/elementTargetMatch.js.map +1 -1
- package/lib/module/anchors/fiber/fiberInternals.js +65 -0
- package/lib/module/anchors/fiber/fiberInternals.js.map +1 -1
- package/lib/module/anchors/fiber/indexHostFibers.js +4 -3
- package/lib/module/anchors/fiber/indexHostFibers.js.map +1 -1
- package/lib/module/anchors/fiber/resolveFiberPath.js +30 -15
- package/lib/module/anchors/fiber/resolveFiberPath.js.map +1 -1
- package/lib/module/anchors/fiberAnchorCache.js +19 -0
- package/lib/module/anchors/fiberAnchorCache.js.map +1 -1
- package/lib/module/anchors/gestureHandlerHandoff.js +298 -0
- package/lib/module/anchors/gestureHandlerHandoff.js.map +1 -0
- package/lib/module/anchors/resolveAnchorHost.js +1 -0
- package/lib/module/anchors/resolveAnchorHost.js.map +1 -1
- package/lib/module/anchors/resolveAutoAnchorHost.js +11 -6
- package/lib/module/anchors/resolveAutoAnchorHost.js.map +1 -1
- package/lib/module/anchors/resolveElementTargets.js +63 -8
- package/lib/module/anchors/resolveElementTargets.js.map +1 -1
- package/lib/module/anchors/semanticMatcher.js +43 -21
- package/lib/module/anchors/semanticMatcher.js.map +1 -1
- package/lib/module/anchors/tapSlop.js +11 -0
- package/lib/module/anchors/tapSlop.js.map +1 -0
- package/lib/module/anchors/targetPressMonitor.js +53 -8
- package/lib/module/anchors/targetPressMonitor.js.map +1 -1
- package/lib/module/authoring/fingerprintFromInstance.js +6 -10
- package/lib/module/authoring/fingerprintFromInstance.js.map +1 -1
- package/lib/module/content/RichContentRenderer.js +3 -3
- package/lib/module/content/RichContentRenderer.js.map +1 -1
- package/lib/module/content/openSafeUrl.js +3 -1
- package/lib/module/content/openSafeUrl.js.map +1 -1
- package/lib/module/expo-router/useUserflowExpoRouter.js +7 -1
- package/lib/module/expo-router/useUserflowExpoRouter.js.map +1 -1
- package/lib/module/index.js +22 -7
- package/lib/module/index.js.map +1 -1
- package/lib/module/installCheck.js +2 -2
- package/lib/module/installCheck.js.map +1 -1
- package/lib/module/navigation/navigateUrlUtils.js +94 -0
- package/lib/module/navigation/navigateUrlUtils.js.map +1 -0
- package/lib/module/navigation/openExternalNavigateUrl.js +11 -0
- package/lib/module/navigation/openExternalNavigateUrl.js.map +1 -0
- package/lib/module/navigation/openFlowNavigateUrl.js +22 -36
- package/lib/module/navigation/openFlowNavigateUrl.js.map +1 -1
- package/lib/module/pairing/PairingController.js +3 -1
- package/lib/module/pairing/PairingController.js.map +1 -1
- package/lib/module/runtime/BannerHost.js +38 -29
- package/lib/module/runtime/BannerHost.js.map +1 -1
- package/lib/module/runtime/TargetPressBridge.js +56 -8
- package/lib/module/runtime/TargetPressBridge.js.map +1 -1
- package/lib/module/runtime/UserflowOverlay.js +5 -11
- package/lib/module/runtime/UserflowOverlay.js.map +1 -1
- package/lib/module/runtime/UserflowProvider.js +66 -40
- package/lib/module/runtime/UserflowProvider.js.map +1 -1
- package/lib/module/runtime/bannerEngine.js +7 -2
- package/lib/module/runtime/bannerEngine.js.map +1 -1
- package/lib/module/runtime/bannerFlowSpacer.js +65 -0
- package/lib/module/runtime/bannerFlowSpacer.js.map +1 -0
- package/lib/module/runtime/flowEngine.js +11 -0
- package/lib/module/runtime/flowEngine.js.map +1 -1
- package/lib/module/runtime/previewStart.js +35 -0
- package/lib/module/runtime/previewStart.js.map +1 -0
- package/lib/module/runtime/pushBannerLayoutContext.js +91 -0
- package/lib/module/runtime/pushBannerLayoutContext.js.map +1 -0
- package/lib/module/screenTracker.js +11 -0
- package/lib/module/screenTracker.js.map +1 -1
- package/lib/module/surfaces/BannerBar.js +80 -62
- package/lib/module/surfaces/BannerBar.js.map +1 -1
- package/lib/module/surfaces/BannerSurface.js +26 -0
- package/lib/module/surfaces/BannerSurface.js.map +1 -1
- package/lib/module/surfaces/TooltipSurface.js +44 -42
- package/lib/module/surfaces/TooltipSurface.js.map +1 -1
- package/lib/module/surfaces/bannerBarLayout.js +72 -11
- package/lib/module/surfaces/bannerBarLayout.js.map +1 -1
- package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js +113 -64
- package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
- package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js +59 -11
- package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
- package/lib/module/surfaces/hostAccessibilityTrap.js +24 -0
- package/lib/module/surfaces/hostAccessibilityTrap.js.map +1 -0
- package/lib/module/surfaces/resolveBannerTheme.js +6 -3
- package/lib/module/surfaces/resolveBannerTheme.js.map +1 -1
- package/lib/module/surfaces/tooltip/TooltipBubble.js +3 -2
- package/lib/module/surfaces/tooltip/TooltipBubble.js.map +1 -1
- package/lib/module/surfaces/tooltip/targetSearchSchedule.js +128 -0
- package/lib/module/surfaces/tooltip/targetSearchSchedule.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/src/anchors/AutoAnchorRegistry.d.ts +36 -2
- package/lib/typescript/commonjs/src/anchors/AutoAnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/animationFrame.d.ts +0 -4
- package/lib/typescript/commonjs/src/anchors/animationFrame.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/appActive.d.ts +8 -0
- package/lib/typescript/commonjs/src/anchors/appActive.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts +17 -0
- package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts +7 -0
- package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiber/fiberInternals.d.ts +7 -0
- package/lib/typescript/commonjs/src/anchors/fiber/fiberInternals.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiber/indexHostFibers.d.ts +3 -2
- package/lib/typescript/commonjs/src/anchors/fiber/indexHostFibers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiber/resolveFiberPath.d.ts +3 -0
- package/lib/typescript/commonjs/src/anchors/fiber/resolveFiberPath.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiberAnchorCache.d.ts +5 -0
- package/lib/typescript/commonjs/src/anchors/fiberAnchorCache.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/gestureHandlerHandoff.d.ts +70 -0
- package/lib/typescript/commonjs/src/anchors/gestureHandlerHandoff.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts +1 -0
- package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts +11 -4
- package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/semanticMatcher.d.ts +2 -0
- package/lib/typescript/commonjs/src/anchors/semanticMatcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/tapSlop.d.ts +9 -0
- package/lib/typescript/commonjs/src/anchors/tapSlop.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts +13 -8
- package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/authoring/fingerprintFromInstance.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/content/RichContentRenderer.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/content/openSafeUrl.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-router/useUserflowExpoRouter.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +6 -5
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/installCheck.d.ts +1 -1
- package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/navigation/navigateUrlUtils.d.ts +15 -0
- package/lib/typescript/commonjs/src/navigation/navigateUrlUtils.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/navigation/openExternalNavigateUrl.d.ts +3 -0
- package/lib/typescript/commonjs/src/navigation/openExternalNavigateUrl.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/navigation/openFlowNavigateUrl.d.ts +2 -5
- package/lib/typescript/commonjs/src/navigation/openFlowNavigateUrl.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/pairing/PairingController.d.ts +6 -0
- package/lib/typescript/commonjs/src/pairing/PairingController.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/BannerHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts +3 -0
- package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/UserflowOverlay.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/UserflowProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/bannerEngine.d.ts +5 -1
- package/lib/typescript/commonjs/src/runtime/bannerEngine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/bannerFlowSpacer.d.ts +15 -0
- package/lib/typescript/commonjs/src/runtime/bannerFlowSpacer.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/flowEngine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/previewStart.d.ts +13 -0
- package/lib/typescript/commonjs/src/runtime/previewStart.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts +17 -0
- package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/screenTracker.d.ts +6 -0
- package/lib/typescript/commonjs/src/screenTracker.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/BannerBar.d.ts +3 -1
- package/lib/typescript/commonjs/src/surfaces/BannerBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts +5 -1
- package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts +1 -1
- package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/bannerBarLayout.d.ts +17 -0
- package/lib/typescript/commonjs/src/surfaces/bannerBarLayout.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/bannerWebView/BannerWebViewBar.d.ts +3 -1
- package/lib/typescript/commonjs/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/hostAccessibilityTrap.d.ts +14 -0
- package/lib/typescript/commonjs/src/surfaces/hostAccessibilityTrap.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/surfaces/resolveBannerTheme.d.ts +4 -1
- package/lib/typescript/commonjs/src/surfaces/resolveBannerTheme.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/tooltip/TooltipBubble.d.ts +7 -1
- package/lib/typescript/commonjs/src/surfaces/tooltip/TooltipBubble.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/tooltip/targetSearchSchedule.d.ts +60 -0
- package/lib/typescript/commonjs/src/surfaces/tooltip/targetSearchSchedule.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/version.d.ts +1 -1
- package/lib/typescript/module/src/anchors/AutoAnchorRegistry.d.ts +36 -2
- package/lib/typescript/module/src/anchors/AutoAnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/animationFrame.d.ts +0 -4
- package/lib/typescript/module/src/anchors/animationFrame.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/appActive.d.ts +8 -0
- package/lib/typescript/module/src/anchors/appActive.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts +17 -0
- package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts +7 -0
- package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiber/fiberInternals.d.ts +7 -0
- package/lib/typescript/module/src/anchors/fiber/fiberInternals.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiber/indexHostFibers.d.ts +3 -2
- package/lib/typescript/module/src/anchors/fiber/indexHostFibers.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiber/resolveFiberPath.d.ts +3 -0
- package/lib/typescript/module/src/anchors/fiber/resolveFiberPath.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiberAnchorCache.d.ts +5 -0
- package/lib/typescript/module/src/anchors/fiberAnchorCache.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/gestureHandlerHandoff.d.ts +70 -0
- package/lib/typescript/module/src/anchors/gestureHandlerHandoff.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts +1 -0
- package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts +11 -4
- package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/semanticMatcher.d.ts +2 -0
- package/lib/typescript/module/src/anchors/semanticMatcher.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/tapSlop.d.ts +9 -0
- package/lib/typescript/module/src/anchors/tapSlop.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts +13 -8
- package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts.map +1 -1
- package/lib/typescript/module/src/authoring/fingerprintFromInstance.d.ts.map +1 -1
- package/lib/typescript/module/src/content/RichContentRenderer.d.ts.map +1 -1
- package/lib/typescript/module/src/content/openSafeUrl.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-router/useUserflowExpoRouter.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +6 -5
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/installCheck.d.ts +1 -1
- package/lib/typescript/module/src/installCheck.d.ts.map +1 -1
- package/lib/typescript/module/src/navigation/navigateUrlUtils.d.ts +15 -0
- package/lib/typescript/module/src/navigation/navigateUrlUtils.d.ts.map +1 -0
- package/lib/typescript/module/src/navigation/openExternalNavigateUrl.d.ts +3 -0
- package/lib/typescript/module/src/navigation/openExternalNavigateUrl.d.ts.map +1 -0
- package/lib/typescript/module/src/navigation/openFlowNavigateUrl.d.ts +2 -5
- package/lib/typescript/module/src/navigation/openFlowNavigateUrl.d.ts.map +1 -1
- package/lib/typescript/module/src/pairing/PairingController.d.ts +6 -0
- package/lib/typescript/module/src/pairing/PairingController.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/BannerHost.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts +3 -0
- package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/UserflowOverlay.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/UserflowProvider.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/bannerEngine.d.ts +5 -1
- package/lib/typescript/module/src/runtime/bannerEngine.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/bannerFlowSpacer.d.ts +15 -0
- package/lib/typescript/module/src/runtime/bannerFlowSpacer.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/flowEngine.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/previewStart.d.ts +13 -0
- package/lib/typescript/module/src/runtime/previewStart.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts +17 -0
- package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts.map +1 -0
- package/lib/typescript/module/src/screenTracker.d.ts +6 -0
- package/lib/typescript/module/src/screenTracker.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/BannerBar.d.ts +3 -1
- package/lib/typescript/module/src/surfaces/BannerBar.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/BannerSurface.d.ts +5 -1
- package/lib/typescript/module/src/surfaces/BannerSurface.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts +1 -1
- package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/bannerBarLayout.d.ts +17 -0
- package/lib/typescript/module/src/surfaces/bannerBarLayout.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/bannerWebView/BannerWebViewBar.d.ts +3 -1
- package/lib/typescript/module/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/hostAccessibilityTrap.d.ts +14 -0
- package/lib/typescript/module/src/surfaces/hostAccessibilityTrap.d.ts.map +1 -0
- package/lib/typescript/module/src/surfaces/resolveBannerTheme.d.ts +4 -1
- package/lib/typescript/module/src/surfaces/resolveBannerTheme.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/tooltip/TooltipBubble.d.ts +7 -1
- package/lib/typescript/module/src/surfaces/tooltip/TooltipBubble.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/tooltip/targetSearchSchedule.d.ts +60 -0
- package/lib/typescript/module/src/surfaces/tooltip/targetSearchSchedule.d.ts.map +1 -0
- package/lib/typescript/module/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/anchors/AutoAnchorRegistry.ts +166 -35
- package/src/anchors/animationFrame.ts +31 -1
- package/src/anchors/appActive.ts +63 -0
- package/src/anchors/elementConditionEvaluator.ts +29 -12
- package/src/anchors/elementTapMonitor.ts +68 -10
- package/src/anchors/elementTargetMatch.ts +34 -7
- package/src/anchors/fiber/fiberInternals.ts +68 -0
- package/src/anchors/fiber/indexHostFibers.ts +8 -3
- package/src/anchors/fiber/resolveFiberPath.ts +25 -16
- package/src/anchors/fiberAnchorCache.ts +19 -0
- package/src/anchors/gestureHandlerHandoff.ts +356 -0
- package/src/anchors/resolveAnchorHost.ts +2 -0
- package/src/anchors/resolveAutoAnchorHost.ts +18 -8
- package/src/anchors/resolveElementTargets.ts +64 -7
- package/src/anchors/semanticMatcher.ts +48 -27
- package/src/anchors/tapSlop.ts +18 -0
- package/src/anchors/targetPressMonitor.ts +56 -19
- package/src/authoring/fingerprintFromInstance.ts +4 -5
- package/src/content/RichContentRenderer.tsx +4 -3
- package/src/content/openSafeUrl.ts +3 -1
- package/src/expo-router/useUserflowExpoRouter.ts +10 -1
- package/src/index.ts +20 -9
- package/src/installCheck.ts +4 -4
- package/src/navigation/navigateUrlUtils.ts +100 -0
- package/src/navigation/openExternalNavigateUrl.ts +9 -0
- package/src/navigation/openFlowNavigateUrl.ts +36 -36
- package/src/pairing/PairingController.ts +9 -1
- package/src/runtime/BannerHost.tsx +41 -23
- package/src/runtime/TargetPressBridge.tsx +66 -9
- package/src/runtime/UserflowOverlay.tsx +7 -9
- package/src/runtime/UserflowProvider.tsx +56 -32
- package/src/runtime/bannerEngine.ts +10 -2
- package/src/runtime/bannerFlowSpacer.tsx +70 -0
- package/src/runtime/flowEngine.ts +11 -0
- package/src/runtime/previewStart.ts +49 -0
- package/src/runtime/pushBannerLayoutContext.tsx +121 -0
- package/src/screenTracker.ts +11 -0
- package/src/surfaces/BannerBar.tsx +93 -55
- package/src/surfaces/BannerSurface.tsx +59 -3
- package/src/surfaces/TooltipSurface.tsx +63 -51
- package/src/surfaces/bannerBarLayout.ts +80 -7
- package/src/surfaces/bannerWebView/BannerWebViewBar.tsx +149 -57
- package/src/surfaces/bannerWebView/buildCustomBannerHtml.ts +63 -11
- package/src/surfaces/hostAccessibilityTrap.ts +25 -0
- package/src/surfaces/resolveBannerTheme.ts +14 -3
- package/src/surfaces/tooltip/TooltipBubble.tsx +8 -1
- package/src/surfaces/tooltip/targetSearchSchedule.ts +140 -0
- package/src/version.ts +1 -1
- package/vendor/core/dist/engine/BannerSessionEngine.d.ts +29 -2
- package/vendor/core/dist/engine/BannerSessionEngine.d.ts.map +1 -1
- package/vendor/core/dist/engine/BannerSessionEngine.js +87 -6
- package/vendor/core/dist/engine/BannerSessionEngine.js.map +1 -1
- package/vendor/core/dist/engine/ClientConditionEngine.d.ts.map +1 -1
- package/vendor/core/dist/engine/ClientConditionEngine.js +30 -7
- package/vendor/core/dist/engine/ClientConditionEngine.js.map +1 -1
- package/vendor/core/dist/engine/PairingEngine.d.ts +6 -1
- package/vendor/core/dist/engine/PairingEngine.d.ts.map +1 -1
- package/vendor/core/dist/engine/PairingEngine.js +23 -3
- package/vendor/core/dist/engine/PairingEngine.js.map +1 -1
- package/vendor/core/dist/engine/handleActions.d.ts.map +1 -1
- package/vendor/core/dist/engine/handleActions.js +67 -2
- package/vendor/core/dist/engine/handleActions.js.map +1 -1
- package/vendor/core/dist/version.d.ts +1 -1
- package/vendor/core/dist/version.js +1 -1
- package/vendor/core/package.json +1 -1
- package/vendor/protocol/package.json +1 -1
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taps that react-native-gesture-handler takes away from React Native.
|
|
3
|
+
*
|
|
4
|
+
* When a gesture-handler control activates, it cancels RN's touch stream, so
|
|
5
|
+
* the bridge sees `touchcancel` instead of `touchend`. The handler tag is read
|
|
6
|
+
* off the touched fiber (no import of gesture-handler), and the touch counts
|
|
7
|
+
* as a tap only if that handler later ends from ACTIVE on
|
|
8
|
+
* `onGestureHandlerStateChange`. Apps that never hit this path never subscribe.
|
|
9
|
+
*
|
|
10
|
+
* State numbers are gesture-handler's `State` enum. A future major that
|
|
11
|
+
* renumbers them degrades to "no tap", which is today's behavior.
|
|
12
|
+
*/
|
|
13
|
+
import type { AnyFiber } from './fiber/fiberInternals';
|
|
14
|
+
import {
|
|
15
|
+
getMemoizedProps,
|
|
16
|
+
getReturn,
|
|
17
|
+
getStateNode,
|
|
18
|
+
MAX_ANCESTOR_WALK,
|
|
19
|
+
} from './fiber/fiberInternals';
|
|
20
|
+
import { type TouchPoint, withinTapSlop } from './tapSlop';
|
|
21
|
+
|
|
22
|
+
/** gesture-handler `State` values this hand-off acts on. */
|
|
23
|
+
export const GESTURE_HANDLER_STATE = {
|
|
24
|
+
FAILED: 1,
|
|
25
|
+
CANCELLED: 3,
|
|
26
|
+
ACTIVE: 4,
|
|
27
|
+
END: 5,
|
|
28
|
+
} as const;
|
|
29
|
+
|
|
30
|
+
/** Drop a handed-off touch that never reaches a terminal state. */
|
|
31
|
+
export const GESTURE_HANDLER_HANDOFF_TIMEOUT_MS = 10_000;
|
|
32
|
+
|
|
33
|
+
const STATE_CHANGE_EVENT = 'onGestureHandlerStateChange';
|
|
34
|
+
|
|
35
|
+
export interface HandedOffTouch {
|
|
36
|
+
identifier: number | string;
|
|
37
|
+
pageX: number;
|
|
38
|
+
pageY: number;
|
|
39
|
+
handlerTags: readonly number[];
|
|
40
|
+
/**
|
|
41
|
+
* The gesture completed as a tap. `end` is set when the payload carried
|
|
42
|
+
* window coordinates; otherwise only the start point is known.
|
|
43
|
+
*/
|
|
44
|
+
onResolve: (end: TouchPoint | null) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface DeviceEventSubscription {
|
|
48
|
+
remove: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface DeviceEventEmitterLike {
|
|
52
|
+
addListener(event: string, listener: (payload: unknown) => void): DeviceEventSubscription;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface GestureStatePayload {
|
|
56
|
+
handlerTag: number;
|
|
57
|
+
state: number;
|
|
58
|
+
oldState?: number;
|
|
59
|
+
pointerInside?: boolean;
|
|
60
|
+
translationX?: number;
|
|
61
|
+
translationY?: number;
|
|
62
|
+
absoluteX?: number;
|
|
63
|
+
absoluteY?: number;
|
|
64
|
+
/** Present only so a long-press payload can be told apart from a tap. */
|
|
65
|
+
hasDuration: boolean;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
interface PendingTouch extends HandedOffTouch {
|
|
69
|
+
id: number;
|
|
70
|
+
settled: boolean;
|
|
71
|
+
/** Tags that already reached a terminal state without counting as a tap. */
|
|
72
|
+
finishedTags: Set<number>;
|
|
73
|
+
timer: ReturnType<typeof setTimeout>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Handler tags for the control under `fiber`.
|
|
78
|
+
*
|
|
79
|
+
* Old-API buttons keep `handlerTag` on the class instance. `GestureDetector`
|
|
80
|
+
* keeps a `gesture` prop whose `toGestureArray()` entries each have one.
|
|
81
|
+
* Negative tags (gesture-handler's own root helper) are ignored.
|
|
82
|
+
*
|
|
83
|
+
* The walk stops at the nearest fiber that has a tag. A native handler with
|
|
84
|
+
* `disallowInterruption` (gesture-handler `ScrollView`, `FlatList`,
|
|
85
|
+
* `RefreshControl`) is a container, not a tap, so a scroll that starts inside
|
|
86
|
+
* it is dropped instead of latching the row. Never throws.
|
|
87
|
+
*/
|
|
88
|
+
export function collectGestureHandlerTags(fiber: AnyFiber | null): number[] {
|
|
89
|
+
if (fiber == null) return [];
|
|
90
|
+
try {
|
|
91
|
+
let current: AnyFiber | null = fiber;
|
|
92
|
+
let steps = 0;
|
|
93
|
+
while (current && steps < MAX_ANCESTOR_WALK) {
|
|
94
|
+
steps += 1;
|
|
95
|
+
const found = tagsOnFiber(current);
|
|
96
|
+
if (found.pressable.length > 0) return found.pressable;
|
|
97
|
+
// The nearest handler is a scroll container. An outer control did not
|
|
98
|
+
// receive this touch.
|
|
99
|
+
if (found.container) return [];
|
|
100
|
+
current = getReturn(current);
|
|
101
|
+
}
|
|
102
|
+
} catch {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class GestureHandlerHandoff {
|
|
109
|
+
private readonly pending = new Map<number, PendingTouch>();
|
|
110
|
+
private nextId = 1;
|
|
111
|
+
private subscription: DeviceEventSubscription | null = null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Keep `touch` until its handler finishes or it expires.
|
|
115
|
+
* Returns a stop function; calling it drops the touch without a tap.
|
|
116
|
+
*/
|
|
117
|
+
track(touch: HandedOffTouch): () => void {
|
|
118
|
+
if (touch.handlerTags.length === 0) return () => {};
|
|
119
|
+
const id = this.nextId;
|
|
120
|
+
this.nextId += 1;
|
|
121
|
+
const pending: PendingTouch = {
|
|
122
|
+
...touch,
|
|
123
|
+
id,
|
|
124
|
+
settled: false,
|
|
125
|
+
finishedTags: new Set(),
|
|
126
|
+
timer: setTimeout(() => {
|
|
127
|
+
this.drop(id);
|
|
128
|
+
}, GESTURE_HANDLER_HANDOFF_TIMEOUT_MS),
|
|
129
|
+
};
|
|
130
|
+
this.pending.set(id, pending);
|
|
131
|
+
this.ensureListening();
|
|
132
|
+
return () => this.drop(id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Test helper. Drops every pending touch and unsubscribes. */
|
|
136
|
+
resetForTests(): void {
|
|
137
|
+
for (const touch of this.pending.values()) {
|
|
138
|
+
touch.settled = true;
|
|
139
|
+
clearTimeout(touch.timer);
|
|
140
|
+
}
|
|
141
|
+
this.pending.clear();
|
|
142
|
+
this.stopListeningIfIdle();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** @internal Test hook: how many touches are still waiting. */
|
|
146
|
+
get pendingCount(): number {
|
|
147
|
+
return this.pending.size;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
private ensureListening(): void {
|
|
151
|
+
if (this.subscription || this.pending.size === 0) return;
|
|
152
|
+
const emitter = deviceEventEmitter();
|
|
153
|
+
if (!emitter) return;
|
|
154
|
+
this.subscription = emitter.addListener(STATE_CHANGE_EVENT, (payload: unknown) => {
|
|
155
|
+
try {
|
|
156
|
+
this.handleStateChange(payload);
|
|
157
|
+
} catch {
|
|
158
|
+
// A bad payload must not break the next gesture.
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private stopListeningIfIdle(): void {
|
|
164
|
+
if (this.pending.size > 0 || !this.subscription) return;
|
|
165
|
+
this.subscription.remove();
|
|
166
|
+
this.subscription = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
private handleStateChange(event: unknown): void {
|
|
170
|
+
const payload = asPayload(event);
|
|
171
|
+
if (!payload) return;
|
|
172
|
+
const matching = [...this.pending.values()].filter(
|
|
173
|
+
(touch) => !touch.settled && touch.handlerTags.includes(payload.handlerTag),
|
|
174
|
+
);
|
|
175
|
+
for (const touch of matching) {
|
|
176
|
+
this.apply(touch, payload);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
private apply(touch: PendingTouch, payload: GestureStatePayload): void {
|
|
181
|
+
if (touch.settled || !isTerminal(payload.state)) return;
|
|
182
|
+
if (isCompletedTap(touch, payload)) {
|
|
183
|
+
this.resolve(touch, endPoint(payload));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
touch.finishedTags.add(payload.handlerTag);
|
|
187
|
+
const allDone = touch.handlerTags.every((tag) => touch.finishedTags.has(tag));
|
|
188
|
+
if (allDone) this.drop(touch.id);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private resolve(touch: PendingTouch, end: TouchPoint | null): void {
|
|
192
|
+
const notify = touch.onResolve;
|
|
193
|
+
this.drop(touch.id);
|
|
194
|
+
try {
|
|
195
|
+
notify(end);
|
|
196
|
+
} catch {
|
|
197
|
+
// One monitor's failure must not skip the other touch on this event.
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private drop(id: number): void {
|
|
202
|
+
const touch = this.pending.get(id);
|
|
203
|
+
if (!touch || touch.settled) return;
|
|
204
|
+
touch.settled = true;
|
|
205
|
+
clearTimeout(touch.timer);
|
|
206
|
+
this.pending.delete(id);
|
|
207
|
+
this.stopListeningIfIdle();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Shared singleton used by the tap monitors. */
|
|
212
|
+
export const gestureHandlerHandoff = new GestureHandlerHandoff();
|
|
213
|
+
|
|
214
|
+
interface FiberTags {
|
|
215
|
+
/** Tags that can count as a tap. */
|
|
216
|
+
pressable: number[];
|
|
217
|
+
/** The fiber has a container handler and no pressable tag. */
|
|
218
|
+
container: boolean;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function tagsOnFiber(fiber: AnyFiber): FiberTags {
|
|
222
|
+
try {
|
|
223
|
+
const pressable: number[] = [];
|
|
224
|
+
const seen = new Set<number>();
|
|
225
|
+
let container = false;
|
|
226
|
+
const add = (tag: number | undefined, isContainer: boolean): void => {
|
|
227
|
+
if (tag === undefined) return;
|
|
228
|
+
if (isContainer) {
|
|
229
|
+
container = true;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (seen.has(tag)) return;
|
|
233
|
+
seen.add(tag);
|
|
234
|
+
pressable.push(tag);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const props = getMemoizedProps(fiber);
|
|
238
|
+
add(handlerTagOf(getStateNode(fiber)), props?.disallowInterruption === true);
|
|
239
|
+
|
|
240
|
+
const gesture = props?.gesture;
|
|
241
|
+
if (gesture && typeof gesture === 'object' && 'toGestureArray' in gesture) {
|
|
242
|
+
const toArray = (gesture as { toGestureArray?: unknown }).toGestureArray;
|
|
243
|
+
if (typeof toArray === 'function') {
|
|
244
|
+
const list = (toArray as () => unknown).call(gesture);
|
|
245
|
+
if (Array.isArray(list)) {
|
|
246
|
+
for (const item of list) {
|
|
247
|
+
const tag = handlerTagOf(item) ?? handlerTagOf(readHandlers(item));
|
|
248
|
+
add(tag, disallowsInterruption(item));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return { pressable, container: container && pressable.length === 0 };
|
|
254
|
+
} catch {
|
|
255
|
+
return { pressable: [], container: false };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Gesture-handler scroll containers set this so a drag is not stolen. */
|
|
260
|
+
function disallowsInterruption(value: unknown): boolean {
|
|
261
|
+
if (!value || typeof value !== 'object' || !('config' in value)) return false;
|
|
262
|
+
const config = (value as { config?: unknown }).config;
|
|
263
|
+
if (!config || typeof config !== 'object' || !('disallowInterruption' in config)) return false;
|
|
264
|
+
return (config as { disallowInterruption?: unknown }).disallowInterruption === true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function readHandlers(value: unknown): unknown {
|
|
268
|
+
if (!value || typeof value !== 'object' || !('handlers' in value)) return undefined;
|
|
269
|
+
return (value as { handlers?: unknown }).handlers;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** A mounted gesture-handler tag. `-1` means "not attached yet". */
|
|
273
|
+
function handlerTagOf(value: unknown): number | undefined {
|
|
274
|
+
if (!value || typeof value !== 'object' || !('handlerTag' in value)) return undefined;
|
|
275
|
+
const tag = (value as { handlerTag?: unknown }).handlerTag;
|
|
276
|
+
if (typeof tag !== 'number' || !Number.isFinite(tag) || tag < 0) return undefined;
|
|
277
|
+
return tag;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function deviceEventEmitter(): DeviceEventEmitterLike | null {
|
|
281
|
+
try {
|
|
282
|
+
// Lazy so a tap that is never cancelled does not load React Native's emitter
|
|
283
|
+
// into unit tests that only exercise the monitors.
|
|
284
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
285
|
+
const reactNative = require('react-native') as {
|
|
286
|
+
DeviceEventEmitter?: DeviceEventEmitterLike;
|
|
287
|
+
};
|
|
288
|
+
const emitter = reactNative.DeviceEventEmitter;
|
|
289
|
+
if (!emitter || typeof emitter.addListener !== 'function') return null;
|
|
290
|
+
return emitter;
|
|
291
|
+
} catch {
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function asPayload(event: unknown): GestureStatePayload | null {
|
|
297
|
+
if (!event || typeof event !== 'object') return null;
|
|
298
|
+
const raw = event as Record<string, unknown>;
|
|
299
|
+
const body =
|
|
300
|
+
raw.nativeEvent && typeof raw.nativeEvent === 'object'
|
|
301
|
+
? (raw.nativeEvent as Record<string, unknown>)
|
|
302
|
+
: raw;
|
|
303
|
+
if (typeof body.handlerTag !== 'number' || typeof body.state !== 'number') return null;
|
|
304
|
+
return {
|
|
305
|
+
handlerTag: body.handlerTag,
|
|
306
|
+
state: body.state,
|
|
307
|
+
...(typeof body.oldState === 'number' ? { oldState: body.oldState } : {}),
|
|
308
|
+
...(typeof body.pointerInside === 'boolean' ? { pointerInside: body.pointerInside } : {}),
|
|
309
|
+
...(typeof body.translationX === 'number' ? { translationX: body.translationX } : {}),
|
|
310
|
+
...(typeof body.translationY === 'number' ? { translationY: body.translationY } : {}),
|
|
311
|
+
...(typeof body.absoluteX === 'number' ? { absoluteX: body.absoluteX } : {}),
|
|
312
|
+
...(typeof body.absoluteY === 'number' ? { absoluteY: body.absoluteY } : {}),
|
|
313
|
+
hasDuration: 'duration' in body && body.duration != null,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function isTerminal(state: number): boolean {
|
|
318
|
+
return (
|
|
319
|
+
state === GESTURE_HANDLER_STATE.FAILED ||
|
|
320
|
+
state === GESTURE_HANDLER_STATE.CANCELLED ||
|
|
321
|
+
state === GESTURE_HANDLER_STATE.END
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function isCompletedTap(touch: PendingTouch, payload: GestureStatePayload): boolean {
|
|
326
|
+
if (payload.state !== GESTURE_HANDLER_STATE.END) return false;
|
|
327
|
+
if (payload.oldState !== GESTURE_HANDLER_STATE.ACTIVE) return false;
|
|
328
|
+
if (payload.pointerInside === false) return false;
|
|
329
|
+
if (payload.hasDuration) return false;
|
|
330
|
+
if (
|
|
331
|
+
payload.translationX !== undefined &&
|
|
332
|
+
payload.translationY !== undefined &&
|
|
333
|
+
!withinTapSlop(
|
|
334
|
+
{ pageX: 0, pageY: 0 },
|
|
335
|
+
{ pageX: payload.translationX, pageY: payload.translationY },
|
|
336
|
+
)
|
|
337
|
+
) {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
if (payload.absoluteX !== undefined && payload.absoluteY !== undefined) {
|
|
341
|
+
if (
|
|
342
|
+
!withinTapSlop(
|
|
343
|
+
{ pageX: touch.pageX, pageY: touch.pageY },
|
|
344
|
+
{ pageX: payload.absoluteX, pageY: payload.absoluteY },
|
|
345
|
+
)
|
|
346
|
+
) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function endPoint(payload: GestureStatePayload): TouchPoint | null {
|
|
354
|
+
if (payload.absoluteX === undefined || payload.absoluteY === undefined) return null;
|
|
355
|
+
return { pageX: payload.absoluteX, pageY: payload.absoluteY };
|
|
356
|
+
}
|
|
@@ -15,6 +15,8 @@ import {
|
|
|
15
15
|
setCachedFiberAnchor,
|
|
16
16
|
} from './fiberAnchorCache';
|
|
17
17
|
|
|
18
|
+
export { subscribeAnchorScreenChange } from './fiberAnchorCache';
|
|
19
|
+
|
|
18
20
|
export type AnchorResolution =
|
|
19
21
|
| { status: 'found'; host: AnchorHost; fiber?: AnyFiber }
|
|
20
22
|
| { status: 'ambiguous' }
|
|
@@ -9,7 +9,7 @@ import type { AutoAnchorInstance } from './AutoAnchorRegistry';
|
|
|
9
9
|
import type { AnyFiber } from './fiber/fiberInternals';
|
|
10
10
|
import { fiberFromPublicInstance } from './fiber/fiberInternals';
|
|
11
11
|
import { createFiberMeasureInWindow } from './fiber/measureHostFiber';
|
|
12
|
-
import { resolveFiberPath } from './fiber/resolveFiberPath';
|
|
12
|
+
import { resolveFiberPath, type FiberPathMissReason } from './fiber/resolveFiberPath';
|
|
13
13
|
import { createVisibleInstanceFilter } from './isInstanceOnVisibleScreen';
|
|
14
14
|
import { resolveAnchorHost } from './resolveAnchorHost';
|
|
15
15
|
import { matchFingerprint, type MatchResult } from './semanticMatcher';
|
|
@@ -28,7 +28,15 @@ export type AutoAnchorResolution =
|
|
|
28
28
|
}
|
|
29
29
|
| { status: 'ambiguous'; match?: MatchResult }
|
|
30
30
|
| { status: 'absent'; match?: MatchResult }
|
|
31
|
-
| {
|
|
31
|
+
| {
|
|
32
|
+
status: 'unknown';
|
|
33
|
+
match?: MatchResult;
|
|
34
|
+
/**
|
|
35
|
+
* Set when a fiber path did not resolve. Element conditions treat
|
|
36
|
+
* `no-match` as absent and `no-root` / `empty` as unknown.
|
|
37
|
+
*/
|
|
38
|
+
fiberMiss?: FiberPathMissReason;
|
|
39
|
+
};
|
|
32
40
|
|
|
33
41
|
type MeasureInWindowCallback = (x: number, y: number, width: number, height: number) => void;
|
|
34
42
|
|
|
@@ -121,7 +129,8 @@ export function resolveScoredAutoAnchor(fp: MobileFingerprint): AutoAnchorResolu
|
|
|
121
129
|
if (visible.instance) return mapMatch(visible);
|
|
122
130
|
|
|
123
131
|
if (visible.reason === 'no-candidates') {
|
|
124
|
-
|
|
132
|
+
// Nothing can commit between the two matches, so the first read stands.
|
|
133
|
+
const any = matchFingerprint(fp, { liveContentFresh: true });
|
|
125
134
|
if (any.instance) return { status: 'absent', match: any };
|
|
126
135
|
if (any.reason === 'all-contradicted') return mapMatch(any);
|
|
127
136
|
}
|
|
@@ -136,10 +145,11 @@ export function resolveScoredAutoAnchor(fp: MobileFingerprint): AutoAnchorResolu
|
|
|
136
145
|
* carries a real `testID` / `userflowId` / non-`uf_` `nativeID` still resolves
|
|
137
146
|
* under Babel `stampOnly: true` (no auto-registry population).
|
|
138
147
|
*
|
|
139
|
-
* Scored matches run over `autoAnchorRegistry
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* are skipped the same way the fiber scan
|
|
148
|
+
* Scored matches run over `autoAnchorRegistry`. A covered screen and a
|
|
149
|
+
* contradicted fingerprint are `absent`. Weak, missing, or tied evidence stays
|
|
150
|
+
* `unknown` here; element conditions decide which of those count as absent.
|
|
151
|
+
* Hosts on a covered navigator screen are skipped the same way the fiber scan
|
|
152
|
+
* skips them.
|
|
143
153
|
*/
|
|
144
154
|
export function resolveAutoAnchorHost(
|
|
145
155
|
autoData: string | MobileAutoData | null | undefined,
|
|
@@ -159,7 +169,7 @@ export function resolveAutoAnchorHost(
|
|
|
159
169
|
};
|
|
160
170
|
}
|
|
161
171
|
if (fiberHit.status === 'ambiguous') return { status: 'ambiguous' };
|
|
162
|
-
return { status: 'unknown' };
|
|
172
|
+
return { status: 'unknown', fiberMiss: fiberHit.reason };
|
|
163
173
|
}
|
|
164
174
|
|
|
165
175
|
const exactId = exactIdFromFingerprint(fp);
|
|
@@ -6,9 +6,16 @@
|
|
|
6
6
|
* `testID`, then a real `nativeID`) use that same multi-host lookup, and fall
|
|
7
7
|
* through to scoring only when nothing matched.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
decodeMobileAutoData,
|
|
11
|
+
type FlowSelectorFragment,
|
|
12
|
+
type MobileAutoData,
|
|
13
|
+
} from '@userflow-tech/protocol';
|
|
14
|
+
|
|
15
|
+
import { screenIdFromLocation, getCurrentScreenLocation } from '../screenTracker';
|
|
10
16
|
|
|
11
17
|
import { type AnchorHost, anchorRegistry } from './AnchorRegistry';
|
|
18
|
+
import { autoAnchorRegistry } from './AutoAnchorRegistry';
|
|
12
19
|
import type { ElementTap } from './elementTapMonitor';
|
|
13
20
|
import { anchorIdsAboveTap } from './elementTargetMatch';
|
|
14
21
|
import { fiberScanRoot } from './fiber/FiberScanRoot';
|
|
@@ -85,21 +92,38 @@ function resolveAutoTargets(autoData: string | null): ElementTargetResolution {
|
|
|
85
92
|
if (!decoded || !fingerprint) return { status: 'unknown' };
|
|
86
93
|
|
|
87
94
|
if (decoded.resolver === 'fiber' && decoded.fiberPath) {
|
|
88
|
-
return
|
|
95
|
+
return conditionVerdict(resolveAutoAnchorHost(decoded), decoded);
|
|
89
96
|
}
|
|
90
97
|
|
|
91
98
|
const exactId = exactIdFromFingerprint(fingerprint);
|
|
99
|
+
let exactIdAbsent = false;
|
|
92
100
|
if (exactId) {
|
|
93
101
|
const manual = resolveManualTargets(exactId);
|
|
94
102
|
if (manual.status === 'found') return manual;
|
|
103
|
+
// "Absent" before a scan root exists only means we have not looked yet.
|
|
104
|
+
exactIdAbsent = fiberScanRoot.getFiber() != null;
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
// The exact id already missed above, so go straight to scoring rather than
|
|
98
108
|
// letting resolveAutoAnchorHost scan for it again.
|
|
99
|
-
return
|
|
109
|
+
return conditionVerdict(resolveScoredAutoAnchor(fingerprint), decoded, exactIdAbsent);
|
|
100
110
|
}
|
|
101
111
|
|
|
102
|
-
|
|
112
|
+
/**
|
|
113
|
+
* Turn an AUTO resolution into the value an element condition can report.
|
|
114
|
+
*
|
|
115
|
+
* A miss (nothing scored, or an exact id that a scan did not find) is absent,
|
|
116
|
+
* matching web. A tie stays unknown on the screen where the element was picked,
|
|
117
|
+
* because the target may be one of the look-alikes, and is absent on any other
|
|
118
|
+
* screen. An empty registry with no exact id, a missing scan root, and a
|
|
119
|
+
* missing fingerprint stay unknown so `stampOnly` cannot open a "not present"
|
|
120
|
+
* gate.
|
|
121
|
+
*/
|
|
122
|
+
function conditionVerdict(
|
|
123
|
+
resolution: AutoAnchorResolution,
|
|
124
|
+
decoded: MobileAutoData,
|
|
125
|
+
exactIdAbsent = false,
|
|
126
|
+
): ElementTargetResolution {
|
|
103
127
|
if (resolution.status === 'found') {
|
|
104
128
|
// Scored matches only try the direct ref pointer, which Paper release
|
|
105
129
|
// builds null out. Without the index fallback a tap could never match.
|
|
@@ -112,12 +136,45 @@ function fromAutoResolution(resolution: AutoAnchorResolution): ElementTargetReso
|
|
|
112
136
|
};
|
|
113
137
|
}
|
|
114
138
|
if (resolution.status === 'absent') return { status: 'absent' };
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
|
|
140
|
+
if (resolution.status === 'unknown') {
|
|
141
|
+
if (resolution.fiberMiss === 'no-root' || resolution.fiberMiss === 'empty') {
|
|
142
|
+
return { status: 'unknown' };
|
|
143
|
+
}
|
|
144
|
+
if (resolution.fiberMiss === 'no-match') return { status: 'absent' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const reason = resolution.match?.reason;
|
|
148
|
+
const tie =
|
|
149
|
+
resolution.status === 'ambiguous' ||
|
|
150
|
+
reason === 'not-unique' ||
|
|
151
|
+
reason === 'ambiguous-explicit-id';
|
|
152
|
+
if (tie) {
|
|
153
|
+
if (offPickedScreen(decoded.screenId)) return { status: 'absent' };
|
|
154
|
+
// A real tie is not a broken selector, so it does not warn. A weak
|
|
155
|
+
// not-unique miss still does, matching the previous unknown report.
|
|
156
|
+
return reason === 'not-unique' ? { status: 'unknown' } : { status: 'unknown', silent: true };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (reason === 'below-threshold') return { status: 'absent' };
|
|
160
|
+
|
|
161
|
+
if (reason === 'no-candidates') {
|
|
162
|
+
const nothingToMatch = autoAnchorRegistry.allInstances().length === 0 && !exactIdAbsent;
|
|
163
|
+
if (nothingToMatch) return { status: 'unknown' };
|
|
164
|
+
return { status: 'absent' };
|
|
165
|
+
}
|
|
166
|
+
|
|
118
167
|
return { status: 'unknown' };
|
|
119
168
|
}
|
|
120
169
|
|
|
170
|
+
/** True when the picked screen is known and the app is on a different one. */
|
|
171
|
+
function offPickedScreen(pickedScreenId: string | undefined): boolean {
|
|
172
|
+
if (!pickedScreenId || pickedScreenId === 'unknown') return false;
|
|
173
|
+
const current = screenIdFromLocation(getCurrentScreenLocation());
|
|
174
|
+
if (current === 'unknown') return false;
|
|
175
|
+
return current !== pickedScreenId;
|
|
176
|
+
}
|
|
177
|
+
|
|
121
178
|
function resolveManualTargets(id: string): ElementTargetResolution {
|
|
122
179
|
const registered = anchorRegistry.getAll(id);
|
|
123
180
|
if (registered.length > 0) {
|
|
@@ -190,18 +190,21 @@ function intrinsicNonTextAgrees(fp: MobileFingerprint, inst: AutoAnchorInstance)
|
|
|
190
190
|
);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
/**
|
|
194
|
+
* An id or kind conflict, or a childText disagreement no text-independent
|
|
195
|
+
* identity (authoritative id, matching icon/image) vouches for.
|
|
196
|
+
*/
|
|
197
|
+
function isContradicted(fp: MobileFingerprint, inst: AutoAnchorInstance): boolean {
|
|
198
|
+
if (contradictsStrong(fp, inst)) return true;
|
|
199
|
+
return childTextDisagrees(fp, inst) && !idAgrees(fp, inst) && !intrinsicNonTextAgrees(fp, inst);
|
|
200
|
+
}
|
|
201
|
+
|
|
193
202
|
function scoreInstance(
|
|
194
203
|
fp: MobileFingerprint,
|
|
195
204
|
inst: AutoAnchorInstance,
|
|
196
205
|
ambiguous: boolean,
|
|
197
206
|
): number {
|
|
198
|
-
if (
|
|
199
|
-
|
|
200
|
-
// A childText disagreement is disqualifying unless a text-independent identity
|
|
201
|
-
// vouches for the element (authoritative id, or matching icon/image).
|
|
202
|
-
if (childTextDisagrees(fp, inst) && !idAgrees(fp, inst) && !intrinsicNonTextAgrees(fp, inst)) {
|
|
203
|
-
return -Infinity;
|
|
204
|
-
}
|
|
207
|
+
if (isContradicted(fp, inst)) return -Infinity;
|
|
205
208
|
|
|
206
209
|
let score = 0;
|
|
207
210
|
|
|
@@ -231,11 +234,14 @@ function scoreInstance(
|
|
|
231
234
|
|
|
232
235
|
score += scoreAncestors(fp.ancestors ?? [], inst.ancestors ?? []);
|
|
233
236
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
// The window scans the registry, so skip it when there is nothing to compare.
|
|
238
|
+
if (fp.previousSiblings?.length || fp.nextSiblings?.length) {
|
|
239
|
+
const window = autoAnchorRegistry.siblingWindow(inst.instanceId, 2);
|
|
240
|
+
score += scoreSiblingWindow(fp.previousSiblings ?? [], window.previous);
|
|
241
|
+
// Score next siblings once. The POC added a reversed pass *and* a direct pass,
|
|
242
|
+
// which double-counted the next window.
|
|
243
|
+
score += scoreSiblingWindow(fp.nextSiblings ?? [], window.next);
|
|
244
|
+
}
|
|
239
245
|
|
|
240
246
|
if (ambiguous && !inst.manualId) score -= 40;
|
|
241
247
|
|
|
@@ -246,6 +252,8 @@ type InstanceFilter = (inst: AutoAnchorInstance) => boolean;
|
|
|
246
252
|
|
|
247
253
|
interface MatchOptions {
|
|
248
254
|
instanceFilter?: InstanceFilter;
|
|
255
|
+
/** The caller refreshed live content earlier in this same synchronous call. */
|
|
256
|
+
liveContentFresh?: boolean;
|
|
249
257
|
}
|
|
250
258
|
|
|
251
259
|
/** Each instance's visibility is computed once per match. */
|
|
@@ -260,10 +268,7 @@ function memoizeFilter(filter: InstanceFilter): InstanceFilter {
|
|
|
260
268
|
};
|
|
261
269
|
}
|
|
262
270
|
|
|
263
|
-
function candidatePool(
|
|
264
|
-
fp: MobileFingerprint,
|
|
265
|
-
instanceFilter?: InstanceFilter,
|
|
266
|
-
): AutoAnchorInstance[] {
|
|
271
|
+
function candidatePool(fp: MobileFingerprint): AutoAnchorInstance[] {
|
|
267
272
|
// Explicit ids are authoritative: a miss is no-candidates, not a full-registry search.
|
|
268
273
|
let pool: AutoAnchorInstance[];
|
|
269
274
|
if (fp.manualId) {
|
|
@@ -286,29 +291,45 @@ function candidatePool(
|
|
|
286
291
|
if (filtered.length > 0) pool = filtered;
|
|
287
292
|
}
|
|
288
293
|
}
|
|
289
|
-
return
|
|
294
|
+
return pool;
|
|
290
295
|
}
|
|
291
296
|
|
|
292
297
|
function miss(reason: MatchMissReason, score = 0, secondScore = 0): MatchResult {
|
|
293
298
|
return { instance: null, score, secondScore, reason };
|
|
294
299
|
}
|
|
295
300
|
|
|
296
|
-
function matchFingerprintCore(
|
|
301
|
+
function matchFingerprintCore(
|
|
302
|
+
fp: MobileFingerprint,
|
|
303
|
+
instanceFilter?: InstanceFilter,
|
|
304
|
+
liveContentFresh = false,
|
|
305
|
+
): MatchResult {
|
|
297
306
|
// Live text / testID must be applied *before* pooling. A shared Babel helper
|
|
298
307
|
// (`const field = () => <TextInput testID={id} />`) reuses one auto-anchor
|
|
299
308
|
// callback, so the runtime bag is last-writer-wins until we read the host.
|
|
309
|
+
// An unchanged host is a fiber and props-identity check after one shared
|
|
310
|
+
// ancestor walk per pass, so refreshing every instance stays cheap; the
|
|
311
|
+
// per-instance screen walk is what is not, and it only runs for the pool
|
|
312
|
+
// below.
|
|
313
|
+
const all = autoAnchorRegistry.allInstances();
|
|
314
|
+
if (all.length > 0 && !liveContentFresh) {
|
|
315
|
+
autoAnchorRegistry.refreshLiveContent(all);
|
|
316
|
+
}
|
|
317
|
+
|
|
300
318
|
// A filter's verdict is memoized so covered-screen checks run once per instance.
|
|
301
319
|
const filter = instanceFilter ? memoizeFilter(instanceFilter) : undefined;
|
|
302
|
-
const
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
320
|
+
const candidates = candidatePool(fp);
|
|
321
|
+
// A contradiction does not depend on visibility, so settle it before the
|
|
322
|
+
// covered-screen check. Visibility of contradicted instances only matters to
|
|
323
|
+
// tell `all-contradicted` from `no-candidates` when nothing else is left.
|
|
324
|
+
const viable: AutoAnchorInstance[] = [];
|
|
325
|
+
const contradicted: AutoAnchorInstance[] = [];
|
|
326
|
+
for (const inst of candidates) {
|
|
327
|
+
(isContradicted(fp, inst) ? contradicted : viable).push(inst);
|
|
307
328
|
}
|
|
308
|
-
|
|
309
|
-
const pool = candidatePool(fp, filter);
|
|
329
|
+
const pool = filter ? viable.filter(filter) : viable;
|
|
310
330
|
if (pool.length === 0) {
|
|
311
|
-
|
|
331
|
+
const anyContradictedCandidate = filter ? contradicted.some(filter) : contradicted.length > 0;
|
|
332
|
+
return miss(anyContradictedCandidate ? 'all-contradicted' : 'no-candidates');
|
|
312
333
|
}
|
|
313
334
|
|
|
314
335
|
// Visibility can clear a registry-wide ambiguous flag (a covered twin) but
|
|
@@ -367,7 +388,7 @@ function matchFingerprintCore(fp: MobileFingerprint, instanceFilter?: InstanceFi
|
|
|
367
388
|
* can flag weak (`structural`) targets.
|
|
368
389
|
*/
|
|
369
390
|
export function matchFingerprint(fp: MobileFingerprint, opts?: MatchOptions): MatchResult {
|
|
370
|
-
const result = matchFingerprintCore(fp, opts?.instanceFilter);
|
|
391
|
+
const result = matchFingerprintCore(fp, opts?.instanceFilter, opts?.liveContentFresh);
|
|
371
392
|
if (fp.identityStrength) result.identityStrength = fp.identityStrength;
|
|
372
393
|
return result;
|
|
373
394
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Max finger travel (px) between start and end to count as a tap, not a scroll. */
|
|
2
|
+
export const TARGET_PRESS_TAP_SLOP = 10;
|
|
3
|
+
|
|
4
|
+
export interface TouchPoint {
|
|
5
|
+
pageX: number;
|
|
6
|
+
pageY: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** True when the finger moved no more than `slop` between start and end. */
|
|
10
|
+
export function withinTapSlop(
|
|
11
|
+
start: TouchPoint,
|
|
12
|
+
end: TouchPoint,
|
|
13
|
+
slop: number = TARGET_PRESS_TAP_SLOP,
|
|
14
|
+
): boolean {
|
|
15
|
+
const dx = end.pageX - start.pageX;
|
|
16
|
+
const dy = end.pageY - start.pageY;
|
|
17
|
+
return dx * dx + dy * dy <= slop * slop;
|
|
18
|
+
}
|