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

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 (85) 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/NativeStackView.js +11 -8
  8. package/lib/commonjs/views/NativeStackView.js.map +1 -1
  9. package/lib/commonjs/views/NativeStackView.native.js +84 -95
  10. package/lib/commonjs/views/NativeStackView.native.js.map +1 -1
  11. package/lib/commonjs/views/ScreenStackContent.js +70 -0
  12. package/lib/commonjs/views/ScreenStackContent.js.map +1 -0
  13. package/lib/commonjs/views/{HeaderConfig.js → useHeaderConfigProps.js} +59 -48
  14. package/lib/commonjs/views/useHeaderConfigProps.js.map +1 -0
  15. package/lib/module/navigators/createNativeStackNavigator.js +3 -1
  16. package/lib/module/navigators/createNativeStackNavigator.js.map +1 -1
  17. package/lib/module/views/DebugContainer.native.js +9 -11
  18. package/lib/module/views/DebugContainer.native.js.map +1 -1
  19. package/lib/module/views/FooterComponent.js +14 -0
  20. package/lib/module/views/FooterComponent.js.map +1 -0
  21. package/lib/module/views/NativeStackView.js +11 -8
  22. package/lib/module/views/NativeStackView.js.map +1 -1
  23. package/lib/module/views/NativeStackView.native.js +85 -95
  24. package/lib/module/views/NativeStackView.native.js.map +1 -1
  25. package/lib/module/views/ScreenStackContent.js +63 -0
  26. package/lib/module/views/ScreenStackContent.js.map +1 -0
  27. package/lib/module/views/{HeaderConfig.js → useHeaderConfigProps.js} +59 -48
  28. package/lib/module/views/useHeaderConfigProps.js.map +1 -0
  29. package/lib/typescript/commonjs/src/index.d.ts +1 -1
  30. package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
  31. package/lib/typescript/commonjs/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
  32. package/lib/typescript/commonjs/src/types.d.ts +80 -18
  33. package/lib/typescript/commonjs/src/types.d.ts.map +1 -1
  34. package/lib/typescript/commonjs/src/views/DebugContainer.d.ts +1 -1
  35. package/lib/typescript/commonjs/src/views/DebugContainer.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/src/views/DebugContainer.native.d.ts +1 -1
  37. package/lib/typescript/commonjs/src/views/DebugContainer.native.d.ts.map +1 -1
  38. package/lib/typescript/commonjs/src/views/FooterComponent.d.ts +7 -0
  39. package/lib/typescript/commonjs/src/views/FooterComponent.d.ts.map +1 -0
  40. package/lib/typescript/commonjs/src/views/NativeStackView.d.ts +4 -3
  41. package/lib/typescript/commonjs/src/views/NativeStackView.d.ts.map +1 -1
  42. package/lib/typescript/commonjs/src/views/NativeStackView.native.d.ts +4 -3
  43. package/lib/typescript/commonjs/src/views/NativeStackView.native.d.ts.map +1 -1
  44. package/lib/typescript/commonjs/src/views/ScreenStackContent.d.ts +9 -0
  45. package/lib/typescript/commonjs/src/views/ScreenStackContent.d.ts.map +1 -0
  46. package/lib/typescript/commonjs/src/views/useHeaderConfigProps.d.ts +41 -0
  47. package/lib/typescript/commonjs/src/views/useHeaderConfigProps.d.ts.map +1 -0
  48. package/lib/typescript/commonjs/tsconfig.build.tsbuildinfo +1 -1
  49. package/lib/typescript/module/src/index.d.ts +1 -1
  50. package/lib/typescript/module/src/index.d.ts.map +1 -1
  51. package/lib/typescript/module/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
  52. package/lib/typescript/module/src/types.d.ts +80 -18
  53. package/lib/typescript/module/src/types.d.ts.map +1 -1
  54. package/lib/typescript/module/src/views/DebugContainer.d.ts +1 -1
  55. package/lib/typescript/module/src/views/DebugContainer.d.ts.map +1 -1
  56. package/lib/typescript/module/src/views/DebugContainer.native.d.ts +1 -1
  57. package/lib/typescript/module/src/views/DebugContainer.native.d.ts.map +1 -1
  58. package/lib/typescript/module/src/views/FooterComponent.d.ts +7 -0
  59. package/lib/typescript/module/src/views/FooterComponent.d.ts.map +1 -0
  60. package/lib/typescript/module/src/views/NativeStackView.d.ts +4 -3
  61. package/lib/typescript/module/src/views/NativeStackView.d.ts.map +1 -1
  62. package/lib/typescript/module/src/views/NativeStackView.native.d.ts +4 -3
  63. package/lib/typescript/module/src/views/NativeStackView.native.d.ts.map +1 -1
  64. package/lib/typescript/module/src/views/ScreenStackContent.d.ts +9 -0
  65. package/lib/typescript/module/src/views/ScreenStackContent.d.ts.map +1 -0
  66. package/lib/typescript/module/src/views/useHeaderConfigProps.d.ts +41 -0
  67. package/lib/typescript/module/src/views/useHeaderConfigProps.d.ts.map +1 -0
  68. package/lib/typescript/module/tsconfig.build.tsbuildinfo +1 -1
  69. package/package.json +9 -9
  70. package/src/index.tsx +2 -0
  71. package/src/navigators/createNativeStackNavigator.tsx +2 -1
  72. package/src/types.tsx +74 -18
  73. package/src/views/DebugContainer.native.tsx +13 -6
  74. package/src/views/DebugContainer.tsx +1 -1
  75. package/src/views/FooterComponent.tsx +10 -0
  76. package/src/views/NativeStackView.native.tsx +111 -151
  77. package/src/views/NativeStackView.tsx +24 -12
  78. package/src/views/ScreenStackContent.tsx +121 -0
  79. package/src/views/{HeaderConfig.tsx → useHeaderConfigProps.tsx} +67 -71
  80. package/lib/commonjs/views/HeaderConfig.js.map +0 -1
  81. package/lib/module/views/HeaderConfig.js.map +0 -1
  82. package/lib/typescript/commonjs/src/views/HeaderConfig.d.ts +0 -11
  83. package/lib/typescript/commonjs/src/views/HeaderConfig.d.ts.map +0 -1
  84. package/lib/typescript/module/src/views/HeaderConfig.d.ts +0 -11
  85. package/lib/typescript/module/src/views/HeaderConfig.d.ts.map +0 -1
