@react-navigation/stack 8.0.0-alpha.3 → 8.0.0-alpha.30
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/utils/useCardAnimation.js +1 -1
- package/lib/module/utils/useCardAnimation.js.map +1 -1
- package/lib/module/utils/useGestureHandlerRef.js +1 -1
- package/lib/module/utils/useGestureHandlerRef.js.map +1 -1
- package/lib/module/utils/useKeyboardManager.js +50 -26
- package/lib/module/utils/useKeyboardManager.js.map +1 -1
- package/lib/module/views/Header/Header.js +2 -2
- package/lib/module/views/Header/Header.js.map +1 -1
- package/lib/module/views/Header/HeaderContainer.js +32 -16
- package/lib/module/views/Header/HeaderContainer.js.map +1 -1
- package/lib/module/views/Header/HeaderSegment.js +2 -2
- package/lib/module/views/Header/HeaderSegment.js.map +1 -1
- package/lib/module/views/Stack/Card.js +25 -20
- package/lib/module/views/Stack/Card.js.map +1 -1
- package/lib/module/views/Stack/CardA11yWrapper.js +6 -7
- package/lib/module/views/Stack/CardA11yWrapper.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +18 -19
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +105 -133
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +82 -49
- 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 +46 -53
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +1 -1
- package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
- package/lib/typescript/src/utils/useKeyboardManager.d.ts +9 -2
- package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts +1 -1
- package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts +2 -1
- package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts +2 -2
- package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/Card.d.ts +3 -3
- package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +2 -2
- package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardContainer.d.ts +2 -2
- package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts +1 -1
- package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
- package/lib/typescript/src/views/Stack/StackView.d.ts +6 -9
- package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
- package/package.json +20 -22
- package/src/index.tsx +1 -0
- package/src/navigators/createStackNavigator.tsx +9 -40
- package/src/types.tsx +73 -73
- package/src/utils/gestureActivationCriteria.tsx +1 -1
- package/src/utils/useCardAnimation.tsx +1 -1
- package/src/utils/useGestureHandlerRef.tsx +1 -1
- package/src/utils/useKeyboardManager.tsx +67 -33
- package/src/views/Header/Header.tsx +2 -2
- package/src/views/Header/HeaderContainer.tsx +37 -18
- package/src/views/Header/HeaderSegment.tsx +4 -4
- package/src/views/Stack/Card.tsx +32 -23
- package/src/views/Stack/CardA11yWrapper.tsx +40 -51
- package/src/views/Stack/CardContainer.tsx +10 -21
- package/src/views/Stack/CardStack.tsx +291 -328
- package/src/views/Stack/StackView.tsx +138 -66
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { getDefaultHeaderHeight } from '@react-navigation/elements';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
ActivityView,
|
|
4
|
+
SafeAreaProviderCompat,
|
|
5
|
+
} from '@react-navigation/elements/internal';
|
|
3
6
|
import type {
|
|
4
7
|
LocaleDirection,
|
|
5
8
|
ParamListBase,
|
|
@@ -15,7 +18,6 @@ import {
|
|
|
15
18
|
View,
|
|
16
19
|
} from 'react-native';
|
|
17
20
|
import type { EdgeInsets } from 'react-native-safe-area-context';
|
|
18
|
-
import { Screen, ScreenContainer } from 'react-native-screens';
|
|
19
21
|
|
|
20
22
|
import {
|
|
21
23
|
forModalPresentationIOS,
|
|
@@ -44,7 +46,6 @@ import type {
|
|
|
44
46
|
StackNavigationOptions,
|
|
45
47
|
TransitionPreset,
|
|
46
48
|
} from '../../types';
|
|
47
|
-
import { findLastIndex } from '../../utils/findLastIndex';
|
|
48
49
|
import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
|
|
49
50
|
import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
|
|
50
51
|
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
|
@@ -62,6 +63,7 @@ type Props = {
|
|
|
62
63
|
routes: Route<string>[];
|
|
63
64
|
openingRouteKeys: string[];
|
|
64
65
|
closingRouteKeys: string[];
|
|
66
|
+
replacingRouteKeys: string[];
|
|
65
67
|
onOpenRoute: (props: { route: Route<string> }) => void;
|
|
66
68
|
onCloseRoute: (props: { route: Route<string> }) => void;
|
|
67
69
|
getPreviousRoute: (props: {
|
|
@@ -87,7 +89,6 @@ type State = {
|
|
|
87
89
|
scenes: Scene[];
|
|
88
90
|
gestures: GestureValues;
|
|
89
91
|
layout: Layout;
|
|
90
|
-
activeStates: (0 | 1 | Animated.AnimatedInterpolation<0 | 1>)[];
|
|
91
92
|
headerHeights: Record<string, number>;
|
|
92
93
|
};
|
|
93
94
|
|
|
@@ -107,12 +108,6 @@ const NAMED_TRANSITIONS_PRESETS = {
|
|
|
107
108
|
}),
|
|
108
109
|
} as const satisfies Record<StackAnimationName, TransitionPreset>;
|
|
109
110
|
|
|
110
|
-
const EPSILON = 1e-5;
|
|
111
|
-
|
|
112
|
-
const STATE_INACTIVE = 0;
|
|
113
|
-
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
|
114
|
-
const STATE_ON_TOP = 2;
|
|
115
|
-
|
|
116
111
|
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
|
117
112
|
|
|
118
113
|
const getInterpolationIndex = (scenes: Scene[], index: number) => {
|
|
@@ -263,6 +258,27 @@ export function getAnimationEnabled(animation: StackAnimationName | undefined) {
|
|
|
263
258
|
return getDefaultAnimation(animation) !== 'none';
|
|
264
259
|
}
|
|
265
260
|
|
|
261
|
+
const getAllRoutes = (
|
|
262
|
+
routes: Route<string>[],
|
|
263
|
+
state: StackNavigationState<ParamListBase>
|
|
264
|
+
) => {
|
|
265
|
+
const routeKeys = new Set(routes.map((route) => route.key));
|
|
266
|
+
const inactiveRoutes = state.routes.slice(state.index + 1);
|
|
267
|
+
|
|
268
|
+
// If a route is moved from active routes to inactive routes,
|
|
269
|
+
// It can still be in the local copy of `routes` until the animation ends
|
|
270
|
+
// So we need to deduplicate the routes to avoid rendering the same route twice
|
|
271
|
+
return [
|
|
272
|
+
...routes,
|
|
273
|
+
...inactiveRoutes.filter((route) => !routeKeys.has(route.key)),
|
|
274
|
+
];
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const isInactiveRoute = (route: Route<string>, routes: Route<string>[]) =>
|
|
278
|
+
// `routes` contains active routes and routes animating during transitions.
|
|
279
|
+
// Any route added by `getAllRoutes` that's missing from this list is inactive.
|
|
280
|
+
!routes.some((currentRoute) => currentRoute.key === route.key);
|
|
281
|
+
|
|
266
282
|
export class CardStack extends React.Component<Props, State> {
|
|
267
283
|
static getDerivedStateFromProps(
|
|
268
284
|
props: Props,
|
|
@@ -275,10 +291,9 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
275
291
|
return null;
|
|
276
292
|
}
|
|
277
293
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
].reduce<GestureValues>((acc, curr) => {
|
|
294
|
+
const allRoutes = getAllRoutes(props.routes, props.state);
|
|
295
|
+
|
|
296
|
+
const gestures = allRoutes.reduce<GestureValues>((acc, curr) => {
|
|
282
297
|
const descriptor = props.descriptors[curr.key];
|
|
283
298
|
const { animation } = descriptor?.options || {};
|
|
284
299
|
|
|
@@ -287,7 +302,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
287
302
|
new Animated.Value(
|
|
288
303
|
(props.openingRouteKeys.includes(curr.key) &&
|
|
289
304
|
getAnimationEnabled(animation)) ||
|
|
290
|
-
|
|
305
|
+
isInactiveRoute(curr, props.routes)
|
|
291
306
|
? getDistanceFromOptions(
|
|
292
307
|
state.layout,
|
|
293
308
|
descriptor?.options,
|
|
@@ -299,254 +314,170 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
299
314
|
return acc;
|
|
300
315
|
}, {});
|
|
301
316
|
|
|
302
|
-
const modalRouteKeys = getModalRouteKeys(
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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
|
-
);
|
|
317
|
+
const modalRouteKeys = getModalRouteKeys(allRoutes, props.descriptors);
|
|
318
|
+
|
|
319
|
+
const scenes = allRoutes.map((route, index, self) => {
|
|
320
|
+
// For preloaded or retained screens, we don't care about the previous and the next screen
|
|
321
|
+
const isInactive = isInactiveRoute(route, props.routes);
|
|
322
|
+
const previousRoute = isInactive ? undefined : self[index - 1];
|
|
323
|
+
const nextRoute = isInactive ? undefined : self[index + 1];
|
|
324
|
+
|
|
325
|
+
const oldScene = state.scenes[index];
|
|
326
|
+
|
|
327
|
+
const currentGesture = gestures[route.key];
|
|
328
|
+
const previousGesture = previousRoute
|
|
329
|
+
? gestures[previousRoute.key]
|
|
330
|
+
: undefined;
|
|
331
|
+
const nextGesture = nextRoute ? gestures[nextRoute.key] : undefined;
|
|
332
|
+
|
|
333
|
+
const descriptor =
|
|
334
|
+
props.descriptors[route.key] ||
|
|
335
|
+
state.descriptors[route.key] ||
|
|
336
|
+
(oldScene ? oldScene.descriptor : FALLBACK_DESCRIPTOR);
|
|
337
|
+
|
|
338
|
+
const nextOptions =
|
|
339
|
+
nextRoute &&
|
|
340
|
+
(props.descriptors[nextRoute?.key] || state.descriptors[nextRoute?.key])
|
|
341
|
+
?.options;
|
|
342
|
+
|
|
343
|
+
const previousOptions =
|
|
344
|
+
previousRoute &&
|
|
345
|
+
(
|
|
346
|
+
props.descriptors[previousRoute?.key] ||
|
|
347
|
+
state.descriptors[previousRoute?.key]
|
|
348
|
+
)?.options;
|
|
349
|
+
|
|
350
|
+
// When a screen is not the last, it should use next screen's transition config
|
|
351
|
+
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
|
352
|
+
// For example combining a slide and a modal transition would look wrong otherwise
|
|
353
|
+
// With this approach, combining different transition styles in the same navigator mostly looks right
|
|
354
|
+
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
|
355
|
+
// but the majority of the transitions look alright
|
|
356
|
+
const optionsForTransitionConfig =
|
|
357
|
+
index !== self.length - 1 &&
|
|
358
|
+
nextOptions &&
|
|
359
|
+
nextOptions?.presentation !== 'transparentModal'
|
|
360
|
+
? nextOptions
|
|
361
|
+
: descriptor.options;
|
|
362
|
+
|
|
363
|
+
// Assume modal if there are already modal screens in the stack
|
|
364
|
+
// or current screen is a modal when no presentation is specified
|
|
365
|
+
const isModal = modalRouteKeys.includes(route.key);
|
|
366
|
+
|
|
367
|
+
const animation = getDefaultAnimation(
|
|
368
|
+
optionsForTransitionConfig.animation
|
|
369
|
+
);
|
|
361
370
|
|
|
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
|
-
},
|
|
371
|
+
const isAnimationEnabled = getAnimationEnabled(animation);
|
|
372
|
+
|
|
373
|
+
const transitionPreset =
|
|
374
|
+
animation !== 'default'
|
|
375
|
+
? NAMED_TRANSITIONS_PRESETS[animation]
|
|
376
|
+
: optionsForTransitionConfig.presentation === 'transparentModal'
|
|
377
|
+
? ModalFadeTransition
|
|
378
|
+
: optionsForTransitionConfig.presentation === 'modal' || isModal
|
|
379
|
+
? ModalTransition
|
|
380
|
+
: DefaultTransition;
|
|
381
|
+
|
|
382
|
+
const {
|
|
383
|
+
gestureEnabled = Platform.OS === 'ios' && isAnimationEnabled,
|
|
384
|
+
gestureDirection = transitionPreset.gestureDirection,
|
|
385
|
+
transitionSpec = transitionPreset.transitionSpec,
|
|
386
|
+
cardStyleInterpolator = isAnimationEnabled
|
|
387
|
+
? transitionPreset.cardStyleInterpolator
|
|
388
|
+
: forNoAnimationCard,
|
|
389
|
+
headerStyleInterpolator = transitionPreset.headerStyleInterpolator,
|
|
390
|
+
cardOverlayEnabled = (Platform.OS !== 'ios' &&
|
|
391
|
+
optionsForTransitionConfig.presentation !== 'transparentModal') ||
|
|
392
|
+
getIsModalPresentation(cardStyleInterpolator),
|
|
393
|
+
} = optionsForTransitionConfig;
|
|
394
|
+
|
|
395
|
+
const headerMode: StackHeaderMode =
|
|
396
|
+
descriptor.options.headerMode ??
|
|
397
|
+
(!(
|
|
398
|
+
optionsForTransitionConfig.presentation === 'modal' ||
|
|
399
|
+
optionsForTransitionConfig.presentation === 'transparentModal' ||
|
|
400
|
+
nextOptions?.presentation === 'modal' ||
|
|
401
|
+
nextOptions?.presentation === 'transparentModal' ||
|
|
402
|
+
getIsModalPresentation(cardStyleInterpolator)
|
|
403
|
+
) &&
|
|
404
|
+
Platform.OS === 'ios' &&
|
|
405
|
+
descriptor.options.header === undefined
|
|
406
|
+
? 'float'
|
|
407
|
+
: 'screen');
|
|
408
|
+
|
|
409
|
+
const isRTL = props.direction === 'rtl';
|
|
410
|
+
|
|
411
|
+
const scene = {
|
|
412
|
+
route,
|
|
413
|
+
descriptor: {
|
|
414
|
+
...descriptor,
|
|
415
|
+
options: {
|
|
416
|
+
...descriptor.options,
|
|
417
|
+
animation,
|
|
418
|
+
cardOverlayEnabled,
|
|
419
|
+
cardStyleInterpolator,
|
|
420
|
+
gestureDirection,
|
|
421
|
+
gestureEnabled,
|
|
422
|
+
headerStyleInterpolator,
|
|
423
|
+
transitionSpec,
|
|
424
|
+
headerMode,
|
|
417
425
|
},
|
|
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
|
|
426
|
+
},
|
|
427
|
+
progress: {
|
|
428
|
+
current: getProgressFromGesture(
|
|
429
|
+
currentGesture,
|
|
430
|
+
state.layout,
|
|
431
|
+
descriptor.options,
|
|
432
|
+
isRTL
|
|
433
|
+
),
|
|
434
|
+
next:
|
|
435
|
+
nextGesture && nextOptions?.presentation !== 'transparentModal'
|
|
435
436
|
? getProgressFromGesture(
|
|
436
|
-
|
|
437
|
+
nextGesture,
|
|
437
438
|
state.layout,
|
|
438
|
-
|
|
439
|
+
nextOptions,
|
|
439
440
|
isRTL
|
|
440
441
|
)
|
|
441
442
|
: undefined,
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
return oldScene;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
return scene;
|
|
465
|
-
}
|
|
466
|
-
);
|
|
443
|
+
previous: previousGesture
|
|
444
|
+
? getProgressFromGesture(
|
|
445
|
+
previousGesture,
|
|
446
|
+
state.layout,
|
|
447
|
+
previousOptions,
|
|
448
|
+
isRTL
|
|
449
|
+
)
|
|
450
|
+
: undefined,
|
|
451
|
+
},
|
|
452
|
+
__memo: [
|
|
453
|
+
state.layout,
|
|
454
|
+
descriptor,
|
|
455
|
+
nextOptions,
|
|
456
|
+
previousOptions,
|
|
457
|
+
currentGesture,
|
|
458
|
+
nextGesture,
|
|
459
|
+
previousGesture,
|
|
460
|
+
],
|
|
461
|
+
};
|
|
467
462
|
|
|
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
|
-
}
|
|
463
|
+
if (
|
|
464
|
+
oldScene &&
|
|
465
|
+
scene.__memo.every((it, i) => {
|
|
466
|
+
// @ts-expect-error: we haven't added __memo to the annotation to prevent usage elsewhere
|
|
467
|
+
return oldScene.__memo[i] === it;
|
|
468
|
+
})
|
|
469
|
+
) {
|
|
470
|
+
return oldScene;
|
|
503
471
|
}
|
|
504
472
|
|
|
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
|
-
}
|
|
473
|
+
return scene;
|
|
474
|
+
});
|
|
543
475
|
|
|
544
476
|
return {
|
|
545
477
|
routes: props.routes,
|
|
546
478
|
scenes,
|
|
547
479
|
gestures,
|
|
548
480
|
descriptors: props.descriptors,
|
|
549
|
-
activeStates,
|
|
550
481
|
headerHeights: getHeaderHeights(
|
|
551
482
|
scenes,
|
|
552
483
|
props.insets,
|
|
@@ -567,7 +498,6 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
567
498
|
gestures: {},
|
|
568
499
|
layout: SafeAreaProviderCompat.initialMetrics.frame,
|
|
569
500
|
descriptors: this.props.descriptors,
|
|
570
|
-
activeStates: [],
|
|
571
501
|
// Used when card's header is null and mode is float to make transition
|
|
572
502
|
// between screens with headers and those without headers smooth.
|
|
573
503
|
// This is not a great heuristic here. We don't know synchronously
|
|
@@ -654,23 +584,22 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
654
584
|
routes,
|
|
655
585
|
openingRouteKeys,
|
|
656
586
|
closingRouteKeys,
|
|
587
|
+
replacingRouteKeys,
|
|
657
588
|
onOpenRoute,
|
|
658
589
|
onCloseRoute,
|
|
590
|
+
onGestureStart,
|
|
591
|
+
onGestureEnd,
|
|
592
|
+
onGestureCancel,
|
|
659
593
|
renderHeader,
|
|
660
594
|
isParentHeaderShown,
|
|
661
595
|
isParentModal,
|
|
662
596
|
onTransitionStart,
|
|
663
597
|
onTransitionEnd,
|
|
664
|
-
onGestureStart,
|
|
665
|
-
onGestureEnd,
|
|
666
|
-
onGestureCancel,
|
|
667
|
-
detachInactiveScreens = Platform.OS === 'web' ||
|
|
668
|
-
Platform.OS === 'android' ||
|
|
669
|
-
Platform.OS === 'ios',
|
|
670
598
|
} = this.props;
|
|
671
599
|
|
|
672
|
-
const { scenes, layout, gestures,
|
|
673
|
-
|
|
600
|
+
const { scenes, layout, gestures, headerHeights } = this.state;
|
|
601
|
+
|
|
602
|
+
const allRoutes = getAllRoutes(routes, state);
|
|
674
603
|
|
|
675
604
|
const focusedRoute = state.routes[state.index];
|
|
676
605
|
|
|
@@ -700,6 +629,7 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
700
629
|
scenes,
|
|
701
630
|
getPreviousScene: this.getPreviousScene,
|
|
702
631
|
getFocusedRoute: this.getFocusedRoute,
|
|
632
|
+
contentHeight: headerHeights[focusedRoute.key],
|
|
703
633
|
onContentHeightChange: this.handleHeaderLayout,
|
|
704
634
|
style: [
|
|
705
635
|
isFloatHeaderAbsolute && [
|
|
@@ -712,34 +642,18 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
712
642
|
],
|
|
713
643
|
],
|
|
714
644
|
})}
|
|
715
|
-
<
|
|
716
|
-
|
|
717
|
-
style={styles.container}
|
|
718
|
-
onLayout={this.handleLayout}
|
|
719
|
-
>
|
|
720
|
-
{[...routes, ...state.preloadedRoutes].map((route, index) => {
|
|
645
|
+
<View style={styles.container} onLayout={this.handleLayout}>
|
|
646
|
+
{allRoutes.map((route, index, self) => {
|
|
721
647
|
const focused = focusedRoute.key === route.key;
|
|
722
648
|
const gesture = gestures[route.key];
|
|
723
649
|
const scene = scenes[index];
|
|
724
|
-
|
|
725
|
-
// Particularly, if the screen is removed with `retain`, then it needs a moment to execute the animation.
|
|
726
|
-
// However, due to the router action, it immediately populates the `preloadedRoutes` array.
|
|
727
|
-
// Practically, the logic below takes care that it is rendered only once.
|
|
728
|
-
const isPreloaded =
|
|
729
|
-
state.preloadedRoutes.includes(route) && !routes.includes(route);
|
|
730
|
-
if (
|
|
731
|
-
state.preloadedRoutes.includes(route) &&
|
|
732
|
-
routes.includes(route) &&
|
|
733
|
-
index >= routes.length
|
|
734
|
-
) {
|
|
735
|
-
return null;
|
|
736
|
-
}
|
|
650
|
+
const isInactive = isInactiveRoute(route, this.props.routes);
|
|
737
651
|
|
|
738
652
|
const {
|
|
653
|
+
inactiveBehavior = 'pause',
|
|
654
|
+
animation,
|
|
739
655
|
headerShown = true,
|
|
740
656
|
headerTransparent,
|
|
741
|
-
freezeOnBlur,
|
|
742
|
-
autoHideHomeIndicator,
|
|
743
657
|
} = scene.descriptor.options;
|
|
744
658
|
|
|
745
659
|
const safeAreaInsetTop = insets.top;
|
|
@@ -758,67 +672,116 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
758
672
|
isParentModal
|
|
759
673
|
);
|
|
760
674
|
|
|
675
|
+
const isAnimationEnabled =
|
|
676
|
+
getAnimationEnabled(animation) ||
|
|
677
|
+
// Also check next screen's animation,
|
|
678
|
+
// As it will result in both screens being visible
|
|
679
|
+
(scenes[index + 1]
|
|
680
|
+
? getAnimationEnabled(
|
|
681
|
+
scenes[index + 1].descriptor.options.animation
|
|
682
|
+
)
|
|
683
|
+
: false);
|
|
684
|
+
|
|
761
685
|
const isNextScreenTransparent =
|
|
762
686
|
scenes[index + 1]?.descriptor.options.presentation ===
|
|
763
687
|
'transparentModal';
|
|
764
688
|
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
689
|
+
const isRemoving =
|
|
690
|
+
replacingRouteKeys.includes(route.key) ||
|
|
691
|
+
closingRouteKeys.includes(route.key);
|
|
692
|
+
|
|
693
|
+
const isFocusing =
|
|
694
|
+
openingRouteKeys.includes(route.key) ||
|
|
695
|
+
[...closingRouteKeys, ...replacingRouteKeys].includes(
|
|
696
|
+
self[index + 1]?.key
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
const isBeforeLast = index === routes.length - 2;
|
|
700
|
+
|
|
701
|
+
// Keep animating and the last two rendered routes visible for smoother transitions
|
|
702
|
+
const isVisible =
|
|
703
|
+
focused ||
|
|
704
|
+
isNextScreenTransparent ||
|
|
705
|
+
// We only need to keep other screens visible when animation is enabled
|
|
706
|
+
(isAnimationEnabled &&
|
|
707
|
+
(isFocusing ||
|
|
708
|
+
isRemoving ||
|
|
709
|
+
// Preloaded and retained screens should stay mounted, but remain hidden until focused
|
|
710
|
+
(!isInactive && index >= routes.length - 2)));
|
|
711
|
+
|
|
712
|
+
const activityMode = // Render focused and animating screens normally
|
|
713
|
+
focused || isFocusing
|
|
714
|
+
? 'normal'
|
|
715
|
+
: inactiveBehavior === 'none' ||
|
|
716
|
+
// Unpause preloaded or retained screens so updates are visible
|
|
717
|
+
isInactive ||
|
|
718
|
+
// Keep the screen before transparent screen active
|
|
719
|
+
// This lets the screen under the transparent screen update and animate
|
|
720
|
+
isNextScreenTransparent ||
|
|
721
|
+
// Keep the screen before last screen active
|
|
722
|
+
// Otherwise it breaks animation when going back
|
|
723
|
+
isBeforeLast
|
|
724
|
+
? 'inert'
|
|
725
|
+
: inactiveBehavior === 'unmount' &&
|
|
726
|
+
// Don't unmount screens that needs to stay visible
|
|
727
|
+
!isVisible &&
|
|
728
|
+
// Don't unmount screens with nested navigators
|
|
729
|
+
// So we don't lose their state
|
|
730
|
+
!('state' in route && route.state)
|
|
731
|
+
? 'unmounted'
|
|
732
|
+
: 'paused';
|
|
733
|
+
|
|
734
|
+
if (activityMode === 'unmounted') {
|
|
735
|
+
return null;
|
|
736
|
+
}
|
|
772
737
|
|
|
773
738
|
return (
|
|
774
|
-
<
|
|
739
|
+
<CardContainer
|
|
775
740
|
key={route.key}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
741
|
+
index={index}
|
|
742
|
+
interpolationIndex={interpolationIndex}
|
|
743
|
+
modal={isModal}
|
|
744
|
+
active={index === routes.length - 1}
|
|
745
|
+
focused={focused}
|
|
746
|
+
opening={openingRouteKeys.includes(route.key)}
|
|
747
|
+
closing={closingRouteKeys.includes(route.key)}
|
|
748
|
+
layout={layout}
|
|
749
|
+
gesture={gesture}
|
|
750
|
+
scene={scene}
|
|
751
|
+
safeAreaInsetTop={safeAreaInsetTop}
|
|
752
|
+
safeAreaInsetRight={safeAreaInsetRight}
|
|
753
|
+
safeAreaInsetBottom={safeAreaInsetBottom}
|
|
754
|
+
safeAreaInsetLeft={safeAreaInsetLeft}
|
|
755
|
+
onGestureStart={onGestureStart}
|
|
756
|
+
onGestureCancel={onGestureCancel}
|
|
757
|
+
onGestureEnd={onGestureEnd}
|
|
758
|
+
headerHeight={headerHeight}
|
|
759
|
+
isParentHeaderShown={isParentHeaderShown}
|
|
760
|
+
onHeaderHeightChange={this.handleHeaderLayout}
|
|
761
|
+
getPreviousScene={this.getPreviousScene}
|
|
762
|
+
getFocusedRoute={this.getFocusedRoute}
|
|
763
|
+
hasAbsoluteFloatHeader={
|
|
764
|
+
isFloatHeaderAbsolute && !headerTransparent
|
|
765
|
+
}
|
|
766
|
+
renderHeader={renderHeader}
|
|
767
|
+
onOpenRoute={onOpenRoute}
|
|
768
|
+
onCloseRoute={onCloseRoute}
|
|
769
|
+
onTransitionStart={onTransitionStart}
|
|
770
|
+
onTransitionEnd={onTransitionEnd}
|
|
771
|
+
isNextScreenTransparent={isNextScreenTransparent}
|
|
772
|
+
preloaded={isInactive}
|
|
782
773
|
>
|
|
783
|
-
<
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
layout={layout}
|
|
792
|
-
gesture={gesture}
|
|
793
|
-
scene={scene}
|
|
794
|
-
safeAreaInsetTop={safeAreaInsetTop}
|
|
795
|
-
safeAreaInsetRight={safeAreaInsetRight}
|
|
796
|
-
safeAreaInsetBottom={safeAreaInsetBottom}
|
|
797
|
-
safeAreaInsetLeft={safeAreaInsetLeft}
|
|
798
|
-
onGestureStart={onGestureStart}
|
|
799
|
-
onGestureCancel={onGestureCancel}
|
|
800
|
-
onGestureEnd={onGestureEnd}
|
|
801
|
-
headerHeight={headerHeight}
|
|
802
|
-
isParentHeaderShown={isParentHeaderShown}
|
|
803
|
-
onHeaderHeightChange={this.handleHeaderLayout}
|
|
804
|
-
getPreviousScene={this.getPreviousScene}
|
|
805
|
-
getFocusedRoute={this.getFocusedRoute}
|
|
806
|
-
hasAbsoluteFloatHeader={
|
|
807
|
-
isFloatHeaderAbsolute && !headerTransparent
|
|
808
|
-
}
|
|
809
|
-
renderHeader={renderHeader}
|
|
810
|
-
onOpenRoute={onOpenRoute}
|
|
811
|
-
onCloseRoute={onCloseRoute}
|
|
812
|
-
onTransitionStart={onTransitionStart}
|
|
813
|
-
onTransitionEnd={onTransitionEnd}
|
|
814
|
-
isNextScreenTransparent={isNextScreenTransparent}
|
|
815
|
-
detachCurrentScreen={detachCurrentScreen}
|
|
816
|
-
preloaded={isPreloaded}
|
|
817
|
-
/>
|
|
818
|
-
</Screen>
|
|
774
|
+
<ActivityView
|
|
775
|
+
mode={activityMode}
|
|
776
|
+
visible={isVisible}
|
|
777
|
+
style={StyleSheet.absoluteFill}
|
|
778
|
+
>
|
|
779
|
+
{scene.descriptor.render()}
|
|
780
|
+
</ActivityView>
|
|
781
|
+
</CardContainer>
|
|
819
782
|
);
|
|
820
783
|
})}
|
|
821
|
-
</
|
|
784
|
+
</View>
|
|
822
785
|
</View>
|
|
823
786
|
);
|
|
824
787
|
}
|