@userflow-tech/react-native 0.5.0 → 0.7.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 (328) hide show
  1. package/README.md +65 -33
  2. package/docs/custom-fonts.md +36 -0
  3. package/docs/troubleshooting.md +45 -44
  4. package/expo-plugin/hostDependencyWarnings.d.ts +5 -5
  5. package/expo-plugin/hostDependencyWarnings.js +7 -7
  6. package/expo-plugin/hostDependencyWarnings.js.map +1 -1
  7. package/lib/commonjs/anchors/AnchorRegistry.js +5 -0
  8. package/lib/commonjs/anchors/AnchorRegistry.js.map +1 -1
  9. package/lib/commonjs/anchors/ScrollContainerRegistry.js +8 -0
  10. package/lib/commonjs/anchors/ScrollContainerRegistry.js.map +1 -1
  11. package/lib/commonjs/anchors/fiber/FiberScanRootStore.js +8 -0
  12. package/lib/commonjs/anchors/fiber/FiberScanRootStore.js.map +1 -1
  13. package/lib/commonjs/anchors/fiber/detectAppFontFamily.js +341 -0
  14. package/lib/commonjs/anchors/fiber/detectAppFontFamily.js.map +1 -0
  15. package/lib/commonjs/anchors/fiber/presentedModal.js +215 -0
  16. package/lib/commonjs/anchors/fiber/presentedModal.js.map +1 -0
  17. package/lib/commonjs/anchors/fiber/touchTargetFiber.js +1 -0
  18. package/lib/commonjs/anchors/fiber/touchTargetFiber.js.map +1 -1
  19. package/lib/commonjs/anchors/fiber/visibleRect.js +12 -3
  20. package/lib/commonjs/anchors/fiber/visibleRect.js.map +1 -1
  21. package/lib/commonjs/anchors/fiber/visibleScreens.js +6 -0
  22. package/lib/commonjs/anchors/fiber/visibleScreens.js.map +1 -1
  23. package/lib/commonjs/anchors/useUserflowAnchor.js.map +1 -1
  24. package/lib/commonjs/anchors/useUserflowScrollContainer.js.map +1 -1
  25. package/lib/commonjs/authoring/ElementPickerOverlay.js +197 -138
  26. package/lib/commonjs/authoring/ElementPickerOverlay.js.map +1 -1
  27. package/lib/commonjs/authoring/hitTest.js +67 -10
  28. package/lib/commonjs/authoring/hitTest.js.map +1 -1
  29. package/lib/commonjs/authoring/hostAtPoint.js +45 -21
  30. package/lib/commonjs/authoring/hostAtPoint.js.map +1 -1
  31. package/lib/commonjs/content/BannerStepButton.js +2 -1
  32. package/lib/commonjs/content/BannerStepButton.js.map +1 -1
  33. package/lib/commonjs/content/RichContentRenderer.js +27 -9
  34. package/lib/commonjs/content/RichContentRenderer.js.map +1 -1
  35. package/lib/commonjs/content/StepButton.js +2 -1
  36. package/lib/commonjs/content/StepButton.js.map +1 -1
  37. package/lib/commonjs/content/resolveContentFontFamily.js +96 -0
  38. package/lib/commonjs/content/resolveContentFontFamily.js.map +1 -0
  39. package/lib/commonjs/deepLinking.js.map +1 -1
  40. package/lib/commonjs/deviceContext.js +39 -19
  41. package/lib/commonjs/deviceContext.js.map +1 -1
  42. package/lib/commonjs/index.js +13 -5
  43. package/lib/commonjs/index.js.map +1 -1
  44. package/lib/commonjs/installCheck.js +3 -3
  45. package/lib/commonjs/installCheck.js.map +1 -1
  46. package/lib/commonjs/lifecycle.js +2 -0
  47. package/lib/commonjs/lifecycle.js.map +1 -1
  48. package/lib/commonjs/pairing/PairingController.js.map +1 -1
  49. package/lib/commonjs/runtime/BannerHost.js +3 -3
  50. package/lib/commonjs/runtime/BannerHost.js.map +1 -1
  51. package/lib/commonjs/runtime/UserflowOverlay.js +17 -13
  52. package/lib/commonjs/runtime/UserflowOverlay.js.map +1 -1
  53. package/lib/commonjs/runtime/WindowOverlayHost.js +83 -0
  54. package/lib/commonjs/runtime/WindowOverlayHost.js.map +1 -0
  55. package/lib/commonjs/runtime/pushBannerLayoutContext.js +11 -8
  56. package/lib/commonjs/runtime/pushBannerLayoutContext.js.map +1 -1
  57. package/lib/commonjs/runtime/userflowCore.js +9 -2
  58. package/lib/commonjs/runtime/userflowCore.js.map +1 -1
  59. package/lib/commonjs/surfaces/BannerSurface.js +2 -6
  60. package/lib/commonjs/surfaces/BannerSurface.js.map +1 -1
  61. package/lib/commonjs/surfaces/ProgressBar.js +2 -1
  62. package/lib/commonjs/surfaces/ProgressBar.js.map +1 -1
  63. package/lib/commonjs/surfaces/TooltipSurface.js +114 -51
  64. package/lib/commonjs/surfaces/TooltipSurface.js.map +1 -1
  65. package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js +6 -4
  66. package/lib/commonjs/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
  67. package/lib/commonjs/surfaces/bannerWebView/bannerNeedsWebView.js +3 -33
  68. package/lib/commonjs/surfaces/bannerWebView/bannerNeedsWebView.js.map +1 -1
  69. package/lib/commonjs/surfaces/bannerWebView/bannerThemeStyles.js +2 -1
  70. package/lib/commonjs/surfaces/bannerWebView/bannerThemeStyles.js.map +1 -1
  71. package/lib/commonjs/surfaces/bannerWebView/bannerWebViewHeight.js +33 -0
  72. package/lib/commonjs/surfaces/bannerWebView/bannerWebViewHeight.js.map +1 -0
  73. package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js +45 -12
  74. package/lib/commonjs/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
  75. package/lib/commonjs/surfaces/tooltip/TooltipHighlight.js +6 -6
  76. package/lib/commonjs/surfaces/tooltip/TooltipHighlight.js.map +1 -1
  77. package/lib/commonjs/surfaces/tooltip/targetOrigin.js +74 -0
  78. package/lib/commonjs/surfaces/tooltip/targetOrigin.js.map +1 -0
  79. package/lib/commonjs/version.js +1 -1
  80. package/lib/module/anchors/AnchorRegistry.js +5 -0
  81. package/lib/module/anchors/AnchorRegistry.js.map +1 -1
  82. package/lib/module/anchors/ScrollContainerRegistry.js +8 -0
  83. package/lib/module/anchors/ScrollContainerRegistry.js.map +1 -1
  84. package/lib/module/anchors/fiber/FiberScanRootStore.js +8 -0
  85. package/lib/module/anchors/fiber/FiberScanRootStore.js.map +1 -1
  86. package/lib/module/anchors/fiber/detectAppFontFamily.js +330 -0
  87. package/lib/module/anchors/fiber/detectAppFontFamily.js.map +1 -0
  88. package/lib/module/anchors/fiber/presentedModal.js +206 -0
  89. package/lib/module/anchors/fiber/presentedModal.js.map +1 -0
  90. package/lib/module/anchors/fiber/touchTargetFiber.js +1 -0
  91. package/lib/module/anchors/fiber/touchTargetFiber.js.map +1 -1
  92. package/lib/module/anchors/fiber/visibleRect.js +12 -3
  93. package/lib/module/anchors/fiber/visibleRect.js.map +1 -1
  94. package/lib/module/anchors/fiber/visibleScreens.js +5 -0
  95. package/lib/module/anchors/fiber/visibleScreens.js.map +1 -1
  96. package/lib/module/anchors/useUserflowAnchor.js.map +1 -1
  97. package/lib/module/anchors/useUserflowScrollContainer.js.map +1 -1
  98. package/lib/module/authoring/ElementPickerOverlay.js +199 -140
  99. package/lib/module/authoring/ElementPickerOverlay.js.map +1 -1
  100. package/lib/module/authoring/hitTest.js +67 -11
  101. package/lib/module/authoring/hitTest.js.map +1 -1
  102. package/lib/module/authoring/hostAtPoint.js +45 -21
  103. package/lib/module/authoring/hostAtPoint.js.map +1 -1
  104. package/lib/module/content/BannerStepButton.js +2 -1
  105. package/lib/module/content/BannerStepButton.js.map +1 -1
  106. package/lib/module/content/RichContentRenderer.js +27 -9
  107. package/lib/module/content/RichContentRenderer.js.map +1 -1
  108. package/lib/module/content/StepButton.js +2 -1
  109. package/lib/module/content/StepButton.js.map +1 -1
  110. package/lib/module/content/resolveContentFontFamily.js +89 -0
  111. package/lib/module/content/resolveContentFontFamily.js.map +1 -0
  112. package/lib/module/deepLinking.js.map +1 -1
  113. package/lib/module/deviceContext.js +38 -17
  114. package/lib/module/deviceContext.js.map +1 -1
  115. package/lib/module/index.js +13 -5
  116. package/lib/module/index.js.map +1 -1
  117. package/lib/module/installCheck.js +3 -3
  118. package/lib/module/installCheck.js.map +1 -1
  119. package/lib/module/lifecycle.js +2 -0
  120. package/lib/module/lifecycle.js.map +1 -1
  121. package/lib/module/pairing/PairingController.js.map +1 -1
  122. package/lib/module/runtime/BannerHost.js +3 -3
  123. package/lib/module/runtime/BannerHost.js.map +1 -1
  124. package/lib/module/runtime/UserflowOverlay.js +18 -14
  125. package/lib/module/runtime/UserflowOverlay.js.map +1 -1
  126. package/lib/module/runtime/WindowOverlayHost.js +76 -0
  127. package/lib/module/runtime/WindowOverlayHost.js.map +1 -0
  128. package/lib/module/runtime/pushBannerLayoutContext.js +11 -8
  129. package/lib/module/runtime/pushBannerLayoutContext.js.map +1 -1
  130. package/lib/module/runtime/userflowCore.js +9 -2
  131. package/lib/module/runtime/userflowCore.js.map +1 -1
  132. package/lib/module/surfaces/BannerSurface.js +2 -6
  133. package/lib/module/surfaces/BannerSurface.js.map +1 -1
  134. package/lib/module/surfaces/ProgressBar.js +2 -1
  135. package/lib/module/surfaces/ProgressBar.js.map +1 -1
  136. package/lib/module/surfaces/TooltipSurface.js +114 -51
  137. package/lib/module/surfaces/TooltipSurface.js.map +1 -1
  138. package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js +6 -4
  139. package/lib/module/surfaces/bannerWebView/BannerWebViewBar.js.map +1 -1
  140. package/lib/module/surfaces/bannerWebView/bannerNeedsWebView.js +3 -31
  141. package/lib/module/surfaces/bannerWebView/bannerNeedsWebView.js.map +1 -1
  142. package/lib/module/surfaces/bannerWebView/bannerThemeStyles.js +2 -1
  143. package/lib/module/surfaces/bannerWebView/bannerThemeStyles.js.map +1 -1
  144. package/lib/module/surfaces/bannerWebView/bannerWebViewHeight.js +29 -0
  145. package/lib/module/surfaces/bannerWebView/bannerWebViewHeight.js.map +1 -0
  146. package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js +45 -12
  147. package/lib/module/surfaces/bannerWebView/buildCustomBannerHtml.js.map +1 -1
  148. package/lib/module/surfaces/tooltip/TooltipHighlight.js +6 -6
  149. package/lib/module/surfaces/tooltip/TooltipHighlight.js.map +1 -1
  150. package/lib/module/surfaces/tooltip/targetOrigin.js +67 -0
  151. package/lib/module/surfaces/tooltip/targetOrigin.js.map +1 -0
  152. package/lib/module/version.js +1 -1
  153. package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts +7 -0
  154. package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts.map +1 -1
  155. package/lib/typescript/commonjs/src/anchors/ScrollContainerRegistry.d.ts +11 -2
  156. package/lib/typescript/commonjs/src/anchors/ScrollContainerRegistry.d.ts.map +1 -1
  157. package/lib/typescript/commonjs/src/anchors/fiber/FiberScanRootStore.d.ts +5 -0
  158. package/lib/typescript/commonjs/src/anchors/fiber/FiberScanRootStore.d.ts.map +1 -1
  159. package/lib/typescript/commonjs/src/anchors/fiber/detectAppFontFamily.d.ts +30 -0
  160. package/lib/typescript/commonjs/src/anchors/fiber/detectAppFontFamily.d.ts.map +1 -0
  161. package/lib/typescript/commonjs/src/anchors/fiber/presentedModal.d.ts +45 -0
  162. package/lib/typescript/commonjs/src/anchors/fiber/presentedModal.d.ts.map +1 -0
  163. package/lib/typescript/commonjs/src/anchors/fiber/visibleRect.d.ts +5 -2
  164. package/lib/typescript/commonjs/src/anchors/fiber/visibleRect.d.ts.map +1 -1
  165. package/lib/typescript/commonjs/src/anchors/fiber/visibleScreens.d.ts +2 -0
  166. package/lib/typescript/commonjs/src/anchors/fiber/visibleScreens.d.ts.map +1 -1
  167. package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts +2 -2
  168. package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts.map +1 -1
  169. package/lib/typescript/commonjs/src/anchors/useUserflowScrollContainer.d.ts +3 -2
  170. package/lib/typescript/commonjs/src/anchors/useUserflowScrollContainer.d.ts.map +1 -1
  171. package/lib/typescript/commonjs/src/authoring/ElementPickerOverlay.d.ts +4 -0
  172. package/lib/typescript/commonjs/src/authoring/ElementPickerOverlay.d.ts.map +1 -1
  173. package/lib/typescript/commonjs/src/authoring/hitTest.d.ts +25 -2
  174. package/lib/typescript/commonjs/src/authoring/hitTest.d.ts.map +1 -1
  175. package/lib/typescript/commonjs/src/authoring/hostAtPoint.d.ts +6 -1
  176. package/lib/typescript/commonjs/src/authoring/hostAtPoint.d.ts.map +1 -1
  177. package/lib/typescript/commonjs/src/content/BannerStepButton.d.ts.map +1 -1
  178. package/lib/typescript/commonjs/src/content/RichContentRenderer.d.ts.map +1 -1
  179. package/lib/typescript/commonjs/src/content/StepButton.d.ts.map +1 -1
  180. package/lib/typescript/commonjs/src/content/resolveContentFontFamily.d.ts +35 -0
  181. package/lib/typescript/commonjs/src/content/resolveContentFontFamily.d.ts.map +1 -0
  182. package/lib/typescript/commonjs/src/deepLinking.d.ts +1 -1
  183. package/lib/typescript/commonjs/src/deepLinking.d.ts.map +1 -1
  184. package/lib/typescript/commonjs/src/deviceContext.d.ts +14 -5
  185. package/lib/typescript/commonjs/src/deviceContext.d.ts.map +1 -1
  186. package/lib/typescript/commonjs/src/index.d.ts +11 -5
  187. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  188. package/lib/typescript/commonjs/src/installCheck.d.ts +2 -2
  189. package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -1
  190. package/lib/typescript/commonjs/src/lifecycle.d.ts +1 -0
  191. package/lib/typescript/commonjs/src/lifecycle.d.ts.map +1 -1
  192. package/lib/typescript/commonjs/src/pairing/PairingController.d.ts +1 -1
  193. package/lib/typescript/commonjs/src/pairing/PairingController.d.ts.map +1 -1
  194. package/lib/typescript/commonjs/src/runtime/UserflowOverlay.d.ts.map +1 -1
  195. package/lib/typescript/commonjs/src/runtime/WindowOverlayHost.d.ts +34 -0
  196. package/lib/typescript/commonjs/src/runtime/WindowOverlayHost.d.ts.map +1 -0
  197. package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts +5 -0
  198. package/lib/typescript/commonjs/src/runtime/pushBannerLayoutContext.d.ts.map +1 -1
  199. package/lib/typescript/commonjs/src/runtime/userflowCore.d.ts.map +1 -1
  200. package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts +1 -4
  201. package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts.map +1 -1
  202. package/lib/typescript/commonjs/src/surfaces/ProgressBar.d.ts.map +1 -1
  203. package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts.map +1 -1
  204. package/lib/typescript/commonjs/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
  205. package/lib/typescript/commonjs/src/surfaces/bannerWebView/bannerNeedsWebView.d.ts +3 -12
  206. package/lib/typescript/commonjs/src/surfaces/bannerWebView/bannerNeedsWebView.d.ts.map +1 -1
  207. package/lib/typescript/commonjs/src/surfaces/bannerWebView/bannerThemeStyles.d.ts.map +1 -1
  208. package/lib/typescript/commonjs/src/surfaces/bannerWebView/bannerWebViewHeight.d.ts +13 -0
  209. package/lib/typescript/commonjs/src/surfaces/bannerWebView/bannerWebViewHeight.d.ts.map +1 -0
  210. package/lib/typescript/commonjs/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
  211. package/lib/typescript/commonjs/src/surfaces/tooltip/targetOrigin.d.ts +26 -0
  212. package/lib/typescript/commonjs/src/surfaces/tooltip/targetOrigin.d.ts.map +1 -0
  213. package/lib/typescript/commonjs/src/version.d.ts +1 -1
  214. package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts +7 -0
  215. package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts.map +1 -1
  216. package/lib/typescript/module/src/anchors/ScrollContainerRegistry.d.ts +11 -2
  217. package/lib/typescript/module/src/anchors/ScrollContainerRegistry.d.ts.map +1 -1
  218. package/lib/typescript/module/src/anchors/fiber/FiberScanRootStore.d.ts +5 -0
  219. package/lib/typescript/module/src/anchors/fiber/FiberScanRootStore.d.ts.map +1 -1
  220. package/lib/typescript/module/src/anchors/fiber/detectAppFontFamily.d.ts +30 -0
  221. package/lib/typescript/module/src/anchors/fiber/detectAppFontFamily.d.ts.map +1 -0
  222. package/lib/typescript/module/src/anchors/fiber/presentedModal.d.ts +45 -0
  223. package/lib/typescript/module/src/anchors/fiber/presentedModal.d.ts.map +1 -0
  224. package/lib/typescript/module/src/anchors/fiber/visibleRect.d.ts +5 -2
  225. package/lib/typescript/module/src/anchors/fiber/visibleRect.d.ts.map +1 -1
  226. package/lib/typescript/module/src/anchors/fiber/visibleScreens.d.ts +2 -0
  227. package/lib/typescript/module/src/anchors/fiber/visibleScreens.d.ts.map +1 -1
  228. package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts +2 -2
  229. package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts.map +1 -1
  230. package/lib/typescript/module/src/anchors/useUserflowScrollContainer.d.ts +3 -2
  231. package/lib/typescript/module/src/anchors/useUserflowScrollContainer.d.ts.map +1 -1
  232. package/lib/typescript/module/src/authoring/ElementPickerOverlay.d.ts +4 -0
  233. package/lib/typescript/module/src/authoring/ElementPickerOverlay.d.ts.map +1 -1
  234. package/lib/typescript/module/src/authoring/hitTest.d.ts +25 -2
  235. package/lib/typescript/module/src/authoring/hitTest.d.ts.map +1 -1
  236. package/lib/typescript/module/src/authoring/hostAtPoint.d.ts +6 -1
  237. package/lib/typescript/module/src/authoring/hostAtPoint.d.ts.map +1 -1
  238. package/lib/typescript/module/src/content/BannerStepButton.d.ts.map +1 -1
  239. package/lib/typescript/module/src/content/RichContentRenderer.d.ts.map +1 -1
  240. package/lib/typescript/module/src/content/StepButton.d.ts.map +1 -1
  241. package/lib/typescript/module/src/content/resolveContentFontFamily.d.ts +35 -0
  242. package/lib/typescript/module/src/content/resolveContentFontFamily.d.ts.map +1 -0
  243. package/lib/typescript/module/src/deepLinking.d.ts +1 -1
  244. package/lib/typescript/module/src/deepLinking.d.ts.map +1 -1
  245. package/lib/typescript/module/src/deviceContext.d.ts +14 -5
  246. package/lib/typescript/module/src/deviceContext.d.ts.map +1 -1
  247. package/lib/typescript/module/src/index.d.ts +11 -5
  248. package/lib/typescript/module/src/index.d.ts.map +1 -1
  249. package/lib/typescript/module/src/installCheck.d.ts +2 -2
  250. package/lib/typescript/module/src/installCheck.d.ts.map +1 -1
  251. package/lib/typescript/module/src/lifecycle.d.ts +1 -0
  252. package/lib/typescript/module/src/lifecycle.d.ts.map +1 -1
  253. package/lib/typescript/module/src/pairing/PairingController.d.ts +1 -1
  254. package/lib/typescript/module/src/pairing/PairingController.d.ts.map +1 -1
  255. package/lib/typescript/module/src/runtime/UserflowOverlay.d.ts.map +1 -1
  256. package/lib/typescript/module/src/runtime/WindowOverlayHost.d.ts +34 -0
  257. package/lib/typescript/module/src/runtime/WindowOverlayHost.d.ts.map +1 -0
  258. package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts +5 -0
  259. package/lib/typescript/module/src/runtime/pushBannerLayoutContext.d.ts.map +1 -1
  260. package/lib/typescript/module/src/runtime/userflowCore.d.ts.map +1 -1
  261. package/lib/typescript/module/src/surfaces/BannerSurface.d.ts +1 -4
  262. package/lib/typescript/module/src/surfaces/BannerSurface.d.ts.map +1 -1
  263. package/lib/typescript/module/src/surfaces/ProgressBar.d.ts.map +1 -1
  264. package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts.map +1 -1
  265. package/lib/typescript/module/src/surfaces/bannerWebView/BannerWebViewBar.d.ts.map +1 -1
  266. package/lib/typescript/module/src/surfaces/bannerWebView/bannerNeedsWebView.d.ts +3 -12
  267. package/lib/typescript/module/src/surfaces/bannerWebView/bannerNeedsWebView.d.ts.map +1 -1
  268. package/lib/typescript/module/src/surfaces/bannerWebView/bannerThemeStyles.d.ts.map +1 -1
  269. package/lib/typescript/module/src/surfaces/bannerWebView/bannerWebViewHeight.d.ts +13 -0
  270. package/lib/typescript/module/src/surfaces/bannerWebView/bannerWebViewHeight.d.ts.map +1 -0
  271. package/lib/typescript/module/src/surfaces/bannerWebView/buildCustomBannerHtml.d.ts.map +1 -1
  272. package/lib/typescript/module/src/surfaces/tooltip/targetOrigin.d.ts +26 -0
  273. package/lib/typescript/module/src/surfaces/tooltip/targetOrigin.d.ts.map +1 -0
  274. package/lib/typescript/module/src/version.d.ts +1 -1
  275. package/package.json +5 -4
  276. package/src/anchors/AnchorRegistry.ts +8 -0
  277. package/src/anchors/ScrollContainerRegistry.ts +15 -3
  278. package/src/anchors/fiber/FiberScanRootStore.ts +8 -0
  279. package/src/anchors/fiber/detectAppFontFamily.ts +400 -0
  280. package/src/anchors/fiber/presentedModal.ts +203 -0
  281. package/src/anchors/fiber/touchTargetFiber.ts +2 -1
  282. package/src/anchors/fiber/visibleRect.ts +12 -3
  283. package/src/anchors/fiber/visibleScreens.ts +5 -0
  284. package/src/anchors/useUserflowAnchor.ts +4 -5
  285. package/src/anchors/useUserflowScrollContainer.ts +5 -4
  286. package/src/authoring/ElementPickerOverlay.tsx +242 -166
  287. package/src/authoring/hitTest.ts +88 -13
  288. package/src/authoring/hostAtPoint.ts +67 -21
  289. package/src/content/BannerStepButton.tsx +2 -1
  290. package/src/content/RichContentRenderer.tsx +16 -9
  291. package/src/content/StepButton.tsx +2 -1
  292. package/src/content/resolveContentFontFamily.ts +119 -0
  293. package/src/deepLinking.ts +2 -1
  294. package/src/deviceContext.ts +50 -14
  295. package/src/index.ts +13 -5
  296. package/src/installCheck.ts +3 -3
  297. package/src/lifecycle.ts +2 -1
  298. package/src/pairing/PairingController.ts +2 -1
  299. package/src/runtime/BannerHost.tsx +3 -3
  300. package/src/runtime/UserflowOverlay.tsx +29 -21
  301. package/src/runtime/WindowOverlayHost.tsx +95 -0
  302. package/src/runtime/pushBannerLayoutContext.tsx +14 -9
  303. package/src/runtime/userflowCore.ts +8 -2
  304. package/src/surfaces/BannerSurface.tsx +2 -6
  305. package/src/surfaces/ProgressBar.tsx +2 -1
  306. package/src/surfaces/TooltipSurface.tsx +118 -47
  307. package/src/surfaces/bannerWebView/BannerWebViewBar.tsx +6 -4
  308. package/src/surfaces/bannerWebView/bannerNeedsWebView.ts +4 -36
  309. package/src/surfaces/bannerWebView/bannerThemeStyles.ts +2 -1
  310. package/src/surfaces/bannerWebView/bannerWebViewHeight.ts +32 -0
  311. package/src/surfaces/bannerWebView/buildCustomBannerHtml.ts +48 -12
  312. package/src/surfaces/tooltip/TooltipHighlight.tsx +3 -5
  313. package/src/surfaces/tooltip/targetOrigin.ts +74 -0
  314. package/src/version.ts +1 -1
  315. package/vendor/core/dist/engine/UserflowCore.d.ts +7 -0
  316. package/vendor/core/dist/engine/UserflowCore.d.ts.map +1 -1
  317. package/vendor/core/dist/engine/UserflowCore.js +3 -2
  318. package/vendor/core/dist/engine/UserflowCore.js.map +1 -1
  319. package/vendor/core/dist/transport/phoenix.d.ts +12 -2
  320. package/vendor/core/dist/transport/phoenix.d.ts.map +1 -1
  321. package/vendor/core/dist/transport/phoenix.js +7 -6
  322. package/vendor/core/dist/transport/phoenix.js.map +1 -1
  323. package/vendor/core/dist/version.d.ts +1 -1
  324. package/vendor/core/dist/version.js +1 -1
  325. package/vendor/core/package.json +1 -1
  326. package/vendor/protocol/dist/flows.d.ts +2 -0
  327. package/vendor/protocol/dist/flows.d.ts.map +1 -1
  328. package/vendor/protocol/package.json +1 -1
