react-native-screen-transitions 3.9.0-alpha.3 → 3.9.0-beta.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.
Files changed (105) hide show
  1. package/lib/commonjs/shared/components/boundary/portal/components/portal-boundary-host.js +31 -1
  2. package/lib/commonjs/shared/components/boundary/portal/components/portal-boundary-host.js.map +1 -1
  3. package/lib/commonjs/shared/components/boundary/portal/components/portal.js +7 -43
  4. package/lib/commonjs/shared/components/boundary/portal/components/portal.js.map +1 -1
  5. package/lib/commonjs/shared/components/boundary/portal/hooks/use-placeholder-styles.js +57 -0
  6. package/lib/commonjs/shared/components/boundary/portal/hooks/use-placeholder-styles.js.map +1 -0
  7. package/lib/commonjs/shared/components/boundary/portal/utils/offset-style.js +4 -3
  8. package/lib/commonjs/shared/components/boundary/portal/utils/offset-style.js.map +1 -1
  9. package/lib/commonjs/shared/components/masked-view.js +5 -0
  10. package/lib/commonjs/shared/components/masked-view.js.map +1 -1
  11. package/lib/commonjs/shared/components/screen-container/layers/backdrop.js +18 -8
  12. package/lib/commonjs/shared/components/screen-container/layers/backdrop.js.map +1 -1
  13. package/lib/commonjs/shared/components/screen-container/layers/content.js +34 -16
  14. package/lib/commonjs/shared/components/screen-container/layers/content.js.map +1 -1
  15. package/lib/commonjs/shared/components/screen-container/layers/render-component.js +10 -0
  16. package/lib/commonjs/shared/components/screen-container/layers/render-component.js.map +1 -0
  17. package/lib/commonjs/shared/components/screen-container/layers/surface-container.js +5 -3
  18. package/lib/commonjs/shared/components/screen-container/layers/surface-container.js.map +1 -1
  19. package/lib/commonjs/shared/index.js +4 -0
  20. package/lib/commonjs/shared/index.js.map +1 -1
  21. package/lib/commonjs/shared/providers/register-bounds.provider.js +6 -0
  22. package/lib/commonjs/shared/providers/register-bounds.provider.js.map +1 -1
  23. package/lib/commonjs/shared/providers/screen/animation/helpers/read-screen-animation-revisions.js +6 -1
  24. package/lib/commonjs/shared/providers/screen/animation/helpers/read-screen-animation-revisions.js.map +1 -1
  25. package/lib/commonjs/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.js +50 -0
  26. package/lib/commonjs/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.js.map +1 -0
  27. package/lib/commonjs/shared/providers/screen/styles/hooks/use-interpolated-style-maps.js +7 -1
  28. package/lib/commonjs/shared/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  29. package/lib/module/shared/components/boundary/portal/components/portal-boundary-host.js +31 -1
  30. package/lib/module/shared/components/boundary/portal/components/portal-boundary-host.js.map +1 -1
  31. package/lib/module/shared/components/boundary/portal/components/portal.js +8 -44
  32. package/lib/module/shared/components/boundary/portal/components/portal.js.map +1 -1
  33. package/lib/module/shared/components/boundary/portal/hooks/use-placeholder-styles.js +52 -0
  34. package/lib/module/shared/components/boundary/portal/hooks/use-placeholder-styles.js.map +1 -0
  35. package/lib/module/shared/components/boundary/portal/utils/offset-style.js +4 -3
  36. package/lib/module/shared/components/boundary/portal/utils/offset-style.js.map +1 -1
  37. package/lib/module/shared/components/masked-view.js +5 -0
  38. package/lib/module/shared/components/masked-view.js.map +1 -1
  39. package/lib/module/shared/components/screen-container/layers/backdrop.js +19 -9
  40. package/lib/module/shared/components/screen-container/layers/backdrop.js.map +1 -1
  41. package/lib/module/shared/components/screen-container/layers/content.js +35 -17
  42. package/lib/module/shared/components/screen-container/layers/content.js.map +1 -1
  43. package/lib/module/shared/components/screen-container/layers/render-component.js +6 -0
  44. package/lib/module/shared/components/screen-container/layers/render-component.js.map +1 -0
  45. package/lib/module/shared/components/screen-container/layers/surface-container.js +5 -3
  46. package/lib/module/shared/components/screen-container/layers/surface-container.js.map +1 -1
  47. package/lib/module/shared/index.js +4 -0
  48. package/lib/module/shared/index.js.map +1 -1
  49. package/lib/module/shared/providers/register-bounds.provider.js +6 -0
  50. package/lib/module/shared/providers/register-bounds.provider.js.map +1 -1
  51. package/lib/module/shared/providers/screen/animation/helpers/read-screen-animation-revisions.js +6 -1
  52. package/lib/module/shared/providers/screen/animation/helpers/read-screen-animation-revisions.js.map +1 -1
  53. package/lib/module/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.js +45 -0
  54. package/lib/module/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.js.map +1 -0
  55. package/lib/module/shared/providers/screen/styles/hooks/use-interpolated-style-maps.js +7 -1
  56. package/lib/module/shared/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  57. package/lib/typescript/component-stack/types.d.ts +26 -0
  58. package/lib/typescript/component-stack/types.d.ts.map +1 -1
  59. package/lib/typescript/shared/components/boundary/portal/components/portal-boundary-host.d.ts.map +1 -1
  60. package/lib/typescript/shared/components/boundary/portal/components/portal.d.ts.map +1 -1
  61. package/lib/typescript/shared/components/boundary/portal/hooks/use-placeholder-styles.d.ts +17 -0
  62. package/lib/typescript/shared/components/boundary/portal/hooks/use-placeholder-styles.d.ts.map +1 -0
  63. package/lib/typescript/shared/components/boundary/portal/utils/offset-style.d.ts +10 -1
  64. package/lib/typescript/shared/components/boundary/portal/utils/offset-style.d.ts.map +1 -1
  65. package/lib/typescript/shared/components/masked-view.d.ts +4 -0
  66. package/lib/typescript/shared/components/masked-view.d.ts.map +1 -1
  67. package/lib/typescript/shared/components/screen-container/layers/backdrop.d.ts.map +1 -1
  68. package/lib/typescript/shared/components/screen-container/layers/content.d.ts.map +1 -1
  69. package/lib/typescript/shared/components/screen-container/layers/render-component.d.ts +3 -0
  70. package/lib/typescript/shared/components/screen-container/layers/render-component.d.ts.map +1 -0
  71. package/lib/typescript/shared/components/screen-container/layers/surface-container.d.ts.map +1 -1
  72. package/lib/typescript/shared/index.d.ts +37 -33
  73. package/lib/typescript/shared/index.d.ts.map +1 -1
  74. package/lib/typescript/shared/providers/register-bounds.provider.d.ts.map +1 -1
  75. package/lib/typescript/shared/providers/screen/animation/helpers/read-screen-animation-revisions.d.ts +4 -1
  76. package/lib/typescript/shared/providers/screen/animation/helpers/read-screen-animation-revisions.d.ts.map +1 -1
  77. package/lib/typescript/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.d.ts +4 -0
  78. package/lib/typescript/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.d.ts.map +1 -0
  79. package/lib/typescript/shared/providers/screen/styles/hooks/use-interpolated-style-maps.d.ts.map +1 -1
  80. package/lib/typescript/shared/providers/screen/styles/hooks/use-maybe-block-visibility.d.ts +1 -1
  81. package/lib/typescript/shared/types/index.d.ts +1 -1
  82. package/lib/typescript/shared/types/index.d.ts.map +1 -1
  83. package/lib/typescript/shared/types/screen.types.d.ts +99 -4
  84. package/lib/typescript/shared/types/screen.types.d.ts.map +1 -1
  85. package/lib/typescript/shared/utils/bounds/types/options.d.ts +2 -0
  86. package/lib/typescript/shared/utils/bounds/types/options.d.ts.map +1 -1
  87. package/package.json +1 -1
  88. package/src/component-stack/types.ts +26 -0
  89. package/src/shared/components/boundary/portal/components/portal-boundary-host.tsx +51 -0
  90. package/src/shared/components/boundary/portal/components/portal.tsx +7 -39
  91. package/src/shared/components/boundary/portal/hooks/use-placeholder-styles.ts +55 -0
  92. package/src/shared/components/boundary/portal/utils/offset-style.ts +9 -2
  93. package/src/shared/components/masked-view.tsx +4 -0
  94. package/src/shared/components/screen-container/layers/backdrop.tsx +31 -10
  95. package/src/shared/components/screen-container/layers/content.tsx +61 -22
  96. package/src/shared/components/screen-container/layers/render-component.ts +9 -0
  97. package/src/shared/components/screen-container/layers/surface-container.tsx +5 -4
  98. package/src/shared/index.ts +9 -0
  99. package/src/shared/providers/register-bounds.provider.tsx +5 -0
  100. package/src/shared/providers/screen/animation/helpers/read-screen-animation-revisions.ts +14 -0
  101. package/src/shared/providers/screen/styles/helpers/collect-interpolator-shared-values.ts +75 -0
  102. package/src/shared/providers/screen/styles/hooks/use-interpolated-style-maps.tsx +11 -0
  103. package/src/shared/types/index.ts +5 -0
  104. package/src/shared/types/screen.types.ts +111 -4
  105. package/src/shared/utils/bounds/types/options.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-screen-transitions",
