react-native-screen-transitions 4.0.0-alpha.3 → 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 (138) hide show
  1. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js +27 -15
  2. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js.map +1 -1
  3. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-source-measurement.js +3 -1
  4. package/lib/commonjs/components/boundary/hooks/lifecycles/use-initial-source-measurement.js.map +1 -1
  5. package/lib/commonjs/components/boundary/hooks/lifecycles/use-refresh-boundary.js +3 -5
  6. package/lib/commonjs/components/boundary/hooks/lifecycles/use-refresh-boundary.js.map +1 -1
  7. package/lib/commonjs/components/boundary/hooks/use-boundary-measurement.js +5 -4
  8. package/lib/commonjs/components/boundary/hooks/use-boundary-measurement.js.map +1 -1
  9. package/lib/commonjs/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js +4 -15
  10. package/lib/commonjs/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js.map +1 -1
  11. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js +22 -24
  12. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js.map +1 -1
  13. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js +3 -36
  14. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js.map +1 -1
  15. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js +1 -1
  16. package/lib/commonjs/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js.map +1 -1
  17. package/lib/commonjs/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js +1 -1
  18. package/lib/commonjs/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js.map +1 -1
  19. package/lib/commonjs/components/boundary/utils/destination-signals.js +8 -4
  20. package/lib/commonjs/components/boundary/utils/destination-signals.js.map +1 -1
  21. package/lib/commonjs/components/boundary/utils/refresh-signals.js +2 -4
  22. package/lib/commonjs/components/boundary/utils/refresh-signals.js.map +1 -1
  23. package/lib/commonjs/providers/screen/descriptors/descriptors.provider.js +3 -7
  24. package/lib/commonjs/providers/screen/descriptors/descriptors.provider.js.map +1 -1
  25. package/lib/commonjs/providers/screen/descriptors/helpers/derive-descriptor-derivations.js +1 -3
  26. package/lib/commonjs/providers/screen/descriptors/helpers/derive-descriptor-derivations.js.map +1 -1
  27. package/lib/commonjs/providers/screen/descriptors/helpers/get-ancestor-keys.js +3 -20
  28. package/lib/commonjs/providers/screen/descriptors/helpers/get-ancestor-keys.js.map +1 -1
  29. package/lib/commonjs/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js +15 -14
  30. package/lib/commonjs/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js.map +1 -1
  31. package/lib/commonjs/providers/screen/styles/hooks/use-interpolated-style-maps.js +17 -17
  32. package/lib/commonjs/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  33. package/lib/commonjs/stores/bounds/internals/entries.js +27 -0
  34. package/lib/commonjs/stores/bounds/internals/entries.js.map +1 -1
  35. package/lib/commonjs/stores/bounds/internals/links.js +25 -0
  36. package/lib/commonjs/stores/bounds/internals/links.js.map +1 -1
  37. package/lib/commonjs/stores/bounds/internals/resolver.js +2 -12
  38. package/lib/commonjs/stores/bounds/internals/resolver.js.map +1 -1
  39. package/lib/commonjs/utils/bounds/helpers/create-link-accessor.js +2 -2
  40. package/lib/commonjs/utils/bounds/helpers/create-link-accessor.js.map +1 -1
  41. package/lib/module/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js +30 -18
  42. package/lib/module/components/boundary/hooks/lifecycles/use-initial-destination-measurement.js.map +1 -1
  43. package/lib/module/components/boundary/hooks/lifecycles/use-initial-source-measurement.js +3 -1
  44. package/lib/module/components/boundary/hooks/lifecycles/use-initial-source-measurement.js.map +1 -1
  45. package/lib/module/components/boundary/hooks/lifecycles/use-refresh-boundary.js +3 -5
  46. package/lib/module/components/boundary/hooks/lifecycles/use-refresh-boundary.js.map +1 -1
  47. package/lib/module/components/boundary/hooks/use-boundary-measurement.js +5 -4
  48. package/lib/module/components/boundary/hooks/use-boundary-measurement.js.map +1 -1
  49. package/lib/module/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js +4 -15
  50. package/lib/module/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.js.map +1 -1
  51. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js +22 -24
  52. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.js.map +1 -1
  53. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js +3 -36
  54. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.js.map +1 -1
  55. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js +2 -2
  56. package/lib/module/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.js.map +1 -1
  57. package/lib/module/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js +1 -1
  58. package/lib/module/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.js.map +1 -1
  59. package/lib/module/components/boundary/utils/destination-signals.js +8 -4
  60. package/lib/module/components/boundary/utils/destination-signals.js.map +1 -1
  61. package/lib/module/components/boundary/utils/refresh-signals.js +2 -4
  62. package/lib/module/components/boundary/utils/refresh-signals.js.map +1 -1
  63. package/lib/module/providers/screen/descriptors/descriptors.provider.js +4 -8
  64. package/lib/module/providers/screen/descriptors/descriptors.provider.js.map +1 -1
  65. package/lib/module/providers/screen/descriptors/helpers/derive-descriptor-derivations.js +1 -3
  66. package/lib/module/providers/screen/descriptors/helpers/derive-descriptor-derivations.js.map +1 -1
  67. package/lib/module/providers/screen/descriptors/helpers/get-ancestor-keys.js +3 -19
  68. package/lib/module/providers/screen/descriptors/helpers/get-ancestor-keys.js.map +1 -1
  69. package/lib/module/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js +15 -14
  70. package/lib/module/providers/screen/styles/helpers/resolve-interpolator-style-handoff.js.map +1 -1
  71. package/lib/module/providers/screen/styles/hooks/use-interpolated-style-maps.js +17 -17
  72. package/lib/module/providers/screen/styles/hooks/use-interpolated-style-maps.js.map +1 -1
  73. package/lib/module/stores/bounds/internals/entries.js +27 -1
  74. package/lib/module/stores/bounds/internals/entries.js.map +1 -1
  75. package/lib/module/stores/bounds/internals/links.js +25 -2
  76. package/lib/module/stores/bounds/internals/links.js.map +1 -1
  77. package/lib/module/stores/bounds/internals/resolver.js +3 -13
  78. package/lib/module/stores/bounds/internals/resolver.js.map +1 -1
  79. package/lib/module/utils/bounds/helpers/create-link-accessor.js +3 -3
  80. package/lib/module/utils/bounds/helpers/create-link-accessor.js.map +1 -1
  81. package/lib/typescript/components/boundary/hooks/lifecycles/use-initial-destination-measurement.d.ts.map +1 -1
  82. package/lib/typescript/components/boundary/hooks/lifecycles/use-initial-source-measurement.d.ts.map +1 -1
  83. package/lib/typescript/components/boundary/hooks/lifecycles/use-refresh-boundary.d.ts.map +1 -1
  84. package/lib/typescript/components/boundary/hooks/use-boundary-measurement.d.ts.map +1 -1
  85. package/lib/typescript/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.d.ts.map +1 -1
  86. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.d.ts +1 -5
  87. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.d.ts.map +1 -1
  88. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.d.ts.map +1 -1
  89. package/lib/typescript/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.d.ts.map +1 -1
  90. package/lib/typescript/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.d.ts +0 -2
  91. package/lib/typescript/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.d.ts.map +1 -1
  92. package/lib/typescript/components/boundary/utils/destination-signals.d.ts +2 -3
  93. package/lib/typescript/components/boundary/utils/destination-signals.d.ts.map +1 -1
  94. package/lib/typescript/components/boundary/utils/refresh-signals.d.ts +0 -2
  95. package/lib/typescript/components/boundary/utils/refresh-signals.d.ts.map +1 -1
  96. package/lib/typescript/providers/screen/descriptors/descriptors.provider.d.ts.map +1 -1
  97. package/lib/typescript/providers/screen/descriptors/helpers/derive-descriptor-derivations.d.ts +1 -3
  98. package/lib/typescript/providers/screen/descriptors/helpers/derive-descriptor-derivations.d.ts.map +1 -1
  99. package/lib/typescript/providers/screen/descriptors/helpers/get-ancestor-keys.d.ts +0 -8
  100. package/lib/typescript/providers/screen/descriptors/helpers/get-ancestor-keys.d.ts.map +1 -1
  101. package/lib/typescript/providers/screen/styles/helpers/resolve-interpolator-style-handoff.d.ts +1 -3
  102. package/lib/typescript/providers/screen/styles/helpers/resolve-interpolator-style-handoff.d.ts.map +1 -1
  103. package/lib/typescript/providers/screen/styles/hooks/use-interpolated-style-maps.d.ts +4 -4
  104. package/lib/typescript/providers/screen/styles/hooks/use-interpolated-style-maps.d.ts.map +1 -1
  105. package/lib/typescript/stores/bounds/internals/entries.d.ts +2 -1
  106. package/lib/typescript/stores/bounds/internals/entries.d.ts.map +1 -1
  107. package/lib/typescript/stores/bounds/internals/links.d.ts +3 -1
  108. package/lib/typescript/stores/bounds/internals/links.d.ts.map +1 -1
  109. package/lib/typescript/stores/bounds/internals/resolver.d.ts.map +1 -1
  110. package/lib/typescript/utils/bounds/helpers/create-link-accessor.d.ts.map +1 -1
  111. package/package.json +1 -1
  112. package/src/components/boundary/hooks/lifecycles/use-initial-destination-measurement.ts +67 -24
  113. package/src/components/boundary/hooks/lifecycles/use-initial-source-measurement.ts +6 -1
  114. package/src/components/boundary/hooks/lifecycles/use-refresh-boundary.ts +8 -9
  115. package/src/components/boundary/hooks/use-boundary-measurement.ts +5 -4
  116. package/src/components/boundary/portal/components/boundary-portal/components/portal-boundary-host.tsx +4 -20
  117. package/src/components/boundary/portal/components/boundary-portal/hooks/use-active-portal-boundary-host.ts +27 -37
  118. package/src/components/boundary/portal/components/boundary-portal/hooks/use-boundary-portal-attachment.ts +10 -57
  119. package/src/components/boundary/portal/components/boundary-portal/hooks/use-host-measurement.ts +1 -8
  120. package/src/components/boundary/portal/components/boundary-portal/stores/portal-boundary-host.store.ts +0 -3
  121. package/src/components/boundary/utils/destination-signals.ts +8 -8
  122. package/src/components/boundary/utils/refresh-signals.ts +2 -10
  123. package/src/providers/screen/descriptors/descriptors.provider.tsx +4 -11
  124. package/src/providers/screen/descriptors/helpers/derive-descriptor-derivations.ts +0 -4
  125. package/src/providers/screen/descriptors/helpers/get-ancestor-keys.ts +3 -30
  126. package/src/providers/screen/styles/helpers/resolve-interpolator-style-handoff.ts +16 -16
  127. package/src/providers/screen/styles/hooks/use-interpolated-style-maps.tsx +21 -24
  128. package/src/stores/bounds/internals/entries.ts +38 -1
  129. package/src/stores/bounds/internals/links.ts +36 -0
  130. package/src/stores/bounds/internals/resolver.ts +7 -21
  131. package/src/utils/bounds/helpers/create-link-accessor.ts +9 -3
  132. package/lib/commonjs/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js +0 -18
  133. package/lib/commonjs/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js.map +0 -1
  134. package/lib/module/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js +0 -13
  135. package/lib/module/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.js.map +0 -1
  136. package/lib/typescript/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.d.ts +0 -3
  137. package/lib/typescript/components/boundary/portal/components/boundary-portal/helpers/has-local-slot.d.ts.map +0 -1
  138. 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,10 +7,17 @@ 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
