@react-navigation/stack 8.0.0-alpha.30 → 8.0.0-alpha.32

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.
@@ -48,13 +48,18 @@ import type {
48
48
  } from '../../types';
49
49
  import { getDistanceForDirection } from '../../utils/getDistanceForDirection';
50
50
  import { getModalRouteKeys } from '../../utils/getModalRoutesKeys';
51
- import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
51
+ import type {
52
+ HeaderHeight,
53
+ Props as HeaderContainerProps,
54
+ } from '../Header/HeaderContainer';
52
55
  import { CardContainer } from './CardContainer';
53
56
 
54
57
  type GestureValues = {
55
58
  [key: string]: Animated.Value;
56
59
  };
57
60
 
61
+ type HeaderHeights = Record<string, HeaderHeight>;
62
+
58
63
  type Props = {
59
64
  direction: LocaleDirection;
60
65
  insets: EdgeInsets;
@@ -89,7 +94,7 @@ type State = {
89
94
  scenes: Scene[];
90
95
  gestures: GestureValues;
91
96
  layout: Layout;
92
- headerHeights: Record<string, number>;
97
+ headerHeights: HeaderHeights;
93
98
  };
94
99
 
95
100
  const NAMED_TRANSITIONS_PRESETS = {
@@ -110,6 +115,8 @@ const NAMED_TRANSITIONS_PRESETS = {
110
115
 
111
116
  const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
112
117
 
118
+ const HIDDEN_HEADER_HEIGHT = { measured: false, value: 0 };
119
+
113
120
  const getInterpolationIndex = (scenes: Scene[], index: number) => {
114
121
  const { cardStyleInterpolator } = scenes[index].descriptor.options;
115
122
 
@@ -162,9 +169,9 @@ const getHeaderHeights = (
162
169
  isParentHeaderShown: boolean,
163
170
  isParentModal: boolean,
164
171
  layout: Layout,
165
- previous: Record<string, number>
172
+ previous: HeaderHeights
166
173
  ) => {
167
- return scenes.reduce<Record<string, number>>((acc, curr, index) => {
174
+ return scenes.reduce<HeaderHeights>((acc, curr, index) => {
168
175
  const {
169
176
  headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
170
177
  headerStyle,
@@ -175,19 +182,34 @@ const getHeaderHeights = (
175
182
  const height =
176
183
  'height' in style && typeof style.height === 'number'
177
184
  ? style.height
178
- : previous[curr.route.key];
185
+ : undefined;
179
186
 
180
187
  const interpolationIndex = getInterpolationIndex(scenes, index);
181
188
  const isModal = getIsModal(curr, interpolationIndex, isParentModal);
182
189
 
183
- acc[curr.route.key] =
184
- typeof height === 'number'
185
- ? height
186
- : getDefaultHeaderHeight({
187
- landscape: layout.width > layout.height,
188
- modalPresentation: isModal,
189
- topInset: headerStatusBarHeight,
190
- });
190
+ if (typeof height === 'number') {
191
+ acc[curr.route.key] = { measured: true, value: height };
192
+ } else {
193
+ const previousHeight = previous[curr.route.key];
194
+ const defaultHeight = getDefaultHeaderHeight({
195
+ landscape: layout.width > layout.height,
196
+ modalPresentation: isModal,
197
+ topInset: headerStatusBarHeight,
198
+ });
199
+
200
+ // If we hadn't measured before, use re-computed default height
201
+ if (
202
+ !previousHeight?.measured &&
203
+ defaultHeight !== previousHeight?.value
204
+ ) {
205
+ acc[curr.route.key] = {
206
+ measured: false,
207
+ value: defaultHeight,
208
+ };
209
+ } else if (previousHeight) {
210
+ acc[curr.route.key] = previousHeight;
211
+ }
212
+ }
191
213
 
192
214
  return acc;
193
215
  }, {});
@@ -541,14 +563,17 @@ export class CardStack extends React.Component<Props, State> {
541
563
  this.setState(({ headerHeights }) => {
542
564
  const previousHeight = headerHeights[route.key];
543
565
 
544
- if (previousHeight === height) {
566
+ if (previousHeight?.value === height) {
545
567
  return null;
546
568
  }
547
569
 
548
570
  return {
549
571
  headerHeights: {
550
572
  ...headerHeights,
551
- [route.key]: height,
573
+ [route.key]: {
574
+ measured: true,
575
+ value: height,
576
+ },
552
577
  },
553
578
  };
554
579
  });
@@ -603,7 +628,14 @@ export class CardStack extends React.Component<Props, State> {
603
628
 
604
629
  const focusedRoute = state.routes[state.index];
605
630
 
606
- const isFloatHeaderAbsolute = scenes.slice(-2).some((scene) => {
631
+ // Render only two top-most active headers as a workaround for
632
+ // https://github.com/react-navigation/react-navigation/issues/12456.
633
+ // If the header is persisted, it might be placed incorrectly when navigating back
634
+ const activeHeaderScenes = scenes
635
+ .filter((scene) => !isInactiveRoute(scene.route, routes))
636
+ .slice(-2);
637
+
638
+ const isFloatHeaderAbsolute = activeHeaderScenes.some((scene) => {
607
639
  const options = scene.descriptor.options ?? {};
608
640
  const { headerMode, headerTransparent, headerShown = true } = options;
609
641
 
@@ -622,11 +654,13 @@ export class CardStack extends React.Component<Props, State> {
622
654
  scenes.findLast((scene) => scene.route.key === focusedRoute.key)
623
655
  ?.descriptor.options.headerMode === 'float';
624
656
 
657
+ const focusedHeaderHeight = headerHeights[focusedRoute.key];
658
+
625
659
  return (
626
660
  <View style={styles.container}>
627
661
  {renderHeader({
628
662
  mode: 'float',
629
- scenes,
663
+ scenes: activeHeaderScenes,
630
664
  getPreviousScene: this.getPreviousScene,
631
665
  getFocusedRoute: this.getFocusedRoute,
632
666
  contentHeight: headerHeights[focusedRoute.key],
@@ -637,7 +671,7 @@ export class CardStack extends React.Component<Props, State> {
637
671
  // The header title can't be found with maestro without this
638
672
  // In some cases, header buttons may not be touchable as well
639
673
  isFloatHeaderFocused && {
640
- height: headerHeights[focusedRoute.key],
674
+ height: focusedHeaderHeight?.value,
641
675
  },
642
676
  ],
643
677
  ],
@@ -647,12 +681,12 @@ export class CardStack extends React.Component<Props, State> {
647
681
  const focused = focusedRoute.key === route.key;
648
682
  const gesture = gestures[route.key];
649
683
  const scene = scenes[index];
650
- const isInactive = isInactiveRoute(route, this.props.routes);
651
684
 
652
685
  const {
653
686
  inactiveBehavior = 'pause',
654
687
  animation,
655
688
  headerShown = true,
689
+ headerMode,
656
690
  headerTransparent,
657
691
  } = scene.descriptor.options;
658
692
 
@@ -662,7 +696,9 @@ export class CardStack extends React.Component<Props, State> {
662
696
  const safeAreaInsetLeft = insets.left;
663
697
 
664
698
  const headerHeight =
665
- headerShown !== false ? headerHeights[route.key] : 0;
699
+ headerShown !== false
700
+ ? headerHeights[route.key]
701
+ : HIDDEN_HEADER_HEIGHT;
666
702
 
667
703
  // Start from current card and count backwards the number of cards with same interpolation
668
704
  const interpolationIndex = getInterpolationIndex(scenes, index);
@@ -696,25 +732,36 @@ export class CardStack extends React.Component<Props, State> {
696
732
  self[index + 1]?.key
697
733
  );
698
734
 
735
+ const isInactive = isInactiveRoute(route, this.props.routes);
736
+ const isRetained = state.retainedRouteKeys?.includes(route.key);
737
+ const isPreloaded = isInactive && !isRetained;
738
+
739
+ const isTopmost = index === routes.length - 1;
699
740
  const isBeforeLast = index === routes.length - 2;
700
741
 
701
742
  // Keep animating and the last two rendered routes visible for smoother transitions
702
743
  const isVisible =
703
744
  focused ||
704
745
  isNextScreenTransparent ||
746
+ // React native destroys the view when hidden
747
+ // So we keep retained screens visible
748
+ // This makes sure any videos being played don't stop etc.
749
+ isRetained ||
705
750
  // We only need to keep other screens visible when animation is enabled
706
751
  (isAnimationEnabled &&
707
752
  (isFocusing ||
708
753
  isRemoving ||
709
- // Preloaded and retained screens should stay mounted, but remain hidden until focused
710
- (!isInactive && index >= routes.length - 2)));
754
+ // Preloaded screens should stay mounted, but remain hidden until focused
755
+ (!isPreloaded && index >= routes.length - 2)));
711
756
 
712
757
  const activityMode = // Render focused and animating screens normally
713
758
  focused || isFocusing
714
759
  ? 'normal'
715
760
  : inactiveBehavior === 'none' ||
716
761
  // Unpause preloaded or retained screens so updates are visible
762
+ // Handle retained explicitly as isInactive won't be updated until animation end
717
763
  isInactive ||
764
+ isRetained ||
718
765
  // Keep the screen before transparent screen active
719
766
  // This lets the screen under the transparent screen update and animate
720
767
  isNextScreenTransparent ||
@@ -736,49 +783,62 @@ export class CardStack extends React.Component<Props, State> {
736
783
  }
737
784
 
738
785
  return (
739
- <CardContainer
786
+ <ActivityView
740
787
  key={route.key}
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}
788
+ mode={activityMode}
789
+ visible={isVisible}
790
+ style={{
791
+ ...StyleSheet.absoluteFill,
792
+ // Avoid unfocused larger pages increasing scroll area
793
+ // e.g. when a smaller screen is pushed over a larger one on web
794
+ overflow: isTopmost ? undefined : 'hidden',
795
+ marginTop:
796
+ headerTransparent || headerMode === 'screen'
797
+ ? 0
798
+ : // Add offset for non-transparent floating header
799
+ isFloatHeaderAbsolute
800
+ ? // If header is absolute, offset the card by the header height
801
+ headerHeight?.value
802
+ : // Otherwise, the in-flow float header already takes the
803
+ // focused header's space, so offset by the difference
804
+ headerHeight && focusedHeaderHeight
805
+ ? headerHeight.value - focusedHeaderHeight.value
806
+ : 0,
807
+ }}
773
808
  >
774
- <ActivityView
775
- mode={activityMode}
776
- visible={isVisible}
777
- style={StyleSheet.absoluteFill}
809
+ <CardContainer
810
+ index={index}
811
+ interpolationIndex={interpolationIndex}
812
+ modal={isModal}
813
+ active={isTopmost}
814
+ focused={focused}
815
+ opening={openingRouteKeys.includes(route.key)}
816
+ closing={closingRouteKeys.includes(route.key)}
817
+ layout={layout}
818
+ gesture={gesture}
819
+ scene={scene}
820
+ safeAreaInsetTop={safeAreaInsetTop}
821
+ safeAreaInsetRight={safeAreaInsetRight}
822
+ safeAreaInsetBottom={safeAreaInsetBottom}
823
+ safeAreaInsetLeft={safeAreaInsetLeft}
824
+ onGestureStart={onGestureStart}
825
+ onGestureCancel={onGestureCancel}
826
+ onGestureEnd={onGestureEnd}
827
+ headerHeight={headerHeight}
828
+ isParentHeaderShown={isParentHeaderShown}
829
+ onHeaderHeightChange={this.handleHeaderLayout}
830
+ getPreviousScene={this.getPreviousScene}
831
+ getFocusedRoute={this.getFocusedRoute}
832
+ renderHeader={renderHeader}
833
+ onOpenRoute={onOpenRoute}
834
+ onCloseRoute={onCloseRoute}
835
+ onTransitionStart={onTransitionStart}
836
+ onTransitionEnd={onTransitionEnd}
837
+ preloaded={isInactive}
778
838
  >
779
839
  {scene.descriptor.render()}
780
- </ActivityView>
781
- </CardContainer>
840
+ </CardContainer>
841
+ </ActivityView>
782
842
  );
783
843
  })}
784
844
  </View>
@@ -1,42 +0,0 @@
1
- "use strict";
2
-
3
- import * as React from 'react';
4
- import { Platform, StyleSheet, View } from 'react-native';
5
- import { jsx as _jsx } from "react/jsx-runtime";
6
- export function CardA11yWrapper({
7
- focused,
8
- active,
9
- animated,
10
- isNextScreenTransparent,
11
- children,
12
- ref
13
- }) {
14
- // Manage this in separate component to avoid re-rendering card during gestures
15
- // Otherwise the gesture animation will be interrupted as state hasn't updated yet
16
- const [inert, setInert] = React.useState(false);
17
- React.useImperativeHandle(ref, () => ({
18
- setInert
19
- }), []);
20
- const isHidden = !animated && isNextScreenTransparent === false && !focused;
21
- return /*#__PURE__*/_jsx(View, {
22
- "aria-hidden": !focused,
23
- style: [StyleSheet.absoluteFill, {
24
- pointerEvents: (animated ? inert : !focused) ? 'none' : 'box-none',
25
- // This is necessary to avoid unfocused larger pages increasing scroll area
26
- // The issue can be seen on the web when a smaller screen is pushed over a larger one
27
- overflow: active ? undefined : 'hidden',
28
- // We use visibility on web
29
- display: Platform.OS !== 'web' && isHidden ? 'none' : 'flex',
30
- // Hide unfocused screens when animation isn't enabled
31
- // This is also necessary for a11y on web
32
- // @ts-expect-error visibility is only available on web
33
- visibility: isHidden ? 'hidden' : 'visible'
34
- }]
35
- // Make sure this view is not removed on the new architecture, as it causes focus loss during navigation on Android.
36
- // This can happen when the view flattening results in different trees - due to `overflow` style changing in a parent.
37
- ,
38
- collapsable: false,
39
- children: children
40
- });
41
- }
42
- //# sourceMappingURL=CardA11yWrapper.js.map
@@ -1 +0,0 @@
1
- {"version":3,"names":["React","Platform","StyleSheet","View","jsx","_jsx","CardA11yWrapper","focused","active","animated","isNextScreenTransparent","children","ref","inert","setInert","useState","useImperativeHandle","isHidden","style","absoluteFill","pointerEvents","overflow","undefined","display","OS","visibility","collapsable"],"sourceRoot":"../../../../src","sources":["views/Stack/CardA11yWrapper.tsx"],"mappings":";;AAAA,OAAO,KAAKA,KAAK,MAAM,OAAO;AAC9B,SAASC,QAAQ,EAAEC,UAAU,EAAEC,IAAI,QAAQ,cAAc;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAa1D,OAAO,SAASC,eAAeA,CAAC;EAC9BC,OAAO;EACPC,MAAM;EACNC,QAAQ;EACRC,uBAAuB;EACvBC,QAAQ;EACRC;AACK,CAAC,EAAE;EACR;EACA;EACA,MAAM,CAACC,KAAK,EAAEC,QAAQ,CAAC,GAAGd,KAAK,CAACe,QAAQ,CAAC,KAAK,CAAC;EAE/Cf,KAAK,CAACgB,mBAAmB,CAACJ,GAAG,EAAE,OAAO;IAAEE;EAAS,CAAC,CAAC,EAAE,EAAE,CAAC;EAExD,MAAMG,QAAQ,GAAG,CAACR,QAAQ,IAAIC,uBAAuB,KAAK,KAAK,IAAI,CAACH,OAAO;EAE3E,oBACEF,IAAA,CAACF,IAAI;IACH,eAAa,CAACI,OAAQ;IACtBW,KAAK,EAAE,CACLhB,UAAU,CAACiB,YAAY,EACvB;MACEC,aAAa,EAAE,CAACX,QAAQ,GAAGI,KAAK,GAAG,CAACN,OAAO,IAAI,MAAM,GAAG,UAAU;MAClE;MACA;MACAc,QAAQ,EAAEb,MAAM,GAAGc,SAAS,GAAG,QAAQ;MACvC;MACAC,OAAO,EAAEtB,QAAQ,CAACuB,EAAE,KAAK,KAAK,IAAIP,QAAQ,GAAG,MAAM,GAAG,MAAM;MAC5D;MACA;MACA;MACAQ,UAAU,EAAER,QAAQ,GAAG,QAAQ,GAAG;IACpC,CAAC;IAEH;IACA;IAAA;IACAS,WAAW,EAAE,KAAM;IAAAf,QAAA,EAElBA;EAAQ,CACL,CAAC;AAEX","ignoreList":[]}
@@ -1,15 +0,0 @@
1
- import * as React from 'react';
2
- type Props = {
3
- focused: boolean;
4
- active: boolean;
5
- animated: boolean;
6
- isNextScreenTransparent: boolean;
7
- children: React.ReactNode;
8
- ref?: React.Ref<CardA11yWrapperRef>;
9
- };
10
- export type CardA11yWrapperRef = {
11
- setInert: (value: boolean) => void;
12
- };
13
- export declare function CardA11yWrapper({ focused, active, animated, isNextScreenTransparent, children, ref, }: Props): import("react/jsx-runtime").JSX.Element;
14
- export {};
15
- //# sourceMappingURL=CardA11yWrapper.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CardA11yWrapper.d.ts","sourceRoot":"","sources":["../../../../../src/views/Stack/CardA11yWrapper.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,KAAK,KAAK,GAAG;IACX,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,uBAAuB,EAAE,OAAO,CAAC;IACjC,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;CACrC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAAE,CAAC;AAExE,wBAAgB,eAAe,CAAC,EAC9B,OAAO,EACP,MAAM,EACN,QAAQ,EACR,uBAAuB,EACvB,QAAQ,EACR,GAAG,GACJ,EAAE,KAAK,2CAkCP"}
@@ -1,56 +0,0 @@
1
- import * as React from 'react';
2
- import { Platform, StyleSheet, View } from 'react-native';
3
-
4
- type Props = {
5
- focused: boolean;
6
- active: boolean;
7
- animated: boolean;
8
- isNextScreenTransparent: boolean;
9
- children: React.ReactNode;
10
- ref?: React.Ref<CardA11yWrapperRef>;
11
- };
12
-
13
- export type CardA11yWrapperRef = { setInert: (value: boolean) => void };
14
-
15
- export function CardA11yWrapper({
16
- focused,
17
- active,
18
- animated,
19
- isNextScreenTransparent,
20
- children,
21
- ref,
22
- }: Props) {
23
- // Manage this in separate component to avoid re-rendering card during gestures
24
- // Otherwise the gesture animation will be interrupted as state hasn't updated yet
25
- const [inert, setInert] = React.useState(false);
26
-
27
- React.useImperativeHandle(ref, () => ({ setInert }), []);
28
-
29
- const isHidden = !animated && isNextScreenTransparent === false && !focused;
30
-
31
- return (
32
- <View
33
- aria-hidden={!focused}
34
- style={[
35
- StyleSheet.absoluteFill,
36
- {
37
- pointerEvents: (animated ? inert : !focused) ? 'none' : 'box-none',
38
- // This is necessary to avoid unfocused larger pages increasing scroll area
39
- // The issue can be seen on the web when a smaller screen is pushed over a larger one
40
- overflow: active ? undefined : 'hidden',
41
- // We use visibility on web
42
- display: Platform.OS !== 'web' && isHidden ? 'none' : 'flex',
43
- // Hide unfocused screens when animation isn't enabled
44
- // This is also necessary for a11y on web
45
- // @ts-expect-error visibility is only available on web
46
- visibility: isHidden ? 'hidden' : 'visible',
47
- },
48
- ]}
49
- // Make sure this view is not removed on the new architecture, as it causes focus loss during navigation on Android.
50
- // This can happen when the view flattening results in different trees - due to `overflow` style changing in a parent.
51
- collapsable={false}
52
- >
53
- {children}
54
- </View>
55
- );
56
- }