@react-navigation/stack 8.0.0-alpha.6 → 8.0.0-alpha.60

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 (147) hide show
  1. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +280 -84
  2. package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  3. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +11 -44
  4. package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
  5. package/lib/module/TransitionConfigs/TransitionEasings.js +71 -0
  6. package/lib/module/TransitionConfigs/TransitionEasings.js.map +1 -0
  7. package/lib/module/TransitionConfigs/TransitionPresets.js +50 -10
  8. package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
  9. package/lib/module/TransitionConfigs/TransitionSpecs.js +69 -34
  10. package/lib/module/TransitionConfigs/TransitionSpecs.js.map +1 -1
  11. package/lib/module/index.js +2 -2
  12. package/lib/module/index.js.map +1 -1
  13. package/lib/module/navigators/createStackNavigator.js +37 -33
  14. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  15. package/lib/module/utils/GestureHandlerContext.js +5 -0
  16. package/lib/module/utils/GestureHandlerContext.js.map +1 -0
  17. package/lib/module/utils/gestureActivationCriteria.js +10 -8
  18. package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
  19. package/lib/module/utils/useCardAnimation.js +1 -1
  20. package/lib/module/utils/useCardAnimation.js.map +1 -1
  21. package/lib/module/utils/useGestureHandler.js +9 -0
  22. package/lib/module/utils/useGestureHandler.js.map +1 -0
  23. package/lib/module/utils/useKeyboardManager.js +71 -26
  24. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  25. package/lib/module/views/GestureHandler.js +15 -14
  26. package/lib/module/views/GestureHandler.js.map +1 -1
  27. package/lib/module/views/GestureHandlerNative.js +16 -10
  28. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  29. package/lib/module/views/Header/Header.js +4 -2
  30. package/lib/module/views/Header/Header.js.map +1 -1
  31. package/lib/module/views/Header/HeaderContainer.js +60 -21
  32. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  33. package/lib/module/views/Header/HeaderSegment.js +6 -9
  34. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  35. package/lib/module/views/Stack/Card.js +170 -123
  36. package/lib/module/views/Stack/Card.js.map +1 -1
  37. package/lib/module/views/Stack/CardContainer.js +96 -98
  38. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  39. package/lib/module/views/Stack/CardContent.js +10 -37
  40. package/lib/module/views/Stack/CardContent.js.map +1 -1
  41. package/lib/module/views/Stack/CardStack.js +211 -147
  42. package/lib/module/views/Stack/CardStack.js.map +1 -1
  43. package/lib/module/views/Stack/StackView.js +106 -57
  44. package/lib/module/views/Stack/StackView.js.map +1 -1
  45. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +20 -12
  46. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  47. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +4 -4
  48. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  49. package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts +38 -0
  50. package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts.map +1 -0
  51. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +15 -3
  52. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  53. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +44 -15
  54. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  55. package/lib/typescript/src/index.d.ts +12 -12
  56. package/lib/typescript/src/index.d.ts.map +1 -1
  57. package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
  58. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  59. package/lib/typescript/src/types.d.ts +120 -108
  60. package/lib/typescript/src/types.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
  62. package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
  63. package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
  64. package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
  65. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
  66. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  67. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  68. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  69. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  70. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  71. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
  72. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
  73. package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
  74. package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
  75. package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
  76. package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
  77. package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
  78. package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
  79. package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
  80. package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
  81. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
  82. package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
  83. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
  84. package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
  85. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
  86. package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
  87. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  88. package/lib/typescript/src/views/Header/Header.d.ts +2 -2
  89. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  90. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
  91. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  92. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +6 -4
  93. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  94. package/lib/typescript/src/views/Stack/Card.d.ts +14 -8
  95. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  96. package/lib/typescript/src/views/Stack/CardContainer.d.ts +7 -7
  97. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  98. package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
  99. package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
  100. package/lib/typescript/src/views/Stack/CardStack.d.ts +10 -6
  101. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  102. package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
  103. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  104. package/package.json +23 -24
  105. package/src/TransitionConfigs/CardStyleInterpolators.tsx +399 -100
  106. package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +15 -46
  107. package/src/TransitionConfigs/TransitionEasings.tsx +90 -0
  108. package/src/TransitionConfigs/TransitionPresets.tsx +56 -10
  109. package/src/TransitionConfigs/TransitionSpecs.tsx +68 -34
  110. package/src/index.tsx +3 -2
  111. package/src/navigators/createStackNavigator.tsx +74 -91
  112. package/src/types.tsx +149 -123
  113. package/src/utils/GestureHandlerContext.tsx +5 -0
  114. package/src/utils/gestureActivationCriteria.tsx +14 -10
  115. package/src/utils/useCardAnimation.tsx +1 -1
  116. package/src/utils/useGestureHandler.tsx +9 -0
  117. package/src/utils/useKeyboardManager.tsx +96 -33
  118. package/src/views/GestureHandler.tsx +24 -16
  119. package/src/views/GestureHandlerNative.tsx +24 -13
  120. package/src/views/Header/Header.tsx +4 -2
  121. package/src/views/Header/HeaderContainer.tsx +79 -37
  122. package/src/views/Header/HeaderSegment.tsx +10 -11
  123. package/src/views/Stack/Card.tsx +241 -154
  124. package/src/views/Stack/CardContainer.tsx +111 -127
  125. package/src/views/Stack/CardContent.tsx +13 -51
  126. package/src/views/Stack/CardStack.tsx +478 -335
  127. package/src/views/Stack/StackView.tsx +181 -82
  128. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  129. package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
  130. package/lib/module/utils/findLastIndex.js +0 -11
  131. package/lib/module/utils/findLastIndex.js.map +0 -1
  132. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  133. package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
  134. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  135. package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
  136. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
  137. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
  138. package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
  139. package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
  140. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
  141. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
  142. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
  143. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
  144. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  145. package/src/utils/findLastIndex.tsx +0 -9
  146. package/src/utils/useGestureHandlerRef.tsx +0 -15
  147. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
