@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.
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js +280 -84
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js.map +1 -1
- package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +11 -44
- package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionEasings.js +71 -0
- package/lib/module/TransitionConfigs/TransitionEasings.js.map +1 -0
- package/lib/module/TransitionConfigs/TransitionPresets.js +50 -10
- package/lib/module/TransitionConfigs/TransitionPresets.js.map +1 -1
- package/lib/module/TransitionConfigs/TransitionSpecs.js +69 -34
- package/lib/module/TransitionConfigs/TransitionSpecs.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 +37 -33
- 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 +4 -2
- package/lib/module/views/Header/Header.js.map +1 -1
- package/lib/module/views/Header/HeaderContainer.js +60 -21
- package/lib/module/views/Header/HeaderContainer.js.map +1 -1
- package/lib/module/views/Header/HeaderSegment.js +6 -9
- package/lib/module/views/Header/HeaderSegment.js.map +1 -1
- package/lib/module/views/Stack/Card.js +170 -123
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +96 -98
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardContent.js +10 -37
- package/lib/module/views/Stack/CardContent.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +211 -147
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +106 -57
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +20 -12
- package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +4 -4
- package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts +38 -0
- package/lib/typescript/src/TransitionConfigs/TransitionEasings.d.ts.map +1 -0
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +15 -3
- package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
- package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +44 -15
- 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 +120 -108
- 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 +6 -4
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +14 -8
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts +7 -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 +10 -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 +399 -100
- package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +15 -46
- package/src/TransitionConfigs/TransitionEasings.tsx +90 -0
- package/src/TransitionConfigs/TransitionPresets.tsx +56 -10
- package/src/TransitionConfigs/TransitionSpecs.tsx +68 -34
- package/src/index.tsx +3 -2
- package/src/navigators/createStackNavigator.tsx +74 -91
- package/src/types.tsx +149 -123
- 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 +24 -16
- package/src/views/GestureHandlerNative.tsx +24 -13
- package/src/views/Header/Header.tsx +4 -2
- package/src/views/Header/HeaderContainer.tsx +79 -37
- package/src/views/Header/HeaderSegment.tsx +10 -11
- package/src/views/Stack/Card.tsx +241 -154
- package/src/views/Stack/CardContainer.tsx +111 -127
- package/src/views/Stack/CardContent.tsx +13 -51
- package/src/views/Stack/CardStack.tsx +478 -335
- package/src/views/Stack/StackView.tsx +181 -82
- 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,18 +18,26 @@ 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 {
|
|
23
|
+
forBottomSheetAndroid,
|
|
24
|
+
forCrossDissolveIOS,
|
|
25
|
+
forDialogAndroid,
|
|
26
|
+
forFadeFromBottomAndroid,
|
|
27
|
+
forFadeFromRightAndroid,
|
|
28
|
+
forFlipIOS,
|
|
21
29
|
forModalPresentationIOS,
|
|
22
30
|
forNoAnimation as forNoAnimationCard,
|
|
31
|
+
forRevealFromBottomAndroid,
|
|
32
|
+
forScaleFromCenterAndroid,
|
|
33
|
+
forVerticalIOS,
|
|
23
34
|
} from '../../TransitionConfigs/CardStyleInterpolators';
|
|
24
35
|
import {
|
|
25
|
-
BottomSheetAndroid,
|
|
26
36
|
DefaultTransition,
|
|
27
37
|
FadeFromBottomAndroid,
|
|
28
38
|
FadeFromRightAndroid,
|
|
29
39
|
ModalFadeTransition,
|
|
40
|
+
ModalFlipIOS,
|
|
30
41
|
ModalSlideFromBottomIOS,
|
|
31
42
|
ModalTransition,
|
|
32
43
|
RevealFromBottomAndroid,
|
|
@@ -44,16 +55,20 @@ import type {
|
|
|
44
55
|
StackNavigationOptions,
|
|
45
56
|
TransitionPreset,
|
|
46
57
|
} from '../../types';
|
|
47
|
-
import { findLastIndex } from '../../utils/findLastIndex';
|
|
48
58
|
import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
|
|
49
59
|
import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
|
|
50
|
-
import type {
|
|
60
|
+
import type {
|
|
61
|
+
HeaderHeight,
|
|
62
|
+
Props as HeaderContainerProps,
|
|
63
|
+
} from '../Header/HeaderContainer';
|
|
51
64
|
import { CardContainer } from './CardContainer';
|
|
52
65
|
|
|
53
66
|
type GestureValues = {
|
|
54
67
|
[key: string]: Animated.Value;
|
|
55
68
|
};
|
|
56
69
|
|
|
70
|
+
type HeaderHeights = Record<string, HeaderHeight>;
|
|
71
|
+
|
|
57
72
|
type Props = {
|
|
58
73
|
direction: LocaleDirection;
|
|
59
74
|
insets: EdgeInsets;
|
|
@@ -62,6 +77,7 @@ type Props = {
|
|
|
62
77
|
routes: Route<string>[];
|
|
63
78
|
openingRouteKeys: string[];
|
|
64
79
|
closingRouteKeys: string[];
|
|
80
|
+
replacingRouteKeys: string[];
|
|
65
81
|
onOpenRoute: (props: { route: Route<string> }) => void;
|
|
66
82
|
onCloseRoute: (props: { route: Route<string> }) => void;
|
|
67
83
|
getPreviousRoute: (props: {
|
|
@@ -84,11 +100,11 @@ type Props = {
|
|
|
84
100
|
type State = {
|
|
85
101
|
routes: Route<string>[];
|
|
86
102
|
descriptors: StackDescriptorMap;
|
|
87
|
-
scenes: Scene[];
|
|
103
|
+
scenes: (Scene & { __memo: unknown[] })[];
|
|
88
104
|
gestures: GestureValues;
|
|
89
105
|
layout: Layout;
|
|
90
|
-
|
|
91
|
-
|
|
106
|
+
headerHeights: HeaderHeights;
|
|
107
|
+
nestedStateRouteKeys: string[];
|
|
92
108
|
};
|
|
93
109
|
|
|
94
110
|
const NAMED_TRANSITIONS_PRESETS = {
|
|
@@ -96,27 +112,43 @@ const NAMED_TRANSITIONS_PRESETS = {
|
|
|
96
112
|
fade: ModalFadeTransition,
|
|
97
113
|
fade_from_bottom: FadeFromBottomAndroid,
|
|
98
114
|
fade_from_right: FadeFromRightAndroid,
|
|
115
|
+
flip: ModalFlipIOS,
|
|
116
|
+
ios_from_left: SlideFromLeftIOS,
|
|
117
|
+
ios_from_right: SlideFromRightIOS,
|
|
99
118
|
none: DefaultTransition,
|
|
100
119
|
reveal_from_bottom: RevealFromBottomAndroid,
|
|
101
120
|
scale_from_center: ScaleFromCenterAndroid,
|
|
102
121
|
slide_from_left: SlideFromLeftIOS,
|
|
103
122
|
slide_from_right: SlideFromRightIOS,
|
|
104
|
-
slide_from_bottom:
|
|
105
|
-
ios: ModalSlideFromBottomIOS,
|
|
106
|
-
default: BottomSheetAndroid,
|
|
107
|
-
}),
|
|
123
|
+
slide_from_bottom: ModalSlideFromBottomIOS,
|
|
108
124
|
} as const satisfies Record<StackAnimationName, TransitionPreset>;
|
|
109
125
|
|
|
110
|
-
const
|
|
126
|
+
const GESTURE_DISABLED_INTERPOLATORS = [
|
|
127
|
+
forCrossDissolveIOS,
|
|
128
|
+
forDialogAndroid,
|
|
129
|
+
forFadeFromBottomAndroid,
|
|
130
|
+
forFadeFromRightAndroid,
|
|
131
|
+
forFlipIOS,
|
|
132
|
+
forRevealFromBottomAndroid,
|
|
133
|
+
forScaleFromCenterAndroid,
|
|
134
|
+
forVerticalIOS,
|
|
135
|
+
];
|
|
111
136
|
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
137
|
+
const SCREEN_HEADER_INTERPOLATORS = [
|
|
138
|
+
...GESTURE_DISABLED_INTERPOLATORS,
|
|
139
|
+
forBottomSheetAndroid,
|
|
140
|
+
];
|
|
115
141
|
|
|
116
|
-
const
|
|
142
|
+
const HIDDEN_HEADER_HEIGHT = { measured: false, value: 0 };
|
|
117
143
|
|
|
118
144
|
const getInterpolationIndex = (scenes: Scene[], index: number) => {
|
|
119
|
-
const
|
|
145
|
+
const scene = scenes[index];
|
|
146
|
+
|
|
147
|
+
if (scene == null) {
|
|
148
|
+
throw new Error(`Couldn't find a scene at index ${index}.`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { cardStyleInterpolator } = scene.descriptor.options;
|
|
120
152
|
|
|
121
153
|
// Start from current card and count backwards the number of cards with same interpolation
|
|
122
154
|
let interpolationIndex = 0;
|
|
@@ -140,12 +172,27 @@ const getIsModalPresentation = (
|
|
|
140
172
|
) => {
|
|
141
173
|
return (
|
|
142
174
|
cardStyleInterpolator === forModalPresentationIOS ||
|
|
143
|
-
|
|
144
|
-
cardStyleInterpolator.name === 'forModalPresentationIOS'
|
|
175
|
+
cardStyleInterpolator.name === forModalPresentationIOS.name
|
|
145
176
|
);
|
|
146
177
|
};
|
|
147
178
|
|
|
148
|
-
const
|
|
179
|
+
const getIsGestureEnabledByDefault = (
|
|
180
|
+
cardStyleInterpolator: StackCardStyleInterpolator
|
|
181
|
+
) =>
|
|
182
|
+
!GESTURE_DISABLED_INTERPOLATORS.some(
|
|
183
|
+
(interpolator) =>
|
|
184
|
+
cardStyleInterpolator === interpolator ||
|
|
185
|
+
cardStyleInterpolator.name === interpolator.name
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const getIsScreenHeader = (cardStyleInterpolator: StackCardStyleInterpolator) =>
|
|
189
|
+
SCREEN_HEADER_INTERPOLATORS.some(
|
|
190
|
+
(interpolator) =>
|
|
191
|
+
cardStyleInterpolator === interpolator ||
|
|
192
|
+
cardStyleInterpolator.name === interpolator.name
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const getIsInModalPresentation = (
|
|
149
196
|
scene: Scene,
|
|
150
197
|
interpolationIndex: number,
|
|
151
198
|
isParentModal: boolean
|
|
@@ -155,6 +202,7 @@ const getIsModal = (
|
|
|
155
202
|
}
|
|
156
203
|
|
|
157
204
|
const { cardStyleInterpolator } = scene.descriptor.options;
|
|
205
|
+
|
|
158
206
|
const isModalPresentation = getIsModalPresentation(cardStyleInterpolator);
|
|
159
207
|
const isModal = isModalPresentation && interpolationIndex !== 0;
|
|
160
208
|
|
|
@@ -167,32 +215,51 @@ const getHeaderHeights = (
|
|
|
167
215
|
isParentHeaderShown: boolean,
|
|
168
216
|
isParentModal: boolean,
|
|
169
217
|
layout: Layout,
|
|
170
|
-
previous:
|
|
218
|
+
previous: HeaderHeights
|
|
171
219
|
) => {
|
|
172
|
-
return scenes.reduce<
|
|
220
|
+
return scenes.reduce<HeaderHeights>((acc, curr, index) => {
|
|
173
221
|
const {
|
|
174
222
|
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
|
175
223
|
headerStyle,
|
|
176
224
|
} = curr.descriptor.options;
|
|
177
225
|
|
|
178
|
-
const style = StyleSheet.flatten(headerStyle
|
|
226
|
+
const style = StyleSheet.flatten(headerStyle);
|
|
179
227
|
|
|
180
228
|
const height =
|
|
181
|
-
'height' in style && typeof style.height === 'number'
|
|
229
|
+
style != null && 'height' in style && typeof style.height === 'number'
|
|
182
230
|
? style.height
|
|
183
|
-
:
|
|
231
|
+
: undefined;
|
|
184
232
|
|
|
185
233
|
const interpolationIndex = getInterpolationIndex(scenes, index);
|
|
186
|
-
const isModal =
|
|
234
|
+
const isModal = getIsInModalPresentation(
|
|
235
|
+
curr,
|
|
236
|
+
interpolationIndex,
|
|
237
|
+
isParentModal
|
|
238
|
+
);
|
|
187
239
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
240
|
+
if (typeof height === 'number') {
|
|
241
|
+
acc[curr.route.key] = { measured: true, value: height };
|
|
242
|
+
} else {
|
|
243
|
+
const previousHeight = previous[curr.route.key];
|
|
244
|
+
const defaultHeight = getDefaultHeaderHeight({
|
|
245
|
+
landscape: layout.width > layout.height,
|
|
246
|
+
modalPresentation: isModal,
|
|
247
|
+
topInset: headerStatusBarHeight,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// If we hadn't measured before, use re-computed default height
|
|
251
|
+
if (
|
|
252
|
+
!previousHeight?.measured &&
|
|
253
|
+
defaultHeight !== previousHeight?.value
|
|
254
|
+
) {
|
|
255
|
+
acc[curr.route.key] = {
|
|
256
|
+
measured: false,
|
|
257
|
+
value: defaultHeight,
|
|
258
|
+
};
|
|
259
|
+
} else if (previousHeight) {
|
|
260
|
+
acc[curr.route.key] = previousHeight;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
196
263
|
|
|
197
264
|
return acc;
|
|
198
265
|
}, {});
|
|
@@ -263,6 +330,27 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
|
|
|
263
330
|
return getDefaultAnimation(animation) !== 'none';
|
|
264
331
|
}
|
|
265
332
|
|
|
333
|
+
const getAllRoutes = (
|
|
334
|
+
routes: Route<string>[],
|
|
335
|
+
state: StackNavigationState<ParamListBase>
|
|
336
|
+
) => {
|
|
337
|
+
const routeKeys = new Set(routes.map((route) => route.key));
|
|
338
|
+
const inactiveRoutes = state.routes.slice(state.index + 1);
|
|
339
|
+
|
|
340
|
+
// If a route is moved from active routes to inactive routes,
|
|
341
|
+
// It can still be in the local copy of `routes` until the animation ends
|
|
342
|
+
// So we need to deduplicate the routes to avoid rendering the same route twice
|
|
343
|
+
return [
|
|
344
|
+
...routes,
|
|
345
|
+
...inactiveRoutes.filter((route) => !routeKeys.has(route.key)),
|
|
346
|
+
];
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const isInactiveRoute = (route: Route<string>, routes: Route<string>[]) =>
|
|
350
|
+
// `routes` contains active routes and routes animating during transitions.
|
|
351
|
+
// Any route added by `getAllRoutes` that's missing from this list is inactive.
|
|
352
|
+
!routes.some((currentRoute) => currentRoute.key === route.key);
|
|
353
|
+
|
|
266
354
|
export class CardStack extends React.Component<Props, State> {
|
|
267
355
|
static getDerivedStateFromProps(
|
|
268
356
|
props: Props,
|
|
@@ -275,10 +363,8 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
275
363
|
return null;
|
|
276
364
|
}
|
|
277
365
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
...props.state.preloadedRoutes,
|
|
281
|
-
].reduce<GestureValues>((acc, curr) => {
|
|
366
|
+
const allRoutes = getAllRoutes(props.routes, props.state);
|
|
367
|
+
const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
|
|
282
368
|
const descriptor = props.descriptors[curr.key];
|
|
283
369
|
const { animation } = descriptor?.options || {};
|
|
284
370
|
|
|
@@ -287,7 +373,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
287
373
|
new Animated.Value(
|
|
288
374
|
(props.openingRouteKeys.includes(curr.key) &&
|
|
289
375
|
getAnimationEnabled(animation)) ||
|
|
290
|
-
|
|
376
|
+
isInactiveRoute(curr, props.routes)
|
|
291
377
|
? getDistanceFromOptions(
|
|
292
378
|
state.layout,
|
|
293
379
|
descriptor?.options,
|
|
@@ -299,254 +385,189 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
299
385
|
return acc;
|
|
300
386
|
}, {});
|
|
301
387
|
|
|
302
|
-
const modalRouteKeys = getModalRouteKeys(
|
|
303
|
-
[...props.routes, ...props.state.preloadedRoutes],
|
|
304
|
-
props.descriptors
|
|
305
|
-
);
|
|
388
|
+
const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
|
|
306
389
|
|
|
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
|
-
);
|
|
390
|
+
const scenes = allRoutes.map((route, index, self) => {
|
|
391
|
+
// For preloaded or retained screens, we don't care about the previous and the next screen
|
|
392
|
+
const isInactive = isInactiveRoute(route, props.routes);
|
|
393
|
+
const previousRoute = isInactive ? undefined : self[index - 1];
|
|
394
|
+
const nextRoute = isInactive ? undefined : self[index + 1];
|
|
361
395
|
|
|
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
|
-
|
|
396
|
+
const oldScene = state.scenes[index];
|
|
397
|
+
const previousScene =
|
|
398
|
+
oldScene?.route.key === route.key
|
|
399
|
+
? oldScene
|
|
400
|
+
: state.scenes.find((scene) => scene.route.key === route.key);
|
|
401
|
+
const closing =
|
|
402
|
+
previousScene?.closing ??
|
|
403
|
+
new Animated.Value(props.closingRouteKeys.includes(route.key) ? 1 : 0);
|
|
404
|
+
|
|
405
|
+
const currentGesture = gestures[route.key];
|
|
406
|
+
|
|
407
|
+
if (currentGesture == null) {
|
|
408
|
+
throw new Error(`Couldn't find a gesture for route '${route.key}'.`);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const previousGesture = previousRoute
|
|
412
|
+
? gestures[previousRoute.key]
|
|
413
|
+
: undefined;
|
|
414
|
+
const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
|
|
415
|
+
|
|
416
|
+
const descriptor =
|
|
417
|
+
props.descriptors[route.key] ??
|
|
418
|
+
state.descriptors[route.key] ??
|
|
419
|
+
oldScene?.descriptor;
|
|
420
|
+
|
|
421
|
+
if (descriptor == null) {
|
|
422
|
+
throw new Error(`Couldn't find a descriptor for route '${route.key}'.`);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const nextOptions = nextRoute
|
|
426
|
+
? (props.descriptors[nextRoute.key] ?? state.descriptors[nextRoute.key])
|
|
427
|
+
?.options
|
|
428
|
+
: undefined;
|
|
429
|
+
|
|
430
|
+
const previousOptions = previousRoute
|
|
431
|
+
? (
|
|
432
|
+
props.descriptors[previousRoute.key] ??
|
|
433
|
+
state.descriptors[previousRoute.key]
|
|
434
|
+
)?.options
|
|
435
|
+
: undefined;
|
|
436
|
+
|
|
437
|
+
// When a screen is not the last, it should use next screen's transition config
|
|
438
|
+
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
|
439
|
+
// For example combining a slide and a modal transition would look wrong otherwise
|
|
440
|
+
// With this approach, combining different transition styles in the same navigator mostly looks right
|
|
441
|
+
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
|
442
|
+
// but the majority of the transitions look alright
|
|
443
|
+
const optionsForTransitionConfig =
|
|
444
|
+
index !== self.length - 1 &&
|
|
445
|
+
nextOptions &&
|
|
446
|
+
nextOptions?.presentation !== 'transparentModal'
|
|
447
|
+
? nextOptions
|
|
448
|
+
: descriptor.options;
|
|
449
|
+
|
|
450
|
+
// Assume modal if there are already modal screens in the stack
|
|
451
|
+
// or current screen is a modal when no presentation is specified
|
|
452
|
+
const isModal = modalRouteKeys.includes(route.key);
|
|
453
|
+
|
|
454
|
+
const animation = getDefaultAnimation(
|
|
455
|
+
optionsForTransitionConfig.animation
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
const isAnimationEnabled = getAnimationEnabled(animation);
|
|
459
|
+
|
|
460
|
+
const transitionPreset =
|
|
461
|
+
animation !== 'default'
|
|
462
|
+
? NAMED_TRANSITIONS_PRESETS[animation]
|
|
463
|
+
: optionsForTransitionConfig.presentation === 'transparentModal'
|
|
464
|
+
? ModalFadeTransition
|
|
465
|
+
: optionsForTransitionConfig.presentation === 'modal' || isModal
|
|
466
|
+
? ModalTransition
|
|
467
|
+
: DefaultTransition;
|
|
468
|
+
|
|
469
|
+
const {
|
|
470
|
+
gestureDirection = transitionPreset.gestureDirection,
|
|
471
|
+
transitionSpec = transitionPreset.transitionSpec,
|
|
472
|
+
cardStyleInterpolator = isAnimationEnabled
|
|
473
|
+
? transitionPreset.cardStyleInterpolator
|
|
474
|
+
: forNoAnimationCard,
|
|
475
|
+
gestureEnabled = Platform.OS === 'ios' &&
|
|
476
|
+
isAnimationEnabled &&
|
|
477
|
+
getIsGestureEnabledByDefault(cardStyleInterpolator),
|
|
478
|
+
headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
|
|
479
|
+
cardOverlayEnabled,
|
|
480
|
+
cardShadowEnabled,
|
|
481
|
+
} = optionsForTransitionConfig;
|
|
482
|
+
|
|
483
|
+
const headerMode: StackHeaderMode =
|
|
484
|
+
descriptor.options.headerMode ??
|
|
485
|
+
(!(
|
|
486
|
+
optionsForTransitionConfig.presentation === 'modal' ||
|
|
487
|
+
optionsForTransitionConfig.presentation === 'transparentModal' ||
|
|
488
|
+
nextOptions?.presentation === 'modal' ||
|
|
489
|
+
nextOptions?.presentation === 'transparentModal' ||
|
|
490
|
+
getIsModalPresentation(cardStyleInterpolator) ||
|
|
491
|
+
getIsScreenHeader(cardStyleInterpolator)
|
|
492
|
+
) &&
|
|
493
|
+
Platform.OS === 'ios' &&
|
|
494
|
+
descriptor.options.header === undefined
|
|
495
|
+
? 'float'
|
|
496
|
+
: 'screen');
|
|
497
|
+
|
|
498
|
+
const isRTL = props.direction === 'rtl';
|
|
499
|
+
|
|
500
|
+
const scene = {
|
|
501
|
+
route,
|
|
502
|
+
descriptor: {
|
|
503
|
+
...descriptor,
|
|
504
|
+
options: {
|
|
505
|
+
...descriptor.options,
|
|
506
|
+
animation,
|
|
507
|
+
cardOverlayEnabled,
|
|
508
|
+
cardShadowEnabled,
|
|
509
|
+
cardStyleInterpolator,
|
|
510
|
+
gestureDirection,
|
|
511
|
+
gestureEnabled,
|
|
512
|
+
headerStyleInterpolator,
|
|
513
|
+
transitionSpec,
|
|
514
|
+
headerMode,
|
|
417
515
|
},
|
|
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
|
|
516
|
+
},
|
|
517
|
+
progress: {
|
|
518
|
+
current: getProgressFromGesture(
|
|
519
|
+
currentGesture,
|
|
520
|
+
state.layout,
|
|
521
|
+
descriptor.options,
|
|
522
|
+
isRTL
|
|
523
|
+
),
|
|
524
|
+
next:
|
|
525
|
+
nextGesture && nextOptions?.presentation !== 'transparentModal'
|
|
435
526
|
? getProgressFromGesture(
|
|
436
|
-
|
|
527
|
+
nextGesture,
|
|
437
528
|
state.layout,
|
|
438
|
-
|
|
529
|
+
nextOptions,
|
|
439
530
|
isRTL
|
|
440
531
|
)
|
|
441
532
|
: undefined,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return scene;
|
|
465
|
-
}
|
|
466
|
-
);
|
|
533
|
+
previous: previousGesture
|
|
534
|
+
? getProgressFromGesture(
|
|
535
|
+
previousGesture,
|
|
536
|
+
state.layout,
|
|
537
|
+
previousOptions,
|
|
538
|
+
isRTL
|
|
539
|
+
)
|
|
540
|
+
: undefined,
|
|
541
|
+
},
|
|
542
|
+
closing,
|
|
543
|
+
__memo: [
|
|
544
|
+
state.layout,
|
|
545
|
+
descriptor,
|
|
546
|
+
nextOptions,
|
|
547
|
+
previousOptions,
|
|
548
|
+
currentGesture,
|
|
549
|
+
nextGesture,
|
|
550
|
+
previousGesture,
|
|
551
|
+
],
|
|
552
|
+
};
|
|
467
553
|
|
|
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
|
-
}
|
|
554
|
+
if (
|
|
555
|
+
oldScene &&
|
|
556
|
+
scene.__memo.every((it, i) => {
|
|
557
|
+
return oldScene.__memo[i] === it;
|
|
558
|
+
})
|
|
559
|
+
) {
|
|
560
|
+
return oldScene;
|
|
503
561
|
}
|
|
504
562
|
|
|
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
|
-
}
|
|
563
|
+
return scene;
|
|
564
|
+
});
|
|
543
565
|
|
|
544
566
|
return {
|
|
545
567
|
routes: props.routes,
|
|
546
568
|
scenes,
|
|
547
569
|
gestures,
|
|
548
570
|
descriptors: props.descriptors,
|
|
549
|
-
activeStates,
|
|
550
571
|
headerHeights: getHeaderHeights(
|
|
551
572
|
scenes,
|
|
552
573
|
props.insets,
|
|
@@ -555,6 +576,15 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
555
576
|
state.layout,
|
|
556
577
|
state.headerHeights
|
|
557
578
|
),
|
|
579
|
+
// Nested navigators clean up `route.state` when the screen is paused with `Activity`
|
|
580
|
+
// So we remember routes which had state until they are removed
|
|
581
|
+
nestedStateRouteKeys: allRoutes
|
|
582
|
+
.filter(
|
|
583
|
+
(route) =>
|
|
584
|
+
('state' in route && route.state) ||
|
|
585
|
+
state.nestedStateRouteKeys.includes(route.key)
|
|
586
|
+
)
|
|
587
|
+
.map((route) => route.key),
|
|
558
588
|
};
|
|
559
589
|
}
|
|
560
590
|
|
|
@@ -567,13 +597,13 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
567
597
|
gestures: {},
|
|
568
598
|
layout: SafeAreaProviderCompat.initialMetrics.frame,
|
|
569
599
|
descriptors: this.props.descriptors,
|
|
570
|
-
activeStates: [],
|
|
571
600
|
// Used when card's header is null and mode is float to make transition
|
|
572
601
|
// between screens with headers and those without headers smooth.
|
|
573
602
|
// This is not a great heuristic here. We don't know synchronously
|
|
574
603
|
// on mount what the header height is so we have just used the most
|
|
575
604
|
// common cases here.
|
|
576
605
|
headerHeights: {},
|
|
606
|
+
nestedStateRouteKeys: [],
|
|
577
607
|
};
|
|
578
608
|
}
|
|
579
609
|
|
|
@@ -611,14 +641,17 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
611
641
|
this.setState(({ headerHeights }) => {
|
|
612
642
|
const previousHeight = headerHeights[route.key];
|
|
613
643
|
|
|
614
|
-
if (previousHeight === height) {
|
|
644
|
+
if (previousHeight?.value === height) {
|
|
615
645
|
return null;
|
|
616
646
|
}
|
|
617
647
|
|
|
618
648
|
return {
|
|
619
649
|
headerHeights: {
|
|
620
650
|
...headerHeights,
|
|
621
|
-
[route.key]:
|
|
651
|
+
[route.key]: {
|
|
652
|
+
measured: true,
|
|
653
|
+
value: height,
|
|
654
|
+
},
|
|
622
655
|
},
|
|
623
656
|
};
|
|
624
657
|
});
|
|
@@ -627,7 +660,13 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
627
660
|
private getFocusedRoute = () => {
|
|
628
661
|
const { state } = this.props;
|
|
629
662
|
|
|
630
|
-
|
|
663
|
+
const route = state.routes[state.index];
|
|
664
|
+
|
|
665
|
+
if (route == null) {
|
|
666
|
+
throw new Error(`Couldn't find a route at index ${state.index}.`);
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return route;
|
|
631
670
|
};
|
|
632
671
|
|
|
633
672
|
private getPreviousScene = ({ route }: { route: Route<string> }) => {
|
|
@@ -647,34 +686,45 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
647
686
|
return undefined;
|
|
648
687
|
};
|
|
649
688
|
|
|
650
|
-
render() {
|
|
689
|
+
override render() {
|
|
651
690
|
const {
|
|
652
691
|
insets,
|
|
653
692
|
state,
|
|
654
693
|
routes,
|
|
655
694
|
openingRouteKeys,
|
|
656
695
|
closingRouteKeys,
|
|
696
|
+
replacingRouteKeys,
|
|
657
697
|
onOpenRoute,
|
|
658
698
|
onCloseRoute,
|
|
699
|
+
onGestureStart,
|
|
700
|
+
onGestureEnd,
|
|
701
|
+
onGestureCancel,
|
|
659
702
|
renderHeader,
|
|
660
703
|
isParentHeaderShown,
|
|
661
704
|
isParentModal,
|
|
662
705
|
onTransitionStart,
|
|
663
706
|
onTransitionEnd,
|
|
664
|
-
onGestureStart,
|
|
665
|
-
onGestureEnd,
|
|
666
|
-
onGestureCancel,
|
|
667
|
-
detachInactiveScreens = Platform.OS === 'web' ||
|
|
668
|
-
Platform.OS === 'android' ||
|
|
669
|
-
Platform.OS === 'ios',
|
|
670
707
|
} = this.props;
|
|
671
708
|
|
|
672
|
-
const { scenes, layout, gestures,
|
|
709
|
+
const { scenes, layout, gestures, headerHeights, nestedStateRouteKeys } =
|
|
673
710
|
this.state;
|
|
674
711
|
|
|
712
|
+
const allRoutes = getAllRoutes(routes, state);
|
|
713
|
+
|
|
675
714
|
const focusedRoute = state.routes[state.index];
|
|
676
715
|
|
|
677
|
-
|
|
716
|
+
if (focusedRoute == null) {
|
|
717
|
+
throw new Error(`Couldn't find a route at index ${state.index}.`);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Render only two top-most active headers as a workaround for
|
|
721
|
+
// https://github.com/react-navigation/react-navigation/issues/12456.
|
|
722
|
+
// If the header is persisted, it might be placed incorrectly when navigating back
|
|
723
|
+
const activeHeaderScenes = scenes
|
|
724
|
+
.filter((scene) => !isInactiveRoute(scene.route, routes))
|
|
725
|
+
.slice(-2);
|
|
726
|
+
|
|
727
|
+
const isFloatHeaderAbsolute = activeHeaderScenes.some((scene) => {
|
|
678
728
|
const options = scene.descriptor.options ?? {};
|
|
679
729
|
const { headerMode, headerTransparent, headerShown = true } = options;
|
|
680
730
|
|
|
@@ -693,13 +743,16 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
693
743
|
scenes.findLast((scene) => scene.route.key === focusedRoute.key)
|
|
694
744
|
?.descriptor.options.headerMode === 'float';
|
|
695
745
|
|
|
746
|
+
const focusedHeaderHeight = headerHeights[focusedRoute.key];
|
|
747
|
+
|
|
696
748
|
return (
|
|
697
749
|
<View style={styles.container}>
|
|
698
750
|
{renderHeader({
|
|
699
751
|
mode: 'float',
|
|
700
|
-
scenes,
|
|
752
|
+
scenes: activeHeaderScenes,
|
|
701
753
|
getPreviousScene: this.getPreviousScene,
|
|
702
754
|
getFocusedRoute: this.getFocusedRoute,
|
|
755
|
+
contentHeight: headerHeights[focusedRoute.key],
|
|
703
756
|
onContentHeightChange: this.handleHeaderLayout,
|
|
704
757
|
style: [
|
|
705
758
|
isFloatHeaderAbsolute && [
|
|
@@ -707,39 +760,36 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
707
760
|
// The header title can't be found with maestro without this
|
|
708
761
|
// In some cases, header buttons may not be touchable as well
|
|
709
762
|
isFloatHeaderFocused && {
|
|
710
|
-
height:
|
|
763
|
+
height: focusedHeaderHeight?.value,
|
|
711
764
|
},
|
|
712
765
|
],
|
|
713
766
|
],
|
|
714
767
|
})}
|
|
715
|
-
<
|
|
716
|
-
|
|
717
|
-
style={styles.container}
|
|
718
|
-
onLayout={this.handleLayout}
|
|
719
|
-
>
|
|
720
|
-
{[...routes, ...state.preloadedRoutes].map((route, index) => {
|
|
768
|
+
<View style={styles.container} onLayout={this.handleLayout}>
|
|
769
|
+
{allRoutes.map((route, index, self) => {
|
|
721
770
|
const focused = focusedRoute.key === route.key;
|
|
722
771
|
const gesture = gestures[route.key];
|
|
723
772
|
const scene = scenes[index];
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
index >= routes.length
|
|
734
|
-
) {
|
|
735
|
-
return null;
|
|
773
|
+
|
|
774
|
+
if (gesture == null) {
|
|
775
|
+
throw new Error(
|
|
776
|
+
`Couldn't find a gesture for route '${route.key}'.`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
if (scene == null) {
|
|
781
|
+
throw new Error(`Couldn't find a scene at index ${index}.`);
|
|
736
782
|
}
|
|
737
783
|
|
|
784
|
+
const nextScene = scenes[index + 1];
|
|
785
|
+
const nextRoute = self[index + 1];
|
|
786
|
+
|
|
738
787
|
const {
|
|
788
|
+
inactiveBehavior = 'pause',
|
|
789
|
+
animation,
|
|
739
790
|
headerShown = true,
|
|
791
|
+
headerMode,
|
|
740
792
|
headerTransparent,
|
|
741
|
-
freezeOnBlur,
|
|
742
|
-
autoHideHomeIndicator,
|
|
743
793
|
} = scene.descriptor.options;
|
|
744
794
|
|
|
745
795
|
const safeAreaInsetTop = insets.top;
|
|
@@ -748,46 +798,142 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
748
798
|
const safeAreaInsetLeft = insets.left;
|
|
749
799
|
|
|
750
800
|
const headerHeight =
|
|
751
|
-
headerShown !== false
|
|
801
|
+
headerShown !== false
|
|
802
|
+
? headerHeights[route.key]
|
|
803
|
+
: HIDDEN_HEADER_HEIGHT;
|
|
752
804
|
|
|
753
805
|
// Start from current card and count backwards the number of cards with same interpolation
|
|
754
806
|
const interpolationIndex = getInterpolationIndex(scenes, index);
|
|
755
|
-
const
|
|
807
|
+
const isInModalPresentation = getIsInModalPresentation(
|
|
756
808
|
scene,
|
|
757
809
|
interpolationIndex,
|
|
758
810
|
isParentModal
|
|
759
811
|
);
|
|
760
812
|
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
813
|
+
const isModal =
|
|
814
|
+
scene.descriptor.options.presentation === 'modal' ||
|
|
815
|
+
getIsModalPresentation(
|
|
816
|
+
scene.descriptor.options.cardStyleInterpolator
|
|
817
|
+
);
|
|
764
818
|
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
819
|
+
const isNextScreenModal =
|
|
820
|
+
nextScene != null &&
|
|
821
|
+
(nextScene.descriptor.options.presentation === 'modal' ||
|
|
822
|
+
getIsModalPresentation(
|
|
823
|
+
nextScene.descriptor.options.cardStyleInterpolator
|
|
824
|
+
));
|
|
768
825
|
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
826
|
+
const isNextScreenTransparent =
|
|
827
|
+
nextScene?.descriptor.options.presentation === 'transparentModal';
|
|
828
|
+
|
|
829
|
+
const isAnimationEnabled =
|
|
830
|
+
getAnimationEnabled(animation) ||
|
|
831
|
+
// Also check next screen's animation,
|
|
832
|
+
// As it will result in both screens being visible
|
|
833
|
+
(nextScene
|
|
834
|
+
? getAnimationEnabled(nextScene.descriptor.options.animation)
|
|
835
|
+
: false);
|
|
836
|
+
|
|
837
|
+
const isRemoving =
|
|
838
|
+
replacingRouteKeys.includes(route.key) ||
|
|
839
|
+
closingRouteKeys.includes(route.key);
|
|
840
|
+
|
|
841
|
+
const isFocusing =
|
|
842
|
+
openingRouteKeys.includes(route.key) ||
|
|
843
|
+
(nextRoute != null &&
|
|
844
|
+
[...closingRouteKeys, ...replacingRouteKeys].includes(
|
|
845
|
+
nextRoute.key
|
|
846
|
+
));
|
|
847
|
+
|
|
848
|
+
const isInactive = isInactiveRoute(route, this.props.routes);
|
|
849
|
+
const isRetained = state.retainedRouteKeys?.includes(route.key);
|
|
850
|
+
const isPreloaded = isInactive && !isRetained;
|
|
851
|
+
|
|
852
|
+
const isTopmost = index === routes.length - 1;
|
|
853
|
+
const isBeforeLast = index === routes.length - 2;
|
|
854
|
+
|
|
855
|
+
// Keep animating and the last two rendered routes visible for smoother transitions
|
|
856
|
+
const isVisible =
|
|
857
|
+
focused ||
|
|
858
|
+
// React native destroys the view when hidden
|
|
859
|
+
// So we keep retained screens visible
|
|
860
|
+
// This makes sure any videos being played don't stop etc.
|
|
861
|
+
isRetained ||
|
|
862
|
+
isNextScreenTransparent ||
|
|
863
|
+
// Modal presentation on iOS has multiple screens visible at the same time
|
|
864
|
+
// The base screen before the modals renders the dim, which is always persistent
|
|
865
|
+
// On iPadOS, the base screen is visible while modal is in a smaller area on top
|
|
866
|
+
// The top edge of the last 3 screens can be seen animating behind the modal
|
|
867
|
+
(isNextScreenModal && (!isModal || index >= routes.length - 3)) ||
|
|
868
|
+
// We only need to keep other screens visible when animation is enabled
|
|
869
|
+
(isAnimationEnabled &&
|
|
870
|
+
(isFocusing ||
|
|
871
|
+
isRemoving ||
|
|
872
|
+
// Preloaded screens should stay mounted, but remain hidden until focused
|
|
873
|
+
(!isPreloaded && index >= routes.length - 2)));
|
|
874
|
+
|
|
875
|
+
const activityMode =
|
|
876
|
+
// Keep focused and focusing screens interactive
|
|
877
|
+
focused || isFocusing
|
|
878
|
+
? 'normal'
|
|
879
|
+
: inactiveBehavior === 'none' ||
|
|
880
|
+
// Keep effects running until the closing animation finishes
|
|
881
|
+
isRemoving ||
|
|
882
|
+
// Unpause preloaded or retained screens so updates are visible
|
|
883
|
+
// Handle retained explicitly as isInactive won't be updated until animation end
|
|
884
|
+
isInactive ||
|
|
885
|
+
isRetained ||
|
|
886
|
+
// Keep the screen before transparent screen active
|
|
887
|
+
// This lets the screen under the transparent screen update and animate
|
|
888
|
+
isNextScreenTransparent ||
|
|
889
|
+
// Keep the screen before last screen active
|
|
890
|
+
// Otherwise it breaks animation when going back
|
|
891
|
+
isBeforeLast
|
|
892
|
+
? 'inert'
|
|
893
|
+
: inactiveBehavior === 'unmount' &&
|
|
894
|
+
// Don't unmount screens that needs to stay visible
|
|
895
|
+
!isVisible &&
|
|
896
|
+
// Don't unmount screens with nested navigators
|
|
897
|
+
// So we don't lose their state
|
|
898
|
+
!nestedStateRouteKeys.includes(route.key)
|
|
899
|
+
? 'unmounted'
|
|
900
|
+
: 'paused';
|
|
901
|
+
|
|
902
|
+
if (activityMode === 'unmounted') {
|
|
903
|
+
return null;
|
|
904
|
+
}
|
|
772
905
|
|
|
773
906
|
return (
|
|
774
|
-
<
|
|
907
|
+
<ActivityView
|
|
775
908
|
key={route.key}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
909
|
+
mode={activityMode}
|
|
910
|
+
visible={isVisible}
|
|
911
|
+
style={{
|
|
912
|
+
...StyleSheet.absoluteFill,
|
|
913
|
+
marginTop:
|
|
914
|
+
headerTransparent || headerMode === 'screen'
|
|
915
|
+
? 0
|
|
916
|
+
: // Add offset for non-transparent floating header
|
|
917
|
+
isFloatHeaderAbsolute
|
|
918
|
+
? // If header is absolute, offset the card by the header height
|
|
919
|
+
headerHeight?.value
|
|
920
|
+
: // Otherwise, the in-flow float header already takes the
|
|
921
|
+
// focused header's space, so offset by the difference
|
|
922
|
+
headerHeight && focusedHeaderHeight
|
|
923
|
+
? headerHeight.value - focusedHeaderHeight.value
|
|
924
|
+
: 0,
|
|
925
|
+
}}
|
|
782
926
|
>
|
|
783
927
|
<CardContainer
|
|
784
928
|
index={index}
|
|
785
929
|
interpolationIndex={interpolationIndex}
|
|
786
|
-
modal={
|
|
787
|
-
active={
|
|
930
|
+
modal={isInModalPresentation}
|
|
931
|
+
active={isTopmost}
|
|
788
932
|
focused={focused}
|
|
789
933
|
opening={openingRouteKeys.includes(route.key)}
|
|
790
934
|
closing={closingRouteKeys.includes(route.key)}
|
|
935
|
+
closingAnimated={scene.closing}
|
|
936
|
+
nextClosingAnimated={nextScene?.closing}
|
|
791
937
|
layout={layout}
|
|
792
938
|
gesture={gesture}
|
|
793
939
|
scene={scene}
|
|
@@ -803,22 +949,19 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
803
949
|
onHeaderHeightChange={this.handleHeaderLayout}
|
|
804
950
|
getPreviousScene={this.getPreviousScene}
|
|
805
951
|
getFocusedRoute={this.getFocusedRoute}
|
|
806
|
-
hasAbsoluteFloatHeader={
|
|
807
|
-
isFloatHeaderAbsolute && !headerTransparent
|
|
808
|
-
}
|
|
809
952
|
renderHeader={renderHeader}
|
|
810
953
|
onOpenRoute={onOpenRoute}
|
|
811
954
|
onCloseRoute={onCloseRoute}
|
|
812
955
|
onTransitionStart={onTransitionStart}
|
|
813
956
|
onTransitionEnd={onTransitionEnd}
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
</
|
|
957
|
+
preloaded={isInactive}
|
|
958
|
+
>
|
|
959
|
+
{scene.descriptor.render()}
|
|
960
|
+
</CardContainer>
|
|
961
|
+
</ActivityView>
|
|
819
962
|
);
|
|
820
963
|
})}
|
|
821
|
-
</
|
|
964
|
+
</View>
|
|
822
965
|
</View>
|
|
823
966
|
);
|
|
824
967
|
}
|