@@ -10,7 +10,7 @@ import {
10
10
  NavigationContext,
11
11
  NavigationRouteContext,
12
12
  type ParamListBase,
13
- type Route,
13
+ type RouteProp,
14
14
  StackActions,
15
15
  type StackNavigationState,
16
16
  usePreventRemoveContext,
@@ -29,107 +29,24 @@ import {
29
29
  useSafeAreaFrame,
30
30
  useSafeAreaInsets,
31
31
  } from 'react-native-safe-area-context';
32
- import {
33
- Screen,
34
- type ScreenProps,
35
- ScreenStack,
36
- type StackPresentationTypes,
37
- } from 'react-native-screens';
38
- import warnOnce from 'warn-once';
32
+ import { type ScreenProps, ScreenStack } from 'react-native-screens';
39
33
 
40
34
  import type {
41
35
  NativeStackDescriptor,
42
36
  NativeStackDescriptorMap,
43
37
  NativeStackNavigationHelpers,
44
- NativeStackNavigationOptions,
45
38
  } from '../types';
46
39
  import { debounce } from '../utils/debounce';
47
40
  import { getModalRouteKeys } from '../utils/getModalRoutesKeys';
48
41
  import { AnimatedHeaderHeightContext } from '../utils/useAnimatedHeaderHeight';
49
42
  import { useDismissedRouteError } from '../utils/useDismissedRouteError';
50
43
  import { useInvalidPreventRemoveError } from '../utils/useInvalidPreventRemoveError';
51
- import { DebugContainer } from './DebugContainer';
52
- import { HeaderConfig } from './HeaderConfig';
44
+ import { FooterComponent } from './FooterComponent';
45
+ import { ScreenStackContent } from './ScreenStackContent';
46
+ import { useHeaderConfigProps } from './useHeaderConfigProps';
53
47
 
54
48
  const ANDROID_DEFAULT_HEADER_HEIGHT = 56;
55
49
 
56
- const MaybeNestedStack = ({
57
- options,
58
- route,
59
- presentation,
60
- headerHeight,
61
- headerTopInsetEnabled,
62
- children,
63
- }: {
64
- options: NativeStackNavigationOptions;
65
- route: Route<string>;
66
- presentation: Exclude<StackPresentationTypes, 'push'> | 'card';
67
- headerHeight: number;
68
- headerTopInsetEnabled: boolean;
69
- children: React.ReactNode;
70
- }) => {
71
- const { colors } = useTheme();
72
- const { header, headerShown = true, contentStyle } = options;
73
-
74
- const isHeaderInModal =
75
- Platform.OS === 'android'
76
- ? false
77
- : presentation !== 'card' && headerShown === true && header === undefined;
78
-
79
- const headerShownPreviousRef = React.useRef(headerShown);
80
-
81
- React.useEffect(() => {
82
- warnOnce(
83
- Platform.OS !== 'android' &&
84
- presentation !== 'card' &&
85
- headerShownPreviousRef.current !== headerShown,
86
- `Dynamically changing 'headerShown' in modals will result in remounting the screen and losing all local state. See options for the screen '${route.name}'.`
87
- );
88
-
89
- headerShownPreviousRef.current = headerShown;
90
- }, [headerShown, presentation, route.name]);
91
-
92
- const content = (
93
- <DebugContainer
94
- style={[
95
- styles.container,
96
- presentation !== 'transparentModal' &&
97
- presentation !== 'containedTransparentModal' && {
98
- backgroundColor: colors.background,
99
- },
100
- contentStyle,
101
- ]}
102
- stackPresentation={presentation === 'card' ? 'push' : presentation}
103
- >
104
- {children}
105
- </DebugContainer>
106
- );
107
-
108
- if (isHeaderInModal) {
109
- return (
110
- <ScreenStack style={styles.container}>
111
- <Screen
112
- enabled
113
- isNativeStack
114
- hasLargeHeader={options.headerLargeTitle ?? false}
115
- style={StyleSheet.absoluteFill}
116
- >
117
- {content}
118
- <HeaderConfig
119
- {...options}
120
- route={route}
121
- headerHeight={headerHeight}
122
- headerTopInsetEnabled={headerTopInsetEnabled}
123
- canGoBack
124
- />
125
- </Screen>
126
- </ScreenStack>
127
- );
128
- }
129
-
130
- return content;
131
- };
132
-
133
50
  type SceneViewProps = {
134
51
  index: number;
135
52
  focused: boolean;
@@ -137,6 +54,7 @@ type SceneViewProps = {
137
54
  previousDescriptor?: NativeStackDescriptor;
138
55
  nextDescriptor?: NativeStackDescriptor;
139
56
  isPresentationModal?: boolean;
57
+ isPreloaded?: boolean;
140
58
  onWillDisappear: () => void;
141
59
  onWillAppear: () => void;
142
60
  onAppear: () => void;
@@ -145,6 +63,7 @@ type SceneViewProps = {
145
63
  onHeaderBackButtonClicked: ScreenProps['onHeaderBackButtonClicked'];
146
64
  onNativeDismissCancelled: ScreenProps['onDismissed'];
147
65
  onGestureCancel: ScreenProps['onGestureCancel'];
66
+ onSheetDetentChanged: ScreenProps['onSheetDetentChanged'];
148
67
  };
149
68
 
150
69
  const SceneView = ({
@@ -154,6 +73,7 @@ const SceneView = ({
154
73
  previousDescriptor,
155
74
  nextDescriptor,
156
75
  isPresentationModal,
76
+ isPreloaded,
157
77
  onWillDisappear,
158
78
  onWillAppear,
159
79
  onAppear,
@@ -162,6 +82,7 @@ const SceneView = ({
162
82
  onHeaderBackButtonClicked,
163
83
  onNativeDismissCancelled,
164
84
  onGestureCancel,
85
+ onSheetDetentChanged,
165
86
  }: SceneViewProps) => {
166
87
  const { route, navigation, options, render } = descriptor;
167
88
 
@@ -170,6 +91,7 @@ const SceneView = ({
170
91
  animationMatchesGesture,
171
92
  presentation = isPresentationModal ? 'modal' : 'card',
172
93
  fullScreenGestureEnabled,
94
+ unstable_screenStyle = null,
173
95
  } = options;
174
96
 
175
97
  const {
@@ -190,19 +112,31 @@ const SceneView = ({
190
112
  navigationBarTranslucent,
191
113
  navigationBarHidden,
192
114
  orientation,
193
- sheetAllowedDetents = 'large',
194
- sheetLargestUndimmedDetent = 'all',
115
+ sheetAllowedDetents = [1.0],
116
+ sheetLargestUndimmedDetentIndex = -1,
195
117
  sheetGrabberVisible = false,
196
118
  sheetCornerRadius = -1.0,
119
+ sheetElevation = 24,
197
120
  sheetExpandsWhenScrolledToEdge = true,
121
+ sheetInitialDetentIndex = 0,
198
122
  statusBarAnimation,
199
123
  statusBarHidden,
200
124
  statusBarStyle,
201
125
  statusBarTranslucent,
202
126
  statusBarBackgroundColor,
127
+ unstable_sheetFooter = null,
203
128
  freezeOnBlur,
129
+ contentStyle,
204
130
  } = options;
205
131
 
132
+ // We want to allow only backgroundColor setting for now.
133
+ // This allows to workaround one issue with truncated
134
+ // content with formSheet presentation.
135
+ unstable_screenStyle =
136
+ unstable_screenStyle && presentation === 'formSheet'
137
+ ? { backgroundColor: unstable_screenStyle.backgroundColor }
138
+ : null;
139
+
206
140
  if (gestureDirection === 'vertical' && Platform.OS === 'ios') {
207
141
  // for `vertical` direction to work, we need to set `fullScreenGestureEnabled` to `true`
208
142
  // so the screen can be dismissed from any point on screen.
@@ -234,6 +168,7 @@ const SceneView = ({
234
168
  presentation = 'card';
235
169
  }
236
170
 
171
+ const { colors } = useTheme();
237
172
  const insets = useSafeAreaInsets();
238
173
  const frame = useSafeAreaFrame();
239
174
 
@@ -309,32 +244,52 @@ const SceneView = ({
309
244
  ? statusBarTranslucent
310
245
  : topInset !== 0;
311
246
 
247
+ const canGoBack = previousDescriptor != null || parentHeaderBack != null;
312
248
  const backTitle = previousDescriptor
313
249
  ? getHeaderTitle(previousDescriptor.options, previousDescriptor.route.name)
314
250
  : parentHeaderBack?.title;
315
251
 
316
- const headerBack = React.useMemo(
317
- () => ({
318
- // No href needed for native
319
- href: undefined,
320
- title: backTitle,
321
- }),
322
- [backTitle]
323
- );
252
+ const headerBack = React.useMemo(() => {
253
+ if (canGoBack) {
254
+ return {
255
+ href: undefined, // No href needed for native
256
+ title: backTitle,
257
+ };
258
+ }
259
+
260
+ return undefined;
261
+ }, [canGoBack, backTitle]);
324
262
 
325
263
  const isRemovePrevented = preventedRoutes[route.key]?.preventRemove;
326
264
 
265
+ const headerConfig = useHeaderConfigProps({
266
+ ...options,
267
+ route,
268
+ canGoBack,
269
+ headerBackButtonMenuEnabled:
270
+ isRemovePrevented !== undefined
271
+ ? !isRemovePrevented
272
+ : headerBackButtonMenuEnabled,
273
+ headerBackTitle:
274
+ options.headerBackTitle !== undefined
275
+ ? options.headerBackTitle
276
+ : undefined,
277
+ headerHeight,
278
+ headerShown: header !== undefined ? false : headerShown,
279
+ headerTopInsetEnabled,
280
+ });
281
+
327
282
  return (
328
- <Screen
283
+ <ScreenStackContent
329
284
  key={route.key}
330
- enabled
331
- isNativeStack
285
+ activityState={isPreloaded ? 0 : 2}
286
+ style={[StyleSheet.absoluteFill, unstable_screenStyle]}
332
287
  accessibilityElementsHidden={!focused}
333
288
  importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
334
- style={StyleSheet.absoluteFill}
335
- hasLargeHeader={options.headerLargeTitle ?? false}
336
289
  customAnimationOnSwipe={animationMatchesGesture}
337
290
  fullScreenSwipeEnabled={fullScreenGestureEnabled}
291
+ fullScreenSwipeShadowEnabled={fullScreenGestureShadowEnabled}
292
+ freezeOnBlur={freezeOnBlur}
338
293
  gestureEnabled={
339
294
  Platform.OS === 'android'
340
295
  ? // This prop enables handling of system back gestures on Android
@@ -352,9 +307,11 @@ const SceneView = ({
352
307
  stackAnimation={animation}
353
308
  screenOrientation={orientation}
354
309
  sheetAllowedDetents={sheetAllowedDetents}
355
- sheetLargestUndimmedDetent={sheetLargestUndimmedDetent}
310
+ sheetLargestUndimmedDetentIndex={sheetLargestUndimmedDetentIndex}
356
311
  sheetGrabberVisible={sheetGrabberVisible}
312
+ sheetInitialDetentIndex={sheetInitialDetentIndex}
357
313
  sheetCornerRadius={sheetCornerRadius}
314
+ sheetElevation={sheetElevation}
358
315
  sheetExpandsWhenScrolledToEdge={sheetExpandsWhenScrolledToEdge}
359
316
  statusBarAnimation={statusBarAnimation}
360
317
  statusBarHidden={statusBarHidden}
@@ -369,6 +326,7 @@ const SceneView = ({
369
326
  onDisappear={onDisappear}
370
327
  onDismissed={onDismissed}
371
328
  onGestureCancel={onGestureCancel}
329
+ onSheetDetentChanged={onSheetDetentChanged}
372
330
  gestureResponseDistance={gestureResponseDistance}
373
331
  nativeBackButtonDismissalEnabled={false} // on Android
374
332
  onHeaderBackButtonClicked={onHeaderBackButtonClicked}
@@ -423,12 +381,17 @@ const SceneView = ({
423
381
  },
424
382
  }
425
383
  )}
426
- freezeOnBlur={freezeOnBlur}
384
+ contentStyle={[
385
+ presentation !== 'transparentModal' &&
386
+ presentation !== 'containedTransparentModal' && {
387
+ backgroundColor: colors.background,
388
+ },
389
+ contentStyle,
390
+ ]}
391
+ headerConfig={headerConfig}
427
392
  // When ts-expect-error is added, it affects all the props below it
428
393
  // So we keep any props that need it at the end
429
394
  // 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
395
  >
433
396
  <NavigationContext.Provider value={navigation}>
434
397
  <NavigationRouteContext.Provider value={route}>
@@ -477,52 +440,18 @@ const SceneView = ({
477
440
  <HeaderShownContext.Provider
478
441
  value={isParentHeaderShown || headerShown !== false}
479
442
  >
480
- <MaybeNestedStack
481
- options={options}
482
- route={route}
483
- presentation={presentation}
484
- headerHeight={headerHeight}
485
- headerTopInsetEnabled={headerTopInsetEnabled}
486
- >
487
- <HeaderBackContext.Provider value={headerBack}>
488
- {render()}
489
- </HeaderBackContext.Provider>
490
- </MaybeNestedStack>
443
+ <HeaderBackContext.Provider value={headerBack}>
444
+ {render()}
445
+ </HeaderBackContext.Provider>
491
446
  </HeaderShownContext.Provider>
492
- {/**
493
- * `HeaderConfig` needs to be the direct child of `Screen` without any intermediate `View`
494
- * We don't render it conditionally to make it possible to dynamically render a custom `header`
495
- * Otherwise dynamically rendering a custom `header` leaves the native header visible
496
- *
497
- * https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig
498
- *
499
- * HeaderConfig must not be first child of a Screen.
500
- * See https://github.com/software-mansion/react-native-screens/pull/1825
501
- * for detailed explanation
502
- */}
503
- <HeaderConfig
504
- {...options}
505
- route={route}
506
- headerBackButtonMenuEnabled={
507
- isRemovePrevented !== undefined
508
- ? !isRemovePrevented
509
- : headerBackButtonMenuEnabled
510
- }
511
- headerShown={header !== undefined ? false : headerShown}
512
- headerHeight={headerHeight}
513
- headerBackTitle={
514
- options.headerBackTitle !== undefined
515
- ? options.headerBackTitle
516
- : undefined
517
- }
518
- headerTopInsetEnabled={headerTopInsetEnabled}
519
- canGoBack={headerBack !== undefined}
520
- />
447
+ {presentation === 'formSheet' && unstable_sheetFooter && (
448
+ <FooterComponent>{unstable_sheetFooter()}</FooterComponent>
449
+ )}
521
450
  </HeaderHeightContext.Provider>
522
451
  </AnimatedHeaderHeightContext.Provider>
523
452
  </NavigationRouteContext.Provider>
524
453
  </NavigationContext.Provider>
525
- </Screen>
454
+ </ScreenStackContent>
526
455
  );
527
456
  };
528
457
 
@@ -530,9 +459,18 @@ type Props = {
530
459
  state: StackNavigationState<ParamListBase>;
531
460
  navigation: NativeStackNavigationHelpers;
532
461
  descriptors: NativeStackDescriptorMap;
462
+ describe: (
463
+ route: RouteProp<ParamListBase>,
464
+ placeholder: boolean
465
+ ) => NativeStackDescriptor;
533
466
  };
534
467
 
535
- export function NativeStackView({ state, navigation, descriptors }: Props) {
468
+ export function NativeStackView({
469
+ state,
470
+ navigation,
471
+ descriptors,
472
+ describe,
473
+ }: Props) {
536
474
  const { setNextDismissedKey } = useDismissedRouteError(state);
537
475
 
538
476
  const { colors } = useTheme();
@@ -541,11 +479,18 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
541
479
 
542
480
  const modalRouteKeys = getModalRouteKeys(state.routes, descriptors);
543
481
 
482
+ const preloadedDescriptors =
483
+ state.preloadedRoutes.reduce<NativeStackDescriptorMap>((acc, route) => {
484
+ acc[route.key] = acc[route.key] || describe(route, true);
485
+ return acc;
486
+ }, {});
487
+
544
488
  return (
545
489
  <SafeAreaProviderCompat style={{ backgroundColor: colors.background }}>
546
490
  <ScreenStack style={styles.container}>
547
- {state.routes.map((route, index) => {
548
- const descriptor = descriptors[route.key];
491
+ {state.routes.concat(state.preloadedRoutes).map((route, index) => {
492
+ const descriptor =
493
+ descriptors[route.key] ?? preloadedDescriptors[route.key];
549
494
  const isFocused = state.index === index;
550
495
  const previousKey = state.routes[index - 1]?.key;
551
496
  const nextKey = state.routes[index + 1]?.key;
@@ -556,6 +501,10 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
556
501
 
557
502
  const isModal = modalRouteKeys.includes(route.key);
558
503
 
504
+ const isPreloaded =
505
+ preloadedDescriptors[route.key] !== undefined &&
506
+ descriptors[route.key] === undefined;
507
+
559
508
  return (
560
509
  <SceneView
561
510
  key={route.key}
@@ -565,6 +514,7 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
565
514
  previousDescriptor={previousDescriptor}
566
515
  nextDescriptor={nextDescriptor}
567
516
  isPresentationModal={isModal}
517
+ isPreloaded={isPreloaded}
568
518
  onWillDisappear={() => {
569
519
  navigation.emit({
570
520
  type: 'transitionStart',
@@ -622,6 +572,16 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
622
572
  target: route.key,
623
573
  });
624
574
  }}
575
+ onSheetDetentChanged={(event) => {
576
+ navigation.emit({
577
+ type: 'sheetDetentChange',
578
+ target: route.key,
579
+ data: {
580
+ index: event.nativeEvent.index,
581
+ stable: event.nativeEvent.isStable,
582
+ },
583
+ });
584
+ }}
625
585
  />
626
586
  );
627
587
  })}
@@ -9,6 +9,7 @@ import {
9
9
  } from '@react-navigation/elements';
10
10
  import {
11
11
  type ParamListBase,
12
+ type RouteProp,
12
13
  type StackNavigationState,
13
14
  useLinkBuilder,
14
15
  useTheme,
@@ -17,6 +18,7 @@ import * as React from 'react';
17
18
  import { Animated, Image, StyleSheet, View } from 'react-native';
18
19
 
19
20
  import type {
21
+ NativeStackDescriptor,
20
22
  NativeStackDescriptorMap,
21
23
  NativeStackNavigationHelpers,
22
24
  } from '../types';
@@ -27,6 +29,10 @@ type Props = {
27
29
  // This is used for the native implementation of the stack.
28
30
  navigation: NativeStackNavigationHelpers;
29
31
  descriptors: NativeStackDescriptorMap;
32
+ describe: (
33
+ route: RouteProp<ParamListBase>,
34
+ placeholder: boolean
35
+ ) => NativeStackDescriptor;
30
36
  };
31
37
 
32
38
  const TRANSPARENT_PRESENTATIONS = [
@@ -34,20 +40,20 @@ const TRANSPARENT_PRESENTATIONS = [
34
40
  'containedTransparentModal',
35
41
  ];
36
42
 
37
- export function NativeStackView({ state, descriptors }: Props) {
43
+ export function NativeStackView({ state, descriptors, describe }: Props) {
38
44
  const parentHeaderBack = React.useContext(HeaderBackContext);
39
45
  const { buildHref } = useLinkBuilder();
40
46
  const { colors } = useTheme();
41
47
 
42
- if (state.preloadedRoutes.length !== 0) {
43
- throw new Error(
44
- 'Preloading routes is not supported in the NativeStackNavigator navigator.'
45
- );
46
- }
48
+ const preloadedDescriptors =
49
+ state.preloadedRoutes.reduce<NativeStackDescriptorMap>((acc, route) => {
50
+ acc[route.key] = acc[route.key] || describe(route, true);
51
+ return acc;
52
+ }, {});
47
53
 
48
54
  return (
49
55
  <SafeAreaProviderCompat style={{ backgroundColor: colors.background }}>
50
- {state.routes.map((route, i) => {
56
+ {state.routes.concat(state.preloadedRoutes).map((route, i) => {
51
57
  const isFocused = state.index === i;
52
58
  const previousKey = state.routes[i - 1]?.key;
53
59
  const nextKey = state.routes[i + 1]?.key;
@@ -55,7 +61,8 @@ export function NativeStackView({ state, descriptors }: Props) {
55
61
  ? descriptors[previousKey]
56
62
  : undefined;
57
63
  const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
58
- const { options, navigation, render } = descriptors[route.key];
64
+ const { options, navigation, render } =
65
+ descriptors[route.key] ?? preloadedDescriptors[route.key];
59
66
 
60
67
  const headerBack = previousDescriptor
61
68
  ? {
@@ -70,7 +77,7 @@ export function NativeStackView({ state, descriptors }: Props) {
70
77
  }
71
78
  : parentHeaderBack;
72
79
 
73
- const canGoBack = headerBack !== undefined;
80
+ const canGoBack = headerBack != null;
74
81
 
75
82
  const {
76
83
  header,
@@ -86,6 +93,10 @@ export function NativeStackView({ state, descriptors }: Props) {
86
93
 
87
94
  const nextPresentation = nextDescriptor?.options.presentation;
88
95
 
96
+ const isPreloaded =
97
+ preloadedDescriptors[route.key] !== undefined &&
98
+ descriptors[route.key] === undefined;
99
+
89
100
  return (
90
101
  <Screen
91
102
  key={route.key}
@@ -146,9 +157,10 @@ export function NativeStackView({ state, descriptors }: Props) {
146
157
  StyleSheet.absoluteFill,
147
158
  {
148
159
  display:
149
- isFocused ||
150
- (nextPresentation != null &&
151
- TRANSPARENT_PRESENTATIONS.includes(nextPresentation))
160
+ (isFocused ||
161
+ (nextPresentation != null &&
162
+ TRANSPARENT_PRESENTATIONS.includes(nextPresentation))) &&
163
+ !isPreloaded
152
164
  ? 'flex'
153
165
  : 'none',
154
166
  },
@@ -0,0 +1,121 @@
1
+ import * as React from 'react';
2
+ import {
3
+ Platform,
4
+ type StyleProp,
5
+ StyleSheet,
6
+ type ViewStyle,
7
+ } from 'react-native';
8
+ import {
9
+ Screen,
10
+ type ScreenProps,
11
+ ScreenStack,
12
+ ScreenStackHeaderConfig,
13
+ type ScreenStackHeaderConfigProps,
14
+ } from 'react-native-screens';
15
+ import warnOnce from 'warn-once';
16
+
17
+ import { DebugContainer } from './DebugContainer';
18
+
19
+ type Props = Omit<
20
+ ScreenProps,
21
+ 'enabled' | 'isNativeStack' | 'hasLargeHeader'
22
+ > & {
23
+ headerConfig?: ScreenStackHeaderConfigProps;
24
+ contentStyle?: StyleProp<ViewStyle>;
25
+ };
26
+
27
+ export function ScreenStackContent({
28
+ children,
29
+ headerConfig,
30
+ activityState,
31
+ stackPresentation,
32
+ contentStyle,
33
+ ...rest
34
+ }: Props) {
35
+ const isHeaderInModal =
36
+ Platform.OS === 'android'
37
+ ? false
38
+ : stackPresentation !== 'push' && headerConfig?.hidden === false;
39
+
40
+ const headerHiddenPreviousRef = React.useRef(headerConfig?.hidden);
41
+
42
+ React.useEffect(() => {
43
+ warnOnce(
44
+ Platform.OS !== 'android' &&
45
+ stackPresentation !== 'push' &&
46
+ headerHiddenPreviousRef.current !== headerConfig?.hidden,
47
+ `Dynamically changing header's visibility in modals will result in remounting the screen and losing all local state.`
48
+ );
49
+
50
+ headerHiddenPreviousRef.current = headerConfig?.hidden;
51
+ }, [headerConfig?.hidden, stackPresentation]);
52
+
53
+ const content = (
54
+ <>
55
+ <DebugContainer
56
+ style={[
57
+ stackPresentation === 'formSheet'
58
+ ? Platform.OS === 'ios'
59
+ ? styles.absolute
60
+ : null
61
+ : styles.container,
62
+ contentStyle,
63
+ ]}
64
+ stackPresentation={stackPresentation ?? 'push'}
65
+ >
66
+ {children}
67
+ </DebugContainer>
68
+ {/**
69
+ * `HeaderConfig` needs to be the direct child of `Screen` without any intermediate `View`
70
+ * We don't render it conditionally based on visibility to make it possible to dynamically render a custom `header`
71
+ * Otherwise dynamically rendering a custom `header` leaves the native header visible
72
+ *
73
+ * https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md#screenstackheaderconfig
74
+ *
75
+ * HeaderConfig must not be first child of a Screen.
76
+ * See https://github.com/software-mansion/react-native-screens/pull/1825
77
+ * for detailed explanation.
78
+ */}
79
+ <ScreenStackHeaderConfig {...headerConfig} />
80
+ </>
81
+ );
82
+
83
+ return (
84
+ <Screen
85
+ enabled
86
+ isNativeStack
87
+ activityState={activityState}
88
+ stackPresentation={stackPresentation}
89
+ hasLargeHeader={headerConfig?.largeTitle ?? false}
90
+ {...rest}
91
+ >
92
+ {isHeaderInModal ? (
93
+ <ScreenStack style={styles.container}>
94
+ <Screen
95
+ enabled
96
+ isNativeStack
97
+ activityState={activityState}
98
+ hasLargeHeader={headerConfig?.largeTitle ?? false}
99
+ style={StyleSheet.absoluteFill}
100
+ >
101
+ {content}
102
+ </Screen>
103
+ </ScreenStack>
104
+ ) : (
105
+ content
106
+ )}
107
+ </Screen>
108
+ );
109
+ }
110
+
111
+ const styles = StyleSheet.create({
112
+ container: {
113
+ flex: 1,
114
+ },
115
+ absolute: {
116
+ position: 'absolute',
117
+ top: 0,
118
+ start: 0,
119
+ end: 0,
120
+ },
121
+ });