@getuserfeedback/react-native 5.11.0 → 5.14.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 +27 -8
- package/dist/client.d.ts +29 -22
- package/dist/core-webview-host.js +1 -1
- package/dist/flow-container.js +1 -4
- package/dist/native-core-command-channel.d.ts +7 -0
- package/dist/native-core-command-channel.js +17 -0
- package/dist/native-flow-container-context.d.ts +3 -0
- package/dist/native-link-router-rpc.d.ts +14 -0
- package/dist/native-link-router-rpc.js +163 -0
- package/dist/native-provider-client-controller.d.ts +1 -0
- package/dist/native-provider-client-controller.js +1 -0
- package/dist/native-provider-client.d.ts +1 -1
- package/dist/native-provider-client.js +0 -2
- package/dist/native-provider-flow-registry.d.ts +8 -1
- package/dist/native-provider-flow-registry.js +46 -3
- package/dist/native-provider.d.ts +3 -1
- package/dist/native-provider.js +29 -3
- package/dist/native-runtime-root.d.ts +8 -2
- package/dist/native-runtime-root.js +32 -7
- package/dist/native-view-host-projection.js +8 -2
- package/dist/native-view-layout-transition.d.ts +25 -0
- package/dist/native-view-layout-transition.js +244 -0
- package/dist/native-view-presentation-surface.d.ts +3 -1
- package/dist/native-view-presentation-surface.js +48 -16
- package/dist/native-view-record-controller.d.ts +7 -1
- package/dist/native-view-record-controller.js +50 -6
- package/dist/use-flow.d.ts +32 -0
- package/dist/use-flow.js +50 -0
- package/dist/version.js +1 -1
- package/dist/view-webview-host.js +1 -1
- package/package.json +4 -4
package/dist/native-provider.js
CHANGED
|
@@ -12,6 +12,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
12
12
|
import { parseInitOptions } from "@getuserfeedback/protocol";
|
|
13
13
|
import { createUniqueId } from "@getuserfeedback/protocol/host";
|
|
14
14
|
import { createActionRegistry, } from "@getuserfeedback/sdk/internal/action-registry";
|
|
15
|
+
import { resolveLinkRouter } from "@getuserfeedback/sdk/internal/link-router";
|
|
15
16
|
import { createContext, createElement, Fragment, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
16
17
|
import { NativeStandardFlowContainer } from "./flow-container.js";
|
|
17
18
|
import { NativeFlowContainerContext, } from "./native-flow-container-context.js";
|
|
@@ -19,6 +20,7 @@ import { createNativeProviderClientController } from "./native-provider-client-c
|
|
|
19
20
|
import { REACT_NATIVE_PINNED_CORE_URL } from "./native-runtime-endpoints.js";
|
|
20
21
|
import { NativeProviderRuntime } from "./native-runtime-root.js";
|
|
21
22
|
export { FlowContent, useFlowContainer } from "./flow-container.js";
|
|
23
|
+
export { useFlow } from "./use-flow.js";
|
|
22
24
|
const GetUserFeedbackContext = createContext(null);
|
|
23
25
|
const toClientOptionsKey = (value) => {
|
|
24
26
|
if (value === null || typeof value !== "object") {
|
|
@@ -37,6 +39,20 @@ const toClientOptionsKey = (value) => {
|
|
|
37
39
|
const compareClientOptionsKeys = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
38
40
|
const haveSameActionRegistrations = (left, right) => left.length === right.length &&
|
|
39
41
|
left.every((identity, index) => identity === right[index]);
|
|
42
|
+
const useNativeLinkRouter = (links) => {
|
|
43
|
+
const router = resolveLinkRouter(links);
|
|
44
|
+
const hasRouterRef = useRef(router !== undefined);
|
|
45
|
+
if (hasRouterRef.current !== (router !== undefined)) {
|
|
46
|
+
throw new Error("links.router presence cannot change after provider mount; keep the router configured for the provider lifetime");
|
|
47
|
+
}
|
|
48
|
+
const routerRef = useRef(router);
|
|
49
|
+
useLayoutEffect(() => {
|
|
50
|
+
routerRef.current = router;
|
|
51
|
+
}, [router]);
|
|
52
|
+
return {
|
|
53
|
+
getRouter: useCallback(() => routerRef.current, []),
|
|
54
|
+
};
|
|
55
|
+
};
|
|
40
56
|
const useNativeActionRegistry = (actions) => {
|
|
41
57
|
const nextRegistry = createActionRegistry(actions);
|
|
42
58
|
if (nextRegistry.getOpenUrlHandler() !== undefined) {
|
|
@@ -69,7 +85,7 @@ const toInitFlags = (flags) => {
|
|
|
69
85
|
return normalizedFlags.length === 0 ? undefined : normalizedFlags;
|
|
70
86
|
};
|
|
71
87
|
const parseNativeInitOptions = (_a, hostActions) => {
|
|
72
|
-
var { actions: _actions, capabilities, flags } = _a, clientOptions = __rest(_a, ["actions", "capabilities", "flags"]);
|
|
88
|
+
var { actions: _actions, capabilities, flags, links: _links } = _a, clientOptions = __rest(_a, ["actions", "capabilities", "flags", "links"]);
|
|
73
89
|
const normalizedFlags = toInitFlags(flags);
|
|
74
90
|
const initOptions = parseInitOptions(Object.assign(Object.assign(Object.assign(Object.assign({}, clientOptions), (normalizedFlags === undefined ? {} : { flags: normalizedFlags })), (hostActions !== undefined && hostActions.length > 0
|
|
75
91
|
? { hostActions }
|
|
@@ -91,7 +107,8 @@ const parseNativeInitOptions = (_a, hostActions) => {
|
|
|
91
107
|
: normalizedHostCapabilities });
|
|
92
108
|
};
|
|
93
109
|
export function GetUserFeedbackProvider({ children, clientOptions, instanceId: instanceIdProp, onError, webViewComponent, }) {
|
|
94
|
-
var _a
|
|
110
|
+
var _a;
|
|
111
|
+
var _b;
|
|
95
112
|
const [instanceId] = useState(() => {
|
|
96
113
|
const normalizedInstanceId = instanceIdProp === null || instanceIdProp === void 0 ? void 0 : instanceIdProp.trim();
|
|
97
114
|
return normalizedInstanceId && normalizedInstanceId.length > 0
|
|
@@ -102,6 +119,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
102
119
|
const [projection, setProjection] = useState(null);
|
|
103
120
|
const contentOwnerRef = useRef(null);
|
|
104
121
|
const actionRegistry = useNativeActionRegistry(clientOptions.actions);
|
|
122
|
+
const linkRouter = useNativeLinkRouter(clientOptions.links);
|
|
105
123
|
const normalizedClientOptions = parseNativeInitOptions(clientOptions, actionRegistry.definitions);
|
|
106
124
|
const clientOptionsKey = toClientOptionsKey(normalizedClientOptions);
|
|
107
125
|
const coreUrl = (_b = (_a = normalizedClientOptions.runtimeEndpoints) === null || _a === void 0 ? void 0 : _a.coreUrl) !== null && _b !== void 0 ? _b : REACT_NATIVE_PINNED_CORE_URL;
|
|
@@ -148,9 +166,16 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
148
166
|
const flowContainerContext = useMemo(() => ({
|
|
149
167
|
client: controller.client,
|
|
150
168
|
contentOwner,
|
|
169
|
+
flowRegistry: controller.flowRegistry,
|
|
170
|
+
projection,
|
|
171
|
+
registerContent,
|
|
172
|
+
}), [
|
|
173
|
+
contentOwner,
|
|
174
|
+
controller.client,
|
|
175
|
+
controller.flowRegistry,
|
|
151
176
|
projection,
|
|
152
177
|
registerContent,
|
|
153
|
-
|
|
178
|
+
]);
|
|
154
179
|
const controllerLifecycleTokensRef = useRef(new WeakMap());
|
|
155
180
|
useEffect(() => {
|
|
156
181
|
const lifecycleToken = {};
|
|
@@ -182,6 +207,7 @@ export function GetUserFeedbackProvider({ children, clientOptions, instanceId: i
|
|
|
182
207
|
key: clientOptionsKey,
|
|
183
208
|
getRuntimeConfiguration,
|
|
184
209
|
getHostActionHandler: actionRegistry.getHandler,
|
|
210
|
+
getLinkRouter: linkRouter.getRouter,
|
|
185
211
|
onCommandSessionChange: handleCommandSessionChange,
|
|
186
212
|
onError: reportError,
|
|
187
213
|
onHandleInvalidated: handleHandleInvalidated,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CoreCommandEnvelope } from "@getuserfeedback/protocol";
|
|
2
|
+
import { type LinkRouter } from "@getuserfeedback/protocol/host";
|
|
2
3
|
import type { EventHandleInvalidated } from "@getuserfeedback/protocol/internal/core-handle-invalidation";
|
|
3
4
|
import type { ConfigureOptions, InitOptions } from "@getuserfeedback/sdk";
|
|
4
5
|
import type { ActionRegistry } from "@getuserfeedback/sdk/internal/action-registry";
|
|
@@ -17,6 +18,10 @@ export type NativeRuntimeRootProps = {
|
|
|
17
18
|
getHandler: ActionRegistry["getHandler"];
|
|
18
19
|
instanceId: string;
|
|
19
20
|
};
|
|
21
|
+
linkRouter?: {
|
|
22
|
+
getRouter: () => LinkRouter | undefined;
|
|
23
|
+
instanceId: string;
|
|
24
|
+
};
|
|
20
25
|
onCommandSessionChange?: (session: NativeCoreCommandSession | null) => void;
|
|
21
26
|
onError?: (error: Error) => void;
|
|
22
27
|
onHandleInvalidated?: (message: EventHandleInvalidated) => void;
|
|
@@ -40,8 +45,9 @@ type NativeProviderRuntimeProps = Omit<NativeRuntimeRootProps, "createStartupCom
|
|
|
40
45
|
instanceId: string;
|
|
41
46
|
getRuntimeConfiguration?: () => ConfigureOptions;
|
|
42
47
|
getHostActionHandler?: ActionRegistry["getHandler"];
|
|
48
|
+
getLinkRouter?: () => LinkRouter | undefined;
|
|
43
49
|
onCommandSessionChange?: (session: NativeDirectCommandSession | null) => void;
|
|
44
50
|
};
|
|
45
|
-
export declare function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, hostActions, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs, viewNativeViewComponent, viewWebViewComponent, }: NativeRuntimeRootProps): ReactElement;
|
|
46
|
-
export declare function NativeProviderRuntime({ directSessionController, getHostActionHandler, getRuntimeConfiguration, initOptions, instanceId, onCommandSessionChange, onError, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
|
|
51
|
+
export declare function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, hostActions, linkRouter, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs, viewNativeViewComponent, viewWebViewComponent, }: NativeRuntimeRootProps): ReactElement;
|
|
52
|
+
export declare function NativeProviderRuntime({ directSessionController, getHostActionHandler, getLinkRouter, getRuntimeConfiguration, initOptions, instanceId, onCommandSessionChange, onError, ...runtimeProps }: NativeProviderRuntimeProps): ReactElement;
|
|
47
53
|
export {};
|
|
@@ -10,6 +10,7 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { CommandSettlementError, CommandSettlementTimeoutError, } from "@getuserfeedback/protocol/host";
|
|
13
|
+
import { BROWSER_NAVIGATION_AVAILABILITY_EVENT } from "@getuserfeedback/protocol/internal/navigation-rpc";
|
|
13
14
|
import { createElement, Fragment, useCallback, useEffect, useLayoutEffect, useRef, useState, } from "react";
|
|
14
15
|
import { CoreWebViewHost, } from "./core-webview-host.js";
|
|
15
16
|
import { createNativeCoreCommandChannel } from "./native-core-command-channel.js";
|
|
@@ -20,7 +21,7 @@ import { createNativeViewRecordController, } from "./native-view-record-controll
|
|
|
20
21
|
import { createProviderCommandEnvelope, REACT_NATIVE_CLIENT_META, } from "./provider-command-helpers.js";
|
|
21
22
|
const DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS = 30000;
|
|
22
23
|
const getEmptyRuntimeConfiguration = () => ({});
|
|
23
|
-
export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, hostActions, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, viewNativeViewComponent, viewWebViewComponent, }) {
|
|
24
|
+
export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewComponent, createStartupCommand, hostActions, linkRouter, onCommandSessionChange, onError, onHandleInvalidated, onInvalidMessage, observeOpenRequest, onStartupTerminalError, startupCommandSettlementTimeoutMs = DEFAULT_STARTUP_COMMAND_SETTLEMENT_TIMEOUT_MS, viewNativeViewComponent, viewWebViewComponent, }) {
|
|
24
25
|
const [coreHostRevision, setCoreHostRevision] = useState(0);
|
|
25
26
|
const [session, setSession] = useState(null);
|
|
26
27
|
const sessionRef = useRef(null);
|
|
@@ -42,6 +43,8 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
42
43
|
createStartupCommandRef.current = createStartupCommand;
|
|
43
44
|
const hostActionsRef = useRef(hostActions);
|
|
44
45
|
hostActionsRef.current = hostActions;
|
|
46
|
+
const linkRouterRef = useRef(linkRouter);
|
|
47
|
+
linkRouterRef.current = linkRouter;
|
|
45
48
|
const reportError = useCallback((error) => {
|
|
46
49
|
var _a;
|
|
47
50
|
try {
|
|
@@ -84,8 +87,20 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
84
87
|
}
|
|
85
88
|
const controller = createNativeViewRecordController({
|
|
86
89
|
coreConnection,
|
|
90
|
+
getBrowserNavigationAvailability: linkRouterRef.current
|
|
91
|
+
? () => {
|
|
92
|
+
var _a;
|
|
93
|
+
const available = ((_a = linkRouterRef.current) === null || _a === void 0 ? void 0 : _a.getRouter()) !== undefined;
|
|
94
|
+
return {
|
|
95
|
+
available,
|
|
96
|
+
explicitTargetAvailable: available,
|
|
97
|
+
hostActionLinkAvailable: false,
|
|
98
|
+
type: BROWSER_NAVIGATION_AVAILABILITY_EVENT,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
: undefined,
|
|
87
102
|
onError: reportError,
|
|
88
|
-
observeOpenRequest: (payload) => { var _a
|
|
103
|
+
observeOpenRequest: (payload) => { var _a; var _b; return (_b = (_a = observeOpenRequestRef.current) === null || _a === void 0 ? void 0 : _a.call(observeOpenRequestRef, payload)) !== null && _b !== void 0 ? _b : { allowed: true }; },
|
|
89
104
|
});
|
|
90
105
|
const commandChannelOwner = createNativeCoreCommandChannel({
|
|
91
106
|
coreConnection,
|
|
@@ -100,6 +115,16 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
100
115
|
subscribeToViewActivity: controller.subscribe,
|
|
101
116
|
}
|
|
102
117
|
: undefined,
|
|
118
|
+
linkRouter: linkRouterRef.current
|
|
119
|
+
? {
|
|
120
|
+
getActiveViewIds: () => controller
|
|
121
|
+
.getSnapshot()
|
|
122
|
+
.filter((record) => record.isActive)
|
|
123
|
+
.map((record) => record.viewId),
|
|
124
|
+
getRouter: () => { var _a; return (_a = linkRouterRef.current) === null || _a === void 0 ? void 0 : _a.getRouter(); },
|
|
125
|
+
instanceId: linkRouterRef.current.instanceId,
|
|
126
|
+
}
|
|
127
|
+
: undefined,
|
|
103
128
|
onHostActionError: reportError,
|
|
104
129
|
onSessionFailure: (error) => {
|
|
105
130
|
reportError(error);
|
|
@@ -140,10 +165,8 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
140
165
|
};
|
|
141
166
|
const startupCommand = (_a = createStartupCommandRef.current) === null || _a === void 0 ? void 0 : _a.call(createStartupCommandRef);
|
|
142
167
|
if (startupCommand) {
|
|
143
|
-
// TODO(
|
|
144
|
-
//
|
|
145
|
-
// public open request still times out waiting for Core's settlement.
|
|
146
|
-
// [principle=one-authoritative-command-lifecycle] [scope=slice] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=2]
|
|
168
|
+
// TODO(testing): Run and record the authored direct-runtime open and embedded-close smoke path on Android and iOS devices as device evidence.
|
|
169
|
+
// [seam=native-direct-runtime] [layer=smoke] [workflow=test:react-native-expo-smoke:e2e] [priority=medium] [impact=medium] [risk=medium] [epic=runtime-neutral-view-host-orchestration] [epic_order=2]
|
|
147
170
|
void commandSessionOwner.session
|
|
148
171
|
.dispatch(startupCommand, {
|
|
149
172
|
settlementTimeoutMs: startupCommandSettlementTimeoutMs,
|
|
@@ -257,7 +280,7 @@ export function NativeRuntimeRoot({ attachViewProjection, coreUrl, coreWebViewCo
|
|
|
257
280
|
}));
|
|
258
281
|
}
|
|
259
282
|
export function NativeProviderRuntime(_a) {
|
|
260
|
-
var { directSessionController, getHostActionHandler, getRuntimeConfiguration = getEmptyRuntimeConfiguration, initOptions, instanceId, onCommandSessionChange, onError } = _a, runtimeProps = __rest(_a, ["directSessionController", "getHostActionHandler", "getRuntimeConfiguration", "initOptions", "instanceId", "onCommandSessionChange", "onError"]);
|
|
283
|
+
var { directSessionController, getHostActionHandler, getLinkRouter, getRuntimeConfiguration = getEmptyRuntimeConfiguration, initOptions, instanceId, onCommandSessionChange, onError } = _a, runtimeProps = __rest(_a, ["directSessionController", "getHostActionHandler", "getLinkRouter", "getRuntimeConfiguration", "initOptions", "instanceId", "onCommandSessionChange", "onError"]);
|
|
261
284
|
const directSessionRef = useRef(null);
|
|
262
285
|
useLayoutEffect(() => {
|
|
263
286
|
directSessionController.beginAttachmentAttempt();
|
|
@@ -298,5 +321,7 @@ export function NativeProviderRuntime(_a) {
|
|
|
298
321
|
}, [getRuntimeConfiguration, initOptions, instanceId]);
|
|
299
322
|
return createElement(NativeRuntimeRoot, Object.assign(Object.assign({}, runtimeProps), { hostActions: getHostActionHandler
|
|
300
323
|
? { getHandler: getHostActionHandler, instanceId }
|
|
324
|
+
: undefined, linkRouter: getLinkRouter
|
|
325
|
+
? { getRouter: getLinkRouter, instanceId }
|
|
301
326
|
: undefined, onCommandSessionChange: handleCommandSessionChange, onError, onStartupTerminalError: handleStartupTerminalError, createStartupCommand }));
|
|
302
327
|
}
|
|
@@ -36,8 +36,9 @@ export function NativeProjectedViewContent({ controller, onInvalidMessage, recor
|
|
|
36
36
|
});
|
|
37
37
|
}
|
|
38
38
|
function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInvalidMessage, presentationMode, record, webViewComponent, }) {
|
|
39
|
-
var _a;
|
|
39
|
+
var _a, _b;
|
|
40
40
|
const layoutTransactionId = (_a = record.size) === null || _a === void 0 ? void 0 : _a.layoutTransactionId;
|
|
41
|
+
const transitionSchedule = (_b = record.size) === null || _b === void 0 ? void 0 : _b.transitionSchedule;
|
|
41
42
|
useLayoutEffect(() => {
|
|
42
43
|
if (!layoutTransactionId) {
|
|
43
44
|
return;
|
|
@@ -67,7 +68,12 @@ function NativeViewHostProjectionEntry({ controller, nativeViewComponent, onInva
|
|
|
67
68
|
viewId: record.viewId,
|
|
68
69
|
});
|
|
69
70
|
});
|
|
70
|
-
return createElement(NativeViewPresentationSurface, {
|
|
71
|
+
return createElement(NativeViewPresentationSurface, {
|
|
72
|
+
nativeViewComponent,
|
|
73
|
+
presentationMode,
|
|
74
|
+
record,
|
|
75
|
+
transitionSchedule,
|
|
76
|
+
}, createElement(NativeProjectedViewContent, {
|
|
71
77
|
controller,
|
|
72
78
|
onInvalidMessage,
|
|
73
79
|
record,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type WidgetLayoutTimedTransitionPolicy, type WidgetLayoutTransitionSchedule } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { type ComponentType } from "react";
|
|
3
|
+
export type NativeViewFrame = Readonly<{
|
|
4
|
+
bottomLeftRadius: number;
|
|
5
|
+
bottomRightRadius: number;
|
|
6
|
+
height: number;
|
|
7
|
+
topLeftRadius: number;
|
|
8
|
+
topRightRadius: number;
|
|
9
|
+
width: number;
|
|
10
|
+
}>;
|
|
11
|
+
export type NativeViewFrameSurfaceStyles = Readonly<{
|
|
12
|
+
border: Record<string, unknown>;
|
|
13
|
+
content: Record<string, unknown>;
|
|
14
|
+
frame: Record<string, unknown>;
|
|
15
|
+
}>;
|
|
16
|
+
export declare function useNativeViewFrameTransition(input: {
|
|
17
|
+
frame: NativeViewFrame | null;
|
|
18
|
+
nativeView: ComponentType<Record<string, unknown>>;
|
|
19
|
+
revision: number;
|
|
20
|
+
schedule?: WidgetLayoutTransitionSchedule;
|
|
21
|
+
styles: NativeViewFrameSurfaceStyles;
|
|
22
|
+
transitionPolicy?: WidgetLayoutTimedTransitionPolicy;
|
|
23
|
+
}): NativeViewFrameSurfaceStyles & {
|
|
24
|
+
frameView: ComponentType<Record<string, unknown>>;
|
|
25
|
+
};
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { resolveWidgetLayoutResumedEasing, resolveWidgetLayoutTransitionExecution, } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { useLayoutEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
const resolveNativeAnimationRuntime = () => {
|
|
4
|
+
try {
|
|
5
|
+
const reactNativeModule = require("react-native");
|
|
6
|
+
return reactNativeModule.Animated
|
|
7
|
+
? {
|
|
8
|
+
Animated: reactNativeModule.Animated,
|
|
9
|
+
Easing: reactNativeModule.Easing,
|
|
10
|
+
}
|
|
11
|
+
: null;
|
|
12
|
+
}
|
|
13
|
+
catch (_a) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
const FRAME_PROPERTIES = [
|
|
18
|
+
"width",
|
|
19
|
+
"height",
|
|
20
|
+
"topLeftRadius",
|
|
21
|
+
"topRightRadius",
|
|
22
|
+
"bottomLeftRadius",
|
|
23
|
+
"bottomRightRadius",
|
|
24
|
+
];
|
|
25
|
+
export function useNativeViewFrameTransition(input) {
|
|
26
|
+
const animationRuntime = useMemo(resolveNativeAnimationRuntime, []);
|
|
27
|
+
const animatedFrameView = useMemo(() => animationRuntime
|
|
28
|
+
? animationRuntime.Animated.createAnimatedComponent(input.nativeView)
|
|
29
|
+
: input.nativeView, [animationRuntime, input.nativeView]);
|
|
30
|
+
const animatedValuesRef = useRef(null);
|
|
31
|
+
const previousFrameRef = useRef(null);
|
|
32
|
+
const presentedStylesRef = useRef(input.styles);
|
|
33
|
+
const consumedScheduleRef = useRef(undefined);
|
|
34
|
+
const activeScheduleRef = useRef(undefined);
|
|
35
|
+
const activeAnimationsRef = useRef([]);
|
|
36
|
+
const pendingStartRef = useRef(undefined);
|
|
37
|
+
const animationAttemptRef = useRef(0);
|
|
38
|
+
const [isAnimating, setIsAnimating] = useState(false);
|
|
39
|
+
const latestInputRef = useRef(input);
|
|
40
|
+
latestInputRef.current = input;
|
|
41
|
+
const hasFrame = input.frame !== null;
|
|
42
|
+
if (animationRuntime && input.frame && animatedValuesRef.current === null) {
|
|
43
|
+
animatedValuesRef.current = {
|
|
44
|
+
bottomLeftRadius: new animationRuntime.Animated.Value(input.frame.bottomLeftRadius),
|
|
45
|
+
bottomRightRadius: new animationRuntime.Animated.Value(input.frame.bottomRightRadius),
|
|
46
|
+
height: new animationRuntime.Animated.Value(input.frame.height),
|
|
47
|
+
topLeftRadius: new animationRuntime.Animated.Value(input.frame.topLeftRadius),
|
|
48
|
+
topRightRadius: new animationRuntime.Animated.Value(input.frame.topRightRadius),
|
|
49
|
+
width: new animationRuntime.Animated.Value(input.frame.width),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
const hasPendingSchedule = input.schedule !== undefined &&
|
|
53
|
+
consumedScheduleRef.current !== input.schedule;
|
|
54
|
+
const shouldUseAnimatedStyles = animationRuntime !== null &&
|
|
55
|
+
input.frame !== null &&
|
|
56
|
+
animatedValuesRef.current !== null &&
|
|
57
|
+
(hasPendingSchedule ||
|
|
58
|
+
(isAnimating && activeScheduleRef.current === input.schedule));
|
|
59
|
+
// Report revision and frame presence are the occurrence boundaries. The
|
|
60
|
+
// latter cancels active motion when a connection loses its measured frame.
|
|
61
|
+
// latestInputRef carries the complete snapshot without making styles effect keys.
|
|
62
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: revision intentionally owns the occurrence
|
|
63
|
+
useLayoutEffect(() => {
|
|
64
|
+
const currentInput = latestInputRef.current;
|
|
65
|
+
const stopAnimations = () => {
|
|
66
|
+
var _a;
|
|
67
|
+
clearTimeout(pendingStartRef.current);
|
|
68
|
+
pendingStartRef.current = undefined;
|
|
69
|
+
for (const animation of activeAnimationsRef.current) {
|
|
70
|
+
(_a = animation.stop) === null || _a === void 0 ? void 0 : _a.call(animation);
|
|
71
|
+
}
|
|
72
|
+
activeAnimationsRef.current = [];
|
|
73
|
+
};
|
|
74
|
+
const frame = currentInput.frame;
|
|
75
|
+
const animatedValues = animatedValuesRef.current;
|
|
76
|
+
const previousFrame = previousFrameRef.current;
|
|
77
|
+
previousFrameRef.current = frame;
|
|
78
|
+
if (!animationRuntime || !frame || !animatedValues) {
|
|
79
|
+
animationAttemptRef.current += 1;
|
|
80
|
+
stopAnimations();
|
|
81
|
+
activeScheduleRef.current = undefined;
|
|
82
|
+
presentedStylesRef.current = currentInput.styles;
|
|
83
|
+
setIsAnimating(false);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const schedule = currentInput.schedule;
|
|
87
|
+
const transitionPolicy = currentInput.transitionPolicy;
|
|
88
|
+
if (schedule === undefined ||
|
|
89
|
+
transitionPolicy === undefined ||
|
|
90
|
+
consumedScheduleRef.current === schedule ||
|
|
91
|
+
previousFrame === null) {
|
|
92
|
+
animationAttemptRef.current += 1;
|
|
93
|
+
stopAnimations();
|
|
94
|
+
activeScheduleRef.current = undefined;
|
|
95
|
+
presentedStylesRef.current = currentInput.styles;
|
|
96
|
+
for (const property of FRAME_PROPERTIES) {
|
|
97
|
+
animatedValues[property].setValue(frame[property]);
|
|
98
|
+
}
|
|
99
|
+
setIsAnimating(false);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
consumedScheduleRef.current = schedule;
|
|
103
|
+
const execution = resolveWidgetLayoutTransitionExecution({
|
|
104
|
+
durationMs: transitionPolicy.durationMs,
|
|
105
|
+
nowEpochMs: Date.now(),
|
|
106
|
+
schedule,
|
|
107
|
+
});
|
|
108
|
+
if (execution.isComplete) {
|
|
109
|
+
animationAttemptRef.current += 1;
|
|
110
|
+
stopAnimations();
|
|
111
|
+
activeScheduleRef.current = undefined;
|
|
112
|
+
presentedStylesRef.current = currentInput.styles;
|
|
113
|
+
for (const property of FRAME_PROPERTIES) {
|
|
114
|
+
animatedValues[property].setValue(frame[property]);
|
|
115
|
+
}
|
|
116
|
+
setIsAnimating(false);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
animationAttemptRef.current += 1;
|
|
120
|
+
const animationAttempt = animationAttemptRef.current;
|
|
121
|
+
stopAnimations();
|
|
122
|
+
activeScheduleRef.current = schedule;
|
|
123
|
+
setIsAnimating(true);
|
|
124
|
+
const startAnimations = () => {
|
|
125
|
+
if (animationAttemptRef.current !== animationAttempt) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
pendingStartRef.current = undefined;
|
|
129
|
+
const liveFrame = {};
|
|
130
|
+
let remainingSamples = FRAME_PROPERTIES.length;
|
|
131
|
+
for (const property of FRAME_PROPERTIES) {
|
|
132
|
+
animatedValues[property].stopAnimation((value) => {
|
|
133
|
+
var _a, _b;
|
|
134
|
+
if (animationAttemptRef.current !== animationAttempt) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
liveFrame[property] = value;
|
|
138
|
+
remainingSamples -= 1;
|
|
139
|
+
if (remainingSamples !== 0) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const currentExecution = resolveWidgetLayoutTransitionExecution({
|
|
143
|
+
durationMs: transitionPolicy.durationMs,
|
|
144
|
+
nowEpochMs: Date.now(),
|
|
145
|
+
schedule,
|
|
146
|
+
});
|
|
147
|
+
if (currentExecution.isComplete) {
|
|
148
|
+
presentedStylesRef.current = currentInput.styles;
|
|
149
|
+
for (const frameProperty of FRAME_PROPERTIES) {
|
|
150
|
+
animatedValues[frameProperty].setValue(frame[frameProperty]);
|
|
151
|
+
}
|
|
152
|
+
activeScheduleRef.current = undefined;
|
|
153
|
+
setIsAnimating(false);
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const elapsedProgress = Math.min(1, currentExecution.elapsedMs / transitionPolicy.durationMs);
|
|
157
|
+
const resumedEasing = currentExecution.elapsedMs > 0
|
|
158
|
+
? resolveWidgetLayoutResumedEasing({
|
|
159
|
+
easing: transitionPolicy.easing,
|
|
160
|
+
elapsedProgress,
|
|
161
|
+
remainingProgress: 0,
|
|
162
|
+
})
|
|
163
|
+
: undefined;
|
|
164
|
+
if (resumedEasing) {
|
|
165
|
+
for (const frameProperty of FRAME_PROPERTIES) {
|
|
166
|
+
animatedValues[frameProperty].setValue(liveFrame[frameProperty] +
|
|
167
|
+
(frame[frameProperty] - liveFrame[frameProperty]) *
|
|
168
|
+
resumedEasing.initialProgress);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const easing = resumedEasing
|
|
172
|
+
? (remainingProgress) => resolveWidgetLayoutResumedEasing({
|
|
173
|
+
easing: transitionPolicy.easing,
|
|
174
|
+
elapsedProgress,
|
|
175
|
+
remainingProgress,
|
|
176
|
+
}).progress
|
|
177
|
+
: (_b = (_a = animationRuntime.Easing) === null || _a === void 0 ? void 0 : _a.bezier) === null || _b === void 0 ? void 0 : _b.call(_a, ...transitionPolicy.easing);
|
|
178
|
+
let remainingAnimations = FRAME_PROPERTIES.length;
|
|
179
|
+
let allAnimationsFinished = true;
|
|
180
|
+
const targetStyles = currentInput.styles;
|
|
181
|
+
const animations = FRAME_PROPERTIES.map((frameProperty) => animationRuntime.Animated.timing(animatedValues[frameProperty], {
|
|
182
|
+
duration: currentExecution.durationMs,
|
|
183
|
+
easing,
|
|
184
|
+
toValue: frame[frameProperty],
|
|
185
|
+
useNativeDriver: false,
|
|
186
|
+
}));
|
|
187
|
+
activeAnimationsRef.current = animations;
|
|
188
|
+
for (const animation of animations) {
|
|
189
|
+
animation.start((result) => {
|
|
190
|
+
if (animationAttemptRef.current !== animationAttempt) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
allAnimationsFinished && (allAnimationsFinished = (result === null || result === void 0 ? void 0 : result.finished) !== false);
|
|
194
|
+
remainingAnimations -= 1;
|
|
195
|
+
if (remainingAnimations !== 0) {
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
activeAnimationsRef.current = [];
|
|
199
|
+
activeScheduleRef.current = undefined;
|
|
200
|
+
if (allAnimationsFinished) {
|
|
201
|
+
presentedStylesRef.current = targetStyles;
|
|
202
|
+
}
|
|
203
|
+
setIsAnimating(false);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
// RN's timing delay starts a fresh duration after its timer fires. Own the
|
|
210
|
+
// wait so a late JS startup samples and resumes the original schedule.
|
|
211
|
+
if (execution.delayMs > 0) {
|
|
212
|
+
pendingStartRef.current = setTimeout(startAnimations, execution.delayMs);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
startAnimations();
|
|
216
|
+
}
|
|
217
|
+
}, [animationRuntime, input.revision, hasFrame]);
|
|
218
|
+
useLayoutEffect(() => () => {
|
|
219
|
+
var _a;
|
|
220
|
+
animationAttemptRef.current += 1;
|
|
221
|
+
clearTimeout(pendingStartRef.current);
|
|
222
|
+
pendingStartRef.current = undefined;
|
|
223
|
+
for (const animation of activeAnimationsRef.current) {
|
|
224
|
+
(_a = animation.stop) === null || _a === void 0 ? void 0 : _a.call(animation);
|
|
225
|
+
}
|
|
226
|
+
activeAnimationsRef.current = [];
|
|
227
|
+
}, []);
|
|
228
|
+
const animatedValues = animatedValuesRef.current;
|
|
229
|
+
if (!shouldUseAnimatedStyles || animatedValues === null) {
|
|
230
|
+
return Object.assign(Object.assign({}, input.styles), { frameView: animatedFrameView });
|
|
231
|
+
}
|
|
232
|
+
const radiusStyles = {
|
|
233
|
+
borderBottomLeftRadius: animatedValues.bottomLeftRadius,
|
|
234
|
+
borderBottomRightRadius: animatedValues.bottomRightRadius,
|
|
235
|
+
borderTopLeftRadius: animatedValues.topLeftRadius,
|
|
236
|
+
borderTopRightRadius: animatedValues.topRightRadius,
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
border: Object.assign(Object.assign({}, presentedStylesRef.current.border), radiusStyles),
|
|
240
|
+
content: Object.assign(Object.assign({}, presentedStylesRef.current.content), radiusStyles),
|
|
241
|
+
frame: Object.assign(Object.assign(Object.assign({}, presentedStylesRef.current.frame), radiusStyles), { height: animatedValues.height, width: animatedValues.width }),
|
|
242
|
+
frameView: animatedFrameView,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import type { WidgetLayoutTransitionSchedule } from "@getuserfeedback/protocol/host";
|
|
1
2
|
import { type ComponentType, type ReactElement, type ReactNode } from "react";
|
|
2
3
|
import type { NativeViewRecord } from "./native-view-record-controller.js";
|
|
3
4
|
export type NativeViewPresentationMode = "content" | "overlay";
|
|
4
|
-
export declare function NativeViewPresentationSurface({ children, presentationMode, nativeViewComponent, record, }: {
|
|
5
|
+
export declare function NativeViewPresentationSurface({ children, presentationMode, nativeViewComponent, record, transitionSchedule, }: {
|
|
5
6
|
children?: ReactNode;
|
|
6
7
|
presentationMode?: NativeViewPresentationMode;
|
|
7
8
|
nativeViewComponent?: ComponentType<Record<string, unknown>>;
|
|
8
9
|
record: NativeViewRecord;
|
|
10
|
+
transitionSchedule?: WidgetLayoutTransitionSchedule;
|
|
9
11
|
}): ReactElement;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createElement, } from "react";
|
|
2
|
+
import { useNativeViewFrameTransition, } from "./native-view-layout-transition.js";
|
|
2
3
|
import { toNativeWidgetCornerRadii } from "./native-widget-corner-radii.js";
|
|
3
4
|
import { toNativeWidgetResolvedBorderStyle } from "./native-widget-frame-border.js";
|
|
4
5
|
import { toNativeWidgetFrameShadow, toNativeWidgetSupportedBoxShadow, } from "./native-widget-frame-shadow.js";
|
|
@@ -55,16 +56,20 @@ const resolveNativeView = () => {
|
|
|
55
56
|
}
|
|
56
57
|
return reactNativeModule.View;
|
|
57
58
|
};
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// TODO(architecture): Land native-host dismissal and controller dismissal in the same vertical slice.
|
|
60
|
+
// [principle=host-adapter-boundary] [scope=slice]
|
|
61
|
+
// [epic=runtime-neutral-view-host-orchestration] [epic_order=3] [priority=medium]
|
|
62
|
+
export function NativeViewPresentationSurface({ children, presentationMode = "overlay", nativeViewComponent, record, transitionSchedule, }) {
|
|
63
|
+
var _a;
|
|
60
64
|
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
61
65
|
const isPresented = record.isActive && record.size !== null;
|
|
62
66
|
const hostStyle = presentationMode === "content" && isPresented
|
|
63
|
-
?
|
|
64
|
-
?
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
? CONTENT_HOST_SURFACE_STYLE
|
|
68
|
+
: Object.assign(Object.assign(Object.assign({}, HOST_SURFACE_STYLE), (((_a = record.size) === null || _a === void 0 ? void 0 : _a.placement) === "docked-top"
|
|
69
|
+
? TOP_PLACEMENT_STYLE
|
|
70
|
+
: BOTTOM_PLACEMENT_STYLE)), (isPresented
|
|
71
|
+
? PRESENTED_HOST_SURFACE_STYLE
|
|
72
|
+
: HIDDEN_HOST_SURFACE_STYLE));
|
|
68
73
|
return createElement(NativeView, {
|
|
69
74
|
accessibilityElementsHidden: !isPresented,
|
|
70
75
|
importantForAccessibility: isPresented ? "auto" : "no-hide-descendants",
|
|
@@ -75,10 +80,11 @@ export function NativeViewPresentationSurface({ children, presentationMode = "ov
|
|
|
75
80
|
children,
|
|
76
81
|
nativeViewComponent,
|
|
77
82
|
record,
|
|
83
|
+
transitionSchedule,
|
|
78
84
|
}));
|
|
79
85
|
}
|
|
80
|
-
function NativeViewPresentationFrame({ children, nativeViewComponent, record, }) {
|
|
81
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
86
|
+
function NativeViewPresentationFrame({ children, nativeViewComponent, record, transitionSchedule, }) {
|
|
87
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
82
88
|
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
83
89
|
const isPresented = record.isActive && record.size !== null;
|
|
84
90
|
const hasMeasuredFrame = isPresented || ((_a = record.size) === null || _a === void 0 ? void 0 : _a.layoutTransactionId) !== undefined;
|
|
@@ -100,17 +106,43 @@ function NativeViewPresentationFrame({ children, nativeViewComponent, record, })
|
|
|
100
106
|
borderTopLeftRadius: cornerRadii.topLeft,
|
|
101
107
|
borderTopRightRadius: cornerRadii.topRight,
|
|
102
108
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
109
|
+
const targetStyles = {
|
|
110
|
+
border: Object.assign(Object.assign(Object.assign(Object.assign({}, VIEW_FRAME_BORDER_OVERLAY_STYLE), cornerRadiusStyle), borderStyle), frameOverlayShadowStyle),
|
|
111
|
+
content: Object.assign(Object.assign({}, VIEW_FRAME_CONTENT_STYLE), cornerRadiusStyle),
|
|
112
|
+
frame: hasMeasuredFrame
|
|
106
113
|
? Object.assign(Object.assign(Object.assign(Object.assign({}, MEASURED_VIEW_FRAME_STYLE), cornerRadiusStyle), frameShadowStyle), { height: (_g = record.size) === null || _g === void 0 ? void 0 : _g.height, width: (_h = record.size) === null || _h === void 0 ? void 0 : _h.width }) : PREPARING_VIEW_FRAME_STYLE,
|
|
114
|
+
};
|
|
115
|
+
const frame = hasMeasuredFrame && record.size
|
|
116
|
+
? {
|
|
117
|
+
bottomLeftRadius: cornerRadii.bottomLeft,
|
|
118
|
+
bottomRightRadius: cornerRadii.bottomRight,
|
|
119
|
+
height: record.size.height,
|
|
120
|
+
topLeftRadius: cornerRadii.topLeft,
|
|
121
|
+
topRightRadius: cornerRadii.topRight,
|
|
122
|
+
width: record.size.width,
|
|
123
|
+
}
|
|
124
|
+
: null;
|
|
125
|
+
const transition = useNativeViewFrameTransition({
|
|
126
|
+
frame,
|
|
127
|
+
nativeView: NativeView,
|
|
128
|
+
revision: record.layoutReportRevision,
|
|
129
|
+
schedule: transitionSchedule,
|
|
130
|
+
styles: targetStyles,
|
|
131
|
+
transitionPolicy: ((_k = (_j = record.size) === null || _j === void 0 ? void 0 : _j.transitionPolicy) === null || _k === void 0 ? void 0 : _k.kind) === "timed"
|
|
132
|
+
? record.size.transitionPolicy
|
|
133
|
+
: undefined,
|
|
134
|
+
});
|
|
135
|
+
const FrameView = transition.frameView;
|
|
136
|
+
return createElement(FrameView, {
|
|
137
|
+
pointerEvents: isPresented ? "auto" : "none",
|
|
138
|
+
style: transition.frame,
|
|
107
139
|
testID: `gx-native-view-frame-${record.viewId}`,
|
|
108
|
-
}, createElement(
|
|
109
|
-
style:
|
|
140
|
+
}, createElement(FrameView, {
|
|
141
|
+
style: transition.content,
|
|
110
142
|
testID: `gx-native-view-frame-content-${record.viewId}`,
|
|
111
|
-
}, children), createElement(
|
|
143
|
+
}, children), createElement(FrameView, {
|
|
112
144
|
pointerEvents: "none",
|
|
113
|
-
style:
|
|
145
|
+
style: transition.border,
|
|
114
146
|
testID: `gx-native-view-frame-border-${record.viewId}`,
|
|
115
147
|
}));
|
|
116
148
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
import { type WidgetLayoutTransitionSchedule } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import type { BrowserNavigationAvailabilityPayload } from "@getuserfeedback/protocol/internal/navigation-rpc";
|
|
1
3
|
import { type EventViewSize } from "@getuserfeedback/protocol/internal/view-host-control-messages";
|
|
2
4
|
import type { OpenRequestDispatcher } from "@getuserfeedback/sdk/internal/open-request-dispatcher";
|
|
3
5
|
import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
|
|
4
6
|
import type { ViewWebViewHostConnection } from "./view-webview-host-controller.js";
|
|
5
|
-
type NativeViewSizeReport = Omit<EventViewSize, "gen" | "t" | "viewId"
|
|
7
|
+
type NativeViewSizeReport = Omit<EventViewSize, "gen" | "t" | "viewId"> & {
|
|
8
|
+
transitionSchedule?: WidgetLayoutTransitionSchedule;
|
|
9
|
+
};
|
|
6
10
|
export type NativeViewRecord = Readonly<{
|
|
7
11
|
allocationId: number;
|
|
12
|
+
flowId: string | null;
|
|
8
13
|
generation: number;
|
|
9
14
|
isActive: boolean;
|
|
10
15
|
layoutReportRevision: number;
|
|
@@ -39,6 +44,7 @@ export type NativeViewRecordController = {
|
|
|
39
44
|
type NativeOpenRequestObserver = (payload: Parameters<OpenRequestDispatcher["dispatch"]>[0]) => ReturnType<OpenRequestDispatcher["dispatch"]>;
|
|
40
45
|
export declare function createNativeViewRecordController(input: {
|
|
41
46
|
coreConnection: CoreWebViewHostConnection;
|
|
47
|
+
getBrowserNavigationAvailability?: () => BrowserNavigationAvailabilityPayload | undefined;
|
|
42
48
|
onError: (error: Error) => void;
|
|
43
49
|
observeOpenRequest?: NativeOpenRequestObserver;
|
|
44
50
|
}): NativeViewRecordController;
|