react-native-screens 3.7.2 → 3.10.1
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/README.md +69 -3
- package/android/build.gradle +8 -7
- package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
- package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
- package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
- package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +7 -41
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +55 -40
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +19 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +30 -101
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +76 -14
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +13 -4
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +90 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +150 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +40 -0
- package/ios/RNSScreen.h +1 -0
- package/ios/RNSScreen.m +35 -0
- package/ios/RNSScreenContainer.h +2 -0
- package/ios/RNSScreenStack.m +24 -6
- package/ios/RNSScreenStackHeaderConfig.m +45 -2
- package/ios/RNSScreenWindowTraits.h +5 -0
- package/ios/RNSScreenWindowTraits.m +29 -0
- package/lib/commonjs/index.js +24 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.native.js +103 -17
- package/lib/commonjs/index.native.js.map +1 -1
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
- package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
- package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/commonjs/reanimated/index.js +2 -2
- package/lib/commonjs/reanimated/index.js.map +1 -1
- package/lib/commonjs/utils.js +20 -0
- package/lib/commonjs/utils.js.map +1 -0
- package/lib/module/index.js +5 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.native.js +99 -19
- package/lib/module/index.native.js.map +1 -1
- package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
- package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/module/native-stack/views/HeaderConfig.js +46 -5
- package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/module/reanimated/ReanimatedScreen.js +6 -73
- package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
- package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/module/reanimated/index.js +1 -1
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/utils.js +8 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/native-stack/types.d.ts +0 -2
- package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
- package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
- package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
- package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
- package/lib/typescript/reanimated/index.d.ts +1 -1
- package/lib/typescript/types.d.ts +46 -1
- package/lib/typescript/utils.d.ts +2 -0
- package/native-stack/README.md +35 -7
- package/package.json +5 -2
- package/src/index.native.tsx +138 -43
- package/src/index.tsx +10 -0
- package/src/native-stack/types.tsx +0 -2
- package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
- package/src/native-stack/views/HeaderConfig.tsx +46 -3
- package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
- package/src/reanimated/ReanimatedScreen.tsx +6 -84
- package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
- package/src/reanimated/index.tsx +1 -1
- package/src/types.tsx +46 -1
- package/src/utils.ts +12 -0
package/src/index.native.tsx
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
View,
|
|
11
11
|
ViewProps,
|
|
12
12
|
} from 'react-native';
|
|
13
|
+
import { Freeze } from 'react-freeze';
|
|
13
14
|
// @ts-ignore Getting private component
|
|
14
15
|
// eslint-disable-next-line import/default
|
|
15
16
|
import processColor from 'react-native/Libraries/StyleSheet/processColor';
|
|
17
|
+
import { version } from 'react-native/package.json';
|
|
16
18
|
|
|
17
19
|
import TransitionProgressContext from './TransitionProgressContext';
|
|
18
20
|
import useTransitionProgress from './useTransitionProgress';
|
|
@@ -29,6 +31,10 @@ import {
|
|
|
29
31
|
ScreenStackHeaderConfigProps,
|
|
30
32
|
SearchBarProps,
|
|
31
33
|
} from './types';
|
|
34
|
+
import {
|
|
35
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
36
|
+
executeNativeBackPress,
|
|
37
|
+
} from './utils';
|
|
32
38
|
|
|
33
39
|
// web implementation is taken from `index.tsx`
|
|
34
40
|
const isPlatformSupported =
|
|
@@ -47,6 +53,21 @@ function enableScreens(shouldEnableScreens = true): void {
|
|
|
47
53
|
}
|
|
48
54
|
}
|
|
49
55
|
|
|
56
|
+
let ENABLE_FREEZE = false;
|
|
57
|
+
|
|
58
|
+
function enableFreeze(shouldEnableReactFreeze = true): void {
|
|
59
|
+
const minor = parseInt(version.split('.')[1]); // eg. takes 66 from '0.66.0'
|
|
60
|
+
|
|
61
|
+
// react-freeze requires react-native >=0.64, react-native from main is 0.0.0
|
|
62
|
+
if (!(minor === 0 || minor >= 64) && shouldEnableReactFreeze) {
|
|
63
|
+
console.warn(
|
|
64
|
+
'react-freeze library requires at least react-native 0.64. Please upgrade your react-native version in order to use this feature.'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ENABLE_FREEZE = shouldEnableReactFreeze;
|
|
69
|
+
}
|
|
70
|
+
|
|
50
71
|
// const that tells if the library should use new implementation, will be undefined for older versions
|
|
51
72
|
const shouldUseActivityState = true;
|
|
52
73
|
|
|
@@ -85,7 +106,9 @@ const ScreensNativeModules = {
|
|
|
85
106
|
get NativeScreenNavigationContainer() {
|
|
86
107
|
NativeScreenNavigationContainerValue =
|
|
87
108
|
NativeScreenNavigationContainerValue ||
|
|
88
|
-
|
|
109
|
+
(Platform.OS === 'ios'
|
|
110
|
+
? requireNativeComponent('RNSScreenNavigationContainer')
|
|
111
|
+
: this.NativeScreenContainer);
|
|
89
112
|
return NativeScreenNavigationContainerValue;
|
|
90
113
|
},
|
|
91
114
|
|
|
@@ -121,6 +144,65 @@ const ScreensNativeModules = {
|
|
|
121
144
|
},
|
|
122
145
|
};
|
|
123
146
|
|
|
147
|
+
interface FreezeWrapperProps {
|
|
148
|
+
freeze: boolean;
|
|
149
|
+
children: React.ReactNode;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// This component allows one more render before freezing the screen.
|
|
153
|
+
// Allows activityState to reach the native side and useIsFocused to work correctly.
|
|
154
|
+
function DelayedFreeze({ freeze, children }: FreezeWrapperProps) {
|
|
155
|
+
// flag used for determining whether freeze should be enabled
|
|
156
|
+
const [freezeState, setFreezeState] = React.useState(false);
|
|
157
|
+
|
|
158
|
+
if (freeze !== freezeState) {
|
|
159
|
+
// setImmediate is executed at the end of the JS execution block.
|
|
160
|
+
// Used here for changing the state right after the render.
|
|
161
|
+
setImmediate(() => {
|
|
162
|
+
setFreezeState(freeze);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return <Freeze freeze={freeze ? freezeState : false}>{children}</Freeze>;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function MaybeFreeze({ freeze, children }: FreezeWrapperProps) {
|
|
170
|
+
if (ENABLE_FREEZE) {
|
|
171
|
+
return <DelayedFreeze freeze={freeze}>{children}</DelayedFreeze>;
|
|
172
|
+
} else {
|
|
173
|
+
return <>{children}</>;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function ScreenStack(props: ScreenStackProps) {
|
|
178
|
+
if (ENABLE_FREEZE) {
|
|
179
|
+
const { children, ...rest } = props;
|
|
180
|
+
const size = React.Children.count(children);
|
|
181
|
+
// freezes all screens except the top one
|
|
182
|
+
const childrenWithFreeze = React.Children.map(children, (child, index) => (
|
|
183
|
+
<DelayedFreeze freeze={size - index > 1}>{child}</DelayedFreeze>
|
|
184
|
+
));
|
|
185
|
+
return (
|
|
186
|
+
<ScreensNativeModules.NativeScreenStack {...rest}>
|
|
187
|
+
{childrenWithFreeze}
|
|
188
|
+
</ScreensNativeModules.NativeScreenStack>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return <ScreensNativeModules.NativeScreenStack {...props} />;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Incomplete type, all accessible properties available at:
|
|
195
|
+
// react-native/Libraries/Components/View/ReactNativeViewViewConfig.js
|
|
196
|
+
interface ViewConfig extends View {
|
|
197
|
+
viewConfig: {
|
|
198
|
+
validAttributes: {
|
|
199
|
+
style: {
|
|
200
|
+
display: boolean;
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
124
206
|
class Screen extends React.Component<ScreenProps> {
|
|
125
207
|
private ref: React.ElementRef<typeof View> | null = null;
|
|
126
208
|
private closing = new Animated.Value(0);
|
|
@@ -166,40 +248,52 @@ class Screen extends React.Component<ScreenProps> {
|
|
|
166
248
|
const processedColor = processColor(statusBarColor);
|
|
167
249
|
|
|
168
250
|
return (
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
251
|
+
<MaybeFreeze freeze={activityState === 0}>
|
|
252
|
+
<AnimatedNativeScreen
|
|
253
|
+
{...props}
|
|
254
|
+
statusBarColor={processedColor}
|
|
255
|
+
activityState={activityState}
|
|
256
|
+
// This prevents showing blank screen when navigating between multiple screens with freezing
|
|
257
|
+
// https://github.com/software-mansion/react-native-screens/pull/1208
|
|
258
|
+
ref={(ref: ViewConfig) => {
|
|
259
|
+
if (ref?.viewConfig?.validAttributes?.style) {
|
|
260
|
+
ref.viewConfig.validAttributes.style = {
|
|
261
|
+
...ref.viewConfig.validAttributes.style,
|
|
262
|
+
display: false,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
this.setRef(ref);
|
|
266
|
+
}}
|
|
267
|
+
onTransitionProgress={
|
|
268
|
+
!isNativeStack
|
|
269
|
+
? undefined
|
|
270
|
+
: Animated.event(
|
|
271
|
+
[
|
|
272
|
+
{
|
|
273
|
+
nativeEvent: {
|
|
274
|
+
progress: this.progress,
|
|
275
|
+
closing: this.closing,
|
|
276
|
+
goingForward: this.goingForward,
|
|
277
|
+
},
|
|
184
278
|
},
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
</
|
|
279
|
+
],
|
|
280
|
+
{ useNativeDriver: true }
|
|
281
|
+
)
|
|
282
|
+
}>
|
|
283
|
+
{!isNativeStack ? ( // see comment of this prop in types.tsx for information why it is needed
|
|
284
|
+
children
|
|
285
|
+
) : (
|
|
286
|
+
<TransitionProgressContext.Provider
|
|
287
|
+
value={{
|
|
288
|
+
progress: this.progress,
|
|
289
|
+
closing: this.closing,
|
|
290
|
+
goingForward: this.goingForward,
|
|
291
|
+
}}>
|
|
292
|
+
{children}
|
|
293
|
+
</TransitionProgressContext.Provider>
|
|
294
|
+
)}
|
|
295
|
+
</AnimatedNativeScreen>
|
|
296
|
+
</MaybeFreeze>
|
|
203
297
|
);
|
|
204
298
|
} else {
|
|
205
299
|
// same reason as above
|
|
@@ -321,6 +415,7 @@ module.exports = {
|
|
|
321
415
|
Screen,
|
|
322
416
|
ScreenContainer,
|
|
323
417
|
ScreenContext,
|
|
418
|
+
ScreenStack,
|
|
324
419
|
|
|
325
420
|
get NativeScreen() {
|
|
326
421
|
return ScreensNativeModules.NativeScreen;
|
|
@@ -331,15 +426,9 @@ module.exports = {
|
|
|
331
426
|
},
|
|
332
427
|
|
|
333
428
|
get NativeScreenNavigationContainer() {
|
|
334
|
-
|
|
335
|
-
return ScreensNativeModules.NativeScreenNavigationContainer;
|
|
336
|
-
}
|
|
337
|
-
return ScreensNativeModules.NativeScreenContainer;
|
|
429
|
+
return ScreensNativeModules.NativeScreenNavigationContainer;
|
|
338
430
|
},
|
|
339
431
|
|
|
340
|
-
get ScreenStack() {
|
|
341
|
-
return ScreensNativeModules.NativeScreenStack;
|
|
342
|
-
},
|
|
343
432
|
get ScreenStackHeaderConfig() {
|
|
344
433
|
return ScreensNativeModules.NativeScreenStackHeaderConfig;
|
|
345
434
|
},
|
|
@@ -347,8 +436,10 @@ module.exports = {
|
|
|
347
436
|
return ScreensNativeModules.NativeScreenStackHeaderSubview;
|
|
348
437
|
},
|
|
349
438
|
get SearchBar() {
|
|
350
|
-
if (
|
|
351
|
-
console.warn(
|
|
439
|
+
if (!isSearchBarAvailableForCurrentPlatform) {
|
|
440
|
+
console.warn(
|
|
441
|
+
'Importing SearchBar is only valid on iOS and Android devices.'
|
|
442
|
+
);
|
|
352
443
|
return View;
|
|
353
444
|
}
|
|
354
445
|
|
|
@@ -371,7 +462,11 @@ module.exports = {
|
|
|
371
462
|
ScreenStackHeaderSearchBarView,
|
|
372
463
|
|
|
373
464
|
enableScreens,
|
|
465
|
+
enableFreeze,
|
|
374
466
|
screensEnabled,
|
|
375
467
|
shouldUseActivityState,
|
|
376
468
|
useTransitionProgress,
|
|
469
|
+
|
|
470
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
471
|
+
executeNativeBackPress,
|
|
377
472
|
};
|
package/src/index.tsx
CHANGED
|
@@ -11,6 +11,10 @@ import {
|
|
|
11
11
|
|
|
12
12
|
export * from './types';
|
|
13
13
|
export { default as useTransitionProgress } from './useTransitionProgress';
|
|
14
|
+
export {
|
|
15
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
16
|
+
executeNativeBackPress,
|
|
17
|
+
} from './utils';
|
|
14
18
|
|
|
15
19
|
let ENABLE_SCREENS = true;
|
|
16
20
|
|
|
@@ -22,6 +26,12 @@ export function screensEnabled(): boolean {
|
|
|
22
26
|
return ENABLE_SCREENS;
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
// @ts-ignore function stub, freezing logic is located in index.native.tsx
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
31
|
+
export function enableFreeze(shouldEnableReactFreeze = true): void {
|
|
32
|
+
// noop
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
export class NativeScreen extends React.Component<ScreenProps> {
|
|
26
36
|
render(): JSX.Element {
|
|
27
37
|
let {
|
|
@@ -273,8 +273,6 @@ export type NativeStackNavigationOptions = {
|
|
|
273
273
|
screenOrientation?: ScreenProps['screenOrientation'];
|
|
274
274
|
/**
|
|
275
275
|
* Object in which you should pass props in order to render native iOS searchBar.
|
|
276
|
-
*
|
|
277
|
-
* @platform ios
|
|
278
276
|
*/
|
|
279
277
|
searchBar?: SearchBarProps;
|
|
280
278
|
/**
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { BackHandler, NativeEventSubscription } from 'react-native';
|
|
3
|
+
|
|
4
|
+
interface Args {
|
|
5
|
+
onBackPress: () => boolean;
|
|
6
|
+
isDisabled: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface UseBackPressSubscription {
|
|
10
|
+
handleAttached: () => void;
|
|
11
|
+
handleDetached: () => void;
|
|
12
|
+
createSubscription: () => void;
|
|
13
|
+
clearSubscription: () => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This hook is an abstraction for keeping back press subscription
|
|
18
|
+
* logic in one place.
|
|
19
|
+
*/
|
|
20
|
+
export function useBackPressSubscription({
|
|
21
|
+
onBackPress,
|
|
22
|
+
isDisabled,
|
|
23
|
+
}: Args): UseBackPressSubscription {
|
|
24
|
+
const [isActive, setIsActive] = React.useState(false);
|
|
25
|
+
const subscription = React.useRef<NativeEventSubscription | undefined>();
|
|
26
|
+
|
|
27
|
+
const clearSubscription = React.useCallback((shouldSetActive = true) => {
|
|
28
|
+
subscription.current?.remove();
|
|
29
|
+
subscription.current = undefined;
|
|
30
|
+
if (shouldSetActive) setIsActive(false);
|
|
31
|
+
}, []);
|
|
32
|
+
|
|
33
|
+
const createSubscription = React.useCallback(() => {
|
|
34
|
+
if (!isDisabled) {
|
|
35
|
+
subscription.current?.remove();
|
|
36
|
+
subscription.current = BackHandler.addEventListener(
|
|
37
|
+
'hardwareBackPress',
|
|
38
|
+
onBackPress
|
|
39
|
+
);
|
|
40
|
+
setIsActive(true);
|
|
41
|
+
}
|
|
42
|
+
}, [isDisabled, onBackPress]);
|
|
43
|
+
|
|
44
|
+
const handleAttached = React.useCallback(() => {
|
|
45
|
+
if (isActive) {
|
|
46
|
+
createSubscription();
|
|
47
|
+
}
|
|
48
|
+
}, [createSubscription, isActive]);
|
|
49
|
+
|
|
50
|
+
const handleDetached = React.useCallback(() => {
|
|
51
|
+
clearSubscription(false);
|
|
52
|
+
}, [clearSubscription]);
|
|
53
|
+
|
|
54
|
+
React.useEffect(() => {
|
|
55
|
+
if (isDisabled) {
|
|
56
|
+
clearSubscription();
|
|
57
|
+
}
|
|
58
|
+
}, [isDisabled, clearSubscription]);
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
handleAttached,
|
|
62
|
+
handleDetached,
|
|
63
|
+
createSubscription,
|
|
64
|
+
clearSubscription,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
ScreenStackHeaderRightView,
|
|
10
10
|
ScreenStackHeaderSearchBarView,
|
|
11
11
|
SearchBar,
|
|
12
|
+
SearchBarProps,
|
|
13
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
14
|
+
executeNativeBackPress,
|
|
12
15
|
} from 'react-native-screens';
|
|
13
16
|
import { NativeStackNavigationOptions } from '../types';
|
|
17
|
+
import { useBackPressSubscription } from '../utils/useBackPressSubscription';
|
|
14
18
|
import { processFonts } from './FontProcessor';
|
|
15
19
|
|
|
16
20
|
type Props = NativeStackNavigationOptions & {
|
|
@@ -48,6 +52,19 @@ export default function HeaderConfig({
|
|
|
48
52
|
const { colors } = useTheme();
|
|
49
53
|
const tintColor = headerTintColor ?? colors.primary;
|
|
50
54
|
|
|
55
|
+
// We need to use back press subscription here to override back button behavior on JS side.
|
|
56
|
+
// Because screens are usually used with react-navigation and this library overrides back button
|
|
57
|
+
// we need to handle it first in case when search bar is open
|
|
58
|
+
const {
|
|
59
|
+
handleAttached,
|
|
60
|
+
handleDetached,
|
|
61
|
+
clearSubscription,
|
|
62
|
+
createSubscription,
|
|
63
|
+
} = useBackPressSubscription({
|
|
64
|
+
onBackPress: executeNativeBackPress,
|
|
65
|
+
isDisabled: !searchBar || !!searchBar.disableBackButtonOverride,
|
|
66
|
+
});
|
|
67
|
+
|
|
51
68
|
const [
|
|
52
69
|
backTitleFontFamily,
|
|
53
70
|
largeTitleFontFamily,
|
|
@@ -58,6 +75,29 @@ export default function HeaderConfig({
|
|
|
58
75
|
headerTitleStyle.fontFamily,
|
|
59
76
|
]);
|
|
60
77
|
|
|
78
|
+
// We want to clear clearSubscription only when components unmounts or search bar changes
|
|
79
|
+
React.useEffect(() => clearSubscription, [searchBar]);
|
|
80
|
+
|
|
81
|
+
const processedSearchBarOptions = React.useMemo(() => {
|
|
82
|
+
if (
|
|
83
|
+
Platform.OS === 'android' &&
|
|
84
|
+
searchBar &&
|
|
85
|
+
!searchBar.disableBackButtonOverride
|
|
86
|
+
) {
|
|
87
|
+
const onFocus: SearchBarProps['onFocus'] = (...args) => {
|
|
88
|
+
createSubscription();
|
|
89
|
+
searchBar.onFocus?.(...args);
|
|
90
|
+
};
|
|
91
|
+
const onClose: SearchBarProps['onClose'] = (...args) => {
|
|
92
|
+
clearSubscription();
|
|
93
|
+
searchBar.onClose?.(...args);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return { ...searchBar, onFocus, onClose };
|
|
97
|
+
}
|
|
98
|
+
return searchBar;
|
|
99
|
+
}, [searchBar, createSubscription, clearSubscription]);
|
|
100
|
+
|
|
61
101
|
return (
|
|
62
102
|
<ScreenStackHeaderConfig
|
|
63
103
|
backButtonInCustomView={backButtonInCustomView}
|
|
@@ -99,7 +139,9 @@ export default function HeaderConfig({
|
|
|
99
139
|
titleFontSize={headerTitleStyle.fontSize}
|
|
100
140
|
titleFontWeight={headerTitleStyle.fontWeight}
|
|
101
141
|
topInsetEnabled={headerTopInsetEnabled}
|
|
102
|
-
translucent={headerTranslucent === true}
|
|
142
|
+
translucent={headerTranslucent === true}
|
|
143
|
+
onAttached={handleAttached}
|
|
144
|
+
onDetached={handleDetached}>
|
|
103
145
|
{headerRight !== undefined ? (
|
|
104
146
|
<ScreenStackHeaderRightView>
|
|
105
147
|
{headerRight({ tintColor })}
|
|
@@ -121,9 +163,10 @@ export default function HeaderConfig({
|
|
|
121
163
|
{headerCenter({ tintColor })}
|
|
122
164
|
</ScreenStackHeaderCenterView>
|
|
123
165
|
) : null}
|
|
124
|
-
{
|
|
166
|
+
{isSearchBarAvailableForCurrentPlatform &&
|
|
167
|
+
processedSearchBarOptions !== undefined ? (
|
|
125
168
|
<ScreenStackHeaderSearchBarView>
|
|
126
|
-
<SearchBar {...
|
|
169
|
+
<SearchBar {...processedSearchBarOptions} />
|
|
127
170
|
</ScreenStackHeaderSearchBarView>
|
|
128
171
|
) : null}
|
|
129
172
|
</ScreenStackHeaderConfig>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Platform } from 'react-native';
|
|
3
|
+
import {
|
|
4
|
+
Screen,
|
|
5
|
+
ScreenProps,
|
|
6
|
+
TransitionProgressEventType,
|
|
7
|
+
} from 'react-native-screens';
|
|
8
|
+
|
|
9
|
+
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
|
|
10
|
+
import Animated, { useEvent, useSharedValue } from 'react-native-reanimated';
|
|
11
|
+
import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext';
|
|
12
|
+
|
|
13
|
+
const AnimatedScreen = Animated.createAnimatedComponent(
|
|
14
|
+
(Screen as unknown) as React.ComponentClass
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const ReanimatedNativeStackScreen = React.forwardRef<
|
|
18
|
+
typeof AnimatedScreen,
|
|
19
|
+
ScreenProps
|
|
20
|
+
>((props, ref) => {
|
|
21
|
+
const { children, ...rest } = props;
|
|
22
|
+
|
|
23
|
+
const progress = useSharedValue(0);
|
|
24
|
+
const closing = useSharedValue(0);
|
|
25
|
+
const goingForward = useSharedValue(0);
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<AnimatedScreen
|
|
29
|
+
// @ts-ignore some problems with ref and onTransitionProgressReanimated being "fake" prop for parsing of `useEvent` return value
|
|
30
|
+
ref={ref}
|
|
31
|
+
onTransitionProgressReanimated={useEvent(
|
|
32
|
+
(event: TransitionProgressEventType) => {
|
|
33
|
+
'worklet';
|
|
34
|
+
progress.value = event.progress;
|
|
35
|
+
closing.value = event.closing;
|
|
36
|
+
goingForward.value = event.goingForward;
|
|
37
|
+
},
|
|
38
|
+
[
|
|
39
|
+
// This should not be necessary, but is not properly managed by `react-native-reanimated`
|
|
40
|
+
// @ts-ignore wrong type
|
|
41
|
+
Platform.OS === 'android'
|
|
42
|
+
? 'onTransitionProgress'
|
|
43
|
+
: 'topTransitionProgress',
|
|
44
|
+
]
|
|
45
|
+
)}
|
|
46
|
+
{...rest}>
|
|
47
|
+
<ReanimatedTransitionProgressContext.Provider
|
|
48
|
+
value={{
|
|
49
|
+
progress: progress,
|
|
50
|
+
closing: closing,
|
|
51
|
+
goingForward: goingForward,
|
|
52
|
+
}}>
|
|
53
|
+
{children}
|
|
54
|
+
</ReanimatedTransitionProgressContext.Provider>
|
|
55
|
+
</AnimatedScreen>
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
ReanimatedNativeStackScreen.displayName = 'ReanimatedNativeStackScreen';
|
|
60
|
+
|
|
61
|
+
export default ReanimatedNativeStackScreen;
|
|
@@ -1,103 +1,25 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
Screen,
|
|
5
|
-
ScreenProps,
|
|
6
|
-
ScreenContext,
|
|
7
|
-
TransitionProgressEventType,
|
|
8
|
-
} from 'react-native-screens';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Screen, ScreenProps } from 'react-native-screens';
|
|
9
3
|
|
|
10
4
|
// @ts-ignore file to be used only if `react-native-reanimated` available in the project
|
|
11
|
-
import Animated
|
|
12
|
-
import ReanimatedTransitionProgressContext from './ReanimatedTransitionProgressContext';
|
|
5
|
+
import Animated from 'react-native-reanimated';
|
|
13
6
|
|
|
14
7
|
const AnimatedScreen = Animated.createAnimatedComponent(
|
|
15
8
|
(Screen as unknown) as React.ComponentClass
|
|
16
9
|
);
|
|
17
10
|
|
|
18
|
-
class ReanimatedScreenWrapper extends React.Component<ScreenProps> {
|
|
19
|
-
private ref: React.ElementRef<typeof View> | null = null;
|
|
20
|
-
|
|
21
|
-
setNativeProps(props: ScreenProps): void {
|
|
22
|
-
this.ref?.setNativeProps(props);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
setRef = (ref: React.ElementRef<typeof View> | null): void => {
|
|
26
|
-
this.ref = ref;
|
|
27
|
-
this.props.onComponentRef?.(ref);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
render() {
|
|
31
|
-
return (
|
|
32
|
-
<ReanimatedScreen
|
|
33
|
-
{...this.props}
|
|
34
|
-
// @ts-ignore some problems with ref
|
|
35
|
-
ref={this.setRef}
|
|
36
|
-
/>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
11
|
const ReanimatedScreen = React.forwardRef<typeof AnimatedScreen, ScreenProps>(
|
|
42
12
|
(props, ref) => {
|
|
43
|
-
const { children, ...rest } = props;
|
|
44
|
-
|
|
45
|
-
const progress = useSharedValue(0);
|
|
46
|
-
const closing = useSharedValue(0);
|
|
47
|
-
const goingForward = useSharedValue(0);
|
|
48
|
-
|
|
49
13
|
return (
|
|
50
14
|
<AnimatedScreen
|
|
51
15
|
// @ts-ignore some problems with ref and onTransitionProgressReanimated being "fake" prop for parsing of `useEvent` return value
|
|
52
16
|
ref={ref}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
!props.isNativeStack
|
|
56
|
-
? undefined
|
|
57
|
-
: useEvent(
|
|
58
|
-
(event: TransitionProgressEventType) => {
|
|
59
|
-
'worklet';
|
|
60
|
-
progress.value = event.progress;
|
|
61
|
-
closing.value = event.closing;
|
|
62
|
-
goingForward.value = event.goingForward;
|
|
63
|
-
},
|
|
64
|
-
[
|
|
65
|
-
// This should not be necessary, but is not properly managed by `react-native-reanimated`
|
|
66
|
-
// @ts-ignore wrong type
|
|
67
|
-
Platform.OS === 'android'
|
|
68
|
-
? 'onTransitionProgress'
|
|
69
|
-
: 'topTransitionProgress',
|
|
70
|
-
]
|
|
71
|
-
)
|
|
72
|
-
}
|
|
73
|
-
{...rest}>
|
|
74
|
-
{!props.isNativeStack ? ( // see comment of this prop in types.tsx for information why it is needed
|
|
75
|
-
children
|
|
76
|
-
) : (
|
|
77
|
-
<ReanimatedTransitionProgressContext.Provider
|
|
78
|
-
value={{
|
|
79
|
-
progress: progress,
|
|
80
|
-
closing: closing,
|
|
81
|
-
goingForward: goingForward,
|
|
82
|
-
}}>
|
|
83
|
-
{children}
|
|
84
|
-
</ReanimatedTransitionProgressContext.Provider>
|
|
85
|
-
)}
|
|
86
|
-
</AnimatedScreen>
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
87
19
|
);
|
|
88
20
|
}
|
|
89
21
|
);
|
|
90
22
|
|
|
91
|
-
// used to silence error "Component definition is missing display name"
|
|
92
23
|
ReanimatedScreen.displayName = 'ReanimatedScreen';
|
|
93
24
|
|
|
94
|
-
export default
|
|
95
|
-
props: PropsWithChildren<unknown>
|
|
96
|
-
) {
|
|
97
|
-
return (
|
|
98
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
99
|
-
<ScreenContext.Provider value={ReanimatedScreenWrapper as any}>
|
|
100
|
-
{props.children}
|
|
101
|
-
</ScreenContext.Provider>
|
|
102
|
-
);
|
|
103
|
-
}
|
|
25
|
+
export default ReanimatedScreen;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import React, { PropsWithChildren } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { ScreenProps, ScreenContext } from 'react-native-screens';
|
|
4
|
+
import ReanimatedNativeStackScreen from './ReanimatedNativeStackScreen';
|
|
5
|
+
import AnimatedScreen from './ReanimatedScreen';
|
|
6
|
+
|
|
7
|
+
class ReanimatedScreenWrapper extends React.Component<ScreenProps> {
|
|
8
|
+
private ref: React.ElementRef<typeof View> | null = null;
|
|
9
|
+
|
|
10
|
+
setNativeProps(props: ScreenProps): void {
|
|
11
|
+
this.ref?.setNativeProps(props);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
setRef = (ref: React.ElementRef<typeof View> | null): void => {
|
|
15
|
+
this.ref = ref;
|
|
16
|
+
this.props.onComponentRef?.(ref);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
render() {
|
|
20
|
+
const ReanimatedScreen = this.props.isNativeStack
|
|
21
|
+
? ReanimatedNativeStackScreen
|
|
22
|
+
: AnimatedScreen;
|
|
23
|
+
return (
|
|
24
|
+
<ReanimatedScreen
|
|
25
|
+
{...this.props}
|
|
26
|
+
// @ts-ignore some problems with ref
|
|
27
|
+
ref={this.setRef}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default function ReanimatedScreenProvider(
|
|
34
|
+
props: PropsWithChildren<unknown>
|
|
35
|
+
) {
|
|
36
|
+
return (
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
+
<ScreenContext.Provider value={ReanimatedScreenWrapper as any}>
|
|
39
|
+
{props.children}
|
|
40
|
+
</ScreenContext.Provider>
|
|
41
|
+
);
|
|
42
|
+
}
|
package/src/reanimated/index.tsx
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { default as ReanimatedScreenProvider } from './
|
|
1
|
+
export { default as ReanimatedScreenProvider } from './ReanimatedScreenProvider';
|
|
2
2
|
export { default as useReanimatedTransitionProgress } from './useReanimatedTransitionProgress';
|