react-native-screen-transitions 4.0.0-alpha.4 → 4.0.0-alpha.5

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 (74) hide show
  1. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js +17 -9
  2. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js.map +1 -1
  3. package/lib/commonjs/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js +4 -15
  4. package/lib/commonjs/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js.map +1 -1
  5. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js +22 -24
  6. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js.map +1 -1
  7. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js +3 -36
  8. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js.map +1 -1
  9. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js +1 -1
  10. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js.map +1 -1
  11. package/lib/commonjs/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js +1 -1
  12. package/lib/commonjs/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js.map +1 -1
  13. package/lib/commonjs/components/boundary/utils/destination-signals.js +7 -1
  14. package/lib/commonjs/components/boundary/utils/destination-signals.js.map +1 -1
  15. package/lib/commonjs/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js +15 -14
  16. package/lib/commonjs/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js.map +1 -1
  17. package/lib/commonjs/providers/screen/styles/hooks/use-interpolated-style-maps.js +17 -17
  18. package/lib/commonjs/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  19. package/lib/commonjs/stores/bounds/internals/entries.js +13 -3
  20. package/lib/commonjs/stores/bounds/internals/entries.js.map +1 -1
  21. package/lib/module/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js +18 -10
  22. package/lib/module/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js.map +1 -1
  23. package/lib/module/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js +4 -15
  24. package/lib/module/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js.map +1 -1
  25. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js +22 -24
  26. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js.map +1 -1
  27. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js +3 -36
  28. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js.map +1 -1
  29. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js +2 -2
  30. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js.map +1 -1
  31. package/lib/module/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js +1 -1
  32. package/lib/module/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js.map +1 -1
  33. package/lib/module/components/boundary/utils/destination-signals.js +7 -1
  34. package/lib/module/components/boundary/utils/destination-signals.js.map +1 -1
  35. package/lib/module/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js +15 -14
  36. package/lib/module/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js.map +1 -1
  37. package/lib/module/providers/screen/styles/hooks/use-interpolated-style-maps.js +17 -17
  38. package/lib/module/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  39. package/lib/module/stores/bounds/internals/entries.js +13 -3
  40. package/lib/module/stores/bounds/internals/entries.js.map +1 -1
  41. package/lib/typescript/components/boundary/hooks/lifecycles/use-initial-destination-measurement.d.ts.map +1 -1
  42. package/lib/typescript/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.d.ts.map +1 -1
  43. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.d.ts +1 -5
  44. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.d.ts.map +1 -1
  45. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.d.ts.map +1 -1
  46. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.d.ts.map +1 -1
  47. package/lib/typescript/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.d.ts +0 -2
  48. package/lib/typescript/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.d.ts.map +1 -1
  49. package/lib/typescript/components/boundary/utils/destination-signals.d.ts +1 -1
  50. package/lib/typescript/components/boundary/utils/destination-signals.d.ts.map +1 -1
  51. package/lib/typescript/providers/screen/styles/helpers/resolve-interpolator-style-handoff.d.ts +1 -3
  52. package/lib/typescript/providers/screen/styles/helpers/resolve-interpolator-style-handoff.d.ts.map +1 -1
  53. package/lib/typescript/providers/screen/styles/hooks/use-interpolated-style-maps.d.ts +4 -4
  54. package/lib/typescript/providers/screen/styles/hooks/use-interpolated-style-maps.d.ts.map +1 -1
  55. package/lib/typescript/stores/bounds/internals/entries.d.ts +1 -1
  56. package/lib/typescript/stores/bounds/internals/entries.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/src/components/boundary/hooks/lifecycles/use-initial-destination-measurement.ts +34 -13
  59. package/src/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.tsx +4 -20
  60. package/src/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.ts +27 -37
  61. package/src/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.ts +10 -57
  62. package/src/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.ts +1 -8
  63. package/src/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.ts +0 -3
  64. package/src/components/boundary/utils/destination-signals.ts +6 -2
  65. package/src/providers/screen/styles/helpers/resolve-interpolator-style-handoff.ts +16 -16
  66. package/src/providers/screen/styles/hooks/use-interpolated-style-maps.tsx +21 -24
  67. package/src/stores/bounds/internals/entries.ts +12 -1
  68. package/lib/commonjs/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js +0 -18
  69. package/lib/commonjs/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js.map +0 -1
  70. package/lib/module/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js +0 -13
  71. package/lib/module/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js.map +0 -1
  72. package/lib/typescript/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.d.ts +0 -3
  73. package/lib/typescript/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.d.ts.map +0 -1
  74. package/src/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.ts +0 -16
