@react-navigation/stack 8.0.0-alpha.4 → 8.0.0-alpha.41

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 (126) hide show
  1. package/lib/module/index.js +2 -2
  2. package/lib/module/index.js.map +1 -1
  3. package/lib/module/navigators/createStackNavigator.js +3 -7
  4. package/lib/module/navigators/createStackNavigator.js.map +1 -1
  5. package/lib/module/utils/GestureHandlerContext.js +5 -0
  6. package/lib/module/utils/GestureHandlerContext.js.map +1 -0
  7. package/lib/module/utils/gestureActivationCriteria.js +10 -8
  8. package/lib/module/utils/gestureActivationCriteria.js.map +1 -1
  9. package/lib/module/utils/useCardAnimation.js +1 -1
  10. package/lib/module/utils/useCardAnimation.js.map +1 -1
  11. package/lib/module/utils/useGestureHandler.js +9 -0
  12. package/lib/module/utils/useGestureHandler.js.map +1 -0
  13. package/lib/module/utils/useKeyboardManager.js +71 -26
  14. package/lib/module/utils/useKeyboardManager.js.map +1 -1
  15. package/lib/module/views/GestureHandler.js +15 -14
  16. package/lib/module/views/GestureHandler.js.map +1 -1
  17. package/lib/module/views/GestureHandlerNative.js +16 -10
  18. package/lib/module/views/GestureHandlerNative.js.map +1 -1
  19. package/lib/module/views/Header/Header.js +2 -2
  20. package/lib/module/views/Header/Header.js.map +1 -1
  21. package/lib/module/views/Header/HeaderContainer.js +52 -18
  22. package/lib/module/views/Header/HeaderContainer.js.map +1 -1
  23. package/lib/module/views/Header/HeaderSegment.js +2 -2
  24. package/lib/module/views/Header/HeaderSegment.js.map +1 -1
  25. package/lib/module/views/Stack/Card.js +90 -87
  26. package/lib/module/views/Stack/Card.js.map +1 -1
  27. package/lib/module/views/Stack/CardContainer.js +72 -98
  28. package/lib/module/views/Stack/CardContainer.js.map +1 -1
  29. package/lib/module/views/Stack/CardStack.js +171 -126
  30. package/lib/module/views/Stack/CardStack.js.map +1 -1
  31. package/lib/module/views/Stack/StackView.js +103 -53
  32. package/lib/module/views/Stack/StackView.js.map +1 -1
  33. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts +1 -1
  34. package/lib/typescript/src/TransitionConfigs/CardStyleInterpolators.d.ts.map +1 -1
  35. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts +1 -1
  36. package/lib/typescript/src/TransitionConfigs/HeaderStyleInterpolators.d.ts.map +1 -1
  37. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts +1 -1
  38. package/lib/typescript/src/TransitionConfigs/TransitionPresets.d.ts.map +1 -1
  39. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts +1 -1
  40. package/lib/typescript/src/TransitionConfigs/TransitionSpecs.d.ts.map +1 -1
  41. package/lib/typescript/src/index.d.ts +12 -12
  42. package/lib/typescript/src/index.d.ts.map +1 -1
  43. package/lib/typescript/src/navigators/createStackNavigator.d.ts +10 -15
  44. package/lib/typescript/src/navigators/createStackNavigator.d.ts.map +1 -1
  45. package/lib/typescript/src/types.d.ts +46 -53
  46. package/lib/typescript/src/types.d.ts.map +1 -1
  47. package/lib/typescript/src/utils/CardAnimationContext.d.ts +1 -1
  48. package/lib/typescript/src/utils/CardAnimationContext.d.ts.map +1 -1
  49. package/lib/typescript/src/utils/GestureHandlerContext.d.ts +3 -0
  50. package/lib/typescript/src/utils/GestureHandlerContext.d.ts.map +1 -0
  51. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts +4 -51
  52. package/lib/typescript/src/utils/gestureActivationCriteria.d.ts.map +1 -1
  53. package/lib/typescript/src/utils/getDistanceForDirection.d.ts +1 -1
  54. package/lib/typescript/src/utils/getDistanceForDirection.d.ts.map +1 -1
  55. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts +1 -1
  56. package/lib/typescript/src/utils/getInvertedMultiplier.d.ts.map +1 -1
  57. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
  58. package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
  59. package/lib/typescript/src/utils/useCardAnimation.d.ts +1 -1
  60. package/lib/typescript/src/utils/useCardAnimation.d.ts.map +1 -1
  61. package/lib/typescript/src/utils/useGestureHandler.d.ts +2 -0
  62. package/lib/typescript/src/utils/useGestureHandler.d.ts.map +1 -0
  63. package/lib/typescript/src/utils/useKeyboardManager.d.ts +12 -2
  64. package/lib/typescript/src/utils/useKeyboardManager.d.ts.map +1 -1
  65. package/lib/typescript/src/views/GestureHandler.android.d.ts +1 -1
  66. package/lib/typescript/src/views/GestureHandler.android.d.ts.map +1 -1
  67. package/lib/typescript/src/views/GestureHandler.d.ts +7 -11
  68. package/lib/typescript/src/views/GestureHandler.d.ts.map +1 -1
  69. package/lib/typescript/src/views/GestureHandler.ios.d.ts +1 -1
  70. package/lib/typescript/src/views/GestureHandler.ios.d.ts.map +1 -1
  71. package/lib/typescript/src/views/GestureHandlerNative.d.ts +9 -4
  72. package/lib/typescript/src/views/GestureHandlerNative.d.ts.map +1 -1
  73. package/lib/typescript/src/views/Header/Header.d.ts +2 -2
  74. package/lib/typescript/src/views/Header/Header.d.ts.map +1 -1
  75. package/lib/typescript/src/views/Header/HeaderContainer.d.ts +9 -3
  76. package/lib/typescript/src/views/Header/HeaderContainer.d.ts.map +1 -1
  77. package/lib/typescript/src/views/Header/HeaderSegment.d.ts +5 -4
  78. package/lib/typescript/src/views/Header/HeaderSegment.d.ts.map +1 -1
  79. package/lib/typescript/src/views/Stack/Card.d.ts +5 -5
  80. package/lib/typescript/src/views/Stack/Card.d.ts.map +1 -1
  81. package/lib/typescript/src/views/Stack/CardContainer.d.ts +5 -7
  82. package/lib/typescript/src/views/Stack/CardContainer.d.ts.map +1 -1
  83. package/lib/typescript/src/views/Stack/CardContent.d.ts +1 -1
  84. package/lib/typescript/src/views/Stack/CardContent.d.ts.map +1 -1
  85. package/lib/typescript/src/views/Stack/CardStack.d.ts +6 -5
  86. package/lib/typescript/src/views/Stack/CardStack.d.ts.map +1 -1
  87. package/lib/typescript/src/views/Stack/StackView.d.ts +10 -13
  88. package/lib/typescript/src/views/Stack/StackView.d.ts.map +1 -1
  89. package/package.json +21 -23
  90. package/src/index.tsx +3 -2
  91. package/src/navigators/createStackNavigator.tsx +9 -40
  92. package/src/types.tsx +73 -73
  93. package/src/utils/GestureHandlerContext.tsx +5 -0
  94. package/src/utils/gestureActivationCriteria.tsx +14 -10
  95. package/src/utils/useCardAnimation.tsx +1 -1
  96. package/src/utils/useGestureHandler.tsx +9 -0
  97. package/src/utils/useKeyboardManager.tsx +96 -33
  98. package/src/views/GestureHandler.tsx +27 -16
  99. package/src/views/GestureHandlerNative.tsx +26 -12
  100. package/src/views/Header/Header.tsx +2 -2
  101. package/src/views/Header/HeaderContainer.tsx +67 -24
  102. package/src/views/Header/HeaderSegment.tsx +4 -4
  103. package/src/views/Stack/Card.tsx +134 -115
  104. package/src/views/Stack/CardContainer.tsx +86 -127
  105. package/src/views/Stack/CardStack.tsx +380 -323
  106. package/src/views/Stack/StackView.tsx +173 -71
  107. package/lib/module/utils/GestureHandlerRefContext.js +0 -5
  108. package/lib/module/utils/GestureHandlerRefContext.js.map +0 -1
  109. package/lib/module/utils/findLastIndex.js +0 -11
  110. package/lib/module/utils/findLastIndex.js.map +0 -1
  111. package/lib/module/utils/useGestureHandlerRef.js +0 -12
  112. package/lib/module/utils/useGestureHandlerRef.js.map +0 -1
  113. package/lib/module/views/Stack/CardA11yWrapper.js +0 -43
  114. package/lib/module/views/Stack/CardA11yWrapper.js.map +0 -1
  115. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts +0 -3
  116. package/lib/typescript/src/utils/GestureHandlerRefContext.d.ts.map +0 -1
  117. package/lib/typescript/src/utils/findLastIndex.d.ts +0 -2
  118. package/lib/typescript/src/utils/findLastIndex.d.ts.map +0 -1
  119. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts +0 -3
  120. package/lib/typescript/src/utils/useGestureHandlerRef.d.ts.map +0 -1
  121. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts +0 -15
  122. package/lib/typescript/src/views/Stack/CardA11yWrapper.d.ts.map +0 -1
  123. package/src/utils/GestureHandlerRefContext.tsx +0 -5
  124. package/src/utils/findLastIndex.tsx +0 -9
  125. package/src/utils/useGestureHandlerRef.tsx +0 -15
  126. package/src/views/Stack/CardA11yWrapper.tsx +0 -67