3
- "version": "3.9.0-alpha.3",
3
+ "version": "3.9.0-beta.1",
4
4
  "description": "Easy screen transitions for React Native and Expo",
5
5
  "author": "Ed",
6
6
  "license": "MIT",
@@ -13,8 +13,14 @@ import type {
13
13
  import type { ScreenTransitionConfig } from "../shared";
14
14
  import type { OverlayProps } from "../shared/types/overlay.types";
15
15
 
16
+ /**
17
+ * @deprecated Component stack is deprecated. Use blank stack instead.
18
+ */
16
19
  export type ComponentStackNavigationEventMap = {};
17
20
 
21
+ /**
22
+ * @deprecated Component stack is deprecated. Use blank stack instead.
23
+ */
18
24
  export type ComponentStackNavigationProp<
19
25
  ParamList extends ParamListBase,
20
26
  RouteName extends keyof ParamList = string,
@@ -29,6 +35,9 @@ export type ComponentStackNavigationProp<
29
35
  > &
30
36
  StackActionHelpers<ParamList>;
31
37
 
38
+ /**
39
+ * @deprecated Component stack is deprecated. Use blank stack instead.
40
+ */
32
41
  export type ComponentStackScreenProps<
33
42
  ParamList extends ParamListBase,
34
43
  RouteName extends keyof ParamList = string,
@@ -38,6 +47,9 @@ export type ComponentStackScreenProps<
38
47
  route: RouteProp<ParamList, RouteName>;
39
48
  };
