@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.
Files changed (431) hide show
  1. package/README.md +124 -34
  2. package/docs/anchoring.md +6 -1
  3. package/docs/deep-linking.md +26 -0
  4. package/docs/troubleshooting.md +50 -23
  5. package/expo-plugin/hostDependencyWarnings.d.ts +6 -4
  6. package/expo-plugin/hostDependencyWarnings.d.ts.map +1 -1
  7. package/expo-plugin/hostDependencyWarnings.js +8 -6
  8. package/expo-plugin/hostDependencyWarnings.js.map +1 -1
  9. package/lib/commonjs/anchors/AutoAnchorRegistry.js +144 -34
  10. package/lib/commonjs/anchors/AutoAnchorRegistry.js.map +1 -1
  11. package/lib/commonjs/anchors/animationFrame.js +29 -1
  12. package/lib/commonjs/anchors/animationFrame.js.map +1 -1
  13. package/lib/commonjs/anchors/appActive.js +61 -0
  14. package/lib/commonjs/anchors/appActive.js.map +1 -0
  15. package/lib/commonjs/anchors/elementConditionEvaluator.js +25 -8
  16. package/lib/commonjs/anchors/elementConditionEvaluator.js.map +1 -1
  17. package/lib/commonjs/anchors/elementTapMonitor.js +67 -12
  18. package/lib/commonjs/anchors/elementTapMonitor.js.map +1 -1
  19. package/lib/commonjs/anchors/elementTargetMatch.js +33 -7
  20. package/lib/commonjs/anchors/elementTargetMatch.js.map +1 -1
  21. package/lib/commonjs/anchors/fiber/fiberInternals.js +66 -0
  22. package/lib/commonjs/anchors/fiber/fiberInternals.js.map +1 -1
  23. package/lib/commonjs/anchors/fiber/indexHostFibers.js +4 -3
  24. package/lib/commonjs/anchors/fiber/indexHostFibers.js.map +1 -1
  25. package/lib/commonjs/anchors/fiber/resolveFiberPath.js +30 -15
  26. package/lib/commonjs/anchors/fiber/resolveFiberPath.js.map +1 -1
  27. package/lib/commonjs/anchors/fiberAnchorCache.js +20 -0
  28. package/lib/commonjs/anchors/fiberAnchorCache.js.map +1 -1
  29. package/lib/commonjs/anchors/gestureHandlerHandoff.js +304 -0
  30. package/lib/commonjs/anchors/gestureHandlerHandoff.js.map +1 -0
  31. package/lib/commonjs/anchors/resolveAnchorHost.js +6 -0
  32. package/lib/commonjs/anchors/resolveAnchorHost.js.map +1 -1
  33. package/lib/commonjs/anchors/resolveAutoAnchorHost.js +11 -6
  34. package/lib/commonjs/anchors/resolveAutoAnchorHost.js.map +1 -1
  35. package/lib/commonjs/anchors/resolveElementTargets.js +63 -8
  36. package/lib/commonjs/anchors/resolveElementTargets.js.map +1 -1
  37. package/lib/commonjs/anchors/semanticMatcher.js +43 -21
  38. package/lib/commonjs/anchors/semanticMatcher.js.map +1 -1
  39. package/lib/commonjs/anchors/tapSlop.js +16 -0
  40. package/lib/commonjs/anchors/tapSlop.js.map +1 -0
  41. package/lib/commonjs/anchors/targetPressMonitor.js +66 -12
  42. package/lib/commonjs/anchors/targetPressMonitor.js.map +1 -1
  43. package/lib/commonjs/authoring/fingerprintFromInstance.js +5 -9
  44. package/lib/commonjs/authoring/fingerprintFromInstance.js.map +1 -1
  45. package/lib/commonjs/content/RichContentRenderer.js +3 -3
  46. package/lib/commonjs/content/RichContentRenderer.js.map +1 -1
  47. package/lib/commonjs/content/openSafeUrl.js +3 -1
  48. package/lib/commonjs/content/openSafeUrl.js.map +1 -1
  49. package/lib/commonjs/expo-router/useUserflowExpoRouter.js +6 -0
  50. package/lib/commonjs/expo-router/useUserflowExpoRouter.js.map +1 -1
  51. package/lib/commonjs/index.js +19 -5
  52. package/lib/commonjs/index.js.map +1 -1
  53. package/lib/commonjs/installCheck.js +2 -2
  54. package/lib/commonjs/installCheck.js.map +1 -1
  55. package/lib/commonjs/navigation/navigateUrlUtils.js +105 -0
  56. package/lib/commonjs/navigation/navigateUrlUtils.js.map +1 -0
  57. package/lib/commonjs/navigation/openExternalNavigateUrl.js +14 -0
  58. package/lib/commonjs/navigation/openExternalNavigateUrl.js.map +1 -0
  59. package/lib/commonjs/navigation/openFlowNavigateUrl.js +47 -41
  60. package/lib/commonjs/navigation/openFlowNavigateUrl.js.map +1 -1
  61. package/lib/commonjs/pairing/PairingController.js +3 -1
  62. package/lib/commonjs/pairing/PairingController.js.map +1 -1
  63. package/lib/commonjs/runtime/BannerHost.js +38 -29
  64. package/lib/commonjs/runtime/BannerHost.js.map +1 -1
  65. package/lib/commonjs/runtime/TargetPressBridge.js +55 -7
  66. package/lib/commonjs/runtime/TargetPressBridge.js.map +1 -1
  67. package/lib/commonjs/runtime/UserflowOverlay.js +5 -11
  68. package/lib/commonjs/runtime/UserflowOverlay.js.map +1 -1
  69. package/lib/commonjs/runtime/UserflowProvider.js +66 -40
  70. package/lib/commonjs/runtime/UserflowProvider.js.map +1 -1
  71. package/lib/commonjs/runtime/bannerEngine.js +8 -2
  72. package/lib/commonjs/runtime/bannerEngine.js.map +1 -1
  73. package/lib/commonjs/runtime/bannerFlowSpacer.js +71 -0
  74. package/lib/commonjs/runtime/bannerFlowSpacer.js.map +1 -0
  75. package/lib/commonjs/runtime/flowEngine.js +11 -0
  76. package/lib/commonjs/runtime/flowEngine.js.map +1 -1
  77. package/lib/commonjs/runtime/previewStart.js +39 -0
  78. package/lib/commonjs/runtime/previewStart.js.map +1 -0
  79. package/lib/commonjs/runtime/pushBannerLayoutContext.js +98 -0
  80. package/lib/commonjs/runtime/pushBannerLayoutContext.js.map +1 -0
  81. package/lib/commonjs/screenTracker.js +12 -0
  82. package/lib/commonjs/screenTracker.js.map +1 -1
  83. package/lib/commonjs/surfaces/BannerBar.js +79 -61
  84. package/lib/commonjs/surfaces/BannerBar.js.map +1 -1
  85. package/lib/commonjs/surfaces/BannerSurface.js +26 -0
  86. package/lib/commonjs/surfaces/BannerSurface.js.map +1 -1
  87. package/lib/commonjs/surfaces/TooltipSurface.js +43 -41
  88. package/lib/commonjs/surfaces/TooltipSurface.js.map +1 -1
  89. package/lib/commonjs/surfaces/bannerBarLayout.js +77 -10
  90. package/lib/commonjs/surfaces/bannerBarLayout.js.map +1 -1
  91. package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js +112 -63
  92. package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
  93. package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js +59 -11
  94. package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
  95. package/lib/commonjs/surfaces/hostAccessibilityTrap.js +28 -0
  96. package/lib/commonjs/surfaces/hostAccessibilityTrap.js.map +1 -0
  97. package/lib/commonjs/surfaces/resolveBannerTheme.js +6 -3
  98. package/lib/commonjs/surfaces/resolveBannerTheme.js.map +1 -1
  99. package/lib/commonjs/surfaces/tooltip/TooltipBubble.js +3 -2
  100. package/lib/commonjs/surfaces/tooltip/TooltipBubble.js.map +1 -1
  101. package/lib/commonjs/surfaces/tooltip/targetSearchSchedule.js +134 -0
  102. package/lib/commonjs/surfaces/tooltip/targetSearchSchedule.js.map +1 -0
  103. package/lib/commonjs/version.js +1 -1
  104. package/lib/module/anchors/AutoAnchorRegistry.js +145 -35
  105. package/lib/module/anchors/AutoAnchorRegistry.js.map +1 -1
  106. package/lib/module/anchors/animationFrame.js +29 -2
  107. package/lib/module/anchors/animationFrame.js.map +1 -1
  108. package/lib/module/anchors/appActive.js +57 -0
  109. package/lib/module/anchors/appActive.js.map +1 -0
  110. package/lib/module/anchors/elementConditionEvaluator.js +26 -9
  111. package/lib/module/anchors/elementConditionEvaluator.js.map +1 -1
  112. package/lib/module/anchors/elementTapMonitor.js +67 -12
  113. package/lib/module/anchors/elementTapMonitor.js.map +1 -1
  114. package/lib/module/anchors/elementTargetMatch.js +32 -7
  115. package/lib/module/anchors/elementTargetMatch.js.map +1 -1
  116. package/lib/module/anchors/fiber/fiberInternals.js +65 -0
  117. package/lib/module/anchors/fiber/fiberInternals.js.map +1 -1
  118. package/lib/module/anchors/fiber/indexHostFibers.js +4 -3
  119. package/lib/module/anchors/fiber/indexHostFibers.js.map +1 -1
  120. package/lib/module/anchors/fiber/resolveFiberPath.js +30 -15
  121. package/lib/module/anchors/fiber/resolveFiberPath.js.map +1 -1
  122. package/lib/module/anchors/fiberAnchorCache.js +19 -0
  123. package/lib/module/anchors/fiberAnchorCache.js.map +1 -1
  124. package/lib/module/anchors/gestureHandlerHandoff.js +298 -0
  125. package/lib/module/anchors/gestureHandlerHandoff.js.map +1 -0
  126. package/lib/module/anchors/resolveAnchorHost.js +1 -0
  127. package/lib/module/anchors/resolveAnchorHost.js.map +1 -1
  128. package/lib/module/anchors/resolveAutoAnchorHost.js +11 -6
  129. package/lib/module/anchors/resolveAutoAnchorHost.js.map +1 -1
  130. package/lib/module/anchors/resolveElementTargets.js +63 -8
  131. package/lib/module/anchors/resolveElementTargets.js.map +1 -1
  132. package/lib/module/anchors/semanticMatcher.js +43 -21
  133. package/lib/module/anchors/semanticMatcher.js.map +1 -1
  134. package/lib/module/anchors/tapSlop.js +11 -0
  135. package/lib/module/anchors/tapSlop.js.map +1 -0
  136. package/lib/module/anchors/targetPressMonitor.js +53 -8
  137. package/lib/module/anchors/targetPressMonitor.js.map +1 -1
  138. package/lib/module/authoring/fingerprintFromInstance.js +6 -10
  139. package/lib/module/authoring/fingerprintFromInstance.js.map +1 -1
  140. package/lib/module/content/RichContentRenderer.js +3 -3
  141. package/lib/module/content/RichContentRenderer.js.map +1 -1
  142. package/lib/module/content/openSafeUrl.js +3 -1
  143. package/lib/module/content/openSafeUrl.js.map +1 -1
  144. package/lib/module/expo-router/useUserflowExpoRouter.js +7 -1
  145. package/lib/module/expo-router/useUserflowExpoRouter.js.map +1 -1
  146. package/lib/module/index.js +22 -7
  147. package/lib/module/index.js.map +1 -1
  148. package/lib/module/installCheck.js +2 -2
  149. package/lib/module/installCheck.js.map +1 -1
  150. package/lib/module/navigation/navigateUrlUtils.js +94 -0
  151. package/lib/module/navigation/navigateUrlUtils.js.map +1 -0
  152. package/lib/module/navigation/openExternalNavigateUrl.js +11 -0
  153. package/lib/module/navigation/openExternalNavigateUrl.js.map +1 -0
  154. package/lib/module/navigation/openFlowNavigateUrl.js +22 -36
  155. package/lib/module/navigation/openFlowNavigateUrl.js.map +1 -1
  156. package/lib/module/pairing/PairingController.js +3 -1
  157. package/lib/module/pairing/PairingController.js.map +1 -1
  158. package/lib/module/runtime/BannerHost.js +38 -29
  159. package/lib/module/runtime/BannerHost.js.map +1 -1
  160. package/lib/module/runtime/TargetPressBridge.js +56 -8
  161. package/lib/module/runtime/TargetPressBridge.js.map +1 -1
  162. package/lib/module/runtime/UserflowOverlay.js +5 -11
  163. package/lib/module/runtime/UserflowOverlay.js.map +1 -1
  164. package/lib/module/runtime/UserflowProvider.js +66 -40
  165. package/lib/module/runtime/UserflowProvider.js.map +1 -1
  166. package/lib/module/runtime/bannerEngine.js +7 -2
  167. package/lib/module/runtime/bannerEngine.js.map +1 -1
  168. package/lib/module/runtime/bannerFlowSpacer.js +65 -0
  169. package/lib/module/runtime/bannerFlowSpacer.js.map +1 -0
  170. package/lib/module/runtime/flowEngine.js +11 -0
  171. package/lib/module/runtime/flowEngine.js.map +1 -1
  172. package/lib/module/runtime/previewStart.js +35 -0
  173. package/lib/module/runtime/previewStart.js.map +1 -0
  174. package/lib/module/runtime/pushBannerLayoutContext.js +91 -0
  175. package/lib/module/runtime/pushBannerLayoutContext.js.map +1 -0
  176. package/lib/module/screenTracker.js +11 -0
  177. package/lib/module/screenTracker.js.map +1 -1
  178. package/lib/module/surfaces/BannerBar.js +80 -62
  179. package/lib/module/surfaces/BannerBar.js.map +1 -1
  180. package/lib/module/surfaces/BannerSurface.js +26 -0
  181. package/lib/module/surfaces/BannerSurface.js.map +1 -1
  182. package/lib/module/surfaces/TooltipSurface.js +44 -42
  183. package/lib/module/surfaces/TooltipSurface.js.map +1 -1
  184. package/lib/module/surfaces/bannerBarLayout.js +72 -11
  185. package/lib/module/surfaces/bannerBarLayout.js.map +1 -1
  186. package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js +113 -64
  187. package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
  188. package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js +59 -11
  189. package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
  190. package/lib/module/surfaces/hostAccessibilityTrap.js +24 -0
  191. package/lib/module/surfaces/hostAccessibilityTrap.js.map +1 -0
  192. package/lib/module/surfaces/resolveBannerTheme.js +6 -3
  193. package/lib/module/surfaces/resolveBannerTheme.js.map +1 -1
  194. package/lib/module/surfaces/tooltip/TooltipBubble.js +3 -2
  195. package/lib/module/surfaces/tooltip/TooltipBubble.js.map +1 -1
  196. package/lib/module/surfaces/tooltip/targetSearchSchedule.js +128 -0
  197. package/lib/module/surfaces/tooltip/targetSearchSchedule.js.map +1 -0
  198. package/lib/module/version.js +1 -1
  199. package/lib/typescript/commonjs/src/anchors/AutoAnchorRegistry.d.ts +36 -2
  200. package/lib/typescript/commonjs/src/anchors/AutoAnchorRegistry.d.ts.map +1 -1
  201. package/lib/typescript/commonjs/src/anchors/animationFrame.d.ts +0 -4
  202. package/lib/typescript/commonjs/src/anchors/animationFrame.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/anchors/appActive.d.ts +8 -0
  204. package/lib/typescript/commonjs/src/anchors/appActive.d.ts.map +1 -0
  205. package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts +17 -0
  206. package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts +7 -0
  208. package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts.map +1 -1
  209. package/lib/typescript/commonjs/src/anchors/fiber/fiberInternals.d.ts +7 -0
  210. package/lib/typescript/commonjs/src/anchors/fiber/fiberInternals.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/src/anchors/fiber/indexHostFibers.d.ts +3 -2
  212. package/lib/typescript/commonjs/src/anchors/fiber/indexHostFibers.d.ts.map +1 -1
  213. package/lib/typescript/commonjs/src/anchors/fiber/resolveFiberPath.d.ts +3 -0
  214. package/lib/typescript/commonjs/src/anchors/fiber/resolveFiberPath.d.ts.map +1 -1
  215. package/lib/typescript/commonjs/src/anchors/fiberAnchorCache.d.ts +5 -0
  216. package/lib/typescript/commonjs/src/anchors/fiberAnchorCache.d.ts.map +1 -1
  217. package/lib/typescript/commonjs/src/anchors/gestureHandlerHandoff.d.ts +70 -0
  218. package/lib/typescript/commonjs/src/anchors/gestureHandlerHandoff.d.ts.map +1 -0
  219. package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts +1 -0
  220. package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts.map +1 -1
  221. package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts +11 -4
  222. package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
  223. package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts.map +1 -1
  224. package/lib/typescript/commonjs/src/anchors/semanticMatcher.d.ts +2 -0
  225. package/lib/typescript/commonjs/src/anchors/semanticMatcher.d.ts.map +1 -1
  226. package/lib/typescript/commonjs/src/anchors/tapSlop.d.ts +9 -0
  227. package/lib/typescript/commonjs/src/anchors/tapSlop.d.ts.map +1 -0
  228. package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts +13 -8
  229. package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts.map +1 -1
  230. package/lib/typescript/commonjs/src/authoring/fingerprintFromInstance.d.ts.map +1 -1
  231. package/lib/typescript/commonjs/src/content/RichContentRenderer.d.ts.map +1 -1
  232. package/lib/typescript/commonjs/src/content/openSafeUrl.d.ts.map +1 -1
  233. package/lib/typescript/commonjs/src/expo-router/useUserflowExpoRouter.d.ts.map +1 -1
  234. package/lib/typescript/commonjs/src/index.d.ts +6 -5
  235. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  236. package/lib/typescript/commonjs/src/installCheck.d.ts +1 -1
  237. package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -1
  238. package/lib/typescript/commonjs/src/navigation/navigateUrlUtils.d.ts +15 -0
  239. package/lib/typescript/commonjs/src/navigation/navigateUrlUtils.d.ts.map +1 -0
  240. package/lib/typescript/commonjs/src/navigation/openExternalNavigateUrl.d.ts +3 -0
  241. package/lib/typescript/commonjs/src/navigation/openExternalNavigateUrl.d.ts.map +1 -0
  242. package/lib/typescript/commonjs/src/navigation/openFlowNavigateUrl.d.ts +2 -5
  243. package/lib/typescript/commonjs/src/navigation/openFlowNavigateUrl.d.ts.map +1 -1
  244. package/lib/typescript/commonjs/src/pairing/PairingController.d.ts +6 -0
  245. package/lib/typescript/commonjs/src/pairing/PairingController.d.ts.map +1 -1
  246. package/lib/typescript/commonjs/src/runtime/BannerHost.d.ts.map +1 -1
  247. package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts +3 -0
  248. package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts.map +1 -1
  249. package/lib/typescript/commonjs/src/runtime/UserflowOverlay.d.ts.map +1 -1
  250. package/lib/typescript/commonjs/src/runtime/UserflowProvider.d.ts.map +1 -1
  251. package/lib/typescript/commonjs/src/runtime/bannerEngine.d.ts +5 -1
  252. package/lib/typescript/commonjs/src/runtime/bannerEngine.d.ts.map +1 -1
  253. package/lib/typescript/commonjs/src/runtime/bannerFlowSpacer.d.ts +15 -0
  254. package/lib/typescript/commonjs/src/runtime/bannerFlowSpacer.d.ts.map +1 -0
  255. package/lib/typescript/commonjs/src/runtime/flowEngine.d.ts.map +1 -1
  256. package/lib/typescript/commonjs/src/runtime/previewStart.d.ts +13 -0
  257. package/lib/typescript/commonjs/src/runtime/previewStart.d.ts.map +1 -0
  258. package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts +17 -0
  259. package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts.map +1 -0
  260. package/lib/typescript/commonjs/src/screenTracker.d.ts +6 -0
  261. package/lib/typescript/commonjs/src/screenTracker.d.ts.map +1 -1
  262. package/lib/typescript/commonjs/src/surfaces/BannerBar.d.ts +3 -1
  263. package/lib/typescript/commonjs/src/surfaces/BannerBar.d.ts.map +1 -1
  264. package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts +5 -1
  265. package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts.map +1 -1
  266. package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts +1 -1
  267. package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts.map +1 -1
  268. package/lib/typescript/commonjs/src/surfaces/bannerBarLayout.d.ts +17 -0
  269. package/lib/typescript/commonjs/src/surfaces/bannerBarLayout.d.ts.map +1 -1
  270. package/lib/typescript/commonjs/src/surfaces/bannerWebView/BannerWebViewBar.d.ts +3 -1
  271. package/lib/typescript/commonjs/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
  272. package/lib/typescript/commonjs/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
  273. package/lib/typescript/commonjs/src/surfaces/hostAccessibilityTrap.d.ts +14 -0
  274. package/lib/typescript/commonjs/src/surfaces/hostAccessibilityTrap.d.ts.map +1 -0
  275. package/lib/typescript/commonjs/src/surfaces/resolveBannerTheme.d.ts +4 -1
  276. package/lib/typescript/commonjs/src/surfaces/resolveBannerTheme.d.ts.map +1 -1
  277. package/lib/typescript/commonjs/src/surfaces/tooltip/TooltipBubble.d.ts +7 -1
  278. package/lib/typescript/commonjs/src/surfaces/tooltip/TooltipBubble.d.ts.map +1 -1
  279. package/lib/typescript/commonjs/src/surfaces/tooltip/targetSearchSchedule.d.ts +60 -0
  280. package/lib/typescript/commonjs/src/surfaces/tooltip/targetSearchSchedule.d.ts.map +1 -0
  281. package/lib/typescript/commonjs/src/version.d.ts +1 -1
  282. package/lib/typescript/module/src/anchors/AutoAnchorRegistry.d.ts +36 -2
  283. package/lib/typescript/module/src/anchors/AutoAnchorRegistry.d.ts.map +1 -1
  284. package/lib/typescript/module/src/anchors/animationFrame.d.ts +0 -4
  285. package/lib/typescript/module/src/anchors/animationFrame.d.ts.map +1 -1
  286. package/lib/typescript/module/src/anchors/appActive.d.ts +8 -0
  287. package/lib/typescript/module/src/anchors/appActive.d.ts.map +1 -0
  288. package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts +17 -0
  289. package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts.map +1 -1
  290. package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts +7 -0
  291. package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts.map +1 -1
  292. package/lib/typescript/module/src/anchors/fiber/fiberInternals.d.ts +7 -0
  293. package/lib/typescript/module/src/anchors/fiber/fiberInternals.d.ts.map +1 -1
  294. package/lib/typescript/module/src/anchors/fiber/indexHostFibers.d.ts +3 -2
  295. package/lib/typescript/module/src/anchors/fiber/indexHostFibers.d.ts.map +1 -1
  296. package/lib/typescript/module/src/anchors/fiber/resolveFiberPath.d.ts +3 -0
  297. package/lib/typescript/module/src/anchors/fiber/resolveFiberPath.d.ts.map +1 -1
  298. package/lib/typescript/module/src/anchors/fiberAnchorCache.d.ts +5 -0
  299. package/lib/typescript/module/src/anchors/fiberAnchorCache.d.ts.map +1 -1
  300. package/lib/typescript/module/src/anchors/gestureHandlerHandoff.d.ts +70 -0
  301. package/lib/typescript/module/src/anchors/gestureHandlerHandoff.d.ts.map +1 -0
  302. package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts +1 -0
  303. package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts.map +1 -1
  304. package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts +11 -4
  305. package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
  306. package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts.map +1 -1
  307. package/lib/typescript/module/src/anchors/semanticMatcher.d.ts +2 -0
  308. package/lib/typescript/module/src/anchors/semanticMatcher.d.ts.map +1 -1
  309. package/lib/typescript/module/src/anchors/tapSlop.d.ts +9 -0
  310. package/lib/typescript/module/src/anchors/tapSlop.d.ts.map +1 -0
  311. package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts +13 -8
  312. package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts.map +1 -1
  313. package/lib/typescript/module/src/authoring/fingerprintFromInstance.d.ts.map +1 -1
  314. package/lib/typescript/module/src/content/RichContentRenderer.d.ts.map +1 -1
  315. package/lib/typescript/module/src/content/openSafeUrl.d.ts.map +1 -1
  316. package/lib/typescript/module/src/expo-router/useUserflowExpoRouter.d.ts.map +1 -1
  317. package/lib/typescript/module/src/index.d.ts +6 -5
  318. package/lib/typescript/module/src/index.d.ts.map +1 -1
  319. package/lib/typescript/module/src/installCheck.d.ts +1 -1
  320. package/lib/typescript/module/src/installCheck.d.ts.map +1 -1
  321. package/lib/typescript/module/src/navigation/navigateUrlUtils.d.ts +15 -0
  322. package/lib/typescript/module/src/navigation/navigateUrlUtils.d.ts.map +1 -0
  323. package/lib/typescript/module/src/navigation/openExternalNavigateUrl.d.ts +3 -0
  324. package/lib/typescript/module/src/navigation/openExternalNavigateUrl.d.ts.map +1 -0
  325. package/lib/typescript/module/src/navigation/openFlowNavigateUrl.d.ts +2 -5
  326. package/lib/typescript/module/src/navigation/openFlowNavigateUrl.d.ts.map +1 -1
  327. package/lib/typescript/module/src/pairing/PairingController.d.ts +6 -0
  328. package/lib/typescript/module/src/pairing/PairingController.d.ts.map +1 -1
  329. package/lib/typescript/module/src/runtime/BannerHost.d.ts.map +1 -1
  330. package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts +3 -0
  331. package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts.map +1 -1
  332. package/lib/typescript/module/src/runtime/UserflowOverlay.d.ts.map +1 -1
  333. package/lib/typescript/module/src/runtime/UserflowProvider.d.ts.map +1 -1
  334. package/lib/typescript/module/src/runtime/bannerEngine.d.ts +5 -1
  335. package/lib/typescript/module/src/runtime/bannerEngine.d.ts.map +1 -1
  336. package/lib/typescript/module/src/runtime/bannerFlowSpacer.d.ts +15 -0
  337. package/lib/typescript/module/src/runtime/bannerFlowSpacer.d.ts.map +1 -0
  338. package/lib/typescript/module/src/runtime/flowEngine.d.ts.map +1 -1
  339. package/lib/typescript/module/src/runtime/previewStart.d.ts +13 -0
  340. package/lib/typescript/module/src/runtime/previewStart.d.ts.map +1 -0
  341. package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts +17 -0
  342. package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts.map +1 -0
  343. package/lib/typescript/module/src/screenTracker.d.ts +6 -0
  344. package/lib/typescript/module/src/screenTracker.d.ts.map +1 -1
  345. package/lib/typescript/module/src/surfaces/BannerBar.d.ts +3 -1
  346. package/lib/typescript/module/src/surfaces/BannerBar.d.ts.map +1 -1
  347. package/lib/typescript/module/src/surfaces/BannerSurface.d.ts +5 -1
  348. package/lib/typescript/module/src/surfaces/BannerSurface.d.ts.map +1 -1
  349. package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts +1 -1
  350. package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts.map +1 -1
  351. package/lib/typescript/module/src/surfaces/bannerBarLayout.d.ts +17 -0
  352. package/lib/typescript/module/src/surfaces/bannerBarLayout.d.ts.map +1 -1
  353. package/lib/typescript/module/src/surfaces/bannerWebView/BannerWebViewBar.d.ts +3 -1
  354. package/lib/typescript/module/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
  355. package/lib/typescript/module/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
  356. package/lib/typescript/module/src/surfaces/hostAccessibilityTrap.d.ts +14 -0
  357. package/lib/typescript/module/src/surfaces/hostAccessibilityTrap.d.ts.map +1 -0
  358. package/lib/typescript/module/src/surfaces/resolveBannerTheme.d.ts +4 -1
  359. package/lib/typescript/module/src/surfaces/resolveBannerTheme.d.ts.map +1 -1
  360. package/lib/typescript/module/src/surfaces/tooltip/TooltipBubble.d.ts +7 -1
  361. package/lib/typescript/module/src/surfaces/tooltip/TooltipBubble.d.ts.map +1 -1
  362. package/lib/typescript/module/src/surfaces/tooltip/targetSearchSchedule.d.ts +60 -0
  363. package/lib/typescript/module/src/surfaces/tooltip/targetSearchSchedule.d.ts.map +1 -0
  364. package/lib/typescript/module/src/version.d.ts +1 -1
  365. package/package.json +1 -1
  366. package/src/anchors/AutoAnchorRegistry.ts +166 -35
  367. package/src/anchors/animationFrame.ts +31 -1
  368. package/src/anchors/appActive.ts +63 -0
  369. package/src/anchors/elementConditionEvaluator.ts +29 -12
  370. package/src/anchors/elementTapMonitor.ts +68 -10
  371. package/src/anchors/elementTargetMatch.ts +34 -7
  372. package/src/anchors/fiber/fiberInternals.ts +68 -0
  373. package/src/anchors/fiber/indexHostFibers.ts +8 -3
  374. package/src/anchors/fiber/resolveFiberPath.ts +25 -16
  375. package/src/anchors/fiberAnchorCache.ts +19 -0
  376. package/src/anchors/gestureHandlerHandoff.ts +356 -0
  377. package/src/anchors/resolveAnchorHost.ts +2 -0
  378. package/src/anchors/resolveAutoAnchorHost.ts +18 -8
  379. package/src/anchors/resolveElementTargets.ts +64 -7
  380. package/src/anchors/semanticMatcher.ts +48 -27
  381. package/src/anchors/tapSlop.ts +18 -0
  382. package/src/anchors/targetPressMonitor.ts +56 -19
  383. package/src/authoring/fingerprintFromInstance.ts +4 -5
  384. package/src/content/RichContentRenderer.tsx +4 -3
  385. package/src/content/openSafeUrl.ts +3 -1
  386. package/src/expo-router/useUserflowExpoRouter.ts +10 -1
  387. package/src/index.ts +20 -9
  388. package/src/installCheck.ts +4 -4
  389. package/src/navigation/navigateUrlUtils.ts +100 -0
  390. package/src/navigation/openExternalNavigateUrl.ts +9 -0
  391. package/src/navigation/openFlowNavigateUrl.ts +36 -36
  392. package/src/pairing/PairingController.ts +9 -1
  393. package/src/runtime/BannerHost.tsx +41 -23
  394. package/src/runtime/TargetPressBridge.tsx +66 -9
  395. package/src/runtime/UserflowOverlay.tsx +7 -9
  396. package/src/runtime/UserflowProvider.tsx +56 -32
  397. package/src/runtime/bannerEngine.ts +10 -2
  398. package/src/runtime/bannerFlowSpacer.tsx +70 -0
  399. package/src/runtime/flowEngine.ts +11 -0
  400. package/src/runtime/previewStart.ts +49 -0
  401. package/src/runtime/pushBannerLayoutContext.tsx +121 -0
  402. package/src/screenTracker.ts +11 -0
  403. package/src/surfaces/BannerBar.tsx +93 -55
  404. package/src/surfaces/BannerSurface.tsx +59 -3
  405. package/src/surfaces/TooltipSurface.tsx +63 -51
  406. package/src/surfaces/bannerBarLayout.ts +80 -7
  407. package/src/surfaces/bannerWebView/BannerWebViewBar.tsx +149 -57
  408. package/src/surfaces/bannerWebView/buildCustomBannerHtml.ts +63 -11
  409. package/src/surfaces/hostAccessibilityTrap.ts +25 -0
  410. package/src/surfaces/resolveBannerTheme.ts +14 -3
  411. package/src/surfaces/tooltip/TooltipBubble.tsx +8 -1
  412. package/src/surfaces/tooltip/targetSearchSchedule.ts +140 -0
  413. package/src/version.ts +1 -1
  414. package/vendor/core/dist/engine/BannerSessionEngine.d.ts +29 -2
  415. package/vendor/core/dist/engine/BannerSessionEngine.d.ts.map +1 -1
  416. package/vendor/core/dist/engine/BannerSessionEngine.js +87 -6
  417. package/vendor/core/dist/engine/BannerSessionEngine.js.map +1 -1
  418. package/vendor/core/dist/engine/ClientConditionEngine.d.ts.map +1 -1
  419. package/vendor/core/dist/engine/ClientConditionEngine.js +30 -7
  420. package/vendor/core/dist/engine/ClientConditionEngine.js.map +1 -1
  421. package/vendor/core/dist/engine/PairingEngine.d.ts +6 -1
  422. package/vendor/core/dist/engine/PairingEngine.d.ts.map +1 -1
  423. package/vendor/core/dist/engine/PairingEngine.js +23 -3
  424. package/vendor/core/dist/engine/PairingEngine.js.map +1 -1
  425. package/vendor/core/dist/engine/handleActions.d.ts.map +1 -1
  426. package/vendor/core/dist/engine/handleActions.js +67 -2
  427. package/vendor/core/dist/engine/handleActions.js.map +1 -1
  428. package/vendor/core/dist/version.d.ts +1 -1
  429. package/vendor/core/dist/version.js +1 -1
  430. package/vendor/core/package.json +1 -1
  431. package/vendor/protocol/package.json +1 -1
