@react-navigation/stack 7.0.0-alpha.2 → 7.0.0-alpha.20

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 (176) hide show
  1. package/lib/commonjs/TransitionConfigs/CardStyleInterpolators.js +29 -14
  2. package/lib/commonjs/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  3. package/lib/commonjs/TransitionConfigs/HeaderStyleInterpolators.js +19 -12
  4. package/lib/commonjs/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
  5. package/lib/commonjs/TransitionConfigs/TransitionPresets.js +20 -22
  6. package/lib/commonjs/TransitionConfigs/TransitionPresets.js.map +1 -1
  7. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js +7 -14
  8. package/lib/commonjs/TransitionConfigs/TransitionSpecs.js.map +1 -1
  9. package/lib/commonjs/index.js +2 -2
  10. package/lib/commonjs/index.js.map +1 -1
  11. package/lib/commonjs/navigators/createStackNavigator.js +35 -25
  12. package/lib/commonjs/navigators/createStackNavigator.js.map +1 -1
  13. package/lib/commonjs/types.js.map +1 -1
  14. package/lib/commonjs/utils/CardAnimationContext.js +3 -4
  15. package/lib/commonjs/utils/CardAnimationContext.js.map +1 -1
  16. package/lib/commonjs/utils/GestureHandlerRefContext.js +3 -4
  17. package/lib/commonjs/utils/GestureHandlerRefContext.js.map +1 -1
  18. package/lib/commonjs/utils/ModalPresentationContext.js +3 -4
  19. package/lib/commonjs/utils/ModalPresentationContext.js.map +1 -1
  20. package/lib/commonjs/utils/conditional.js.map +1 -1
  21. package/lib/commonjs/utils/debounce.js.map +1 -1
  22. package/lib/commonjs/utils/findLastIndex.js +1 -1
  23. package/lib/commonjs/utils/findLastIndex.js.map +1 -1
  24. package/lib/commonjs/utils/getDistanceForDirection.js +2 -2
  25. package/lib/commonjs/utils/getDistanceForDirection.js.map +1 -1
  26. package/lib/commonjs/utils/getInvertedMultiplier.js +3 -4
  27. package/lib/commonjs/utils/getInvertedMultiplier.js.map +1 -1
  28. package/lib/commonjs/utils/getModalRoutesKeys.js +17 -0
  29. package/lib/commonjs/utils/getModalRoutesKeys.js.map +1 -0
  30. package/lib/commonjs/utils/memoize.js.map +1 -1
  31. package/lib/commonjs/utils/useCardAnimation.js +2 -2
  32. package/lib/commonjs/utils/useCardAnimation.js.map +1 -1
  33. package/lib/commonjs/utils/useGestureHandlerRef.js +2 -2
  34. package/lib/commonjs/utils/useGestureHandlerRef.js.map +1 -1
  35. package/lib/commonjs/utils/useKeyboardManager.js +6 -6
  36. package/lib/commonjs/utils/useKeyboardManager.js.map +1 -1
  37. package/lib/commonjs/views/GestureHandler.android.js.map +1 -1
  38. package/lib/commonjs/views/GestureHandler.ios.js.map +1 -1
  39. package/lib/commonjs/views/GestureHandler.js +5 -8
  40. package/lib/commonjs/views/GestureHandler.js.map +1 -1
  41. package/lib/commonjs/views/GestureHandlerNative.js +2 -2
  42. package/lib/commonjs/views/GestureHandlerNative.js.map +1 -1
  43. package/lib/commonjs/views/Header/Header.js +4 -4
  44. package/lib/commonjs/views/Header/Header.js.map +1 -1
  45. package/lib/commonjs/views/Header/HeaderContainer.js +14 -11
  46. package/lib/commonjs/views/Header/HeaderContainer.js.map +1 -1
  47. package/lib/commonjs/views/Header/HeaderSegment.js +9 -2
  48. package/lib/commonjs/views/Header/HeaderSegment.js.map +1 -1
  49. package/lib/commonjs/views/Screens.js +2 -2
  50. package/lib/commonjs/views/Screens.js.map +1 -1
  51. package/lib/commonjs/views/Stack/Card.js +55 -48
  52. package/lib/commonjs/views/Stack/Card.js.map +1 -1
  53. package/lib/commonjs/views/Stack/CardContainer.js +27 -23
  54. package/lib/commonjs/views/Stack/CardContainer.js.map +1 -1
  55. package/lib/commonjs/views/Stack/CardSheet.js +24 -4
  56. package/lib/commonjs/views/Stack/CardSheet.js.map +1 -1
  57. package/lib/commonjs/views/Stack/CardStack.js +99 -73
  58. package/lib/commonjs/views/Stack/CardStack.js.map +1 -1
  59. package/lib/commonjs/views/Stack/StackView.js +27 -31
  60. package/lib/commonjs/views/Stack/StackView.js.map +1 -1
  61. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +28 -14
  62. package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  63. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +20 -13
  64. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
  65. package/lib/module/TransitionConfigs/TransitionPresets.js +10 -2
  66. package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
  67. package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -1
  68. package/lib/module/index.js.map +1 -1
  69. package/lib/module/navigators/createStackNavigator.js +33 -22
  70. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  71. package/lib/module/types.js.map +1 -1
  72. package/lib/module/utils/CardAnimationContext.js.map +1 -1
  73. package/lib/module/utils/GestureHandlerRefContext.js.map +1 -1
  74. package/lib/module/utils/ModalPresentationContext.js.map +1 -1
  75. package/lib/module/utils/conditional.js.map +1 -1
  76. package/lib/module/utils/debounce.js.map +1 -1
  77. package/lib/module/utils/findLastIndex.js +1 -1
  78. package/lib/module/utils/findLastIndex.js.map +1 -1
  79. package/lib/module/utils/getDistanceForDirection.js +2 -2
  80. package/lib/module/utils/getDistanceForDirection.js.map +1 -1
  81. package/lib/module/utils/getInvertedMultiplier.js +3 -4
  82. package/lib/module/utils/getInvertedMultiplier.js.map +1 -1
  83. package/lib/module/utils/getModalRoutesKeys.js +10 -0
  84. package/lib/module/utils/getModalRoutesKeys.js.map +1 -0
  85. package/lib/module/utils/memoize.js.map +1 -1
  86. package/lib/module/utils/useCardAnimation.js.map +1 -1
  87. package/lib/module/utils/useGestureHandlerRef.js.map +1 -1
  88. package/lib/module/utils/useKeyboardManager.js +4 -4
  89. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  90. package/lib/module/views/GestureHandler.android.js.map +1 -1
  91. package/lib/module/views/GestureHandler.ios.js.map +1 -1
  92. package/lib/module/views/GestureHandler.js.map +1 -1
  93. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  94. package/lib/module/views/Header/Header.js +1 -0
  95. package/lib/module/views/Header/Header.js.map +1 -1
  96. package/lib/module/views/Header/HeaderContainer.js +13 -10
  97. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  98. package/lib/module/views/Header/HeaderSegment.js +7 -0
  99. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  100. package/lib/module/views/Screens.js.map +1 -1
  101. package/lib/module/views/Stack/Card.js +52 -44
  102. package/lib/module/views/Stack/Card.js.map +1 -1
  103. package/lib/module/views/Stack/CardContainer.js +25 -20
  104. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  105. package/lib/module/views/Stack/CardSheet.js +21 -0
  106. package/lib/module/views/Stack/CardSheet.js.map +1 -1
  107. package/lib/module/views/Stack/CardStack.js +95 -68
  108. package/lib/module/views/Stack/CardStack.js.map +1 -1
  109. package/lib/module/views/Stack/StackView.js +25 -29
  110. package/lib/module/views/Stack/StackView.js.map +1 -1
  111. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +4 -0
  112. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  113. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +3 -3
  114. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  115. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +4 -0
  116. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  117. package/lib/typescript/src/index.d.ts +1 -1
  118. package/lib/typescript/src/index.d.ts.map +1 -1
  119. package/lib/typescript/src/navigators/createStackNavigator.d.ts +15 -9
  120. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  121. package/lib/typescript/src/types.d.ts +70 -15
  122. package/lib/typescript/src/types.d.ts.map +1 -1
  123. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  124. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  125. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  126. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  127. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +4 -0
  128. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -0
  129. package/lib/typescript/src/views/GestureHandlerNative.d.ts +3 -2
  130. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  131. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  132. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +4 -3
  133. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  134. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +3 -1
  135. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  136. package/lib/typescript/src/views/Screens.d.ts +4 -3
  137. package/lib/typescript/src/views/Screens.d.ts.map +1 -1
  138. package/lib/typescript/src/views/Stack/Card.d.ts +6 -5
  139. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  140. package/lib/typescript/src/views/Stack/CardContainer.d.ts +3 -6
  141. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  142. package/lib/typescript/src/views/Stack/CardSheet.d.ts +1 -1
  143. package/lib/typescript/src/views/Stack/CardSheet.d.ts.map +1 -1
  144. package/lib/typescript/src/views/Stack/CardStack.d.ts +4 -5
  145. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  146. package/lib/typescript/src/views/Stack/StackView.d.ts +9 -80
  147. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  148. package/package.json +19 -20
  149. package/src/TransitionConfigs/CardStyleInterpolators.tsx +18 -5
  150. package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +32 -22
  151. package/src/TransitionConfigs/TransitionPresets.tsx +13 -4
  152. package/src/index.tsx +2 -0
  153. package/src/navigators/createStackNavigator.tsx +49 -14
  154. package/src/types.tsx +88 -15
  155. package/src/utils/findLastIndex.tsx +1 -1
  156. package/src/utils/getDistanceForDirection.tsx +3 -2
  157. package/src/utils/getInvertedMultiplier.tsx +4 -5
  158. package/src/utils/getModalRoutesKeys.ts +21 -0
  159. package/src/utils/useKeyboardManager.tsx +1 -1
  160. package/src/views/GestureHandlerNative.tsx +1 -1
  161. package/src/views/Header/Header.tsx +3 -2
  162. package/src/views/Header/HeaderContainer.tsx +19 -8
  163. package/src/views/Header/HeaderSegment.tsx +10 -3
  164. package/src/views/Screens.tsx +2 -1
  165. package/src/views/Stack/Card.tsx +82 -63
  166. package/src/views/Stack/CardContainer.tsx +20 -11
  167. package/src/views/Stack/CardSheet.tsx +25 -1
  168. package/src/views/Stack/CardStack.tsx +278 -193
  169. package/src/views/Stack/StackView.tsx +25 -28
  170. package/lib/commonjs/views/ModalStatusBarManager.js +0 -44
  171. package/lib/commonjs/views/ModalStatusBarManager.js.map +0 -1
  172. package/lib/module/views/ModalStatusBarManager.js +0 -36
  173. package/lib/module/views/ModalStatusBarManager.js.map +0 -1
  174. package/lib/typescript/src/views/ModalStatusBarManager.d.ts +0 -11
  175. package/lib/typescript/src/views/ModalStatusBarManager.d.ts.map +0 -1
  176. package/src/views/ModalStatusBarManager.tsx +0 -45
