@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
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Host-app font used when a theme is still on the default CSS stack.
3
+ *
4
+ * React Native does not inherit `fontFamily` from a parent view, so the family
5
+ * actually in use is the one set on `Text`. The primary font is whichever
6
+ * family appears on the most visible host text under the scan root (the SDK
7
+ * overlay is a sibling and is not walked). Embedded banners are inside that
8
+ * root; their hosts use a `userflow-` testID and are not votes, or the bar's
9
+ * own text would win. Text with no family counts as the platform font. Icon
10
+ * faces are not content. Nested text with no family does not vote: the
11
+ * ancestor text already does, and a child run must not add another vote for
12
+ * that family or for the platform font.
13
+ *
14
+ * A finished walk is cached until the visible screen changes, the reported
15
+ * screen changes, or `Userflow.reset()`. A walk that hits the node cap is not
16
+ * cached. Failures return null.
17
+ */
18
+ import { getCurrentScreenLocation } from '../../screenTracker';
19
+
20
+ import { fiberScanRoot } from './FiberScanRootStore';
21
+ import {
22
+ type AnyFiber,
23
+ MAX_ANCESTOR_WALK,
24
+ getHostTypeName,
25
+ getLiveHostProps,
26
+ getReturn,
27
+ getStateNode,
28
+ } from './fiberInternals';
29
+ import { isFiberOnVisibleScreen, isScreenHostFiber } from './visibleScreens';
30
+ import { walkVisibleFibers } from './walkVisibleFibers';
31
+
32
+ const TEXT_HOST_NAMES = new Set(['Text', 'RCTText', 'RCTVirtualText']);
33
+
34
+ /** SDK hosts inside the scan root (embedded banners). Not the app's font. */
35
+ const SDK_HOST_TEST_ID_PREFIX = 'userflow-';
36
+
37
+ /** Cycle guard for nested-text inheritance. Real trees are only a few levels deep. */
38
+ const MAX_FONT_ANCESTORS = 50;
39
+
40
+ const CSS_GENERIC_FAMILIES = new Set([
41
+ 'sans-serif',
42
+ 'serif',
43
+ 'monospace',
44
+ 'system-ui',
45
+ 'ui-sans-serif',
46
+ 'ui-serif',
47
+ 'ui-monospace',
48
+ 'ui-rounded',
49
+ 'cursive',
50
+ 'fantasy',
51
+ 'emoji',
52
+ 'math',
53
+ 'fangsong',
54
+ 'inherit',
55
+ 'initial',
56
+ 'unset',
57
+ ]);
58
+
59
+ /** Short vector-icon families. Matched after stripping spaces and punctuation. */
60
+ const ICON_FONT_EXACT = new Set([
61
+ 'feather',
62
+ 'antdesign',
63
+ 'entypo',
64
+ 'fontisto',
65
+ 'simplelineicons',
66
+ 'octicons',
67
+ 'zocial',
68
+ 'foundation',
69
+ 'evilicons',
70
+ ]);
71
+
72
+ interface FontCache {
73
+ instance: object;
74
+ /** null: countable text voted for the platform font, or nothing voted. */
75
+ family: string | null;
76
+ /** Visible screens this tally belongs to. Empty when the tree has none. */
77
+ screens: AnyFiber[];
78
+ /** `updateScreen` / router identity at tally time. Null before the first report. */
79
+ screenKey: string | null;
80
+ }
81
+
82
+ let cache: FontCache | null = null;
83
+
84
+ /** Drop the cached app font. Called from `Userflow.reset()`. */
85
+ export function resetDetectedAppFont(): void {
86
+ cache = null;
87
+ }
88
+
89
+ /**
90
+ * Family to apply, or null when the app font is the platform face or could
91
+ * not be read. Never throws.
92
+ */
93
+ export function detectAppFontFamily(): string | null {
94
+ try {
95
+ const instance = fiberScanRoot.getInstance();
96
+ if (!instance || typeof instance !== 'object') return null;
97
+
98
+ const root = fiberScanRoot.getFiber();
99
+ if (!root) return null;
100
+ if (cache?.instance === instance && cacheStillCurrent(cache, root)) return cache.family;
101
+
102
+ const tally = tallyHostText(root);
103
+ if (tally.truncated) {
104
+ if (!tally.voted) return null;
105
+ return pickMajority(tally.counts);
106
+ }
107
+
108
+ const family = tally.voted ? pickMajority(tally.counts) : null;
109
+ cache = { instance, family, screens: tally.screens, screenKey: reportedScreenKey() };
110
+ return family;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Faces that appear in the builder's default system stack. A comma-separated
118
+ * list made only of these and generic keywords is that default, not a custom
119
+ * font. A single concrete name is still kept, including `Roboto`.
120
+ */
121
+ const DEFAULT_STACK_FACES = new Set([
122
+ 'blinkmacsystemfont',
123
+ 'segoe ui',
124
+ 'roboto',
125
+ 'helvetica',
126
+ 'arial',
127
+ 'apple color emoji',
128
+ 'segoe ui emoji',
129
+ 'segoe ui symbol',
130
+ ]);
131
+
132
+ /**
133
+ * A single React Native family name. CSS stacks and generic keywords are not
134
+ * usable as a host `fontFamily` — RN treats the whole string as one face.
135
+ * Theme stacks are parsed by {@link themeConcreteFontFamily}.
136
+ */
137
+ export function concreteFontFamily(value: unknown): string | undefined {
138
+ if (typeof value !== 'string') return undefined;
139
+ const name = unwrapFontName(value);
140
+ if (!name || name.includes(',') || name.startsWith('-')) return undefined;
141
+ if (CSS_GENERIC_FAMILIES.has(name.toLowerCase())) return undefined;
142
+ return name;
143
+ }
144
+
145
+ /**
146
+ * Family a theme author chose. A single concrete name is kept. A comma-separated
147
+ * list keeps the first family that is not a generic or a default-stack face,
148
+ * so `"Source Sans Pro", sans-serif` stays Source Sans Pro and the system
149
+ * stack still resolves to undefined.
150
+ */
151
+ export function themeConcreteFontFamily(value: unknown): string | undefined {
152
+ if (typeof value !== 'string') return undefined;
153
+ const parts = splitCssFamilies(value);
154
+ if (parts.length === 0) return undefined;
155
+ if (parts.length === 1) return concreteFontFamily(parts[0]);
156
+ for (const part of parts) {
157
+ const name = concreteFontFamily(part);
158
+ if (!name || DEFAULT_STACK_FACES.has(name.toLowerCase())) continue;
159
+ return name;
160
+ }
161
+ return undefined;
162
+ }
163
+
164
+ /**
165
+ * Split a CSS `font-family` list. A quote that opens a family is wrapping and
166
+ * is removed; a quote inside a name stays, so `Foo"Bar` is one family.
167
+ * Commas inside wrapping quotes are not separators.
168
+ */
169
+ export function splitCssFamilies(value: string): string[] {
170
+ const parts: string[] = [];
171
+ let current = '';
172
+ let quote: '"' | "'" | null = null;
173
+ for (const char of value) {
174
+ if (quote) {
175
+ if (char === quote) quote = null;
176
+ else current += char;
177
+ continue;
178
+ }
179
+ if ((char === '"' || char === "'") && current.trim() === '') {
180
+ quote = char;
181
+ continue;
182
+ }
183
+ if (char === ',') {
184
+ const name = current.trim();
185
+ if (name) parts.push(name);
186
+ current = '';
187
+ continue;
188
+ }
189
+ current += char;
190
+ }
191
+ const name = current.trim();
192
+ if (name) parts.push(name);
193
+ return parts;
194
+ }
195
+
196
+ /** Generic keywords and vendor-prefixed faces stay unquoted in CSS. */
197
+ export function isBareCssFamily(name: string): boolean {
198
+ return name.startsWith('-') || CSS_GENERIC_FAMILIES.has(name.toLowerCase());
199
+ }
200
+
201
+ export function isIconFontFamily(name: string): boolean {
202
+ const key = name.toLowerCase().replace(/[^a-z0-9]/g, '');
203
+ if (ICON_FONT_EXACT.has(key)) return true;
204
+ return (
205
+ key.startsWith('fontawesome') ||
206
+ key.startsWith('ionicons') ||
207
+ key.startsWith('materialcommunityicons') ||
208
+ key.startsWith('materialicons') ||
209
+ key.startsWith('materialsymbols')
210
+ );
211
+ }
212
+
213
+ interface Tally {
214
+ voted: boolean;
215
+ truncated: boolean;
216
+ screens: AnyFiber[];
217
+ counts: Map<string | null, number>;
218
+ }
219
+
220
+ function tallyHostText(root: AnyFiber): Tally {
221
+ const counts = new Map<string | null, number>();
222
+ const screens: AnyFiber[] = [];
223
+ let voted = false;
224
+ const { truncated } = walkVisibleFibers(root, (fiber) => {
225
+ try {
226
+ if (isScreenHostFiber(fiber)) screens.push(fiber);
227
+ if (!isTextHost(fiber)) return;
228
+ const vote = voteFor(fiber, root);
229
+ if (vote === 'skip') return;
230
+ voted = true;
231
+ counts.set(vote, (counts.get(vote) ?? 0) + 1);
232
+ } catch {
233
+ // One unreadable node is not a vote.
234
+ }
235
+ });
236
+ return { voted, truncated, screens, counts };
237
+ }
238
+
239
+ /** Usable family, null for the platform font, or skip for an icon face. */
240
+ function voteFor(fiber: AnyFiber, root: AnyFiber): string | null | 'skip' {
241
+ if (isUnderSdkHost(fiber, root)) return 'skip';
242
+ const own = classifyFamily(readFontFamily(fiber));
243
+ if (own.kind === 'icon') return 'skip';
244
+ if (own.kind === 'family') return own.name;
245
+ if (own.kind === 'unusable') return null;
246
+ // No family of its own. The ancestor text node casts the vote for this run.
247
+ if (hasAncestorText(fiber, root)) return 'skip';
248
+ return null;
249
+ }
250
+
251
+ function hasAncestorText(fiber: AnyFiber, root: AnyFiber): boolean {
252
+ let parent = getReturn(fiber);
253
+ for (let depth = 0; parent && parent !== root && depth < MAX_FONT_ANCESTORS; depth += 1) {
254
+ if (isTextHost(parent)) return true;
255
+ parent = getReturn(parent);
256
+ }
257
+ return false;
258
+ }
259
+
260
+ function cacheStillCurrent(cached: FontCache, root: AnyFiber): boolean {
261
+ if (cached.screenKey !== reportedScreenKey()) return false;
262
+ if (cached.screens.length === 0) return true;
263
+ return cached.screens.every((screen) => screenStillCurrent(screen, root));
264
+ }
265
+
266
+ function screenStillCurrent(screen: AnyFiber, root: AnyFiber): boolean {
267
+ if (!isFiberOnVisibleScreen(screen)) return false;
268
+ let current: AnyFiber = screen;
269
+ for (let depth = 0; current && depth < MAX_ANCESTOR_WALK; depth += 1) {
270
+ if (current === root) return true;
271
+ current = getReturn(current);
272
+ }
273
+ return false;
274
+ }
275
+
276
+ function reportedScreenKey(): string | null {
277
+ const location = getCurrentScreenLocation();
278
+ if (!location) return null;
279
+ const key = 'path' in location ? location.path : location.screen;
280
+ return key || null;
281
+ }
282
+
283
+ type Classified =
284
+ | { kind: 'absent' }
285
+ | { kind: 'icon' }
286
+ | { kind: 'family'; name: string }
287
+ | { kind: 'unusable' };
288
+
289
+ function classifyFamily(raw: string | undefined): Classified {
290
+ if (!raw) return { kind: 'absent' };
291
+ if (isIconFontFamily(raw)) return { kind: 'icon' };
292
+ const concrete = concreteFontFamily(raw);
293
+ if (!concrete) return { kind: 'unusable' };
294
+ return { kind: 'family', name: concrete };
295
+ }
296
+
297
+ function pickMajority(counts: Map<string | null, number>): string | null {
298
+ const platform = counts.get(null) ?? 0;
299
+ let bestName: string | null = null;
300
+ let bestCount = 0;
301
+ for (const [name, count] of counts) {
302
+ if (name == null) continue;
303
+ if (bestName == null || count > bestCount || (count === bestCount && name < bestName)) {
304
+ bestName = name;
305
+ bestCount = count;
306
+ }
307
+ }
308
+ if (bestName == null || platform > bestCount) return null;
309
+ return bestName;
310
+ }
311
+
312
+ function isUnderSdkHost(fiber: AnyFiber, root: AnyFiber): boolean {
313
+ let parent = getReturn(fiber);
314
+ for (let depth = 0; parent && parent !== root && depth < MAX_FONT_ANCESTORS; depth += 1) {
315
+ try {
316
+ const testID = getLiveHostProps(parent)?.testID;
317
+ if (typeof testID === 'string' && testID.startsWith(SDK_HOST_TEST_ID_PREFIX)) {
318
+ return true;
319
+ }
320
+ } catch {
321
+ // An unreadable ancestor is not an SDK host.
322
+ }
323
+ parent = getReturn(parent);
324
+ }
325
+ return false;
326
+ }
327
+
328
+ function isTextHost(fiber: AnyFiber): boolean {
329
+ const typeName = getHostTypeName(fiber);
330
+ if (typeName && TEXT_HOST_NAMES.has(typeName)) return true;
331
+ const elementType = fiber?.elementType;
332
+ if (typeof elementType === 'string' && TEXT_HOST_NAMES.has(elementType)) return true;
333
+ try {
334
+ const node = getStateNode(fiber) as {
335
+ viewConfig?: { uiViewClassName?: unknown };
336
+ __viewConfig?: { uiViewClassName?: unknown };
337
+ } | null;
338
+ const className = node?.viewConfig?.uiViewClassName ?? node?.__viewConfig?.uiViewClassName;
339
+ return typeof className === 'string' && TEXT_HOST_NAMES.has(className);
340
+ } catch {
341
+ return false;
342
+ }
343
+ }
344
+
345
+ function readFontFamily(fiber: AnyFiber): string | undefined {
346
+ try {
347
+ const flat = flattenFontStyle(getLiveHostProps(fiber)?.style);
348
+ const family = flat?.fontFamily;
349
+ if (typeof family !== 'string') return undefined;
350
+ const trimmed = family.trim();
351
+ return trimmed.length > 0 ? trimmed : undefined;
352
+ } catch {
353
+ return undefined;
354
+ }
355
+ }
356
+
357
+ function flattenFontStyle(style: unknown): { fontFamily?: unknown } | null {
358
+ if (style == null || style === false) return null;
359
+ if (typeof style === 'number') return flattenRegisteredStyle(style);
360
+ if (Array.isArray(style)) {
361
+ let family: unknown;
362
+ for (const entry of style) {
363
+ const flat = flattenFontStyle(entry);
364
+ if (flat && flat.fontFamily != null) family = flat.fontFamily;
365
+ }
366
+ return { fontFamily: family };
367
+ }
368
+ if (typeof style === 'object') return style as { fontFamily?: unknown };
369
+ return null;
370
+ }
371
+
372
+ /**
373
+ * StyleSheet ids are numbers. Resolve them only when one shows up so tests
374
+ * that pass plain style objects do not load react-native.
375
+ */
376
+ function flattenRegisteredStyle(styleId: number): { fontFamily?: unknown } | null {
377
+ try {
378
+ // Lazy require so unit tests that pass plain style objects do not load RN.
379
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
380
+ const { StyleSheet } = require('react-native') as {
381
+ StyleSheet?: { flatten?: (style: number) => { fontFamily?: unknown } | null | undefined };
382
+ };
383
+ const flat = StyleSheet?.flatten?.(styleId);
384
+ return flat ?? null;
385
+ } catch {
386
+ return null;
387
+ }
388
+ }
389
+
390
+ function unwrapFontName(value: string): string {
391
+ const trimmed = value.trim();
392
+ if (trimmed.length >= 2) {
393
+ const first = trimmed[0];
394
+ const last = trimmed[trimmed.length - 1];
395
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
396
+ return trimmed.slice(1, -1).trim();
397
+ }
398
+ }
399
+ return trimmed;
400
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Where a natively presented modal sits in the window.
3
+ *
4
+ * Fabric measures and hit-tests content inside a react-native-screens modal
5
+ * screen (`RNSModalScreen`) or a React Native `<Modal>` relative to the modal,
6
+ * not the window: both shadow nodes are `RootNodeKind`, and the native side
7
+ * reports only their size. That is the window for full-screen presentations,
8
+ * but a page sheet or form sheet starts below the top of the window, so window
9
+ * points and measured rects disagree by the sheet's origin.
10
+ *
11
+ * The SDK has no native code to ask UIKit where the sheet is, so the origin is
12
+ * derived from sizes: a sheet as wide as the window is anchored to its bottom.
13
+ * Measured on iOS 17.4 and 26.5 and an iPadOS 26.5 portrait iPad: page sheets
14
+ * at 852 − 783 = 69, 874 − 812 = 62 and 1210 − 1104 = 106, a `fitToContents`
15
+ * form sheet at 852 − 424 = 428.
16
+ *
17
+ * A narrower sheet (an iPad form sheet) cannot be placed from sizes. One
18
+ * measured 580×376 on a 834×1210 window sat at y = 549, neither centered (417)
19
+ * nor bottom-anchored (834): its detent is anchored inside a taller container
20
+ * the shadow tree never sees. It gets no origin, so picks on it fail closed.
21
+ *
22
+ * Only a sheet at its full-height detent is reliably on the bottom edge. A
23
+ * shorter one is not, in two cases, and also gets no origin:
24
+ * - From iOS 26 it floats. On iOS 26.5 the `fitToContents` trade ticket
25
+ * measured 402×424 on a 402×874 window, but UIKit drew it about 8pt in from
26
+ * the sides and bottom at about 0.96 scale, so a bottom-anchored origin was
27
+ * up to 8pt off. Page sheets there stay edge to edge (origin 62, exact).
28
+ * - While the keyboard is up, a sheet that fits above the keyboard may have
29
+ * been lifted to clear it. One that is taller than the space above the
30
+ * keyboard was not: on iOS 26.5 a page sheet stayed at 62 with a 335pt
31
+ * keyboard, and on iOS 17.4 the trade ticket grew from 424 to 715 under a
32
+ * 291pt keyboard and stayed on the bottom edge (852 − 715 = 137, exact).
33
+ *
34
+ * Paper measures with UIKit, in window coordinates, so its origin is zero.
35
+ * Outside iOS the modal is measured where it sits, as before this lookup.
36
+ */
37
+ import { Dimensions, Keyboard, Platform } from 'react-native';
38
+
39
+ import type { AnyFiber } from './fiberInternals';
40
+ import { measureHostFiber } from './measureHostFiber';
41
+ import { fiberIsUnder, topmostVisibleModalHost } from './visibleRect';
42
+
43
+ export interface Point {
44
+ x: number;
45
+ y: number;
46
+ }
47
+
48
+ export interface Size {
49
+ width: number;
50
+ height: number;
51
+ }
52
+
53
+ export interface PresentedModal {
54
+ /** `RNSModalScreen`, or React Native's `ModalHostView`. */
55
+ host: AnyFiber;
56
+ /**
57
+ * Window position of `(0, 0)` in the space the modal's content is measured
58
+ * and hit-tested in. Null when the modal cannot be placed.
59
+ */
60
+ origin: Point | null;
61
+ /** The modal's size, when it was measured (iOS Fabric only). */
62
+ size: Size | null;
63
+ /** True on iOS Fabric, where that space is the modal's own rather than the window's. */
64
+ modalSpace: boolean;
65
+ }
66
+
67
+ const ZERO: Point = { x: 0, y: 0 };
68
+
69
+ const MEASURE_TIMEOUT_MS = 400;
70
+
71
+ /** Layout sizes are fractional and may miss the window size by a hair. */
72
+ const SIZE_SLACK = 1;
73
+
74
+ /**
75
+ * A sheet whose top is within this fraction of the window height is at its
76
+ * full-height detent. Measured: 69/852 (iOS 17.4), 62/874 (iOS 26.5) and
77
+ * 106/1210 (iPadOS 26.5). Medium and `fitToContents` detents start far lower.
78
+ */
79
+ const FULL_HEIGHT_SHEET_MAX_TOP = 0.15;
80
+
81
+ /** First iOS release that floats sheets shorter than full height. */
82
+ const FLOATING_SHEETS_IOS = 26;
83
+
84
+ function isFabric(): boolean {
85
+ const manager = (globalThis as { nativeFabricUIManager?: unknown }).nativeFabricUIManager;
86
+ return manager != null && typeof manager === 'object';
87
+ }
88
+
89
+ function readWindowSize(): Size | null {
90
+ try {
91
+ const { width, height } = Dimensions.get('window');
92
+ if (width > 0 && height > 0) return { width, height };
93
+ } catch {
94
+ // Tests and odd hosts.
95
+ }
96
+ return null;
97
+ }
98
+
99
+ function floatsShortSheets(): boolean {
100
+ const major = parseInt(String(Platform.Version), 10);
101
+ return Number.isFinite(major) && major >= FLOATING_SHEETS_IOS;
102
+ }
103
+
104
+ /** Window y of the keyboard's top edge, or null when it is down. Unknown counts as up at the bottom. */
105
+ function keyboardTop(windowSize: Size): number | null {
106
+ try {
107
+ if (typeof Keyboard?.isVisible !== 'function' || !Keyboard.isVisible()) return null;
108
+ const metrics = typeof Keyboard.metrics === 'function' ? Keyboard.metrics() : undefined;
109
+ return metrics && metrics.height > 0 ? metrics.screenY : windowSize.height;
110
+ } catch {
111
+ return null;
112
+ }
113
+ }
114
+
115
+ function measureSize(fiber: AnyFiber): Promise<Size | null> {
116
+ return new Promise((resolve) => {
117
+ let settled = false;
118
+ const timer = setTimeout(() => {
119
+ if (settled) return;
120
+ settled = true;
121
+ resolve(null);
122
+ }, MEASURE_TIMEOUT_MS);
123
+ try {
124
+ measureHostFiber(fiber, (_x, _y, width, height) => {
125
+ if (settled) return;
126
+ settled = true;
127
+ clearTimeout(timer);
128
+ resolve(width > 0 && height > 0 ? { width, height } : null);
129
+ });
130
+ } catch {
131
+ if (settled) return;
132
+ settled = true;
133
+ clearTimeout(timer);
134
+ resolve(null);
135
+ }
136
+ });
137
+ }
138
+
139
+ /**
140
+ * Window origin of a presented modal of `size`, bottom-anchored. Null when it
141
+ * is narrower than the window (see above) or does not fit in it, as
142
+ * mid-rotation.
143
+ */
144
+ export function modalOriginInWindow(size: Size, windowSize: Size): Point | null {
145
+ if (size.width <= 0 || size.height <= 0) return null;
146
+ if (size.width > windowSize.width + SIZE_SLACK || size.height > windowSize.height + SIZE_SLACK) {
147
+ return null;
148
+ }
149
+ if (size.width < windowSize.width - SIZE_SLACK) return null;
150
+ return { x: 0, y: Math.max(0, windowSize.height - size.height) };
151
+ }
152
+
153
+ /** {@link modalOriginInWindow}, or null when a short sheet may be off the bottom edge (see above). */
154
+ function placeModal(size: Size, windowSize: Size): Point | null {
155
+ const origin = modalOriginInWindow(size, windowSize);
156
+ if (!origin || origin.y <= windowSize.height * FULL_HEIGHT_SHEET_MAX_TOP) return origin;
157
+ if (floatsShortSheets()) return null;
158
+ const keyboard = keyboardTop(windowSize);
159
+ if (keyboard != null && size.height <= keyboard) return null;
160
+ return origin;
161
+ }
162
+
163
+ /**
164
+ * The modal drawn on top of the app and where its content space sits in the
165
+ * window, or null when nothing is presented. Look it up per tap: a
166
+ * `fitToContents` sheet resizes as its content changes.
167
+ */
168
+ export async function locatePresentedModal(root: AnyFiber | null): Promise<PresentedModal | null> {
169
+ const host = topmostVisibleModalHost(root);
170
+ if (!host) return null;
171
+ if (Platform.OS !== 'ios' || !isFabric()) {
172
+ return { host, origin: ZERO, size: null, modalSpace: false };
173
+ }
174
+ const size = await measureSize(host);
175
+ const windowSize = readWindowSize();
176
+ const origin = size && windowSize ? placeModal(size, windowSize) : null;
177
+ return { host, origin, size, modalSpace: true };
178
+ }
179
+
180
+ /** True when window point `(x, y)` is outside a placed modal, on the screen it covers. */
181
+ export function isOutsideModal(x: number, y: number, modal: PresentedModal): boolean {
182
+ const { origin, size } = modal;
183
+ if (!origin || !size) return false;
184
+ return x < origin.x || y < origin.y || x > origin.x + size.width || y > origin.y + size.height;
185
+ }
186
+
187
+ /**
188
+ * Window position of `(0, 0)` in the space `fiber` is measured in, given what
189
+ * {@link locatePresentedModal} returned. Null when `modal` covers `fiber`, or
190
+ * `fiber` is inside a modal that cannot be placed.
191
+ */
192
+ export function measureOriginOf(fiber: AnyFiber, modal: PresentedModal | null): Point | null {
193
+ if (!modal) return ZERO;
194
+ if (!fiberIsUnder(fiber, modal.host)) return null;
195
+ return modal.origin;
196
+ }
197
+
198
+ /** `rect`, measured in `modal`'s content space, in window coordinates. */
199
+ export function rectInWindow<T extends Point>(rect: T, modal: PresentedModal): T {
200
+ const origin = modal.origin ?? ZERO;
201
+ if (origin.x === 0 && origin.y === 0) return rect;
202
+ return { ...rect, x: rect.x + origin.x, y: rect.y + origin.y };
203
+ }
@@ -73,9 +73,10 @@ function nativeTagForTouch(
73
73
  event: GestureResponderEvent,
74
74
  identifier: number | string | undefined,
75
75
  ): number | undefined {
76
+ // Readonly: React Native 0.88 types changedTouches as a readonly array.
76
77
  const native = event.nativeEvent as {
77
78
  target?: unknown;
78
- changedTouches?: Array<{ identifier?: number | string; target?: unknown }>;
79
+ changedTouches?: ReadonlyArray<{ identifier?: number | string; target?: unknown }>;
79
80
  };
80
81
  if (identifier != null && native.changedTouches) {
81
82
  const touch = native.changedTouches.find((entry) => entry.identifier === identifier);
@@ -17,6 +17,7 @@ import {
17
17
  } from './fiberInternals';
18
18
  import { findScrollAncestors } from './findScrollAncestors';
19
19
  import { measureHostFiber } from './measureHostFiber';
20
+ import { isFiberOnVisibleScreen } from './visibleScreens';
20
21
 
21
22
  export interface VisibleRect {
22
23
  x: number;
@@ -30,6 +31,9 @@ const MEASURE_TIMEOUT_MS = 400;
30
31
  /** Paper and Fabric names for React Native's `<Modal>`. */
31
32
  const MODAL_HOST_TYPES = new Set(['RCTModalHostView', 'ModalHostView']);
32
33
 
34
+ /** react-native-screens' iOS host for `modal`, `transparentModal`, `formSheet` and other presented screens. */
35
+ const SCREENS_MODAL_TYPE = 'RNSModalScreen';
36
+
33
37
  type MeasureCallback = (x: number, y: number, width: number, height: number) => void;
34
38
 
35
39
  function measureFiberRect(fiber: AnyFiber): Promise<VisibleRect | null> {
@@ -99,10 +103,12 @@ export async function clipToScrollViewports(
99
103
 
100
104
  function isModalHost(fiber: AnyFiber): boolean {
101
105
  const type = getHostTypeName(fiber);
102
- return type !== undefined && MODAL_HOST_TYPES.has(type);
106
+ return type !== undefined && (MODAL_HOST_TYPES.has(type) || type === SCREENS_MODAL_TYPE);
103
107
  }
104
108
 
105
109
  function isModalVisible(fiber: AnyFiber): boolean {
110
+ // A presented screen stays mounted under the next one; only the top of its stack is visible.
111
+ if (getHostTypeName(fiber) === SCREENS_MODAL_TYPE) return isFiberOnVisibleScreen(fiber);
106
112
  const props = getMemoizedProps(fiber);
107
113
  if (!props) return true;
108
114
  if (props.visible === false) return false;
@@ -110,8 +116,11 @@ function isModalVisible(fiber: AnyFiber): boolean {
110
116
  }
111
117
 
112
118
  /**
113
- * The visible modal host drawn on top, or null. Later siblings win: React
114
- * Native mounts a newer `<Modal>` after the previous one.
119
+ * The visible modal host drawn on top, or null: a React Native `<Modal>` or a
120
+ * screen react-native-screens presents natively. Later fibers win: React Native
121
+ * mounts a newer `<Modal>` after the previous one, a native stack's newest
122
+ * screen is its last child, and a modal presented from inside another one sits
123
+ * deeper in the tree.
115
124
  */
116
125
  export function topmostVisibleModalHost(root: AnyFiber | null, maxNodes = 8_000): AnyFiber | null {
117
126
  if (!root) return null;
@@ -64,6 +64,11 @@ function isScreenFiber(fiber: AnyFiber): boolean {
64
64
  return type !== undefined && SCREEN_TYPES.has(type);
65
65
  }
66
66
 
67
+ /** Host screen the walk can key a cache on (`RNSScreen`, `RNSModalScreen`). */
68
+ export function isScreenHostFiber(fiber: AnyFiber): boolean {
69
+ return isScreenFiber(fiber);
70
+ }
71
+
67
72
  function isStackContainer(fiber: AnyFiber): boolean {
68
73
  const type = getHostTypeName(fiber);
69
74
  return type !== undefined && STACK_CONTAINER_TYPES.has(type);