@amazon-devices/react-navigation__stack 8.0.9000000000 → 8.0.9000000002
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/SystemModules/amzn__react-navigation__stack-8.private.modules.txt +39 -34
- package/kepler-compatibility.json +4 -2
- package/lib/module/index.js +0 -34
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/oss-attribution/attribution.txt +76 -0
- package/oss-attribution/licenseInfos.json +27 -0
- package/package.json +1 -1
- package/src/index.tsx +0 -55
- package/SystemBundles/98ca5eeb45684e3e5615c766e1d3a956fee53d14cd6c2318098c23c210bd76d7.bundle +0 -13464
- package/SystemBundles/98ca5eeb45684e3e5615c766e1d3a956fee53d14cd6c2318098c23c210bd76d7.map +0 -4638
- package/lib/module/TransitionConfigs/CardStyleInterpolators.js +0 -394
- package/lib/module/TransitionConfigs/HeaderStyleInterpolators.js +0 -262
- package/lib/module/TransitionConfigs/TransitionPresets.js +0 -156
- package/lib/module/TransitionConfigs/TransitionSpecs.js +0 -124
- package/lib/module/navigators/createStackNavigator.js +0 -71
- package/lib/module/types.js +0 -4
- package/lib/module/utils/CardAnimationContext.js +0 -5
- package/lib/module/utils/GestureHandlerRefContext.js +0 -5
- package/lib/module/utils/ModalPresentationContext.js +0 -5
- package/lib/module/utils/conditional.js +0 -28
- package/lib/module/utils/findLastIndex.js +0 -11
- package/lib/module/utils/gestureActivationCriteria.js +0 -60
- package/lib/module/utils/getDistanceForDirection.js +0 -15
- package/lib/module/utils/getInvertedMultiplier.js +0 -15
- package/lib/module/utils/getModalRoutesKeys.js +0 -12
- package/lib/module/utils/getShadowStyle.js +0 -24
- package/lib/module/utils/throttle.js +0 -14
- package/lib/module/utils/useCardAnimation.js +0 -12
- package/lib/module/utils/useGestureHandlerRef.js +0 -12
- package/lib/module/utils/useKeyboardManager.js +0 -90
- package/lib/module/views/GestureHandler.android.js +0 -4
- package/lib/module/views/GestureHandler.ios.js +0 -4
- package/lib/module/views/GestureHandler.js +0 -21
- package/lib/module/views/GestureHandler.kepler.js +0 -4
- package/lib/module/views/GestureHandlerNative.js +0 -18
- package/lib/module/views/Header/Header.js +0 -56
- package/lib/module/views/Header/HeaderContainer.js +0 -128
- package/lib/module/views/Header/HeaderSegment.js +0 -142
- package/lib/module/views/Screens.js +0 -42
- package/lib/module/views/Stack/Card.js +0 -433
- package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
- package/lib/module/views/Stack/CardContainer.js +0 -276
- package/lib/module/views/Stack/CardContent.js +0 -74
- package/lib/module/views/Stack/CardStack.js +0 -493
- package/lib/module/views/Stack/StackView.js +0 -339
- package/src/TransitionConfigs/CardStyleInterpolators.tsx +0 -460
- package/src/TransitionConfigs/HeaderStyleInterpolators.tsx +0 -286
- package/src/TransitionConfigs/TransitionPresets.tsx +0 -182
- package/src/TransitionConfigs/TransitionSpecs.tsx +0 -124
- package/src/navigators/createStackNavigator.tsx +0 -120
- package/src/types.tsx +0 -639
- package/src/utils/CardAnimationContext.tsx +0 -7
- package/src/utils/GestureHandlerRefContext.tsx +0 -5
- package/src/utils/ModalPresentationContext.tsx +0 -3
- package/src/utils/conditional.tsx +0 -33
- package/src/utils/findLastIndex.tsx +0 -9
- package/src/utils/gestureActivationCriteria.tsx +0 -70
- package/src/utils/getDistanceForDirection.tsx +0 -19
- package/src/utils/getInvertedMultiplier.tsx +0 -17
- package/src/utils/getModalRoutesKeys.ts +0 -21
- package/src/utils/getShadowStyle.tsx +0 -37
- package/src/utils/throttle.tsx +0 -16
- package/src/utils/useCardAnimation.tsx +0 -15
- package/src/utils/useGestureHandlerRef.tsx +0 -15
- package/src/utils/useKeyboardManager.tsx +0 -103
- package/src/views/GestureHandler.android.tsx +0 -1
- package/src/views/GestureHandler.ios.tsx +0 -1
- package/src/views/GestureHandler.kepler.tsx +0 -1
- package/src/views/GestureHandler.tsx +0 -23
- package/src/views/GestureHandlerNative.tsx +0 -23
- package/src/views/Header/Header.tsx +0 -73
- package/src/views/Header/HeaderContainer.tsx +0 -191
- package/src/views/Header/HeaderSegment.tsx +0 -203
- package/src/views/Screens.tsx +0 -45
- package/src/views/Stack/Card.tsx +0 -646
- package/src/views/Stack/CardA11yWrapper.tsx +0 -67
- package/src/views/Stack/CardContainer.tsx +0 -339
- package/src/views/Stack/CardContent.tsx +0 -92
- package/src/views/Stack/CardStack.tsx +0 -844
- package/src/views/Stack/StackView.tsx +0 -485
|
@@ -1,844 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getDefaultHeaderHeight,
|
|
3
|
-
SafeAreaProviderCompat,
|
|
4
|
-
} from '@amazon-devices/react-navigation__elements';
|
|
5
|
-
import type {
|
|
6
|
-
LocaleDirection,
|
|
7
|
-
ParamListBase,
|
|
8
|
-
Route,
|
|
9
|
-
StackNavigationState,
|
|
10
|
-
} from '@amazon-devices/react-navigation__native';
|
|
11
|
-
import * as React from 'react';
|
|
12
|
-
import {
|
|
13
|
-
Animated,
|
|
14
|
-
type LayoutChangeEvent,
|
|
15
|
-
Platform,
|
|
16
|
-
StyleSheet,
|
|
17
|
-
View,
|
|
18
|
-
} from 'react-native';
|
|
19
|
-
import type { EdgeInsets } from '@amazon-devices/react-native-safe-area-context';
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
forModalPresentationIOS,
|
|
23
|
-
forNoAnimation as forNoAnimationCard,
|
|
24
|
-
} from '../../TransitionConfigs/CardStyleInterpolators';
|
|
25
|
-
import {
|
|
26
|
-
BottomSheetAndroid,
|
|
27
|
-
DefaultTransition,
|
|
28
|
-
FadeFromBottomAndroid,
|
|
29
|
-
FadeFromRightAndroid,
|
|
30
|
-
ModalFadeTransition,
|
|
31
|
-
ModalSlideFromBottomIOS,
|
|
32
|
-
ModalTransition,
|
|
33
|
-
RevealFromBottomAndroid,
|
|
34
|
-
ScaleFromCenterAndroid,
|
|
35
|
-
SlideFromLeftIOS,
|
|
36
|
-
SlideFromRightIOS,
|
|
37
|
-
} from '../../TransitionConfigs/TransitionPresets';
|
|
38
|
-
import type {
|
|
39
|
-
Layout,
|
|
40
|
-
Scene,
|
|
41
|
-
StackAnimationName,
|
|
42
|
-
StackCardStyleInterpolator,
|
|
43
|
-
StackDescriptorMap,
|
|
44
|
-
StackHeaderMode,
|
|
45
|
-
StackNavigationOptions,
|
|
46
|
-
TransitionPreset,
|
|
47
|
-
} from '../../types';
|
|
48
|
-
import { findLastIndex } from '../../utils/findLastIndex';
|
|
49
|
-
import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
|
|
50
|
-
import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
|
|
51
|
-
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
|
52
|
-
import { MaybeScreen, MaybeScreenContainer } from '../Screens';
|
|
53
|
-
import { CardContainer } from './CardContainer';
|
|
54
|
-
|
|
55
|
-
type GestureValues = {
|
|
56
|
-
[key: string]: Animated.Value;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
type Props = {
|
|
60
|
-
direction: LocaleDirection;
|
|
61
|
-
insets: EdgeInsets;
|
|
62
|
-
state: StackNavigationState<ParamListBase>;
|
|
63
|
-
descriptors: StackDescriptorMap;
|
|
64
|
-
preloadedDescriptors: StackDescriptorMap;
|
|
65
|
-
routes: Route<string>[];
|
|
66
|
-
openingRouteKeys: string[];
|
|
67
|
-
closingRouteKeys: string[];
|
|
68
|
-
onOpenRoute: (props: { route: Route<string> }) => void;
|
|
69
|
-
onCloseRoute: (props: { route: Route<string> }) => void;
|
|
70
|
-
getPreviousRoute: (props: {
|
|
71
|
-
route: Route<string>;
|
|
72
|
-
}) => Route<string> | undefined;
|
|
73
|
-
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
|
74
|
-
isParentHeaderShown: boolean;
|
|
75
|
-
isParentModal: boolean;
|
|
76
|
-
onTransitionStart: (
|
|
77
|
-
props: { route: Route<string> },
|
|
78
|
-
closing: boolean
|
|
79
|
-
) => void;
|
|
80
|
-
onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
|
|
81
|
-
onGestureStart: (props: { route: Route<string> }) => void;
|
|
82
|
-
onGestureEnd: (props: { route: Route<string> }) => void;
|
|
83
|
-
onGestureCancel: (props: { route: Route<string> }) => void;
|
|
84
|
-
detachInactiveScreens?: boolean;
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
type State = {
|
|
88
|
-
routes: Route<string>[];
|
|
89
|
-
descriptors: StackDescriptorMap;
|
|
90
|
-
scenes: Scene[];
|
|
91
|
-
gestures: GestureValues;
|
|
92
|
-
layout: Layout;
|
|
93
|
-
activeStates: (0 | 1 | Animated.AnimatedInterpolation<0 | 1>)[];
|
|
94
|
-
headerHeights: Record<string, number>;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const NAMED_TRANSITIONS_PRESETS = {
|
|
98
|
-
default: DefaultTransition,
|
|
99
|
-
fade: ModalFadeTransition,
|
|
100
|
-
fade_from_bottom: FadeFromBottomAndroid,
|
|
101
|
-
fade_from_right: FadeFromRightAndroid,
|
|
102
|
-
none: DefaultTransition,
|
|
103
|
-
reveal_from_bottom: RevealFromBottomAndroid,
|
|
104
|
-
scale_from_center: ScaleFromCenterAndroid,
|
|
105
|
-
slide_from_left: SlideFromLeftIOS,
|
|
106
|
-
slide_from_right: SlideFromRightIOS,
|
|
107
|
-
slide_from_bottom: Platform.select({
|
|
108
|
-
ios: ModalSlideFromBottomIOS,
|
|
109
|
-
default: BottomSheetAndroid,
|
|
110
|
-
}),
|
|
111
|
-
} as const satisfies Record<StackAnimationName, TransitionPreset>;
|
|
112
|
-
|
|
113
|
-
const EPSILON = 1e-5;
|
|
114
|
-
|
|
115
|
-
const STATE_INACTIVE = 0;
|
|
116
|
-
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
|
117
|
-
const STATE_ON_TOP = 2;
|
|
118
|
-
|
|
119
|
-
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
|
120
|
-
|
|
121
|
-
const getInterpolationIndex = (scenes: Scene[], index: number) => {
|
|
122
|
-
const { cardStyleInterpolator } = scenes[index].descriptor.options;
|
|
123
|
-
|
|
124
|
-
// Start from current card and count backwards the number of cards with same interpolation
|
|
125
|
-
let interpolationIndex = 0;
|
|
126
|
-
|
|
127
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
128
|
-
const cardStyleInterpolatorCurrent =
|
|
129
|
-
scenes[i]?.descriptor.options.cardStyleInterpolator;
|
|
130
|
-
|
|
131
|
-
if (cardStyleInterpolatorCurrent !== cardStyleInterpolator) {
|
|
132
|
-
break;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
interpolationIndex++;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return interpolationIndex;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
const getIsModalPresentation = (
|
|
142
|
-
cardStyleInterpolator: StackCardStyleInterpolator
|
|
143
|
-
) => {
|
|
144
|
-
return (
|
|
145
|
-
cardStyleInterpolator === forModalPresentationIOS ||
|
|
146
|
-
// Handle custom modal presentation interpolators as well
|
|
147
|
-
cardStyleInterpolator.name === 'forModalPresentationIOS'
|
|
148
|
-
);
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const getIsModal = (
|
|
152
|
-
scene: Scene,
|
|
153
|
-
interpolationIndex: number,
|
|
154
|
-
isParentModal: boolean
|
|
155
|
-
) => {
|
|
156
|
-
if (isParentModal) {
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const { cardStyleInterpolator } = scene.descriptor.options;
|
|
161
|
-
const isModalPresentation = getIsModalPresentation(cardStyleInterpolator);
|
|
162
|
-
const isModal = isModalPresentation && interpolationIndex !== 0;
|
|
163
|
-
|
|
164
|
-
return isModal;
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
const getHeaderHeights = (
|
|
168
|
-
scenes: Scene[],
|
|
169
|
-
insets: EdgeInsets,
|
|
170
|
-
isParentHeaderShown: boolean,
|
|
171
|
-
isParentModal: boolean,
|
|
172
|
-
layout: Layout,
|
|
173
|
-
previous: Record<string, number>
|
|
174
|
-
) => {
|
|
175
|
-
return scenes.reduce<Record<string, number>>((acc, curr, index) => {
|
|
176
|
-
const {
|
|
177
|
-
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
|
178
|
-
headerStyle,
|
|
179
|
-
} = curr.descriptor.options;
|
|
180
|
-
|
|
181
|
-
const style = StyleSheet.flatten(headerStyle || {});
|
|
182
|
-
|
|
183
|
-
const height =
|
|
184
|
-
'height' in style && typeof style.height === 'number'
|
|
185
|
-
? style.height
|
|
186
|
-
: previous[curr.route.key];
|
|
187
|
-
|
|
188
|
-
const interpolationIndex = getInterpolationIndex(scenes, index);
|
|
189
|
-
const isModal = getIsModal(curr, interpolationIndex, isParentModal);
|
|
190
|
-
|
|
191
|
-
acc[curr.route.key] =
|
|
192
|
-
typeof height === 'number'
|
|
193
|
-
? height
|
|
194
|
-
: getDefaultHeaderHeight(layout, isModal, headerStatusBarHeight);
|
|
195
|
-
|
|
196
|
-
return acc;
|
|
197
|
-
}, {});
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const getDistanceFromOptions = (
|
|
201
|
-
layout: Layout,
|
|
202
|
-
options: StackNavigationOptions | undefined,
|
|
203
|
-
isRTL: boolean
|
|
204
|
-
) => {
|
|
205
|
-
if (options?.gestureDirection) {
|
|
206
|
-
return getDistanceForDirection(layout, options.gestureDirection, isRTL);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const defaultGestureDirection =
|
|
210
|
-
options?.presentation === 'modal'
|
|
211
|
-
? ModalTransition.gestureDirection
|
|
212
|
-
: DefaultTransition.gestureDirection;
|
|
213
|
-
|
|
214
|
-
const gestureDirection = options?.animation
|
|
215
|
-
? NAMED_TRANSITIONS_PRESETS[options?.animation]?.gestureDirection
|
|
216
|
-
: defaultGestureDirection;
|
|
217
|
-
|
|
218
|
-
return getDistanceForDirection(layout, gestureDirection, isRTL);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const getProgressFromGesture = (
|
|
222
|
-
gesture: Animated.Value,
|
|
223
|
-
layout: Layout,
|
|
224
|
-
options: StackNavigationOptions | undefined,
|
|
225
|
-
isRTL: boolean
|
|
226
|
-
) => {
|
|
227
|
-
const distance = getDistanceFromOptions(
|
|
228
|
-
{
|
|
229
|
-
// Make sure that we have a non-zero distance, otherwise there will be incorrect progress
|
|
230
|
-
// This causes blank screen on web if it was previously inside container with display: none
|
|
231
|
-
width: Math.max(1, layout.width),
|
|
232
|
-
height: Math.max(1, layout.height),
|
|
233
|
-
},
|
|
234
|
-
options,
|
|
235
|
-
isRTL
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
if (distance > 0) {
|
|
239
|
-
return gesture.interpolate({
|
|
240
|
-
inputRange: [0, distance],
|
|
241
|
-
outputRange: [1, 0],
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return gesture.interpolate({
|
|
246
|
-
inputRange: [distance, 0],
|
|
247
|
-
outputRange: [0, 1],
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
function getDefaultAnimation(animation: StackAnimationName | undefined) {
|
|
252
|
-
// Disable screen transition animation by default on web, windows and macos to match the native behavior
|
|
253
|
-
const excludedPlatforms =
|
|
254
|
-
Platform.OS !== 'web' &&
|
|
255
|
-
Platform.OS !== 'windows' &&
|
|
256
|
-
Platform.OS !== 'macos';
|
|
257
|
-
|
|
258
|
-
return animation ?? (excludedPlatforms ? 'default' : 'none');
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
export function getAnimationEnabled(animation: StackAnimationName | undefined) {
|
|
262
|
-
return getDefaultAnimation(animation) !== 'none';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
export class CardStack extends React.Component<Props, State> {
|
|
266
|
-
static getDerivedStateFromProps(
|
|
267
|
-
props: Props,
|
|
268
|
-
state: State
|
|
269
|
-
): Partial<State> | null {
|
|
270
|
-
if (
|
|
271
|
-
props.routes === state.routes &&
|
|
272
|
-
props.descriptors === state.descriptors
|
|
273
|
-
) {
|
|
274
|
-
return null;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const gestures = [
|
|
278
|
-
...props.routes,
|
|
279
|
-
...props.state.preloadedRoutes,
|
|
280
|
-
].reduce<GestureValues>((acc, curr) => {
|
|
281
|
-
const descriptor =
|
|
282
|
-
props.descriptors[curr.key] || props.preloadedDescriptors[curr.key];
|
|
283
|
-
const { animation } = descriptor?.options || {};
|
|
284
|
-
|
|
285
|
-
acc[curr.key] =
|
|
286
|
-
state.gestures[curr.key] ||
|
|
287
|
-
new Animated.Value(
|
|
288
|
-
(props.openingRouteKeys.includes(curr.key) &&
|
|
289
|
-
getAnimationEnabled(animation)) ||
|
|
290
|
-
props.state.preloadedRoutes.includes(curr)
|
|
291
|
-
? getDistanceFromOptions(
|
|
292
|
-
state.layout,
|
|
293
|
-
descriptor?.options,
|
|
294
|
-
props.direction === 'rtl'
|
|
295
|
-
)
|
|
296
|
-
: 0
|
|
297
|
-
);
|
|
298
|
-
|
|
299
|
-
return acc;
|
|
300
|
-
}, {});
|
|
301
|
-
|
|
302
|
-
const modalRouteKeys = getModalRouteKeys(
|
|
303
|
-
[...props.routes, ...props.state.preloadedRoutes],
|
|
304
|
-
{
|
|
305
|
-
...props.descriptors,
|
|
306
|
-
...props.preloadedDescriptors,
|
|
307
|
-
}
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
const scenes = [...props.routes, ...props.state.preloadedRoutes].map(
|
|
311
|
-
(route, index, self) => {
|
|
312
|
-
// For preloaded screens, we don't care about the previous and the next screen
|
|
313
|
-
const isPreloaded = props.state.preloadedRoutes.includes(route);
|
|
314
|
-
const previousRoute = isPreloaded ? undefined : self[index - 1];
|
|
315
|
-
const nextRoute = isPreloaded ? undefined : self[index + 1];
|
|
316
|
-
|
|
317
|
-
const oldScene = state.scenes[index];
|
|
318
|
-
|
|
319
|
-
const currentGesture = gestures[route.key];
|
|
320
|
-
const previousGesture = previousRoute
|
|
321
|
-
? gestures[previousRoute.key]
|
|
322
|
-
: undefined;
|
|
323
|
-
const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
|
|
324
|
-
|
|
325
|
-
const descriptor =
|
|
326
|
-
(isPreloaded ? props.preloadedDescriptors : props.descriptors)[
|
|
327
|
-
route.key
|
|
328
|
-
] ||
|
|
329
|
-
state.descriptors[route.key] ||
|
|
330
|
-
(oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
|
|
331
|
-
|
|
332
|
-
const nextOptions =
|
|
333
|
-
nextRoute &&
|
|
334
|
-
(
|
|
335
|
-
props.descriptors[nextRoute?.key] ||
|
|
336
|
-
state.descriptors[nextRoute?.key]
|
|
337
|
-
)?.options;
|
|
338
|
-
|
|
339
|
-
const previousOptions =
|
|
340
|
-
previousRoute &&
|
|
341
|
-
(
|
|
342
|
-
props.descriptors[previousRoute?.key] ||
|
|
343
|
-
state.descriptors[previousRoute?.key]
|
|
344
|
-
)?.options;
|
|
345
|
-
|
|
346
|
-
// When a screen is not the last, it should use next screen's transition config
|
|
347
|
-
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
|
348
|
-
// For example combining a slide and a modal transition would look wrong otherwise
|
|
349
|
-
// With this approach, combining different transition styles in the same navigator mostly looks right
|
|
350
|
-
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
|
351
|
-
// but the majority of the transitions look alright
|
|
352
|
-
const optionsForTransitionConfig =
|
|
353
|
-
index !== self.length - 1 &&
|
|
354
|
-
nextOptions &&
|
|
355
|
-
nextOptions?.presentation !== 'transparentModal'
|
|
356
|
-
? nextOptions
|
|
357
|
-
: descriptor.options;
|
|
358
|
-
|
|
359
|
-
// Assume modal if there are already modal screens in the stack
|
|
360
|
-
// or current screen is a modal when no presentation is specified
|
|
361
|
-
const isModal = modalRouteKeys.includes(route.key);
|
|
362
|
-
|
|
363
|
-
const animation = getDefaultAnimation(
|
|
364
|
-
optionsForTransitionConfig.animation
|
|
365
|
-
);
|
|
366
|
-
|
|
367
|
-
const isAnimationEnabled = getAnimationEnabled(animation);
|
|
368
|
-
|
|
369
|
-
const transitionPreset =
|
|
370
|
-
animation !== 'default'
|
|
371
|
-
? NAMED_TRANSITIONS_PRESETS[animation]
|
|
372
|
-
: optionsForTransitionConfig.presentation === 'transparentModal'
|
|
373
|
-
? ModalFadeTransition
|
|
374
|
-
: optionsForTransitionConfig.presentation === 'modal' || isModal
|
|
375
|
-
? ModalTransition
|
|
376
|
-
: DefaultTransition;
|
|
377
|
-
|
|
378
|
-
const {
|
|
379
|
-
gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
|
|
380
|
-
gestureDirection = transitionPreset.gestureDirection,
|
|
381
|
-
transitionSpec = transitionPreset.transitionSpec,
|
|
382
|
-
cardStyleInterpolator = isAnimationEnabled
|
|
383
|
-
? transitionPreset.cardStyleInterpolator
|
|
384
|
-
: forNoAnimationCard,
|
|
385
|
-
headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
|
|
386
|
-
cardOverlayEnabled = (Platform.OS !== 'ios' &&
|
|
387
|
-
optionsForTransitionConfig.presentation !== 'transparentModal') ||
|
|
388
|
-
getIsModalPresentation(cardStyleInterpolator),
|
|
389
|
-
} = optionsForTransitionConfig;
|
|
390
|
-
|
|
391
|
-
const headerMode: StackHeaderMode =
|
|
392
|
-
descriptor.options.headerMode ??
|
|
393
|
-
(!(
|
|
394
|
-
optionsForTransitionConfig.presentation === 'modal' ||
|
|
395
|
-
optionsForTransitionConfig.presentation === 'transparentModal' ||
|
|
396
|
-
nextOptions?.presentation === 'modal' ||
|
|
397
|
-
nextOptions?.presentation === 'transparentModal' ||
|
|
398
|
-
getIsModalPresentation(cardStyleInterpolator)
|
|
399
|
-
) &&
|
|
400
|
-
Platform.OS === 'ios' &&
|
|
401
|
-
descriptor.options.header === undefined
|
|
402
|
-
? 'float'
|
|
403
|
-
: 'screen');
|
|
404
|
-
|
|
405
|
-
const isRTL = props.direction === 'rtl';
|
|
406
|
-
|
|
407
|
-
const scene = {
|
|
408
|
-
route,
|
|
409
|
-
descriptor: {
|
|
410
|
-
...descriptor,
|
|
411
|
-
options: {
|
|
412
|
-
...descriptor.options,
|
|
413
|
-
animation,
|
|
414
|
-
cardOverlayEnabled,
|
|
415
|
-
cardStyleInterpolator,
|
|
416
|
-
gestureDirection,
|
|
417
|
-
gestureEnabled,
|
|
418
|
-
headerStyleInterpolator,
|
|
419
|
-
transitionSpec,
|
|
420
|
-
headerMode,
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
progress: {
|
|
424
|
-
current: getProgressFromGesture(
|
|
425
|
-
currentGesture,
|
|
426
|
-
state.layout,
|
|
427
|
-
descriptor.options,
|
|
428
|
-
isRTL
|
|
429
|
-
),
|
|
430
|
-
next:
|
|
431
|
-
nextGesture && nextOptions?.presentation !== 'transparentModal'
|
|
432
|
-
? getProgressFromGesture(
|
|
433
|
-
nextGesture,
|
|
434
|
-
state.layout,
|
|
435
|
-
nextOptions,
|
|
436
|
-
isRTL
|
|
437
|
-
)
|
|
438
|
-
: undefined,
|
|
439
|
-
previous: previousGesture
|
|
440
|
-
? getProgressFromGesture(
|
|
441
|
-
previousGesture,
|
|
442
|
-
state.layout,
|
|
443
|
-
previousOptions,
|
|
444
|
-
isRTL
|
|
445
|
-
)
|
|
446
|
-
: undefined,
|
|
447
|
-
},
|
|
448
|
-
__memo: [
|
|
449
|
-
state.layout,
|
|
450
|
-
descriptor,
|
|
451
|
-
nextOptions,
|
|
452
|
-
previousOptions,
|
|
453
|
-
currentGesture,
|
|
454
|
-
nextGesture,
|
|
455
|
-
previousGesture,
|
|
456
|
-
],
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
if (
|
|
460
|
-
oldScene &&
|
|
461
|
-
scene.__memo.every((it, i) => {
|
|
462
|
-
// @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
|
|
463
|
-
return oldScene.__memo[i] === it;
|
|
464
|
-
})
|
|
465
|
-
) {
|
|
466
|
-
return oldScene;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return scene;
|
|
470
|
-
}
|
|
471
|
-
);
|
|
472
|
-
|
|
473
|
-
let activeStates = state.activeStates;
|
|
474
|
-
|
|
475
|
-
if (props.routes.length !== state.routes.length) {
|
|
476
|
-
let activeScreensLimit = 1;
|
|
477
|
-
|
|
478
|
-
for (let i = props.routes.length - 1; i >= 0; i--) {
|
|
479
|
-
const { options } = scenes[i].descriptor;
|
|
480
|
-
|
|
481
|
-
const {
|
|
482
|
-
// By default, we don't want to detach the previous screen of the active one for modals
|
|
483
|
-
detachPreviousScreen = options.presentation === 'transparentModal'
|
|
484
|
-
? false
|
|
485
|
-
: getIsModalPresentation(options.cardStyleInterpolator)
|
|
486
|
-
? i !==
|
|
487
|
-
findLastIndex(scenes, (scene) => {
|
|
488
|
-
const { cardStyleInterpolator } = scene.descriptor.options;
|
|
489
|
-
|
|
490
|
-
return (
|
|
491
|
-
cardStyleInterpolator === forModalPresentationIOS ||
|
|
492
|
-
cardStyleInterpolator?.name === 'forModalPresentationIOS'
|
|
493
|
-
);
|
|
494
|
-
})
|
|
495
|
-
: true,
|
|
496
|
-
} = options;
|
|
497
|
-
|
|
498
|
-
if (detachPreviousScreen === false) {
|
|
499
|
-
activeScreensLimit++;
|
|
500
|
-
} else {
|
|
501
|
-
// Check at least last 2 screens before stopping
|
|
502
|
-
// This will make sure that screen isn't detached when another screen is animating on top of the transparent one
|
|
503
|
-
// e.g. opaque -> transparent -> opaque
|
|
504
|
-
if (i <= props.routes.length - 2) {
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
activeStates = props.routes.map((_, index, self) => {
|
|
511
|
-
// The activity state represents state of the screen:
|
|
512
|
-
// 0 - inactive, the screen is detached
|
|
513
|
-
// 1 - transitioning or below the top screen, the screen is mounted but interaction is disabled
|
|
514
|
-
// 2 - on top of the stack, the screen is mounted and interaction is enabled
|
|
515
|
-
let activityState:
|
|
516
|
-
| Animated.AnimatedInterpolation<0 | 1 | 2>
|
|
517
|
-
| 0
|
|
518
|
-
| 1
|
|
519
|
-
| 2;
|
|
520
|
-
|
|
521
|
-
const lastActiveState = state.activeStates[index];
|
|
522
|
-
const activeAfterTransition = index >= self.length - activeScreensLimit;
|
|
523
|
-
|
|
524
|
-
if (lastActiveState === STATE_INACTIVE && !activeAfterTransition) {
|
|
525
|
-
// screen was inactive before and it will still be inactive after the transition
|
|
526
|
-
activityState = STATE_INACTIVE;
|
|
527
|
-
} else {
|
|
528
|
-
const sceneForActivity = scenes[self.length - 1];
|
|
529
|
-
const outputValue =
|
|
530
|
-
index === self.length - 1
|
|
531
|
-
? STATE_ON_TOP // the screen is on top after the transition
|
|
532
|
-
: activeAfterTransition
|
|
533
|
-
? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
|
|
534
|
-
: STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
|
|
535
|
-
|
|
536
|
-
activityState = sceneForActivity
|
|
537
|
-
? sceneForActivity.progress.current.interpolate({
|
|
538
|
-
inputRange: [0, 1 - EPSILON, 1],
|
|
539
|
-
outputRange: [1, 1, outputValue],
|
|
540
|
-
extrapolate: 'clamp',
|
|
541
|
-
})
|
|
542
|
-
: STATE_TRANSITIONING_OR_BELOW_TOP;
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
return activityState;
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
return {
|
|
550
|
-
routes: props.routes,
|
|
551
|
-
scenes,
|
|
552
|
-
gestures,
|
|
553
|
-
descriptors: props.descriptors,
|
|
554
|
-
activeStates,
|
|
555
|
-
headerHeights: getHeaderHeights(
|
|
556
|
-
scenes,
|
|
557
|
-
props.insets,
|
|
558
|
-
props.isParentHeaderShown,
|
|
559
|
-
props.isParentModal,
|
|
560
|
-
state.layout,
|
|
561
|
-
state.headerHeights
|
|
562
|
-
),
|
|
563
|
-
};
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
constructor(props: Props) {
|
|
567
|
-
super(props);
|
|
568
|
-
|
|
569
|
-
this.state = {
|
|
570
|
-
routes: [],
|
|
571
|
-
scenes: [],
|
|
572
|
-
gestures: {},
|
|
573
|
-
layout: SafeAreaProviderCompat.initialMetrics.frame,
|
|
574
|
-
descriptors: this.props.descriptors,
|
|
575
|
-
activeStates: [],
|
|
576
|
-
// Used when card's header is null and mode is float to make transition
|
|
577
|
-
// between screens with headers and those without headers smooth.
|
|
578
|
-
// This is not a great heuristic here. We don't know synchronously
|
|
579
|
-
// on mount what the header height is so we have just used the most
|
|
580
|
-
// common cases here.
|
|
581
|
-
headerHeights: {},
|
|
582
|
-
};
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
private handleLayout = (e: LayoutChangeEvent) => {
|
|
586
|
-
const { height, width } = e.nativeEvent.layout;
|
|
587
|
-
|
|
588
|
-
const layout = { width, height };
|
|
589
|
-
|
|
590
|
-
this.setState((state, props) => {
|
|
591
|
-
if (height === state.layout.height && width === state.layout.width) {
|
|
592
|
-
return null;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return {
|
|
596
|
-
layout,
|
|
597
|
-
headerHeights: getHeaderHeights(
|
|
598
|
-
state.scenes,
|
|
599
|
-
props.insets,
|
|
600
|
-
props.isParentHeaderShown,
|
|
601
|
-
props.isParentModal,
|
|
602
|
-
layout,
|
|
603
|
-
state.headerHeights
|
|
604
|
-
),
|
|
605
|
-
};
|
|
606
|
-
});
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
private handleHeaderLayout = ({
|
|
610
|
-
route,
|
|
611
|
-
height,
|
|
612
|
-
}: {
|
|
613
|
-
route: Route<string>;
|
|
614
|
-
height: number;
|
|
615
|
-
}) => {
|
|
616
|
-
this.setState(({ headerHeights }) => {
|
|
617
|
-
const previousHeight = headerHeights[route.key];
|
|
618
|
-
|
|
619
|
-
if (previousHeight === height) {
|
|
620
|
-
return null;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return {
|
|
624
|
-
headerHeights: {
|
|
625
|
-
...headerHeights,
|
|
626
|
-
[route.key]: height,
|
|
627
|
-
},
|
|
628
|
-
};
|
|
629
|
-
});
|
|
630
|
-
};
|
|
631
|
-
|
|
632
|
-
private getFocusedRoute = () => {
|
|
633
|
-
const { state } = this.props;
|
|
634
|
-
|
|
635
|
-
return state.routes[state.index];
|
|
636
|
-
};
|
|
637
|
-
|
|
638
|
-
private getPreviousScene = ({ route }: { route: Route<string> }) => {
|
|
639
|
-
const { getPreviousRoute } = this.props;
|
|
640
|
-
const { scenes } = this.state;
|
|
641
|
-
|
|
642
|
-
const previousRoute = getPreviousRoute({ route });
|
|
643
|
-
|
|
644
|
-
if (previousRoute) {
|
|
645
|
-
const previousScene = scenes.find(
|
|
646
|
-
(scene) => scene.descriptor.route.key === previousRoute.key
|
|
647
|
-
);
|
|
648
|
-
|
|
649
|
-
return previousScene;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
return undefined;
|
|
653
|
-
};
|
|
654
|
-
|
|
655
|
-
render() {
|
|
656
|
-
const {
|
|
657
|
-
insets,
|
|
658
|
-
state,
|
|
659
|
-
routes,
|
|
660
|
-
openingRouteKeys,
|
|
661
|
-
closingRouteKeys,
|
|
662
|
-
onOpenRoute,
|
|
663
|
-
onCloseRoute,
|
|
664
|
-
renderHeader,
|
|
665
|
-
isParentHeaderShown,
|
|
666
|
-
isParentModal,
|
|
667
|
-
onTransitionStart,
|
|
668
|
-
onTransitionEnd,
|
|
669
|
-
onGestureStart,
|
|
670
|
-
onGestureEnd,
|
|
671
|
-
onGestureCancel,
|
|
672
|
-
detachInactiveScreens = Platform.OS === 'web' ||
|
|
673
|
-
Platform.OS === 'android' ||
|
|
674
|
-
Platform.OS === 'ios' ||
|
|
675
|
-
// @ts-ignore - accept kepler as a platform
|
|
676
|
-
Platform.OS === 'kepler',
|
|
677
|
-
} = this.props;
|
|
678
|
-
|
|
679
|
-
const { scenes, layout, gestures, activeStates, headerHeights } =
|
|
680
|
-
this.state;
|
|
681
|
-
|
|
682
|
-
const focusedRoute = state.routes[state.index];
|
|
683
|
-
const focusedHeaderHeight = headerHeights[focusedRoute.key];
|
|
684
|
-
|
|
685
|
-
const isFloatHeaderAbsolute = this.state.scenes.slice(-2).some((scene) => {
|
|
686
|
-
const options = scene.descriptor.options ?? {};
|
|
687
|
-
const { headerMode, headerTransparent, headerShown = true } = options;
|
|
688
|
-
|
|
689
|
-
if (
|
|
690
|
-
headerTransparent ||
|
|
691
|
-
headerShown === false ||
|
|
692
|
-
headerMode === 'screen'
|
|
693
|
-
) {
|
|
694
|
-
return true;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
return false;
|
|
698
|
-
});
|
|
699
|
-
|
|
700
|
-
return (
|
|
701
|
-
<View style={styles.container}>
|
|
702
|
-
{renderHeader({
|
|
703
|
-
mode: 'float',
|
|
704
|
-
layout,
|
|
705
|
-
scenes,
|
|
706
|
-
getPreviousScene: this.getPreviousScene,
|
|
707
|
-
getFocusedRoute: this.getFocusedRoute,
|
|
708
|
-
onContentHeightChange: this.handleHeaderLayout,
|
|
709
|
-
style: [
|
|
710
|
-
styles.floating,
|
|
711
|
-
isFloatHeaderAbsolute && [
|
|
712
|
-
// Without this, the header buttons won't be touchable on Android when headerTransparent: true
|
|
713
|
-
{ height: focusedHeaderHeight },
|
|
714
|
-
styles.absolute,
|
|
715
|
-
],
|
|
716
|
-
],
|
|
717
|
-
})}
|
|
718
|
-
<MaybeScreenContainer
|
|
719
|
-
enabled={detachInactiveScreens}
|
|
720
|
-
style={styles.container}
|
|
721
|
-
onLayout={this.handleLayout}
|
|
722
|
-
>
|
|
723
|
-
{[...routes, ...state.preloadedRoutes].map((route, index) => {
|
|
724
|
-
const focused = focusedRoute.key === route.key;
|
|
725
|
-
const gesture = gestures[route.key];
|
|
726
|
-
const scene = scenes[index];
|
|
727
|
-
// It is possible that for a short period the route appears in both arrays.
|
|
728
|
-
// Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
|
|
729
|
-
// However, due to the router action, it immediately populates the `preloadedRoutes` array.
|
|
730
|
-
// Practically, the logic below takes care that it is rendered only once.
|
|
731
|
-
const isPreloaded =
|
|
732
|
-
state.preloadedRoutes.includes(route) && !routes.includes(route);
|
|
733
|
-
if (
|
|
734
|
-
state.preloadedRoutes.includes(route) &&
|
|
735
|
-
routes.includes(route) &&
|
|
736
|
-
index >= routes.length
|
|
737
|
-
) {
|
|
738
|
-
return null;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
const {
|
|
742
|
-
headerShown = true,
|
|
743
|
-
headerTransparent,
|
|
744
|
-
freezeOnBlur,
|
|
745
|
-
autoHideHomeIndicator,
|
|
746
|
-
} = scene.descriptor.options;
|
|
747
|
-
|
|
748
|
-
const safeAreaInsetTop = insets.top;
|
|
749
|
-
const safeAreaInsetRight = insets.right;
|
|
750
|
-
const safeAreaInsetBottom = insets.bottom;
|
|
751
|
-
const safeAreaInsetLeft = insets.left;
|
|
752
|
-
|
|
753
|
-
const headerHeight =
|
|
754
|
-
headerShown !== false ? headerHeights[route.key] : 0;
|
|
755
|
-
|
|
756
|
-
// Start from current card and count backwards the number of cards with same interpolation
|
|
757
|
-
const interpolationIndex = getInterpolationIndex(scenes, index);
|
|
758
|
-
const isModal = getIsModal(
|
|
759
|
-
scene,
|
|
760
|
-
interpolationIndex,
|
|
761
|
-
isParentModal
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
const isNextScreenTransparent =
|
|
765
|
-
scenes[index + 1]?.descriptor.options.presentation ===
|
|
766
|
-
'transparentModal';
|
|
767
|
-
|
|
768
|
-
const detachCurrentScreen =
|
|
769
|
-
scenes[index + 1]?.descriptor.options.detachPreviousScreen !==
|
|
770
|
-
false;
|
|
771
|
-
|
|
772
|
-
const activityState = isPreloaded
|
|
773
|
-
? STATE_INACTIVE
|
|
774
|
-
: activeStates[index];
|
|
775
|
-
|
|
776
|
-
return (
|
|
777
|
-
<MaybeScreen
|
|
778
|
-
key={route.key}
|
|
779
|
-
style={[StyleSheet.absoluteFill]}
|
|
780
|
-
enabled={detachInactiveScreens}
|
|
781
|
-
active={activityState}
|
|
782
|
-
freezeOnBlur={freezeOnBlur}
|
|
783
|
-
shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
|
|
784
|
-
homeIndicatorHidden={autoHideHomeIndicator}
|
|
785
|
-
pointerEvents="box-none"
|
|
786
|
-
>
|
|
787
|
-
<CardContainer
|
|
788
|
-
index={index}
|
|
789
|
-
interpolationIndex={interpolationIndex}
|
|
790
|
-
modal={isModal}
|
|
791
|
-
active={index === routes.length - 1}
|
|
792
|
-
focused={focused}
|
|
793
|
-
opening={openingRouteKeys.includes(route.key)}
|
|
794
|
-
closing={closingRouteKeys.includes(route.key)}
|
|
795
|
-
layout={layout}
|
|
796
|
-
gesture={gesture}
|
|
797
|
-
scene={scene}
|
|
798
|
-
safeAreaInsetTop={safeAreaInsetTop}
|
|
799
|
-
safeAreaInsetRight={safeAreaInsetRight}
|
|
800
|
-
safeAreaInsetBottom={safeAreaInsetBottom}
|
|
801
|
-
safeAreaInsetLeft={safeAreaInsetLeft}
|
|
802
|
-
onGestureStart={onGestureStart}
|
|
803
|
-
onGestureCancel={onGestureCancel}
|
|
804
|
-
onGestureEnd={onGestureEnd}
|
|
805
|
-
headerHeight={headerHeight}
|
|
806
|
-
isParentHeaderShown={isParentHeaderShown}
|
|
807
|
-
onHeaderHeightChange={this.handleHeaderLayout}
|
|
808
|
-
getPreviousScene={this.getPreviousScene}
|
|
809
|
-
getFocusedRoute={this.getFocusedRoute}
|
|
810
|
-
hasAbsoluteFloatHeader={
|
|
811
|
-
isFloatHeaderAbsolute && !headerTransparent
|
|
812
|
-
}
|
|
813
|
-
renderHeader={renderHeader}
|
|
814
|
-
onOpenRoute={onOpenRoute}
|
|
815
|
-
onCloseRoute={onCloseRoute}
|
|
816
|
-
onTransitionStart={onTransitionStart}
|
|
817
|
-
onTransitionEnd={onTransitionEnd}
|
|
818
|
-
isNextScreenTransparent={isNextScreenTransparent}
|
|
819
|
-
detachCurrentScreen={detachCurrentScreen}
|
|
820
|
-
preloaded={isPreloaded}
|
|
821
|
-
/>
|
|
822
|
-
</MaybeScreen>
|
|
823
|
-
);
|
|
824
|
-
})}
|
|
825
|
-
</MaybeScreenContainer>
|
|
826
|
-
</View>
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
const styles = StyleSheet.create({
|
|
832
|
-
container: {
|
|
833
|
-
flex: 1,
|
|
834
|
-
},
|
|
835
|
-
absolute: {
|
|
836
|
-
position: 'absolute',
|
|
837
|
-
top: 0,
|
|
838
|
-
start: 0,
|
|
839
|
-
end: 0,
|
|
840
|
-
},
|
|
841
|
-
floating: {
|
|
842
|
-
zIndex: 1,
|
|
843
|
-
},
|
|
844
|
-
});
|