mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 09:30:30 +08:00
Compare commits
24 Commits
react-navi
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9b55493e76 | ||
|
|
ebe70f51fb | ||
|
|
256e5aec2a | ||
|
|
707bea3cba | ||
|
|
6bdc1e9e5f | ||
|
|
b3db099a00 | ||
|
|
1f5000e86b | ||
|
|
6390aacd07 | ||
|
|
20e2625f35 | ||
|
|
162e225fa4 | ||
|
|
ce9991ffff | ||
|
|
3bb21e256f | ||
|
|
f1a06e2f92 | ||
|
|
c0763fca16 | ||
|
|
62da341b67 | ||
|
|
09a10faa44 | ||
|
|
b9ecbd222d | ||
|
|
a57e47786c | ||
|
|
424923019a | ||
|
|
06a69f1bfd | ||
|
|
1fa2edd9f2 | ||
|
|
fcd7d83c4c | ||
|
|
4f7983134b | ||
|
|
46b797dd29 |
@@ -14,5 +14,8 @@
|
||||
"react-native-screens"
|
||||
]
|
||||
},
|
||||
"env": { "browser": true, "node": true }
|
||||
"env": { "browser": true, "node": true },
|
||||
"rules": {
|
||||
"import/named": "off"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import {
|
||||
Animated,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"slug": "react-navigation-4-example",
|
||||
"description": "Demo app to showcase various functionality of React Navigation",
|
||||
"privacy": "public",
|
||||
"sdkVersion": "36.0.0",
|
||||
"platforms": [
|
||||
"ios",
|
||||
"android",
|
||||
|
||||
@@ -8,29 +8,29 @@
|
||||
"ios": "expo start --ios"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"@expo/vector-icons": "^10.0.6",
|
||||
"@react-native-community/masked-view": "0.1.7",
|
||||
"expo": "^36.0.0",
|
||||
"expo-asset": "~8.0.0",
|
||||
"expo-barcode-scanner": "^8.0.0",
|
||||
"expo-blur": "~8.0.0",
|
||||
"expo-constants": "~8.0.0",
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@expo/vector-icons": "^10.0.0",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"expo": "^39.0.0",
|
||||
"expo-asset": "~8.2.0",
|
||||
"expo-barcode-scanner": "~9.0.0",
|
||||
"expo-blur": "~8.2.0",
|
||||
"expo-constants": "~9.2.0",
|
||||
"hoist-non-react-statics": "^3.3.1",
|
||||
"react": "16.9.0",
|
||||
"react-native": "https://github.com/expo/react-native/archive/sdk-36.0.1.tar.gz",
|
||||
"react-native-gesture-handler": "~1.6.0",
|
||||
"react-native-iphone-x-helper": "^1.2.1",
|
||||
"react-native-maps": "0.27.0",
|
||||
"react-native-paper": "^3.4.0",
|
||||
"react-native-reanimated": "~1.7.0",
|
||||
"react-native-safe-area-context": "0.7.3",
|
||||
"react-native-screens": "2.3.0",
|
||||
"react-native-webview": "8.1.2",
|
||||
"react-navigation-header-buttons": "^3.0.5"
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-iphone-x-helper": "^1.3.0",
|
||||
"react-native-maps": "0.27.1",
|
||||
"react-native-paper": "^4.0.1",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-webview": "10.7.0",
|
||||
"react-navigation-header-buttons": "^6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"expo-cli": "^3.13.8"
|
||||
"expo-cli": "^3.28.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
TouchableOpacity,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StyleSheet } from 'react-native';
|
||||
import { BorderlessButton } from 'react-native-gesture-handler';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { View, Text, StyleSheet, Dimensions } from 'react-native';
|
||||
import { NavigationStackScreenComponent } from 'react-navigation-stack';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StyleProp, TextStyle } from 'react-native';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, View, Text } from 'react-native';
|
||||
import { createBottomTabNavigator } from 'react-navigation-tabs';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dimensions,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
AccessibilityStates,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
Text,
|
||||
@@ -88,11 +87,9 @@ export default class Button extends React.Component<ButtonProps> {
|
||||
buttonStyles.push({ backgroundColor: color });
|
||||
}
|
||||
}
|
||||
const accessibilityStates: AccessibilityStates[] = [];
|
||||
if (disabled) {
|
||||
buttonStyles.push(styles.buttonDisabled);
|
||||
textStyles.push(styles.textDisabled);
|
||||
accessibilityStates.push('disabled');
|
||||
}
|
||||
const formattedTitle =
|
||||
Platform.OS === 'android' ? title.toUpperCase() : title;
|
||||
@@ -100,7 +97,7 @@ export default class Button extends React.Component<ButtonProps> {
|
||||
<TouchableOpacity
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
accessibilityRole="button"
|
||||
accessibilityStates={accessibilityStates}
|
||||
accessibilityState={{ disabled }}
|
||||
testID={testID}
|
||||
disabled={disabled}
|
||||
onPress={onPress}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import BaseButton, { ButtonProps } from './Button';
|
||||
|
||||
|
||||
@@ -23,13 +23,13 @@ class NavigationAwareScrollViewBase extends React.Component<{
|
||||
componentDidMount() {
|
||||
this.subscription = this.props.navigation.addListener('refocus', () => {
|
||||
if (this.props.navigation.isFocused()) {
|
||||
this.root.current && this.root.current.scrollTo({ x: 0, y: 0 });
|
||||
this.root.current?.scrollTo({ x: 0, y: 0 });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscription && this.subscription.remove();
|
||||
this.subscription?.remove();
|
||||
}
|
||||
|
||||
setNativeProps(props: ScrollViewProperties) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
ThemeColors,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Text, View } from 'react-native';
|
||||
import { Themed } from 'react-navigation';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Text } from 'react-native';
|
||||
import {
|
||||
Themed,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { ScrollView, StatusBar } from 'react-native';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView } from 'react-native';
|
||||
import { Themed, SafeAreaView } from 'react-navigation';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { MaterialIcons } from '@expo/vector-icons';
|
||||
import { createDrawerNavigator } from 'react-navigation-drawer';
|
||||
import SimpleTabs from './SimpleTabs';
|
||||
|
||||
40
package.json
40
package.json
@@ -25,28 +25,28 @@
|
||||
"example": "yarn --cwd example"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^25.1.4",
|
||||
"babel-jest": "^25.2.3",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"eslint": "^7.0.0",
|
||||
"eslint-config-satya164": "^3.1.7",
|
||||
"husky": "^4.2.1",
|
||||
"jest": "^25.1.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^2.0.5",
|
||||
"typescript": "^3.9.5"
|
||||
"@babel/plugin-proposal-class-properties": "^7.12.1",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.1",
|
||||
"@babel/preset-react": "^7.12.1",
|
||||
"@babel/preset-typescript": "^7.12.1",
|
||||
"@babel/runtime": "^7.12.1",
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@types/jest": "^26.0.15",
|
||||
"babel-jest": "^26.6.1",
|
||||
"commitlint": "^11.0.0",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^7.12.0",
|
||||
"eslint-config-satya164": "^3.1.8",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.6.1",
|
||||
"lerna": "^3.22.1",
|
||||
"prettier": "^2.1.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5"
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [0.6.3](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.2...react-navigation-animated-switch@0.6.3) (2020-10-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.2](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.1...react-navigation-animated-switch@0.6.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.6.1](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.0...react-navigation-animated-switch@0.6.1) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [0.6.0](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.12...react-navigation-animated-switch@0.6.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-animated-switch",
|
||||
"version": "0.6.0",
|
||||
"version": "0.6.3",
|
||||
"description": "Animated switch for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -21,15 +21,15 @@
|
||||
"access": "public"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "0.10.0",
|
||||
"@types/react": "16.9.23",
|
||||
"@types/react-native": "0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-reanimated": "~1.7.0",
|
||||
"react-navigation": "^4.4.0",
|
||||
"typescript": "^3.9.5"
|
||||
"@react-native-community/bob": "0.16.2",
|
||||
"@types/react": "16.9.53",
|
||||
"@types/react-native": "0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-navigation": "^4.4.3",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.7.9](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.8...@react-navigation/core@3.7.9) (2020-10-26)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.8](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.7...@react-navigation/core@3.7.8) (2020-10-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* NavigationEvents subscribe events on new nav state ([#8920](https://github.com/react-navigation/react-navigation-core/issues/8920)) ([6390aac](https://github.com/react-navigation/react-navigation-core/commit/6390aacd07fd647d925dfec842a766c8aad5272f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.7](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.6...@react-navigation/core@3.7.7) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.6](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.5...@react-navigation/core@3.7.6) (2020-04-30)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"version": "3.7.6",
|
||||
"version": "3.7.9",
|
||||
"description": "Core utilities for the react-navigation framework",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -37,16 +37,16 @@
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"path-to-regexp": "^1.8.0",
|
||||
"query-string": "^6.11.1",
|
||||
"query-string": "^6.13.6",
|
||||
"react-is": "^16.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-test-renderer": "^16.9.2"
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-testing-library": "^6.0.0",
|
||||
"react-test-renderer": "~16.13.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
||||
@@ -93,6 +93,7 @@ const StateUtils = {
|
||||
*/
|
||||
jumpTo(state, key) {
|
||||
const index = StateUtils.indexOf(state, key);
|
||||
invariant(index !== -1, 'attempt to jump to unknown key "%s"', key);
|
||||
return StateUtils.jumpToIndex(state, index);
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/sort-comp */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { NavigationActions, getNavigation, NavigationProvider } from '../index';
|
||||
|
||||
export default function createNavigationContainer(Component) {
|
||||
|
||||
@@ -3,32 +3,51 @@ import NavigationStateUtils from '../StateUtils';
|
||||
const routeName = 'Anything';
|
||||
|
||||
describe('StateUtils', () => {
|
||||
// Getters
|
||||
it('gets route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
||||
key: 'a',
|
||||
routeName,
|
||||
describe('get', () => {
|
||||
it('gets route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.get(state, 'a')).toEqual({
|
||||
key: 'a',
|
||||
routeName,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null when getting an unknown route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
|
||||
});
|
||||
expect(NavigationStateUtils.get(state, 'b')).toBe(null);
|
||||
});
|
||||
|
||||
it('gets route index', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||
expect(NavigationStateUtils.indexOf(state, 'c')).toBe(-1);
|
||||
describe('indexOf', () => {
|
||||
it('gets route index', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.indexOf(state, 'a')).toBe(0);
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(1);
|
||||
});
|
||||
|
||||
it('returns -1 when getting an unknown route index', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.indexOf(state, 'b')).toBe(-1);
|
||||
});
|
||||
});
|
||||
|
||||
it('has a route', () => {
|
||||
@@ -44,303 +63,390 @@ describe('StateUtils', () => {
|
||||
expect(NavigationStateUtils.has(state, 'c')).toBe(false);
|
||||
});
|
||||
|
||||
// Push
|
||||
it('pushes a route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
};
|
||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
||||
newState
|
||||
);
|
||||
});
|
||||
|
||||
it('does not push duplicated route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() =>
|
||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
||||
).toThrow('should not push route with duplicated key a');
|
||||
});
|
||||
|
||||
// Pop
|
||||
it('pops route', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not pop route if not applicable', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
|
||||
// Jump
|
||||
it('jumps to new index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow(
|
||||
'invalid index 2 to jump to'
|
||||
);
|
||||
});
|
||||
|
||||
it('jumps to new key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow(
|
||||
'invalid index -1 to jump to'
|
||||
);
|
||||
});
|
||||
|
||||
it('move backwards', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.back(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
it('move forwards', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||
expect(NavigationStateUtils.forward(newState)).toBe(newState);
|
||||
});
|
||||
|
||||
// Replace
|
||||
it('Replaces by key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'c', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Replaces by index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'c', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
||||
).toEqual({ ...state, index: 1 });
|
||||
});
|
||||
|
||||
// Reset
|
||||
it('Resets routes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(state, [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
])
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(state, []);
|
||||
}).toThrow('invalid routes to replace');
|
||||
});
|
||||
|
||||
it('Resets routes with index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
describe('push', () => {
|
||||
it('pushes a route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
0
|
||||
)
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
100
|
||||
};
|
||||
expect(NavigationStateUtils.push(state, { key: 'b', routeName })).toEqual(
|
||||
newState
|
||||
);
|
||||
}).toThrow('invalid index 100 to reset');
|
||||
});
|
||||
|
||||
it('does not push duplicated route', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() =>
|
||||
NavigationStateUtils.push(state, { key: 'a', routeName })
|
||||
).toThrow('should not push route with duplicated key a');
|
||||
});
|
||||
});
|
||||
|
||||
describe('pop', () => {
|
||||
it('pops route', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not pop route if not applicable with single route config', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [{ key: 'a', routeName }],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
|
||||
it('does not pop route if not applicable with multiple route config', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.pop(state)).toBe(state);
|
||||
});
|
||||
});
|
||||
|
||||
describe('jumpToIndex', () => {
|
||||
it('jumps to new index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 0)).toBe(state);
|
||||
expect(NavigationStateUtils.jumpToIndex(state, 1)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpToIndex(state, 2)).toThrow(
|
||||
'invalid index 2 to jump to'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('jumpTo', () => {
|
||||
it('jumps to the current key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'a')).toBe(state);
|
||||
});
|
||||
|
||||
it('jumps to new key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.jumpTo(state, 'b')).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws if jumps to invalid key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => NavigationStateUtils.jumpTo(state, 'c')).toThrow(
|
||||
'attempt to jump to unknown key "c"'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('back', () => {
|
||||
it('move backwards', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.back(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not move backwards when the active route is the first', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.back(state)).toBe(state);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forward', () => {
|
||||
it('move forwards', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(newState);
|
||||
});
|
||||
|
||||
it('does not move forward when active route is already the top-most', () => {
|
||||
const state = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(NavigationStateUtils.forward(state)).toEqual(state);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replace', () => {
|
||||
it('Replaces by key', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'c', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAt(state, 'b', { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Replaces by index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'c', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, { key: 'c', routeName })
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('Returns the state with updated index if route is unchanged but index changes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.replaceAtIndex(state, 1, state.routes[1])
|
||||
).toEqual({ ...state, index: 1 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('Resets routes', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 1,
|
||||
routes: [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(state, [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
])
|
||||
).toEqual(newState);
|
||||
});
|
||||
|
||||
it('throws when attempting to set empty state', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(state, []);
|
||||
}).toThrow('invalid routes to replace');
|
||||
});
|
||||
|
||||
it('Resets routes with index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const newState = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
0
|
||||
)
|
||||
).toEqual(newState);
|
||||
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
100
|
||||
);
|
||||
}).toThrow('invalid index 100 to reset');
|
||||
});
|
||||
|
||||
it('throws when attempting to set an out of range route index', () => {
|
||||
const state = {
|
||||
index: 0,
|
||||
routes: [
|
||||
{ key: 'a', routeName },
|
||||
{ key: 'b', routeName },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(() => {
|
||||
NavigationStateUtils.reset(
|
||||
state,
|
||||
[
|
||||
{ key: 'x', routeName },
|
||||
{ key: 'y', routeName },
|
||||
],
|
||||
100
|
||||
);
|
||||
}).toThrow('invalid index 100 to reset');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import getNavigation from '../getNavigation';
|
||||
import * as NavigationActions from '../NavigationActions';
|
||||
|
||||
it('getNavigation provides default action helpers', () => {
|
||||
const router = {
|
||||
@@ -22,12 +23,11 @@ it('getNavigation provides default action helpers', () => {
|
||||
topNav.navigate('GreatRoute');
|
||||
|
||||
expect(dispatch.mock.calls.length).toBe(1);
|
||||
expect(dispatch.mock.calls[0][0].type).toBe('Navigation/NAVIGATE');
|
||||
expect(dispatch.mock.calls[0][0].type).toBe(NavigationActions.NAVIGATE);
|
||||
expect(dispatch.mock.calls[0][0].routeName).toBe('GreatRoute');
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('getNavigation provides router action helpers', () => {
|
||||
it('getNavigation provides router action helpers', () => {
|
||||
const router = {
|
||||
getActionCreators: () => ({
|
||||
foo: (bar) => ({ type: 'FooBarAction', bar }),
|
||||
@@ -39,7 +39,8 @@ it.skip('getNavigation provides router action helpers', () => {
|
||||
|
||||
const dispatch = jest.fn();
|
||||
|
||||
const topNav = getNavigation(
|
||||
let topNav;
|
||||
topNav = getNavigation(
|
||||
router,
|
||||
{},
|
||||
dispatch,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function getChildRouter(router: any, routeName: string) {
|
||||
if (router.childRouters && router.childRouters[routeName]) {
|
||||
if (router.childRouters?.[routeName]) {
|
||||
return router.childRouters[routeName];
|
||||
}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
);
|
||||
}
|
||||
|
||||
const descriptors = routes.reduce((acc, route) => {
|
||||
const descriptors = routes.reduce((descriptors, route) => {
|
||||
if (
|
||||
prevDescriptors &&
|
||||
prevDescriptors[route.key] &&
|
||||
@@ -44,8 +44,8 @@ function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
screenProps === currentState.screenProps &&
|
||||
currentState.themeContext === currentState.theme
|
||||
) {
|
||||
acc[route.key] = prevDescriptors[route.key];
|
||||
return acc;
|
||||
descriptors[route.key] = prevDescriptors[route.key];
|
||||
return descriptors;
|
||||
}
|
||||
const getComponent = router.getComponentForRouteName.bind(
|
||||
null,
|
||||
@@ -57,14 +57,14 @@ function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
screenProps,
|
||||
currentState.themeContext
|
||||
);
|
||||
acc[route.key] = {
|
||||
descriptors[route.key] = {
|
||||
key: route.key,
|
||||
getComponent,
|
||||
options,
|
||||
state: route,
|
||||
navigation: childNavigation,
|
||||
};
|
||||
return acc;
|
||||
return descriptors;
|
||||
}, {});
|
||||
|
||||
return { descriptors, screenProps, theme: state.themeContext };
|
||||
|
||||
@@ -19,7 +19,7 @@ export default (routeConfigs, config = {}) => {
|
||||
const getCustomActionCreators =
|
||||
config.getCustomActionCreators || defaultActionCreators;
|
||||
|
||||
const initialRouteParams = config.initialRouteParams;
|
||||
const { initialRouteParams } = config;
|
||||
const initialRouteName = config.initialRouteName || order[0];
|
||||
const backBehavior = config.backBehavior || 'none';
|
||||
const resetOnBlur = config.hasOwnProperty('resetOnBlur')
|
||||
@@ -59,7 +59,7 @@ export default (routeConfigs, config = {}) => {
|
||||
|
||||
function resetChildRoute(routeName) {
|
||||
let initialParams =
|
||||
routeName === initialRouteName ? initialRouteParams : undefined;
|
||||
routeName === initialRouteName ? initialRouteParams : null;
|
||||
// note(brentvatne): merging initialRouteParams *on top* of default params
|
||||
// on the route seems incorrect but it's consistent with existing behavior
|
||||
// in stackrouter
|
||||
@@ -71,13 +71,13 @@ export default (routeConfigs, config = {}) => {
|
||||
...childRouter.getStateForAction(childAction),
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
...(params ? { params } : {}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
key: routeName,
|
||||
routeName,
|
||||
params,
|
||||
...(params ? { params } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
12
packages/core/src/routers/__tests__/KeyGenerator.test.js
Normal file
12
packages/core/src/routers/__tests__/KeyGenerator.test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { _TESTING_ONLY_normalize_keys, generateKey } from '../KeyGenerator';
|
||||
|
||||
it('should generate a new string key when called', () => {
|
||||
_TESTING_ONLY_normalize_keys();
|
||||
|
||||
expect(generateKey()).toBe('id-0');
|
||||
expect(generateKey()).toBe('id-1');
|
||||
});
|
||||
|
||||
it('should generate unique string keys without being normalized', () => {
|
||||
expect(generateKey()).not.toBe(generateKey());
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint no-shadow:0, react/no-multi-comp:0, react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import SwitchRouter from '../SwitchRouter';
|
||||
import StackRouter from '../StackRouter';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint react/no-multi-comp:0, react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import StackRouter from '../StackRouter';
|
||||
import TabRouter from '../TabRouter';
|
||||
@@ -114,6 +114,51 @@ Object.keys(ROUTERS).forEach((routerName) => {
|
||||
);
|
||||
expect(state0.routes[state0.index].params.foo).toEqual(42);
|
||||
});
|
||||
|
||||
it('merges existing params when set params on existing state', () => {
|
||||
const Screen = () => <div />;
|
||||
|
||||
const router = Router({
|
||||
Foo: {
|
||||
screen: Screen,
|
||||
params: { a: 1 },
|
||||
},
|
||||
});
|
||||
const key = 'Foo';
|
||||
const state = router.getStateForAction({
|
||||
type: NavigationActions.INIT,
|
||||
key,
|
||||
});
|
||||
|
||||
expect(state).toMatchObject({
|
||||
index: 0,
|
||||
routes: [{ key, params: { a: 1 } }],
|
||||
});
|
||||
|
||||
const newState = router.getStateForAction(
|
||||
NavigationActions.setParams({ key, params: { b: 2 } }),
|
||||
state
|
||||
);
|
||||
|
||||
expect(newState.routes[newState.index].params).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
it('merges params when setting params during init', () => {
|
||||
const Screen = () => <div />;
|
||||
|
||||
const router = Router({
|
||||
Foo: {
|
||||
screen: Screen,
|
||||
params: { a: 1 },
|
||||
},
|
||||
});
|
||||
|
||||
const newState = router.getStateForAction(
|
||||
NavigationActions.setParams({ key: 'Foo', params: { b: 2 } })
|
||||
);
|
||||
|
||||
expect(newState.routes[newState.index].params).toEqual({ a: 1, b: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint no-shadow:0, react/no-multi-comp:0, react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import StackRouter from '../StackRouter';
|
||||
import * as StackActions from '../StackActions';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import SwitchRouter from '../SwitchRouter';
|
||||
import StackRouter from '../StackRouter';
|
||||
import * as NavigationActions from '../../NavigationActions';
|
||||
@@ -158,7 +158,7 @@ describe('SwitchRouter', () => {
|
||||
expect(getSubState(1).routeName).toEqual('A');
|
||||
|
||||
// The back action should not switch to B. It should stay on A
|
||||
back({ key: null });
|
||||
back(null);
|
||||
expect(getSubState(1).routeName).toEqual('A');
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint react/display-name:0 */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import TabRouter from '../TabRouter';
|
||||
|
||||
import * as NavigationActions from '../../NavigationActions';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import validateRouteConfigMap from '../validateRouteConfigMap';
|
||||
import StackRouter from '../StackRouter';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import withNavigation from './withNavigation';
|
||||
|
||||
const EventNameToPropName = {
|
||||
@@ -12,11 +12,28 @@ const EventNames = Object.keys(EventNameToPropName);
|
||||
|
||||
class NavigationEvents extends React.Component {
|
||||
componentDidMount() {
|
||||
this.subscriptions = {};
|
||||
|
||||
// We register all navigation listeners on mount to ensure listener stability across re-render
|
||||
// A former implementation was replacing (removing/adding) listeners on all update (if prop provided)
|
||||
// but there were issues (see https://github.com/react-navigation/react-navigation/issues/5058)
|
||||
this.subscribeAll();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.navigation !== prevProps.navigation) {
|
||||
this.removeAll();
|
||||
this.subscribeAll();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.removeAll();
|
||||
}
|
||||
|
||||
getPropListener = (eventName) => this.props[EventNameToPropName[eventName]];
|
||||
|
||||
subscribeAll() {
|
||||
this.subscriptions = {};
|
||||
|
||||
EventNames.forEach((eventName) => {
|
||||
this.subscriptions[eventName] = this.props.navigation.addListener(
|
||||
eventName,
|
||||
@@ -28,14 +45,12 @@ class NavigationEvents extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
removeAll() {
|
||||
EventNames.forEach((eventName) => {
|
||||
this.subscriptions[eventName].remove();
|
||||
});
|
||||
}
|
||||
|
||||
getPropListener = (eventName) => this.props[EventNameToPropName[eventName]];
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
export default class SceneView extends React.PureComponent {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import SceneView from '../SceneView';
|
||||
|
||||
export default class SwitchView extends React.Component {
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import NavigationEvents from '../NavigationEvents';
|
||||
import NavigationContext from '../NavigationContext';
|
||||
|
||||
const createPropListener = () => jest.fn();
|
||||
|
||||
const EVENT_TO_PROP_NAME = {
|
||||
willFocus: 'onWillFocus',
|
||||
didFocus: 'onDidFocus',
|
||||
willBlur: 'onWillBlur',
|
||||
didBlur: 'onDidBlur',
|
||||
};
|
||||
|
||||
// An easy way to create the 4 listeners prop
|
||||
const createEventListenersProp = () => ({
|
||||
onWillFocus: createPropListener(),
|
||||
@@ -122,6 +129,39 @@ describe('NavigationEvents', () => {
|
||||
checkPropListenerIsCalled('didBlur', 'onDidBlur');
|
||||
});
|
||||
|
||||
it('wires props listeners to latest navigation updates', () => {
|
||||
const {
|
||||
navigation,
|
||||
NavigationListenersAPI,
|
||||
} = createTestNavigationAndHelpers();
|
||||
const {
|
||||
navigation: nextNavigation,
|
||||
NavigationListenersAPI: nextNavigationListenersAPI,
|
||||
} = createTestNavigationAndHelpers();
|
||||
|
||||
const eventListenerProps = createEventListenersProp();
|
||||
const component = renderer.create(
|
||||
<NavigationEvents navigation={navigation} {...eventListenerProps} />
|
||||
);
|
||||
|
||||
Object.entries(EVENT_TO_PROP_NAME).forEach(([eventName, propName]) => {
|
||||
expect(eventListenerProps[propName]).toHaveBeenCalledTimes(0);
|
||||
NavigationListenersAPI.call(eventName);
|
||||
expect(eventListenerProps[propName]).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
component.update(
|
||||
<NavigationEvents navigation={nextNavigation} {...eventListenerProps} />
|
||||
);
|
||||
|
||||
Object.entries(EVENT_TO_PROP_NAME).forEach(([eventName, propName]) => {
|
||||
NavigationListenersAPI.call(eventName);
|
||||
expect(eventListenerProps[propName]).toHaveBeenCalledTimes(1);
|
||||
nextNavigationListenersAPI.call(eventName);
|
||||
expect(eventListenerProps[propName]).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
it('wire latest props listener to navigation listeners on updates (support closure/arrow functions update)', () => {
|
||||
const {
|
||||
navigation,
|
||||
|
||||
307
packages/core/src/views/__tests__/NavigationFocusEvents.test.js
Normal file
307
packages/core/src/views/__tests__/NavigationFocusEvents.test.js
Normal file
@@ -0,0 +1,307 @@
|
||||
import * as React from 'react';
|
||||
import renderer from 'react-test-renderer';
|
||||
import NavigationFocusEvents from '../NavigationFocusEvents';
|
||||
import getEventManager from '../../getEventManager';
|
||||
import { NavigationActions, StackActions } from '@react-navigation/core';
|
||||
|
||||
const getNavigationMock = (mock = {}) => {
|
||||
const { addListener, emit } = getEventManager('target');
|
||||
|
||||
return {
|
||||
state: {
|
||||
routes: [
|
||||
{ key: 'a', routeName: 'foo' },
|
||||
{ key: 'b', routeName: 'bar' },
|
||||
],
|
||||
index: 0,
|
||||
},
|
||||
isFocused: () => true,
|
||||
addListener: jest.fn(addListener),
|
||||
emit: emit,
|
||||
dangerouslyGetParent: () => null,
|
||||
...mock,
|
||||
};
|
||||
};
|
||||
|
||||
it('emits refocus event with current route key on refocus', () => {
|
||||
const navigation = getNavigationMock();
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
navigation.emit('refocus');
|
||||
|
||||
expect(onEvent).toHaveBeenCalledTimes(1);
|
||||
const key = navigation.state.routes[navigation.state.index].key;
|
||||
expect(onEvent).toHaveBeenCalledWith(key, 'refocus');
|
||||
});
|
||||
|
||||
describe('on navigation action emitted', () => {
|
||||
it('does not emit if navigation is not focused', () => {
|
||||
const navigation = getNavigationMock({
|
||||
isFocused: () => false,
|
||||
});
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
navigation.emit('action', {
|
||||
state: navigation.state,
|
||||
action: NavigationActions.init(),
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
expect(onEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('emits only willFocus and willBlur if state is transitioning', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 1,
|
||||
routeKeyHistory: ['First', 'Second'],
|
||||
isTransitioning: true,
|
||||
};
|
||||
const action = NavigationActions.init();
|
||||
|
||||
const navigation = getNavigationMock({
|
||||
state: state,
|
||||
});
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
const lastState = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 0,
|
||||
routeKeyHistory: ['First'],
|
||||
};
|
||||
navigation.emit('action', {
|
||||
state,
|
||||
lastState,
|
||||
action,
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
const expectedPayload = {
|
||||
action,
|
||||
state: { key: 'Second', routeName: 'Second' },
|
||||
lastState: { key: 'First', routeName: 'First' },
|
||||
context: 'Second:Navigation/INIT_Root',
|
||||
type: 'action',
|
||||
};
|
||||
|
||||
expect(onEvent.mock.calls).toEqual([
|
||||
['Second', 'willFocus', expectedPayload],
|
||||
['First', 'willBlur', expectedPayload],
|
||||
['Second', 'action', expectedPayload],
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits didFocus after willFocus and didBlur after willBlur if no transitions', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 1,
|
||||
routeKeyHistory: ['First', 'Second'],
|
||||
};
|
||||
const action = NavigationActions.navigate({
|
||||
routeName: 'Second',
|
||||
});
|
||||
|
||||
const navigation = getNavigationMock({
|
||||
state: state,
|
||||
});
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
const lastState = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 0,
|
||||
routeKeyHistory: ['First'],
|
||||
};
|
||||
navigation.emit('action', {
|
||||
state,
|
||||
lastState,
|
||||
action,
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
const expectedPayload = {
|
||||
action,
|
||||
state: { key: 'Second', routeName: 'Second' },
|
||||
lastState: { key: 'First', routeName: 'First' },
|
||||
context: 'Second:Navigation/NAVIGATE_Root',
|
||||
type: 'action',
|
||||
};
|
||||
|
||||
expect(onEvent.mock.calls).toEqual([
|
||||
['Second', 'willFocus', expectedPayload],
|
||||
['Second', 'didFocus', expectedPayload],
|
||||
['First', 'willBlur', expectedPayload],
|
||||
['First', 'didBlur', expectedPayload],
|
||||
['Second', 'action', expectedPayload],
|
||||
]);
|
||||
});
|
||||
|
||||
it('emits didBlur and didFocus when transition ends', () => {
|
||||
const initialState = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 0,
|
||||
routeKeyHistory: ['First'],
|
||||
isTransitioning: true,
|
||||
};
|
||||
const intermediateState = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 1,
|
||||
routeKeyHistory: ['First', 'Second'],
|
||||
isTransitioning: true,
|
||||
};
|
||||
const finalState = {
|
||||
routes: [
|
||||
{ key: 'First', routeName: 'First' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 1,
|
||||
routeKeyHistory: ['First', 'Second'],
|
||||
isTransitioning: false,
|
||||
};
|
||||
const actionNavigate = NavigationActions.navigate({ routeName: 'Second' });
|
||||
const actionEndTransition = StackActions.completeTransition({
|
||||
key: 'Second',
|
||||
});
|
||||
|
||||
const navigation = getNavigationMock({
|
||||
state: intermediateState,
|
||||
});
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
navigation.emit('action', {
|
||||
state: intermediateState,
|
||||
lastState: initialState,
|
||||
action: actionNavigate,
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
const expectedPayloadNavigate = {
|
||||
action: actionNavigate,
|
||||
state: { key: 'Second', routeName: 'Second' },
|
||||
lastState: { key: 'First', routeName: 'First' },
|
||||
context: 'Second:Navigation/NAVIGATE_Root',
|
||||
type: 'action',
|
||||
};
|
||||
|
||||
expect(onEvent.mock.calls).toEqual([
|
||||
['Second', 'willFocus', expectedPayloadNavigate],
|
||||
['First', 'willBlur', expectedPayloadNavigate],
|
||||
['Second', 'action', expectedPayloadNavigate],
|
||||
]);
|
||||
onEvent.mockClear();
|
||||
|
||||
navigation.emit('action', {
|
||||
state: finalState,
|
||||
lastState: intermediateState,
|
||||
action: actionEndTransition,
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
const expectedPayloadEndTransition = {
|
||||
action: actionEndTransition,
|
||||
state: { key: 'Second', routeName: 'Second' },
|
||||
lastState: { key: 'Second', routeName: 'Second' },
|
||||
context: 'Second:Navigation/COMPLETE_TRANSITION_Root',
|
||||
type: 'action',
|
||||
};
|
||||
|
||||
expect(onEvent.mock.calls).toEqual([
|
||||
['First', 'didBlur', expectedPayloadEndTransition],
|
||||
['Second', 'didFocus', expectedPayloadEndTransition],
|
||||
['Second', 'action', expectedPayloadEndTransition],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('on willFocus emitted', () => {
|
||||
it('emits didFocus after willFocus if no transition', () => {
|
||||
const navigation = getNavigationMock({
|
||||
state: {
|
||||
routes: [
|
||||
{ key: 'FirstLanding', routeName: 'FirstLanding' },
|
||||
{ key: 'Second', routeName: 'Second' },
|
||||
],
|
||||
index: 0,
|
||||
key: 'First',
|
||||
routeName: 'First',
|
||||
},
|
||||
});
|
||||
const onEvent = jest.fn();
|
||||
|
||||
renderer.create(
|
||||
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
|
||||
);
|
||||
|
||||
const lastState = { key: 'Third', routeName: 'Third' };
|
||||
const action = NavigationActions.navigate({ routeName: 'First' });
|
||||
|
||||
navigation.emit('willFocus', {
|
||||
lastState,
|
||||
action,
|
||||
context: 'First:Navigation/NAVIGATE_Root',
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
const expectedPayload = {
|
||||
action,
|
||||
state: { key: 'FirstLanding', routeName: 'FirstLanding' },
|
||||
context:
|
||||
'FirstLanding:Navigation/NAVIGATE_First:Navigation/NAVIGATE_Root',
|
||||
type: 'action',
|
||||
};
|
||||
|
||||
expect(onEvent.mock.calls).toEqual([
|
||||
['FirstLanding', 'willFocus', expectedPayload],
|
||||
['FirstLanding', 'didFocus', expectedPayload],
|
||||
]);
|
||||
|
||||
onEvent.mockClear();
|
||||
|
||||
// the nested navigator might emit a didFocus that should be ignored
|
||||
navigation.emit('didFocus', {
|
||||
lastState,
|
||||
action,
|
||||
context: 'First:Navigation/NAVIGATE_Root',
|
||||
type: 'action',
|
||||
});
|
||||
|
||||
expect(onEvent).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import invariant from '../utils/invariant';
|
||||
import NavigationContext from './NavigationContext';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import withNavigation from './withNavigation';
|
||||
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.6.0](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.2...react-navigation-drawer@2.6.0) (2020-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add detachInactiveScreens prop for bottom tabs and drawer ([#8816](https://github.com/react-navigation/drawer/issues/8816)) ([707bea3](https://github.com/react-navigation/drawer/commit/707bea3cba1aa1f7b1eb5884dd1d129dcc2bbdc2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.5.2](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.1...react-navigation-drawer@2.5.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.5.1](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.0...react-navigation-drawer@2.5.1) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.5.0](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.13...react-navigation-drawer@2.5.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-drawer",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.0",
|
||||
"description": "Drawer navigator component for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -38,19 +38,19 @@
|
||||
},
|
||||
"homepage": "https://github.com/react-navigation/drawer#readme",
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.2.0",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-navigation": "^4.4.0",
|
||||
"typescript": "^3.9.5"
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-native-testing-library": "^6.0.0",
|
||||
"react-navigation": "^4.4.3",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -69,6 +69,7 @@ export type NavigationDrawerConfig = {
|
||||
drawerBackgroundColor?: ThemedColor;
|
||||
overlayColor?: ThemedColor;
|
||||
screenContainerStyle?: StyleProp<ViewStyle>;
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
export type NavigationDrawerRouterConfig = {
|
||||
|
||||
@@ -49,6 +49,7 @@ type Props = {
|
||||
contentOptions?: object;
|
||||
};
|
||||
screenProps: unknown;
|
||||
detachInactiveScreens: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -166,7 +167,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
private renderContent = () => {
|
||||
let { lazy, navigation } = this.props;
|
||||
let { lazy, navigation, detachInactiveScreens = true } = this.props;
|
||||
let { loaded } = this.state;
|
||||
let { routes } = navigation.state;
|
||||
|
||||
@@ -183,7 +184,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ScreenContainer style={styles.content}>
|
||||
// @ts-ignore
|
||||
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
|
||||
{routes.map((route, index) => {
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
@@ -201,6 +203,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
{ opacity: isFocused ? 1 : 0 },
|
||||
]}
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
<SceneView
|
||||
navigation={descriptor.navigation}
|
||||
|
||||
@@ -6,6 +6,7 @@ type Props = {
|
||||
isVisible: boolean;
|
||||
children: React.ReactNode;
|
||||
style?: any;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
@@ -13,7 +14,7 @@ const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
if (screensEnabled?.() && this.props.enabled && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [2.3.3](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.2...react-navigation-material-bottom-tabs@2.3.3) (2020-10-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.2](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.1...react-navigation-material-bottom-tabs@2.3.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.3.1](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.0...react-navigation-material-bottom-tabs@2.3.1) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.3.0](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.12...react-navigation-material-bottom-tabs@2.3.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-material-bottom-tabs",
|
||||
"version": "2.3.0",
|
||||
"version": "2.3.3",
|
||||
"description": "Material Bottom Tab Navigation component for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -38,18 +38,16 @@
|
||||
},
|
||||
"homepage": "https://github.com/react-navigation/react-navigation-material-bottom-tabs#readme",
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-paper": "^3.1.1",
|
||||
"react-native-reanimated": "^1.2.0",
|
||||
"react-navigation": "^4.4.0",
|
||||
"typescript": "^3.9.5"
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-paper": "^4.2.0",
|
||||
"react-navigation": "^4.4.3",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -3,6 +3,33 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [3.8.3](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.2...@react-navigation/native@3.8.3) (2020-10-26)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.8.2](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.1...@react-navigation/native@3.8.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.8.1](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.0...@react-navigation/native@3.8.1) (2020-09-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use deprecated currentlyFocusedField ([#8684](https://github.com/react-navigation/react-navigation-native/issues/8684)) ([06a69f1](https://github.com/react-navigation/react-navigation-native/commit/06a69f1bfd81fe06b784ff4e6da290fee0c6467d)), closes [#8457](https://github.com/react-navigation/react-navigation-native/issues/8457)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.8.0](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.13...@react-navigation/native@3.8.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"version": "3.8.0",
|
||||
"version": "3.8.3",
|
||||
"description": "React Native support for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -39,14 +39,14 @@
|
||||
"react-native-safe-area-view": "^0.14.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/core": "^3.7.6",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-test-renderer": "^16.9.2"
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/core": "^3.7.9",
|
||||
"@types/react-test-renderer": "^16.9.3",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-test-renderer": "~16.13.1"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { SceneView } from '@react-navigation/core';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
ScrollView,
|
||||
Platform,
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
SectionList,
|
||||
RefreshControl,
|
||||
} from 'react-native';
|
||||
// eslint-disable-next-line import/named
|
||||
import { ScrollView as GHScrollView } from 'react-native-gesture-handler';
|
||||
import createNavigationAwareScrollable from './createNavigationAwareScrollable';
|
||||
import invariant from './utils/invariant';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
import withOrientation, { isOrientationLandscape } from '../withOrientation';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* eslint-disable react/sort-comp */
|
||||
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Linking, Platform, BackHandler } from 'react-native';
|
||||
import {
|
||||
NavigationActions,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { TextInput } from 'react-native';
|
||||
|
||||
export default (Navigator, navigatorConfig) =>
|
||||
@@ -8,7 +8,9 @@ export default (Navigator, navigatorConfig) =>
|
||||
_previouslyFocusedTextInput = null;
|
||||
|
||||
_handleGestureBegin = () => {
|
||||
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedField();
|
||||
this._previouslyFocusedTextInput = TextInput.State.currentlyFocusedInput
|
||||
? TextInput.State.currentlyFocusedInput()
|
||||
: TextInput.State.currentlyFocusedField();
|
||||
if (this._previouslyFocusedTextInput) {
|
||||
TextInput.State.blurTextInput(this._previouslyFocusedTextInput);
|
||||
}
|
||||
@@ -32,7 +34,9 @@ export default (Navigator, navigatorConfig) =>
|
||||
// in the case where the index did not change, I believe. We
|
||||
// should revisit this after 2.0 release.
|
||||
if (transitionProps.index !== prevTransitionProps.index) {
|
||||
const currentField = TextInput.State.currentlyFocusedField();
|
||||
const currentField = TextInput.State.currentlyFocusedInput
|
||||
? TextInput.State.currentlyFocusedInput()
|
||||
: TextInput.State.currentlyFocusedField();
|
||||
if (currentField) {
|
||||
TextInput.State.blurTextInput(currentField);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import { withNavigation } from '@react-navigation/core';
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [4.4.3](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.2...react-navigation@4.4.3) (2020-10-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.4.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.1...react-navigation@4.4.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.4.1](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.0...react-navigation@4.4.1) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.4.0](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.9...react-navigation@4.4.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "4.4.0",
|
||||
"version": "4.4.3",
|
||||
"description": "Routing and navigation for your React Native apps",
|
||||
"main": "src/index.js",
|
||||
"types": "typescript/react-navigation.d.ts",
|
||||
@@ -24,16 +24,16 @@
|
||||
"react-native": "*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^3.7.6",
|
||||
"@react-navigation/native": "^3.8.0"
|
||||
"@react-navigation/core": "^3.7.9",
|
||||
"@react-navigation/native": "^3.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-test-renderer": "^16.9.2",
|
||||
"typescript": "^3.9.5"
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"@types/react-test-renderer": "^16.9.3",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-test-renderer": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
|
||||
@@ -812,6 +812,7 @@ export interface NavigationDescriptor<
|
||||
getComponent: () => React.ComponentType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type NavigationView<Options, State, ScreenProps = unknown> =
|
||||
| React.ComponentType<
|
||||
{
|
||||
@@ -897,11 +898,13 @@ export function withNavigation<
|
||||
}
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function withNavigation<P extends NavigationInjectedProps>(
|
||||
Component: React.ComponentType<P>
|
||||
): React.ComponentType<Omit<P, keyof NavigationInjectedProps>>;
|
||||
|
||||
// For backwards compatibility
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function withNavigation<T = {}, P = NavigationParams>(
|
||||
Component: React.ComponentType<T & NavigationInjectedProps<P>>
|
||||
): React.ComponentType<
|
||||
@@ -929,11 +932,13 @@ export function withNavigationFocus<
|
||||
}
|
||||
>;
|
||||
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function withNavigationFocus<P extends NavigationFocusInjectedProps>(
|
||||
Component: React.ComponentType<P>
|
||||
): React.ComponentType<Omit<P, keyof NavigationFocusInjectedProps>>;
|
||||
|
||||
// For backwards compatibility
|
||||
// eslint-disable-next-line no-redeclare
|
||||
export function withNavigationFocus<T = {}, P = NavigationParams>(
|
||||
Component: React.ComponentType<T & NavigationFocusInjectedProps<P>>
|
||||
): React.ComponentType<
|
||||
|
||||
@@ -3,6 +3,44 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.9.0](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.4...react-navigation-stack@2.9.0) (2020-10-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack ([6bdc1e9](https://github.com/react-navigation/react-navigation-stack/commit/6bdc1e9e5f6ca05e0494f6b9a1f7b9b60764628a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.4](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.3...react-navigation-stack@2.8.4) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.3](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.2...react-navigation-stack@2.8.3) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.2](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.1...react-navigation-stack@2.8.2) (2020-06-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sync latest stack ([46b797d](https://github.com/react-navigation/react-navigation-stack/commit/46b797dd297f789e3c92ff6f5c4432fedc891767))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.8.1](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.0...react-navigation-stack@2.8.1) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ yarn example start
|
||||
|
||||
The code in this repo uses the source from [`@react-navigation/stack`](https://github.com/react-navigation/navigation-ex/tree/master/packages/stack) and patches it to make it usable in React Navigation 4. If you need to make changes, please send a pull request there.
|
||||
|
||||
If the change is specifically related to React Navigation 4 integration, first run `yarn sync`, then change the files in `src/vendor` and then run `yarn patch` to update the patch file with the latest changes.
|
||||
If the change is specifically related to React Navigation 4 integration, first run `yarn patch:apply`, then change the files in `src/vendor` to resolve any conflicts and then run `yarn patch:create` to update the patch file with the latest changes.
|
||||
|
||||
Make sure your code passes TypeScript and ESLint. Run the following to verify:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-stack",
|
||||
"version": "2.8.1",
|
||||
"version": "2.9.0",
|
||||
"description": "Stack navigator component for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -13,8 +13,8 @@
|
||||
"scripts": {
|
||||
"prepare": "bob build",
|
||||
"clean": "del lib",
|
||||
"sync": "bash scripts/sync-stack.sh",
|
||||
"patch": "diff -Naur ../../node_modules/@react-navigation/stack/src src/vendor > scripts/stack.patch; printf ''"
|
||||
"patch:apply": "bash scripts/sync-stack.sh",
|
||||
"patch:create": "diff -Naur ../../node_modules/@react-navigation/stack/src src/vendor > scripts/stack.patch; printf ''"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
@@ -39,26 +39,26 @@
|
||||
},
|
||||
"homepage": "https://github.com/react-navigation/react-navigation-stack#readme",
|
||||
"dependencies": {
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
"color": "^3.1.3",
|
||||
"react-native-iphone-x-helper": "^1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-native-community/masked-view": "^0.1.7",
|
||||
"@react-navigation/stack": "^5.6.0",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-native-community/masked-view": "0.1.10",
|
||||
"@react-navigation/stack": "^5.10.0",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.4",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-navigation": "^4.4.0",
|
||||
"react-test-renderer": "~16.9.0",
|
||||
"typescript": "^3.9.5"
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"@types/react-test-renderer": "^16.9.3",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-safe-area-context": "3.1.4",
|
||||
"react-native-screens": "~2.10.1",
|
||||
"react-navigation": "^4.4.3",
|
||||
"react-test-renderer": "~16.13.1",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-community/masked-view": ">=0.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/index.tsx 2020-06-24 23:16:17.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/index.tsx 2020-10-26 16:08:35.000000000 +0100
|
||||
@@ -3,11 +3,6 @@
|
||||
import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs';
|
||||
import * as TransitionPresets from './TransitionConfigs/TransitionPresets';
|
||||
@@ -28,9 +28,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/i
|
||||
StackHeaderLeftButtonProps,
|
||||
StackHeaderTitleProps,
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100
|
||||
@@ -1,96 +0,0 @@
|
||||
@@ -1,101 +0,0 @@
|
||||
-import * as React from 'react';
|
||||
-import { Platform } from 'react-native';
|
||||
-import {
|
||||
@@ -42,6 +42,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
|
||||
- StackRouterOptions,
|
||||
- StackNavigationState,
|
||||
- StackActions,
|
||||
- ParamListBase,
|
||||
- StackActionHelpers,
|
||||
-} from '@react-navigation/native';
|
||||
-import StackView from '../views/Stack/StackView';
|
||||
-import type {
|
||||
@@ -62,12 +64,16 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
|
||||
-}: Props) {
|
||||
- const defaultOptions = {
|
||||
- gestureEnabled: Platform.OS === 'ios',
|
||||
- animationEnabled: Platform.OS !== 'web',
|
||||
- animationEnabled:
|
||||
- Platform.OS !== 'web' &&
|
||||
- Platform.OS !== 'windows' &&
|
||||
- Platform.OS !== 'macos',
|
||||
- };
|
||||
-
|
||||
- const { state, descriptors, navigation } = useNavigationBuilder<
|
||||
- StackNavigationState,
|
||||
- StackNavigationState<ParamListBase>,
|
||||
- StackRouterOptions,
|
||||
- StackActionHelpers<ParamListBase>,
|
||||
- StackNavigationOptions,
|
||||
- StackNavigationEventMap
|
||||
- >(StackRouter, {
|
||||
@@ -87,8 +93,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
|
||||
-
|
||||
- React.useEffect(
|
||||
- () =>
|
||||
- navigation.addListener &&
|
||||
- navigation.addListener('tabPress', (e) => {
|
||||
- navigation.addListener?.('tabPress', (e) => {
|
||||
- const isFocused = navigation.isFocused();
|
||||
-
|
||||
- // Run the operation in the next frame so we're sure all listeners have been run
|
||||
@@ -122,15 +127,15 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
|
||||
-}
|
||||
-
|
||||
-export default createNavigatorFactory<
|
||||
- StackNavigationState,
|
||||
- StackNavigationState<ParamListBase>,
|
||||
- StackNavigationOptions,
|
||||
- StackNavigationEventMap,
|
||||
- typeof StackNavigator
|
||||
->(StackNavigator);
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/types.tsx 2020-06-24 23:18:40.000000000 +0200
|
||||
@@ -8,15 +8,28 @@
|
||||
--- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/types.tsx 2020-10-26 16:13:50.000000000 +0100
|
||||
@@ -8,15 +8,29 @@
|
||||
} from 'react-native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type {
|
||||
@@ -155,6 +160,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
|
||||
+} from 'react-navigation';
|
||||
+
|
||||
+// @ts-ignore
|
||||
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
+export type Route<T extends string> = NavigationRoute;
|
||||
+
|
||||
+export type NavigationStackState = NavigationState;
|
||||
@@ -167,23 +173,24 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
|
||||
|
||||
export type StackNavigationEventMap = {
|
||||
/**
|
||||
@@ -29,29 +42,28 @@
|
||||
transitionEnd: { data: { closing: boolean } };
|
||||
@@ -41,30 +55,29 @@
|
||||
gestureCancel: { data: undefined };
|
||||
};
|
||||
|
||||
-export type StackNavigationHelpers = NavigationHelpers<
|
||||
- ParamListBase,
|
||||
- StackNavigationEventMap
|
||||
->;
|
||||
-
|
||||
-> &
|
||||
- StackActionHelpers<ParamListBase>;
|
||||
+export type StackNavigationHelpers = NavigationProp<NavigationStackState>;
|
||||
|
||||
export type StackNavigationProp<
|
||||
- ParamList extends ParamListBase,
|
||||
- RouteName extends keyof ParamList = string
|
||||
-> = NavigationProp<
|
||||
- ParamList,
|
||||
- RouteName,
|
||||
- StackNavigationState,
|
||||
- StackNavigationState<ParamList>,
|
||||
- StackNavigationOptions,
|
||||
- StackNavigationEventMap
|
||||
-> &
|
||||
@@ -218,7 +225,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
|
||||
};
|
||||
|
||||
export type Layout = { width: number; height: number };
|
||||
@@ -228,24 +240,27 @@
|
||||
@@ -241,24 +254,27 @@
|
||||
/**
|
||||
* Navigation prop for the header.
|
||||
*/
|
||||
@@ -233,7 +240,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
|
||||
-export type StackDescriptor = Descriptor<
|
||||
- ParamListBase,
|
||||
- string,
|
||||
- StackNavigationState,
|
||||
- StackNavigationState<ParamListBase>,
|
||||
- StackNavigationOptions
|
||||
+export type StackDescriptor = NavigationDescriptor<
|
||||
+ NavigationParams,
|
||||
@@ -252,18 +259,27 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
|
||||
export type StackNavigationOptions = StackHeaderOptions &
|
||||
Partial<TransitionPreset> & {
|
||||
/**
|
||||
@@ -330,6 +345,8 @@
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
@@ -352,6 +368,8 @@
|
||||
* Defaults to `false` for the last screen when mode='modal', otherwise `true`.
|
||||
*/
|
||||
detachPreviousScreen?: boolean;
|
||||
+ onTransitionStart?: (props: TransitionCallbackProps) => void;
|
||||
+ onTransitionEnd?: (props: TransitionCallbackProps) => void;
|
||||
};
|
||||
|
||||
export type StackNavigationConfig = {
|
||||
@@ -365,7 +383,7 @@
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
- * Defaults to `true`.
|
||||
+ * Defaults to `true` on Android, `false` on iOS.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx src/vendor/utils/PreviousSceneContext.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/utils/PreviousSceneContext.tsx 2020-06-24 23:26:38.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/utils/PreviousSceneContext.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
-import type { Route } from '@react-navigation/native';
|
||||
@@ -273,8 +289,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneCon
|
||||
const PreviousSceneContext = React.createContext<
|
||||
Scene<Route<string>> | undefined
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/Header.tsx 2020-06-24 23:33:25.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/Header.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -1,12 +1,15 @@
|
||||
import * as React from 'react';
|
||||
-import { StackActions } from '@react-navigation/native';
|
||||
@@ -303,7 +319,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
|
||||
|
||||
let leftLabel;
|
||||
|
||||
@@ -38,17 +41,18 @@
|
||||
@@ -38,17 +41,20 @@
|
||||
? o.headerTitle
|
||||
: o.title !== undefined
|
||||
? o.title
|
||||
@@ -319,16 +335,18 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
|
||||
- ...StackActions.pop(),
|
||||
- source: scene.route.key,
|
||||
- });
|
||||
+ if (navigation.isFirstRouteInParent()) {
|
||||
+ // If we're the first route, we're going back to a parent navigator
|
||||
+ // So we can't specify a key here
|
||||
+ navigation.dispatch(StackActions.pop());
|
||||
+ } else {
|
||||
+ navigation.dispatch(StackActions.pop({ key: scene.route.key }));
|
||||
+ const key = navigation.isFirstRouteInParent()
|
||||
+ ? // If we're the first route, we're going back to a parent navigator
|
||||
+ // So we need to get the key of the route we're nested in
|
||||
+ navigation.dangerouslyGetParent()?.state.key
|
||||
+ : scene.route.key;
|
||||
+
|
||||
+ if (key !== undefined) {
|
||||
+ navigation.dispatch(StackActions.pop({ key }));
|
||||
}
|
||||
}, 50),
|
||||
[navigation, scene.route.key]
|
||||
@@ -64,7 +68,10 @@
|
||||
@@ -64,7 +70,10 @@
|
||||
leftLabel={leftLabel}
|
||||
headerTitle={
|
||||
typeof options.headerTitle !== 'function'
|
||||
@@ -340,7 +358,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
|
||||
: options.headerTitle
|
||||
}
|
||||
onGoBack={previous ? goBack : undefined}
|
||||
@@ -72,3 +79,18 @@
|
||||
@@ -72,3 +81,18 @@
|
||||
/>
|
||||
);
|
||||
});
|
||||
@@ -360,22 +378,22 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
|
||||
+
|
||||
+export default Header;
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/HeaderBackButton.tsx 2020-06-24 23:21:49.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/HeaderBackButton.tsx 2020-10-26 16:14:24.000000000 +0100
|
||||
@@ -8,9 +8,9 @@
|
||||
StyleSheet,
|
||||
LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
-import { useTheme } from '@react-navigation/native';
|
||||
import MaskedView from '../MaskedView';
|
||||
import { TouchableItem } from '../TouchableItem';
|
||||
import TouchableItem from '../TouchableItem';
|
||||
+import useTheme from '../../../utils/useTheme';
|
||||
import type { StackHeaderLeftButtonProps } from '../../types';
|
||||
|
||||
type Props = StackHeaderLeftButtonProps;
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/HeaderBackground.tsx 2020-06-24 23:16:23.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/HeaderBackground.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -7,7 +7,7 @@
|
||||
StyleProp,
|
||||
ViewStyle,
|
||||
@@ -386,11 +404,11 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBac
|
||||
type Props = ViewProps & {
|
||||
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/HeaderContainer.tsx 2020-06-24 23:21:29.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/HeaderContainer.tsx 2020-10-26 16:15:18.000000000 +0100
|
||||
@@ -1,11 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
-import {
|
||||
- NavigationContext,
|
||||
- NavigationRouteContext,
|
||||
@@ -409,7 +427,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
|
||||
Scene,
|
||||
StackHeaderStyleInterpolator,
|
||||
StackNavigationProp,
|
||||
@@ -109,9 +105,7 @@
|
||||
@@ -105,9 +101,7 @@
|
||||
insets,
|
||||
scene,
|
||||
previous,
|
||||
@@ -420,7 +438,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
|
||||
styleInterpolator:
|
||||
mode === 'float'
|
||||
? isHeaderStatic
|
||||
@@ -130,7 +124,7 @@
|
||||
@@ -126,7 +120,7 @@
|
||||
key={scene.route.key}
|
||||
value={scene.descriptor.navigation}
|
||||
>
|
||||
@@ -429,7 +447,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
|
||||
<View
|
||||
onLayout={
|
||||
onContentHeightChange
|
||||
@@ -156,7 +150,7 @@
|
||||
@@ -155,7 +149,7 @@
|
||||
>
|
||||
{header !== undefined ? header(props) : <Header {...props} />}
|
||||
</View>
|
||||
@@ -439,8 +457,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
|
||||
);
|
||||
})}
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/HeaderSegment.tsx 2020-06-24 23:20:58.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/HeaderSegment.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -8,7 +8,7 @@
|
||||
ViewStyle,
|
||||
} from 'react-native';
|
||||
@@ -449,8 +467,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSeg
|
||||
+import type { NavigationRoute } from 'react-navigation';
|
||||
import HeaderBackButton from './HeaderBackButton';
|
||||
import HeaderBackground from './HeaderBackground';
|
||||
import memoize from '../../utils/memoize';
|
||||
@@ -28,7 +28,7 @@
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
@@ -29,7 +29,7 @@
|
||||
onGoBack?: () => void;
|
||||
title?: string;
|
||||
leftLabel?: string;
|
||||
@@ -460,31 +478,32 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSeg
|
||||
};
|
||||
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Header/HeaderTitle.tsx 2020-06-24 23:16:23.000000000 +0200
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, StyleSheet, Platform } from 'react-native';
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Header/HeaderTitle.tsx 2020-10-26 16:14:52.000000000 +0100
|
||||
@@ -7,7 +7,7 @@
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
-import { useTheme } from '@react-navigation/native';
|
||||
+import useTheme from '../../../utils/useTheme';
|
||||
|
||||
type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & {
|
||||
type Props = Omit<TextProps, 'style'> & {
|
||||
tintColor?: string;
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Stack/Card.tsx 2020-06-24 23:16:23.000000000 +0200
|
||||
@@ -146,7 +146,7 @@
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Stack/Card.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -162,7 +162,7 @@
|
||||
|
||||
private interactionHandle: number | undefined;
|
||||
|
||||
- private pendingGestureCallback: number | undefined;
|
||||
+ private pendingGestureCallback: any;
|
||||
|
||||
private animate = ({
|
||||
closing,
|
||||
private lastToValue: number | undefined;
|
||||
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Stack/CardContainer.tsx 2020-06-24 23:20:38.000000000 +0200
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Stack/CardContainer.tsx 2020-10-26 16:08:40.000000000 +0100
|
||||
@@ -1,12 +1,13 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
@@ -501,18 +520,22 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContai
|
||||
Layout,
|
||||
StackHeaderMode,
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Stack/CardStack.tsx 2020-06-24 23:20:16.000000000 +0200
|
||||
@@ -7,7 +7,7 @@
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Stack/CardStack.tsx 2020-10-26 16:17:14.000000000 +0100
|
||||
@@ -7,11 +7,7 @@
|
||||
Platform,
|
||||
} from 'react-native';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
-import type { Route, StackNavigationState } from '@react-navigation/native';
|
||||
-import type {
|
||||
- ParamListBase,
|
||||
- Route,
|
||||
- StackNavigationState,
|
||||
-} from '@react-navigation/native';
|
||||
+import type { NavigationState as StackNavigationState } from 'react-navigation';
|
||||
|
||||
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
@@ -25,6 +25,7 @@
|
||||
import {
|
||||
MaybeScreenContainer,
|
||||
@@ -32,6 +28,7 @@
|
||||
Layout,
|
||||
StackHeaderMode,
|
||||
StackCardMode,
|
||||
@@ -520,10 +543,19 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.
|
||||
Scene,
|
||||
StackDescriptorMap,
|
||||
StackNavigationOptions,
|
||||
@@ -45,7 +42,7 @@
|
||||
type Props = {
|
||||
mode: StackCardMode;
|
||||
insets: EdgeInsets;
|
||||
- state: StackNavigationState<ParamListBase>;
|
||||
+ state: StackNavigationState;
|
||||
descriptors: StackDescriptorMap;
|
||||
routes: Route<string>[];
|
||||
openingRouteKeys: string[];
|
||||
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-06-24 23:11:52.000000000 +0200
|
||||
+++ src/vendor/views/Stack/StackView.tsx 2020-06-24 23:19:46.000000000 +0200
|
||||
@@ -2,11 +2,11 @@
|
||||
--- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-10-26 16:07:00.000000000 +0100
|
||||
+++ src/vendor/views/Stack/StackView.tsx 2020-10-26 16:20:15.000000000 +0100
|
||||
@@ -2,12 +2,11 @@
|
||||
import { View, Platform, StyleSheet } from 'react-native';
|
||||
import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
|
||||
import {
|
||||
@@ -531,6 +563,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
StackActions,
|
||||
- StackNavigationState,
|
||||
- Route,
|
||||
- ParamListBase,
|
||||
-} from '@react-navigation/native';
|
||||
+ NavigationState as StackNavigationState,
|
||||
+ NavigationActions,
|
||||
@@ -539,7 +572,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
|
||||
import { GestureHandlerRootView } from '../GestureHandler';
|
||||
import CardStack from './CardStack';
|
||||
@@ -16,6 +16,7 @@
|
||||
@@ -17,6 +16,7 @@
|
||||
} from '../Header/HeaderContainer';
|
||||
import SafeAreaProviderCompat from '../SafeAreaProviderCompat';
|
||||
import type {
|
||||
@@ -547,15 +580,19 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
StackNavigationHelpers,
|
||||
StackNavigationConfig,
|
||||
StackDescriptorMap,
|
||||
@@ -25,6 +26,7 @@
|
||||
state: StackNavigationState;
|
||||
@@ -24,9 +24,10 @@
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
|
||||
type Props = StackNavigationConfig & {
|
||||
- state: StackNavigationState<ParamListBase>;
|
||||
+ state: StackNavigationState;
|
||||
navigation: StackNavigationHelpers;
|
||||
descriptors: StackDescriptorMap;
|
||||
+ screenProps: unknown;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -293,7 +295,9 @@
|
||||
@@ -295,7 +296,9 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -566,7 +603,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -321,26 +325,49 @@
|
||||
@@ -323,26 +326,49 @@
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -619,7 +656,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
} else {
|
||||
this.setState((state) => ({
|
||||
routes: state.replacingRouteKeys.length
|
||||
@@ -366,12 +393,11 @@
|
||||
@@ -368,12 +394,11 @@
|
||||
// If a route exists in state, trigger a pop
|
||||
// This will happen in when the route was closed from the card component
|
||||
// e.g. When the close animation triggered from a gesture ends
|
||||
@@ -636,7 +673,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
// We need to clean up any state tracking the route and pop it immediately
|
||||
this.setState((state) => ({
|
||||
routes: state.routes.filter((r) => r.key !== route.key),
|
||||
@@ -388,26 +414,29 @@
|
||||
@@ -390,47 +415,41 @@
|
||||
private handleTransitionStart = (
|
||||
{ route }: { route: Route<string> },
|
||||
closing: boolean
|
||||
@@ -663,13 +700,45 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
- data: { closing },
|
||||
- target: route.key,
|
||||
- });
|
||||
-
|
||||
- private handleGestureStart = ({ route }: { route: Route<string> }) => {
|
||||
- this.props.navigation.emit({
|
||||
- type: 'gestureStart',
|
||||
- target: route.key,
|
||||
- });
|
||||
- };
|
||||
-
|
||||
- private handleGestureEnd = ({ route }: { route: Route<string> }) => {
|
||||
- this.props.navigation.emit({
|
||||
- type: 'gestureEnd',
|
||||
- target: route.key,
|
||||
- });
|
||||
- };
|
||||
-
|
||||
- private handleGestureCancel = ({ route }: { route: Route<string> }) => {
|
||||
- this.props.navigation.emit({
|
||||
- type: 'gestureCancel',
|
||||
- target: route.key,
|
||||
- });
|
||||
+ ) => {
|
||||
+ const { descriptors } = this.props;
|
||||
+ const descriptor =
|
||||
+ descriptors[route.key] || this.state.descriptors[route.key];
|
||||
+
|
||||
+ descriptor?.options.onTransitionEnd?.({ closing });
|
||||
+ descriptor?.options.onTransitionStart?.({ closing });
|
||||
+ };
|
||||
+
|
||||
+ private handleGestureStart = () => {
|
||||
+ // Do nothing
|
||||
+ };
|
||||
+
|
||||
+ private handleGestureEnd = () => {
|
||||
+ // Do nothing
|
||||
+ };
|
||||
+
|
||||
+ private handleGestureCancel = () => {
|
||||
+ // Do nothing
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
@@ -678,7 +747,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
navigation,
|
||||
keyboardHandlingEnabled,
|
||||
mode = 'card',
|
||||
@@ -427,7 +456,7 @@
|
||||
@@ -450,7 +469,7 @@
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
@@ -687,7 +756,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
|
||||
<GestureHandlerWrapper style={styles.container}>
|
||||
<SafeAreaProviderCompat>
|
||||
<SafeAreaConsumer>
|
||||
@@ -460,7 +489,7 @@
|
||||
@@ -491,7 +510,7 @@
|
||||
</SafeAreaConsumer>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
|
||||
@@ -11,12 +11,9 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
},
|
||||
false,
|
||||
]
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -99,6 +96,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
>
|
||||
<Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
@@ -152,6 +150,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
onClose={[Function]}
|
||||
onGestureBegin={[Function]}
|
||||
onGestureCanceled={[Function]}
|
||||
onGestureEnd={[Function]}
|
||||
onOpen={[Function]}
|
||||
onTransitionStart={[Function]}
|
||||
pointerEvents="box-none"
|
||||
@@ -192,6 +191,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
}
|
||||
>
|
||||
<View
|
||||
needsOffscreenAlphaCompositing={false}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
@@ -319,6 +319,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
onClose={[Function]}
|
||||
onGestureBegin={[Function]}
|
||||
onGestureCanceled={[Function]}
|
||||
onGestureEnd={[Function]}
|
||||
onOpen={[Function]}
|
||||
onTransitionStart={[Function]}
|
||||
pointerEvents="box-none"
|
||||
@@ -359,6 +360,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
}
|
||||
>
|
||||
<View
|
||||
needsOffscreenAlphaCompositing={false}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
@@ -455,17 +457,14 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
},
|
||||
Object {
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
},
|
||||
]
|
||||
Object {
|
||||
"height": 44,
|
||||
"left": 0,
|
||||
"position": "absolute",
|
||||
"right": 0,
|
||||
"top": 0,
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -11,12 +11,9 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
},
|
||||
false,
|
||||
]
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -100,6 +97,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
>
|
||||
<Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
@@ -169,6 +167,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
onClose={[Function]}
|
||||
onGestureBegin={[Function]}
|
||||
onGestureCanceled={[Function]}
|
||||
onGestureEnd={[Function]}
|
||||
onOpen={[Function]}
|
||||
onTransitionStart={[Function]}
|
||||
pointerEvents="box-none"
|
||||
@@ -209,6 +208,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
|
||||
}
|
||||
>
|
||||
<View
|
||||
needsOffscreenAlphaCompositing={false}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
@@ -315,12 +315,9 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
<View
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
},
|
||||
false,
|
||||
]
|
||||
Object {
|
||||
"zIndex": 1,
|
||||
}
|
||||
}
|
||||
>
|
||||
<View
|
||||
@@ -404,6 +401,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
>
|
||||
<Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
onLayout={[Function]}
|
||||
style={
|
||||
@@ -457,6 +455,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
onClose={[Function]}
|
||||
onGestureBegin={[Function]}
|
||||
onGestureCanceled={[Function]}
|
||||
onGestureEnd={[Function]}
|
||||
onOpen={[Function]}
|
||||
onTransitionStart={[Function]}
|
||||
pointerEvents="box-none"
|
||||
@@ -497,6 +496,7 @@ exports[`StackNavigator renders successfully 1`] = `
|
||||
}
|
||||
>
|
||||
<View
|
||||
needsOffscreenAlphaCompositing={false}
|
||||
pointerEvents="box-none"
|
||||
style={
|
||||
Object {
|
||||
|
||||
@@ -289,8 +289,8 @@ export function forScaleFromCenterAndroid({
|
||||
);
|
||||
|
||||
const opacity = progress.interpolate({
|
||||
inputRange: [0, 0.8, 1, 1.2, 2],
|
||||
outputRange: [0, 0.5, 1, 0.33, 0],
|
||||
inputRange: [0, 0.75, 0.875, 1, 1.0825, 1.2075, 2],
|
||||
outputRange: [0, 0, 1, 1, 1, 1, 0],
|
||||
});
|
||||
|
||||
const scale = conditional(
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import type { TransitionPreset } from '../types';
|
||||
|
||||
const ANDROID_VERSION_PIE = 28;
|
||||
const ANDROID_VERSION_10 = 29;
|
||||
|
||||
/**
|
||||
* Standard iOS navigation transition.
|
||||
@@ -102,10 +103,13 @@ export const ScaleFromCenterAndroid: TransitionPreset = {
|
||||
*/
|
||||
export const DefaultTransition = Platform.select({
|
||||
ios: SlideFromRightIOS,
|
||||
default:
|
||||
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_PIE
|
||||
android:
|
||||
Platform.Version >= ANDROID_VERSION_10
|
||||
? ScaleFromCenterAndroid
|
||||
: Platform.Version >= ANDROID_VERSION_PIE
|
||||
? RevealFromBottomAndroid
|
||||
: FadeFromBottomAndroid,
|
||||
default: ScaleFromCenterAndroid,
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
43
packages/stack/src/vendor/types.tsx
vendored
43
packages/stack/src/vendor/types.tsx
vendored
@@ -21,6 +21,7 @@ import type {
|
||||
} from 'react-navigation';
|
||||
|
||||
// @ts-ignore
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export type Route<T extends string> = NavigationRoute;
|
||||
|
||||
export type NavigationStackState = NavigationState;
|
||||
@@ -40,9 +41,22 @@ export type StackNavigationEventMap = {
|
||||
* Event which fires when a transition animation ends.
|
||||
*/
|
||||
transitionEnd: { data: { closing: boolean } };
|
||||
/**
|
||||
* Event which fires when navigation gesture starts.
|
||||
*/
|
||||
gestureStart: { data: undefined };
|
||||
/**
|
||||
* Event which fires when navigation gesture is completed.
|
||||
*/
|
||||
gestureEnd: { data: undefined };
|
||||
/**
|
||||
* Event which fires when navigation gesture is canceled.
|
||||
*/
|
||||
gestureCancel: { data: undefined };
|
||||
};
|
||||
|
||||
export type StackNavigationHelpers = NavigationProp<NavigationStackState>;
|
||||
|
||||
export type StackNavigationProp<
|
||||
State = NavigationRoute,
|
||||
Params = NavigationParams
|
||||
@@ -124,7 +138,7 @@ export type StackHeaderOptions = {
|
||||
/**
|
||||
* Style object for the title component.
|
||||
*/
|
||||
headerTitleStyle?: React.ComponentProps<typeof Animated.Text>['style'];
|
||||
headerTitleStyle?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
|
||||
/**
|
||||
* Style object for the container of the `headerTitle` component, for example to add padding.
|
||||
* By default, `headerTitleContainerStyle` is with an absolute position style and offsets both `left` and `right`.
|
||||
@@ -288,7 +302,9 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
/**
|
||||
* Function that returns a React Element to display as a overlay for the card.
|
||||
*/
|
||||
cardOverlay?: (props: { style: StyleProp<ViewStyle> }) => React.ReactNode;
|
||||
cardOverlay?: (props: {
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
}) => React.ReactNode;
|
||||
/**
|
||||
* Style object for the card in stack.
|
||||
* You can provide a custom background color to use instead of the default background here.
|
||||
@@ -321,11 +337,11 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
*/
|
||||
gestureResponseDistance?: {
|
||||
/**
|
||||
* Distance for horizontal direction. Defaults to 25.
|
||||
* Distance for vertical direction. Defaults to 135.
|
||||
*/
|
||||
vertical?: number;
|
||||
/**
|
||||
* Distance for vertical direction. Defaults to 135.
|
||||
* Distance for horizontal direction. Defaults to 25.
|
||||
*/
|
||||
horizontal?: number;
|
||||
};
|
||||
@@ -345,6 +361,13 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
bottom?: number;
|
||||
left?: number;
|
||||
};
|
||||
/**
|
||||
* Whether to detach the previous screen from the view hierarchy to save memory.
|
||||
* Set it to `false` if you need the previous screen to be seen through the active screen.
|
||||
* Only applicable if `detachInactiveScreens` isn't set to `false`.
|
||||
* Defaults to `false` for the last screen when mode='modal', otherwise `true`.
|
||||
*/
|
||||
detachPreviousScreen?: boolean;
|
||||
onTransitionStart?: (props: TransitionCallbackProps) => void;
|
||||
onTransitionEnd?: (props: TransitionCallbackProps) => void;
|
||||
};
|
||||
@@ -357,6 +380,12 @@ export type StackNavigationConfig = {
|
||||
* Defaults to `true`.
|
||||
*/
|
||||
keyboardHandlingEnabled?: boolean;
|
||||
/**
|
||||
* Whether inactive screens should be detached from the view hierarchy to save memory.
|
||||
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
|
||||
* Defaults to `true` on Android, `false` on iOS.
|
||||
*/
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
export type StackHeaderLeftButtonProps = {
|
||||
@@ -423,6 +452,10 @@ export type StackHeaderLeftButtonProps = {
|
||||
* Accessibility label for the button for screen readers.
|
||||
*/
|
||||
accessibilityLabel?: string;
|
||||
/**
|
||||
* Style object for the button.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>;
|
||||
};
|
||||
|
||||
export type StackHeaderTitleProps = {
|
||||
@@ -445,7 +478,7 @@ export type StackHeaderTitleProps = {
|
||||
/**
|
||||
* Style object for the title element.
|
||||
*/
|
||||
style?: React.ComponentProps<typeof Animated.Text>['style'];
|
||||
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
|
||||
};
|
||||
|
||||
export type TransitionSpec =
|
||||
|
||||
@@ -39,7 +39,6 @@ export default class BorderlessButton extends React.Component<Props> {
|
||||
const { children, style, enabled, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
// @ts-expect-error: the Animated.createAnimatedComponent types don't work properly
|
||||
<AnimatedBaseButton
|
||||
{...rest}
|
||||
onActiveStateChange={this.handleActiveStateChange}
|
||||
|
||||
1
packages/stack/src/vendor/views/GestureHandler.android.tsx
vendored
Normal file
1
packages/stack/src/vendor/views/GestureHandler.android.tsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './GestureHandlerNative';
|
||||
1
packages/stack/src/vendor/views/GestureHandler.ios.tsx
vendored
Normal file
1
packages/stack/src/vendor/views/GestureHandler.ios.tsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export * from './GestureHandlerNative';
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
import MaskedView from '../MaskedView';
|
||||
import { TouchableItem } from '../TouchableItem';
|
||||
import TouchableItem from '../TouchableItem';
|
||||
import useTheme from '../../../utils/useTheme';
|
||||
import type { StackHeaderLeftButtonProps } from '../../types';
|
||||
|
||||
@@ -30,6 +30,7 @@ export default function HeaderBackButton({
|
||||
titleLayout,
|
||||
truncatedLabel = 'Back',
|
||||
accessibilityLabel = label && label !== 'Back' ? `${label}, back` : 'Go back',
|
||||
style,
|
||||
}: Props) {
|
||||
const { dark, colors } = useTheme();
|
||||
|
||||
@@ -160,7 +161,7 @@ export default function HeaderBackButton({
|
||||
delayPressIn={0}
|
||||
onPress={disabled ? undefined : handlePress}
|
||||
pressColor={pressColorAndroid}
|
||||
style={[styles.container, disabled && styles.disabled]}
|
||||
style={[styles.container, disabled && styles.disabled, style]}
|
||||
hitSlop={Platform.select({
|
||||
ios: undefined,
|
||||
default: { top: 16, right: 16, bottom: 16, left: 16 },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
|
||||
import { NavigationContext } from 'react-navigation';
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
|
||||
@@ -28,7 +28,6 @@ export type Props = {
|
||||
scenes: (Scene<Route<string>> | undefined)[];
|
||||
getPreviousScene: (props: {
|
||||
route: Route<string>;
|
||||
index: number;
|
||||
}) => Scene<Route<string>> | undefined;
|
||||
getFocusedRoute: () => Route<string>;
|
||||
onContentHeightChange?: (props: {
|
||||
@@ -57,7 +56,7 @@ export default function HeaderContainer({
|
||||
const parentPreviousScene = React.useContext(PreviousSceneContext);
|
||||
|
||||
return (
|
||||
<View pointerEvents="box-none" style={style}>
|
||||
<Animated.View pointerEvents="box-none" style={style}>
|
||||
{scenes.slice(-3).map((scene, i, self) => {
|
||||
if ((mode === 'screen' && i !== self.length - 1) || !scene) {
|
||||
return null;
|
||||
@@ -75,10 +74,7 @@ export default function HeaderContainer({
|
||||
|
||||
const isFocused = focusedRoute.key === scene.route.key;
|
||||
const previous =
|
||||
getPreviousScene({
|
||||
route: scene.route,
|
||||
index: i,
|
||||
}) ?? parentPreviousScene;
|
||||
getPreviousScene({ route: scene.route }) ?? parentPreviousScene;
|
||||
|
||||
// If the screen is next to a headerless screen, we need to make the header appear static
|
||||
// This makes the header look like it's moving with the screen
|
||||
@@ -128,11 +124,14 @@ export default function HeaderContainer({
|
||||
<View
|
||||
onLayout={
|
||||
onContentHeightChange
|
||||
? (e) =>
|
||||
? (e) => {
|
||||
const { height } = e.nativeEvent.layout;
|
||||
|
||||
onContentHeightChange({
|
||||
route: scene.route,
|
||||
height: e.nativeEvent.layout.height,
|
||||
})
|
||||
height,
|
||||
});
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
pointerEvents={isFocused ? 'box-none' : 'none'}
|
||||
@@ -154,7 +153,7 @@ export default function HeaderContainer({
|
||||
</NavigationContext.Provider>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
</Animated.View>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { NavigationRoute } from 'react-navigation';
|
||||
import HeaderBackButton from './HeaderBackButton';
|
||||
import HeaderBackground from './HeaderBackground';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import memoize from '../../utils/memoize';
|
||||
import type {
|
||||
Layout,
|
||||
@@ -32,11 +33,6 @@ type Props = StackHeaderOptions & {
|
||||
styleInterpolator: StackHeaderStyleInterpolator;
|
||||
};
|
||||
|
||||
type State = {
|
||||
titleLayout?: Layout;
|
||||
leftLabelLayout?: Layout;
|
||||
};
|
||||
|
||||
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
|
||||
Object.keys(styles).forEach((styleProp) => {
|
||||
const value = styles[styleProp];
|
||||
@@ -76,30 +72,35 @@ export const getDefaultHeaderHeight = (
|
||||
return headerHeight + statusBarHeight;
|
||||
};
|
||||
|
||||
export default class HeaderSegment extends React.Component<Props, State> {
|
||||
state: State = {};
|
||||
export default function HeaderSegment(props: Props) {
|
||||
const [leftLabelLayout, setLeftLabelLayout] = React.useState<
|
||||
Layout | undefined
|
||||
>(undefined);
|
||||
|
||||
private handleTitleLayout = (e: LayoutChangeEvent) => {
|
||||
const [titleLayout, setTitleLayout] = React.useState<Layout | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
|
||||
const handleTitleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
this.setState(({ titleLayout }) => {
|
||||
setTitleLayout((titleLayout) => {
|
||||
if (
|
||||
titleLayout &&
|
||||
height === titleLayout.height &&
|
||||
width === titleLayout.width
|
||||
) {
|
||||
return null;
|
||||
return titleLayout;
|
||||
}
|
||||
|
||||
return {
|
||||
titleLayout: { height, width },
|
||||
};
|
||||
return { height, width };
|
||||
});
|
||||
};
|
||||
|
||||
private handleLeftLabelLayout = (e: LayoutChangeEvent) => {
|
||||
const handleLeftLabelLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
const { leftLabelLayout } = this.state;
|
||||
|
||||
if (
|
||||
leftLabelLayout &&
|
||||
@@ -109,10 +110,10 @@ export default class HeaderSegment extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ leftLabelLayout: { height, width } });
|
||||
setLeftLabelLayout({ height, width });
|
||||
};
|
||||
|
||||
private getInterpolatedStyle = memoize(
|
||||
const getInterpolatedStyle = memoize(
|
||||
(
|
||||
styleInterpolator: StackHeaderStyleInterpolator,
|
||||
layout: Layout,
|
||||
@@ -137,257 +138,251 @@ export default class HeaderSegment extends React.Component<Props, State> {
|
||||
})
|
||||
);
|
||||
|
||||
render() {
|
||||
const {
|
||||
scene,
|
||||
layout,
|
||||
insets,
|
||||
title: currentTitle,
|
||||
leftLabel: previousTitle,
|
||||
onGoBack,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft: left = onGoBack
|
||||
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
|
||||
: undefined,
|
||||
headerTransparent,
|
||||
headerTintColor,
|
||||
headerBackground,
|
||||
headerRight: right,
|
||||
headerBackImage: backImage,
|
||||
headerBackTitle: leftLabel,
|
||||
headerBackTitleVisible,
|
||||
headerTruncatedBackTitle: truncatedLabel,
|
||||
headerPressColorAndroid: pressColorAndroid,
|
||||
headerBackAllowFontScaling: backAllowFontScaling,
|
||||
headerTitleAllowFontScaling: titleAllowFontScaling,
|
||||
headerTitleStyle: customTitleStyle,
|
||||
headerBackTitleStyle: customLeftLabelStyle,
|
||||
headerLeftContainerStyle: leftContainerStyle,
|
||||
headerRightContainerStyle: rightContainerStyle,
|
||||
headerTitleContainerStyle: titleContainerStyle,
|
||||
headerStyle: customHeaderStyle,
|
||||
headerStatusBarHeight = insets.top,
|
||||
styleInterpolator,
|
||||
} = this.props;
|
||||
const {
|
||||
scene,
|
||||
layout,
|
||||
insets,
|
||||
title: currentTitle,
|
||||
leftLabel: previousTitle,
|
||||
onGoBack,
|
||||
headerTitle,
|
||||
headerTitleAlign = Platform.select({
|
||||
ios: 'center',
|
||||
default: 'left',
|
||||
}),
|
||||
headerLeft: left = onGoBack
|
||||
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
|
||||
: undefined,
|
||||
headerTransparent,
|
||||
headerTintColor,
|
||||
headerBackground,
|
||||
headerRight: right,
|
||||
headerBackImage: backImage,
|
||||
headerBackTitle: leftLabel,
|
||||
headerBackTitleVisible,
|
||||
headerTruncatedBackTitle: truncatedLabel,
|
||||
headerPressColorAndroid: pressColorAndroid,
|
||||
headerBackAllowFontScaling: backAllowFontScaling,
|
||||
headerTitleAllowFontScaling: titleAllowFontScaling,
|
||||
headerTitleStyle: customTitleStyle,
|
||||
headerBackTitleStyle: customLeftLabelStyle,
|
||||
headerLeftContainerStyle: leftContainerStyle,
|
||||
headerRightContainerStyle: rightContainerStyle,
|
||||
headerTitleContainerStyle: titleContainerStyle,
|
||||
headerStyle: customHeaderStyle,
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
|
||||
styleInterpolator,
|
||||
} = props;
|
||||
|
||||
const { leftLabelLayout, titleLayout } = this.state;
|
||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||
|
||||
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
|
||||
const {
|
||||
height = defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: web support for shadow
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
...unsafeStyles
|
||||
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
|
||||
|
||||
const {
|
||||
height = defaultHeight,
|
||||
minHeight,
|
||||
maxHeight,
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: web support for shadow
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
...unsafeStyles
|
||||
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warnIfHeaderStylesDefined(unsafeStyles);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
warnIfHeaderStylesDefined(unsafeStyles);
|
||||
const safeStyles: ViewStyle = {
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
// @ts-expect-error: boxShadow is only for Web
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
};
|
||||
|
||||
// Setting a property to undefined triggers default style
|
||||
// So we need to filter them out
|
||||
// Users can use `null` instead
|
||||
for (const styleProp in safeStyles) {
|
||||
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
|
||||
if (safeStyles[styleProp] === undefined) {
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete safeStyles[styleProp];
|
||||
}
|
||||
}
|
||||
|
||||
const safeStyles = {
|
||||
backgroundColor,
|
||||
borderBottomColor,
|
||||
borderBottomEndRadius,
|
||||
borderBottomLeftRadius,
|
||||
borderBottomRightRadius,
|
||||
borderBottomStartRadius,
|
||||
borderBottomWidth,
|
||||
borderColor,
|
||||
borderEndColor,
|
||||
borderEndWidth,
|
||||
borderLeftColor,
|
||||
borderLeftWidth,
|
||||
borderRadius,
|
||||
borderRightColor,
|
||||
borderRightWidth,
|
||||
borderStartColor,
|
||||
borderStartWidth,
|
||||
borderStyle,
|
||||
borderTopColor,
|
||||
borderTopEndRadius,
|
||||
borderTopLeftRadius,
|
||||
borderTopRightRadius,
|
||||
borderTopStartRadius,
|
||||
borderTopWidth,
|
||||
borderWidth,
|
||||
boxShadow,
|
||||
elevation,
|
||||
shadowColor,
|
||||
shadowOffset,
|
||||
shadowOpacity,
|
||||
shadowRadius,
|
||||
opacity,
|
||||
transform,
|
||||
};
|
||||
const {
|
||||
titleStyle,
|
||||
leftButtonStyle,
|
||||
leftLabelStyle,
|
||||
rightButtonStyle,
|
||||
backgroundStyle,
|
||||
} = getInterpolatedStyle(
|
||||
styleInterpolator,
|
||||
layout,
|
||||
scene.progress.current,
|
||||
scene.progress.next,
|
||||
titleLayout,
|
||||
previousTitle ? leftLabelLayout : undefined,
|
||||
typeof height === 'number' ? height : defaultHeight
|
||||
);
|
||||
|
||||
// Setting a property to undefined triggers default style
|
||||
// So we need to filter them out
|
||||
// Users can use `null` instead
|
||||
for (const styleProp in safeStyles) {
|
||||
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
|
||||
if (safeStyles[styleProp] === undefined) {
|
||||
// @ts-expect-error
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete safeStyles[styleProp];
|
||||
}
|
||||
}
|
||||
const leftButton = left
|
||||
? left({
|
||||
backImage,
|
||||
pressColorAndroid,
|
||||
allowFontScaling: backAllowFontScaling,
|
||||
onPress: onGoBack,
|
||||
labelVisible: headerBackTitleVisible,
|
||||
label: leftLabel !== undefined ? leftLabel : previousTitle,
|
||||
truncatedLabel,
|
||||
labelStyle: [leftLabelStyle, customLeftLabelStyle],
|
||||
onLabelLayout: handleLeftLabelLayout,
|
||||
screenLayout: layout,
|
||||
titleLayout,
|
||||
tintColor: headerTintColor,
|
||||
canGoBack: Boolean(onGoBack),
|
||||
})
|
||||
: null;
|
||||
|
||||
const {
|
||||
titleStyle,
|
||||
leftButtonStyle,
|
||||
leftLabelStyle,
|
||||
rightButtonStyle,
|
||||
backgroundStyle,
|
||||
} = this.getInterpolatedStyle(
|
||||
styleInterpolator,
|
||||
layout,
|
||||
scene.progress.current,
|
||||
scene.progress.next,
|
||||
titleLayout,
|
||||
previousTitle ? leftLabelLayout : undefined,
|
||||
typeof height === 'number' ? height : defaultHeight
|
||||
);
|
||||
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
|
||||
|
||||
const leftButton = left
|
||||
? left({
|
||||
backImage,
|
||||
pressColorAndroid,
|
||||
allowFontScaling: backAllowFontScaling,
|
||||
onPress: onGoBack,
|
||||
labelVisible: headerBackTitleVisible,
|
||||
label: leftLabel !== undefined ? leftLabel : previousTitle,
|
||||
truncatedLabel,
|
||||
labelStyle: [leftLabelStyle, customLeftLabelStyle],
|
||||
onLabelLayout: this.handleLeftLabelLayout,
|
||||
screenLayout: layout,
|
||||
titleLayout,
|
||||
tintColor: headerTintColor,
|
||||
canGoBack: Boolean(onGoBack),
|
||||
})
|
||||
: null;
|
||||
|
||||
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
|
||||
>
|
||||
{headerBackground ? (
|
||||
headerBackground({ style: safeStyles })
|
||||
) : headerTransparent ? null : (
|
||||
<HeaderBackground style={safeStyles} />
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[{ height, minHeight, maxHeight, opacity, transform }]}
|
||||
>
|
||||
<View
|
||||
pointerEvents="none"
|
||||
style={{ height: headerStatusBarHeight }}
|
||||
/>
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
{leftButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.left,
|
||||
{ left: insets.left },
|
||||
leftButtonStyle,
|
||||
leftContainerStyle,
|
||||
]}
|
||||
>
|
||||
{leftButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
|
||||
>
|
||||
{headerBackground ? (
|
||||
headerBackground({ style: safeStyles })
|
||||
) : headerTransparent ? null : (
|
||||
<HeaderBackground style={safeStyles} />
|
||||
)}
|
||||
</Animated.View>
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[{ height, minHeight, maxHeight, opacity, transform }]}
|
||||
>
|
||||
<View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
|
||||
<View pointerEvents="box-none" style={styles.content}>
|
||||
{leftButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: (leftButton ? 72 : 16) + insets.left,
|
||||
right: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
(leftButton && headerBackTitleVisible !== false
|
||||
? 40
|
||||
: 0) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
titleStyle,
|
||||
titleContainerStyle,
|
||||
styles.left,
|
||||
{ left: insets.left },
|
||||
leftButtonStyle,
|
||||
leftContainerStyle,
|
||||
]}
|
||||
>
|
||||
{headerTitle({
|
||||
children: currentTitle,
|
||||
onLayout: this.handleTitleLayout,
|
||||
allowFontScaling: titleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: customTitleStyle,
|
||||
})}
|
||||
{leftButton}
|
||||
</Animated.View>
|
||||
{rightButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.right,
|
||||
{ right: insets.right },
|
||||
rightButtonStyle,
|
||||
rightContainerStyle,
|
||||
]}
|
||||
>
|
||||
{rightButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
) : null}
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
headerTitleAlign === 'left'
|
||||
? {
|
||||
position: 'absolute',
|
||||
left: (leftButton ? 72 : 16) + insets.left,
|
||||
right: (rightButton ? 72 : 16) + insets.right,
|
||||
}
|
||||
: {
|
||||
marginHorizontal:
|
||||
(leftButton ? 32 : 16) +
|
||||
(leftButton && headerBackTitleVisible !== false
|
||||
? 40
|
||||
: 0) +
|
||||
Math.max(insets.left, insets.right),
|
||||
},
|
||||
titleStyle,
|
||||
titleContainerStyle,
|
||||
]}
|
||||
>
|
||||
{headerTitle({
|
||||
children: currentTitle,
|
||||
onLayout: handleTitleLayout,
|
||||
allowFontScaling: titleAllowFontScaling,
|
||||
tintColor: headerTintColor,
|
||||
style: customTitleStyle,
|
||||
})}
|
||||
</Animated.View>
|
||||
{rightButton ? (
|
||||
<Animated.View
|
||||
pointerEvents="box-none"
|
||||
style={[
|
||||
styles.right,
|
||||
{ right: insets.right },
|
||||
rightButtonStyle,
|
||||
rightContainerStyle,
|
||||
]}
|
||||
>
|
||||
{rightButton}
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</View>
|
||||
</Animated.View>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, StyleSheet, Platform } from 'react-native';
|
||||
import {
|
||||
Animated,
|
||||
StyleSheet,
|
||||
Platform,
|
||||
TextProps,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
} from 'react-native';
|
||||
import useTheme from '../../../utils/useTheme';
|
||||
|
||||
type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & {
|
||||
type Props = Omit<TextProps, 'style'> & {
|
||||
tintColor?: string;
|
||||
children?: string;
|
||||
style?: Animated.WithAnimatedValue<StyleProp<TextStyle>>;
|
||||
};
|
||||
|
||||
export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
|
||||
@@ -13,6 +21,7 @@ export default function HeaderTitle({ tintColor, style, ...rest }: Props) {
|
||||
return (
|
||||
<Animated.Text
|
||||
accessibilityRole="header"
|
||||
aria-level="1"
|
||||
numberOfLines={1}
|
||||
{...rest}
|
||||
style={[
|
||||
|
||||
@@ -17,7 +17,7 @@ export default class KeyboardManager extends React.Component<Props> {
|
||||
|
||||
// Numeric id of the previously focused text input
|
||||
// When a gesture didn't change the tab, we can restore the focused input with this
|
||||
private previouslyFocusedTextInput: number | null = null;
|
||||
private previouslyFocusedTextInput: any | null = null;
|
||||
private startTimestamp: number = 0;
|
||||
private keyboardTimeout: any;
|
||||
|
||||
@@ -35,10 +35,8 @@ export default class KeyboardManager extends React.Component<Props> {
|
||||
|
||||
this.clearKeyboardTimeout();
|
||||
|
||||
// @ts-expect-error: currentlyFocusedInput is pretty new, so not in the type definitions
|
||||
const input = TextInput.State.currentlyFocusedInput
|
||||
? // @ts-expect-error
|
||||
TextInput.State.currentlyFocusedInput()
|
||||
const input: any = TextInput.State.currentlyFocusedInput
|
||||
? TextInput.State.currentlyFocusedInput()
|
||||
: TextInput.State.currentlyFocusedField();
|
||||
|
||||
// When a page change begins, blur the currently focused input
|
||||
|
||||
1
packages/stack/src/vendor/views/MaskedView.android.tsx
vendored
Normal file
1
packages/stack/src/vendor/views/MaskedView.android.tsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './MaskedViewNative';
|
||||
1
packages/stack/src/vendor/views/MaskedView.ios.tsx
vendored
Normal file
1
packages/stack/src/vendor/views/MaskedView.ios.tsx
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './MaskedViewNative';
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* Use a stub for MaskedView on all Platforms that don't support it.
|
||||
*/
|
||||
import type * as React from 'react';
|
||||
|
||||
type Props = {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
/**
|
||||
* The native MaskedView that we explicitly re-export for supported platforms: Android, iOS.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import { UIManager } from 'react-native';
|
||||
|
||||
@@ -10,6 +13,8 @@ type Props = React.ComponentProps<MaskedViewType> & {
|
||||
let RNCMaskedView: MaskedViewType | undefined;
|
||||
|
||||
try {
|
||||
// Add try/catch to support usage even if it's not installed, since it's optional.
|
||||
// Newer versions of Metro will handle it properly.
|
||||
RNCMaskedView = require('@react-native-community/masked-view').default;
|
||||
} catch (e) {
|
||||
// Ignore
|
||||
28
packages/stack/src/vendor/views/Screens.tsx
vendored
28
packages/stack/src/vendor/views/Screens.tsx
vendored
@@ -34,6 +34,9 @@ class WebScreen extends React.Component<
|
||||
|
||||
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
|
||||
|
||||
// @ts-ignore
|
||||
export const shouldUseActivityState = Screens?.shouldUseActivityState;
|
||||
|
||||
export const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
@@ -41,8 +44,11 @@ export const MaybeScreenContainer = ({
|
||||
enabled: boolean;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS !== 'web' && Screens && Screens.screensEnabled()) {
|
||||
return <Screens.ScreenContainer {...rest} />;
|
||||
if (enabled && Platform.OS !== 'web' && Screens?.screensEnabled()) {
|
||||
return (
|
||||
// @ts-ignore
|
||||
<Screens.ScreenContainer enabled={enabled} {...rest} />
|
||||
);
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
@@ -54,17 +60,25 @@ export const MaybeScreen = ({
|
||||
...rest
|
||||
}: ViewProps & {
|
||||
enabled: boolean;
|
||||
active: 0 | 1 | Animated.AnimatedInterpolation;
|
||||
active: 0 | 1 | 2 | Animated.AnimatedInterpolation;
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
if (enabled && Platform.OS === 'web') {
|
||||
// @ts-expect-error: the Animated.createAnimatedComponent types don't work properly
|
||||
return <AnimatedWebScreen active={active} {...rest} />;
|
||||
}
|
||||
|
||||
if (enabled && Screens && Screens.screensEnabled()) {
|
||||
// @ts-expect-error: stackPresentation is incorrectly marked as required
|
||||
return <Screens.Screen active={active} {...rest} />;
|
||||
if (enabled && Screens?.screensEnabled()) {
|
||||
if (shouldUseActivityState) {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
|
||||
<Screens.Screen enabled={enabled} active={active} {...rest} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <View {...rest} />;
|
||||
|
||||
46
packages/stack/src/vendor/views/Stack/Card.tsx
vendored
46
packages/stack/src/vendor/views/Stack/Card.tsx
vendored
@@ -79,6 +79,15 @@ const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
|
||||
|
||||
const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
const hasOpacityStyle = (style: any) => {
|
||||
if (style) {
|
||||
const flattenedStyle = StyleSheet.flatten(style);
|
||||
return flattenedStyle.opacity != null;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export default class Card extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
overlayEnabled: Platform.OS !== 'ios',
|
||||
@@ -97,6 +106,7 @@ export default class Card extends React.Component<Props> {
|
||||
|
||||
componentDidMount() {
|
||||
this.animate({ closing: this.props.closing });
|
||||
this.isCurrentlyMounted = true;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
@@ -115,8 +125,11 @@ export default class Card extends React.Component<Props> {
|
||||
this.inverted.setValue(getInvertedMultiplier(gestureDirection));
|
||||
}
|
||||
|
||||
const toValue = this.getAnimateToValue(this.props);
|
||||
|
||||
if (
|
||||
this.getAnimateToValue(this.props) !== this.getAnimateToValue(prevProps)
|
||||
this.getAnimateToValue(prevProps) !== toValue ||
|
||||
this.lastToValue !== toValue
|
||||
) {
|
||||
// We need to trigger the animation when route was closed
|
||||
// Thr route might have been closed by a `POP` action or by a gesture
|
||||
@@ -128,9 +141,12 @@ export default class Card extends React.Component<Props> {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.isCurrentlyMounted = false;
|
||||
this.handleEndInteraction();
|
||||
}
|
||||
|
||||
private isCurrentlyMounted = false;
|
||||
|
||||
private isClosing = new Animated.Value(FALSE);
|
||||
|
||||
private inverted = new Animated.Value(
|
||||
@@ -148,6 +164,8 @@ export default class Card extends React.Component<Props> {
|
||||
|
||||
private pendingGestureCallback: any;
|
||||
|
||||
private lastToValue: number | undefined;
|
||||
|
||||
private animate = ({
|
||||
closing,
|
||||
velocity,
|
||||
@@ -168,6 +186,8 @@ export default class Card extends React.Component<Props> {
|
||||
closing,
|
||||
});
|
||||
|
||||
this.lastToValue = toValue;
|
||||
|
||||
const spec = closing ? transitionSpec.close : transitionSpec.open;
|
||||
|
||||
const animation =
|
||||
@@ -196,6 +216,11 @@ export default class Card extends React.Component<Props> {
|
||||
} else {
|
||||
onOpen();
|
||||
}
|
||||
|
||||
if (this.isCurrentlyMounted) {
|
||||
// Make sure to re-open screen if it wasn't removed
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -301,10 +326,13 @@ export default class Card extends React.Component<Props> {
|
||||
if (closing) {
|
||||
// We call onClose with a delay to make sure that the animation has already started
|
||||
// This will make sure that the state update caused by this doesn't affect start of animation
|
||||
this.pendingGestureCallback = (setTimeout(
|
||||
onClose,
|
||||
32
|
||||
) as any) as number;
|
||||
this.pendingGestureCallback = (setTimeout(() => {
|
||||
onClose();
|
||||
|
||||
// Trigger an update after we dispatch the action to remove the screen
|
||||
// This will make sure that we check if the screen didn't get removed so we can cancel the animation
|
||||
this.forceUpdate();
|
||||
}, 32) as any) as number;
|
||||
}
|
||||
|
||||
onGestureEnd?.();
|
||||
@@ -489,9 +517,10 @@ export default class Card extends React.Component<Props> {
|
||||
: undefined;
|
||||
|
||||
const { backgroundColor } = StyleSheet.flatten(contentStyle || {});
|
||||
const isTransparent = backgroundColor
|
||||
? Color(backgroundColor).alpha() === 0
|
||||
: false;
|
||||
const isTransparent =
|
||||
typeof backgroundColor === 'string'
|
||||
? Color(backgroundColor).alpha() === 0
|
||||
: false;
|
||||
|
||||
return (
|
||||
<CardAnimationContext.Provider value={animationContext}>
|
||||
@@ -513,6 +542,7 @@ export default class Card extends React.Component<Props> {
|
||||
</View>
|
||||
) : null}
|
||||
<Animated.View
|
||||
needsOffscreenAlphaCompositing={hasOpacityStyle(containerStyle)}
|
||||
style={[styles.container, containerStyle, customContainerStyle]}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
|
||||
@@ -27,13 +27,14 @@ type Props = TransitionPreset & {
|
||||
safeAreaInsetRight: number;
|
||||
safeAreaInsetBottom: number;
|
||||
safeAreaInsetLeft: number;
|
||||
cardOverlay?: (props: { style: StyleProp<ViewStyle> }) => React.ReactNode;
|
||||
cardOverlay?: (props: {
|
||||
style: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
|
||||
}) => React.ReactNode;
|
||||
cardOverlayEnabled?: boolean;
|
||||
cardShadowEnabled?: boolean;
|
||||
cardStyle?: StyleProp<ViewStyle>;
|
||||
getPreviousScene: (props: {
|
||||
route: Route<string>;
|
||||
index: number;
|
||||
}) => Scene<Route<string>> | undefined;
|
||||
getFocusedRoute: () => Route<string>;
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
@@ -48,6 +49,9 @@ type Props = TransitionPreset & {
|
||||
onPageChangeStart?: () => void;
|
||||
onPageChangeConfirm?: () => void;
|
||||
onPageChangeCancel?: () => void;
|
||||
onGestureStart?: (props: { route: Route<string> }) => void;
|
||||
onGestureEnd?: (props: { route: Route<string> }) => void;
|
||||
onGestureCancel?: (props: { route: Route<string> }) => void;
|
||||
gestureEnabled?: boolean;
|
||||
gestureResponseDistance?: {
|
||||
vertical?: number;
|
||||
@@ -63,6 +67,7 @@ type Props = TransitionPreset & {
|
||||
route: Route<string>;
|
||||
height: number;
|
||||
}) => void;
|
||||
isParentHeaderShown: boolean;
|
||||
};
|
||||
|
||||
const EPSILON = 0.1;
|
||||
@@ -90,6 +95,7 @@ function CardContainer({
|
||||
hasAbsoluteHeader,
|
||||
headerHeight,
|
||||
onHeaderHeightChange,
|
||||
isParentHeaderShown,
|
||||
index,
|
||||
layout,
|
||||
onCloseRoute,
|
||||
@@ -97,6 +103,9 @@ function CardContainer({
|
||||
onPageChangeCancel,
|
||||
onPageChangeConfirm,
|
||||
onPageChangeStart,
|
||||
onGestureCancel,
|
||||
onGestureEnd,
|
||||
onGestureStart,
|
||||
onTransitionEnd,
|
||||
onTransitionStart,
|
||||
renderHeader,
|
||||
@@ -122,6 +131,20 @@ function CardContainer({
|
||||
onCloseRoute({ route: scene.route });
|
||||
};
|
||||
|
||||
const handleGestureBegin = () => {
|
||||
onPageChangeStart?.();
|
||||
onGestureStart?.({ route: scene.route });
|
||||
};
|
||||
|
||||
const handleGestureCanceled = () => {
|
||||
onPageChangeCancel?.();
|
||||
onGestureCancel?.({ route: scene.route });
|
||||
};
|
||||
|
||||
const handleGestureEnd = () => {
|
||||
onGestureEnd?.({ route: scene.route });
|
||||
};
|
||||
|
||||
const handleTransitionStart = ({ closing }: { closing: boolean }) => {
|
||||
if (active && closing) {
|
||||
onPageChangeConfirm?.();
|
||||
@@ -161,9 +184,8 @@ function CardContainer({
|
||||
};
|
||||
}, [pointerEvents, scene.progress.next]);
|
||||
|
||||
const isParentHeaderShown = React.useContext(HeaderShownContext);
|
||||
const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false;
|
||||
const previousScene = getPreviousScene({ route: scene.route, index });
|
||||
const previousScene = getPreviousScene({ route: scene.route });
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -181,8 +203,9 @@ function CardContainer({
|
||||
overlayEnabled={cardOverlayEnabled}
|
||||
shadowEnabled={cardShadowEnabled}
|
||||
onTransitionStart={handleTransitionStart}
|
||||
onGestureBegin={onPageChangeStart}
|
||||
onGestureCanceled={onPageChangeCancel}
|
||||
onGestureBegin={handleGestureBegin}
|
||||
onGestureCanceled={handleGestureCanceled}
|
||||
onGestureEnd={handleGestureEnd}
|
||||
gestureEnabled={gestureEnabled}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
gestureVelocityImpact={gestureVelocityImpact}
|
||||
|
||||
490
packages/stack/src/vendor/views/Stack/CardStack.tsx
vendored
490
packages/stack/src/vendor/views/Stack/CardStack.tsx
vendored
@@ -9,7 +9,11 @@ import {
|
||||
import type { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import type { NavigationState as StackNavigationState } from 'react-navigation';
|
||||
|
||||
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
|
||||
import {
|
||||
MaybeScreenContainer,
|
||||
MaybeScreen,
|
||||
shouldUseActivityState,
|
||||
} from '../Screens';
|
||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||
import CardContainer from './CardContainer';
|
||||
@@ -19,7 +23,6 @@ import {
|
||||
} from '../../TransitionConfigs/TransitionPresets';
|
||||
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
|
||||
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
import getDistanceForDirection from '../../utils/getDistanceForDirection';
|
||||
import type {
|
||||
Layout,
|
||||
@@ -53,6 +56,7 @@ type Props = {
|
||||
renderHeader: (props: HeaderContainerProps) => React.ReactNode;
|
||||
renderScene: (props: { route: Route<string> }) => React.ReactNode;
|
||||
headerMode: StackHeaderMode;
|
||||
isParentHeaderShown: boolean;
|
||||
onTransitionStart: (
|
||||
props: { route: Route<string> },
|
||||
closing: boolean
|
||||
@@ -61,6 +65,10 @@ type Props = {
|
||||
onPageChangeStart?: () => void;
|
||||
onPageChangeConfirm?: () => void;
|
||||
onPageChangeCancel?: () => void;
|
||||
onGestureStart?: (props: { route: Route<string> }) => void;
|
||||
onGestureEnd?: (props: { route: Route<string> }) => void;
|
||||
onGestureCancel?: (props: { route: Route<string> }) => void;
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
type State = {
|
||||
@@ -74,11 +82,16 @@ type State = {
|
||||
|
||||
const EPSILON = 0.01;
|
||||
|
||||
const STATE_INACTIVE = 0;
|
||||
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
|
||||
const STATE_ON_TOP = 2;
|
||||
|
||||
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
||||
|
||||
const getHeaderHeights = (
|
||||
routes: Route<string>[],
|
||||
insets: EdgeInsets,
|
||||
isParentHeaderShown: boolean,
|
||||
descriptors: StackDescriptorMap,
|
||||
layout: Layout,
|
||||
previous: Record<string, number>
|
||||
@@ -95,7 +108,9 @@ const getHeaderHeights = (
|
||||
...options.safeAreaInsets,
|
||||
};
|
||||
|
||||
const { headerStatusBarHeight = safeAreaInsets.top } = options;
|
||||
const {
|
||||
headerStatusBarHeight = isParentHeaderShown ? 0 : safeAreaInsets.top,
|
||||
} = options;
|
||||
|
||||
acc[curr.key] =
|
||||
typeof height === 'number'
|
||||
@@ -258,6 +273,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
headerHeights: getHeaderHeights(
|
||||
props.routes,
|
||||
props.insets,
|
||||
props.isParentHeaderShown,
|
||||
state.descriptors,
|
||||
state.layout,
|
||||
state.headerHeights
|
||||
@@ -300,6 +316,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
headerHeights: getHeaderHeights(
|
||||
props.routes,
|
||||
props.insets,
|
||||
props.isParentHeaderShown,
|
||||
state.descriptors,
|
||||
layout,
|
||||
state.headerHeights
|
||||
@@ -337,31 +354,21 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
return state.routes[state.index];
|
||||
};
|
||||
|
||||
private getPreviousScene = ({
|
||||
route,
|
||||
index,
|
||||
}: {
|
||||
route: Route<string>;
|
||||
index: number;
|
||||
}) => {
|
||||
const previousRoute = this.props.getPreviousRoute({ route });
|
||||
private getPreviousScene = ({ route }: { route: Route<string> }) => {
|
||||
const { getPreviousRoute } = this.props;
|
||||
const { scenes } = this.state;
|
||||
|
||||
let previous: Scene<Route<string>> | undefined;
|
||||
const previousRoute = getPreviousRoute({ route });
|
||||
|
||||
if (previousRoute) {
|
||||
// The previous scene will be shortly before the current scene in the array
|
||||
// So loop back from current index to avoid looping over the full array
|
||||
for (let j = index - 1; j >= 0; j--) {
|
||||
const s = this.state.scenes[j];
|
||||
const previousScene = scenes.find(
|
||||
(scene) => scene.route.key === previousRoute.key
|
||||
);
|
||||
|
||||
if (s && s.route.key === previousRoute.key) {
|
||||
previous = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return previousScene;
|
||||
}
|
||||
|
||||
return previous;
|
||||
return undefined;
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -378,11 +385,18 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
renderHeader,
|
||||
renderScene,
|
||||
headerMode,
|
||||
isParentHeaderShown,
|
||||
onTransitionStart,
|
||||
onTransitionEnd,
|
||||
onPageChangeStart,
|
||||
onPageChangeConfirm,
|
||||
onPageChangeCancel,
|
||||
onGestureStart,
|
||||
onGestureEnd,
|
||||
onGestureCancel,
|
||||
detachInactiveScreens = Platform.OS === 'ios'
|
||||
? false // Disable `react-native-screens` on iOS by default since it's buggy
|
||||
: shouldUseActivityState || mode !== 'modal', // Enable on new versions of screens or for non modals on older versions
|
||||
} = this.props;
|
||||
|
||||
const { scenes, layout, gestures, headerHeights } = this.state;
|
||||
@@ -390,6 +404,7 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
const focusedRoute = state.routes[state.index];
|
||||
const focusedDescriptor = descriptors[focusedRoute.key];
|
||||
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
|
||||
const focusedHeaderHeight = headerHeights[focusedRoute.key];
|
||||
|
||||
let defaultTransitionPreset =
|
||||
mode === 'modal' ? ModalTransition : DefaultTransition;
|
||||
@@ -408,209 +423,250 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
left = insets.left,
|
||||
} = focusedOptions.safeAreaInsets || {};
|
||||
|
||||
// Screens is buggy on iOS and web, so we only enable it on Android
|
||||
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
||||
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
||||
let activeScreensLimit = 1;
|
||||
|
||||
for (let i = scenes.length - 1; i >= 0; i--) {
|
||||
const {
|
||||
// By default, we don't want to detach the previous screen of the active one for modals
|
||||
detachPreviousScreen = mode === 'modal'
|
||||
? i !== scenes.length - 1
|
||||
: true,
|
||||
} = scenes[i].descriptor.options;
|
||||
|
||||
if (detachPreviousScreen === false) {
|
||||
activeScreensLimit++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const isFloatHeaderAbsolute =
|
||||
headerMode === 'float'
|
||||
? this.state.scenes.slice(-2).some((scene) => {
|
||||
const { descriptor } = scene;
|
||||
const options = descriptor ? descriptor.options : {};
|
||||
const {
|
||||
headerTransparent,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
} = options;
|
||||
|
||||
if (headerTransparent || headerShown === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
: false;
|
||||
|
||||
const floatingHeader =
|
||||
headerMode === 'float' ? (
|
||||
<React.Fragment key="header">
|
||||
{renderHeader({
|
||||
mode: 'float',
|
||||
layout,
|
||||
insets: { top, right, bottom, left },
|
||||
scenes,
|
||||
getPreviousScene: this.getPreviousScene,
|
||||
getFocusedRoute: this.getFocusedRoute,
|
||||
onContentHeightChange: this.handleHeaderLayout,
|
||||
gestureDirection:
|
||||
focusedOptions.gestureDirection !== undefined
|
||||
? focusedOptions.gestureDirection
|
||||
: defaultTransitionPreset.gestureDirection,
|
||||
styleInterpolator:
|
||||
focusedOptions.headerStyleInterpolator !== undefined
|
||||
? focusedOptions.headerStyleInterpolator
|
||||
: defaultTransitionPreset.headerStyleInterpolator,
|
||||
style: [
|
||||
styles.floating,
|
||||
isFloatHeaderAbsolute && [
|
||||
// Without this, the header buttons won't be touchable on Android when headerTransparent: true
|
||||
{ height: focusedHeaderHeight },
|
||||
styles.absolute,
|
||||
],
|
||||
],
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<HeaderShownContext.Consumer>
|
||||
{(isParentHeaderShown) => {
|
||||
const isFloatHeaderAbsolute =
|
||||
headerMode === 'float'
|
||||
? this.state.scenes.slice(-2).some((scene) => {
|
||||
const { descriptor } = scene;
|
||||
const options = descriptor ? descriptor.options : {};
|
||||
const {
|
||||
headerTransparent,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
} = options;
|
||||
<React.Fragment>
|
||||
{isFloatHeaderAbsolute ? null : floatingHeader}
|
||||
<MaybeScreenContainer
|
||||
enabled={detachInactiveScreens}
|
||||
style={styles.container}
|
||||
onLayout={this.handleLayout}
|
||||
>
|
||||
{routes.map((route, index, self) => {
|
||||
const focused = focusedRoute.key === route.key;
|
||||
const gesture = gestures[route.key];
|
||||
const scene = scenes[index];
|
||||
|
||||
if (headerTransparent || headerShown === false) {
|
||||
return true;
|
||||
}
|
||||
// For the screens that shouldn't be active, the value is 0
|
||||
// For those that should be active, but are not the top screen, the value is 1
|
||||
// For those on top of the stack and with interaction enabled, the value is 2
|
||||
// For the old implementation, it stays the same it was
|
||||
let isScreenActive: Animated.AnimatedInterpolation | 2 | 1 | 0 = 1;
|
||||
|
||||
return false;
|
||||
})
|
||||
: false;
|
||||
if (shouldUseActivityState) {
|
||||
if (index < self.length - activeScreensLimit - 1) {
|
||||
// screen should be inactive because it is too deep in the stack
|
||||
isScreenActive = STATE_INACTIVE;
|
||||
} else {
|
||||
const sceneForActivity = scenes[self.length - 1];
|
||||
const outputValue =
|
||||
index === self.length - 1
|
||||
? STATE_ON_TOP // the screen is on top after the transition
|
||||
: index >= self.length - activeScreensLimit
|
||||
? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
|
||||
: STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
|
||||
isScreenActive = sceneForActivity
|
||||
? sceneForActivity.progress.current.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, outputValue],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: STATE_TRANSITIONING_OR_BELOW_TOP;
|
||||
}
|
||||
} else {
|
||||
isScreenActive = scene.progress.next
|
||||
? scene.progress.next.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: 1;
|
||||
}
|
||||
|
||||
const floatingHeader =
|
||||
headerMode === 'float' ? (
|
||||
<React.Fragment key="header">
|
||||
{renderHeader({
|
||||
mode: 'float',
|
||||
layout,
|
||||
insets: { top, right, bottom, left },
|
||||
scenes,
|
||||
getPreviousScene: this.getPreviousScene,
|
||||
getFocusedRoute: this.getFocusedRoute,
|
||||
onContentHeightChange: this.handleHeaderLayout,
|
||||
gestureDirection:
|
||||
focusedOptions.gestureDirection !== undefined
|
||||
? focusedOptions.gestureDirection
|
||||
: defaultTransitionPreset.gestureDirection,
|
||||
styleInterpolator:
|
||||
focusedOptions.headerStyleInterpolator !== undefined
|
||||
? focusedOptions.headerStyleInterpolator
|
||||
: defaultTransitionPreset.headerStyleInterpolator,
|
||||
style: [
|
||||
styles.floating,
|
||||
isFloatHeaderAbsolute && styles.absolute,
|
||||
],
|
||||
})}
|
||||
</React.Fragment>
|
||||
) : null;
|
||||
const {
|
||||
safeAreaInsets,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
headerTransparent,
|
||||
cardShadowEnabled,
|
||||
cardOverlayEnabled,
|
||||
cardOverlay,
|
||||
cardStyle,
|
||||
animationEnabled,
|
||||
gestureResponseDistance,
|
||||
gestureVelocityImpact,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = scene.descriptor
|
||||
? scene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isFloatHeaderAbsolute ? null : floatingHeader}
|
||||
<MaybeScreenContainer
|
||||
enabled={isScreensEnabled}
|
||||
style={styles.container}
|
||||
onLayout={this.handleLayout}
|
||||
let transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
|
||||
// When a screen is not the last, it should use next screen's transition config
|
||||
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
||||
// For example combining a slide and a modal transition would look wrong otherwise
|
||||
// With this approach, combining different transition styles in the same navigator mostly looks right
|
||||
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
||||
// but majority of the transitions look alright
|
||||
if (index !== self.length - 1) {
|
||||
const nextScene = scenes[index + 1];
|
||||
|
||||
if (nextScene) {
|
||||
const {
|
||||
animationEnabled,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = nextScene.descriptor
|
||||
? nextScene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
top: safeAreaInsetTop = insets.top,
|
||||
right: safeAreaInsetRight = insets.right,
|
||||
bottom: safeAreaInsetBottom = insets.bottom,
|
||||
left: safeAreaInsetLeft = insets.left,
|
||||
} = safeAreaInsets || {};
|
||||
|
||||
const headerHeight =
|
||||
headerMode !== 'none' && headerShown !== false
|
||||
? headerHeights[route.key]
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
enabled={detachInactiveScreens}
|
||||
active={isScreenActive}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
{routes.map((route, index, self) => {
|
||||
const focused = focusedRoute.key === route.key;
|
||||
const gesture = gestures[route.key];
|
||||
const scene = scenes[index];
|
||||
|
||||
const isScreenActive = scene.progress.next
|
||||
? scene.progress.next.interpolate({
|
||||
inputRange: [0, 1 - EPSILON, 1],
|
||||
outputRange: [1, 1, 0],
|
||||
extrapolate: 'clamp',
|
||||
})
|
||||
: 1;
|
||||
|
||||
const {
|
||||
safeAreaInsets,
|
||||
headerShown = isParentHeaderShown === false,
|
||||
headerTransparent,
|
||||
cardShadowEnabled,
|
||||
cardOverlayEnabled,
|
||||
cardOverlay,
|
||||
cardStyle,
|
||||
animationEnabled,
|
||||
gestureResponseDistance,
|
||||
gestureVelocityImpact,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = scene.descriptor
|
||||
? scene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
let transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
|
||||
// When a screen is not the last, it should use next screen's transition config
|
||||
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
|
||||
// For example combining a slide and a modal transition would look wrong otherwise
|
||||
// With this approach, combining different transition styles in the same navigator mostly looks right
|
||||
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
|
||||
// but majority of the transitions look alright
|
||||
if (index !== self.length - 1) {
|
||||
const nextScene = scenes[index + 1];
|
||||
|
||||
if (nextScene) {
|
||||
const {
|
||||
animationEnabled,
|
||||
gestureDirection = defaultTransitionPreset.gestureDirection,
|
||||
transitionSpec = defaultTransitionPreset.transitionSpec,
|
||||
cardStyleInterpolator = animationEnabled === false
|
||||
? forNoAnimationCard
|
||||
: defaultTransitionPreset.cardStyleInterpolator,
|
||||
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
|
||||
} = nextScene.descriptor
|
||||
? nextScene.descriptor.options
|
||||
: ({} as StackNavigationOptions);
|
||||
|
||||
transitionConfig = {
|
||||
gestureDirection,
|
||||
transitionSpec,
|
||||
cardStyleInterpolator,
|
||||
headerStyleInterpolator,
|
||||
};
|
||||
}
|
||||
<CardContainer
|
||||
index={index}
|
||||
active={index === self.length - 1}
|
||||
focused={focused}
|
||||
closing={closingRouteKeys.includes(route.key)}
|
||||
layout={layout}
|
||||
gesture={gesture}
|
||||
scene={scene}
|
||||
safeAreaInsetTop={safeAreaInsetTop}
|
||||
safeAreaInsetRight={safeAreaInsetRight}
|
||||
safeAreaInsetBottom={safeAreaInsetBottom}
|
||||
safeAreaInsetLeft={safeAreaInsetLeft}
|
||||
cardOverlay={cardOverlay}
|
||||
cardOverlayEnabled={cardOverlayEnabled}
|
||||
cardShadowEnabled={cardShadowEnabled}
|
||||
cardStyle={cardStyle}
|
||||
onPageChangeStart={onPageChangeStart}
|
||||
onPageChangeConfirm={onPageChangeConfirm}
|
||||
onPageChangeCancel={onPageChangeCancel}
|
||||
onGestureStart={onGestureStart}
|
||||
onGestureCancel={onGestureCancel}
|
||||
onGestureEnd={onGestureEnd}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
headerHeight={headerHeight}
|
||||
isParentHeaderShown={isParentHeaderShown}
|
||||
onHeaderHeightChange={this.handleHeaderLayout}
|
||||
getPreviousScene={this.getPreviousScene}
|
||||
getFocusedRoute={this.getFocusedRoute}
|
||||
mode={mode}
|
||||
headerMode={headerMode}
|
||||
headerShown={headerShown}
|
||||
hasAbsoluteHeader={
|
||||
isFloatHeaderAbsolute && !headerTransparent
|
||||
}
|
||||
|
||||
const {
|
||||
top: safeAreaInsetTop = insets.top,
|
||||
right: safeAreaInsetRight = insets.right,
|
||||
bottom: safeAreaInsetBottom = insets.bottom,
|
||||
left: safeAreaInsetLeft = insets.left,
|
||||
} = safeAreaInsets || {};
|
||||
|
||||
const headerHeight =
|
||||
headerMode !== 'none' && headerShown !== false
|
||||
? headerHeights[route.key]
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<MaybeScreen
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
enabled={isScreensEnabled}
|
||||
active={isScreenActive}
|
||||
pointerEvents="box-none"
|
||||
>
|
||||
<CardContainer
|
||||
index={index}
|
||||
active={index === self.length - 1}
|
||||
focused={focused}
|
||||
closing={closingRouteKeys.includes(route.key)}
|
||||
layout={layout}
|
||||
gesture={gesture}
|
||||
scene={scene}
|
||||
safeAreaInsetTop={safeAreaInsetTop}
|
||||
safeAreaInsetRight={safeAreaInsetRight}
|
||||
safeAreaInsetBottom={safeAreaInsetBottom}
|
||||
safeAreaInsetLeft={safeAreaInsetLeft}
|
||||
cardOverlay={cardOverlay}
|
||||
cardOverlayEnabled={cardOverlayEnabled}
|
||||
cardShadowEnabled={cardShadowEnabled}
|
||||
cardStyle={cardStyle}
|
||||
onPageChangeStart={onPageChangeStart}
|
||||
onPageChangeConfirm={onPageChangeConfirm}
|
||||
onPageChangeCancel={onPageChangeCancel}
|
||||
gestureResponseDistance={gestureResponseDistance}
|
||||
headerHeight={headerHeight}
|
||||
onHeaderHeightChange={this.handleHeaderLayout}
|
||||
getPreviousScene={this.getPreviousScene}
|
||||
getFocusedRoute={this.getFocusedRoute}
|
||||
mode={mode}
|
||||
headerMode={headerMode}
|
||||
headerShown={headerShown}
|
||||
hasAbsoluteHeader={
|
||||
isFloatHeaderAbsolute && !headerTransparent
|
||||
}
|
||||
renderHeader={renderHeader}
|
||||
renderScene={renderScene}
|
||||
onOpenRoute={onOpenRoute}
|
||||
onCloseRoute={onCloseRoute}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
gestureEnabled={
|
||||
index !== 0 && getGesturesEnabled({ route })
|
||||
}
|
||||
gestureVelocityImpact={gestureVelocityImpact}
|
||||
{...transitionConfig}
|
||||
/>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
{isFloatHeaderAbsolute ? floatingHeader : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}}
|
||||
</HeaderShownContext.Consumer>
|
||||
renderHeader={renderHeader}
|
||||
renderScene={renderScene}
|
||||
onOpenRoute={onOpenRoute}
|
||||
onCloseRoute={onCloseRoute}
|
||||
onTransitionStart={onTransitionStart}
|
||||
onTransitionEnd={onTransitionEnd}
|
||||
gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
|
||||
gestureVelocityImpact={gestureVelocityImpact}
|
||||
{...transitionConfig}
|
||||
/>
|
||||
</MaybeScreen>
|
||||
);
|
||||
})}
|
||||
</MaybeScreenContainer>
|
||||
{isFloatHeaderAbsolute ? floatingHeader : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import type {
|
||||
StackNavigationConfig,
|
||||
StackDescriptorMap,
|
||||
} from '../../types';
|
||||
import HeaderShownContext from '../../utils/HeaderShownContext';
|
||||
|
||||
type Props = StackNavigationConfig & {
|
||||
state: StackNavigationState;
|
||||
@@ -430,7 +431,19 @@ export default class StackView extends React.Component<Props, State> {
|
||||
const descriptor =
|
||||
descriptors[route.key] || this.state.descriptors[route.key];
|
||||
|
||||
descriptor?.options.onTransitionEnd?.({ closing });
|
||||
descriptor?.options.onTransitionStart?.({ closing });
|
||||
};
|
||||
|
||||
private handleGestureStart = () => {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
private handleGestureEnd = () => {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
private handleGestureCancel = () => {
|
||||
// Do nothing
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -463,26 +476,34 @@ export default class StackView extends React.Component<Props, State> {
|
||||
{(insets) => (
|
||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||
{(props) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
<HeaderShownContext.Consumer>
|
||||
{(isParentHeaderShown) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
isParentHeaderShown={isParentHeaderShown}
|
||||
getPreviousRoute={this.getPreviousRoute}
|
||||
getGesturesEnabled={this.getGesturesEnabled}
|
||||
routes={routes}
|
||||
openingRouteKeys={openingRouteKeys}
|
||||
closingRouteKeys={closingRouteKeys}
|
||||
onOpenRoute={this.handleOpenRoute}
|
||||
onCloseRoute={this.handleCloseRoute}
|
||||
onTransitionStart={this.handleTransitionStart}
|
||||
onTransitionEnd={this.handleTransitionEnd}
|
||||
renderHeader={this.renderHeader}
|
||||
renderScene={this.renderScene}
|
||||
headerMode={headerMode}
|
||||
state={state}
|
||||
descriptors={descriptors}
|
||||
onGestureStart={this.handleGestureStart}
|
||||
onGestureEnd={this.handleGestureEnd}
|
||||
onGestureCancel={this.handleGestureCancel}
|
||||
{...rest}
|
||||
{...props}
|
||||
/>
|
||||
)}
|
||||
</HeaderShownContext.Consumer>
|
||||
)}
|
||||
</KeyboardManager>
|
||||
)}
|
||||
|
||||
50
packages/stack/src/vendor/views/TouchableItem.ios.tsx
vendored
Normal file
50
packages/stack/src/vendor/views/TouchableItem.ios.tsx
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
import * as React from 'react';
|
||||
import { Animated, Platform } from 'react-native';
|
||||
import { BaseButton } from 'react-native-gesture-handler';
|
||||
|
||||
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
|
||||
|
||||
type Props = React.ComponentProps<typeof BaseButton> & {
|
||||
activeOpacity: number;
|
||||
};
|
||||
|
||||
const useNativeDriver = Platform.OS !== 'web';
|
||||
|
||||
export default class TouchableItem extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
activeOpacity: 0.3,
|
||||
borderless: true,
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
private opacity = new Animated.Value(1);
|
||||
|
||||
private handleActiveStateChange = (active: boolean) => {
|
||||
Animated.spring(this.opacity, {
|
||||
stiffness: 1000,
|
||||
damping: 500,
|
||||
mass: 3,
|
||||
overshootClamping: true,
|
||||
restDisplacementThreshold: 0.01,
|
||||
restSpeedThreshold: 0.01,
|
||||
toValue: active ? this.props.activeOpacity : 1,
|
||||
useNativeDriver,
|
||||
}).start();
|
||||
|
||||
this.props.onActiveStateChange?.(active);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, style, enabled, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<AnimatedBaseButton
|
||||
{...rest}
|
||||
onActiveStateChange={this.handleActiveStateChange}
|
||||
style={[style, enabled && { opacity: this.opacity }]}
|
||||
>
|
||||
{children}
|
||||
</AnimatedBaseButton>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* TouchableItem renders a touchable that looks native on both iOS and Android.
|
||||
*
|
||||
* It provides an abstraction on top of TouchableNativeFeedback and
|
||||
* TouchableOpacity.
|
||||
*
|
||||
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
|
||||
* of TouchableNativeFeedback.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
|
||||
import BorderlessButton from './BorderlessButton';
|
||||
|
||||
export type Props = ViewProps & {
|
||||
pressColor: string;
|
||||
disabled?: boolean;
|
||||
borderless?: boolean;
|
||||
delayPressIn?: number;
|
||||
onPress?: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
|
||||
export class TouchableItem extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
borderless: false,
|
||||
pressColor: 'rgba(0, 0, 0, .32)',
|
||||
};
|
||||
|
||||
render() {
|
||||
/*
|
||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
||||
* therefore only enable it on Android Lollipop and above.
|
||||
*
|
||||
* All touchables on Android should have the ripple effect according to
|
||||
* platform design guidelines.
|
||||
* We need to pass the background prop to specify a borderless ripple effect.
|
||||
*/
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
||||
) {
|
||||
const { style, pressColor, borderless, children, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<TouchableNativeFeedback
|
||||
{...rest}
|
||||
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
|
||||
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
|
||||
>
|
||||
<View style={style}>{React.Children.only(children)}</View>
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
} else if (Platform.OS === 'ios') {
|
||||
return (
|
||||
<BorderlessButton
|
||||
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
|
||||
disallowInterruption
|
||||
enabled={!this.props.disabled}
|
||||
{...this.props}
|
||||
>
|
||||
{this.props.children}
|
||||
</BorderlessButton>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableOpacity {...this.props}>
|
||||
{this.props.children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,63 @@
|
||||
import { TouchableOpacity } from 'react-native';
|
||||
/**
|
||||
* TouchableItem provides an abstraction on top of TouchableNativeFeedback and
|
||||
* TouchableOpacity to handle platform differences.
|
||||
*
|
||||
* On Android, you can pass the props of TouchableNativeFeedback.
|
||||
* On other platforms, you can pass the props of TouchableOpacity.
|
||||
*/
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Platform,
|
||||
TouchableNativeFeedback,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewProps,
|
||||
} from 'react-native';
|
||||
|
||||
export const TouchableItem = (TouchableOpacity as any) as typeof import('./TouchableItem.native').TouchableItem;
|
||||
export type Props = ViewProps & {
|
||||
pressColor?: string;
|
||||
disabled?: boolean;
|
||||
borderless?: boolean;
|
||||
delayPressIn?: number;
|
||||
onPress?: () => void;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ANDROID_VERSION_LOLLIPOP = 21;
|
||||
|
||||
export default function TouchableItem({
|
||||
borderless = false,
|
||||
pressColor = 'rgba(0, 0, 0, .32)',
|
||||
style,
|
||||
children,
|
||||
...rest
|
||||
}: Props) {
|
||||
/*
|
||||
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
|
||||
* therefore only enable it on Android Lollipop and above.
|
||||
*
|
||||
* All touchables on Android should have the ripple effect according to
|
||||
* platform design guidelines.
|
||||
* We need to pass the background prop to specify a borderless ripple effect.
|
||||
*/
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
Platform.Version >= ANDROID_VERSION_LOLLIPOP
|
||||
) {
|
||||
return (
|
||||
<TouchableNativeFeedback
|
||||
{...rest}
|
||||
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
|
||||
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
|
||||
>
|
||||
<View style={style}>{React.Children.only(children)}</View>
|
||||
</TouchableNativeFeedback>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<TouchableOpacity style={style} {...rest}>
|
||||
{children}
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [2.10.0](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.2...react-navigation-tabs@2.10.0) (2020-10-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use react-native-iphone-x-helper in bottom-tabs navigator ([ebe70f5](https://github.com/react-navigation/tabs/commit/ebe70f51fbb8d892be9c44b0c226947684c24338))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add detachInactiveScreens prop for bottom tabs and drawer ([#8816](https://github.com/react-navigation/tabs/issues/8816)) ([707bea3](https://github.com/react-navigation/tabs/commit/707bea3cba1aa1f7b1eb5884dd1d129dcc2bbdc2))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.2](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.1...react-navigation-tabs@2.9.2) (2020-10-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.9.1](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.0...react-navigation-tabs@2.9.1) (2020-09-24)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [2.9.0](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.8.13...react-navigation-tabs@2.9.0) (2020-06-25)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-tabs",
|
||||
"version": "2.9.0",
|
||||
"version": "2.10.0",
|
||||
"description": "Tab Navigation components for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"module": "lib/module/index.js",
|
||||
@@ -42,22 +42,22 @@
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"react-native-safe-area-view": "^0.14.9",
|
||||
"react-native-tab-view": "^2.11.0"
|
||||
"react-native-iphone-x-helper": "^1.3.0",
|
||||
"react-native-tab-view": "^2.15.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.2.0",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-native": "^0.63.30",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-native-tab-view": "^2.13.0",
|
||||
"react-navigation": "^4.4.0",
|
||||
"typescript": "^3.9.5"
|
||||
"react-navigation": "^4.4.3",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
AccessibilityRole,
|
||||
AccessibilityStates,
|
||||
} from 'react-native';
|
||||
import { View, StyleSheet, AccessibilityRole } from 'react-native';
|
||||
import { NavigationRoute } from 'react-navigation';
|
||||
|
||||
import { ScreenContainer } from 'react-native-screens';
|
||||
@@ -25,6 +20,7 @@ type Config = {
|
||||
lazy?: boolean;
|
||||
tabBarComponent?: React.ComponentType<any>;
|
||||
tabBarOptions?: BottomTabBarOptions;
|
||||
detachInactiveScreens?: boolean;
|
||||
};
|
||||
|
||||
type Props = NavigationViewProps &
|
||||
@@ -35,7 +31,7 @@ type Props = NavigationViewProps &
|
||||
getAccessibilityStates: (props: {
|
||||
route: NavigationRoute;
|
||||
focused: boolean;
|
||||
}) => AccessibilityStates[];
|
||||
}) => string[];
|
||||
navigation: NavigationTabProp;
|
||||
descriptors: SceneDescriptorMap;
|
||||
screenProps?: unknown;
|
||||
@@ -49,11 +45,8 @@ class TabNavigationView extends React.PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
lazy: true,
|
||||
getAccessibilityRole: (): AccessibilityRole => 'button',
|
||||
getAccessibilityStates: ({
|
||||
focused,
|
||||
}: {
|
||||
focused: boolean;
|
||||
}): AccessibilityStates[] => (focused ? ['selected'] : []),
|
||||
getAccessibilityStates: ({ focused }: { focused: boolean }) =>
|
||||
focused ? ['selected'] : [],
|
||||
};
|
||||
|
||||
static getDerivedStateFromProps(nextProps: Props, prevState: State) {
|
||||
@@ -139,13 +132,19 @@ class TabNavigationView extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { navigation, renderScene, lazy } = this.props;
|
||||
const {
|
||||
navigation,
|
||||
renderScene,
|
||||
lazy,
|
||||
detachInactiveScreens = true,
|
||||
} = this.props;
|
||||
const { routes } = navigation.state;
|
||||
const { loaded } = this.state;
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<ScreenContainer style={styles.pages}>
|
||||
{/* @ts-ignore */}
|
||||
<ScreenContainer enabled={detachInactiveScreens} style={styles.pages}>
|
||||
{routes.map((route, index) => {
|
||||
if (lazy && !loaded.includes(index)) {
|
||||
// Don't render a screen if we've never navigated to it
|
||||
@@ -159,6 +158,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
|
||||
key={route.key}
|
||||
style={StyleSheet.absoluteFill}
|
||||
isVisible={isFocused}
|
||||
enabled={detachInactiveScreens}
|
||||
>
|
||||
{renderScene({ route })}
|
||||
</ResourceSavingScene>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user