react-native-screens 3.8.0 → 3.10.2
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 +61 -3
- package/android/build.gradle +0 -2
- 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 -5
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +77 -12
- 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.m +35 -0
- package/ios/RNSScreenStack.m +24 -6
- package/ios/RNSScreenStackHeaderConfig.m +41 -0
- package/lib/commonjs/index.js +24 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.native.js +101 -11
- 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 +97 -13
- 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 +134 -38
- 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
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ScreenProps } from 'react-native-screens';
|
|
3
|
+
import Animated from 'react-native-reanimated';
|
|
4
|
+
declare const ReanimatedScreen: React.ForwardRefExoticComponent<Pick<ScreenProps, "children" | "active" | "activityState" | "customAnimationOnSwipe" | "enabled" | "isNativeStack" | "fullScreenSwipeEnabled" | "gestureEnabled" | "nativeBackButtonDismissalEnabled" | "onAppear" | "onComponentRef" | "onDisappear" | "onDismissed" | "onHeaderBackButtonClicked" | "onTransitionProgress" | "onWillAppear" | "onWillDisappear" | "replaceAnimation" | "screenOrientation" | "stackAnimation" | "stackPresentation" | "statusBarAnimation" | "statusBarColor" | "statusBarHidden" | "statusBarStyle" | "statusBarTranslucent" | "hitSlop" | "onLayout" | "pointerEvents" | "removeClippedSubviews" | "style" | "testID" | "nativeID" | "collapsable" | "needsOffscreenAlphaCompositing" | "renderToHardwareTextureAndroid" | "focusable" | "shouldRasterizeIOS" | "isTVSelectable" | "hasTVPreferredFocus" | "tvParallaxProperties" | "tvParallaxShiftDistanceX" | "tvParallaxShiftDistanceY" | "tvParallaxTiltAngle" | "tvParallaxMagnification" | "onStartShouldSetResponder" | "onMoveShouldSetResponder" | "onResponderEnd" | "onResponderGrant" | "onResponderReject" | "onResponderMove" | "onResponderRelease" | "onResponderStart" | "onResponderTerminationRequest" | "onResponderTerminate" | "onStartShouldSetResponderCapture" | "onMoveShouldSetResponderCapture" | "onTouchStart" | "onTouchMove" | "onTouchEnd" | "onTouchCancel" | "onTouchEndCapture" | "accessible" | "accessibilityActions" | "accessibilityLabel" | "accessibilityRole" | "accessibilityState" | "accessibilityHint" | "accessibilityValue" | "onAccessibilityAction" | "accessibilityComponentType" | "accessibilityLiveRegion" | "importantForAccessibility" | "accessibilityElementsHidden" | "accessibilityTraits" | "accessibilityViewIsModal" | "onAccessibilityEscape" | "onAccessibilityTap" | "onMagicTap" | "accessibilityIgnoresInvertColors"> & React.RefAttributes<React.ComponentClass<Animated.AnimateProps<{}>, any>>>;
|
|
5
|
+
export default ReanimatedScreen;
|
|
@@ -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';
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
import { Animated, NativeSyntheticEvent, ViewProps, View, TargetedEvent, TextInputFocusEventData } from 'react-native';
|
|
3
2
|
export declare type StackPresentationTypes = 'push' | 'modal' | 'transparentModal' | 'containedModal' | 'containedTransparentModal' | 'fullScreenModal' | 'formSheet';
|
|
4
3
|
export declare type StackAnimationTypes = 'default' | 'fade' | 'fade_from_bottom' | 'flip' | 'none' | 'simple_push' | 'slide_from_bottom' | 'slide_from_right' | 'slide_from_left';
|
|
@@ -270,6 +269,14 @@ export interface ScreenStackHeaderConfigProps extends ViewProps {
|
|
|
270
269
|
* Boolean that allows for disabling drop shadow under navigation header when the edge of any scrollable content reaches the matching edge of the navigation bar.
|
|
271
270
|
*/
|
|
272
271
|
largeTitleHideShadow?: boolean;
|
|
272
|
+
/**
|
|
273
|
+
* Callback which is executed when screen header is attached
|
|
274
|
+
*/
|
|
275
|
+
onAttached?: () => void;
|
|
276
|
+
/**
|
|
277
|
+
* Callback which is executed when screen header is detached
|
|
278
|
+
*/
|
|
279
|
+
onDetached?: () => void;
|
|
273
280
|
/**
|
|
274
281
|
* String that can be displayed in the header as a fallback for `headerTitle`.
|
|
275
282
|
*/
|
|
@@ -309,22 +316,46 @@ export interface SearchBarProps {
|
|
|
309
316
|
* The auto-capitalization behavior
|
|
310
317
|
*/
|
|
311
318
|
autoCapitalize?: 'none' | 'words' | 'sentences' | 'characters';
|
|
319
|
+
/**
|
|
320
|
+
* Automatically focuses search bar on mount
|
|
321
|
+
*
|
|
322
|
+
* @platform android
|
|
323
|
+
*/
|
|
324
|
+
autoFocus?: boolean;
|
|
312
325
|
/**
|
|
313
326
|
* The search field background color
|
|
314
327
|
*/
|
|
315
328
|
barTintColor?: string;
|
|
316
329
|
/**
|
|
317
330
|
* The text to be used instead of default `Cancel` button text
|
|
331
|
+
*
|
|
332
|
+
* @platform ios
|
|
318
333
|
*/
|
|
319
334
|
cancelButtonText?: string;
|
|
335
|
+
/**
|
|
336
|
+
* Specifies whether the back button should close search bar's text input or not.
|
|
337
|
+
*
|
|
338
|
+
* @platform android
|
|
339
|
+
*/
|
|
340
|
+
disableBackButtonOverride?: boolean;
|
|
320
341
|
/**
|
|
321
342
|
* Indicates whether to hide the navigation bar
|
|
343
|
+
*
|
|
344
|
+
* @platform ios
|
|
322
345
|
*/
|
|
323
346
|
hideNavigationBar?: boolean;
|
|
324
347
|
/**
|
|
325
348
|
* Indicates whether to hide the search bar when scrolling
|
|
349
|
+
*
|
|
350
|
+
* @platform ios
|
|
326
351
|
*/
|
|
327
352
|
hideWhenScrolling?: boolean;
|
|
353
|
+
/**
|
|
354
|
+
* Sets type of the input. Defaults to `text`.
|
|
355
|
+
*
|
|
356
|
+
* @platform android
|
|
357
|
+
*/
|
|
358
|
+
inputType?: 'text' | 'phone' | 'number' | 'email';
|
|
328
359
|
/**
|
|
329
360
|
* Indicates whether to to obscure the underlying content
|
|
330
361
|
*/
|
|
@@ -335,6 +366,8 @@ export interface SearchBarProps {
|
|
|
335
366
|
onBlur?: (e: NativeSyntheticEvent<TargetedEvent>) => void;
|
|
336
367
|
/**
|
|
337
368
|
* A callback that gets called when the cancel button is pressed
|
|
369
|
+
*
|
|
370
|
+
* @platform ios
|
|
338
371
|
*/
|
|
339
372
|
onCancelButtonPress?: (e: NativeSyntheticEvent<TargetedEvent>) => void;
|
|
340
373
|
/**
|
|
@@ -344,7 +377,19 @@ export interface SearchBarProps {
|
|
|
344
377
|
/**
|
|
345
378
|
* A callback that gets called when search bar has received focus
|
|
346
379
|
*/
|
|
380
|
+
onClose?: () => void;
|
|
381
|
+
/**
|
|
382
|
+
* A callback that gets called when search bar is opened
|
|
383
|
+
*
|
|
384
|
+
* @platform android
|
|
385
|
+
*/
|
|
347
386
|
onFocus?: (e: NativeSyntheticEvent<TargetedEvent>) => void;
|
|
387
|
+
/**
|
|
388
|
+
* A callback that gets called when search bar is closed
|
|
389
|
+
*
|
|
390
|
+
* @platform android
|
|
391
|
+
*/
|
|
392
|
+
onOpen?: () => void;
|
|
348
393
|
/**
|
|
349
394
|
* A callback that gets called when the search button is pressed. It receives the current text value of the search bar.
|
|
350
395
|
*/
|
package/native-stack/README.md
CHANGED
|
@@ -362,7 +362,7 @@ Search bar is only supported on iOS.
|
|
|
362
362
|
Example:
|
|
363
363
|
|
|
364
364
|
```js
|
|
365
|
-
React.
|
|
365
|
+
React.useLayoutEffect(() => {
|
|
366
366
|
navigation.setOptions({
|
|
367
367
|
searchBar: {
|
|
368
368
|
// search bar options
|
|
@@ -371,6 +371,8 @@ React.useEffect(() => {
|
|
|
371
371
|
}, [navigation]);
|
|
372
372
|
```
|
|
373
373
|
|
|
374
|
+
We advise using `useLayoutEffect` hook instead of `useEffect` when managing `searchBar` props to avoid unexpected layout issues.
|
|
375
|
+
|
|
374
376
|
Supported properties are described below.
|
|
375
377
|
|
|
376
378
|
#### `autoCapitalize`
|
|
@@ -383,7 +385,11 @@ Possible values:
|
|
|
383
385
|
- `sentences`
|
|
384
386
|
- `characters`
|
|
385
387
|
|
|
386
|
-
Defaults to `sentences
|
|
388
|
+
Defaults to `sentences` on iOS and `'none'` on Android.
|
|
389
|
+
|
|
390
|
+
#### `autoFocus` (Android only)
|
|
391
|
+
|
|
392
|
+
When set to `true` focuses search bar automatically when screen is appearing. Default value is `false`.
|
|
387
393
|
|
|
388
394
|
#### `barTintColor`
|
|
389
395
|
|
|
@@ -391,23 +397,37 @@ The search field background color.
|
|
|
391
397
|
|
|
392
398
|
By default bar tint color is translucent.
|
|
393
399
|
|
|
394
|
-
#### `cancelButtonText`
|
|
400
|
+
#### `cancelButtonText` (iOS only)
|
|
395
401
|
|
|
396
402
|
The text to be used instead of default `Cancel` button text.
|
|
397
403
|
|
|
398
|
-
#### `
|
|
404
|
+
#### `disableBackButtonOverride` (Android only)
|
|
405
|
+
|
|
406
|
+
Default behavior is to prevent screen from going back when search bar is open (`disableBackButtonOverride: false`). If you don't want this to happen set `disableBackButtonOverride` to `true`
|
|
407
|
+
|
|
408
|
+
#### `hideNavigationBar` (iOS only)
|
|
399
409
|
|
|
400
410
|
Boolean indicating whether to hide the navigation bar during searching.
|
|
401
411
|
|
|
402
412
|
Defaults to `true`.
|
|
403
413
|
|
|
404
|
-
#### `hideWhenScrolling`
|
|
414
|
+
#### `hideWhenScrolling` (iOS only)
|
|
405
415
|
|
|
406
416
|
Boolean indicating whether to hide the search bar when scrolling.
|
|
407
417
|
|
|
408
418
|
Defaults to `true`.
|
|
409
419
|
|
|
410
|
-
####
|
|
420
|
+
#### `inputType` (Android only)
|
|
421
|
+
|
|
422
|
+
This prop is used to change type of the input and keyboard. Default value is `'text'`.
|
|
423
|
+
|
|
424
|
+
All values:
|
|
425
|
+
- `'text'` - normal text input
|
|
426
|
+
- `'number'` - number input
|
|
427
|
+
- `'email'` - email input
|
|
428
|
+
- `'phone'` - phone input
|
|
429
|
+
|
|
430
|
+
#### `obscureBackground` (iOS only)
|
|
411
431
|
|
|
412
432
|
Boolean indicating whether to obscure the underlying content with semi-transparent overlay.
|
|
413
433
|
|
|
@@ -430,7 +450,7 @@ Example:
|
|
|
430
450
|
```js
|
|
431
451
|
const [search, setSearch] = React.useState('');
|
|
432
452
|
|
|
433
|
-
React.
|
|
453
|
+
React.useLayoutEffect(() => {
|
|
434
454
|
navigation.setOptions({
|
|
435
455
|
searchBar: {
|
|
436
456
|
onChangeText: (event) => setSearch(event.nativeEvent.text),
|
|
@@ -438,11 +458,19 @@ React.useEffect(() => {
|
|
|
438
458
|
});
|
|
439
459
|
}, [navigation]);
|
|
440
460
|
```
|
|
461
|
+
#### `onClose` (Android only)
|
|
462
|
+
|
|
463
|
+
A callback that gets called when search bar is closing
|
|
464
|
+
|
|
441
465
|
|
|
442
466
|
#### `onFocus`
|
|
443
467
|
|
|
444
468
|
A callback that gets called when search bar has received focus.
|
|
445
469
|
|
|
470
|
+
#### `onOpen` (Android only)
|
|
471
|
+
|
|
472
|
+
A callback that gets called when search bar is expanding
|
|
473
|
+
|
|
446
474
|
#### `onSearchButtonPress`
|
|
447
475
|
|
|
448
476
|
A callback that gets called when the search button is pressed. It receives the current text value of the search bar.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-screens",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.10.2",
|
|
4
4
|
"description": "Native navigation primitives for your React Native app.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"check-types": "tsc --noEmit",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"windows/",
|
|
36
36
|
"RNScreens.podspec",
|
|
37
37
|
"README.md",
|
|
38
|
-
"!**/__tests__"
|
|
38
|
+
"!**/__tests__",
|
|
39
|
+
"!android/src/main/java/com/swmansion/rnscreens/LifecycleHelper.kt"
|
|
39
40
|
],
|
|
40
41
|
"repository": {
|
|
41
42
|
"type": "git",
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
},
|
|
53
54
|
"homepage": "https://github.com/software-mansion/react-native-screens#readme",
|
|
54
55
|
"dependencies": {
|
|
56
|
+
"react-freeze": "^1.0.0",
|
|
55
57
|
"warn-once": "^0.1.0"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
@@ -80,6 +82,7 @@
|
|
|
80
82
|
"eslint-plugin-node": "^11.1.0",
|
|
81
83
|
"eslint-plugin-promise": "^4.2.1",
|
|
82
84
|
"eslint-plugin-react": "^7.20.5",
|
|
85
|
+
"eslint-plugin-react-hooks": "^4.2.0",
|
|
83
86
|
"eslint-plugin-react-native": "^3.2.1",
|
|
84
87
|
"eslint-plugin-standard": "^4.0.1",
|
|
85
88
|
"husky": "^7.0.1",
|
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
|
|
|
@@ -123,6 +144,65 @@ const ScreensNativeModules = {
|
|
|
123
144
|
},
|
|
124
145
|
};
|
|
125
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
|
+
|
|
126
206
|
class Screen extends React.Component<ScreenProps> {
|
|
127
207
|
private ref: React.ElementRef<typeof View> | null = null;
|
|
128
208
|
private closing = new Animated.Value(0);
|
|
@@ -168,40 +248,52 @@ class Screen extends React.Component<ScreenProps> {
|
|
|
168
248
|
const processedColor = processColor(statusBarColor);
|
|
169
249
|
|
|
170
250
|
return (
|
|
171
|
-
<
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
+
},
|
|
186
278
|
},
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
</
|
|
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>
|
|
205
297
|
);
|
|
206
298
|
} else {
|
|
207
299
|
// same reason as above
|
|
@@ -323,6 +415,7 @@ module.exports = {
|
|
|
323
415
|
Screen,
|
|
324
416
|
ScreenContainer,
|
|
325
417
|
ScreenContext,
|
|
418
|
+
ScreenStack,
|
|
326
419
|
|
|
327
420
|
get NativeScreen() {
|
|
328
421
|
return ScreensNativeModules.NativeScreen;
|
|
@@ -336,9 +429,6 @@ module.exports = {
|
|
|
336
429
|
return ScreensNativeModules.NativeScreenNavigationContainer;
|
|
337
430
|
},
|
|
338
431
|
|
|
339
|
-
get ScreenStack() {
|
|
340
|
-
return ScreensNativeModules.NativeScreenStack;
|
|
341
|
-
},
|
|
342
432
|
get ScreenStackHeaderConfig() {
|
|
343
433
|
return ScreensNativeModules.NativeScreenStackHeaderConfig;
|
|
344
434
|
},
|
|
@@ -346,8 +436,10 @@ module.exports = {
|
|
|
346
436
|
return ScreensNativeModules.NativeScreenStackHeaderSubview;
|
|
347
437
|
},
|
|
348
438
|
get SearchBar() {
|
|
349
|
-
if (
|
|
350
|
-
console.warn(
|
|
439
|
+
if (!isSearchBarAvailableForCurrentPlatform) {
|
|
440
|
+
console.warn(
|
|
441
|
+
'Importing SearchBar is only valid on iOS and Android devices.'
|
|
442
|
+
);
|
|
351
443
|
return View;
|
|
352
444
|
}
|
|
353
445
|
|
|
@@ -370,7 +462,11 @@ module.exports = {
|
|
|
370
462
|
ScreenStackHeaderSearchBarView,
|
|
371
463
|
|
|
372
464
|
enableScreens,
|
|
465
|
+
enableFreeze,
|
|
373
466
|
screensEnabled,
|
|
374
467
|
shouldUseActivityState,
|
|
375
468
|
useTransitionProgress,
|
|
469
|
+
|
|
470
|
+
isSearchBarAvailableForCurrentPlatform,
|
|
471
|
+
executeNativeBackPress,
|
|
376
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>
|