- import { getSourceScreenKeyFromPairKey } from "../../../../stores/bounds/helpers/link-pairs.helpers";
13
- import { getEntry } from "../../../../stores/bounds/internals/entries";
13
+ import {
14
+ createScreenPairKey,
15
+ getSourceScreenKeyFromPairKey,
16
+ } from "../../../../stores/bounds/helpers/link-pairs.helpers";
17
+ import {
18
+ getEntry,
19
+ getMatchingSourceScreenKey,
20
+ } from "../../../../stores/bounds/internals/entries";
14
21
  import { getLink } from "../../../../stores/bounds/internals/links";
15
22
  import { pairs } from "../../../../stores/bounds/internals/state";
16
23
  import type { BoundTag } from "../../../../stores/bounds/types";
@@ -50,12 +57,21 @@ export const useInitialDestinationMeasurement = ({
50
57
  const destinationPairKey = useDescriptorsStore(
51
58
  (s) => s.derivations.destinationPairKey,
52
59
  );
53
- const ancestorDestinationPairKey = useDescriptorsStore(
54
- (s) => s.derivations.ancestorDestinationPairKey,
55
- );
56
60
  const destinationEnabled = enabled && !nextScreenKey;
57
- const initialDestinationPairKey =
58
- destinationPairKey ?? ancestorDestinationPairKey;
61
+ const canReceiveDestination = destinationEnabled && !!destinationPairKey;
62
+ const preferredSourceScreenKey = destinationPairKey
63
+ ? getSourceScreenKeyFromPairKey(destinationPairKey)
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
+ );
59
75
  const progress = AnimationStore.getValue(
60
76
  currentScreenKey,
61
77
  "transitionProgress",
@@ -69,6 +85,7 @@ export const useInitialDestinationMeasurement = ({
69
85
  const retryToken = useSharedValue(0);
70
86
  const handshakeRetries = useSharedValue(0);
71
87
  const hasGivenUp = useSharedValue(0);
88
+ const hasFinishedInitialMeasurement = useSharedValue(0);
72
89
 
73
90
  const releaseLifecycleStartBlock = useCallback(() => {
74
91
  "worklet";
@@ -78,12 +95,25 @@ export const useInitialDestinationMeasurement = ({
78
95
  return;
79
96
  }
80
97
 
98
+ hasFinishedInitialMeasurement.set(1);
81
99
  isBlockingLifecycleStart.set(0);
82
100
  unblockLifecycleStart();
83
- }, [isBlockingLifecycleStart, retryToken, unblockLifecycleStart]);
101
+ }, [
102
+ hasFinishedInitialMeasurement,
103
+ isBlockingLifecycleStart,
104
+ retryToken,
105
+ unblockLifecycleStart,
106
+ ]);
84
107
 
85
108
  const claimLifecycleStartBlock = useCallback(() => {
86
109
  "worklet";
110
+ if (
111
+ !canReceiveDestination ||
112
+ hasFinishedInitialMeasurement.get() ||
113
+ !getMatchingSourceScreenKey(tag, currentScreenKey)
114
+ ) {
115
+ return;
116
+ }
87
117
 
88
118
  // The progress check and block claim must share one UI-thread operation.
89
119
  // Otherwise a JS-thread layout effect can observe zero, enqueue the block,
@@ -94,10 +124,18 @@ export const useInitialDestinationMeasurement = ({
94
124
 
95
125
  blockLifecycleStart();
96
126
  isBlockingLifecycleStart.set(1);
97
- }, [blockLifecycleStart, isBlockingLifecycleStart, progress]);
127
+ }, [
128
+ blockLifecycleStart,
129
+ canReceiveDestination,
130
+ currentScreenKey,
131
+ hasFinishedInitialMeasurement,
132
+ isBlockingLifecycleStart,
133
+ progress,
134
+ tag,
135
+ ]);
98
136
 
99
137
  useLayoutEffect(() => {
100
- if (!destinationEnabled || !initialDestinationPairKey) {
138
+ if (!canReceiveDestination) {
101
139
  return;
102
140
  }
103
141
 
@@ -115,9 +153,8 @@ export const useInitialDestinationMeasurement = ({
115
153
  };
116
154
  }, [
117
155
  claimLifecycleStartBlock,
118
- destinationEnabled,
119
156
  escapeClipping,
120
- initialDestinationPairKey,
157
+ canReceiveDestination,
121
158
  releaseLifecycleStartBlock,
122
159
  ]);
123
160
 
@@ -126,8 +163,8 @@ export const useInitialDestinationMeasurement = ({
126
163
  "worklet";
127
164
 
128
165
  if (
129
- !destinationEnabled ||
130
- !initialDestinationPairKey ||
166
+ !canReceiveDestination ||
167
+ hasFinishedInitialMeasurement.get() ||
131
168
  isBlockingLifecycleStart.get() <= 0
132
169
  ) {
133
170
  return null;
@@ -138,17 +175,22 @@ export const useInitialDestinationMeasurement = ({
138
175
  }
139
176
 
140
177
  const retryTick = retryToken.get();
141
- const sourceScreenKey = getSourceScreenKeyFromPairKey(
142
- initialDestinationPairKey,
178
+ const sourceScreenKey = getMatchingSourceScreenKey(
179
+ tag,
180
+ currentScreenKey,
181
+ preferredSourceScreenKey,
182
+ closingSourceScreenKeys,
143
183
  );
184
+ const pairKey = sourceScreenKey
185
+ ? createScreenPairKey(sourceScreenKey, currentScreenKey)
186
+ : undefined;
144
187
  const signal = getInitialDestinationMeasurementSignal({
145
188
  enabled: destinationEnabled,
146
- destinationPairKey,
147
- ancestorDestinationPairKey,
189
+ pairKey,
148
190
  linkId: linkKey,
149
191
  group,
150
192
  destinationPresent: getEntry(tag, currentScreenKey) !== null,
151
- sourcePresent: getEntry(tag, sourceScreenKey) !== null,
193
+ sourcePresent: sourceScreenKey !== null,
152
194
  linkState: pairs.get(),
153
195
  });
154
196
 
@@ -160,12 +202,12 @@ export const useInitialDestinationMeasurement = ({
160
202
  },
161
203
  (next, previous) => {
162
204
  "worklet";
163
- if (!next) {
205
+ if (!next || hasFinishedInitialMeasurement.get()) {
164
206
  return;
165
207
  }
166
208
 
167
209
  const [measurePairKey, action, retryTick] = next;
168
- if (!measurePairKey || !action) {
210
+ if (!action) {
169
211
  return;
170
212
  }
171
213
 
@@ -191,19 +233,20 @@ export const useInitialDestinationMeasurement = ({
191
233
  return;
192
234
  }
193
235
 
194
- if (action === "measure") {
236
+ if (action === "measure" && measurePairKey) {
195
237
  measureBoundary({
196
238
  type: "destination",
197
239
  pairKey: measurePairKey,
198
240
  });
199
241
  }
200
242
 
201
- const link = getLink(measurePairKey, linkKey);
243
+ const link = measurePairKey ? getLink(measurePairKey, linkKey) : null;
202
244
  const linkComplete = !!link?.source && !!link.destination;
203
245
 
204
246
  if (linkComplete || action === "complete") {
205
247
  cancelAnimation(retryToken);
206
248
  handshakeRetries.set(0);
249
+ hasFinishedInitialMeasurement.set(1);
207
250
  if (escapeClipping) {
208
251
  // Screen-level escape has a second readiness phase after destination
209
252
  // matching: the host must commit before the transition starts, or
@@ -1,5 +1,6 @@
1
1
  import { useAnimatedReaction, useSharedValue } from "react-native-reanimated";
2
2
  import { useDescriptorsStore } from "../../../../providers/screen/descriptors";
3
+ import { getPairKeyForSource } from "../../../../stores/bounds/internals/links";
3
4
  import { pairs } from "../../../../stores/bounds/internals/state";
4
5
  import type { BoundTag } from "../../../../stores/bounds/types";
5
6
  import type { MeasureBoundary } from "../../types";
@@ -11,12 +12,16 @@ export const useInitialSourceMeasurement = (params: {
11
12
  boundTag: BoundTag;
12
13
  }) => {
13
14
  const { enabled, measureBoundary, boundTag } = params;
14
- const sourcePairKey = useDescriptorsStore((s) => s.derivations.sourcePairKey);
15
+ const currentScreenKey = useDescriptorsStore(
16
+ (s) => s.derivations.currentScreenKey,
17
+ );
15
18
  const lastSourceCaptureSignal = useSharedValue<string | null>(null);
16
19
 
17
20
  useAnimatedReaction(
18
21
  () => {
19
22
  "worklet";
23
+ const sourcePairKey =
24
+ getPairKeyForSource(boundTag.tag, currentScreenKey) ?? undefined;
20
25
  return getInitialSourceCaptureSignal({
21
26
  enabled,
22
27
  sourcePairKey,
@@ -1,6 +1,10 @@
1
1
  import { useAnimatedReaction } from "react-native-reanimated";
2
2
  import { useDescriptorsStore } from "../../../../providers/screen/descriptors";
3
3
  import { AnimationStore } from "../../../../stores/animation.store";
4
+ import {
5
+ getPairKeyForDestination,
6
+ getPairKeyForSource,
7
+ } from "../../../../stores/bounds/internals/links";
4
8
  import { pairs } from "../../../../stores/bounds/internals/state";
5
9
  import type { BoundTag } from "../../../../stores/bounds/types";
6
10
  import type { MeasureBoundary } from "../../types";
@@ -22,13 +26,6 @@ export const useRefreshBoundary = ({
22
26
  (s) => s.derivations.currentScreenKey,
23
27
  );
24
28
  const nextScreenKey = useDescriptorsStore((s) => s.derivations.nextScreenKey);
25
- const sourcePairKey = useDescriptorsStore((s) => s.derivations.sourcePairKey);
26
- const destinationPairKey = useDescriptorsStore(
27
- (s) => s.derivations.destinationPairKey,
28
- );
29
- const ancestorDestinationPairKey = useDescriptorsStore(
30
- (s) => s.derivations.ancestorDestinationPairKey,
31
- );
32
29
  // Source-side boundaries refresh from the next screen's lifecycle pulse.
33
30
  // Destination-side boundaries have no next screen, so they refresh from self.
34
31
  const refreshScreenKey = nextScreenKey ?? currentScreenKey;
@@ -46,14 +43,16 @@ export const useRefreshBoundary = ({
46
43
  if (!shouldRefresh) {
47
44
  return null;
48
45
  }
46
+ const sourcePairKey =
47
+ getPairKeyForSource(boundTag.tag, currentScreenKey) ?? undefined;
48
+ const destinationPairKey =
49
+ getPairKeyForDestination(boundTag.tag, currentScreenKey) ?? undefined;
49
50
 
50
51
  return getRefreshBoundarySignal({
51
52
  enabled,
52
53
  currentScreenKey,
53
54
  sourcePairKey,
54
55
  destinationPairKey,
55
- ancestorDestinationPairKey,
56
- nextScreenKey,
57
56
  linkId: linkKey,
58
57
  group,
59
58
  shouldRefresh,
@@ -60,10 +60,11 @@ export const useBoundaryMeasurement = ({
60
60
  escapeClipping,
61
61
  });
62
62
 
63
- // Register/unregister this boundary in the presence map so source/destination
64
- // matching can resolve across concrete screen keys.
63
+ // Presence and source capture must not depend on this screen owning an
64
+ // interpolator: a nested source can participate in a transition owned by a
65
+ // different navigator.
65
66
  useBoundaryPresence({
66
- enabled: runtimeEnabled,
67
+ enabled,
67
68
  boundTag,
68
69
  currentScreenKey,
69
70
  boundaryConfig,
@@ -72,7 +73,7 @@ export const useBoundaryMeasurement = ({
72
73
  });
73
74
 
74
75
  useInitialSourceMeasurement({
75
- enabled: runtimeEnabled,
76
+ enabled,
76
77
  measureBoundary,
77
78
  boundTag,
78
79
  });
@@ -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,14 +14,13 @@ 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
 
21
21
  export const getInitialDestinationMeasurementSignal = (params: {
22
22
  enabled: boolean;
23
- destinationPairKey?: ScreenPairKey;
24
- ancestorDestinationPairKey?: ScreenPairKey;
23
+ pairKey?: ScreenPairKey;
25
24
  linkId: string;
26
25
  group?: string;
27
26
  destinationPresent: boolean;
@@ -31,20 +30,21 @@ export const getInitialDestinationMeasurementSignal = (params: {
31
30
  "worklet";
32
31
  const {
33
32
  enabled,
34
- destinationPairKey,
35
- ancestorDestinationPairKey,
33
+ pairKey,
36
34
  linkId,
37
35
  group,
38
36
  destinationPresent,
39
37
  sourcePresent,
40
38
  linkState,
41
39
  } = params;
42
- const pairKey = destinationPairKey ?? ancestorDestinationPairKey;
43
-
44
- if (!enabled || !pairKey) {
40
+ if (!enabled) {
45
41
  return null;
46
42
  }
47
43
 
44
+ if (!pairKey) {
45
+ return { pairKey: null, action: "wait" };
46
+ }
47
+
48
48
  if (!destinationPresent) {
49
49
  return { pairKey, action: "wait" };
50
50
  }