@react-navigation/native-stack 8.0.0-alpha.4 → 8.0.0-alpha.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/module/index.js.map +1 -1
- package/lib/module/navigators/createNativeStackNavigator.js +4 -8
- package/lib/module/navigators/createNativeStackNavigator.js.map +1 -1
- package/lib/module/utils/useAnimatedHeaderHeight.js +1 -1
- package/lib/module/utils/useAnimatedHeaderHeight.js.map +1 -1
- package/lib/module/utils/useInvalidPreventRemoveError.js +1 -1
- package/lib/module/utils/useInvalidPreventRemoveError.js.map +1 -1
- package/lib/module/views/FooterComponent.js +1 -1
- package/lib/module/views/FooterComponent.js.map +1 -1
- package/lib/module/views/NativeStackView.js +42 -25
- package/lib/module/views/NativeStackView.js.map +1 -1
- package/lib/module/views/NativeStackView.native.js +113 -68
- package/lib/module/views/NativeStackView.native.js.map +1 -1
- package/lib/module/views/useHeaderConfigProps.js +71 -21
- package/lib/module/views/useHeaderConfigProps.js.map +1 -1
- package/lib/typescript/src/index.d.ts +3 -3
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/navigators/createNativeStackNavigator.d.ts +10 -15
- package/lib/typescript/src/navigators/createNativeStackNavigator.d.ts.map +1 -1
- package/lib/typescript/src/types.d.ts +145 -148
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts +1 -1
- package/lib/typescript/src/utils/getModalRoutesKeys.d.ts.map +1 -1
- package/lib/typescript/src/utils/useInvalidPreventRemoveError.d.ts +1 -1
- package/lib/typescript/src/utils/useInvalidPreventRemoveError.d.ts.map +1 -1
- package/lib/typescript/src/views/FooterComponent.d.ts +2 -2
- package/lib/typescript/src/views/FooterComponent.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.d.ts +3 -2
- package/lib/typescript/src/views/NativeStackView.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.native.d.ts +3 -2
- package/lib/typescript/src/views/NativeStackView.native.d.ts.map +1 -1
- package/lib/typescript/src/views/useHeaderConfigProps.d.ts +1 -1
- package/lib/typescript/src/views/useHeaderConfigProps.d.ts.map +1 -1
- package/package.json +18 -19
- package/src/index.tsx +1 -0
- package/src/navigators/createNativeStackNavigator.tsx +11 -47
- package/src/types.tsx +206 -186
- package/src/utils/useAnimatedHeaderHeight.tsx +1 -1
- package/src/utils/useInvalidPreventRemoveError.tsx +2 -1
- package/src/views/FooterComponent.tsx +1 -1
- package/src/views/NativeStackView.native.tsx +168 -89
- package/src/views/NativeStackView.tsx +70 -41
- package/src/views/useHeaderConfigProps.tsx +100 -36
|
@@ -6,7 +6,7 @@ export const AnimatedHeaderHeightContext = React.createContext<
|
|
|
6
6
|
>(undefined);
|
|
7
7
|
|
|
8
8
|
export function useAnimatedHeaderHeight() {
|
|
9
|
-
const animatedValue = React.
|
|
9
|
+
const animatedValue = React.use(AnimatedHeaderHeightContext);
|
|
10
10
|
|
|
11
11
|
if (animatedValue === undefined) {
|
|
12
12
|
throw new Error(
|
|
@@ -8,7 +8,8 @@ export function useInvalidPreventRemoveError(
|
|
|
8
8
|
) {
|
|
9
9
|
const { preventedRoutes } = usePreventRemoveContext();
|
|
10
10
|
const preventedRouteKey = Object.keys(preventedRoutes)[0];
|
|
11
|
-
const preventedDescriptor =
|
|
11
|
+
const preventedDescriptor =
|
|
12
|
+
preventedRouteKey == null ? undefined : descriptors[preventedRouteKey];
|
|
12
13
|
const isHeaderBackButtonMenuEnabledOnPreventedScreen =
|
|
13
14
|
preventedDescriptor?.options?.headerBackButtonMenuEnabled;
|
|
14
15
|
const preventedRouteName = preventedDescriptor?.route?.name;
|
|
@@ -6,7 +6,10 @@ import {
|
|
|
6
6
|
HeaderShownContext,
|
|
7
7
|
useFrameSize,
|
|
8
8
|
} from '@react-navigation/elements';
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
ActivityView,
|
|
11
|
+
SafeAreaProviderCompat,
|
|
12
|
+
} from '@react-navigation/elements/internal';
|
|
10
13
|
import {
|
|
11
14
|
NavigationProvider,
|
|
12
15
|
type ParamListBase,
|
|
@@ -44,19 +47,18 @@ import { useHeaderConfigProps } from './useHeaderConfigProps';
|
|
|
44
47
|
|
|
45
48
|
const ANDROID_DEFAULT_HEADER_HEIGHT = 56;
|
|
46
49
|
|
|
47
|
-
function isFabric() {
|
|
48
|
-
return 'nativeFabricUIManager' in global;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
50
|
type SceneViewProps = {
|
|
52
51
|
index: number;
|
|
53
52
|
focused: boolean;
|
|
54
|
-
shouldFreeze: boolean;
|
|
55
53
|
descriptor: NativeStackDescriptor;
|
|
56
|
-
previousDescriptor?: NativeStackDescriptor;
|
|
57
|
-
nextDescriptor?: NativeStackDescriptor;
|
|
58
|
-
isPresentationModal
|
|
59
|
-
|
|
54
|
+
previousDescriptor?: NativeStackDescriptor | undefined;
|
|
55
|
+
nextDescriptor?: NativeStackDescriptor | undefined;
|
|
56
|
+
isPresentationModal: boolean;
|
|
57
|
+
isNextScreenTransparent: boolean;
|
|
58
|
+
isNextScreenSheet: boolean;
|
|
59
|
+
isNextScreenModal: boolean;
|
|
60
|
+
isInactive: boolean;
|
|
61
|
+
isBeforeLast: boolean;
|
|
60
62
|
onWillDisappear: () => void;
|
|
61
63
|
onWillAppear: () => void;
|
|
62
64
|
onAppear: () => void;
|
|
@@ -70,14 +72,24 @@ type SceneViewProps = {
|
|
|
70
72
|
|
|
71
73
|
const useNativeDriver = Platform.OS !== 'web';
|
|
72
74
|
|
|
75
|
+
const TRANSPARENT_PRESENTATIONS = [
|
|
76
|
+
'transparentModal',
|
|
77
|
+
'containedTransparentModal',
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
const SHEET_PRESENTATIONS = ['formSheet', 'pageSheet'];
|
|
81
|
+
|
|
73
82
|
const SceneView = ({
|
|
74
83
|
index,
|
|
75
84
|
focused,
|
|
76
|
-
shouldFreeze,
|
|
77
85
|
descriptor,
|
|
78
86
|
previousDescriptor,
|
|
79
87
|
isPresentationModal,
|
|
80
|
-
|
|
88
|
+
isNextScreenTransparent,
|
|
89
|
+
isNextScreenSheet,
|
|
90
|
+
isNextScreenModal,
|
|
91
|
+
isInactive,
|
|
92
|
+
isBeforeLast,
|
|
81
93
|
onWillDisappear,
|
|
82
94
|
onWillAppear,
|
|
83
95
|
onAppear,
|
|
@@ -91,6 +103,7 @@ const SceneView = ({
|
|
|
91
103
|
const { route, navigation, options, render } = descriptor;
|
|
92
104
|
|
|
93
105
|
const {
|
|
106
|
+
inactiveBehavior = 'pause',
|
|
94
107
|
animation,
|
|
95
108
|
animationDuration,
|
|
96
109
|
animationMatchesGesture,
|
|
@@ -123,7 +136,6 @@ const SceneView = ({
|
|
|
123
136
|
statusBarStyle,
|
|
124
137
|
unstable_sheetFooter,
|
|
125
138
|
scrollEdgeEffects,
|
|
126
|
-
freezeOnBlur,
|
|
127
139
|
contentStyle,
|
|
128
140
|
} = options;
|
|
129
141
|
|
|
@@ -147,9 +159,9 @@ const SceneView = ({
|
|
|
147
159
|
// Modals are fullscreen in landscape only on iPhone
|
|
148
160
|
const isIPhone = Platform.OS === 'ios' && !(Platform.isPad || Platform.isTV);
|
|
149
161
|
|
|
150
|
-
const isParentHeaderShown = React.
|
|
151
|
-
const parentHeaderHeight = React.
|
|
152
|
-
const parentHeaderBack = React.
|
|
162
|
+
const isParentHeaderShown = React.use(HeaderShownContext);
|
|
163
|
+
const parentHeaderHeight = React.use(HeaderHeightContext);
|
|
164
|
+
const parentHeaderBack = React.use(HeaderBackContext);
|
|
153
165
|
|
|
154
166
|
const isLandscape = useFrameSize((frame) => frame.width > frame.height);
|
|
155
167
|
|
|
@@ -226,6 +238,15 @@ const SceneView = ({
|
|
|
226
238
|
headerBack,
|
|
227
239
|
});
|
|
228
240
|
|
|
241
|
+
const headerContainerRef = React.useRef<View>(null);
|
|
242
|
+
|
|
243
|
+
React.useLayoutEffect(() => {
|
|
244
|
+
headerContainerRef.current?.measure((_x, _y, _width, height) => {
|
|
245
|
+
animatedHeaderHeight.setValue(height);
|
|
246
|
+
setHeaderHeight(height);
|
|
247
|
+
});
|
|
248
|
+
}, [animatedHeaderHeight]);
|
|
249
|
+
|
|
229
250
|
const onHeaderHeightChange = hasCustomHeader
|
|
230
251
|
? // If we have a custom header, don't use native header height
|
|
231
252
|
undefined
|
|
@@ -265,7 +286,8 @@ const SceneView = ({
|
|
|
265
286
|
if (
|
|
266
287
|
Platform.OS === 'android' &&
|
|
267
288
|
headerHeight !== 0 &&
|
|
268
|
-
|
|
289
|
+
// On some devices, height maybe slightly off (e.g. 56.17 instead of 56)
|
|
290
|
+
Math.round(headerHeight) <= ANDROID_DEFAULT_HEADER_HEIGHT
|
|
269
291
|
) {
|
|
270
292
|
// FIXME: On Android, events may get delivered out-of-order
|
|
271
293
|
// https://github.com/facebook/react-native/issues/54636
|
|
@@ -285,18 +307,102 @@ const SceneView = ({
|
|
|
285
307
|
}
|
|
286
308
|
);
|
|
287
309
|
|
|
310
|
+
const activityMode =
|
|
311
|
+
// Render focused screens normally
|
|
312
|
+
// Unpause preloaded and retained screens so updates are visible
|
|
313
|
+
// Unpause previous screen so update isn't delayed for swipe back
|
|
314
|
+
// This lets effects on those screens run
|
|
315
|
+
// We don't need to handle inert as it'll be handled natively
|
|
316
|
+
inactiveBehavior === 'none' ||
|
|
317
|
+
focused ||
|
|
318
|
+
isInactive ||
|
|
319
|
+
isNextScreenTransparent ||
|
|
320
|
+
// Unpause the screen behind a sheet so it stays up to date
|
|
321
|
+
// Sheets don't cover the whole screen, so the screen behind is visible
|
|
322
|
+
isNextScreenSheet ||
|
|
323
|
+
// On iPadOS, the base screen is visible while modal is in a smaller area
|
|
324
|
+
// So also keep the screens unpaused
|
|
325
|
+
(isNextScreenModal && !isModal) ||
|
|
326
|
+
isBeforeLast
|
|
327
|
+
? 'normal'
|
|
328
|
+
: inactiveBehavior === 'unmount' && !('state' in route && route.state)
|
|
329
|
+
? 'unmounted'
|
|
330
|
+
: 'paused';
|
|
331
|
+
|
|
332
|
+
const content = (
|
|
333
|
+
<AnimatedHeaderHeightContext.Provider value={animatedHeaderHeight}>
|
|
334
|
+
<HeaderHeightContext.Provider
|
|
335
|
+
value={headerShown !== false ? headerHeight : (parentHeaderHeight ?? 0)}
|
|
336
|
+
>
|
|
337
|
+
{headerBackground != null ? (
|
|
338
|
+
/**
|
|
339
|
+
* To show a custom header background, we render it at the top of the screen below the header
|
|
340
|
+
* The header also needs to be positioned absolutely (with `translucent` style)
|
|
341
|
+
*/
|
|
342
|
+
<View
|
|
343
|
+
style={[
|
|
344
|
+
styles.background,
|
|
345
|
+
headerTransparent ? styles.translucent : null,
|
|
346
|
+
{ height: headerHeight },
|
|
347
|
+
]}
|
|
348
|
+
>
|
|
349
|
+
{headerBackground()}
|
|
350
|
+
</View>
|
|
351
|
+
) : null}
|
|
352
|
+
{header != null && headerShown !== false ? (
|
|
353
|
+
<View
|
|
354
|
+
style={[
|
|
355
|
+
styles.header,
|
|
356
|
+
headerTransparent
|
|
357
|
+
? [
|
|
358
|
+
styles.absolute,
|
|
359
|
+
// Specify an explicit min height for Android screen readers
|
|
360
|
+
{ minHeight: headerHeight },
|
|
361
|
+
]
|
|
362
|
+
: null,
|
|
363
|
+
]}
|
|
364
|
+
>
|
|
365
|
+
<View
|
|
366
|
+
ref={headerContainerRef}
|
|
367
|
+
onLayout={(e) => {
|
|
368
|
+
const headerHeight = e.nativeEvent.layout.height;
|
|
369
|
+
|
|
370
|
+
animatedHeaderHeight.setValue(headerHeight);
|
|
371
|
+
setHeaderHeight(headerHeight);
|
|
372
|
+
}}
|
|
373
|
+
style={{ pointerEvents: 'box-none' }}
|
|
374
|
+
>
|
|
375
|
+
{header({
|
|
376
|
+
back: headerBack,
|
|
377
|
+
options,
|
|
378
|
+
route,
|
|
379
|
+
navigation,
|
|
380
|
+
})}
|
|
381
|
+
</View>
|
|
382
|
+
</View>
|
|
383
|
+
) : null}
|
|
384
|
+
<HeaderShownContext.Provider
|
|
385
|
+
value={isParentHeaderShown || headerShown !== false}
|
|
386
|
+
>
|
|
387
|
+
<HeaderBackContext.Provider value={headerBack}>
|
|
388
|
+
{render()}
|
|
389
|
+
</HeaderBackContext.Provider>
|
|
390
|
+
</HeaderShownContext.Provider>
|
|
391
|
+
</HeaderHeightContext.Provider>
|
|
392
|
+
</AnimatedHeaderHeightContext.Provider>
|
|
393
|
+
);
|
|
394
|
+
|
|
288
395
|
return (
|
|
289
396
|
<NavigationProvider navigation={navigation} route={route}>
|
|
290
397
|
<ScreenStackItem
|
|
291
398
|
key={route.key}
|
|
292
399
|
screenId={route.key}
|
|
293
|
-
activityState={
|
|
400
|
+
activityState={isInactive ? 0 : 2}
|
|
294
401
|
style={StyleSheet.absoluteFill}
|
|
295
402
|
aria-hidden={!focused}
|
|
296
403
|
customAnimationOnSwipe={animationMatchesGesture}
|
|
297
404
|
fullScreenSwipeEnabled={fullScreenGestureEnabled}
|
|
298
405
|
fullScreenSwipeShadowEnabled={fullScreenGestureShadowEnabled}
|
|
299
|
-
freezeOnBlur={freezeOnBlur}
|
|
300
406
|
gestureEnabled={
|
|
301
407
|
Platform.OS === 'android'
|
|
302
408
|
? // This prop enables handling of system back gestures on Android
|
|
@@ -353,62 +459,21 @@ const SceneView = ({
|
|
|
353
459
|
]}
|
|
354
460
|
headerConfig={headerConfig}
|
|
355
461
|
unstable_sheetFooter={unstable_sheetFooter}
|
|
356
|
-
// When ts-expect-error is added, it affects all the props below it
|
|
357
|
-
// So we keep any props that need it at the end
|
|
358
|
-
// Otherwise invalid props may not be caught by TypeScript
|
|
359
|
-
shouldFreeze={shouldFreeze}
|
|
360
462
|
>
|
|
361
|
-
|
|
362
|
-
<
|
|
363
|
-
|
|
364
|
-
|
|
463
|
+
{activityMode === 'unmounted' ? null : (
|
|
464
|
+
<ActivityView
|
|
465
|
+
mode={activityMode}
|
|
466
|
+
visible={
|
|
467
|
+
// We don't need to hide the content since it's handled natively
|
|
468
|
+
// Hiding may also cause flash due to lag after native tab switch
|
|
469
|
+
// So we leave it always visible
|
|
470
|
+
true
|
|
365
471
|
}
|
|
472
|
+
style={styles.content}
|
|
366
473
|
>
|
|
367
|
-
{
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
* The header also needs to be positioned absolutely (with `translucent` style)
|
|
371
|
-
*/
|
|
372
|
-
<View
|
|
373
|
-
style={[
|
|
374
|
-
styles.background,
|
|
375
|
-
headerTransparent ? styles.translucent : null,
|
|
376
|
-
{ height: headerHeight },
|
|
377
|
-
]}
|
|
378
|
-
>
|
|
379
|
-
{headerBackground()}
|
|
380
|
-
</View>
|
|
381
|
-
) : null}
|
|
382
|
-
{header != null && headerShown !== false ? (
|
|
383
|
-
<View
|
|
384
|
-
onLayout={(e) => {
|
|
385
|
-
const headerHeight = e.nativeEvent.layout.height;
|
|
386
|
-
|
|
387
|
-
animatedHeaderHeight.setValue(headerHeight);
|
|
388
|
-
setHeaderHeight(headerHeight);
|
|
389
|
-
}}
|
|
390
|
-
style={[
|
|
391
|
-
styles.header,
|
|
392
|
-
headerTransparent ? styles.absolute : null,
|
|
393
|
-
]}
|
|
394
|
-
>
|
|
395
|
-
{header({
|
|
396
|
-
back: headerBack,
|
|
397
|
-
options,
|
|
398
|
-
route,
|
|
399
|
-
navigation,
|
|
400
|
-
})}
|
|
401
|
-
</View>
|
|
402
|
-
) : null}
|
|
403
|
-
<HeaderShownContext.Provider
|
|
404
|
-
value={isParentHeaderShown || headerShown !== false}
|
|
405
|
-
>
|
|
406
|
-
<HeaderBackContext.Provider value={headerBack}>
|
|
407
|
-
{render()}
|
|
408
|
-
</HeaderBackContext.Provider>
|
|
409
|
-
</HeaderShownContext.Provider>
|
|
410
|
-
</HeaderHeightContext.Provider>
|
|
411
|
-
</AnimatedHeaderHeightContext.Provider>
|
|
474
|
+
{content}
|
|
475
|
+
</ActivityView>
|
|
476
|
+
)}
|
|
412
477
|
</ScreenStackItem>
|
|
413
478
|
</NavigationProvider>
|
|
414
479
|
);
|
|
@@ -425,46 +490,57 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
|
|
|
425
490
|
|
|
426
491
|
useInvalidPreventRemoveError(descriptors);
|
|
427
492
|
|
|
428
|
-
const
|
|
493
|
+
const activeRoutes = state.routes.slice(0, state.index + 1);
|
|
494
|
+
const modalRouteKeys = getModalRouteKeys(activeRoutes, descriptors);
|
|
429
495
|
|
|
430
496
|
return (
|
|
431
497
|
<SafeAreaProviderCompat>
|
|
432
498
|
<ScreenStack style={styles.container}>
|
|
433
|
-
{state.routes.
|
|
499
|
+
{state.routes.map((route, index) => {
|
|
434
500
|
const descriptor = descriptors[route.key];
|
|
501
|
+
|
|
502
|
+
if (descriptor == null) {
|
|
503
|
+
throw new Error(
|
|
504
|
+
`Couldn't find a descriptor for route '${route.key}'.`
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
435
508
|
const isFocused = state.index === index;
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
const nextKey = state.routes[index + 1]?.key;
|
|
509
|
+
const previousKey = activeRoutes[index - 1]?.key;
|
|
510
|
+
const nextKey = activeRoutes[index + 1]?.key;
|
|
439
511
|
const previousDescriptor = previousKey
|
|
440
512
|
? descriptors[previousKey]
|
|
441
513
|
: undefined;
|
|
442
514
|
const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
|
|
443
515
|
|
|
444
|
-
const
|
|
445
|
-
const isModalOnIos = isModal && Platform.OS === 'ios';
|
|
516
|
+
const nextPresentation = nextDescriptor?.options.presentation;
|
|
446
517
|
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
518
|
+
const isNextScreenTransparent =
|
|
519
|
+
nextPresentation != null &&
|
|
520
|
+
TRANSPARENT_PRESENTATIONS.includes(nextPresentation);
|
|
521
|
+
const isNextScreenSheet =
|
|
522
|
+
nextPresentation != null &&
|
|
523
|
+
SHEET_PRESENTATIONS.includes(nextPresentation);
|
|
524
|
+
const isNextScreenModal = nextKey
|
|
525
|
+
? modalRouteKeys.includes(nextKey)
|
|
526
|
+
: false;
|
|
450
527
|
|
|
451
|
-
|
|
452
|
-
// due to component being unmounted. To avoid this, we don't freeze the previous screen there
|
|
453
|
-
const shouldFreeze = isFabric()
|
|
454
|
-
? !isPreloaded && !isFocused && !isBelowFocused && !isModalOnIos
|
|
455
|
-
: !isPreloaded && !isFocused && !isModalOnIos;
|
|
528
|
+
const isModal = modalRouteKeys.includes(route.key);
|
|
456
529
|
|
|
457
530
|
return (
|
|
458
531
|
<SceneView
|
|
459
532
|
key={route.key}
|
|
460
533
|
index={index}
|
|
461
534
|
focused={isFocused}
|
|
462
|
-
shouldFreeze={shouldFreeze}
|
|
463
535
|
descriptor={descriptor}
|
|
464
536
|
previousDescriptor={previousDescriptor}
|
|
465
537
|
nextDescriptor={nextDescriptor}
|
|
466
538
|
isPresentationModal={isModal}
|
|
467
|
-
|
|
539
|
+
isNextScreenTransparent={isNextScreenTransparent}
|
|
540
|
+
isNextScreenSheet={isNextScreenSheet}
|
|
541
|
+
isNextScreenModal={isNextScreenModal}
|
|
542
|
+
isInactive={index > state.index}
|
|
543
|
+
isBeforeLast={index === activeRoutes.length - 2}
|
|
468
544
|
onWillDisappear={() => {
|
|
469
545
|
navigation.emit({
|
|
470
546
|
type: 'transitionStart',
|
|
@@ -544,6 +620,9 @@ const styles = StyleSheet.create({
|
|
|
544
620
|
container: {
|
|
545
621
|
flex: 1,
|
|
546
622
|
},
|
|
623
|
+
content: {
|
|
624
|
+
flex: 1,
|
|
625
|
+
},
|
|
547
626
|
header: {
|
|
548
627
|
zIndex: 1,
|
|
549
628
|
},
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
useHeaderHeight,
|
|
7
7
|
} from '@react-navigation/elements';
|
|
8
8
|
import {
|
|
9
|
+
ActivityView,
|
|
9
10
|
SafeAreaProviderCompat,
|
|
10
11
|
Screen,
|
|
11
12
|
} from '@react-navigation/elements/internal';
|
|
@@ -15,12 +16,13 @@ import {
|
|
|
15
16
|
useLinkBuilder,
|
|
16
17
|
} from '@react-navigation/native';
|
|
17
18
|
import * as React from 'react';
|
|
18
|
-
import { Animated,
|
|
19
|
+
import { Animated, StyleSheet, View } from 'react-native';
|
|
19
20
|
|
|
20
21
|
import type {
|
|
21
22
|
NativeStackDescriptorMap,
|
|
22
23
|
NativeStackNavigationHelpers,
|
|
23
24
|
} from '../types';
|
|
25
|
+
import { getModalRouteKeys } from '../utils/getModalRoutesKeys';
|
|
24
26
|
import { AnimatedHeaderHeightContext } from '../utils/useAnimatedHeaderHeight';
|
|
25
27
|
|
|
26
28
|
type Props = {
|
|
@@ -35,20 +37,31 @@ const TRANSPARENT_PRESENTATIONS = [
|
|
|
35
37
|
];
|
|
36
38
|
|
|
37
39
|
export function NativeStackView({ state, descriptors }: Props) {
|
|
38
|
-
const parentHeaderBack = React.
|
|
40
|
+
const parentHeaderBack = React.use(HeaderBackContext);
|
|
39
41
|
const { buildHref } = useLinkBuilder();
|
|
40
42
|
|
|
43
|
+
const activeRoutes = state.routes.slice(0, state.index + 1);
|
|
44
|
+
const modalRouteKeys = getModalRouteKeys(activeRoutes, descriptors);
|
|
45
|
+
|
|
41
46
|
return (
|
|
42
47
|
<SafeAreaProviderCompat>
|
|
43
|
-
{state.routes.
|
|
48
|
+
{state.routes.map((route, i) => {
|
|
44
49
|
const isFocused = state.index === i;
|
|
45
|
-
const previousKey =
|
|
46
|
-
const nextKey =
|
|
50
|
+
const previousKey = activeRoutes[i - 1]?.key;
|
|
51
|
+
const nextKey = activeRoutes[i + 1]?.key;
|
|
47
52
|
const previousDescriptor = previousKey
|
|
48
53
|
? descriptors[previousKey]
|
|
49
54
|
: undefined;
|
|
50
55
|
const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
|
|
51
|
-
const
|
|
56
|
+
const descriptor = descriptors[route.key];
|
|
57
|
+
|
|
58
|
+
if (descriptor == null) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`Couldn't find a descriptor for route '${route.key}'.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const { options, navigation, render } = descriptor;
|
|
52
65
|
|
|
53
66
|
const headerBack = previousDescriptor
|
|
54
67
|
? {
|
|
@@ -66,6 +79,7 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
66
79
|
const canGoBack = headerBack != null;
|
|
67
80
|
|
|
68
81
|
const {
|
|
82
|
+
inactiveBehavior = 'pause',
|
|
69
83
|
header,
|
|
70
84
|
headerShown,
|
|
71
85
|
headerBackIcon,
|
|
@@ -79,11 +93,40 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
79
93
|
|
|
80
94
|
const nextPresentation = nextDescriptor?.options.presentation;
|
|
81
95
|
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
96
|
+
const isNextScreenTransparent =
|
|
97
|
+
nextPresentation != null &&
|
|
98
|
+
TRANSPARENT_PRESENTATIONS.includes(nextPresentation);
|
|
85
99
|
|
|
86
|
-
|
|
100
|
+
const isInactive = i > state.index;
|
|
101
|
+
|
|
102
|
+
const isBeforeLast = i === activeRoutes.length - 2;
|
|
103
|
+
|
|
104
|
+
const isNextScreenModal = nextKey
|
|
105
|
+
? modalRouteKeys.includes(nextKey)
|
|
106
|
+
: false;
|
|
107
|
+
|
|
108
|
+
const activityMode =
|
|
109
|
+
// Render focused screens normally
|
|
110
|
+
isFocused
|
|
111
|
+
? 'normal'
|
|
112
|
+
: // Unpause preloaded and retained screens so updates are visible
|
|
113
|
+
// This lets effects on those screens run
|
|
114
|
+
inactiveBehavior === 'none' ||
|
|
115
|
+
isInactive ||
|
|
116
|
+
isNextScreenTransparent
|
|
117
|
+
? 'inert'
|
|
118
|
+
: inactiveBehavior === 'unmount' &&
|
|
119
|
+
!isNextScreenModal &&
|
|
120
|
+
!isBeforeLast &&
|
|
121
|
+
!route.state
|
|
122
|
+
? 'unmounted'
|
|
123
|
+
: 'paused';
|
|
124
|
+
|
|
125
|
+
if (activityMode === 'unmounted') {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const content = (
|
|
87
130
|
<Screen
|
|
88
131
|
key={route.key}
|
|
89
132
|
focused={isFocused}
|
|
@@ -117,18 +160,7 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
117
160
|
{...rest}
|
|
118
161
|
label={headerBackTitle ?? label}
|
|
119
162
|
tintColor={tintColor}
|
|
120
|
-
|
|
121
|
-
headerBackIcon !== undefined
|
|
122
|
-
? () => (
|
|
123
|
-
<Image
|
|
124
|
-
source={headerBackIcon.source}
|
|
125
|
-
resizeMode="contain"
|
|
126
|
-
tintColor={tintColor}
|
|
127
|
-
style={styles.backImage}
|
|
128
|
-
/>
|
|
129
|
-
)
|
|
130
|
-
: undefined
|
|
131
|
-
}
|
|
163
|
+
icon={headerBackIcon}
|
|
132
164
|
onPress={navigation.goBack}
|
|
133
165
|
/>
|
|
134
166
|
)
|
|
@@ -138,22 +170,13 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
138
170
|
/>
|
|
139
171
|
)
|
|
140
172
|
}
|
|
141
|
-
style={
|
|
142
|
-
StyleSheet.absoluteFill,
|
|
143
|
-
|
|
144
|
-
display:
|
|
145
|
-
(isFocused ||
|
|
146
|
-
(nextPresentation != null &&
|
|
147
|
-
TRANSPARENT_PRESENTATIONS.includes(nextPresentation))) &&
|
|
148
|
-
!isPreloaded
|
|
149
|
-
? 'flex'
|
|
150
|
-
: 'none',
|
|
151
|
-
},
|
|
152
|
-
presentation != null &&
|
|
173
|
+
style={{
|
|
174
|
+
...StyleSheet.absoluteFill,
|
|
175
|
+
...(presentation != null &&
|
|
153
176
|
TRANSPARENT_PRESENTATIONS.includes(presentation)
|
|
154
177
|
? { backgroundColor: 'transparent' }
|
|
155
|
-
: null,
|
|
156
|
-
|
|
178
|
+
: null),
|
|
179
|
+
}}
|
|
157
180
|
>
|
|
158
181
|
<HeaderBackContext.Provider value={headerBack}>
|
|
159
182
|
<AnimatedHeaderHeightProvider>
|
|
@@ -164,6 +187,17 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
164
187
|
</HeaderBackContext.Provider>
|
|
165
188
|
</Screen>
|
|
166
189
|
);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<ActivityView
|
|
193
|
+
key={route.key}
|
|
194
|
+
mode={activityMode}
|
|
195
|
+
visible={isFocused || isNextScreenTransparent}
|
|
196
|
+
style={StyleSheet.absoluteFill}
|
|
197
|
+
>
|
|
198
|
+
{content}
|
|
199
|
+
</ActivityView>
|
|
200
|
+
);
|
|
167
201
|
})}
|
|
168
202
|
</SafeAreaProviderCompat>
|
|
169
203
|
);
|
|
@@ -194,9 +228,4 @@ const styles = StyleSheet.create({
|
|
|
194
228
|
contentContainer: {
|
|
195
229
|
flex: 1,
|
|
196
230
|
},
|
|
197
|
-
backImage: {
|
|
198
|
-
height: 24,
|
|
199
|
-
width: 24,
|
|
200
|
-
margin: 3,
|
|
201
|
-
},
|
|
202
231
|
});
|