@@ -1,13 +1,11 @@
1
1
  "use strict";
2
2
 
3
3
  import { getDefaultHeaderHeight } from '@react-navigation/elements';
4
- import { SafeAreaProviderCompat } from '@react-navigation/elements/internal';
4
+ import { ActivityView, SafeAreaProviderCompat } from '@react-navigation/elements/internal';
5
5
  import * as React from 'react';
6
6
  import { Animated, Platform, StyleSheet, View } from 'react-native';
7
- import { Screen, ScreenContainer } from 'react-native-screens';
8
- import { forModalPresentationIOS, forNoAnimation as forNoAnimationCard } from "../../TransitionConfigs/CardStyleInterpolators.js";
9
- import { BottomSheetAndroid, DefaultTransition, FadeFromBottomAndroid, FadeFromRightAndroid, ModalFadeTransition, ModalSlideFromBottomIOS, ModalTransition, RevealFromBottomAndroid, ScaleFromCenterAndroid, SlideFromLeftIOS, SlideFromRightIOS } from "../../TransitionConfigs/TransitionPresets.js";
10
- import { findLastIndex } from "../../utils/findLastIndex.js";
7
+ import { forBottomSheetAndroid, forCrossDissolveIOS, forDialogAndroid, forFadeFromBottomAndroid, forFadeFromRightAndroid, forFlipIOS, forModalPresentationIOS, forNoAnimation as forNoAnimationCard, forRevealFromBottomAndroid, forScaleFromCenterAndroid, forVerticalIOS } from "../../TransitionConfigs/CardStyleInterpolators.js";
8
+ import { DefaultTransition, FadeFromBottomAndroid, FadeFromRightAndroid, ModalFadeTransition, ModalFlipIOS, ModalSlideFromBottomIOS, ModalTransition, RevealFromBottomAndroid, ScaleFromCenterAndroid, SlideFromLeftIOS, SlideFromRightIOS } from "../../TransitionConfigs/TransitionPresets.js";
11
9
  import { getDistanceForDirection } from "../../utils/getDistanceForDirection.js";
12
10
  import { getModalRouteKeys } from "../../utils/getModalRoutesKeys.js";
13
11
  import { CardContainer } from "./CardContainer.js";
@@ -17,27 +15,30 @@ const NAMED_TRANSITIONS_PRESETS = {
17
15
  fade: ModalFadeTransition,
18
16
  fade_from_bottom: FadeFromBottomAndroid,
19
17
  fade_from_right: FadeFromRightAndroid,
18
+ flip: ModalFlipIOS,
19
+ ios_from_left: SlideFromLeftIOS,
20
+ ios_from_right: SlideFromRightIOS,
20
21
  none: DefaultTransition,
21
22
  reveal_from_bottom: RevealFromBottomAndroid,
22
23
  scale_from_center: ScaleFromCenterAndroid,
23
24
  slide_from_left: SlideFromLeftIOS,
24
25
  slide_from_right: SlideFromRightIOS,
25
- slide_from_bottom: Platform.select({
26
- ios: ModalSlideFromBottomIOS,
27
- default: BottomSheetAndroid
28
- })
26
+ slide_from_bottom: ModalSlideFromBottomIOS
27
+ };
28
+ const GESTURE_DISABLED_INTERPOLATORS = [forCrossDissolveIOS, forDialogAndroid, forFadeFromBottomAndroid, forFadeFromRightAndroid, forFlipIOS, forRevealFromBottomAndroid, forScaleFromCenterAndroid, forVerticalIOS];
29
+ const SCREEN_HEADER_INTERPOLATORS = [...GESTURE_DISABLED_INTERPOLATORS, forBottomSheetAndroid];
30
+ const HIDDEN_HEADER_HEIGHT = {
31
+ measured: false,
32
+ value: 0
29
33
  };