@@ -1,11 +1,10 @@
1
1
  import type { BannerSessionEngine } from '@userflow-tech/core';
2
- import type { BannerSessionFragment } from '@userflow-tech/protocol';
3
2
  import * as React from 'react';
4
3
  import { StyleSheet, View } from 'react-native';
5
4
 
6
5
  import { BannerSurface } from '../surfaces/BannerSurface';
7
6
  import { bannerOverlayHostPadding } from '../surfaces/bannerBarLayout';
8
- import { resolveBannerBackgroundColor } from '../surfaces/resolveBannerTheme';
7
+ import { bannerNeedsWebView } from '../surfaces/bannerWebView/bannerNeedsWebView';
9
8
  import { useUserflowSafeAreaInsets } from '../surfaces/safeArea';
10
9
 
11
10
  import { SdkErrorBoundary } from './SdkErrorBoundary';
@@ -50,53 +49,72 @@ export function BannerHost({
50
49
  if (edgeFilter && edge !== edgeFilter) return null;
51
50
 
52
51
  const floats = banner.overlay !== false;
53
- if ((placement === 'overlay') !== floats) return null;
52
+ const usesWebView = bannerNeedsWebView(banner, session.version.theme);
53
+ // Custom-css / eval WebViews paint in the overlay host (same as userflow.js). A second
54
+ // WKWebView in the flow slot often fails to load after the first banner dismisses.
55
+ const hostAsOverlay = floats || usesWebView;
56
+ if ((placement === 'overlay') !== hostAsOverlay) return null;
57
+
58
+ const pushLayoutEdge = placement === 'flow' && !hostAsOverlay ? edge : null;
59
+ // Builder overlay:true floats below the status bar. Push-layout WebViews in the overlay
60
+ // host still paint safe-area chrome (overlay:false + custom CSS).
61
+ const overlaySafeEdge = hostAsOverlay && !floats ? edge : null;
62
+
63
+ const surface = (
64
+ <BannerSurface
65
+ session={session}
66
+ pushLayoutEdge={pushLayoutEdge}
67
+ overlaySafeEdge={overlaySafeEdge}
68
+ onActions={(actions) => engine.handleActions(actions)}
69
+ onDismiss={(endReason) => {
70
+ void engine.endBanner(endReason);
71
+ }}
72
+ />
73
+ );
54
74
 
55
75
  const bar = (
56
76
  <SdkErrorBoundary scope="banner" resetKey={session.id}>
57
- <BannerSurface
58
- session={session}
59
- onActions={(actions) => engine.handleActions(actions)}
60
- onDismiss={(endReason) => {
61
- void engine.endBanner(endReason);
62
- }}
63
- />
77
+ {placement === 'flow' ? (
78
+ <View testID="userflow-banner-flow" style={styles.flow}>
79
+ {surface}
80
+ </View>
81
+ ) : (
82
+ surface
83
+ )}
64
84
  </SdkErrorBoundary>
65
85
  );
66
86
 
67
87
  if (placement === 'flow') {
68
- const backgroundColor = bannerBackground(session);
69
- const safePad = edge === 'top' ? { paddingTop: insets.top } : { paddingBottom: insets.bottom };
70
- return (
71
- <View testID="userflow-banner-flow" style={[styles.flow, safePad, { backgroundColor }]}>
72
- {bar}
73
- </View>
74
- );
88
+ return bar;
75
89
  }
76
90
 
77
- const edgeStyle = edge === 'top' ? { top: insets.top } : { bottom: insets.bottom };
91
+ const edgeStyle =
92
+ edge === 'top' ? { top: floats ? insets.top : 0 } : { bottom: floats ? insets.bottom : 0 };
78
93
 
79
94
  return (
80
95
  <View
81
96
  pointerEvents="box-none"
82
97
  style={[styles.pin, edgeStyle, bannerOverlayHostPadding(insets)]}
83
98
  >
84
- {bar}
99
+ <View pointerEvents="auto" style={styles.pinInteractive}>
100
+ {bar}
101
+ </View>
85
102
  </View>
86
103
  );
87
104
  }
88
105
 
89
- function bannerBackground(session: BannerSessionFragment): string {
90
- return resolveBannerBackgroundColor(session.version.theme);
91
- }
92
-
93
106
  const styles = StyleSheet.create({
94
107
  pin: {
95
108
  position: 'absolute',
96
109
  left: 0,
97
110
  right: 0,
111
+ zIndex: 10,
112
+ elevation: 10,
98
113
  },
99
114
  flow: {
100
115
  alignSelf: 'stretch',
101
116
  },
117
+ pinInteractive: {
118
+ alignSelf: 'stretch',
119
+ },
102
120
  });
@@ -2,10 +2,12 @@ import * as React from 'react';
2
2
  import { StyleSheet, View, type GestureResponderEvent } from 'react-native';
3
3
 
4
4
  import { elementTapMonitor } from '../anchors/elementTapMonitor';
5
+ import type { AnyFiber } from '../anchors/fiber/fiberInternals';
5
6
  import { touchBelongsToEvent, touchTargetFiber } from '../anchors/fiber/touchTargetFiber';
6
- import { targetPressMonitor } from '../anchors/targetPressMonitor';
7
+ import { collectGestureHandlerTags } from '../anchors/gestureHandlerHandoff';
8
+ import { targetPressMonitor, type TouchIdentifier } from '../anchors/targetPressMonitor';
7
9
 
8
- import { changedTouchPoints, forEachChangedTouch } from './changedTouchPoints';
10
+ import { forEachChangedTouch } from './changedTouchPoints';
9
11
  import { isolateSync } from './isolateCall';
10
12
 
11
13
  export interface TargetPressBridgeProps {
@@ -25,6 +27,9 @@ export interface TargetPressBridgeProps {
25
27
  * Preview-chrome double taps are observed by `DoubleTapBridge` around the
26
28
  * whole provider tree — this bridge never sees overlay touches.
27
29
  *
30
+ * A touch that gesture-handler cancels is not dropped. The start is kept and
31
+ * counts as a tap if that handler later ends from ACTIVE.
32
+ *
28
33
  * Does not claim the gesture responder.
29
34
  */
30
35
  export function TargetPressBridge({ children }: TargetPressBridgeProps): React.ReactElement {
@@ -57,15 +62,23 @@ export function TargetPressBridge({ children }: TargetPressBridgeProps): React.R
57
62
 
58
63
  const onTouchCancel = React.useCallback((event: GestureResponderEvent) => {
59
64
  isolateSync('target-press', () => {
60
- const points = changedTouchPoints(event);
61
- if (points.length === 0) {
62
- targetPressMonitor.cancelTouch();
63
- elementTapMonitor.cancelTouch();
65
+ const changed = event.nativeEvent.changedTouches;
66
+ if (!changed || changed.length === 0) {
67
+ const identifiers = inProgressIdentifiers();
68
+ if (identifiers.length === 0) {
69
+ targetPressMonitor.cancelTouch();
70
+ elementTapMonitor.cancelTouch();
71
+ return;
72
+ }
73
+ // No per-finger target on this event; every finger shares its view.
74
+ const fallback = touchTargetFiber(event);
75
+ for (const identifier of identifiers) {
76
+ handOffCancelledTouch(event, identifier, fallback);
77
+ }
64
78
  return;
65
79
  }
66
- for (const touch of points) {
67
- targetPressMonitor.cancelTouch(touch.identifier);
68
- elementTapMonitor.cancelTouch(touch.identifier);
80
+ for (const touch of changed) {
81
+ handOffCancelledTouch(event, touch.identifier, null);
69
82
  }
70
83
  });
71
84
  }, []);
@@ -84,6 +97,50 @@ export function TargetPressBridge({ children }: TargetPressBridgeProps): React.R
84
97
  );
85
98
  }
86
99
 
100
+ /**
101
+ * A finger gesture-handler cancelled. Keep it if the touched view belongs to a
102
+ * gesture-handler control. A scroll container, or a touch with no handler, is
103
+ * dropped the way a system cancel is.
104
+ * `fallbackFiber` is used when this event names no per-finger target.
105
+ */
106
+ function handOffCancelledTouch(
107
+ event: GestureResponderEvent,
108
+ identifier: TouchIdentifier,
109
+ fallbackFiber: AnyFiber | null,
110
+ ): void {
111
+ const elementHas = elementTapMonitor.hasTouch(identifier);
112
+ const tooltipHas = targetPressMonitor.hasTouch(identifier);
113
+ if (!elementHas && !tooltipHas) {
114
+ elementTapMonitor.cancelTouch(identifier);
115
+ targetPressMonitor.cancelTouch(identifier);
116
+ return;
117
+ }
118
+ const stored = elementHas ? elementTapMonitor.startFiber(identifier) : null;
119
+ const fiber = stored ?? fallbackFiber ?? touchTargetFiber(event, identifier);
120
+ const tags = collectGestureHandlerTags(fiber);
121
+ if (tags.length === 0) {
122
+ elementTapMonitor.cancelTouch(identifier);
123
+ targetPressMonitor.cancelTouch(identifier);
124
+ return;
125
+ }
126
+ if (elementHas) elementTapMonitor.handOffTouch(identifier, tags);
127
+ if (tooltipHas) targetPressMonitor.handOffTouch(identifier, tags);
128
+ }
129
+
130
+ function inProgressIdentifiers(): TouchIdentifier[] {
131
+ const seen = new Set<TouchIdentifier>();
132
+ const identifiers: TouchIdentifier[] = [];
133
+ for (const identifier of [
134
+ ...elementTapMonitor.inProgressIdentifiers(),
135
+ ...targetPressMonitor.inProgressIdentifiers(),
136
+ ]) {
137
+ if (seen.has(identifier)) continue;
138
+ seen.add(identifier);
139
+ identifiers.push(identifier);
140
+ }
141
+ return identifiers;
142
+ }
143
+
87
144
  const styles = StyleSheet.create({
88
145
  root: {
89
146
  flex: 1,
@@ -8,6 +8,7 @@ import { PreviewModeBanner } from '../pairing/PreviewModeBanner';
8
8
  import { getPairingController } from '../pairing/pairingRuntime';
9
9
  import { usePairingState } from '../pairing/usePairingState';
10
10
  import { usePreviewBarHold } from '../pairing/usePreviewBarHold';
11
+ import { stepTrapsHostAccessibility } from '../surfaces/hostAccessibilityTrap';
11
12
 
12
13
  import { BannerHost } from './BannerHost';
13
14
  import { FlowRenderer } from './FlowRenderer';
@@ -57,14 +58,7 @@ export function UserflowOverlay({
57
58
  if (!session || !currentStep) {
58
59
  return false;
59
60
  }
60
- const appearance = String(currentStep.appearance).toUpperCase();
61
- if (appearance === 'HIDDEN') {
62
- return false;
63
- }
64
- if (appearance !== 'TOOLTIP') {
65
- return true;
66
- }
67
- return currentStep.tooltipTargetBlocked;
61
+ return stepTrapsHostAccessibility(currentStep);
68
62
  }, [pickerActive, previewConfirming, session, currentStep]);
69
63
 
70
64
  React.useLayoutEffect(() => {
@@ -105,13 +99,17 @@ export function UserflowOverlay({
105
99
  * captureScreen would otherwise include the modal or tooltip.
106
100
  * Remounting reads the same session back.
107
101
  */}
108
- <BannerHost engine={bannerEngine} hidden={pickerActive} />
109
102
  {pickerActive ? null : (
110
103
  <FlowRenderer
111
104
  engine={engine}
112
105
  previewRestartGeneration={pairingState.previewRestartGeneration}
113
106
  />
114
107
  )}
108
+ {/*
109
+ * After flow chrome so BODY_FIRST / BODY_LAST overlay banners stay visible
110
+ * while a tour step (modal, tooltip, condition wait) is still active.
111
+ */}
112
+ <BannerHost engine={bannerEngine} hidden={pickerActive} />
115
113
  <ElementPickerOverlay />
116
114
  </>
117
115
  );
@@ -15,15 +15,15 @@ import type { EdgeInsets } from '../surfaces/safeArea';
15
15
  import { SurfaceLayoutInsetsProvider } from '../surfaces/surfaceLayoutInsets';
16
16
 
17
17
  import { BannerHost } from './BannerHost';
18
+ import { BannerWebViewFlowSpacer } from './bannerFlowSpacer';
18
19
  import { SdkErrorBoundary } from './SdkErrorBoundary';
19
20
  import { TargetPressBridge } from './TargetPressBridge';
20
21
  import { UserflowOverlay } from './UserflowOverlay';
21
22
  import { getBannerSessionEngine } from './bannerEngine';
22
23
  import { BannerEngineContext } from './bannerEngineContext';
23
- import { bannerEdge } from './bannerPlacement';
24
24
  import { getFlowSessionEngine } from './flowEngine';
25
+ import { PushBannerLayoutProvider, type PushBannerLayoutEdge } from './pushBannerLayoutContext';
25
26
  import { isolateSync } from './isolateCall';
26
- import { useBannerSession } from './useBannerSession';
27
27
  import { getUserflowCore } from './userflowCore';
28
28
 
29
29
  export interface UserflowContextValue {
@@ -122,8 +122,9 @@ export function UserflowProvider({
122
122
  const onOverlayError = React.useCallback(() => {
123
123
  setTrapHostAccessibility(false);
124
124
  }, []);
125
- const bannerSession = useBannerSession(bannerEngine);
126
125
  const safeInsets = useUserflowSafeAreaInsets();
126
+ const [pushBannerLayoutEdge, setPushBannerLayoutEdge] =
127
+ React.useState<PushBannerLayoutEdge | null>(null);
127
128
  const [pickerActive, setPickerActive] = React.useState(
128
129
  () => elementPickerController.getState().active,
129
130
  );
@@ -134,15 +135,12 @@ export function UserflowProvider({
134
135
  });
135
136
  }, []);
136
137
 
137
- const banner = bannerSession?.version.banner ?? null;
138
- const flowEdge =
139
- banner && banner.overlay === false && !pickerActive ? bannerEdge(banner.embedMode) : null;
140
138
  const hostInsets: EdgeInsets | null =
141
- flowEdge === 'top' || flowEdge === 'bottom'
139
+ pushBannerLayoutEdge === 'top' || pushBannerLayoutEdge === 'bottom'
142
140
  ? {
143
141
  ...safeInsets,
144
- top: flowEdge === 'top' ? 0 : safeInsets.top,
145
- bottom: flowEdge === 'bottom' ? 0 : safeInsets.bottom,
142
+ top: pushBannerLayoutEdge === 'top' ? 0 : safeInsets.top,
143
+ bottom: pushBannerLayoutEdge === 'bottom' ? 0 : safeInsets.bottom,
146
144
  }
147
145
  : null;
148
146
 
@@ -163,31 +161,44 @@ export function UserflowProvider({
163
161
  <BannerEngineContext.Provider value={bannerEngine}>
164
162
  <UserflowContext.Provider value={contextValue}>
165
163
  <SurfaceLayoutInsetsProvider>
166
- <DoubleTapBridge>
167
- <View style={styles.root} collapsable={false} testID="userflow-root">
168
- <BannerHost engine={bannerEngine} placement="flow" edge="top" hidden={pickerActive} />
169
- <HostSafeArea insets={hostInsets}>{appContent}</HostSafeArea>
170
- <BannerHost
171
- engine={bannerEngine}
172
- placement="flow"
173
- edge="bottom"
174
- hidden={pickerActive}
175
- />
176
- <View style={styles.overlay} pointerEvents="box-none">
177
- <SdkErrorBoundary
178
- scope="overlay"
179
- resetKey={overlayResetKey}
180
- onError={onOverlayError}
181
- >
182
- <UserflowOverlay
183
- engine={engine}
184
- bannerEngine={bannerEngine}
185
- onTrapChange={onTrapChange}
164
+ <PushBannerLayoutProvider onActiveEdgeChange={setPushBannerLayoutEdge}>
165
+ <DoubleTapBridge>
166
+ <View style={styles.root} collapsable={false} testID="userflow-root">
167
+ <View style={styles.flowBannerTop}>
168
+ <BannerHost
169
+ engine={bannerEngine}
170
+ placement="flow"
171
+ edge="top"
172
+ hidden={pickerActive}
186
173
  />
187
- </SdkErrorBoundary>
174
+ <BannerWebViewFlowSpacer engine={bannerEngine} edge="top" />
175
+ </View>
176
+ <HostSafeArea insets={hostInsets}>{appContent}</HostSafeArea>
177
+ <View style={styles.flowBannerBottom}>
178
+ <BannerHost
179
+ engine={bannerEngine}
180
+ placement="flow"
181
+ edge="bottom"
182
+ hidden={pickerActive}
183
+ />
184
+ <BannerWebViewFlowSpacer engine={bannerEngine} edge="bottom" />
185
+ </View>
186
+ <View style={styles.overlay} pointerEvents="box-none">
187
+ <SdkErrorBoundary
188
+ scope="overlay"
189
+ resetKey={overlayResetKey}
190
+ onError={onOverlayError}
191
+ >
192
+ <UserflowOverlay
193
+ engine={engine}
194
+ bannerEngine={bannerEngine}
195
+ onTrapChange={onTrapChange}
196
+ />
197
+ </SdkErrorBoundary>
198
+ </View>
188
199
  </View>
189
- </View>
190
- </DoubleTapBridge>
200
+ </DoubleTapBridge>
201
+ </PushBannerLayoutProvider>
191
202
  </SurfaceLayoutInsetsProvider>
192
203
  </UserflowContext.Provider>
193
204
  </BannerEngineContext.Provider>
@@ -228,7 +239,20 @@ const styles = StyleSheet.create({
228
239
  appContent: {
229
240
  flex: 1,
230
241
  },
242
+ flowBannerTop: {
243
+ zIndex: 1,
244
+ elevation: 1,
245
+ pointerEvents: 'box-none',
246
+ },
247
+ flowBannerBottom: {
248
+ zIndex: 1,
249
+ elevation: 1,
250
+ pointerEvents: 'box-none',
251
+ },
231
252
  overlay: {
232
253
  ...ABSOLUTE_FILL,
254
+ // Above flow banner slots so WebView banners receive touches (spacers are layout-only).
255
+ zIndex: 2,
256
+ elevation: 2,
233
257
  },
234
258
  });
@@ -1,4 +1,5 @@
1
1
  import { BannerSessionEngine, type UserflowCore } from '@userflow-tech/core';
2
+ import type { EndFlowReasonInput } from '@userflow-tech/protocol';
2
3
 
3
4
  import { evalJsController } from '../evalJs/evalJsController';
4
5
  import { openFlowNavigateUrl } from '../navigation/openFlowNavigateUrl';
@@ -44,8 +45,15 @@ export function resetBannerSessionEngine(): void {
44
45
  }
45
46
 
46
47
  /** Drop the local banner without detaching (preview exit, user switch). */
47
- export function clearBannerSessionEngineLocalSession(): void {
48
- bannerEngine?.clearLocalSession();
48
+ export function clearBannerSessionEngineLocalSession(options?: {
49
+ rememberDraftForPreviewEnd?: boolean;
50
+ }): void {
51
+ bannerEngine?.clearLocalSession(options);
52
+ }
53
+
54
+ /** Best-effort end of a Builder preview banner draft (including after local clear). */
55
+ export async function endDraftBannerSession(): Promise<void> {
56
+ await bannerEngine?.endDraftBannerSession('USER_CLOSED' as EndFlowReasonInput);
49
57
  }
50
58
 
51
59
  /** Test helper: drop the singleton so the next get recreates it. */
@@ -0,0 +1,70 @@
1
+ import type { BannerSessionEngine } from '@userflow-tech/core';
2
+ import * as React from 'react';
3
+ import { View } from 'react-native';
4
+
5
+ import { bannerEdge } from './bannerPlacement';
6
+ import { useBannerSession } from './useBannerSession';
7
+
8
+ type SpacerEdge = 'top' | 'bottom';
9
+
10
+ const heights: Record<SpacerEdge, number> = { top: 0, bottom: 0 };
11
+ const listeners = new Set<() => void>();
12
+
13
+ function emit(): void {
14
+ for (const listener of listeners) {
15
+ listener();
16
+ }
17
+ }
18
+
19
+ /** @internal */
20
+ export function setBannerFlowSpacerHeight(edge: SpacerEdge, height: number): void {
21
+ const next = Math.max(0, Math.ceil(height));
22
+ if (heights[edge] === next) {
23
+ return;
24
+ }
25
+ heights[edge] = next;
26
+ emit();
27
+ }
28
+
29
+ function subscribe(listener: () => void): () => void {
30
+ listeners.add(listener);
31
+ return () => {
32
+ listeners.delete(listener);
33
+ };
34
+ }
35
+
36
+ function getHeight(edge: SpacerEdge): number {
37
+ return heights[edge];
38
+ }
39
+
40
+ /**
41
+ * Reserves layout space when a custom-css WebView banner is painted in the
42
+ * overlay host but the builder marked it `overlay: false` (push layout).
43
+ */
44
+ export function BannerWebViewFlowSpacer({
45
+ engine,
46
+ edge,
47
+ }: {
48
+ engine: BannerSessionEngine;
49
+ edge: SpacerEdge;
50
+ }): React.ReactElement | null {
51
+ const session = useBannerSession(engine);
52
+ const height = React.useSyncExternalStore(
53
+ subscribe,
54
+ () => getHeight(edge),
55
+ () => getHeight(edge),
56
+ );
57
+
58
+ if (!session?.version.banner || height <= 0) {
59
+ return null;
60
+ }
61
+ const pinnedEdge = bannerEdge(session.version.banner.embedMode);
62
+ if (pinnedEdge !== edge) {
63
+ return null;
64
+ }
65
+
66
+ // Layout-only: the painted banner lives in the overlay host (lower z-index).
67
+ return (
68
+ <View pointerEvents="none" testID={`userflow-banner-flow-spacer-${edge}`} style={{ height }} />
69
+ );
70
+ }
@@ -31,6 +31,17 @@ export function getFlowSessionEngine(core: UserflowCore): FlowSessionEngine {
31
31
  updateUser: (attributes) => core.updateUser(attributes),
32
32
  updateGroup: (attributes, options) => core.updateGroup(attributes, options),
33
33
  });
34
+ flowEngine.on((event) => {
35
+ if (event.type !== 'sessionChange' || event.session !== null) {
36
+ return;
37
+ }
38
+ // Drop a flow's eval page when the tour ends. A banner WebView reuses the
39
+ // same controller and must keep its page alive until the banner dismisses.
40
+ if (evalJsController.getSnapshot().bannerOwnsWebView) {
41
+ return;
42
+ }
43
+ evalJsController.reset();
44
+ });
34
45
  if (endDraftOnArrival) watchForRestoredDraft();
35
46
  }
36
47
  return flowEngine;
@@ -0,0 +1,49 @@
1
+ import type { UserflowCore } from '@userflow-tech/core';
2
+ import type { EndFlowReasonInput } from '@userflow-tech/protocol';
3
+
4
+ import { evalJsController } from '../evalJs/evalJsController';
5
+
6
+ import {
7
+ clearBannerSessionEngineLocalSession,
8
+ endDraftBannerSession,
9
+ getBannerSessionEngine,
10
+ } from './bannerEngine';
11
+ import {
12
+ cancelPendingDraftEnd,
13
+ clearFlowSessionEngineLocalSession,
14
+ endDraftFlowSession,
15
+ } from './flowEngine';
16
+
17
+ /**
18
+ * Drop local tour state and reset eval before a Builder `startFlowWithToken`.
19
+ *
20
+ * When a draft banner is still active, end it on the server first. The channel
21
+ * dedupes an unchanged `SetBannerSession`, so clearing the banner locally
22
+ * without ending the draft leaves nothing visible on a second preview of the
23
+ * same banner.
24
+ */
25
+ export async function prepareForBuilderPreviewStart(core: UserflowCore): Promise<void> {
26
+ cancelPendingDraftEnd();
27
+ clearFlowSessionEngineLocalSession();
28
+ core.setFlowSessionId(null);
29
+
30
+ const bannerEngine = getBannerSessionEngine(core);
31
+ const hasDraftBanner =
32
+ bannerEngine.getBannerSession()?.draftMode === true ||
33
+ bannerEngine.getDeferredDraftBannerSessionId() !== null;
34
+
35
+ if (hasDraftBanner) {
36
+ await bannerEngine.endDraftBannerForPreview('USER_CLOSED' as EndFlowReasonInput);
37
+ } else {
38
+ clearBannerSessionEngineLocalSession();
39
+ core.setBannerSessionId(null);
40
+ }
41
+
42
+ evalJsController.reset();
43
+ }
44
+
45
+ /** End tour and banner Builder drafts on the server (preview exit). */
46
+ export async function endDraftPreviewSessions(): Promise<void> {
47
+ await endDraftFlowSession();
48
+ await endDraftBannerSession();
49
+ }
@@ -0,0 +1,121 @@
1
+ import * as React from 'react';
2
+ import type { LayoutChangeEvent } from 'react-native';
3
+
4
+ export type PushBannerLayoutEdge = 'top' | 'bottom';
5
+
6
+ interface PushBannerLayoutContextValue {
7
+ /** Push-layout banner chrome mounted; host safe-area may follow this edge. */
8
+ register: (edge: PushBannerLayoutEdge, token: string) => void;
9
+ unregister: (token: string) => void;
10
+ }
11
+
12
+ const PushBannerLayoutContext = React.createContext<PushBannerLayoutContextValue | null>(null);
13
+
14
+ function pickEdge(registrations: Map<string, PushBannerLayoutEdge>): PushBannerLayoutEdge | null {
15
+ let top = false;
16
+ let bottom = false;
17
+ for (const edge of registrations.values()) {
18
+ if (edge === 'top') {
19
+ top = true;
20
+ }
21
+ if (edge === 'bottom') {
22
+ bottom = true;
23
+ }
24
+ }
25
+ if (top) {
26
+ return 'top';
27
+ }
28
+ if (bottom) {
29
+ return 'bottom';
30
+ }
31
+ return null;
32
+ }
33
+
34
+ export function PushBannerLayoutProvider({
35
+ children,
36
+ onActiveEdgeChange,
37
+ }: {
38
+ children: React.ReactNode;
39
+ onActiveEdgeChange: (edge: PushBannerLayoutEdge | null) => void;
40
+ }): React.ReactElement {
41
+ const registrations = React.useRef(new Map<string, PushBannerLayoutEdge>());
42
+ const onActiveEdgeChangeRef = React.useRef(onActiveEdgeChange);
43
+ onActiveEdgeChangeRef.current = onActiveEdgeChange;
44
+
45
+ const sync = React.useCallback(() => {
46
+ onActiveEdgeChangeRef.current(pickEdge(registrations.current));
47
+ }, []);
48
+
49
+ const value = React.useMemo<PushBannerLayoutContextValue>(
50
+ () => ({
51
+ register: (edge, token) => {
52
+ registrations.current.set(token, edge);
53
+ sync();
54
+ },
55
+ unregister: (token) => {
56
+ if (registrations.current.delete(token)) {
57
+ sync();
58
+ }
59
+ },
60
+ }),
61
+ [sync],
62
+ );
63
+
64
+ React.useLayoutEffect(() => {
65
+ sync();
66
+ }, [sync]);
67
+
68
+ return (
69
+ <PushBannerLayoutContext.Provider value={value}>{children}</PushBannerLayoutContext.Provider>
70
+ );
71
+ }
72
+
73
+ /** @internal */
74
+ export function usePushBannerLayoutRegistration(
75
+ edge: PushBannerLayoutEdge | null | undefined,
76
+ sessionId: string | undefined,
77
+ ): void {
78
+ const ctx = React.useContext(PushBannerLayoutContext);
79
+ React.useLayoutEffect(() => {
80
+ if (!ctx || !edge || !sessionId) {
81
+ return;
82
+ }
83
+ const token = `${sessionId}:${edge}`;
84
+ ctx.register(edge, token);
85
+ return () => {
86
+ ctx.unregister(token);
87
+ };
88
+ }, [ctx, edge, sessionId]);
89
+ }
90
+
91
+ /**
92
+ * Clears the host top/bottom safe-area override only after push-layout chrome
93
+ * has laid out, so a session without visible chrome cannot leave a white strip.
94
+ *
95
+ * @internal
96
+ */
97
+ export function usePushBannerHostInsetRegistration(
98
+ edge: PushBannerLayoutEdge | null | undefined,
99
+ sessionId: string,
100
+ enabled: boolean,
101
+ ): (event: LayoutChangeEvent) => void {
102
+ const [laidOut, setLaidOut] = React.useState(false);
103
+
104
+ React.useEffect(() => {
105
+ setLaidOut(false);
106
+ }, [edge, enabled]);
107
+
108
+ usePushBannerLayoutRegistration(enabled && laidOut && edge ? edge : null, sessionId);
109
+
110
+ return React.useCallback(
111
+ (event: LayoutChangeEvent) => {
112
+ if (!enabled || !edge) {
113
+ return;
114
+ }
115
+ if (event.nativeEvent.layout.height > 0) {
116
+ setLaidOut(true);
117
+ }
118
+ },
119
+ [edge, enabled],
120
+ );
121
+ }