@react-navigation/stack 7.0.0-alpha.7 → 7.0.0-alpha.8

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 (41) hide show
  1. package/lib/commonjs/navigators/createStackNavigator.js +4 -0
  2. package/lib/commonjs/navigators/createStackNavigator.js.map +1 -1
  3. package/lib/commonjs/views/Header/HeaderContainer.js +1 -1
  4. package/lib/commonjs/views/Header/HeaderContainer.js.map +1 -1
  5. package/lib/commonjs/views/Stack/Card.js +12 -9
  6. package/lib/commonjs/views/Stack/Card.js.map +1 -1
  7. package/lib/commonjs/views/Stack/CardContainer.js +4 -5
  8. package/lib/commonjs/views/Stack/CardContainer.js.map +1 -1
  9. package/lib/commonjs/views/Stack/CardStack.js +29 -20
  10. package/lib/commonjs/views/Stack/CardStack.js.map +1 -1
  11. package/lib/commonjs/views/Stack/StackView.js +20 -26
  12. package/lib/commonjs/views/Stack/StackView.js.map +1 -1
  13. package/lib/module/navigators/createStackNavigator.js +4 -0
  14. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  15. package/lib/module/views/Header/HeaderContainer.js +2 -2
  16. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  17. package/lib/module/views/Stack/Card.js +12 -9
  18. package/lib/module/views/Stack/Card.js.map +1 -1
  19. package/lib/module/views/Stack/CardContainer.js +5 -6
  20. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  21. package/lib/module/views/Stack/CardStack.js +29 -20
  22. package/lib/module/views/Stack/CardStack.js.map +1 -1
  23. package/lib/module/views/Stack/StackView.js +20 -26
  24. package/lib/module/views/Stack/StackView.js.map +1 -1
  25. package/lib/typescript/src/navigators/createStackNavigator.d.ts +1 -1
  26. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  27. package/lib/typescript/src/views/Stack/Card.d.ts +1 -0
  28. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  29. package/lib/typescript/src/views/Stack/CardContainer.d.ts +2 -4
  30. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  31. package/lib/typescript/src/views/Stack/CardStack.d.ts +1 -3
  32. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  33. package/lib/typescript/src/views/Stack/StackView.d.ts +5 -77
  34. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  35. package/package.json +5 -5
  36. package/src/navigators/createStackNavigator.tsx +4 -1
  37. package/src/views/Header/HeaderContainer.tsx +2 -2
  38. package/src/views/Stack/Card.tsx +16 -6
  39. package/src/views/Stack/CardContainer.tsx +6 -5
  40. package/src/views/Stack/CardStack.tsx +176 -149
  41. package/src/views/Stack/StackView.tsx +13 -12
