@react-navigation/stack 8.0.0-alpha.24 → 8.0.0-alpha.25
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/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/views/Stack/CardContainer.js +5 -1
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +36 -17
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +3 -2
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createStackNavigator.d.ts +8 -14
- package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +0 -6
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/index.tsx +1 -0
- package/src/navigators/createStackNavigator.tsx +9 -40
- package/src/types.tsx +0 -6
- package/src/views/Stack/CardContainer.tsx +10 -1
- package/src/views/Stack/CardStack.tsx +209 -181
- package/src/views/Stack/StackView.tsx +5 -4
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-navigation/stack",
|
|
3
3
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
|
4
|
-
"version": "8.0.0-alpha.
|
|
4
|
+
"version": "8.0.0-alpha.25",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native-component",
|
|
7
7
|
"react-component",
|
|
@@ -45,13 +45,13 @@
|
|
|
45
45
|
"clean": "del lib"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@react-navigation/elements": "^3.0.0-alpha.
|
|
48
|
+
"@react-navigation/elements": "^3.0.0-alpha.23",
|
|
49
49
|
"color": "^5.0.3",
|
|
50
50
|
"use-latest-callback": "^0.3.3"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@jest/globals": "^30.3.0",
|
|
54
|
-
"@react-navigation/native": "^8.0.0-alpha.
|
|
54
|
+
"@react-navigation/native": "^8.0.0-alpha.20",
|
|
55
55
|
"@testing-library/react-native": "^13.3.3",
|
|
56
56
|
"@types/color": "^4.2.0",
|
|
57
57
|
"@types/react": "~19.2.2",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
"typescript": "^6.0.2"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"@react-navigation/native": "^8.0.0-alpha.
|
|
69
|
+
"@react-navigation/native": "^8.0.0-alpha.20",
|
|
70
70
|
"react": ">= 19.2.0",
|
|
71
71
|
"react-native": "*",
|
|
72
72
|
"react-native-gesture-handler": ">= 2.0.0",
|
|
@@ -91,5 +91,5 @@
|
|
|
91
91
|
]
|
|
92
92
|
]
|
|
93
93
|
},
|
|
94
|
-
"gitHead": "
|
|
94
|
+
"gitHead": "d465f4e6b2996e5ed4648b23487e2027e83b263d"
|
|
95
95
|
}
|
package/src/index.tsx
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createNavigatorFactory,
|
|
3
|
+
createScreenFactory,
|
|
3
4
|
type EventArg,
|
|
5
|
+
type NavigatorTypeBagBase,
|
|
4
6
|
type ParamListBase,
|
|
5
7
|
type StackActionHelpers,
|
|
6
8
|
StackActions,
|
|
7
9
|
type StackNavigationState,
|
|
8
10
|
StackRouter,
|
|
9
11
|
type StackRouterOptions,
|
|
10
|
-
type StaticConfig,
|
|
11
|
-
type StaticParamList,
|
|
12
|
-
type StaticScreenConfig,
|
|
13
|
-
type StaticScreenConfigLinking,
|
|
14
|
-
type StaticScreenConfigScreen,
|
|
15
|
-
type TypedNavigator,
|
|
16
12
|
useLocale,
|
|
17
13
|
useNavigationBuilder,
|
|
18
14
|
} from '@react-navigation/native';
|
|
@@ -21,7 +17,6 @@ import * as React from 'react';
|
|
|
21
17
|
import type {
|
|
22
18
|
StackNavigationEventMap,
|
|
23
19
|
StackNavigationOptions,
|
|
24
|
-
StackNavigationProp,
|
|
25
20
|
StackNavigatorProps,
|
|
26
21
|
} from '../types';
|
|
27
22
|
import { StackView } from '../views/Stack/StackView';
|
|
@@ -96,41 +91,15 @@ function StackNavigator({
|
|
|
96
91
|
);
|
|
97
92
|
}
|
|
98
93
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
State: StackNavigationState<ParamList>;
|
|
94
|
+
export interface StackTypeBag extends NavigatorTypeBagBase {
|
|
95
|
+
State: StackNavigationState<this['ParamList']>;
|
|
102
96
|
ScreenOptions: StackNavigationOptions;
|
|
103
97
|
EventMap: StackNavigationEventMap;
|
|
104
|
-
|
|
105
|
-
[RouteName in keyof ParamList]: StackNavigationProp<ParamList, RouteName>;
|
|
106
|
-
};
|
|
98
|
+
ActionHelpers: StackActionHelpers<this['ParamList']>;
|
|
107
99
|
Navigator: typeof StackNavigator;
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
export function createStackNavigator<
|
|
111
|
-
const ParamList extends ParamListBase,
|
|
112
|
-
>(): TypedNavigator<StackTypeBag<ParamList>, undefined>;
|
|
113
|
-
export function createStackNavigator<
|
|
114
|
-
const Config extends StaticConfig<StackTypeBag<ParamListBase>>,
|
|
115
|
-
>(
|
|
116
|
-
config: Config
|
|
117
|
-
): TypedNavigator<StackTypeBag<StaticParamList<{ config: Config }>>, Config>;
|
|
118
|
-
export function createStackNavigator(config?: unknown) {
|
|
119
|
-
return createNavigatorFactory(StackNavigator)(config);
|
|
120
100
|
}
|
|
121
101
|
|
|
122
|
-
export
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
>(
|
|
126
|
-
config: StaticScreenConfig<
|
|
127
|
-
Linking,
|
|
128
|
-
Screen,
|
|
129
|
-
StackNavigationState<ParamListBase>,
|
|
130
|
-
StackNavigationOptions,
|
|
131
|
-
StackNavigationEventMap,
|
|
132
|
-
StackNavigationProp<ParamListBase>
|
|
133
|
-
>
|
|
134
|
-
) {
|
|
135
|
-
return config;
|
|
136
|
-
}
|
|
102
|
+
export const createStackNavigator =
|
|
103
|
+
createNavigatorFactory<StackTypeBag>(StackNavigator);
|
|
104
|
+
|
|
105
|
+
export const createStackScreen = createScreenFactory<StackTypeBag>();
|
package/src/types.tsx
CHANGED
|
@@ -346,12 +346,6 @@ export type StackNavigationOptions = StackHeaderOptions &
|
|
|
346
346
|
/**
|
|
347
347
|
* Style object for the card in stack.
|
|
348
348
|
* You can provide a custom background color to use instead of the default background here.
|
|
349
|
-
*
|
|
350
|
-
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
|
|
351
|
-
* This is useful to implement things like modal dialogs.
|
|
352
|
-
*
|
|
353
|
-
* You might also need to change the animation of the screen using `cardStyleInterpolator`
|
|
354
|
-
* so that the previous screen isn't transformed or invisible.
|
|
355
349
|
*/
|
|
356
350
|
cardStyle?: StyleProp<ViewStyle>;
|
|
357
351
|
/**
|
|
@@ -194,6 +194,7 @@ function CardContainerInner({
|
|
|
194
194
|
gestureVelocityImpact,
|
|
195
195
|
headerMode,
|
|
196
196
|
headerShown,
|
|
197
|
+
headerTransparent,
|
|
197
198
|
transitionSpec,
|
|
198
199
|
} = scene.descriptor.options;
|
|
199
200
|
|
|
@@ -284,7 +285,15 @@ function CardContainerInner({
|
|
|
284
285
|
getPreviousScene,
|
|
285
286
|
getFocusedRoute,
|
|
286
287
|
onContentHeightChange: onHeaderHeightChange,
|
|
287
|
-
style:
|
|
288
|
+
style: [
|
|
289
|
+
styles.header,
|
|
290
|
+
headerTransparent
|
|
291
|
+
? {
|
|
292
|
+
// Specify an explicit min height for Android screen readers
|
|
293
|
+
minHeight: headerHeight,
|
|
294
|
+
}
|
|
295
|
+
: null,
|
|
296
|
+
],
|
|
288
297
|
})
|
|
289
298
|
: null}
|
|
290
299
|
<View style={styles.scene}>
|
|
@@ -258,6 +258,39 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
|
|
|
258
258
|
return getDefaultAnimation(animation) !== 'none';
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
+
const getAllRoutes = (
|
|
262
|
+
routes: Route<string>[],
|
|
263
|
+
preloadedRoutes: Route<string>[]
|
|
264
|
+
) => {
|
|
265
|
+
const routeKeys = new Set(routes.map((route) => route.key));
|
|
266
|
+
|
|
267
|
+
// If a route is moved from `state.routes` to `state.preloadedRoutes`,
|
|
268
|
+
// It can still be in the local copy of `routes` until the animation ends
|
|
269
|
+
// So we need to deduplicate the routes to avoid rendering the same route twice
|
|
270
|
+
return [
|
|
271
|
+
...routes,
|
|
272
|
+
...preloadedRoutes.filter((route) => !routeKeys.has(route.key)),
|
|
273
|
+
];
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const isPreloadedRoute = (
|
|
277
|
+
route: Route<string>,
|
|
278
|
+
props: {
|
|
279
|
+
routes: Route<string>[];
|
|
280
|
+
state: StackNavigationState<ParamListBase>;
|
|
281
|
+
}
|
|
282
|
+
) => {
|
|
283
|
+
// The route can be in both `routes` and `preloadedRoutes` until the animation ends
|
|
284
|
+
// Treat it as not preloaded, similar to how removed routes are treated until the animation ends
|
|
285
|
+
if (props.routes.some((currentRoute) => currentRoute.key === route.key)) {
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return props.state.preloadedRoutes.some(
|
|
290
|
+
(currentRoute) => currentRoute.key === route.key
|
|
291
|
+
);
|
|
292
|
+
};
|
|
293
|
+
|
|
261
294
|
export class CardStack extends React.Component<Props, State> {
|
|
262
295
|
static getDerivedStateFromProps(
|
|
263
296
|
props: Props,
|
|
@@ -270,10 +303,9 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
270
303
|
return null;
|
|
271
304
|
}
|
|
272
305
|
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
].reduce<GestureValues>((acc, curr) => {
|
|
306
|
+
const allRoutes = getAllRoutes(props.routes, props.state.preloadedRoutes);
|
|
307
|
+
|
|
308
|
+
const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
|
|
277
309
|
const descriptor = props.descriptors[curr.key];
|
|
278
310
|
const { animation } = descriptor?.options || {};
|
|
279
311
|
|
|
@@ -282,7 +314,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
282
314
|
new Animated.Value(
|
|
283
315
|
(props.openingRouteKeys.includes(curr.key) &&
|
|
284
316
|
getAnimationEnabled(animation)) ||
|
|
285
|
-
|
|
317
|
+
isPreloadedRoute(curr, props)
|
|
286
318
|
? getDistanceFromOptions(
|
|
287
319
|
state.layout,
|
|
288
320
|
descriptor?.options,
|
|
@@ -294,171 +326,164 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
294
326
|
return acc;
|
|
295
327
|
}, {});
|
|
296
328
|
|
|
297
|
-
const modalRouteKeys = getModalRouteKeys(
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
// or current screen is a modal when no presentation is specified
|
|
351
|
-
const isModal = modalRouteKeys.includes(route.key);
|
|
352
|
-
|
|
353
|
-
const animation = getDefaultAnimation(
|
|
354
|
-
optionsForTransitionConfig.animation
|
|
355
|
-
);
|
|
329
|
+
const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
|
|
330
|
+
|
|
331
|
+
const scenes = allRoutes.map((route, index, self) => {
|
|
332
|
+
// For preloaded screens, we don't care about the previous and the next screen
|
|
333
|
+
const isPreloaded = isPreloadedRoute(route, props);
|
|
334
|
+
const previousRoute = isPreloaded ? undefined : self[index - 1];
|
|
335
|
+
const nextRoute = isPreloaded ? undefined : self[index + 1];
|
|
336
|
+
|
|
337
|
+
const oldScene = state.scenes[index];
|
|
338
|
+
|
|
339
|
+
const currentGesture = gestures[route.key];
|
|
340
|
+
const previousGesture = previousRoute
|
|
341
|
+
? gestures[previousRoute.key]
|
|
342
|
+
: undefined;
|
|
343
|
+
const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
|
|
344
|
+
|
|
345
|
+
const descriptor =
|
|
346
|
+
props.descriptors[route.key] ||
|
|
347
|
+
state.descriptors[route.key] ||
|
|
348
|
+
(oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
|
|
349
|
+
|
|
350
|
+
const nextOptions =
|
|
351
|
+
nextRoute &&
|
|
352
|
+
(props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key])
|
|
353
|
+
?.options;
|
|
354
|
+
|
|
355
|
+
const previousOptions =
|
|
356
|
+
previousRoute &&
|
|
357
|
+
(
|
|
358
|
+
props.descriptors[previousRoute?.key] ||
|
|
359
|
+
state.descriptors[previousRoute?.key]
|
|
360
|
+
)?.options;
|
|
361
|
+
|
|
362
|
+
// When a screen is not the last, it should use next screen's transition config
|
|
363
|
+
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
|
364
|
+
// For example combining a slide and a modal transition would look wrong otherwise
|
|
365
|
+
// With this approach, combining different transition styles in the same navigator mostly looks right
|
|
366
|
+
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
|
367
|
+
// but the majority of the transitions look alright
|
|
368
|
+
const optionsForTransitionConfig =
|
|
369
|
+
index !== self.length - 1 &&
|
|
370
|
+
nextOptions &&
|
|
371
|
+
nextOptions?.presentation !== 'transparentModal'
|
|
372
|
+
? nextOptions
|
|
373
|
+
: descriptor.options;
|
|
374
|
+
|
|
375
|
+
// Assume modal if there are already modal screens in the stack
|
|
376
|
+
// or current screen is a modal when no presentation is specified
|
|
377
|
+
const isModal = modalRouteKeys.includes(route.key);
|
|
378
|
+
|
|
379
|
+
const animation = getDefaultAnimation(
|
|
380
|
+
optionsForTransitionConfig.animation
|
|
381
|
+
);
|
|
356
382
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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
|
-
},
|
|
383
|
+
const isAnimationEnabled = getAnimationEnabled(animation);
|
|
384
|
+
|
|
385
|
+
const transitionPreset =
|
|
386
|
+
animation !== 'default'
|
|
387
|
+
? NAMED_TRANSITIONS_PRESETS[animation]
|
|
388
|
+
: optionsForTransitionConfig.presentation === 'transparentModal'
|
|
389
|
+
? ModalFadeTransition
|
|
390
|
+
: optionsForTransitionConfig.presentation === 'modal' || isModal
|
|
391
|
+
? ModalTransition
|
|
392
|
+
: DefaultTransition;
|
|
393
|
+
|
|
394
|
+
const {
|
|
395
|
+
gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
|
|
396
|
+
gestureDirection = transitionPreset.gestureDirection,
|
|
397
|
+
transitionSpec = transitionPreset.transitionSpec,
|
|
398
|
+
cardStyleInterpolator = isAnimationEnabled
|
|
399
|
+
? transitionPreset.cardStyleInterpolator
|
|
400
|
+
: forNoAnimationCard,
|
|
401
|
+
headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
|
|
402
|
+
cardOverlayEnabled = (Platform.OS !== 'ios' &&
|
|
403
|
+
optionsForTransitionConfig.presentation !== 'transparentModal') ||
|
|
404
|
+
getIsModalPresentation(cardStyleInterpolator),
|
|
405
|
+
} = optionsForTransitionConfig;
|
|
406
|
+
|
|
407
|
+
const headerMode: StackHeaderMode =
|
|
408
|
+
descriptor.options.headerMode ??
|
|
409
|
+
(!(
|
|
410
|
+
optionsForTransitionConfig.presentation === 'modal' ||
|
|
411
|
+
optionsForTransitionConfig.presentation === 'transparentModal' ||
|
|
412
|
+
nextOptions?.presentation === 'modal' ||
|
|
413
|
+
nextOptions?.presentation === 'transparentModal' ||
|
|
414
|
+
getIsModalPresentation(cardStyleInterpolator)
|
|
415
|
+
) &&
|
|
416
|
+
Platform.OS === 'ios' &&
|
|
417
|
+
descriptor.options.header === undefined
|
|
418
|
+
? 'float'
|
|
419
|
+
: 'screen');
|
|
420
|
+
|
|
421
|
+
const isRTL = props.direction === 'rtl';
|
|
422
|
+
|
|
423
|
+
const scene = {
|
|
424
|
+
route,
|
|
425
|
+
descriptor: {
|
|
426
|
+
...descriptor,
|
|
427
|
+
options: {
|
|
428
|
+
...descriptor.options,
|
|
429
|
+
animation,
|
|
430
|
+
cardOverlayEnabled,
|
|
431
|
+
cardStyleInterpolator,
|
|
432
|
+
gestureDirection,
|
|
433
|
+
gestureEnabled,
|
|
434
|
+
headerStyleInterpolator,
|
|
435
|
+
transitionSpec,
|
|
436
|
+
headerMode,
|
|
412
437
|
},
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
nextGesture,
|
|
424
|
-
state.layout,
|
|
425
|
-
nextOptions,
|
|
426
|
-
isRTL
|
|
427
|
-
)
|
|
428
|
-
: undefined,
|
|
429
|
-
previous: previousGesture
|
|
438
|
+
},
|
|
439
|
+
progress: {
|
|
440
|
+
current: getProgressFromGesture(
|
|
441
|
+
currentGesture,
|
|
442
|
+
state.layout,
|
|
443
|
+
descriptor.options,
|
|
444
|
+
isRTL
|
|
445
|
+
),
|
|
446
|
+
next:
|
|
447
|
+
nextGesture && nextOptions?.presentation !== 'transparentModal'
|
|
430
448
|
? getProgressFromGesture(
|
|
431
|
-
|
|
449
|
+
nextGesture,
|
|
432
450
|
state.layout,
|
|
433
|
-
|
|
451
|
+
nextOptions,
|
|
434
452
|
isRTL
|
|
435
453
|
)
|
|
436
454
|
: undefined,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
455
|
+
previous: previousGesture
|
|
456
|
+
? getProgressFromGesture(
|
|
457
|
+
previousGesture,
|
|
458
|
+
state.layout,
|
|
459
|
+
previousOptions,
|
|
460
|
+
isRTL
|
|
461
|
+
)
|
|
462
|
+
: undefined,
|
|
463
|
+
},
|
|
464
|
+
__memo: [
|
|
465
|
+
state.layout,
|
|
466
|
+
descriptor,
|
|
467
|
+
nextOptions,
|
|
468
|
+
previousOptions,
|
|
469
|
+
currentGesture,
|
|
470
|
+
nextGesture,
|
|
471
|
+
previousGesture,
|
|
472
|
+
],
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
if (
|
|
476
|
+
oldScene &&
|
|
477
|
+
scene.__memo.every((it, i) => {
|
|
478
|
+
// @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
|
|
479
|
+
return oldScene.__memo[i] === it;
|
|
480
|
+
})
|
|
481
|
+
) {
|
|
482
|
+
return oldScene;
|
|
460
483
|
}
|
|
461
|
-
|
|
484
|
+
|
|
485
|
+
return scene;
|
|
486
|
+
});
|
|
462
487
|
|
|
463
488
|
return {
|
|
464
489
|
routes: props.routes,
|
|
@@ -586,6 +611,8 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
586
611
|
|
|
587
612
|
const { scenes, layout, gestures, headerHeights } = this.state;
|
|
588
613
|
|
|
614
|
+
const allRoutes = getAllRoutes(routes, state.preloadedRoutes);
|
|
615
|
+
|
|
589
616
|
const focusedRoute = state.routes[state.index];
|
|
590
617
|
|
|
591
618
|
const isFloatHeaderAbsolute = scenes.slice(-2).some((scene) => {
|
|
@@ -627,26 +654,15 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
627
654
|
],
|
|
628
655
|
})}
|
|
629
656
|
<View style={styles.container} onLayout={this.handleLayout}>
|
|
630
|
-
{
|
|
657
|
+
{allRoutes.map((route, index, self) => {
|
|
631
658
|
const focused = focusedRoute.key === route.key;
|
|
632
659
|
const gesture = gestures[route.key];
|
|
633
660
|
const scene = scenes[index];
|
|
634
|
-
|
|
635
|
-
// Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
|
|
636
|
-
// However, due to the router action, it immediately populates the `preloadedRoutes` array.
|
|
637
|
-
// Practically, the logic below takes care that it is rendered only once.
|
|
638
|
-
const isPreloaded =
|
|
639
|
-
state.preloadedRoutes.includes(route) && !routes.includes(route);
|
|
640
|
-
if (
|
|
641
|
-
state.preloadedRoutes.includes(route) &&
|
|
642
|
-
routes.includes(route) &&
|
|
643
|
-
index >= routes.length
|
|
644
|
-
) {
|
|
645
|
-
return null;
|
|
646
|
-
}
|
|
661
|
+
const isPreloaded = isPreloadedRoute(route, this.props);
|
|
647
662
|
|
|
648
663
|
const {
|
|
649
664
|
inactiveBehavior = 'pause',
|
|
665
|
+
animation,
|
|
650
666
|
headerShown = true,
|
|
651
667
|
headerTransparent,
|
|
652
668
|
} = scene.descriptor.options;
|
|
@@ -667,6 +683,16 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
667
683
|
isParentModal
|
|
668
684
|
);
|
|
669
685
|
|
|
686
|
+
const isAnimationEnabled =
|
|
687
|
+
getAnimationEnabled(animation) ||
|
|
688
|
+
// Also check next screen's animation,
|
|
689
|
+
// As it will result in both screens being visible
|
|
690
|
+
(scenes[index + 1]
|
|
691
|
+
? getAnimationEnabled(
|
|
692
|
+
scenes[index + 1].descriptor.options.animation
|
|
693
|
+
)
|
|
694
|
+
: false);
|
|
695
|
+
|
|
670
696
|
const isNextScreenTransparent =
|
|
671
697
|
scenes[index + 1]?.descriptor.options.presentation ===
|
|
672
698
|
'transparentModal';
|
|
@@ -683,14 +709,16 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
683
709
|
|
|
684
710
|
const isBeforeLast = index === routes.length - 2;
|
|
685
711
|
|
|
686
|
-
// Keep animating and the last two rendered routes visible for smoother transitions
|
|
687
|
-
// Preloaded routes should stay mounted, but remain hidden until focused.
|
|
712
|
+
// Keep animating and the last two rendered routes visible for smoother transitions
|
|
688
713
|
const isVisible =
|
|
689
714
|
focused ||
|
|
690
|
-
isFocusing ||
|
|
691
|
-
isRemoving ||
|
|
692
715
|
isNextScreenTransparent ||
|
|
693
|
-
|
|
716
|
+
// We only need to keep other screens visible when animation is enabled
|
|
717
|
+
(isAnimationEnabled &&
|
|
718
|
+
(isFocusing ||
|
|
719
|
+
isRemoving ||
|
|
720
|
+
// Preloaded routes should stay mounted, but remain hidden until focused
|
|
721
|
+
(!isPreloaded && index >= routes.length - 2)));
|
|
694
722
|
|
|
695
723
|
const activityMode = // Render focused and animating screens normally
|
|
696
724
|
focused || isFocusing
|
|
@@ -66,6 +66,10 @@ export class StackView extends React.Component<Props, State> {
|
|
|
66
66
|
const previousRoutes = state.previousState
|
|
67
67
|
? [...state.previousState.routes, ...state.previousState.preloadedRoutes]
|
|
68
68
|
: [];
|
|
69
|
+
const previousFocusedRoute = state.previousState
|
|
70
|
+
? state.previousState.routes[state.previousState.index]
|
|
71
|
+
: undefined;
|
|
72
|
+
const nextFocusedRouteFromState = props.state.routes[props.state.index];
|
|
69
73
|
|
|
70
74
|
// If there was no change in routes, we don't need to compute anything
|
|
71
75
|
if (
|
|
@@ -73,6 +77,7 @@ export class StackView extends React.Component<Props, State> {
|
|
|
73
77
|
allRoutes.map((r) => r.key),
|
|
74
78
|
previousRoutes.map((r) => r.key)
|
|
75
79
|
) &&
|
|
80
|
+
previousFocusedRoute?.key === nextFocusedRouteFromState?.key &&
|
|
76
81
|
state.routes.length
|
|
77
82
|
) {
|
|
78
83
|
// If there were any routes being closed or replaced,
|
|
@@ -158,10 +163,6 @@ export class StackView extends React.Component<Props, State> {
|
|
|
158
163
|
|
|
159
164
|
// Get previous focused route from previousState (actual focused route, not last in previousRoutes
|
|
160
165
|
// which can be a preloaded route that was never focused)
|
|
161
|
-
const previousFocusedRoute = state.previousState
|
|
162
|
-
? state.previousState.routes[state.previousState.index]
|
|
163
|
-
: undefined;
|
|
164
|
-
|
|
165
166
|
const nextFocusedRoute = routes[routes.length - 1];
|
|
166
167
|
|
|
167
168
|
const isAnimationEnabled = (key: string) => {
|