react-native-screens 3.24.0 → 3.26.0

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 (140) hide show
  1. package/README.md +1 -1
  2. package/RNScreens.podspec +53 -32
  3. package/android/build.gradle +13 -11
  4. package/android/src/main/java/com/swmansion/rnscreens/FragmentHolder.kt +7 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +41 -10
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +35 -36
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainerViewManager.kt +7 -7
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenEventDispatcher.kt +21 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +46 -38
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragmentWrapper.kt +22 -0
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +40 -39
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +35 -10
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragmentWrapper.kt +15 -0
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +6 -4
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +11 -18
  16. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +27 -7
  17. package/android/src/main/java/com/swmansion/rnscreens/ScreensShadowNode.kt +1 -1
  18. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +23 -7
  19. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderAttachedEvent.kt +3 -5
  20. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderBackButtonClickedEvent.kt +3 -5
  21. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderDetachedEvent.kt +3 -5
  22. package/android/src/main/java/com/swmansion/rnscreens/events/HeaderHeightChangeEvent.kt +26 -0
  23. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenAppearEvent.kt +3 -5
  24. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenDisappearEvent.kt +3 -5
  25. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenDismissedEvent.kt +4 -7
  26. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenTransitionProgressEvent.kt +7 -8
  27. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillAppearEvent.kt +3 -5
  28. package/android/src/main/java/com/swmansion/rnscreens/events/ScreenWillDisappearEvent.kt +3 -5
  29. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarBlurEvent.kt +3 -5
  30. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarChangeTextEvent.kt +5 -6
  31. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarCloseEvent.kt +3 -5
  32. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarFocusEvent.kt +3 -5
  33. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarOpenEvent.kt +3 -5
  34. package/android/src/main/java/com/swmansion/rnscreens/events/SearchBarSearchButtonPressEvent.kt +4 -6
  35. package/android/src/main/java/com/swmansion/rnscreens/events/StackFinishTransitioningEvent.kt +3 -5
  36. package/android/src/main/java/com/swmansion/rnscreens/utils/DeviceUtils.kt +12 -0
  37. package/android/src/main/res/v33/anim-v33/rns_default_enter_out.xml +1 -1
  38. package/ios/RNSFullWindowOverlay.mm +2 -2
  39. package/ios/RNSScreen.h +5 -0
  40. package/ios/RNSScreen.mm +164 -7
  41. package/ios/RNSScreenStack.mm +18 -0
  42. package/ios/RNSScreenStackHeaderConfig.mm +18 -0
  43. package/ios/events/RNSHeaderHeightChangeEvent.h +8 -0
  44. package/ios/events/RNSHeaderHeightChangeEvent.mm +44 -0
  45. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  46. package/lib/commonjs/index.native.js +11 -11
  47. package/lib/commonjs/index.native.js.map +1 -1
  48. package/lib/commonjs/native-stack/index.js +14 -0
  49. package/lib/commonjs/native-stack/index.js.map +1 -1
  50. package/lib/commonjs/native-stack/navigators/createNativeStackNavigator.js +1 -1
  51. package/lib/commonjs/native-stack/navigators/createNativeStackNavigator.js.map +1 -1
  52. package/lib/commonjs/native-stack/types.js.map +1 -1
  53. package/lib/commonjs/native-stack/utils/AnimatedHeaderHeightContext.js +13 -0
  54. package/lib/commonjs/native-stack/utils/AnimatedHeaderHeightContext.js.map +1 -0
  55. package/lib/commonjs/native-stack/utils/SafeAreaProviderCompat.js.map +1 -1
  56. package/lib/commonjs/native-stack/utils/getStatusBarHeight.js +22 -0
  57. package/lib/commonjs/native-stack/utils/getStatusBarHeight.js.map +1 -0
  58. package/lib/commonjs/native-stack/utils/useAnimatedHeaderHeight.js +19 -0
  59. package/lib/commonjs/native-stack/utils/useAnimatedHeaderHeight.js.map +1 -0
  60. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +2 -2
  61. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -1
  62. package/lib/commonjs/native-stack/views/HeaderConfig.js +2 -2
  63. package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
  64. package/lib/commonjs/native-stack/views/NativeStackView.js +39 -21
  65. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  66. package/lib/commonjs/reanimated/ReanimatedHeaderHeightContext.js +13 -0
  67. package/lib/commonjs/reanimated/ReanimatedHeaderHeightContext.js.map +1 -0
  68. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +37 -6
  69. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  70. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +2 -2
  71. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -1
  72. package/lib/commonjs/reanimated/index.js +7 -0
  73. package/lib/commonjs/reanimated/index.js.map +1 -1
  74. package/lib/commonjs/reanimated/useReanimatedHeaderHeight.js +19 -0
  75. package/lib/commonjs/reanimated/useReanimatedHeaderHeight.js.map +1 -0
  76. package/lib/commonjs/types.js.map +1 -1
  77. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  78. package/lib/module/index.native.js +12 -12
  79. package/lib/module/index.native.js.map +1 -1
  80. package/lib/module/native-stack/index.js +2 -0
  81. package/lib/module/native-stack/index.js.map +1 -1
  82. package/lib/module/native-stack/navigators/createNativeStackNavigator.js +1 -1
  83. package/lib/module/native-stack/navigators/createNativeStackNavigator.js.map +1 -1
  84. package/lib/module/native-stack/types.js.map +1 -1
  85. package/lib/module/native-stack/utils/AnimatedHeaderHeightContext.js +4 -0
  86. package/lib/module/native-stack/utils/AnimatedHeaderHeightContext.js.map +1 -0
  87. package/lib/module/native-stack/utils/SafeAreaProviderCompat.js.map +1 -1
  88. package/lib/module/native-stack/utils/getStatusBarHeight.js +16 -0
  89. package/lib/module/native-stack/utils/getStatusBarHeight.js.map +1 -0
  90. package/lib/module/native-stack/utils/useAnimatedHeaderHeight.js +10 -0
  91. package/lib/module/native-stack/utils/useAnimatedHeaderHeight.js.map +1 -0
  92. package/lib/module/native-stack/utils/useBackPressSubscription.js +2 -2
  93. package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -1
  94. package/lib/module/native-stack/views/HeaderConfig.js +2 -2
  95. package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
  96. package/lib/module/native-stack/views/NativeStackView.js +40 -22
  97. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  98. package/lib/module/reanimated/ReanimatedHeaderHeightContext.js +5 -0
  99. package/lib/module/reanimated/ReanimatedHeaderHeightContext.js.map +1 -0
  100. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +37 -6
  101. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  102. package/lib/module/reanimated/ReanimatedScreenProvider.js +2 -2
  103. package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -1
  104. package/lib/module/reanimated/index.js +1 -0
  105. package/lib/module/reanimated/index.js.map +1 -1
  106. package/lib/module/reanimated/useReanimatedHeaderHeight.js +10 -0
  107. package/lib/module/reanimated/useReanimatedHeaderHeight.js.map +1 -0
  108. package/lib/module/types.js.map +1 -1
  109. package/lib/typescript/fabric/ScreenNativeComponent.d.ts +4 -0
  110. package/lib/typescript/native-stack/index.d.ts +2 -0
  111. package/lib/typescript/native-stack/types.d.ts +20 -12
  112. package/lib/typescript/native-stack/utils/AnimatedHeaderHeightContext.d.ts +4 -0
  113. package/lib/typescript/native-stack/utils/getStatusBarHeight.d.ts +2 -0
  114. package/lib/typescript/native-stack/utils/useAnimatedHeaderHeight.d.ts +1 -0
  115. package/lib/typescript/reanimated/ReanimatedHeaderHeightContext.d.ts +4 -0
  116. package/lib/typescript/reanimated/index.d.ts +1 -0
  117. package/lib/typescript/reanimated/useReanimatedHeaderHeight.d.ts +3 -0
  118. package/lib/typescript/types.d.ts +26 -15
  119. package/native-stack/README.md +16 -1
  120. package/package.json +36 -38
  121. package/src/fabric/ScreenNativeComponent.ts +5 -0
  122. package/src/index.native.tsx +17 -19
  123. package/src/native-stack/index.tsx +3 -0
  124. package/src/native-stack/types.tsx +21 -12
  125. package/src/native-stack/utils/AnimatedHeaderHeightContext.tsx +8 -0
  126. package/src/native-stack/utils/SafeAreaProviderCompat.tsx +1 -1
  127. package/src/native-stack/utils/getStatusBarHeight.tsx +22 -0
  128. package/src/native-stack/utils/useAnimatedHeaderHeight.tsx +15 -0
  129. package/src/native-stack/views/HeaderConfig.tsx +1 -2
  130. package/src/native-stack/views/NativeStackView.tsx +78 -44
  131. package/src/reanimated/ReanimatedHeaderHeightContext.tsx +7 -0
  132. package/src/reanimated/ReanimatedNativeStackScreen.tsx +58 -11
  133. package/src/reanimated/index.tsx +1 -0
  134. package/src/reanimated/useReanimatedHeaderHeight.tsx +14 -0
  135. package/src/types.tsx +29 -14
  136. package/windows/RNScreens/RNScreens.vcxproj +6 -3
  137. package/windows/RNScreens/ScreenStack.cpp +16 -0
  138. package/windows/RNScreens/ScreenStack.h +1 -0
  139. /package/ios/{RNSScreenViewEvent.h → events/RNSScreenViewEvent.h} +0 -0
  140. /package/ios/{RNSScreenViewEvent.mm → events/RNSScreenViewEvent.mm} +0 -0
