@getuserfeedback/react-native 3.0.80 → 3.0.90
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/dist/index.js +29 -89
- package/dist/native-direct-command-session-controller.d.ts +9 -0
- package/dist/native-direct-command-session-controller.js +78 -0
- package/dist/native-direct-command-session.js +2 -2
- package/dist/native-provider-client.d.ts +64 -0
- package/dist/native-provider-client.js +76 -0
- package/dist/native-runtime-root.d.ts +5 -3
- package/dist/native-runtime-root.js +31 -7
- package/dist/native-view-host-projection.d.ts +2 -1
- package/dist/native-view-host-projection.js +13 -9
- package/dist/native-view-presentation-surface.d.ts +7 -0
- package/dist/native-view-presentation-surface.js +82 -0
- package/dist/native-view-record-controller.d.ts +2 -0
- package/dist/native-view-record-controller.js +58 -8
- package/dist/native-widget-corner-radii.d.ts +10 -0
- package/dist/native-widget-corner-radii.js +54 -0
- package/dist/native-widget-frame-border.d.ts +7 -0
- package/dist/native-widget-frame-border.js +21 -0
- package/dist/native-widget-frame-shadow.d.ts +30 -0
- package/dist/native-widget-frame-shadow.js +49 -0
- package/dist/provider-widget-border-radius.js +5 -18
- package/dist/provider-widget-frame-appearance.d.ts +5 -34
- package/dist/provider-widget-frame-appearance.js +10 -69
- package/dist/provider-widget-host-overlay.d.ts +2 -1
- package/dist/provider-widget-host-overlay.js +7 -6
- package/dist/use-native-direct-command-session-controller.d.ts +2 -0
- package/dist/use-native-direct-command-session-controller.js +21 -0
- package/dist/version.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { createElement, } from "react";
|
|
2
|
+
import { toNativeWidgetCornerRadii } from "./native-widget-corner-radii.js";
|
|
3
|
+
import { toNativeWidgetResolvedBorderStyle } from "./native-widget-frame-border.js";
|
|
4
|
+
const HOST_SURFACE_STYLE = {
|
|
5
|
+
alignItems: "center",
|
|
6
|
+
bottom: 0,
|
|
7
|
+
justifyContent: "flex-end",
|
|
8
|
+
left: 0,
|
|
9
|
+
position: "absolute",
|
|
10
|
+
right: 0,
|
|
11
|
+
top: 0,
|
|
12
|
+
};
|
|
13
|
+
const HIDDEN_HOST_SURFACE_STYLE = {
|
|
14
|
+
opacity: 0,
|
|
15
|
+
};
|
|
16
|
+
const PRESENTED_HOST_SURFACE_STYLE = {
|
|
17
|
+
zIndex: 2147483647,
|
|
18
|
+
};
|
|
19
|
+
const TOP_PLACEMENT_STYLE = {
|
|
20
|
+
justifyContent: "flex-start",
|
|
21
|
+
};
|
|
22
|
+
const BOTTOM_PLACEMENT_STYLE = {
|
|
23
|
+
justifyContent: "flex-end",
|
|
24
|
+
};
|
|
25
|
+
const PREPARING_VIEW_FRAME_STYLE = {
|
|
26
|
+
height: "100%",
|
|
27
|
+
width: "100%",
|
|
28
|
+
};
|
|
29
|
+
const PRESENTED_VIEW_FRAME_STYLE = {
|
|
30
|
+
maxHeight: "100%",
|
|
31
|
+
maxWidth: "100%",
|
|
32
|
+
overflow: "hidden",
|
|
33
|
+
};
|
|
34
|
+
const VIEW_FRAME_BORDER_OVERLAY_STYLE = {
|
|
35
|
+
bottom: 0,
|
|
36
|
+
left: 0,
|
|
37
|
+
position: "absolute",
|
|
38
|
+
right: 0,
|
|
39
|
+
top: 0,
|
|
40
|
+
};
|
|
41
|
+
const resolveNativeView = () => {
|
|
42
|
+
const reactNativeModule = require("react-native");
|
|
43
|
+
if (!reactNativeModule.View) {
|
|
44
|
+
throw new Error("react-native must export View to present native views");
|
|
45
|
+
}
|
|
46
|
+
return reactNativeModule.View;
|
|
47
|
+
};
|
|
48
|
+
// TODO(migration): Project shadow appearance onto Core-owned native frames before layering and scoped dismissal.
|
|
49
|
+
// [from=native-frame-border-shadow-projection] [to=native-frame-shadow-projection] [scope=slice] [principle=core-owned-flow-lifecycle] [priority=high] [impact=high] [risk=high] [epic=runtime-neutral-view-host-orchestration] [epic_order=189]
|
|
50
|
+
export function NativeViewPresentationSurface({ children, nativeViewComponent, record, }) {
|
|
51
|
+
var _a, _b, _c, _d, _e, _f;
|
|
52
|
+
const NativeView = nativeViewComponent !== null && nativeViewComponent !== void 0 ? nativeViewComponent : resolveNativeView();
|
|
53
|
+
const isPresented = record.activationState === "active" && record.size !== null;
|
|
54
|
+
const cornerRadii = toNativeWidgetCornerRadii((_a = record.size) === null || _a === void 0 ? void 0 : _a.frameAppearance);
|
|
55
|
+
const borderStyle = toNativeWidgetResolvedBorderStyle(((_c = (_b = record.size) === null || _b === void 0 ? void 0 : _b.frameAppearance) === null || _c === void 0 ? void 0 : _c.kind) === "page"
|
|
56
|
+
? record.size.frameAppearance.resolvedBorder
|
|
57
|
+
: undefined);
|
|
58
|
+
const cornerRadiusStyle = {
|
|
59
|
+
borderBottomLeftRadius: cornerRadii.bottomLeft,
|
|
60
|
+
borderBottomRightRadius: cornerRadii.bottomRight,
|
|
61
|
+
borderTopLeftRadius: cornerRadii.topLeft,
|
|
62
|
+
borderTopRightRadius: cornerRadii.topRight,
|
|
63
|
+
};
|
|
64
|
+
return createElement(NativeView, {
|
|
65
|
+
pointerEvents: isPresented ? "box-none" : "none",
|
|
66
|
+
style: Object.assign(Object.assign(Object.assign({}, HOST_SURFACE_STYLE), (((_d = record.size) === null || _d === void 0 ? void 0 : _d.placement) === "docked-top"
|
|
67
|
+
? TOP_PLACEMENT_STYLE
|
|
68
|
+
: BOTTOM_PLACEMENT_STYLE)), (isPresented
|
|
69
|
+
? PRESENTED_HOST_SURFACE_STYLE
|
|
70
|
+
: HIDDEN_HOST_SURFACE_STYLE)),
|
|
71
|
+
testID: `gx-native-view-presentation-${record.viewId}`,
|
|
72
|
+
}, createElement(NativeView, {
|
|
73
|
+
pointerEvents: isPresented ? "auto" : "none",
|
|
74
|
+
style: isPresented
|
|
75
|
+
? Object.assign(Object.assign(Object.assign({}, PRESENTED_VIEW_FRAME_STYLE), cornerRadiusStyle), { height: (_e = record.size) === null || _e === void 0 ? void 0 : _e.height, width: (_f = record.size) === null || _f === void 0 ? void 0 : _f.width }) : PREPARING_VIEW_FRAME_STYLE,
|
|
76
|
+
testID: `gx-native-view-frame-${record.viewId}`,
|
|
77
|
+
}, children, createElement(NativeView, {
|
|
78
|
+
pointerEvents: "none",
|
|
79
|
+
style: Object.assign(Object.assign(Object.assign({}, VIEW_FRAME_BORDER_OVERLAY_STYLE), cornerRadiusStyle), borderStyle),
|
|
80
|
+
testID: `gx-native-view-frame-border-${record.viewId}`,
|
|
81
|
+
})));
|
|
82
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type OpenRequestMetadata } from "@getuserfeedback/protocol/internal/core-view-host-actions";
|
|
1
2
|
import { type EventViewSize } from "@getuserfeedback/protocol/internal/view-host-control-messages";
|
|
2
3
|
import type { CoreWebViewHostConnection } from "./core-webview-host-controller.js";
|
|
3
4
|
import type { ViewWebViewHostConnection } from "./view-webview-host-controller.js";
|
|
@@ -8,6 +9,7 @@ export type NativeViewRecord = Readonly<{
|
|
|
8
9
|
activationState: NativeViewActivationState;
|
|
9
10
|
generation: number;
|
|
10
11
|
instanceId: string;
|
|
12
|
+
openRequest: Readonly<OpenRequestMetadata> | undefined;
|
|
11
13
|
size: NativeViewSizeReport | null;
|
|
12
14
|
srcdoc: string;
|
|
13
15
|
viewId: string;
|
|
@@ -28,21 +28,26 @@ export function createNativeViewRecordController(input) {
|
|
|
28
28
|
listener();
|
|
29
29
|
}
|
|
30
30
|
};
|
|
31
|
-
const ensurePendingActivation = (viewId) => {
|
|
31
|
+
const ensurePendingActivation = (viewId, openRequest) => {
|
|
32
32
|
const existing = pendingActivations.get(viewId);
|
|
33
33
|
if (existing) {
|
|
34
|
+
existing.openRequest = openRequest;
|
|
34
35
|
return existing;
|
|
35
36
|
}
|
|
36
|
-
const created =
|
|
37
|
+
const created = {
|
|
38
|
+
activation: createViewActivationController(),
|
|
39
|
+
openRequest,
|
|
40
|
+
};
|
|
37
41
|
pendingActivations.set(viewId, created);
|
|
38
42
|
return created;
|
|
39
43
|
};
|
|
40
44
|
const removeEntry = (entry) => {
|
|
41
|
-
var _a;
|
|
42
45
|
if (entries.get(entry.viewId) !== entry) {
|
|
43
46
|
return false;
|
|
44
47
|
}
|
|
45
|
-
|
|
48
|
+
const detachViewReadiness = entry.viewReadinessDetach;
|
|
49
|
+
entry.viewReadinessDetach = null;
|
|
50
|
+
detachViewReadiness === null || detachViewReadiness === void 0 ? void 0 : detachViewReadiness();
|
|
46
51
|
entries.delete(entry.viewId);
|
|
47
52
|
if (!disposed) {
|
|
48
53
|
notify();
|
|
@@ -58,7 +63,8 @@ export function createNativeViewRecordController(input) {
|
|
|
58
63
|
if (!preparation) {
|
|
59
64
|
return;
|
|
60
65
|
}
|
|
61
|
-
const
|
|
66
|
+
const pendingActivation = pendingActivations.get(action.viewId);
|
|
67
|
+
const activation = (_a = pendingActivation === null || pendingActivation === void 0 ? void 0 : pendingActivation.activation) !== null && _a !== void 0 ? _a : createViewActivationController();
|
|
62
68
|
pendingActivations.delete(action.viewId);
|
|
63
69
|
activation.startPrerender();
|
|
64
70
|
nextAllocationId += 1;
|
|
@@ -67,6 +73,9 @@ export function createNativeViewRecordController(input) {
|
|
|
67
73
|
allocationId: nextAllocationId,
|
|
68
74
|
generation: action.gen,
|
|
69
75
|
instanceId: action.instanceId,
|
|
76
|
+
openRequest: pendingActivation
|
|
77
|
+
? pendingActivation.openRequest
|
|
78
|
+
: toOpenRequestMetadata(action.openRequest),
|
|
70
79
|
preparation,
|
|
71
80
|
preparationState: "claimed",
|
|
72
81
|
size: null,
|
|
@@ -81,12 +90,19 @@ export function createNativeViewRecordController(input) {
|
|
|
81
90
|
notify();
|
|
82
91
|
};
|
|
83
92
|
const handleActivate = (action) => {
|
|
84
|
-
var _a;
|
|
85
93
|
const entry = entries.get(action.viewId);
|
|
86
|
-
const
|
|
94
|
+
const openRequest = toOpenRequestMetadata(action.openRequest);
|
|
95
|
+
const activation = entry
|
|
96
|
+
? entry.activation
|
|
97
|
+
: ensurePendingActivation(action.viewId, openRequest).activation;
|
|
87
98
|
const previousState = activation.getState();
|
|
88
99
|
activation.requestActivation();
|
|
89
|
-
if (entry
|
|
100
|
+
if (!entry) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const openRequestChanged = !areOpenRequestsEqual(entry.openRequest, openRequest);
|
|
104
|
+
entry.openRequest = openRequest;
|
|
105
|
+
if (activation.getState() !== previousState || openRequestChanged) {
|
|
90
106
|
notify();
|
|
91
107
|
}
|
|
92
108
|
};
|
|
@@ -187,6 +203,12 @@ export function createNativeViewRecordController(input) {
|
|
|
187
203
|
connectionReadiness.release();
|
|
188
204
|
if (entry.viewReadinessDetach === detach) {
|
|
189
205
|
entry.viewReadinessDetach = null;
|
|
206
|
+
if (entry.size !== null) {
|
|
207
|
+
entry.size = null;
|
|
208
|
+
if (!disposed && entries.get(viewId) === entry) {
|
|
209
|
+
notify();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
190
212
|
}
|
|
191
213
|
};
|
|
192
214
|
const attemptReadinessDelivery = (isRetry = false) => {
|
|
@@ -281,6 +303,10 @@ export function createNativeViewRecordController(input) {
|
|
|
281
303
|
}
|
|
282
304
|
entry.viewReadinessDetach = detach;
|
|
283
305
|
previousDetach === null || previousDetach === void 0 ? void 0 : previousDetach();
|
|
306
|
+
if (entry.size !== null) {
|
|
307
|
+
entry.size = null;
|
|
308
|
+
notify();
|
|
309
|
+
}
|
|
284
310
|
subscribed = true;
|
|
285
311
|
for (const message of bufferedMessages) {
|
|
286
312
|
handleMessage(message);
|
|
@@ -332,11 +358,35 @@ function toRecord(entry) {
|
|
|
332
358
|
allocationId: entry.allocationId,
|
|
333
359
|
generation: entry.generation,
|
|
334
360
|
instanceId: entry.instanceId,
|
|
361
|
+
openRequest: entry.openRequest,
|
|
335
362
|
size: entry.size,
|
|
336
363
|
srcdoc: entry.srcdoc,
|
|
337
364
|
viewId: entry.viewId,
|
|
338
365
|
});
|
|
339
366
|
}
|
|
367
|
+
function toOpenRequestMetadata(openRequest) {
|
|
368
|
+
return openRequest === undefined
|
|
369
|
+
? undefined
|
|
370
|
+
: Object.freeze(Object.assign({}, openRequest));
|
|
371
|
+
}
|
|
372
|
+
function areOpenRequestsEqual(left, right) {
|
|
373
|
+
if (left === right) {
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
if (!left || !right || left.source !== right.source) {
|
|
377
|
+
return false;
|
|
378
|
+
}
|
|
379
|
+
if (left.source === "targeting" && right.source === "targeting") {
|
|
380
|
+
return left.flowId === right.flowId;
|
|
381
|
+
}
|
|
382
|
+
if (left.source !== "command" || right.source !== "command") {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return (left.flowId === right.flowId &&
|
|
386
|
+
left.requestId === right.requestId &&
|
|
387
|
+
left.flowHandleId === right.flowHandleId &&
|
|
388
|
+
left.hideCloseButton === right.hideCloseButton);
|
|
389
|
+
}
|
|
340
390
|
function toViewSizeReport(event) {
|
|
341
391
|
return {
|
|
342
392
|
width: event.width,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WidgetFrameAppearance, WidgetFrameResolvedBorderRadius } from "@getuserfeedback/protocol/host";
|
|
2
|
+
export type NativeWidgetCornerRadii = {
|
|
3
|
+
bottomLeft: number;
|
|
4
|
+
bottomRight: number;
|
|
5
|
+
topLeft: number;
|
|
6
|
+
topRight: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function safeParseNativeWidgetCanonicalRadii(value: string): NativeWidgetCornerRadii | undefined;
|
|
9
|
+
export declare function toNativeWidgetConservativeResolvedCornerRadii(resolved: WidgetFrameResolvedBorderRadius): NativeWidgetCornerRadii;
|
|
10
|
+
export declare function toNativeWidgetCornerRadii(appearance?: WidgetFrameAppearance): NativeWidgetCornerRadii;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const CLEARED_NATIVE_WIDGET_CORNER_RADII = {
|
|
2
|
+
bottomLeft: 0,
|
|
3
|
+
bottomRight: 0,
|
|
4
|
+
topLeft: 0,
|
|
5
|
+
topRight: 0,
|
|
6
|
+
};
|
|
7
|
+
const CANONICAL_PX_PATTERN = /^(?:0|[1-9]\d*)(?:\.\d+)?px$/;
|
|
8
|
+
const safeParseCanonicalPx = (value) => {
|
|
9
|
+
if (!CANONICAL_PX_PATTERN.test(value)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const parsed = Number.parseFloat(value);
|
|
13
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
14
|
+
};
|
|
15
|
+
export function safeParseNativeWidgetCanonicalRadii(value) {
|
|
16
|
+
if (value.includes("/")) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const radii = value.trim().split(/\s+/).map(safeParseCanonicalPx);
|
|
20
|
+
if (radii.length === 0 ||
|
|
21
|
+
radii.length > 4 ||
|
|
22
|
+
radii.some((radius) => radius === undefined)) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const [topLeft, topRight = topLeft, bottomRight = topLeft, bottomLeft = topRight,] = radii;
|
|
26
|
+
if (topLeft === undefined ||
|
|
27
|
+
topRight === undefined ||
|
|
28
|
+
bottomRight === undefined ||
|
|
29
|
+
bottomLeft === undefined) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
return { bottomLeft, bottomRight, topLeft, topRight };
|
|
33
|
+
}
|
|
34
|
+
export function toNativeWidgetConservativeResolvedCornerRadii(resolved) {
|
|
35
|
+
// React Native exposes one scalar per corner, so use a smaller circle that
|
|
36
|
+
// does not exceed the authored ellipse on either axis.
|
|
37
|
+
return {
|
|
38
|
+
bottomLeft: Math.min(resolved.bottomLeft.horizontalPx, resolved.bottomLeft.verticalPx),
|
|
39
|
+
bottomRight: Math.min(resolved.bottomRight.horizontalPx, resolved.bottomRight.verticalPx),
|
|
40
|
+
topLeft: Math.min(resolved.topLeft.horizontalPx, resolved.topLeft.verticalPx),
|
|
41
|
+
topRight: Math.min(resolved.topRight.horizontalPx, resolved.topRight.verticalPx),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export function toNativeWidgetCornerRadii(appearance) {
|
|
45
|
+
var _a;
|
|
46
|
+
if (appearance === undefined || appearance.kind === "none") {
|
|
47
|
+
return Object.assign({}, CLEARED_NATIVE_WIDGET_CORNER_RADII);
|
|
48
|
+
}
|
|
49
|
+
const resolved = appearance.resolvedBorderRadius;
|
|
50
|
+
if (resolved !== undefined) {
|
|
51
|
+
return toNativeWidgetConservativeResolvedCornerRadii(resolved);
|
|
52
|
+
}
|
|
53
|
+
return ((_a = safeParseNativeWidgetCanonicalRadii(appearance.borderRadius)) !== null && _a !== void 0 ? _a : Object.assign({}, CLEARED_NATIVE_WIDGET_CORNER_RADII));
|
|
54
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { WidgetFrameResolvedBorder } from "@getuserfeedback/protocol/host";
|
|
2
|
+
export type NativeWidgetFrameBorderStyle = {
|
|
3
|
+
borderColor: string;
|
|
4
|
+
borderStyle: "dashed" | "dotted" | "solid";
|
|
5
|
+
borderWidth: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function toNativeWidgetResolvedBorderStyle(border?: WidgetFrameResolvedBorder): NativeWidgetFrameBorderStyle;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const CLEARED_NATIVE_WIDGET_FRAME_BORDER_STYLE = {
|
|
2
|
+
borderColor: "transparent",
|
|
3
|
+
borderStyle: "solid",
|
|
4
|
+
borderWidth: 0,
|
|
5
|
+
};
|
|
6
|
+
export function toNativeWidgetResolvedBorderStyle(border) {
|
|
7
|
+
if (border === undefined ||
|
|
8
|
+
border.widthPx === 0 ||
|
|
9
|
+
border.style === "none" ||
|
|
10
|
+
border.style === "hidden") {
|
|
11
|
+
return Object.assign({}, CLEARED_NATIVE_WIDGET_FRAME_BORDER_STYLE);
|
|
12
|
+
}
|
|
13
|
+
const { alpha, blue, green, red } = border.color;
|
|
14
|
+
return {
|
|
15
|
+
borderColor: `rgba(${red}, ${green}, ${blue}, ${alpha})`,
|
|
16
|
+
borderStyle: border.style === "dashed" || border.style === "dotted"
|
|
17
|
+
? border.style
|
|
18
|
+
: "solid",
|
|
19
|
+
borderWidth: border.widthPx,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { WidgetFrameResolvedBoxShadowLayer } from "@getuserfeedback/protocol/host";
|
|
2
|
+
export type NativeWidgetFrameBoxShadowValue = {
|
|
3
|
+
blurRadius: number;
|
|
4
|
+
color: string;
|
|
5
|
+
inset: boolean;
|
|
6
|
+
offsetX: number;
|
|
7
|
+
offsetY: number;
|
|
8
|
+
spreadDistance: number;
|
|
9
|
+
};
|
|
10
|
+
type NativeWidgetFrameLegacyShadowStyle = {
|
|
11
|
+
elevation: number;
|
|
12
|
+
shadowColor: string;
|
|
13
|
+
shadowOffset: {
|
|
14
|
+
height: number;
|
|
15
|
+
width: number;
|
|
16
|
+
};
|
|
17
|
+
shadowOpacity: number;
|
|
18
|
+
shadowRadius: number;
|
|
19
|
+
};
|
|
20
|
+
export type NativeWidgetFrameShadowProjection = {
|
|
21
|
+
kind: "clear";
|
|
22
|
+
} | {
|
|
23
|
+
boxShadow: NativeWidgetFrameBoxShadowValue[];
|
|
24
|
+
fallbackStyle: NativeWidgetFrameLegacyShadowStyle;
|
|
25
|
+
kind: "resolved";
|
|
26
|
+
};
|
|
27
|
+
export type NativeWidgetBoxShadowSupport = "all" | "none" | "outset";
|
|
28
|
+
export declare function toNativeWidgetFrameShadow(layers?: WidgetFrameResolvedBoxShadowLayer[]): NativeWidgetFrameShadowProjection;
|
|
29
|
+
export declare function toNativeWidgetSupportedBoxShadow(shadow: NativeWidgetFrameShadowProjection, support?: NativeWidgetBoxShadowSupport): NativeWidgetFrameBoxShadowValue[] | undefined;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const CLEARED_NATIVE_WIDGET_FRAME_SHADOW_STYLE = {
|
|
2
|
+
elevation: 0,
|
|
3
|
+
shadowColor: "transparent",
|
|
4
|
+
shadowOffset: { height: 0, width: 0 },
|
|
5
|
+
shadowOpacity: 0,
|
|
6
|
+
shadowRadius: 0,
|
|
7
|
+
};
|
|
8
|
+
const toNativeWidgetFrameColor = (color) => `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha})`;
|
|
9
|
+
const toNativeWidgetFrameBoxShadow = (layers) => layers.map((layer) => ({
|
|
10
|
+
blurRadius: layer.blurRadiusPx,
|
|
11
|
+
color: toNativeWidgetFrameColor(layer.color),
|
|
12
|
+
inset: layer.inset,
|
|
13
|
+
offsetX: layer.offsetXPx,
|
|
14
|
+
offsetY: layer.offsetYPx,
|
|
15
|
+
spreadDistance: layer.spreadRadiusPx,
|
|
16
|
+
}));
|
|
17
|
+
const toNativeWidgetFrameLegacyShadow = (layers) => {
|
|
18
|
+
const layer = layers.find((candidate) => !candidate.inset && candidate.color.alpha > 0);
|
|
19
|
+
if (layer === undefined) {
|
|
20
|
+
return Object.assign({}, CLEARED_NATIVE_WIDGET_FRAME_SHADOW_STYLE);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
elevation: Math.max(1, Math.ceil(layer.blurRadiusPx + Math.max(0, layer.spreadRadiusPx))),
|
|
24
|
+
shadowColor: `rgb(${layer.color.red}, ${layer.color.green}, ${layer.color.blue})`,
|
|
25
|
+
shadowOffset: { height: layer.offsetYPx, width: layer.offsetXPx },
|
|
26
|
+
shadowOpacity: layer.color.alpha,
|
|
27
|
+
shadowRadius: layer.blurRadiusPx,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
export function toNativeWidgetFrameShadow(layers) {
|
|
31
|
+
if (layers === undefined) {
|
|
32
|
+
return { kind: "clear" };
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
boxShadow: toNativeWidgetFrameBoxShadow(layers),
|
|
36
|
+
fallbackStyle: toNativeWidgetFrameLegacyShadow(layers),
|
|
37
|
+
kind: "resolved",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function toNativeWidgetSupportedBoxShadow(shadow, support) {
|
|
41
|
+
if (shadow.kind !== "resolved" ||
|
|
42
|
+
support === undefined ||
|
|
43
|
+
support === "none") {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
return support === "all"
|
|
47
|
+
? shadow.boxShadow
|
|
48
|
+
: shadow.boxShadow.filter((layer) => !layer.inset);
|
|
49
|
+
}
|
|
@@ -1,26 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
import { safeParseNativeWidgetCanonicalRadii } from "./native-widget-corner-radii.js";
|
|
2
2
|
const CANONICAL_RADIUS_COMPONENT_PATTERN = /^((?:\d+(?:\.\d+)?|\.\d+))(px|%)$/;
|
|
3
|
-
const safeParseCanonicalPx = (value) => {
|
|
4
|
-
if (!CANONICAL_PX_PATTERN.test(value)) {
|
|
5
|
-
return undefined;
|
|
6
|
-
}
|
|
7
|
-
const parsed = Number.parseFloat(value);
|
|
8
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
9
|
-
};
|
|
10
3
|
export function safeParseProviderWidgetCanonicalTopRadii(value) {
|
|
11
|
-
if (value.
|
|
12
|
-
return undefined;
|
|
13
|
-
}
|
|
14
|
-
const radii = value.split(/\s+/).map(safeParseCanonicalPx);
|
|
15
|
-
if (radii.length === 0 ||
|
|
16
|
-
radii.length > 4 ||
|
|
17
|
-
radii.some((radius) => radius === undefined)) {
|
|
4
|
+
if (value !== value.trim()) {
|
|
18
5
|
return undefined;
|
|
19
6
|
}
|
|
20
|
-
const
|
|
21
|
-
return
|
|
7
|
+
const radii = safeParseNativeWidgetCanonicalRadii(value);
|
|
8
|
+
return radii === undefined
|
|
22
9
|
? undefined
|
|
23
|
-
: { topLeft, topRight };
|
|
10
|
+
: { topLeft: radii.topLeft, topRight: radii.topRight };
|
|
24
11
|
}
|
|
25
12
|
const parseCanonicalRadiusComponent = (value) => {
|
|
26
13
|
var _a;
|
|
@@ -1,44 +1,15 @@
|
|
|
1
|
-
import type { WidgetFrameAppearance
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
borderWidth: number;
|
|
6
|
-
};
|
|
7
|
-
type ProviderWidgetFrameBoxShadowValue = {
|
|
8
|
-
blurRadius: number;
|
|
9
|
-
color: string;
|
|
10
|
-
inset: boolean;
|
|
11
|
-
offsetX: number;
|
|
12
|
-
offsetY: number;
|
|
13
|
-
spreadDistance: number;
|
|
14
|
-
};
|
|
15
|
-
type ProviderWidgetFrameLegacyShadowStyle = {
|
|
16
|
-
elevation: number;
|
|
17
|
-
shadowColor: string;
|
|
18
|
-
shadowOffset: {
|
|
19
|
-
height: number;
|
|
20
|
-
width: number;
|
|
21
|
-
};
|
|
22
|
-
shadowOpacity: number;
|
|
23
|
-
shadowRadius: number;
|
|
24
|
-
};
|
|
25
|
-
export type ProviderWidgetFrameShadowProjection = {
|
|
26
|
-
kind: "clear";
|
|
27
|
-
} | {
|
|
1
|
+
import type { WidgetFrameAppearance } from "@getuserfeedback/protocol/host";
|
|
2
|
+
import { type NativeWidgetFrameBorderStyle } from "./native-widget-frame-border.js";
|
|
3
|
+
import { type NativeWidgetFrameShadowProjection } from "./native-widget-frame-shadow.js";
|
|
4
|
+
type ProviderWidgetFrameShadowProjection = NativeWidgetFrameShadowProjection | {
|
|
28
5
|
kind: "legacy";
|
|
29
|
-
} | {
|
|
30
|
-
boxShadow: ProviderWidgetFrameBoxShadowValue[];
|
|
31
|
-
fallbackStyle: ProviderWidgetFrameLegacyShadowStyle;
|
|
32
|
-
kind: "resolved";
|
|
33
6
|
};
|
|
34
7
|
export type ProviderWidgetFrameAppearanceProjection = {
|
|
35
|
-
borderStyle?:
|
|
8
|
+
borderStyle?: NativeWidgetFrameBorderStyle;
|
|
36
9
|
shadow: ProviderWidgetFrameShadowProjection;
|
|
37
10
|
topLeftRadius: number;
|
|
38
11
|
topRightRadius: number;
|
|
39
12
|
};
|
|
40
|
-
export declare function toProviderWidgetFrameShadow(layers?: WidgetFrameResolvedBoxShadowLayer[]): ProviderWidgetFrameShadowProjection;
|
|
41
|
-
export declare function toProviderWidgetFrameBorderStyle(border?: WidgetFrameResolvedBorder): ProviderWidgetFrameBorderStyle;
|
|
42
13
|
export declare function toProviderWidgetFrameAppearance(appearance?: WidgetFrameAppearance, nativeSheetSize?: {
|
|
43
14
|
height: number;
|
|
44
15
|
width: number;
|
|
@@ -1,71 +1,12 @@
|
|
|
1
|
+
import { toNativeWidgetConservativeResolvedCornerRadii } from "./native-widget-corner-radii.js";
|
|
2
|
+
import { toNativeWidgetResolvedBorderStyle, } from "./native-widget-frame-border.js";
|
|
3
|
+
import { toNativeWidgetFrameShadow, } from "./native-widget-frame-shadow.js";
|
|
1
4
|
import { resolveProviderWidgetCanonicalBorderRadius, safeParseProviderWidgetCanonicalTopRadii, } from "./provider-widget-border-radius.js";
|
|
2
5
|
const LEGACY_WIDGET_SHEET_RADIUS = 24;
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
borderWidth: 0,
|
|
6
|
+
const toProviderWidgetConservativeResolvedTopRadii = (radius) => {
|
|
7
|
+
const resolved = toNativeWidgetConservativeResolvedCornerRadii(radius);
|
|
8
|
+
return { topLeft: resolved.topLeft, topRight: resolved.topRight };
|
|
7
9
|
};
|
|
8
|
-
const CLEARED_WIDGET_FRAME_SHADOW_STYLE = {
|
|
9
|
-
elevation: 0,
|
|
10
|
-
shadowColor: "transparent",
|
|
11
|
-
shadowOffset: { height: 0, width: 0 },
|
|
12
|
-
shadowOpacity: 0,
|
|
13
|
-
shadowRadius: 0,
|
|
14
|
-
};
|
|
15
|
-
const toProviderWidgetFrameColor = (color) => `rgba(${color.red}, ${color.green}, ${color.blue}, ${color.alpha})`;
|
|
16
|
-
const toProviderWidgetFrameBoxShadow = (layers) => layers.map((layer) => ({
|
|
17
|
-
blurRadius: layer.blurRadiusPx,
|
|
18
|
-
color: toProviderWidgetFrameColor(layer.color),
|
|
19
|
-
inset: layer.inset,
|
|
20
|
-
offsetX: layer.offsetXPx,
|
|
21
|
-
offsetY: layer.offsetYPx,
|
|
22
|
-
spreadDistance: layer.spreadRadiusPx,
|
|
23
|
-
}));
|
|
24
|
-
const toProviderWidgetFrameLegacyShadow = (layers) => {
|
|
25
|
-
const layer = layers.find((candidate) => !candidate.inset && candidate.color.alpha > 0);
|
|
26
|
-
if (layer === undefined) {
|
|
27
|
-
return Object.assign({}, CLEARED_WIDGET_FRAME_SHADOW_STYLE);
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
elevation: Math.max(1, Math.ceil(layer.blurRadiusPx + Math.max(0, layer.spreadRadiusPx))),
|
|
31
|
-
shadowColor: `rgb(${layer.color.red}, ${layer.color.green}, ${layer.color.blue})`,
|
|
32
|
-
shadowOffset: { height: layer.offsetYPx, width: layer.offsetXPx },
|
|
33
|
-
shadowOpacity: layer.color.alpha,
|
|
34
|
-
shadowRadius: layer.blurRadiusPx,
|
|
35
|
-
};
|
|
36
|
-
};
|
|
37
|
-
export function toProviderWidgetFrameShadow(layers) {
|
|
38
|
-
if (layers === undefined) {
|
|
39
|
-
return { kind: "clear" };
|
|
40
|
-
}
|
|
41
|
-
return {
|
|
42
|
-
boxShadow: toProviderWidgetFrameBoxShadow(layers),
|
|
43
|
-
fallbackStyle: toProviderWidgetFrameLegacyShadow(layers),
|
|
44
|
-
kind: "resolved",
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
const toProviderWidgetConservativeResolvedTopRadii = (radius) => ({
|
|
48
|
-
// React Native exposes one scalar per corner, so use a deliberately smaller
|
|
49
|
-
// circle that does not exceed the authored ellipse on either axis.
|
|
50
|
-
topLeft: Math.min(radius.topLeft.horizontalPx, radius.topLeft.verticalPx),
|
|
51
|
-
topRight: Math.min(radius.topRight.horizontalPx, radius.topRight.verticalPx),
|
|
52
|
-
});
|
|
53
|
-
export function toProviderWidgetFrameBorderStyle(border) {
|
|
54
|
-
if (border === undefined ||
|
|
55
|
-
border.widthPx === 0 ||
|
|
56
|
-
border.style === "none" ||
|
|
57
|
-
border.style === "hidden") {
|
|
58
|
-
return Object.assign({}, CLEARED_WIDGET_FRAME_BORDER_STYLE);
|
|
59
|
-
}
|
|
60
|
-
const { alpha, blue, green, red } = border.color;
|
|
61
|
-
return {
|
|
62
|
-
borderColor: `rgba(${red}, ${green}, ${blue}, ${alpha})`,
|
|
63
|
-
borderStyle: border.style === "dashed" || border.style === "dotted"
|
|
64
|
-
? border.style
|
|
65
|
-
: "solid",
|
|
66
|
-
borderWidth: border.widthPx,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
10
|
export function toProviderWidgetFrameAppearance(appearance, nativeSheetSize) {
|
|
70
11
|
var _a, _b;
|
|
71
12
|
if (appearance === undefined) {
|
|
@@ -77,8 +18,8 @@ export function toProviderWidgetFrameAppearance(appearance, nativeSheetSize) {
|
|
|
77
18
|
}
|
|
78
19
|
if (appearance.kind === "none") {
|
|
79
20
|
return {
|
|
80
|
-
borderStyle:
|
|
81
|
-
shadow:
|
|
21
|
+
borderStyle: toNativeWidgetResolvedBorderStyle(),
|
|
22
|
+
shadow: toNativeWidgetFrameShadow(),
|
|
82
23
|
topLeftRadius: 0,
|
|
83
24
|
topRightRadius: 0,
|
|
84
25
|
};
|
|
@@ -94,8 +35,8 @@ export function toProviderWidgetFrameAppearance(appearance, nativeSheetSize) {
|
|
|
94
35
|
? safeParseProviderWidgetCanonicalTopRadii(appearance.borderRadius)
|
|
95
36
|
: toProviderWidgetConservativeResolvedTopRadii(appearance.resolvedBorderRadius);
|
|
96
37
|
return {
|
|
97
|
-
borderStyle:
|
|
98
|
-
shadow:
|
|
38
|
+
borderStyle: toNativeWidgetResolvedBorderStyle(appearance.resolvedBorder),
|
|
39
|
+
shadow: toNativeWidgetFrameShadow(appearance.resolvedBoxShadow),
|
|
99
40
|
topLeftRadius: (_a = radius === null || radius === void 0 ? void 0 : radius.topLeft) !== null && _a !== void 0 ? _a : 0,
|
|
100
41
|
topRightRadius: (_b = radius === null || radius === void 0 ? void 0 : radius.topRight) !== null && _b !== void 0 ? _b : 0,
|
|
101
42
|
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { resolveWidgetLayoutTransitionExecution, type WidgetFrameAppearance, type WidgetLayoutComparableSizeUpdate, type WidgetLayoutSizeUpdate, type WidgetLayoutTimedCapableTransitionPlan, type WidgetLayoutTransitionPolicy, type WidgetLayoutTransitionSchedule } from "@getuserfeedback/protocol/host";
|
|
2
2
|
import { type ComponentType, type ReactElement, type ReactNode } from "react";
|
|
3
|
+
import { type NativeWidgetBoxShadowSupport } from "./native-widget-frame-shadow.js";
|
|
3
4
|
import type { WidgetHostProps } from "./widget-host.js";
|
|
4
5
|
type NativeViewComponent = ComponentType<Record<string, unknown>>;
|
|
5
6
|
type NativeReactVersion = {
|
|
6
7
|
major?: unknown;
|
|
7
8
|
minor?: unknown;
|
|
8
9
|
};
|
|
9
|
-
export type ProviderWidgetNativeBoxShadowSupport =
|
|
10
|
+
export type ProviderWidgetNativeBoxShadowSupport = NativeWidgetBoxShadowSupport;
|
|
10
11
|
export declare function toProviderWidgetResumedTransitionTiming(input: {
|
|
11
12
|
durationMs: number;
|
|
12
13
|
easing: readonly [number, number, number, number];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { areWidgetFrameAppearancesEqual, planWidgetLayoutTransition, resolveWidgetLayoutResumedEasing, resolveWidgetLayoutTransitionExecution, toInstantWidgetLayoutSizeUpdate, } from "@getuserfeedback/protocol/host";
|
|
2
2
|
import { createElement, useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
+
import { toNativeWidgetSupportedBoxShadow, } from "./native-widget-frame-shadow.js";
|
|
3
4
|
import { toProviderWidgetFrameAppearance } from "./provider-widget-frame-appearance.js";
|
|
4
5
|
const HIDDEN_WIDGET_HOST_STYLE = {
|
|
5
6
|
bottom: 0,
|
|
@@ -192,6 +193,10 @@ function resolveNativeBoxShadowSupport() {
|
|
|
192
193
|
const reactNativeModule = require("react-native");
|
|
193
194
|
// React Native exposes renderer capability to JavaScript process-wide.
|
|
194
195
|
// There is no stable public API for querying the renderer of this View.
|
|
196
|
+
// TODO(architecture): Extract native box-shadow capability detection to
|
|
197
|
+
// native-widget-frame-shadow-support once the overlay diff fits the small-PR
|
|
198
|
+
// production line gate. [principle=architecture-gardening] [scope=slice]
|
|
199
|
+
// [priority=medium]
|
|
195
200
|
const fabricUIManager = Reflect.get(globalThis, "nativeFabricUIManager");
|
|
196
201
|
return toProviderWidgetNativeBoxShadowSupport({
|
|
197
202
|
hasFabricUIManager: fabricUIManager !== undefined && fabricUIManager !== null,
|
|
@@ -289,13 +294,9 @@ export function toProviderWidgetSheetSurfaceStyles(input) {
|
|
|
289
294
|
borderTopLeftRadius: appearance.topLeftRadius,
|
|
290
295
|
borderTopRightRadius: appearance.topRightRadius,
|
|
291
296
|
};
|
|
292
|
-
const nativeBoxShadow = appearance.shadow.kind
|
|
293
|
-
input.nativeBoxShadowSupport === undefined ||
|
|
294
|
-
input.nativeBoxShadowSupport === "none"
|
|
297
|
+
const nativeBoxShadow = appearance.shadow.kind === "legacy"
|
|
295
298
|
? undefined
|
|
296
|
-
: input.nativeBoxShadowSupport
|
|
297
|
-
? appearance.shadow.boxShadow
|
|
298
|
-
: appearance.shadow.boxShadow.filter((shadow) => !shadow.inset);
|
|
299
|
+
: toNativeWidgetSupportedBoxShadow(appearance.shadow, input.nativeBoxShadowSupport);
|
|
299
300
|
const usesNativeBoxShadow = nativeBoxShadow !== undefined;
|
|
300
301
|
const sheetShadowStyle = appearance.shadow.kind === "legacy"
|
|
301
302
|
? WIDGET_SHEET_LEGACY_FRAME_STYLE
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
import { createNativeDirectCommandSessionController, } from "./native-direct-command-session-controller.js";
|
|
3
|
+
export function useNativeDirectCommandSessionController() {
|
|
4
|
+
var _a;
|
|
5
|
+
const controllerRef = useRef(null);
|
|
6
|
+
(_a = controllerRef.current) !== null && _a !== void 0 ? _a : (controllerRef.current = createNativeDirectCommandSessionController());
|
|
7
|
+
const controller = controllerRef.current;
|
|
8
|
+
const lifecycleRevisionRef = useRef(0);
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
lifecycleRevisionRef.current += 1;
|
|
11
|
+
const lifecycleRevision = lifecycleRevisionRef.current;
|
|
12
|
+
return () => {
|
|
13
|
+
queueMicrotask(() => {
|
|
14
|
+
if (lifecycleRevisionRef.current === lifecycleRevision) {
|
|
15
|
+
controller.dispose(new Error("Native provider runtime unmounted"));
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}, [controller]);
|
|
20
|
+
return controller;
|
|
21
|
+
}
|