@react-navigation/stack 8.0.0-alpha.11 → 8.0.0-alpha.13

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.
Files changed (35) hide show
  1. package/lib/module/views/Stack/Card.js +25 -20
  2. package/lib/module/views/Stack/Card.js.map +1 -1
  3. package/lib/module/views/Stack/CardA11yWrapper.js +1 -2
  4. package/lib/module/views/Stack/CardA11yWrapper.js.map +1 -1
  5. package/lib/module/views/Stack/CardContainer.js +3 -4
  6. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  7. package/lib/module/views/Stack/CardStack.js +56 -117
  8. package/lib/module/views/Stack/CardStack.js.map +1 -1
  9. package/lib/module/views/Stack/StackView.js +18 -14
  10. package/lib/module/views/Stack/StackView.js.map +1 -1
  11. package/lib/typescript/src/types.d.ts +20 -42
  12. package/lib/typescript/src/types.d.ts.map +1 -1
  13. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +1 -1
  14. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  15. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +2 -2
  16. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  17. package/lib/typescript/src/views/Stack/Card.d.ts +3 -3
  18. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  19. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -1
  20. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +1 -1
  21. package/lib/typescript/src/views/Stack/CardContainer.d.ts +2 -2
  22. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  23. package/lib/typescript/src/views/Stack/CardStack.d.ts +1 -1
  24. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  25. package/lib/typescript/src/views/Stack/StackView.d.ts +5 -15
  26. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  27. package/package.json +13 -13
  28. package/src/types.tsx +46 -61
  29. package/src/utils/gestureActivationCriteria.tsx +1 -1
  30. package/src/views/Header/HeaderSegment.tsx +2 -2
  31. package/src/views/Stack/Card.tsx +32 -23
  32. package/src/views/Stack/CardA11yWrapper.tsx +2 -14
  33. package/src/views/Stack/CardContainer.tsx +3 -4
  34. package/src/views/Stack/CardStack.tsx +82 -153
  35. package/src/views/Stack/StackView.tsx +34 -17
@@ -1,5 +1,8 @@
1
1
  import { getDefaultHeaderHeight } from '@react-navigation/elements';
2
- import { SafeAreaProviderCompat } from '@react-navigation/elements/internal';
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, activeStates, headerHeights } =
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
- <ScreenContainer
716
- enabled={detachInactiveScreens}
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,83 @@ export class CardStack extends React.Component<Props, State> {
762
671
  scenes[index + 1]?.descriptor.options.presentation ===
763
672
  'transparentModal';
764
673
 
765
- const detachCurrentScreen =
766
- scenes[index + 1]?.descriptor.options.detachPreviousScreen !==
767
- false;
674
+ const isRemoving =
675
+ replacingRouteKeys.includes(route.key) ||
676
+ closingRouteKeys.includes(route.key);
768
677
 
769
- const activityState = isPreloaded
770
- ? STATE_INACTIVE
771
- : activeStates[index];
678
+ const isFocusing =
679
+ openingRouteKeys.includes(route.key) ||
680
+ [...closingRouteKeys, ...replacingRouteKeys].includes(
681
+ self[index + 1]?.key
682
+ );
772
683
 
773
684
  return (
774
- <Screen
685
+ <CardContainer
775
686
  key={route.key}
776
- style={[StyleSheet.absoluteFill, { pointerEvents: 'box-none' }]}
777
- enabled={detachInactiveScreens}
778
- activityState={activityState}
779
- freezeOnBlur={freezeOnBlur}
780
- shouldFreeze={activityState === STATE_INACTIVE && !isPreloaded}
781
- homeIndicatorHidden={autoHideHomeIndicator}
687
+ index={index}
688
+ interpolationIndex={interpolationIndex}
689
+ modal={isModal}
690
+ active={index === routes.length - 1}
691
+ focused={focused}
692
+ opening={openingRouteKeys.includes(route.key)}
693
+ closing={closingRouteKeys.includes(route.key)}
694
+ layout={layout}
695
+ gesture={gesture}
696
+ scene={scene}
697
+ safeAreaInsetTop={safeAreaInsetTop}
698
+ safeAreaInsetRight={safeAreaInsetRight}
699
+ safeAreaInsetBottom={safeAreaInsetBottom}
700
+ safeAreaInsetLeft={safeAreaInsetLeft}
701
+ onGestureStart={onGestureStart}
702
+ onGestureCancel={onGestureCancel}
703
+ onGestureEnd={onGestureEnd}
704
+ headerHeight={headerHeight}
705
+ isParentHeaderShown={isParentHeaderShown}
706
+ onHeaderHeightChange={this.handleHeaderLayout}
707
+ getPreviousScene={this.getPreviousScene}
708
+ getFocusedRoute={this.getFocusedRoute}
709
+ hasAbsoluteFloatHeader={
710
+ isFloatHeaderAbsolute && !headerTransparent
711
+ }
712
+ renderHeader={renderHeader}
713
+ onOpenRoute={onOpenRoute}
714
+ onCloseRoute={onCloseRoute}
715
+ onTransitionStart={onTransitionStart}
716
+ onTransitionEnd={onTransitionEnd}
717
+ isNextScreenTransparent={isNextScreenTransparent}
718
+ preloaded={isPreloaded}
782
719
  >
783
- <CardContainer
784
- index={index}
785
- interpolationIndex={interpolationIndex}
786
- modal={isModal}
787
- active={index === routes.length - 1}
788
- focused={focused}
789
- opening={openingRouteKeys.includes(route.key)}
790
- closing={closingRouteKeys.includes(route.key)}
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
720
+ <ActivityView
721
+ mode={
722
+ // Render focused and animating screens normally
723
+ focused || isFocusing
724
+ ? 'normal'
725
+ : // Unpause preloaded screens so updates are visible
726
+ // This lets preloaded screens initialize
727
+ // And avoids things like pressable animation from being frozen
728
+ inactiveBehavior === 'none' ||
729
+ isPreloaded ||
730
+ isRemoving ||
731
+ isNextScreenTransparent
732
+ ? 'inert'
733
+ : 'paused'
734
+ }
735
+ visible={
736
+ // keep animating, preloaded & last two screens visible for smoother transitions
737
+ isFocusing ||
738
+ isRemoving ||
739
+ isPreloaded ||
740
+ isNextScreenTransparent ||
741
+ index >= routes.length - 2
808
742
  }
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>
743
+ style={StyleSheet.absoluteFill}
744
+ >
745
+ {scene.descriptor.render()}
746
+ </ActivityView>
747
+ </CardContainer>
819
748
  );
820
749
  })}