@@ -5,7 +5,6 @@ import {
5
5
  Image,
6
6
  ImageProps,
7
7
  Platform,
8
- requireNativeComponent,
9
8
  StyleProp,
10
9
  StyleSheet,
11
10
  UIManager,
@@ -137,28 +136,29 @@ const ScreensNativeModules = {
137
136
  NativeScreenNavigationContainerValue =
138
137
  NativeScreenNavigationContainerValue ||
139
138
  (Platform.OS === 'ios'
140
- ? requireNativeComponent('RNSScreenNavigationContainer')
139
+ ? require('./fabric/ScreenNavigationContainerNativeComponent').default
141
140
  : this.NativeScreenContainer);
142
141
  return NativeScreenNavigationContainerValue;
143
142
  },
144
143
 
145
144
  get NativeScreenStack() {
146
145
  NativeScreenStack =
147
- NativeScreenStack || requireNativeComponent('RNSScreenStack');
146
+ NativeScreenStack ||
147
+ require('./fabric/ScreenStackNativeComponent').default;
148
148
  return NativeScreenStack;
149
149
  },
150
150
 
151
151
  get NativeScreenStackHeaderConfig() {
152
152
  NativeScreenStackHeaderConfig =
153
153
  NativeScreenStackHeaderConfig ||
154
- requireNativeComponent('RNSScreenStackHeaderConfig');
154
+ require('./fabric/ScreenStackHeaderConfigNativeComponent').default;
155
155
  return NativeScreenStackHeaderConfig;
156
156
  },
157
157
 
158
158
  get NativeScreenStackHeaderSubview() {
159
159
  NativeScreenStackHeaderSubview =
160
160
  NativeScreenStackHeaderSubview ||
161
- requireNativeComponent('RNSScreenStackHeaderSubview');
161
+ require('./fabric/ScreenStackHeaderSubviewNativeComponent').default;
162
162
  return NativeScreenStackHeaderSubview;
163
163
  },
164
164
 
@@ -167,6 +167,7 @@ const ScreensNativeModules = {
167
167
  NativeSearchBar || require('./fabric/SearchBarNativeComponent').default;
168
168
  return NativeSearchBar;
169
169
  },
170
+
170
171
  get NativeSearchBarCommands() {
171
172
  NativeSearchBarCommands =
172
173
  NativeSearchBarCommands ||
@@ -176,7 +177,8 @@ const ScreensNativeModules = {
176
177
 
177
178
  get NativeFullWindowOverlay() {
178
179
  NativeFullWindowOverlay =
179
- NativeFullWindowOverlay || requireNativeComponent('RNSFullWindowOverlay');
180
+ NativeFullWindowOverlay ||
181
+ require('./fabric/FullWindowOverlayNativeComponent').default;
180
182
  return NativeFullWindowOverlay;
181
183
  },
182
184
  };
@@ -346,8 +348,7 @@ class InnerScreen extends React.Component<ScreenProps> {
346
348
  (() => {
347
349
  // for internal use
348
350
  })
349
- }
350
- >
351
+ }>
351
352
  {!isNativeStack ? ( // see comment of this prop in types.tsx for information why it is needed
352
353
  children
353
354
  ) : (
@@ -356,8 +357,7 @@ class InnerScreen extends React.Component<ScreenProps> {
356
357
  progress: this.progress,
357
358
  closing: this.closing,
358
359
  goingForward: this.goingForward,
359
- }}
360
- >
360
+ }}>
361
361
  {children}
