@react-navigation/stack 8.0.0-alpha.2 → 8.0.0-alpha.21
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/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 +8 -4
- 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 +1 -2
- package/lib/module/views/Stack/CardA11yWrapper.js.map +1 -1
- package/lib/module/views/Stack/CardContainer.js +17 -19
- package/lib/module/views/Stack/CardContainer.js.map +1 -1
- package/lib/module/views/Stack/CardStack.js +72 -117
- package/lib/module/views/Stack/CardStack.js.map +1 -1
- package/lib/module/views/Stack/StackView.js +59 -23
- package/lib/module/views/Stack/StackView.js.map +1 -1
- package/lib/typescript/src/types.d.ts +46 -47
- 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.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 +0 -1
- 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 -6
- package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
- package/package.json +16 -16
- package/src/types.tsx +73 -67
- 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 +8 -3
- package/src/views/Header/HeaderSegment.tsx +4 -4
- package/src/views/Stack/Card.tsx +32 -23
- package/src/views/Stack/CardA11yWrapper.tsx +2 -14
- package/src/views/Stack/CardContainer.tsx +9 -21
- package/src/views/Stack/CardStack.tsx +102 -155
- package/src/views/Stack/StackView.tsx +106 -34
|
@@ -57,7 +57,7 @@ type Props = {
|
|
|
57
57
|
}) => void;
|
|
58
58
|
isParentHeaderShown: boolean;
|
|
59
59
|
isNextScreenTransparent: boolean;
|
|
60
|
-
|
|
60
|
+
children: React.ReactNode;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
63
|
const EPSILON = 0.1;
|
|
@@ -78,7 +78,6 @@ function CardContainerInner({
|
|
|
78
78
|
onHeaderHeightChange,
|
|
79
79
|
isParentHeaderShown,
|
|
80
80
|
isNextScreenTransparent,
|
|
81
|
-
detachCurrentScreen,
|
|
82
81
|
layout,
|
|
83
82
|
onCloseRoute,
|
|
84
83
|
onOpenRoute,
|
|
@@ -94,23 +93,19 @@ function CardContainerInner({
|
|
|
94
93
|
safeAreaInsetRight,
|
|
95
94
|
safeAreaInsetTop,
|
|
96
95
|
scene,
|
|
96
|
+
children,
|
|
97
97
|
}: Props) {
|
|
98
98
|
const wrapperRef = React.useRef<CardA11yWrapperRef>(null);
|
|
99
99
|
|
|
100
100
|
const { direction } = useLocale();
|
|
101
101
|
|
|
102
|
-
const parentHeaderHeight = React.
|
|
102
|
+
const parentHeaderHeight = React.use(HeaderHeightContext);
|
|
103
|
+
|
|
104
|
+
const { options } = scene.descriptor;
|
|
105
|
+
const enabled = focused && options.keyboardHandlingEnabled !== false;
|
|
103
106
|
|
|
104
107
|
const { onPageChangeStart, onPageChangeCancel, onPageChangeConfirm } =
|
|
105
|
-
useKeyboardManager(
|
|
106
|
-
React.useCallback(() => {
|
|
107
|
-
const { options, navigation } = scene.descriptor;
|
|
108
|
-
|
|
109
|
-
return (
|
|
110
|
-
navigation.isFocused() && options.keyboardHandlingEnabled !== false
|
|
111
|
-
);
|
|
112
|
-
}, [scene.descriptor])
|
|
113
|
-
);
|
|
108
|
+
useKeyboardManager({ enabled, focused });
|
|
114
109
|
|
|
115
110
|
const handleOpen = () => {
|
|
116
111
|
const { route } = scene.descriptor;
|
|
@@ -157,13 +152,7 @@ function CardContainerInner({
|
|
|
157
152
|
|
|
158
153
|
const { route } = scene.descriptor;
|
|
159
154
|
|
|
160
|
-
|
|
161
|
-
onPageChangeConfirm?.(true);
|
|
162
|
-
} else if (active && closing) {
|
|
163
|
-
onPageChangeConfirm?.(false);
|
|
164
|
-
} else {
|
|
165
|
-
onPageChangeCancel?.();
|
|
166
|
-
}
|
|
155
|
+
onPageChangeConfirm?.({ gesture, active, closing });
|
|
167
156
|
|
|
168
157
|
onTransitionStart?.({ route }, closing);
|
|
169
158
|
};
|
|
@@ -242,7 +231,6 @@ function CardContainerInner({
|
|
|
242
231
|
active={active}
|
|
243
232
|
animated={animated}
|
|
244
233
|
isNextScreenTransparent={isNextScreenTransparent}
|
|
245
|
-
detachCurrentScreen={detachCurrentScreen}
|
|
246
234
|
>
|
|
247
235
|
<Card
|
|
248
236
|
animated={animated}
|
|
@@ -311,7 +299,7 @@ function CardContainerInner({
|
|
|
311
299
|
: (parentHeaderHeight ?? 0)
|
|
312
300
|
}
|
|
313
301
|
>
|
|
314
|
-
{
|
|
302
|
+
{children}
|
|
315
303
|
</HeaderHeightContext.Provider>
|
|
316
304
|
</HeaderShownContext.Provider>
|
|
317
305
|
</HeaderBackContext.Provider>
|
|
@@ -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) => {
|
|
@@ -465,88 +460,11 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
465
460
|
}
|
|
466
461
|
);
|
|
467
462
|
|
|
468
|
-
let activeStates = state.activeStates;
|
|
469
|
-
|
|
470
|
-
if (props.routes.length !== state.routes.length) {
|
|
471
|
-
let activeScreensLimit = 1;
|
|
472
|
-
|
|
473
|
-
for (let i = props.routes.length - 1; i >= 0; i--) {
|
|
474
|
-
const { options } = scenes[i].descriptor;
|
|
475
|
-
|
|
476
|
-
const {
|
|
477
|
-
// By default, we don't want to detach the previous screen of the active one for modals
|
|
478
|
-
detachPreviousScreen = options.presentation === 'transparentModal'
|
|
479
|
-
? false
|
|
480
|
-
: getIsModalPresentation(options.cardStyleInterpolator)
|
|
481
|
-
? i !==
|
|
482
|
-
findLastIndex(scenes, (scene) => {
|
|
483
|
-
const { cardStyleInterpolator } = scene.descriptor.options;
|
|
484
|
-
|
|
485
|
-
return (
|
|
486
|
-
cardStyleInterpolator === forModalPresentationIOS ||
|
|
487
|
-
cardStyleInterpolator?.name === 'forModalPresentationIOS'
|
|
488
|
-
);
|
|
489
|
-
})
|
|
490
|
-
: true,
|
|
491
|
-
} = options;
|
|
492
|
-
|
|
493
|
-
if (detachPreviousScreen === false) {
|
|
494
|
-
activeScreensLimit++;
|
|
495
|
-
} else {
|
|
496
|
-
// Check at least last 2 screens before stopping
|
|
497
|
-
// This will make sure that screen isn't detached when another screen is animating on top of the transparent one
|
|
498
|
-
// e.g. opaque -> transparent -> opaque
|
|
499
|
-
if (i <= props.routes.length - 2) {
|
|
500
|
-
break;
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
activeStates = props.routes.map((_, index, self) => {
|
|
506
|
-
// The activity state represents state of the screen:
|
|
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
|
-
}
|
|
543
|
-
|
|
544
463
|
return {
|
|
545
464
|
routes: props.routes,
|
|
546
465
|
scenes,
|
|
547
466
|
gestures,
|
|
548
467
|
descriptors: props.descriptors,
|
|
549
|
-
activeStates,
|
|
550
468
|
headerHeights: getHeaderHeights(
|
|
551
469
|
scenes,
|
|
552
470
|
props.insets,
|
|
@@ -567,7 +485,6 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
567
485
|
gestures: {},
|
|
568
486
|
layout: SafeAreaProviderCompat.initialMetrics.frame,
|
|
569
487
|
descriptors: this.props.descriptors,
|
|
570
|
-
activeStates: [],
|
|
571
488
|
// Used when card's header is null and mode is float to make transition
|
|
572
489
|
// between screens with headers and those without headers smooth.
|
|
573
490
|
// This is not a great heuristic here. We don't know synchronously
|
|
@@ -654,23 +571,20 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
654
571
|
routes,
|
|
655
572
|
openingRouteKeys,
|
|
656
573
|
closingRouteKeys,
|
|
574
|
+
replacingRouteKeys,
|
|
657
575
|
onOpenRoute,
|
|
658
576
|
onCloseRoute,
|
|
577
|
+
onGestureStart,
|
|
578
|
+
onGestureEnd,
|
|
579
|
+
onGestureCancel,
|
|
659
580
|
renderHeader,
|
|
660
581
|
isParentHeaderShown,
|
|
661
582
|
isParentModal,
|
|
662
583
|
onTransitionStart,
|
|
663
584
|
onTransitionEnd,
|
|
664
|
-
onGestureStart,
|
|
665
|
-
onGestureEnd,
|
|
666
|
-
onGestureCancel,
|
|
667
|
-
detachInactiveScreens = Platform.OS === 'web' ||
|
|
668
|
-
Platform.OS === 'android' ||
|
|
669
|
-
Platform.OS === 'ios',
|
|
670
585
|
} = this.props;
|
|
671
586
|
|
|
672
|
-
const { scenes, layout, gestures,
|
|
673
|
-
this.state;
|
|
587
|
+
const { scenes, layout, gestures, headerHeights } = this.state;
|
|
674
588
|
|
|
675
589
|
const focusedRoute = state.routes[state.index];
|
|
676
590
|
|
|
@@ -712,12 +626,8 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
712
626
|
],
|
|
713
627
|
],
|
|
714
628
|
})}
|
|
715
|
-
<
|
|
716
|
-
|
|
717
|
-
style={styles.container}
|
|
718
|
-
onLayout={this.handleLayout}
|
|
719
|
-
>
|
|
720
|
-
{[...routes, ...state.preloadedRoutes].map((route, index) => {
|
|
629
|
+
<View style={styles.container} onLayout={this.handleLayout}>
|
|
630
|
+
{[...routes, ...state.preloadedRoutes].map((route, index, self) => {
|
|
721
631
|
const focused = focusedRoute.key === route.key;
|
|
722
632
|
const gesture = gestures[route.key];
|
|
723
633
|
const scene = scenes[index];
|
|
@@ -736,10 +646,9 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
736
646
|
}
|
|
737
647
|
|
|
738
648
|
const {
|
|
649
|
+
inactiveBehavior = 'pause',
|
|
739
650
|
headerShown = true,
|
|
740
651
|
headerTransparent,
|
|
741
|
-
freezeOnBlur,
|
|
742
|
-
autoHideHomeIndicator,
|
|
743
652
|
} = scene.descriptor.options;
|
|
744
653
|
|
|
745
654
|
const safeAreaInsetTop = insets.top;
|
|
@@ -762,63 +671,101 @@ export class CardStack extends React.Component<Props, State> {
|
|
|
762
671
|
scenes[index + 1]?.descriptor.options.presentation ===
|
|
763
672
|
'transparentModal';
|
|
764
673
|
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
674
|
+
const isRemoving =
|
|
675
|
+
replacingRouteKeys.includes(route.key) ||
|
|
676
|
+
closingRouteKeys.includes(route.key);
|
|
677
|
+
|
|
678
|
+
const isFocusing =
|
|
679
|
+
openingRouteKeys.includes(route.key) ||
|
|
680
|
+
[...closingRouteKeys, ...replacingRouteKeys].includes(
|
|
681
|
+
self[index + 1]?.key
|
|
682
|
+
);
|
|
683
|
+
|
|
684
|
+
const isBeforeLast = index === routes.length - 2;
|
|
685
|
+
|
|
686
|
+
// Keep animating, preloaded & last two screens visible for smoother transitions
|
|
687
|
+
const isVisible =
|
|
688
|
+
focused ||
|
|
689
|
+
isFocusing ||
|
|
690
|
+
isRemoving ||
|
|
691
|
+
isPreloaded ||
|
|
692
|
+
isNextScreenTransparent ||
|
|
693
|
+
index >= routes.length - 2;
|
|
694
|
+
|
|
695
|
+
const activityMode = // Render focused and animating screens normally
|
|
696
|
+
focused || isFocusing
|
|
697
|
+
? 'normal'
|
|
698
|
+
: inactiveBehavior === 'none' ||
|
|
699
|
+
// Unpause preloaded screens so updates are visible
|
|
700
|
+
// This lets preloaded screens initialize
|
|
701
|
+
isPreloaded ||
|
|
702
|
+
// Keep the screen before transparent screen active
|
|
703
|
+
// This lets the screen under the transparent screen update and animate
|
|
704
|
+
isNextScreenTransparent ||
|
|
705
|
+
// Keep the screen before last screen active
|
|
706
|
+
// Otherwise it breaks animation when going back
|
|
707
|
+
isBeforeLast
|
|
708
|
+
? 'inert'
|
|
709
|
+
: inactiveBehavior === 'unmount' &&
|
|
710
|
+
// Don't unmount screens that needs to stay visible
|
|
711
|
+
!isVisible &&
|
|
712
|
+
// Don't unmount screens with nested navigators
|
|
713
|
+
// So we don't lose their state
|
|
714
|
+
!('state' in route && route.state)
|
|
715
|
+
? 'unmounted'
|
|
716
|
+
: 'paused';
|
|
717
|
+
|
|
718
|
+
if (activityMode === 'unmounted') {
|
|
719
|
+
return null;
|
|
720
|
+
}
|
|
772
721
|
|
|
773
722
|
return (
|
|
774
|
-
<
|
|
723
|
+
<CardContainer
|
|
775
724
|
key={route.key}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
725
|
+
index={index}
|
|
726
|
+
interpolationIndex={interpolationIndex}
|
|
727
|
+
modal={isModal}
|
|
728
|
+
active={index === routes.length - 1}
|
|
729
|
+
focused={focused}
|
|
730
|
+
opening={openingRouteKeys.includes(route.key)}
|
|
731
|
+
closing={closingRouteKeys.includes(route.key)}
|
|
732
|
+
layout={layout}
|
|
733
|
+
gesture={gesture}
|
|
734
|
+
scene={scene}
|
|
735
|
+
safeAreaInsetTop={safeAreaInsetTop}
|
|
736
|
+
safeAreaInsetRight={safeAreaInsetRight}
|
|
737
|
+
safeAreaInsetBottom={safeAreaInsetBottom}
|
|
738
|
+
safeAreaInsetLeft={safeAreaInsetLeft}
|
|
739
|
+
onGestureStart={onGestureStart}
|
|
740
|
+
onGestureCancel={onGestureCancel}
|
|
741
|
+
onGestureEnd={onGestureEnd}
|
|
742
|
+
headerHeight={headerHeight}
|
|
743
|
+
isParentHeaderShown={isParentHeaderShown}
|
|
744
|
+
onHeaderHeightChange={this.handleHeaderLayout}
|
|
745
|
+
getPreviousScene={this.getPreviousScene}
|
|
746
|
+
getFocusedRoute={this.getFocusedRoute}
|
|
747
|
+
hasAbsoluteFloatHeader={
|
|
748
|
+
isFloatHeaderAbsolute && !headerTransparent
|
|
749
|
+
}
|
|
750
|
+
renderHeader={renderHeader}
|
|
751
|
+
onOpenRoute={onOpenRoute}
|
|
752
|
+
onCloseRoute={onCloseRoute}
|
|
753
|
+
onTransitionStart={onTransitionStart}
|
|
754
|
+
onTransitionEnd={onTransitionEnd}
|
|
755
|
+
isNextScreenTransparent={isNextScreenTransparent}
|
|
756
|
+
preloaded={isPreloaded}
|
|
782
757
|
>
|
|
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>
|
|
758
|
+
<ActivityView
|
|
759
|
+
mode={activityMode}
|
|
760
|
+
visible={isVisible}
|
|
761
|
+
style={StyleSheet.absoluteFill}
|
|
762
|
+
>
|
|
763
|
+
{scene.descriptor.render()}
|
|
764
|
+
</ActivityView>
|
|
765
|
+
</CardContainer>
|
|
819
766
|
);
|
|
820
767
|
})}
|
|
821
|
-
</
|
|
768
|
+
</View>
|
|
822
769
|
</View>
|
|
823
770
|
);
|
|
824
771
|
}
|
|
@@ -35,8 +35,8 @@ type Props = StackNavigationConfig & {
|
|
|
35
35
|
type State = {
|
|
36
36
|
// Local copy of the routes which are actually rendered
|
|
37
37
|
routes: Route<string>[];
|
|
38
|
-
// Previous
|
|
39
|
-
|
|
38
|
+
// Previous navigation state for comparison
|
|
39
|
+
previousState: StackNavigationState<ParamListBase> | undefined;
|
|
40
40
|
// Previous descriptors, to compare whether descriptors have changed or not
|
|
41
41
|
previousDescriptors: StackDescriptorMap;
|
|
42
42
|
// List of routes being opened, we need to animate pushing of these new routes
|
|
@@ -65,22 +65,52 @@ export class StackView extends React.Component<Props, State> {
|
|
|
65
65
|
state: Readonly<State>
|
|
66
66
|
) {
|
|
67
67
|
const allRoutes = [...props.state.routes, ...props.state.preloadedRoutes];
|
|
68
|
+
const previousRoutes = state.previousState
|
|
69
|
+
? [...state.previousState.routes, ...state.previousState.preloadedRoutes]
|
|
70
|
+
: [];
|
|
68
71
|
|
|
69
72
|
// If there was no change in routes, we don't need to compute anything
|
|
70
73
|
if (
|
|
71
74
|
isArrayEqual(
|
|
72
75
|
allRoutes.map((r) => r.key),
|
|
73
|
-
|
|
76
|
+
previousRoutes.map((r) => r.key)
|
|
74
77
|
) &&
|
|
75
78
|
state.routes.length
|
|
76
79
|
) {
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
// If there were any routes being closed or replaced,
|
|
81
|
+
// We need to make sure they are preserved in the new state from props.state
|
|
82
|
+
// So first we get all such routes from the previous state (that included the animating routes)
|
|
83
|
+
// Then we add them back to the new state if they don't already exist
|
|
84
|
+
|
|
85
|
+
const closingRoutes = state.routes.filter(
|
|
86
|
+
(r) =>
|
|
87
|
+
state.closingRouteKeys.includes(r.key) &&
|
|
88
|
+
!props.state.routes.some((pr) => pr.key === r.key)
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const replacingRoutes = state.routes.filter(
|
|
92
|
+
(r) =>
|
|
93
|
+
state.replacingRouteKeys.includes(r.key) &&
|
|
94
|
+
!props.state.routes.some((pr) => pr.key === r.key)
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
let routes: Route<string>[] = props.state.routes.slice();
|
|
98
|
+
|
|
99
|
+
// Replacing routes go before the focused route (they're being covered)
|
|
100
|
+
if (replacingRoutes.length) {
|
|
101
|
+
routes.splice(routes.length - 1, 0, ...replacingRoutes);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Closing routes go at the end (they're animating out on top)
|
|
105
|
+
if (closingRoutes.length) {
|
|
106
|
+
routes.push(...closingRoutes);
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
let descriptors = props.descriptors;
|
|
80
110
|
let previousDescriptors = state.previousDescriptors;
|
|
81
111
|
|
|
82
112
|
if (props.descriptors !== state.previousDescriptors) {
|
|
83
|
-
descriptors =
|
|
113
|
+
descriptors = routes.reduce<StackDescriptorMap>((acc, route) => {
|
|
84
114
|
acc[route.key] =
|
|
85
115
|
props.descriptors[route.key] || state.descriptors[route.key];
|
|
86
116
|
|
|
@@ -90,7 +120,7 @@ export class StackView extends React.Component<Props, State> {
|
|
|
90
120
|
previousDescriptors = props.descriptors;
|
|
91
121
|
}
|
|
92
122
|
|
|
93
|
-
if (!isArrayEqual(allRoutes,
|
|
123
|
+
if (!isArrayEqual(allRoutes, previousRoutes)) {
|
|
94
124
|
// if any route objects have changed, we should update them
|
|
95
125
|
const map = allRoutes.reduce<Record<string, Route<string>>>(
|
|
96
126
|
(acc, route) => {
|
|
@@ -100,13 +130,12 @@ export class StackView extends React.Component<Props, State> {
|
|
|
100
130
|
{}
|
|
101
131
|
);
|
|
102
132
|
|
|
103
|
-
routes =
|
|
104
|
-
previousRoutes = allRoutes;
|
|
133
|
+
routes = routes.map((route) => map[route.key] || route);
|
|
105
134
|
}
|
|
106
135
|
|
|
107
136
|
return {
|
|
108
137
|
routes,
|
|
109
|
-
|
|
138
|
+
previousState: props.state,
|
|
110
139
|
descriptors,
|
|
111
140
|
previousDescriptors,
|
|
112
141
|
};
|
|
@@ -122,14 +151,24 @@ export class StackView extends React.Component<Props, State> {
|
|
|
122
151
|
props.state.routes.slice(0, props.state.index + 1)
|
|
123
152
|
: props.state.routes;
|
|
124
153
|
|
|
125
|
-
// Now we need to determine which routes were added and removed
|
|
126
|
-
const { previousRoutes } = state;
|
|
127
|
-
|
|
128
154
|
let { openingRouteKeys, closingRouteKeys, replacingRouteKeys } = state;
|
|
129
155
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
156
|
+
// If a route that was closing or being replaced is now back in the routes,
|
|
157
|
+
// it was added back before the animation finished, so stop tracking it
|
|
158
|
+
closingRouteKeys = closingRouteKeys.filter(
|
|
159
|
+
(key) => !routes.some((r) => r.key === key)
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
replacingRouteKeys = replacingRouteKeys.filter(
|
|
163
|
+
(key) => !routes.some((r) => r.key === key)
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Get previous focused route from previousState (actual focused route, not last in previousRoutes
|
|
167
|
+
// which can be a preloaded route that was never focused)
|
|
168
|
+
const previousFocusedRoute = state.previousState
|
|
169
|
+
? state.previousState.routes[state.previousState.index]
|
|
170
|
+
: undefined;
|
|
171
|
+
|
|
133
172
|
const nextFocusedRoute = routes[routes.length - 1];
|
|
134
173
|
|
|
135
174
|
const isAnimationEnabled = (key: string) => {
|
|
@@ -233,6 +272,18 @@ export class StackView extends React.Component<Props, State> {
|
|
|
233
272
|
// After the push animation is completed, routes being replaced will be removed completely
|
|
234
273
|
routes = routes.slice();
|
|
235
274
|
routes.splice(routes.length - 1, 0, previousFocusedRoute);
|
|
275
|
+
|
|
276
|
+
// Preserve any other routes still being replaced from previous transitions
|
|
277
|
+
const previousReplacingRoutes = state.routes.filter(
|
|
278
|
+
(r) =>
|
|
279
|
+
replacingRouteKeys.includes(r.key) &&
|
|
280
|
+
!routes.some((existing) => existing.key === r.key)
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (previousReplacingRoutes.length) {
|
|
284
|
+
// Insert before the route we just added (previousFocusedRoute)
|
|
285
|
+
routes.splice(routes.length - 2, 0, ...previousReplacingRoutes);
|
|
286
|
+
}
|
|
236
287
|
}
|
|
237
288
|
}
|
|
238
289
|
}
|
|
@@ -266,7 +317,7 @@ export class StackView extends React.Component<Props, State> {
|
|
|
266
317
|
|
|
267
318
|
return {
|
|
268
319
|
routes,
|
|
269
|
-
|
|
320
|
+
previousState: props.state,
|
|
270
321
|
previousDescriptors: props.descriptors,
|
|
271
322
|
openingRouteKeys,
|
|
272
323
|
closingRouteKeys,
|
|
@@ -277,7 +328,7 @@ export class StackView extends React.Component<Props, State> {
|
|
|
277
328
|
|
|
278
329
|
state: State = {
|
|
279
330
|
routes: [],
|
|
280
|
-
|
|
331
|
+
previousState: undefined,
|
|
281
332
|
previousDescriptors: {},
|
|
282
333
|
openingRouteKeys: [],
|
|
283
334
|
closingRouteKeys: [],
|
|
@@ -328,20 +379,35 @@ export class StackView extends React.Component<Props, State> {
|
|
|
328
379
|
});
|
|
329
380
|
});
|
|
330
381
|
} else {
|
|
331
|
-
this.setState((state) =>
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
382
|
+
this.setState((state) => {
|
|
383
|
+
const routeIndex = state.routes.findIndex((r) => r.key === route.key);
|
|
384
|
+
|
|
385
|
+
// Remove replacing routes that were before the route that just opened
|
|
386
|
+
// Those were replaced by this or earlier routes and should be cleaned up
|
|
387
|
+
const replacingRoutesToRemove = new Set(
|
|
388
|
+
state.routes
|
|
389
|
+
.slice(0, routeIndex)
|
|
390
|
+
.filter((r) => state.replacingRouteKeys.includes(r.key))
|
|
391
|
+
.map((r) => r.key)
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
const newRoutes = state.routes.filter(
|
|
395
|
+
(r) => !replacingRoutesToRemove.has(r.key)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
return {
|
|
399
|
+
routes: newRoutes,
|
|
400
|
+
openingRouteKeys: state.openingRouteKeys.filter(
|
|
401
|
+
(key) => key !== route.key
|
|
402
|
+
),
|
|
403
|
+
closingRouteKeys: state.closingRouteKeys.filter(
|
|
404
|
+
(key) => key !== route.key
|
|
405
|
+
),
|
|
406
|
+
replacingRouteKeys: state.replacingRouteKeys.filter(
|
|
407
|
+
(key) => !replacingRoutesToRemove.has(key)
|
|
408
|
+
),
|
|
409
|
+
};
|
|
410
|
+
});
|
|
345
411
|
}
|
|
346
412
|
};
|
|
347
413
|
|
|
@@ -420,8 +486,13 @@ export class StackView extends React.Component<Props, State> {
|
|
|
420
486
|
...rest
|
|
421
487
|
} = this.props;
|
|
422
488
|
|
|
423
|
-
const {
|
|
424
|
-
|
|
489
|
+
const {
|
|
490
|
+
routes,
|
|
491
|
+
descriptors,
|
|
492
|
+
openingRouteKeys,
|
|
493
|
+
closingRouteKeys,
|
|
494
|
+
replacingRouteKeys,
|
|
495
|
+
} = this.state;
|
|
425
496
|
|
|
426
497
|
return (
|
|
427
498
|
<GestureHandlerWrapper style={styles.container}>
|
|
@@ -440,6 +511,7 @@ export class StackView extends React.Component<Props, State> {
|
|
|
440
511
|
routes={routes}
|
|
441
512
|
openingRouteKeys={openingRouteKeys}
|
|
442
513
|
closingRouteKeys={closingRouteKeys}
|
|
514
|
+
replacingRouteKeys={replacingRouteKeys}
|
|
443
515
|
onOpenRoute={this.handleOpenRoute}
|
|
444
516
|
onCloseRoute={this.handleCloseRoute}
|
|
445
517
|
onTransitionStart={this.handleTransitionStart}
|