@@ -1 +1 @@
1
- {"version":3,"file":"StackView.d.ts","sourceRoot":"","sources":["../../../../../src/views/Stack/StackView.tsx"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,KAAK,EAEV,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EACV,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AASrB,KAAK,KAAK,GAAG,qBAAqB,GAAG;IACnC,SAAS,EAAE,eAAe,CAAC;IAC3B,KAAK,EAAE,oBAAoB,CAAC,aAAa,CAAC,CAAC;IAC3C,UAAU,EAAE,sBAAsB,CAAC;IACnC,WAAW,EAAE,kBAAkB,CAAC;CACjC,CAAC;AAEF,KAAK,KAAK,GAAG;IAEX,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAExB,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAEhC,mBAAmB,EAAE,kBAAkB,CAAC;IAExC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAG7B,WAAW,EAAE,kBAAkB,CAAC;CACjC,CAAC;AAWF,qBAAa,SAAU,SAAQ,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAC1D,MAAM,CAAC,wBAAwB,CAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAmNxB,KAAK,EAAE,KAAK,CAQV;IAEF,OAAO,CAAC,gBAAgB,CAYtB;IAEF,OAAO,CAAC,WAAW,CASjB;IAEF,OAAO,CAAC,YAAY,CAElB;IAEF,OAAO,CAAC,eAAe,CAwCrB;IAEF,OAAO,CAAC,gBAAgB,CAwBtB;IAEF,OAAO,CAAC,qBAAqB,CAQxB;IAEL,OAAO,CAAC,mBAAmB,CAQtB;IAEL,OAAO,CAAC,kBAAkB,CAKxB;IAEF,OAAO,CAAC,gBAAgB,CAKtB;IAEF,OAAO,CAAC,mBAAmB,CAKzB;IAEF,MAAM;CAmDP"}
1
+ {"version":3,"file":"StackView.d.ts","sourceRoot":"","sources":["../../../../../src/views/Stack/StackView.tsx"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,KAAK,EACV,KAAK,SAAS,EAEd,KAAK,oBAAoB,EAC1B,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAI/B,OAAO,KAAK,EACV,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AASrB,KAAK,KAAK,GAAG,qBAAqB,GAAG;IACnC,SAAS,EAAE,eAAe,CAAC;IAC3B,KAAK,EAAE,oBAAoB,CAAC,aAAa,CAAC,CAAC;IAC3C,UAAU,EAAE,sBAAsB,CAAC;IACnC,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,CACR,KAAK,EAAE,SAAS,CAAC,aAAa,CAAC,EAC/B,WAAW,EAAE,OAAO,KACjB,eAAe,CAAC;CACtB,CAAC;AAEF,KAAK,KAAK,GAAG;IAEX,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAExB,cAAc,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;IAEhC,mBAAmB,EAAE,kBAAkB,CAAC;IAExC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAE3B,kBAAkB,EAAE,MAAM,EAAE,CAAC;IAG7B,WAAW,EAAE,kBAAkB,CAAC;CACjC,CAAC;AAWF,qBAAa,SAAU,SAAQ,KAAK,CAAC,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;IAC1D,MAAM,CAAC,wBAAwB,CAC7B,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,EACtB,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;IAmNxB,KAAK,EAAE,KAAK,CAQV;IAEF,OAAO,CAAC,gBAAgB,CAYtB;IAEF,OAAO,CAAC,YAAY,CAElB;IAEF,OAAO,CAAC,eAAe,CAwCrB;IAEF,OAAO,CAAC,gBAAgB,CAwBtB;IAEF,OAAO,CAAC,qBAAqB,CAQxB;IAEL,OAAO,CAAC,mBAAmB,CAQtB;IAEL,OAAO,CAAC,kBAAkB,CAKxB;IAEF,OAAO,CAAC,gBAAgB,CAKtB;IAEF,OAAO,CAAC,mBAAmB,CAKzB;IAEF,MAAM;CAyDP"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/stack",
3
3
  "description": "Stack navigator component for iOS and Android with animated transitions and gestures",
4
- "version": "7.0.0-alpha.7",
4
+ "version": "7.0.0-alpha.8",
5
5
  "keywords": [
6
6
  "react-native-component",
7
7
  "react-component",
@@ -40,11 +40,11 @@
40
40
  "clean": "del lib"
41
41
  },
42
42
  "dependencies": {
43
- "@react-navigation/elements": "^2.0.0-alpha.4",
43
+ "@react-navigation/elements": "^2.0.0-alpha.5",
44
44
  "color": "^4.2.3"
45
45
  },
46
46
  "devDependencies": {
47
- "@react-navigation/native": "^7.0.0-alpha.6",
47
+ "@react-navigation/native": "^7.0.0-alpha.7",
48
48
  "@testing-library/react-native": "^12.3.1",
49
49
  "@types/color": "^3.0.5",
50
50
  "@types/react": "~18.2.33",
@@ -58,7 +58,7 @@
58
58
  "typescript": "^5.2.2"
59
59
  },
60
60
  "peerDependencies": {
61
- "@react-navigation/native": "^7.0.0-alpha.6",
61
+ "@react-navigation/native": "^7.0.0-alpha.7",
62
62
  "react": "*",
63
63
  "react-native": "0.72.6",
64
64
  "react-native-gesture-handler": "~2.12.0",
@@ -79,5 +79,5 @@
79
79
  ]
80
80
  ]
81
81
  },
82
- "gitHead": "d718b717320d41c90051c2a5f849b8d74f518a18"
82
+ "gitHead": "d0e6a2cd79d2c6aff0cf54e46e31edc407c3c46b"
83
83
  }
@@ -37,11 +37,12 @@ function StackNavigator({
37
37
  layout,
38
38
  screenListeners,
39
39
  screenOptions,
40
+ screenLayout,
40
41
  ...rest
41
42
  }: Props) {
42
43
  const { direction } = useLocale();
43
44
 
44
- const { state, descriptors, navigation, NavigationContent } =
45
+ const { state, describe, descriptors, navigation, NavigationContent } =
45
46
  useNavigationBuilder<
46
47
  StackNavigationState<ParamListBase>,
47
48
  StackRouterOptions,
@@ -55,6 +56,7 @@ function StackNavigator({
55
56
  layout,
56
57
  screenListeners,
57
58
  screenOptions,
59
+ screenLayout,
58
60
  getStateForRouteNamesChange,
59
61
  });
60
62
 
@@ -90,6 +92,7 @@ function StackNavigator({
90
92
  {...rest}
91
93
  direction={direction}
92
94
  state={state}
95
+ describe={describe}
93
96
  descriptors={descriptors}
94
97
  navigation={navigation}
95
98
  />
@@ -4,7 +4,7 @@ import {
4
4
  NavigationRouteContext,
5
5
  type ParamListBase,
6
6
  type Route,
7
- useLinkTools,
7
+ useLinkBuilder,
8
8
  } from '@react-navigation/native';
9
9
  import * as React from 'react';
10
10
  import {
@@ -54,7 +54,7 @@ export function HeaderContainer({
54
54
  }: Props) {
55
55
  const focusedRoute = getFocusedRoute();
56
56
  const parentHeaderBack = React.useContext(HeaderBackContext);
57
- const { buildHref } = useLinkTools();
57
+ const { buildHref } = useLinkBuilder();
58
58
 
59
59
  return (
60
60
  <Animated.View pointerEvents="box-none" style={style}>
@@ -61,6 +61,7 @@ type Props = ViewProps & {
61
61
  open: TransitionSpec;
62
62
  close: TransitionSpec;
63
63
  };
64
+ preloaded: boolean;
64
65
  styleInterpolator: StackCardStyleInterpolator;
65
66
  containerStyle?: StyleProp<ViewStyle>;
66
67
  contentStyle?: StyleProp<ViewStyle>;
@@ -104,7 +105,11 @@ export class Card extends React.Component<Props> {
104
105
  };
105
106
 
106
107
  componentDidMount() {
107
- this.animate({ closing: this.props.closing });
108
+ if (!this.props.preloaded) {
109
+ this.animate({
110
+ closing: this.props.closing,
111
+ });
112
+ }
108
113
  this.isCurrentlyMounted = true;
109
114
  }
110
115
 
@@ -133,7 +138,7 @@ export class Card extends React.Component<Props> {
133
138
  this.lastToValue !== toValue
134
139
  ) {
135
140
  // We need to trigger the animation when route was closed
136
- // Thr route might have been closed by a `POP` action or by a gesture
141
+ // The route might have been closed by a `POP` action or by a gesture
137
142
  // When route was closed due to a gesture, the animation would've happened already
138
143
  // It's still important to trigger the animation so that `onClose` is called
139
144
  // If `onClose` is not called, cleanup step won't be performed for gestures
@@ -142,7 +147,7 @@ export class Card extends React.Component<Props> {
142
147
  }
143
148
 
144
149
  componentWillUnmount() {
145
- this.props.gesture.stopAnimation();
150
+ this.props.gesture?.stopAnimation();
146
151
  this.isCurrentlyMounted = false;
147
152
  this.handleEndInteraction();
148
153
  }
@@ -178,7 +183,7 @@ export class Card extends React.Component<Props> {
178
183
  closing: boolean;
179
184
  velocity?: number;
180
185
  }) => {
181
- const { gesture, transitionSpec, onOpen, onClose, onTransition } =
186
+ const { transitionSpec, onOpen, onClose, onTransition, gesture } =
182
187
  this.props;
183
188
 
184
189
  const toValue = this.getAnimateToValue({
@@ -232,13 +237,15 @@ export class Card extends React.Component<Props> {
232
237
  layout,
233
238
  gestureDirection,
234
239
  direction,
240
+ preloaded,
235
241
  }: {
236
242
  closing?: boolean;
237
243
  layout: Layout;
238
244
  gestureDirection: GestureDirection;
239
245
  direction: LocaleDirection;
246
+ preloaded: boolean;
240
247
  }) => {
241
- if (!closing) {
248
+ if (!closing && !preloaded) {
242
249
  return 0;
243
250
  }
244
251
 
@@ -298,7 +305,10 @@ export class Card extends React.Component<Props> {
298
305
  ? nativeEvent.velocityY
299
306
  : nativeEvent.velocityX;
300
307
 
301
- this.animate({ closing: this.props.closing, velocity });
308
+ this.animate({
309
+ closing: this.props.closing,
310
+ velocity,
311
+ });
302
312
 
303
313
  onGestureCanceled?.();
304
314
  break;
@@ -6,7 +6,7 @@ import {
6
6
  } from '@react-navigation/elements';
7
7
  import {
8
8
  type Route,
9
- useLinkTools,
9
+ useLinkBuilder,
10
10
  useLocale,
11
11
  useTheme,
12
12
  } from '@react-navigation/native';
@@ -28,6 +28,7 @@ type Props = {
28
28
  modal: boolean;
29
29
  layout: Layout;
30
30
  gesture: Animated.Value;
31
+ preloaded: boolean;
31
32
  scene: Scene;
32
33
  safeAreaInsetTop: number;
33
34
  safeAreaInsetRight: number;
@@ -36,7 +37,6 @@ type Props = {
36
37
  getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
37
38
  getFocusedRoute: () => Route<string>;
38
39
  renderHeader: (props: HeaderContainerProps) => React.ReactNode;
39
- renderScene: (props: { route: Route<string> }) => React.ReactNode;
40
40
  onOpenRoute: (props: { route: Route<string> }) => void;
41
41
  onCloseRoute: (props: { route: Route<string> }) => void;
42
42
  onTransitionStart: (
@@ -84,8 +84,8 @@ function CardContainerInner({
84
84
  onGestureStart,
85
85
  onTransitionEnd,
86
86
  onTransitionStart,
87
+ preloaded,
87
88
  renderHeader,
88
- renderScene,
89
89
  safeAreaInsetBottom,
90
90
  safeAreaInsetLeft,
91
91
  safeAreaInsetRight,
@@ -205,7 +205,7 @@ function CardContainerInner({
205
205
  transitionSpec,
206
206
  } = scene.descriptor.options;
207
207
 
208
- const { buildHref } = useLinkTools();
208
+ const { buildHref } = useLinkBuilder();
209
209
  const previousScene = getPreviousScene({ route: scene.descriptor.route });
210
210
 
211
211
  let backTitle: string | undefined;
@@ -252,6 +252,7 @@ function CardContainerInner({
252
252
  importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
253
253
  pointerEvents={active ? 'box-none' : pointerEvents}
254
254
  pageOverflowEnabled={headerMode !== 'float' && presentation !== 'modal'}
255
+ preloaded={preloaded}
255
256
  containerStyle={
256
257
  hasAbsoluteFloatHeader && headerMode !== 'screen'
257
258
  ? { marginTop: headerHeight }
@@ -294,7 +295,7 @@ function CardContainerInner({
294
295
  <HeaderHeightContext.Provider
295
296
  value={headerShown ? headerHeight : parentHeaderHeight ?? 0}
296
297
  >
297
- {renderScene({ route: scene.descriptor.route })}
298
+ {scene.descriptor.render()}
298
299
  </HeaderHeightContext.Provider>
299
300
  </HeaderShownContext.Provider>
300
301
  </HeaderBackContext.Provider>
@@ -52,6 +52,8 @@ type Props = {
52
52
  insets: EdgeInsets;
53
53
  state: StackNavigationState<ParamListBase>;
54
54
  descriptors: StackDescriptorMap;
55
+ // eslint-disable-next-line react/no-unused-prop-types
56
+ preloadedDescriptors: StackDescriptorMap;
55
57
  routes: Route<string>[];
56
58
  // eslint-disable-next-line react/no-unused-prop-types
57
59
  openingRouteKeys: string[];
@@ -62,7 +64,6 @@ type Props = {
62
64
  route: Route<string>;
63
65
  }) => Route<string> | undefined;
64
66
  renderHeader: (props: HeaderContainerProps) => React.ReactNode;
65
- renderScene: (props: { route: Route<string> }) => React.ReactNode;
66
67
  isParentHeaderShown: boolean;
67
68
  isParentModal: boolean;
68
69
  onTransitionStart: (
@@ -174,7 +175,7 @@ const getHeaderHeights = (
174
175
 
175
176
  const getDistanceFromOptions = (
176
177
  layout: Layout,
177
- descriptor: StackDescriptor,
178
+ descriptor: StackDescriptor | undefined,
178
179
  isRTL: boolean
179
180
  ) => {
180
181
  const {
@@ -190,7 +191,7 @@ const getDistanceFromOptions = (
190
191
  const getProgressFromGesture = (
191
192
  gesture: Animated.Value,
192
193
  layout: Layout,
193
- descriptor: StackDescriptor,
194
+ descriptor: StackDescriptor | undefined,
194
195
  isRTL: boolean
195
196
  ) => {
196
197
  const distance = getDistanceFromOptions(
@@ -229,15 +230,20 @@ export class CardStack extends React.Component<Props, State> {
229
230
  return null;
230
231
  }
231
232
 
232
- const gestures = props.routes.reduce<GestureValues>((acc, curr) => {
233
- const descriptor = props.descriptors[curr.key];
233
+ const gestures = [
234
+ ...props.routes,
235
+ ...props.state.preloadedRoutes,
236
+ ].reduce<GestureValues>((acc, curr) => {
237
+ const descriptor =
238
+ props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
234
239
  const { animationEnabled } = descriptor?.options || {};
235
240
 
236
241
  acc[curr.key] =
237
242
  state.gestures[curr.key] ||
238
243
  new Animated.Value(
239
- props.openingRouteKeys.includes(curr.key) &&
240
- animationEnabled !== false
244
+ (props.openingRouteKeys.includes(curr.key) &&
245
+ animationEnabled !== false) ||
246
+ props.state.preloadedRoutes.includes(curr)
241
247
  ? getDistanceFromOptions(
242
248
  state.layout,
243
249
  descriptor,
@@ -249,147 +255,156 @@ export class CardStack extends React.Component<Props, State> {
249
255
  return acc;
250
256
  }, {});
251
257
 
252
- const scenes = props.routes.map((route, index, self) => {
253
- const previousRoute = self[index - 1];
254
- const nextRoute = self[index + 1];
255
-
256
- const oldScene = state.scenes[index];
257
-
258
- const currentGesture = gestures[route.key];
259
- const previousGesture = previousRoute
260
- ? gestures[previousRoute.key]
261
- : undefined;
262
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
263
-
264
- const descriptor =
265
- props.descriptors[route.key] ||
266
- state.descriptors[route.key] ||
267
- (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
268
-
269
- const nextDescriptor =
270
- props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key];
271
-
272
- const previousDescriptor =
273
- props.descriptors[previousRoute?.key] ||
274
- state.descriptors[previousRoute?.key];
275
-
276
- // When a screen is not the last, it should use next screen's transition config
277
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
278
- // For example combining a slide and a modal transition would look wrong otherwise
279
- // With this approach, combining different transition styles in the same navigator mostly looks right
280
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
281
- // but majority of the transitions look alright
282
- const optionsForTransitionConfig =
283
- index !== self.length - 1 &&
284
- nextDescriptor &&
285
- nextDescriptor.options.presentation !== 'transparentModal'
286
- ? nextDescriptor.options
287
- : descriptor.options;
288
-
289
- const defaultTransitionPreset =
290
- optionsForTransitionConfig.presentation === 'modal'
291
- ? ModalTransition
292
- : optionsForTransitionConfig.presentation === 'transparentModal'
293
- ? ModalFadeTransition
294
- : DefaultTransition;
295
-
296
- const {
297
- animationEnabled = Platform.OS !== 'web' &&
298
- Platform.OS !== 'windows' &&
299
- Platform.OS !== 'macos',
300
- gestureEnabled = Platform.OS === 'ios' && animationEnabled,
301
- gestureDirection = defaultTransitionPreset.gestureDirection,
302
- transitionSpec = defaultTransitionPreset.transitionSpec,
303
- cardStyleInterpolator = animationEnabled === false
304
- ? forNoAnimationCard
305
- : defaultTransitionPreset.cardStyleInterpolator,
306
- headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
307
- cardOverlayEnabled = (Platform.OS !== 'ios' &&
308
- optionsForTransitionConfig.presentation !== 'transparentModal') ||
309
- getIsModalPresentation(cardStyleInterpolator),
310
- } = optionsForTransitionConfig;
311
-
312
- const headerMode: StackHeaderMode =
313
- descriptor.options.headerMode ??
314
- (!(
315
- optionsForTransitionConfig.presentation === 'modal' ||
316
- optionsForTransitionConfig.presentation === 'transparentModal' ||
317
- nextDescriptor?.options.presentation === 'modal' ||
318
- nextDescriptor?.options.presentation === 'transparentModal' ||
319
- getIsModalPresentation(cardStyleInterpolator)
320
- ) &&
321
- Platform.OS === 'ios' &&
322
- descriptor.options.header === undefined
323
- ? 'float'
324
- : 'screen');
325
-
326
- const isRTL = props.direction === 'rtl';
327
-
328
- const scene = {
329
- route,
330
- descriptor: {
331
- ...descriptor,
332
- options: {
333
- ...descriptor.options,
334
- animationEnabled,
335
- cardOverlayEnabled,
336
- cardStyleInterpolator,
337
- gestureDirection,
338
- gestureEnabled,
339
- headerStyleInterpolator,
340
- transitionSpec,
341
- headerMode,
258
+ const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
259
+ (route, index, self) => {
260
+ // For preloaded screens, we don't care about the previous and the next screen
261
+ const isPreloaded = props.state.preloadedRoutes.includes(route);
262
+ const previousRoute = isPreloaded ? undefined : self[index - 1];
263
+ const nextRoute = isPreloaded ? undefined : self[index + 1];
264
+
265
+ const oldScene = state.scenes[index];
266
+
267
+ const currentGesture = gestures[route.key];
268
+ const previousGesture = previousRoute
269
+ ? gestures[previousRoute.key]
270
+ : undefined;
271
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
272
+
273
+ const descriptor =
274
+ (isPreloaded ? props.preloadedDescriptors : props.descriptors)[
275
+ route.key
276
+ ] ||
277
+ state.descriptors[route.key] ||
278
+ (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
279
+
280
+ const nextDescriptor =
281
+ nextRoute &&
282
+ (props.descriptors[nextRoute?.key] ||
283
+ state.descriptors[nextRoute?.key]);
284
+
285
+ const previousDescriptor =
286
+ previousRoute &&
287
+ (props.descriptors[previousRoute?.key] ||
288
+ state.descriptors[previousRoute?.key]);
289
+
290
+ // When a screen is not the last, it should use next screen's transition config
291
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
292
+ // For example combining a slide and a modal transition would look wrong otherwise
293
+ // With this approach, combining different transition styles in the same navigator mostly looks right
294
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
295
+ // but the majority of the transitions look alright
296
+ const optionsForTransitionConfig =
297
+ index !== self.length - 1 &&
298
+ nextDescriptor &&
299
+ nextDescriptor.options.presentation !== 'transparentModal'
300
+ ? nextDescriptor.options
301
+ : descriptor.options;
302
+
303
+ const defaultTransitionPreset =
304
+ optionsForTransitionConfig.presentation === 'modal'
305
+ ? ModalTransition
306
+ : optionsForTransitionConfig.presentation === 'transparentModal'
307
+ ? ModalFadeTransition
308
+ : DefaultTransition;
309
+
310
+ const {
311
+ animationEnabled = Platform.OS !== 'web' &&
312
+ Platform.OS !== 'windows' &&
313
+ Platform.OS !== 'macos',
314
+ gestureEnabled = Platform.OS === 'ios' && animationEnabled,
315
+ gestureDirection = defaultTransitionPreset.gestureDirection,
316
+ transitionSpec = defaultTransitionPreset.transitionSpec,
317
+ cardStyleInterpolator = animationEnabled === false
318
+ ? forNoAnimationCard
319
+ : defaultTransitionPreset.cardStyleInterpolator,
320
+ headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
321
+ cardOverlayEnabled = (Platform.OS !== 'ios' &&
322
+ optionsForTransitionConfig.presentation !== 'transparentModal') ||
323
+ getIsModalPresentation(cardStyleInterpolator),
324
+ } = optionsForTransitionConfig;
325
+
326
+ const headerMode: StackHeaderMode =
327
+ descriptor.options.headerMode ??
328
+ (!(
329
+ optionsForTransitionConfig.presentation === 'modal' ||
330
+ optionsForTransitionConfig.presentation === 'transparentModal' ||
331
+ nextDescriptor?.options.presentation === 'modal' ||
332
+ nextDescriptor?.options.presentation === 'transparentModal' ||
333
+ getIsModalPresentation(cardStyleInterpolator)
334
+ ) &&
335
+ Platform.OS === 'ios' &&
336
+ descriptor.options.header === undefined
337
+ ? 'float'
338
+ : 'screen');
339
+
340
+ const isRTL = props.direction === 'rtl';
341
+
342
+ const scene = {
343
+ route,
344
+ descriptor: {
345
+ ...descriptor,
346
+ options: {
347
+ ...descriptor.options,
348
+ animationEnabled,
349
+ cardOverlayEnabled,
350
+ cardStyleInterpolator,
351
+ gestureDirection,
352
+ gestureEnabled,
353
+ headerStyleInterpolator,
354
+ transitionSpec,
355
+ headerMode,
356
+ },
342
357
  },
343
- },
344
- progress: {
345
- current: getProgressFromGesture(
346
- currentGesture,
347
- state.layout,
348
- descriptor,
349
- isRTL
350
- ),
351
- next:
352
- nextGesture &&
353
- nextDescriptor?.options.presentation !== 'transparentModal'
358
+ progress: {
359
+ current: getProgressFromGesture(
360
+ currentGesture,
361
+ state.layout,
362
+ descriptor,
363
+ isRTL
364
+ ),
365
+ next:
366
+ nextGesture &&
367
+ nextDescriptor?.options.presentation !== 'transparentModal'
368
+ ? getProgressFromGesture(
369
+ nextGesture,
370
+ state.layout,
371
+ nextDescriptor,
372
+ isRTL
373
+ )
374
+ : undefined,
375
+ previous: previousGesture
354
376
  ? getProgressFromGesture(
355
- nextGesture,
377
+ previousGesture,
356
378
  state.layout,
357
- nextDescriptor,
379
+ previousDescriptor,
358
380
  isRTL
359
381
  )
360
382
  : undefined,
361
- previous: previousGesture
362
- ? getProgressFromGesture(
363
- previousGesture,
364
- state.layout,
365
- previousDescriptor,
366
- isRTL
367
- )
368
- : undefined,
369
- },
370
- __memo: [
371
- state.layout,
372
- descriptor,
373
- nextDescriptor,
374
- previousDescriptor,
375
- currentGesture,
376
- nextGesture,
377
- previousGesture,
378
- ],
379
- };
383
+ },
384
+ __memo: [
385
+ state.layout,
386
+ descriptor,
387
+ nextDescriptor,
388
+ previousDescriptor,
389
+ currentGesture,
390
+ nextGesture,
391
+ previousGesture,
392
+ ],
393
+ };
394
+
395
+ if (
396
+ oldScene &&
397
+ scene.__memo.every((it, i) => {
398
+ // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
399
+ return oldScene.__memo[i] === it;
400
+ })
401
+ ) {
402
+ return oldScene;
403
+ }
380
404
 
381
- if (
382
- oldScene &&
383
- scene.__memo.every((it, i) => {
384
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
385
- return oldScene.__memo[i] === it;
386
- })
387
- ) {
388
- return oldScene;
405
+ return scene;
389
406
  }
390
-
391
- return scene;
392
- });
407
+ );
393
408
 
394
409
  return {
395
410
  routes: props.routes,
@@ -504,7 +519,6 @@ export class CardStack extends React.Component<Props, State> {
504
519
  onOpenRoute,
505
520
  onCloseRoute,
506
521
  renderHeader,
507
- renderScene,
508
522
  isParentHeaderShown,
509
523
  isParentModal,
510
524
  onTransitionStart,
@@ -599,10 +613,23 @@ export class CardStack extends React.Component<Props, State> {
599
613
  style={styles.container}
600
614
  onLayout={this.handleLayout}
601
615
  >
602
- {routes.map((route, index, self) => {
616
+ {[...routes, ...state.preloadedRoutes].map((route, index) => {
603
617
  const focused = focusedRoute.key === route.key;
604
618
  const gesture = gestures[route.key];
605
619
  const scene = scenes[index];
620
+ // It is possible that for a short period the route appears in both arrays.
621
+ // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
622
+ // However, due to the router action, it immediately populates the `preloadedRoutes` array.
623
+ // Practically, the logic below takes care that it is rendered only once.
624
+ const isPreloaded =
625
+ state.preloadedRoutes.includes(route) && !routes.includes(route);
626
+ if (
627
+ state.preloadedRoutes.includes(route) &&
628
+ routes.includes(route) &&
629
+ index >= routes.length
630
+ ) {
631
+ return null;
632
+ }
606
633
 
607
634
  // For the screens that shouldn't be active, the value is 0
608
635
  // For those that should be active, but are not the top screen, the value is 1
@@ -614,15 +641,15 @@ export class CardStack extends React.Component<Props, State> {
614
641
  | 1
615
642
  | 2 = 1;
616
643
 
617
- if (index < self.length - activeScreensLimit - 1) {
644
+ if (index < routes.length - activeScreensLimit - 1 || isPreloaded) {
618
645
  // screen should be inactive because it is too deep in the stack
619
646
  isScreenActive = STATE_INACTIVE;
620
647
  } else {
621
- const sceneForActivity = scenes[self.length - 1];
648
+ const sceneForActivity = scenes[routes.length - 1];
622
649
  const outputValue =
623
- index === self.length - 1
650
+ index === routes.length - 1
624
651
  ? STATE_ON_TOP // the screen is on top after the transition
625
- : index >= self.length - activeScreensLimit
652
+ : index >= routes.length - activeScreensLimit
626
653
  ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
627
654
  : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
628
655
  isScreenActive = sceneForActivity
@@ -667,7 +694,7 @@ export class CardStack extends React.Component<Props, State> {
667
694
  return (
668
695
  <MaybeScreen
669
696
  key={route.key}
670
- style={StyleSheet.absoluteFill}
697
+ style={[StyleSheet.absoluteFill]}
671
698
  enabled={detachInactiveScreens}
672
699
  active={isScreenActive}
673
700
  freezeOnBlur={freezeOnBlur}
@@ -677,7 +704,7 @@ export class CardStack extends React.Component<Props, State> {
677
704
  index={index}
678
705
  interpolationIndex={interpolationIndex}
679
706
  modal={isModal}
680
- active={index === self.length - 1}
707
+ active={index === routes.length - 1}
681
708
  focused={focused}
682
709
  closing={closingRouteKeys.includes(route.key)}
683
710
  layout={layout}
@@ -699,13 +726,13 @@ export class CardStack extends React.Component<Props, State> {
699
726
  isFloatHeaderAbsolute && !headerTransparent
700
727
  }
701
728
  renderHeader={renderHeader}
702
- renderScene={renderScene}
703
729
  onOpenRoute={onOpenRoute}
704
730
  onCloseRoute={onCloseRoute}
705
731
  onTransitionStart={onTransitionStart}
706
732
  onTransitionEnd={onTransitionEnd}
707
733
  isNextScreenTransparent={isNextScreenTransparent}
708
734
  detachCurrentScreen={detachCurrentScreen}
735
+ preloaded={isPreloaded}
709
736
  />
710
737
  </MaybeScreen>
711
738
  );