@userflow-tech/react-native 0.6.0 → 0.7.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 +41 -33
- package/docs/troubleshooting.md +38 -38
- package/expo-plugin/hostDependencyWarnings.d.ts +5 -5
- package/expo-plugin/hostDependencyWarnings.js +7 -7
- package/expo-plugin/hostDependencyWarnings.js.map +1 -1
- package/lib/commonjs/anchors/AnchorRegistry.js +5 -0
- package/lib/commonjs/anchors/AnchorRegistry.js.map +1 -1
- package/lib/commonjs/anchors/ScrollContainerRegistry.js +8 -0
- package/lib/commonjs/anchors/ScrollContainerRegistry.js.map +1 -1
- package/lib/commonjs/anchors/fiber/presentedModal.js +215 -0
- package/lib/commonjs/anchors/fiber/presentedModal.js.map +1 -0
- package/lib/commonjs/anchors/fiber/touchTargetFiber.js +1 -0
- package/lib/commonjs/anchors/fiber/touchTargetFiber.js.map +1 -1
- package/lib/commonjs/anchors/fiber/visibleRect.js +12 -3
- package/lib/commonjs/anchors/fiber/visibleRect.js.map +1 -1
- package/lib/commonjs/anchors/useUserflowAnchor.js.map +1 -1
- package/lib/commonjs/anchors/useUserflowScrollContainer.js.map +1 -1
- package/lib/commonjs/authoring/ElementPickerOverlay.js +197 -138
- package/lib/commonjs/authoring/ElementPickerOverlay.js.map +1 -1
- package/lib/commonjs/authoring/hitTest.js +67 -10
- package/lib/commonjs/authoring/hitTest.js.map +1 -1
- package/lib/commonjs/authoring/hostAtPoint.js +45 -21
- package/lib/commonjs/authoring/hostAtPoint.js.map +1 -1
- package/lib/commonjs/content/RichContentRenderer.js +19 -7
- package/lib/commonjs/content/RichContentRenderer.js.map +1 -1
- package/lib/commonjs/deepLinking.js.map +1 -1
- package/lib/commonjs/deviceContext.js +39 -19
- package/lib/commonjs/deviceContext.js.map +1 -1
- package/lib/commonjs/index.js +11 -5
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/installCheck.js +3 -3
- package/lib/commonjs/installCheck.js.map +1 -1
- package/lib/commonjs/lifecycle.js +2 -0
- package/lib/commonjs/lifecycle.js.map +1 -1
- package/lib/commonjs/pairing/PairingController.js.map +1 -1
- package/lib/commonjs/runtime/UserflowOverlay.js +17 -13
- package/lib/commonjs/runtime/UserflowOverlay.js.map +1 -1
- package/lib/commonjs/runtime/WindowOverlayHost.js +83 -0
- package/lib/commonjs/runtime/WindowOverlayHost.js.map +1 -0
- package/lib/commonjs/runtime/userflowCore.js +9 -2
- package/lib/commonjs/runtime/userflowCore.js.map +1 -1
- package/lib/commonjs/surfaces/TooltipSurface.js +109 -51
- package/lib/commonjs/surfaces/TooltipSurface.js.map +1 -1
- package/lib/commonjs/surfaces/tooltip/TooltipHighlight.js +6 -6
- package/lib/commonjs/surfaces/tooltip/TooltipHighlight.js.map +1 -1
- package/lib/commonjs/surfaces/tooltip/targetOrigin.js +74 -0
- package/lib/commonjs/surfaces/tooltip/targetOrigin.js.map +1 -0
- package/lib/commonjs/version.js +1 -1
- package/lib/module/anchors/AnchorRegistry.js +5 -0
- package/lib/module/anchors/AnchorRegistry.js.map +1 -1
- package/lib/module/anchors/ScrollContainerRegistry.js +8 -0
- package/lib/module/anchors/ScrollContainerRegistry.js.map +1 -1
- package/lib/module/anchors/fiber/presentedModal.js +206 -0
- package/lib/module/anchors/fiber/presentedModal.js.map +1 -0
- package/lib/module/anchors/fiber/touchTargetFiber.js +1 -0
- package/lib/module/anchors/fiber/touchTargetFiber.js.map +1 -1
- package/lib/module/anchors/fiber/visibleRect.js +12 -3
- package/lib/module/anchors/fiber/visibleRect.js.map +1 -1
- package/lib/module/anchors/useUserflowAnchor.js.map +1 -1
- package/lib/module/anchors/useUserflowScrollContainer.js.map +1 -1
- package/lib/module/authoring/ElementPickerOverlay.js +199 -140
- package/lib/module/authoring/ElementPickerOverlay.js.map +1 -1
- package/lib/module/authoring/hitTest.js +67 -11
- package/lib/module/authoring/hitTest.js.map +1 -1
- package/lib/module/authoring/hostAtPoint.js +45 -21
- package/lib/module/authoring/hostAtPoint.js.map +1 -1
- package/lib/module/content/RichContentRenderer.js +19 -7
- package/lib/module/content/RichContentRenderer.js.map +1 -1
- package/lib/module/deepLinking.js.map +1 -1
- package/lib/module/deviceContext.js +38 -17
- package/lib/module/deviceContext.js.map +1 -1
- package/lib/module/index.js +11 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/installCheck.js +3 -3
- package/lib/module/installCheck.js.map +1 -1
- package/lib/module/lifecycle.js +2 -0
- package/lib/module/lifecycle.js.map +1 -1
- package/lib/module/pairing/PairingController.js.map +1 -1
- package/lib/module/runtime/UserflowOverlay.js +18 -14
- package/lib/module/runtime/UserflowOverlay.js.map +1 -1
- package/lib/module/runtime/WindowOverlayHost.js +76 -0
- package/lib/module/runtime/WindowOverlayHost.js.map +1 -0
- package/lib/module/runtime/userflowCore.js +9 -2
- package/lib/module/runtime/userflowCore.js.map +1 -1
- package/lib/module/surfaces/TooltipSurface.js +109 -51
- package/lib/module/surfaces/TooltipSurface.js.map +1 -1
- package/lib/module/surfaces/tooltip/TooltipHighlight.js +6 -6
- package/lib/module/surfaces/tooltip/TooltipHighlight.js.map +1 -1
- package/lib/module/surfaces/tooltip/targetOrigin.js +67 -0
- package/lib/module/surfaces/tooltip/targetOrigin.js.map +1 -0
- package/lib/module/version.js +1 -1
- package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts +7 -0
- package/lib/typescript/commonjs/src/anchors/AnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/ScrollContainerRegistry.d.ts +11 -2
- package/lib/typescript/commonjs/src/anchors/ScrollContainerRegistry.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/fiber/presentedModal.d.ts +45 -0
- package/lib/typescript/commonjs/src/anchors/fiber/presentedModal.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/anchors/fiber/visibleRect.d.ts +5 -2
- package/lib/typescript/commonjs/src/anchors/fiber/visibleRect.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts +2 -2
- package/lib/typescript/commonjs/src/anchors/useUserflowAnchor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/anchors/useUserflowScrollContainer.d.ts +3 -2
- package/lib/typescript/commonjs/src/anchors/useUserflowScrollContainer.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/authoring/ElementPickerOverlay.d.ts +4 -0
- package/lib/typescript/commonjs/src/authoring/ElementPickerOverlay.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/authoring/hitTest.d.ts +25 -2
- package/lib/typescript/commonjs/src/authoring/hitTest.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/authoring/hostAtPoint.d.ts +6 -1
- package/lib/typescript/commonjs/src/authoring/hostAtPoint.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/deepLinking.d.ts +1 -1
- package/lib/typescript/commonjs/src/deepLinking.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/deviceContext.d.ts +14 -5
- package/lib/typescript/commonjs/src/deviceContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +11 -5
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/installCheck.d.ts +2 -2
- package/lib/typescript/commonjs/src/installCheck.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/lifecycle.d.ts +1 -0
- package/lib/typescript/commonjs/src/lifecycle.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/pairing/PairingController.d.ts +1 -1
- package/lib/typescript/commonjs/src/pairing/PairingController.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/UserflowOverlay.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/runtime/WindowOverlayHost.d.ts +34 -0
- package/lib/typescript/commonjs/src/runtime/WindowOverlayHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/runtime/userflowCore.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/TooltipSurface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/surfaces/tooltip/targetOrigin.d.ts +26 -0
- package/lib/typescript/commonjs/src/surfaces/tooltip/targetOrigin.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/version.d.ts +1 -1
- package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts +7 -0
- package/lib/typescript/module/src/anchors/AnchorRegistry.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/ScrollContainerRegistry.d.ts +11 -2
- package/lib/typescript/module/src/anchors/ScrollContainerRegistry.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/fiber/presentedModal.d.ts +45 -0
- package/lib/typescript/module/src/anchors/fiber/presentedModal.d.ts.map +1 -0
- package/lib/typescript/module/src/anchors/fiber/visibleRect.d.ts +5 -2
- package/lib/typescript/module/src/anchors/fiber/visibleRect.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts +2 -2
- package/lib/typescript/module/src/anchors/useUserflowAnchor.d.ts.map +1 -1
- package/lib/typescript/module/src/anchors/useUserflowScrollContainer.d.ts +3 -2
- package/lib/typescript/module/src/anchors/useUserflowScrollContainer.d.ts.map +1 -1
- package/lib/typescript/module/src/authoring/ElementPickerOverlay.d.ts +4 -0
- package/lib/typescript/module/src/authoring/ElementPickerOverlay.d.ts.map +1 -1
- package/lib/typescript/module/src/authoring/hitTest.d.ts +25 -2
- package/lib/typescript/module/src/authoring/hitTest.d.ts.map +1 -1
- package/lib/typescript/module/src/authoring/hostAtPoint.d.ts +6 -1
- package/lib/typescript/module/src/authoring/hostAtPoint.d.ts.map +1 -1
- package/lib/typescript/module/src/deepLinking.d.ts +1 -1
- package/lib/typescript/module/src/deepLinking.d.ts.map +1 -1
- package/lib/typescript/module/src/deviceContext.d.ts +14 -5
- package/lib/typescript/module/src/deviceContext.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +11 -5
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/installCheck.d.ts +2 -2
- package/lib/typescript/module/src/installCheck.d.ts.map +1 -1
- package/lib/typescript/module/src/lifecycle.d.ts +1 -0
- package/lib/typescript/module/src/lifecycle.d.ts.map +1 -1
- package/lib/typescript/module/src/pairing/PairingController.d.ts +1 -1
- package/lib/typescript/module/src/pairing/PairingController.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/UserflowOverlay.d.ts.map +1 -1
- package/lib/typescript/module/src/runtime/WindowOverlayHost.d.ts +34 -0
- package/lib/typescript/module/src/runtime/WindowOverlayHost.d.ts.map +1 -0
- package/lib/typescript/module/src/runtime/userflowCore.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/TooltipSurface.d.ts.map +1 -1
- package/lib/typescript/module/src/surfaces/tooltip/targetOrigin.d.ts +26 -0
- package/lib/typescript/module/src/surfaces/tooltip/targetOrigin.d.ts.map +1 -0
- package/lib/typescript/module/src/version.d.ts +1 -1
- package/package.json +5 -4
- package/src/anchors/AnchorRegistry.ts +8 -0
- package/src/anchors/ScrollContainerRegistry.ts +15 -3
- package/src/anchors/fiber/presentedModal.ts +203 -0
- package/src/anchors/fiber/touchTargetFiber.ts +2 -1
- package/src/anchors/fiber/visibleRect.ts +12 -3
- package/src/anchors/useUserflowAnchor.ts +4 -5
- package/src/anchors/useUserflowScrollContainer.ts +5 -4
- package/src/authoring/ElementPickerOverlay.tsx +242 -166
- package/src/authoring/hitTest.ts +88 -13
- package/src/authoring/hostAtPoint.ts +67 -21
- package/src/content/RichContentRenderer.tsx +11 -9
- package/src/deepLinking.ts +2 -1
- package/src/deviceContext.ts +50 -14
- package/src/index.ts +11 -5
- package/src/installCheck.ts +3 -3
- package/src/lifecycle.ts +2 -1
- package/src/pairing/PairingController.ts +2 -1
- package/src/runtime/UserflowOverlay.tsx +29 -21
- package/src/runtime/WindowOverlayHost.tsx +95 -0
- package/src/runtime/userflowCore.ts +8 -2
- package/src/surfaces/TooltipSurface.tsx +103 -45
- package/src/surfaces/tooltip/TooltipHighlight.tsx +3 -5
- package/src/surfaces/tooltip/targetOrigin.ts +74 -0
- package/src/version.ts +1 -1
- package/vendor/core/dist/engine/UserflowCore.d.ts +7 -0
- package/vendor/core/dist/engine/UserflowCore.d.ts.map +1 -1
- package/vendor/core/dist/engine/UserflowCore.js +3 -2
- package/vendor/core/dist/engine/UserflowCore.js.map +1 -1
- package/vendor/core/dist/transport/phoenix.d.ts +12 -2
- package/vendor/core/dist/transport/phoenix.d.ts.map +1 -1
- package/vendor/core/dist/transport/phoenix.js +7 -6
- package/vendor/core/dist/transport/phoenix.js.map +1 -1
- package/vendor/core/dist/version.d.ts +1 -1
- package/vendor/core/dist/version.js +1 -1
- package/vendor/core/package.json +1 -1
- package/vendor/protocol/package.json +1 -1
package/src/authoring/hitTest.ts
CHANGED
|
@@ -17,11 +17,16 @@ import {
|
|
|
17
17
|
} from '../anchors/fiber/fiberInternals';
|
|
18
18
|
import { createLazyHostFiberIndex, lookupHostFiber } from '../anchors/fiber/indexHostFibers';
|
|
19
19
|
import { measureHostFiber } from '../anchors/fiber/measureHostFiber';
|
|
20
|
+
import {
|
|
21
|
+
isOutsideModal,
|
|
22
|
+
locatePresentedModal,
|
|
23
|
+
rectInWindow,
|
|
24
|
+
type PresentedModal,
|
|
25
|
+
} from '../anchors/fiber/presentedModal';
|
|
20
26
|
import { readHostText } from '../anchors/fiber/readHostContent';
|
|
21
27
|
import {
|
|
22
28
|
clipToScrollViewports,
|
|
23
29
|
fiberIsUnder,
|
|
24
|
-
topmostVisibleModalHost,
|
|
25
30
|
type VisibleRect,
|
|
26
31
|
} from '../anchors/fiber/visibleRect';
|
|
27
32
|
import { isFiberOnVisibleScreen } from '../anchors/fiber/visibleScreens';
|
|
@@ -61,7 +66,17 @@ export interface HitCandidate {
|
|
|
61
66
|
|
|
62
67
|
export type MeasureInstance = (inst: AutoAnchorInstance) => Promise<HitRect | null>;
|
|
63
68
|
export type InstanceFilter = (inst: AutoAnchorInstance) => boolean;
|
|
64
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Why a tap on a presented modal found nothing: the modal cannot be placed in
|
|
71
|
+
* the window, or the tap was outside it.
|
|
72
|
+
*/
|
|
73
|
+
export type ModalMiss = 'unplaceable' | 'outside';
|
|
74
|
+
|
|
75
|
+
export type HostAtPoint = (
|
|
76
|
+
x: number,
|
|
77
|
+
y: number,
|
|
78
|
+
presented?: PresentedModal | null,
|
|
79
|
+
) => Promise<AnyFiber | null>;
|
|
65
80
|
|
|
66
81
|
const MEASURE_TIMEOUT_MS = 400;
|
|
67
82
|
|
|
@@ -259,10 +274,14 @@ function targetForHost(
|
|
|
259
274
|
* {@link FiberScanRoot}. Hosts above it (the provider's own wrappers) are never
|
|
260
275
|
* resolvable at play time. Empty when `host` is on a covered screen: its
|
|
261
276
|
* ancestors above the navigator are not what was tapped.
|
|
277
|
+
*
|
|
278
|
+
* The walk also stops at `boundary`, a presented modal: hosts above it are
|
|
279
|
+
* drawn beneath the modal, not around what was tapped.
|
|
262
280
|
*/
|
|
263
281
|
export function candidateChain(
|
|
264
282
|
host: AnyFiber,
|
|
265
283
|
byFiber: Map<AnyFiber, AutoAnchorInstance>,
|
|
284
|
+
boundary?: AnyFiber | null,
|
|
266
285
|
): PickTarget[] {
|
|
267
286
|
const chain: PickTarget[] = [];
|
|
268
287
|
if (!isFiberOnVisibleScreen(host)) return chain;
|
|
@@ -281,6 +300,7 @@ export function candidateChain(
|
|
|
281
300
|
seenFibers.add(target.fiber);
|
|
282
301
|
chain.push(target);
|
|
283
302
|
}
|
|
303
|
+
if (boundary && (current === boundary || current === getAlternate(boundary))) break;
|
|
284
304
|
current = getReturn(current);
|
|
285
305
|
}
|
|
286
306
|
return chain;
|
|
@@ -335,16 +355,30 @@ export function defaultCandidateIndex(candidates: HitCandidate[]): number {
|
|
|
335
355
|
return index >= 0 ? index : 0;
|
|
336
356
|
}
|
|
337
357
|
|
|
358
|
+
function targetFiber(target: PickTarget): AnyFiber | null {
|
|
359
|
+
return target.source === 'fiber'
|
|
360
|
+
? target.fiber
|
|
361
|
+
: fiberFromPublicInstanceSafe(target.instance.ref.current);
|
|
362
|
+
}
|
|
363
|
+
|
|
338
364
|
async function clipCandidate(candidate: HitCandidate): Promise<HitCandidate | null> {
|
|
339
|
-
const fiber =
|
|
340
|
-
candidate.target.source === 'fiber'
|
|
341
|
-
? candidate.target.fiber
|
|
342
|
-
: fiberFromPublicInstanceSafe(candidate.target.instance.ref.current);
|
|
365
|
+
const fiber = targetFiber(candidate.target);
|
|
343
366
|
const clipped = await clipToScrollViewports(fiber, candidate.rect as VisibleRect);
|
|
344
367
|
if (!clipped) return null;
|
|
345
368
|
return { ...candidate, rect: clipped, area: areaOf(clipped) };
|
|
346
369
|
}
|
|
347
370
|
|
|
371
|
+
/** `candidate` in window coordinates. A presented modal's content is measured in the modal's space. */
|
|
372
|
+
function candidateInWindow(
|
|
373
|
+
candidate: HitCandidate,
|
|
374
|
+
presented: PresentedModal | null,
|
|
375
|
+
): HitCandidate {
|
|
376
|
+
if (!presented) return candidate;
|
|
377
|
+
const fiber = targetFiber(candidate.target);
|
|
378
|
+
if (fiber && !fiberIsUnder(fiber, presented.host)) return candidate;
|
|
379
|
+
return { ...candidate, rect: rectInWindow(candidate.rect, presented) };
|
|
380
|
+
}
|
|
381
|
+
|
|
348
382
|
/**
|
|
349
383
|
* Hit-test window coordinates.
|
|
350
384
|
*
|
|
@@ -353,6 +387,12 @@ async function clipCandidate(candidate: HitCandidate): Promise<HitCandidate | nu
|
|
|
353
387
|
* lands on a covered screen. A visible native hit with no identifiable
|
|
354
388
|
* ancestor returns no candidates: geometry ignores stacking order and would
|
|
355
389
|
* offer whatever lies underneath the tap.
|
|
390
|
+
*
|
|
391
|
+
* While a modal is presented, only its content is a candidate, and candidate
|
|
392
|
+
* rects come back in window coordinates. A modal that cannot be placed in the
|
|
393
|
+
* window yields no candidates rather than a guess, and so does a tap outside
|
|
394
|
+
* the modal, on the screen it covers. Both call `onModalMiss` so the caller
|
|
395
|
+
* can say why.
|
|
356
396
|
*/
|
|
357
397
|
export async function hitTestAt(
|
|
358
398
|
x: number,
|
|
@@ -362,6 +402,7 @@ export async function hitTestAt(
|
|
|
362
402
|
measure?: MeasureInstance;
|
|
363
403
|
instances?: AutoAnchorInstance[];
|
|
364
404
|
hostAtPoint?: HostAtPoint;
|
|
405
|
+
onModalMiss?: (miss: ModalMiss) => void;
|
|
365
406
|
},
|
|
366
407
|
): Promise<HitCandidate[]> {
|
|
367
408
|
const filter = opts?.instanceFilter ?? createVisibleInstanceFilter();
|
|
@@ -369,21 +410,33 @@ export async function hitTestAt(
|
|
|
369
410
|
const pool = (opts?.instances ?? autoAnchorRegistry.allInstances()).filter(filter);
|
|
370
411
|
autoAnchorRegistry.refreshLiveContent(pool);
|
|
371
412
|
|
|
413
|
+
const presented = await locatePresentedModal(fiberScanRoot.getFiber());
|
|
414
|
+
if (presented && !presented.origin) {
|
|
415
|
+
opts?.onModalMiss?.('unplaceable');
|
|
416
|
+
return [];
|
|
417
|
+
}
|
|
418
|
+
if (presented && isOutsideModal(x, y, presented)) {
|
|
419
|
+
opts?.onModalMiss?.('outside');
|
|
420
|
+
return [];
|
|
421
|
+
}
|
|
422
|
+
|
|
372
423
|
const injected = opts?.hostAtPoint;
|
|
373
424
|
const locate = injected ?? (opts?.measure === undefined ? hostAtPoint : null);
|
|
374
425
|
if (locate) {
|
|
375
426
|
try {
|
|
376
|
-
const host = await locate(x, y);
|
|
427
|
+
const host = await locate(x, y, presented);
|
|
377
428
|
if (host && isFiberOnVisibleScreen(host)) {
|
|
378
|
-
const chain = candidateChain(host, registryMap(pool));
|
|
379
|
-
|
|
429
|
+
const chain = candidateChain(host, registryMap(pool), presented?.host);
|
|
430
|
+
if (chain.length === 0) return [];
|
|
431
|
+
const candidates = await candidatesFromChain(chain, measure);
|
|
432
|
+
return candidates.map((candidate) => candidateInWindow(candidate, presented));
|
|
380
433
|
}
|
|
381
434
|
} catch {
|
|
382
435
|
// Native hit-testing is optional; geometry still runs.
|
|
383
436
|
}
|
|
384
437
|
}
|
|
385
438
|
|
|
386
|
-
return geometryHits(x, y, pool, measure);
|
|
439
|
+
return geometryHits(x, y, pool, measure, presented);
|
|
387
440
|
}
|
|
388
441
|
|
|
389
442
|
async function geometryHits(
|
|
@@ -391,9 +444,10 @@ async function geometryHits(
|
|
|
391
444
|
y: number,
|
|
392
445
|
pool: AutoAnchorInstance[],
|
|
393
446
|
measure: MeasureInstance,
|
|
447
|
+
presented: PresentedModal | null,
|
|
394
448
|
): Promise<HitCandidate[]> {
|
|
395
449
|
const root = fiberScanRoot.getFiber();
|
|
396
|
-
const modal =
|
|
450
|
+
const modal = presented?.host ?? null;
|
|
397
451
|
const byFiber = registryMap(pool);
|
|
398
452
|
|
|
399
453
|
const scoped = modal
|
|
@@ -426,14 +480,35 @@ async function geometryHits(
|
|
|
426
480
|
if (!raw) return null;
|
|
427
481
|
const clipped = await clipCandidate({ target, rect: raw, area: areaOf(raw) });
|
|
428
482
|
if (!clipped) return null;
|
|
429
|
-
|
|
430
|
-
return
|
|
483
|
+
const placed = candidateInWindow(clipped, presented);
|
|
484
|
+
if (!pointInRect(x, y, placed.rect)) return null;
|
|
485
|
+
return placed;
|
|
431
486
|
}),
|
|
432
487
|
);
|
|
433
488
|
|
|
434
489
|
return measured.filter((c): c is HitCandidate => c != null).sort((a, b) => a.area - b.area);
|
|
435
490
|
}
|
|
436
491
|
|
|
492
|
+
/**
|
|
493
|
+
* `candidates` measured again, for when the screen may have moved under a
|
|
494
|
+
* selection: the keyboard opening or closing resizes or moves a sheet. Null
|
|
495
|
+
* when one can no longer be measured, or its modal can no longer be placed,
|
|
496
|
+
* so the caller drops the selection rather than keep a stale rect.
|
|
497
|
+
*/
|
|
498
|
+
export async function remeasureCandidates(
|
|
499
|
+
candidates: HitCandidate[],
|
|
500
|
+
measure: MeasureInstance = measureInstanceRect,
|
|
501
|
+
): Promise<HitCandidate[] | null> {
|
|
502
|
+
const presented = await locatePresentedModal(fiberScanRoot.getFiber());
|
|
503
|
+
if (presented && !presented.origin) return null;
|
|
504
|
+
const measured = await candidatesFromChain(
|
|
505
|
+
candidates.map((candidate) => candidate.target),
|
|
506
|
+
measure,
|
|
507
|
+
);
|
|
508
|
+
if (measured.length !== candidates.length) return null;
|
|
509
|
+
return measured.map((candidate) => candidateInWindow(candidate, presented));
|
|
510
|
+
}
|
|
511
|
+
|
|
437
512
|
/** Cycle to the next-larger containing candidate (wraps). */
|
|
438
513
|
export function nextContainingIndex(candidates: HitCandidate[], currentIndex: number): number {
|
|
439
514
|
if (candidates.length === 0) return -1;
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
* (see `FindNodeAtPointTest.overlappingViewsWithParentPointerEventsNone`).
|
|
12
12
|
* - Scroll views subtract `contentOffset` in `getContentOriginOffset`, so
|
|
13
13
|
* content scrolled out of the viewport is not a hit.
|
|
14
|
-
* -
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* - Content of a presented modal (`RNSModalScreen`, `ModalHostView`) lives in
|
|
15
|
+
* the same shadow tree, but Fabric lays it out relative to the modal. While
|
|
16
|
+
* one is presented, everything else is drawn beneath it, so only the modal is
|
|
17
|
+
* hit, with the point moved into its space (see `presentedModal.ts`).
|
|
17
18
|
* - Screens a navigator covers stay laid out in the shadow tree, on top of the
|
|
18
19
|
* visible one, and react-native-screens clears their zIndex, so the
|
|
19
20
|
* last-mounted sibling wins. After visiting a later tab, a tap on an earlier
|
|
@@ -33,7 +34,11 @@ import {
|
|
|
33
34
|
import { nearestHostDescendant } from '../anchors/fiber/findAnchorFiber';
|
|
34
35
|
import { createHostFiberIndex } from '../anchors/fiber/indexHostFibers';
|
|
35
36
|
import { measureHostFiber } from '../anchors/fiber/measureHostFiber';
|
|
36
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
locatePresentedModal,
|
|
39
|
+
type Point,
|
|
40
|
+
type PresentedModal,
|
|
41
|
+
} from '../anchors/fiber/presentedModal';
|
|
37
42
|
import { isFiberOnVisibleScreen, screensCoveringFiber } from '../anchors/fiber/visibleScreens';
|
|
38
43
|
|
|
39
44
|
const HIT_TIMEOUT_MS = 400;
|
|
@@ -167,11 +172,20 @@ function originInParent(
|
|
|
167
172
|
}, none);
|
|
168
173
|
}
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
/**
|
|
176
|
+
* `rootOrigin` is where `rootHost` sits in the point's space, for a root that
|
|
177
|
+
* `measureInWindow` cannot place: a presented modal, whose space is its own.
|
|
178
|
+
*/
|
|
179
|
+
async function hitFabric(
|
|
180
|
+
rootHost: AnyFiber,
|
|
181
|
+
x: number,
|
|
182
|
+
y: number,
|
|
183
|
+
rootOrigin?: Point,
|
|
184
|
+
): Promise<AnyFiber | null> {
|
|
171
185
|
const manager = fabricManager();
|
|
172
186
|
const node = shadowNodeFromHost(rootHost);
|
|
173
187
|
if (!manager || node == null) return null;
|
|
174
|
-
const origin = await measureOrigin(rootHost);
|
|
188
|
+
const origin = rootOrigin ?? (await measureOrigin(rootHost));
|
|
175
189
|
if (!origin) return null;
|
|
176
190
|
const inParent = await originInParent(manager, node);
|
|
177
191
|
const handle = await withTimeout<unknown>((settle) => {
|
|
@@ -199,25 +213,43 @@ async function hitPaper(rootHost: AnyFiber, x: number, y: number): Promise<AnyFi
|
|
|
199
213
|
return index.byNativeTag.get(nativeTag) ?? null;
|
|
200
214
|
}
|
|
201
215
|
|
|
202
|
-
|
|
203
|
-
|
|
216
|
+
/**
|
|
217
|
+
* `inModalSpace`: the point is in a presented modal's own space. Paper's
|
|
218
|
+
* lookup takes window points, so it would land elsewhere there (and
|
|
219
|
+
* bridgeless logs an error when it finds nothing).
|
|
220
|
+
*/
|
|
221
|
+
async function hitUnder(
|
|
222
|
+
rootHost: AnyFiber,
|
|
223
|
+
x: number,
|
|
224
|
+
y: number,
|
|
225
|
+
rootOrigin: Point | undefined,
|
|
226
|
+
inModalSpace: boolean,
|
|
227
|
+
): Promise<AnyFiber | null> {
|
|
228
|
+
const fabric = await hitFabric(rootHost, x, y, rootOrigin);
|
|
204
229
|
if (fabric) return fabric;
|
|
230
|
+
if (inModalSpace) return null;
|
|
205
231
|
return hitPaper(rootHost, x, y);
|
|
206
232
|
}
|
|
207
233
|
|
|
208
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* {@link hitUnder}, re-hit from the visible screen while the hit lands on a
|
|
236
|
+
* covered one. A `rootOrigin` means the point is in a presented modal's space;
|
|
237
|
+
* screens inside the modal measure in that space too.
|
|
238
|
+
*/
|
|
209
239
|
async function hitOnVisibleScreen(
|
|
210
240
|
rootHost: AnyFiber,
|
|
211
241
|
x: number,
|
|
212
242
|
y: number,
|
|
243
|
+
rootOrigin?: Point,
|
|
213
244
|
): Promise<AnyFiber | null> {
|
|
214
|
-
|
|
245
|
+
const inModalSpace = rootOrigin !== undefined;
|
|
246
|
+
let hit = await hitUnder(rootHost, x, y, rootOrigin, inModalSpace);
|
|
215
247
|
for (let attempt = 0; hit && attempt < MAX_COVERED_SCREEN_RETRIES; attempt += 1) {
|
|
216
248
|
const screens = screensCoveringFiber(hit);
|
|
217
249
|
if (screens.length === 0) return hit;
|
|
218
250
|
let next: AnyFiber | null = null;
|
|
219
251
|
for (const screen of screens) {
|
|
220
|
-
next = await hitUnder(screen, x, y);
|
|
252
|
+
next = await hitUnder(screen, x, y, undefined, inModalSpace);
|
|
221
253
|
if (next) break;
|
|
222
254
|
}
|
|
223
255
|
hit = next;
|
|
@@ -226,22 +258,36 @@ async function hitOnVisibleScreen(
|
|
|
226
258
|
return hit && isFiberOnVisibleScreen(hit) ? hit : null;
|
|
227
259
|
}
|
|
228
260
|
|
|
261
|
+
const MODAL_SPACE_ORIGIN: Point = { x: 0, y: 0 };
|
|
262
|
+
|
|
229
263
|
/**
|
|
230
264
|
* Host fiber drawn on top at window coordinates, or null when neither Fabric
|
|
231
265
|
* nor Paper hit-testing is available.
|
|
266
|
+
*
|
|
267
|
+
* While a modal is presented, only its content can be hit, and nothing is
|
|
268
|
+
* when the modal cannot be placed in the window. Pass `presented` when the
|
|
269
|
+
* caller has already looked it up.
|
|
232
270
|
*/
|
|
233
|
-
export async function hostAtPoint(
|
|
271
|
+
export async function hostAtPoint(
|
|
272
|
+
x: number,
|
|
273
|
+
y: number,
|
|
274
|
+
presented?: PresentedModal | null,
|
|
275
|
+
): Promise<AnyFiber | null> {
|
|
234
276
|
const root = fiberScanRoot.getFiber();
|
|
235
277
|
if (!root) return null;
|
|
236
|
-
const rootHost = nearestHostDescendant(root);
|
|
237
|
-
if (!rootHost) return null;
|
|
238
278
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
279
|
+
const modal = presented === undefined ? await locatePresentedModal(root) : presented;
|
|
280
|
+
if (modal) {
|
|
281
|
+
if (!modal.origin) return null;
|
|
282
|
+
return hitOnVisibleScreen(
|
|
283
|
+
modal.host,
|
|
284
|
+
x - modal.origin.x,
|
|
285
|
+
y - modal.origin.y,
|
|
286
|
+
modal.modalSpace ? MODAL_SPACE_ORIGIN : undefined,
|
|
287
|
+
);
|
|
245
288
|
}
|
|
246
|
-
|
|
289
|
+
|
|
290
|
+
const rootHost = nearestHostDescendant(root);
|
|
291
|
+
if (!rootHost) return null;
|
|
292
|
+
return hitOnVisibleScreen(rootHost, x, y);
|
|
247
293
|
}
|
|
@@ -293,7 +293,8 @@ function TextBlock({
|
|
|
293
293
|
| Heading1Element
|
|
294
294
|
| Heading2Element
|
|
295
295
|
| { align?: TextAlign | null; children: RichContentInlineNode[] };
|
|
296
|
-
|
|
296
|
+
// React Native 0.88's StyleSheet.flatten can return null or undefined.
|
|
297
|
+
style: TextStyle | null | undefined;
|
|
297
298
|
accessibilityRole?: 'header' | 'text';
|
|
298
299
|
}): React.ReactElement {
|
|
299
300
|
return (
|
|
@@ -576,15 +577,16 @@ function baseText(theme: ThemeFragment): TextStyle {
|
|
|
576
577
|
}
|
|
577
578
|
|
|
578
579
|
function markStyle(node: AbstractRichContentTextFormatting): TextStyle {
|
|
579
|
-
const style: TextStyle = {};
|
|
580
|
-
if (node.bold) style.fontWeight = '700';
|
|
581
|
-
if (node.italic) style.fontStyle = 'italic';
|
|
582
580
|
const decoration = textDecoration(node);
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
581
|
+
const fontSize = node.small ? 12 : node.sup ? 10 : undefined;
|
|
582
|
+
// Built in one literal: React Native 0.88 types TextStyle as readonly.
|
|
583
|
+
return {
|
|
584
|
+
...(node.bold ? { fontWeight: '700' as const } : null),
|
|
585
|
+
...(node.italic ? { fontStyle: 'italic' as const } : null),
|
|
586
|
+
...(decoration ? { textDecorationLine: decoration } : null),
|
|
587
|
+
...(fontSize != null ? { fontSize } : null),
|
|
588
|
+
...(node.color ? { color: node.color } : null),
|
|
589
|
+
};
|
|
588
590
|
}
|
|
589
591
|
|
|
590
592
|
/** View wrapper so inline code padding/radius work reliably on Android. */
|
package/src/deepLinking.ts
CHANGED
|
@@ -27,7 +27,8 @@ export interface DeepLinkingDeps {
|
|
|
27
27
|
startFlowWithToken: DeepLinkStartFlowWithToken;
|
|
28
28
|
/** Optional Linking overrides for tests. */
|
|
29
29
|
linking?: {
|
|
30
|
-
|
|
30
|
+
// React Native 0.88 adds undefined to the result.
|
|
31
|
+
getInitialURL: () => Promise<string | null | undefined>;
|
|
31
32
|
addEventListener: (
|
|
32
33
|
type: 'url',
|
|
33
34
|
handler: (event: { url: string }) => void,
|
package/src/deviceContext.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Resolve mobile platform metadata for Userflow
|
|
2
|
+
* Resolve mobile platform metadata and the running SDK version for Userflow
|
|
3
|
+
* user attributes.
|
|
3
4
|
*
|
|
4
5
|
* Cached once during {@link Userflow.init} and merged into identify attributes
|
|
5
6
|
* (same lifecycle as {@link hostAppVersion.ts}).
|
|
6
7
|
*/
|
|
7
8
|
|
|
9
|
+
import { SDK_VERSION } from './version';
|
|
10
|
+
|
|
8
11
|
export type SdkPlatform = 'react_native';
|
|
9
12
|
export type DeviceOs = 'ios' | 'android';
|
|
10
13
|
export type DeviceType = 'phone' | 'tablet';
|
|
11
14
|
|
|
12
15
|
export interface DeviceContext {
|
|
13
16
|
sdk: SdkPlatform;
|
|
17
|
+
/**
|
|
18
|
+
* Published `@userflow-tech/react-native` version, e.g. `0.4.6`. Always set by
|
|
19
|
+
* the SDK; optional so context objects built by host code (mocks) stay valid.
|
|
20
|
+
*/
|
|
21
|
+
sdkVersion?: string;
|
|
14
22
|
deviceOs?: DeviceOs;
|
|
15
23
|
deviceType?: DeviceType;
|
|
16
24
|
}
|
|
@@ -19,6 +27,7 @@ export interface DeviceContext {
|
|
|
19
27
|
export const ANDROID_TABLET_SMALLEST_WIDTH_DP = 600;
|
|
20
28
|
|
|
21
29
|
export const SDK_USER_ATTRIBUTE = 'sdk';
|
|
30
|
+
export const SDK_VERSION_USER_ATTRIBUTE = 'sdk_version';
|
|
22
31
|
export const DEVICE_OS_USER_ATTRIBUTE = 'device_os';
|
|
23
32
|
export const DEVICE_TYPE_USER_ATTRIBUTE = 'device_type';
|
|
24
33
|
|
|
@@ -70,6 +79,7 @@ export function resolveDeviceContextFromSources(sources: {
|
|
|
70
79
|
const deviceType = resolveDeviceTypeFromSources(sources);
|
|
71
80
|
return {
|
|
72
81
|
sdk: 'react_native',
|
|
82
|
+
sdkVersion: SDK_VERSION,
|
|
73
83
|
...(deviceOs ? { deviceOs } : {}),
|
|
74
84
|
...(deviceType ? { deviceType } : {}),
|
|
75
85
|
};
|
|
@@ -103,6 +113,7 @@ export function resolveDeviceContext(): DeviceContext {
|
|
|
103
113
|
|
|
104
114
|
/** `undefined` until the first {@link cacheDeviceContextAtInit}; then resolved. */
|
|
105
115
|
let cachedDeviceContext: DeviceContext | undefined;
|
|
116
|
+
let didWarnSdkVersionOverride = false;
|
|
106
117
|
|
|
107
118
|
/**
|
|
108
119
|
* Resolve and cache device metadata during {@link Userflow.init}.
|
|
@@ -118,33 +129,57 @@ export function getCachedDeviceContext(): DeviceContext | null {
|
|
|
118
129
|
return cachedDeviceContext ?? null;
|
|
119
130
|
}
|
|
120
131
|
|
|
121
|
-
/**
|
|
122
|
-
|
|
123
|
-
const cached = getCachedDeviceContext();
|
|
124
|
-
if (!cached) return {};
|
|
132
|
+
/** Device fields for UpsertUser that identify attributes may override. */
|
|
133
|
+
function overridableDeviceAttributes(context: DeviceContext): Record<string, string> {
|
|
125
134
|
return {
|
|
126
|
-
[SDK_USER_ATTRIBUTE]:
|
|
127
|
-
...(
|
|
128
|
-
...(
|
|
135
|
+
[SDK_USER_ATTRIBUTE]: context.sdk,
|
|
136
|
+
...(context.deviceOs ? { [DEVICE_OS_USER_ATTRIBUTE]: context.deviceOs } : {}),
|
|
137
|
+
...(context.deviceType ? { [DEVICE_TYPE_USER_ATTRIBUTE]: context.deviceType } : {}),
|
|
129
138
|
};
|
|
130
139
|
}
|
|
131
140
|
|
|
132
141
|
/** Optional user-attribute keys added by {@link mergeDeviceContextUserAttributes}. */
|
|
133
142
|
export type DeviceContextUserAttributeMap = Partial<{
|
|
134
143
|
[SDK_USER_ATTRIBUTE]: SdkPlatform;
|
|
144
|
+
[SDK_VERSION_USER_ATTRIBUTE]: string;
|
|
135
145
|
[DEVICE_OS_USER_ATTRIBUTE]: DeviceOs;
|
|
136
146
|
[DEVICE_TYPE_USER_ATTRIBUTE]: DeviceType;
|
|
137
147
|
}>;
|
|
138
148
|
|
|
139
|
-
/**
|
|
149
|
+
/** One development warning when identify attributes try to set `sdk_version`. */
|
|
150
|
+
function warnIfSdkVersionOverridden(attributes: Record<string, unknown> | undefined): void {
|
|
151
|
+
if (!__DEV__ || didWarnSdkVersionOverride) return;
|
|
152
|
+
const passed = attributes?.[SDK_VERSION_USER_ATTRIBUTE];
|
|
153
|
+
// `{ set: value }` is the long form of a bare literal.
|
|
154
|
+
const value =
|
|
155
|
+
typeof passed === 'object' && passed !== null && 'set' in passed
|
|
156
|
+
? (passed as { set: unknown }).set
|
|
157
|
+
: passed;
|
|
158
|
+
if (value === undefined || value === SDK_VERSION) return;
|
|
159
|
+
didWarnSdkVersionOverride = true;
|
|
160
|
+
// eslint-disable-next-line no-console -- intentional development diagnostic
|
|
161
|
+
console.warn(
|
|
162
|
+
`[Userflow] "${SDK_VERSION_USER_ATTRIBUTE}" is set by the SDK (${SDK_VERSION}); the value passed to identify / identifyAnonymous was ignored.`,
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merges cached device attrs into identify attributes. Caller values win on key
|
|
168
|
+
* conflicts, except `sdk_version`, which always reports the running SDK.
|
|
169
|
+
*/
|
|
140
170
|
export function mergeDeviceContextUserAttributes<T extends Record<string, unknown>>(
|
|
141
171
|
attributes?: T,
|
|
142
172
|
): (T & DeviceContextUserAttributeMap) | undefined {
|
|
143
|
-
const
|
|
144
|
-
if (
|
|
173
|
+
const cached = getCachedDeviceContext();
|
|
174
|
+
if (!cached) {
|
|
145
175
|
return attributes as (T & DeviceContextUserAttributeMap) | undefined;
|
|
146
176
|
}
|
|
147
|
-
|
|
177
|
+
warnIfSdkVersionOverridden(attributes);
|
|
178
|
+
return {
|
|
179
|
+
...overridableDeviceAttributes(cached),
|
|
180
|
+
...(attributes ?? {}),
|
|
181
|
+
[SDK_VERSION_USER_ATTRIBUTE]: SDK_VERSION,
|
|
182
|
+
} as T & DeviceContextUserAttributeMap;
|
|
148
183
|
}
|
|
149
184
|
|
|
150
185
|
export function formatDeviceContextLog(resolved: DeviceContext | null): string {
|
|
@@ -153,7 +188,7 @@ export function formatDeviceContextLog(resolved: DeviceContext | null): string {
|
|
|
153
188
|
}
|
|
154
189
|
const os = resolved.deviceOs ?? 'unknown';
|
|
155
190
|
const type = resolved.deviceType ?? 'unknown';
|
|
156
|
-
return `[Userflow] device context sdk=${resolved.sdk} device_os=${os} device_type=${type}`;
|
|
191
|
+
return `[Userflow] device context sdk=${resolved.sdk} sdk_version=${resolved.sdkVersion ?? 'unknown'} device_os=${os} device_type=${type}`;
|
|
157
192
|
}
|
|
158
193
|
|
|
159
194
|
/** Log cached user-attribute values. Requires {@link cacheDeviceContextAtInit}. */
|
|
@@ -162,7 +197,8 @@ export function logDeviceContext(): void {
|
|
|
162
197
|
console.log(formatDeviceContextLog(getCachedDeviceContext()));
|
|
163
198
|
}
|
|
164
199
|
|
|
165
|
-
/** @internal Resets init cache between tests. */
|
|
200
|
+
/** @internal Resets init cache and the one-time override warning between tests. */
|
|
166
201
|
export function resetCachedDeviceContextForTests(): void {
|
|
167
202
|
cachedDeviceContext = undefined;
|
|
203
|
+
didWarnSdkVersionOverride = false;
|
|
168
204
|
}
|
package/src/index.ts
CHANGED
|
@@ -368,7 +368,10 @@ export const Userflow = {
|
|
|
368
368
|
setOnNavigate(handler: FlowNavigateHandler | null | undefined): void {
|
|
369
369
|
setHostOnNavigate(handler);
|
|
370
370
|
},
|
|
371
|
-
/**
|
|
371
|
+
/**
|
|
372
|
+
* Identify the current user and upsert their attributes. The SDK adds `sdk_version`
|
|
373
|
+
* itself; a value passed for it here is ignored.
|
|
374
|
+
*/
|
|
372
375
|
async identify(
|
|
373
376
|
userId: string,
|
|
374
377
|
attributes?: UserflowAttributes,
|
|
@@ -391,7 +394,10 @@ export const Userflow = {
|
|
|
391
394
|
await core.identify(userId, mergeSystemUserAttributes(attributes), options);
|
|
392
395
|
await afterIdentify();
|
|
393
396
|
},
|
|
394
|
-
/**
|
|
397
|
+
/**
|
|
398
|
+
* Identify an anonymous user with a stable auto-generated id stored on device. Like
|
|
399
|
+
* {@link identify}, the SDK sets `sdk_version` itself.
|
|
400
|
+
*/
|
|
395
401
|
async identifyAnonymous(
|
|
396
402
|
attributes?: UserflowAttributes,
|
|
397
403
|
options?: UserflowIdentifyOptions,
|
|
@@ -478,8 +484,8 @@ export const Userflow = {
|
|
|
478
484
|
return getCachedHostAppVersion();
|
|
479
485
|
},
|
|
480
486
|
/**
|
|
481
|
-
* Platform metadata cached during {@link init} (`sdk`, `
|
|
482
|
-
* or `null` before init.
|
|
487
|
+
* Platform metadata cached during {@link init} (`sdk`, `sdkVersion`, `deviceOs`,
|
|
488
|
+
* `deviceType`), or `null` before init.
|
|
483
489
|
*/
|
|
484
490
|
getDeviceContext(): DeviceContext | null {
|
|
485
491
|
return getCachedDeviceContext();
|
|
@@ -576,7 +582,7 @@ export const Userflow = {
|
|
|
576
582
|
logHostAppVersionToConsole();
|
|
577
583
|
},
|
|
578
584
|
/**
|
|
579
|
-
* Log cached user-attribute values (`sdk`, `device_os`, `device_type`).
|
|
585
|
+
* Log cached user-attribute values (`sdk`, `sdk_version`, `device_os`, `device_type`).
|
|
580
586
|
* Requires {@link init}; before init, logs that init is required.
|
|
581
587
|
*/
|
|
582
588
|
logDeviceContext(): void {
|
package/src/installCheck.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const INSTALL_ASYNC_STORAGE_LINE =
|
|
|
10
10
|
'- @react-native-async-storage/async-storage is not linked. Anonymous users and Builder pairing will not persist. Add it to the app package.json (npx expo install @react-native-async-storage/async-storage), run pod install on iOS, and rebuild.';
|
|
11
11
|
|
|
12
12
|
export const INSTALL_VIEW_SHOT_LINE =
|
|
13
|
-
'- react-native-view-shot is not linked, so Builder element selection gets no screenshot. Install it and rebuild. On Expo SDK 56
|
|
13
|
+
'- react-native-view-shot is not linked, so Builder element selection gets no screenshot. Install it and rebuild. On Expo SDK 56 to 58, run npx expo install react-native-view-shot unpinned, with no expo.install.exclude entry for it. Otherwise install react-native-view-shot@^5.0.0; on Expo SDK 55 and older, an unpinned npx expo install resolves 4.0.3.';
|
|
14
14
|
|
|
15
15
|
export const INSTALL_WEBVIEW_LINE =
|
|
16
16
|
'- react-native-webview is not linked. Evaluate JavaScript and WebView banners will not run. Install it (npx expo install react-native-webview) and rebuild.';
|
|
@@ -36,7 +36,7 @@ export const INSTALL_SAFE_AREA_WARNING =
|
|
|
36
36
|
export function formatViewShotVersionWarning(version: string): string {
|
|
37
37
|
return (
|
|
38
38
|
`[Userflow] react-native-view-shot@${version} is older than 5.0.0, so Builder element selection may not get a screenshot. ` +
|
|
39
|
-
'On Expo SDK 56
|
|
39
|
+
'On Expo SDK 56 to 58, run npx expo install react-native-view-shot unpinned, with no expo.install.exclude entry for it, and rebuild. ' +
|
|
40
40
|
'Otherwise install react-native-view-shot@^5.0.0 in the same command as the SDK and rebuild; on Expo SDK 55 and older, also add "react-native-view-shot" to expo.install.exclude. ' +
|
|
41
41
|
'Do not retry the install with --force or --legacy-peer-deps; that leaves this version in place.\n' +
|
|
42
42
|
INSTALL_CHECK_FOOTER
|
|
@@ -89,7 +89,7 @@ function readViewShotVersion(): string | null {
|
|
|
89
89
|
}
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
/** Dev warning when view-shot 4.x is installed. Null on 5.x or when it is absent. */
|
|
92
|
+
/** Dev warning when view-shot 4.x is installed. Null on 5.x and later, or when it is absent. */
|
|
93
93
|
export function viewShotVersionWarning(): string | null {
|
|
94
94
|
const version = readViewShotVersion();
|
|
95
95
|
if (!version) return null;
|
package/src/lifecycle.ts
CHANGED
|
@@ -94,7 +94,8 @@ export class LifecycleController {
|
|
|
94
94
|
private readonly scheduler: LifecycleScheduler;
|
|
95
95
|
private readonly now: () => number;
|
|
96
96
|
private readonly suspendGraceMs: number;
|
|
97
|
-
|
|
97
|
+
/** Wider than AppStateStatus: React Native 0.88 types currentState as `string | null | undefined`. */
|
|
98
|
+
private readonly getCurrentAppState: () => string | null | undefined;
|
|
98
99
|
private readonly subscribeAppState: (handler: (status: AppStateStatus) => void) => () => void;
|
|
99
100
|
|
|
100
101
|
private unsubscribeAppState: (() => void) | null = null;
|
|
@@ -59,7 +59,8 @@ export interface PairingControllerOptions {
|
|
|
59
59
|
*/
|
|
60
60
|
cancelPendingDraftEnd?: () => void;
|
|
61
61
|
LinkingModule?: {
|
|
62
|
-
|
|
62
|
+
// React Native 0.88 adds undefined to the result.
|
|
63
|
+
getInitialURL(): Promise<string | null | undefined>;
|
|
63
64
|
addEventListener(type: 'url', handler: (event: { url: string }) => void): { remove(): void };
|
|
64
65
|
};
|
|
65
66
|
AppStateModule?: {
|