@react-navigation/stack 8.0.0-alpha.5 → 8.0.0-alpha.50

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 (133) hide show
  1. package/lib/module/TransitionConfigs/CardStyleInterpolators.js +36 -0
  2. package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
  3. package/lib/module/index.js +2 -2
  4. package/lib/module/index.js.map +1 -1
  5. package/lib/module/navigators/createStackNavigator.js +3 -7
  6. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  7. package/lib/module/utils/GestureHandlerContext.js +5 -0
  8. package/lib/module/utils/GestureHandlerContext.js.map +1 -0
  9. package/lib/module/utils/gestureActivationCriteria.js +10 -8
  10. package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
  11. package/lib/module/utils/useCardAnimation.js +1 -1
  12. package/lib/module/utils/useCardAnimation.js.map +1 -1
  13. package/lib/module/utils/useGestureHandler.js +9 -0
  14. package/lib/module/utils/useGestureHandler.js.map +1 -0
  15. package/lib/module/utils/useKeyboardManager.js +71 -26
  16. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  17. package/lib/module/views/GestureHandler.js +15 -14
  18. package/lib/module/views/GestureHandler.js.map +1 -1
  19. package/lib/module/views/GestureHandlerNative.js +16 -10
  20. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  21. package/lib/module/views/Header/Header.js +2 -2
  22. package/lib/module/views/Header/Header.js.map +1 -1
  23. package/lib/module/views/Header/HeaderContainer.js +52 -18
  24. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  25. package/lib/module/views/Header/HeaderSegment.js +3 -3
  26. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  27. package/lib/module/views/Stack/Card.js +101 -92
  28. package/lib/module/views/Stack/Card.js.map +1 -1
  29. package/lib/module/views/Stack/CardContainer.js +72 -98
  30. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  31. package/lib/module/views/Stack/CardContent.js +2 -0
  32. package/lib/module/views/Stack/CardContent.js.map +1 -1
  33. package/lib/module/views/Stack/CardStack.js +183 -132
  34. package/lib/module/views/Stack/CardStack.js.map +1 -1
  35. package/lib/module/views/Stack/StackView.js +103 -54
  36. package/lib/module/views/Stack/StackView.js.map +1 -1
  37. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +1 -1
  38. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  39. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +1 -1
  40. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  41. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  42. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  43. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  44. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  45. package/lib/typescript/src/index.d.ts +12 -12
  46. package/lib/typescript/src/index.d.ts.map +1 -1
  47. package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
  48. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  49. package/lib/typescript/src/types.d.ts +52 -55
  50. package/lib/typescript/src/types.d.ts.map +1 -1
  51. package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
  52. package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
  53. package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
  54. package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
  55. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
  56. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  57. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  58. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  59. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  60. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
  62. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
  63. package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
  64. package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
  65. package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
  66. package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
  67. package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
  68. package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
  69. package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
  70. package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
  71. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
  72. package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
  73. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
  74. package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
  75. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
  76. package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
  77. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  78. package/lib/typescript/src/views/Header/Header.d.ts +2 -2
  79. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  80. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
  81. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  82. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +5 -4
  83. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  84. package/lib/typescript/src/views/Stack/Card.d.ts +5 -5
  85. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  86. package/lib/typescript/src/views/Stack/CardContainer.d.ts +5 -7
  87. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  88. package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
  89. package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
  90. package/lib/typescript/src/views/Stack/CardStack.d.ts +9 -6
  91. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  92. package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
  93. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  94. package/package.json +23 -24
  95. package/src/TransitionConfigs/CardStyleInterpolators.tsx +44 -0
  96. package/src/index.tsx +3 -2
  97. package/src/navigators/createStackNavigator.tsx +9 -40
  98. package/src/types.tsx +79 -75
  99. package/src/utils/GestureHandlerContext.tsx +5 -0
  100. package/src/utils/gestureActivationCriteria.tsx +14 -10
  101. package/src/utils/useCardAnimation.tsx +1 -1
  102. package/src/utils/useGestureHandler.tsx +9 -0
  103. package/src/utils/useKeyboardManager.tsx +96 -33
  104. package/src/views/GestureHandler.tsx +27 -16
  105. package/src/views/GestureHandlerNative.tsx +26 -12
  106. package/src/views/Header/Header.tsx +2 -2
  107. package/src/views/Header/HeaderContainer.tsx +69 -33
  108. package/src/views/Header/HeaderSegment.tsx +6 -7
  109. package/src/views/Stack/Card.tsx +147 -118
  110. package/src/views/Stack/CardContainer.tsx +86 -127
  111. package/src/views/Stack/CardContent.tsx +1 -0
  112. package/src/views/Stack/CardStack.tsx +409 -330
  113. package/src/views/Stack/StackView.tsx +176 -79
  114. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  115. package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
  116. package/lib/module/utils/findLastIndex.js +0 -11
  117. package/lib/module/utils/findLastIndex.js.map +0 -1
  118. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  119. package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
  120. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  121. package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
  122. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
  123. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
  124. package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
  125. package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
  126. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
  127. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
  128. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
  129. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
  130. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  131. package/src/utils/findLastIndex.tsx +0 -9
  132. package/src/utils/useGestureHandlerRef.tsx +0 -15
  133. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
@@ -1,5 +1,8 @@
1
1
  import { getDefaultHeaderHeight } from '@react-navigation/elements';