@@ -1,4 +1,5 @@
1
1
  import type { LocaleDirection } from '@react-navigation/native';
2
+ import type { PanGestureConfig } from 'react-native-gesture-handler';
2
3
 
3
4
  import type { GestureDirection, Layout } from '../types';
4
5
  import { getInvertedMultiplier } from './getInvertedMultiplier';
@@ -17,9 +18,9 @@ export const gestureActivationCriteria = ({
17
18
  }: {
18
19
  direction: LocaleDirection;
19
20
  gestureDirection: GestureDirection;
20
- gestureResponseDistance?: number;
21
+ gestureResponseDistance?: number | undefined;
21
22
  layout: Layout;
22
- }) => {
23
+ }): PanGestureConfig => {
23
24
  const enableTrackpadTwoFingerGesture = true;
24
25
 
25
26
  const distance =
@@ -30,17 +31,20 @@ export const gestureActivationCriteria = ({
30
31
  ? GESTURE_RESPONSE_DISTANCE_VERTICAL
31
32
  : GESTURE_RESPONSE_DISTANCE_HORIZONTAL;
32
33
 
34
+ const failOffsetX: [number, number] = [-15, 15];
35
+ const failOffsetY: [number, number] = [-20, 20];
36
+
33
37
  if (gestureDirection === 'vertical') {
34
38
  return {
35
- maxDeltaX: 15,
36
- minOffsetY: 5,
39
+ failOffsetX,
40
+ activeOffsetY: 5,
37
41
  hitSlop: { bottom: -layout.height + distance },
38
42
  enableTrackpadTwoFingerGesture,
39
43
  };
40
44
  } else if (gestureDirection === 'vertical-inverted') {
41
45
  return {
42
- maxDeltaX: 15,
43
- minOffsetY: -5,
46
+ failOffsetX,
47
+ activeOffsetY: -5,
44
48
  hitSlop: { top: -layout.height + distance },
45
49
  enableTrackpadTwoFingerGesture,
46
50
  };
@@ -53,15 +57,15 @@ export const gestureActivationCriteria = ({
53
57
 
54
58
  if (invertedMultiplier === 1) {
55
59
  return {
56
- minOffsetX: 5,
57
- maxDeltaY: 20,
60
+ activeOffsetX: 5,
61
+ failOffsetY,
58
62
  hitSlop: { right: hitSlop },
59
63
  enableTrackpadTwoFingerGesture,
60
64
  };
61
65
  } else {
62
66
  return {
63
- minOffsetX: -5,
64
- maxDeltaY: 20,
67
+ activeOffsetX: -5,
68
+ failOffsetY,
65
69
  hitSlop: { left: hitSlop },
66
70
  enableTrackpadTwoFingerGesture,
67
71
  };
@@ -3,7 +3,7 @@ import * as React from 'react';
3
3
  import { CardAnimationContext } from './CardAnimationContext';
4
4
 
5
5
  export function useCardAnimation() {
6
- const animation = React.useContext(CardAnimationContext);
6
+ const animation = React.use(CardAnimationContext);
7
7
 
8
8
  if (animation === undefined) {
9
9
  throw new Error(
@@ -0,0 +1,9 @@
1
+ import * as React from 'react';
2
+
3
+ import { GestureHandlerContext } from './GestureHandlerContext';
4
+
5
+ export function useGestureHandler() {
6
+ const gesture = React.useContext(GestureHandlerContext);
7
+
8
+ return gesture;
9
+ }
@@ -1,12 +1,23 @@
1
1
  import * as React from 'react';
2
2
  import { type HostInstance, Keyboard, TextInput } from 'react-native';
3
3
 
4
- export function useKeyboardManager(isEnabled: () => boolean) {
4
+ export function useKeyboardManager({
5
+ enabled,
6
+ focused,
7
+ contentRef,
8
+ }: {
9
+ enabled: boolean;
10
+ focused: boolean;
11
+ contentRef: React.RefObject<HostInstance | null>;
12
+ }) {
5
13
  // Numeric id of the previously focused text input
6
14
  // When a gesture didn't change the tab, we can restore the focused input with this
7
15
  const previouslyFocusedTextInputRef = React.useRef<HostInstance>(undefined);
8
16
  const startTimestampRef = React.useRef<number>(0);
9
- const keyboardTimeoutRef = React.useRef<NodeJS.Timeout>(undefined);
17
+ const keyboardTimeoutRef =
18
+ React.useRef<ReturnType<typeof setTimeout>>(undefined);
19
+ const enabledRef = React.useRef(enabled);
20
+ const focusedRef = React.useRef(focused);
10
21
 
11
22
  const clearKeyboardTimeout = React.useCallback(() => {
12
23
  if (keyboardTimeoutRef.current !== undefined) {
@@ -16,7 +27,7 @@ export function useKeyboardManager(isEnabled: () => boolean) {
16
27
  }, []);
17
28
 
18
29
  const onPageChangeStart = React.useCallback(() => {
19
- if (!isEnabled()) {
30
+ if (!enabledRef.current || !focusedRef.current) {
20
31
  return;
21
32
  }
22
33
 
@@ -32,37 +43,10 @@ export function useKeyboardManager(isEnabled: () => boolean) {
32
43
 
33
44
  // Store timestamp for touch start
34
45
  startTimestampRef.current = Date.now();
35
- }, [clearKeyboardTimeout, isEnabled]);
36
-
37
- const onPageChangeConfirm = React.useCallback(
38
- (force: boolean) => {
39
- if (!isEnabled()) {
40
- return;
41
- }
42
-
43
- clearKeyboardTimeout();
44
-
45
- if (force) {
46
- // Always dismiss input, even if we don't have a ref to it
47
- // We might not have the ref if onPageChangeStart was never called
48
- // This can happen if page change was not from a gesture
49
- Keyboard.dismiss();
50
- } else {
51
- const input = previouslyFocusedTextInputRef.current;
52
-
53
- // Dismiss the keyboard only if an input was a focused before
54
- // This makes sure we don't dismiss input on going back and focusing an input
55
- input?.blur();
56
- }
57
-
58
- // Cleanup the ID on successful page change
59
- previouslyFocusedTextInputRef.current = undefined;
60
- },
61
- [clearKeyboardTimeout, isEnabled]
62
- );
46
+ }, [clearKeyboardTimeout]);
63
47
 
64
48
  const onPageChangeCancel = React.useCallback(() => {
65
- if (!isEnabled()) {
49
+ if (!enabledRef.current || !focusedRef.current) {
66
50
  return;
67
51
  }
68
52
 
@@ -89,7 +73,86 @@ export function useKeyboardManager(isEnabled: () => boolean) {
89
73
  previouslyFocusedTextInputRef.current = undefined;
90
74
  }
91
75
  }
92
- }, [clearKeyboardTimeout, isEnabled]);
76
+ }, [clearKeyboardTimeout]);
77
+
78
+ const onPageChangeConfirm = React.useCallback(
79
+ ({
80
+ gesture,
81
+ active,
82
+ closing,
83
+ }: {
84
+ gesture: boolean;
85
+ active: boolean;
86
+ closing: boolean;
87
+ }) => {
88
+ if (!enabledRef.current || !focusedRef.current) {
89
+ return;
90
+ }
91
+
92
+ if (!closing) {
93
+ onPageChangeCancel();
94
+ return;
95
+ }
96
+
97
+ clearKeyboardTimeout();
98
+
99
+ if (!gesture) {
100
+ // Always dismiss input, even if we don't have a ref to it
101
+ // We might not have the ref if onPageChangeStart was never called
102
+ // This can happen if page change was not from a gesture
103
+ Keyboard.dismiss();
104
+ } else if (active) {
105
+ const input = previouslyFocusedTextInputRef.current;
106
+
107
+ // Dismiss the keyboard only if an input was a focused before
108
+ // This makes sure we don't dismiss input on going back and focusing an input
109
+ input?.blur();
110
+ }
111
+
112
+ // Cleanup the ID on successful page change
113
+ previouslyFocusedTextInputRef.current = undefined;
114
+ },
115
+ [clearKeyboardTimeout, onPageChangeCancel]
116
+ );
117
+
118
+ // Dismiss keyboard when screen loses focus (e.g. when pushing a new screen).
119
+ // This handles the "navigate forward" case so we don't dismiss the new screen's
120
+ // auto-focused input from handleTransition.
121
+ React.useLayoutEffect(() => {
122
+ if (enabledRef.current && !focused) {
123
+ Keyboard.dismiss();
124
+ }
125
+ }, [focused]);
126
+
127
+ // Dismiss keyboard if native focus is restored to an input in an unfocused screen.
128
+ // This can happen after dismissing a native alert when `autoFocus` is used on iOS.
129
+ React.useEffect(() => {
130
+ const blurInputInScreen = () => {
131
+ if (focusedRef.current || !enabledRef.current) {
132
+ return;
133
+ }
134
+
135
+ const input = TextInput.State.currentlyFocusedInput();
136
+
137
+ if (input && contentRef.current?.contains(input)) {
138
+ input.blur();
139
+ }
140
+ };
141
+
142
+ const subscriptions = [
143
+ Keyboard.addListener('keyboardWillShow', blurInputInScreen),
144
+ Keyboard.addListener('keyboardDidShow', blurInputInScreen),
145
+ ];
146
+
147
+ return () => {
148
+ subscriptions.forEach((subscription) => subscription.remove());
149
+ };
150
+ }, [contentRef]);
151
+
152
+ React.useLayoutEffect(() => {
153
+ enabledRef.current = enabled;
154
+ focusedRef.current = focused;
155
+ });
93
156
 
94
157
  React.useEffect(() => {
95
158
  return () => clearKeyboardTimeout();
@@ -1,23 +1,34 @@
1
1
  import * as React from 'react';
2
2
  import { View } from 'react-native';
3
- import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
3
+ import type {
4
+ PanGesture,
5
+ PanGestureConfig,
6
+ } from 'react-native-gesture-handler';
4
7
 
5
- const Dummy: any = ({ children }: { children: React.ReactNode }) => (
6
- <>{children}</>
7
- );
8
+ import { GestureHandlerContext } from '../utils/GestureHandlerContext';
8
9
 
9
- export const PanGestureHandler =
10
- Dummy as React.ComponentType<PanGestureHandlerProperties>;
10
+ export function GestureDetector({
11
+ gesture,
12
+ children,
13
+ }: {
14
+ gesture: PanGesture | undefined;
15
+ children: React.ReactNode;
16
+ }) {
17
+ return (
18
+ <GestureHandlerContext.Provider value={gesture ?? null}>
19
+ {children}
20
+ </GestureHandlerContext.Provider>
21
+ );
22
+ }
11
23
 
12
- export const GestureHandlerRootView = View;
24
+ // eslint-disable-next-line @eslint-react/hooks-extra/no-unnecessary-use-prefix, @eslint-react/hooks-extra/ensure-custom-hooks-using-other-hooks
25
+ export function usePanGesture(_config: PanGestureConfig) {
26
+ return undefined;
27
+ }
13
28
 
14
- export const GestureState = {
15
- UNDETERMINED: 0,
16
- FAILED: 1,
17
- BEGAN: 2,
18
- CANCELLED: 3,
19
- ACTIVE: 4,
20
- END: 5,
21
- };
29
+ export const GestureHandlerRootView = View;
22
30
 
23
- export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
31
+ export type {
32
+ PanGesture,
33
+ PanGestureActiveEvent,
34
+ } from 'react-native-gesture-handler';
@@ -1,23 +1,37 @@
1
1
  import * as React from 'react';
2
2
  import {
3
- PanGestureHandler as PanGestureHandlerNative,
4
- type PanGestureHandlerProperties,
3
+ GestureDetector as GestureDetectorNative,
4
+ type PanGesture,
5
+ usePanGesture,
5
6
  } from 'react-native-gesture-handler';
6
7
 
7
- import { GestureHandlerRefContext } from '../utils/GestureHandlerRefContext';
8
+ import { GestureHandlerContext } from '../utils/GestureHandlerContext';
8
9
 
9
- export function PanGestureHandler(props: PanGestureHandlerProperties) {
10
- const gestureRef = React.useRef<PanGestureHandlerNative>(null);
10
+ export function GestureDetector({
11
+ gesture,
12
+ children,
13
+ }: {
14
+ gesture: PanGesture | undefined;
15
+ children: React.ReactNode;
16
+ }) {
17
+ if (gesture == null) {
18
+ throw new Error(
19
+ 'The provided gesture is undefined. A valid gesture must be passed to the GestureDetector on native platforms.'
20
+ );
21
+ }
11
22
 
12
23
  return (
13
- <GestureHandlerRefContext.Provider value={gestureRef}>
14
- <PanGestureHandlerNative {...props} ref={gestureRef} />
15
- </GestureHandlerRefContext.Provider>
24
+ <GestureHandlerContext.Provider value={gesture}>
25
+ <GestureDetectorNative gesture={gesture}>
26
+ {children}
27
+ </GestureDetectorNative>
28
+ </GestureHandlerContext.Provider>
16
29
  );
17
30
  }
18
31
 
19
- export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';
20
- export {
21
- GestureHandlerRootView,
22
- State as GestureState,
32
+ export { usePanGesture };
33
+ export type {
34
+ PanGesture,
35
+ PanGestureActiveEvent,
23
36
  } from 'react-native-gesture-handler';
37
+ export { GestureHandlerRootView } from 'react-native-gesture-handler';
@@ -41,8 +41,8 @@ export const Header = React.memo(function Header({
41
41
  [navigation, route.key]
42
42
  );
43
43
 
44
- const isModal = React.useContext(ModalPresentationContext);
45
- const isParentHeaderShown = React.useContext(HeaderShownContext);
44
+ const isModal = React.use(ModalPresentationContext);
45
+ const isParentHeaderShown = React.use(HeaderShownContext);
46
46
 
47
47
  const statusBarHeight =
48
48
  options.headerStatusBarHeight !== undefined
@@ -1,4 +1,5 @@
1
1
  import { getHeaderTitle, HeaderBackContext } from '@react-navigation/elements';
2
+ import { ActivityView } from '@react-navigation/elements/internal';
2
3
  import {
3
4
  NavigationProvider,
4
5
  type ParamListBase,
@@ -6,7 +7,13 @@ import {
6
7
  useLinkBuilder,
7
8
  } from '@react-navigation/native';
8
9
  import * as React from 'react';
9
- import { type StyleProp, StyleSheet, View, type ViewStyle } from 'react-native';
10
+ import {
11
+ type LayoutChangeEvent,
12
+ type StyleProp,
13
+ StyleSheet,
14
+ View,
15
+ type ViewStyle,
16
+ } from 'react-native';
10
17
 
11
18
  import {
12
19
  forNoAnimation,
@@ -22,12 +29,18 @@ import type {
22
29
  } from '../../types';
23
30
  import { Header } from './Header';
24
31
 
32
+ export type HeaderHeight = {
33
+ measured: boolean;
34
+ value: number;
35
+ };
36
+
25
37
  export type Props = {
26
38
  mode: StackHeaderMode;
27
39
  scenes: (Scene | undefined)[];
28
40
  getPreviousScene: (props: { route: Route<string> }) => Scene | undefined;
29
41
  getFocusedRoute: () => Route<string>;
30
- onContentHeightChange?: (props: {
42
+ contentHeight: HeaderHeight | undefined;
43
+ onContentHeightChange: (props: {
31
44
  route: Route<string>;
32
45
  height: number;
33
46
  }) => void;
@@ -39,19 +52,17 @@ export function HeaderContainer({
39
52
  scenes,
40
53
  getPreviousScene,
41
54
  getFocusedRoute,
55
+ contentHeight,
42
56
  onContentHeightChange,
43
57
  style,
44
58
  }: Props) {
45
59
  const focusedRoute = getFocusedRoute();
46
- const parentHeaderBack = React.useContext(HeaderBackContext);
60
+ const parentHeaderBack = React.use(HeaderBackContext);
47
61
  const { buildHref } = useLinkBuilder();
48
62
 
49
63
  return (
50
64
  <View style={[styles.container, style]}>
51
- {/* We render header only on two top-most headers as
52
- a workaround for https://github.com/react-navigation/react-navigation/issues/12456.
53
- If the header is persisted, it might be placed incorrectly when navigating back. */}
54
- {scenes.slice(-2).map((scene, i, self) => {
65
+ {scenes.map((scene, i, self) => {
55
66
  if ((mode === 'screen' && i !== self.length - 1) || !scene) {
56
67
  return null;
57
68
  }
@@ -96,6 +107,9 @@ export function HeaderContainer({
96
107
  headerMode: previousHeaderMode,
97
108
  } = previousDescriptor?.options || {};
98
109
 
110
+ const { headerTransparent: nextHeaderTransparent } =
111
+ nextDescriptor?.options || {};
112
+
99
113
  // If any of the next screens don't have a header or header is part of the screen
100
114
  // Then we need to move this header offscreen so that it doesn't cover it
101
115
  const nextHeaderlessScene = self.slice(i + 1).find((scene) => {
@@ -115,6 +129,9 @@ export function HeaderContainer({
115
129
  // We still need to animate when coming back from next scene
116
130
  // A hacky way to check this is if the next scene exists
117
131
  !nextDescriptor) ||
132
+ // If next header is tranparent, we want to treat this header as static
133
+ // Otherwise it may show through the next header
134
+ nextHeaderTransparent ||
118
135
  nextHeaderlessScene;
119
136
 
120
137
  const props: StackHeaderProps = {
@@ -137,6 +154,26 @@ export function HeaderContainer({
137
154
  : forNoAnimation,
138
155
  };
139
156
 
157
+ const onRef = (node: View | null) => {
158
+ node?.measure((_x, _y, _width, height) => {
159
+ if (height && !contentHeight?.measured) {
160
+ onContentHeightChange({
161
+ route: scene.descriptor.route,
162
+ height,
163
+ });
164
+ }
165
+ });
166
+ };
167
+
168
+ const onLayout = (e: LayoutChangeEvent) => {
169
+ const { height } = e.nativeEvent.layout;
170
+
171
+ onContentHeightChange({
172
+ route: scene.descriptor.route,
173
+ height,
174
+ });
175
+ };
176
+
140
177
  return (
141
178
  <NavigationProvider
142
179
  key={scene.descriptor.route.key}
@@ -144,31 +181,37 @@ export function HeaderContainer({
144
181
  route={scene.descriptor.route}
145
182
  >
146
183
  <View
147
- onLayout={
148
- onContentHeightChange
149
- ? (e) => {
150
- const { height } = e.nativeEvent.layout;
151
-
152
- onContentHeightChange({
153
- route: scene.descriptor.route,
154
- height,
155
- });
156
- }
157
- : undefined
158
- }
159
- aria-hidden={!isFocused}
160
184
  style={[
161
- // Avoid positioning the focused header absolutely
185
+ // Avoid absolutely positioning the focused header unnecessarily
162
186
  // Otherwise accessibility tools don't seem to be able to find it
163
187
  (mode === 'float' && !isFocused) || headerTransparent
164
- ? styles.header
188
+ ? [
189
+ styles.absolute,
190
+ mode === 'screen'
191
+ ? // For transparent headers, specify a min height
192
+ // This is needed for screen readers, and testing tools on Android
193
+ // For float header, it's handled in CardStack
194
+ { minHeight: contentHeight?.value }
195
+ : null,
196
+ ]
165
197
  : null,
166
198
  {
167
199
  pointerEvents: isFocused ? 'box-none' : 'none',
168
200
  },
169
201
  ]}
170
202
  >
171
- {header !== undefined ? header(props) : <Header {...props} />}
203
+ <View
204
+ ref={onRef}
205
+ onLayout={onLayout}
206
+ style={{ pointerEvents: 'box-none' }}
207
+ >
208
+ <ActivityView
209
+ mode={isFocused ? 'normal' : 'inert'}
210
+ visible={true}
211
+ >
212
+ {header !== undefined ? header(props) : <Header {...props} />}
213
+ </ActivityView>
214
+ </View>
172
215
  </View>
173
216
  </NavigationProvider>
174
217
  );
@@ -181,7 +224,7 @@ const styles = StyleSheet.create({
181
224
  container: {
182
225
  pointerEvents: 'box-none',
183
226
  },
184
- header: {
227
+ absolute: {
185
228
  position: 'absolute',
186
229
  top: 0,
187
230
  start: 0,
@@ -20,8 +20,8 @@ type Props = Omit<StackHeaderOptions, 'headerStatusBarHeight'> & {
20
20
  headerStatusBarHeight: number;
21
21
  title: string;
22
22
  modal: boolean;
23
- onGoBack?: () => void;
24
- backHref?: string;
23
+ onGoBack?: (() => void) | undefined;
24
+ backHref?: string | undefined;
25
25
  progress: SceneProgress;
26
26
  styleInterpolator: StackHeaderStyleInterpolator;
27
27
  };
@@ -40,7 +40,7 @@ export function HeaderSegment(props: Props) {
40
40
  ? (props: HeaderBackButtonProps) => <HeaderBackButton {...props} />
41
41
  : undefined,
42
42
  headerRight: right,
43
- headerBackImage,
43
+ headerBackIcon,
44
44
  headerBackTitle,
45
45
  headerBackButtonDisplayMode,
46
46
  headerBackTruncatedTitle,
@@ -93,7 +93,7 @@ export function HeaderSegment(props: Props) {
93
93
  left({
94
94
  ...props,
95
95
  href: backHref,
96
- backImage: headerBackImage,
96
+ icon: headerBackIcon,
97
97
  accessibilityLabel: headerBackAccessibilityLabel,
98
98
  testID: headerBackTestID,
99
99
  allowFontScaling: headerBackAllowFontScaling,