362
362
  </TransitionProgressContext.Provider>
363
363
  )}
@@ -408,8 +408,7 @@ function FullWindowOverlay(props: { children: ReactNode }) {
408
408
  }
409
409
  return (
410
410
  <ScreensNativeModules.NativeFullWindowOverlay
411
- style={{ position: 'absolute', width: '100%', height: '100%' }}
412
- >
411
+ style={{ position: 'absolute', width: '100%', height: '100%' }}>
413
412
  {props.children}
414
413
  </ScreensNativeModules.NativeFullWindowOverlay>
415
414
  );
@@ -429,8 +428,7 @@ const styles = StyleSheet.create({
429
428
  const ScreenStackHeaderBackButtonImage = (props: ImageProps): JSX.Element => (
430
429
  <ScreensNativeModules.NativeScreenStackHeaderSubview
431
430
  type="back"
432
- style={styles.headerSubview}
433
- >
431
+ style={styles.headerSubview}>
434
432
  <Image resizeMode="center" fadeDuration={0} {...props} />
435
433
  </ScreensNativeModules.NativeScreenStackHeaderSubview>
436
434
  );
@@ -455,31 +453,31 @@ class SearchBar extends React.Component<SearchBarProps> {
455
453
  }
456
454
 
457
455
  blur() {
458
- this._callMethodWithRef((ref) =>
456
+ this._callMethodWithRef(ref =>
459
457
  ScreensNativeModules.NativeSearchBarCommands.blur(ref)
460
458
  );
461
459
  }
462
460
 
463
461
  focus() {
464
- this._callMethodWithRef((ref) =>
462
+ this._callMethodWithRef(ref =>
465
463
  ScreensNativeModules.NativeSearchBarCommands.focus(ref)
466
464
  );
467
465
  }
468
466
 
469
467
  toggleCancelButton(flag: boolean) {
470
- this._callMethodWithRef((ref) =>
468
+ this._callMethodWithRef(ref =>
471
469
  ScreensNativeModules.NativeSearchBarCommands.toggleCancelButton(ref, flag)
472
470
  );
473
471
  }
474
472
 
475
473
  clearText() {
476
- this._callMethodWithRef((ref) =>
474
+ this._callMethodWithRef(ref =>
477
475
  ScreensNativeModules.NativeSearchBarCommands.clearText(ref)
478
476
  );
479
477
  }
480
478
 
481
479
  setText(text: string) {
482
- this._callMethodWithRef((ref) =>
480
+ this._callMethodWithRef(ref =>
483
481
  ScreensNativeModules.NativeSearchBarCommands.setText(ref, text)
484
482
  );
485
483
  }
@@ -14,6 +14,9 @@ export { default as NativeStackView } from './views/NativeStackView';
14
14
  export { default as useHeaderHeight } from './utils/useHeaderHeight';
15
15
  export { default as HeaderHeightContext } from './utils/HeaderHeightContext';
16
16
 
17
+ export { default as useAnimatedHeaderHeight } from './utils/useAnimatedHeaderHeight';
18
+ export { default as AnimatedHeaderHeightContext } from './utils/AnimatedHeaderHeightContext';
19
+
17
20
  /**
18
21
  * Types
19
22
  */
@@ -10,7 +10,12 @@ import {
10
10
  RouteProp,
11
11
  } from '@react-navigation/native';
12
12
  import * as React from 'react';
13
- import { ImageSourcePropType, StyleProp, ViewStyle } from 'react-native';
13
+ import {
14
+ ImageSourcePropType,
15
+ StyleProp,
16
+ ViewStyle,
17
+ ColorValue,
18
+ } from 'react-native';
14
19
  import {
15
20
  ScreenProps,
16
21
  ScreenStackHeaderConfigProps,
@@ -41,6 +46,10 @@ export type NativeStackNavigationEventMap = {
41
46
  * Event which fires when a swipe back is canceled on iOS.
42
47
  */
43
48
  gestureCancel: { data: undefined };
49
+ /**
50
+ * Event which fires when a header height gets changed.
51
+ */
52
+ headerHeightChange: { data: { headerHeight: number } };
44
53
  };
45
54
 
46
55
  export type NativeStackNavigationProp<
@@ -157,7 +166,7 @@ export type NativeStackNavigationOptions = {
157
166
  /**
158
167
  * Function which returns a React Element to display in the center of the header.
159
168
  */
160
- headerCenter?: (props: { tintColor?: string }) => React.ReactNode;
169
+ headerCenter?: (props: { tintColor?: ColorValue }) => React.ReactNode;
161
170
  /**
162
171
  * Boolean indicating whether to hide the back button in header.
163
172
  */
@@ -173,7 +182,7 @@ export type NativeStackNavigationOptions = {
173
182
  * @platform ios
174
183
  */
175
184
  headerLargeStyle?: {
176
- backgroundColor?: string;
185
+ backgroundColor?: ColorValue;
177
186
  };
178
187
  /**
179
188
  * Boolean to set native property to prefer large title header (like in iOS setting).
@@ -202,16 +211,16 @@ export type NativeStackNavigationOptions = {
202
211
  fontFamily?: string;
203
212
  fontSize?: number;
204
213
  fontWeight?: string;
205
- color?: string;
214
+ color?: ColorValue;
206
215
  };
207
216
  /**
208
217
  * Function which returns a React Element to display on the left side of the header.
209
218
  */
210
- headerLeft?: (props: { tintColor?: string }) => React.ReactNode;
219
+ headerLeft?: (props: { tintColor?: ColorValue }) => React.ReactNode;
211
220
  /**
212
221
  * Function which returns a React Element to display on the right side of the header.
213
222
  */
214
- headerRight?: (props: { tintColor?: string }) => React.ReactNode;
223
+ headerRight?: (props: { tintColor?: ColorValue }) => React.ReactNode;
215
224
  /**
216
225
  * Whether to show the header.
217
226
  */
@@ -222,13 +231,13 @@ export type NativeStackNavigationOptions = {
222
231
  * - blurEffect
223
232
  */
224
233
  headerStyle?: {
225
- backgroundColor?: string;
234
+ backgroundColor?: ColorValue;
226
235
  blurEffect?: ScreenStackHeaderConfigProps['blurEffect'];
227
236
  };
228
237
  /**
229
238
  * Tint color for the header. Changes the color of back button and title.
230
239
  */
231
- headerTintColor?: string;
240
+ headerTintColor?: ColorValue;
232
241
  /**
233
242
  * String to display in the header as title. Defaults to scene `title`.
234
243
  */
@@ -244,7 +253,7 @@ export type NativeStackNavigationOptions = {
244
253
  fontFamily?: string;
245
254
  fontSize?: number;
246
255
  fontWeight?: string;
247
- color?: string;
256
+ color?: ColorValue;
248
257
  };
249
258
  /**
250
259
  * A flag to that lets you opt out of insetting the header. You may want to
@@ -284,7 +293,7 @@ export type NativeStackNavigationOptions = {
284
293
  *
285
294
  * @platform android
286
295
  */
287
- navigationBarColor?: string;
296
+ navigationBarColor?: ColorValue;
288
297
  /**
289
298
  * Sets the visibility of the navigation bar. Defaults to `false`.
290
299
  *
@@ -341,7 +350,7 @@ export type NativeStackNavigationOptions = {
341
350
  * The corner radius that the sheet will try to render with.
342
351
  * Works only when `stackPresentation` is set to `formSheet`.
343
352
  *
344
- * If set to non-negative value it will try to render sheet with provided radius, else ti will apply system default.
353
+ * If set to non-negative value it will try to render sheet with provided radius, else it will apply system default.
345
354
  *
346
355
  * If left unset system default is used.
347
356
  *
@@ -406,7 +415,7 @@ export type NativeStackNavigationOptions = {
406
415
  *
407
416
  * @platform android
408
417
  */
409
- statusBarColor?: string;
418
+ statusBarColor?: ColorValue;
410
419
  /**
411
420
  * Whether the status bar should be hidden on this screen. Requires enabling (or deleting) `View controller-based status bar appearance` in your Info.plist file on iOS. Defaults to `false`.
412
421
  */
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ import { Animated } from 'react-native';
3
+
4
+ const AnimatedHeaderHeightContext = React.createContext<
5
+ Animated.Value | undefined
6
+ >(undefined);
7
+
8
+ export default AnimatedHeaderHeightContext;
@@ -36,7 +36,7 @@ const initialMetrics =
36
36
  export default function SafeAreaProviderCompat({ children, style }: Props) {
37
37
  return (
38
38
  <SafeAreaInsetsContext.Consumer>
39
- {(insets) => {
39
+ {insets => {
40
40
  if (insets) {
41
41
  // If we already have insets, don't wrap the stack in another safe area provider
42
42
  // This avoids an issue with updates at the cost of potentially incorrect values
@@ -0,0 +1,22 @@
1
+ import { Rect } from 'react-native-safe-area-context';
2
+ import { Platform } from 'react-native';
3
+
4
+ export default function getStatusBarHeight(
5
+ topInset: number,
6
+ dimensions: Rect,
7
+ isStatusBarTranslucent: boolean
8
+ ) {
9
+ if (Platform.OS === 'ios') {
10
+ // It looks like some iOS devices don't have strictly set status bar height to 44.
11
+ // Thus, if the top inset is higher than 50, then the device should have a dynamic island.
12
+ // On models with Dynamic Island the status bar height is smaller than the safe area top inset by 5 pixels.
13
+ // See https://developer.apple.com/forums/thread/662466 for more details about status bar height.
14
+ const hasDynamicIsland = topInset > 50;
15
+ return hasDynamicIsland ? topInset - 5 : topInset;
16
+ } else if (Platform.OS === 'android') {
17
+ // On Android we should also rely on frame's y-axis position, as topInset is 0 on visible status bar.
18
+ return isStatusBarTranslucent ? topInset : dimensions.y;
19
+ }
20
+
21
+ return topInset;
22
+ }
@@ -0,0 +1,15 @@
1
+ import * as React from 'react';
2
+
3
+ import AnimatedHeaderHeightContext from './AnimatedHeaderHeightContext';
4
+
5
+ export default function useAnimatedHeaderHeight() {
6
+ const animatedValue = React.useContext(AnimatedHeaderHeightContext);
7
+
8
+ if (animatedValue === undefined) {
9
+ throw new Error(
10
+ "Couldn't find the header height. Are you inside a screen in a navigator with a header?"
11
+ );
12
+ }
13
+
14
+ return animatedValue;
15
+ }
@@ -140,8 +140,7 @@ export default function HeaderConfig({
140
140
  topInsetEnabled={headerTopInsetEnabled}
141
141
  translucent={headerTranslucent === true}
142
142
  onAttached={handleAttached}
143
- onDetached={handleDetached}
144
- >
143
+ onDetached={handleDetached}>
145
144
  {headerRight !== undefined ? (
146
145
  <ScreenStackHeaderRightView>
147
146
  {headerRight({ tintColor })}
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import { Platform, StyleSheet, View, ViewProps } from 'react-native';
2
+ import { Animated, Platform, StyleSheet, View, ViewProps } from 'react-native';
3
3
  // @ts-ignore Getting private component
4
4
  // eslint-disable-next-line import/no-named-as-default, import/default, import/no-named-as-default-member, import/namespace
5
5
  import AppContainer from 'react-native/Libraries/ReactNative/AppContainer';
@@ -22,7 +22,6 @@ import {
22
22
  useSafeAreaFrame,
23
23
  useSafeAreaInsets,
24
24
  } from 'react-native-safe-area-context';
25
-
26
25
  import {
27
26
  NativeStackDescriptorMap,
28
27
  NativeStackNavigationHelpers,
@@ -31,7 +30,9 @@ import {
31
30
  import HeaderConfig from './HeaderConfig';
32
31
  import SafeAreaProviderCompat from '../utils/SafeAreaProviderCompat';
33
32
  import getDefaultHeaderHeight from '../utils/getDefaultHeaderHeight';
33
+ import getStatusBarHeight from '../utils/getStatusBarHeight';
34
34
  import HeaderHeightContext from '../utils/HeaderHeightContext';
35
+ import AnimatedHeaderHeightContext from '../utils/AnimatedHeaderHeightContext';
35
36
 
36
37
  const isAndroid = Platform.OS === 'android';
37
38
 
@@ -103,32 +104,37 @@ const MaybeNestedStack = ({
103
104
  // This view must *not* be flattened.
104
105
  // See https://github.com/software-mansion/react-native-screens/pull/1825
105
106
  // for detailed explanation.
106
- collapsable={false}
107
- >
107
+ collapsable={false}>
108
108
  {children}
109
109
  </Container>
110
110
  );
111
111
 
112
112
  const dimensions = useSafeAreaFrame();
113
113
  const topInset = useSafeAreaInsets().top;
114
- let statusBarHeight = topInset;
115
- const hasDynamicIsland = Platform.OS === 'ios' && topInset === 59;
116
- const isLargeHeader = options.headerLargeTitle ?? false;
117
- if (hasDynamicIsland) {
118
- // On models with Dynamic Island the status bar height is smaller than the safe area top inset.
119
- statusBarHeight = 54;
120
- }
114
+ const isStatusBarTranslucent = options.statusBarTranslucent ?? false;
115
+ const statusBarHeight = getStatusBarHeight(
116
+ topInset,
117
+ dimensions,
118
+ isStatusBarTranslucent
119
+ );
120
+
121
+ const hasLargeHeader = options.headerLargeTitle ?? false;
122
+
121
123
  const headerHeight = getDefaultHeaderHeight(
122
124
  dimensions,
123
125
  statusBarHeight,
124
126
  stackPresentation,
125
- isLargeHeader
127
+ hasLargeHeader
126
128
  );
127
129
 
128
130
  if (isHeaderInModal) {
129
131
  return (
130
132
  <ScreenStack style={styles.container}>
131
- <Screen enabled isNativeStack style={StyleSheet.absoluteFill}>
133
+ <Screen
134
+ enabled
135
+ isNativeStack
136
+ hasLargeHeader={hasLargeHeader}
137
+ style={StyleSheet.absoluteFill}>
132
138
  <HeaderHeightContext.Provider value={headerHeight}>
133
139
  <HeaderConfig {...options} route={route} />
134
140
  {content}
@@ -217,27 +223,42 @@ const RouteView = ({
217
223
  stackPresentation = 'push';
218
224
  }
219
225
 
220
- const isHeaderInPush = isAndroid
221
- ? headerShown
222
- : stackPresentation === 'push' && headerShown !== false;
223
-
224
226
  const dimensions = useSafeAreaFrame();
225
227
  const topInset = useSafeAreaInsets().top;
226
- let statusBarHeight = topInset;
227
- const hasDynamicIsland = Platform.OS === 'ios' && topInset === 59;
228
- const isLargeHeader = options.headerLargeTitle ?? false;
228
+ const isStatusBarTranslucent = options.statusBarTranslucent ?? false;
229
+ const statusBarHeight = getStatusBarHeight(
230
+ topInset,
231
+ dimensions,
232
+ isStatusBarTranslucent
233
+ );
229
234
 
230
- if (hasDynamicIsland) {
231
- // On models with Dynamic Island the status bar height is smaller than the safe area top inset.
232
- statusBarHeight = 54;
233
- }
234
- const headerHeight = getDefaultHeaderHeight(
235
+ const hasLargeHeader = options.headerLargeTitle ?? false;
236
+
237
+ const defaultHeaderHeight = getDefaultHeaderHeight(
235
238
  dimensions,
236
239
  statusBarHeight,
237
240
  stackPresentation,
238
- isLargeHeader
241
+ hasLargeHeader
239
242
  );
243
+
240
244
  const parentHeaderHeight = React.useContext(HeaderHeightContext);
245
+ const isHeaderInPush = isAndroid
246
+ ? headerShown
247
+ : stackPresentation === 'push' && headerShown !== false;
248
+
249
+ const staticHeaderHeight =
250
+ isHeaderInPush !== false ? defaultHeaderHeight : parentHeaderHeight ?? 0;
251
+
252
+ // We need to ensure the first retrieved header height will be cached and set in animatedHeaderHeight.
253
+ // We're caching the header height here, as on iOS native side events are not always coming to the JS on first notify.
254
+ // TODO: Check why first event is not being received once it is cached on the native side.
255
+ const cachedAnimatedHeaderHeight = React.useRef(defaultHeaderHeight);
256
+ const animatedHeaderHeight = React.useRef(
257
+ new Animated.Value(staticHeaderHeight, {
258
+ useNativeDriver: true,
259
+ })
260
+ ).current;
261
+
241
262
  const Screen = React.useContext(ScreenContext);
242
263
 
243
264
  const { dark } = useTheme();
@@ -247,6 +268,7 @@ const RouteView = ({
247
268
  key={route.key}
248
269
  enabled
249
270
  isNativeStack
271
+ hasLargeHeader={hasLargeHeader}
250
272
  style={StyleSheet.absoluteFill}
251
273
  sheetAllowedDetents={sheetAllowedDetents}
252
274
  sheetLargestUndimmedDetent={sheetLargestUndimmedDetent}
@@ -313,7 +335,19 @@ const RouteView = ({
313
335
  target: route.key,
314
336
  });
315
337
  }}
316
- onDismissed={(e) => {
338
+ onHeaderHeightChange={e => {
339
+ const headerHeight = e.nativeEvent.headerHeight;
340
+
341
+ if (cachedAnimatedHeaderHeight.current !== headerHeight) {
342
+ // Currently, we're setting value by Animated#setValue, because we want to cache animated value.
343
+ // Also, in React Native 0.72 there was a bug on Fabric causing a large delay between the screen transition,
344
+ // which should not occur.
345
+ // TODO: Check if it's possible to replace animated#setValue to Animated#event.
346
+ animatedHeaderHeight.setValue(headerHeight);
347
+ cachedAnimatedHeaderHeight.current = headerHeight;
348
+ }
349
+ }}
350
+ onDismissed={e => {
317
351
  navigation.emit({
318
352
  type: 'dismiss',
319
353
  target: route.key,
@@ -333,25 +367,25 @@ const RouteView = ({
333
367
  type: 'gestureCancel',
334
368
  target: route.key,
335
369
  });
336
- }}
337
- >
338
- <HeaderHeightContext.Provider
339
- value={
340
- isHeaderInPush !== false ? headerHeight : parentHeaderHeight ?? 0
341
- }
342
- >
343
- <MaybeNestedStack
344
- options={options}
345
- route={route}
346
- stackPresentation={stackPresentation}
347
- >
348
- {renderScene()}
349
- </MaybeNestedStack>
350
- {/* HeaderConfig must not be first child of a Screen.
370
+ }}>
371
+ <AnimatedHeaderHeightContext.Provider value={animatedHeaderHeight}>
372
+ <HeaderHeightContext.Provider value={staticHeaderHeight}>
373
+ <MaybeNestedStack
374
+ options={options}
375
+ route={route}
376
+ stackPresentation={stackPresentation}>
377
+ {renderScene()}
378
+ </MaybeNestedStack>
379
+ {/* HeaderConfig must not be first child of a Screen.
351
380
  See https://github.com/software-mansion/react-native-screens/pull/1825
352
381
  for detailed explanation */}
353
- <HeaderConfig {...options} route={route} headerShown={isHeaderInPush} />
354
- </HeaderHeightContext.Provider>
382
+ <HeaderConfig
383
+ {...options}
384
+ route={route}
385
+ headerShown={isHeaderInPush}
386
+ />
387
+ </HeaderHeightContext.Provider>
388
+ </AnimatedHeaderHeightContext.Provider>
355
389
  </Screen>
356
390
  );
357
391
  };
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ // @ts-ignore file to be used only if `react-native-reanimated` available in the project
3
+ import Animated from 'react-native-reanimated';
4
+
5
+ export default React.createContext<Animated.SharedValue<number> | undefined>(
6
+ undefined
7
+ );
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import { Platform } from 'react-native';
3
3
  import {
4
+ HeaderHeightChangeEventType,
4
5
  InnerScreen,
5
6
  ScreenProps,
6
7
  TransitionProgressEventType,
@@ -9,6 +10,13 @@ import {
9
10
  // @ts-ignore file to be used only if `react-native-reanimated` available in the project
10
11
  import Animated, { useEvent, useSharedValue } from 'react-native-reanimated';
11
12
  import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext';
13
+ import {
14
+ useSafeAreaFrame,
15
+ useSafeAreaInsets,
16
+ } from 'react-native-safe-area-context';
17
+ import getDefaultHeaderHeight from '../native-stack/utils/getDefaultHeaderHeight';
18
+ import getStatusBarHeight from '../native-stack/utils/getStatusBarHeight';
19
+ import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext';
12
20
 
13
21
  const AnimatedScreen = Animated.createAnimatedComponent(
14
22
  InnerScreen as unknown as React.ComponentClass
@@ -24,6 +32,28 @@ const ReanimatedNativeStackScreen = React.forwardRef<
24
32
  ScreenProps
25
33
  >((props, ref) => {
26
34
  const { children, ...rest } = props;
35
+ const { stackPresentation = 'push', hasLargeHeader } = rest;
36
+
37
+ const dimensions = useSafeAreaFrame();
38
+ const topInset = useSafeAreaInsets().top;
39
+ const isStatusBarTranslucent = rest.statusBarTranslucent ?? false;
40
+ const statusBarHeight = getStatusBarHeight(
41
+ topInset,
42
+ dimensions,
43
+ isStatusBarTranslucent
44
+ );
45
+
46
+ // Default header height, normally used in `useHeaderHeight` hook.
47
+ // Here, it is used for returning a default value for shared value.
48
+ const defaultHeaderHeight = getDefaultHeaderHeight(
49
+ dimensions,
50
+ statusBarHeight,
51
+ stackPresentation,
52
+ hasLargeHeader
53
+ );
54
+
55
+ const cachedHeaderHeight = React.useRef(defaultHeaderHeight);
56
+ const headerHeight = useSharedValue(defaultHeaderHeight);
27
57
 
28
58
  const progress = useSharedValue(0);
29
59
  const closing = useSharedValue(0);
@@ -51,17 +81,34 @@ const ReanimatedNativeStackScreen = React.forwardRef<
51
81
  : 'topTransitionProgress',
52
82
  ]
53
83
  )}
54
- {...rest}
55
- >
56
- <ReanimatedTransitionProgressContext.Provider
57
- value={{
58
- progress: progress,
59
- closing: closing,
60
- goingForward: goingForward,
61
- }}
62
- >
63
- {children}
64
- </ReanimatedTransitionProgressContext.Provider>
84
+ onHeaderHeightChangeReanimated={useEvent(
85
+ (event: HeaderHeightChangeEventType) => {
86
+ 'worklet';
87
+ if (event.headerHeight !== cachedHeaderHeight.current) {
88
+ headerHeight.value = event.headerHeight;
89
+ cachedHeaderHeight.current = event.headerHeight;
90
+ }
91
+ },
92
+ [
93
+ // @ts-ignore wrong type
94
+ Platform.OS === 'android'
95
+ ? 'onHeaderHeightChange'
96
+ : ENABLE_FABRIC
97
+ ? 'onHeaderHeightChange'
98
+ : 'topHeaderHeightChange',
99
+ ]
100
+ )}
101
+ {...rest}>
102
+ <ReanimatedHeaderHeightContext.Provider value={headerHeight}>
103
+ <ReanimatedTransitionProgressContext.Provider
104
+ value={{
105
+ progress,
106
+ closing,
107
+ goingForward,
108
+ }}>
109
+ {children}
110
+ </ReanimatedTransitionProgressContext.Provider>
111
+ </ReanimatedHeaderHeightContext.Provider>
65
112
  </AnimatedScreen>
66
113
  );
67
114
  });
@@ -1,2 +1,3 @@
1
1
  export { default as ReanimatedScreenProvider } from './ReanimatedScreenProvider';
2
2
  export { default as useReanimatedTransitionProgress } from './useReanimatedTransitionProgress';
3
+ export { default as useReanimatedHeaderHeight } from './useReanimatedHeaderHeight';
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ import ReanimatedHeaderHeightContext from './ReanimatedHeaderHeightContext';
3
+
4
+ export default function useReanimatedHeaderHeight() {
5
+ const height = React.useContext(ReanimatedHeaderHeightContext);
6
+
7
+ if (height === undefined) {
8
+ throw new Error(
9
+ "Couldn't find the header height using Reanimated. Are you inside a screen in a navigator with a header and your NavigationContainer is wrapped in ReanimatedScreenProvider?"
10
+ );
11
+ }
12
+
13
+ return height;
14
+ }