@userflow-tech/react-native 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -70
- package/docs/anchoring.md +58 -42
- package/docs/troubleshooting.md +69 -27
- package/expo-plugin/hostDependencyWarnings.d.ts +21 -0
- package/expo-plugin/hostDependencyWarnings.d.ts.map +1 -0
- package/expo-plugin/hostDependencyWarnings.js +101 -0
- package/expo-plugin/hostDependencyWarnings.js.map +1 -0
- package/expo-plugin/withUserflow.d.ts.map +1 -1
- package/expo-plugin/withUserflow.js +16 -0
- package/expo-plugin/withUserflow.js.map +1 -1
- package/lib/commonjs/anchors/AnchorRegistry.js +7 -0
- package/lib/commonjs/anchors/AnchorRegistry.js.map +1 -1
- package/lib/commonjs/anchors/elementConditionEvaluator.js +189 -55
- package/lib/commonjs/anchors/elementConditionEvaluator.js.map +1 -1
- package/lib/commonjs/anchors/elementTapMonitor.js +92 -0
- package/lib/commonjs/anchors/elementTapMonitor.js.map +1 -0
- package/lib/commonjs/anchors/elementTargetMatch.js +85 -0
- package/lib/commonjs/anchors/elementTargetMatch.js.map +1 -0
- package/lib/commonjs/anchors/fiber/findAnchorFiber.js +64 -39
- package/lib/commonjs/anchors/fiber/findAnchorFiber.js.map +1 -1
- package/lib/commonjs/anchors/fiber/touchTargetFiber.js +105 -0
- package/lib/commonjs/anchors/fiber/touchTargetFiber.js.map +1 -0
- package/lib/commonjs/anchors/resolveAnchorHost.js +2 -1
- package/lib/commonjs/anchors/resolveAnchorHost.js.map +1 -1
- package/lib/commonjs/anchors/resolveAutoAnchorHost.js +4 -2
- package/lib/commonjs/anchors/resolveAutoAnchorHost.js.map +1 -1
- package/lib/commonjs/anchors/resolveElementTargets.js +158 -0
- package/lib/commonjs/anchors/resolveElementTargets.js.map +1 -0
- package/lib/commonjs/anchors/targetPressMonitor.js +9 -3
- package/lib/commonjs/anchors/targetPressMonitor.js.map +1 -1
- package/lib/commonjs/anchors/useUserflowAnchor.js +1 -0
- package/lib/commonjs/anchors/useUserflowAnchor.js.map +1 -1
- package/lib/commonjs/anonymousId.js +16 -7
- package/lib/commonjs/anonymousId.js.map +1 -1
- package/lib/commonjs/authoring/capturePickerScreenshot.js +9 -1
- package/lib/commonjs/authoring/capturePickerScreenshot.js.map +1 -1
- package/lib/commonjs/evalJs/getWebView.js +5 -13
- package/lib/commonjs/evalJs/getWebView.js.map +1 -1
- package/lib/commonjs/hostAppVersion.js +6 -1
- package/lib/commonjs/hostAppVersion.js.map +1 -1
- package/lib/commonjs/index.js +5 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/installCheck.js +183 -0
- package/lib/commonjs/installCheck.js.map +1 -0
- package/lib/commonjs/nativeModules.js +44 -0
- package/lib/commonjs/nativeModules.js.map +1 -0
- package/lib/commonjs/network.js +20 -7
- package/lib/commonjs/network.js.map +1 -1
- package/lib/commonjs/pairing/deviceId.js +14 -11
- package/lib/commonjs/pairing/deviceId.js.map +1 -1
- package/lib/commonjs/pairing/tokenStorage.js +17 -4
- package/lib/commonjs/pairing/tokenStorage.js.map +1 -1
- package/lib/commonjs/runtime/TargetPressBridge.js +12 -1
- package/lib/commonjs/runtime/TargetPressBridge.js.map +1 -1
- package/lib/commonjs/runtime/UserflowProvider.js +6 -3
- package/lib/commonjs/runtime/UserflowProvider.js.map +1 -1
- package/lib/commonjs/storage/asyncStorage.js +67 -0
- package/lib/commonjs/storage/asyncStorage.js.map +1 -0
- package/lib/commonjs/surfaces/BannerSurface.js +3 -1
- package/lib/commonjs/surfaces/BannerSurface.js.map +1 -1
- package/lib/commonjs/surfaces/safeArea.js +74 -28
- package/lib/commonjs/surfaces/safeArea.js.map +1 -1
- package/lib/commonjs/version.js +1 -1
- package/lib/module/anchors/AnchorRegistry.js +7 -0
- package/lib/module/anchors/AnchorRegistry.js.map +1 -1
- package/lib/module/anchors/elementConditionEvaluator.js +189 -55
- package/lib/module/anchors/elementConditionEvaluator.js.map +1 -1
- package/lib/module/anchors/elementTapMonitor.js +87 -0
- package/lib/module/anchors/elementTapMonitor.js.map +1 -0
- package/lib/module/anchors/elementTargetMatch.js +78 -0
- package/lib/module/anchors/elementTargetMatch.js.map +1 -0
- package/lib/module/anchors/fiber/findAnchorFiber.js +62 -39
- package/lib/module/anchors/fiber/findAnchorFiber.js.map +1 -1
- package/lib/module/anchors/fiber/touchTargetFiber.js +100 -0
- package/lib/module/anchors/fiber/touchTargetFiber.js.map +1 -0
- package/lib/module/anchors/resolveAnchorHost.js +2 -1
- package/lib/module/anchors/resolveAnchorHost.js.map +1 -1
- package/lib/module/anchors/resolveAutoAnchorHost.js +3 -3
- package/lib/module/anchors/resolveAutoAnchorHost.js.map +1 -1
- package/lib/module/anchors/resolveElementTargets.js +152 -0
- package/lib/module/anchors/resolveElementTargets.js.map +1 -0
- package/lib/module/anchors/targetPressMonitor.js +8 -3
- package/lib/module/anchors/targetPressMonitor.js.map +1 -1
- package/lib/module/anchors/useUserflowAnchor.js +1 -0
- package/lib/module/anchors/useUserflowAnchor.js.map +1 -1
- package/lib/module/anonymousId.js +15 -5
- package/lib/module/anonymousId.js.map +1 -1
- package/lib/module/authoring/capturePickerScreenshot.js +9 -1
- package/lib/module/authoring/capturePickerScreenshot.js.map +1 -1
- package/lib/module/evalJs/getWebView.js +5 -13
- package/lib/module/evalJs/getWebView.js.map +1 -1
- package/lib/module/hostAppVersion.js +6 -1
- package/lib/module/hostAppVersion.js.map +1 -1
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/installCheck.js +169 -0
- package/lib/module/installCheck.js.map +1 -0
- package/lib/module/nativeModules.js +40 -0
- package/lib/module/nativeModules.js.map +1 -0
- package/lib/module/network.js +20 -7
- package/lib/module/network.js.map +1 -1
- package/lib/module/pairing/deviceId.js +14 -10
- package/lib/module/pairing/deviceId.js.map +1 -1
- package/lib/module/pairing/tokenStorage.js +17 -4
- package/lib/module/pairing/tokenStorage.js.map +1 -1
- package/lib/module/runtime/TargetPressBridge.js +12 -1
- package/lib/module/runtime/TargetPressBridge.js.map +1 -1
- package/lib/module/runtime/UserflowProvider.js +7 -4
- package/lib/module/runtime/UserflowProvider.js.map +1 -1
- package/lib/module/storage/asyncStorage.js +61 -0
- package/lib/module/storage/asyncStorage.js.map +1 -0
- package/lib/module/surfaces/BannerSurface.js +3 -1
- package/lib/module/surfaces/BannerSurface.js.map +1 -1
- package/lib/module/surfaces/safeArea.js +70 -28
- package/lib/module/surfaces/safeArea.js.map +1 -1
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts +7 -0
- package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/elementConditionEvaluator.d.ts +12 -16
- package/lib/typescript/commonjs/src/anchors/elementConditionEvaluator.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts +36 -0
- package/lib/typescript/commonjs/src/anchors/elementTapMonitor.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts +20 -0
- package/lib/typescript/commonjs/src/anchors/elementTargetMatch.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/fiber/findAnchorFiber.d.ts +17 -5
- package/lib/typescript/commonjs/src/anchors/fiber/findAnchorFiber.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiber/touchTargetFiber.d.ts +24 -0
- package/lib/typescript/commonjs/src/anchors/fiber/touchTargetFiber.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts +2 -1
- package/lib/typescript/commonjs/src/anchors/resolveAnchorHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts +10 -1
- package/lib/typescript/commonjs/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts +39 -0
- package/lib/typescript/commonjs/src/anchors/resolveElementTargets.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts +2 -0
- package/lib/typescript/commonjs/src/anchors/targetPressMonitor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anonymousId.d.ts +2 -0
- package/lib/typescript/commonjs/src/anonymousId.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/authoring/capturePickerScreenshot.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/evalJs/getWebView.d.ts +2 -1
- package/lib/typescript/commonjs/src/evalJs/getWebView.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/hostAppVersion.d.ts +1 -1
- package/lib/typescript/commonjs/src/hostAppVersion.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/installCheck.d.ts +48 -0
- package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/nativeModules.d.ts +13 -0
- package/lib/typescript/commonjs/src/nativeModules.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/network.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/pairing/deviceId.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/pairing/tokenStorage.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts +2 -1
- package/lib/typescript/commonjs/src/runtime/TargetPressBridge.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/UserflowProvider.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/storage/asyncStorage.d.ts +15 -0
- package/lib/typescript/commonjs/src/storage/asyncStorage.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/surfaces/BannerSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/safeArea.d.ts +6 -2
- package/lib/typescript/commonjs/src/surfaces/safeArea.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/version.d.ts +1 -1
- package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts +7 -0
- package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/elementConditionEvaluator.d.ts +12 -16
- package/lib/typescript/module/src/anchors/elementConditionEvaluator.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts +36 -0
- package/lib/typescript/module/src/anchors/elementTapMonitor.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts +20 -0
- package/lib/typescript/module/src/anchors/elementTargetMatch.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/fiber/findAnchorFiber.d.ts +17 -5
- package/lib/typescript/module/src/anchors/fiber/findAnchorFiber.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiber/touchTargetFiber.d.ts +24 -0
- package/lib/typescript/module/src/anchors/fiber/touchTargetFiber.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts +2 -1
- package/lib/typescript/module/src/anchors/resolveAnchorHost.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts +10 -1
- package/lib/typescript/module/src/anchors/resolveAutoAnchorHost.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts +39 -0
- package/lib/typescript/module/src/anchors/resolveElementTargets.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts +2 -0
- package/lib/typescript/module/src/anchors/targetPressMonitor.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts.map +1 -1
- package/lib/typescript/module/src/anonymousId.d.ts +2 -0
- package/lib/typescript/module/src/anonymousId.d.ts.map +1 -1
- package/lib/typescript/module/src/authoring/capturePickerScreenshot.d.ts.map +1 -1
- package/lib/typescript/module/src/evalJs/getWebView.d.ts +2 -1
- package/lib/typescript/module/src/evalJs/getWebView.d.ts.map +1 -1
- package/lib/typescript/module/src/hostAppVersion.d.ts +1 -1
- package/lib/typescript/module/src/hostAppVersion.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/installCheck.d.ts +48 -0
- package/lib/typescript/module/src/installCheck.d.ts.map +1 -0
- package/lib/typescript/module/src/nativeModules.d.ts +13 -0
- package/lib/typescript/module/src/nativeModules.d.ts.map +1 -0
- package/lib/typescript/module/src/network.d.ts.map +1 -1
- package/lib/typescript/module/src/pairing/deviceId.d.ts.map +1 -1
- package/lib/typescript/module/src/pairing/tokenStorage.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts +2 -1
- package/lib/typescript/module/src/runtime/TargetPressBridge.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/UserflowProvider.d.ts.map +1 -1
- package/lib/typescript/module/src/storage/asyncStorage.d.ts +15 -0
- package/lib/typescript/module/src/storage/asyncStorage.d.ts.map +1 -0
- package/lib/typescript/module/src/surfaces/BannerSurface.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/safeArea.d.ts +6 -2
- package/lib/typescript/module/src/surfaces/safeArea.d.ts.map +1 -1
- package/lib/typescript/module/src/version.d.ts +1 -1
- package/package.json +25 -8
- package/src/anchors/AnchorRegistry.ts +12 -0
- package/src/anchors/elementConditionEvaluator.ts +235 -70
- package/src/anchors/elementTapMonitor.ts +95 -0
- package/src/anchors/elementTargetMatch.ts +90 -0
- package/src/anchors/fiber/findAnchorFiber.ts +66 -35
- package/src/anchors/fiber/touchTargetFiber.ts +125 -0
- package/src/anchors/resolveAnchorHost.ts +2 -1
- package/src/anchors/resolveAutoAnchorHost.ts +3 -3
- package/src/anchors/resolveElementTargets.ts +165 -0
- package/src/anchors/targetPressMonitor.ts +12 -3
- package/src/anchors/useUserflowAnchor.ts +1 -0
- package/src/anonymousId.ts +19 -8
- package/src/authoring/capturePickerScreenshot.ts +10 -1
- package/src/evalJs/getWebView.ts +6 -13
- package/src/hostAppVersion.ts +7 -1
- package/src/index.ts +10 -0
- package/src/installCheck.ts +225 -0
- package/src/nativeModules.ts +48 -0
- package/src/network.ts +27 -11
- package/src/pairing/deviceId.ts +14 -11
- package/src/pairing/tokenStorage.ts +23 -7
- package/src/runtime/TargetPressBridge.tsx +17 -1
- package/src/runtime/UserflowProvider.tsx +11 -4
- package/src/storage/asyncStorage.ts +88 -0
- package/src/surfaces/BannerSurface.tsx +7 -5
- package/src/surfaces/safeArea.ts +85 -40
- package/src/version.ts +1 -1
- package/vendor/core/dist/constants.d.ts +2 -0
- package/vendor/core/dist/constants.d.ts.map +1 -1
- package/vendor/core/dist/constants.js +2 -0
- package/vendor/core/dist/constants.js.map +1 -1
- package/vendor/core/dist/engine/ClientConditionEngine.d.ts +20 -1
- package/vendor/core/dist/engine/ClientConditionEngine.d.ts.map +1 -1
- package/vendor/core/dist/engine/ClientConditionEngine.js +13 -10
- package/vendor/core/dist/engine/ClientConditionEngine.js.map +1 -1
- package/vendor/core/dist/engine/UserflowCore.d.ts.map +1 -1
- package/vendor/core/dist/engine/UserflowCore.js +21 -2
- package/vendor/core/dist/engine/UserflowCore.js.map +1 -1
- package/vendor/core/dist/index.d.ts +1 -1
- package/vendor/core/dist/index.d.ts.map +1 -1
- package/vendor/core/dist/index.js.map +1 -1
- package/vendor/core/dist/version.d.ts +1 -1
- package/vendor/core/dist/version.js +1 -1
- package/vendor/core/package.json +1 -1
- package/vendor/protocol/package.json +1 -1
- package/docs/webview-custom-banner-poc.md +0 -64
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@userflow-tech/react-native",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Userflow SDK for React Native — onboarding flows, tooltips, and modals.",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"main": "./lib/commonjs/index.js",
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
"source": "src/index.ts",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
+
"types": {
|
|
14
|
+
"import": "./lib/typescript/module/src/index.d.ts",
|
|
15
|
+
"require": "./lib/typescript/commonjs/src/index.d.ts"
|
|
16
|
+
},
|
|
13
17
|
"react-native": "./src/index.ts",
|
|
14
18
|
"import": {
|
|
15
19
|
"types": "./lib/typescript/module/src/index.d.ts",
|
|
@@ -21,6 +25,10 @@
|
|
|
21
25
|
}
|
|
22
26
|
},
|
|
23
27
|
"./navigation": {
|
|
28
|
+
"types": {
|
|
29
|
+
"import": "./lib/typescript/module/src/navigation/index.d.ts",
|
|
30
|
+
"require": "./lib/typescript/commonjs/src/navigation/index.d.ts"
|
|
31
|
+
},
|
|
24
32
|
"react-native": "./src/navigation/index.ts",
|
|
25
33
|
"import": {
|
|
26
34
|
"types": "./lib/typescript/module/src/navigation/index.d.ts",
|
|
@@ -32,6 +40,10 @@
|
|
|
32
40
|
}
|
|
33
41
|
},
|
|
34
42
|
"./expo-router": {
|
|
43
|
+
"types": {
|
|
44
|
+
"import": "./lib/typescript/module/src/expo-router/index.d.ts",
|
|
45
|
+
"require": "./lib/typescript/commonjs/src/expo-router/index.d.ts"
|
|
46
|
+
},
|
|
35
47
|
"react-native": "./src/expo-router/index.ts",
|
|
36
48
|
"import": {
|
|
37
49
|
"types": "./lib/typescript/module/src/expo-router/index.d.ts",
|
|
@@ -74,15 +86,16 @@
|
|
|
74
86
|
"clean": "rimraf lib vendor expo-plugin babel-plugin"
|
|
75
87
|
},
|
|
76
88
|
"peerDependencies": {
|
|
77
|
-
"@react-native-community/netinfo": ">=11.0.0",
|
|
89
|
+
"@react-native-community/netinfo": ">=11.0.0 <13.0.0",
|
|
78
90
|
"@react-navigation/native": ">=6.0.0",
|
|
79
91
|
"expo-router": ">=3.0.0",
|
|
80
92
|
"expo-secure-store": ">=13.0.0",
|
|
81
93
|
"react": ">=18.2.0",
|
|
82
|
-
"react-native": ">=0.
|
|
83
|
-
"react-native-
|
|
84
|
-
"react-native-
|
|
85
|
-
"react-native-
|
|
94
|
+
"react-native": ">=0.76.0",
|
|
95
|
+
"react-native-device-info": ">=11.0.0 <16.0.0",
|
|
96
|
+
"react-native-safe-area-context": ">=4.0.0 <6.0.0",
|
|
97
|
+
"react-native-view-shot": "^5.0.0",
|
|
98
|
+
"react-native-webview": ">=13.8.0 <14.0.0"
|
|
86
99
|
},
|
|
87
100
|
"peerDependenciesMeta": {
|
|
88
101
|
"@react-native-community/netinfo": {
|
|
@@ -97,12 +110,15 @@
|
|
|
97
110
|
"expo-secure-store": {
|
|
98
111
|
"optional": true
|
|
99
112
|
},
|
|
113
|
+
"react-native-device-info": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
100
116
|
"react-native-safe-area-context": {
|
|
101
117
|
"optional": true
|
|
102
118
|
}
|
|
103
119
|
},
|
|
104
120
|
"dependencies": {
|
|
105
|
-
"@react-native-async-storage/async-storage": ">=1.23.0",
|
|
121
|
+
"@react-native-async-storage/async-storage": ">=1.23.0 <3.0.0",
|
|
106
122
|
"@userflow-tech/core": "file:./vendor/core",
|
|
107
123
|
"@userflow-tech/protocol": "file:./vendor/protocol"
|
|
108
124
|
},
|
|
@@ -122,6 +138,7 @@
|
|
|
122
138
|
"react": "19.1.0",
|
|
123
139
|
"react-native": "0.81.5",
|
|
124
140
|
"react-native-builder-bob": "^0.30.0",
|
|
141
|
+
"react-native-device-info": "15.0.2",
|
|
125
142
|
"react-native-safe-area-context": "~5.6.0",
|
|
126
143
|
"react-native-view-shot": "^5.1.1",
|
|
127
144
|
"react-native-webview": "13.16.1",
|
|
@@ -154,6 +171,6 @@
|
|
|
154
171
|
]
|
|
155
172
|
},
|
|
156
173
|
"publishConfig": {
|
|
157
|
-
"access": "
|
|
174
|
+
"access": "public"
|
|
158
175
|
}
|
|
159
176
|
}
|
|
@@ -8,6 +8,11 @@ export interface AnchorHost {
|
|
|
8
8
|
* Called when the measured rect is not fully visible (bounded retries per step).
|
|
9
9
|
*/
|
|
10
10
|
scrollIntoView?: () => void;
|
|
11
|
+
/**
|
|
12
|
+
* Public instance this host was registered from, when known. Element click
|
|
13
|
+
* conditions use it to match a tap to this anchor.
|
|
14
|
+
*/
|
|
15
|
+
node?: object | null;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
type Listener = () => void;
|
|
@@ -52,6 +57,13 @@ export class AnchorRegistry {
|
|
|
52
57
|
return last;
|
|
53
58
|
}
|
|
54
59
|
|
|
60
|
+
/** Every host registered for `id`, in registration order. */
|
|
61
|
+
getAll(id: string): AnchorHost[] {
|
|
62
|
+
const set = this.anchors.get(id);
|
|
63
|
+
if (!set || set.size === 0) return [];
|
|
64
|
+
return [...set];
|
|
65
|
+
}
|
|
66
|
+
|
|
55
67
|
/** True when more than one host is registered for the same id. */
|
|
56
68
|
isAmbiguous(id: string): boolean {
|
|
57
69
|
return (this.anchors.get(id)?.size ?? 0) > 1;
|
|
@@ -1,35 +1,43 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type {
|
|
2
|
+
ConditionEvaluator,
|
|
3
|
+
ConditionEvaluatorContext,
|
|
4
|
+
ConditionReport,
|
|
5
|
+
} from '@userflow-tech/core';
|
|
6
|
+
import { decodeMobileAutoData, type FlowConditionFragment } from '@userflow-tech/protocol';
|
|
3
7
|
|
|
4
8
|
import { anchorChangeTicker } from './anchorChangeTicker';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
9
|
+
import { elementTapMonitor } from './elementTapMonitor';
|
|
10
|
+
import { fiberIsUnder, tapPathDisabled } from './elementTargetMatch';
|
|
11
|
+
import {
|
|
12
|
+
resolveElementTargets,
|
|
13
|
+
tapMayHitSelector,
|
|
14
|
+
type ElementTarget,
|
|
15
|
+
} from './resolveElementTargets';
|
|
7
16
|
import { resolveManualAnchorId } from './resolveManualAnchorId';
|
|
8
17
|
|
|
9
|
-
/**
|
|
10
|
-
const
|
|
18
|
+
/** How many hosts to measure before giving up on a crowded duplicate id. */
|
|
19
|
+
const MAX_MEASURED_HOSTS = 8;
|
|
20
|
+
|
|
21
|
+
/** Same bound tooltip measurement uses. A host that never calls back is unknown. */
|
|
22
|
+
const MEASURE_TIMEOUT_MS = 400;
|
|
11
23
|
|
|
12
24
|
/**
|
|
13
25
|
* Evaluate ELEMENT client conditions against manual anchors, fiber-resolved
|
|
14
|
-
* `testID` / `nativeID` / `accessibilityLabel` targets, and AUTO
|
|
15
|
-
* selectors.
|
|
26
|
+
* `userflowId` / `testID` / `nativeID` / `accessibilityLabel` targets, and AUTO
|
|
27
|
+
* (codeless) selectors.
|
|
16
28
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* just because the auto-anchor registry is empty. Web-interaction states
|
|
21
|
-
* (CLICK, DISABLED, …) still report unknown.
|
|
29
|
+
* PRESENT / NOT_PRESENT: any uniquely-or-not host on a visible navigator screen
|
|
30
|
+
* counts (web parity — duplicates are present). A measured 0×0 host counts as
|
|
31
|
+
* absent. AUTO `unknown` / `ambiguous` stay `null`.
|
|
22
32
|
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* ScrollView, opacity 0, etc.) would need async `measureInWindow` and is out
|
|
28
|
-
* of scope.
|
|
33
|
+
* CLICK / NOT_CLICK: latch from the first matching tap, including a tap on a
|
|
34
|
+
* descendant. The latch survives re-attach via `previousIsTrue` and is cleared
|
|
35
|
+
* when the server untracks. Disabled hosts are ignored. Until it latches, an
|
|
36
|
+
* AUTO selector that does not resolve reports `null`, like presence.
|
|
29
37
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
38
|
+
* Element states are compared as string literals rather than protocol enums so
|
|
39
|
+
* the wire values stay obvious next to the branches. They match
|
|
40
|
+
* `FlowConditionElementState`.
|
|
33
41
|
*/
|
|
34
42
|
export function createElementConditionEvaluator(
|
|
35
43
|
warn: (message: string) => void = (message) => {
|
|
@@ -47,75 +55,232 @@ export function createElementConditionEvaluator(
|
|
|
47
55
|
warn(message);
|
|
48
56
|
};
|
|
49
57
|
|
|
50
|
-
return (condition, report) => {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
report
|
|
54
|
-
}
|
|
58
|
+
return (condition, report, context) => {
|
|
59
|
+
const elementState = condition.elementState as string | null;
|
|
60
|
+
if (elementState === 'PRESENT' || elementState === 'NOT_PRESENT') {
|
|
61
|
+
return trackPresence(condition, elementState, report, warnOnce);
|
|
62
|
+
}
|
|
63
|
+
if (elementState === 'CLICK' || elementState === 'NOT_CLICK') {
|
|
64
|
+
return trackClick(condition, elementState, report, context, warnOnce);
|
|
65
|
+
}
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
warnOnce(
|
|
68
|
+
condition.id,
|
|
69
|
+
`[Userflow] ELEMENT condition ${condition.id} uses unsupported elementState ` +
|
|
70
|
+
`"${elementState ?? 'null'}" (supported: PRESENT, NOT_PRESENT, CLICK, NOT_CLICK); ` +
|
|
71
|
+
'leaving unknown',
|
|
72
|
+
);
|
|
73
|
+
report(null);
|
|
74
|
+
return () => {};
|
|
58
75
|
};
|
|
59
76
|
}
|
|
60
77
|
|
|
61
|
-
function
|
|
78
|
+
function trackPresence(
|
|
62
79
|
condition: FlowConditionFragment,
|
|
80
|
+
elementState: 'PRESENT' | 'NOT_PRESENT',
|
|
81
|
+
report: ConditionReport,
|
|
63
82
|
warnOnce: (conditionId: string, message: string) => void,
|
|
64
|
-
):
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
): () => void {
|
|
84
|
+
// A synchronous result supersedes any older measurement. Only one
|
|
85
|
+
// measurement runs at a time, and a change during it re-runs once it settles
|
|
86
|
+
// instead of discarding it, so steady anchor churn cannot hold the condition
|
|
87
|
+
// at unknown.
|
|
88
|
+
let generation = 0;
|
|
89
|
+
let reportedGeneration = 0;
|
|
90
|
+
let measuring = false;
|
|
91
|
+
let changedWhileMeasuring = false;
|
|
92
|
+
let disposed = false;
|
|
93
|
+
|
|
94
|
+
const evaluate = (): void => {
|
|
95
|
+
if (disposed) return;
|
|
96
|
+
const gen = ++generation;
|
|
97
|
+
const resolution = resolveElementTargets(condition.selector);
|
|
98
|
+
|
|
99
|
+
if (resolution.status === 'unknown') {
|
|
100
|
+
if (!resolution.silent) warnUnknown(condition, warnOnce);
|
|
101
|
+
reportedGeneration = gen;
|
|
102
|
+
report(null);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (resolution.status === 'absent') {
|
|
106
|
+
reportedGeneration = gen;
|
|
107
|
+
report(elementState === 'PRESENT' ? false : true);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (measuring) {
|
|
111
|
+
changedWhileMeasuring = true;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
measuring = true;
|
|
116
|
+
void measureVisibility(resolution.targets)
|
|
117
|
+
.catch((): HostVisibility => 'unknown')
|
|
118
|
+
.then((visibility) => {
|
|
119
|
+
measuring = false;
|
|
120
|
+
if (disposed) return;
|
|
121
|
+
if (gen > reportedGeneration) {
|
|
122
|
+
reportedGeneration = gen;
|
|
123
|
+
if (visibility === 'unknown') {
|
|
124
|
+
report(null);
|
|
125
|
+
} else {
|
|
126
|
+
const present = visibility === 'present';
|
|
127
|
+
report(elementState === 'PRESENT' ? present : !present);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (changedWhileMeasuring) {
|
|
131
|
+
changedWhileMeasuring = false;
|
|
132
|
+
evaluate();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
evaluate();
|
|
138
|
+
const unsubscribe = anchorChangeTicker.subscribe(evaluate);
|
|
139
|
+
return () => {
|
|
140
|
+
disposed = true;
|
|
141
|
+
unsubscribe();
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function trackClick(
|
|
146
|
+
condition: FlowConditionFragment,
|
|
147
|
+
elementState: 'CLICK' | 'NOT_CLICK',
|
|
148
|
+
report: ConditionReport,
|
|
149
|
+
context: ConditionEvaluatorContext | undefined,
|
|
150
|
+
warnOnce: (conditionId: string, message: string) => void,
|
|
151
|
+
): () => void {
|
|
152
|
+
if (!clickSelectorUsable(condition)) {
|
|
153
|
+
warnUnknown(condition, warnOnce);
|
|
154
|
+
report(null);
|
|
155
|
+
return () => {};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// CLICK starts false and latches true. NOT_CLICK starts true and latches false.
|
|
159
|
+
const valueWhenOccurred = elementState === 'CLICK';
|
|
160
|
+
if (context?.previousIsTrue === valueWhenOccurred) {
|
|
161
|
+
report(valueWhenOccurred);
|
|
162
|
+
return () => {};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let latched = false;
|
|
166
|
+
let stopWatchingResolution = (): void => {};
|
|
167
|
+
if ((condition.selector?.type as string | null) === 'AUTO') {
|
|
168
|
+
// An AUTO selector can stay unresolvable (empty auto-anchor registry, a
|
|
169
|
+
// tie between look-alikes), and then no tap can latch. Report unknown
|
|
170
|
+
// until it resolves, as presence does, rather than an unclicked value
|
|
171
|
+
// nothing could clear.
|
|
172
|
+
const reportUnclickedOnceResolvable = (): void => {
|
|
173
|
+
if (latched) return;
|
|
174
|
+
const resolution = resolveElementTargets(condition.selector);
|
|
175
|
+
if (resolution.status === 'unknown') {
|
|
176
|
+
if (!resolution.silent) warnUnknown(condition, warnOnce);
|
|
177
|
+
report(null);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
report(!valueWhenOccurred);
|
|
181
|
+
};
|
|
182
|
+
reportUnclickedOnceResolvable();
|
|
183
|
+
stopWatchingResolution = anchorChangeTicker.subscribe(reportUnclickedOnceResolvable);
|
|
184
|
+
} else {
|
|
185
|
+
report(!valueWhenOccurred);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const subscription = {
|
|
189
|
+
unsubscribe: (): void => {},
|
|
190
|
+
};
|
|
191
|
+
subscription.unsubscribe = elementTapMonitor.subscribe((tap) => {
|
|
192
|
+
if (latched) return;
|
|
193
|
+
if (!tapMayHitSelector(condition.selector, tap)) return;
|
|
194
|
+
const resolution = resolveElementTargets(condition.selector);
|
|
195
|
+
if (resolution.status !== 'found') return;
|
|
196
|
+
|
|
197
|
+
const matched = resolution.targets.find(
|
|
198
|
+
(target) => target.fiber != null && fiberIsUnder(tap.targetFiber, target.fiber),
|
|
72
199
|
);
|
|
73
|
-
return
|
|
200
|
+
if (!matched?.fiber) return;
|
|
201
|
+
if (tapPathDisabled(tap.targetFiber, matched.fiber)) return;
|
|
202
|
+
|
|
203
|
+
latched = true;
|
|
204
|
+
report(valueWhenOccurred, { immediate: true });
|
|
205
|
+
subscription.unsubscribe();
|
|
206
|
+
stopWatchingResolution();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
return () => {
|
|
210
|
+
subscription.unsubscribe();
|
|
211
|
+
stopWatchingResolution();
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function clickSelectorUsable(condition: FlowConditionFragment): boolean {
|
|
216
|
+
const selector = condition.selector;
|
|
217
|
+
if (selector == null) return false;
|
|
218
|
+
if ((selector.type as string | null) === 'AUTO') {
|
|
219
|
+
// Without a fingerprint the selector can never resolve, so a definite
|
|
220
|
+
// NOT_CLICK true would start the flow on a target nobody can tap.
|
|
221
|
+
return decodeMobileAutoData(selector.autoData)?.fingerprint != null;
|
|
74
222
|
}
|
|
223
|
+
return resolveManualAnchorId(selector.css) != null;
|
|
224
|
+
}
|
|
75
225
|
|
|
226
|
+
function warnUnknown(
|
|
227
|
+
condition: FlowConditionFragment,
|
|
228
|
+
warnOnce: (conditionId: string, message: string) => void,
|
|
229
|
+
): void {
|
|
76
230
|
const selector = condition.selector;
|
|
77
231
|
if (selector == null) {
|
|
78
232
|
warnOnce(
|
|
79
233
|
condition.id,
|
|
80
234
|
`[Userflow] ELEMENT condition ${condition.id} has no selector; leaving unknown`,
|
|
81
235
|
);
|
|
82
|
-
return
|
|
236
|
+
return;
|
|
83
237
|
}
|
|
84
|
-
|
|
85
238
|
if ((selector.type as string | null) === 'AUTO') {
|
|
86
|
-
const resolution = resolveAutoAnchorHost(selector.autoData);
|
|
87
|
-
if (resolution.status === 'unknown') {
|
|
88
|
-
warnOnce(
|
|
89
|
-
condition.id,
|
|
90
|
-
`[Userflow] ELEMENT condition ${condition.id} uses an AUTO (codeless) selector ` +
|
|
91
|
-
'that could not be resolved; leaving unknown',
|
|
92
|
-
);
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
if (resolution.status === 'ambiguous') {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
const present = resolution.status === 'found';
|
|
99
|
-
return elementState === 'PRESENT' ? present : !present;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const anchorId = resolveManualAnchorId(selector.css);
|
|
103
|
-
if (anchorId == null) {
|
|
104
239
|
warnOnce(
|
|
105
240
|
condition.id,
|
|
106
|
-
`[Userflow] ELEMENT condition ${condition.id}
|
|
107
|
-
'leaving unknown',
|
|
241
|
+
`[Userflow] ELEMENT condition ${condition.id} uses an AUTO (codeless) selector ` +
|
|
242
|
+
'that could not be resolved; leaving unknown',
|
|
108
243
|
);
|
|
109
|
-
return
|
|
244
|
+
return;
|
|
110
245
|
}
|
|
246
|
+
warnOnce(
|
|
247
|
+
condition.id,
|
|
248
|
+
`[Userflow] ELEMENT condition ${condition.id} has no resolvable manual anchor id; ` +
|
|
249
|
+
'leaving unknown',
|
|
250
|
+
);
|
|
251
|
+
}
|
|
111
252
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
253
|
+
type HostVisibility = 'present' | 'absent' | 'unknown';
|
|
254
|
+
|
|
255
|
+
/** Measures hosts in parallel so duplicates cost one timeout, not one each. */
|
|
256
|
+
async function measureVisibility(targets: ElementTarget[]): Promise<HostVisibility> {
|
|
257
|
+
const slice = targets.slice(0, MAX_MEASURED_HOSTS);
|
|
258
|
+
const measured = await Promise.all(slice.map(measureHost));
|
|
259
|
+
if (measured.some((size) => size !== 'timeout' && size.width > 0 && size.height > 0)) {
|
|
260
|
+
return 'present';
|
|
118
261
|
}
|
|
119
|
-
|
|
120
|
-
return
|
|
262
|
+
if (measured.includes('timeout') || targets.length > slice.length) return 'unknown';
|
|
263
|
+
return 'absent';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function measureHost(
|
|
267
|
+
target: ElementTarget,
|
|
268
|
+
): Promise<{ width: number; height: number } | 'timeout'> {
|
|
269
|
+
return new Promise((resolve) => {
|
|
270
|
+
let settled = false;
|
|
271
|
+
const finish = (value: { width: number; height: number } | 'timeout'): void => {
|
|
272
|
+
if (settled) return;
|
|
273
|
+
settled = true;
|
|
274
|
+
clearTimeout(timer);
|
|
275
|
+
resolve(value);
|
|
276
|
+
};
|
|
277
|
+
const timer = setTimeout(() => finish('timeout'), MEASURE_TIMEOUT_MS);
|
|
278
|
+
try {
|
|
279
|
+
target.host.measureInWindow((_x, _y, width, height) => {
|
|
280
|
+
finish({ width, height });
|
|
281
|
+
});
|
|
282
|
+
} catch {
|
|
283
|
+
finish('timeout');
|
|
284
|
+
}
|
|
285
|
+
});
|
|
121
286
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pass-through tap observation for ELEMENT CLICK / NOT_CLICK conditions.
|
|
3
|
+
*
|
|
4
|
+
* Many conditions can subscribe at once. Touch start/end are paired per finger
|
|
5
|
+
* and a tap is emitted only when movement stays within the same slop the
|
|
6
|
+
* tooltip target-press monitor uses. The host fiber is the one captured at
|
|
7
|
+
* touch start, before the press's setState commits a new tree.
|
|
8
|
+
*
|
|
9
|
+
* With no subscribers the monitor stores nothing, so the bridge can skip the
|
|
10
|
+
* fiber lookup entirely.
|
|
11
|
+
*/
|
|
12
|
+
import type { AnyFiber } from './fiber/fiberInternals';
|
|
13
|
+
import { type TouchIdentifier, type TouchPoint, withinTapSlop } from './targetPressMonitor';
|
|
14
|
+
|
|
15
|
+
export interface ElementTap {
|
|
16
|
+
targetFiber: AnyFiber;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ElementTapListener = (tap: ElementTap) => void;
|
|
20
|
+
|
|
21
|
+
interface TouchStart extends TouchPoint {
|
|
22
|
+
targetFiber: AnyFiber | null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ElementTapMonitor {
|
|
26
|
+
private readonly listeners = new Set<ElementTapListener>();
|
|
27
|
+
private readonly touchStarts = new Map<TouchIdentifier, TouchStart>();
|
|
28
|
+
|
|
29
|
+
get subscriberCount(): number {
|
|
30
|
+
return this.listeners.size;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
subscribe(listener: ElementTapListener): () => void {
|
|
34
|
+
this.listeners.add(listener);
|
|
35
|
+
return () => {
|
|
36
|
+
this.listeners.delete(listener);
|
|
37
|
+
if (this.listeners.size === 0) {
|
|
38
|
+
this.touchStarts.clear();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleTouchStart(
|
|
44
|
+
identifier: TouchIdentifier,
|
|
45
|
+
pageX: number,
|
|
46
|
+
pageY: number,
|
|
47
|
+
targetFiber: AnyFiber | null,
|
|
48
|
+
): void {
|
|
49
|
+
if (this.listeners.size === 0) {
|
|
50
|
+
this.touchStarts.clear();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.touchStarts.set(identifier, { pageX, pageY, targetFiber });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Emit a tap when this finger started and ended within slop on a resolved
|
|
58
|
+
* host. Never claims the responder.
|
|
59
|
+
*/
|
|
60
|
+
handleTouchEnd(identifier: TouchIdentifier, pageX: number, pageY: number): void {
|
|
61
|
+
const start = this.touchStarts.get(identifier) ?? null;
|
|
62
|
+
this.touchStarts.delete(identifier);
|
|
63
|
+
if (!start || this.listeners.size === 0 || !start.targetFiber) return;
|
|
64
|
+
if (!withinTapSlop(start, { pageX, pageY })) return;
|
|
65
|
+
|
|
66
|
+
const tap: ElementTap = { targetFiber: start.targetFiber };
|
|
67
|
+
for (const listener of this.listeners) {
|
|
68
|
+
// One condition's failure must not cost the others this tap, or abort
|
|
69
|
+
// the bridge's handling of the event's remaining touches.
|
|
70
|
+
try {
|
|
71
|
+
listener(tap);
|
|
72
|
+
} catch {
|
|
73
|
+
// The condition stays at its current value.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Drop in-progress gesture(s). Omit `identifier` to clear every finger. */
|
|
79
|
+
cancelTouch(identifier?: TouchIdentifier): void {
|
|
80
|
+
if (identifier == null) {
|
|
81
|
+
this.touchStarts.clear();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.touchStarts.delete(identifier);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Test helper. */
|
|
88
|
+
resetForTests(): void {
|
|
89
|
+
this.listeners.clear();
|
|
90
|
+
this.touchStarts.clear();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Shared singleton used by TargetPressBridge and the element-condition evaluator. */
|
|
95
|
+
export const elementTapMonitor = new ElementTapMonitor();
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compare a touch-start fiber with a host resolved later, tolerating the
|
|
3
|
+
* alternate swap a Pressable `pressed` re-render does mid-gesture.
|
|
4
|
+
*/
|
|
5
|
+
import type { ElementTap } from './elementTapMonitor';
|
|
6
|
+
import {
|
|
7
|
+
type AnyFiber,
|
|
8
|
+
MAX_ANCESTOR_WALK,
|
|
9
|
+
getAlternate,
|
|
10
|
+
getLiveHostProps,
|
|
11
|
+
getMemoizedProps,
|
|
12
|
+
getReturn,
|
|
13
|
+
} from './fiber/fiberInternals';
|
|
14
|
+
import { ANCHOR_PROP_TIERS } from './fiber/findAnchorFiber';
|
|
15
|
+
|
|
16
|
+
const anchorIdsByTap = new WeakMap<ElementTap, ReadonlySet<string>>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Anchor ids on the tapped fiber and its ancestors, built once per tap and
|
|
20
|
+
* shared by every click condition it is delivered to.
|
|
21
|
+
*/
|
|
22
|
+
export function anchorIdsAboveTap(tap: ElementTap): ReadonlySet<string> {
|
|
23
|
+
const cached = anchorIdsByTap.get(tap);
|
|
24
|
+
if (cached) return cached;
|
|
25
|
+
|
|
26
|
+
const ids = new Set<string>();
|
|
27
|
+
let current: AnyFiber = tap.targetFiber;
|
|
28
|
+
for (let depth = 0; current && depth < MAX_ANCESTOR_WALK; depth += 1) {
|
|
29
|
+
const props = getMemoizedProps(current);
|
|
30
|
+
if (props) {
|
|
31
|
+
for (const tier of ANCHOR_PROP_TIERS) {
|
|
32
|
+
const value = props[tier];
|
|
33
|
+
if (typeof value === 'string') ids.add(value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
current = getReturn(current);
|
|
37
|
+
}
|
|
38
|
+
anchorIdsByTap.set(tap, ids);
|
|
39
|
+
return ids;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function sameFiber(a: AnyFiber, b: AnyFiber): boolean {
|
|
43
|
+
if (a == null || b == null) return false;
|
|
44
|
+
if (a === b) return true;
|
|
45
|
+
const aAlt = getAlternate(a);
|
|
46
|
+
const bAlt = getAlternate(b);
|
|
47
|
+
return a === bAlt || aAlt === b || (aAlt != null && aAlt === bAlt);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** True when `node` is `host` or a descendant of it. */
|
|
51
|
+
export function fiberIsUnder(node: AnyFiber, host: AnyFiber): boolean {
|
|
52
|
+
let current: AnyFiber = node;
|
|
53
|
+
for (let depth = 0; current && depth < MAX_ANCESTOR_WALK; depth += 1) {
|
|
54
|
+
if (sameFiber(current, host)) return true;
|
|
55
|
+
current = getReturn(current);
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fiberDisabled(fiber: AnyFiber): boolean {
|
|
61
|
+
let props: Record<string, unknown> | undefined;
|
|
62
|
+
try {
|
|
63
|
+
props = getLiveHostProps(fiber);
|
|
64
|
+
} catch {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (!props) return false;
|
|
68
|
+
if (props.disabled === true) return true;
|
|
69
|
+
if (props['aria-disabled'] === true) return true;
|
|
70
|
+
const state = props.accessibilityState;
|
|
71
|
+
return (
|
|
72
|
+
typeof state === 'object' &&
|
|
73
|
+
state !== null &&
|
|
74
|
+
(state as { disabled?: unknown }).disabled === true
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* True when the tapped fiber, the matched host, or a fiber between them is
|
|
80
|
+
* disabled. Browsers do not fire `click` on a disabled control.
|
|
81
|
+
*/
|
|
82
|
+
export function tapPathDisabled(tapped: AnyFiber, host: AnyFiber): boolean {
|
|
83
|
+
let current: AnyFiber = tapped;
|
|
84
|
+
for (let depth = 0; current && depth < MAX_ANCESTOR_WALK; depth += 1) {
|
|
85
|
+
if (fiberDisabled(current)) return true;
|
|
86
|
+
if (sameFiber(current, host)) return false;
|
|
87
|
+
current = getReturn(current);
|
|
88
|
+
}
|
|
89
|
+
return fiberDisabled(host);
|
|
90
|
+
}
|