@getuserfeedback/react-native 5.12.0 → 5.14.1

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.
@@ -1,3 +1,4 @@
1
+ import { createWidgetLayoutTransitionSchedule, } from "@getuserfeedback/protocol/host";
1
2
  import { coreViewHostActionSchema, } from "@getuserfeedback/protocol/internal/core-view-host-actions-v3";
2
3
  import { actionRequestCloseViewSchema, actionViewConnectSchema, buildEventLayoutTransactionCommitted, buildEventViewSize, eventViewSizeSchema, } from "@getuserfeedback/protocol/internal/view-host-control-messages";
3
4
  import { buildEventViewActivated, buildEventViewActivationDecided, buildEventViewAllocationFailed, buildEventViewClosed, buildEventViewCloseFailed, buildEventViewPrerendered, } from "@getuserfeedback/protocol/internal/view-host-lifecycle-events-v3";
@@ -223,6 +224,8 @@ export function createNativeViewRecordController(input) {
223
224
  }
224
225
  };
225
226
  const handlePrerender = (action) => {
227
+ var _a;
228
+ var _b;
226
229
  const existing = entries.get(action.viewId);
227
230
  if (existing) {
228
231
  if (existing.generation !== action.gen ||
@@ -239,6 +242,9 @@ export function createNativeViewRecordController(input) {
239
242
  nextAllocationId += 1;
240
243
  const entry = {
241
244
  allocationId: nextAllocationId,
245
+ flowId: ((_a = action.openRequest) === null || _a === void 0 ? void 0 : _a.source) === "command"
246
+ ? ((_b = action.openRequest.flowId) !== null && _b !== void 0 ? _b : null)
247
+ : null,
242
248
  generation: action.gen,
243
249
  flowRunId: action.flowRunId,
244
250
  instanceId: action.instanceId,
@@ -272,7 +278,8 @@ export function createNativeViewRecordController(input) {
272
278
  };
273
279
  // Governing contract: docs/specs/2026-08-29-sdk-open-request-interception.md.
274
280
  const handleActivate = (action) => {
275
- var _a;
281
+ var _a, _b;
282
+ var _c;
276
283
  const entry = entries.get(action.viewId);
277
284
  if (!entry ||
278
285
  entry.instanceId !== action.instanceId ||
@@ -302,6 +309,9 @@ export function createNativeViewRecordController(input) {
302
309
  return;
303
310
  }
304
311
  if (!entry.isActive) {
312
+ if (((_b = action.openRequest) === null || _b === void 0 ? void 0 : _b.source) === "command") {
313
+ entry.flowId = (_c = action.openRequest.flowId) !== null && _c !== void 0 ? _c : entry.flowId;
314
+ }
305
315
  entry.isActive = true;
306
316
  notify();
307
317
  }
@@ -487,6 +497,7 @@ export function createNativeViewRecordController(input) {
487
497
  connection.transport.postMessage(buildEventLayoutTransactionCommitted({
488
498
  gen: entry.generation,
489
499
  layoutTransactionId,
500
+ transitionSchedule: entry.size.transitionSchedule,
490
501
  viewId,
491
502
  }));
492
503
  }
@@ -580,7 +591,7 @@ export function createNativeViewRecordController(input) {
580
591
  }
581
592
  };
582
593
  const handleMessage = (value) => {
583
- var _a;
594
+ var _a, _b;
584
595
  if (coreConnectionFailed ||
585
596
  detached ||
586
597
  disposed ||
@@ -594,8 +605,24 @@ export function createNativeViewRecordController(input) {
594
605
  connected.data.viewId === viewId) {
595
606
  const wasViewConnected = entry.isViewConnected;
596
607
  entry.isViewConnected = true;
608
+ const navigationAvailability = !wasViewConnected
609
+ ? (_a = input.getBrowserNavigationAvailability) === null || _a === void 0 ? void 0 : _a.call(input)
610
+ : undefined;
611
+ if (navigationAvailability !== undefined) {
612
+ try {
613
+ connection.transport.postMessage({
614
+ gen: entry.generation,
615
+ payload: navigationAvailability,
616
+ t: "getuserfeedback:event:message",
617
+ });
618
+ }
619
+ catch (error) {
620
+ failCoreConnection(error, "Native navigation availability delivery failed");
621
+ return;
622
+ }
623
+ }
597
624
  if (entry.size === null &&
598
- ((_a = entry.retainedSize) === null || _a === void 0 ? void 0 : _a.connectionId) === connection.connectionId) {
625
+ ((_b = entry.retainedSize) === null || _b === void 0 ? void 0 : _b.connectionId) === connection.connectionId) {
599
626
  entry.size = entry.retainedSize.size;
600
627
  const readySize = connectionReadiness.recordSize(entry.size);
601
628
  if (readySize) {
@@ -646,13 +673,18 @@ export function createNativeViewRecordController(input) {
646
673
  }
647
674
  return;
648
675
  }
649
- const size = toViewSizeReport(parsed.data);
676
+ const layoutReportRevision = entry.layoutReportRevision + 1;
677
+ const size = toViewSizeReport(parsed.data, {
678
+ layoutReportRevision,
679
+ previousSize: entry.size,
680
+ viewId,
681
+ });
650
682
  entry.size = size;
651
683
  entry.retainedSize = {
652
684
  connectionId: connection.connectionId,
653
685
  size,
654
686
  };
655
- entry.layoutReportRevision += 1;
687
+ entry.layoutReportRevision = layoutReportRevision;
656
688
  const readySize = connectionReadiness.recordSize(size);
657
689
  if (readySize) {
658
690
  deliverReadiness(readySize);
@@ -747,6 +779,7 @@ export function createNativeViewRecordController(input) {
747
779
  function toRecord(entry) {
748
780
  return Object.freeze({
749
781
  allocationId: entry.allocationId,
782
+ flowId: entry.flowId,
750
783
  generation: entry.generation,
751
784
  isActive: entry.isActive,
752
785
  layoutReportRevision: entry.layoutReportRevision,
@@ -755,7 +788,17 @@ function toRecord(entry) {
755
788
  viewId: entry.viewId,
756
789
  });
757
790
  }
758
- function toViewSizeReport(event) {
791
+ function toViewSizeReport(event, context) {
792
+ var _a;
793
+ const transitionSchedule = context.previousSize !== null &&
794
+ event.layoutTransactionId !== undefined &&
795
+ ((_a = event.transitionPolicy) === null || _a === void 0 ? void 0 : _a.kind) === "timed"
796
+ ? createWidgetLayoutTransitionSchedule({
797
+ nowEpochMs: Date.now(),
798
+ sequence: context.layoutReportRevision,
799
+ transactionId: `${context.viewId}:${event.layoutTransactionId}`,
800
+ })
801
+ : undefined;
759
802
  return {
760
803
  width: event.width,
761
804
  height: event.height,
@@ -765,5 +808,6 @@ function toViewSizeReport(event) {
765
808
  placement: event.placement,
766
809
  transitionPolicy: event.transitionPolicy,
767
810
  layoutTransactionId: event.layoutTransactionId,
811
+ transitionSchedule,
768
812
  };
769
813
  }
@@ -0,0 +1,32 @@
1
+ import type { OpenFlowOptions } from "./client.js";
2
+ /** Options for the React Native `useFlow` hook. */
3
+ export interface UseFlowOptions {
4
+ /** The flow/survey ID to control. */
5
+ flowId: string;
6
+ /** Prefetch the flow resources when the hook mounts. Default `false`. */
7
+ prefetchOnMount?: boolean;
8
+ /** When true, the flow does not show a close button. */
9
+ hideCloseButton?: boolean;
10
+ }
11
+ /** Per-open options for the React Native `useFlow` hook. */
12
+ export type UseFlowOpenOptions = Pick<OpenFlowOptions, "metadata">;
13
+ /** State and commands for one React Native on-demand flow. */
14
+ export interface UseFlowReturn {
15
+ /** True when the flow has an active, measured projected view. */
16
+ readonly isOpen: boolean;
17
+ /** True while the runtime is preparing this flow for display. */
18
+ readonly isLoading: boolean;
19
+ /** Open the flow. */
20
+ readonly open: (options?: UseFlowOpenOptions) => Promise<void>;
21
+ /** Close the flow. */
22
+ readonly close: () => Promise<void>;
23
+ /** Prefetch the flow resources without opening it. */
24
+ readonly prefetch: () => Promise<void>;
25
+ /** Prerender the flow without opening it. */
26
+ readonly prerender: () => Promise<void>;
27
+ }
28
+ /**
29
+ * Controls one on-demand flow in the provider's native presentation surface.
30
+ * React Native custom containers remain provider-wide via `useFlowContainer()`.
31
+ */
32
+ export declare function useFlow(options: UseFlowOptions): UseFlowReturn;
@@ -0,0 +1,50 @@
1
+ import { useCallback, useContext, useEffect, useMemo, useSyncExternalStore, } from "react";
2
+ import { NativeFlowContainerContext } from "./native-flow-container-context.js";
3
+ const EMPTY_RECORDS = Object.freeze([]);
4
+ const EMPTY_REGISTRY_RECORDS = Object.freeze([]);
5
+ const subscribeNoop = () => () => { };
6
+ const getEmptyRecords = () => EMPTY_RECORDS;
7
+ const getEmptyRegistryRecords = () => EMPTY_REGISTRY_RECORDS;
8
+ const getFlowRegistryRecord = (flowId, records) => records.find((record) => record.flowId === flowId);
9
+ const deriveFlowState = (flowId, registryRecords, records) => {
10
+ const flow = getFlowRegistryRecord(flowId, registryRecords);
11
+ const isOpen = records.some((record) => record.flowId === flowId && record.isActive && record.size !== null);
12
+ const isLoading = !isOpen &&
13
+ ((flow === null || flow === void 0 ? void 0 : flow.isOpening) === true ||
14
+ records.some((record) => record.flowId === flowId && record.isActive && record.size === null));
15
+ return { isLoading, isOpen };
16
+ };
17
+ /**
18
+ * Controls one on-demand flow in the provider's native presentation surface.
19
+ * React Native custom containers remain provider-wide via `useFlowContainer()`.
20
+ */
21
+ export function useFlow(options) {
22
+ var _a, _b, _c;
23
+ const context = useContext(NativeFlowContainerContext);
24
+ if (!context) {
25
+ throw new Error("useFlow must be used inside GetUserFeedbackProvider");
26
+ }
27
+ const flowRun = useMemo(() => context.client.flow(options.flowId), [context.client, options.flowId]);
28
+ const registryRecords = useSyncExternalStore(context.flowRegistry.subscribe, context.flowRegistry.getSnapshot, getEmptyRegistryRecords);
29
+ const projection = context.projection;
30
+ const viewRecords = useSyncExternalStore((_a = projection === null || projection === void 0 ? void 0 : projection.controller.subscribe) !== null && _a !== void 0 ? _a : subscribeNoop, (_b = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _b !== void 0 ? _b : getEmptyRecords, (_c = projection === null || projection === void 0 ? void 0 : projection.controller.getSnapshot) !== null && _c !== void 0 ? _c : getEmptyRecords);
31
+ const state = useMemo(() => {
32
+ return deriveFlowState(flowRun.flowId, registryRecords, viewRecords);
33
+ }, [flowRun.flowId, registryRecords, viewRecords]);
34
+ const open = useCallback((openOptions) => flowRun.open({
35
+ hideCloseButton: options.hideCloseButton,
36
+ metadata: openOptions === null || openOptions === void 0 ? void 0 : openOptions.metadata,
37
+ }), [flowRun, options.hideCloseButton]);
38
+ const close = useCallback(() => flowRun.close(), [flowRun]);
39
+ const prefetch = useCallback(() => flowRun.prefetch(), [flowRun]);
40
+ const prerender = useCallback(() => flowRun.prerender({ hideCloseButton: options.hideCloseButton }), [flowRun, options.hideCloseButton]);
41
+ useEffect(() => {
42
+ if (options.prefetchOnMount) {
43
+ void prefetch().catch(() => { });
44
+ }
45
+ }, [options.prefetchOnMount, prefetch]);
46
+ return Object.assign(Object.assign({}, state), { open,
47
+ close,
48
+ prefetch,
49
+ prerender });
50
+ }
package/dist/version.js CHANGED
@@ -3,4 +3,4 @@ const reactNativeSdkVersion = typeof __GX_REACT_NATIVE_SDK_VERSION__ === "string
3
3
  : "";
4
4
  // Build scripts patch this fallback to the package version for published artifacts.
5
5
  // Source-linked workspace usage keeps the local fallback.
6
- export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.12.0";
6
+ export const REACT_NATIVE_SDK_VERSION = reactNativeSdkVersion.length > 0 ? reactNativeSdkVersion : "5.14.1";
@@ -33,7 +33,7 @@ function ViewWebViewHostDocument({ generation, onConnected, onDisconnected, onAc
33
33
  const controller = controllerRef.current;
34
34
  const buildDocumentStartScript = useMemo(() => () => buildViewWebViewBridgeScript({ bridgeId, phase: "documentStart" }), [bridgeId]);
35
35
  const source = useMemo(() => toExternalViewSource({ bridgeId, srcdoc }), [bridgeId, srcdoc]);
36
- return createElement((WebViewDocumentHost), {
36
+ return createElement(WebViewDocumentHost, {
37
37
  buildDocumentStartScript,
38
38
  controller,
39
39
  parseMessage: parseViewWebViewBridgeMessage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getuserfeedback/react-native",
3
- "version": "5.12.0",
3
+ "version": "5.14.1",
4
4
  "description": "getuserfeedback React Native SDK",
5
5
  "keywords": [
6
6
  "getuserfeedback",
@@ -54,8 +54,8 @@
54
54
  "lint": "biome check ."
55
55
  },
56
56
  "dependencies": {
57
- "@getuserfeedback/protocol": "^5.4.2",
58
- "@getuserfeedback/sdk": "^0.18.0",
57
+ "@getuserfeedback/protocol": "^5.5.0",
58
+ "@getuserfeedback/sdk": "^0.19.0",
59
59
  "robot3": "^1.2.0"
60
60
  },
61
61
  "peerDependencies": {
@@ -64,6 +64,6 @@
64
64
  "react-native-webview": ">=13.0.0"
65
65
  },
66
66
  "devDependencies": {
67
- "typescript": "^5.8.3"
67
+ "typescript": "^7.0.2"
68
68
  }
69
69
  }