30
- const EPSILON = 1e-5;
31
- const STATE_INACTIVE = 0;
32
- const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
33
- const STATE_ON_TOP = 2;
34
- const FALLBACK_DESCRIPTOR = Object.freeze({
35
- options: {}
36
- });
37
34
  const getInterpolationIndex = (scenes, index) => {
35
+ const scene = scenes[index];
36
+ if (scene == null) {
37
+ throw new Error(`Couldn't find a scene at index ${index}.`);
38
+ }
38
39
  const {
39
40
  cardStyleInterpolator
40
- } = scenes[index].descriptor.options;
41
+ } = scene.descriptor.options;
41
42
 
42
43
  // Start from current card and count backwards the number of cards with same interpolation
43
44
  let interpolationIndex = 0;
@@ -51,11 +52,11 @@ const getInterpolationIndex = (scenes, index) => {
51
52
  return interpolationIndex;
52
53
  };
53
54
  const getIsModalPresentation = cardStyleInterpolator => {
54
- return cardStyleInterpolator === forModalPresentationIOS ||
55
- // Handle custom modal presentation interpolators as well
56
- cardStyleInterpolator.name === 'forModalPresentationIOS';
55
+ return cardStyleInterpolator === forModalPresentationIOS || cardStyleInterpolator.name === forModalPresentationIOS.name;
57
56
  };
58
- const getIsModal = (scene, interpolationIndex, isParentModal) => {
57
+ const getIsGestureEnabledByDefault = cardStyleInterpolator => !GESTURE_DISABLED_INTERPOLATORS.some(interpolator => cardStyleInterpolator === interpolator || cardStyleInterpolator.name === interpolator.name);
58
+ const getIsScreenHeader = cardStyleInterpolator => SCREEN_HEADER_INTERPOLATORS.some(interpolator => cardStyleInterpolator === interpolator || cardStyleInterpolator.name === interpolator.name);
59
+ const getIsInModalPresentation = (scene, interpolationIndex, isParentModal) => {
59
60
  if (isParentModal) {
60
61
  return true;
61
62
  }
@@ -72,15 +73,33 @@ const getHeaderHeights = (scenes, insets, isParentHeaderShown, isParentModal, la
72
73
  headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
73
74
  headerStyle
74
75
  } = curr.descriptor.options;
75
- const style = StyleSheet.flatten(headerStyle || {});
76
- const height = 'height' in style && typeof style.height === 'number' ? style.height : previous[curr.route.key];
76
+ const style = StyleSheet.flatten(headerStyle);
77
+ const height = style != null && 'height' in style && typeof style.height === 'number' ? style.height : undefined;
77
78
  const interpolationIndex = getInterpolationIndex(scenes, index);
78
- const isModal = getIsModal(curr, interpolationIndex, isParentModal);
79
- acc[curr.route.key] = typeof height === 'number' ? height : getDefaultHeaderHeight({
80
- landscape: layout.width > layout.height,
81
- modalPresentation: isModal,
82
- topInset: headerStatusBarHeight
83
- });
79
+ const isModal = getIsInModalPresentation(curr, interpolationIndex, isParentModal);
80
+ if (typeof height === 'number') {
81
+ acc[curr.route.key] = {
82
+ measured: true,
83
+ value: height
84
+ };
85
+ } else {
86
+ const previousHeight = previous[curr.route.key];
87
+ const defaultHeight = getDefaultHeaderHeight({
88
+ landscape: layout.width > layout.height,
89
+ modalPresentation: isModal,
90
+ topInset: headerStatusBarHeight
91
+ });
92
+
93
+ // If we hadn't measured before, use re-computed default height
94
+ if (!previousHeight?.measured && defaultHeight !== previousHeight?.value) {
95
+ acc[curr.route.key] = {
96
+ measured: false,
97
+ value: defaultHeight
98
+ };
99
+ } else if (previousHeight) {
100
+ acc[curr.route.key] = previousHeight;
101
+ }
102
+ }
84
103
  return acc;
85
104
  }, {});
86
105
  };