821
- </ScreenContainer>
750
+ </View>
822
751
  </View>
823
752
  );
824
753
  }
@@ -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 routes, to compare whether routes have changed or not
39
- previousRoutes: Route<string>[];
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,12 +65,15 @@ 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
- state.previousRoutes.map((r) => r.key)
76
+ previousRoutes.map((r) => r.key)
74
77
  ) &&
75
78
  state.routes.length
76
79
  ) {
@@ -103,7 +106,6 @@ export class StackView extends React.Component<Props, State> {
103
106
  routes.push(...closingRoutes);
104
107
  }
105
108
 
106
- let previousRoutes = state.previousRoutes;
107
109
  let descriptors = props.descriptors;
108
110
  let previousDescriptors = state.previousDescriptors;
109
111
 
@@ -118,7 +120,7 @@ export class StackView extends React.Component<Props, State> {
118
120
  previousDescriptors = props.descriptors;
119
121
  }
120
122
 
121
- if (!isArrayEqual(allRoutes, state.previousRoutes)) {
123
+ if (!isArrayEqual(allRoutes, previousRoutes)) {
122
124
  // if any route objects have changed, we should update them
123
125
  const map = allRoutes.reduce<Record<string, Route<string>>>(
124
126
  (acc, route) => {
@@ -129,12 +131,11 @@ export class StackView extends React.Component<Props, State> {
129
131
  );
130
132
 
131
133
  routes = routes.map((route) => map[route.key] || route);
132
- previousRoutes = allRoutes;
133
134
  }
134
135
 
135
136
  return {
136
137
  routes,
137
- previousRoutes,
138
+ previousState: props.state,
138
139
  descriptors,
139
140
  previousDescriptors,
140
141
  };
@@ -150,14 +151,24 @@ export class StackView extends React.Component<Props, State> {
150
151
  props.state.routes.slice(0, props.state.index + 1)
151
152
  : props.state.routes;
152
153
 
153
- // Now we need to determine which routes were added and removed
154
- const { previousRoutes } = state;
155
-
156
154
  let { openingRouteKeys, closingRouteKeys, replacingRouteKeys } = state;
157
155
 
158
- const previousFocusedRoute = previousRoutes[previousRoutes.length - 1] as
159
- | Route<string>
160
- | undefined;
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
+
161
172
  const nextFocusedRoute = routes[routes.length - 1];
162
173
 
163
174
  const isAnimationEnabled = (key: string) => {
@@ -306,7 +317,7 @@ export class StackView extends React.Component<Props, State> {
306
317
 
307
318
  return {
308
319
  routes,
309
- previousRoutes: [...props.state.routes, ...props.state.preloadedRoutes],
320
+ previousState: props.state,
310
321
  previousDescriptors: props.descriptors,
311
322
  openingRouteKeys,
312
323
  closingRouteKeys,
@@ -317,7 +328,7 @@ export class StackView extends React.Component<Props, State> {
317
328
 
318
329
  state: State = {
319
330
  routes: [],
320
- previousRoutes: [],
331
+ previousState: undefined,
321
332
  previousDescriptors: {},
322
333
  openingRouteKeys: [],
323
334
  closingRouteKeys: [],
@@ -475,8 +486,13 @@ export class StackView extends React.Component<Props, State> {
475
486
  ...rest
476
487
  } = this.props;
477
488
 
478
- const { routes, descriptors, openingRouteKeys, closingRouteKeys } =
479
- this.state;
489
+ const {
490
+ routes,
491
+ descriptors,
492
+ openingRouteKeys,
493
+ closingRouteKeys,
494
+ replacingRouteKeys,
495
+ } = this.state;
480
496
 
481
497
  return (
482
498
  <GestureHandlerWrapper style={styles.container}>
@@ -495,6 +511,7 @@ export class StackView extends React.Component<Props, State> {
495
511
  routes={routes}
496
512
  openingRouteKeys={openingRouteKeys}
497
513
  closingRouteKeys={closingRouteKeys}
514
+ replacingRouteKeys={replacingRouteKeys}
498
515
  onOpenRoute={this.handleOpenRoute}
499
516
  onCloseRoute={this.handleCloseRoute}
500
517
  onTransitionStart={this.handleTransitionStart}