@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.
Files changed (29) hide show
  1. package/dist/index.js +29 -89
  2. package/dist/native-direct-command-session-controller.d.ts +9 -0
  3. package/dist/native-direct-command-session-controller.js +78 -0
  4. package/dist/native-direct-command-session.js +2 -2
  5. package/dist/native-provider-client.d.ts +64 -0
  6. package/dist/native-provider-client.js +76 -0
  7. package/dist/native-runtime-root.d.ts +5 -3
  8. package/dist/native-runtime-root.js +31 -7
  9. package/dist/native-view-host-projection.d.ts +2 -1
  10. package/dist/native-view-host-projection.js +13 -9
  11. package/dist/native-view-presentation-surface.d.ts +7 -0
  12. package/dist/native-view-presentation-surface.js +82 -0
  13. package/dist/native-view-record-controller.d.ts +2 -0
  14. package/dist/native-view-record-controller.js +58 -8
  15. package/dist/native-widget-corner-radii.d.ts +10 -0
  16. package/dist/native-widget-corner-radii.js +54 -0
  17. package/dist/native-widget-frame-border.d.ts +7 -0
  18. package/dist/native-widget-frame-border.js +21 -0
  19. package/dist/native-widget-frame-shadow.d.ts +30 -0
  20. package/dist/native-widget-frame-shadow.js +49 -0
  21. package/dist/provider-widget-border-radius.js +5 -18
  22. package/dist/provider-widget-frame-appearance.d.ts +5 -34
  23. package/dist/provider-widget-frame-appearance.js +10 -69
  24. package/dist/provider-widget-host-overlay.d.ts +2 -1
  25. package/dist/provider-widget-host-overlay.js +7 -6
  26. package/dist/use-native-direct-command-session-controller.d.ts +2 -0
  27. package/dist/use-native-direct-command-session-controller.js +21 -0
  28. package/dist/version.js +1 -1
  29. 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 = createViewActivationController();
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
- (_a = entry.viewReadinessDetach) === null || _a === void 0 ? void 0 : _a.call(entry);
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 activation = (_a = pendingActivations.get(action.viewId)) !== null && _a !== void 0 ? _a : createViewActivationController();
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 activation = (_a = entry === null || entry === void 0 ? void 0 : entry.activation) !== null && _a !== void 0 ? _a : ensurePendingActivation(action.viewId);
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 && activation.getState() !== previousState) {
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
- const CANONICAL_PX_PATTERN = /^(?:0|[1-9]\d*)(?:\.\d+)?px$/;
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.includes("/")) {
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 [topLeft, topRight = topLeft] = radii;
21
- return topLeft === undefined || topRight === undefined
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, WidgetFrameResolvedBorder, WidgetFrameResolvedBoxShadowLayer } from "@getuserfeedback/protocol/host";
2
- export type ProviderWidgetFrameBorderStyle = {
3
- borderColor: string;
4
- borderStyle: "dashed" | "dotted" | "solid";
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?: ProviderWidgetFrameBorderStyle;
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 CLEARED_WIDGET_FRAME_BORDER_STYLE = {
4
- borderColor: "transparent",
5
- borderStyle: "solid",
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: toProviderWidgetFrameBorderStyle(),
81
- shadow: { kind: "clear" },
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: toProviderWidgetFrameBorderStyle(appearance.resolvedBorder),
98
- shadow: toProviderWidgetFrameShadow(appearance.resolvedBoxShadow),
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 = "all" | "none" | "outset";
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 !== "resolved" ||
293
- input.nativeBoxShadowSupport === undefined ||
294
- input.nativeBoxShadowSupport === "none"
297
+ const nativeBoxShadow = appearance.shadow.kind === "legacy"
295
298
  ? undefined
296
- : input.nativeBoxShadowSupport === "all"
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,2 @@
1
+ import { type NativeDirectCommandSessionController } from "./native-direct-command-session-controller.js";
2
+ export declare function useNativeDirectCommandSessionController(): NativeDirectCommandSessionController;
@@ -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
+ }