@@ -118,32 +137,54 @@ function getDefaultAnimation(animation) {
118
137
  export function getAnimationEnabled(animation) {
119
138
  return getDefaultAnimation(animation) !== 'none';
120
139
  }
140
+ const getAllRoutes = (routes, state) => {
141
+ const routeKeys = new Set(routes.map(route => route.key));
142
+ const inactiveRoutes = state.routes.slice(state.index + 1);
143
+
144
+ // If a route is moved from active routes to inactive routes,
145
+ // It can still be in the local copy of `routes` until the animation ends
146
+ // So we need to deduplicate the routes to avoid rendering the same route twice
147
+ return [...routes, ...inactiveRoutes.filter(route => !routeKeys.has(route.key))];
148
+ };
149
+ const isInactiveRoute = (route, routes) =>
150
+ // `routes` contains active routes and routes animating during transitions.
151
+ // Any route added by `getAllRoutes` that's missing from this list is inactive.
152
+ !routes.some(currentRoute => currentRoute.key === route.key);
121
153
  export class CardStack extends React.Component {
122
154
  static getDerivedStateFromProps(props, state) {
123
155
  if (props.routes === state.routes && props.descriptors === state.descriptors) {
124
156
  return null;
125
157
  }
126
- const gestures = [...props.routes, ...props.state.preloadedRoutes].reduce((acc, curr) => {
158
+ const allRoutes = getAllRoutes(props.routes, props.state);
159
+ const gestures = allRoutes.reduce((acc, curr) => {
127
160
  const descriptor = props.descriptors[curr.key];
128
161
  const {
129
162
  animation
130
163
  } = descriptor?.options || {};
131
- acc[curr.key] = state.gestures[curr.key] || new Animated.Value(props.openingRouteKeys.includes(curr.key) && getAnimationEnabled(animation) || props.state.preloadedRoutes.includes(curr) ? getDistanceFromOptions(state.layout, descriptor?.options, props.direction === 'rtl') : 0);
164
+ acc[curr.key] = state.gestures[curr.key] || new Animated.Value(props.openingRouteKeys.includes(curr.key) && getAnimationEnabled(animation) || isInactiveRoute(curr, props.routes) ? getDistanceFromOptions(state.layout, descriptor?.options, props.direction === 'rtl') : 0);
132
165
  return acc;
133
166
  }, {});
134
- const modalRouteKeys = getModalRouteKeys([...props.routes, ...props.state.preloadedRoutes], props.descriptors);
135
- const scenes = [...props.routes, ...props.state.preloadedRoutes].map((route, index, self) => {
136
- // For preloaded screens, we don't care about the previous and the next screen
137
- const isPreloaded = props.state.preloadedRoutes.includes(route);
138
- const previousRoute = isPreloaded ? undefined : self[index - 1];
139
- const nextRoute = isPreloaded ? undefined : self[index + 1];
167
+ const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
168
+ const scenes = allRoutes.map((route, index, self) => {
169
+ // For preloaded or retained screens, we don't care about the previous and the next screen
170
+ const isInactive = isInactiveRoute(route, props.routes);
171
+ const previousRoute = isInactive ? undefined : self[index - 1];
172
+ const nextRoute = isInactive ? undefined : self[index + 1];
140
173
  const oldScene = state.scenes[index];
174
+ const previousScene = oldScene?.route.key === route.key ? oldScene : state.scenes.find(scene => scene.route.key === route.key);
175
+ const closing = previousScene?.closing ?? new Animated.Value(props.closingRouteKeys.includes(route.key) ? 1 : 0);
141
176
  const currentGesture = gestures[route.key];
177
+ if (currentGesture == null) {
178
+ throw new Error(`Couldn't find a gesture for route '${route.key}'.`);
179
+ }
142
180
  const previousGesture = previousRoute ? gestures[previousRoute.key] : undefined;
143
181
  const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
144
- const descriptor = props.descriptors[route.key] || state.descriptors[route.key] || (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
145
- const nextOptions = nextRoute && (props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key])?.options;
146
- const previousOptions = previousRoute && (props.descriptors[previousRoute?.key] || state.descriptors[previousRoute?.key])?.options;
182
+ const descriptor = props.descriptors[route.key] ?? state.descriptors[route.key] ?? oldScene?.descriptor;
183
+ if (descriptor == null) {
184
+ throw new Error(`Couldn't find a descriptor for route '${route.key}'.`);
185
+ }
186
+ const nextOptions = nextRoute ? (props.descriptors[nextRoute.key] ?? state.descriptors[nextRoute.key])?.options : undefined;
187
+ const previousOptions = previousRoute ? (props.descriptors[previousRoute.key] ?? state.descriptors[previousRoute.key])?.options : undefined;
147
188
 
148
189
  // When a screen is not the last, it should use next screen's transition config
149
190
  // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
@@ -160,14 +201,15 @@ export class CardStack extends React.Component {
160
201
  const isAnimationEnabled = getAnimationEnabled(animation);
161
202
  const transitionPreset = animation !== 'default' ? NAMED_TRANSITIONS_PRESETS[animation] : optionsForTransitionConfig.presentation === 'transparentModal' ? ModalFadeTransition : optionsForTransitionConfig.presentation === 'modal' || isModal ? ModalTransition : DefaultTransition;
162
203
  const {
163
- gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
164
204
  gestureDirection = transitionPreset.gestureDirection,
165
205
  transitionSpec = transitionPreset.transitionSpec,
166
206
  cardStyleInterpolator = isAnimationEnabled ? transitionPreset.cardStyleInterpolator : forNoAnimationCard,
207
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled && getIsGestureEnabledByDefault(cardStyleInterpolator),
167
208
  headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
168
- cardOverlayEnabled = Platform.OS !== 'ios' && optionsForTransitionConfig.presentation !== 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)
209
+ cardOverlayEnabled,
210
+ cardShadowEnabled
169
211
  } = optionsForTransitionConfig;
170
- const headerMode = descriptor.options.headerMode ?? (!(optionsForTransitionConfig.presentation === 'modal' || optionsForTransitionConfig.presentation === 'transparentModal' || nextOptions?.presentation === 'modal' || nextOptions?.presentation === 'transparentModal' || getIsModalPresentation(cardStyleInterpolator)) && Platform.OS === 'ios' && descriptor.options.header === undefined ? 'float' : 'screen');
212
+ const headerMode = descriptor.options.headerMode ?? (!(optionsForTransitionConfig.presentation === 'modal' || optionsForTransitionConfig.presentation === 'transparentModal' || nextOptions?.presentation === 'modal' || nextOptions?.presentation === 'transparentModal' || getIsModalPresentation(cardStyleInterpolator) || getIsScreenHeader(cardStyleInterpolator)) && Platform.OS === 'ios' && descriptor.options.header === undefined ? 'float' : 'screen');
171
213
  const isRTL = props.direction === 'rtl';
172
214
  const scene = {
173
215
  route,
@@ -177,6 +219,7 @@ export class CardStack extends React.Component {
177
219
  ...descriptor.options,
178
220
  animation,
179
221
  cardOverlayEnabled,
222
+ cardShadowEnabled,
180
223
  cardStyleInterpolator,
181
224
  gestureDirection,
182
225
  gestureEnabled,
@@ -190,76 +233,25 @@ export class CardStack extends React.Component {
190
233
  next: nextGesture && nextOptions?.presentation !== 'transparentModal' ? getProgressFromGesture(nextGesture, state.layout, nextOptions, isRTL) : undefined,
191
234
  previous: previousGesture ? getProgressFromGesture(previousGesture, state.layout, previousOptions, isRTL) : undefined
192
235
  },
236
+ closing,
193
237
  __memo: [state.layout, descriptor, nextOptions, previousOptions, currentGesture, nextGesture, previousGesture]
194
238
  };
195
239
  if (oldScene && scene.__memo.every((it, i) => {
196
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
197
240
  return oldScene.__memo[i] === it;
198
241
  })) {
199
242
  return oldScene;
200
243
  }
201
244
  return scene;
202
245
  });
203
- let activeStates = state.activeStates;
204
- if (props.routes.length !== state.routes.length) {
205
- let activeScreensLimit = 1;
206
- for (let i = props.routes.length - 1; i >= 0; i--) {
207
- const {
208
- options
209
- } = scenes[i].descriptor;
210
- const {
211
- // By default, we don't want to detach the previous screen of the active one for modals
212
- detachPreviousScreen = options.presentation === 'transparentModal' ? false : getIsModalPresentation(options.cardStyleInterpolator) ? i !== findLastIndex(scenes, scene => {
213
- const {
214
- cardStyleInterpolator
215
- } = scene.descriptor.options;
216
- return cardStyleInterpolator === forModalPresentationIOS || cardStyleInterpolator?.name === 'forModalPresentationIOS';
217
- }) : true
218
- } = options;
219
- if (detachPreviousScreen === false) {
220
- activeScreensLimit++;
221
- } else {
222
- // Check at least last 2 screens before stopping
223
- // This will make sure that screen isn't detached when another screen is animating on top of the transparent one
224
- // e.g. opaque -> transparent -> opaque
225
- if (i <= props.routes.length - 2) {
226
- break;
227
- }
228
- }
229
- }
230
- activeStates = props.routes.map((_, index, self) => {
231
- // The activity state represents state of the screen:
232
- // 0 - inactive, the screen is detached
233
- // 1 - transitioning or below the top screen, the screen is mounted but interaction is disabled
234
- // 2 - on top of the stack, the screen is mounted and interaction is enabled
235
- let activityState;
236
- const lastActiveState = state.activeStates[index];
237
- const activeAfterTransition = index >= self.length - activeScreensLimit;
238
- if (lastActiveState === STATE_INACTIVE && !activeAfterTransition) {
239
- // screen was inactive before and it will still be inactive after the transition
240
- activityState = STATE_INACTIVE;
241
- } else {
242
- const sceneForActivity = scenes[self.length - 1];
243
- const outputValue = index === self.length - 1 ? STATE_ON_TOP // the screen is on top after the transition
244
- : activeAfterTransition ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
245
- : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
246
-
247
- activityState = sceneForActivity ? sceneForActivity.progress.current.interpolate({
248
- inputRange: [0, 1 - EPSILON, 1],
249
- outputRange: [1, 1, outputValue],
250
- extrapolate: 'clamp'
251
- }) : STATE_TRANSITIONING_OR_BELOW_TOP;
252
- }
253
- return activityState;
254
- });
255
- }
256
246
  return {
257
247
  routes: props.routes,
258
248
  scenes,
259
249
  gestures,
260
250
  descriptors: props.descriptors,
261
- activeStates,
262
- headerHeights: getHeaderHeights(scenes, props.insets, props.isParentHeaderShown, props.isParentModal, state.layout, state.headerHeights)
251
+ headerHeights: getHeaderHeights(scenes, props.insets, props.isParentHeaderShown, props.isParentModal, state.layout, state.headerHeights),
252
+ // Nested navigators clean up `route.state` when the screen is paused with `Activity`
253
+ // So we remember routes which had state until they are removed
254
+ nestedStateRouteKeys: allRoutes.filter(route => 'state' in route && route.state || state.nestedStateRouteKeys.includes(route.key)).map(route => route.key)
263
255
  };
264
256
  }
265
257
  constructor(props) {
@@ -270,13 +262,13 @@ export class CardStack extends React.Component {
270
262
  gestures: {},
271
263
  layout: SafeAreaProviderCompat.initialMetrics.frame,
272
264
  descriptors: this.props.descriptors,
273
- activeStates: [],
274
265
  // Used when card's header is null and mode is float to make transition
275
266
  // between screens with headers and those without headers smooth.
276
267
  // This is not a great heuristic here. We don't know synchronously
277
268
  // on mount what the header height is so we have just used the most
278
269
  // common cases here.
279
- headerHeights: {}
270
+ headerHeights: {},
271
+ nestedStateRouteKeys: []
280
272
  };
281
273
  }
282
274
  handleLayout = e => {
@@ -306,13 +298,16 @@ export class CardStack extends React.Component {
306
298
  headerHeights
307
299
  }) => {
308
300
  const previousHeight = headerHeights[route.key];
309
- if (previousHeight === height) {
301
+ if (previousHeight?.value === height) {
310
302
  return null;
311
303
  }
312
304
  return {
313
305
  headerHeights: {
314
306
  ...headerHeights,
315
- [route.key]: height
307
+ [route.key]: {
308
+ measured: true,
309
+ value: height
310
+ }
316
311
  }
317
312
  };
318
313
  });
@@ -321,7 +316,11 @@ export class CardStack extends React.Component {
321
316
  const {
322
317
  state
323
318
  } = this.props;
324
- return state.routes[state.index];
319
+ const route = state.routes[state.index];
320
+ if (route == null) {
321
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
322
+ }
323
+ return route;
325
324
  };
326
325
  getPreviousScene = ({
327
326
  route
@@ -348,27 +347,36 @@ export class CardStack extends React.Component {
348
347
  routes,
349
348
  openingRouteKeys,
350
349
  closingRouteKeys,
350
+ replacingRouteKeys,
351
351
  onOpenRoute,
352
352
  onCloseRoute,
353
+ onGestureStart,
354
+ onGestureEnd,
355
+ onGestureCancel,
353
356
  renderHeader,
354
357
  isParentHeaderShown,
355
358
  isParentModal,
356
359
  onTransitionStart,
357
- onTransitionEnd,
358
- onGestureStart,
359
- onGestureEnd,
360
- onGestureCancel,
361
- detachInactiveScreens = Platform.OS === 'web' || Platform.OS === 'android' || Platform.OS === 'ios'
360
+ onTransitionEnd
362
361
  } = this.props;
363
362
  const {
364
363
  scenes,
365
364
  layout,
366
365
  gestures,
367
- activeStates,
368
- headerHeights
366
+ headerHeights,
367
+ nestedStateRouteKeys
369
368
  } = this.state;
369
+ const allRoutes = getAllRoutes(routes, state);
370
370
  const focusedRoute = state.routes[state.index];
371
- const isFloatHeaderAbsolute = scenes.slice(-2).some(scene => {
371
+ if (focusedRoute == null) {
372
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
373
+ }
374
+
375
+ // Render only two top-most active headers as a workaround for
376
+ // https://github.com/react-navigation/react-navigation/issues/12456.
377
+ // If the header is persisted, it might be placed incorrectly when navigating back
378
+ const activeHeaderScenes = scenes.filter(scene => !isInactiveRoute(scene.route, routes)).slice(-2);
379
+ const isFloatHeaderAbsolute = activeHeaderScenes.some(scene => {
372
380
  const options = scene.descriptor.options ?? {};
373
381
  const {
374
382
  headerMode,
@@ -381,71 +389,129 @@ export class CardStack extends React.Component {
381
389
  return false;
382
390
  });
383
391
  const isFloatHeaderFocused = scenes.findLast(scene => scene.route.key === focusedRoute.key)?.descriptor.options.headerMode === 'float';
392
+ const focusedHeaderHeight = headerHeights[focusedRoute.key];
384
393
  return /*#__PURE__*/_jsxs(View, {
385
394
  style: styles.container,
386
395
  children: [renderHeader({
387
396
  mode: 'float',
388
- scenes,
397
+ scenes: activeHeaderScenes,
389
398
  getPreviousScene: this.getPreviousScene,
390
399
  getFocusedRoute: this.getFocusedRoute,
400
+ contentHeight: headerHeights[focusedRoute.key],
391
401
  onContentHeightChange: this.handleHeaderLayout,
392
402
  style: [isFloatHeaderAbsolute && [styles.absolute,
393
403
  // The header title can't be found with maestro without this
394
404
  // In some cases, header buttons may not be touchable as well
395
405
  isFloatHeaderFocused && {
396
- height: headerHeights[focusedRoute.key]
406
+ height: focusedHeaderHeight?.value
397
407
  }]]
398
- }), /*#__PURE__*/_jsx(ScreenContainer, {
399
- enabled: detachInactiveScreens,
408
+ }), /*#__PURE__*/_jsx(View, {
400
409
  style: styles.container,
401
410
  onLayout: this.handleLayout,
402
- children: [...routes, ...state.preloadedRoutes].map((route, index) => {
411
+ children: allRoutes.map((route, index, self) => {
403
412
  const focused = focusedRoute.key === route.key;
404
413
  const gesture = gestures[route.key];
405
414
  const scene = scenes[index];
406
- // It is possible that for a short period the route appears in both arrays.
407
- // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
408
- // However, due to the router action, it immediately populates the `preloadedRoutes` array.
409
- // Practically, the logic below takes care that it is rendered only once.
410
- const isPreloaded = state.preloadedRoutes.includes(route) && !routes.includes(route);
411
- if (state.preloadedRoutes.includes(route) && routes.includes(route) && index >= routes.length) {
412
- return null;
415
+ if (gesture == null) {
416
+ throw new Error(`Couldn't find a gesture for route '${route.key}'.`);
417
+ }
418
+ if (scene == null) {
419
+ throw new Error(`Couldn't find a scene at index ${index}.`);
413
420
  }
421
+ const nextScene = scenes[index + 1];
422
+ const nextRoute = self[index + 1];
414
423
  const {
424
+ inactiveBehavior = 'pause',
425
+ animation,
415
426
  headerShown = true,
416
- headerTransparent,
417
- freezeOnBlur,
418
- autoHideHomeIndicator
427
+ headerMode,
428
+ headerTransparent
419
429
  } = scene.descriptor.options;
420
430
  const safeAreaInsetTop = insets.top;
421
431
  const safeAreaInsetRight = insets.right;
422
432
  const safeAreaInsetBottom = insets.bottom;
423
433
  const safeAreaInsetLeft = insets.left;
424
- const headerHeight = headerShown !== false ? headerHeights[route.key] : 0;
434
+ const headerHeight = headerShown !== false ? headerHeights[route.key] : HIDDEN_HEADER_HEIGHT;
425
435
 
426
436
  // Start from current card and count backwards the number of cards with same interpolation
427
437
  const interpolationIndex = getInterpolationIndex(scenes, index);
428
- const isModal = getIsModal(scene, interpolationIndex, isParentModal);
429
- const isNextScreenTransparent = scenes[index + 1]?.descriptor.options.presentation === 'transparentModal';
430
- const detachCurrentScreen = scenes[index + 1]?.descriptor.options.detachPreviousScreen !== false;
431
- const activityState = isPreloaded ? STATE_INACTIVE : activeStates[index];
432
- return /*#__PURE__*/_jsx(Screen, {
433
- style: [StyleSheet.absoluteFill, {
434
- pointerEvents: 'box-none'
435
- }],
436
- enabled: detachInactiveScreens,
437
- activityState: activityState,
438
- freezeOnBlur: freezeOnBlur,
439
- shouldFreeze: activityState === STATE_INACTIVE && !isPreloaded,
440
- homeIndicatorHidden: autoHideHomeIndicator,
438
+ const isInModalPresentation = getIsInModalPresentation(scene, interpolationIndex, isParentModal);
439
+ const isModal = scene.descriptor.options.presentation === 'modal' || getIsModalPresentation(scene.descriptor.options.cardStyleInterpolator);
440
+ const isNextScreenModal = nextScene != null && (nextScene.descriptor.options.presentation === 'modal' || getIsModalPresentation(nextScene.descriptor.options.cardStyleInterpolator));
441
+ const isNextScreenTransparent = nextScene?.descriptor.options.presentation === 'transparentModal';
442
+ const isAnimationEnabled = getAnimationEnabled(animation) || (
443
+ // Also check next screen's animation,
444
+ // As it will result in both screens being visible
445
+ nextScene ? getAnimationEnabled(nextScene.descriptor.options.animation) : false);
446
+ const isRemoving = replacingRouteKeys.includes(route.key) || closingRouteKeys.includes(route.key);
447
+ const isFocusing = openingRouteKeys.includes(route.key) || nextRoute != null && [...closingRouteKeys, ...replacingRouteKeys].includes(nextRoute.key);
448
+ const isInactive = isInactiveRoute(route, this.props.routes);
449
+ const isRetained = state.retainedRouteKeys?.includes(route.key);
450
+ const isPreloaded = isInactive && !isRetained;
451
+ const isTopmost = index === routes.length - 1;
452
+ const isBeforeLast = index === routes.length - 2;
453
+
454
+ // Keep animating and the last two rendered routes visible for smoother transitions
455
+ const isVisible = focused ||
456
+ // React native destroys the view when hidden
457
+ // So we keep retained screens visible
458
+ // This makes sure any videos being played don't stop etc.
459
+ isRetained || isNextScreenTransparent ||
460
+ // Modal presentation on iOS has multiple screens visible at the same time
461
+ // The base screen before the modals renders the dim, which is always persistent
462
+ // On iPadOS, the base screen is visible while modal is in a smaller area on top
463
+ // The top edge of the last 3 screens can be seen animating behind the modal
464
+ isNextScreenModal && (!isModal || index >= routes.length - 3) ||
465
+ // We only need to keep other screens visible when animation is enabled
466
+ isAnimationEnabled && (isFocusing || isRemoving ||
467
+ // Preloaded screens should stay mounted, but remain hidden until focused
468
+ !isPreloaded && index >= routes.length - 2);
469
+ const activityMode =
470
+ // Keep focused and focusing screens interactive
471
+ focused || isFocusing ? 'normal' : inactiveBehavior === 'none' ||
472
+ // Keep effects running until the closing animation finishes
473
+ isRemoving ||
474
+ // Unpause preloaded or retained screens so updates are visible
475
+ // Handle retained explicitly as isInactive won't be updated until animation end
476
+ isInactive || isRetained ||
477
+ // Keep the screen before transparent screen active
478
+ // This lets the screen under the transparent screen update and animate
479
+ isNextScreenTransparent ||
480
+ // Keep the screen before last screen active
481
+ // Otherwise it breaks animation when going back
482
+ isBeforeLast ? 'inert' : inactiveBehavior === 'unmount' &&
483
+ // Don't unmount screens that needs to stay visible
484
+ !isVisible &&
485
+ // Don't unmount screens with nested navigators
486
+ // So we don't lose their state
487
+ !nestedStateRouteKeys.includes(route.key) ? 'unmounted' : 'paused';
488
+ if (activityMode === 'unmounted') {
489
+ return null;
490
+ }
491
+ return /*#__PURE__*/_jsx(ActivityView, {
492
+ mode: activityMode,
493
+ visible: isVisible,
494
+ style: {
495
+ ...StyleSheet.absoluteFill,
496
+ marginTop: headerTransparent || headerMode === 'screen' ? 0 :
497
+ // Add offset for non-transparent floating header
498
+ isFloatHeaderAbsolute ?
499
+ // If header is absolute, offset the card by the header height
500
+ headerHeight?.value :
501
+ // Otherwise, the in-flow float header already takes the
502
+ // focused header's space, so offset by the difference
503
+ headerHeight && focusedHeaderHeight ? headerHeight.value - focusedHeaderHeight.value : 0
504
+ },
441
505
  children: /*#__PURE__*/_jsx(CardContainer, {
442
506
  index: index,
443
507
  interpolationIndex: interpolationIndex,
444
- modal: isModal,
445
- active: index === routes.length - 1,
508
+ modal: isInModalPresentation,
509
+ active: isTopmost,
446
510
  focused: focused,
447
511
  opening: openingRouteKeys.includes(route.key),
448
512
  closing: closingRouteKeys.includes(route.key),
513
+ closingAnimated: scene.closing,
514
+ nextClosingAnimated: nextScene?.closing,
449
515
  layout: layout,
450
516
  gesture: gesture,
451
517
  scene: scene,
@@ -461,15 +527,13 @@ export class CardStack extends React.Component {
461
527
  onHeaderHeightChange: this.handleHeaderLayout,
462
528
  getPreviousScene: this.getPreviousScene,
463
529
  getFocusedRoute: this.getFocusedRoute,
464
- hasAbsoluteFloatHeader: isFloatHeaderAbsolute && !headerTransparent,
465
530
  renderHeader: renderHeader,
466
531
  onOpenRoute: onOpenRoute,
467
532
  onCloseRoute: onCloseRoute,
468
533
  onTransitionStart: onTransitionStart,
469
534
  onTransitionEnd: onTransitionEnd,
470
- isNextScreenTransparent: isNextScreenTransparent,
471
- detachCurrentScreen: detachCurrentScreen,
472
- preloaded: isPreloaded
535
+ preloaded: isInactive,
536
+ children: scene.descriptor.render()
473
537
  })
474
538
  }, route.key);
475
539
  })