40
49
 
50
+ /**
51
+ * @deprecated Component stack is deprecated. Use blank stack instead.
52
+ */
41
53
  export type ComponentStackOptionsArgs<
42
54
  ParamList extends ParamListBase,
43
55
  RouteName extends keyof ParamList = keyof ParamList,
@@ -46,6 +58,9 @@ export type ComponentStackOptionsArgs<
46
58
  theme: Theme;
47
59
  };
48
60
 
61
+ /**
62
+ * @deprecated Component stack is deprecated. Use blank stack instead.
63
+ */
49
64
  export type ComponentStackNavigationHelpers = NavigationHelpers<
50
65
  ParamListBase,
51
66
  ComponentStackNavigationEventMap
@@ -56,6 +71,8 @@ type ComponentStackNavigationConfig = {};
56
71
  /**
57
72
  * Props passed to overlay components in component-stack.
58
73
  * Uses the shared OverlayProps type with component-stack's navigation type.
74
+ *
75
+ * @deprecated Component stack is deprecated. Use blank stack instead.
59
76
  */
60
77
  export type ComponentStackOverlayProps = OverlayProps<
61
78
  ComponentStackNavigationProp<ParamListBase>
@@ -70,9 +87,15 @@ type ComponentStackScreenTransitionConfig = ScreenTransitionConfig & {
70
87
  detachPreviousScreen?: boolean;
71
88
  };
72
89
 
90
+ /**
91
+ * @deprecated Component stack is deprecated. Use blank stack instead.
92
+ */
73
93
  export type ComponentStackNavigationOptions =
74
94
  ComponentStackScreenTransitionConfig & {};
75
95
 
96
+ /**
97
+ * @deprecated Component stack is deprecated. Use blank stack instead.
98
+ */
76
99
  export type ComponentStackNavigatorProps = DefaultNavigatorOptions<
77
100
  ParamListBase,
78
101
  string | undefined,
@@ -84,6 +107,9 @@ export type ComponentStackNavigatorProps = DefaultNavigatorOptions<
84
107
  StackRouterOptions &
85
108
  ComponentStackNavigationConfig;
86
109
 
110
+ /**
111
+ * @deprecated Component stack is deprecated. Use blank stack instead.
112
+ */
87
113
  export type ComponentStackDescriptor = Descriptor<
88
114
  ComponentStackNavigationOptions,
89
115
  ComponentStackNavigationProp<ParamListBase>,
@@ -3,7 +3,13 @@ import { type StyleProp, StyleSheet, type ViewStyle } from "react-native";
3
3
  import Animated, { useAnimatedStyle } from "react-native-reanimated";
4
4
  import { NO_STYLES } from "../../../../constants";
5
5
  import { composeSlotStyleWithLocalTransform } from "../../../../providers/screen/styles/helpers/compose-slot-style";
6
+ import { AnimationStore } from "../../../../stores/animation.store";
7
+ import { getSourceScreenKeyFromPairKey } from "../../../../stores/bounds/helpers/link-pairs.helpers";
6
8
  import { getLink } from "../../../../stores/bounds/internals/links";
9
+ import {
10
+ getClampedScrollAxisDelta,
11
+ ScrollStore,
12
+ } from "../../../../stores/scroll.store";
7
13
  import type { ScrollMeasuredDimensions } from "../../utils/measured-bounds";
8
14
  import type { ActivePortalBoundaryHost } from "../stores/portal-boundary-host.store";
9
15
  import { NativePortalHost } from "../teleport";
