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

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
  });
@@ -622,6 +647,8 @@ export class CardStack extends React.Component<Props, State> {
622
647
  scenes.findLast((scene) => scene.route.key === focusedRoute.key)
623
648
  ?.descriptor.options.headerMode === 'float';
624
649
 
650
+ const focusedHeaderHeight = headerHeights[focusedRoute.key];
651
+
625
652
  return (
626
653
  <View style={styles.container}>
627
654
  {renderHeader({
@@ -637,7 +664,7 @@ export class CardStack extends React.Component<Props, State> {
637
664
  // The header title can't be found with maestro without this
638
665
  // In some cases, header buttons may not be touchable as well
639
666
  isFloatHeaderFocused && {
640
- height: headerHeights[focusedRoute.key],
667
+ height: focusedHeaderHeight?.value,
641
668
  },
642
669
  ],
643
670
  ],
@@ -647,12 +674,12 @@ export class CardStack extends React.Component<Props, State> {
647
674
  const focused = focusedRoute.key === route.key;
648
675
  const gesture = gestures[route.key];
649
676
  const scene = scenes[index];
650
- const isInactive = isInactiveRoute(route, this.props.routes);
651
677
 
652
678
  const {
653
679
  inactiveBehavior = 'pause',
654
680
  animation,
655
681
  headerShown = true,
682
+ headerMode,
656
683
  headerTransparent,
657
684
  } = scene.descriptor.options;
658
685
 
@@ -662,7 +689,9 @@ export class CardStack extends React.Component<Props, State> {
662
689
  const safeAreaInsetLeft = insets.left;
663
690
 
664
691
  const headerHeight =
665
- headerShown !== false ? headerHeights[route.key] : 0;
692
+ headerShown !== false
693
+ ? headerHeights[route.key]
694
+ : HIDDEN_HEADER_HEIGHT;
666
695
 
667
696
  // Start from current card and count backwards the number of cards with same interpolation
668
697
  const interpolationIndex = getInterpolationIndex(scenes, index);
@@ -696,25 +725,36 @@ export class CardStack extends React.Component<Props, State> {
696
725
  self[index + 1]?.key
697
726
  );
698
727
 
728
+ const isInactive = isInactiveRoute(route, this.props.routes);
729
+ const isRetained = state.retainedRouteKeys?.includes(route.key);
730
+ const isPreloaded = isInactive && !isRetained;
731
+
732
+ const isTopmost = index === routes.length - 1;
699
733
  const isBeforeLast = index === routes.length - 2;
700
734
 
701
735
  // Keep animating and the last two rendered routes visible for smoother transitions
702
736
  const isVisible =
703
737
  focused ||
704
738
  isNextScreenTransparent ||
739
+ // React native destroys the view when hidden
740
+ // So we keep retained screens visible
741
+ // This makes sure any videos being played don't stop etc.
742
+ isRetained ||
705
743
  // We only need to keep other screens visible when animation is enabled
706
744
  (isAnimationEnabled &&
707
745
  (isFocusing ||
708
746
  isRemoving ||
709
- // Preloaded and retained screens should stay mounted, but remain hidden until focused
710
- (!isInactive && index >= routes.length - 2)));
747
+ // Preloaded screens should stay mounted, but remain hidden until focused
748
+ (!isPreloaded && index >= routes.length - 2)));
711
749
 
712
750
  const activityMode = // Render focused and animating screens normally
713
751
  focused || isFocusing
714
752
  ? 'normal'
715
753
  : inactiveBehavior === 'none' ||
716
754
  // Unpause preloaded or retained screens so updates are visible
755
+ // Handle retained explicitly as isInactive won't be updated until animation end
717
756
  isInactive ||
757
+ isRetained ||
718
758
  // Keep the screen before transparent screen active
719
759
  // This lets the screen under the transparent screen update and animate
720
760
  isNextScreenTransparent ||
@@ -736,49 +776,62 @@ export class CardStack extends React.Component<Props, State> {
736
776
  }
737
777
 
738
778
  return (
739
- <CardContainer
779
+ <ActivityView
740
780
  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}
781
+ mode={activityMode}
782
+ visible={isVisible}
783
+ style={{
784
+ ...StyleSheet.absoluteFill,
785
+ // Avoid unfocused larger pages increasing scroll area
786
+ // e.g. when a smaller screen is pushed over a larger one on web
787
+ overflow: isTopmost ? undefined : 'hidden',
788
+ marginTop:
789
+ headerTransparent || headerMode === 'screen'
790
+ ? 0
791
+ : // Add offset for non-transparent floating header
792
+ isFloatHeaderAbsolute
793
+ ? // If header is absolute, offset the card by the header height
794
+ headerHeight?.value
795
+ : // Otherwise, the in-flow float header already takes the
796
+ // focused header's space, so offset by the difference
797
+ headerHeight && focusedHeaderHeight
798
+ ? headerHeight.value - focusedHeaderHeight.value
799
+ : 0,
800
+ }}
773
801
  >
774
- <ActivityView
775
- mode={activityMode}
776
- visible={isVisible}
777
- style={StyleSheet.absoluteFill}
802
+ <CardContainer
803
+ index={index}
804
+ interpolationIndex={interpolationIndex}
805
+ modal={isModal}
806
+ active={isTopmost}
807
+ focused={focused}
808
+ opening={openingRouteKeys.includes(route.key)}
809
+ closing={closingRouteKeys.includes(route.key)}
810
+ layout={layout}
811
+ gesture={gesture}
812
+ scene={scene}
813
+ safeAreaInsetTop={safeAreaInsetTop}
814
+ safeAreaInsetRight={safeAreaInsetRight}
815
+ safeAreaInsetBottom={safeAreaInsetBottom}
816
+ safeAreaInsetLeft={safeAreaInsetLeft}
817
+ onGestureStart={onGestureStart}
818
+ onGestureCancel={onGestureCancel}
819
+ onGestureEnd={onGestureEnd}
820
+ headerHeight={headerHeight}
821
+ isParentHeaderShown={isParentHeaderShown}
822
+ onHeaderHeightChange={this.handleHeaderLayout}
823
+ getPreviousScene={this.getPreviousScene}
824
+ getFocusedRoute={this.getFocusedRoute}
825
+ renderHeader={renderHeader}
826
+ onOpenRoute={onOpenRoute}
827
+ onCloseRoute={onCloseRoute}
828
+ onTransitionStart={onTransitionStart}
829
+ onTransitionEnd={onTransitionEnd}
830
+ preloaded={isInactive}
778
831
  >
779
832
  {scene.descriptor.render()}
780
- </ActivityView>
781
- </CardContainer>
833
+ </CardContainer>
834
+ </ActivityView>
782
835
  );
783
836
  })}
784
837
  </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
- }