@react-navigation/native-stack 7.0.0-rc.27 → 7.0.0-rc.28

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 (68) hide show
  1. package/lib/commonjs/navigators/createNativeStackNavigator.js +3 -1
  2. package/lib/commonjs/navigators/createNativeStackNavigator.js.map +1 -1
  3. package/lib/commonjs/views/DebugContainer.native.js +8 -10
  4. package/lib/commonjs/views/DebugContainer.native.js.map +1 -1
  5. package/lib/commonjs/views/FooterComponent.js +19 -0
  6. package/lib/commonjs/views/FooterComponent.js.map +1 -0
  7. package/lib/commonjs/views/HeaderConfig.js +24 -14
  8. package/lib/commonjs/views/HeaderConfig.js.map +1 -1
  9. package/lib/commonjs/views/NativeStackView.js +10 -7
  10. package/lib/commonjs/views/NativeStackView.js.map +1 -1
  11. package/lib/commonjs/views/NativeStackView.native.js +56 -18
  12. package/lib/commonjs/views/NativeStackView.native.js.map +1 -1
  13. package/lib/module/navigators/createNativeStackNavigator.js +3 -1
  14. package/lib/module/navigators/createNativeStackNavigator.js.map +1 -1
  15. package/lib/module/views/DebugContainer.native.js +9 -11
  16. package/lib/module/views/DebugContainer.native.js.map +1 -1
  17. package/lib/module/views/FooterComponent.js +14 -0
  18. package/lib/module/views/FooterComponent.js.map +1 -0
  19. package/lib/module/views/HeaderConfig.js +24 -14
  20. package/lib/module/views/HeaderConfig.js.map +1 -1
  21. package/lib/module/views/NativeStackView.js +10 -7
  22. package/lib/module/views/NativeStackView.js.map +1 -1
  23. package/lib/module/views/NativeStackView.native.js +56 -18
  24. package/lib/module/views/NativeStackView.native.js.map +1 -1
  25. package/lib/typescript/commonjs/src/index.d.ts +1 -1
  26. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  27. package/lib/typescript/commonjs/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
  28. package/lib/typescript/commonjs/src/types.d.ts +80 -18
  29. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  30. package/lib/typescript/commonjs/src/views/DebugContainer.d.ts +1 -1
  31. package/lib/typescript/commonjs/src/views/DebugContainer.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/src/views/DebugContainer.native.d.ts +1 -1
  33. package/lib/typescript/commonjs/src/views/DebugContainer.native.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/src/views/FooterComponent.d.ts +7 -0
  35. package/lib/typescript/commonjs/src/views/FooterComponent.d.ts.map +1 -0
  36. package/lib/typescript/commonjs/src/views/HeaderConfig.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/src/views/NativeStackView.d.ts +4 -3
  38. package/lib/typescript/commonjs/src/views/NativeStackView.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/src/views/NativeStackView.native.d.ts +4 -3
  40. package/lib/typescript/commonjs/src/views/NativeStackView.native.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/tsconfig.build.tsbuildinfo +1 -1
  42. package/lib/typescript/module/src/index.d.ts +1 -1
  43. package/lib/typescript/module/src/index.d.ts.map +1 -1
  44. package/lib/typescript/module/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
  45. package/lib/typescript/module/src/types.d.ts +80 -18
  46. package/lib/typescript/module/src/types.d.ts.map +1 -1
  47. package/lib/typescript/module/src/views/DebugContainer.d.ts +1 -1
  48. package/lib/typescript/module/src/views/DebugContainer.d.ts.map +1 -1
  49. package/lib/typescript/module/src/views/DebugContainer.native.d.ts +1 -1
  50. package/lib/typescript/module/src/views/DebugContainer.native.d.ts.map +1 -1
  51. package/lib/typescript/module/src/views/FooterComponent.d.ts +7 -0
  52. package/lib/typescript/module/src/views/FooterComponent.d.ts.map +1 -0
  53. package/lib/typescript/module/src/views/HeaderConfig.d.ts.map +1 -1
  54. package/lib/typescript/module/src/views/NativeStackView.d.ts +4 -3
  55. package/lib/typescript/module/src/views/NativeStackView.d.ts.map +1 -1
  56. package/lib/typescript/module/src/views/NativeStackView.native.d.ts +4 -3
  57. package/lib/typescript/module/src/views/NativeStackView.native.d.ts.map +1 -1
  58. package/lib/typescript/module/tsconfig.build.tsbuildinfo +1 -1
  59. package/package.json +7 -7
  60. package/src/index.tsx +2 -0
  61. package/src/navigators/createNativeStackNavigator.tsx +2 -1
  62. package/src/types.tsx +74 -18
  63. package/src/views/DebugContainer.native.tsx +13 -6
  64. package/src/views/DebugContainer.tsx +1 -1
  65. package/src/views/FooterComponent.tsx +10 -0
  66. package/src/views/HeaderConfig.tsx +23 -21
  67. package/src/views/NativeStackView.native.tsx +81 -14
  68. package/src/views/NativeStackView.tsx +23 -11
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-navigation/native-stack",
3
3
  "description": "Native stack navigator using react-native-screens",