@@ -4,15 +4,15 @@ import {
4
4
  SafeAreaProviderCompat,
5
5
  } from '@react-navigation/elements';
6
6
  import type {
7
+ LocaleDirection,
7
8
  ParamListBase,
8
9
  Route,
9
10
  StackNavigationState,
10
11
  } from '@react-navigation/native';
11
- import Color from 'color';
12
12
  import * as React from 'react';
13
13
  import {
14
14
  Animated,
15
- LayoutChangeEvent,
15
+ type LayoutChangeEvent,
16
16
  Platform,
17
17
  StyleSheet,
18
18
  } from 'react-native';
@@ -23,23 +23,32 @@ import {
23
23
  forNoAnimation as forNoAnimationCard,
24
24
  } from '../../TransitionConfigs/CardStyleInterpolators';
25
25
  import {
26
+ BottomSheetAndroid,
26
27
  DefaultTransition,
28
+ FadeFromBottomAndroid,
27
29
  ModalFadeTransition,
30
+ ModalSlideFromBottomIOS,
28
31
  ModalTransition,
32
+ RevealFromBottomAndroid,
33
+ ScaleFromCenterAndroid,
34
+ SlideFromLeftIOS,
35
+ SlideFromRightIOS,
29
36
  } from '../../TransitionConfigs/TransitionPresets';