@@ -23,7 +23,8 @@ import { autoAnchorRegistry } from '../anchors/AutoAnchorRegistry';
23
23
  import { addAnimationFrameCallback, removeAnimationFrameCallback } from '../anchors/animationFrame';
24
24
  import { isAppActive, subscribeAppActive } from '../anchors/appActive';
25
25
  import { fiberScanRoot } from '../anchors/fiber/FiberScanRoot';
26
- import type { AnyFiber } from '../anchors/fiber/fiberInternals';
26
+ import { type AnyFiber, fiberFromPublicInstance } from '../anchors/fiber/fiberInternals';
27
+ import type { Point } from '../anchors/fiber/presentedModal';
27
28
  import { clipToScrollViewports } from '../anchors/fiber/visibleRect';
28
29
  import {
29
30
  clearFiberAnchorCache,
@@ -37,6 +38,7 @@ import { scrollAnchorIntoView } from '../anchors/scrollAnchorIntoView';
37
38
  import { getScrollPadding } from '../anchors/scrollConfig';
38
39
  import { targetPressMonitor } from '../anchors/targetPressMonitor';
39
40
  import { RichContentRenderer } from '../content/RichContentRenderer';
41
+ import { resolveContentFontFamily } from '../content/resolveContentFontFamily';
40
42
  import type { LookupAttribute } from '../content/types';
41
43
 
42
44
  import { CardFade } from './CardFade';
@@ -81,6 +83,7 @@ import {
81
83
  tooltipSideGap,
82
84
  tooltipVerticalGap,
83
85
  } from './tooltip/resolveSideCardWidth';
86
+ import { locateTargetOrigin, offsetRect } from './tooltip/targetOrigin';
84
87
  import { TargetSearchSchedule } from './tooltip/targetSearchSchedule';
85
88
 
86
89
  export interface TooltipSurfaceProps {
@@ -156,6 +159,7 @@ export function TooltipSurface({
156
159
  }: TooltipSurfaceProps): React.ReactElement | null {
157
160
  const version = session.version;
158
161
  const chrome = resolveBubbleChrome(theme, 'tooltip');
162
+ const contentFontFamily = resolveContentFontFamily(theme);
159
163
  const progress = resolveStepProgress(version, currentStep.id);
160
164
  const showClose = !version.closeDisabled;
161
165
  const backdropOpacity = parseBackdropOpacity(theme.backdropOpacity);
@@ -199,6 +203,12 @@ export function TooltipSurface({
199
203
  const [missingMode, setMissingMode] = React.useState<MissingMode>('waiting');
200
204
  /** True after at least one *failed* measure for the current step/anchor. */
201
205
  const [measureAttempted, setMeasureAttempted] = React.useState(false);
206
+ /**
207
+ * True while a natively presented modal hides the target, or the target is
208
+ * in a sheet that cannot be placed. The step waits without counting the
209
+ * target as missing, and draws nothing over the modal.
210
+ */
211
+ const [concealed, setConcealed] = React.useState(false);
202
212
  /**
203
213
  * False only after a same-step `resetNonce` bump (preview restart) until one
204
214
  * frame has passed, so dialog teardown cannot 0-size FLAG.
@@ -236,7 +246,15 @@ export function TooltipSurface({
236
246
  const resolveEpochRef = React.useRef(0);
237
247
  /** Latest resolved host fiber (for scroll-ancestor walk + rAF remeasure). */
238
248
  const hostFiberRef = React.useRef<AnyFiber | null>(null);
239
- /** Target rect clipped to scroll viewports, for tap detection only. */
249
+ /**
250
+ * Window position of the host's measure space, from the last resolve. Not
251
+ * zero only inside a native sheet, whose content measures and receives
252
+ * touches relative to the sheet.
253
+ */
254
+ const measureOriginRef = React.useRef<Point>({ x: 0, y: 0 });
255
+ /** `targetRef` in the host's measure space. */
256
+ const measuredRef = React.useRef<Rect | null>(null);
257
+ /** Target rect clipped to scroll viewports, for tap detection only. In measure space, as touches are. */
240
258
  const clippedPressRef = React.useRef<Rect | null>(null);
241
259
  const clipGenRef = React.useRef(0);
242
260
  const hostMeasureRef = React.useRef<
@@ -261,6 +279,8 @@ export function TooltipSurface({
261
279
  insetsRef.current = layoutInsets;
262
280
  const missingModeRef = React.useRef(missingMode);
263
281
  missingModeRef.current = missingMode;
282
+ const concealedRef = React.useRef(concealed);
283
+ concealedRef.current = concealed;
264
284
  const behaviorRef = React.useRef(behavior);
265
285
  behaviorRef.current = behavior;
266
286
  const draftModeRef = React.useRef(session.draftMode);
@@ -290,6 +310,7 @@ export function TooltipSurface({
290
310
  warnedSidePlacementStepRef.current = null;
291
311
  targetStepIdRef.current = null;
292
312
  targetRef.current = null;
313
+ measuredRef.current = null;
293
314
  // Drop the previous card's measured size and side so the next step does not
294
315
  // inherit a wrapped 115×400 LEFT into `fits(currentPlacement)`.
295
316
  const nextPlacement = desiredPlacementRef.current ?? 'BELOW';
@@ -298,6 +319,7 @@ export function TooltipSurface({
298
319
  setBubbleSize({ width: 280, height: 160 });
299
320
  setMissingMode('waiting');
300
321
  setMeasureAttempted(false);
322
+ setConcealed(false);
301
323
  setTarget(null);
302
324
  const prev = targetingIdentityRef.current;
303
325
  const sameIdentity = prev.stepId === currentStep.id && prev.targetKey === targetKey;
@@ -317,10 +339,13 @@ export function TooltipSurface({
317
339
  return () => clearTimeout(handle);
318
340
  }, [resolveArmed]);
319
341
 
320
- const applyTarget = React.useCallback((next: Rect | null) => {
342
+ /** `next` is in window coordinates; `measured` is the same rect in the host's measure space. */
343
+ const applyTarget = React.useCallback((next: Rect | null, measured: Rect | null = next) => {
321
344
  if (!mountedRef.current) return;
345
+ if (next != null) setConcealed(false);
322
346
  targetStepIdRef.current = next == null ? null : currentStepIdRef.current;
323
- if (rectsEqual(targetRef.current, next)) return;
347
+ if (rectsEqual(targetRef.current, next) && rectsEqual(measuredRef.current, measured)) return;
348
+ measuredRef.current = measured;
324
349
  if (next == null) {
325
350
  // The anchor went away (unmount / navigation), so whatever scroll position
326
351
  // the user had settled on no longer applies. Treat its next appearance as
@@ -330,15 +355,15 @@ export function TooltipSurface({
330
355
  }
331
356
  targetRef.current = next;
332
357
  setTarget(next);
333
- if (next == null) {
358
+ if (next == null || measured == null) {
334
359
  clippedPressRef.current = null;
335
360
  return;
336
361
  }
337
- clippedPressRef.current = next;
362
+ clippedPressRef.current = measured;
338
363
  const fiber = hostFiberRef.current;
339
364
  const gen = (clipGenRef.current += 1);
340
365
  if (!fiber) return;
341
- void clipToScrollViewports(fiber, next).then((clipped) => {
366
+ void clipToScrollViewports(fiber, measured).then((clipped) => {
342
367
  if (clipGenRef.current !== gen) return;
343
368
  clippedPressRef.current = clipped;
344
369
  });
@@ -362,10 +387,28 @@ export function TooltipSurface({
362
387
  hostMeasureRef.current = null;
363
388
  hostScrollIntoViewRef.current = undefined;
364
389
  fiberCacheIdRef.current = null;
390
+ setConcealed(false);
365
391
  markMeasureAttempted();
366
392
  applyTarget(null);
367
393
  }, [applyTarget, markMeasureAttempted]);
368
394
 
395
+ /**
396
+ * Forget the resolved host while a natively presented modal hides it. Unlike
397
+ * {@link dropHost} the target is not gone: the missing-target timer does not
398
+ * run, so opening a sheet mid-step cannot flag or end the flow, and the
399
+ * tooltip comes back when the sheet closes.
400
+ */
401
+ const concealHost = React.useCallback(() => {
402
+ resolveEpochRef.current += 1;
403
+ measureInFlightRef.current = false;
404
+ hostFiberRef.current = null;
405
+ hostMeasureRef.current = null;
406
+ hostScrollIntoViewRef.current = undefined;
407
+ fiberCacheIdRef.current = null;
408
+ setConcealed(true);
409
+ applyTarget(null);
410
+ }, [applyTarget]);
411
+
369
412
  const effectiveViewport = React.useCallback(() => {
370
413
  const windowSize = Dimensions.get('window');
371
414
  const scrollPadding = getScrollPadding();
@@ -384,8 +427,9 @@ export function TooltipSurface({
384
427
  );
385
428
  }, []);
386
429
 
430
+ /** `rect` is in window coordinates; `measured` is the same rect in the host's measure space. */
387
431
  const maybeScrollIntoView = React.useCallback(
388
- (rect: Rect) => {
432
+ (rect: Rect, measured: Rect) => {
389
433
  // Scrolling the app to reveal a target we are fading away from is visible.
390
434
  if (exitingRef.current) return;
391
435
  if (scrollSettledRef.current) return;
@@ -406,8 +450,9 @@ export function TooltipSurface({
406
450
  ? { scrollIntoView: hostScrollIntoViewRef.current }
407
451
  : {}),
408
452
  };
453
+ // Scroll containers are measured in the same space as the host.
409
454
  scrollAnchorIntoView({
410
- rect,
455
+ rect: measured,
411
456
  host,
412
457
  fiber: hostFiberRef.current,
413
458
  });
@@ -418,6 +463,31 @@ export function TooltipSurface({
418
463
  [effectiveViewport],
419
464
  );
420
465
 
466
+ /** Measure callback for both the resolve and the rAF remeasure of `epoch`'s host. */
467
+ const onHostMeasured = React.useCallback(
468
+ (epoch: number, x: number, y: number, width: number, height: number) => {
469
+ if (resolveEpochRef.current !== epoch) {
470
+ measureInFlightRef.current = false;
471
+ return;
472
+ }
473
+ measureInFlightRef.current = false;
474
+ if (!mountedRef.current) return;
475
+
476
+ if (width <= 0 || height <= 0) {
477
+ const cacheId = fiberCacheIdRef.current;
478
+ if (cacheId) invalidateFiberAnchor(cacheId);
479
+ dropHost();
480
+ return;
481
+ }
482
+
483
+ const measured: Rect = { x, y, width, height };
484
+ const rect = offsetRect(measured, measureOriginRef.current);
485
+ maybeScrollIntoView(rect, measured);
486
+ applyTarget(rect, measured);
487
+ },
488
+ [applyTarget, dropHost, maybeScrollIntoView],
489
+ );
490
+
421
491
  /**
422
492
  * Resolve the anchor host (registry / fiber scan). Driven by
423
493
  * {@link TargetSearchSchedule} — intentionally NOT on every animation frame.
@@ -448,26 +518,25 @@ export function TooltipSurface({
448
518
  if (measureInFlightRef.current) return;
449
519
  const epoch = resolveEpochRef.current;
450
520
  measureInFlightRef.current = true;
451
- resolution.host.measureInWindow((x, y, width, height) => {
452
- if (resolveEpochRef.current !== epoch) {
521
+ const { host } = resolution;
522
+ const placedFiber = resolution.fiber ?? fiberFromPublicInstance(host.node);
523
+ // Refreshed per resolve, not per frame: a sheet moves rarely, and the
524
+ // lookup walks the whole tree.
525
+ locateTargetOrigin(placedFiber, (origin) => {
526
+ if (resolveEpochRef.current !== epoch || !mountedRef.current) {
453
527
  measureInFlightRef.current = false;
454
528
  return;
455
529
  }
456
- measureInFlightRef.current = false;
457
- if (!mountedRef.current) return;
458
-
459
- if (width <= 0 || height <= 0) {
460
- const cacheId = fiberCacheIdRef.current;
461
- if (cacheId) invalidateFiberAnchor(cacheId);
462
- dropHost();
530
+ if (!origin) {
531
+ concealHost();
463
532
  return;
464
533
  }
465
-
466
- const rect: Rect = { x, y, width, height };
467
- maybeScrollIntoView(rect);
468
- applyTarget(rect);
534
+ measureOriginRef.current = origin;
535
+ host.measureInWindow((x, y, width, height) => {
536
+ onHostMeasured(epoch, x, y, width, height);
537
+ });
469
538
  });
470
- }, [anchorId, applyTarget, decodedAutoData, dropHost, isAuto, maybeScrollIntoView]);
539
+ }, [anchorId, concealHost, decodedAutoData, dropHost, isAuto, onHostMeasured]);
471
540
 
472
541
  /**
473
542
  * Live re-measure of an already-resolved host. Driven by the shared rAF hub
@@ -482,25 +551,9 @@ export function TooltipSurface({
482
551
  const epoch = resolveEpochRef.current;
483
552
  measureInFlightRef.current = true;
484
553
  measure((x, y, width, height) => {
485
- if (resolveEpochRef.current !== epoch) {
486
- measureInFlightRef.current = false;
487
- return;
488
- }
489
- measureInFlightRef.current = false;
490
- if (!mountedRef.current) return;
491
-
492
- if (width <= 0 || height <= 0) {
493
- const cacheId = fiberCacheIdRef.current;
494
- if (cacheId) invalidateFiberAnchor(cacheId);
495
- dropHost();
496
- return;
497
- }
498
-
499
- const rect: Rect = { x, y, width, height };
500
- maybeScrollIntoView(rect);
501
- applyTarget(rect);
554
+ onHostMeasured(epoch, x, y, width, height);
502
555
  });
503
- }, [applyTarget, dropHost, maybeScrollIntoView]);
556
+ }, [onHostMeasured]);
504
557
 
505
558
  const resolveTargetRef = React.useRef(resolveTarget);
506
559
  resolveTargetRef.current = resolveTarget;
@@ -519,7 +572,8 @@ export function TooltipSurface({
519
572
  }
520
573
  const search = new TargetSearchSchedule(() => resolveTargetRef.current());
521
574
  searchRef.current = search;
522
- search.setMode(targetRef.current ? 'anchored' : 'missing');
575
+ // A concealed target keeps the steady poll, so the tooltip is back soon after the modal closes.
576
+ search.setMode(targetRef.current || concealedRef.current ? 'anchored' : 'missing');
523
577
 
524
578
  const trigger = () => search.trigger();
525
579
  const unsubscribers = [
@@ -554,8 +608,8 @@ export function TooltipSurface({
554
608
  }, [resolveArmed, searchStopped, currentStep.id, targetKey]);
555
609
 
556
610
  React.useEffect(() => {
557
- searchRef.current?.setMode(target ? 'anchored' : 'missing');
558
- }, [target]);
611
+ searchRef.current?.setMode(target || concealed ? 'anchored' : 'missing');
612
+ }, [target, concealed]);
559
613
 
560
614
  // rAF tracking while a host is resolved (and we are not paused after report).
561
615
  React.useEffect(() => {
@@ -590,7 +644,7 @@ export function TooltipSurface({
590
644
  return;
591
645
  }
592
646
 
593
- if (!measureAttempted) {
647
+ if (!measureAttempted || concealed) {
594
648
  return;
595
649
  }
596
650
 
@@ -606,6 +660,7 @@ export function TooltipSurface({
606
660
  }, [
607
661
  target,
608
662
  measureAttempted,
663
+ concealed,
609
664
  engine,
610
665
  currentStep.id,
611
666
  theme.tooltipMissingBehavior,
@@ -870,6 +925,11 @@ export function TooltipSurface({
870
925
  return null;
871
926
  }
872
927
 
928
+ // A native modal hides the target: nothing over it, not even the blocker.
929
+ if (isMissing && concealed) {
930
+ return null;
931
+ }
932
+
873
933
  const showBranding = session.flow.company.flowBranding;
874
934
  const contentPadding = chrome.contentPadding;
875
935
  // Modal only while the host is trapped too. Otherwise VoiceOver would skip
@@ -915,6 +975,7 @@ export function TooltipSurface({
915
975
  <Text
916
976
  style={{
917
977
  color: theme.fontColor,
978
+ fontFamily: contentFontFamily,
918
979
  fontSize: theme.fontSize,
919
980
  marginTop: 12,
920
981
  textAlign: 'center',
@@ -962,11 +1023,21 @@ export function TooltipSurface({
962
1023
  {showMissingUi ? (
963
1024
  <View testID="userflow-tooltip-missing">
964
1025
  <Text
965
- style={[styles.missingTitle, { color: theme.fontColor, fontSize: theme.fontSize }]}
1026
+ style={[
1027
+ styles.missingTitle,
1028
+ { color: theme.fontColor, fontFamily: contentFontFamily, fontSize: theme.fontSize },
1029
+ ]}
966
1030
  >
967
1031
  Target not found
968
1032
  </Text>
969
- <Text style={{ color: theme.fontColor, fontSize: theme.fontSize, opacity: 0.8 }}>
1033
+ <Text
1034
+ style={{
1035
+ color: theme.fontColor,
1036
+ fontFamily: contentFontFamily,
1037
+ fontSize: theme.fontSize,
1038
+ opacity: 0.8,
1039
+ }}
1040
+ >
970
1041
  This step's anchor is missing. Close to continue, or wait if the screen is still
971
1042
  loading.
972
1043
  </Text>
@@ -34,7 +34,6 @@ import {
34
34
  resolveBannerBarBorderRadius,
35
35
  } from '../bannerBarLayout';
36
36
  import { bannerOverlayShadowStyle } from '../bannerBarTokens';
37
- import { resolveBannerBackgroundColor } from '../resolveBannerTheme';
38
37
  import { useUserflowSafeAreaInsets } from '../safeArea';
39
38
 
40
39
  import { collectBannerAutoRunEvalCodes } from './bannerAutoRunEvalJs';
@@ -311,15 +310,18 @@ export function BannerWebViewBar({
311
310
  const frameStyle = bannerFrameStyle(banner);
312
311
  const overlayEdge = banner.embedMode === BannerEmbedMode.BODY_LAST ? 'bottom' : 'top';
313
312
  const barRadius = resolveBannerBarBorderRadius(banner);
314
- const backgroundColor = resolveBannerBackgroundColor(session.version.theme);
313
+ // Custom CSS paints the card, often with its own radius. The theme color
314
+ // (brand blue when banner tokens are unset) would otherwise fill the
315
+ // status-bar pad and the corners outside that radius.
316
+ const shellBackground = 'transparent';
315
317
  const webStyle: ViewStyle = {
316
318
  width: '100%',
317
319
  height: contentHeight,
318
- backgroundColor,
320
+ backgroundColor: shellBackground,
319
321
  };
320
322
  const debugging = typeof __DEV__ !== 'undefined' && __DEV__ ? true : undefined;
321
323
  const insetChrome = safeAreaEdge
322
- ? bannerPushLayoutChrome(safeAreaEdge, insets, backgroundColor)
324
+ ? bannerPushLayoutChrome(safeAreaEdge, insets, shellBackground)
323
325
  : null;
324
326
  const insetChromeOutsideFade = insetChrome != null && fadeIn;
325
327
 
@@ -1,38 +1,6 @@
1
- import type { BannerFragment, ThemeFragment } from '@userflow-tech/protocol';
2
- import { FlowActionType } from '@userflow-tech/protocol';
1
+ import type { BannerFragment } from '@userflow-tech/protocol';
3
2
 
4
- import { themeHasPublishedCustomCss } from './resolveCdnHost';
5
-
6
- let debugForceWebViewBanner = false;
7
-
8
- /** POC / local testing when the builder flag is not on the wire yet. */
9
- export function setDebugForceWebViewBanner(enabled: boolean): void {
10
- debugForceWebViewBanner = enabled;
11
- }
12
-
13
- /** @internal */
14
- export function resetDebugForceWebViewBannerForTests(): void {
15
- debugForceWebViewBanner = false;
16
- }
17
-
18
- function bannerHasEvalJsAction(banner: BannerFragment): boolean {
19
- return banner.buttons.some((button) =>
20
- button.actions.some((action) => action.type === FlowActionType.EVAL_JS),
21
- );
22
- }
23
-
24
- /**
25
- * Use the WebView banner renderer when any button runs Evaluate JavaScript
26
- * (needs the shared eval page), when the theme has published custom CSS
27
- * (`.userflow-custom-container-*` / `.userflow-custom-button-*` via CDN),
28
- * or when {@link setDebugForceWebViewBanner} is true.
29
- */
30
- export function bannerNeedsWebView(banner: BannerFragment, theme?: ThemeFragment | null): boolean {
31
- if (debugForceWebViewBanner) {
32
- return true;
33
- }
34
- if (theme != null && themeHasPublishedCustomCss(theme)) {
35
- return true;
36
- }
37
- return bannerHasEvalJsAction(banner);
3
+ /** Use the WebView banner renderer when the builder enabled custom banners. */
4
+ export function bannerNeedsWebView(banner: BannerFragment): boolean {
5
+ return banner.customBannerEnabled === true;
38
6
  }
@@ -1,5 +1,6 @@
1
1
  import type { ThemeFragment } from '@userflow-tech/protocol';
2
2
 
3
+ import { contentFontFamilyCss } from '../../content/resolveContentFontFamily';
3
4
  import { resolveBannerBackgroundColor, resolveBannerForegroundColor } from '../resolveBannerTheme';
4
5
 
5
6
  function readString(theme: ThemeFragment, key: string, fallback: string): string {
@@ -68,7 +69,7 @@ export function bannerThemeInlineStyles(theme: ThemeFragment): Record<string, st
68
69
  'bannerActiveForegroundColor',
69
70
  bannerFg,
70
71
  ),
71
- '--userflow-font-family': readString(theme, 'fontFamily', 'system-ui, sans-serif'),
72
+ '--userflow-font-family': contentFontFamilyCss(theme),
72
73
  '--userflow-font-size': `${fontSize}px`,
73
74
  '--userflow-line-height': `${lineHeight}px`,
74
75
  '--userflow-button-height': readCssLength(theme, 'buttonHeight', 32),
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Height the native WebView should use for a custom banner.
3
+ *
4
+ * Custom CSS often sizes the card with `min-height` and takes the copy and
5
+ * buttons out of flow (`position: absolute`). The WebView is created shorter
6
+ * than that card, so a root that only looks as tall as the viewport because of
7
+ * `min-height` must not be replaced with the close button's box.
8
+ *
9
+ * When the root really is stretching to fill a taller viewport (`height: 100%`),
10
+ * fall back to in-flow content, and never below `min-height`.
11
+ */
12
+ export function resolveBannerWebViewHeight(
13
+ rectHeight: number,
14
+ scrollHeight: number,
15
+ viewportHeight: number,
16
+ minHeight: number,
17
+ contentHeight: number,
18
+ ): number {
19
+ const rect = rectHeight > 0 ? rectHeight : 0;
20
+ const scroll = scrollHeight > 0 ? scrollHeight : 0;
21
+ let height = Math.ceil(Math.max(rect, scroll));
22
+ const floor = minHeight > 0 ? Math.ceil(minHeight) : 0;
23
+ if (viewportHeight > 0 && height >= viewportHeight - 2 && height > floor + 1) {
24
+ if (contentHeight > 0 && contentHeight < height) {
25
+ height = Math.ceil(contentHeight > floor ? contentHeight : floor);
26
+ }
27
+ }
28
+ if (floor > height) {
29
+ height = floor;
30
+ }
31
+ return height;
32
+ }
@@ -2,6 +2,10 @@ import { interpolateVariables } from '@userflow-tech/core';
2
2
  import type { BannerFragment, BannerSessionFragment } from '@userflow-tech/protocol';
3
3
  import { BannerContentLayout } from '@userflow-tech/protocol';
4
4
 
5
+ import {
6
+ bannerContentFontLockCss,
7
+ contentFontFaceCss,
8
+ } from '../../content/resolveContentFontFamily';
5
9
  import { buildBootstrapHtml, wrapCode } from '../../evalJs/bootstrap';
6
10
  import type { EvalJsContext } from '../../evalJs/types';
7
11
 
@@ -27,26 +31,45 @@ function contentLayoutClass(layout: BannerContentLayout): string {
27
31
  }
28
32
 
29
33
  function layoutMeasureScript(): string {
34
+ // Inlined on purpose. Hermes' Function#toString is not the source text, so
35
+ // interpolating resolveBannerWebViewHeight.toString() ships a dead function.
36
+ // Keep this body in sync with resolveBannerWebViewHeight.
30
37
  return `
31
38
  (function(){
39
+ function resolveBannerWebViewHeight(rectHeight,scrollHeight,viewportHeight,minHeight,contentHeight){
40
+ var rect=rectHeight>0?rectHeight:0;
41
+ var scroll=scrollHeight>0?scrollHeight:0;
42
+ var height=Math.ceil(Math.max(rect,scroll));
43
+ var floor=minHeight>0?Math.ceil(minHeight):0;
44
+ if(viewportHeight>0&&height>=viewportHeight-2&&height>floor+1){
45
+ if(contentHeight>0&&contentHeight<height){
46
+ height=Math.ceil(contentHeight>floor?contentHeight:floor);
47
+ }
48
+ }
49
+ if(floor>height) height=floor;
50
+ return height;
51
+ }
32
52
  function postLayout(){
33
53
  var root=document.querySelector('.userflowjs-banner-root');
34
54
  if(!root||!window.ReactNativeWebView||!window.ReactNativeWebView.postMessage)return;
35
55
  var rectH=root.getBoundingClientRect().height;
36
56
  var scrollH=root.scrollHeight;
37
- var h=Math.ceil(Math.max(scrollH,rectH));
57
+ var minH=parseFloat(window.getComputedStyle(root).minHeight);
58
+ if(!(minH>0)) minH=0;
38
59
  var vh=window.innerHeight||0;
39
- if(vh>0&&h>=vh-2){
40
- var sum=0;
41
- for(var i=0;i<root.childNodes.length;i++){
42
- var node=root.childNodes[i];
43
- if(node&&node.nodeType===1){
44
- var el=node;
45
- sum+=el.scrollHeight||el.offsetHeight||0;
46
- }
47
- }
48
- if(sum>0&&sum<h) h=Math.ceil(sum);
60
+ var inFlow=0;
61
+ var all=0;
62
+ for(var i=0;i<root.childNodes.length;i++){
63
+ var node=root.childNodes[i];
64
+ if(!node||node.nodeType!==1) continue;
65
+ var box=node.scrollHeight||node.offsetHeight||0;
66
+ all+=box;
67
+ var cs=window.getComputedStyle(node);
68
+ if(cs.display==='none'||cs.position==='absolute'||cs.position==='fixed') continue;
69
+ inFlow+=box;
49
70
  }
71
+ var content=inFlow>0?inFlow:all;
72
+ var h=resolveBannerWebViewHeight(rectH,scrollH,vh,minH,content);
50
73
  window.ReactNativeWebView.postMessage(JSON.stringify({__userflow:1,type:'bannerLayout',height:h}));
51
74
  }
52
75
  window.__userflowBannerLayout=postLayout;
@@ -54,8 +77,18 @@ function layoutMeasureScript(): string {
54
77
  var root=document.querySelector('.userflowjs-banner-root');
55
78
  if(root){new ResizeObserver(postLayout).observe(root);}
56
79
  }
80
+ var links=document.querySelectorAll('link[rel="stylesheet"]');
81
+ for(var s=0;s<links.length;s++){
82
+ links[s].addEventListener('load',postLayout);
83
+ links[s].addEventListener('error',postLayout);
84
+ }
57
85
  window.addEventListener('load',postLayout);
86
+ // Custom CSS is often still applying on the first pass. A short WebView
87
+ // then clamps used height, so min-height never changes the box and
88
+ // ResizeObserver does not fire. Measure again after the stylesheet can apply.
58
89
  setTimeout(postLayout,0);
90
+ setTimeout(postLayout,60);
91
+ setTimeout(postLayout,250);
59
92
  })();
60
93
  `;
61
94
  }
@@ -171,6 +204,8 @@ export function buildCustomBannerBodyHtml(
171
204
  const theme = session.version.theme;
172
205
  const layoutClass = contentLayoutClass(banner.contentLayout);
173
206
  const themeStyle = bannerThemeStyleAttribute(theme);
207
+ const fontFaceCss = contentFontFaceCss(theme);
208
+ const fontLockCss = bannerContentFontLockCss(theme);
174
209
  const customCssUrl = resolveCustomStylesheetUrl(serverEndpoint, theme);
175
210
  const innerMaxWidth = resolveBannerInnerMaxWidth(banner);
176
211
  const maxInner =
@@ -200,8 +235,9 @@ export function buildCustomBannerBodyHtml(
200
235
  : '';
201
236
 
202
237
  return `
203
- <style>${BANNER_WEB_FRAME_CSS}</style>
238
+ <style>${fontFaceCss}${BANNER_WEB_FRAME_CSS}</style>
204
239
  ${customLink}
240
+ <style>${fontLockCss}</style>
205
241
  <div class="userflowjs-banner-root userflowjs-theme-root ${layoutClass}" style="${themeStyle}">
206
242
  <div class="userflowjs-banner-start-spacer"></div>
207
243
  <div class="userflowjs-banner-inner"${maxInner}>
@@ -90,16 +90,14 @@ export function buildHighlightRings(rect: HighlightRect, chrome: HighlightChrome
90
90
 
91
91
  function strokeGlow(chrome: HighlightChrome): ViewStyle {
92
92
  const blur = Math.max(chrome.radius, 6);
93
- const glow: ViewStyle = {
93
+ // Built in one literal: React Native 0.88 types ViewStyle as readonly.
94
+ return {
94
95
  shadowColor: chrome.color,
95
96
  shadowOffset: { width: 0, height: 0 },
96
97
  shadowOpacity: Math.min(0.45, chrome.opacity),
97
98
  shadowRadius: blur,
99
+ ...(Platform?.OS !== 'ios' ? { boxShadow: `0px 0px ${blur}px 0px ${chrome.glowColor}` } : null),
98
100
  };
99
- if (Platform?.OS !== 'ios') {
100
- glow.boxShadow = `0px 0px ${blur}px 0px ${chrome.glowColor}`;
101
- }
102
- return glow;
103
101
  }
104
102
 
105
103
  function ringFrame(
@@ -0,0 +1,74 @@
1
+ import { fiberScanRoot } from '../../anchors/fiber/FiberScanRootStore';
2
+ import type { AnyFiber } from '../../anchors/fiber/fiberInternals';
3
+ import {
4
+ locatePresentedModal,
5
+ measureOriginOf,
6
+ type Point,
7
+ } from '../../anchors/fiber/presentedModal';
8
+ import { getFiberAnchorScreenKey } from '../../anchors/fiberAnchorCache';
9
+ import { hasWindowOverlayHost } from '../../runtime/WindowOverlayHost';
10
+
11
+ import type { Rect } from './inferTooltipPlacement';
12
+
13
+ const ZERO: Point = { x: 0, y: 0 };
14
+
15
+ /**
16
+ * How long "nothing is presented" is trusted on the same screen. The lookup
17
+ * walks the whole fiber tree (1.4ms for 2,200 fibers in a debug build), and
18
+ * an anchored tooltip resolves four times a second. Expo Router and native
19
+ * stack modals change the screen, which skips this wait; a React Native
20
+ * `<Modal>` over the target is noticed within it.
21
+ */
22
+ export const NO_MODAL_TRUST_MS = 1000;
23
+
24
+ let noModalSince: { at: number; screenKey: string | null } | null = null;
25
+
26
+ /**
27
+ * Where a tooltip target's measured rects sit in the window. `done` gets the
28
+ * window position of `(0, 0)` in the target's measure space, or null when the
29
+ * tooltip cannot point at it: a natively presented modal covers the target,
30
+ * or the target is inside a modal that cannot be placed (see
31
+ * `presentedModal.ts`). Also null when the target has no fiber to place.
32
+ *
33
+ * Without the window host, flow chrome is drawn in the root view under any
34
+ * native modal, as before, and `done` runs synchronously with zero.
35
+ */
36
+ export function locateTargetOrigin(
37
+ fiber: AnyFiber | null,
38
+ done: (origin: Point | null) => void,
39
+ ): void {
40
+ if (!hasWindowOverlayHost()) {
41
+ done(ZERO);
42
+ return;
43
+ }
44
+ const screenKey = getFiberAnchorScreenKey();
45
+ const now = Date.now();
46
+ if (
47
+ noModalSince &&
48
+ noModalSince.screenKey === screenKey &&
49
+ now - noModalSince.at < NO_MODAL_TRUST_MS
50
+ ) {
51
+ done(ZERO);
52
+ return;
53
+ }
54
+ locatePresentedModal(fiberScanRoot.getFiber()).then(
55
+ (modal) => {
56
+ noModalSince = modal == null ? { at: now, screenKey } : null;
57
+ done(modal == null ? ZERO : fiber ? measureOriginOf(fiber, modal) : null);
58
+ },
59
+ () => {
60
+ noModalSince = null;
61
+ done(null);
62
+ },
63
+ );
64
+ }
65
+
66
+ export function offsetRect(rect: Rect, origin: Point): Rect {
67
+ if (origin.x === 0 && origin.y === 0) return rect;
68
+ return { ...rect, x: rect.x + origin.x, y: rect.y + origin.y };
69
+ }
70
+
71
+ /** @internal Forgets the cached "nothing is presented" between tests. */
72
+ export function resetTargetOriginForTests(): void {
73
+ noModalSince = null;
74
+ }