@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
@@ -17,11 +17,16 @@ import {
17
17
  } from '../anchors/fiber/fiberInternals';
18
18
  import { createLazyHostFiberIndex, lookupHostFiber } from '../anchors/fiber/indexHostFibers';
19
19
  import { measureHostFiber } from '../anchors/fiber/measureHostFiber';
20
+ import {
21
+ isOutsideModal,
22
+ locatePresentedModal,
23
+ rectInWindow,
24
+ type PresentedModal,
25
+ } from '../anchors/fiber/presentedModal';
20
26
  import { readHostText } from '../anchors/fiber/readHostContent';
21
27
  import {
22
28
  clipToScrollViewports,
23
29
  fiberIsUnder,
24
- topmostVisibleModalHost,
25
30
  type VisibleRect,
26
31
  } from '../anchors/fiber/visibleRect';
27
32
  import { isFiberOnVisibleScreen } from '../anchors/fiber/visibleScreens';
@@ -61,7 +66,17 @@ export interface HitCandidate {
61
66
 
62
67
  export type MeasureInstance = (inst: AutoAnchorInstance) => Promise<HitRect | null>;
63
68
  export type InstanceFilter = (inst: AutoAnchorInstance) => boolean;
64
- export type HostAtPoint = (x: number, y: number) => Promise<AnyFiber | null>;
69
+ /**
70
+ * Why a tap on a presented modal found nothing: the modal cannot be placed in
71
+ * the window, or the tap was outside it.
72
+ */
73
+ export type ModalMiss = 'unplaceable' | 'outside';
74
+
75
+ export type HostAtPoint = (
76
+ x: number,
77
+ y: number,
78
+ presented?: PresentedModal | null,
79
+ ) => Promise<AnyFiber | null>;
65
80
 
66
81
  const MEASURE_TIMEOUT_MS = 400;
67
82
 
@@ -259,10 +274,14 @@ function targetForHost(
259
274
  * {@link FiberScanRoot}. Hosts above it (the provider's own wrappers) are never
260
275
  * resolvable at play time. Empty when `host` is on a covered screen: its
261
276
  * ancestors above the navigator are not what was tapped.
277
+ *
278
+ * The walk also stops at `boundary`, a presented modal: hosts above it are
279
+ * drawn beneath the modal, not around what was tapped.
262
280
  */
263
281
  export function candidateChain(
264
282
  host: AnyFiber,
265
283
  byFiber: Map<AnyFiber, AutoAnchorInstance>,
284
+ boundary?: AnyFiber | null,
266
285
  ): PickTarget[] {
267
286
  const chain: PickTarget[] = [];
268
287
  if (!isFiberOnVisibleScreen(host)) return chain;
@@ -281,6 +300,7 @@ export function candidateChain(
281
300
  seenFibers.add(target.fiber);
282
301
  chain.push(target);
283
302
  }
303
+ if (boundary && (current === boundary || current === getAlternate(boundary))) break;
284
304
  current = getReturn(current);
285
305
  }
286
306
  return chain;
@@ -335,16 +355,30 @@ export function defaultCandidateIndex(candidates: HitCandidate[]): number {
335
355
  return index >= 0 ? index : 0;
336
356
  }
337
357
 
358
+ function targetFiber(target: PickTarget): AnyFiber | null {
359
+ return target.source === 'fiber'
360
+ ? target.fiber
361
+ : fiberFromPublicInstanceSafe(target.instance.ref.current);
362
+ }
363
+
338
364
  async function clipCandidate(candidate: HitCandidate): Promise<HitCandidate | null> {
339
- const fiber =
340
- candidate.target.source === 'fiber'
341
- ? candidate.target.fiber
342
- : fiberFromPublicInstanceSafe(candidate.target.instance.ref.current);
365
+ const fiber = targetFiber(candidate.target);
343
366
  const clipped = await clipToScrollViewports(fiber, candidate.rect as VisibleRect);
344
367
  if (!clipped) return null;
345
368
  return { ...candidate, rect: clipped, area: areaOf(clipped) };
346
369
  }
347
370
 
371
+ /** `candidate` in window coordinates. A presented modal's content is measured in the modal's space. */
372
+ function candidateInWindow(
373
+ candidate: HitCandidate,
374
+ presented: PresentedModal | null,
375
+ ): HitCandidate {
376
+ if (!presented) return candidate;
377
+ const fiber = targetFiber(candidate.target);
378
+ if (fiber && !fiberIsUnder(fiber, presented.host)) return candidate;
379
+ return { ...candidate, rect: rectInWindow(candidate.rect, presented) };
380
+ }
381
+
348
382
  /**
349
383
  * Hit-test window coordinates.
350
384
  *
@@ -353,6 +387,12 @@ async function clipCandidate(candidate: HitCandidate): Promise<HitCandidate | nu
353
387
  * lands on a covered screen. A visible native hit with no identifiable
354
388
  * ancestor returns no candidates: geometry ignores stacking order and would
355
389
  * offer whatever lies underneath the tap.
390
+ *
391
+ * While a modal is presented, only its content is a candidate, and candidate
392
+ * rects come back in window coordinates. A modal that cannot be placed in the
393
+ * window yields no candidates rather than a guess, and so does a tap outside
394
+ * the modal, on the screen it covers. Both call `onModalMiss` so the caller
395
+ * can say why.
356
396
  */
357
397
  export async function hitTestAt(
358
398
  x: number,
@@ -362,6 +402,7 @@ export async function hitTestAt(
362
402
  measure?: MeasureInstance;
363
403
  instances?: AutoAnchorInstance[];
364
404
  hostAtPoint?: HostAtPoint;
405
+ onModalMiss?: (miss: ModalMiss) => void;
365
406
  },
366
407
  ): Promise<HitCandidate[]> {
367
408
  const filter = opts?.instanceFilter ?? createVisibleInstanceFilter();
@@ -369,21 +410,33 @@ export async function hitTestAt(
369
410
  const pool = (opts?.instances ?? autoAnchorRegistry.allInstances()).filter(filter);
370
411
  autoAnchorRegistry.refreshLiveContent(pool);
371
412
 
413
+ const presented = await locatePresentedModal(fiberScanRoot.getFiber());
414
+ if (presented && !presented.origin) {
415
+ opts?.onModalMiss?.('unplaceable');
416
+ return [];
417
+ }
418
+ if (presented && isOutsideModal(x, y, presented)) {
419
+ opts?.onModalMiss?.('outside');
420
+ return [];
421
+ }
422
+
372
423
  const injected = opts?.hostAtPoint;
373
424
  const locate = injected ?? (opts?.measure === undefined ? hostAtPoint : null);
374
425
  if (locate) {
375
426
  try {
376
- const host = await locate(x, y);
427
+ const host = await locate(x, y, presented);
377
428
  if (host && isFiberOnVisibleScreen(host)) {
378
- const chain = candidateChain(host, registryMap(pool));
379
- return chain.length > 0 ? candidatesFromChain(chain, measure) : [];
429
+ const chain = candidateChain(host, registryMap(pool), presented?.host);
430
+ if (chain.length === 0) return [];
431
+ const candidates = await candidatesFromChain(chain, measure);
432
+ return candidates.map((candidate) => candidateInWindow(candidate, presented));
380
433
  }
381
434
  } catch {
382
435
  // Native hit-testing is optional; geometry still runs.
383
436
  }
384
437
  }
385
438
 
386
- return geometryHits(x, y, pool, measure);
439
+ return geometryHits(x, y, pool, measure, presented);
387
440
  }
388
441
 
389
442
  async function geometryHits(
@@ -391,9 +444,10 @@ async function geometryHits(
391
444
  y: number,
392
445
  pool: AutoAnchorInstance[],
393
446
  measure: MeasureInstance,
447
+ presented: PresentedModal | null,
394
448
  ): Promise<HitCandidate[]> {
395
449
  const root = fiberScanRoot.getFiber();
396
- const modal = root ? topmostVisibleModalHost(root) : null;
450
+ const modal = presented?.host ?? null;
397
451
  const byFiber = registryMap(pool);
398
452
 
399
453
  const scoped = modal
@@ -426,14 +480,35 @@ async function geometryHits(
426
480
  if (!raw) return null;
427
481
  const clipped = await clipCandidate({ target, rect: raw, area: areaOf(raw) });
428
482
  if (!clipped) return null;
429
- if (!pointInRect(x, y, clipped.rect)) return null;
430
- return clipped;
483
+ const placed = candidateInWindow(clipped, presented);
484
+ if (!pointInRect(x, y, placed.rect)) return null;
485
+ return placed;
431
486
  }),
432
487
  );
433
488
 
434
489
  return measured.filter((c): c is HitCandidate => c != null).sort((a, b) => a.area - b.area);
435
490
  }
436
491
 
492
+ /**
493
+ * `candidates` measured again, for when the screen may have moved under a
494
+ * selection: the keyboard opening or closing resizes or moves a sheet. Null
495
+ * when one can no longer be measured, or its modal can no longer be placed,
496
+ * so the caller drops the selection rather than keep a stale rect.
497
+ */
498
+ export async function remeasureCandidates(
499
+ candidates: HitCandidate[],
500
+ measure: MeasureInstance = measureInstanceRect,
501
+ ): Promise<HitCandidate[] | null> {
502
+ const presented = await locatePresentedModal(fiberScanRoot.getFiber());
503
+ if (presented && !presented.origin) return null;
504
+ const measured = await candidatesFromChain(
505
+ candidates.map((candidate) => candidate.target),
506
+ measure,
507
+ );
508
+ if (measured.length !== candidates.length) return null;
509
+ return measured.map((candidate) => candidateInWindow(candidate, presented));
510
+ }
511
+
437
512
  /** Cycle to the next-larger containing candidate (wraps). */
438
513
  export function nextContainingIndex(candidates: HitCandidate[], currentIndex: number): number {
439
514
  if (candidates.length === 0) return -1;
@@ -11,9 +11,10 @@
11
11
  * (see `FindNodeAtPointTest.overlappingViewsWithParentPointerEventsNone`).
12
12
  * - Scroll views subtract `contentOffset` in `getContentOriginOffset`, so
13
13
  * content scrolled out of the viewport is not a hit.
14
- * - Modal content lives under `ModalHostView` in the same shadow tree, but
15
- * its frame is relative to the modal window. If the root hit misses the
16
- * visible modal's subtree, retry rooted at that modal.
14
+ * - Content of a presented modal (`RNSModalScreen`, `ModalHostView`) lives in
15
+ * the same shadow tree, but Fabric lays it out relative to the modal. While
16
+ * one is presented, everything else is drawn beneath it, so only the modal is
17
+ * hit, with the point moved into its space (see `presentedModal.ts`).
17
18
  * - Screens a navigator covers stay laid out in the shadow tree, on top of the
18
19
  * visible one, and react-native-screens clears their zIndex, so the
19
20
  * last-mounted sibling wins. After visiting a later tab, a tap on an earlier
@@ -33,7 +34,11 @@ import {
33
34
  import { nearestHostDescendant } from '../anchors/fiber/findAnchorFiber';
34
35
  import { createHostFiberIndex } from '../anchors/fiber/indexHostFibers';
35
36
  import { measureHostFiber } from '../anchors/fiber/measureHostFiber';
36
- import { fiberIsUnder, topmostVisibleModalHost } from '../anchors/fiber/visibleRect';
37
+ import {
38
+ locatePresentedModal,
39
+ type Point,
40
+ type PresentedModal,
41
+ } from '../anchors/fiber/presentedModal';
37
42
  import { isFiberOnVisibleScreen, screensCoveringFiber } from '../anchors/fiber/visibleScreens';
38
43
 
39
44
  const HIT_TIMEOUT_MS = 400;
@@ -167,11 +172,20 @@ function originInParent(
167
172
  }, none);
168
173
  }
169
174
 
170
- async function hitFabric(rootHost: AnyFiber, x: number, y: number): Promise<AnyFiber | null> {
175
+ /**
176
+ * `rootOrigin` is where `rootHost` sits in the point's space, for a root that
177
+ * `measureInWindow` cannot place: a presented modal, whose space is its own.
178
+ */
179
+ async function hitFabric(
180
+ rootHost: AnyFiber,
181
+ x: number,
182
+ y: number,
183
+ rootOrigin?: Point,
184
+ ): Promise<AnyFiber | null> {
171
185
  const manager = fabricManager();
172
186
  const node = shadowNodeFromHost(rootHost);
173
187
  if (!manager || node == null) return null;
174
- const origin = await measureOrigin(rootHost);
188
+ const origin = rootOrigin ?? (await measureOrigin(rootHost));
175
189
  if (!origin) return null;
176
190
  const inParent = await originInParent(manager, node);
177
191
  const handle = await withTimeout<unknown>((settle) => {
@@ -199,25 +213,43 @@ async function hitPaper(rootHost: AnyFiber, x: number, y: number): Promise<AnyFi
199
213
  return index.byNativeTag.get(nativeTag) ?? null;
200
214
  }
201
215
 
202
- async function hitUnder(rootHost: AnyFiber, x: number, y: number): Promise<AnyFiber | null> {
203
- const fabric = await hitFabric(rootHost, x, y);
216
+ /**
217
+ * `inModalSpace`: the point is in a presented modal's own space. Paper's
218
+ * lookup takes window points, so it would land elsewhere there (and
219
+ * bridgeless logs an error when it finds nothing).
220
+ */
221
+ async function hitUnder(
222
+ rootHost: AnyFiber,
223
+ x: number,
224
+ y: number,
225
+ rootOrigin: Point | undefined,
226
+ inModalSpace: boolean,
227
+ ): Promise<AnyFiber | null> {
228
+ const fabric = await hitFabric(rootHost, x, y, rootOrigin);
204
229
  if (fabric) return fabric;
230
+ if (inModalSpace) return null;
205
231
  return hitPaper(rootHost, x, y);
206
232
  }
207
233
 
208
- /** {@link hitUnder}, re-hit from the visible screen while the hit lands on a covered one. */
234
+ /**
235
+ * {@link hitUnder}, re-hit from the visible screen while the hit lands on a
236
+ * covered one. A `rootOrigin` means the point is in a presented modal's space;
237
+ * screens inside the modal measure in that space too.
238
+ */
209
239
  async function hitOnVisibleScreen(
210
240
  rootHost: AnyFiber,
211
241
  x: number,
212
242
  y: number,
243
+ rootOrigin?: Point,
213
244
  ): Promise<AnyFiber | null> {
214
- let hit = await hitUnder(rootHost, x, y);
245
+ const inModalSpace = rootOrigin !== undefined;
246
+ let hit = await hitUnder(rootHost, x, y, rootOrigin, inModalSpace);
215
247
  for (let attempt = 0; hit && attempt < MAX_COVERED_SCREEN_RETRIES; attempt += 1) {
216
248
  const screens = screensCoveringFiber(hit);
217
249
  if (screens.length === 0) return hit;
218
250
  let next: AnyFiber | null = null;
219
251
  for (const screen of screens) {
220
- next = await hitUnder(screen, x, y);
252
+ next = await hitUnder(screen, x, y, undefined, inModalSpace);
221
253
  if (next) break;
222
254
  }
223
255
  hit = next;
@@ -226,22 +258,36 @@ async function hitOnVisibleScreen(
226
258
  return hit && isFiberOnVisibleScreen(hit) ? hit : null;
227
259
  }
228
260
 
261
+ const MODAL_SPACE_ORIGIN: Point = { x: 0, y: 0 };
262
+
229
263
  /**
230
264
  * Host fiber drawn on top at window coordinates, or null when neither Fabric
231
265
  * nor Paper hit-testing is available.
266
+ *
267
+ * While a modal is presented, only its content can be hit, and nothing is
268
+ * when the modal cannot be placed in the window. Pass `presented` when the
269
+ * caller has already looked it up.
232
270
  */
233
- export async function hostAtPoint(x: number, y: number): Promise<AnyFiber | null> {
271
+ export async function hostAtPoint(
272
+ x: number,
273
+ y: number,
274
+ presented?: PresentedModal | null,
275
+ ): Promise<AnyFiber | null> {
234
276
  const root = fiberScanRoot.getFiber();
235
277
  if (!root) return null;
236
- const rootHost = nearestHostDescendant(root);
237
- if (!rootHost) return null;
238
278
 
239
- let hit = await hitOnVisibleScreen(rootHost, x, y);
240
- const modal = topmostVisibleModalHost(root);
241
- const modalHost = modal ? nearestHostDescendant(modal) : null;
242
- if (modalHost && (!hit || !fiberIsUnder(hit, modal))) {
243
- const modalHit = await hitOnVisibleScreen(modalHost, x, y);
244
- if (modalHit) hit = modalHit;
279
+ const modal = presented === undefined ? await locatePresentedModal(root) : presented;
280
+ if (modal) {
281
+ if (!modal.origin) return null;
282
+ return hitOnVisibleScreen(
283
+ modal.host,
284
+ x - modal.origin.x,
285
+ y - modal.origin.y,
286
+ modal.modalSpace ? MODAL_SPACE_ORIGIN : undefined,
287
+ );
245
288
  }
246
- return hit;
289
+
290
+ const rootHost = nearestHostDescendant(root);
291
+ if (!rootHost) return null;
292
+ return hitOnVisibleScreen(rootHost, x, y);
247
293
  }
@@ -14,6 +14,7 @@ import {
14
14
  } from '../surfaces/resolveBannerTheme';
15
15
 
16
16
  import { resolveBannerButtonChrome } from './resolveBannerButtonChrome';
17
+ import { resolveContentFontFamily } from './resolveContentFontFamily';
17
18
  import { stepButtonContainerStyle } from './resolveStepButtonChrome';
18
19
 
19
20
  export interface BannerStepButtonProps {
@@ -57,7 +58,7 @@ export function BannerStepButton({
57
58
  styles.label,
58
59
  {
59
60
  color: chrome.fontColor,
60
- fontFamily: theme.fontFamily || undefined,
61
+ fontFamily: resolveContentFontFamily(theme),
61
62
  fontSize: chrome.fontSize,
62
63
  fontWeight: chrome.fontWeight,
63
64
  ...labelMetrics(chrome.lineHeight),
@@ -19,6 +19,7 @@ import {
19
19
  } from './deserialize';
20
20
  import { resolveImageFrame } from './imageLayout';
21
21
  import { migrateFlowBlocksIntoContent } from './migrateFlowBlocks';
22
+ import { resolveContentFontFamily } from './resolveContentFontFamily';
22
23
  import {
23
24
  ROOT_CONTENT_GAP,
24
25
  WEB_CODE_BG_ALPHA,
@@ -292,7 +293,8 @@ function TextBlock({
292
293
  | Heading1Element
293
294
  | Heading2Element
294
295
  | { align?: TextAlign | null; children: RichContentInlineNode[] };
295
- style: TextStyle;
296
+ // React Native 0.88's StyleSheet.flatten can return null or undefined.
297
+ style: TextStyle | null | undefined;
296
298
  accessibilityRole?: 'header' | 'text';
297
299
  }): React.ReactElement {
298
300
  return (
@@ -563,22 +565,27 @@ function RenderedButton({
563
565
  }
564
566
 
565
567
  function baseText(theme: ThemeFragment): TextStyle {
568
+ const fontFamily = resolveContentFontFamily(theme);
566
569
  return {
567
570
  color: theme.fontColor,
568
- fontFamily: theme.fontFamily || undefined,
569
571
  fontSize: theme.fontSize,
572
+ // Omit the key when unset. An explicit `undefined` is flattened to null,
573
+ // and RN treats that as "use the default face" rather than keeping a
574
+ // family set earlier on the same run.
575
+ ...(fontFamily != null ? { fontFamily } : {}),
570
576
  };
571
577
  }
572
578
 
573
579
  function markStyle(node: AbstractRichContentTextFormatting): TextStyle {
580
+ const decoration = textDecoration(node);
581
+ const fontSize = node.small ? 12 : node.sup ? 10 : undefined;
582
+ // Built in one literal: React Native 0.88 types TextStyle as readonly.
574
583
  return {
575
- fontWeight: node.bold ? '700' : undefined,
576
- fontStyle: node.italic ? 'italic' : undefined,
577
- textDecorationLine: textDecoration(node),
578
- fontSize: node.small ? 12 : node.sup ? 10 : undefined,
579
- color: node.color || undefined,
580
- // Inline `code` uses View chrome via RenderedText / RenderedAttribute.
581
- fontFamily: node.code ? monoFontFamily() : undefined,
584
+ ...(node.bold ? { fontWeight: '700' as const } : null),
585
+ ...(node.italic ? { fontStyle: 'italic' as const } : null),
586
+ ...(decoration ? { textDecorationLine: decoration } : null),
587
+ ...(fontSize != null ? { fontSize } : null),
588
+ ...(node.color ? { color: node.color } : null),
582
589
  };
583
590
  }
584
591
 
@@ -9,6 +9,7 @@ import type { TextStyle } from 'react-native';
9
9
  import { Platform, Pressable, StyleSheet, Text } from 'react-native';
10
10
 
11
11
  import { isPrimaryButtonAppearance } from './buttonAppearance';
12
+ import { resolveContentFontFamily } from './resolveContentFontFamily';
12
13
  import { resolveStepButtonChrome, stepButtonContainerStyle } from './resolveStepButtonChrome';
13
14
 
14
15
  export interface StepButtonProps {
@@ -45,7 +46,7 @@ export function StepButton({
45
46
  styles.label,
46
47
  {
47
48
  color: chrome.fontColor,
48
- fontFamily: theme.fontFamily || undefined,
49
+ fontFamily: resolveContentFontFamily(theme),
49
50
  fontSize: chrome.fontSize,
50
51
  fontWeight: chrome.fontWeight,
51
52
  ...labelMetrics(chrome.lineHeight),
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Font family for SDK content text.
3
+ *
4
+ * A single concrete theme family is kept. A comma-separated custom list keeps
5
+ * the first family that is not part of the default system stack. That stack,
6
+ * generics, and empty values use the detected app font, or omit `fontFamily`
7
+ * so the platform face renders with no warning.
8
+ */
9
+ import type { ThemeFragment } from '@userflow-tech/protocol';
10
+
11
+ import {
12
+ concreteFontFamily,
13
+ detectAppFontFamily,
14
+ isBareCssFamily,
15
+ isIconFontFamily,
16
+ splitCssFamilies,
17
+ themeConcreteFontFamily,
18
+ } from '../anchors/fiber/detectAppFontFamily';
19
+
20
+ export type AppFontDetector = () => string | null;
21
+
22
+ const CSS_FONT_FALLBACK = 'system-ui, sans-serif';
23
+
24
+ export function resolveContentFontFamily(
25
+ theme: ThemeFragment,
26
+ detect: AppFontDetector = detectAppFontFamily,
27
+ ): string | undefined {
28
+ return explicitThemeFamily(theme) ?? detectedFamily(detect);
29
+ }
30
+
31
+ /**
32
+ * CSS `font-family` for banner WebViews. Names are quoted so a multi-word
33
+ * family stays one face. A substituted app face keeps a platform fallback for
34
+ * when the WebView cannot resolve it.
35
+ */
36
+ export function contentFontFamilyCss(
37
+ theme: ThemeFragment,
38
+ detect: AppFontDetector = detectAppFontFamily,
39
+ ): string {
40
+ const authored = explicitThemeCss(theme);
41
+ if (authored) return authored;
42
+ const family = detectedFamily(detect);
43
+ if (!family) return CSS_FONT_FALLBACK;
44
+ return `${quoteCssFamily(family)}, ${CSS_FONT_FALLBACK}`;
45
+ }
46
+
47
+ /**
48
+ * `@font-face` rule that publishes the native family name inside the WebView.
49
+ *
50
+ * One regular face only. A weight range would make that static file match
51
+ * bold, and an italic rule with the same upright source would match italic,
52
+ * so the WebView would not synthesize either. `local()` is the family native
53
+ * text already uses. A `file://` source is not included: iOS treats that URL
54
+ * as a navigation and replaces the banner page.
55
+ */
56
+ export function contentFontFaceCss(
57
+ theme: ThemeFragment,
58
+ detect: AppFontDetector = detectAppFontFamily,
59
+ ): string {
60
+ const family = resolvedFamily(theme, detect);
61
+ if (!family) return '';
62
+ const quoted = quoteCssFamily(family);
63
+ return `@font-face{font-family:${quoted};src:local(${quoted});font-weight:400;font-style:normal;font-display:swap;}`;
64
+ }
65
+
66
+ /**
67
+ * Re-applies the banner face after theme custom.css. Equal-specificity rules
68
+ * in that stylesheet would otherwise replace it with a web font the WebView
69
+ * has not loaded. Code stays mono, including `<strong>`, `<em>`, and `<u>`
70
+ * wrapped inside `<code>`.
71
+ */
72
+ export function bannerContentFontLockCss(
73
+ theme: ThemeFragment,
74
+ detect: AppFontDetector = detectAppFontFamily,
75
+ ): string {
76
+ const family = contentFontFamilyCss(theme, detect);
77
+ return (
78
+ `.userflowjs-banner-root,.userflowjs-banner-root *:not(code):not(pre){font-family:${family};}` +
79
+ `.userflowjs-banner-root code,.userflowjs-banner-root pre,` +
80
+ `.userflowjs-banner-root code *,.userflowjs-banner-root pre *{font-family:ui-monospace,Menlo,monospace;}`
81
+ );
82
+ }
83
+
84
+ function resolvedFamily(theme: ThemeFragment, detect: AppFontDetector): string | undefined {
85
+ return explicitThemeFamily(theme) ?? detectedFamily(detect);
86
+ }
87
+
88
+ function explicitThemeFamily(theme: ThemeFragment): string | undefined {
89
+ return themeConcreteFontFamily(theme.fontFamily);
90
+ }
91
+
92
+ /** Authored theme list, quoted so a multi-word family stays one face. */
93
+ function explicitThemeCss(theme: ThemeFragment): string | undefined {
94
+ const native = explicitThemeFamily(theme);
95
+ if (!native) return undefined;
96
+ const parts = splitCssFamilies(theme.fontFamily);
97
+ if (parts.length <= 1) return quoteCssFamily(native);
98
+ return parts.map((part) => (isBareCssFamily(part) ? part : quoteCssFamily(part))).join(', ');
99
+ }
100
+
101
+ function detectedFamily(detect: AppFontDetector): string | undefined {
102
+ try {
103
+ const detected = detect();
104
+ if (typeof detected !== 'string') return undefined;
105
+ const concrete = concreteFontFamily(detected);
106
+ if (!concrete || isIconFontFamily(concrete)) return undefined;
107
+ return concrete;
108
+ } catch {
109
+ return undefined;
110
+ }
111
+ }
112
+
113
+ function quoteCssFamily(name: string): string {
114
+ return `"${escapeCssFamily(name)}"`;
115
+ }
116
+
117
+ function escapeCssFamily(name: string): string {
118
+ return name.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
119
+ }
@@ -27,7 +27,8 @@ export interface DeepLinkingDeps {
27
27
  startFlowWithToken: DeepLinkStartFlowWithToken;
28
28
  /** Optional Linking overrides for tests. */
29
29
  linking?: {
30
- getInitialURL: () => Promise<string | null>;
30
+ // React Native 0.88 adds undefined to the result.
31
+ getInitialURL: () => Promise<string | null | undefined>;
31
32
  addEventListener: (
32
33
  type: 'url',
33
34
  handler: (event: { url: string }) => void,