@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.
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js +36 -0
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
- package/lib/module/index.js +2 -2
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createStackNavigator.js +3 -7
- package/lib/module/navigators/createStackNavigator.js.map +1 -1
- package/lib/module/utils/GestureHandlerContext.js +5 -0
- package/lib/module/utils/GestureHandlerContext.js.map +1 -0
- package/lib/module/utils/gestureActivationCriteria.js +10 -8
- package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
- package/lib/module/utils/useCardAnimation.js +1 -1
- package/lib/module/utils/useCardAnimation.js.map +1 -1
- package/lib/module/utils/useGestureHandler.js +9 -0
- package/lib/module/utils/useGestureHandler.js.map +1 -0
- package/lib/module/utils/useKeyboardManager.js +71 -26
- package/lib/module/utils/useKeyboardManager.js.map +1 -1
- package/lib/module/views/GestureHandler.js +15 -14
- package/lib/module/views/GestureHandler.js.map +1 -1
- package/lib/module/views/GestureHandlerNative.js +16 -10
- package/lib/module/views/GestureHandlerNative.js.map +1 -1
- package/lib/module/views/Header/Header.js +2 -2
- package/lib/module/views/Header/Header.js.map +1 -1
- package/lib/module/views/Header/HeaderContainer.js +52 -18
- package/lib/module/views/Header/HeaderContainer.js.map +1 -1
- package/lib/module/views/Header/HeaderSegment.js +3 -3
- package/lib/module/views/Header/HeaderSegment.js.map +1 -1
- package/lib/module/views/Stack/Card.js +101 -92
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +72 -98
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardContent.js +2 -0
- package/lib/module/views/Stack/CardContent.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +183 -132
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +103 -54
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +1 -1
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +1 -1
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +12 -12
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
- package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +52 -55
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
- package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
- package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
- package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
- package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
- package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
- package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
- package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
- package/lib/typescript/src/utils/getShadowStyle.d.ts +5 -5
- package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
- package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
- package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
- package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
- package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
- package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
- package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
- package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
- package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
- package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
- package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts +2 -2
- package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts +5 -4
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +5 -5
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts +5 -7
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
- package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts +9 -6
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/StackView.d.ts +14 -17
- package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
- package/package.json +23 -24
- package/src/TransitionConfigs/CardStyleInterpolators.tsx +44 -0
- package/src/index.tsx +3 -2
- package/src/navigators/createStackNavigator.tsx +9 -40
- package/src/types.tsx +79 -75
- package/src/utils/GestureHandlerContext.tsx +5 -0
- package/src/utils/gestureActivationCriteria.tsx +14 -10
- package/src/utils/useCardAnimation.tsx +1 -1
- package/src/utils/useGestureHandler.tsx +9 -0
- package/src/utils/useKeyboardManager.tsx +96 -33
- package/src/views/GestureHandler.tsx +27 -16
- package/src/views/GestureHandlerNative.tsx +26 -12
- package/src/views/Header/Header.tsx +2 -2
- package/src/views/Header/HeaderContainer.tsx +69 -33
- package/src/views/Header/HeaderSegment.tsx +6 -7
- package/src/views/Stack/Card.tsx +147 -118
- package/src/views/Stack/CardContainer.tsx +86 -127
- package/src/views/Stack/CardContent.tsx +1 -0
- package/src/views/Stack/CardStack.tsx +409 -330
- package/src/views/Stack/StackView.tsx +176 -79
- package/lib/module/utils/GestureHandlerRefContext.js +0 -5
- package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
- package/lib/module/utils/findLastIndex.js +0 -11
- package/lib/module/utils/findLastIndex.js.map +0 -1
- package/lib/module/utils/useGestureHandlerRef.js +0 -12
- package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
- package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
- package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
- package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
- package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
- package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
- package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
- package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
- package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
- package/src/utils/GestureHandlerRefContext.tsx +0 -5
- package/src/utils/findLastIndex.tsx +0 -9
- package/src/utils/useGestureHandlerRef.tsx +0 -15
- package/src/views/Stack/CardA11yWrapper.tsx +0 -67
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getDefaultHeaderHeight } from '@react-navigation/elements';
|
|
2
|
-
import {
|
|
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 {
|
|
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
|
-
|
|
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
|
|
116
|
+
const HIDDEN_HEADER_HEIGHT = { measured: false, value: 0 };
|
|
111
117
|
|
|
112
|
-
const
|
|
113
|
-
const
|
|
114
|
-
const STATE_ON_TOP = 2;
|
|
118
|
+
const getInterpolationIndex = (scenes: Scene[], index: number) => {
|
|
119
|
+
const scene = scenes[index];
|
|
115
120
|
|
|
116
|
-
|
|
121
|
+
if (scene == null) {
|
|
122
|
+
throw new Error(`Couldn't find a scene at index ${index}.`);
|
|
123
|
+
}
|
|
117
124
|
|
|
118
|
-
const
|
|
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
|
|
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:
|
|
177
|
+
previous: HeaderHeights
|
|
171
178
|
) => {
|
|
172
|
-
return scenes.reduce<
|
|
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
|
-
:
|
|
190
|
+
: undefined;
|
|
184
191
|
|
|
185
192
|
const interpolationIndex = getInterpolationIndex(scenes, index);
|
|
186
|
-
const isModal =
|
|
193
|
+
const isModal = getIsInModalPresentation(
|
|
194
|
+
curr,
|
|
195
|
+
interpolationIndex,
|
|
196
|
+
isParentModal
|
|
197
|
+
);
|
|
187
198
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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 =
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
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
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
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
|
-
|
|
477
|
+
nextGesture,
|
|
437
478
|
state.layout,
|
|
438
|
-
|
|
479
|
+
nextOptions,
|
|
439
480
|
isRTL
|
|
440
481
|
)
|
|
441
482
|
: undefined,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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
|
-
|
|
506
|
-
|
|
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]:
|
|
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
|
-
|
|
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,
|
|
673
|
-
|
|
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
|
-
|
|
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:
|
|
701
|
+
height: focusedHeaderHeight?.value,
|
|
711
702
|
},
|
|
712
703
|
],
|
|
713
704
|
],
|
|
714
705
|
})}
|
|
715
|
-
<
|
|
716
|
-
|
|
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
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
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
|
|
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
|
|
745
|
+
const isInModalPresentation = getIsInModalPresentation(
|
|
756
746
|
scene,
|
|
757
747
|
interpolationIndex,
|
|
758
748
|
isParentModal
|
|
759
749
|
);
|
|
760
750
|
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
751
|
+
const isModal =
|
|
752
|
+
scene.descriptor.options.presentation === 'modal' ||
|
|
753
|
+
getIsModalPresentation(
|
|
754
|
+
scene.descriptor.options.cardStyleInterpolator
|
|
755
|
+
);
|
|
764
756
|
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
757
|
+
const isNextScreenModal =
|
|
758
|
+
nextScene != null &&
|
|
759
|
+
(nextScene.descriptor.options.presentation === 'modal' ||
|
|
760
|
+
getIsModalPresentation(
|
|
761
|
+
nextScene.descriptor.options.cardStyleInterpolator
|
|
762
|
+
));
|
|
768
763
|
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
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
|
-
<
|
|
842
|
+
<ActivityView
|
|
775
843
|
key={route.key}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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={
|
|
787
|
-
active={
|
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
</
|
|
893
|
+
preloaded={isInactive}
|
|
894
|
+
>
|
|
895
|
+
{scene.descriptor.render()}
|
|
896
|
+
</CardContainer>
|
|
897
|
+
</ActivityView>
|
|
819
898
|
);
|
|
820
899
|
})}
|
|
821
|
-
</
|
|
900
|
+
</View>
|
|
822
901
|
</View>
|
|
823
902
|
);
|
|
824
903
|
}
|