2
- import { SafeAreaProviderCompat } from '@react-navigation/elements/internal';
2
+ import {
3
+ ActivityView,
4
+ SafeAreaProviderCompat,
5
+ } from '@react-navigation/elements/internal';
3
6
  import type {
4
7
  LocaleDirection,
5
8
  ParamListBase,
@@ -15,7 +18,6 @@ import {
15
18
  View,
16
19
  } from 'react-native';
17
20
  import type { EdgeInsets } from 'react-native-safe-area-context';
18
- import { Screen, ScreenContainer } from 'react-native-screens';
19
21
 
20
22
  import {
21
23
  forModalPresentationIOS,
@@ -44,16 +46,20 @@ import type {
44
46
  StackNavigationOptions,
45
47
  TransitionPreset,
46
48
  } from '../../types';
47
- import { findLastIndex } from '../../utils/findLastIndex';
48
49
  import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
49
50
  import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
50
- import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
51
+ import type {
52
+ HeaderHeight,
53
+ Props as HeaderContainerProps,
54
+ } from '../Header/HeaderContainer';
51
55
  import { CardContainer } from './CardContainer';
52
56
 
53
57
  type GestureValues = {
54
58
  [key: string]: Animated.Value;
55
59
  };
56
60
 
61
+ type HeaderHeights = Record<string, HeaderHeight>;
62
+
57
63
  type Props = {
58
64
  direction: LocaleDirection;
59
65
  insets: EdgeInsets;
@@ -62,6 +68,7 @@ type Props = {
62
68
  routes: Route<string>[];
63
69
  openingRouteKeys: string[];
64
70
  closingRouteKeys: string[];
71
+ replacingRouteKeys: string[];
65
72
  onOpenRoute: (props: { route: Route<string> }) => void;
66
73
  onCloseRoute: (props: { route: Route<string> }) => void;
67
74
  getPreviousRoute: (props: {
@@ -84,11 +91,10 @@ type Props = {
84
91
  type State = {
85
92
  routes: Route<string>[];
86
93
  descriptors: StackDescriptorMap;
87
- scenes: Scene[];
94
+ scenes: (Scene & { __memo: unknown[] })[];
88
95
  gestures: GestureValues;
89
96
  layout: Layout;
90
- activeStates: (0 | 1 | Animated.AnimatedInterpolation<0 | 1>)[];
91
- headerHeights: Record<string, number>;
97
+ headerHeights: HeaderHeights;
92
98
  };
93
99
 
94
100
  const NAMED_TRANSITIONS_PRESETS = {
@@ -107,16 +113,16 @@ const NAMED_TRANSITIONS_PRESETS = {
107
113
  }),
108
114
  } as const satisfies Record<StackAnimationName, TransitionPreset>;
109
115
 
110
- const EPSILON = 1e-5;
116
+ const HIDDEN_HEADER_HEIGHT = { measured: false, value: 0 };
111
117
 
112
- const STATE_INACTIVE = 0;
113
- const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
114
- const STATE_ON_TOP = 2;
118
+ const getInterpolationIndex = (scenes: Scene[], index: number) => {
119
+ const scene = scenes[index];
115
120
 
116
- const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
121
+ if (scene == null) {
122
+ throw new Error(`Couldn't find a scene at index ${index}.`);
123
+ }
117
124
 
118
- const getInterpolationIndex = (scenes: Scene[], index: number) => {
119
- const { cardStyleInterpolator } = scenes[index].descriptor.options;
125
+ const { cardStyleInterpolator } = scene.descriptor.options;
120
126
 
121
127
  // Start from current card and count backwards the number of cards with same interpolation
122
128
  let interpolationIndex = 0;
@@ -145,7 +151,7 @@ const getIsModalPresentation = (
145
151
  );
146
152
  };
147
153
 
148
- const getIsModal = (
154
+ const getIsInModalPresentation = (
149
155
  scene: Scene,
150
156
  interpolationIndex: number,
151
157
  isParentModal: boolean
@@ -155,6 +161,7 @@ const getIsModal = (
155
161
  }
156
162
 
157
163
  const { cardStyleInterpolator } = scene.descriptor.options;
164
+
158
165
  const isModalPresentation = getIsModalPresentation(cardStyleInterpolator);
159
166
  const isModal = isModalPresentation && interpolationIndex !== 0;
160
167
 
@@ -167,32 +174,51 @@ const getHeaderHeights = (
167
174
  isParentHeaderShown: boolean,
168
175
  isParentModal: boolean,
169
176
  layout: Layout,
170
- previous: Record<string, number>
177
+ previous: HeaderHeights
171
178
  ) => {
172
- return scenes.reduce<Record<string, number>>((acc, curr, index) => {
179
+ return scenes.reduce<HeaderHeights>((acc, curr, index) => {
173
180
  const {
174
181
  headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
175
182
  headerStyle,
176
183
  } = curr.descriptor.options;
177
184
 
178
- const style = StyleSheet.flatten(headerStyle || {});
185
+ const style = StyleSheet.flatten(headerStyle);
179
186
 
180
187
  const height =
181
- 'height' in style && typeof style.height === 'number'
188
+ style != null && 'height' in style && typeof style.height === 'number'
182
189
  ? style.height
183
- : previous[curr.route.key];
190
+ : undefined;
184
191
 
185
192
  const interpolationIndex = getInterpolationIndex(scenes, index);
186
- const isModal = getIsModal(curr, interpolationIndex, isParentModal);
193
+ const isModal = getIsInModalPresentation(
194
+ curr,
195
+ interpolationIndex,
196
+ isParentModal
197
+ );
187
198
 
188
- acc[curr.route.key] =
189
- typeof height === 'number'
190
- ? height
191
- : getDefaultHeaderHeight({
192
- landscape: layout.width > layout.height,
193
- modalPresentation: isModal,
194
- topInset: headerStatusBarHeight,
195
- });
199
+ if (typeof height === 'number') {
200
+ acc[curr.route.key] = { measured: true, value: height };
201
+ } else {
202
+ const previousHeight = previous[curr.route.key];
203
+ const defaultHeight = getDefaultHeaderHeight({
204
+ landscape: layout.width > layout.height,
205
+ modalPresentation: isModal,
206
+ topInset: headerStatusBarHeight,
207
+ });
208
+
209
+ // If we hadn't measured before, use re-computed default height
210
+ if (
211
+ !previousHeight?.measured &&
212
+ defaultHeight !== previousHeight?.value
213
+ ) {
214
+ acc[curr.route.key] = {
215
+ measured: false,
216
+ value: defaultHeight,
217
+ };
218
+ } else if (previousHeight) {
219
+ acc[curr.route.key] = previousHeight;
220
+ }
221
+ }
196
222
 
197
223
  return acc;
198
224
  }, {});
@@ -263,6 +289,27 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
263
289
  return getDefaultAnimation(animation) !== 'none';
264
290
  }
265
291
 
292
+ const getAllRoutes = (
293
+ routes: Route<string>[],
294
+ state: StackNavigationState<ParamListBase>
295
+ ) => {
296
+ const routeKeys = new Set(routes.map((route) => route.key));
297
+ const inactiveRoutes = state.routes.slice(state.index + 1);
298
+
299
+ // If a route is moved from active routes to inactive routes,
300
+ // It can still be in the local copy of `routes` until the animation ends
301
+ // So we need to deduplicate the routes to avoid rendering the same route twice
302
+ return [
303
+ ...routes,
304
+ ...inactiveRoutes.filter((route) => !routeKeys.has(route.key)),
305
+ ];
306
+ };
307
+
308
+ const isInactiveRoute = (route: Route<string>, routes: Route<string>[]) =>
309
+ // `routes` contains active routes and routes animating during transitions.
310
+ // Any route added by `getAllRoutes` that's missing from this list is inactive.
311
+ !routes.some((currentRoute) => currentRoute.key === route.key);
312
+
266
313
  export class CardStack extends React.Component<Props, State> {
267
314
  static getDerivedStateFromProps(
268
315
  props: Props,
@@ -275,10 +322,9 @@ export class CardStack extends React.Component<Props, State> {
275
322
  return null;
276
323
  }
277
324
 
278
- const gestures = [
279
- ...props.routes,
280
- ...props.state.preloadedRoutes,
281
- ].reduce<GestureValues>((acc, curr) => {
325
+ const allRoutes = getAllRoutes(props.routes, props.state);
326
+
327
+ const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
282
328
  const descriptor = props.descriptors[curr.key];
283
329
  const { animation } = descriptor?.options || {};
284
330
 
@@ -287,7 +333,7 @@ export class CardStack extends React.Component<Props, State> {
287
333
  new Animated.Value(
288
334
  (props.openingRouteKeys.includes(curr.key) &&
289
335
  getAnimationEnabled(animation)) ||
290
- props.state.preloadedRoutes.includes(curr)
336
+ isInactiveRoute(curr, props.routes)
291
337
  ? getDistanceFromOptions(
292
338
  state.layout,
293
339
  descriptor?.options,
@@ -299,254 +345,178 @@ export class CardStack extends React.Component<Props, State> {
299
345
  return acc;
300
346
  }, {});
301
347
 
302
- const modalRouteKeys = getModalRouteKeys(
303
- [...props.routes, ...props.state.preloadedRoutes],
304
- props.descriptors
305
- );
348
+ const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
306
349
 
307
- const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
308
- (route, index, self) => {
309
- // For preloaded screens, we don't care about the previous and the next screen
310
- const isPreloaded = props.state.preloadedRoutes.includes(route);
311
- const previousRoute = isPreloaded ? undefined : self[index - 1];
312
- const nextRoute = isPreloaded ? undefined : self[index + 1];
313
-
314
- const oldScene = state.scenes[index];
315
-
316
- const currentGesture = gestures[route.key];
317
- const previousGesture = previousRoute
318
- ? gestures[previousRoute.key]
319
- : undefined;
320
- const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
321
-
322
- const descriptor =
323
- props.descriptors[route.key] ||
324
- state.descriptors[route.key] ||
325
- (oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
326
-
327
- const nextOptions =
328
- nextRoute &&
329
- (
330
- props.descriptors[nextRoute?.key] ||
331
- state.descriptors[nextRoute?.key]
332
- )?.options;
333
-
334
- const previousOptions =
335
- previousRoute &&
336
- (
337
- props.descriptors[previousRoute?.key] ||
338
- state.descriptors[previousRoute?.key]
339
- )?.options;
340
-
341
- // When a screen is not the last, it should use next screen's transition config
342
- // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
343
- // For example combining a slide and a modal transition would look wrong otherwise
344
- // With this approach, combining different transition styles in the same navigator mostly looks right
345
- // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
346
- // but the majority of the transitions look alright
347
- const optionsForTransitionConfig =
348
- index !== self.length - 1 &&
349
- nextOptions &&
350
- nextOptions?.presentation !== 'transparentModal'
351
- ? nextOptions
352
- : descriptor.options;
353
-
354
- // Assume modal if there are already modal screens in the stack
355
- // or current screen is a modal when no presentation is specified
356
- const isModal = modalRouteKeys.includes(route.key);
357
-
358
- const animation = getDefaultAnimation(
359
- optionsForTransitionConfig.animation
360
- );
350
+ const scenes = allRoutes.map((route, index, self) => {
351
+ // For preloaded or retained screens, we don't care about the previous and the next screen
352
+ const isInactive = isInactiveRoute(route, props.routes);
353
+ const previousRoute = isInactive ? undefined : self[index - 1];
354
+ const nextRoute = isInactive ? undefined : self[index + 1];
361
355
 
362
- const isAnimationEnabled = getAnimationEnabled(animation);
363
-
364
- const transitionPreset =
365
- animation !== 'default'
366
- ? NAMED_TRANSITIONS_PRESETS[animation]
367
- : optionsForTransitionConfig.presentation === 'transparentModal'
368
- ? ModalFadeTransition
369
- : optionsForTransitionConfig.presentation === 'modal' || isModal
370
- ? ModalTransition
371
- : DefaultTransition;
372
-
373
- const {
374
- gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
375
- gestureDirection = transitionPreset.gestureDirection,
376
- transitionSpec = transitionPreset.transitionSpec,
377
- cardStyleInterpolator = isAnimationEnabled
378
- ? transitionPreset.cardStyleInterpolator
379
- : forNoAnimationCard,
380
- headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
381
- cardOverlayEnabled = (Platform.OS !== 'ios' &&
382
- optionsForTransitionConfig.presentation !== 'transparentModal') ||
383
- getIsModalPresentation(cardStyleInterpolator),
384
- } = optionsForTransitionConfig;
385
-
386
- const headerMode: StackHeaderMode =
387
- descriptor.options.headerMode ??
388
- (!(
389
- optionsForTransitionConfig.presentation === 'modal' ||
390
- optionsForTransitionConfig.presentation === 'transparentModal' ||
391
- nextOptions?.presentation === 'modal' ||
392
- nextOptions?.presentation === 'transparentModal' ||
393
- getIsModalPresentation(cardStyleInterpolator)
394
- ) &&
395
- Platform.OS === 'ios' &&
396
- descriptor.options.header === undefined
397
- ? 'float'
398
- : 'screen');
399
-
400
- const isRTL = props.direction === 'rtl';
401
-
402
- const scene = {
403
- route,
404
- descriptor: {
405
- ...descriptor,
406
- options: {
407
- ...descriptor.options,
408
- animation,
409
- cardOverlayEnabled,
410
- cardStyleInterpolator,
411
- gestureDirection,
412
- gestureEnabled,
413
- headerStyleInterpolator,
414
- transitionSpec,
415
- headerMode,
416
- },
356
+ const oldScene = state.scenes[index];
357
+
358
+ const currentGesture = gestures[route.key];
359
+
360
+ if (currentGesture == null) {
361
+ throw new Error(`Couldn't find a gesture for route '${route.key}'.`);
362
+ }
363
+
364
+ const previousGesture = previousRoute
365
+ ? gestures[previousRoute.key]
366
+ : undefined;
367
+ const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
368
+
369
+ const descriptor =
370
+ props.descriptors[route.key] ??
371
+ state.descriptors[route.key] ??
372
+ oldScene?.descriptor;
373
+
374
+ if (descriptor == null) {
375
+ throw new Error(`Couldn't find a descriptor for route '${route.key}'.`);
376
+ }
377
+
378
+ const nextOptions = nextRoute
379
+ ? (props.descriptors[nextRoute.key] ?? state.descriptors[nextRoute.key])
380
+ ?.options
381
+ : undefined;
382
+
383
+ const previousOptions = previousRoute
384
+ ? (
385
+ props.descriptors[previousRoute.key] ??
386
+ state.descriptors[previousRoute.key]
387
+ )?.options
388
+ : undefined;
389
+
390
+ // When a screen is not the last, it should use next screen's transition config
391
+ // Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
392
+ // For example combining a slide and a modal transition would look wrong otherwise
393
+ // With this approach, combining different transition styles in the same navigator mostly looks right
394
+ // This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
395
+ // but the majority of the transitions look alright
396
+ const optionsForTransitionConfig =
397
+ index !== self.length - 1 &&
398
+ nextOptions &&
399
+ nextOptions?.presentation !== 'transparentModal'
400
+ ? nextOptions
401
+ : descriptor.options;
402
+
403
+ // Assume modal if there are already modal screens in the stack
404
+ // or current screen is a modal when no presentation is specified
405
+ const isModal = modalRouteKeys.includes(route.key);
406
+
407
+ const animation = getDefaultAnimation(
408
+ optionsForTransitionConfig.animation
409
+ );
410
+
411
+ const isAnimationEnabled = getAnimationEnabled(animation);
412
+
413
+ const transitionPreset =
414
+ animation !== 'default'
415
+ ? NAMED_TRANSITIONS_PRESETS[animation]
416
+ : optionsForTransitionConfig.presentation === 'transparentModal'
417
+ ? ModalFadeTransition
418
+ : optionsForTransitionConfig.presentation === 'modal' || isModal
419
+ ? ModalTransition
420
+ : DefaultTransition;
421
+
422
+ const {
423
+ gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
424
+ gestureDirection = transitionPreset.gestureDirection,
425
+ transitionSpec = transitionPreset.transitionSpec,
426
+ cardStyleInterpolator = isAnimationEnabled
427
+ ? transitionPreset.cardStyleInterpolator
428
+ : forNoAnimationCard,
429
+ headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
430
+ cardOverlayEnabled = (Platform.OS !== 'ios' &&
431
+ optionsForTransitionConfig.presentation !== 'transparentModal') ||
432
+ getIsModalPresentation(cardStyleInterpolator),
433
+ } = optionsForTransitionConfig;
434
+
435
+ const headerMode: StackHeaderMode =
436
+ descriptor.options.headerMode ??
437
+ (!(
438
+ optionsForTransitionConfig.presentation === 'modal' ||
439
+ optionsForTransitionConfig.presentation === 'transparentModal' ||
440
+ nextOptions?.presentation === 'modal' ||
441
+ nextOptions?.presentation === 'transparentModal' ||
442
+ getIsModalPresentation(cardStyleInterpolator)
443
+ ) &&
444
+ Platform.OS === 'ios' &&
445
+ descriptor.options.header === undefined
446
+ ? 'float'
447
+ : 'screen');
448
+
449
+ const isRTL = props.direction === 'rtl';
450
+
451
+ const scene = {
452
+ route,
453
+ descriptor: {
454
+ ...descriptor,
455
+ options: {
456
+ ...descriptor.options,
457
+ animation,
458
+ cardOverlayEnabled,
459
+ cardStyleInterpolator,
460
+ gestureDirection,
461
+ gestureEnabled,
462
+ headerStyleInterpolator,
463
+ transitionSpec,
464
+ headerMode,
417
465
  },
418
- progress: {
419
- current: getProgressFromGesture(
420
- currentGesture,
421
- state.layout,
422
- descriptor.options,
423
- isRTL
424
- ),
425
- next:
426
- nextGesture && nextOptions?.presentation !== 'transparentModal'
427
- ? getProgressFromGesture(
428
- nextGesture,
429
- state.layout,
430
- nextOptions,
431
- isRTL
432
- )
433
- : undefined,
434
- previous: previousGesture
466
+ },
467
+ progress: {
468
+ current: getProgressFromGesture(
469
+ currentGesture,
470
+ state.layout,
471
+ descriptor.options,
472
+ isRTL
473
+ ),
474
+ next:
475
+ nextGesture && nextOptions?.presentation !== 'transparentModal'
435
476
  ? getProgressFromGesture(
436
- previousGesture,
477
+ nextGesture,
437
478
  state.layout,
438
- previousOptions,
479
+ nextOptions,
439
480
  isRTL
440
481
  )
441
482
  : undefined,
442
- },
443
- __memo: [
444
- state.layout,
445
- descriptor,
446
- nextOptions,
447
- previousOptions,
448
- currentGesture,
449
- nextGesture,
450
- previousGesture,
451
- ],
452
- };
453
-
454
- if (
455
- oldScene &&
456
- scene.__memo.every((it, i) => {
457
- // @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
458
- return oldScene.__memo[i] === it;
459
- })
460
- ) {
461
- return oldScene;
462
- }
463
-
464
- return scene;
465
- }
466
- );
483
+ previous: previousGesture
484
+ ? getProgressFromGesture(
485
+ previousGesture,
486
+ state.layout,
487
+ previousOptions,
488
+ isRTL
489
+ )
490
+ : undefined,
491
+ },
492
+ __memo: [
493
+ state.layout,
494
+ descriptor,
495
+ nextOptions,
496
+ previousOptions,
497
+ currentGesture,
498
+ nextGesture,
499
+ previousGesture,
500
+ ],
501
+ };
467
502
 
468
- let activeStates = state.activeStates;
469
-
470
- if (props.routes.length !== state.routes.length) {
471
- let activeScreensLimit = 1;
472
-
473
- for (let i = props.routes.length - 1; i >= 0; i--) {
474
- const { options } = scenes[i].descriptor;
475
-
476
- const {
477
- // By default, we don't want to detach the previous screen of the active one for modals
478
- detachPreviousScreen = options.presentation === 'transparentModal'
479
- ? false
480
- : getIsModalPresentation(options.cardStyleInterpolator)
481
- ? i !==
482
- findLastIndex(scenes, (scene) => {
483
- const { cardStyleInterpolator } = scene.descriptor.options;
484
-
485
- return (
486
- cardStyleInterpolator === forModalPresentationIOS ||
487
- cardStyleInterpolator?.name === 'forModalPresentationIOS'
488
- );
489
- })
490
- : true,
491
- } = options;
492
-
493
- if (detachPreviousScreen === false) {
494
- activeScreensLimit++;
495
- } else {
496
- // Check at least last 2 screens before stopping
497
- // This will make sure that screen isn't detached when another screen is animating on top of the transparent one
498
- // e.g. opaque -> transparent -> opaque
499
- if (i <= props.routes.length - 2) {
500
- break;
501
- }
502
- }
503
+ if (
504
+ oldScene &&
505
+ scene.__memo.every((it, i) => {
506
+ return oldScene.__memo[i] === it;
507
+ })
508
+ ) {
509
+ return oldScene;
503
510
  }
504
511
 
505
- activeStates = props.routes.map((_, index, self) => {
506
- // The activity state represents state of the screen:
507
- // 0 - inactive, the screen is detached
508
- // 1 - transitioning or below the top screen, the screen is mounted but interaction is disabled
509
- // 2 - on top of the stack, the screen is mounted and interaction is enabled
510
- let activityState:
511
- | Animated.AnimatedInterpolation<0 | 1 | 2>
512
- | 0
513
- | 1
514
- | 2;
515
-
516
- const lastActiveState = state.activeStates[index];
517
- const activeAfterTransition = index >= self.length - activeScreensLimit;
518
-
519
- if (lastActiveState === STATE_INACTIVE && !activeAfterTransition) {
520
- // screen was inactive before and it will still be inactive after the transition
521
- activityState = STATE_INACTIVE;
522
- } else {
523
- const sceneForActivity = scenes[self.length - 1];
524
- const outputValue =
525
- index === self.length - 1
526
- ? STATE_ON_TOP // the screen is on top after the transition
527
- : activeAfterTransition
528
- ? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
529
- : STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
530
-
531
- activityState = sceneForActivity
532
- ? sceneForActivity.progress.current.interpolate({
533
- inputRange: [0, 1 - EPSILON, 1],
534
- outputRange: [1, 1, outputValue],
535
- extrapolate: 'clamp',
536
- })
537
- : STATE_TRANSITIONING_OR_BELOW_TOP;
538
- }
539
-
540
- return activityState;
541
- });
542
- }
512
+ return scene;
513
+ });
543
514
 
544
515
  return {
545
516
  routes: props.routes,
546
517
  scenes,
547
518
  gestures,
548
519
  descriptors: props.descriptors,
549
- activeStates,
550
520
  headerHeights: getHeaderHeights(
551
521
  scenes,
552
522
  props.insets,
@@ -567,7 +537,6 @@ export class CardStack extends React.Component<Props, State> {
567
537
  gestures: {},
568
538
  layout: SafeAreaProviderCompat.initialMetrics.frame,
569
539
  descriptors: this.props.descriptors,
570
- activeStates: [],
571
540
  // Used when card's header is null and mode is float to make transition
572
541
  // between screens with headers and those without headers smooth.
573
542
  // This is not a great heuristic here. We don't know synchronously
@@ -611,14 +580,17 @@ export class CardStack extends React.Component<Props, State> {
611
580
  this.setState(({ headerHeights }) => {
612
581
  const previousHeight = headerHeights[route.key];
613
582
 
614
- if (previousHeight === height) {
583
+ if (previousHeight?.value === height) {
615
584
  return null;
616
585
  }
617
586
 
618
587
  return {
619
588
  headerHeights: {
620
589
  ...headerHeights,
621
- [route.key]: height,
590
+ [route.key]: {
591
+ measured: true,
592
+ value: height,
593
+ },
622
594
  },
623
595
  };
624
596
  });
@@ -627,7 +599,13 @@ export class CardStack extends React.Component<Props, State> {
627
599
  private getFocusedRoute = () => {
628
600
  const { state } = this.props;
629
601
 
630
- return state.routes[state.index];
602
+ const route = state.routes[state.index];
603
+
604
+ if (route == null) {
605
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
606
+ }
607
+
608
+ return route;
631
609
  };
632
610
 
633
611
  private getPreviousScene = ({ route }: { route: Route<string> }) => {
@@ -647,34 +625,44 @@ export class CardStack extends React.Component<Props, State> {
647
625
  return undefined;
648
626
  };
649
627
 
650
- render() {
628
+ override render() {
651
629
  const {
652
630
  insets,
653
631
  state,
654
632
  routes,
655
633
  openingRouteKeys,
656
634
  closingRouteKeys,
635
+ replacingRouteKeys,
657
636
  onOpenRoute,
658
637
  onCloseRoute,
638
+ onGestureStart,
639
+ onGestureEnd,
640
+ onGestureCancel,
659
641
  renderHeader,
660
642
  isParentHeaderShown,
661
643
  isParentModal,
662
644
  onTransitionStart,
663
645
  onTransitionEnd,
664
- onGestureStart,
665
- onGestureEnd,
666
- onGestureCancel,
667
- detachInactiveScreens = Platform.OS === 'web' ||
668
- Platform.OS === 'android' ||
669
- Platform.OS === 'ios',
670
646
  } = this.props;
671
647
 
672
- const { scenes, layout, gestures, activeStates, headerHeights } =
673
- this.state;
648
+ const { scenes, layout, gestures, headerHeights } = this.state;
649
+
650
+ const allRoutes = getAllRoutes(routes, state);
674
651
 
675
652
  const focusedRoute = state.routes[state.index];
676
653
 
677
- const isFloatHeaderAbsolute = scenes.slice(-2).some((scene) => {
654
+ if (focusedRoute == null) {
655
+ throw new Error(`Couldn't find a route at index ${state.index}.`);
656
+ }
657
+
658
+ // Render only two top-most active headers as a workaround for
659
+ // https://github.com/react-navigation/react-navigation/issues/12456.
660
+ // If the header is persisted, it might be placed incorrectly when navigating back
661
+ const activeHeaderScenes = scenes
662
+ .filter((scene) => !isInactiveRoute(scene.route, routes))
663
+ .slice(-2);
664
+
665
+ const isFloatHeaderAbsolute = activeHeaderScenes.some((scene) => {
678
666
  const options = scene.descriptor.options ?? {};
679
667
  const { headerMode, headerTransparent, headerShown = true } = options;
680
668
 
@@ -693,13 +681,16 @@ export class CardStack extends React.Component<Props, State> {
693
681
  scenes.findLast((scene) => scene.route.key === focusedRoute.key)
694
682
  ?.descriptor.options.headerMode === 'float';
695
683
 
684
+ const focusedHeaderHeight = headerHeights[focusedRoute.key];
685
+
696
686
  return (
697
687
  <View style={styles.container}>
698
688
  {renderHeader({
699
689
  mode: 'float',
700
- scenes,
690
+ scenes: activeHeaderScenes,
701
691
  getPreviousScene: this.getPreviousScene,
702
692
  getFocusedRoute: this.getFocusedRoute,
693
+ contentHeight: headerHeights[focusedRoute.key],
703
694
  onContentHeightChange: this.handleHeaderLayout,
704
695
  style: [
705
696
  isFloatHeaderAbsolute && [
@@ -707,39 +698,36 @@ export class CardStack extends React.Component<Props, State> {
707
698
  // The header title can't be found with maestro without this
708
699
  // In some cases, header buttons may not be touchable as well
709
700
  isFloatHeaderFocused && {
710
- height: headerHeights[focusedRoute.key],
701
+ height: focusedHeaderHeight?.value,
711
702
  },
712
703
  ],
713
704
  ],
714
705
  })}
715
- <ScreenContainer
716
- enabled={detachInactiveScreens}
717
- style={styles.container}
718
- onLayout={this.handleLayout}
719
- >
720
- {[...routes, ...state.preloadedRoutes].map((route, index) => {
706
+ <View style={styles.container} onLayout={this.handleLayout}>
707
+ {allRoutes.map((route, index, self) => {
721
708
  const focused = focusedRoute.key === route.key;
722
709
  const gesture = gestures[route.key];
723
710
  const scene = scenes[index];
724
- // It is possible that for a short period the route appears in both arrays.
725
- // Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
726
- // However, due to the router action, it immediately populates the `preloadedRoutes` array.
727
- // Practically, the logic below takes care that it is rendered only once.
728
- const isPreloaded =
729
- state.preloadedRoutes.includes(route) && !routes.includes(route);
730
- if (
731
- state.preloadedRoutes.includes(route) &&
732
- routes.includes(route) &&
733
- index >= routes.length
734
- ) {
735
- return null;
711
+
712
+ if (gesture == null) {
713
+ throw new Error(
714
+ `Couldn't find a gesture for route '${route.key}'.`
715
+ );
716
+ }
717
+
718
+ if (scene == null) {
719
+ throw new Error(`Couldn't find a scene at index ${index}.`);
736
720
  }
737
721
 
722
+ const nextScene = scenes[index + 1];
723
+ const nextRoute = self[index + 1];
724
+
738
725
  const {
726
+ inactiveBehavior = 'pause',
727
+ animation,
739
728
  headerShown = true,
729
+ headerMode,
740
730
  headerTransparent,
741
- freezeOnBlur,
742
- autoHideHomeIndicator,
743
731
  } = scene.descriptor.options;
744
732
 
745
733
  const safeAreaInsetTop = insets.top;
@@ -748,43 +736,137 @@ export class CardStack extends React.Component<Props, State> {
748
736
  const safeAreaInsetLeft = insets.left;
749
737
 
750
738
  const headerHeight =
751
- headerShown !== false ? headerHeights[route.key] : 0;
739
+ headerShown !== false
740
+ ? headerHeights[route.key]
741
+ : HIDDEN_HEADER_HEIGHT;
752
742
 
753
743
  // Start from current card and count backwards the number of cards with same interpolation
754
744
  const interpolationIndex = getInterpolationIndex(scenes, index);
755
- const isModal = getIsModal(
745
+ const isInModalPresentation = getIsInModalPresentation(
756
746
  scene,
757
747
  interpolationIndex,
758
748
  isParentModal
759
749
  );
760
750
 
761
- const isNextScreenTransparent =
762
- scenes[index + 1]?.descriptor.options.presentation ===
763
- 'transparentModal';
751
+ const isModal =
752
+ scene.descriptor.options.presentation === 'modal' ||
753
+ getIsModalPresentation(
754
+ scene.descriptor.options.cardStyleInterpolator
755
+ );
764
756
 
765
- const detachCurrentScreen =
766
- scenes[index + 1]?.descriptor.options.detachPreviousScreen !==
767
- false;
757
+ const isNextScreenModal =
758
+ nextScene != null &&
759
+ (nextScene.descriptor.options.presentation === 'modal' ||
760
+ getIsModalPresentation(
761
+ nextScene.descriptor.options.cardStyleInterpolator
762
+ ));
768
763
 
769
- const activityState = isPreloaded
770
- ? STATE_INACTIVE
771
- : activeStates[index];
764
+ const isNextScreenTransparent =
765
+ nextScene?.descriptor.options.presentation === 'transparentModal';
766
+
767
+ const isAnimationEnabled =
768
+ getAnimationEnabled(animation) ||
769
+ // Also check next screen's animation,
770
+ // As it will result in both screens being visible
771
+ (nextScene
772
+ ? getAnimationEnabled(nextScene.descriptor.options.animation)
773
+ : false);
774
+
775
+ const isRemoving =
776
+ replacingRouteKeys.includes(route.key) ||
777
+ closingRouteKeys.includes(route.key);
778
+
779
+ const isFocusing =
780
+ openingRouteKeys.includes(route.key) ||
781
+ (nextRoute != null &&
782
+ [...closingRouteKeys, ...replacingRouteKeys].includes(
783
+ nextRoute.key
784
+ ));
785
+
786
+ const isInactive = isInactiveRoute(route, this.props.routes);
787
+ const isRetained = state.retainedRouteKeys?.includes(route.key);
788
+ const isPreloaded = isInactive && !isRetained;
789
+
790
+ const isTopmost = index === routes.length - 1;
791
+ const isBeforeLast = index === routes.length - 2;
792
+
793
+ // Keep animating and the last two rendered routes visible for smoother transitions
794
+ const isVisible =
795
+ focused ||
796
+ // React native destroys the view when hidden
797
+ // So we keep retained screens visible
798
+ // This makes sure any videos being played don't stop etc.
799
+ isRetained ||
800
+ isNextScreenTransparent ||
801
+ // Modal presentation on iOS has multiple screens visible at the same time
802
+ // The base screen before the modals renders the dim, which is always persistent
803
+ // On iPadOS, the base screen is visible while modal is in a smaller area on top
804
+ // The top edge of the last 3 screens can be seen animating behind the modal
805
+ (isNextScreenModal && (!isModal || index >= routes.length - 3)) ||
806
+ // We only need to keep other screens visible when animation is enabled
807
+ (isAnimationEnabled &&
808
+ (isFocusing ||
809
+ isRemoving ||
810
+ // Preloaded screens should stay mounted, but remain hidden until focused
811
+ (!isPreloaded && index >= routes.length - 2)));
812
+
813
+ const activityMode = // Render focused and animating screens normally
814
+ focused || isFocusing
815
+ ? 'normal'
816
+ : inactiveBehavior === 'none' ||
817
+ // Unpause preloaded or retained screens so updates are visible
818
+ // Handle retained explicitly as isInactive won't be updated until animation end
819
+ isInactive ||
820
+ isRetained ||
821
+ // Keep the screen before transparent screen active
822
+ // This lets the screen under the transparent screen update and animate
823
+ isNextScreenTransparent ||
824
+ // Keep the screen before last screen active
825
+ // Otherwise it breaks animation when going back
826
+ isBeforeLast
827
+ ? 'inert'
828
+ : inactiveBehavior === 'unmount' &&
829
+ // Don't unmount screens that needs to stay visible
830
+ !isVisible &&
831
+ // Don't unmount screens with nested navigators
832
+ // So we don't lose their state
833
+ !('state' in route && route.state)
834
+ ? 'unmounted'
835
+ : 'paused';
836
+
837
+ if (activityMode === 'unmounted') {
838
+ return null;
839
+ }
772
840
 
773
841
  return (
774
- <Screen
842
+ <ActivityView
775
843
  key={route.key}
776
- style={[StyleSheet.absoluteFill, { pointerEvents: 'box-none' }]}
777
- enabled={detachInactiveScreens}
778
- activityState={activityState}
779
- freezeOnBlur={freezeOnBlur}
780
- shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
781
- homeIndicatorHidden={autoHideHomeIndicator}
844
+ mode={activityMode}
845
+ visible={isVisible}
846
+ style={{
847
+ ...StyleSheet.absoluteFill,
848
+ // Avoid unfocused larger pages increasing scroll area
849
+ // e.g. when a smaller screen is pushed over a larger one on web
850
+ overflow: isTopmost ? undefined : 'hidden',
851
+ marginTop:
852
+ headerTransparent || headerMode === 'screen'
853
+ ? 0
854
+ : // Add offset for non-transparent floating header
855
+ isFloatHeaderAbsolute
856
+ ? // If header is absolute, offset the card by the header height
857
+ headerHeight?.value
858
+ : // Otherwise, the in-flow float header already takes the
859
+ // focused header's space, so offset by the difference
860
+ headerHeight && focusedHeaderHeight
861
+ ? headerHeight.value - focusedHeaderHeight.value
862
+ : 0,
863
+ }}
782
864
  >
783
865
  <CardContainer
784
866
  index={index}
785
867
  interpolationIndex={interpolationIndex}
786
- modal={isModal}
787
- active={index === routes.length - 1}
868
+ modal={isInModalPresentation}
869
+ active={isTopmost}
788
870
  focused={focused}
789
871
  opening={openingRouteKeys.includes(route.key)}
790
872
  closing={closingRouteKeys.includes(route.key)}
@@ -803,22 +885,19 @@ export class CardStack extends React.Component<Props, State> {
803
885
  onHeaderHeightChange={this.handleHeaderLayout}
804
886
  getPreviousScene={this.getPreviousScene}
805
887
  getFocusedRoute={this.getFocusedRoute}
806
- hasAbsoluteFloatHeader={
807
- isFloatHeaderAbsolute && !headerTransparent
808
- }
809
888
  renderHeader={renderHeader}
810
889
  onOpenRoute={onOpenRoute}
811
890
  onCloseRoute={onCloseRoute}
812
891
  onTransitionStart={onTransitionStart}
813
892
  onTransitionEnd={onTransitionEnd}
814
- isNextScreenTransparent={isNextScreenTransparent}
815
- detachCurrentScreen={detachCurrentScreen}
816
- preloaded={isPreloaded}
817
- />
818
- </Screen>
893
+ preloaded={isInactive}
894
+ >
895
+ {scene.descriptor.render()}
896
+ </CardContainer>
897
+ </ActivityView>
819
898
  );
820
899
  })}
821
- </ScreenContainer>
900
+ </View>
822
901
  </View>
823
902
  );
824
903
  }