4
- "version": "7.0.0-rc.27",
4
+ "version": "7.0.0-rc.28",
5
5
  "keywords": [
6
6
  "react-native-component",
7
7
  "react-component",
@@ -52,27 +52,27 @@
52
52
  "clean": "del lib"
53
53
  },
54
54
  "dependencies": {
55
- "@react-navigation/elements": "^2.0.0-rc.24",
55
+ "@react-navigation/elements": "^2.0.0-rc.25",
56
56
  "warn-once": "^0.1.1"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@jest/globals": "^29.7.0",
60
- "@react-navigation/native": "^7.0.0-rc.19",
60
+ "@react-navigation/native": "^7.0.0-rc.20",
61
61
  "@testing-library/react-native": "^12.4.3",
62
62
  "@types/react": "~18.2.79",
63
63
  "del-cli": "^5.1.0",
64
64
  "react": "18.2.0",
65
65
  "react-native": "0.74.2",
66
66
  "react-native-builder-bob": "^0.29.0",
67
- "react-native-screens": "3.32.0",
67
+ "react-native-screens": "4.0.0-beta.9",
68
68
  "typescript": "^5.5.2"
69
69
  },
70
70
  "peerDependencies": {
71
- "@react-navigation/native": "^7.0.0-rc.19",
71
+ "@react-navigation/native": "^7.0.0-rc.20",
72
72
  "react": ">= 18.2.0",
73
73
  "react-native": ">= 0.72.0",
74
74
  "react-native-safe-area-context": ">= 4.0.0",
75
- "react-native-screens": ">= 3.21.0"
75
+ "react-native-screens": ">= 4.0.0"
76
76
  },
77
77
  "react-native-builder-bob": {
78
78
  "source": "src",
@@ -99,5 +99,5 @@
99
99
  ]
100
100
  ]
101
101
  },
102
- "gitHead": "cfebf105844ac1674eee28ec1ff4ccd30e7895ee"
102
+ "gitHead": "1ad42666923bd76dd9e7dd731ba39cc31dd27317"
103
103
  }
package/src/index.tsx CHANGED
@@ -17,7 +17,9 @@ export { useAnimatedHeaderHeight } from './utils/useAnimatedHeaderHeight';
17
17
  * Types
18
18
  */
