mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 17:32:55 +08:00
Compare commits
80 Commits
react-navi
...
react-navi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f5000e86b | ||
|
|
6390aacd07 | ||
|
|
20e2625f35 | ||
|
|
162e225fa4 | ||
|
|
ce9991ffff | ||
|
|
3bb21e256f | ||
|
|
f1a06e2f92 | ||
|
|
c0763fca16 | ||
|
|
62da341b67 | ||
|
|
09a10faa44 | ||
|
|
b9ecbd222d | ||
|
|
a57e47786c | ||
|
|
424923019a | ||
|
|
06a69f1bfd | ||
|
|
1fa2edd9f2 | ||
|
|
fcd7d83c4c | ||
|
|
4f7983134b | ||
|
|
46b797dd29 | ||
|
|
1a6f4a581f | ||
|
|
2e7f4a6d10 | ||
|
|
6515fbe2dc | ||
|
|
454e05b02e | ||
|
|
442ca3e700 | ||
|
|
e00c63e32b | ||
|
|
ba6d7dcbed | ||
|
|
2ca8c34b2d | ||
|
|
03f9f8cd8e | ||
|
|
1ee8c76df3 | ||
|
|
bc1313b61d | ||
|
|
28982ffca0 | ||
|
|
bb82d5000f | ||
|
|
1f30227049 | ||
|
|
f34ec46111 | ||
|
|
8b79e9bee6 | ||
|
|
e09906a423 | ||
|
|
ed7ffb98f4 | ||
|
|
90eff6a5e2 | ||
|
|
b4691d7de2 | ||
|
|
f99f523a37 | ||
|
|
05b47dbb09 | ||
|
|
4c4e66f05c | ||
|
|
720d1f970d | ||
|
|
66a4dbccd8 | ||
|
|
7925f0c8a3 | ||
|
|
b3d77b79b7 | ||
|
|
1d527ce16a | ||
|
|
3bec1c964a | ||
|
|
a7e0c193cd | ||
|
|
ac98c0a668 | ||
|
|
14a6538cc8 | ||
|
|
c9313a1419 | ||
|
|
ec39721a74 | ||
|
|
a54d48b650 | ||
|
|
8a9a9cd7d1 | ||
|
|
cd08338186 | ||
|
|
d2433f0ab8 | ||
|
|
66374db859 | ||
|
|
8dbe0299a8 | ||
|
|
e4bc6dd506 | ||
|
|
4bac3bfc1a | ||
|
|
7656b35ee8 | ||
|
|
597aa51fad | ||
|
|
a929933bde | ||
|
|
911d6bb2f4 | ||
|
|
5afc82b11a | ||
|
|
80c8c9d1de | ||
|
|
fd7c5ca9b2 | ||
|
|
72bbebc80e | ||
|
|
915861e601 | ||
|
|
8152ae1212 | ||
|
|
79125bfab9 | ||
|
|
44c390075f | ||
|
|
1c5e7a5ff2 | ||
|
|
cfc1bac4e1 | ||
|
|
c0fb54b5a2 | ||
|
|
5927f4287f | ||
|
|
37a664b433 | ||
|
|
8d0b61f1b7 | ||
|
|
d3f092cb84 | ||
|
|
f5a3880969 |
@@ -14,5 +14,8 @@
|
||||
"react-native-screens"
|
||||
]
|
||||
},
|
||||
"env": { "browser": true, "node": true }
|
||||
"env": { "browser": true, "node": true },
|
||||
"rules": {
|
||||
"import/named": "off"
|
||||
}
|
||||
}
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,6 +4,7 @@
|
||||
.idea
|
||||
.expo
|
||||
.gradle
|
||||
.history
|
||||
|
||||
local.properties
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { registerRootComponent } from 'expo';
|
||||
import {
|
||||
Animated,
|
||||
@@ -41,7 +41,7 @@ import StackWithTranslucentHeader from './src/StackWithTranslucentHeader';
|
||||
import SwitchWithStacks from './src/SwitchWithStacks';
|
||||
import TabsInDrawer from './src/TabsInDrawer';
|
||||
import DragLimitedToModal from './src/DragLimitedToModal';
|
||||
import EventsStack from './src/EventsStack';
|
||||
import FocusEvents from './src/FocusEvents';
|
||||
import FullScreen from './src/FullScreen';
|
||||
import GestureInteraction from './src/GestureInteraction';
|
||||
import {
|
||||
@@ -144,7 +144,7 @@ const ExampleInfo = {
|
||||
name: 'Drag limited to modal',
|
||||
screen: DragLimitedToModal,
|
||||
},
|
||||
EventsStack: { name: 'Events Stack', screen: EventsStack },
|
||||
EventsStack: { name: 'Focus Events', screen: FocusEvents },
|
||||
FullScreen: { name: 'Fullscreen Stack', screen: FullScreen },
|
||||
GestureInteraction: {
|
||||
name: 'Gesture interaction',
|
||||
@@ -335,7 +335,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
}}
|
||||
>
|
||||
{(Object.keys(ExampleInfo) as (keyof typeof ExampleInfo)[]).map(
|
||||
routeName => (
|
||||
(routeName) => (
|
||||
<RectButton
|
||||
key={routeName}
|
||||
underlayColor="#ccc"
|
||||
@@ -346,6 +346,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
if (route.screen || route.path || route.params) {
|
||||
// @ts-ignore
|
||||
const { path, params, screen } = route;
|
||||
// @ts-ignore
|
||||
const { router } = screen;
|
||||
const action =
|
||||
path &&
|
||||
@@ -386,6 +387,7 @@ class MainScreen extends React.Component<any, State> {
|
||||
|
||||
const Navigation = createAppContainer(
|
||||
createStackNavigator(
|
||||
// @ts-ignore
|
||||
{
|
||||
...ExampleInfo,
|
||||
Index: {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function(api) {
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
|
||||
@@ -11,8 +11,8 @@ const modules = ['@expo/vector-icons']
|
||||
// List all packages under `packages/`
|
||||
.readdirSync(packages)
|
||||
// Ignore hidden files such as .DS_Store
|
||||
.filter(p => !p.startsWith('.'))
|
||||
.map(p => {
|
||||
.filter((p) => !p.startsWith('.'))
|
||||
.map((p) => {
|
||||
const pak = JSON.parse(
|
||||
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
|
||||
);
|
||||
@@ -50,7 +50,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
server: {
|
||||
enhanceMiddleware: middleware => {
|
||||
enhanceMiddleware: (middleware) => {
|
||||
return (req, res, next) => {
|
||||
// When an asset is imported outside the project root, it has wrong path on Android
|
||||
// This happens for the back button in stack, so we fix the path to correct one
|
||||
|
||||
@@ -8,29 +8,29 @@
|
||||
"ios": "expo start --ios"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.4",
|
||||
"@expo/vector-icons": "^10.0.6",
|
||||
"@react-native-community/masked-view": "0.1.6",
|
||||
"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.11.0",
|
||||
"@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": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-gesture-handler": "~1.7.0",
|
||||
"react-native-iphone-x-helper": "^1.2.1",
|
||||
"react-native-maps": "0.26.1",
|
||||
"react-native-paper": "^3.4.0",
|
||||
"react-native-reanimated": "~1.7.0",
|
||||
"react-native-safe-area-context": "0.7.3",
|
||||
"react-native-screens": "2.0.0-beta.10",
|
||||
"react-native-webview": "8.1.2",
|
||||
"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": "^3.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"expo-cli": "^3.13.1"
|
||||
"expo-cli": "^3.27.8"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -62,7 +62,7 @@ const CustomTabBar = ({
|
||||
const { routes } = navigation.state;
|
||||
return (
|
||||
<SafeAreaView style={styles.tabContainer}>
|
||||
{routes.map(route => (
|
||||
{routes.map((route) => (
|
||||
<BorderlessButton
|
||||
onPress={() => navigation.navigate(route.routeName)}
|
||||
style={styles.tab}
|
||||
|
||||
@@ -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,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, View, Text } from 'react-native';
|
||||
import { createBottomTabNavigator } from 'react-navigation-tabs';
|
||||
import {
|
||||
createStackNavigator,
|
||||
NavigationStackProp,
|
||||
@@ -22,24 +23,36 @@ class FocusTag extends React.Component<NavigationStackScreenProps> {
|
||||
state = { mode: 'didBlur' };
|
||||
|
||||
componentDidMount() {
|
||||
this.props.navigation.addListener('willFocus', () => {
|
||||
this.setMode('willFocus');
|
||||
this._willFocusSub = this.props.navigation.addListener('willFocus', () => {
|
||||
this._handleEvent('willFocus');
|
||||
});
|
||||
|
||||
this.props.navigation.addListener('willBlur', () => {
|
||||
this.setMode('willBlur');
|
||||
this._willBlurSub = this.props.navigation.addListener('willBlur', () => {
|
||||
this._handleEvent('willBlur');
|
||||
});
|
||||
|
||||
this.props.navigation.addListener('didFocus', () => {
|
||||
this.setMode('didFocus');
|
||||
this._didFocusSub = this.props.navigation.addListener('didFocus', () => {
|
||||
this._handleEvent('didFocus');
|
||||
});
|
||||
|
||||
this.props.navigation.addListener('didBlur', () => {
|
||||
this.setMode('didBlur');
|
||||
this._didBlurSub = this.props.navigation.addListener('didBlur', () => {
|
||||
this._handleEvent('didBlur');
|
||||
});
|
||||
}
|
||||
|
||||
setMode = (mode: string) => {
|
||||
componentWillUnmount() {
|
||||
this._willFocusSub?.remove();
|
||||
this._willBlurSub?.remove();
|
||||
this._didFocusSub?.remove();
|
||||
this._didBlurSub?.remove();
|
||||
}
|
||||
|
||||
_willFocusSub: { remove: () => void } | undefined;
|
||||
_willBlurSub: { remove: () => void } | undefined;
|
||||
_didFocusSub: { remove: () => void } | undefined;
|
||||
_didBlurSub: { remove: () => void } | undefined;
|
||||
|
||||
_handleEvent = (mode: string) => {
|
||||
this.setState({ mode });
|
||||
};
|
||||
|
||||
@@ -119,18 +132,19 @@ class SampleScreen extends React.Component<NavigationStackScreenProps> {
|
||||
}
|
||||
}
|
||||
|
||||
const SimpleStack = createStackNavigator(
|
||||
const Stack = createStackNavigator(
|
||||
{
|
||||
PageOne: {
|
||||
screen: SampleScreen,
|
||||
},
|
||||
PageTwo: {
|
||||
screen: SampleScreen,
|
||||
},
|
||||
PageOne: SampleScreen,
|
||||
PageTwo: SampleScreen,
|
||||
},
|
||||
{
|
||||
initialRouteName: 'PageOne',
|
||||
}
|
||||
);
|
||||
|
||||
export default SimpleStack;
|
||||
const Tabs = createBottomTabNavigator({
|
||||
A: Stack,
|
||||
B: Stack,
|
||||
});
|
||||
|
||||
export default Tabs;
|
||||
@@ -44,7 +44,7 @@ const ContainerWithButtons = withNavigation(
|
||||
const MapScreen = () => (
|
||||
<ContainerWithButtons>
|
||||
<DrawerGestureContext.Consumer>
|
||||
{ref => (
|
||||
{(ref) => (
|
||||
<NativeViewGestureHandler waitFor={ref}>
|
||||
<MapView style={{ flex: 1 }} />
|
||||
</NativeViewGestureHandler>
|
||||
@@ -60,7 +60,7 @@ MapScreen.navigationOptions = {
|
||||
const WebViewScreen = () => (
|
||||
<ContainerWithButtons>
|
||||
<DrawerGestureContext.Consumer>
|
||||
{ref => (
|
||||
{(ref) => (
|
||||
<NativeViewGestureHandler waitFor={ref}>
|
||||
<WebView
|
||||
style={{ flex: 1 }}
|
||||
|
||||
@@ -33,7 +33,7 @@ const BarCodeScreenBase = (
|
||||
<BarCodeScanner
|
||||
onBarCodeScanned={
|
||||
props.isFocused
|
||||
? data => {
|
||||
? (data) => {
|
||||
console.log('scanned...');
|
||||
props.navigation.navigate('Info', { data });
|
||||
}
|
||||
@@ -51,7 +51,7 @@ BarCodeScreenBase.navigationOptions = {
|
||||
|
||||
const BarCodeScreen = withNavigationFocus(BarCodeScreenBase);
|
||||
|
||||
const InfoScreen: NavigationStackScreenComponent = props => {
|
||||
const InfoScreen: NavigationStackScreenComponent = (props) => {
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text>{JSON.stringify(props.navigation.getParam('data'))}</Text>
|
||||
|
||||
@@ -82,7 +82,7 @@ class ListScreen extends React.Component<
|
||||
state = { isInverted: false };
|
||||
|
||||
onSwitch = () =>
|
||||
this.setState(prevState => ({ isInverted: !prevState.isInverted }));
|
||||
this.setState((prevState) => ({ isInverted: !prevState.isInverted }));
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dimensions,
|
||||
@@ -114,7 +114,7 @@ const MyNavScreen = ({
|
||||
title="Set unlocked"
|
||||
/>
|
||||
),
|
||||
unlocked: (
|
||||
'unlocked': (
|
||||
<Button
|
||||
onPress={() =>
|
||||
navigation.setParams({ drawerLockMode: 'locked-open' })
|
||||
@@ -158,7 +158,7 @@ const DrawerContents = ({
|
||||
<Animated.View style={{ transform: [{ translateX }] }}>
|
||||
<ScrollView>
|
||||
<SafeAreaView forceInset={{ top: 'always' }}>
|
||||
{navigation.state.routes.map(route => (
|
||||
{navigation.state.routes.map((route) => (
|
||||
<DrawerItem
|
||||
key={route.key}
|
||||
navigation={descriptors[route.key].navigation}
|
||||
|
||||
@@ -32,7 +32,7 @@ class RightDrawer extends Component<DrawerContentComponentProps> {
|
||||
<ScrollView
|
||||
style={{ height: '100%', width: '100%', backgroundColor: '#333333' }}
|
||||
>
|
||||
{this.state.categories.map(key => {
|
||||
{this.state.categories.map((key) => {
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={key.n}
|
||||
|
||||
@@ -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) {
|
||||
@@ -59,7 +59,7 @@ export default function PhotoGrid({ id }: { id: string }) {
|
||||
|
||||
return (
|
||||
<NavigationAwareScrollView contentContainerStyle={styles.content}>
|
||||
{PHOTOS.map(uri => (
|
||||
{PHOTOS.map((uri) => (
|
||||
<View key={uri} style={styles.item}>
|
||||
<Image source={{ uri }} style={styles.photo} />
|
||||
</View>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Button, ScrollView, StyleSheet, View } from 'react-native';
|
||||
import {
|
||||
ThemeColors,
|
||||
@@ -106,7 +106,7 @@ const MyNavScreen = ({
|
||||
title="Set unlocked"
|
||||
/>
|
||||
),
|
||||
unlocked: (
|
||||
'unlocked': (
|
||||
<Button
|
||||
onPress={() =>
|
||||
navigation.setParams({ drawerLockMode: 'locked-open' })
|
||||
|
||||
@@ -64,7 +64,7 @@ class ListScreen extends React.Component {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Text>List Screen</Text>
|
||||
@@ -96,7 +96,7 @@ class DetailsScreen extends React.Component<NavigationStackScreenProps> {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Text>Details Screen</Text>
|
||||
@@ -119,7 +119,7 @@ class HeaderlessScreen extends React.Component {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: '#fff',
|
||||
// backgroundColor: '#fff',
|
||||
}}
|
||||
>
|
||||
<Text>Headerless Screen</Text>
|
||||
|
||||
@@ -25,7 +25,7 @@ const ListScreen = (props: NavigationStackScreenProps) => (
|
||||
|
||||
const AnotherScreen = () => (
|
||||
<CardAnimationContext.Consumer>
|
||||
{value => {
|
||||
{(value) => {
|
||||
const scale = value
|
||||
? value.current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
@@ -67,7 +67,7 @@ const YetAnotherScreen = () => (
|
||||
}}
|
||||
>
|
||||
<CardAnimationContext.Consumer>
|
||||
{value => (
|
||||
{(value) => (
|
||||
<Animated.Text
|
||||
style={{
|
||||
fontSize: 24,
|
||||
@@ -84,7 +84,7 @@ const YetAnotherScreen = () => (
|
||||
)}
|
||||
</CardAnimationContext.Consumer>
|
||||
<CardAnimationContext.Consumer>
|
||||
{value => (
|
||||
{(value) => (
|
||||
<Animated.Text
|
||||
style={{
|
||||
fontSize: 24,
|
||||
|
||||
@@ -59,7 +59,7 @@ const Drawer = createDrawerNavigator(
|
||||
},
|
||||
},
|
||||
{
|
||||
contentComponent: props => <Menu {...props} />,
|
||||
contentComponent: (props) => <Menu {...props} />,
|
||||
navigationOptions: { title: 'Example' },
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
@@ -91,8 +91,4 @@ const DrawerExample = createDrawerNavigator(
|
||||
}
|
||||
);
|
||||
|
||||
DrawerExample.navigationOptions = {
|
||||
headerShown: false,
|
||||
};
|
||||
|
||||
export default DrawerExample;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
NavigationStackScreenComponent,
|
||||
} from 'react-navigation-stack';
|
||||
|
||||
const ListScreen: NavigationStackScreenComponent = function(props) {
|
||||
const ListScreen: NavigationStackScreenComponent = function (props) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -29,7 +29,7 @@ const ListScreen: NavigationStackScreenComponent = function(props) {
|
||||
);
|
||||
};
|
||||
|
||||
const ModalDialogScreen: NavigationStackScreenComponent = function(props) {
|
||||
const ModalDialogScreen: NavigationStackScreenComponent = function (props) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
|
||||
41
package.json
41
package.json
@@ -18,34 +18,35 @@
|
||||
"author": "Adam Miskiewicz <adam@sk3vy.com>, Eric Vicenti <ericvicenti@gmail.com>, Brent Vatne <brent@expo.io>, Satyajit Sahoo <satyajit.happy@gmail.com>",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||
"typescript": "tsc --noEmit",
|
||||
"typescript": "tsc --noEmit --composite false",
|
||||
"test": "jest",
|
||||
"prerelease": "yarn lerna run clean",
|
||||
"release": "yarn lerna publish",
|
||||
"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.4",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.4",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^25.1.2",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-satya164": "^3.1.5",
|
||||
"husky": "^4.2.1",
|
||||
"jest": "^25.1.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "~3.7.5"
|
||||
"@babel/plugin-proposal-class-properties": "^7.10.4",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.11.0",
|
||||
"@babel/preset-env": "^7.11.0",
|
||||
"@babel/preset-react": "^7.10.4",
|
||||
"@babel/preset-typescript": "^7.10.4",
|
||||
"@babel/runtime": "^7.11.0",
|
||||
"@commitlint/config-conventional": "^11.0.0",
|
||||
"@types/jest": "^26.0.8",
|
||||
"babel-jest": "^26.2.2",
|
||||
"commitlint": "^11.0.0",
|
||||
"core-js": "^3.6.5",
|
||||
"eslint": "^7.6.0",
|
||||
"eslint-config-satya164": "^3.1.8",
|
||||
"husky": "^4.2.5",
|
||||
"jest": "^26.2.2",
|
||||
"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,113 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack and upgrade typescript ([454e05b](https://github.com/react-navigation/react-navigation/commit/454e05b02ec97f053b381fdc801df899d8c93cb6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.12](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.11...react-navigation-animated-switch@0.5.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.11](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.10...react-navigation-animated-switch@0.5.11) (2020-04-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.10](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.9...react-navigation-animated-switch@0.5.10) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.9](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.8...react-navigation-animated-switch@0.5.9) (2020-03-31)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.8](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.6...react-navigation-animated-switch@0.5.8) (2020-03-28)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.7](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.6...react-navigation-animated-switch@0.5.7) (2020-03-27)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.6](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.5...react-navigation-animated-switch@0.5.6) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.5](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.4...react-navigation-animated-switch@0.5.5) (2020-03-20)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.4](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.3...react-navigation-animated-switch@0.5.4) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.3](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.2...react-navigation-animated-switch@0.5.3) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [0.5.2](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.5.1...react-navigation-animated-switch@0.5.2) (2020-02-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-animated-switch
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-animated-switch",
|
||||
"version": "0.5.2",
|
||||
"version": "0.6.2",
|
||||
"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.9.7",
|
||||
"@types/react": "16.9.22",
|
||||
"@types/react-native": "0.61.17",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-reanimated": "~1.7.0",
|
||||
"react-navigation": "^4.2.2",
|
||||
"typescript": "~3.7.5"
|
||||
"@react-native-community/bob": "0.16.2",
|
||||
"@types/react": "16.9.49",
|
||||
"@types/react-native": "0.63.20",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-reanimated": "~1.13.0",
|
||||
"react-navigation": "^4.4.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -3,6 +3,103 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change old docUrl to new docUrl due v5 ([e09906a](https://github.com/react-navigation/react-navigation-core/commit/e09906a4235a0fca09140923ebe7af34b50b491a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.5](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.4...@react-navigation/core@3.7.5) (2020-04-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* emit refocus for child navigators ([66a4dbc](https://github.com/react-navigation/react-navigation-core/commit/66a4dbccd8e7b3cda51a1d9c7e9397dfc58d6b9e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.4](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.3...@react-navigation/core@3.7.4) (2020-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove isTransitioning from SwitchRouter state ([3bec1c9](https://github.com/react-navigation/react-navigation-core/commit/3bec1c964a49136c0ead8e8ba8a8c66c556bbcba))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.3](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.1...@react-navigation/core@3.7.3) (2020-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* always emit didFocus/didBlur for root navigator ([ac98c0a](https://github.com/react-navigation/react-navigation-core/commit/ac98c0a668fe21200d0c6f62ae3043f92cc5aa7e))
|
||||
* don't handle prune if there's only one route ([d2433f0](https://github.com/react-navigation/react-navigation-core/commit/d2433f0ab8f9791df8169de4ddfdeed9bc699e3e))
|
||||
* emit didFocus and didBlur events based on parent's transition ([14a6538](https://github.com/react-navigation/react-navigation-core/commit/14a6538cc8e12c50d5d10722d75c9395a0a281ec))
|
||||
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-core/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-core/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-core/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-core/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-core/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-core/issues/7749)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.2](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.1...@react-navigation/core@3.7.2) (2020-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't handle prune if there's only one route ([d2433f0](https://github.com/react-navigation/react-navigation-core/commit/d2433f0ab8f9791df8169de4ddfdeed9bc699e3e))
|
||||
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-core/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-core/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-core/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-core/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-core/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-core/issues/7749)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.1](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.0...@react-navigation/core@3.7.1) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [3.7.0](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.6.1...@react-navigation/core@3.7.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add prune option to pop action to match v5 behaviour ([5927f42](https://github.com/react-navigation/react-navigation-core/commit/5927f4287f5e1ab106537865523daa1c03b14b47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.6.1](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.6.0...@react-navigation/core@3.6.1) (2020-02-24)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"version": "3.6.1",
|
||||
"version": "3.7.8",
|
||||
"description": "Core utilities for the react-navigation framework",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -15,7 +15,8 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"tag": "4.x"
|
||||
},
|
||||
"keywords": [
|
||||
"react-navigation",
|
||||
@@ -36,16 +37,16 @@
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"path-to-regexp": "^1.8.0",
|
||||
"query-string": "^6.11.0",
|
||||
"react-is": "^16.8.6"
|
||||
"query-string": "^6.13.1",
|
||||
"react-is": "^16.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.7",
|
||||
"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": "*"
|
||||
|
||||
@@ -13,7 +13,7 @@ const StateUtils = {
|
||||
* Gets a route by key. If the route isn't found, returns `null`.
|
||||
*/
|
||||
get(state, key) {
|
||||
return state.routes.find(route => route.key === key) || null;
|
||||
return state.routes.find((route) => route.key === key) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -21,7 +21,7 @@ const StateUtils = {
|
||||
* routes of the navigation state, or -1 if it is not present.
|
||||
*/
|
||||
indexOf(state, key) {
|
||||
return state.routes.findIndex(route => route.key === key);
|
||||
return state.routes.findIndex((route) => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -29,7 +29,7 @@ const StateUtils = {
|
||||
* routes of the navigation state.
|
||||
*/
|
||||
has(state, key) {
|
||||
return !!state.routes.some(route => route.key === key);
|
||||
return !!state.routes.some((route) => route.key === key);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
|
||||
148
packages/core/src/__fixtures__/createNavigationContainer.js
Normal file
148
packages/core/src/__fixtures__/createNavigationContainer.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/* eslint-disable react/sort-comp */
|
||||
|
||||
import * as React from 'react';
|
||||
import { NavigationActions, getNavigation, NavigationProvider } from '../index';
|
||||
|
||||
export default function createNavigationContainer(Component) {
|
||||
class NavigationContainer extends React.Component {
|
||||
static router = Component.router;
|
||||
static navigationOptions = null;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._initialAction = NavigationActions.init();
|
||||
|
||||
this.state = {
|
||||
nav: !props.loadNavigationState
|
||||
? Component.router.getStateForAction(this._initialAction)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
_actionEventSubscribers = new Set();
|
||||
|
||||
_onNavigationStateChange(prevNav, nav, action) {
|
||||
if (typeof this.props.onNavigationStateChange === 'function') {
|
||||
this.props.onNavigationStateChange(prevNav, nav, action);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
// Clear cached _navState every tick
|
||||
if (this._navState === this.state.nav) {
|
||||
this._navState = null;
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
// Initialize state. This must be done *after* any async code
|
||||
// so we don't end up with a different value for this.state.nav
|
||||
// due to changes while async function was resolving
|
||||
let action = this._initialAction;
|
||||
// eslint-disable-next-line react/no-access-state-in-setstate
|
||||
let startupState = this.state.nav;
|
||||
if (!startupState) {
|
||||
startupState = Component.router.getStateForAction(action);
|
||||
}
|
||||
|
||||
const dispatchActions = () =>
|
||||
this._actionEventSubscribers.forEach((subscriber) =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
state: this.state.nav,
|
||||
lastState: null,
|
||||
})
|
||||
);
|
||||
|
||||
if (startupState === this.state.nav) {
|
||||
dispatchActions();
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/no-did-mount-set-state
|
||||
this.setState({ nav: startupState }, () => {
|
||||
dispatchActions();
|
||||
});
|
||||
}
|
||||
|
||||
dispatch = (action) => {
|
||||
if (this.props.navigation) {
|
||||
return this.props.navigation.dispatch(action);
|
||||
}
|
||||
|
||||
// navState will have the most up-to-date value, because setState sometimes behaves asyncronously
|
||||
this._navState = this._navState || this.state.nav;
|
||||
const lastNavState = this._navState;
|
||||
|
||||
const reducedState = Component.router.getStateForAction(
|
||||
action,
|
||||
lastNavState
|
||||
);
|
||||
const navState = reducedState === null ? lastNavState : reducedState;
|
||||
|
||||
const dispatchActionEvents = () => {
|
||||
this._actionEventSubscribers.forEach((subscriber) =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
state: navState,
|
||||
lastState: lastNavState,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
if (reducedState === null) {
|
||||
// The router will return null when action has been handled and the state hasn't changed.
|
||||
// dispatch returns true when something has been handled.
|
||||
dispatchActionEvents();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (navState !== lastNavState) {
|
||||
// Cache updates to state.nav during the tick to ensure that subsequent calls will not discard this change
|
||||
this._navState = navState;
|
||||
this.setState({ nav: navState }, () => {
|
||||
this._onNavigationStateChange(lastNavState, navState, action);
|
||||
dispatchActionEvents();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
dispatchActionEvents();
|
||||
return false;
|
||||
};
|
||||
|
||||
render() {
|
||||
let navigation = this.props.navigation;
|
||||
|
||||
const navState = this.state.nav;
|
||||
|
||||
if (!navState) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!this._navigation || this._navigation.state !== navState) {
|
||||
this._navigation = getNavigation(
|
||||
Component.router,
|
||||
navState,
|
||||
this.dispatch,
|
||||
this._actionEventSubscribers,
|
||||
this._getScreenProps,
|
||||
() => this._navigation
|
||||
);
|
||||
}
|
||||
|
||||
navigation = this._navigation;
|
||||
|
||||
return (
|
||||
<NavigationProvider value={navigation}>
|
||||
<Component {...this.props} navigation={navigation} />
|
||||
</NavigationProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return NavigationContainer;
|
||||
}
|
||||
241
packages/core/src/__tests__/NavigationFocusEvents.test.js
Normal file
241
packages/core/src/__tests__/NavigationFocusEvents.test.js
Normal file
@@ -0,0 +1,241 @@
|
||||
import * as React from 'react';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import { navigate } from '../NavigationActions';
|
||||
import TabRouter from '../routers/TabRouter';
|
||||
import createNavigator from '../navigators/createNavigator';
|
||||
import createNavigationContainer from '../__fixtures__/createNavigationContainer';
|
||||
|
||||
it('fires focus and blur events in root navigator', async () => {
|
||||
function createTestNavigator(routeConfigMap, config = {}) {
|
||||
const router = TabRouter(routeConfigMap, config);
|
||||
|
||||
return createNavigator(
|
||||
({ descriptors, navigation }) =>
|
||||
navigation.state.routes.map((route) => {
|
||||
const Comp = descriptors[route.key].getComponent();
|
||||
return (
|
||||
<Comp
|
||||
key={route.key}
|
||||
navigation={descriptors[route.key].navigation}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
router,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
const firstBlurCallback = jest.fn();
|
||||
|
||||
const secondFocusCallback = jest.fn();
|
||||
const secondBlurCallback = jest.fn();
|
||||
|
||||
const thirdFocusCallback = jest.fn();
|
||||
const thirdBlurCallback = jest.fn();
|
||||
|
||||
const fourthFocusCallback = jest.fn();
|
||||
const fourthBlurCallback = jest.fn();
|
||||
|
||||
const createComponent = (focusCallback, blurCallback) =>
|
||||
class TestComponent extends React.Component {
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
this.focusSub = navigation.addListener('willFocus', focusCallback);
|
||||
this.blurSub = navigation.addListener('willBlur', blurCallback);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.focusSub?.remove();
|
||||
this.blurSub?.remove();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const navigation = React.createRef();
|
||||
|
||||
const Navigator = createNavigationContainer(
|
||||
createTestNavigator({
|
||||
first: createComponent(firstFocusCallback, firstBlurCallback),
|
||||
second: createComponent(secondFocusCallback, secondBlurCallback),
|
||||
third: createComponent(thirdFocusCallback, thirdBlurCallback),
|
||||
fourth: createComponent(fourthFocusCallback, fourthBlurCallback),
|
||||
})
|
||||
);
|
||||
|
||||
const element = <Navigator ref={navigation} />;
|
||||
|
||||
render(element);
|
||||
|
||||
expect(firstFocusCallback).toBeCalledTimes(1);
|
||||
expect(firstBlurCallback).toBeCalledTimes(0);
|
||||
expect(secondFocusCallback).toBeCalledTimes(0);
|
||||
expect(secondBlurCallback).toBeCalledTimes(0);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(0);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(0);
|
||||
expect(fourthFocusCallback).toBeCalledTimes(0);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'second' }));
|
||||
});
|
||||
|
||||
expect(firstBlurCallback).toBeCalledTimes(1);
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'fourth' }));
|
||||
});
|
||||
|
||||
expect(firstFocusCallback).toBeCalledTimes(1);
|
||||
expect(firstBlurCallback).toBeCalledTimes(1);
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
expect(secondBlurCallback).toBeCalledTimes(1);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(0);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(0);
|
||||
expect(fourthFocusCallback).toBeCalledTimes(1);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('fires focus and blur events in nested navigator', () => {
|
||||
function createTestNavigator(routeConfigMap, config = {}) {
|
||||
const router = TabRouter(routeConfigMap, config);
|
||||
|
||||
return createNavigator(
|
||||
({ descriptors, navigation }) =>
|
||||
navigation.state.routes.map((route) => {
|
||||
const Comp = descriptors[route.key].getComponent();
|
||||
return (
|
||||
<Comp
|
||||
key={route.key}
|
||||
navigation={descriptors[route.key].navigation}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
router,
|
||||
config
|
||||
);
|
||||
}
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
const firstBlurCallback = jest.fn();
|
||||
|
||||
const secondFocusCallback = jest.fn();
|
||||
const secondBlurCallback = jest.fn();
|
||||
|
||||
const thirdFocusCallback = jest.fn();
|
||||
const thirdBlurCallback = jest.fn();
|
||||
|
||||
const fourthFocusCallback = jest.fn();
|
||||
const fourthBlurCallback = jest.fn();
|
||||
|
||||
const createComponent = (focusCallback, blurCallback) =>
|
||||
class TestComponent extends React.Component {
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
this.focusSub = navigation.addListener('willFocus', focusCallback);
|
||||
this.blurSub = navigation.addListener('willBlur', blurCallback);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.focusSub?.remove();
|
||||
this.blurSub?.remove();
|
||||
}
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const Navigator = createNavigationContainer(
|
||||
createTestNavigator({
|
||||
first: createComponent(firstFocusCallback, firstBlurCallback),
|
||||
second: createComponent(secondFocusCallback, secondBlurCallback),
|
||||
nested: createTestNavigator({
|
||||
third: createComponent(thirdFocusCallback, thirdBlurCallback),
|
||||
fourth: createComponent(fourthFocusCallback, fourthBlurCallback),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
const navigation = React.createRef();
|
||||
|
||||
const element = <Navigator ref={navigation} />;
|
||||
|
||||
render(element);
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(0);
|
||||
expect(firstFocusCallback).toBeCalledTimes(1);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'nested' }));
|
||||
});
|
||||
|
||||
expect(firstFocusCallback).toBeCalledTimes(1);
|
||||
expect(fourthFocusCallback).toBeCalledTimes(0);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(1);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'second' }));
|
||||
});
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(1);
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'nested' }));
|
||||
});
|
||||
|
||||
expect(firstBlurCallback).toBeCalledTimes(1);
|
||||
expect(secondBlurCallback).toBeCalledTimes(1);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(2);
|
||||
expect(fourthFocusCallback).toBeCalledTimes(0);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'third' }));
|
||||
});
|
||||
|
||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||
expect(thirdFocusCallback).toBeCalledTimes(2);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'first' }));
|
||||
});
|
||||
|
||||
expect(firstFocusCallback).toBeCalledTimes(2);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'fourth' }));
|
||||
});
|
||||
|
||||
expect(fourthFocusCallback).toBeCalledTimes(1);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(2);
|
||||
expect(firstBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
act(() => {
|
||||
navigation.current.dispatch(navigate({ routeName: 'third' }));
|
||||
});
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(3);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(1);
|
||||
|
||||
// Make sure nothing else has changed
|
||||
expect(firstFocusCallback).toBeCalledTimes(2);
|
||||
expect(firstBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
expect(secondFocusCallback).toBeCalledTimes(1);
|
||||
expect(secondBlurCallback).toBeCalledTimes(1);
|
||||
|
||||
expect(thirdFocusCallback).toBeCalledTimes(3);
|
||||
expect(thirdBlurCallback).toBeCalledTimes(2);
|
||||
|
||||
expect(fourthFocusCallback).toBeCalledTimes(1);
|
||||
expect(fourthBlurCallback).toBeCalledTimes(1);
|
||||
});
|
||||
@@ -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,543 +0,0 @@
|
||||
import getChildEventSubscriber from '../getChildEventSubscriber';
|
||||
|
||||
it('child action events only flow when focused', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const focusedTestState = {
|
||||
...testState,
|
||||
index: 1,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: testState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
state: focusedTestState,
|
||||
lastState: focusedTestState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('grandchildren subscription', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const parentBlurState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [
|
||||
{ key: 'aunt' },
|
||||
{
|
||||
key: 'parent',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const parentTransitionState = {
|
||||
...parentBlurState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
};
|
||||
const parentFocusState = {
|
||||
...parentTransitionState,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentTransitionState,
|
||||
lastState: parentBlurState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: parentFocusState,
|
||||
lastState: parentTransitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('grandchildren transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
],
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(2, true);
|
||||
const blurred2State = makeFakeState(2, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('grandchildren pass through transitions', () => {
|
||||
const grandParentSubscriber = jest.fn();
|
||||
const emitGrandParentAction = payload => {
|
||||
grandParentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const parentSubscriber = getChildEventSubscriber(
|
||||
grandParentSubscriber,
|
||||
'parent'
|
||||
).addListener;
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const makeFakeState = (childIndex, childIsTransitioning) => ({
|
||||
index: childIndex,
|
||||
isTransitioning: childIsTransitioning,
|
||||
routes: [
|
||||
{ key: 'nothing' },
|
||||
{
|
||||
key: 'parent',
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
|
||||
},
|
||||
].slice(0, childIndex + 1),
|
||||
});
|
||||
const blurredState = makeFakeState(0, false);
|
||||
const transitionState = makeFakeState(1, true);
|
||||
const focusState = makeFakeState(1, false);
|
||||
const transition2State = makeFakeState(0, true);
|
||||
const blurred2State = makeFakeState(0, false);
|
||||
|
||||
const childActionHandler = jest.fn();
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('action', childActionHandler);
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transitionState,
|
||||
lastState: blurredState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(0);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: transitionState,
|
||||
action: { type: 'FooAction' },
|
||||
});
|
||||
expect(childActionHandler.mock.calls.length).toBe(0);
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: focusState,
|
||||
lastState: focusState,
|
||||
action: { type: 'TestAction' },
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: transition2State,
|
||||
lastState: focusState,
|
||||
action: { type: 'CauseWillBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(0);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
emitGrandParentAction({
|
||||
type: 'action',
|
||||
state: blurred2State,
|
||||
lastState: transition2State,
|
||||
action: { type: 'CauseDidBlurAction' },
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childActionHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('child focus with transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: true,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
},
|
||||
});
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('child focus with immediate transition', () => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitParentAction = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const childEventSubscriber = getChildEventSubscriber(parentSubscriber, 'key1')
|
||||
.addListener;
|
||||
const randomAction = { type: 'FooAction' };
|
||||
const testState = {
|
||||
key: 'foo',
|
||||
routeName: 'FooRoute',
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
};
|
||||
const childWillFocusHandler = jest.fn();
|
||||
const childDidFocusHandler = jest.fn();
|
||||
const childWillBlurHandler = jest.fn();
|
||||
const childDidBlurHandler = jest.fn();
|
||||
childEventSubscriber('willFocus', childWillFocusHandler);
|
||||
childEventSubscriber('didFocus', childDidFocusHandler);
|
||||
childEventSubscriber('willBlur', childWillBlurHandler);
|
||||
childEventSubscriber('didBlur', childDidBlurHandler);
|
||||
emitParentAction({
|
||||
type: 'didFocus',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: testState,
|
||||
});
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: testState,
|
||||
state: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
});
|
||||
expect(childWillFocusHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidFocusHandler.mock.calls.length).toBe(1);
|
||||
|
||||
emitParentAction({
|
||||
type: 'action',
|
||||
action: randomAction,
|
||||
lastState: {
|
||||
...testState,
|
||||
index: 1,
|
||||
},
|
||||
state: {
|
||||
...testState,
|
||||
index: 0,
|
||||
},
|
||||
});
|
||||
expect(childWillBlurHandler.mock.calls.length).toBe(1);
|
||||
expect(childDidBlurHandler.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
const setupEventTest = (subscriptionKey, initialLastFocusEvent) => {
|
||||
const parentSubscriber = jest.fn();
|
||||
const emitEvent = payload => {
|
||||
parentSubscriber.mock.calls.forEach(subs => {
|
||||
if (subs[0] === payload.type) {
|
||||
subs[1](payload);
|
||||
}
|
||||
});
|
||||
};
|
||||
const subscriptionRemove = () => {};
|
||||
parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
|
||||
const evtProvider = getChildEventSubscriber(
|
||||
parentSubscriber,
|
||||
subscriptionKey,
|
||||
initialLastFocusEvent
|
||||
);
|
||||
const handlers = {};
|
||||
evtProvider.addListener('action', (handlers.action = jest.fn()));
|
||||
evtProvider.addListener('willFocus', (handlers.willFocus = jest.fn()));
|
||||
evtProvider.addListener('didFocus', (handlers.didFocus = jest.fn()));
|
||||
evtProvider.addListener('willBlur', (handlers.willBlur = jest.fn()));
|
||||
evtProvider.addListener('didBlur', (handlers.didBlur = jest.fn()));
|
||||
return { emitEvent, handlers, evtProvider };
|
||||
};
|
||||
|
||||
it('immediate back with uncompleted transition will focus first screen again', () => {
|
||||
const { handlers, emitEvent } = setupEventTest('key0', 'didFocus');
|
||||
emitEvent({
|
||||
type: 'action',
|
||||
state: {
|
||||
index: 1,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
isTransitioning: true,
|
||||
},
|
||||
lastState: {
|
||||
index: 0,
|
||||
routes: [{ key: 'key0' }],
|
||||
isTransitioning: false,
|
||||
},
|
||||
action: { type: 'Any action, does not matter here' },
|
||||
});
|
||||
expect(handlers.willFocus.mock.calls.length).toBe(0);
|
||||
expect(handlers.didFocus.mock.calls.length).toBe(0);
|
||||
expect(handlers.willBlur.mock.calls.length).toBe(1);
|
||||
expect(handlers.didBlur.mock.calls.length).toBe(0);
|
||||
emitEvent({
|
||||
type: 'action',
|
||||
state: {
|
||||
index: 0,
|
||||
routes: [{ key: 'key0' }],
|
||||
isTransitioning: true,
|
||||
},
|
||||
lastState: {
|
||||
index: 1,
|
||||
routes: [{ key: 'key0' }, { key: 'key1' }],
|
||||
isTransitioning: true,
|
||||
},
|
||||
action: { type: 'Any action, does not matter here' },
|
||||
});
|
||||
expect(handlers.willFocus.mock.calls.length).toBe(1);
|
||||
expect(handlers.didFocus.mock.calls.length).toBe(0);
|
||||
expect(handlers.willBlur.mock.calls.length).toBe(1);
|
||||
expect(handlers.didBlur.mock.calls.length).toBe(0);
|
||||
emitEvent({
|
||||
type: 'action',
|
||||
state: {
|
||||
index: 0,
|
||||
routes: [{ key: 'key0' }],
|
||||
isTransitioning: false,
|
||||
},
|
||||
lastState: {
|
||||
index: 0,
|
||||
routes: [{ key: 'key0' }],
|
||||
isTransitioning: true,
|
||||
},
|
||||
action: { type: 'Any action, does not matter here' },
|
||||
});
|
||||
expect(handlers.willFocus.mock.calls.length).toBe(1);
|
||||
expect(handlers.didFocus.mock.calls.length).toBe(1);
|
||||
expect(handlers.willBlur.mock.calls.length).toBe(1);
|
||||
expect(handlers.didBlur.mock.calls.length).toBe(0);
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import getNavigation from '../getNavigation';
|
||||
import * as NavigationActions from '../NavigationActions';
|
||||
|
||||
it('getNavigation provides default action helpers', () => {
|
||||
const router = {
|
||||
@@ -22,15 +23,14 @@ 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 }),
|
||||
foo: (bar) => ({ type: 'FooBarAction', bar }),
|
||||
}),
|
||||
getStateForAction(action, lastState = {}) {
|
||||
return lastState;
|
||||
@@ -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,196 +0,0 @@
|
||||
/*
|
||||
* This is used to extract one children's worth of events from a stream of navigation action events
|
||||
*
|
||||
* Based on the 'action' events that get fired for this navigation state, this utility will fire
|
||||
* focus and blur events for this child
|
||||
*/
|
||||
export default function getChildEventSubscriber(
|
||||
addListener,
|
||||
key,
|
||||
initialLastFocusEvent = 'didBlur'
|
||||
) {
|
||||
const actionSubscribers = new Set();
|
||||
const willFocusSubscribers = new Set();
|
||||
const didFocusSubscribers = new Set();
|
||||
const willBlurSubscribers = new Set();
|
||||
const didBlurSubscribers = new Set();
|
||||
const refocusSubscribers = new Set();
|
||||
|
||||
const removeAll = () => {
|
||||
[
|
||||
actionSubscribers,
|
||||
willFocusSubscribers,
|
||||
didFocusSubscribers,
|
||||
willBlurSubscribers,
|
||||
didBlurSubscribers,
|
||||
refocusSubscribers,
|
||||
].forEach(set => set.clear());
|
||||
|
||||
upstreamSubscribers.forEach(subs => subs && subs.remove());
|
||||
};
|
||||
|
||||
const getChildSubscribers = evtName => {
|
||||
switch (evtName) {
|
||||
case 'action':
|
||||
return actionSubscribers;
|
||||
case 'willFocus':
|
||||
return willFocusSubscribers;
|
||||
case 'didFocus':
|
||||
return didFocusSubscribers;
|
||||
case 'willBlur':
|
||||
return willBlurSubscribers;
|
||||
case 'didBlur':
|
||||
return didBlurSubscribers;
|
||||
case 'refocus':
|
||||
return refocusSubscribers;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const emit = (type, payload) => {
|
||||
const payloadWithType = { ...payload, type };
|
||||
const subscribers = getChildSubscribers(type);
|
||||
subscribers &&
|
||||
subscribers.forEach(subs => {
|
||||
subs(payloadWithType);
|
||||
});
|
||||
};
|
||||
|
||||
// lastFocusEvent keeps track of focus state for one route. First we assume
|
||||
// we are blurred. If we are focused on initialization, the first 'action'
|
||||
// event will cause onFocus+willFocus events because we had previously been
|
||||
// considered blurred
|
||||
let lastFocusEvent = initialLastFocusEvent;
|
||||
|
||||
const upstreamEvents = [
|
||||
'willFocus',
|
||||
'didFocus',
|
||||
'willBlur',
|
||||
'didBlur',
|
||||
'refocus',
|
||||
'action',
|
||||
];
|
||||
|
||||
const upstreamSubscribers = upstreamEvents.map(eventName =>
|
||||
addListener(eventName, payload => {
|
||||
if (eventName === 'refocus') {
|
||||
emit(eventName, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
const { state, lastState, action } = payload;
|
||||
const lastRoutes = lastState && lastState.routes;
|
||||
const routes = state && state.routes;
|
||||
|
||||
// const lastFocusKey =
|
||||
// lastState && lastState.routes && lastState.routes[lastState.index].key;
|
||||
const focusKey = routes && routes[state.index].key;
|
||||
|
||||
const isChildFocused = focusKey === key;
|
||||
const lastRoute =
|
||||
lastRoutes && lastRoutes.find(route => route.key === key);
|
||||
const newRoute = routes && routes.find(route => route.key === key);
|
||||
const childPayload = {
|
||||
context: `${key}:${action.type}_${payload.context || 'Root'}`,
|
||||
state: newRoute,
|
||||
lastState: lastRoute,
|
||||
action,
|
||||
type: eventName,
|
||||
};
|
||||
const isTransitioning = !!state && state.isTransitioning;
|
||||
|
||||
const previouslylastFocusEvent = lastFocusEvent;
|
||||
|
||||
if (lastFocusEvent === 'didBlur') {
|
||||
// The child is currently blurred. Look for willFocus conditions
|
||||
if (eventName === 'willFocus' && isChildFocused) {
|
||||
emit((lastFocusEvent = 'willFocus'), childPayload);
|
||||
} else if (eventName === 'action' && isChildFocused) {
|
||||
emit((lastFocusEvent = 'willFocus'), childPayload);
|
||||
}
|
||||
}
|
||||
if (lastFocusEvent === 'willFocus') {
|
||||
// We are currently mid-focus. Look for didFocus conditions.
|
||||
// If state.isTransitioning is false, this child event happens immediately after willFocus
|
||||
if (eventName === 'didFocus' && isChildFocused && !isTransitioning) {
|
||||
emit((lastFocusEvent = 'didFocus'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
isChildFocused &&
|
||||
!isTransitioning
|
||||
) {
|
||||
emit((lastFocusEvent = 'didFocus'), childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastFocusEvent === 'didFocus') {
|
||||
// The child is currently focused. Look for blurring events
|
||||
if (!isChildFocused) {
|
||||
// The child is no longer focused within this navigation state
|
||||
emit((lastFocusEvent = 'willBlur'), childPayload);
|
||||
} else if (eventName === 'willBlur') {
|
||||
// The parent is getting a willBlur event
|
||||
emit((lastFocusEvent = 'willBlur'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
previouslylastFocusEvent === 'didFocus'
|
||||
) {
|
||||
// While focused, pass action events to children for grandchildren focus
|
||||
emit('action', childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastFocusEvent === 'willBlur') {
|
||||
// The child is mid-blur. Wait for transition to end
|
||||
if (eventName === 'action' && !isChildFocused && !isTransitioning) {
|
||||
// The child is done blurring because transitioning is over, or isTransitioning
|
||||
// never began and didBlur fires immediately after willBlur
|
||||
emit((lastFocusEvent = 'didBlur'), childPayload);
|
||||
} else if (eventName === 'didBlur') {
|
||||
// Pass through the parent didBlur event if it happens
|
||||
emit((lastFocusEvent = 'didBlur'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
isChildFocused &&
|
||||
!isTransitioning
|
||||
) {
|
||||
emit((lastFocusEvent = 'didFocus'), childPayload);
|
||||
} else if (
|
||||
eventName === 'action' &&
|
||||
isChildFocused &&
|
||||
isTransitioning
|
||||
) {
|
||||
emit((lastFocusEvent = 'willFocus'), childPayload);
|
||||
}
|
||||
}
|
||||
|
||||
if (lastFocusEvent === 'didBlur' && !newRoute) {
|
||||
removeAll();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
addListener(eventName, eventHandler) {
|
||||
const subscribers = getChildSubscribers(eventName);
|
||||
if (!subscribers) {
|
||||
throw new Error(`Invalid event name "${eventName}"`);
|
||||
}
|
||||
subscribers.add(eventHandler);
|
||||
const remove = () => {
|
||||
subscribers.delete(eventHandler);
|
||||
};
|
||||
return { remove };
|
||||
},
|
||||
emit(eventName, payload) {
|
||||
if (eventName !== 'refocus') {
|
||||
console.error(
|
||||
`navigation.emit only supports the 'refocus' event currently.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
emit(eventName, payload);
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import getChildEventSubscriber from './getChildEventSubscriber';
|
||||
import getChildRouter from './getChildRouter';
|
||||
import getNavigationActionCreators from './routers/getNavigationActionCreators';
|
||||
import getChildrenNavigationCache from './getChildrenNavigationCache';
|
||||
import getEventManager from './getEventManager';
|
||||
|
||||
const createParamGetter = route => (paramName, defaultValue) => {
|
||||
const createParamGetter = (route) => (paramName, defaultValue) => {
|
||||
const params = route.params;
|
||||
|
||||
if (params && paramName in params) {
|
||||
@@ -15,7 +15,7 @@ const createParamGetter = route => (paramName, defaultValue) => {
|
||||
|
||||
function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
const children = getChildrenNavigationCache(navigation);
|
||||
const childRoute = navigation.state.routes.find(r => r.key === childKey);
|
||||
const childRoute = navigation.state.routes.find((r) => r.key === childKey);
|
||||
|
||||
if (!childRoute) {
|
||||
return null;
|
||||
@@ -47,7 +47,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
};
|
||||
|
||||
const actionHelpers = {};
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
Object.keys(actionCreators).forEach((actionName) => {
|
||||
actionHelpers[actionName] = (...args) => {
|
||||
const actionCreator = actionCreators[actionName];
|
||||
const action = actionCreator(...args);
|
||||
@@ -78,10 +78,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
};
|
||||
return children[childKey];
|
||||
} else {
|
||||
const childSubscriber = getChildEventSubscriber(
|
||||
navigation.addListener,
|
||||
childKey
|
||||
);
|
||||
const { addListener, emit } = getEventManager(childKey);
|
||||
|
||||
children[childKey] = {
|
||||
...actionHelpers,
|
||||
@@ -91,7 +88,7 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
actions: actionCreators,
|
||||
getParam: createParamGetter(childRoute),
|
||||
|
||||
getChildNavigation: grandChildKey =>
|
||||
getChildNavigation: (grandChildKey) =>
|
||||
getChildNavigation(children[childKey], grandChildKey, () => {
|
||||
const nav = getCurrentParentNavigation();
|
||||
return nav && nav.getChildNavigation(childKey);
|
||||
@@ -115,9 +112,10 @@ function getChildNavigation(navigation, childKey, getCurrentParentNavigation) {
|
||||
dispatch: navigation.dispatch,
|
||||
getScreenProps: navigation.getScreenProps,
|
||||
dangerouslyGetParent: getCurrentParentNavigation,
|
||||
addListener: childSubscriber.addListener,
|
||||
emit: childSubscriber.emit,
|
||||
addListener,
|
||||
emit,
|
||||
};
|
||||
|
||||
return children[childKey];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ export default function getChildrenNavigationCache(navigation) {
|
||||
|
||||
let childrenNavigationCache =
|
||||
navigation._childrenNavigation || (navigation._childrenNavigation = {});
|
||||
let childKeys = navigation.state.routes.map(route => route.key);
|
||||
Object.keys(childrenNavigationCache).forEach(cacheKey => {
|
||||
let childKeys = navigation.state.routes.map((route) => route.key);
|
||||
Object.keys(childrenNavigationCache).forEach((cacheKey) => {
|
||||
if (!childKeys.includes(cacheKey) && !navigation.state.isTransitioning) {
|
||||
delete childrenNavigationCache[cacheKey];
|
||||
}
|
||||
|
||||
61
packages/core/src/getEventManager.js
Normal file
61
packages/core/src/getEventManager.js
Normal file
@@ -0,0 +1,61 @@
|
||||
// @ts-check
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
*/
|
||||
export default function getEventManager(target) {
|
||||
/**
|
||||
* @type {Record<string, Record<string, ((e: any) => void)[]>>}
|
||||
*/
|
||||
const listeners = {};
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
const removeListener = (type, callback) => {
|
||||
const callbacks = listeners[type] ? listeners[type][target] : undefined;
|
||||
|
||||
if (!callbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = callbacks.indexOf(callback);
|
||||
|
||||
callbacks.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {() => void} callback
|
||||
*/
|
||||
const addListener = (type, callback) => {
|
||||
listeners[type] = listeners[type] || {};
|
||||
listeners[type][target] = listeners[type][target] || [];
|
||||
listeners[type][target].push(callback);
|
||||
|
||||
return {
|
||||
remove: () => removeListener(type, callback),
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
addListener,
|
||||
|
||||
/**
|
||||
* @param {string} type
|
||||
* @param {any} [data]
|
||||
*/
|
||||
emit: (type, data) => {
|
||||
const items = listeners[type] || {};
|
||||
|
||||
/**
|
||||
* Copy the current list of callbacks in case they are mutated during execution
|
||||
* @type {((data: any) => void)[] | undefined}
|
||||
*/
|
||||
const callbacks = items[target] && items[target].slice();
|
||||
|
||||
callbacks?.forEach((cb) => cb(data));
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -18,9 +18,9 @@ export default function getNavigation(
|
||||
state,
|
||||
dispatch,
|
||||
getScreenProps,
|
||||
getChildNavigation: childKey =>
|
||||
getChildNavigation: (childKey) =>
|
||||
getChildNavigation(navigation, childKey, getCurrentNavigation),
|
||||
isFocused: childKey => {
|
||||
isFocused: (childKey) => {
|
||||
const { routes, index } = getCurrentNavigation().state;
|
||||
if (childKey == null || routes[index].key === childKey) {
|
||||
return true;
|
||||
@@ -48,7 +48,7 @@ export default function getNavigation(
|
||||
...actions,
|
||||
};
|
||||
|
||||
Object.keys(actionCreators).forEach(actionName => {
|
||||
Object.keys(actionCreators).forEach((actionName) => {
|
||||
navigation[actionName] = (...args) =>
|
||||
navigation.dispatch(actionCreators[actionName](...args));
|
||||
});
|
||||
|
||||
@@ -4,8 +4,8 @@ import { render, fireEvent } from 'react-native-testing-library';
|
||||
import { createAppContainer } from '@react-navigation/native';
|
||||
import createSwitchNavigator from '../createSwitchNavigator';
|
||||
|
||||
const getActiveRouteName = state => state.routes[state.index].routeName;
|
||||
const createScreen = (routeName, nextRouteName) => props => (
|
||||
const getActiveRouteName = (state) => state.routes[state.index].routeName;
|
||||
const createScreen = (routeName, nextRouteName) => (props) => (
|
||||
<View>
|
||||
<Text testID="title">{routeName}</Text>
|
||||
<Text testID={routeName + '-isFirstRoute'}>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import invariant from '../utils/invariant';
|
||||
import ThemeContext from '../views/ThemeContext';
|
||||
import NavigationFocusEvents from '../views/NavigationFocusEvents';
|
||||
|
||||
function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
class Navigator extends React.Component {
|
||||
@@ -35,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] &&
|
||||
@@ -43,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,
|
||||
@@ -56,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 };
|
||||
@@ -78,13 +79,21 @@ function createNavigator(NavigatorView, router, navigationConfig) {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<NavigatorView
|
||||
{...this.props}
|
||||
screenProps={this.state.screenProps}
|
||||
navigation={this.props.navigation}
|
||||
navigationConfig={navigationConfig}
|
||||
descriptors={this.state.descriptors}
|
||||
/>
|
||||
<React.Fragment>
|
||||
<NavigationFocusEvents
|
||||
navigation={this.props.navigation}
|
||||
onEvent={(target, type, data) => {
|
||||
this.state.descriptors[target]?.navigation.emit(type, data);
|
||||
}}
|
||||
/>
|
||||
<NavigatorView
|
||||
{...this.props}
|
||||
screenProps={this.state.screenProps}
|
||||
navigation={this.props.navigation}
|
||||
navigationConfig={navigationConfig}
|
||||
descriptors={this.state.descriptors}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,33 +5,33 @@ export const RESET = 'Navigation/RESET';
|
||||
export const REPLACE = 'Navigation/REPLACE';
|
||||
export const COMPLETE_TRANSITION = 'Navigation/COMPLETE_TRANSITION';
|
||||
|
||||
export const pop = payload => ({
|
||||
export const pop = (payload) => ({
|
||||
type: POP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const popToTop = payload => ({
|
||||
export const popToTop = (payload) => ({
|
||||
type: POP_TO_TOP,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const push = payload => ({
|
||||
export const push = (payload) => ({
|
||||
type: PUSH,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const reset = payload => ({
|
||||
export const reset = (payload) => ({
|
||||
type: RESET,
|
||||
key: null,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const replace = payload => ({
|
||||
export const replace = (payload) => ({
|
||||
type: REPLACE,
|
||||
...payload,
|
||||
});
|
||||
|
||||
export const completeTransition = payload => ({
|
||||
export const completeTransition = (payload) => ({
|
||||
type: COMPLETE_TRANSITION,
|
||||
preserveFocus: true,
|
||||
...payload,
|
||||
|
||||
@@ -29,7 +29,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
const routeNames = Object.keys(routeConfigs);
|
||||
|
||||
// Loop through routes and find child routers
|
||||
routeNames.forEach(routeName => {
|
||||
routeNames.forEach((routeName) => {
|
||||
// We're not using `getScreenForRouteName` here to preserve the lazy loading
|
||||
// behaviour of routes. This means that routes with child routers must be
|
||||
// defined using a component directly or with an object with a screen prop.
|
||||
@@ -152,7 +152,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
n,
|
||||
...params,
|
||||
}),
|
||||
popToTop: params => StackActions.popToTop(params),
|
||||
popToTop: (params) => StackActions.popToTop(params),
|
||||
push: (routeName, params, action) =>
|
||||
StackActions.push({
|
||||
routeName,
|
||||
@@ -280,7 +280,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
// Before pushing a new route we first try to find one in the existing route stack
|
||||
// More information on this: https://github.com/react-navigation/rfcs/blob/master/text/0004-less-pushy-navigate.md
|
||||
const lastRouteIndex = state.routes.findIndex(r => {
|
||||
const lastRouteIndex = state.routes.findIndex((r) => {
|
||||
if (action.key) {
|
||||
return r.key === action.key;
|
||||
} else {
|
||||
@@ -425,7 +425,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
if (action.key === undefined && state.routes.length) {
|
||||
routeIndex = state.routes.length - 1;
|
||||
} else {
|
||||
routeIndex = state.routes.findIndex(r => r.key === action.key);
|
||||
routeIndex = state.routes.findIndex((r) => r.key === action.key);
|
||||
}
|
||||
|
||||
// Only replace if the key matches one of our routes
|
||||
@@ -467,7 +467,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
if (action.type === NavigationActions.SET_PARAMS) {
|
||||
const key = action.key;
|
||||
const lastRoute = state.routes.find(route => route.key === key);
|
||||
const lastRoute = state.routes.find((route) => route.key === key);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.params,
|
||||
@@ -496,7 +496,7 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
routes: newStackActions.map(newStackAction => {
|
||||
routes: newStackActions.map((newStackAction) => {
|
||||
const router = childRouters[newStackAction.routeName];
|
||||
|
||||
let childState = {};
|
||||
@@ -532,24 +532,46 @@ export default (routeConfigs, stackConfig = {}) => {
|
||||
action.type === NavigationActions.BACK ||
|
||||
action.type === StackActions.POP
|
||||
) {
|
||||
const { key, n, immediate } = action;
|
||||
let backRouteIndex = state.index;
|
||||
if (action.type === StackActions.POP && n != null) {
|
||||
// determine the index to go back *from*. In this case, n=1 means to go
|
||||
// back from state.index, as if it were a normal "BACK" action
|
||||
backRouteIndex = Math.max(1, state.index - n + 1);
|
||||
} else if (key) {
|
||||
const backRoute = state.routes.find(route => route.key === key);
|
||||
backRouteIndex = state.routes.indexOf(backRoute);
|
||||
}
|
||||
const { key, n, immediate, prune } = action;
|
||||
|
||||
if (backRouteIndex > 0) {
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.slice(0, backRouteIndex),
|
||||
index: backRouteIndex - 1,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
if (action.type === StackActions.POP && prune === false && key) {
|
||||
const index = state.routes.findIndex((r) => r.key === key);
|
||||
|
||||
if (index > 0) {
|
||||
const count = Math.max(index - (n ?? 1) + 1, 1);
|
||||
const routes = state.routes
|
||||
.slice(0, count)
|
||||
.concat(state.routes.slice(index + 1));
|
||||
|
||||
if (routes.length) {
|
||||
return {
|
||||
...state,
|
||||
routes,
|
||||
index: routes.length - 1,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let backRouteIndex = state.index;
|
||||
|
||||
if (action.type === StackActions.POP && n != null) {
|
||||
// determine the index to go back *from*. In this case, n=1 means to go
|
||||
// back from state.index, as if it were a normal "BACK" action
|
||||
backRouteIndex = Math.max(1, state.index - n + 1);
|
||||
} else if (key) {
|
||||
const backRoute = state.routes.find((route) => route.key === key);
|
||||
backRouteIndex = state.routes.indexOf(backRoute);
|
||||
}
|
||||
|
||||
if (backRouteIndex > 0) {
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.slice(0, backRouteIndex),
|
||||
index: backRouteIndex - 1,
|
||||
isTransitioning: immediate !== true,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
@@ -30,12 +30,12 @@ export default (routeConfigs, config = {}) => {
|
||||
if (initialRouteIndex === -1) {
|
||||
throw new Error(
|
||||
`Invalid initialRouteName '${initialRouteName}'.` +
|
||||
`Should be one of ${order.map(n => `"${n}"`).join(', ')}`
|
||||
`Should be one of ${order.map((n) => `"${n}"`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
const childRouters = {};
|
||||
order.forEach(routeName => {
|
||||
order.forEach((routeName) => {
|
||||
childRouters[routeName] = null;
|
||||
const screen = getScreenForRouteName(routeConfigs, routeName);
|
||||
if (screen.router) {
|
||||
@@ -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 } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export default (routeConfigs, config = {}) => {
|
||||
if (action.type === NavigationActions.NAVIGATE) {
|
||||
nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy
|
||||
const keyToAdd = nextState.routes[nextState.index].key;
|
||||
nextRouteKeyHistory = nextRouteKeyHistory.filter(k => k !== keyToAdd); // dedup
|
||||
nextRouteKeyHistory = nextRouteKeyHistory.filter((k) => k !== keyToAdd); // dedup
|
||||
nextRouteKeyHistory.push(keyToAdd);
|
||||
} else if (action.type === NavigationActions.BACK) {
|
||||
nextRouteKeyHistory = [...nextRouteKeyHistory]; // copy
|
||||
@@ -128,8 +128,8 @@ export default (routeConfigs, config = {}) => {
|
||||
const initialState = {
|
||||
routes,
|
||||
index: initialRouteIndex,
|
||||
isTransitioning: false,
|
||||
};
|
||||
|
||||
if (backBehavior === 'history') {
|
||||
const initialKey = routes[initialRouteIndex].key;
|
||||
initialState['routeKeyHistory'] = [initialKey];
|
||||
@@ -156,7 +156,7 @@ export default (routeConfigs, config = {}) => {
|
||||
// Merge any params from the action into all the child routes
|
||||
const { params } = action;
|
||||
if (params) {
|
||||
state.routes = state.routes.map(route => ({
|
||||
state.routes = state.routes.map((route) => ({
|
||||
...route,
|
||||
params: {
|
||||
...route.params,
|
||||
@@ -175,14 +175,14 @@ export default (routeConfigs, config = {}) => {
|
||||
) {
|
||||
const { params } = action;
|
||||
const index = state.routes.findIndex(
|
||||
route => route.routeName === action.routeName
|
||||
(route) => route.routeName === action.routeName
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error(
|
||||
`There is no route named '${action.routeName}' in the navigator with the key '${action.key}'.\n` +
|
||||
`Must be one of: ${state.routes
|
||||
.map(route => `'${route.routeName}'`)
|
||||
.map((route) => `'${route.routeName}'`)
|
||||
.join(',')}`
|
||||
);
|
||||
}
|
||||
@@ -305,7 +305,7 @@ export default (routeConfigs, config = {}) => {
|
||||
|
||||
if (action.type === NavigationActions.SET_PARAMS) {
|
||||
const key = action.key;
|
||||
const lastRoute = state.routes.find(route => route.key === key);
|
||||
const lastRoute = state.routes.find((route) => route.key === key);
|
||||
if (lastRoute) {
|
||||
const params = {
|
||||
...lastRoute.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';
|
||||
@@ -12,7 +12,7 @@ beforeEach(() => {
|
||||
_TESTING_ONLY_normalize_keys();
|
||||
});
|
||||
|
||||
const performRouterTest = createTestRouter => {
|
||||
const performRouterTest = (createTestRouter) => {
|
||||
const ListScreen = () => <div />;
|
||||
|
||||
const ProfileNavigator = () => <div />;
|
||||
@@ -584,7 +584,7 @@ it('Handles nested switch routers', () => {
|
||||
expect(action.action.routeName).toEqual('B');
|
||||
});
|
||||
|
||||
const performRouteNameAsPathDisabledTest = createTestRouter => {
|
||||
const performRouteNameAsPathDisabledTest = (createTestRouter) => {
|
||||
const BScreen = () => <div />;
|
||||
const NestedNavigator = () => <div />;
|
||||
NestedNavigator.router = createTestRouter({
|
||||
|
||||
@@ -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';
|
||||
@@ -24,7 +24,7 @@ const dummyEventSubscriber = () => ({
|
||||
remove: () => {},
|
||||
});
|
||||
|
||||
Object.keys(ROUTERS).forEach(routerName => {
|
||||
Object.keys(ROUTERS).forEach((routerName) => {
|
||||
const Router = ROUTERS[routerName];
|
||||
|
||||
describe(`General router features - ${routerName}`, () => {
|
||||
@@ -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 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -441,7 +486,7 @@ it('Inner actions are only unpacked if the current tab matches', () => {
|
||||
routes: [screenApreState],
|
||||
};
|
||||
|
||||
const comparable = state => {
|
||||
const comparable = (state) => {
|
||||
let result = {};
|
||||
if (typeof state.routeName === 'string') {
|
||||
result = { ...result, routeName: state.routeName };
|
||||
|
||||
@@ -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';
|
||||
@@ -562,6 +562,31 @@ describe('StackRouter', () => {
|
||||
expect(poppedState3.routes.length).toBe(1);
|
||||
expect(poppedState3.index).toBe(0);
|
||||
expect(poppedState3.isTransitioning).toBe(true);
|
||||
|
||||
const poppedState4 = TestRouter.getStateForAction(
|
||||
StackActions.pop({ key: 'C', prune: false, immediate: true }),
|
||||
state
|
||||
);
|
||||
expect(poppedState4.routes.length).toBe(3);
|
||||
expect(poppedState4.index).toBe(2);
|
||||
expect(poppedState4.isTransitioning).toBe(false);
|
||||
expect(poppedState4.routes).toEqual([
|
||||
{ key: 'A', routeName: 'foo' },
|
||||
{ key: 'B', routeName: 'bar', params: { bazId: '321' } },
|
||||
{ key: 'D', routeName: 'bar' },
|
||||
]);
|
||||
|
||||
const poppedState5 = TestRouter.getStateForAction(
|
||||
StackActions.pop({ n: 2, key: 'C', prune: false }),
|
||||
state
|
||||
);
|
||||
expect(poppedState5.routes.length).toBe(2);
|
||||
expect(poppedState5.index).toBe(1);
|
||||
expect(poppedState5.isTransitioning).toBe(true);
|
||||
expect(poppedState5.routes).toEqual([
|
||||
{ key: 'A', routeName: 'foo' },
|
||||
{ key: 'D', routeName: 'bar' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('popToTop works as expected', () => {
|
||||
|
||||
@@ -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';
|
||||
@@ -26,7 +26,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(state).toEqual(expectedState);
|
||||
const state2 = router.getStateForAction(
|
||||
@@ -39,7 +38,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(state2).toEqual(expectedState2);
|
||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||
@@ -65,7 +63,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(state).toEqual(expectedState);
|
||||
const state2 = router.getStateForAction(
|
||||
@@ -78,7 +75,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
],
|
||||
isTransitioning: false,
|
||||
};
|
||||
expect(state2).toEqual(expectedState2);
|
||||
expect(router.getComponentForState(expectedState)).toEqual(ScreenA);
|
||||
@@ -102,7 +98,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
],
|
||||
isTransitioning: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,7 +113,6 @@ describe('TabRouter', () => {
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar', params: { name: 'Qux' } },
|
||||
],
|
||||
isTransitioning: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -230,7 +224,7 @@ describe('TabRouter', () => {
|
||||
const state = router.getStateForAction(navAction);
|
||||
expect(state).toEqual({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
key: 'Foo',
|
||||
@@ -238,7 +232,7 @@ describe('TabRouter', () => {
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
params: { foo: '42' },
|
||||
@@ -281,14 +275,14 @@ describe('TabRouter', () => {
|
||||
let state = router.getStateForAction(navAction);
|
||||
expect(state).toEqual({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{
|
||||
index: 0,
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
@@ -308,7 +302,7 @@ describe('TabRouter', () => {
|
||||
);
|
||||
expect(state && state.routes[1]).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
routes: [
|
||||
@@ -335,14 +329,14 @@ describe('TabRouter', () => {
|
||||
});
|
||||
expect(state).toEqual({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{
|
||||
index: 1,
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
@@ -357,14 +351,14 @@ describe('TabRouter', () => {
|
||||
);
|
||||
expect(state2).toEqual({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{
|
||||
index: 0,
|
||||
key: 'Baz',
|
||||
routeName: 'Baz',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
@@ -403,19 +397,19 @@ describe('TabRouter', () => {
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
{ key: 'Baz', routeName: 'Baz' },
|
||||
@@ -425,7 +419,7 @@ describe('TabRouter', () => {
|
||||
index: 0,
|
||||
key: 'Bar',
|
||||
routeName: 'Bar',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Zoo', routeName: 'Zoo' },
|
||||
{ key: 'Zap', routeName: 'Zap' },
|
||||
@@ -442,19 +436,19 @@ describe('TabRouter', () => {
|
||||
);
|
||||
expect(state2).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 1,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
{ key: 'Baz', routeName: 'Baz' },
|
||||
@@ -464,7 +458,7 @@ describe('TabRouter', () => {
|
||||
index: 1,
|
||||
key: 'Bar',
|
||||
routeName: 'Bar',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Zoo', routeName: 'Zoo' },
|
||||
{ key: 'Zap', routeName: 'Zap' },
|
||||
@@ -494,19 +488,19 @@ describe('TabRouter', () => {
|
||||
});
|
||||
expect(state4).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 1,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 0,
|
||||
key: 'Fee',
|
||||
routeName: 'Fee',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
{ key: 'Baz', routeName: 'Baz' },
|
||||
@@ -516,7 +510,7 @@ describe('TabRouter', () => {
|
||||
index: 1,
|
||||
key: 'Bar',
|
||||
routeName: 'Bar',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Zoo', routeName: 'Zoo' },
|
||||
{ key: 'Zap', routeName: 'Zap' },
|
||||
@@ -557,7 +551,7 @@ describe('TabRouter', () => {
|
||||
const state = router.getStateForAction({ type: NavigationActions.INIT });
|
||||
const expectedState = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo' },
|
||||
{ key: 'Bar', routeName: 'Bar' },
|
||||
@@ -567,7 +561,7 @@ describe('TabRouter', () => {
|
||||
const state2 = router.getStateForAction(expectedAction, state);
|
||||
const expectedState2 = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{
|
||||
@@ -628,13 +622,13 @@ describe('TabRouter', () => {
|
||||
|
||||
const state = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
index: 1,
|
||||
key: 'Foo',
|
||||
routeName: 'Foo',
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Boo', routeName: 'Boo' },
|
||||
{ key: 'Baz', routeName: 'Baz' },
|
||||
@@ -660,7 +654,7 @@ describe('TabRouter', () => {
|
||||
|
||||
expect(state0).toEqual({
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'a', routeName: 'a' },
|
||||
{ key: 'b', routeName: 'b' },
|
||||
@@ -676,7 +670,7 @@ describe('TabRouter', () => {
|
||||
|
||||
expect(state1).toEqual({
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'a', routeName: 'a' },
|
||||
{ key: 'b', routeName: 'b', params },
|
||||
@@ -765,17 +759,17 @@ describe('TabRouter', () => {
|
||||
const screenApreState = {
|
||||
index: 0,
|
||||
key: 'Foo',
|
||||
isTransitioning: false,
|
||||
|
||||
routeName: 'Foo',
|
||||
routes: [{ key: 'Bar', routeName: 'Bar' }],
|
||||
};
|
||||
const preState = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [screenApreState],
|
||||
};
|
||||
|
||||
const comparable = state => {
|
||||
const comparable = (state) => {
|
||||
let result = {};
|
||||
if (typeof state.routeName === 'string') {
|
||||
result = { ...result, routeName: state.routeName };
|
||||
|
||||
@@ -17,7 +17,7 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
|
||||
? undefined
|
||||
: router.getStateForAction({ type: NavigationActions.INIT });
|
||||
|
||||
const applyAction = action => {
|
||||
const applyAction = (action) => {
|
||||
state = router.getStateForAction(action, state);
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@ export const getRouterTestHelper = (router, options = defaultOptions) => {
|
||||
...otherActionAttributes,
|
||||
});
|
||||
|
||||
const back = key =>
|
||||
const back = (key) =>
|
||||
applyAction({
|
||||
type: NavigationActions.BACK,
|
||||
key,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
|
||||
import validateRouteConfigMap from '../validateRouteConfigMap';
|
||||
import StackRouter from '../StackRouter';
|
||||
|
||||
@@ -13,7 +13,7 @@ export default function getScreenForRouteName(routeConfigs, routeName) {
|
||||
throw new Error(
|
||||
`There is no route defined for key ${routeName}.\n` +
|
||||
`Must be one of: ${Object.keys(routeConfigs)
|
||||
.map(a => `'${a}'`)
|
||||
.map((a) => `'${a}'`)
|
||||
.join(',')}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ export const getParamsFromPath = (inputParams, pathMatch, pathMatchKeys) => {
|
||||
return params;
|
||||
};
|
||||
const getRestOfPath = (pathMatch, pathMatchKeys) => {
|
||||
const rest = pathMatch[pathMatchKeys.findIndex(k => k.asterisk) + 1];
|
||||
const rest = pathMatch[pathMatchKeys.findIndex((k) => k.asterisk) + 1];
|
||||
return rest;
|
||||
};
|
||||
|
||||
@@ -81,7 +81,7 @@ export const createPathParser = (
|
||||
let paths = [];
|
||||
|
||||
// Build pathsByRouteNames, which includes a regex to match paths for each route. Keep in mind, the regex will pass for the route and all child routes. The code that uses pathsByRouteNames will need to also verify that the child router produces an action, in the case of isPathMatchable false (a null path).
|
||||
Object.keys(childRouters).forEach(routeName => {
|
||||
Object.keys(childRouters).forEach((routeName) => {
|
||||
let pathPattern;
|
||||
|
||||
// First check for paths on the router, then check the route config
|
||||
@@ -189,7 +189,7 @@ export const createPathParser = (
|
||||
|
||||
return null;
|
||||
};
|
||||
const getPathAndParamsForRoute = route => {
|
||||
const getPathAndParamsForRoute = (route) => {
|
||||
const { routeName, params } = route;
|
||||
const childRouter = childRouters[routeName];
|
||||
const { toPath, exactReKeys } = pathsByRouteNames[routeName];
|
||||
@@ -197,8 +197,8 @@ export const createPathParser = (
|
||||
const nonPathParams = {};
|
||||
if (params) {
|
||||
Object.keys(params)
|
||||
.filter(paramName => !exactReKeys.find(k => k.name === paramName))
|
||||
.forEach(paramName => {
|
||||
.filter((paramName) => !exactReKeys.find((k) => k.name === paramName))
|
||||
.forEach((paramName) => {
|
||||
nonPathParams[paramName] = params[paramName];
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ function validateRouteConfigMap(routeConfigs) {
|
||||
'Please specify at least one route when configuring a navigator.'
|
||||
);
|
||||
|
||||
routeNames.forEach(routeName => {
|
||||
routeNames.forEach((routeName) => {
|
||||
const routeConfig = routeConfigs[routeName];
|
||||
const screenComponent = getScreenComponent(routeConfig);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const deprecatedKeys = ['tabBar'];
|
||||
export default (screenOptions, route) => {
|
||||
const keys = Object.keys(screenOptions);
|
||||
|
||||
const deprecatedKey = keys.find(key => deprecatedKeys.includes(key));
|
||||
const deprecatedKey = keys.find((key) => deprecatedKeys.includes(key));
|
||||
|
||||
if (typeof screenOptions.title === 'function') {
|
||||
throw new Error(
|
||||
@@ -54,7 +54,7 @@ export default (screenOptions, route) => {
|
||||
'{',
|
||||
` ${deprecatedKey}: {`,
|
||||
...Object.keys(screenOptions[deprecatedKey]).map(
|
||||
key => ` ${key}: ...,`
|
||||
(key) => ` ${key}: ...,`
|
||||
),
|
||||
' },',
|
||||
'}',
|
||||
@@ -62,7 +62,7 @@ export default (screenOptions, route) => {
|
||||
'with:',
|
||||
'{',
|
||||
...Object.keys(screenOptions[deprecatedKey]).map(
|
||||
key =>
|
||||
(key) =>
|
||||
` ${deprecatedKey + key[0].toUpperCase() + key.slice(1)}: ...,`
|
||||
),
|
||||
'}',
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export default function docsUrl(path: string) {
|
||||
return `https://reactnavigation.org/docs/${path}`;
|
||||
return `https://reactnavigation.org/docs/4.x/${path}`;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,10 @@ function getSceneIndicesForInterpolationInputRange(props) {
|
||||
const isBack = !scenes[lastSceneIndexInScenes].isActive;
|
||||
|
||||
if (isBack) {
|
||||
const currentSceneIndexInScenes = scenes.findIndex(item => item === scene);
|
||||
const targetSceneIndexInScenes = scenes.findIndex(item => item.isActive);
|
||||
const currentSceneIndexInScenes = scenes.findIndex(
|
||||
(item) => item === scene
|
||||
);
|
||||
const targetSceneIndexInScenes = scenes.findIndex((item) => item.isActive);
|
||||
const targetSceneIndex = scenes[targetSceneIndexInScenes].index;
|
||||
const lastSceneIndex = scenes[lastSceneIndexInScenes].index;
|
||||
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
* will remain to ensure logic does not differ in production.
|
||||
*/
|
||||
|
||||
let validateFormat: (format?: string) => void = function() {};
|
||||
let validateFormat: (format?: string) => void = function () {};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
validateFormat = function(format?: string) {
|
||||
validateFormat = function (format?: string) {
|
||||
if (format === undefined) {
|
||||
throw new Error('invariant requires an error message argument');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import withNavigation from './withNavigation';
|
||||
|
||||
const EventNameToPropName = {
|
||||
@@ -12,12 +12,29 @@ 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)
|
||||
EventNames.forEach(eventName => {
|
||||
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,
|
||||
(...args) => {
|
||||
@@ -28,14 +45,12 @@ class NavigationEvents extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
EventNames.forEach(eventName => {
|
||||
removeAll() {
|
||||
EventNames.forEach((eventName) => {
|
||||
this.subscriptions[eventName].remove();
|
||||
});
|
||||
}
|
||||
|
||||
getPropListener = eventName => this.props[EventNameToPropName[eventName]];
|
||||
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
|
||||
360
packages/core/src/views/NavigationFocusEvents.js
Normal file
360
packages/core/src/views/NavigationFocusEvents.js
Normal file
@@ -0,0 +1,360 @@
|
||||
// @ts-check
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
/**
|
||||
* @typedef {object} State
|
||||
* @prop {number} index
|
||||
* @prop {({ key: string } & (State | {}))[]} routes
|
||||
* @prop {boolean} [isTransitioning]
|
||||
*
|
||||
* @typedef {object} ParentPayload
|
||||
* @prop {string} type
|
||||
* @prop {object} action
|
||||
* @prop {State} state
|
||||
* @prop {State | {key: string, routes?: undefined, index?: undefined, isTransitioning?: undefined} | undefined | null} lastState
|
||||
* @prop {string} [context]
|
||||
*
|
||||
* @typedef {object} Payload
|
||||
* @prop {string} type
|
||||
* @prop {object} action
|
||||
* @prop {State | {key: string}} state
|
||||
* @prop {State | {key: string} | undefined | null} lastState
|
||||
* @prop {string} [context]
|
||||
*
|
||||
* @typedef {object} Props
|
||||
* @prop {object} navigation
|
||||
* @prop {Function} navigation.addListener
|
||||
* @prop {Function} navigation.removeListener
|
||||
* @prop {() => boolean} navigation.isFocused
|
||||
* @prop {() => object | undefined} navigation.dangerouslyGetParent
|
||||
* @prop {State} navigation.state
|
||||
* @prop {(target: string, type: string, data: any) => void} onEvent
|
||||
*
|
||||
* @extends {React.Component<Props>}
|
||||
*/
|
||||
export default class NavigationEventManager extends React.Component {
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
this._actionSubscription = navigation.addListener(
|
||||
'action',
|
||||
this._handleAction
|
||||
);
|
||||
|
||||
this._willFocusSubscription = navigation.addListener(
|
||||
'willFocus',
|
||||
this._handleWillFocus
|
||||
);
|
||||
|
||||
this._willBlurSubscription = navigation.addListener(
|
||||
'willBlur',
|
||||
this._handleWillBlur
|
||||
);
|
||||
|
||||
this._didFocusSubscription = navigation.addListener(
|
||||
'didFocus',
|
||||
this._handleDidFocus
|
||||
);
|
||||
|
||||
this._didBlurSubscription = navigation.addListener(
|
||||
'didBlur',
|
||||
this._handleDidBlur
|
||||
);
|
||||
|
||||
this._refocusSubscription = navigation.addListener(
|
||||
'refocus',
|
||||
this._handleRefocus
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._actionSubscription?.remove();
|
||||
this._willFocusSubscription?.remove();
|
||||
this._willBlurSubscription?.remove();
|
||||
this._didFocusSubscription?.remove();
|
||||
this._didBlurSubscription?.remove();
|
||||
this._refocusSubscription?.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_actionSubscription;
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_willFocusSubscription;
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_willBlurSubscription;
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_didFocusSubscription;
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_didBlurSubscription;
|
||||
|
||||
/**
|
||||
* @type {{ remove(): void } | undefined}
|
||||
*/
|
||||
_refocusSubscription;
|
||||
|
||||
/**
|
||||
* @type {string | undefined}
|
||||
*/
|
||||
_lastWillBlurKey;
|
||||
|
||||
/**
|
||||
* @type {string | undefined}
|
||||
*/
|
||||
_lastWillFocusKey;
|
||||
|
||||
/**
|
||||
* @type {string | undefined}
|
||||
*/
|
||||
_lastDidBlurKey;
|
||||
|
||||
/**
|
||||
* @type {string | undefined}
|
||||
*/
|
||||
_lastDidFocusKey;
|
||||
|
||||
/**
|
||||
* The 'action' event will fire when navigation state changes.
|
||||
* Detect if the focused route changed here and emit appropriate events.
|
||||
*
|
||||
* @param {ParentPayload} payload
|
||||
*/
|
||||
_handleAction = ({ state, lastState, action, type, context }) => {
|
||||
const { navigation, onEvent } = this.props;
|
||||
|
||||
// We should only emit events when the navigator is focused
|
||||
// When navigator is not focused, screens inside shouldn't receive focused status either
|
||||
if (!navigation.isFocused()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previous = lastState
|
||||
? lastState.routes?.[lastState.index]
|
||||
: undefined;
|
||||
const current = state.routes[state.index];
|
||||
|
||||
const payload = {
|
||||
context: `${current.key}:${action.type}_${context || 'Root'}`,
|
||||
state: current,
|
||||
lastState: previous,
|
||||
action,
|
||||
type,
|
||||
};
|
||||
|
||||
if (previous?.key !== current.key) {
|
||||
this._emitWillFocus(current.key, payload);
|
||||
|
||||
if (previous?.key) {
|
||||
this._emitWillBlur(previous.key, payload);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
lastState?.isTransitioning !== state.isTransitioning &&
|
||||
state.isTransitioning === false
|
||||
) {
|
||||
if (this._lastWillBlurKey) {
|
||||
this._emitDidBlur(this._lastWillBlurKey, payload);
|
||||
}
|
||||
|
||||
if (this._lastWillFocusKey) {
|
||||
this._emitDidFocus(this._lastWillFocusKey, payload);
|
||||
}
|
||||
}
|
||||
|
||||
onEvent(current.key, 'action', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ParentPayload} payload
|
||||
*/
|
||||
_handleWillFocus = ({ lastState, action, context, type }) => {
|
||||
const { navigation } = this.props;
|
||||
const route = navigation.state.routes[navigation.state.index];
|
||||
|
||||
this._emitWillFocus(route.key, {
|
||||
context: `${route.key}:${action.type}_${context || 'Root'}`,
|
||||
state: route,
|
||||
lastState: lastState?.routes?.find((r) => r.key === route.key),
|
||||
action,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ParentPayload} payload
|
||||
*/
|
||||
_handleWillBlur = ({ lastState, action, context, type }) => {
|
||||
const { navigation } = this.props;
|
||||
const route = navigation.state.routes[navigation.state.index];
|
||||
|
||||
this._emitWillBlur(route.key, {
|
||||
context: `${route.key}:${action.type}_${context || 'Root'}`,
|
||||
state: route,
|
||||
lastState: lastState?.routes?.find((r) => r.key === route.key),
|
||||
action,
|
||||
type,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ParentPayload} payload
|
||||
*/
|
||||
_handleDidFocus = ({ lastState, action, context, type }) => {
|
||||
const { navigation } = this.props;
|
||||
|
||||
if (this._lastWillFocusKey) {
|
||||
const route = navigation.state.routes.find(
|
||||
(r) => r.key === this._lastWillFocusKey
|
||||
);
|
||||
|
||||
if (route) {
|
||||
this._emitDidFocus(route.key, {
|
||||
context: `${route.key}:${action.type}_${context || 'Root'}`,
|
||||
state: route,
|
||||
lastState: lastState?.routes?.find((r) => r.key === route.key),
|
||||
action,
|
||||
type,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {ParentPayload} payload
|
||||
*/
|
||||
_handleDidBlur = ({ lastState, action, context, type }) => {
|
||||
const { navigation } = this.props;
|
||||
|
||||
if (this._lastWillBlurKey) {
|
||||
const route = navigation.state.routes.find(
|
||||
(r) => r.key === this._lastWillBlurKey
|
||||
);
|
||||
|
||||
if (route) {
|
||||
this._emitDidBlur(route.key, {
|
||||
context: `${route.key}:${action.type}_${context || 'Root'}`,
|
||||
state: route,
|
||||
lastState: lastState?.routes?.find((r) => r.key === route.key),
|
||||
action,
|
||||
type,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_handleRefocus = () => {
|
||||
const { onEvent, navigation } = this.props;
|
||||
const route = navigation.state.routes[navigation.state.index];
|
||||
|
||||
onEvent(route.key, 'refocus');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
* @param {Payload} payload
|
||||
*/
|
||||
_emitWillFocus = (target, payload) => {
|
||||
if (this._lastWillBlurKey === target) {
|
||||
this._lastWillBlurKey = undefined;
|
||||
}
|
||||
|
||||
if (this._lastWillFocusKey === target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastDidFocusKey = undefined;
|
||||
this._lastWillFocusKey = target;
|
||||
|
||||
const { navigation, onEvent } = this.props;
|
||||
|
||||
onEvent(target, 'willFocus', payload);
|
||||
|
||||
if (
|
||||
typeof navigation.state.isTransitioning !== 'boolean' ||
|
||||
(navigation.state.isTransitioning !== true &&
|
||||
!navigation.dangerouslyGetParent())
|
||||
) {
|
||||
this._emitDidFocus(target, payload);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
* @param {Payload} payload
|
||||
*/
|
||||
_emitWillBlur = (target, payload) => {
|
||||
if (this._lastWillFocusKey === target) {
|
||||
this._lastWillFocusKey = undefined;
|
||||
}
|
||||
|
||||
if (this._lastWillBlurKey === target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastDidBlurKey = undefined;
|
||||
this._lastWillBlurKey = target;
|
||||
|
||||
const { navigation, onEvent } = this.props;
|
||||
|
||||
onEvent(target, 'willBlur', payload);
|
||||
|
||||
if (
|
||||
typeof navigation.state.isTransitioning !== 'boolean' ||
|
||||
(navigation.state.isTransitioning !== true &&
|
||||
!navigation.dangerouslyGetParent())
|
||||
) {
|
||||
this._emitDidBlur(target, payload);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
* @param {Payload} payload
|
||||
*/
|
||||
_emitDidFocus = (target, payload) => {
|
||||
if (this._lastWillFocusKey !== target || this._lastDidFocusKey === target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastDidFocusKey = target;
|
||||
|
||||
const { onEvent } = this.props;
|
||||
|
||||
onEvent(target, 'didFocus', payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} target
|
||||
* @param {Payload} payload
|
||||
*/
|
||||
_emitDidBlur = (target, payload) => {
|
||||
if (this._lastWillBlurKey !== target || this._lastDidBlurKey === target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastDidBlurKey = target;
|
||||
|
||||
const { onEvent } = this.props;
|
||||
|
||||
onEvent(target, 'didBlur', payload);
|
||||
};
|
||||
|
||||
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(),
|
||||
@@ -26,13 +33,15 @@ const createTestNavigationAndHelpers = () => {
|
||||
listeners[eventName].push(handler);
|
||||
},
|
||||
remove: (eventName, handler) => {
|
||||
listeners[eventName] = listeners[eventName].filter(h => h !== handler);
|
||||
listeners[eventName] = listeners[eventName].filter(
|
||||
(h) => h !== handler
|
||||
);
|
||||
},
|
||||
get: eventName => {
|
||||
get: (eventName) => {
|
||||
return listeners[eventName];
|
||||
},
|
||||
call: eventName => {
|
||||
listeners[eventName].forEach(listener => listener());
|
||||
call: (eventName) => {
|
||||
listeners[eventName].forEach((listener) => listener());
|
||||
},
|
||||
};
|
||||
})();
|
||||
@@ -120,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,
|
||||
|
||||
@@ -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';
|
||||
@@ -8,14 +8,15 @@ export default function withNavigation(
|
||||
config = { forwardRef: true }
|
||||
) {
|
||||
class ComponentWithNavigation extends React.Component {
|
||||
static displayName = `withNavigation(${Component.displayName ||
|
||||
Component.name})`;
|
||||
static displayName = `withNavigation(${
|
||||
Component.displayName || Component.name
|
||||
})`;
|
||||
|
||||
render() {
|
||||
const navigationProp = this.props.navigation;
|
||||
return (
|
||||
<NavigationContext.Consumer>
|
||||
{navigationContext => {
|
||||
{(navigationContext) => {
|
||||
const navigation = navigationProp || navigationContext;
|
||||
invariant(
|
||||
!!navigation,
|
||||
|
||||
@@ -1,31 +1,32 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import withNavigation from './withNavigation';
|
||||
|
||||
export default function withNavigationFocus(Component) {
|
||||
class ComponentWithNavigationFocus extends React.Component {
|
||||
static displayName = `withNavigationFocus(${Component.displayName ||
|
||||
Component.name})`;
|
||||
static displayName = `withNavigationFocus(${
|
||||
Component.displayName || Component.name
|
||||
})`;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
state = {
|
||||
isFocused: this.props.navigation.isFocused(),
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { navigation } = this.props;
|
||||
|
||||
this.subscriptions = [
|
||||
props.navigation.addListener('didFocus', () =>
|
||||
navigation.addListener('willFocus', () =>
|
||||
this.setState({ isFocused: true })
|
||||
),
|
||||
props.navigation.addListener('willBlur', () =>
|
||||
navigation.addListener('willBlur', () =>
|
||||
this.setState({ isFocused: false })
|
||||
),
|
||||
];
|
||||
|
||||
this.state = {
|
||||
isFocused: props.navigation ? props.navigation.isFocused() : false,
|
||||
};
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscriptions.forEach(sub => sub.remove());
|
||||
this.subscriptions?.forEach((sub) => sub.remove());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
6
packages/core/tsconfig.json
Normal file
6
packages/core/tsconfig.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,127 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack and upgrade typescript ([454e05b](https://github.com/react-navigation/drawer/commit/454e05b02ec97f053b381fdc801df899d8c93cb6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.13](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.12...react-navigation-drawer@2.4.13) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.12](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.11...react-navigation-drawer@2.4.12) (2020-04-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.11](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.10...react-navigation-drawer@2.4.11) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.10](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.9...react-navigation-drawer@2.4.10) (2020-03-31)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove isTransitioning from SwitchRouter state ([3bec1c9](https://github.com/react-navigation/drawer/commit/3bec1c964a49136c0ead8e8ba8a8c66c556bbcba))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.9](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.7...react-navigation-drawer@2.4.9) (2020-03-28)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.8](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.7...react-navigation-drawer@2.4.8) (2020-03-27)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.7](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.6...react-navigation-drawer@2.4.7) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.6](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.5...react-navigation-drawer@2.4.6) (2020-03-20)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.5](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.4...react-navigation-drawer@2.4.5) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use react-native-screens on web ([44c3900](https://github.com/react-navigation/drawer/commit/44c390075f7b76664e09fd9a1a7926645133ebec)), closes [#7485](https://github.com/react-navigation/drawer/issues/7485)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.4](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.3...react-navigation-drawer@2.4.4) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.3](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.2...react-navigation-drawer@2.4.3) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.4.2](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.4.1...react-navigation-drawer@2.4.2) (2020-02-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-drawer
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-drawer",
|
||||
"version": "2.4.2",
|
||||
"version": "2.5.2",
|
||||
"description": "Drawer navigator component for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -38,26 +38,26 @@
|
||||
},
|
||||
"homepage": "https://github.com/react-navigation/drawer#readme",
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.7",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-native": "^0.61.17",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-native": "^0.63.4",
|
||||
"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.0.0-beta.10",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-navigation": "^4.2.2",
|
||||
"typescript": "~3.7.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.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "^1.0.12",
|
||||
"react-native-reanimated": "^1.0.0",
|
||||
"react-native-screens": "^1.0.0 || ^1.0.0-alpha",
|
||||
"react-native-screens": ">=1.0.0 || >= 2.0.0-alpha.0 || >= 2.0.0-beta.0 || >= 2.0.0",
|
||||
"react-navigation": "^4.1.1"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
|
||||
@@ -75,8 +75,11 @@ const DrawerNavigator = (
|
||||
const drawerRouter = DrawerRouter(routeConfigs, mergedConfig);
|
||||
|
||||
// TODO: don't have time to fix it right now
|
||||
// @ts-ignore
|
||||
const navigator = createNavigator(DrawerView, drawerRouter, mergedConfig);
|
||||
const navigator = createNavigator(
|
||||
DrawerView as any,
|
||||
drawerRouter,
|
||||
mergedConfig
|
||||
);
|
||||
return navigator;
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ it('handles basic drawer logic and fires close on switch', () => {
|
||||
const state = router.getStateForAction(INIT_ACTION);
|
||||
const expectedState = {
|
||||
index: 0,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
@@ -32,7 +32,7 @@ it('handles basic drawer logic and fires close on switch', () => {
|
||||
);
|
||||
const expectedState2 = {
|
||||
index: 1,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{ key: 'Foo', routeName: 'Foo', params: undefined },
|
||||
{ key: 'Bar', routeName: 'Bar', params: undefined },
|
||||
@@ -65,7 +65,7 @@ it('handles initial route navigation', () => {
|
||||
expect(state).toEqual({
|
||||
index: 0,
|
||||
isDrawerOpen: false,
|
||||
isTransitioning: false,
|
||||
|
||||
routes: [
|
||||
{
|
||||
key: 'Foo',
|
||||
|
||||
@@ -160,10 +160,6 @@ export default class DrawerView extends React.PureComponent<Props, State> {
|
||||
drawerOpenProgress={progress}
|
||||
navigation={this.props.navigation}
|
||||
descriptors={this.props.descriptors}
|
||||
contentComponent={this.props.navigationConfig.contentComponent}
|
||||
contentOptions={this.props.navigationConfig.contentOptions}
|
||||
drawerPosition={this.props.navigationConfig.drawerPosition}
|
||||
style={this.props.navigationConfig.style}
|
||||
{...this.props.navigationConfig}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
/* @flow */
|
||||
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
@@ -10,21 +8,29 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -3,6 +3,113 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack and upgrade typescript ([454e05b](https://github.com/react-navigation/react-navigation-material-bottom-tabs/commit/454e05b02ec97f053b381fdc801df899d8c93cb6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.12](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.11...react-navigation-material-bottom-tabs@2.2.12) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.11](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.10...react-navigation-material-bottom-tabs@2.2.11) (2020-04-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.10](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.9...react-navigation-material-bottom-tabs@2.2.10) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.9](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.8...react-navigation-material-bottom-tabs@2.2.9) (2020-03-31)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.8](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.6...react-navigation-material-bottom-tabs@2.2.8) (2020-03-28)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.7](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.6...react-navigation-material-bottom-tabs@2.2.7) (2020-03-27)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.6](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.5...react-navigation-material-bottom-tabs@2.2.6) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.5](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.4...react-navigation-material-bottom-tabs@2.2.5) (2020-03-20)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.4](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.3...react-navigation-material-bottom-tabs@2.2.4) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.3](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.2...react-navigation-material-bottom-tabs@2.2.3) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [2.2.2](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.2.1...react-navigation-material-bottom-tabs@2.2.2) (2020-02-26)
|
||||
|
||||
**Note:** Version bump only for package react-navigation-material-bottom-tabs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation-material-bottom-tabs",
|
||||
"version": "2.2.2",
|
||||
"version": "2.3.2",
|
||||
"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.9.7",
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@types/hoist-non-react-statics": "^3.3.1",
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-native": "^0.61.17",
|
||||
"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.2.2",
|
||||
"typescript": "~3.7.5"
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-native": "^0.63.4",
|
||||
"del-cli": "^3.0.1",
|
||||
"react": "~16.13.1",
|
||||
"react-native": "~0.63.2",
|
||||
"react-native-paper": "^4.2.0",
|
||||
"react-navigation": "^4.4.2",
|
||||
"typescript": "^4.0.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -3,6 +3,133 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve error message when installing wrong version ([442ca3e](https://github.com/react-navigation/react-navigation-native/commit/442ca3e700c5805cdc5cd02c68f49042887a5054))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack and upgrade typescript ([454e05b](https://github.com/react-navigation/react-navigation-native/commit/454e05b02ec97f053b381fdc801df899d8c93cb6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.13](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.12...@react-navigation/native@3.7.13) (2020-04-30)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.12](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.11...@react-navigation/native@3.7.12) (2020-04-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.11](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.10...@react-navigation/native@3.7.11) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.10](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.9...@react-navigation/native@3.7.10) (2020-03-31)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.9](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.7...@react-navigation/native@3.7.9) (2020-03-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-native/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-native/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-native/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-native/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-native/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-native/issues/7749)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.8](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.7...@react-navigation/native@3.7.8) (2020-03-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* rework focus and blur events to make them more reliable ([cd08338](https://github.com/react-navigation/react-navigation-native/commit/cd083381866506a192f1ec842ac169f2b4277ca5)), closes [#4867](https://github.com/react-navigation/react-navigation-native/issues/4867) [#6187](https://github.com/react-navigation/react-navigation-native/issues/6187) [#6451](https://github.com/react-navigation/react-navigation-native/issues/6451) [#7628](https://github.com/react-navigation/react-navigation-native/issues/7628) [#7749](https://github.com/react-navigation/react-navigation-native/issues/7749)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.7](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.6...@react-navigation/native@3.7.7) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.6](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.5...@react-navigation/native@3.7.6) (2020-03-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't mix import and module.exports. closes [#7660](https://github.com/react-navigation/react-navigation-native/issues/7660) ([80c8c9d](https://github.com/react-navigation/react-navigation-native/commit/80c8c9d1dead57eab3b977a1eebf1e9f5f35cd1a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.5](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.4...@react-navigation/native@3.7.5) (2020-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* downgrade react-native-safe-area-view ([cfc1bac](https://github.com/react-navigation/react-navigation-native/commit/cfc1bac4e153db4a4ba3f2a9033f77b53367fcbc)), closes [#7813](https://github.com/react-navigation/react-navigation-native/issues/7813)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.4](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.3...@react-navigation/native@3.7.4) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [3.7.3](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.7.0...@react-navigation/native@3.7.3) (2020-02-24)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"version": "3.7.3",
|
||||
"version": "3.8.2",
|
||||
"description": "React Native support for React Navigation",
|
||||
"main": "lib/commonjs/index.js",
|
||||
"react-native": "lib/module/index.js",
|
||||
@@ -15,7 +15,8 @@
|
||||
},
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
"access": "public",
|
||||
"tag": "4.x"
|
||||
},
|
||||
"keywords": [
|
||||
"react-native",
|
||||
@@ -35,18 +36,17 @@
|
||||
"homepage": "https://github.com/react-navigation/react-navigation-native#readme",
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"react-native-safe-area-view": "^0.14.8"
|
||||
"react-native-safe-area-view": "^0.14.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.9.7",
|
||||
"@react-navigation/core": "^3.6.1",
|
||||
"@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-native-screens": "^2.0.0-beta.10",
|
||||
"react-test-renderer": "^16.9.2"
|
||||
"@react-native-community/bob": "^0.16.2",
|
||||
"@react-navigation/core": "^3.7.8",
|
||||
"@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';
|
||||
@@ -52,7 +51,7 @@ const WrappedFlatList = React.forwardRef((props, ref) => (
|
||||
<FlatList
|
||||
ref={ref}
|
||||
{...props}
|
||||
renderScrollComponent={props => (
|
||||
renderScrollComponent={(props) => (
|
||||
<WrappedScrollView {...propsMaybeWithRefreshControl(props)} />
|
||||
)}
|
||||
/>
|
||||
@@ -62,15 +61,14 @@ const WrappedSectionList = React.forwardRef((props, ref) => (
|
||||
<SectionList
|
||||
ref={ref}
|
||||
{...props}
|
||||
renderScrollComponent={props => (
|
||||
renderScrollComponent={(props) => (
|
||||
<WrappedScrollView {...propsMaybeWithRefreshControl(props)} />
|
||||
)}
|
||||
/>
|
||||
));
|
||||
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
module.exports = {
|
||||
ScrollView: WrappedScrollView,
|
||||
FlatList: WrappedFlatList,
|
||||
SectionList: WrappedSectionList,
|
||||
export {
|
||||
WrappedScrollView as ScrollView,
|
||||
WrappedFlatList as FlatList,
|
||||
WrappedSectionList as SectionList,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { View } from 'react-native';
|
||||
|
||||
import renderer from 'react-test-renderer';
|
||||
@@ -274,7 +274,7 @@ describe('NavigationContainer', () => {
|
||||
});
|
||||
|
||||
// https://github.com/facebook/jest/issues/2157#issuecomment-279171856
|
||||
const flushPromises = () => new Promise(resolve => setImmediate(resolve));
|
||||
const flushPromises = () => new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
describe('state persistence', () => {
|
||||
async function createPersistenceEnabledContainer(
|
||||
|
||||
@@ -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,
|
||||
@@ -266,7 +266,7 @@ export default function createNavigationContainer(Component) {
|
||||
}
|
||||
|
||||
const dispatchActions = () =>
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
this._actionEventSubscribers.forEach((subscriber) =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
@@ -316,7 +316,7 @@ export default function createNavigationContainer(Component) {
|
||||
}
|
||||
}
|
||||
|
||||
_persistNavigationState = async nav => {
|
||||
_persistNavigationState = async (nav) => {
|
||||
const { persistNavigationState } = this.props;
|
||||
if (persistNavigationState) {
|
||||
try {
|
||||
@@ -341,7 +341,7 @@ export default function createNavigationContainer(Component) {
|
||||
|
||||
// Per-tick temporary storage for state.nav
|
||||
|
||||
dispatch = action => {
|
||||
dispatch = (action) => {
|
||||
if (this.props.navigation) {
|
||||
return this.props.navigation.dispatch(action);
|
||||
}
|
||||
@@ -357,7 +357,7 @@ export default function createNavigationContainer(Component) {
|
||||
const navState = reducedState === null ? lastNavState : reducedState;
|
||||
|
||||
const dispatchActionEvents = () => {
|
||||
this._actionEventSubscribers.forEach(subscriber =>
|
||||
this._actionEventSubscribers.forEach((subscriber) =>
|
||||
subscriber({
|
||||
type: 'action',
|
||||
action,
|
||||
|
||||
@@ -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,12 +1,13 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import hoistStatics from 'hoist-non-react-statics';
|
||||
import { withNavigation } from '@react-navigation/core';
|
||||
|
||||
export default function createNavigationAwareScrollable(Component) {
|
||||
const ComponentWithNavigationScrolling = withNavigation(
|
||||
class extends React.PureComponent<any> {
|
||||
static displayName = `withNavigationScrolling(${Component.displayName ||
|
||||
Component.name})`;
|
||||
class extends React.PureComponent {
|
||||
static displayName = `withNavigationScrolling(${
|
||||
Component.displayName || Component.name
|
||||
})`;
|
||||
|
||||
componentDidMount() {
|
||||
this._subscription = this.props.navigation.addListener(
|
||||
@@ -49,7 +50,7 @@ export default function createNavigationAwareScrollable(Component) {
|
||||
render() {
|
||||
return (
|
||||
<Component
|
||||
ref={view => {
|
||||
ref={(view) => {
|
||||
this._scrollRef = view;
|
||||
}}
|
||||
{...this.props}
|
||||
@@ -59,11 +60,12 @@ export default function createNavigationAwareScrollable(Component) {
|
||||
}
|
||||
);
|
||||
|
||||
class NavigationAwareScrollable extends React.PureComponent<any> {
|
||||
static displayName = `NavigationAwareScrollable(${Component.displayName ||
|
||||
Component.name})`;
|
||||
class NavigationAwareScrollable extends React.PureComponent {
|
||||
static displayName = `NavigationAwareScrollable(${
|
||||
Component.displayName || Component.name
|
||||
})`;
|
||||
|
||||
_captureRef = view => {
|
||||
_captureRef = (view) => {
|
||||
this._innerRef = view;
|
||||
this.props.onRef && this.props.onRef(view);
|
||||
};
|
||||
|
||||
@@ -43,6 +43,24 @@ module.exports = {
|
||||
get createNavigationFactory() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useNavigationBuilder() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useNavigation() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useRoute() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useFocusEffect() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useIsFocused() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get useNavigationState() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
get NavigationContainer() {
|
||||
return require('./throwIfWrongVersion').default;
|
||||
},
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export default function() {
|
||||
export default function () {
|
||||
throw new Error(
|
||||
"This version of '@react-navigation/native' is not compatible with React Navigation 5. Make sure you install 5.x version of '@react-navigation/native'."
|
||||
'Looks like you are trying to use React Navigation 5 APIs, but have React Navigation 4 installed.\n\nMixing React Navigation 4 and 5 is not supported. See the documentation for installation instructions and usage guide for appropriate versions:\n\n- React Navigation 5: https://reactnavigation.org/docs/getting-started\n- React Navigation 4: https://reactnavigation.org/docs/4.x/getting-started'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,17 +9,17 @@
|
||||
* will remain to ensure logic does not differ in production.
|
||||
*/
|
||||
|
||||
var validateFormat = function() {};
|
||||
var validateFormat = function () {};
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
validateFormat = function(format) {
|
||||
validateFormat = function (format) {
|
||||
if (format === undefined) {
|
||||
throw new Error('invariant requires an error message argument');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function invariant(condition, format, a, b, c, d, e, f) {
|
||||
export default function invariant(condition, format, a, b, c, d, e, f) {
|
||||
validateFormat(format);
|
||||
|
||||
if (!condition) {
|
||||
@@ -32,7 +32,7 @@ function invariant(condition, format, a, b, c, d, e, f) {
|
||||
var args = [a, b, c, d, e, f];
|
||||
var argIndex = 0;
|
||||
error = new Error(
|
||||
format.replace(/%s/g, function() {
|
||||
format.replace(/%s/g, function () {
|
||||
return args[argIndex++];
|
||||
})
|
||||
);
|
||||
@@ -43,6 +43,3 @@ function invariant(condition, format, a, b, c, d, e, f) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
module.exports = invariant;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import * as React from 'react';
|
||||
import { Dimensions } from 'react-native';
|
||||
import hoistNonReactStatic from 'hoist-non-react-statics';
|
||||
|
||||
export const isOrientationLandscape = ({ width, height }) => width > height;
|
||||
|
||||
export default function(WrappedComponent) {
|
||||
export default function (WrappedComponent) {
|
||||
class withOrientation extends React.Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
@@ -3,6 +3,119 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* sync latest stack and upgrade typescript ([454e05b](https://github.com/react-navigation/react-navigation/commit/454e05b02ec97f053b381fdc801df899d8c93cb6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.9](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.8...react-navigation@4.3.9) (2020-04-30)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* change old docUrl to new docUrl due v5 ([e09906a](https://github.com/react-navigation/react-navigation/commit/e09906a4235a0fca09140923ebe7af34b50b491a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.8](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.7...react-navigation@4.3.8) (2020-04-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.7](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.6...react-navigation@4.3.7) (2020-04-02)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.6](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.5...react-navigation@4.3.6) (2020-03-31)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.5](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.3...react-navigation@4.3.5) (2020-03-28)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.4](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.3...react-navigation@4.3.4) (2020-03-27)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.3](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.2...react-navigation@4.3.3) (2020-03-22)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.1...react-navigation@4.3.2) (2020-03-20)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.3.1](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.3.0...react-navigation@4.3.1) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package react-navigation
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [4.3.0](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.2.2...react-navigation@4.3.0) (2020-03-16)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add prune option to pop action to match v5 behaviour ([5927f42](https://github.com/react-navigation/react-navigation/commit/5927f4287f5e1ab106537865523daa1c03b14b47))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [4.2.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.2.1...react-navigation@4.2.2) (2020-02-26)
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-navigation",
|
||||
"version": "4.2.2",
|
||||
"version": "4.4.2",
|
||||
"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.6.1",
|
||||
"@react-navigation/native": "^3.7.3"
|
||||
"@react-navigation/core": "^3.7.8",
|
||||
"@react-navigation/native": "^3.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.22",
|
||||
"@types/react-native": "^0.61.17",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-test-renderer": "^16.9.2",
|
||||
"typescript": "~3.7.5"
|
||||
"@types/react": "^16.9.44",
|
||||
"@types/react-native": "^0.63.4",
|
||||
"@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';
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user