30
37
  import type {
31
38
  Layout,
32
39
  Scene,
40
+ StackAnimationName,
41
+ StackCardStyleInterpolator,
33
42
  StackDescriptor,
34
43
  StackDescriptorMap,
35
44
  StackHeaderMode,
36
- StackNavigationOptions,
45
+ TransitionPreset,
37
46
  } from '../../types';
38
47
  import { findLastIndex } from '../../utils/findLastIndex';
39
48
  import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
49
+ import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
40
50
  import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
41
51
  import { MaybeScreen, MaybeScreenContainer } from '../Screens';
42
- import { getIsModalPresentation } from './Card';
43
52
  import { CardContainer } from './CardContainer';
44
53
 
45
54
  type GestureValues = {
@@ -47,9 +56,13 @@ type GestureValues = {
47
56
  };
48
57
 
49
58
  type Props = {
59
+ // eslint-disable-next-line react/no-unused-prop-types
60
+ direction: LocaleDirection;
50
61
  insets: EdgeInsets;
51
62
  state: StackNavigationState<ParamListBase>;
52
63
  descriptors: StackDescriptorMap;
64
+ // eslint-disable-next-line react/no-unused-prop-types
65
+ preloadedDescriptors: StackDescriptorMap;
53
66
  routes: Route<string>[];
54
67
  // eslint-disable-next-line react/no-unused-prop-types
55
68
  openingRouteKeys: string[];
@@ -60,7 +73,6 @@ type Props = {
60
73
  route: Route<string>;
61
74
  }) => Route<string> | undefined;
62
75
  renderHeader: (props: HeaderContainerProps) => React.ReactNode;
63
- renderScene: (props: { route: Route<string> }) => React.ReactNode;
64
76
  isParentHeaderShown: boolean;
65
77
  isParentModal: boolean;
66
78
  onTransitionStart: (
@@ -83,6 +95,21 @@ type State = {
83
95
  headerHeights: Record<string, number>;
84
96
  };
85
97
 
98
+ const NAMED_TRANSITIONS_PRESETS = {
99
+ default: DefaultTransition,
100
+ fade: ModalFadeTransition,
101
+ fade_from_bottom: FadeFromBottomAndroid,
102
+ none: DefaultTransition,
103
+ reveal_from_bottom: RevealFromBottomAndroid,
104
+ scale_from_center: ScaleFromCenterAndroid,
105
+ slide_from_left: SlideFromLeftIOS,
106
+ slide_from_right: SlideFromRightIOS,
107
+ slide_from_bottom: Platform.select({
108
+ ios: ModalSlideFromBottomIOS,
109
+ default: BottomSheetAndroid,
110
+ }),
111
+ } as const satisfies Record<StackAnimationName, TransitionPreset>;
112
+
86
113
  const EPSILON = 1e-5;
87
114
 
88
115
  const STATE_INACTIVE = 0;
@@ -111,6 +138,16 @@ const getInterpolationIndex = (scenes: Scene[], index: number) => {
111
138
  return interpolationIndex;
112
139
  };
113
140
 
141
+ const getIsModalPresentation = (
142
+ cardStyleInterpolator: StackCardStyleInterpolator
143
+ ) => {
144
+ return (
145
+ cardStyleInterpolator === forModalPresentationIOS ||
146
+ // Handle custom modal presentation interpolators as well
147
+ cardStyleInterpolator.name === 'forModalPresentationIOS'
148
+ );
149
+ };
150
+
114
151
  const getIsModal = (
115
152
  scene: Scene,
116
153
  interpolationIndex: number,
@@ -162,22 +199,34 @@ const getHeaderHeights = (
162
199
 
163
200
  const getDistanceFromOptions = (
164
201
  layout: Layout,
165
- descriptor?: StackDescriptor
202
+ descriptor: StackDescriptor | undefined,
203
+ isRTL: boolean
166
204
  ) => {
167
- const {
168
- presentation,
169
- gestureDirection = presentation === 'modal'
205
+ if (descriptor?.options.gestureDirection) {
206
+ return getDistanceForDirection(
207
+ layout,
208
+ descriptor?.options.gestureDirection,
209
+ isRTL
210
+ );
211
+ }
212
+
213
+ const defaultGestureDirection =
214
+ descriptor?.options.presentation === 'modal'
170
215
  ? ModalTransition.gestureDirection
171
- : DefaultTransition.gestureDirection,
172
- } = (descriptor?.options || {}) as StackNavigationOptions;
216
+ : DefaultTransition.gestureDirection;
217
+
218
+ const gestureDirection = descriptor?.options.animation
219
+ ? NAMED_TRANSITIONS_PRESETS[descriptor?.options.animation]?.gestureDirection
220
+ : defaultGestureDirection;
173
221
 
174
- return getDistanceForDirection(layout, gestureDirection);
222
+ return getDistanceForDirection(layout, gestureDirection, isRTL);
175
223
  };
176
224
 
177
225
  const getProgressFromGesture = (
178
226
  gesture: Animated.Value,
179
227
  layout: Layout,
180
- descriptor?: StackDescriptor
228
+ descriptor: StackDescriptor | undefined,
229
+ isRTL: boolean
181
230
  ) => {
182
231
  const distance = getDistanceFromOptions(
183
232
  {
@@ -186,7 +235,8 @@ const getProgressFromGesture = (
186
235
  width: Math.max(1, layout.width),
187
236
  height: Math.max(1, layout.height),
188
237
  },
189
- descriptor
238
+ descriptor,
239
+ isRTL
190
240
  );
191
241
 
192
242
  if (distance > 0) {
@@ -214,158 +264,202 @@ export class CardStack extends React.Component<Props, State> {
214
264
  return null;
215
265
  }
216
266
 
217
- const gestures = props.routes.reduce<GestureValues>((acc, curr) => {
218
- const descriptor = props.descriptors[curr.key];
219
- const { animationEnabled } = descriptor?.options || {};
267
+ const gestures = [
268
+ ...props.routes,
269
+ ...props.state.preloadedRoutes,
270
+ ].reduce<GestureValues>((acc, curr) => {
271
+ const descriptor =
272
+ props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
273
+ const { animation } = descriptor?.options || {};
220
274
 
221
275
  acc[curr.key] =
222
276
  state.gestures[curr.key] ||
223
277
  new Animated.Value(
224
- props.openingRouteKeys.includes(curr.key) &&
225
- animationEnabled !== false
226
- ? getDistanceFromOptions(state.layout, descriptor)
278
+ (props.openingRouteKeys.includes(curr.key) && animation !== 'none') ||
279
+ props.state.preloadedRoutes.includes(curr)
280
+ ? getDistanceFromOptions(
281
+ state.layout,
282
+ descriptor,
283
+ props.direction === 'rtl'
284
+ )
227
285
  : 0
228
286
  );
229
287
 
230
288
  return acc;
231
289
  }, {});
232
290
 
233
- const scenes = props.routes.map((route, index, self) => {
234
- const previousRoute = self[index - 1];
235
- const nextRoute = self[index + 1];
236
-
237
- const oldScene = state.scenes[index];
238
-
239
- const currentGesture = gestures[route.key];
240
- const previousGesture = previousRoute
241
- ? gestures[previousRoute.key]
242
- : undefined;
243
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
244
-
245
- const descriptor =
246
- props.descriptors[route.key] ||
247
- state.descriptors[route.key] ||
248
- (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
249
-
250
- const nextDescriptor =
251
- props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key];
252
-
253
- const previousDescriptor =
254
- props.descriptors[previousRoute?.key] ||
255
- state.descriptors[previousRoute?.key];
256
-
257
- // When a screen is not the last, it should use next screen's transition config
258
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
259
- // For example combining a slide and a modal transition would look wrong otherwise
260
- // With this approach, combining different transition styles in the same navigator mostly looks right
261
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
262
- // but majority of the transitions look alright
263
- const optionsForTransitionConfig =
264
- index !== self.length - 1 &&
265
- nextDescriptor &&
266
- nextDescriptor.options.presentation !== 'transparentModal'
267
- ? nextDescriptor.options
268
- : descriptor.options;
269
-
270
- let defaultTransitionPreset =
271
- optionsForTransitionConfig.presentation === 'modal'
272
- ? ModalTransition
273
- : optionsForTransitionConfig.presentation === 'transparentModal'
274
- ? ModalFadeTransition
275
- : DefaultTransition;
291
+ const modalRouteKeys = getModalRouteKeys(
292
+ [...props.routes, ...props.state.preloadedRoutes],
293
+ {
294
+ ...props.descriptors,
295
+ ...props.preloadedDescriptors,
296
+ }
297
+ );
276
298
 
277
- const {
278
- animationEnabled = Platform.OS !== 'web' &&
299
+ const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
300
+ (route, index, self) => {
301
+ // For preloaded screens, we don't care about the previous and the next screen
302
+ const isPreloaded = props.state.preloadedRoutes.includes(route);
303
+ const previousRoute = isPreloaded ? undefined : self[index - 1];
304
+ const nextRoute = isPreloaded ? undefined : self[index + 1];
305
+
306
+ const oldScene = state.scenes[index];
307
+
308
+ const currentGesture = gestures[route.key];
309
+ const previousGesture = previousRoute
310
+ ? gestures[previousRoute.key]
311
+ : undefined;
312
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
313
+
314
+ const descriptor =
315
+ (isPreloaded ? props.preloadedDescriptors : props.descriptors)[
316
+ route.key
317
+ ] ||
318
+ state.descriptors[route.key] ||
319
+ (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
320
+
321
+ const nextDescriptor =
322
+ nextRoute &&
323
+ (props.descriptors[nextRoute?.key] ||
324
+ state.descriptors[nextRoute?.key]);
325
+
326
+ const previousDescriptor =
327
+ previousRoute &&
328
+ (props.descriptors[previousRoute?.key] ||
329
+ state.descriptors[previousRoute?.key]);
330
+
331
+ // When a screen is not the last, it should use next screen's transition config
332
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
333
+ // For example combining a slide and a modal transition would look wrong otherwise
334
+ // With this approach, combining different transition styles in the same navigator mostly looks right
335
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
336
+ // but the majority of the transitions look alright
337
+ const optionsForTransitionConfig =
338
+ index !== self.length - 1 &&
339
+ nextDescriptor &&
340
+ nextDescriptor.options.presentation !== 'transparentModal'
341
+ ? nextDescriptor.options
342
+ : descriptor.options;
343
+
344
+ // Assume modal if there are already modal screens in the stack
345
+ // or current screen is a modal when no presentation is specified
346
+ const isModal = modalRouteKeys.includes(route.key);
347
+
348
+ // Disable screen transition animation by default on web, windows and macos to match the native behavior
349
+ const excludedPlatforms =
350
+ Platform.OS !== 'web' &&
279
351
  Platform.OS !== 'windows' &&
280
- Platform.OS !== 'macos',
281
- gestureEnabled = Platform.OS === 'ios' && animationEnabled,
282
- gestureDirection = defaultTransitionPreset.gestureDirection,
283
- transitionSpec = defaultTransitionPreset.transitionSpec,
284
- cardStyleInterpolator = animationEnabled === false
285
- ? forNoAnimationCard
286
- : defaultTransitionPreset.cardStyleInterpolator,
287
- headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
288
- cardOverlayEnabled = (Platform.OS !== 'ios' &&
289
- optionsForTransitionConfig.presentation !== 'transparentModal') ||
290
- getIsModalPresentation(cardStyleInterpolator),
291
- } = optionsForTransitionConfig;
292
-
293
- const headerMode: StackHeaderMode =
294
- descriptor.options.headerMode ??
295
- (!(
296
- optionsForTransitionConfig.presentation === 'modal' ||
297
- optionsForTransitionConfig.presentation === 'transparentModal' ||
298
- nextDescriptor?.options.presentation === 'modal' ||
299
- nextDescriptor?.options.presentation === 'transparentModal' ||
300
- getIsModalPresentation(cardStyleInterpolator)
301
- ) &&
302
- Platform.OS === 'ios' &&
303
- descriptor.options.header === undefined
304
- ? 'float'
305
- : 'screen');
306
-
307
- const scene = {
308
- route,
309
- descriptor: {
310
- ...descriptor,
311
- options: {
312
- ...descriptor.options,
313
- animationEnabled,
314
- cardOverlayEnabled,
315
- cardStyleInterpolator,
316
- gestureDirection,
317
- gestureEnabled,
318
- headerStyleInterpolator,
319
- transitionSpec,
320
- headerMode,
352
+ Platform.OS !== 'macos';
353
+
354
+ const animation =
355
+ optionsForTransitionConfig.animation ??
356
+ (excludedPlatforms ? 'default' : 'none');
357
+ const isAnimationEnabled = animation !== 'none';
358
+
359
+ const transitionPreset =
360
+ animation !== 'default'
361
+ ? NAMED_TRANSITIONS_PRESETS[animation]
362
+ : isModal || optionsForTransitionConfig.presentation === 'modal'
363
+ ? ModalTransition
364
+ : optionsForTransitionConfig.presentation === 'transparentModal'
365
+ ? ModalFadeTransition
366
+ : DefaultTransition;
367
+
368
+ const {
369
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
370
+ gestureDirection = transitionPreset.gestureDirection,
371
+ transitionSpec = transitionPreset.transitionSpec,
372
+ cardStyleInterpolator = isAnimationEnabled
373
+ ? transitionPreset.cardStyleInterpolator
374
+ : forNoAnimationCard,
375
+ headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
376
+ cardOverlayEnabled = (Platform.OS !== 'ios' &&
377
+ optionsForTransitionConfig.presentation !== 'transparentModal') ||
378
+ getIsModalPresentation(cardStyleInterpolator),
379
+ } = optionsForTransitionConfig;
380
+
381
+ const headerMode: StackHeaderMode =
382
+ descriptor.options.headerMode ??
383
+ (!(
384
+ optionsForTransitionConfig.presentation === 'modal' ||
385
+ optionsForTransitionConfig.presentation === 'transparentModal' ||
386
+ nextDescriptor?.options.presentation === 'modal' ||
387
+ nextDescriptor?.options.presentation === 'transparentModal' ||
388
+ getIsModalPresentation(cardStyleInterpolator)
389
+ ) &&
390
+ Platform.OS === 'ios' &&
391
+ descriptor.options.header === undefined
392
+ ? 'float'
393
+ : 'screen');
394
+
395
+ const isRTL = props.direction === 'rtl';
396
+
397
+ const scene = {
398
+ route,
399
+ descriptor: {
400
+ ...descriptor,
401
+ options: {
402
+ ...descriptor.options,
403
+ animation,
404
+ cardOverlayEnabled,
405
+ cardStyleInterpolator,
406
+ gestureDirection,
407
+ gestureEnabled,
408
+ headerStyleInterpolator,
409
+ transitionSpec,
410
+ headerMode,
411
+ },
321
412
  },
322
- },
323
- progress: {
324
- current: getProgressFromGesture(
325
- currentGesture,
326
- state.layout,
327
- descriptor
328
- ),
329
- next:
330
- nextGesture &&
331
- nextDescriptor?.options.presentation !== 'transparentModal'
413
+ progress: {
414
+ current: getProgressFromGesture(
415
+ currentGesture,
416
+ state.layout,
417
+ descriptor,
418
+ isRTL
419
+ ),
420
+ next:
421
+ nextGesture &&
422
+ nextDescriptor?.options.presentation !== 'transparentModal'
423
+ ? getProgressFromGesture(
424
+ nextGesture,
425
+ state.layout,
426
+ nextDescriptor,
427
+ isRTL
428
+ )
429
+ : undefined,
430
+ previous: previousGesture
332
431
  ? getProgressFromGesture(
333
- nextGesture,
432
+ previousGesture,
334
433
  state.layout,
335
- nextDescriptor
434
+ previousDescriptor,
435
+ isRTL
336
436
  )
337
437
  : undefined,
338
- previous: previousGesture
339
- ? getProgressFromGesture(
340
- previousGesture,
341
- state.layout,
342
- previousDescriptor
343
- )
344
- : undefined,
345
- },
346
- __memo: [
347
- state.layout,
348
- descriptor,
349
- nextDescriptor,
350
- previousDescriptor,
351
- currentGesture,
352
- nextGesture,
353
- previousGesture,
354
- ],
355
- };
438
+ },
439
+ __memo: [
440
+ state.layout,
441
+ descriptor,
442
+ nextDescriptor,
443
+ previousDescriptor,
444
+ currentGesture,
445
+ nextGesture,
446
+ previousGesture,
447
+ ],
448
+ };
449
+
450
+ if (
451
+ oldScene &&
452
+ scene.__memo.every((it, i) => {
453
+ // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
454
+ return oldScene.__memo[i] === it;
455
+ })
456
+ ) {
457
+ return oldScene;
458
+ }
356
459
 
357
- if (
358
- oldScene &&
359
- scene.__memo.every((it, i) => {
360
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
361
- return oldScene.__memo[i] === it;
362
- })
363
- ) {
364
- return oldScene;
460
+ return scene;
365
461
  }
366
-
367
- return scene;
368
- });
462
+ );
369
463
 
370
464
  return {
371
465
  routes: props.routes,
@@ -480,7 +574,6 @@ export class CardStack extends React.Component<Props, State> {
480
574
  onOpenRoute,
481
575
  onCloseRoute,
482
576
  renderHeader,
483
- renderScene,
484
577
  isParentHeaderShown,
485
578
  isParentModal,
486
579
  onTransitionStart,
@@ -522,16 +615,16 @@ export class CardStack extends React.Component<Props, State> {
522
615
  detachPreviousScreen = options.presentation === 'transparentModal'
523
616
  ? false
524
617
  : getIsModalPresentation(options.cardStyleInterpolator)
525
- ? i !==
526
- findLastIndex(scenes, (scene) => {
527
- const { cardStyleInterpolator } = scene.descriptor.options;
528
-
529
- return (
530
- cardStyleInterpolator === forModalPresentationIOS ||
531
- cardStyleInterpolator?.name === 'forModalPresentationIOS'
532
- );
533
- })
534
- : true,
618
+ ? i !==
619
+ findLastIndex(scenes, (scene) => {
620
+ const { cardStyleInterpolator } = scene.descriptor.options;
621
+
622
+ return (
623
+ cardStyleInterpolator === forModalPresentationIOS ||
624
+ cardStyleInterpolator?.name === 'forModalPresentationIOS'
625
+ );
626
+ })
627
+ : true,
535
628
  } = options;
536
629
 
537
630
  if (detachPreviousScreen === false) {
@@ -575,10 +668,23 @@ export class CardStack extends React.Component<Props, State> {
575
668
  style={styles.container}
576
669
  onLayout={this.handleLayout}
577
670
  >
578
- {routes.map((route, index, self) => {
671
+ {[...routes, ...state.preloadedRoutes].map((route, index) => {
579
672
  const focused = focusedRoute.key === route.key;
580
673
  const gesture = gestures[route.key];
581
674
  const scene = scenes[index];
675
+ // It is possible that for a short period the route appears in both arrays.
676
+ // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
677
+ // However, due to the router action, it immediately populates the `preloadedRoutes` array.
678
+ // Practically, the logic below takes care that it is rendered only once.
679
+ const isPreloaded =
680
+ state.preloadedRoutes.includes(route) && !routes.includes(route);
681
+ if (
682
+ state.preloadedRoutes.includes(route) &&
683
+ routes.includes(route) &&
684
+ index >= routes.length
685
+ ) {
686
+ return null;
687
+ }
582
688
 
583
689
  // For the screens that shouldn't be active, the value is 0
584
690
  // For those that should be active, but are not the top screen, the value is 1
@@ -590,17 +696,17 @@ export class CardStack extends React.Component<Props, State> {
590
696
  | 1
591
697
  | 2 = 1;
592
698
 
593
- if (index < self.length - activeScreensLimit - 1) {
699
+ if (index < routes.length - activeScreensLimit - 1 || isPreloaded) {
594
700
  // screen should be inactive because it is too deep in the stack
595
701
  isScreenActive = STATE_INACTIVE;
596
702
  } else {
597
- const sceneForActivity = scenes[self.length - 1];
703
+ const sceneForActivity = scenes[routes.length - 1];
598
704
  const outputValue =
599
- index === self.length - 1
705
+ index === routes.length - 1
600
706
  ? STATE_ON_TOP // the screen is on top after the transition
601
- : index >= self.length - activeScreensLimit
602
- ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
603
- : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
707
+ : index >= routes.length - activeScreensLimit
708
+ ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
709
+ : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
604
710
  isScreenActive = sceneForActivity
605
711
  ? sceneForActivity.progress.current.interpolate({
606
712
  inputRange: [0, 1 - EPSILON, 1],
@@ -613,9 +719,8 @@ export class CardStack extends React.Component<Props, State> {
613
719
  const {
614
720
  headerShown = true,
615
721
  headerTransparent,
616
- headerStyle,
617
- headerTintColor,
618
722
  freezeOnBlur,
723
+ autoHideHomeIndicator,
619
724
  } = scene.descriptor.options;
620
725
 
621
726
  const safeAreaInsetTop = insets.top;
@@ -626,26 +731,6 @@ export class CardStack extends React.Component<Props, State> {
626
731
  const headerHeight =
627
732
  headerShown !== false ? headerHeights[route.key] : 0;
628
733
 
629
- let headerDarkContent: boolean | undefined;
630
-
631
- if (headerShown) {
632
- if (typeof headerTintColor === 'string') {
633
- headerDarkContent = Color(headerTintColor).isDark();
634
- } else {
635
- const flattenedHeaderStyle = StyleSheet.flatten(headerStyle);
636
-
637
- if (
638
- flattenedHeaderStyle &&
639
- 'backgroundColor' in flattenedHeaderStyle &&
640
- typeof flattenedHeaderStyle.backgroundColor === 'string'
641
- ) {
642
- headerDarkContent = !Color(
643
- flattenedHeaderStyle.backgroundColor
644
- ).isDark();
645
- }
646
- }
647
- }
648
-
649
734
  // Start from current card and count backwards the number of cards with same interpolation
650
735
  const interpolationIndex = getInterpolationIndex(scenes, index);
651
736
  const isModal = getIsModal(
@@ -665,17 +750,18 @@ export class CardStack extends React.Component<Props, State> {
665
750
  return (
666
751
  <MaybeScreen
667
752
  key={route.key}
668
- style={StyleSheet.absoluteFill}
753
+ style={[StyleSheet.absoluteFill]}
669
754
  enabled={detachInactiveScreens}
670
755
  active={isScreenActive}
671
756
  freezeOnBlur={freezeOnBlur}
757
+ homeIndicatorHidden={autoHideHomeIndicator}
672
758
  pointerEvents="box-none"
673
759
  >
674
760
  <CardContainer
675
761
  index={index}
676
762
  interpolationIndex={interpolationIndex}
677
763
  modal={isModal}
678
- active={index === self.length - 1}
764
+ active={index === routes.length - 1}
679
765
  focused={focused}
680
766
  closing={closingRouteKeys.includes(route.key)}
681
767
  layout={layout}
@@ -693,18 +779,17 @@ export class CardStack extends React.Component<Props, State> {
693
779
  onHeaderHeightChange={this.handleHeaderLayout}
694
780
  getPreviousScene={this.getPreviousScene}
695
781
  getFocusedRoute={this.getFocusedRoute}
696
- headerDarkContent={headerDarkContent}
697
782
  hasAbsoluteFloatHeader={
698
783
  isFloatHeaderAbsolute && !headerTransparent
699
784
  }
700
785
  renderHeader={renderHeader}
701
- renderScene={renderScene}
702
786
  onOpenRoute={onOpenRoute}
703
787
  onCloseRoute={onCloseRoute}
704
788
  onTransitionStart={onTransitionStart}
705
789
  onTransitionEnd={onTransitionEnd}
706
790
  isNextScreenTransparent={isNextScreenTransparent}
707
791
  detachCurrentScreen={detachCurrentScreen}
792
+ preloaded={isPreloaded}
708
793
  />
709
794
  </MaybeScreen>
710
795
  );
@@ -723,8 +808,8 @@ const styles = StyleSheet.create({
723
808
  absolute: {
724
809
  position: 'absolute',
725
810
  top: 0,
726
- left: 0,
727
- right: 0,
811
+ start: 0,
812
+ end: 0,
728
813
  },
729
814
  floating: {
730
815
  zIndex: 1,