19
19
  export type {
20
+ NativeStackHeaderLeftProps,
20
21
  NativeStackHeaderProps,
22
+ NativeStackHeaderRightProps,
21
23
  NativeStackNavigationEventMap,
22
24
  NativeStackNavigationOptions,
23
25
  NativeStackNavigationProp,
@@ -33,7 +33,7 @@ function NativeStackNavigator({
33
33
  UNSTABLE_getStateForRouteNamesChange,
34
34
  ...rest
35
35
  }: NativeStackNavigatorProps) {
36
- const { state, descriptors, navigation, NavigationContent } =
36
+ const { state, describe, descriptors, navigation, NavigationContent } =
37
37
  useNavigationBuilder<
38
38
  StackNavigationState<ParamListBase>,
39
39
  StackRouterOptions,
@@ -84,6 +84,7 @@ function NativeStackNavigator({
84
84
  state={state}
85
85
  navigation={navigation}
86
86
  descriptors={descriptors}
87
+ describe={describe}
87
88
  />
88
89
  </NavigationContent>
89
90
  );
package/src/types.tsx CHANGED
@@ -21,7 +21,6 @@ import type {
21
21
  ScreenProps,
22
22
  ScreenStackHeaderConfigProps,
23
23
  SearchBarProps,
24
- SheetDetentTypes,
25
24
  } from 'react-native-screens';
26
25
 
27
26
  export type NativeStackNavigationEventMap = {
@@ -37,6 +36,15 @@ export type NativeStackNavigationEventMap = {
37
36
  * Event which fires when a swipe back is canceled on iOS.
38
37
  */
39
38
  gestureCancel: { data: undefined };
39
+ /**
40
+ * Event which fires when screen is in sheet presentation & it's detent changes.
41
+ *
42
+ * In payload it caries two fields:
43
+ *
44
+ * * `index` - current detent index in the `sheetAllowedDetents` array,
45
+ * * `stable` - on Android `false` value means that the user is dragging the sheet or it is settling; on iOS it is always `true`.
46
+ */
47
+ sheetDetentChange: { data: { index: number; stable: boolean } };
40
48
  };
41
49
 
42
50
  export type NativeStackNavigationProp<
@@ -439,6 +447,13 @@ export type NativeStackNavigationOptions = {
439
447
  * Style object for the scene content.
440
448
  */
441
449
  contentStyle?: StyleProp<ViewStyle>;
450
+ /**
451
+ * Style object for the screen native component. This might help to workaround
452
+ * some issues when using `formSheet` presentation.
453
+ *
454
+ * Only `backgroundColor` is accepted.
455
+ */
456
+ unstable_screenStyle?: ScreenProps['unstable_screenStyle'];
442
457
  /**
443
458
  * Whether the gesture to dismiss should use animation provided to `animation` prop. Defaults to `false`.
444
459
  *
@@ -497,11 +512,14 @@ export type NativeStackNavigationOptions = {
497
512
  * Supported values:
498
513
  * - "default": use the platform default animation
499
514
  * - "fade": fade screen in or out
515
+ * - "fade_from_bottom" – performs a fade from bottom animation
500
516
  * - "flip": flip the screen, requires presentation: "modal" (iOS only)
501
517
  * - "simple_push": use the platform default animation, but without shadow and native header transition (iOS only)
502
518
  * - "slide_from_bottom": slide in the new screen from bottom
503
519
  * - "slide_from_right": slide in the new screen from right (Android only, uses default animation on iOS)
504
520
  * - "slide_from_left": slide in the new screen from left (Android only, uses default animation on iOS)
521
+ * - "ios_from_right" - iOS like slide in animation. pushes in the new screen from right to left (Android only, resolves to default transition on iOS)
522
+ * - "ios_from_left" - iOS like slide in animation. pushes in the new screen from left to right (Android only, resolves to default transition on iOS)
505
523
  * - "none": don't animate the screen
506
524
  *
507
525
  * Only supported on iOS and Android.
@@ -532,17 +550,32 @@ export type NativeStackNavigationOptions = {
532
550
  /**
533
551
  * Describes heights where a sheet can rest.
534
552
  * Works only when `presentation` is set to `formSheet`.
535
- * Defaults to `large`.
536
553
  *
537
- * Available values:
554
+ * Heights should be described as fraction (a number from `[0, 1]` interval) of screen height / maximum detent height.
555
+ * You can pass an array of ascending values each defining allowed sheet detent. iOS accepts any number of detents,
556
+ * while **Android is limited to three**.
538
557
  *
539
- * - `large` - only large detent level will be allowed
540
- * - `medium` - only medium detent level will be allowed
541
- * - `all` - all detent levels will be allowed
558
+ * There is also possibility to specify `fitToContents` literal, which intents to set the sheet height
559
+ * to the height of its contents.
542
560
  *
543
- * @platform ios
561
+ * Please note that the array **must** be sorted in ascending order. This invariant is verified only in developement mode,
562
+ * where violation results in error.
563
+ *
564
+ * **Android is limited to up 3 values in the array** -- any surplus values, beside first three are ignored.
565
+ *
566
+ * Defaults to `[1.0]`.
567
+ */
568
+ sheetAllowedDetents?: number[] | 'fitToContents';
569
+ /**
570
+ * Integer value describing elevation of the sheet, impacting shadow on the top edge of the sheet.
571
+ *
572
+ * Not dynamic - changing it after the component is rendered won't have an effect.
573
+ *
574
+ * Defaults to `24`.
575
+ *
576
+ * @platform Android
544
577
  */
545
- sheetAllowedDetents?: SheetDetentTypes;
578
+ sheetElevation?: number;
546
579
  /**
547
580
  * Whether the sheet should expand to larger detent when scrolling.
548
581
  * Works only when `presentation` is set to `formSheet`.
@@ -558,10 +591,20 @@ export type NativeStackNavigationOptions = {
558
591
  * If set to non-negative value it will try to render sheet with provided radius, else it will apply system default.
559
592
  *
560
593
  * If left unset system default is used.
561
- *
562
- * @platform ios
563
594
  */
564
595
  sheetCornerRadius?: number;
596
+ /**
597
+ * Index of the detent the sheet should expand to after being opened.
598
+ * Works only when `stackPresentation` is set to `formSheet`.
599
+ *
600
+ * If the specified index is out of bounds of `sheetAllowedDetents` array, in dev environment more error will be thrown,
601
+ * in production the value will be reset to default value.
602
+ *
603
+ * Additionaly there is `last` value available, when set the sheet will expand initially to last (largest) detent.
604
+ *
605
+ * Defaults to `0` - which represents first detent in the detents array.
606
+ */
607
+ sheetInitialDetentIndex?: number | 'last';
565
608
  /**
566
609
  * Boolean indicating whether the sheet shows a grabber at the top.
567
610
  * Works only when `presentation` is set to `formSheet`.
@@ -572,19 +615,19 @@ export type NativeStackNavigationOptions = {
572
615
  sheetGrabberVisible?: boolean;
573
616
  /**
574
617
  * The largest sheet detent for which a view underneath won't be dimmed.
575
- * Works only when `presentation` is se tto `formSheet`.
618
+ * Works only when `presentation` is set to `formSheet`.
576
619
  *
577
- * If this prop is set to:
620
+ * This prop can be set to an number, which indicates index of detent in `sheetAllowedDetents` array for which
621
+ * there won't be a dimming view beneath the sheet.
578
622
  *
579
- * - `large` - the view underneath won't be dimmed at any detent level
580
- * - `medium` - the view underneath will be dimmed only when detent level is `large`
581
- * - `all` - the view underneath will be dimmed for any detent level
623
+ * Additionaly there are following options available:
582
624
  *
583
- * Defaults to `all`.
625
+ * * `none` - there will be dimming view for all detents levels,
626
+ * * `last` - there won't be a dimming view for any detent level.
584
627
  *
585
- * @platform ios
628
+ * Defaults to `none`, indicating that the dimming view should be always present.
586
629
  */
587
- sheetLargestUndimmedDetent?: SheetDetentTypes;
630
+ sheetLargestUndimmedDetentIndex?: number | 'none' | 'last';
588
631
  /**
589
632
  * The display orientation to use for the screen.
590
633
  *
@@ -609,6 +652,19 @@ export type NativeStackNavigationOptions = {
609
652
  * Only supported on iOS and Android.
610
653
  */
611
654
  freezeOnBlur?: boolean;
655
+ /**
656
+ * Footer component that can be used alongside formSheet stack presentation style.
657
+ *
658
+ * This option is provided, because due to implementation details it might be problematic
659
+ * to implement such layout with JS-only code.
660
+ *
661
+ * Please note that this prop is marked as unstable and might be subject of breaking changes,
662
+ * including removal, in particular when we find solution that will make implementing it with JS
663
+ * straightforward.
664
+ *
665
+ * @platform android
666
+ */
667
+ unstable_sheetFooter?: () => React.ReactNode;
612
668
  };
613
669
 
614
670
  export type NativeStackNavigatorProps = DefaultNavigatorOptions<
@@ -1,8 +1,11 @@
1
1
  import * as React from 'react';
2
- import { Platform, View, type ViewProps } from 'react-native';
2
+ import { Platform, type ViewProps } from 'react-native';
3
3
  // @ts-expect-error Getting private component
4
4
  import AppContainer from 'react-native/Libraries/ReactNative/AppContainer';
5
- import type { StackPresentationTypes } from 'react-native-screens';
5
+ import {
6
+ ScreenContentWrapper,
7
+ type StackPresentationTypes,
8
+ } from 'react-native-screens';
6
9
 
7
10
  type ContainerProps = ViewProps & {
8
11
  stackPresentation: StackPresentationTypes;
@@ -15,22 +18,26 @@ type ContainerProps = ViewProps & {
15
18
  * for detailed explanation.
16
19
  */
17
20
  export let DebugContainer = (props: ContainerProps) => {
18
- return <View {...props} collapsable={false} />;
21
+ return <ScreenContentWrapper {...props} />;
19
22
  };
20
23
 
21
24
  if (process.env.NODE_ENV !== 'production') {
22
25
  DebugContainer = (props: ContainerProps) => {
23
26
  const { stackPresentation, ...rest } = props;
24
27
 
25
- if (Platform.OS === 'ios' && stackPresentation !== 'push') {
28
+ if (
29
+ Platform.OS === 'ios' &&
30
+ stackPresentation !== 'push' &&
31
+ stackPresentation !== 'formSheet'
32
+ ) {
26
33
  // This is necessary for LogBox
27
34
  return (
28
35
  <AppContainer>
29
- <View {...rest} collapsable={false} />
36
+ <ScreenContentWrapper {...rest} />
30
37
  </AppContainer>
31
38
  );
32
39
  }
33
40
 
34
- return <View {...rest} collapsable={false} />;
41
+ return <ScreenContentWrapper {...rest} />;
35
42
  };
36
43
  }
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { View, type ViewProps } from 'react-native';
3
- import type { StackPresentationTypes } from 'react-native-screens';
3
+ import { type StackPresentationTypes } from 'react-native-screens';
4
4
 
5
5
  type ContainerProps = ViewProps & {
6
6
  stackPresentation: StackPresentationTypes;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+ import { ScreenFooter } from 'react-native-screens';
3
+
4
+ type FooterProps = {
5
+ children?: React.ReactNode;
6
+ };
7
+
8
+ export function FooterComponent({ children }: FooterProps) {
9
+ return <ScreenFooter collapsable={false}>{children}</ScreenFooter>;
10
+ }
@@ -164,9 +164,11 @@ export function HeaderConfig({
164
164
  * - Back button should stay visible when `headerLeft` is specified
165
165
  * - If `headerTitle` for Android is specified, so we only need to remove the title and keep the back button
166
166
  */
167
- const backButtonInCustomView = headerBackVisible
168
- ? headerLeftElement != null
169
- : Platform.OS === 'android' && headerTitleElement != null;
167
+ const backButtonInCustomView =
168
+ headerBackVisible ||
169
+ (Platform.OS === 'android' &&
170
+ headerTitleElement != null &&
171
+ headerLeftElement == null);
170
172
 
171
173
  const translucent =
172
174
  headerBackground != null ||
@@ -188,6 +190,8 @@ export function HeaderConfig({
188
190
  // Back button menu is not disabled
189
191
  headerBackButtonMenuEnabled !== false;
190
192
 
193
+ const isCenterViewRenderedAndroid = headerTitleAlign === 'center';
194
+
191
195
  return (
192
196
  <ScreenStackHeaderConfig
193
197
  backButtonInCustomView={backButtonInCustomView}
@@ -250,25 +254,30 @@ export function HeaderConfig({
250
254
  ) : (
251
255
  <>
252
256
  {headerLeftElement != null || typeof headerTitle === 'function' ? (
253
- <ScreenStackHeaderLeftView>
254
- <View style={styles.row}>
255
- {headerLeftElement}
256
- {headerTitleAlign !== 'center' ? (
257
- typeof headerTitle === 'function' ? (
258
- headerTitleElement
259
- ) : (
257
+ // The style passed to header left, together with title element being wrapped
258
+ // in flex view is reqruied for proper header layout, in particular,
259
+ // for the text truncation to work.
260
+ <ScreenStackHeaderLeftView
261
+ style={!isCenterViewRenderedAndroid ? { flex: 1 } : null}
262
+ >
263
+ {headerLeftElement}
264
+ {headerTitleAlign !== 'center' ? (
265
+ typeof headerTitle === 'function' ? (
266
+ <View style={{ flex: 1 }}>{headerTitleElement}</View>
267
+ ) : (
268
+ <View style={{ flex: 1 }}>
260
269
  <HeaderTitle
261
270
  tintColor={tintColor}
262
271
  style={headerTitleStyleSupported}
263
272
  >
264
273
  {titleText}
265
274
  </HeaderTitle>
266
- )
267
- ) : null}
268
- </View>
275
+ </View>
276
+ )
277
+ ) : null}
269
278
  </ScreenStackHeaderLeftView>
270
279
  ) : null}
271
- {headerTitleAlign === 'center' ? (
280
+ {isCenterViewRenderedAndroid ? (
272
281
  <ScreenStackHeaderCenterView>
273
282
  {typeof headerTitle === 'function' ? (
274
283
  headerTitleElement
@@ -300,10 +309,3 @@ export function HeaderConfig({
300
309
  </ScreenStackHeaderConfig>
301
310
  );
302
311
  }
303
-
304
- const styles = StyleSheet.create({
305
- row: {
306
- flexDirection: 'row',
307
- alignItems: 'center',
308
- },
309
- });
@@ -11,6 +11,7 @@ import {
11
11
  NavigationRouteContext,
12
12
  type ParamListBase,
13
13
  type Route,
14
+ type RouteProp,
14
15
  StackActions,
15
16
  type StackNavigationState,
16
17
  usePreventRemoveContext,
@@ -49,6 +50,7 @@ import { AnimatedHeaderHeightContext } from '../utils/useAnimatedHeaderHeight';
49
50
  import { useDismissedRouteError } from '../utils/useDismissedRouteError';
50
51
  import { useInvalidPreventRemoveError } from '../utils/useInvalidPreventRemoveError';
51
52
  import { DebugContainer } from './DebugContainer';
53
+ import { FooterComponent } from './FooterComponent';
52
54
  import { HeaderConfig } from './HeaderConfig';
53
55
 
54
56
  const ANDROID_DEFAULT_HEADER_HEIGHT = 56;
@@ -60,6 +62,7 @@ const MaybeNestedStack = ({
60
62
  headerHeight,
61
63
  headerTopInsetEnabled,
62
64
  children,
65
+ isPreloaded,
63
66
  }: {
64
67
  options: NativeStackNavigationOptions;
65
68
  route: Route<string>;
@@ -67,9 +70,15 @@ const MaybeNestedStack = ({
67
70
  headerHeight: number;
68
71
  headerTopInsetEnabled: boolean;
69
72
  children: React.ReactNode;
73
+ isPreloaded?: boolean;
70
74
  }) => {
71
75
  const { colors } = useTheme();
72
- const { header, headerShown = true, contentStyle } = options;
76
+ const {
77
+ header,
78
+ headerShown = true,
79
+ contentStyle,
80
+ unstable_screenStyle = null,
81
+ } = options;
73
82
 
74
83
  const isHeaderInModal =
75
84
  Platform.OS === 'android'
@@ -92,7 +101,11 @@ const MaybeNestedStack = ({
92
101
  const content = (
93
102
  <DebugContainer
94
103
  style={[
95
- styles.container,
104
+ presentation === 'formSheet'
105
+ ? Platform.OS === 'ios'
106
+ ? styles.absolute
107
+ : null
108
+ : styles.container,
96
109
  presentation !== 'transparentModal' &&
97
110
  presentation !== 'containedTransparentModal' && {
98
111
  backgroundColor: colors.background,
@@ -112,7 +125,8 @@ const MaybeNestedStack = ({
112
125
  enabled
113
126
  isNativeStack
114
127
  hasLargeHeader={options.headerLargeTitle ?? false}
115
- style={StyleSheet.absoluteFill}
128
+ style={[StyleSheet.absoluteFill, unstable_screenStyle]}
129
+ activityState={isPreloaded ? 0 : 2}
116
130
  >
117
131
  {content}
118
132
  <HeaderConfig
@@ -137,6 +151,7 @@ type SceneViewProps = {
137
151
  previousDescriptor?: NativeStackDescriptor;
138
152
  nextDescriptor?: NativeStackDescriptor;
139
153
  isPresentationModal?: boolean;
154
+ isPreloaded?: boolean;
140
155
  onWillDisappear: () => void;
141
156
  onWillAppear: () => void;
142
157
  onAppear: () => void;
@@ -145,6 +160,7 @@ type SceneViewProps = {
145
160
  onHeaderBackButtonClicked: ScreenProps['onHeaderBackButtonClicked'];
146
161
  onNativeDismissCancelled: ScreenProps['onDismissed'];
147
162
  onGestureCancel: ScreenProps['onGestureCancel'];
163
+ onSheetDetentChanged: ScreenProps['onSheetDetentChanged'];
148
164
  };
149
165
 
150
166
  const SceneView = ({
@@ -154,6 +170,7 @@ const SceneView = ({
154
170
  previousDescriptor,
155
171
  nextDescriptor,
156
172
  isPresentationModal,
173
+ isPreloaded,
157
174
  onWillDisappear,
158
175
  onWillAppear,
159
176
  onAppear,
@@ -162,6 +179,7 @@ const SceneView = ({
162
179
  onHeaderBackButtonClicked,
163
180
  onNativeDismissCancelled,
164
181
  onGestureCancel,
182
+ onSheetDetentChanged,
165
183
  }: SceneViewProps) => {
166
184
  const { route, navigation, options, render } = descriptor;
167
185
 
@@ -170,6 +188,7 @@ const SceneView = ({
170
188
  animationMatchesGesture,
171
189
  presentation = isPresentationModal ? 'modal' : 'card',
172
190
  fullScreenGestureEnabled,
191
+ unstable_screenStyle = null,
173
192
  } = options;
174
193
 
175
194
  const {
@@ -190,19 +209,30 @@ const SceneView = ({
190
209
  navigationBarTranslucent,
191
210
  navigationBarHidden,
192
211
  orientation,
193
- sheetAllowedDetents = 'large',
194
- sheetLargestUndimmedDetent = 'all',
212
+ sheetAllowedDetents = [1.0],
213
+ sheetLargestUndimmedDetentIndex = -1,
195
214
  sheetGrabberVisible = false,
196
215
  sheetCornerRadius = -1.0,
216
+ sheetElevation = 24,
197
217
  sheetExpandsWhenScrolledToEdge = true,
218
+ sheetInitialDetentIndex = 0,
198
219
  statusBarAnimation,
199
220
  statusBarHidden,
200
221
  statusBarStyle,
201
222
  statusBarTranslucent,
202
223
  statusBarBackgroundColor,
224
+ unstable_sheetFooter = null,
203
225
  freezeOnBlur,
204
226
  } = options;
205
227
 
228
+ // We want to allow only backgroundColor setting for now.
229
+ // This allows to workaround one issue with truncated
230
+ // content with formSheet presentation.
231
+ unstable_screenStyle =
232
+ unstable_screenStyle && presentation === 'formSheet'
233
+ ? { backgroundColor: unstable_screenStyle.backgroundColor }
234
+ : null;
235
+
206
236
  if (gestureDirection === 'vertical' && Platform.OS === 'ios') {
207
237
  // for `vertical` direction to work, we need to set `fullScreenGestureEnabled` to `true`
208
238
  // so the screen can be dismissed from any point on screen.
@@ -329,12 +359,15 @@ const SceneView = ({
329
359
  key={route.key}
330
360
  enabled
331
361
  isNativeStack
362
+ activityState={isPreloaded ? 0 : 2}
363
+ style={[StyleSheet.absoluteFill, unstable_screenStyle]}
332
364
  accessibilityElementsHidden={!focused}
333
365
  importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
334
- style={StyleSheet.absoluteFill}
335
366
  hasLargeHeader={options.headerLargeTitle ?? false}
336
367
  customAnimationOnSwipe={animationMatchesGesture}
337
368
  fullScreenSwipeEnabled={fullScreenGestureEnabled}
369
+ fullScreenSwipeShadowEnabled={fullScreenGestureShadowEnabled}
370
+ freezeOnBlur={freezeOnBlur}
338
371
  gestureEnabled={
339
372
  Platform.OS === 'android'
340
373
  ? // This prop enables handling of system back gestures on Android
@@ -352,9 +385,11 @@ const SceneView = ({
352
385
  stackAnimation={animation}
353
386
  screenOrientation={orientation}
354
387
  sheetAllowedDetents={sheetAllowedDetents}
355
- sheetLargestUndimmedDetent={sheetLargestUndimmedDetent}
388
+ sheetLargestUndimmedDetentIndex={sheetLargestUndimmedDetentIndex}
356
389
  sheetGrabberVisible={sheetGrabberVisible}
390
+ sheetInitialDetentIndex={sheetInitialDetentIndex}
357
391
  sheetCornerRadius={sheetCornerRadius}
392
+ sheetElevation={sheetElevation}
358
393
  sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge}
359
394
  statusBarAnimation={statusBarAnimation}
360
395
  statusBarHidden={statusBarHidden}
@@ -369,6 +404,7 @@ const SceneView = ({
369
404
  onDisappear={onDisappear}
370
405
  onDismissed={onDismissed}
371
406
  onGestureCancel={onGestureCancel}
407
+ onSheetDetentChanged={onSheetDetentChanged}
372
408
  gestureResponseDistance={gestureResponseDistance}
373
409
  nativeBackButtonDismissalEnabled={false} // on Android
374
410
  onHeaderBackButtonClicked={onHeaderBackButtonClicked}
@@ -423,12 +459,9 @@ const SceneView = ({
423
459
  },
424
460
  }
425
461
  )}
426
- freezeOnBlur={freezeOnBlur}
427
462
  // When ts-expect-error is added, it affects all the props below it
428
463
  // So we keep any props that need it at the end
429
464
  // Otherwise invalid props may not be caught by TypeScript
430
- // @ts-expect-error Props available in newer versions of `react-native-screens`
431
- fullScreenSwipeShadowEnabled={fullScreenGestureShadowEnabled} // 3.33.0 onwards
432
465
  >
433
466
  <NavigationContext.Provider value={navigation}>
434
467
  <NavigationRouteContext.Provider value={route}>
@@ -498,7 +531,7 @@ const SceneView = ({
498
531
  *
499
532
  * HeaderConfig must not be first child of a Screen.
500
533
  * See https://github.com/software-mansion/react-native-screens/pull/1825
501
- * for detailed explanation
534
+ * for detailed explanation.
502
535
  */}
503
536
  <HeaderConfig
504
537
  {...options}
@@ -518,6 +551,9 @@ const SceneView = ({
518
551
  headerTopInsetEnabled={headerTopInsetEnabled}
519
552
  canGoBack={headerBack !== undefined}
520
553
  />
554
+ {presentation === 'formSheet' && unstable_sheetFooter && (
555
+ <FooterComponent>{unstable_sheetFooter()}</FooterComponent>
556
+ )}
521
557
  </HeaderHeightContext.Provider>
522
558
  </AnimatedHeaderHeightContext.Provider>
523
559
  </NavigationRouteContext.Provider>
@@ -530,9 +566,18 @@ type Props = {
530
566
  state: StackNavigationState<ParamListBase>;
531
567
  navigation: NativeStackNavigationHelpers;
532
568
  descriptors: NativeStackDescriptorMap;
569
+ describe: (
570
+ route: RouteProp<ParamListBase>,
571
+ placeholder: boolean
572
+ ) => NativeStackDescriptor;
533
573
  };
534
574
 
535
- export function NativeStackView({ state, navigation, descriptors }: Props) {
575
+ export function NativeStackView({
576
+ state,
577
+ navigation,
578
+ descriptors,
579
+ describe,
580
+ }: Props) {
536
581
  const { setNextDismissedKey } = useDismissedRouteError(state);
537
582
 
538
583
  const { colors } = useTheme();
@@ -541,11 +586,18 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
541
586
 
542
587
  const modalRouteKeys = getModalRouteKeys(state.routes, descriptors);
543
588
 
589
+ const preloadedDescriptors =
590
+ state.preloadedRoutes.reduce<NativeStackDescriptorMap>((acc, route) => {
591
+ acc[route.key] = acc[route.key] || describe(route, true);
592
+ return acc;
593
+ }, {});
594
+
544
595
  return (
545
596
  <SafeAreaProviderCompat style={{ backgroundColor: colors.background }}>
546
597
  <ScreenStack style={styles.container}>
547
- {state.routes.map((route, index) => {
548
- const descriptor = descriptors[route.key];
598
+ {state.routes.concat(state.preloadedRoutes).map((route, index) => {
599
+ const descriptor =
600
+ descriptors[route.key] ?? preloadedDescriptors[route.key];
549
601
  const isFocused = state.index === index;
550
602
  const previousKey = state.routes[index - 1]?.key;
551
603
  const nextKey = state.routes[index + 1]?.key;
@@ -556,6 +608,10 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
556
608
 
557
609
  const isModal = modalRouteKeys.includes(route.key);
558
610
 
611
+ const isPreloaded =
612
+ preloadedDescriptors[route.key] !== undefined &&
613
+ descriptors[route.key] === undefined;
614
+
559
615
  return (
560
616
  <SceneView
561
617
  key={route.key}
@@ -565,6 +621,7 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
565
621
  previousDescriptor={previousDescriptor}
566
622
  nextDescriptor={nextDescriptor}
567
623
  isPresentationModal={isModal}
624
+ isPreloaded={isPreloaded}
568
625
  onWillDisappear={() => {
569
626
  navigation.emit({
570
627
  type: 'transitionStart',
@@ -622,6 +679,16 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
622
679
  target: route.key,
623
680
  });
624
681
  }}
682
+ onSheetDetentChanged={(event) => {
683
+ navigation.emit({
684
+ type: 'sheetDetentChange',
685
+ target: route.key,
686
+ data: {
687
+ index: event.nativeEvent.index,
688
+ stable: event.nativeEvent.isStable,
689
+ },
690
+ });
691
+ }}
625
692
  />
626
693
  );
627
694
  })}