@@ -1,4 +1,4 @@
1
- import { useCallback, useLayoutEffect } from "react";
1
+ import { useCallback, useLayoutEffect, useMemo } from "react";
2
2
  import {
3
3
  cancelAnimation,
4
4
  useAnimatedReaction,
@@ -7,6 +7,7 @@ import {
7
7
  withTiming,
8
8
  } from "react-native-reanimated";
9
9
  import { scheduleOnUI } from "react-native-worklets";
10
+ import { useStack } from "../../../../hooks/navigation/use-stack";
10
11
  import { useDescriptorsStore } from "../../../../providers/screen/descriptors";
11
12
  import { AnimationStore } from "../../../../stores/animation.store";
12
13
  import {
@@ -61,6 +62,16 @@ export const useInitialDestinationMeasurement = ({
61
62
  const preferredSourceScreenKey = destinationPairKey
62
63
  ? getSourceScreenKeyFromPairKey(destinationPairKey)
63
64
  : undefined;
65
+ const stackScenes = useStack((store) => store.scenes);
66
+ // A retained closing screen can still have registered boundaries, but it
67
+ // cannot own a new transition link.
68
+ const closingSourceScreenKeys = useMemo(
69
+ () =>
70
+ stackScenes
71
+ .filter((scene) => scene.activity === "closing")
72
+ .map((scene) => scene.route.key),
73
+ [stackScenes],
74
+ );
64
75
  const progress = AnimationStore.getValue(
65
76
  currentScreenKey,
66
77
  "transitionProgress",
@@ -74,6 +85,7 @@ export const useInitialDestinationMeasurement = ({
74
85
  const retryToken = useSharedValue(0);
75
86
  const handshakeRetries = useSharedValue(0);
76
87
  const hasGivenUp = useSharedValue(0);
88
+ const hasFinishedInitialMeasurement = useSharedValue(0);
77
89
 
78
90
  const releaseLifecycleStartBlock = useCallback(() => {
79
91
  "worklet";
@@ -83,19 +95,22 @@ export const useInitialDestinationMeasurement = ({
83
95
  return;
84
96
  }
85
97
 
98
+ hasFinishedInitialMeasurement.set(1);
86
99
  isBlockingLifecycleStart.set(0);
87
100
  unblockLifecycleStart();
88
- }, [isBlockingLifecycleStart, retryToken, unblockLifecycleStart]);
101
+ }, [
102
+ hasFinishedInitialMeasurement,
103
+ isBlockingLifecycleStart,
104
+ retryToken,
105
+ unblockLifecycleStart,
106
+ ]);
89
107
 
90
108
  const claimLifecycleStartBlock = useCallback(() => {
91
109
  "worklet";
92
110
  if (
93
111
  !canReceiveDestination ||
94
- !getMatchingSourceScreenKey(
95
- tag,
96
- currentScreenKey,
97
- preferredSourceScreenKey,
98
- )
112
+ hasFinishedInitialMeasurement.get() ||
113
+ !getMatchingSourceScreenKey(tag, currentScreenKey)
99
114
  ) {
100
115
  return;
101
116
  }
@@ -113,9 +128,9 @@ export const useInitialDestinationMeasurement = ({
113
128
  blockLifecycleStart,
114
129
  canReceiveDestination,
115
130
  currentScreenKey,
131
+ hasFinishedInitialMeasurement,
116
132
  isBlockingLifecycleStart,
117
133
  progress,
118
- preferredSourceScreenKey,
119
134
  tag,
120
135
  ]);
121
136
 
@@ -147,7 +162,11 @@ export const useInitialDestinationMeasurement = ({
147
162
  () => {
148
163
  "worklet";
149
164
 
150
- if (!canReceiveDestination || isBlockingLifecycleStart.get() <= 0) {
165
+ if (
166
+ !canReceiveDestination ||
167
+ hasFinishedInitialMeasurement.get() ||
168
+ isBlockingLifecycleStart.get() <= 0
169
+ ) {
151
170
  return null;
152
171
  }
153
172
 
@@ -160,6 +179,7 @@ export const useInitialDestinationMeasurement = ({
160
179
  tag,
161
180
  currentScreenKey,
162
181
  preferredSourceScreenKey,
182
+ closingSourceScreenKeys,
163
183
  );
164
184
  const pairKey = sourceScreenKey
165
185
  ? createScreenPairKey(sourceScreenKey, currentScreenKey)
@@ -182,12 +202,12 @@ export const useInitialDestinationMeasurement = ({
182
202
  },
183
203
  (next, previous) => {
184
204
  "worklet";
185
- if (!next) {
205
+ if (!next || hasFinishedInitialMeasurement.get()) {
186
206
  return;
187
207
  }
188
208
 
189
209
  const [measurePairKey, action, retryTick] = next;
190
- if (!measurePairKey || !action) {
210
+ if (!action) {
191
211
  return;
192
212
  }
193
213
 
@@ -213,19 +233,20 @@ export const useInitialDestinationMeasurement = ({
213
233
  return;
214
234
  }
215
235
 
216
- if (action === "measure") {
236
+ if (action === "measure" && measurePairKey) {
217
237
  measureBoundary({
218
238
  type: "destination",
219
239
  pairKey: measurePairKey,
220
240
  });
221
241
  }
222
242
 
223
- const link = getLink(measurePairKey, linkKey);
243
+ const link = measurePairKey ? getLink(measurePairKey, linkKey) : null;
224
244
  const linkComplete = !!link?.source && !!link.destination;
225
245
 
226
246
  if (linkComplete || action === "complete") {
227
247
  cancelAnimation(retryToken);
228
248
  handshakeRetries.set(0);
249
+ hasFinishedInitialMeasurement.set(1);
229
250
  if (escapeClipping) {
230
251
  // Screen-level escape has a second readiness phase after destination
231
252
  // matching: the host must commit before the transition starts, or
@@ -16,7 +16,6 @@ import { getLink } from "../../../../../../stores/bounds/internals/links";
16
16
  import { SystemStore } from "../../../../../../stores/system.store";
17
17
  import type { ScrollMeasuredDimensions } from "../../../../utils/measured-bounds";
18
18
  import { NativePortalHost, PORTAL_POINTER_EVENTS } from "../../../teleport";
19
- import { hasLocalSlot } from "../helpers/has-local-slot";
20
19
  import { resolvePortalOffsetStyle } from "../helpers/offset-style";
21
20
  import { getPortalHostBounds } from "../stores/host-bounds.store";
22
21
  import type { ActivePortalBoundaryHost } from "../stores/portal-boundary-host.store";
@@ -44,11 +43,10 @@ export const PortalBoundaryHost = memo(function PortalBoundaryHost({
44
43
  }
45
44
 
46
45
  const link = getLink(host.pairKey, host.boundaryId);
47
- const hasGeometry =
48
- link?.source !== null &&
49
- link !== undefined &&
50
- getPortalHostBounds(host.hostKey) !== null &&
51
- hasLocalSlot(host.localStylesMaps.get(), host.boundaryId);
46
+ const hasSource = !!link?.source;
47
+ const hasHostBounds = getPortalHostBounds(host.hostKey) !== null;
48
+ const hasSlot = host.slotsMap.get()[host.boundaryId] !== undefined;
49
+ const hasGeometry = hasSource && hasHostBounds && hasSlot;
52
50
 
53
51
  if (!hasGeometry) {
54
52
  geometryReadyFrames.set(0);
@@ -103,20 +101,6 @@ export const PortalBoundaryHost = memo(function PortalBoundaryHost({
103
101
  });
104
102
  const slotStyle = useAnimatedStyle(() => {
105
103
  "worklet";
106
- // `slotsMap` is the resolved map: it may contain real interpolator output,
107
- // inherited styles, or resolver-created reset patches for slots that just
108
- // disappeared. Normal components need those reset patches so stale styles
109
- // clear correctly.
110
- //
111
- // A screen-level portal host is different. It is only the temporary visual
112
- // receiver for teleported content, not the original component that needs a
113
- // cleanup frame. If the current local interpolator layers did not emit this
114
- // boundary id, any resolved style here is cleanup/stale residue and should
115
- // not be drawn by the host.
116
- if (!hasLocalSlot(host.localStylesMaps.get(), host.boundaryId)) {
117
- return NO_STYLES;
118
- }
119
-
120
104
  const slot = host.slotsMap.get()[host.boundaryId];
121
105
 
122
106
  return composeSlotStyleWithLocalTransform(
@@ -1,12 +1,10 @@
1
1
  import { useCallback, useLayoutEffect, useRef, useState } from "react";
2
2
  import { type SharedValue, useAnimatedReaction } from "react-native-reanimated";
3
3
  import { scheduleOnRN } from "react-native-worklets";
4
- import type { LocalStyleLayers } from "../../../../../../providers/screen/styles/helpers/resolve-slot-styles";
5
- import { pairs } from "../../../../../../stores/bounds/internals/state";
4
+ import { getPairKeyForSource } from "../../../../../../stores/bounds/internals/links";
6
5
  import type { ScreenPairKey } from "../../../../../../stores/bounds/types";
7
6
  import type { NormalizedTransitionInterpolatedStyle } from "../../../../../../types/animation.types";
8
7
  import { createBoundaryPortalHostName } from "../../../utils/naming";
9
- import { hasActiveBoundaryPortalLink } from "../helpers/active-pair";
10
8
  import {
11
9
  mountPortalBoundaryHost,
12
10
  unmountPortalBoundaryHostByName,
@@ -16,62 +14,57 @@ type UseActivePortalBoundaryHostParams = {
16
14
  boundaryId: string;
17
15
  currentScreenKey: string;
18
16
  escapeHostKey?: string;
19
- localStylesMaps: SharedValue<LocalStyleLayers>;
20
17
  portalHostName: SharedValue<string | null>;
21
18
  portalHostReady: SharedValue<boolean>;
22
19
  slotsMap: SharedValue<NormalizedTransitionInterpolatedStyle>;
23
- sourcePairKey?: ScreenPairKey;
20
+ };
21
+
22
+ type ActivePortal = {
23
+ pairKey: ScreenPairKey;
24
24
  };
25
25
 
26
26
  export const useActivePortalBoundaryHost = ({
27
27
  boundaryId,
28
28
  currentScreenKey,
29
29
  escapeHostKey,
30
- localStylesMaps,
31
30
  portalHostName,
32
31
  portalHostReady,
33
32
  slotsMap,
34
- sourcePairKey,
35
33
  }: UseActivePortalBoundaryHostParams) => {
36
- const [hasActiveLink, setHasActiveLink] = useState(false);
37
- const sourcePairKeyRef = useRef(sourcePairKey);
38
- sourcePairKeyRef.current = sourcePairKey;
34
+ const [activePortal, setActivePortal] = useState<ActivePortal | null>(null);
35
+ const activePairKeyRef = useRef<ScreenPairKey | null>(null);
39
36
 
40
- const updateActiveLink = useCallback(
41
- (
42
- observedSourcePairKey: ScreenPairKey | undefined,
43
- nextHasActiveLink: boolean,
44
- ) => {
45
- if (sourcePairKeyRef.current !== observedSourcePairKey) {
46
- return;
47
- }
37
+ const updateActivePortal = useCallback((pairKey: ScreenPairKey | null) => {
38
+ if (activePairKeyRef.current === pairKey) {
39
+ return;
40
+ }
48
41
 
49
- setHasActiveLink(nextHasActiveLink);
50
- },
51
- [],
52
- );
42
+ activePairKeyRef.current = pairKey;
43
+ setActivePortal(pairKey ? { pairKey } : null);
44
+ }, []);
53
45
 
54
46
  useAnimatedReaction(
55
47
  () => {
56
48
  "worklet";
57
- return hasActiveBoundaryPortalLink({
58
- boundaryId,
59
- pairsState: pairs.get(),
60
- sourcePairKey,
61
- });
49
+ const isBoundaryAnimating = slotsMap.get()[boundaryId] !== undefined;
50
+ if (!isBoundaryAnimating) {
51
+ return null;
52
+ }
53
+
54
+ return getPairKeyForSource(boundaryId, currentScreenKey);
62
55
  },
63
- (nextHasActiveLink, previousHasActiveLink) => {
56
+ (pairKey, previousPairKey) => {
64
57
  "worklet";
65
- if (nextHasActiveLink === previousHasActiveLink) {
58
+ if (pairKey === previousPairKey) {
66
59
  return;
67
60
  }
68
61
 
69
- scheduleOnRN(updateActiveLink, sourcePairKey, nextHasActiveLink);
62
+ scheduleOnRN(updateActivePortal, pairKey);
70
63
  },
71
64
  );
72
65
 
73
66
  useLayoutEffect(() => {
74
- if (!hasActiveLink || !sourcePairKey || !escapeHostKey) {
67
+ if (!activePortal || !escapeHostKey) {
75
68
  portalHostName.set(null);
76
69
  portalHostReady.set(false);
77
70
  return;
@@ -80,14 +73,13 @@ export const useActivePortalBoundaryHost = ({
80
73
  const nextPortalHostName = createBoundaryPortalHostName(
81
74
  escapeHostKey,
82
75
  boundaryId,
83
- sourcePairKey,
76
+ activePortal.pairKey,
84
77
  );
85
78
 
86
79
  mountPortalBoundaryHost({
87
80
  boundaryId,
88
81
  hostKey: escapeHostKey,
89
- localStylesMaps,
90
- pairKey: sourcePairKey,
82
+ pairKey: activePortal.pairKey,
91
83
  portalHostName: nextPortalHostName,
92
84
  portalHostReady,
93
85
  screenKey: currentScreenKey,
@@ -101,14 +93,12 @@ export const useActivePortalBoundaryHost = ({
101
93
  unmountPortalBoundaryHostByName(nextPortalHostName);
102
94
  };
103
95
  }, [
96
+ activePortal,
104
97
  boundaryId,
105
98
  currentScreenKey,
106
99
  escapeHostKey,
107
- localStylesMaps,
108
100
  portalHostName,
109
101
  portalHostReady,
110
- sourcePairKey,
111
102
  slotsMap,
112
- hasActiveLink,
113
103
  ]);
114
104
  };
@@ -1,17 +1,8 @@
1
- import {
2
- type SharedValue,
3
- useAnimatedProps,
4
- useSharedValue,
5
- } from "react-native-reanimated";
1
+ import { useAnimatedProps, useSharedValue } from "react-native-reanimated";
6
2
  import { useDescriptorsStore } from "../../../../../../providers/screen/descriptors";
7
3
  import { useScreenSlots } from "../../../../../../providers/screen/styles";
8
- import { hasCloseTransitionFinished } from "../../../../../../providers/screen/styles/helpers/transition-visual-state";
9
- import { AnimationStore } from "../../../../../../stores/animation.store";
10
- import { pairs } from "../../../../../../stores/bounds/internals/state";
11
- import { SystemStore } from "../../../../../../stores/system.store";
12
4
  import { PORTAL_HOST_NAME_RESET_VALUE } from "../../../utils/naming";
13
5
  import { isTeleportEnabled } from "../../../utils/teleport-control";
14
- import { hasActiveBoundaryPortalLink } from "../helpers/active-pair";
15
6
  import { useActiveHostKey } from "../stores/host-registry.store";
16
7
  import { useActivePortalBoundaryHost } from "./use-active-portal-boundary-host";
17
8
 
@@ -19,42 +10,23 @@ interface UseBoundaryPortalAttachmentParams {
19
10
  boundaryId: string;
20
11
  }
21
12
 
22
- type AttachedDestination = {
23
- animationProgress: SharedValue<number>;
24
- closing: SharedValue<number>;
25
- };
26
-
27
13
  export const useBoundaryPortalAttachment = ({
28
14
  boundaryId,
29
15
  }: UseBoundaryPortalAttachmentParams) => {
30
- const sourcePairKey = useDescriptorsStore((s) => s.derivations.sourcePairKey);
31
16
  const currentScreenKey = useDescriptorsStore(
32
17
  (s) => s.derivations.currentScreenKey,
33
18
  );
34
- const nextScreenKey = useDescriptorsStore((s) => s.derivations.nextScreenKey);
35
- const destinationScreenKey = nextScreenKey ?? currentScreenKey;
36
- const destinationAnimationProgress = SystemStore.getValue(
37
- destinationScreenKey,
38
- "animationProgress",
39
- );
40
- const destinationClosing = AnimationStore.getValue(
41
- destinationScreenKey,
42
- "closing",
43
- );
44
- const { localStylesMaps, slotsMap } = useScreenSlots();
19
+ const { slotsMap } = useScreenSlots();
45
20
  const portalHostName = useSharedValue<string | null>(null);
46
21
  const portalHostReady = useSharedValue(false);
47
- const attachedDestination = useSharedValue<AttachedDestination | null>(null);
48
22
  const escapeHostKey = useActiveHostKey(currentScreenKey);
49
23
 
50
24
  useActivePortalBoundaryHost({
51
25
  boundaryId,
52
26
  currentScreenKey,
53
27
  escapeHostKey,
54
- localStylesMaps,
55
28
  portalHostName,
56
29
  portalHostReady,
57
- sourcePairKey,
58
30
  slotsMap,
59
31
  });
60
32
 
@@ -67,36 +39,17 @@ export const useBoundaryPortalAttachment = ({
67
39
  teleport,
68
40
  ...slotProps
69
41
  } = slot?.props ?? {};
70
- const shouldTeleport = isTeleportEnabled(teleport);
71
- const hostName = portalHostName.get();
72
- const hasActiveLink = hasActiveBoundaryPortalLink({
73
- boundaryId,
74
- pairsState: pairs.get(),
75
- sourcePairKey,
76
- });
77
- const hasAttachableHost =
78
- shouldTeleport && hasActiveLink && portalHostReady.get() && hostName;
79
-
80
- if (hasAttachableHost) {
81
- attachedDestination.set({
82
- animationProgress: destinationAnimationProgress,
83
- closing: destinationClosing,
84
- });
85
- }
86
-
87
- const attached = attachedDestination.get();
88
- const hasAttachedCloseFinished =
89
- attached !== null &&
90
- hasCloseTransitionFinished({
91
- animationProgress: attached.animationProgress.get(),
92
- closing: attached.closing.get(),
93
- });
94
- const canAttach = hasAttachableHost && !hasAttachedCloseFinished;
95
- const targetHostName = canAttach ? hostName : PORTAL_HOST_NAME_RESET_VALUE;
42
+ const shouldAttach =
43
+ slot !== undefined &&
44
+ isTeleportEnabled(teleport) &&
45
+ portalHostReady.get();
46
+ const hostName = shouldAttach
47
+ ? portalHostName.get()
48
+ : PORTAL_HOST_NAME_RESET_VALUE;
96
49
 
97
50
  return {
98
51
  ...slotProps,
99
- hostName: targetHostName,
52
+ hostName,
100
53
  };
101
54
  });
102
55
 
@@ -16,7 +16,6 @@ import { getVisibilityBlockOffset } from "../../../../../../utils/visibility-blo
16
16
  import {
17
17
  adjustedMeasuredBoundsForOverscrollDeltas,
18
18
  correctMeasuredBoundsForVisibilityGate,
19
- isMeasurementInViewport,
20
19
  } from "../../../../utils/measured-bounds";
21
20
  import {
22
21
  clearPortalHostBounds,
@@ -102,13 +101,7 @@ export const useHostMeasurement = ({
102
101
  viewportHeight,
103
102
  });
104
103
 
105
- if (
106
- !isMeasurementInViewport(
107
- correctedMeasured,
108
- viewportWidth,
109
- viewportHeight,
110
- )
111
- ) {
104
+ if (correctedMeasured.width <= 0 || correctedMeasured.height <= 0) {
112
105
  cancelAnimation(retryToken);
113
106
  retryToken.set(
114
107
  withDelay(
@@ -1,12 +1,10 @@
1
1
  import { useSyncExternalStore } from "react";
2
2
  import type { SharedValue } from "react-native-reanimated";
3
- import type { LocalStyleLayers } from "../../../../../../providers/screen/styles/helpers/resolve-slot-styles";
4
3
  import type { NormalizedTransitionInterpolatedStyle } from "../../../../../../types/animation.types";
5
4
 
6
5
  export type ActivePortalBoundaryHost = {
7
6
  boundaryId: string;
8
7
  hostKey: string;
9
- localStylesMaps: SharedValue<LocalStyleLayers>;
10
8
  pairKey: string;
11
9
  portalHostName: string;
12
10
  portalHostReady: SharedValue<boolean>;
@@ -56,7 +54,6 @@ const isSameHost = (
56
54
  return (
57
55
  a.boundaryId === b.boundaryId &&
58
56
  a.hostKey === b.hostKey &&
59
- a.localStylesMaps === b.localStylesMaps &&
60
57
  a.pairKey === b.pairKey &&
61
58
  a.portalHostName === b.portalHostName &&
62
59
  a.portalHostReady === b.portalHostReady &&
@@ -14,7 +14,7 @@ export type InitialDestinationMeasurementAction =
14
14
  | "complete";
15
15
 
16
16
  export type InitialDestinationMeasurementSignal = {
17
- pairKey: ScreenPairKey;
17
+ pairKey: ScreenPairKey | null;
18
18
  action: InitialDestinationMeasurementAction;
19
19
  };
20
20
 
@@ -37,10 +37,14 @@ export const getInitialDestinationMeasurementSignal = (params: {
37
37
  sourcePresent,
38
38
  linkState,
39
39
  } = params;
40
- if (!enabled || !pairKey) {
40
+ if (!enabled) {
41
41
  return null;
42
42
  }
43
43
 
44
+ if (!pairKey) {
45
+ return { pairKey: null, action: "wait" };
46
+ }
47
+
44
48
  if (!destinationPresent) {
45
49
  return { pairKey, action: "wait" };
46
50
  }
@@ -81,18 +81,18 @@ const mergeSlots = (
81
81
  return merged;
82
82
  };
83
83
 
84
- const composeFrozenAndLiveStyles = (
85
- frozen: NormalizedTransitionInterpolatedStyle,
86
- live: NormalizedTransitionInterpolatedStyle,
84
+ const composeCurrentAndNextStyles = (
85
+ current: NormalizedTransitionInterpolatedStyle,
86
+ next: NormalizedTransitionInterpolatedStyle,
87
87
  ) => {
88
88
  "worklet";
89
- const composed: NormalizedTransitionInterpolatedStyle = { ...frozen };
89
+ const composed: NormalizedTransitionInterpolatedStyle = { ...current };
90
90
 
91
- for (const slotId in live) {
92
- const liveSlot = live[slotId];
91
+ for (const slotId in next) {
92
+ const nextSlot = next[slotId];
93
93
 
94
- if (liveSlot) {
95
- composed[slotId] = mergeSlots(frozen[slotId], liveSlot);
94
+ if (nextSlot) {
95
+ composed[slotId] = mergeSlots(current[slotId], nextSlot);
96
96
  }
97
97
  }
98
98
 
@@ -103,36 +103,36 @@ export const resolveInterpolatorStyleHandoff = ({
103
103
  currentOwnsInterpolator,
104
104
  currentStylesMap,
105
105
  nextStylesMap,
106
- frozenCurrentStylesMap,
107
106
  }: {
108
107
  currentOwnsInterpolator: boolean;
109
108
  currentStylesMap: NormalizedTransitionInterpolatedStyle | undefined;
110
109
  nextStylesMap: NormalizedTransitionInterpolatedStyle | undefined;
111
- frozenCurrentStylesMap: NormalizedTransitionInterpolatedStyle;
112
110
  }): {
113
111
  localStylesMaps: LocalStyleLayers;
114
- nextFrozenCurrentStylesMap: NormalizedTransitionInterpolatedStyle;
115
112
  } => {
116
113
  "worklet";
117
114
 
118
115
  if (currentOwnsInterpolator) {
119
116
  return {
120
117
  localStylesMaps: currentStylesMap ? [currentStylesMap] : [],
121
- nextFrozenCurrentStylesMap: currentStylesMap ?? frozenCurrentStylesMap,
122
118
  };
123
119
  }
124
120
 
125
121
  if (!nextStylesMap) {
126
122
  return {
127
- localStylesMaps: frozenCurrentStylesMap ? [frozenCurrentStylesMap] : [],
128
- nextFrozenCurrentStylesMap: frozenCurrentStylesMap,
123
+ localStylesMaps: currentStylesMap ? [currentStylesMap] : [],
124
+ };
125
+ }
126
+
127
+ if (!currentStylesMap) {
128
+ return {
129
+ localStylesMaps: [nextStylesMap],
129
130
  };
130
131
  }
131
132
 
132
133
  return {
133
134
  localStylesMaps: [
134
- composeFrozenAndLiveStyles(frozenCurrentStylesMap, nextStylesMap),
135
+ composeCurrentAndNextStyles(currentStylesMap, nextStylesMap),
135
136
  ],
136
- nextFrozenCurrentStylesMap: frozenCurrentStylesMap,
137
137
  };
138
138
  };
@@ -125,10 +125,10 @@ const runInterpolator = ({
125
125
  * normal interpolator selection once the gesture-driven close is no longer in
126
126
  * play.
127
127
  *
128
- * At an ownership handoff, the last current-owner styles are frozen. The next
129
- * owner replaces matching scalar keys and composes its live transforms after
130
- * the frozen transforms. The previous interpolator is not evaluated again until
131
- * it regains ownership.
128
+ * At an ownership handoff, the current interpolator remains live but is
129
+ * evaluated without the next-screen relationship. The next owner replaces
130
+ * matching scalar keys and composes its live transforms after the current
131
+ * screen's self-owned transforms.
132
132
  */
133
133
  export const useInterpolatedStylesMap = ({
134
134
  enabled,
@@ -177,8 +177,6 @@ export const useInterpolatedStylesMap = ({
177
177
 
178
178
  const isGesturingDuringCloseAnimation = useSharedValue(false);
179
179
  const initialDestinationStylesReady = useSharedValue(0);
180
- const frozenCurrentStylesMap =
181
- useSharedValue<NormalizedTransitionInterpolatedStyle>(NO_STYLES);
182
180
  const shouldPrepareInitialDestinationStyles =
183
181
  enabled && !!destinationPairKey && !nextScreenKey && !!currentInterpolator;
184
182
 
@@ -261,17 +259,21 @@ export const useInterpolatedStylesMap = ({
261
259
  ? "current"
262
260
  : "next";
263
261
 
264
- const selectedFrame = selectInterpolatorFrame(props, isInGestureMode);
265
-
266
- const currentResult = currentOwnsInterpolator
267
- ? runInterpolator({
268
- interpolator: currentInterpolator,
269
- props,
270
- selectedFrame,
271
- bounds: boundsAccessor,
272
- transition,
273
- })
274
- : undefined;
262
+ // Once the next interpolator owns the relationship, the current
263
+ // interpolator remains responsible only for its own live presentation.
264
+ // This preserves snap-point transforms without letting current-screen
265
+ // unfocus rules compete with the next screen's outgoing transition.
266
+ const currentSelectedFrame = selectInterpolatorFrame(
267
+ props,
268
+ isInGestureMode || !!nextInterpolator,
269
+ );
270
+ const currentResult = runInterpolator({
271
+ interpolator: currentInterpolator,
272
+ props,
273
+ selectedFrame: currentSelectedFrame,
274
+ bounds: boundsAccessor,
275
+ transition,
276
+ });
275
277
 
276
278
  const initialDestinationStyleGate = resolveInitialDestinationStyleGate({
277
279
  shouldPrepareStyles: shouldPrepareInitialDestinationStyles,
@@ -295,22 +297,18 @@ export const useInterpolatedStylesMap = ({
295
297
  currentOwnsInterpolator: true,
296
298
  currentStylesMap: currentResult?.stylesMap,
297
299
  nextStylesMap: undefined,
298
- frozenCurrentStylesMap: frozenCurrentStylesMap.get(),
299
300
  });
300
301
 
301
- if (handoff.nextFrozenCurrentStylesMap !== frozenCurrentStylesMap.get()) {
302
- frozenCurrentStylesMap.set(handoff.nextFrozenCurrentStylesMap);
303
- }
304
-
305
302
  return handoff.localStylesMaps.length
306
303
  ? handoff.localStylesMaps
307
304
  : NO_STYLE_LAYERS;
308
305
  }
309
306
 
307
+ const nextSelectedFrame = selectInterpolatorFrame(props, false);
310
308
  const nextResult = runInterpolator({
311
309
  interpolator: nextInterpolator,
312
310
  props,
313
- selectedFrame,
311
+ selectedFrame: nextSelectedFrame,
314
312
  bounds: boundsAccessor,
315
313
  transition,
316
314
  });
@@ -326,7 +324,6 @@ export const useInterpolatedStylesMap = ({
326
324
  currentOwnsInterpolator: false,
327
325
  currentStylesMap: currentResult?.stylesMap,
328
326
  nextStylesMap: nextResult?.stylesMap,
329
- frozenCurrentStylesMap: frozenCurrentStylesMap.get(),
330
327
  });
331
328
 
332
329
  return handoff.localStylesMaps.length