@@ -23,6 +29,20 @@ export const PortalBoundaryHost = memo(function PortalBoundaryHost({
23
29
  host,
24
30
  style,
25
31
  }: PortalBoundaryHostProps) {
32
+ // Cross-screen landing-rect scroll tracking: the flight interpolates toward
33
+ // the source rect stored at measure time, but the source screen stays
34
+ // scrollable while the pair closes (destination pointer events are
35
+ // released). These are read per-frame so the landing point follows the
36
+ // live source scroll instead of the stale snapshot.
37
+ const sourceScrollMetadata = ScrollStore.getValue(
38
+ getSourceScreenKeyFromPairKey(host.pairKey),
39
+ "metadata",
40
+ );
41
+ const hostVisualProgress = AnimationStore.getValue(
42
+ host.screenKey,
43
+ "visualProgress",
44
+ );
45
+
26
46
  const hostStyle = useAnimatedStyle(() => {
27
47
  "worklet";
28
48
  // Strict per-member lookup — a fallback member's source rect would
@@ -35,10 +55,41 @@ export const PortalBoundaryHost = memo(function PortalBoundaryHost({
35
55
  const sourceBounds = link.source.bounds as ScrollMeasuredDimensions;
36
56
  const isCrossScreenPortal = link.source.screenKey !== host.screenKey;
37
57
 
58
+ let landingShift: { x: number; y: number } | undefined;
59
+
60
+ if (isCrossScreenPortal) {
61
+ // Weight by distance-to-landing so the destination end of the flight
62
+ // stays pinned: no correction while the content sits over the
63
+ // destination rect (progress 1), full correction at touchdown on the
64
+ // source rect (progress 0).
65
+ const landingWeight =
66
+ 1 - Math.min(Math.max(hostVisualProgress.get(), 0), 1);
67
+
68
+ if (landingWeight > 0) {
69
+ const liveScroll = sourceScrollMetadata.get();
70
+ const capturedScroll = sourceBounds.scroll ?? null;
71
+
72
+ // Scroll offset grows as content moves up/left, so the rect's
73
+ // on-screen position shifts by the negative delta.
74
+ landingShift = {
75
+ x:
76
+ -getClampedScrollAxisDelta(
77
+ liveScroll,
78
+ capturedScroll,
79
+ "horizontal",
80
+ ) * landingWeight,
81
+ y:
82
+ -getClampedScrollAxisDelta(liveScroll, capturedScroll, "vertical") *
83
+ landingWeight,
84
+ };
85
+ }
86
+ }
87
+
38
88
  return resolvePortalOffsetStyle({
39
89
  bounds: sourceBounds,
40
90
  hostKey: host.hostKey,
41
91
  placement: isCrossScreenPortal ? "cross-screen" : "same-screen",
92
+ landingShift,
42
93
  });
43
94
  });
44
95
  const contentFrameStyle = useAnimatedStyle(() => {
@@ -7,14 +7,13 @@ import {
7
7
  useLayoutEffect,
8
8
  useState,
9
9
  } from "react";
10
- import type { LayoutRectangle, View } from "react-native";
10
+ import type { View } from "react-native";
11
11
  import Animated, {
12
12
  type AnimatedRef,
13
13
  runOnJS,
14
14
  runOnUI,
15
15
  useAnimatedProps,
16
16
  useAnimatedReaction,
17
- useAnimatedStyle,
18
17
  useSharedValue,
19
18
  } from "react-native-reanimated";
20
19
  import { EPSILON } from "../../../../constants";
@@ -24,6 +23,7 @@ import { useRegisteredScreenSlots } from "../../../../providers/screen/styles/st
24
23
  import { AnimationStore } from "../../../../stores/animation.store";
25
24
  import { pairs } from "../../../../stores/bounds/internals/state";
26
25
  import { logger } from "../../../../utils/logger";
26
+ import { usePlaceholderStyles } from "../hooks/use-placeholder-styles";
27
27
  import { useActiveHostKey } from "../stores/host-registry.store";
28
28
  import {
29
29
  dropStalePortalBoundaryHosts,
@@ -86,6 +86,7 @@ export const Portal = memo(function Portal({
86
86
  // (the `return children` path below).
87
87
  const isPortalEnabled =
88
88
  (handoff || escapeClipping) && isTeleportAvailable && id !== undefined;
89
+
89
90
  if (__DEV__ && (handoff || escapeClipping) && id === undefined) {
90
91
  logger.warnOnce(
91
92
  "portal:missing-id",
@@ -103,6 +104,7 @@ export const Portal = memo(function Portal({
103
104
  PortalOwnershipSignal,
104
105
  { status: "complete" }
105
106
  > | null>(null);
107
+
106
108
  const styleOwnerScreenKey = ownership?.ownerScreenKey ?? currentScreenKey;
107
109
  const ownerScreenSlots = useRegisteredScreenSlots(styleOwnerScreenKey);
108
110
  const activeScreenSlots = ownerScreenSlots ?? ownScreenSlots;
@@ -114,8 +116,6 @@ export const Portal = memo(function Portal({
114
116
  const requestedPortalHostName = useSharedValue<string | null>(null);
115
117
  const visiblePortalHostName = useSharedValue<string | null>(null);
116
118
  const canSwitchPortalHostImmediately = useSharedValue(0);
117
- const placeholderWidth = useSharedValue(0);
118
- const placeholderHeight = useSharedValue(0);
119
119
 
120
120
  const targetScreenKey = ownership ? ownership.hostScreenKey : null;
121
121
  const settledHostScreenKey = ownership?.hostScreenKey ?? null;
@@ -386,42 +386,10 @@ export const Portal = memo(function Portal({
386
386
  };
387
387
  });
388
388
 
389
- // Pin the placeholder to its measured size while content lives in the host,
390
- // in the same UI frame the host name flips — no commit in between. Until the
391
- // first layout lands (dims 0) sizing stays natural so an instant attach
392
- // cannot collapse the slot.
393
- const placeholderStyle = useAnimatedStyle(() => {
394
- "worklet";
395
- const isAttached = visiblePortalHostName.get() !== null;
396
- const width = placeholderWidth.get();
397
- const height = placeholderHeight.get();
398
-
399
- if (!isAttached || width === 0) {
400
- return { width: "auto", height: "auto" } as const;
401
- }
402
-
403
- return { width, height };
389
+ const { handleOnLayout, placeholderStyle } = usePlaceholderStyles({
390
+ visiblePortalHostName,
404
391
  });
405
392
 
406
- const handleOnLayout = useCallback(
407
- (layout: LayoutRectangle) => {
408
- "worklet";
409
- const { width, height } = layout;
410
- if (width === 0 || height === 0) {
411
- return;
412
- }
413
-
414
- const hasValidDimensions =
415
- placeholderHeight.get() !== 0 && placeholderWidth.get() !== 0;
416
-
417
- if (!hasValidDimensions) {
418
- placeholderWidth.set(width);
419
- placeholderHeight.set(height);
420
- }
421
- },
422
- [placeholderHeight, placeholderWidth],
423
- );
424
-
425
393
  if (isPortalEnabled && AnimatedNativePortal) {
426
394
  return (
427
395
  <Animated.View
@@ -434,7 +402,7 @@ export const Portal = memo(function Portal({
434
402
  >
435
403
  {placeholderChildren}
436
404
  <AnimatedNativePortal animatedProps={teleportProps} name={boundaryId}>
437
- <Animated.View style={placeholderStyle}>{children}</Animated.View>
405
+ {children}
438
406
  </AnimatedNativePortal>
439
407
  </Animated.View>
440
408
  );
@@ -0,0 +1,55 @@
1
+ import { useCallback } from "react";
2
+ import type { LayoutRectangle } from "react-native";
3
+ import {
4
+ type SharedValue,
5
+ useAnimatedStyle,
6
+ useSharedValue,
7
+ } from "react-native-reanimated";
8
+
9
+ interface UsePlaceholderProps {
10
+ visiblePortalHostName: SharedValue<string | null>;
11
+ }
12
+
13
+ export const usePlaceholderStyles = ({
14
+ visiblePortalHostName,
15
+ }: UsePlaceholderProps) => {
16
+ const placeholderWidth = useSharedValue(0);
17
+ const placeholderHeight = useSharedValue(0);
18
+ // Pin the placeholder to its measured size while content lives in the host,
19
+ // in the same UI frame the host name flips — no commit in between. Until the
20
+ // first layout lands (dims 0) sizing stays natural so an instant attach
21
+ // cannot collapse the slot.
22
+ const placeholderStyle = useAnimatedStyle(() => {
23
+ "worklet";
24
+ const isAttached = visiblePortalHostName.get() !== null;
25
+ const width = placeholderWidth.get();
26
+ const height = placeholderHeight.get();
27
+
28
+ if (!isAttached || width === 0) {
29
+ return { width: "auto", height: "auto" } as const;
30
+ }
31
+
32
+ return { width, height };
33
+ });
34
+
35
+ const handleOnLayout = useCallback(
36
+ (layout: LayoutRectangle) => {
37
+ "worklet";
38
+ const { width, height } = layout;
39
+ if (width === 0 || height === 0) {
40
+ return;
41
+ }
42
+
43
+ const hasValidDimensions =
44
+ placeholderHeight.get() !== 0 && placeholderWidth.get() !== 0;
45
+
46
+ if (!hasValidDimensions) {
47
+ placeholderWidth.set(width);
48
+ placeholderHeight.set(height);
49
+ }
50
+ },
51
+ [placeholderHeight, placeholderWidth],
52
+ );
53
+
54
+ return { placeholderStyle, handleOnLayout };
55
+ };
@@ -19,12 +19,19 @@ type ResolvePortalOffsetStyleParams = {
19
19
  bounds: ScrollMeasuredDimensions;
20
20
  hostKey: string;
21
21
  placement: PortalOffsetPlacement;
22
+ /**
23
+ * Live correction applied on top of the stored source placement. Used by
24
+ * cross-screen hosts to keep the landing rect tracking the source screen's
25
+ * scroll while the source is interactive mid-flight.
26
+ */
27
+ landingShift?: { x: number; y: number };
22
28
  };
23
29
 
24
30
  export const resolvePortalOffsetStyle = ({
25
31
  bounds,
26
32
  hostKey,
27
33
  placement,
34
+ landingShift,
28
35
  }: ResolvePortalOffsetStyleParams): StyleProps => {
29
36
  "worklet";
30
37
  const hostBounds = getPortalHostBounds(hostKey);
@@ -62,8 +69,8 @@ export const resolvePortalOffsetStyle = ({
62
69
 
63
70
  return {
64
71
  transform: [
65
- { translateY: bounds.pageY - adjustedHostPageY },
66
- { translateX: bounds.pageX - adjustedHostPageX },
72
+ { translateY: bounds.pageY - adjustedHostPageY + (landingShift?.y ?? 0) },
73
+ { translateX: bounds.pageX - adjustedHostPageX + (landingShift?.x ?? 0) },
67
74
  ],
68
75
  };
69
76
  };
@@ -12,6 +12,10 @@ try {
12
12
  // noop
13
13
  }
14
14
 
15
+ /**
16
+ * @deprecated Use `navigationMaskEnabled` with the navigation mask style IDs
17
+ * instead.
18
+ */
15
19
  export default function MaskedView({
16
20
  children,
17
21
  style: userStyles = {},
@@ -1,4 +1,4 @@
1
- import { memo, useCallback, useMemo } from "react";
1
+ import { type ComponentType, memo, useCallback, useMemo } from "react";
2
2
  import { Pressable, StyleSheet } from "react-native";
3
3
  import Animated, { runOnUI } from "react-native-reanimated";
4
4
  import { DefaultSnapSpec } from "../../../configs/specs";
@@ -8,9 +8,13 @@ import { useSlotProps, useSlotStyles } from "../../../providers/screen/styles";
8
8
  import { AnimationStore } from "../../../stores/animation.store";
9
9
  import { GestureStore } from "../../../stores/gesture.store";
10
10
  import { SystemStore } from "../../../stores/system.store";
11
- import type { BackdropBehavior } from "../../../types/screen.types";
11
+ import type {
12
+ BackdropBehavior,
13
+ ScreenBackdropComponentProps,
14
+ } from "../../../types/screen.types";
12
15
  import { animateToProgress } from "../../../utils/animation/animate-to-progress";
13
16
  import { findCollapseTarget } from "../helpers/find-collapse-target";
17
+ import { usesLayerRenderProps } from "./render-component";
14
18
 
15
19
  export const BackdropLayer = memo(function BackdropLayer({
16
20
  backdropBehavior,
@@ -30,8 +34,10 @@ export const BackdropLayer = memo(function BackdropLayer({
30
34
 
31
35
  const AnimatedBackdropComponent = useMemo(
32
36
  () =>
33
- BackdropComponent
34
- ? Animated.createAnimatedComponent(BackdropComponent)
37
+ BackdropComponent && !usesLayerRenderProps(BackdropComponent)
38
+ ? Animated.createAnimatedComponent(
39
+ BackdropComponent as ComponentType<any>,
40
+ )
35
41
  : null,
36
42
  [BackdropComponent],
37
43
  );
@@ -108,6 +114,13 @@ export const BackdropLayer = memo(function BackdropLayer({
108
114
 
109
115
  const animatedBackdropStyle = useSlotStyles("backdrop");
110
116
  const animatedBackdropProps = useSlotProps("backdrop");
117
+ const backdropPointerEvents = isBackdropActive ? "auto" : "none";
118
+ const backdropStyles = [
119
+ StyleSheet.absoluteFill,
120
+ animatedBackdropStyle,
121
+ ] as ScreenBackdropComponentProps["styles"];
122
+ const backdropProps =
123
+ animatedBackdropProps as ScreenBackdropComponentProps["props"];
111
124
 
112
125
  return (
113
126
  <Pressable
@@ -115,16 +128,24 @@ export const BackdropLayer = memo(function BackdropLayer({
115
128
  pointerEvents={isBackdropActive ? "auto" : "none"}
116
129
  onPress={isBackdropActive ? handleBackdropPress : undefined}
117
130
  >
118
- {/* Keep blur props and visual style separated.
119
- * BlurView's animatable ref points at the inner native blur view, and mixing
120
- * animated style with animatedProps can break intensity updates. */}
121
- {AnimatedBackdropComponent && (
131
+ {AnimatedBackdropComponent ? (
122
132
  <AnimatedBackdropComponent
123
- style={[StyleSheet.absoluteFill]}
133
+ style={backdropStyles}
124
134
  animatedProps={animatedBackdropProps}
135
+ pointerEvents={backdropPointerEvents}
136
+ />
137
+ ) : BackdropComponent ? (
138
+ <BackdropComponent
139
+ styles={backdropStyles}
140
+ props={backdropProps}
141
+ pointerEvents={backdropPointerEvents}
142
+ />
143
+ ) : (
144
+ <Animated.View
145
+ style={backdropStyles}
146
+ pointerEvents={backdropPointerEvents}
125
147
  />
126
148
  )}
127
- <Animated.View style={[StyleSheet.absoluteFill, animatedBackdropStyle]} />
128
149
  </Pressable>
129
150
  );
130
151
  });
@@ -1,5 +1,5 @@
1
1
  /** biome-ignore-all lint/style/noNonNullAssertion: <Screen gesture is under the gesture context, so this will always exist.> */
2
- import { memo } from "react";
2
+ import { type ComponentType, memo, useMemo } from "react";
3
3
  import { StyleSheet, View } from "react-native";
4
4
  import { GestureDetector } from "react-native-gesture-handler";
5
5
  import Animated from "react-native-reanimated";
@@ -7,9 +7,11 @@ import { useDescriptors } from "../../../providers/screen/descriptors";
7
7
  import { useGestureContext } from "../../../providers/screen/gestures";
8
8
  import { OriginProvider } from "../../../providers/screen/origin.provider";
9
9
  import { useSlotProps, useSlotStyles } from "../../../providers/screen/styles";
10
+ import type { ScreenContentComponentProps } from "../../../types";
10
11
  import { ScreenFallbackHost } from "../../boundary/portal/components/host";
11
12
  import { useContentLayout } from "../hooks/use-content-layout";
12
13
  import { MaybeMaskedNavigationContainer } from "./maybe-masked-navigation-container";
14
+ import { usesLayerRenderProps } from "./render-component";
13
15
  import { SurfaceContainer } from "./surface-container";
14
16
 
15
17
  type Props = {
@@ -23,9 +25,18 @@ export const ContentLayer = memo(
23
25
  const { current } = useDescriptors();
24
26
 
25
27
  const gestureContext = useGestureContext();
28
+ const ContentComponent = current.options.contentComponent;
26
29
  const isNavigationMaskEnabled = !!current.options.navigationMaskEnabled;
27
30
  const contentPointerEvents = isBackdropActive ? "box-none" : pointerEvents;
28
31
 
32
+ const AnimatedContentComponent = useMemo(() => {
33
+ return ContentComponent && !usesLayerRenderProps(ContentComponent)
34
+ ? Animated.createAnimatedComponent(
35
+ ContentComponent as ComponentType<any>,
36
+ )
37
+ : null;
38
+ }, [ContentComponent]);
39
+
29
40
  const hasAutoSnapPoint =
30
41
  current.options.snapPoints?.includes("auto") ?? false;
31
42
 
@@ -33,32 +44,60 @@ export const ContentLayer = memo(
33
44
 
34
45
  const animatedContentStyle = useSlotStyles("content");
35
46
  const animatedContentProps = useSlotProps("content");
47
+ const contentStyles = [
48
+ styles.content,
49
+ animatedContentStyle,
50
+ ] as ScreenContentComponentProps["styles"];
51
+ const contentProps =
52
+ animatedContentProps as ScreenContentComponentProps["props"];
53
+
54
+ const contentChildren = (
55
+ <MaybeMaskedNavigationContainer
56
+ pointerEvents={contentPointerEvents}
57
+ enabled={isNavigationMaskEnabled}
58
+ >
59
+ <SurfaceContainer pointerEvents={contentPointerEvents}>
60
+ <OriginProvider>
61
+ {hasAutoSnapPoint ? (
62
+ <View collapsable={false} onLayout={handleContentLayout}>
63
+ {children}
64
+ </View>
65
+ ) : (
66
+ children
67
+ )}
68
+ <ScreenFallbackHost />
69
+ </OriginProvider>
70
+ </SurfaceContainer>
71
+ </MaybeMaskedNavigationContainer>
72
+ );
36
73
 
37
74
  return (
38
75
  <GestureDetector gesture={gestureContext!.detectorGesture}>
39
- <Animated.View
40
- style={[styles.content, animatedContentStyle]}
41
- animatedProps={animatedContentProps}
42
- pointerEvents={contentPointerEvents}
43
- >
44
- <MaybeMaskedNavigationContainer
76
+ {AnimatedContentComponent ? (
77
+ <AnimatedContentComponent
78
+ style={contentStyles}
79
+ animatedProps={animatedContentProps}
80
+ pointerEvents={contentPointerEvents}
81
+ >
82
+ {contentChildren}
83
+ </AnimatedContentComponent>
84
+ ) : ContentComponent ? (
85
+ <ContentComponent
86
+ styles={contentStyles}
87
+ props={contentProps}
88
+ pointerEvents={contentPointerEvents}
89
+ >
90
+ {contentChildren}
91
+ </ContentComponent>
92
+ ) : (
93
+ <Animated.View
94
+ style={contentStyles}
95
+ animatedProps={animatedContentProps}
45
96
  pointerEvents={contentPointerEvents}
46
- enabled={isNavigationMaskEnabled}
47
97
  >
48
- <SurfaceContainer pointerEvents={contentPointerEvents}>
49
- <OriginProvider>
50
- {hasAutoSnapPoint ? (
51
- <View collapsable={false} onLayout={handleContentLayout}>
52
- {children}
53
- </View>
54
- ) : (
55
- children
56
- )}
57
- <ScreenFallbackHost />
58
- </OriginProvider>
59
- </SurfaceContainer>
60
- </MaybeMaskedNavigationContainer>
61
- </Animated.View>
98
+ {contentChildren}
99
+ </Animated.View>
100
+ )}
62
101
  </GestureDetector>
63
102
  );
64
103
  },
@@ -0,0 +1,9 @@
1
+ import type { ComponentType } from "react";
2
+
3
+ export function usesLayerRenderProps(
4
+ component: ComponentType<any>,
5
+ ): component is ComponentType<Record<string, unknown>> {
6
+ return (
7
+ typeof component === "function" && !component.prototype?.isReactComponent
8
+ );
9
+ }
@@ -12,13 +12,14 @@ type Props = {
12
12
  export const SurfaceContainer = memo(({ children, pointerEvents }: Props) => {
13
13
  const { current } = useDescriptors();
14
14
 
15
- const SurfaceComponent = current.options.surfaceComponent;
15
+ /** @deprecated Use `contentComponent` instead. */
16
+ const DeprecatedSurfaceComponent = current.options.surfaceComponent;
16
17
 
17
18
  const AnimatedSurfaceComponent = useMemo<ComponentType<any> | null>(() => {
18
- return SurfaceComponent
19
- ? Animated.createAnimatedComponent(SurfaceComponent)
19
+ return DeprecatedSurfaceComponent
20
+ ? Animated.createAnimatedComponent(DeprecatedSurfaceComponent)
20
21
  : null;
21
- }, [SurfaceComponent]);
22
+ }, [DeprecatedSurfaceComponent]);
22
23
 
23
24
  if (!AnimatedSurfaceComponent) return children;
24
25
 
@@ -18,6 +18,10 @@ export default {
18
18
  FlatList: createTransitionAwareComponent(FlatList, {
19
19
  isScrollable: true,
20
20
  }),
21
+ /**
22
+ * @deprecated Use `navigationMaskEnabled` with the navigation mask style IDs
23
+ * instead.
24
+ */
21
25
  MaskedView: MaskedView,
22
26
  Presets,
23
27
  Specs,
@@ -63,7 +67,12 @@ export type {
63
67
  InactiveBehavior,
64
68
  OverlayProps,
65
69
  RawGestureValues,
70
+ ScreenBackdropComponent,
71
+ ScreenBackdropComponentProps,
72
+ ScreenContentComponent,
73
+ ScreenContentComponentProps,
66
74
  ScreenInterpolationProps,
75
+ ScreenLayerComponentProps,
67
76
  ScreenStyleInterpolator,
68
77
  ScreenTransitionAccessor,
69
78
  ScreenTransitionConfig,
@@ -25,6 +25,11 @@ import createProvider from "../utils/create-provider";
25
25
  import { applyMeasuredBoundsWrites } from "./helpers/measured-bounds-writes";
26
26
  import { useDescriptorDerivations, useDescriptors } from "./screen/descriptors";
27
27
 
28
+ /**
29
+ * @deprecated Internal legacy provider for the old shared-bound-tag registration
30
+ * path. Do not build new boundary behavior here; use the Transition.Boundary
31
+ * measurement pipeline instead.
32
+ */
28
33
  interface MaybeMeasureAndStoreParams {
29
34
  onPress?: ((...args: unknown[]) => void) | undefined;
30
35
  shouldSetSource?: boolean;
@@ -1,13 +1,17 @@
1
+ import type { SharedValue } from "react-native-reanimated";
1
2
  import type {
2
3
  ScreenAnimationDescendantSources,
3
4
  ScreenAnimationSource,
4
5
  ScreenInterpolatorPropsRevision,
5
6
  } from "../types";
6
7
 
8
+ type ScreenInterpolatorExternalDeps = Pick<SharedValue<unknown>, "get">[];
9
+
7
10
  export const readScreenAnimationRevisions = (
8
11
  screenInterpolatorPropsRevision: ScreenInterpolatorPropsRevision,
9
12
  ancestorScreenAnimationSources: ScreenAnimationSource[],
10
13
  descendantScreenAnimationSources: ScreenAnimationDescendantSources,
14
+ screenInterpolatorExternalDeps?: ScreenInterpolatorExternalDeps,
11
15
  ) => {
12
16
  "worklet";
13
17
  screenInterpolatorPropsRevision.get();
@@ -22,4 +26,14 @@ export const readScreenAnimationRevisions = (
22
26
  for (let index = 0; index < descendantSources.length; index++) {
23
27
  descendantSources[index]?.source.screenInterpolatorPropsRevision.get();
24
28
  }
29
+
30
+ if (screenInterpolatorExternalDeps) {
31
+ for (
32
+ let index = 0;
33
+ index < screenInterpolatorExternalDeps.length;
34
+ index++
35
+ ) {
36
+ screenInterpolatorExternalDeps[index]?.get();
37
+ }
38
+ }
25
39
  };