@react-navigation/native-stack 8.0.0-alpha.12 → 8.0.0-alpha.14
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/views/NativeStackView.js +16 -4
- package/lib/module/views/NativeStackView.js.map +1 -1
- package/lib/module/views/NativeStackView.native.js +59 -56
- package/lib/module/views/NativeStackView.native.js.map +1 -1
- package/lib/module/views/useHeaderConfigProps.js +26 -11
- package/lib/module/views/useHeaderConfigProps.js.map +1 -1
- package/lib/typescript/src/types.d.ts +133 -130
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.d.ts.map +1 -1
- package/lib/typescript/src/views/NativeStackView.native.d.ts.map +1 -1
- package/lib/typescript/src/views/useHeaderConfigProps.d.ts.map +1 -1
- package/package.json +11 -11
- package/src/types.tsx +197 -161
- package/src/views/NativeStackView.native.tsx +95 -77
- package/src/views/NativeStackView.tsx +30 -9
- package/src/views/useHeaderConfigProps.tsx +40 -19
|
@@ -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,15 @@ 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?: boolean;
|
|
59
|
-
|
|
54
|
+
previousDescriptor?: NativeStackDescriptor | undefined;
|
|
55
|
+
nextDescriptor?: NativeStackDescriptor | undefined;
|
|
56
|
+
isPresentationModal?: boolean | undefined;
|
|
57
|
+
isNextScreenTransparent?: boolean | undefined;
|
|
58
|
+
isPreloaded?: boolean | undefined;
|
|
60
59
|
onWillDisappear: () => void;
|
|
61
60
|
onWillAppear: () => void;
|
|
62
61
|
onAppear: () => void;
|
|
@@ -70,13 +69,18 @@ type SceneViewProps = {
|
|
|
70
69
|
|
|
71
70
|
const useNativeDriver = Platform.OS !== 'web';
|
|
72
71
|
|
|
72
|
+
const TRANSPARENT_PRESENTATIONS = [
|
|
73
|
+
'transparentModal',
|
|
74
|
+
'containedTransparentModal',
|
|
75
|
+
];
|
|
76
|
+
|
|
73
77
|
const SceneView = ({
|
|
74
78
|
index,
|
|
75
79
|
focused,
|
|
76
|
-
shouldFreeze,
|
|
77
80
|
descriptor,
|
|
78
81
|
previousDescriptor,
|
|
79
82
|
isPresentationModal,
|
|
83
|
+
isNextScreenTransparent,
|
|
80
84
|
isPreloaded,
|
|
81
85
|
onWillDisappear,
|
|
82
86
|
onWillAppear,
|
|
@@ -91,6 +95,7 @@ const SceneView = ({
|
|
|
91
95
|
const { route, navigation, options, render } = descriptor;
|
|
92
96
|
|
|
93
97
|
const {
|
|
98
|
+
inactiveBehavior = 'pause',
|
|
94
99
|
animation,
|
|
95
100
|
animationDuration,
|
|
96
101
|
animationMatchesGesture,
|
|
@@ -123,7 +128,6 @@ const SceneView = ({
|
|
|
123
128
|
statusBarStyle,
|
|
124
129
|
unstable_sheetFooter,
|
|
125
130
|
scrollEdgeEffects,
|
|
126
|
-
freezeOnBlur,
|
|
127
131
|
contentStyle,
|
|
128
132
|
} = options;
|
|
129
133
|
|
|
@@ -285,6 +289,55 @@ const SceneView = ({
|
|
|
285
289
|
}
|
|
286
290
|
);
|
|
287
291
|
|
|
292
|
+
const content = (
|
|
293
|
+
<AnimatedHeaderHeightContext.Provider value={animatedHeaderHeight}>
|
|
294
|
+
<HeaderHeightContext.Provider
|
|
295
|
+
value={headerShown !== false ? headerHeight : (parentHeaderHeight ?? 0)}
|
|
296
|
+
>
|
|
297
|
+
{headerBackground != null ? (
|
|
298
|
+
/**
|
|
299
|
+
* To show a custom header background, we render it at the top of the screen below the header
|
|
300
|
+
* The header also needs to be positioned absolutely (with `translucent` style)
|
|
301
|
+
*/
|
|
302
|
+
<View
|
|
303
|
+
style={[
|
|
304
|
+
styles.background,
|
|
305
|
+
headerTransparent ? styles.translucent : null,
|
|
306
|
+
{ height: headerHeight },
|
|
307
|
+
]}
|
|
308
|
+
>
|
|
309
|
+
{headerBackground()}
|
|
310
|
+
</View>
|
|
311
|
+
) : null}
|
|
312
|
+
{header != null && headerShown !== false ? (
|
|
313
|
+
<View
|
|
314
|
+
onLayout={(e) => {
|
|
315
|
+
const headerHeight = e.nativeEvent.layout.height;
|
|
316
|
+
|
|
317
|
+
animatedHeaderHeight.setValue(headerHeight);
|
|
318
|
+
setHeaderHeight(headerHeight);
|
|
319
|
+
}}
|
|
320
|
+
style={[styles.header, headerTransparent ? styles.absolute : null]}
|
|
321
|
+
>
|
|
322
|
+
{header({
|
|
323
|
+
back: headerBack,
|
|
324
|
+
options,
|
|
325
|
+
route,
|
|
326
|
+
navigation,
|
|
327
|
+
})}
|
|
328
|
+
</View>
|
|
329
|
+
) : null}
|
|
330
|
+
<HeaderShownContext.Provider
|
|
331
|
+
value={isParentHeaderShown || headerShown !== false}
|
|
332
|
+
>
|
|
333
|
+
<HeaderBackContext.Provider value={headerBack}>
|
|
334
|
+
{render()}
|
|
335
|
+
</HeaderBackContext.Provider>
|
|
336
|
+
</HeaderShownContext.Provider>
|
|
337
|
+
</HeaderHeightContext.Provider>
|
|
338
|
+
</AnimatedHeaderHeightContext.Provider>
|
|
339
|
+
);
|
|
340
|
+
|
|
288
341
|
return (
|
|
289
342
|
<NavigationProvider navigation={navigation} route={route}>
|
|
290
343
|
<ScreenStackItem
|
|
@@ -296,7 +349,6 @@ const SceneView = ({
|
|
|
296
349
|
customAnimationOnSwipe={animationMatchesGesture}
|
|
297
350
|
fullScreenSwipeEnabled={fullScreenGestureEnabled}
|
|
298
351
|
fullScreenSwipeShadowEnabled={fullScreenGestureShadowEnabled}
|
|
299
|
-
freezeOnBlur={freezeOnBlur}
|
|
300
352
|
gestureEnabled={
|
|
301
353
|
Platform.OS === 'android'
|
|
302
354
|
? // This prop enables handling of system back gestures on Android
|
|
@@ -353,62 +405,30 @@ const SceneView = ({
|
|
|
353
405
|
]}
|
|
354
406
|
headerConfig={headerConfig}
|
|
355
407
|
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
408
|
>
|
|
361
|
-
<
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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>
|
|
409
|
+
<ActivityView
|
|
410
|
+
mode={
|
|
411
|
+
// Render focused screens normally
|
|
412
|
+
// Unpause preloaded screens so updates are visible
|
|
413
|
+
// This lets effects on preloaded screens run
|
|
414
|
+
// We don't need to handle inert as it'll be handled natively
|
|
415
|
+
inactiveBehavior === 'none' ||
|
|
416
|
+
focused ||
|
|
417
|
+
isPreloaded ||
|
|
418
|
+
isNextScreenTransparent
|
|
419
|
+
? 'normal'
|
|
420
|
+
: 'paused'
|
|
421
|
+
}
|
|
422
|
+
visible={
|
|
423
|
+
// We don't need to hide the content since it's handled natively
|
|
424
|
+
// Hiding may also cause flash due to lag after native tab switch
|
|
425
|
+
// So we leave it always visible
|
|
426
|
+
true
|
|
427
|
+
}
|
|
428
|
+
style={StyleSheet.absoluteFill}
|
|
429
|
+
>
|
|
430
|
+
{content}
|
|
431
|
+
</ActivityView>
|
|
412
432
|
</ScreenStackItem>
|
|
413
433
|
</NavigationProvider>
|
|
414
434
|
);
|
|
@@ -433,7 +453,6 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
|
|
|
433
453
|
{state.routes.concat(state.preloadedRoutes).map((route, index) => {
|
|
434
454
|
const descriptor = descriptors[route.key];
|
|
435
455
|
const isFocused = state.index === index;
|
|
436
|
-
const isBelowFocused = state.index - 1 === index;
|
|
437
456
|
const previousKey = state.routes[index - 1]?.key;
|
|
438
457
|
const nextKey = state.routes[index + 1]?.key;
|
|
439
458
|
const previousDescriptor = previousKey
|
|
@@ -441,29 +460,28 @@ export function NativeStackView({ state, navigation, descriptors }: Props) {
|
|
|
441
460
|
: undefined;
|
|
442
461
|
const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
|
|
443
462
|
|
|
463
|
+
const nextPresentation = nextDescriptor?.options.presentation;
|
|
464
|
+
|
|
465
|
+
const isNextScreenTransparent =
|
|
466
|
+
nextPresentation != null &&
|
|
467
|
+
TRANSPARENT_PRESENTATIONS.includes(nextPresentation);
|
|
468
|
+
|
|
444
469
|
const isModal = modalRouteKeys.includes(route.key);
|
|
445
|
-
const isModalOnIos = isModal && Platform.OS === 'ios';
|
|
446
470
|
|
|
447
471
|
const isPreloaded = state.preloadedRoutes.some(
|
|
448
472
|
(r) => r.key === route.key
|
|
449
473
|
);
|
|
450
474
|
|
|
451
|
-
// On Fabric, when screen is frozen, animated and reanimated values are not updated
|
|
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;
|
|
456
|
-
|
|
457
475
|
return (
|
|
458
476
|
<SceneView
|
|
459
477
|
key={route.key}
|
|
460
478
|
index={index}
|
|
461
479
|
focused={isFocused}
|
|
462
|
-
shouldFreeze={shouldFreeze}
|
|
463
480
|
descriptor={descriptor}
|
|
464
481
|
previousDescriptor={previousDescriptor}
|
|
465
482
|
nextDescriptor={nextDescriptor}
|
|
466
483
|
isPresentationModal={isModal}
|
|
484
|
+
isNextScreenTransparent={isNextScreenTransparent}
|
|
467
485
|
isPreloaded={isPreloaded}
|
|
468
486
|
onWillDisappear={() => {
|
|
469
487
|
navigation.emit({
|
|
@@ -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';
|
|
@@ -66,6 +67,7 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
66
67
|
const canGoBack = headerBack != null;
|
|
67
68
|
|
|
68
69
|
const {
|
|
70
|
+
inactiveBehavior = 'pause',
|
|
69
71
|
header,
|
|
70
72
|
headerShown,
|
|
71
73
|
headerBackIcon,
|
|
@@ -79,11 +81,15 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
79
81
|
|
|
80
82
|
const nextPresentation = nextDescriptor?.options.presentation;
|
|
81
83
|
|
|
84
|
+
const isNextScreenTransparent =
|
|
85
|
+
nextPresentation != null &&
|
|
86
|
+
TRANSPARENT_PRESENTATIONS.includes(nextPresentation);
|
|
87
|
+
|
|
82
88
|
const isPreloaded = state.preloadedRoutes.some(
|
|
83
89
|
(r) => r.key === route.key
|
|
84
90
|
);
|
|
85
91
|
|
|
86
|
-
|
|
92
|
+
const content = (
|
|
87
93
|
<Screen
|
|
88
94
|
key={route.key}
|
|
89
95
|
focused={isFocused}
|
|
@@ -128,14 +134,7 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
128
134
|
)
|
|
129
135
|
}
|
|
130
136
|
style={{
|
|
131
|
-
...StyleSheet.
|
|
132
|
-
display:
|
|
133
|
-
(isFocused ||
|
|
134
|
-
(nextPresentation != null &&
|
|
135
|
-
TRANSPARENT_PRESENTATIONS.includes(nextPresentation))) &&
|
|
136
|
-
!isPreloaded
|
|
137
|
-
? 'flex'
|
|
138
|
-
: 'none',
|
|
137
|
+
...StyleSheet.absoluteFill,
|
|
139
138
|
...(presentation != null &&
|
|
140
139
|
TRANSPARENT_PRESENTATIONS.includes(presentation)
|
|
141
140
|
? { backgroundColor: 'transparent' }
|
|
@@ -151,6 +150,28 @@ export function NativeStackView({ state, descriptors }: Props) {
|
|
|
151
150
|
</HeaderBackContext.Provider>
|
|
152
151
|
</Screen>
|
|
153
152
|
);
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<ActivityView
|
|
156
|
+
key={route.key}
|
|
157
|
+
mode={
|
|
158
|
+
// Render focused screens normally
|
|
159
|
+
isFocused
|
|
160
|
+
? 'normal'
|
|
161
|
+
: // Unpause preloaded screens so updates are visible
|
|
162
|
+
// This lets effects on preloaded screens run
|
|
163
|
+
inactiveBehavior === 'none' ||
|
|
164
|
+
isPreloaded ||
|
|
165
|
+
isNextScreenTransparent
|
|
166
|
+
? 'inert'
|
|
167
|
+
: 'paused'
|
|
168
|
+
}
|
|
169
|
+
visible={isFocused || isPreloaded || isNextScreenTransparent}
|
|
170
|
+
style={StyleSheet.absoluteFill}
|
|
171
|
+
>
|
|
172
|
+
{content}
|
|
173
|
+
</ActivityView>
|
|
174
|
+
);
|
|
154
175
|
})}
|
|
155
176
|
</SafeAreaProviderCompat>
|
|
156
177
|
);
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
import { useMemo } from 'react';
|
|
11
11
|
import { Platform, StyleSheet, type TextStyle, View } from 'react-native';
|
|
12
12
|
import {
|
|
13
|
-
type HeaderBarButtonItem,
|
|
14
13
|
type HeaderBarButtonItemMenuAction,
|
|
15
14
|
type HeaderBarButtonItemSubmenu,
|
|
15
|
+
type HeaderBarButtonItemWithAction,
|
|
16
|
+
type HeaderBarButtonItemWithMenu,
|
|
16
17
|
isSearchBarAvailableForCurrentPlatform,
|
|
17
18
|
ScreenStackHeaderBackButtonImage,
|
|
18
19
|
ScreenStackHeaderCenterView,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
|
|
26
27
|
import type {
|
|
27
28
|
NativeStackHeaderItem,
|
|
29
|
+
NativeStackHeaderItemButton,
|
|
28
30
|
NativeStackHeaderItemMenuAction,
|
|
29
31
|
NativeStackHeaderItemMenuSubmenu,
|
|
30
32
|
NativeStackNavigationOptions,
|
|
@@ -74,7 +76,7 @@ const processBarButtonItems = (
|
|
|
74
76
|
|
|
75
77
|
const { badge, label, labelStyle, icon, ...rest } = item;
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
const processedItemCommon = {
|
|
78
80
|
...rest,
|
|
79
81
|
index,
|
|
80
82
|
title: label,
|
|
@@ -82,32 +84,34 @@ const processBarButtonItems = (
|
|
|
82
84
|
...fonts.regular,
|
|
83
85
|
...labelStyle,
|
|
84
86
|
},
|
|
85
|
-
icon:
|
|
86
|
-
icon?.type === 'image'
|
|
87
|
-
? icon.tinted === false
|
|
88
|
-
? {
|
|
89
|
-
type: 'imageSource',
|
|
90
|
-
imageSource: icon.source,
|
|
91
|
-
}
|
|
92
|
-
: {
|
|
93
|
-
type: 'templateSource',
|
|
94
|
-
templateSource: icon.source,
|
|
95
|
-
}
|
|
96
|
-
: icon,
|
|
87
|
+
icon: transformIcon(icon),
|
|
97
88
|
};
|
|
98
89
|
|
|
99
|
-
|
|
90
|
+
let processedItem:
|
|
91
|
+
| HeaderBarButtonItemWithAction
|
|
92
|
+
| HeaderBarButtonItemWithMenu;
|
|
93
|
+
|
|
94
|
+
if (processedItemCommon.type === 'menu' && item.type === 'menu') {
|
|
100
95
|
const { multiselectable, layout } = item.menu;
|
|
101
96
|
|
|
102
97
|
processedItem = {
|
|
103
|
-
...
|
|
98
|
+
...processedItemCommon,
|
|
104
99
|
menu: {
|
|
105
|
-
...
|
|
100
|
+
...processedItemCommon.menu,
|
|
106
101
|
singleSelection: !multiselectable,
|
|
107
102
|
displayAsPalette: layout === 'palette',
|
|
108
103
|
items: item.menu.items.map(getMenuItem),
|
|
109
104
|
},
|
|
110
105
|
};
|
|
106
|
+
} else if (
|
|
107
|
+
processedItemCommon.type === 'button' &&
|
|
108
|
+
item.type === 'button'
|
|
109
|
+
) {
|
|
110
|
+
processedItem = processedItemCommon;
|
|
111
|
+
} else {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`Invalid item type: ${JSON.stringify(item)}. Valid types are 'button' and 'menu'.`
|
|
114
|
+
);
|
|
111
115
|
}
|
|
112
116
|
|
|
113
117
|
if (badge) {
|
|
@@ -140,14 +144,30 @@ const processBarButtonItems = (
|
|
|
140
144
|
.filter((item) => item != null);
|
|
141
145
|
};
|
|
142
146
|
|
|
147
|
+
const transformIcon = (
|
|
148
|
+
icon: NativeStackHeaderItemButton['icon']
|
|
149
|
+
):
|
|
150
|
+
| HeaderBarButtonItemWithAction['icon']
|
|
151
|
+
| HeaderBarButtonItemWithMenu['icon'] => {
|
|
152
|
+
if (icon?.type === 'image') {
|
|
153
|
+
return icon.tinted === false
|
|
154
|
+
? { type: 'imageSource', imageSource: icon.source }
|
|
155
|
+
: { type: 'templateSource', templateSource: icon.source };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return icon;
|
|
159
|
+
};
|
|
160
|
+
|
|
143
161
|
const getMenuItem = (
|
|
144
162
|
item: NativeStackHeaderItemMenuAction | NativeStackHeaderItemMenuSubmenu
|
|
145
163
|
): HeaderBarButtonItemMenuAction | HeaderBarButtonItemSubmenu => {
|
|
146
164
|
if (item.type === 'submenu') {
|
|
147
|
-
const { label, inline, layout, items, multiselectable, ...rest } =
|
|
165
|
+
const { label, icon, inline, layout, items, multiselectable, ...rest } =
|
|
166
|
+
item;
|
|
148
167
|
|
|
149
168
|
return {
|
|
150
169
|
...rest,
|
|
170
|
+
icon: transformIcon(icon),
|
|
151
171
|
title: label,
|
|
152
172
|
displayAsPalette: layout === 'palette',
|
|
153
173
|
displayInline: inline,
|
|
@@ -156,10 +176,11 @@ const getMenuItem = (
|
|
|
156
176
|
};
|
|
157
177
|
}
|
|
158
178
|
|
|
159
|
-
const { label, description, ...rest } = item;
|
|
179
|
+
const { label, icon, description, ...rest } = item;
|
|
160
180
|
|
|
161
181
|
return {
|
|
162
182
|
...rest,
|
|
183
|
+
icon: transformIcon(icon),
|
|
163
184
|
title: label,
|
|
164
185
|
subtitle: description,
|
|
165
186
|
};
|