react-native-screen-transitions 2.0.2 → 2.0.4
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/commonjs/utils/bounds/constants.js +3 -3
- package/lib/commonjs/utils/bounds/constants.js.map +1 -1
- package/lib/commonjs/utils/bounds/get-bounds.js +3 -3
- package/lib/commonjs/utils/bounds/get-bounds.js.map +1 -1
- package/lib/module/utils/bounds/constants.js +3 -3
- package/lib/module/utils/bounds/constants.js.map +1 -1
- package/lib/module/utils/bounds/get-bounds.js +3 -3
- package/lib/module/utils/bounds/get-bounds.js.map +1 -1
- package/lib/typescript/utils/bounds/_types/get-bounds.d.ts +2 -2
- package/lib/typescript/utils/bounds/_types/get-bounds.d.ts.map +1 -1
- package/lib/typescript/utils/bounds/constants.d.ts +4 -4
- package/lib/typescript/utils/bounds/constants.d.ts.map +1 -1
- package/lib/typescript/utils/bounds/get-bounds.d.ts +2 -2
- package/lib/typescript/utils/bounds/get-bounds.d.ts.map +1 -1
- package/lib/typescript/utils/bounds/index.d.ts +1 -1
- package/lib/typescript/utils/bounds/index.d.ts.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/geometry.test.ts +127 -0
- package/src/components/bounds-activator.tsx +29 -0
- package/src/components/controllers/screen-lifecycle.tsx +72 -0
- package/src/components/create-transition-aware-component.tsx +99 -0
- package/src/components/root-transition-aware.tsx +56 -0
- package/src/configs/index.ts +2 -0
- package/src/configs/presets.ts +227 -0
- package/src/configs/specs.ts +9 -0
- package/src/hooks/animation/use-associated-style.tsx +28 -0
- package/src/hooks/animation/use-screen-animation.tsx +142 -0
- package/src/hooks/bounds/use-bound-measurer.tsx +71 -0
- package/src/hooks/gestures/use-build-gestures.tsx +369 -0
- package/src/hooks/gestures/use-scroll-progress.tsx +60 -0
- package/src/hooks/use-stable-callback.tsx +15 -0
- package/src/index.ts +32 -0
- package/src/integrations/native-stack/navigators/createNativeStackNavigator.tsx +112 -0
- package/src/integrations/native-stack/utils/debounce.tsx +14 -0
- package/src/integrations/native-stack/utils/getModalRoutesKeys.ts +21 -0
- package/src/integrations/native-stack/utils/useAnimatedHeaderHeight.tsx +18 -0
- package/src/integrations/native-stack/utils/useDismissedRouteError.tsx +30 -0
- package/src/integrations/native-stack/utils/useInvalidPreventRemoveError.tsx +31 -0
- package/src/integrations/native-stack/views/FontProcessor.native.tsx +12 -0
- package/src/integrations/native-stack/views/FontProcessor.tsx +5 -0
- package/src/integrations/native-stack/views/FooterComponent.tsx +10 -0
- package/src/integrations/native-stack/views/NativeStackView.native.tsx +657 -0
- package/src/integrations/native-stack/views/NativeStackView.tsx +214 -0
- package/src/integrations/native-stack/views/useHeaderConfigProps.tsx +295 -0
- package/src/providers/gestures.tsx +89 -0
- package/src/providers/keys.tsx +38 -0
- package/src/stores/animations.ts +45 -0
- package/src/stores/bounds.ts +71 -0
- package/src/stores/gestures.ts +55 -0
- package/src/stores/navigator-dismiss-state.ts +17 -0
- package/src/stores/utils/reset-stores-for-screen.ts +14 -0
- package/src/types/animation.ts +76 -0
- package/src/types/bounds.ts +82 -0
- package/src/types/core.ts +50 -0
- package/src/types/gesture.ts +33 -0
- package/src/types/navigator.ts +744 -0
- package/src/types/utils.ts +3 -0
- package/src/utils/animation/animate.ts +28 -0
- package/src/utils/animation/run-transition.ts +49 -0
- package/src/utils/bounds/_types/builder.ts +35 -0
- package/src/utils/bounds/_types/geometry.ts +17 -0
- package/src/utils/bounds/_types/get-bounds.ts +10 -0
- package/src/utils/bounds/build-bound-styles.ts +184 -0
- package/src/utils/bounds/constants.ts +25 -0
- package/src/utils/bounds/flatten-styles.ts +21 -0
- package/src/utils/bounds/geometry.ts +113 -0
- package/src/utils/bounds/get-bounds.ts +56 -0
- package/src/utils/bounds/index.ts +46 -0
- package/src/utils/bounds/style-composers.ts +172 -0
- package/src/utils/gesture/apply-gesture-activation-criteria.ts +109 -0
- package/src/utils/gesture/map-gesture-to-progress.ts +11 -0
- package/src/utils/gesture/normalize-gesture-translation.ts +20 -0
- package/src/utils/index.ts +1 -0
- package/lib/commonjs/__tests__ /geometry.test.js +0 -178
- package/lib/commonjs/__tests__ /geometry.test.js.map +0 -1
- package/lib/module/__tests__ /geometry.test.js +0 -178
- package/lib/module/__tests__ /geometry.test.js.map +0 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getHeaderTitle,
|
|
3
|
+
Header,
|
|
4
|
+
HeaderBackButton,
|
|
5
|
+
HeaderBackContext,
|
|
6
|
+
SafeAreaProviderCompat,
|
|
7
|
+
Screen,
|
|
8
|
+
useHeaderHeight,
|
|
9
|
+
} from "@react-navigation/elements";
|
|
10
|
+
import {
|
|
11
|
+
type ParamListBase,
|
|
12
|
+
type RouteProp,
|
|
13
|
+
type StackNavigationState,
|
|
14
|
+
useLinkBuilder,
|
|
15
|
+
} from "@react-navigation/native";
|
|
16
|
+
import * as React from "react";
|
|
17
|
+
import { Animated, Image, StyleSheet, View } from "react-native";
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
NativeStackDescriptor,
|
|
21
|
+
NativeStackDescriptorMap,
|
|
22
|
+
NativeStackNavigationHelpers,
|
|
23
|
+
} from "../../../types/navigator";
|
|
24
|
+
import { AnimatedHeaderHeightContext } from "../utils/useAnimatedHeaderHeight";
|
|
25
|
+
|
|
26
|
+
type Props = {
|
|
27
|
+
state: StackNavigationState<ParamListBase>;
|
|
28
|
+
// This is used for the native implementation of the stack.
|
|
29
|
+
navigation: NativeStackNavigationHelpers;
|
|
30
|
+
descriptors: NativeStackDescriptorMap;
|
|
31
|
+
describe: (
|
|
32
|
+
route: RouteProp<ParamListBase>,
|
|
33
|
+
placeholder: boolean,
|
|
34
|
+
) => NativeStackDescriptor;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const TRANSPARENT_PRESENTATIONS = [
|
|
38
|
+
"transparentModal",
|
|
39
|
+
"containedTransparentModal",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
export function NativeStackView({ state, descriptors, describe }: Props) {
|
|
43
|
+
const parentHeaderBack = React.useContext(HeaderBackContext);
|
|
44
|
+
const { buildHref } = useLinkBuilder();
|
|
45
|
+
|
|
46
|
+
const preloadedDescriptors =
|
|
47
|
+
state.preloadedRoutes.reduce<NativeStackDescriptorMap>((acc, route) => {
|
|
48
|
+
acc[route.key] = acc[route.key] || describe(route, true);
|
|
49
|
+
return acc;
|
|
50
|
+
}, {});
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<SafeAreaProviderCompat>
|
|
54
|
+
{state.routes.concat(state.preloadedRoutes).map((route, i) => {
|
|
55
|
+
const isFocused = state.index === i;
|
|
56
|
+
const previousKey = state.routes[i - 1]?.key;
|
|
57
|
+
const nextKey = state.routes[i + 1]?.key;
|
|
58
|
+
const previousDescriptor = previousKey
|
|
59
|
+
? descriptors[previousKey]
|
|
60
|
+
: undefined;
|
|
61
|
+
const nextDescriptor = nextKey ? descriptors[nextKey] : undefined;
|
|
62
|
+
const { options, navigation, render } =
|
|
63
|
+
descriptors[route.key] ?? preloadedDescriptors[route.key];
|
|
64
|
+
|
|
65
|
+
const headerBack = previousDescriptor
|
|
66
|
+
? {
|
|
67
|
+
title: getHeaderTitle(
|
|
68
|
+
previousDescriptor.options,
|
|
69
|
+
previousDescriptor.route.name,
|
|
70
|
+
),
|
|
71
|
+
href: buildHref(
|
|
72
|
+
previousDescriptor.route.name,
|
|
73
|
+
previousDescriptor.route.params,
|
|
74
|
+
),
|
|
75
|
+
}
|
|
76
|
+
: parentHeaderBack;
|
|
77
|
+
|
|
78
|
+
const canGoBack = headerBack != null;
|
|
79
|
+
|
|
80
|
+
const {
|
|
81
|
+
header,
|
|
82
|
+
headerShown,
|
|
83
|
+
headerBackImageSource,
|
|
84
|
+
headerLeft,
|
|
85
|
+
headerTransparent,
|
|
86
|
+
headerBackTitle,
|
|
87
|
+
presentation,
|
|
88
|
+
contentStyle,
|
|
89
|
+
...rest
|
|
90
|
+
} = options;
|
|
91
|
+
|
|
92
|
+
const nextPresentation = nextDescriptor?.options.presentation;
|
|
93
|
+
|
|
94
|
+
const isPreloaded =
|
|
95
|
+
preloadedDescriptors[route.key] !== undefined &&
|
|
96
|
+
descriptors[route.key] === undefined;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<Screen
|
|
100
|
+
key={route.key}
|
|
101
|
+
focused={isFocused}
|
|
102
|
+
route={route}
|
|
103
|
+
navigation={navigation}
|
|
104
|
+
headerShown={headerShown}
|
|
105
|
+
headerTransparent={headerTransparent}
|
|
106
|
+
header={
|
|
107
|
+
header !== undefined ? (
|
|
108
|
+
header({
|
|
109
|
+
back: headerBack,
|
|
110
|
+
options,
|
|
111
|
+
route,
|
|
112
|
+
navigation,
|
|
113
|
+
})
|
|
114
|
+
) : (
|
|
115
|
+
<Header
|
|
116
|
+
{...rest}
|
|
117
|
+
back={headerBack}
|
|
118
|
+
title={getHeaderTitle(options, route.name)}
|
|
119
|
+
headerLeft={
|
|
120
|
+
typeof headerLeft === "function"
|
|
121
|
+
? ({ label, ...rest }) =>
|
|
122
|
+
headerLeft({
|
|
123
|
+
...rest,
|
|
124
|
+
label: headerBackTitle ?? label,
|
|
125
|
+
})
|
|
126
|
+
: headerLeft === undefined && canGoBack
|
|
127
|
+
? ({ tintColor, label, ...rest }) => (
|
|
128
|
+
<HeaderBackButton
|
|
129
|
+
{...rest}
|
|
130
|
+
label={headerBackTitle ?? label}
|
|
131
|
+
tintColor={tintColor}
|
|
132
|
+
backImage={
|
|
133
|
+
headerBackImageSource !== undefined
|
|
134
|
+
? () => (
|
|
135
|
+
<Image
|
|
136
|
+
source={headerBackImageSource}
|
|
137
|
+
resizeMode="contain"
|
|
138
|
+
tintColor={tintColor}
|
|
139
|
+
style={styles.backImage}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
: undefined
|
|
143
|
+
}
|
|
144
|
+
onPress={navigation.goBack}
|
|
145
|
+
/>
|
|
146
|
+
)
|
|
147
|
+
: headerLeft
|
|
148
|
+
}
|
|
149
|
+
headerTransparent={headerTransparent}
|
|
150
|
+
/>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
style={[
|
|
154
|
+
StyleSheet.absoluteFill,
|
|
155
|
+
{
|
|
156
|
+
display:
|
|
157
|
+
(isFocused ||
|
|
158
|
+
(nextPresentation != null &&
|
|
159
|
+
TRANSPARENT_PRESENTATIONS.includes(nextPresentation))) &&
|
|
160
|
+
!isPreloaded
|
|
161
|
+
? "flex"
|
|
162
|
+
: "none",
|
|
163
|
+
},
|
|
164
|
+
presentation != null &&
|
|
165
|
+
TRANSPARENT_PRESENTATIONS.includes(presentation)
|
|
166
|
+
? { backgroundColor: "transparent" }
|
|
167
|
+
: null,
|
|
168
|
+
]}
|
|
169
|
+
>
|
|
170
|
+
<HeaderBackContext.Provider value={headerBack}>
|
|
171
|
+
<AnimatedHeaderHeightProvider>
|
|
172
|
+
<View style={[styles.contentContainer, contentStyle]}>
|
|
173
|
+
{render()}
|
|
174
|
+
</View>
|
|
175
|
+
</AnimatedHeaderHeightProvider>
|
|
176
|
+
</HeaderBackContext.Provider>
|
|
177
|
+
</Screen>
|
|
178
|
+
);
|
|
179
|
+
})}
|
|
180
|
+
</SafeAreaProviderCompat>
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const AnimatedHeaderHeightProvider = ({
|
|
185
|
+
children,
|
|
186
|
+
}: {
|
|
187
|
+
children: React.ReactNode;
|
|
188
|
+
}) => {
|
|
189
|
+
const headerHeight = useHeaderHeight();
|
|
190
|
+
const [animatedHeaderHeight] = React.useState(
|
|
191
|
+
() => new Animated.Value(headerHeight),
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
React.useEffect(() => {
|
|
195
|
+
animatedHeaderHeight.setValue(headerHeight);
|
|
196
|
+
}, [animatedHeaderHeight, headerHeight]);
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<AnimatedHeaderHeightContext.Provider value={animatedHeaderHeight}>
|
|
200
|
+
{children}
|
|
201
|
+
</AnimatedHeaderHeightContext.Provider>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const styles = StyleSheet.create({
|
|
206
|
+
contentContainer: {
|
|
207
|
+
flex: 1,
|
|
208
|
+
},
|
|
209
|
+
backImage: {
|
|
210
|
+
height: 24,
|
|
211
|
+
width: 24,
|
|
212
|
+
margin: 3,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { getHeaderTitle, HeaderTitle } from "@react-navigation/elements";
|
|
2
|
+
import { type Route, useLocale, useTheme } from "@react-navigation/native";
|
|
3
|
+
import { Platform, StyleSheet, type TextStyle, View } from "react-native";
|
|
4
|
+
import {
|
|
5
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
6
|
+
ScreenStackHeaderBackButtonImage,
|
|
7
|
+
ScreenStackHeaderCenterView,
|
|
8
|
+
ScreenStackHeaderLeftView,
|
|
9
|
+
ScreenStackHeaderRightView,
|
|
10
|
+
ScreenStackHeaderSearchBarView,
|
|
11
|
+
SearchBar,
|
|
12
|
+
} from "react-native-screens";
|
|
13
|
+
|
|
14
|
+
import type { NativeStackNavigationOptions } from "../../../types/navigator";
|
|
15
|
+
import { processFonts } from "./FontProcessor";
|
|
16
|
+
|
|
17
|
+
type Props = NativeStackNavigationOptions & {
|
|
18
|
+
headerTopInsetEnabled: boolean;
|
|
19
|
+
headerHeight: number;
|
|
20
|
+
headerBack: { title?: string | undefined; href: undefined } | undefined;
|
|
21
|
+
route: Route<string>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export function useHeaderConfigProps({
|
|
25
|
+
headerBackImageSource,
|
|
26
|
+
headerBackButtonDisplayMode,
|
|
27
|
+
headerBackButtonMenuEnabled,
|
|
28
|
+
headerBackTitle,
|
|
29
|
+
headerBackTitleStyle,
|
|
30
|
+
headerBackVisible,
|
|
31
|
+
headerShadowVisible,
|
|
32
|
+
headerLargeStyle,
|
|
33
|
+
headerLargeTitle,
|
|
34
|
+
headerLargeTitleShadowVisible,
|
|
35
|
+
headerLargeTitleStyle,
|
|
36
|
+
headerBackground,
|
|
37
|
+
headerLeft,
|
|
38
|
+
headerRight,
|
|
39
|
+
headerShown,
|
|
40
|
+
headerStyle,
|
|
41
|
+
headerBlurEffect,
|
|
42
|
+
headerTintColor,
|
|
43
|
+
headerTitle,
|
|
44
|
+
headerTitleAlign,
|
|
45
|
+
headerTitleStyle,
|
|
46
|
+
headerTransparent,
|
|
47
|
+
headerSearchBarOptions,
|
|
48
|
+
headerTopInsetEnabled,
|
|
49
|
+
headerBack,
|
|
50
|
+
route,
|
|
51
|
+
title,
|
|
52
|
+
}: Props) {
|
|
53
|
+
const { direction } = useLocale();
|
|
54
|
+
const { colors, fonts } = useTheme();
|
|
55
|
+
const tintColor =
|
|
56
|
+
headerTintColor ?? (Platform.OS === "ios" ? colors.primary : colors.text);
|
|
57
|
+
|
|
58
|
+
const headerBackTitleStyleFlattened =
|
|
59
|
+
StyleSheet.flatten([fonts.regular, headerBackTitleStyle]) || {};
|
|
60
|
+
const headerLargeTitleStyleFlattened =
|
|
61
|
+
StyleSheet.flatten([
|
|
62
|
+
Platform.select({ ios: fonts.heavy, default: fonts.medium }),
|
|
63
|
+
headerLargeTitleStyle,
|
|
64
|
+
]) || {};
|
|
65
|
+
const headerTitleStyleFlattened =
|
|
66
|
+
StyleSheet.flatten([
|
|
67
|
+
Platform.select({ ios: fonts.bold, default: fonts.medium }),
|
|
68
|
+
headerTitleStyle,
|
|
69
|
+
]) || {};
|
|
70
|
+
const headerStyleFlattened = StyleSheet.flatten(headerStyle) || {};
|
|
71
|
+
const headerLargeStyleFlattened = StyleSheet.flatten(headerLargeStyle) || {};
|
|
72
|
+
|
|
73
|
+
const [backTitleFontFamily, largeTitleFontFamily, titleFontFamily] =
|
|
74
|
+
processFonts([
|
|
75
|
+
headerBackTitleStyleFlattened.fontFamily,
|
|
76
|
+
headerLargeTitleStyleFlattened.fontFamily,
|
|
77
|
+
headerTitleStyleFlattened.fontFamily,
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
const backTitleFontSize =
|
|
81
|
+
"fontSize" in headerBackTitleStyleFlattened
|
|
82
|
+
? headerBackTitleStyleFlattened.fontSize
|
|
83
|
+
: undefined;
|
|
84
|
+
|
|
85
|
+
const titleText = getHeaderTitle({ title, headerTitle }, route.name);
|
|
86
|
+
const titleColor =
|
|
87
|
+
"color" in headerTitleStyleFlattened
|
|
88
|
+
? headerTitleStyleFlattened.color
|
|
89
|
+
: (headerTintColor ?? colors.text);
|
|
90
|
+
const titleFontSize =
|
|
91
|
+
"fontSize" in headerTitleStyleFlattened
|
|
92
|
+
? headerTitleStyleFlattened.fontSize
|
|
93
|
+
: undefined;
|
|
94
|
+
const titleFontWeight = headerTitleStyleFlattened.fontWeight;
|
|
95
|
+
|
|
96
|
+
const largeTitleBackgroundColor = headerLargeStyleFlattened.backgroundColor;
|
|
97
|
+
const largeTitleColor =
|
|
98
|
+
"color" in headerLargeTitleStyleFlattened
|
|
99
|
+
? headerLargeTitleStyleFlattened.color
|
|
100
|
+
: undefined;
|
|
101
|
+
const largeTitleFontSize =
|
|
102
|
+
"fontSize" in headerLargeTitleStyleFlattened
|
|
103
|
+
? headerLargeTitleStyleFlattened.fontSize
|
|
104
|
+
: undefined;
|
|
105
|
+
const largeTitleFontWeight = headerLargeTitleStyleFlattened.fontWeight;
|
|
106
|
+
|
|
107
|
+
const headerTitleStyleSupported: TextStyle = { color: titleColor };
|
|
108
|
+
|
|
109
|
+
if (headerTitleStyleFlattened.fontFamily != null) {
|
|
110
|
+
headerTitleStyleSupported.fontFamily = headerTitleStyleFlattened.fontFamily;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (titleFontSize != null) {
|
|
114
|
+
headerTitleStyleSupported.fontSize = titleFontSize;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (titleFontWeight != null) {
|
|
118
|
+
headerTitleStyleSupported.fontWeight = titleFontWeight;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const headerBackgroundColor =
|
|
122
|
+
headerStyleFlattened.backgroundColor ??
|
|
123
|
+
(headerBackground != null || headerTransparent
|
|
124
|
+
? "transparent"
|
|
125
|
+
: colors.card);
|
|
126
|
+
|
|
127
|
+
const canGoBack = headerBack != null;
|
|
128
|
+
|
|
129
|
+
const headerLeftElement = headerLeft?.({
|
|
130
|
+
tintColor,
|
|
131
|
+
canGoBack,
|
|
132
|
+
label: headerBackTitle ?? headerBack?.title,
|
|
133
|
+
// `href` is only applicable to web
|
|
134
|
+
href: undefined,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const headerRightElement = headerRight?.({
|
|
138
|
+
tintColor,
|
|
139
|
+
canGoBack,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const headerTitleElement =
|
|
143
|
+
typeof headerTitle === "function"
|
|
144
|
+
? headerTitle({
|
|
145
|
+
tintColor,
|
|
146
|
+
children: titleText,
|
|
147
|
+
})
|
|
148
|
+
: null;
|
|
149
|
+
|
|
150
|
+
const hasHeaderSearchBar =
|
|
151
|
+
isSearchBarAvailableForCurrentPlatform && headerSearchBarOptions != null;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* We need to set this in if:
|
|
155
|
+
* - Back button should stay visible when `headerLeft` is specified
|
|
156
|
+
* - If `headerTitle` for Android is specified, so we only need to remove the title and keep the back button
|
|
157
|
+
*/
|
|
158
|
+
const backButtonInCustomView =
|
|
159
|
+
headerBackVisible ||
|
|
160
|
+
(Platform.OS === "android" &&
|
|
161
|
+
headerTitleElement != null &&
|
|
162
|
+
headerLeftElement == null);
|
|
163
|
+
|
|
164
|
+
const translucent =
|
|
165
|
+
headerBackground != null ||
|
|
166
|
+
headerTransparent ||
|
|
167
|
+
// When using a SearchBar or large title, the header needs to be translucent for it to work on iOS
|
|
168
|
+
((hasHeaderSearchBar || headerLargeTitle) &&
|
|
169
|
+
Platform.OS === "ios" &&
|
|
170
|
+
headerTransparent !== false);
|
|
171
|
+
|
|
172
|
+
const isBackButtonDisplayModeAvailable =
|
|
173
|
+
// On iOS 14+
|
|
174
|
+
Platform.OS === "ios" &&
|
|
175
|
+
parseInt(Platform.Version, 10) >= 14 &&
|
|
176
|
+
// Doesn't have custom styling, by default System, see: https://github.com/software-mansion/react-native-screens/pull/2105#discussion_r1565222738
|
|
177
|
+
(backTitleFontFamily == null || backTitleFontFamily === "System") &&
|
|
178
|
+
backTitleFontSize == null &&
|
|
179
|
+
// Back button menu is not disabled
|
|
180
|
+
headerBackButtonMenuEnabled !== false;
|
|
181
|
+
|
|
182
|
+
const isCenterViewRenderedAndroid = headerTitleAlign === "center";
|
|
183
|
+
|
|
184
|
+
const children = (
|
|
185
|
+
<>
|
|
186
|
+
{Platform.OS === "ios" ? (
|
|
187
|
+
<>
|
|
188
|
+
{headerLeftElement != null ? (
|
|
189
|
+
<ScreenStackHeaderLeftView>
|
|
190
|
+
{headerLeftElement}
|
|
191
|
+
</ScreenStackHeaderLeftView>
|
|
192
|
+
) : null}
|
|
193
|
+
{headerTitleElement != null ? (
|
|
194
|
+
<ScreenStackHeaderCenterView>
|
|
195
|
+
{headerTitleElement}
|
|
196
|
+
</ScreenStackHeaderCenterView>
|
|
197
|
+
) : null}
|
|
198
|
+
</>
|
|
199
|
+
) : (
|
|
200
|
+
<>
|
|
201
|
+
{headerLeftElement != null || typeof headerTitle === "function" ? (
|
|
202
|
+
// The style passed to header left, together with title element being wrapped
|
|
203
|
+
// in flex view is reqruied for proper header layout, in particular,
|
|
204
|
+
// for the text truncation to work.
|
|
205
|
+
<ScreenStackHeaderLeftView
|
|
206
|
+
style={!isCenterViewRenderedAndroid ? { flex: 1 } : null}
|
|
207
|
+
>
|
|
208
|
+
{headerLeftElement}
|
|
209
|
+
{headerTitleAlign !== "center" ? (
|
|
210
|
+
typeof headerTitle === "function" ? (
|
|
211
|
+
<View style={{ flex: 1 }}>{headerTitleElement}</View>
|
|
212
|
+
) : (
|
|
213
|
+
<View style={{ flex: 1 }}>
|
|
214
|
+
<HeaderTitle
|
|
215
|
+
tintColor={tintColor}
|
|
216
|
+
style={headerTitleStyleSupported}
|
|
217
|
+
>
|
|
218
|
+
{titleText}
|
|
219
|
+
</HeaderTitle>
|
|
220
|
+
</View>
|
|
221
|
+
)
|
|
222
|
+
) : null}
|
|
223
|
+
</ScreenStackHeaderLeftView>
|
|
224
|
+
) : null}
|
|
225
|
+
{isCenterViewRenderedAndroid ? (
|
|
226
|
+
<ScreenStackHeaderCenterView>
|
|
227
|
+
{typeof headerTitle === "function" ? (
|
|
228
|
+
headerTitleElement
|
|
229
|
+
) : (
|
|
230
|
+
<HeaderTitle
|
|
231
|
+
tintColor={tintColor}
|
|
232
|
+
style={headerTitleStyleSupported}
|
|
233
|
+
>
|
|
234
|
+
{titleText}
|
|
235
|
+
</HeaderTitle>
|
|
236
|
+
)}
|
|
237
|
+
</ScreenStackHeaderCenterView>
|
|
238
|
+
) : null}
|
|
239
|
+
</>
|
|
240
|
+
)}
|
|
241
|
+
{headerBackImageSource !== undefined ? (
|
|
242
|
+
<ScreenStackHeaderBackButtonImage source={headerBackImageSource} />
|
|
243
|
+
) : null}
|
|
244
|
+
{headerRightElement != null ? (
|
|
245
|
+
<ScreenStackHeaderRightView>
|
|
246
|
+
{headerRightElement}
|
|
247
|
+
</ScreenStackHeaderRightView>
|
|
248
|
+
) : null}
|
|
249
|
+
{hasHeaderSearchBar ? (
|
|
250
|
+
<ScreenStackHeaderSearchBarView>
|
|
251
|
+
<SearchBar {...headerSearchBarOptions} />
|
|
252
|
+
</ScreenStackHeaderSearchBarView>
|
|
253
|
+
) : null}
|
|
254
|
+
</>
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
backButtonInCustomView,
|
|
259
|
+
backgroundColor: headerBackgroundColor,
|
|
260
|
+
backTitle: headerBackTitle,
|
|
261
|
+
backTitleVisible: isBackButtonDisplayModeAvailable
|
|
262
|
+
? undefined
|
|
263
|
+
: headerBackButtonDisplayMode !== "minimal",
|
|
264
|
+
backButtonDisplayMode: isBackButtonDisplayModeAvailable
|
|
265
|
+
? headerBackButtonDisplayMode
|
|
266
|
+
: undefined,
|
|
267
|
+
backTitleFontFamily,
|
|
268
|
+
backTitleFontSize,
|
|
269
|
+
blurEffect: headerBlurEffect,
|
|
270
|
+
color: tintColor,
|
|
271
|
+
direction,
|
|
272
|
+
disableBackButtonMenu: headerBackButtonMenuEnabled === false,
|
|
273
|
+
hidden: headerShown === false,
|
|
274
|
+
hideBackButton: headerBackVisible === false,
|
|
275
|
+
hideShadow:
|
|
276
|
+
headerShadowVisible === false ||
|
|
277
|
+
headerBackground != null ||
|
|
278
|
+
(headerTransparent && headerShadowVisible !== true),
|
|
279
|
+
largeTitle: headerLargeTitle,
|
|
280
|
+
largeTitleBackgroundColor,
|
|
281
|
+
largeTitleColor,
|
|
282
|
+
largeTitleFontFamily,
|
|
283
|
+
largeTitleFontSize,
|
|
284
|
+
largeTitleFontWeight,
|
|
285
|
+
largeTitleHideShadow: headerLargeTitleShadowVisible === false,
|
|
286
|
+
title: titleText,
|
|
287
|
+
titleColor,
|
|
288
|
+
titleFontFamily,
|
|
289
|
+
titleFontSize,
|
|
290
|
+
titleFontWeight: String(titleFontWeight),
|
|
291
|
+
topInsetEnabled: headerTopInsetEnabled,
|
|
292
|
+
translucent: translucent === true,
|
|
293
|
+
children,
|
|
294
|
+
} as const;
|
|
295
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import { StyleSheet, View } from "react-native";
|
|
3
|
+
import type { GestureType } from "react-native-gesture-handler";
|
|
4
|
+
import {
|
|
5
|
+
GestureDetector,
|
|
6
|
+
GestureHandlerRootView,
|
|
7
|
+
} from "react-native-gesture-handler";
|
|
8
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
9
|
+
import { useSharedValue } from "react-native-reanimated";
|
|
10
|
+
import { useBuildGestures } from "../hooks/gestures/use-build-gestures";
|
|
11
|
+
|
|
12
|
+
export type ScrollProgress = {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
contentHeight: number;
|
|
16
|
+
contentWidth: number;
|
|
17
|
+
layoutHeight: number;
|
|
18
|
+
layoutWidth: number;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export interface GestureContextType {
|
|
22
|
+
panGesture: GestureType;
|
|
23
|
+
nativeGesture: GestureType;
|
|
24
|
+
scrollProgress: SharedValue<ScrollProgress>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ScreenGestureProviderProps = {
|
|
28
|
+
children: React.ReactNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const DEFAULT_SCROLL_PROGRESS: ScrollProgress = {
|
|
32
|
+
x: 0,
|
|
33
|
+
y: 0,
|
|
34
|
+
contentHeight: 0,
|
|
35
|
+
contentWidth: 0,
|
|
36
|
+
layoutHeight: 0,
|
|
37
|
+
layoutWidth: 0,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const GestureContext = createContext<GestureContextType | undefined>(undefined);
|
|
41
|
+
|
|
42
|
+
export const ScreenGestureProvider = ({
|
|
43
|
+
children,
|
|
44
|
+
}: ScreenGestureProviderProps) => {
|
|
45
|
+
const scrollProgress = useSharedValue<ScrollProgress>(
|
|
46
|
+
DEFAULT_SCROLL_PROGRESS,
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
const { panGesture, nativeGesture } = useBuildGestures({
|
|
50
|
+
scrollProgress,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const value = useMemo(
|
|
54
|
+
() => ({
|
|
55
|
+
panGesture,
|
|
56
|
+
scrollProgress,
|
|
57
|
+
nativeGesture,
|
|
58
|
+
}),
|
|
59
|
+
[panGesture, scrollProgress, nativeGesture],
|
|
60
|
+
) satisfies GestureContextType;
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<GestureHandlerRootView>
|
|
64
|
+
<GestureContext.Provider value={value}>
|
|
65
|
+
<GestureDetector gesture={panGesture}>
|
|
66
|
+
<View style={styles.container}>{children}</View>
|
|
67
|
+
</GestureDetector>
|
|
68
|
+
</GestureContext.Provider>
|
|
69
|
+
</GestureHandlerRootView>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export const useGestureContext = () => {
|
|
74
|
+
const context = useContext(GestureContext);
|
|
75
|
+
|
|
76
|
+
if (!context) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"useGestureContext must be used within a ScreenGestureProvider",
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return context;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const styles = StyleSheet.create({
|
|
86
|
+
container: {
|
|
87
|
+
flex: 1,
|
|
88
|
+
},
|
|
89
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { createContext, useContext, useMemo } from "react";
|
|
2
|
+
import type { NativeStackDescriptor } from "../types/navigator";
|
|
3
|
+
|
|
4
|
+
interface KeysContextType {
|
|
5
|
+
previous?: NativeStackDescriptor;
|
|
6
|
+
current: NativeStackDescriptor;
|
|
7
|
+
next?: NativeStackDescriptor;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const KeysContext = createContext<KeysContextType | undefined>(undefined);
|
|
11
|
+
|
|
12
|
+
interface KeysProviderProps {
|
|
13
|
+
children: React.ReactNode;
|
|
14
|
+
previous?: NativeStackDescriptor;
|
|
15
|
+
current: NativeStackDescriptor;
|
|
16
|
+
next?: NativeStackDescriptor;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const KeysProvider = ({
|
|
20
|
+
children,
|
|
21
|
+
previous,
|
|
22
|
+
current,
|
|
23
|
+
next,
|
|
24
|
+
}: KeysProviderProps) => {
|
|
25
|
+
const value = useMemo(
|
|
26
|
+
() => ({ previous, current, next }),
|
|
27
|
+
[previous, current, next],
|
|
28
|
+
);
|
|
29
|
+
return <KeysContext.Provider value={value}>{children}</KeysContext.Provider>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const useKeys = (): KeysContextType => {
|
|
33
|
+
const context = useContext(KeysContext);
|
|
34
|
+
if (context === undefined) {
|
|
35
|
+
throw new Error("useKeys must be used within a KeysProvider");
|
|
36
|
+
}
|
|
37
|
+
return context;
|
|
38
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { makeMutable, type SharedValue } from "react-native-reanimated";
|
|
2
|
+
import type { ScreenKey } from "../types/navigator";
|
|
3
|
+
|
|
4
|
+
export type AnimationMap = {
|
|
5
|
+
progress: SharedValue<number>;
|
|
6
|
+
closing: SharedValue<number>;
|
|
7
|
+
animating: SharedValue<number>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const store: Record<ScreenKey, AnimationMap> = {};
|
|
11
|
+
|
|
12
|
+
const ensure = (key: ScreenKey) => {
|
|
13
|
+
let bag = store[key];
|
|
14
|
+
if (!bag) {
|
|
15
|
+
bag = {
|
|
16
|
+
progress: makeMutable(0),
|
|
17
|
+
closing: makeMutable(0),
|
|
18
|
+
animating: makeMutable(0),
|
|
19
|
+
};
|
|
20
|
+
store[key] = bag;
|
|
21
|
+
}
|
|
22
|
+
return bag;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function getAnimation(
|
|
26
|
+
key: ScreenKey,
|
|
27
|
+
type: "progress" | "closing" | "animating",
|
|
28
|
+
): SharedValue<number> {
|
|
29
|
+
return ensure(key)[type];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getAll(key: ScreenKey) {
|
|
33
|
+
return ensure(key);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function clear(routeKey: ScreenKey) {
|
|
37
|
+
"worklet";
|
|
38
|
+
delete store[routeKey];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Animations = {
|
|
42
|
+
getAnimation,
|
|
43
|
+
clear,
|
|
44
|
+
getAll,
|
|
45
|
+
};
|