mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 22:42:25 +08:00
Compare commits
49 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5683bebfd6 | ||
|
|
78485cea69 | ||
|
|
1613915669 | ||
|
|
335a04edc1 | ||
|
|
5e0069a896 | ||
|
|
249248e741 | ||
|
|
821343fed3 | ||
|
|
82edb2581b | ||
|
|
cb67530dc5 | ||
|
|
36689e24c2 | ||
|
|
6e51f596fa | ||
|
|
402df73aa2 | ||
|
|
187aefe9c4 | ||
|
|
2613a62874 | ||
|
|
6bdf6ae4ed | ||
|
|
e2bcf5168c | ||
|
|
dfdba8d741 | ||
|
|
a3f7a5feba | ||
|
|
004c7d7ab1 | ||
|
|
49f658fbc0 | ||
|
|
cb2f157a56 | ||
|
|
c4acdaa703 | ||
|
|
f1a8bceba5 | ||
|
|
44081172d4 | ||
|
|
de5d985f3b | ||
|
|
b71de6cc79 | ||
|
|
303f0b78a5 | ||
|
|
ce3994c82c | ||
|
|
ba1f405129 | ||
|
|
d4fd906915 | ||
|
|
b7fa90bf8d | ||
|
|
9556aa9eff | ||
|
|
9a8fea8f2c | ||
|
|
9973db86f0 | ||
|
|
8432e5ab25 | ||
|
|
9bb5cfded3 | ||
|
|
4ac40b5c5d | ||
|
|
cd47915861 | ||
|
|
d649fbc669 | ||
|
|
105da6ab2f | ||
|
|
ac7f972e92 | ||
|
|
babb5027f9 | ||
|
|
78d7a66b2b | ||
|
|
a248c453ba | ||
|
|
e097df880a | ||
|
|
856449b200 | ||
|
|
d94e43c3c8 | ||
|
|
3096de6286 | ||
|
|
1c001424b5 |
4
.github/workflows/triage.yml
vendored
4
.github/workflows/triage.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to reproduce the issue with minimal code (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
|
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to [reproduce the issue with minimal code](https://stackoverflow.com/help/minimal-reproducible-example) (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
|
||||||
|
|
||||||
needs-repro:
|
needs-repro:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
args: comment "Hey! Thanks for opening the issue. Can you provide a minimal repro which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
|
args: comment "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
|
||||||
|
|
||||||
question:
|
question:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|||||||
27
.github/workflows/versions.yml
vendored
Normal file
27
.github/workflows/versions.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
name: Check versions
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-versions:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: react-navigation/check-versions-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
packages: |
|
||||||
|
@react-navigation/bottom-tabs
|
||||||
|
@react-navigation/compat
|
||||||
|
@react-navigation/core
|
||||||
|
@react-navigation/drawer
|
||||||
|
@react-navigation/material-bottom-tabs
|
||||||
|
@react-navigation/material-top-tabs
|
||||||
|
@react-navigation/native
|
||||||
|
@react-navigation/routers
|
||||||
|
@react-navigation/stack
|
||||||
|
react-navigation-animated-switch
|
||||||
|
react-navigation-drawer
|
||||||
|
react-navigation-material-bottom-tabs
|
||||||
|
react-navigation-stack
|
||||||
|
react-navigation-tabs
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
"slug": "react-navigation-example",
|
"slug": "react-navigation-example",
|
||||||
"description": "Demo app to showcase various functionality of React Navigation",
|
"description": "Demo app to showcase various functionality of React Navigation",
|
||||||
"privacy": "public",
|
"privacy": "public",
|
||||||
"sdkVersion": "36.0.0",
|
"sdkVersion": "37.0.0",
|
||||||
"platforms": [
|
"platforms": [
|
||||||
"ios",
|
"ios",
|
||||||
"android",
|
"android",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* eslint-disable jest/no-jasmine-globals, import/no-commonjs */
|
/* eslint-disable import/no-commonjs */
|
||||||
|
|
||||||
const detox = require('detox');
|
const detox = require('detox');
|
||||||
const config = require('../../package.json').detox;
|
const config = require('../../package.json').detox;
|
||||||
|
|||||||
@@ -12,31 +12,30 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@expo/vector-icons": "^10.0.0",
|
"@expo/vector-icons": "^10.0.0",
|
||||||
"@react-native-community/masked-view": "0.1.7",
|
"@react-native-community/masked-view": "^0.1.7",
|
||||||
"@types/react-native-restart": "^0.0.0",
|
|
||||||
"color": "^3.1.2",
|
"color": "^3.1.2",
|
||||||
"expo": "^36.0.2",
|
"expo": "^37.0.0",
|
||||||
"expo-asset": "~8.0.0",
|
"expo-asset": "~8.1.3",
|
||||||
"expo-blur": "^8.0.0",
|
"expo-blur": "~8.1.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-dom": "~16.9.0",
|
"react-dom": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-gesture-handler": "^1.6.0",
|
"react-native-gesture-handler": "^1.6.0",
|
||||||
"react-native-paper": "^3.6.0",
|
"react-native-paper": "^3.7.0",
|
||||||
"react-native-reanimated": "^1.7.0",
|
"react-native-reanimated": "^1.7.0",
|
||||||
"react-native-restart": "^0.0.14",
|
"react-native-restart": "^0.0.14",
|
||||||
"react-native-safe-area-context": "^0.7.3",
|
"react-native-safe-area-context": "^0.7.3",
|
||||||
"react-native-screens": "^2.3.0",
|
"react-native-screens": "^2.3.0",
|
||||||
"react-native-tab-view": "2.13.0",
|
"react-native-tab-view": "2.14.0",
|
||||||
"react-native-unimodules": "^0.7.0",
|
"react-native-unimodules": "~0.8.1",
|
||||||
"react-native-web": "^0.11.7"
|
"react-native-web": "^0.11.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@expo/webpack-config": "^0.11.7",
|
"@expo/webpack-config": "^0.11.19",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.60.22",
|
||||||
"babel-preset-expo": "^8.0.0",
|
"babel-preset-expo": "^8.1.0",
|
||||||
"expo-cli": "^3.13.8",
|
"expo-cli": "^3.17.18",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
127
example/src/Screens/MasterDetail.tsx
Normal file
127
example/src/Screens/MasterDetail.tsx
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Dimensions, ScaledSize } from 'react-native';
|
||||||
|
import { Appbar } from 'react-native-paper';
|
||||||
|
import { ParamListBase } from '@react-navigation/native';
|
||||||
|
import { StackNavigationProp } from '@react-navigation/stack';
|
||||||
|
import {
|
||||||
|
createDrawerNavigator,
|
||||||
|
DrawerNavigationProp,
|
||||||
|
DrawerContent,
|
||||||
|
} from '@react-navigation/drawer';
|
||||||
|
import Article from '../Shared/Article';
|
||||||
|
import Albums from '../Shared/Albums';
|
||||||
|
import NewsFeed from '../Shared/NewsFeed';
|
||||||
|
|
||||||
|
type DrawerParams = {
|
||||||
|
Article: undefined;
|
||||||
|
NewsFeed: undefined;
|
||||||
|
Album: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
|
||||||
|
|
||||||
|
const useIsLargeScreen = () => {
|
||||||
|
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
|
||||||
|
setDimensions(window);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dimensions.addEventListener('change', onDimensionsChange);
|
||||||
|
|
||||||
|
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return dimensions.width > 414;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Header = ({
|
||||||
|
onGoBack,
|
||||||
|
title,
|
||||||
|
}: {
|
||||||
|
onGoBack: () => void;
|
||||||
|
title: string;
|
||||||
|
}) => {
|
||||||
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Appbar.Header>
|
||||||
|
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
|
||||||
|
<Appbar.Content title={title} />
|
||||||
|
</Appbar.Header>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArticleScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Article" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<Article />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const NewsFeedScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Feed" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<NewsFeed />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Header title="Albums" onGoBack={() => navigation.toggleDrawer()} />
|
||||||
|
<Albums />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Drawer = createDrawerNavigator<DrawerParams>();
|
||||||
|
|
||||||
|
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
|
||||||
|
navigation: StackNavigationProp<ParamListBase>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function DrawerScreen({ navigation, ...rest }: Props) {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerShown: false,
|
||||||
|
gestureEnabled: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isLargeScreen = useIsLargeScreen();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer.Navigator
|
||||||
|
openByDefault
|
||||||
|
drawerType={isLargeScreen ? 'permanent' : 'back'}
|
||||||
|
drawerStyle={isLargeScreen ? null : { width: '100%' }}
|
||||||
|
overlayColor="transparent"
|
||||||
|
drawerContent={(props) => (
|
||||||
|
<>
|
||||||
|
<Appbar.Header>
|
||||||
|
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
|
||||||
|
<Appbar.Content title="Pages" />
|
||||||
|
</Appbar.Header>
|
||||||
|
<DrawerContent {...props} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
<Drawer.Screen name="Article" component={ArticleScreen} />
|
||||||
|
<Drawer.Screen
|
||||||
|
name="NewsFeed"
|
||||||
|
component={NewsFeedScreen}
|
||||||
|
options={{ title: 'Feed' }}
|
||||||
|
/>
|
||||||
|
<Drawer.Screen
|
||||||
|
name="Album"
|
||||||
|
component={AlbumsScreen}
|
||||||
|
options={{ title: 'Album' }}
|
||||||
|
/>
|
||||||
|
</Drawer.Navigator>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
ScrollViewProps,
|
ScrollViewProps,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Platform,
|
Platform,
|
||||||
|
ScaledSize,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { useScrollToTop } from '@react-navigation/native';
|
import { useScrollToTop } from '@react-navigation/native';
|
||||||
|
|
||||||
@@ -40,15 +41,38 @@ const COVERS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export default function Albums(props: Partial<ScrollViewProps>) {
|
export default function Albums(props: Partial<ScrollViewProps>) {
|
||||||
|
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
|
||||||
|
setDimensions(window);
|
||||||
|
};
|
||||||
|
|
||||||
|
Dimensions.addEventListener('change', onDimensionsChange);
|
||||||
|
|
||||||
|
return () => Dimensions.removeEventListener('change', onDimensionsChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const ref = React.useRef<ScrollView>(null);
|
const ref = React.useRef<ScrollView>(null);
|
||||||
|
|
||||||
useScrollToTop(ref);
|
useScrollToTop(ref);
|
||||||
|
|
||||||
|
const itemSize = dimensions.width / Math.floor(dimensions.width / 150);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
|
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
|
||||||
{COVERS.map((source, i) => (
|
{COVERS.map((source, i) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
<View
|
||||||
<View key={i} style={styles.item}>
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
|
key={i}
|
||||||
|
style={[
|
||||||
|
styles.item,
|
||||||
|
Platform.OS !== 'web' && {
|
||||||
|
height: itemSize,
|
||||||
|
width: itemSize,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
<Image source={source} style={styles.photo} />
|
<Image source={source} style={styles.photo} />
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
@@ -76,10 +100,6 @@ const styles = StyleSheet.create({
|
|||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
flexWrap: 'wrap',
|
flexWrap: 'wrap',
|
||||||
},
|
},
|
||||||
item: {
|
|
||||||
height: Dimensions.get('window').width / 2,
|
|
||||||
width: '50%',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
photo: {
|
photo: {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
} from '@react-navigation/stack';
|
} from '@react-navigation/stack';
|
||||||
|
|
||||||
import LinkingPrefixes from './LinkingPrefixes';
|
import LinkingPrefixes from './LinkingPrefixes';
|
||||||
|
import SettingsItem from './Shared/SettingsItem';
|
||||||
import SimpleStack from './Screens/SimpleStack';
|
import SimpleStack from './Screens/SimpleStack';
|
||||||
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
import ModalPresentationStack from './Screens/ModalPresentationStack';
|
||||||
import StackTransparent from './Screens/StackTransparent';
|
import StackTransparent from './Screens/StackTransparent';
|
||||||
@@ -53,7 +54,7 @@ import MaterialBottomTabs from './Screens/MaterialBottomTabs';
|
|||||||
import DynamicTabs from './Screens/DynamicTabs';
|
import DynamicTabs from './Screens/DynamicTabs';
|
||||||
import AuthFlow from './Screens/AuthFlow';
|
import AuthFlow from './Screens/AuthFlow';
|
||||||
import CompatAPI from './Screens/CompatAPI';
|
import CompatAPI from './Screens/CompatAPI';
|
||||||
import SettingsItem from './Shared/SettingsItem';
|
import MasterDetail from './Screens/MasterDetail';
|
||||||
|
|
||||||
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
|
||||||
|
|
||||||
@@ -97,6 +98,10 @@ const SCREENS = {
|
|||||||
title: 'Dynamic Tabs',
|
title: 'Dynamic Tabs',
|
||||||
component: DynamicTabs,
|
component: DynamicTabs,
|
||||||
},
|
},
|
||||||
|
MasterDetail: {
|
||||||
|
title: 'Master Detail',
|
||||||
|
component: MasterDetail,
|
||||||
|
},
|
||||||
AuthFlow: {
|
AuthFlow: {
|
||||||
title: 'Auth Flow',
|
title: 'Auth Flow',
|
||||||
component: AuthFlow,
|
component: AuthFlow,
|
||||||
@@ -116,7 +121,7 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
|
|||||||
Asset.loadAsync(StackAssets);
|
Asset.loadAsync(StackAssets);
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const containerRef = React.useRef<NavigationContainerRef>();
|
const containerRef = React.useRef<NavigationContainerRef>(null);
|
||||||
|
|
||||||
// To test deep linking on, run the following in the Terminal:
|
// To test deep linking on, run the following in the Terminal:
|
||||||
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
|
||||||
@@ -214,7 +219,7 @@ export default function App() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLargeScreen = dimensions.width > 900;
|
const isLargeScreen = dimensions.width >= 1024;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PaperProvider theme={paperTheme}>
|
<PaperProvider theme={paperTheme}>
|
||||||
|
|||||||
5
netlify.toml
Normal file
5
netlify.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
[build]
|
||||||
|
base = "/"
|
||||||
|
publish = "example/web-build"
|
||||||
|
command = "yarn example expo build:web"
|
||||||
26
package.json
26
package.json
@@ -18,7 +18,7 @@
|
|||||||
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
|
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||||
"typescript": "tsc --noEmit",
|
"typescript": "tsc --noEmit --composite false",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prerelease": "lerna run clean",
|
"prerelease": "lerna run clean",
|
||||||
"release": "lerna publish",
|
"release": "lerna publish",
|
||||||
@@ -26,25 +26,25 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||||
"@babel/preset-env": "^7.8.7",
|
"@babel/preset-env": "^7.9.0",
|
||||||
"@babel/preset-flow": "^7.8.3",
|
"@babel/preset-flow": "^7.9.0",
|
||||||
"@babel/preset-react": "^7.8.3",
|
"@babel/preset-react": "^7.9.4",
|
||||||
"@babel/preset-typescript": "^7.8.3",
|
"@babel/preset-typescript": "^7.9.0",
|
||||||
"@babel/runtime": "^7.8.7",
|
"@babel/runtime": "^7.9.2",
|
||||||
"@commitlint/config-conventional": "^8.3.4",
|
"@commitlint/config-conventional": "^8.3.4",
|
||||||
"@types/jest": "^25.1.4",
|
"@types/jest": "^25.2.1",
|
||||||
|
"babel-jest": "^25.2.6",
|
||||||
"codecov": "^3.6.5",
|
"codecov": "^3.6.5",
|
||||||
"commitlint": "^8.3.5",
|
"commitlint": "^8.3.5",
|
||||||
"core-js": "^3.6.4",
|
"core-js": "^3.6.4",
|
||||||
"detox": "^16.0.0",
|
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-config-satya164": "^3.1.5",
|
"eslint-config-satya164": "^3.1.6",
|
||||||
"husky": "^4.2.3",
|
"husky": "^4.2.3",
|
||||||
"jest": "^25.1.0",
|
"jest": "^25.2.7",
|
||||||
"lerna": "^3.20.2",
|
"lerna": "^3.20.2",
|
||||||
"prettier": "^2.0.1",
|
"prettier": "^2.0.4",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.5...@react-navigation/bottom-tabs@5.2.6) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.4...@react-navigation/bottom-tabs@5.2.5) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.3...@react-navigation/bottom-tabs@5.2.4) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
|
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/bottom-tabs",
|
"name": "@react-navigation/bottom-tabs",
|
||||||
"description": "Bottom tab navigator following iOS design guidelines",
|
"description": "Bottom tab navigator following iOS design guidelines",
|
||||||
"version": "5.2.3",
|
"version": "5.2.7",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.61.22",
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-safe-area-context": "^0.7.3",
|
"react-native-safe-area-context": "^0.7.3",
|
||||||
"react-native-screens": "^2.3.0",
|
"react-native-screens": "^2.3.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export { default as BottomTabBar } from './views/BottomTabBar';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
BottomTabNavigationOptions,
|
BottomTabNavigationOptions,
|
||||||
BottomTabNavigationProp,
|
BottomTabNavigationProp,
|
||||||
BottomTabBarProps,
|
BottomTabBarProps,
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ export type BottomTabBarOptions = {
|
|||||||
*/
|
*/
|
||||||
inactiveTintColor?: string;
|
inactiveTintColor?: string;
|
||||||
/**
|
/**
|
||||||
* Background olor for the active tab.
|
* Background color for the active tab.
|
||||||
*/
|
*/
|
||||||
activeBackgroundColor?: string;
|
activeBackgroundColor?: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/compat
|
**Note:** Version bump only for package @react-navigation/compat
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/compat",
|
"name": "@react-navigation/compat",
|
||||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||||
"version": "5.1.5",
|
"version": "5.1.9",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@@ -26,10 +26,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function push(routeName: string, params?: object, action?: never) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pop(n: number) {
|
export function pop(n: number = 1) {
|
||||||
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
return StackActions.pop(typeof n === 'number' ? { n } : n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,54 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add initial option for navigating to nested navigators ([004c7d7](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/004c7d7ab1f80faf04b2a1836ec6b79a5419e45f))
|
||||||
|
* add initial param for actions from deep link ([a3f7a5f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/a3f7a5feba2e6aa2158aeaea6cde73ae1603173e))
|
||||||
|
* handle initial: false for nested route after first initialization ([187aefe](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/187aefe9c400b499f920c212bf856414e25c5aaf))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.2...@react-navigation/core@5.3.3) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* switch order of focus and blur events. closes [#7963](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7963) ([ce3994c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/ce3994c82c28669d5742017eb7627e9adf996933))
|
||||||
|
* workaround warning about setState in another component in render ([d4fd906](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d4fd906915cc20d6fb21508384c05a540d8644d8))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.1...@react-navigation/core@5.3.2) (2020-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handle no path property and undefined query params ([#7911](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7911)) ([cd47915](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/cd47915861a56cd7eaa9ac79f5139cde56ca95a7))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.0...@react-navigation/core@5.3.1) (2020-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't emit events for screens that don't exist anymore ([1c00142](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1c001424b595b40f9db9343096c833f75353b099))
|
||||||
|
* only call listeners for focused screen for global events ([3096de6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3096de62868a7ed9ed65e529c8ddfa001b9be486))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
|
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/core",
|
"name": "@react-navigation/core",
|
||||||
"description": "Core utilities for building navigators",
|
"description": "Core utilities for building navigators",
|
||||||
"version": "5.3.0",
|
"version": "5.3.4",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -29,24 +29,23 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/routers": "^5.2.0",
|
"@react-navigation/routers": "^5.4.0",
|
||||||
"escape-string-regexp": "^2.0.0",
|
"escape-string-regexp": "^2.0.0",
|
||||||
"query-string": "^6.11.1",
|
"nanoid": "^3.0.2",
|
||||||
|
"query-string": "^6.12.0",
|
||||||
"react-is": "^16.13.0",
|
"react-is": "^16.13.0",
|
||||||
"shortid": "^2.2.15",
|
|
||||||
"use-subscription": "^1.4.0"
|
"use-subscription": "^1.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-is": "^16.7.1",
|
"@types/react-is": "^16.7.1",
|
||||||
"@types/shortid": "^0.0.29",
|
|
||||||
"@types/use-subscription": "^1.0.0",
|
"@types/use-subscription": "^1.0.0",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-native-testing-library": "^1.12.0",
|
"react-native-testing-library": "^1.12.0",
|
||||||
"react-test-renderer": "~16.9.0",
|
"react-test-renderer": "~16.13.1",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*"
|
"react": "*"
|
||||||
|
|||||||
@@ -9,14 +9,15 @@ import {
|
|||||||
} from '@react-navigation/routers';
|
} from '@react-navigation/routers';
|
||||||
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
import EnsureSingleNavigator from './EnsureSingleNavigator';
|
||||||
import NavigationBuilderContext from './NavigationBuilderContext';
|
import NavigationBuilderContext from './NavigationBuilderContext';
|
||||||
|
import { ScheduleUpdateContext } from './useScheduleUpdate';
|
||||||
import useFocusedListeners from './useFocusedListeners';
|
import useFocusedListeners from './useFocusedListeners';
|
||||||
import useDevTools from './useDevTools';
|
import useDevTools from './useDevTools';
|
||||||
import useStateGetters from './useStateGetters';
|
import useStateGetters from './useStateGetters';
|
||||||
|
import useEventEmitter from './useEventEmitter';
|
||||||
|
import useSyncState from './useSyncState';
|
||||||
import isSerializable from './isSerializable';
|
import isSerializable from './isSerializable';
|
||||||
|
|
||||||
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
import { NavigationContainerRef, NavigationContainerProps } from './types';
|
||||||
import useEventEmitter from './useEventEmitter';
|
|
||||||
import useSyncState from './useSyncState';
|
|
||||||
|
|
||||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
independent,
|
independent,
|
||||||
children,
|
children,
|
||||||
}: NavigationContainerProps,
|
}: NavigationContainerProps,
|
||||||
ref: React.Ref<NavigationContainerRef>
|
ref?: React.Ref<NavigationContainerRef>
|
||||||
) {
|
) {
|
||||||
const parent = React.useContext(NavigationStateContext);
|
const parent = React.useContext(NavigationStateContext);
|
||||||
|
|
||||||
@@ -112,7 +113,13 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const [state, getState, setState] = useSyncState<State>(() =>
|
const [
|
||||||
|
state,
|
||||||
|
getState,
|
||||||
|
setState,
|
||||||
|
scheduleUpdate,
|
||||||
|
flushUpdates,
|
||||||
|
] = useSyncState<State>(() =>
|
||||||
getPartialState(initialState == null ? undefined : initialState)
|
getPartialState(initialState == null ? undefined : initialState)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -218,6 +225,11 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
[addFocusedListener, trackAction, addStateGetter]
|
[addFocusedListener, trackAction, addStateGetter]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scheduleContext = React.useMemo(
|
||||||
|
() => ({ scheduleUpdate, flushUpdates }),
|
||||||
|
[scheduleUpdate, flushUpdates]
|
||||||
|
);
|
||||||
|
|
||||||
const context = React.useMemo(
|
const context = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
state,
|
state,
|
||||||
@@ -263,11 +275,13 @@ const BaseNavigationContainer = React.forwardRef(
|
|||||||
}, [onStateChange, trackState, getRootState, emitter, state]);
|
}, [onStateChange, trackState, getRootState, emitter, state]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationBuilderContext.Provider value={builderContext}>
|
<ScheduleUpdateContext.Provider value={scheduleContext}>
|
||||||
<NavigationStateContext.Provider value={context}>
|
<NavigationBuilderContext.Provider value={builderContext}>
|
||||||
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
<NavigationStateContext.Provider value={context}>
|
||||||
</NavigationStateContext.Provider>
|
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
|
||||||
</NavigationBuilderContext.Provider>
|
</NavigationStateContext.Provider>
|
||||||
|
</NavigationBuilderContext.Provider>
|
||||||
|
</ScheduleUpdateContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ it('gets navigate action from state', () => {
|
|||||||
author: 'jane',
|
author: 'jane',
|
||||||
},
|
},
|
||||||
screen: 'qux',
|
screen: 'qux',
|
||||||
|
initial: true,
|
||||||
},
|
},
|
||||||
screen: 'bar',
|
screen: 'bar',
|
||||||
|
initial: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
type: 'NAVIGATE',
|
type: 'NAVIGATE',
|
||||||
@@ -70,9 +72,11 @@ it('gets navigate action from state', () => {
|
|||||||
payload: {
|
payload: {
|
||||||
name: 'foo',
|
name: 'foo',
|
||||||
params: {
|
params: {
|
||||||
|
initial: true,
|
||||||
screen: 'bar',
|
screen: 'bar',
|
||||||
params: {
|
params: {
|
||||||
screen: 'quz',
|
screen: 'quz',
|
||||||
|
initial: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -521,3 +521,209 @@ it('returns "/" for empty path', () => {
|
|||||||
|
|
||||||
expect(getPathFromState(state, config)).toBe('/');
|
expect(getPathFromState(state, config)).toBe('/');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('parses no path specified', () => {
|
||||||
|
const path = '/Foo/bar';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
screens: {
|
||||||
|
Foe: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar',
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: 'Bar' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses no path specified in nested config', () => {
|
||||||
|
const path = '/Foo/Foe/bar';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar',
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foe',
|
||||||
|
state: {
|
||||||
|
routes: [{ name: 'Bar' }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('strips undefined query params', () => {
|
||||||
|
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
|
Bis: {
|
||||||
|
path: 'bis/:author',
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: 10,
|
||||||
|
answer: undefined,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles stripping all query params', () => {
|
||||||
|
const path = '/bar/sweet/apple/foo/bis/jane';
|
||||||
|
const config = {
|
||||||
|
Foo: {
|
||||||
|
path: 'foo',
|
||||||
|
screens: {
|
||||||
|
Foe: {
|
||||||
|
path: 'foe',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Bar: 'bar/:type/:fruit',
|
||||||
|
Baz: {
|
||||||
|
path: 'baz',
|
||||||
|
screens: {
|
||||||
|
Bos: 'bos',
|
||||||
|
Bis: {
|
||||||
|
path: 'bis/:author',
|
||||||
|
stringify: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||||
|
},
|
||||||
|
parse: {
|
||||||
|
author: (author: string) =>
|
||||||
|
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||||
|
count: Number,
|
||||||
|
valid: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bar',
|
||||||
|
params: { fruit: 'apple', type: 'sweet' },
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Foo',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Baz',
|
||||||
|
state: {
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'Bis',
|
||||||
|
params: {
|
||||||
|
author: 'Jane',
|
||||||
|
count: undefined,
|
||||||
|
answer: undefined,
|
||||||
|
valid: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(getPathFromState(state, config)).toBe(path);
|
||||||
|
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
|
||||||
|
});
|
||||||
|
|||||||
@@ -626,7 +626,7 @@ it('updates route params with setParams applied to parent', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles change in route names', () => {
|
it('handles change in route names', async () => {
|
||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
useNavigationBuilder(MockRouter, props);
|
useNavigationBuilder(MockRouter, props);
|
||||||
return null;
|
return null;
|
||||||
@@ -635,7 +635,7 @@ it('handles change in route names', () => {
|
|||||||
const onStateChange = jest.fn();
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
const root = render(
|
const root = render(
|
||||||
<BaseNavigationContainer onStateChange={onStateChange}>
|
<BaseNavigationContainer>
|
||||||
<TestNavigator initialRouteName="bar">
|
<TestNavigator initialRouteName="bar">
|
||||||
<Screen name="foo" component={jest.fn()} />
|
<Screen name="foo" component={jest.fn()} />
|
||||||
<Screen name="bar" component={jest.fn()} />
|
<Screen name="bar" component={jest.fn()} />
|
||||||
@@ -737,6 +737,366 @@ it('navigates to nested child in a navigator', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('navigates to nested child in a navigator with initial: false', () => {
|
||||||
|
const TestRouter: typeof MockRouter = (options) => {
|
||||||
|
const router = MockRouter(options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...router,
|
||||||
|
|
||||||
|
getStateForAction(state, action, options) {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'NAVIGATE': {
|
||||||
|
if (!options.routeNames.includes(action.payload.name as any)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
...state.routes,
|
||||||
|
{
|
||||||
|
key: String(MockRouterKey.current++),
|
||||||
|
name: action.payload.name,
|
||||||
|
params: action.payload.params,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
index: routes.length - 1,
|
||||||
|
routes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return router.getStateForAction(state, action, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
} as typeof router;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestNavigator = (props: any): any => {
|
||||||
|
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
|
||||||
|
|
||||||
|
return descriptors[state.routes[state.index].key].render();
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = ({ route }: any): any =>
|
||||||
|
`[${route.name}, ${JSON.stringify(route.params)}]`;
|
||||||
|
|
||||||
|
const onStateChange = jest.fn();
|
||||||
|
|
||||||
|
const navigation = React.createRef<NavigationContainerRef>();
|
||||||
|
|
||||||
|
const first = render(
|
||||||
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo-a" component={TestComponent} />
|
||||||
|
<Screen name="foo-b" component={TestComponent} />
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 0,
|
||||||
|
key: '0',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '1',
|
||||||
|
routeNames: ['foo-a', 'foo-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo-a',
|
||||||
|
name: 'foo-a',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'foo-b',
|
||||||
|
name: 'foo-b',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
act(
|
||||||
|
() =>
|
||||||
|
navigation.current &&
|
||||||
|
navigation.current.navigate('bar', {
|
||||||
|
screen: 'bar-b',
|
||||||
|
params: { test: 42 },
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(first).toMatchInlineSnapshot(
|
||||||
|
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 2,
|
||||||
|
key: '0',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{
|
||||||
|
key: '2',
|
||||||
|
name: 'bar',
|
||||||
|
params: { params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '3',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff', test: 42 },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const second = render(
|
||||||
|
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo-a" component={TestComponent} />
|
||||||
|
<Screen name="foo-b" component={TestComponent} />
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 0,
|
||||||
|
key: '4',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
name: 'foo',
|
||||||
|
state: {
|
||||||
|
index: 0,
|
||||||
|
key: '5',
|
||||||
|
routeNames: ['foo-a', 'foo-b'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-a', name: 'foo-a' },
|
||||||
|
{ key: 'foo-b', name: 'foo-b' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
act(
|
||||||
|
() =>
|
||||||
|
navigation.current &&
|
||||||
|
navigation.current.navigate('bar', {
|
||||||
|
screen: 'bar-b',
|
||||||
|
params: { test: 42 },
|
||||||
|
initial: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 2,
|
||||||
|
key: '4',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo', name: 'foo' },
|
||||||
|
{ key: 'bar', name: 'bar' },
|
||||||
|
{
|
||||||
|
key: '6',
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 2,
|
||||||
|
key: '7',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
{ key: '8', name: 'bar-b', params: { test: 42 } },
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
|
||||||
|
const third = render(
|
||||||
|
<BaseNavigationContainer
|
||||||
|
ref={navigation}
|
||||||
|
initialState={{
|
||||||
|
index: 1,
|
||||||
|
routes: [
|
||||||
|
{ name: 'foo' },
|
||||||
|
{
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '7',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'test',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TestNavigator>
|
||||||
|
<Screen name="foo" component={TestComponent} />
|
||||||
|
<Screen name="bar">
|
||||||
|
{() => (
|
||||||
|
<TestNavigator initialRouteName="bar-a">
|
||||||
|
<Screen
|
||||||
|
name="bar-a"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ lol: 'why' }}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="bar-b"
|
||||||
|
component={TestComponent}
|
||||||
|
initialParams={{ some: 'stuff' }}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
)}
|
||||||
|
</Screen>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
|
||||||
|
|
||||||
|
expect(navigation.current?.getRootState()).toEqual({
|
||||||
|
index: 1,
|
||||||
|
key: '11',
|
||||||
|
routeNames: ['foo', 'bar'],
|
||||||
|
routes: [
|
||||||
|
{ key: 'foo-9', name: 'foo' },
|
||||||
|
{
|
||||||
|
key: 'bar-10',
|
||||||
|
name: 'bar',
|
||||||
|
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
|
||||||
|
state: {
|
||||||
|
index: 1,
|
||||||
|
key: '14',
|
||||||
|
routeNames: ['bar-a', 'bar-b'],
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
key: 'bar-a-12',
|
||||||
|
name: 'bar-a',
|
||||||
|
params: { lol: 'why' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bar-b-13',
|
||||||
|
name: 'bar-b',
|
||||||
|
params: { some: 'stuff' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
stale: false,
|
||||||
|
type: 'test',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('gives access to internal state', () => {
|
it('gives access to internal state', () => {
|
||||||
const TestNavigator = (props: any): any => {
|
const TestNavigator = (props: any): any => {
|
||||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|||||||
@@ -262,8 +262,10 @@ it('sets initial options with setOptions', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TestScreen = ({ navigation }: any): any => {
|
const TestScreen = ({ navigation }: any): any => {
|
||||||
navigation.setOptions({
|
React.useEffect(() => {
|
||||||
title: 'Hello world',
|
navigation.setOptions({
|
||||||
|
title: 'Hello world',
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
return 'Test screen';
|
return 'Test screen';
|
||||||
@@ -315,12 +317,12 @@ it('updates options with setOptions', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TestScreen = ({ navigation }: any): any => {
|
const TestScreen = ({ navigation }: any): any => {
|
||||||
navigation.setOptions({
|
|
||||||
title: 'Hello world',
|
|
||||||
description: 'Something here',
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
title: 'Hello world',
|
||||||
|
description: 'Something here',
|
||||||
|
});
|
||||||
|
|
||||||
const timer = setTimeout(() =>
|
const timer = setTimeout(() =>
|
||||||
navigation.setOptions({
|
navigation.setOptions({
|
||||||
title: 'Hello again',
|
title: 'Hello again',
|
||||||
|
|||||||
@@ -97,6 +97,69 @@ it('fires focus and blur events in root navigator', () => {
|
|||||||
expect(fourthBlurCallback).toBeCalledTimes(0);
|
expect(fourthBlurCallback).toBeCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires focus event after blur', () => {
|
||||||
|
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||||
|
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||||
|
MockRouter,
|
||||||
|
props
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||||
|
|
||||||
|
return state.routes.map((route) => descriptors[route.key].render());
|
||||||
|
});
|
||||||
|
|
||||||
|
const callback = jest.fn();
|
||||||
|
|
||||||
|
const Test = ({ route, navigation }: any) => {
|
||||||
|
React.useEffect(
|
||||||
|
() =>
|
||||||
|
navigation.addListener('focus', () => callback(route.name, 'focus')),
|
||||||
|
[navigation, route.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(
|
||||||
|
() => navigation.addListener('blur', () => callback(route.name, 'blur')),
|
||||||
|
[navigation, route.name]
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigation = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator ref={navigation}>
|
||||||
|
<Screen name="first" component={Test} />
|
||||||
|
<Screen name="second" component={Test} />
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([['first', 'focus']]);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('second'));
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([
|
||||||
|
['first', 'focus'],
|
||||||
|
['first', 'blur'],
|
||||||
|
['second', 'focus'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
act(() => navigation.current.navigate('first'));
|
||||||
|
|
||||||
|
expect(callback.mock.calls).toEqual([
|
||||||
|
['first', 'focus'],
|
||||||
|
['first', 'blur'],
|
||||||
|
['second', 'focus'],
|
||||||
|
['second', 'blur'],
|
||||||
|
['first', 'focus'],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it('fires focus and blur events in nested navigator', () => {
|
it('fires focus and blur events in nested navigator', () => {
|
||||||
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||||
const { state, navigation, descriptors } = useNavigationBuilder(
|
const { state, navigation, descriptors } = useNavigationBuilder(
|
||||||
@@ -565,12 +628,10 @@ it('fires custom events added with listeners prop', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||||
expect(secondCallback.mock.calls[0][0].target).toBe(undefined);
|
|
||||||
expect(thirdCallback.mock.calls[1][0].target).toBe(undefined);
|
|
||||||
|
|
||||||
expect(firstCallback).toBeCalledTimes(1);
|
expect(firstCallback).toBeCalledTimes(1);
|
||||||
expect(secondCallback).toBeCalledTimes(1);
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
expect(thirdCallback).toBeCalledTimes(2);
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("doesn't call same listener multiple times with listeners", () => {
|
it("doesn't call same listener multiple times with listeners", () => {
|
||||||
@@ -624,6 +685,91 @@ it("doesn't call same listener multiple times with listeners", () => {
|
|||||||
expect(callback).toBeCalledTimes(1);
|
expect(callback).toBeCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires listeners when callback is provided for listeners prop', () => {
|
||||||
|
const eventName = 'someSuperCoolEvent';
|
||||||
|
|
||||||
|
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
|
||||||
|
const { state, navigation } = useNavigationBuilder(MockRouter, props);
|
||||||
|
|
||||||
|
React.useImperativeHandle(ref, () => ({ navigation, state }), [
|
||||||
|
navigation,
|
||||||
|
state,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const firstCallback = jest.fn();
|
||||||
|
const secondCallback = jest.fn();
|
||||||
|
const thirdCallback = jest.fn();
|
||||||
|
|
||||||
|
const ref = React.createRef<any>();
|
||||||
|
|
||||||
|
const element = (
|
||||||
|
<BaseNavigationContainer>
|
||||||
|
<TestNavigator ref={ref}>
|
||||||
|
<Screen
|
||||||
|
name="first"
|
||||||
|
listeners={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => firstCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
component={jest.fn()}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="second"
|
||||||
|
listeners={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => secondCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
component={jest.fn()}
|
||||||
|
/>
|
||||||
|
<Screen
|
||||||
|
name="third"
|
||||||
|
listeners={({ route, navigation }) => ({
|
||||||
|
someSuperCoolEvent: (e) => thirdCallback(e, route, navigation),
|
||||||
|
})}
|
||||||
|
component={jest.fn()}
|
||||||
|
/>
|
||||||
|
</TestNavigator>
|
||||||
|
</BaseNavigationContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
render(element);
|
||||||
|
|
||||||
|
expect(firstCallback).toBeCalledTimes(0);
|
||||||
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
|
expect(thirdCallback).toBeCalledTimes(0);
|
||||||
|
|
||||||
|
const target =
|
||||||
|
ref.current.state.routes[ref.current.state.routes.length - 1].key;
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
ref.current.navigation.emit({
|
||||||
|
type: eventName,
|
||||||
|
target,
|
||||||
|
data: 42,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(firstCallback).toBeCalledTimes(0);
|
||||||
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].type).toBe('someSuperCoolEvent');
|
||||||
|
expect(thirdCallback.mock.calls[0][0].data).toBe(42);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].target).toBe(target);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].defaultPrevented).toBe(undefined);
|
||||||
|
expect(thirdCallback.mock.calls[0][0].preventDefault).toBe(undefined);
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
ref.current.navigation.emit({ type: eventName });
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(firstCallback.mock.calls[0][0].target).toBe(undefined);
|
||||||
|
|
||||||
|
expect(firstCallback).toBeCalledTimes(1);
|
||||||
|
expect(secondCallback).toBeCalledTimes(0);
|
||||||
|
expect(thirdCallback).toBeCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('has option to prevent default', () => {
|
it('has option to prevent default', () => {
|
||||||
expect.assertions(5);
|
expect.assertions(5);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { PartialState, NavigationState } from '@react-navigation/routers';
|
|||||||
type NavigateParams = {
|
type NavigateParams = {
|
||||||
screen?: string;
|
screen?: string;
|
||||||
params?: NavigateParams;
|
params?: NavigateParams;
|
||||||
|
initial?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type NavigateAction = {
|
type NavigateAction = {
|
||||||
@@ -35,6 +36,7 @@ export default function getActionFromState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
route = current.routes[current.routes.length - 1];
|
route = current.routes[current.routes.length - 1];
|
||||||
|
params.initial = current.routes.length === 1;
|
||||||
params.screen = route.name;
|
params.screen = route.name;
|
||||||
|
|
||||||
if (route.state) {
|
if (route.state) {
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ export default function getPathFromState(
|
|||||||
};
|
};
|
||||||
let currentOptions = options;
|
let currentOptions = options;
|
||||||
let pattern = route.name;
|
let pattern = route.name;
|
||||||
|
// we keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
|
||||||
|
let nestedRouteNames = '';
|
||||||
|
|
||||||
while (route.name in currentOptions) {
|
while (route.name in currentOptions) {
|
||||||
if (typeof currentOptions[route.name] === 'string') {
|
if (typeof currentOptions[route.name] === 'string') {
|
||||||
@@ -77,11 +79,13 @@ export default function getPathFromState(
|
|||||||
}).screens
|
}).screens
|
||||||
) {
|
) {
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||||
|
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
// if it is the end of state, we return pattern
|
// if it is the end of state, we return pattern
|
||||||
if (route.state === undefined) {
|
if (route.state === undefined) {
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||||
|
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
index =
|
index =
|
||||||
@@ -92,11 +96,13 @@ export default function getPathFromState(
|
|||||||
}).screens;
|
}).screens;
|
||||||
// if there is config for next route name, we go deeper
|
// if there is config for next route name, we go deeper
|
||||||
if (nextRoute.name in deeperConfig) {
|
if (nextRoute.name in deeperConfig) {
|
||||||
|
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||||
route = nextRoute as Route<string> & { state?: State };
|
route = nextRoute as Route<string> & { state?: State };
|
||||||
currentOptions = deeperConfig;
|
currentOptions = deeperConfig;
|
||||||
} else {
|
} else {
|
||||||
// if not, there is no sense in going deeper in config
|
// if not, there is no sense in going deeper in config
|
||||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||||
|
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +110,11 @@ export default function getPathFromState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pattern === undefined) {
|
||||||
|
// cut the first `/`
|
||||||
|
pattern = nestedRouteNames.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
// we don't add empty path strings to path
|
// we don't add empty path strings to path
|
||||||
if (pattern !== '') {
|
if (pattern !== '') {
|
||||||
const config =
|
const config =
|
||||||
@@ -147,6 +158,12 @@ export default function getPathFromState(
|
|||||||
if (route.state) {
|
if (route.state) {
|
||||||
path += '/';
|
path += '/';
|
||||||
} else if (params) {
|
} else if (params) {
|
||||||
|
for (let param in params) {
|
||||||
|
if (params[param] === 'undefined') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||||
|
delete params[param];
|
||||||
|
}
|
||||||
|
}
|
||||||
const query = queryString.stringify(params);
|
const query = queryString.stringify(params);
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
|
|||||||
@@ -410,24 +410,19 @@ export type RouteConfig<
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export type NavigationContainerRef =
|
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
|
||||||
| (NavigationHelpers<ParamListBase> &
|
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
|
||||||
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
|
/**
|
||||||
/**
|
* Reset the navigation state of the root navigator to the provided state.
|
||||||
* Reset the navigation state of the root navigator to the provided state.
|
*
|
||||||
*
|
* @param state Navigation state object.
|
||||||
* @param state Navigation state object.
|
*/
|
||||||
*/
|
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
|
||||||
resetRoot(
|
/**
|
||||||
state?: PartialState<NavigationState> | NavigationState
|
* Get the rehydrated navigation state of the navigation tree.
|
||||||
): void;
|
*/
|
||||||
/**
|
getRootState(): NavigationState;
|
||||||
* Get the rehydrated navigation state of the navigation tree.
|
};
|
||||||
*/
|
|
||||||
getRootState(): NavigationState;
|
|
||||||
})
|
|
||||||
| undefined
|
|
||||||
| null;
|
|
||||||
|
|
||||||
export type TypedNavigator<
|
export type TypedNavigator<
|
||||||
ParamList extends ParamListBase,
|
ParamList extends ParamListBase,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
|||||||
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should only dispatch events when the focused key changed and navigator is focused
|
// We should only emit events when the focused key changed and navigator is focused
|
||||||
// When navigator is not focused, screens inside shouldn't receive focused status either
|
// When navigator is not focused, screens inside shouldn't receive focused status either
|
||||||
if (
|
if (
|
||||||
lastFocusedKey === currentFocusedKey ||
|
lastFocusedKey === currentFocusedKey ||
|
||||||
@@ -62,7 +62,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
|
||||||
emitter.emit({ type: 'blur', target: lastFocusedKey });
|
emitter.emit({ type: 'blur', target: lastFocusedKey });
|
||||||
|
emitter.emit({ type: 'focus', target: currentFocusedKey });
|
||||||
}, [currentFocusedKey, emitter, navigation]);
|
}, [currentFocusedKey, emitter, navigation]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import useStateGetters from './useStateGetters';
|
import useStateGetters from './useStateGetters';
|
||||||
import useOnGetState from './useOnGetState';
|
import useOnGetState from './useOnGetState';
|
||||||
|
import useScheduleUpdate from './useScheduleUpdate';
|
||||||
|
|
||||||
// This is to make TypeScript compiler happy
|
// This is to make TypeScript compiler happy
|
||||||
// eslint-disable-next-line babel/no-unused-expressions
|
// eslint-disable-next-line babel/no-unused-expressions
|
||||||
@@ -42,6 +43,7 @@ type NavigatorRoute = {
|
|||||||
params?: {
|
params?: {
|
||||||
screen?: string;
|
screen?: string;
|
||||||
params?: object;
|
params?: object;
|
||||||
|
initial?: boolean;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -175,17 +177,19 @@ export default function useNavigationBuilder<
|
|||||||
| NavigatorRoute
|
| NavigatorRoute
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
const previousRouteRef = React.useRef(route);
|
const previousNestedParamsRef = React.useRef(route?.params);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
previousRouteRef.current = route;
|
previousNestedParamsRef.current = route?.params;
|
||||||
}, [route]);
|
}, [route]);
|
||||||
|
|
||||||
const { children, ...rest } = options;
|
const { children, ...rest } = options;
|
||||||
const { current: router } = React.useRef<Router<State, any>>(
|
const { current: router } = React.useRef<Router<State, any>>(
|
||||||
createRouter({
|
createRouter({
|
||||||
...((rest as unknown) as RouterOptions),
|
...((rest as unknown) as RouterOptions),
|
||||||
...(route?.params && typeof route.params.screen === 'string'
|
...(route?.params &&
|
||||||
|
route.params.initial !== false &&
|
||||||
|
typeof route.params.screen === 'string'
|
||||||
? { initialRouteName: route.params.screen }
|
? { initialRouteName: route.params.screen }
|
||||||
: null),
|
: null),
|
||||||
})
|
})
|
||||||
@@ -218,7 +222,7 @@ export default function useNavigationBuilder<
|
|||||||
(acc, curr) => {
|
(acc, curr) => {
|
||||||
const { initialParams } = screens[curr];
|
const { initialParams } = screens[curr];
|
||||||
const initialParamsFromParams =
|
const initialParamsFromParams =
|
||||||
route?.params && route.params.screen === curr
|
route?.params?.initial !== false && route?.params?.screen === curr
|
||||||
? route.params.params
|
? route.params.params
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
@@ -265,6 +269,8 @@ export default function useNavigationBuilder<
|
|||||||
>();
|
>();
|
||||||
const initializedStateRef = React.useRef<State>();
|
const initializedStateRef = React.useRef<State>();
|
||||||
|
|
||||||
|
let isFirstStateInitialization = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
initializedStateRef.current === undefined ||
|
initializedStateRef.current === undefined ||
|
||||||
currentState !== previousStateRef.current
|
currentState !== previousStateRef.current
|
||||||
@@ -273,16 +279,21 @@ export default function useNavigationBuilder<
|
|||||||
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
|
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
|
||||||
// Otherwise assume that the state was provided as initial state
|
// Otherwise assume that the state was provided as initial state
|
||||||
// So we need to rehydrate it to make it usable
|
// So we need to rehydrate it to make it usable
|
||||||
initializedStateRef.current =
|
if (currentState === undefined || !isStateValid(currentState)) {
|
||||||
currentState === undefined || !isStateValid(currentState)
|
isFirstStateInitialization = true;
|
||||||
? router.getInitialState({
|
initializedStateRef.current = router.getInitialState({
|
||||||
routeNames,
|
routeNames,
|
||||||
routeParamList,
|
routeParamList,
|
||||||
})
|
});
|
||||||
: router.getRehydratedState(currentState as PartialState<State>, {
|
} else {
|
||||||
routeNames,
|
initializedStateRef.current = router.getRehydratedState(
|
||||||
routeParamList,
|
currentState as PartialState<State>,
|
||||||
});
|
{
|
||||||
|
routeNames,
|
||||||
|
routeParamList,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -308,16 +319,14 @@ export default function useNavigationBuilder<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
previousRouteRef.current &&
|
typeof route?.params?.screen === 'string' &&
|
||||||
route &&
|
(route.params !== previousNestedParamsRef.current ||
|
||||||
route.params &&
|
(route.params.initial === false && isFirstStateInitialization))
|
||||||
typeof route.params.screen === 'string' &&
|
|
||||||
route.params !== previousRouteRef.current.params
|
|
||||||
) {
|
) {
|
||||||
// If the route was updated with new name and/or params, we should navigate there
|
// If the route was updated with new name and/or params, we should navigate there
|
||||||
// The update should be limited to current navigator only, so we call the router manually
|
// The update should be limited to current navigator only, so we call the router manually
|
||||||
const updatedState = router.getStateForAction(
|
const updatedState = router.getStateForAction(
|
||||||
state,
|
nextState,
|
||||||
CommonActions.navigate(route.params.screen, route.params.params),
|
CommonActions.navigate(route.params.screen, route.params.params),
|
||||||
{
|
{
|
||||||
routeNames,
|
routeNames,
|
||||||
@@ -331,17 +340,17 @@ export default function useNavigationBuilder<
|
|||||||
routeNames,
|
routeNames,
|
||||||
routeParamList,
|
routeParamList,
|
||||||
})
|
})
|
||||||
: state;
|
: nextState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldUpdate = state !== nextState;
|
const shouldUpdate = state !== nextState;
|
||||||
|
|
||||||
React.useEffect(() => {
|
useScheduleUpdate(() => {
|
||||||
if (shouldUpdate) {
|
if (shouldUpdate) {
|
||||||
// If the state needs to be updated, we'll schedule an update with React
|
// If the state needs to be updated, we'll schedule an update
|
||||||
setState(nextState);
|
setState(nextState);
|
||||||
}
|
}
|
||||||
}, [nextState, setState, shouldUpdate]);
|
});
|
||||||
|
|
||||||
// The up-to-date state will come in next render, but we don't need to wait for it
|
// The up-to-date state will come in next render, but we don't need to wait for it
|
||||||
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
|
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
|
||||||
@@ -371,36 +380,40 @@ export default function useNavigationBuilder<
|
|||||||
const emitter = useEventEmitter((e) => {
|
const emitter = useEventEmitter((e) => {
|
||||||
let routeNames = [];
|
let routeNames = [];
|
||||||
|
|
||||||
let target: Route<string> | undefined;
|
let route: Route<string> | undefined;
|
||||||
|
|
||||||
if (e.target) {
|
if (e.target) {
|
||||||
target = state.routes.find((route) => route.key === e.target);
|
route = state.routes.find((route) => route.key === e.target);
|
||||||
|
|
||||||
if (target?.name) {
|
if (route?.name) {
|
||||||
routeNames.push(target.name);
|
routeNames.push(route.name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
routeNames.push(...Object.keys(screens));
|
route = state.routes[state.index];
|
||||||
|
routeNames.push(
|
||||||
|
...Object.keys(screens).filter((name) => route?.name === name)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (route == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const navigation = descriptors[route.key].navigation;
|
||||||
|
|
||||||
const listeners = ([] as (((e: any) => void) | undefined)[])
|
const listeners = ([] as (((e: any) => void) | undefined)[])
|
||||||
.concat(
|
.concat(
|
||||||
...routeNames.map((name) => {
|
...routeNames.map((name) => {
|
||||||
const { listeners } = screens[name];
|
const { listeners } = screens[name];
|
||||||
|
const map =
|
||||||
|
typeof listeners === 'function'
|
||||||
|
? listeners({ route: route as any, navigation })
|
||||||
|
: listeners;
|
||||||
|
|
||||||
return listeners
|
return map
|
||||||
? Object.keys(listeners)
|
? Object.keys(map)
|
||||||
.filter((type) => type === e.type)
|
.filter((type) => type === e.type)
|
||||||
.map((type) => {
|
.map((type) => map?.[type])
|
||||||
if (typeof listeners === 'function') {
|
|
||||||
const route = target ?? state.routes[state.index];
|
|
||||||
const navigation = descriptors[route.key].navigation;
|
|
||||||
|
|
||||||
return listeners({ route: route as any, navigation })[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
return listeners[type];
|
|
||||||
})
|
|
||||||
: undefined;
|
: undefined;
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import shortid from 'shortid';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { SingleNavigatorContext } from './EnsureSingleNavigator';
|
import { SingleNavigatorContext } from './EnsureSingleNavigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -7,7 +7,7 @@ import { SingleNavigatorContext } from './EnsureSingleNavigator';
|
|||||||
* This is used to prevent multiple navigators under a single container or screen.
|
* This is used to prevent multiple navigators under a single container or screen.
|
||||||
*/
|
*/
|
||||||
export default function useRegisterNavigator() {
|
export default function useRegisterNavigator() {
|
||||||
const [key] = React.useState(() => shortid());
|
const [key] = React.useState(() => nanoid());
|
||||||
const container = React.useContext(SingleNavigatorContext);
|
const container = React.useContext(SingleNavigatorContext);
|
||||||
|
|
||||||
if (container === undefined) {
|
if (container === undefined) {
|
||||||
|
|||||||
32
packages/core/src/useScheduleUpdate.tsx
Normal file
32
packages/core/src/useScheduleUpdate.tsx
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const MISSING_CONTEXT_ERROR = "Couldn't find a schedule context.";
|
||||||
|
|
||||||
|
export const ScheduleUpdateContext = React.createContext<{
|
||||||
|
scheduleUpdate: (callback: () => void) => void;
|
||||||
|
flushUpdates: () => void;
|
||||||
|
}>({
|
||||||
|
scheduleUpdate() {
|
||||||
|
throw new Error(MISSING_CONTEXT_ERROR);
|
||||||
|
},
|
||||||
|
flushUpdates() {
|
||||||
|
throw new Error(MISSING_CONTEXT_ERROR);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When screen config changes, we want to update the navigator in the same update phase.
|
||||||
|
* However, navigation state is in the root component and React won't let us update it from a child.
|
||||||
|
* This is a workaround for that, the scheduled update is stored in the ref without actually calling setState.
|
||||||
|
* It lets all subsequent updates access the latest state so it stays correct.
|
||||||
|
* Then we call setState during after the component updates.
|
||||||
|
*/
|
||||||
|
export default function useScheduleUpdate(callback: () => void) {
|
||||||
|
const { scheduleUpdate, flushUpdates } = React.useContext(
|
||||||
|
ScheduleUpdateContext
|
||||||
|
);
|
||||||
|
|
||||||
|
scheduleUpdate(callback);
|
||||||
|
|
||||||
|
React.useEffect(flushUpdates);
|
||||||
|
}
|
||||||
@@ -2,8 +2,12 @@ import * as React from 'react';
|
|||||||
|
|
||||||
const UNINTIALIZED_STATE = {};
|
const UNINTIALIZED_STATE = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is definitely not compatible with concurrent mode, but we don't have a solution for sync state yet.
|
||||||
|
*/
|
||||||
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
export default function useSyncState<T>(initialState?: (() => T) | T) {
|
||||||
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
|
||||||
|
const isSchedulingRef = React.useRef(false);
|
||||||
|
|
||||||
if (stateRef.current === UNINTIALIZED_STATE) {
|
if (stateRef.current === UNINTIALIZED_STATE) {
|
||||||
stateRef.current =
|
stateRef.current =
|
||||||
@@ -11,7 +15,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
|||||||
typeof initialState === 'function' ? initialState() : initialState;
|
typeof initialState === 'function' ? initialState() : initialState;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [state, setTrackingState] = React.useState(stateRef.current);
|
const [trackingState, setTrackingState] = React.useState(stateRef.current);
|
||||||
|
|
||||||
const getState = React.useCallback(() => stateRef.current, []);
|
const getState = React.useCallback(() => stateRef.current, []);
|
||||||
|
|
||||||
@@ -21,8 +25,35 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stateRef.current = state;
|
stateRef.current = state;
|
||||||
setTrackingState(state);
|
|
||||||
|
if (!isSchedulingRef.current) {
|
||||||
|
setTrackingState(state);
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return [state, getState, setState] as const;
|
const scheduleUpdate = React.useCallback((callback: () => void) => {
|
||||||
|
isSchedulingRef.current = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
callback();
|
||||||
|
} finally {
|
||||||
|
isSchedulingRef.current = false;
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const flushUpdates = React.useCallback(() => {
|
||||||
|
// Make sure that the tracking state is up-to-date.
|
||||||
|
// We call it unconditionally, but React should skip the update if state is unchanged.
|
||||||
|
setTrackingState(stateRef.current);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// If we're rendering and the tracking state is out of date, update it immediately
|
||||||
|
// This will make sure that our updates are applied as early as possible.
|
||||||
|
if (trackingState !== stateRef.current) {
|
||||||
|
setTrackingState(stateRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
const state = stateRef.current;
|
||||||
|
|
||||||
|
return [state, getState, setState, scheduleUpdate, flushUpdates] as const;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,59 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.1...@react-navigation/drawer@5.5.0) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* fix drawer not closing on web ([e2bcf51](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/e2bcf5168c389833eaaeadb4b8794aaea4a66d17)), closes [#6759](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6759)
|
||||||
|
* webkit style error in overlay ([821343f](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/821343fed38577cfdc87a78f13f991d5760bf8f5))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.0...@react-navigation/drawer@5.4.1) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* don't hide content from accessibility with permanent drawer ([cb2f157](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/cb2f157a561a2ce3f073eb4ccb567532c77bd869)), closes [#7976](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7976)
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.4...@react-navigation/drawer@5.4.0) (2020-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disable only swipe gesture on safari ([105da6a](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/105da6ab2fe69847b676c4d4117638212cda1f9a))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add swipeEnabled option to disable swipe gesture in drawer ([#7834](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7834)) ([ac7f972](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/ac7f972e922a82cd32d943356941d100b68bd8b0))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.3...@react-navigation/drawer@5.3.4) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.2...@react-navigation/drawer@5.3.3) (2020-03-22)
|
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.2...@react-navigation/drawer@5.3.3) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/drawer
|
**Note:** Version bump only for package @react-navigation/drawer
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/drawer",
|
"name": "@react-navigation/drawer",
|
||||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||||
"version": "5.3.3",
|
"version": "5.5.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.61.22",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"react-native-reanimated": "^1.7.0",
|
"react-native-reanimated": "^1.7.0",
|
||||||
"react-native-safe-area-context": "^0.7.3",
|
"react-native-safe-area-context": "^0.7.3",
|
||||||
"react-native-screens": "^2.3.0",
|
"react-native-screens": "^2.3.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
DrawerNavigationOptions,
|
DrawerNavigationOptions,
|
||||||
DrawerNavigationProp,
|
DrawerNavigationProp,
|
||||||
DrawerContentOptions,
|
DrawerContentOptions,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
|
|||||||
|
|
||||||
function DrawerNavigator({
|
function DrawerNavigator({
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
|
openByDefault,
|
||||||
backBehavior,
|
backBehavior,
|
||||||
children,
|
children,
|
||||||
screenOptions,
|
screenOptions,
|
||||||
@@ -33,6 +34,7 @@ function DrawerNavigator({
|
|||||||
DrawerNavigationEventMap
|
DrawerNavigationEventMap
|
||||||
>(DrawerRouter, {
|
>(DrawerRouter, {
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
|
openByDefault,
|
||||||
backBehavior,
|
backBehavior,
|
||||||
children,
|
children,
|
||||||
screenOptions,
|
screenOptions,
|
||||||
|
|||||||
@@ -111,9 +111,18 @@ export type DrawerNavigationOptions = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether you can use gestures to open or close the drawer.
|
* Whether you can use gestures to open or close the drawer.
|
||||||
|
* Setting this to `false` disables swipe gestures as well as tap on overlay to close.
|
||||||
|
* See `swipeEnabled` to disable only the swipe gesture.
|
||||||
* Defaults to `true`
|
* Defaults to `true`
|
||||||
*/
|
*/
|
||||||
gestureEnabled?: boolean;
|
gestureEnabled?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether you can use swipe gestures to open or close the drawer.
|
||||||
|
* Defaults to `true`
|
||||||
|
*/
|
||||||
|
swipeEnabled?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether this screen should be unmounted when navigating away from it.
|
* Whether this screen should be unmounted when navigating away from it.
|
||||||
* Defaults to `false`.
|
* Defaults to `false`.
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
PanGestureHandler,
|
PanGestureHandler,
|
||||||
TapGestureHandler,
|
TapGestureHandler,
|
||||||
State,
|
State as GestureState,
|
||||||
|
TapGestureHandlerStateChangeEvent,
|
||||||
} from 'react-native-gesture-handler';
|
} from 'react-native-gesture-handler';
|
||||||
import Animated from 'react-native-reanimated';
|
import Animated from 'react-native-reanimated';
|
||||||
import Overlay from './Overlay';
|
import Overlay from './Overlay';
|
||||||
@@ -80,6 +81,7 @@ type Props = {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||||
gestureEnabled: boolean;
|
gestureEnabled: boolean;
|
||||||
|
swipeEnabled: boolean;
|
||||||
drawerPosition: 'left' | 'right';
|
drawerPosition: 'left' | 'right';
|
||||||
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
||||||
keyboardDismissMode: 'none' | 'on-drag';
|
keyboardDismissMode: 'none' | 'on-drag';
|
||||||
@@ -94,32 +96,15 @@ type Props = {
|
|||||||
renderDrawerContent: Renderer;
|
renderDrawerContent: Renderer;
|
||||||
renderSceneContent: Renderer;
|
renderSceneContent: Renderer;
|
||||||
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
|
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
|
||||||
|
dimensions: { width: number; height: number };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export default class DrawerView extends React.Component<Props> {
|
||||||
* Disables the pan gesture by default on Apple devices in the browser.
|
|
||||||
* https://stackoverflow.com/a/9039885
|
|
||||||
*/
|
|
||||||
function shouldEnableGesture(): boolean {
|
|
||||||
if (
|
|
||||||
Platform.OS === 'web' &&
|
|
||||||
typeof navigator !== 'undefined' &&
|
|
||||||
typeof window !== 'undefined'
|
|
||||||
) {
|
|
||||||
const isWebAppleDevice =
|
|
||||||
/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
||||||
|
|
||||||
return !isWebAppleDevice;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class DrawerView extends React.PureComponent<Props> {
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
|
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
|
||||||
drawerType: 'front',
|
drawerType: 'front',
|
||||||
gestureEnabled: shouldEnableGesture(),
|
gestureEnabled: true,
|
||||||
|
swipeEnabled: Platform.OS !== 'web',
|
||||||
swipeEdgeWidth: 32,
|
swipeEdgeWidth: 32,
|
||||||
swipeVelocityThreshold: 500,
|
swipeVelocityThreshold: 500,
|
||||||
keyboardDismissMode: 'on-drag',
|
keyboardDismissMode: 'on-drag',
|
||||||
@@ -138,16 +123,11 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
open,
|
open,
|
||||||
drawerPosition,
|
drawerPosition,
|
||||||
drawerType,
|
drawerType,
|
||||||
gestureEnabled,
|
|
||||||
swipeDistanceThreshold,
|
swipeDistanceThreshold,
|
||||||
swipeVelocityThreshold,
|
swipeVelocityThreshold,
|
||||||
hideStatusBar,
|
hideStatusBar,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (prevProps.gestureEnabled !== gestureEnabled) {
|
|
||||||
this.isGestureEnabled.setValue(gestureEnabled ? TRUE : FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
// If we're not in the middle of a transition, sync the drawer's open state
|
// If we're not in the middle of a transition, sync the drawer's open state
|
||||||
typeof this.pendingOpenValue !== 'boolean' ||
|
typeof this.pendingOpenValue !== 'boolean' ||
|
||||||
@@ -217,30 +197,54 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private getDrawerWidth = (): number => {
|
||||||
|
const { drawerStyle, dimensions } = this.props;
|
||||||
|
const { width } = StyleSheet.flatten(drawerStyle);
|
||||||
|
|
||||||
|
if (typeof width === 'string' && width.endsWith('%')) {
|
||||||
|
// Try to calculate width if a percentage is given
|
||||||
|
const percentage = Number(width.replace(/%$/, ''));
|
||||||
|
|
||||||
|
if (Number.isFinite(percentage)) {
|
||||||
|
return dimensions.width * (percentage / 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof width === 'number' ? width : 0;
|
||||||
|
};
|
||||||
|
|
||||||
private clock = new Clock();
|
private clock = new Clock();
|
||||||
private interactionHandle: number | undefined;
|
private interactionHandle: number | undefined;
|
||||||
|
|
||||||
private isDrawerTypeFront = new Value<Binary>(
|
private isDrawerTypeFront = new Value<Binary>(
|
||||||
this.props.drawerType === 'front' ? TRUE : FALSE
|
this.props.drawerType === 'front' ? TRUE : FALSE
|
||||||
);
|
);
|
||||||
private isGestureEnabled = new Value(
|
|
||||||
this.props.gestureEnabled ? TRUE : FALSE
|
|
||||||
);
|
|
||||||
|
|
||||||
private isOpen = new Value<Binary>(this.props.open ? TRUE : FALSE);
|
private isOpen = new Value<Binary>(this.props.open ? TRUE : FALSE);
|
||||||
private nextIsOpen = new Value<Binary | -1>(UNSET);
|
private nextIsOpen = new Value<Binary | -1>(UNSET);
|
||||||
private isSwiping = new Value<Binary>(FALSE);
|
private isSwiping = new Value<Binary>(FALSE);
|
||||||
|
|
||||||
private gestureState = new Value<number>(State.UNDETERMINED);
|
private initialDrawerWidth = this.getDrawerWidth();
|
||||||
|
|
||||||
|
private gestureState = new Value<number>(GestureState.UNDETERMINED);
|
||||||
private touchX = new Value<number>(0);
|
private touchX = new Value<number>(0);
|
||||||
private velocityX = new Value<number>(0);
|
private velocityX = new Value<number>(0);
|
||||||
private gestureX = new Value<number>(0);
|
private gestureX = new Value<number>(0);
|
||||||
private offsetX = new Value<number>(0);
|
private offsetX = new Value<number>(0);
|
||||||
private position = new Value<number>(0);
|
private position = new Value<number>(
|
||||||
|
this.props.open
|
||||||
|
? this.initialDrawerWidth *
|
||||||
|
(this.props.drawerPosition === 'right'
|
||||||
|
? DIRECTION_RIGHT
|
||||||
|
: DIRECTION_LEFT)
|
||||||
|
: 0
|
||||||
|
);
|
||||||
|
|
||||||
private containerWidth = new Value<number>(0);
|
private containerWidth = new Value<number>(this.props.dimensions.width);
|
||||||
private drawerWidth = new Value<number>(0);
|
private drawerWidth = new Value<number>(this.initialDrawerWidth);
|
||||||
private drawerOpacity = new Value<number>(0);
|
private drawerOpacity = new Value<number>(
|
||||||
|
this.initialDrawerWidth || this.props.drawerType === 'permanent' ? 1 : 0
|
||||||
|
);
|
||||||
private drawerPosition = new Value<number>(
|
private drawerPosition = new Value<number>(
|
||||||
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
|
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
|
||||||
);
|
);
|
||||||
@@ -418,12 +422,12 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
onChange(
|
onChange(
|
||||||
this.gestureState,
|
this.gestureState,
|
||||||
cond(
|
cond(
|
||||||
eq(this.gestureState, State.ACTIVE),
|
eq(this.gestureState, GestureState.ACTIVE),
|
||||||
call([], this.handleStartInteraction)
|
call([], this.handleStartInteraction)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
cond(
|
cond(
|
||||||
eq(this.gestureState, State.ACTIVE),
|
eq(this.gestureState, GestureState.ACTIVE),
|
||||||
[
|
[
|
||||||
cond(this.isSwiping, NOOP, [
|
cond(this.isSwiping, NOOP, [
|
||||||
// We weren't dragging before, set it to true
|
// We weren't dragging before, set it to true
|
||||||
@@ -507,14 +511,28 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
private handleTapStateChange = event([
|
private handleTapStateChange =
|
||||||
{
|
Platform.OS === 'web'
|
||||||
nativeEvent: {
|
? // FIXME: Drawer doesn't close on Web with the same code that we use for native
|
||||||
oldState: (s: Animated.Value<number>) =>
|
({ nativeEvent }: TapGestureHandlerStateChangeEvent) => {
|
||||||
cond(eq(s, State.ACTIVE), set(this.manuallyTriggerSpring, TRUE)),
|
if (
|
||||||
},
|
nativeEvent.state === GestureState.END &&
|
||||||
},
|
nativeEvent.oldState === GestureState.ACTIVE
|
||||||
]);
|
) {
|
||||||
|
this.toggleDrawer(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: event([
|
||||||
|
{
|
||||||
|
nativeEvent: {
|
||||||
|
oldState: (s: Animated.Value<number>) =>
|
||||||
|
cond(
|
||||||
|
eq(s, GestureState.ACTIVE),
|
||||||
|
set(this.manuallyTriggerSpring, TRUE)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
private handleContainerLayout = (e: LayoutChangeEvent) =>
|
private handleContainerLayout = (e: LayoutChangeEvent) =>
|
||||||
this.containerWidth.setValue(e.nativeEvent.layout.width);
|
this.containerWidth.setValue(e.nativeEvent.layout.width);
|
||||||
@@ -554,6 +572,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
const {
|
const {
|
||||||
open,
|
open,
|
||||||
gestureEnabled,
|
gestureEnabled,
|
||||||
|
swipeEnabled,
|
||||||
drawerPosition,
|
drawerPosition,
|
||||||
drawerType,
|
drawerType,
|
||||||
swipeEdgeWidth,
|
swipeEdgeWidth,
|
||||||
@@ -569,9 +588,15 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
const isOpen = drawerType === 'permanent' ? true : open;
|
const isOpen = drawerType === 'permanent' ? true : open;
|
||||||
const isRight = drawerPosition === 'right';
|
const isRight = drawerPosition === 'right';
|
||||||
|
|
||||||
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
|
const contentTranslateX =
|
||||||
|
drawerType === 'front' || drawerType === 'permanent'
|
||||||
|
? 0
|
||||||
|
: this.translateX;
|
||||||
|
|
||||||
const drawerTranslateX =
|
const drawerTranslateX =
|
||||||
drawerType === 'back'
|
drawerType === 'permanent'
|
||||||
|
? 0
|
||||||
|
: drawerType === 'back'
|
||||||
? I18nManager.isRTL
|
? I18nManager.isRTL
|
||||||
? multiply(
|
? multiply(
|
||||||
sub(this.containerWidth, this.drawerWidth),
|
sub(this.containerWidth, this.drawerWidth),
|
||||||
@@ -605,7 +630,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
onGestureEvent={this.handleGestureEvent}
|
onGestureEvent={this.handleGestureEvent}
|
||||||
onHandlerStateChange={this.handleGestureStateChange}
|
onHandlerStateChange={this.handleGestureStateChange}
|
||||||
hitSlop={hitSlop}
|
hitSlop={hitSlop}
|
||||||
enabled={drawerType !== 'permanent' && gestureEnabled}
|
enabled={drawerType !== 'permanent' && gestureEnabled && swipeEnabled}
|
||||||
{...gestureHandlerProps}
|
{...gestureHandlerProps}
|
||||||
>
|
>
|
||||||
<Animated.View
|
<Animated.View
|
||||||
@@ -621,31 +646,38 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
<Animated.View
|
<Animated.View
|
||||||
style={[
|
style={[
|
||||||
styles.content,
|
styles.content,
|
||||||
drawerType !== 'permanent' && {
|
{ transform: [{ translateX: contentTranslateX }] },
|
||||||
transform: [{ translateX: contentTranslateX }],
|
|
||||||
},
|
|
||||||
sceneContainerStyle as any,
|
sceneContainerStyle as any,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
accessibilityElementsHidden={isOpen}
|
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
|
||||||
importantForAccessibility={
|
importantForAccessibility={
|
||||||
isOpen ? 'no-hide-descendants' : 'auto'
|
isOpen && drawerType !== 'permanent'
|
||||||
|
? 'no-hide-descendants'
|
||||||
|
: 'auto'
|
||||||
}
|
}
|
||||||
style={styles.content}
|
style={styles.content}
|
||||||
>
|
>
|
||||||
{renderSceneContent({ progress })}
|
{renderSceneContent({ progress })}
|
||||||
</View>
|
</View>
|
||||||
{// Disable overlay if sidebar is permanent
|
{
|
||||||
drawerType === 'permanent' ? null : (
|
// Disable overlay if sidebar is permanent
|
||||||
<TapGestureHandler
|
drawerType === 'permanent' ? null : (
|
||||||
enabled={gestureEnabled}
|
<TapGestureHandler
|
||||||
onHandlerStateChange={this.handleTapStateChange}
|
enabled={gestureEnabled}
|
||||||
>
|
onHandlerStateChange={this.handleTapStateChange}
|
||||||
<Overlay progress={progress} style={overlayStyle} />
|
>
|
||||||
</TapGestureHandler>
|
<Overlay progress={progress} style={overlayStyle} />
|
||||||
)}
|
</TapGestureHandler>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
|
<Animated.Code
|
||||||
|
// This is needed to make sure that container width updates with `setValue`
|
||||||
|
// Without this, it won't update when not used in styles
|
||||||
|
exec={this.containerWidth}
|
||||||
|
/>
|
||||||
{drawerType === 'permanent' ? null : (
|
{drawerType === 'permanent' ? null : (
|
||||||
<Animated.Code
|
<Animated.Code
|
||||||
exec={block([
|
exec={block([
|
||||||
@@ -659,11 +691,15 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
accessibilityViewIsModal={isOpen}
|
accessibilityViewIsModal={isOpen && drawerType !== 'permanent'}
|
||||||
removeClippedSubviews={Platform.OS !== 'ios'}
|
removeClippedSubviews={Platform.OS !== 'ios'}
|
||||||
onLayout={this.handleDrawerLayout}
|
onLayout={this.handleDrawerLayout}
|
||||||
style={[
|
style={[
|
||||||
styles.container,
|
styles.container,
|
||||||
|
{
|
||||||
|
transform: [{ translateX: drawerTranslateX }],
|
||||||
|
opacity: this.drawerOpacity,
|
||||||
|
},
|
||||||
drawerType === 'permanent'
|
drawerType === 'permanent'
|
||||||
? // Without this, the `left`/`right` values don't get reset
|
? // Without this, the `left`/`right` values don't get reset
|
||||||
isRight
|
isRight
|
||||||
@@ -671,10 +707,6 @@ export default class DrawerView extends React.PureComponent<Props> {
|
|||||||
: { left: 0 }
|
: { left: 0 }
|
||||||
: [
|
: [
|
||||||
styles.nonPermanent,
|
styles.nonPermanent,
|
||||||
{
|
|
||||||
transform: [{ translateX: drawerTranslateX }],
|
|
||||||
opacity: this.drawerOpacity,
|
|
||||||
},
|
|
||||||
isRight ? { right: offset } : { left: offset },
|
isRight ? { right: offset } : { left: offset },
|
||||||
{ zIndex: drawerType === 'back' ? -1 : 0 },
|
{ zIndex: drawerType === 'back' ? -1 : 0 },
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -89,11 +89,9 @@ export default function DrawerView({
|
|||||||
sceneContainerStyle,
|
sceneContainerStyle,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const [loaded, setLoaded] = React.useState([state.index]);
|
const [loaded, setLoaded] = React.useState([state.index]);
|
||||||
const [drawerWidth, setDrawerWidth] = React.useState(() => {
|
const [dimensions, setDimensions] = React.useState(() =>
|
||||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
Dimensions.get('window')
|
||||||
|
);
|
||||||
return getDefaultDrawerWidth({ height, width });
|
|
||||||
});
|
|
||||||
|
|
||||||
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
||||||
|
|
||||||
@@ -141,13 +139,13 @@ export default function DrawerView({
|
|||||||
}, [handleDrawerClose, isDrawerOpen, navigation, state.key]);
|
}, [handleDrawerClose, isDrawerOpen, navigation, state.key]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const updateWidth = ({ window }: { window: ScaledSize }) => {
|
const updateDimensions = ({ window }: { window: ScaledSize }) => {
|
||||||
setDrawerWidth(getDefaultDrawerWidth(window));
|
setDimensions(window);
|
||||||
};
|
};
|
||||||
|
|
||||||
Dimensions.addEventListener('change', updateWidth);
|
Dimensions.addEventListener('change', updateDimensions);
|
||||||
|
|
||||||
return () => Dimensions.removeEventListener('change', updateWidth);
|
return () => Dimensions.removeEventListener('change', updateDimensions);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!loaded.includes(state.index)) {
|
if (!loaded.includes(state.index)) {
|
||||||
@@ -200,7 +198,7 @@ export default function DrawerView({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activeKey = state.routes[state.index].key;
|
const activeKey = state.routes[state.index].key;
|
||||||
const { gestureEnabled } = descriptors[activeKey].options;
|
const { gestureEnabled, swipeEnabled } = descriptors[activeKey].options;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GestureHandlerWrapper style={styles.content}>
|
<GestureHandlerWrapper style={styles.content}>
|
||||||
@@ -210,6 +208,7 @@ export default function DrawerView({
|
|||||||
<Drawer
|
<Drawer
|
||||||
open={isDrawerOpen}
|
open={isDrawerOpen}
|
||||||
gestureEnabled={gestureEnabled}
|
gestureEnabled={gestureEnabled}
|
||||||
|
swipeEnabled={swipeEnabled}
|
||||||
onOpen={handleDrawerOpen}
|
onOpen={handleDrawerOpen}
|
||||||
onClose={handleDrawerClose}
|
onClose={handleDrawerClose}
|
||||||
onGestureRef={(ref) => {
|
onGestureRef={(ref) => {
|
||||||
@@ -224,7 +223,10 @@ export default function DrawerView({
|
|||||||
sceneContainerStyle,
|
sceneContainerStyle,
|
||||||
]}
|
]}
|
||||||
drawerStyle={[
|
drawerStyle={[
|
||||||
{ width: drawerWidth, backgroundColor: colors.card },
|
{
|
||||||
|
width: getDefaultDrawerWidth(dimensions),
|
||||||
|
backgroundColor: colors.card,
|
||||||
|
},
|
||||||
drawerType === 'permanent' &&
|
drawerType === 'permanent' &&
|
||||||
(drawerPosition === 'left'
|
(drawerPosition === 'left'
|
||||||
? {
|
? {
|
||||||
@@ -246,6 +248,7 @@ export default function DrawerView({
|
|||||||
renderSceneContent={renderContent}
|
renderSceneContent={renderContent}
|
||||||
keyboardDismissMode={keyboardDismissMode}
|
keyboardDismissMode={keyboardDismissMode}
|
||||||
drawerPostion={drawerPosition}
|
drawerPostion={drawerPosition}
|
||||||
|
dimensions={dimensions}
|
||||||
/>
|
/>
|
||||||
</DrawerOpenContext.Provider>
|
</DrawerOpenContext.Provider>
|
||||||
</DrawerGestureContext.Provider>
|
</DrawerGestureContext.Provider>
|
||||||
|
|||||||
@@ -29,22 +29,24 @@ const Overlay = React.forwardRef(function Overlay(
|
|||||||
<Animated.View
|
<Animated.View
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
style={[styles.overlay, animatedStyle, style]}
|
style={[styles.overlay, overlayStyle, animatedStyle, style]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const overlayStyle = Platform.select<Record<string, string>>({
|
||||||
|
web: {
|
||||||
|
// Disable touch highlight on mobile Safari.
|
||||||
|
// WebkitTapHighlightColor must be used outside of StyleSheet.create because react-native-web will omit the property.
|
||||||
|
WebkitTapHighlightColor: 'transparent',
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
});
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
overlay: {
|
overlay: {
|
||||||
...StyleSheet.absoluteFillObject,
|
...StyleSheet.absoluteFillObject,
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||||
...Platform.select({
|
|
||||||
web: {
|
|
||||||
// Disable touch highlight on mobile Safari.
|
|
||||||
WebkitTapHighlightColor: 'transparent',
|
|
||||||
},
|
|
||||||
default: {},
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.8...@react-navigation/material-bottom-tabs@5.1.9) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.7...@react-navigation/material-bottom-tabs@5.1.8) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.6...@react-navigation/material-bottom-tabs@5.1.7) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.5...@react-navigation/material-bottom-tabs@5.1.6) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.4...@react-navigation/material-bottom-tabs@5.1.5) (2020-03-22)
|
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.4...@react-navigation/material-bottom-tabs@5.1.5) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-bottom-tabs",
|
"name": "@react-navigation/material-bottom-tabs",
|
||||||
"description": "Integration for bottom navigation component from react-native-paper",
|
"description": "Integration for bottom navigation component from react-native-paper",
|
||||||
"version": "5.1.5",
|
"version": "5.1.9",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -36,16 +36,16 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.61.22",
|
||||||
"@types/react-native-vector-icons": "^6.4.5",
|
"@types/react-native-vector-icons": "^6.4.5",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-paper": "^3.6.0",
|
"react-native-paper": "^3.7.0",
|
||||||
"react-native-vector-icons": "^6.6.0",
|
"react-native-vector-icons": "^6.6.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export { default as MaterialBottomTabView } from './views/MaterialBottomTabView'
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
MaterialBottomTabNavigationOptions,
|
MaterialBottomTabNavigationOptions,
|
||||||
MaterialBottomTabNavigationProp,
|
MaterialBottomTabNavigationProp,
|
||||||
} from './types';
|
} from './types';
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.8...@react-navigation/material-top-tabs@5.1.9) (2020-04-17)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.7...@react-navigation/material-top-tabs@5.1.8) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.6...@react-navigation/material-top-tabs@5.1.7) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.5...@react-navigation/material-top-tabs@5.1.6) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.4...@react-navigation/material-top-tabs@5.1.5) (2020-03-22)
|
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.4...@react-navigation/material-top-tabs@5.1.5) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/material-top-tabs",
|
"name": "@react-navigation/material-top-tabs",
|
||||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||||
"version": "5.1.5",
|
"version": "5.1.9",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.61.22",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
@@ -47,8 +47,8 @@
|
|||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-gesture-handler": "^1.6.0",
|
"react-native-gesture-handler": "^1.6.0",
|
||||||
"react-native-reanimated": "^1.7.0",
|
"react-native-reanimated": "^1.7.0",
|
||||||
"react-native-tab-view": "^2.13.0",
|
"react-native-tab-view": "^2.14.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-navigation/native": "^5.0.5",
|
"@react-navigation/native": "^5.0.5",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export { default as MaterialTopTabBar } from './views/MaterialTopTabBar';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
MaterialTopTabNavigationOptions,
|
MaterialTopTabNavigationOptions,
|
||||||
MaterialTopTabNavigationProp,
|
MaterialTopTabNavigationProp,
|
||||||
MaterialTopTabBarProps,
|
MaterialTopTabBarProps,
|
||||||
|
|||||||
@@ -3,6 +3,44 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.5...@react-navigation/native@5.1.6) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* handle in-page go back when there's no history ([6bdf6ae](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/6bdf6ae4ed0f83ac1deb3172d9075a6a2adbbe11)), closes [#7852](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/7852)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.4...@react-navigation/native@5.1.5) (2020-04-08)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.3...@react-navigation/native@5.1.4) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.2...@react-navigation/native@5.1.3) (2020-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add info about android launchMode in useLinking error ([d94e43c](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d94e43c3c8625b209a5c883b8cb560496d07fda7))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.1...@react-navigation/native@5.1.2) (2020-03-22)
|
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.1...@react-navigation/native@5.1.2) (2020-03-22)
|
||||||
|
|
||||||
**Note:** Version bump only for package @react-navigation/native
|
**Note:** Version bump only for package @react-navigation/native
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/native",
|
"name": "@react-navigation/native",
|
||||||
"description": "React Native integration for React Navigation",
|
"description": "React Native integration for React Navigation",
|
||||||
"version": "5.1.2",
|
"version": "5.1.6",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native",
|
"react-native",
|
||||||
"react-navigation",
|
"react-navigation",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@react-navigation/core": "^5.3.0"
|
"@react-navigation/core": "^5.3.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
"react": "~16.9.0",
|
"react": "~16.9.0",
|
||||||
"react-native": "~0.61.5",
|
"react-native": "~0.61.5",
|
||||||
"react-native-testing-library": "^1.12.0",
|
"react-native-testing-library": "^1.12.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ type Props = NavigationContainerProps & {
|
|||||||
*/
|
*/
|
||||||
const NavigationContainer = React.forwardRef(function NavigationContainer(
|
const NavigationContainer = React.forwardRef(function NavigationContainer(
|
||||||
{ theme = DefaultTheme, ...rest }: Props,
|
{ theme = DefaultTheme, ...rest }: Props,
|
||||||
ref: React.Ref<NavigationContainerRef>
|
ref?: React.Ref<NavigationContainerRef | null>
|
||||||
) {
|
) {
|
||||||
const refContainer = React.useRef<NavigationContainerRef>(null);
|
const refContainer = React.useRef<NavigationContainerRef>(null);
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export type LinkingOptions = {
|
|||||||
*/
|
*/
|
||||||
getStateFromPath?: typeof getStateFromPathDefault;
|
getStateFromPath?: typeof getStateFromPathDefault;
|
||||||
/**
|
/**
|
||||||
* Custom function to conver the state object to a valid URL (advanced).
|
* Custom function to convert the state object to a valid URL (advanced).
|
||||||
*/
|
*/
|
||||||
getPathFromState?: typeof getPathFromStateDefault;
|
getPathFromState?: typeof getPathFromStateDefault;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Linking } from 'react-native';
|
import { Linking, Platform } from 'react-native';
|
||||||
import {
|
import {
|
||||||
getActionFromState,
|
getActionFromState,
|
||||||
getStateFromPath as getStateFromPathDefault,
|
getStateFromPath as getStateFromPathDefault,
|
||||||
@@ -20,7 +20,10 @@ export default function useLinking(
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isUsingLinking) {
|
if (isUsingLinking) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since deep links should only be handled in one place to avoid conflicts."
|
"Looks like you are using 'useLinking' in multiple components. This is likely an error since deep links should only be handled in one place to avoid conflicts." +
|
||||||
|
(Platform.OS === 'android'
|
||||||
|
? "\n\nIf you're not using it in multiple components, ensure that you have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple activities which run multiple instances of the root component."
|
||||||
|
: '')
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
isUsingLinking = true;
|
isUsingLinking = true;
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function useLinking(
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (isUsingLinking) {
|
if (isUsingLinking) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts."
|
"Looks like you are using 'useLinking' in multiple components. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Also ensure that you set your android activity launchMode to single task in your AndroiManifest.xml file."
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
isUsingLinking = true;
|
isUsingLinking = true;
|
||||||
@@ -224,14 +224,14 @@ export default function useLinking(
|
|||||||
let index = history.state?.index ?? 0;
|
let index = history.state?.index ?? 0;
|
||||||
|
|
||||||
if (previousStateLength === stateLength) {
|
if (previousStateLength === stateLength) {
|
||||||
// If no new enrties were added to history in our navigation state, we want to replaceState
|
// If no new entries were added to history in our navigation state, we want to replaceState
|
||||||
if (location.pathname + location.search !== path) {
|
if (location.pathname + location.search !== path) {
|
||||||
history.replaceState({ index }, '', path);
|
history.replaceState({ index }, '', path);
|
||||||
previousHistoryIndexRef.current = index;
|
previousHistoryIndexRef.current = index;
|
||||||
}
|
}
|
||||||
} else if (stateLength > previousStateLength) {
|
} else if (stateLength > previousStateLength) {
|
||||||
// If new enrties were added, pushState until we have same length
|
// If new entries were added, pushState until we have same length
|
||||||
// This won't be accurate if multiple enrties were added at once, but that's the best we can do
|
// This won't be accurate if multiple entries were added at once, but that's the best we can do
|
||||||
for (let i = 0, l = stateLength - previousStateLength; i < l; i++) {
|
for (let i = 0, l = stateLength - previousStateLength; i < l; i++) {
|
||||||
index++;
|
index++;
|
||||||
history.pushState({ index }, '', path);
|
history.pushState({ index }, '', path);
|
||||||
@@ -239,13 +239,27 @@ export default function useLinking(
|
|||||||
|
|
||||||
previousHistoryIndexRef.current = index;
|
previousHistoryIndexRef.current = index;
|
||||||
} else if (previousStateLength > stateLength) {
|
} else if (previousStateLength > stateLength) {
|
||||||
const delta = previousStateLength - stateLength;
|
const delta = Math.min(
|
||||||
|
previousStateLength - stateLength,
|
||||||
|
// We need to keep at least one item in the history
|
||||||
|
// Otherwise we'll exit the page
|
||||||
|
previousHistoryIndexRef.current - 1
|
||||||
|
);
|
||||||
|
|
||||||
// We need to set this to ignore the `popstate` event
|
if (delta > 0) {
|
||||||
pendingIndexChangeRef.current = index - delta;
|
// We need to set this to ignore the `popstate` event
|
||||||
|
pendingIndexChangeRef.current = index - delta;
|
||||||
|
|
||||||
// If new enrties were removed, go back so that we have same length
|
// If new entries were removed, go back so that we have same length
|
||||||
history.go(-delta);
|
history.go(-delta);
|
||||||
|
} else {
|
||||||
|
// We're not going back in history, but the navigation state changed
|
||||||
|
// The URL probably also changed, so we need to re-sync the URL
|
||||||
|
if (location.pathname + location.search !== path) {
|
||||||
|
history.replaceState({ index }, '', path);
|
||||||
|
previousHistoryIndexRef.current = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,41 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.3.0...@react-navigation/routers@5.4.0) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.2.1...@react-navigation/routers@5.3.0) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* separate normal exports and type exports ([303f0b7](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/303f0b78a5ab717b2d606cd9c8a22f3dae051f0f))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* make replace bubble up ([ba1f405](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/ba1f4051299ad86001592b8d3601c16fece159df))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.2.0...@react-navigation/routers@5.2.1) (2020-03-30)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/routers
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.1...@react-navigation/routers@5.2.0) (2020-03-22)
|
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.1...@react-navigation/routers@5.2.0) (2020-03-22)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/routers",
|
"name": "@react-navigation/routers",
|
||||||
"description": "Routers to help build custom navigators",
|
"description": "Routers to help build custom navigators",
|
||||||
"version": "5.2.0",
|
"version": "5.4.0",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"react-native",
|
"react-native",
|
||||||
@@ -30,12 +30,12 @@
|
|||||||
"clean": "del lib"
|
"clean": "del lib"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shortid": "^2.2.15"
|
"nanoid": "^3.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"del-cli": "^3.0.0",
|
"del-cli": "^3.0.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"@react-native-community/bob": {
|
"@react-native-community/bob": {
|
||||||
"source": "src",
|
"source": "src",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import shortid from 'shortid';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import { CommonNavigationAction, NavigationState, PartialState } from './types';
|
import { CommonNavigationAction, NavigationState, PartialState } from './types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,9 +55,7 @@ const BaseRouter = {
|
|||||||
return {
|
return {
|
||||||
...nextState,
|
...nextState,
|
||||||
routes: nextState.routes.map((route) =>
|
routes: nextState.routes.map((route) =>
|
||||||
route.key
|
route.key ? route : { ...route, key: `${route.name}-${nanoid()}` }
|
||||||
? route
|
|
||||||
: { ...route, key: `${route.name}-${shortid()}` }
|
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import shortid from 'shortid';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import {
|
import {
|
||||||
PartialState,
|
PartialState,
|
||||||
CommonNavigationAction,
|
CommonNavigationAction,
|
||||||
@@ -21,7 +21,9 @@ export type DrawerActionType =
|
|||||||
target?: string;
|
target?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DrawerRouterOptions = TabRouterOptions;
|
export type DrawerRouterOptions = TabRouterOptions & {
|
||||||
|
openByDefault?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type DrawerNavigationState = Omit<
|
export type DrawerNavigationState = Omit<
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
@@ -95,10 +97,14 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function DrawerRouter(
|
export default function DrawerRouter({
|
||||||
options: DrawerRouterOptions
|
openByDefault,
|
||||||
): Router<DrawerNavigationState, DrawerActionType | CommonNavigationAction> {
|
...rest
|
||||||
const router = (TabRouter(options) as unknown) as Router<
|
}: DrawerRouterOptions): Router<
|
||||||
|
DrawerNavigationState,
|
||||||
|
DrawerActionType | CommonNavigationAction
|
||||||
|
> {
|
||||||
|
const router = (TabRouter(rest) as unknown) as Router<
|
||||||
DrawerNavigationState,
|
DrawerNavigationState,
|
||||||
TabActionType | CommonNavigationAction
|
TabActionType | CommonNavigationAction
|
||||||
>;
|
>;
|
||||||
@@ -109,13 +115,17 @@ export default function DrawerRouter(
|
|||||||
type: 'drawer',
|
type: 'drawer',
|
||||||
|
|
||||||
getInitialState({ routeNames, routeParamList }) {
|
getInitialState({ routeNames, routeParamList }) {
|
||||||
const state = router.getInitialState({ routeNames, routeParamList });
|
let state = router.getInitialState({ routeNames, routeParamList });
|
||||||
|
|
||||||
|
if (openByDefault) {
|
||||||
|
state = openDrawer(state);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
stale: false,
|
stale: false,
|
||||||
type: 'drawer',
|
type: 'drawer',
|
||||||
key: `drawer-${shortid()}`,
|
key: `drawer-${nanoid()}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -136,7 +146,7 @@ export default function DrawerRouter(
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
type: 'drawer',
|
type: 'drawer',
|
||||||
key: `drawer-${shortid()}`,
|
key: `drawer-${nanoid()}`,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -162,8 +172,14 @@ export default function DrawerRouter(
|
|||||||
return openDrawer(state);
|
return openDrawer(state);
|
||||||
|
|
||||||
case 'GO_BACK':
|
case 'GO_BACK':
|
||||||
if (isDrawerOpen(state)) {
|
if (openByDefault) {
|
||||||
return closeDrawer(state);
|
if (!isDrawerOpen(state)) {
|
||||||
|
return openDrawer(state);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (isDrawerOpen(state)) {
|
||||||
|
return closeDrawer(state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return router.getStateForAction(state, action, options);
|
return router.getStateForAction(state, action, options);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import shortid from 'shortid';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import BaseRouter from './BaseRouter';
|
import BaseRouter from './BaseRouter';
|
||||||
import {
|
import {
|
||||||
NavigationState,
|
NavigationState,
|
||||||
@@ -113,12 +113,12 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
return {
|
return {
|
||||||
stale: false,
|
stale: false,
|
||||||
type: 'stack',
|
type: 'stack',
|
||||||
key: `stack-${shortid()}`,
|
key: `stack-${nanoid()}`,
|
||||||
index: 0,
|
index: 0,
|
||||||
routeNames,
|
routeNames,
|
||||||
routes: [
|
routes: [
|
||||||
{
|
{
|
||||||
key: `${initialRouteName}-${shortid()}`,
|
key: `${initialRouteName}-${nanoid()}`,
|
||||||
name: initialRouteName,
|
name: initialRouteName,
|
||||||
params: routeParamList[initialRouteName],
|
params: routeParamList[initialRouteName],
|
||||||
},
|
},
|
||||||
@@ -139,7 +139,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
(route) =>
|
(route) =>
|
||||||
({
|
({
|
||||||
...route,
|
...route,
|
||||||
key: route.key || `${route.name}-${shortid()}`,
|
key: route.key || `${route.name}-${nanoid()}`,
|
||||||
params:
|
params:
|
||||||
routeParamList[route.name] !== undefined
|
routeParamList[route.name] !== undefined
|
||||||
? {
|
? {
|
||||||
@@ -157,7 +157,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
: routeNames[0];
|
: routeNames[0];
|
||||||
|
|
||||||
routes.push({
|
routes.push({
|
||||||
key: `${initialRouteName}-${shortid()}`,
|
key: `${initialRouteName}-${nanoid()}`,
|
||||||
name: initialRouteName,
|
name: initialRouteName,
|
||||||
params: routeParamList[initialRouteName],
|
params: routeParamList[initialRouteName],
|
||||||
});
|
});
|
||||||
@@ -166,7 +166,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
return {
|
return {
|
||||||
stale: false,
|
stale: false,
|
||||||
type: 'stack',
|
type: 'stack',
|
||||||
key: `stack-${shortid()}`,
|
key: `stack-${nanoid()}`,
|
||||||
index: routes.length - 1,
|
index: routes.length - 1,
|
||||||
routeNames,
|
routeNames,
|
||||||
routes,
|
routes,
|
||||||
@@ -186,7 +186,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
: routeNames[0];
|
: routeNames[0];
|
||||||
|
|
||||||
routes.push({
|
routes.push({
|
||||||
key: `${initialRouteName}-${shortid()}`,
|
key: `${initialRouteName}-${nanoid()}`,
|
||||||
name: initialRouteName,
|
name: initialRouteName,
|
||||||
params: routeParamList[initialRouteName],
|
params: routeParamList[initialRouteName],
|
||||||
});
|
});
|
||||||
@@ -219,9 +219,10 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
|
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'REPLACE': {
|
case 'REPLACE': {
|
||||||
const index = action.source
|
const index =
|
||||||
? state.routes.findIndex((r) => r.key === action.source)
|
action.target === state.key && action.source
|
||||||
: state.index;
|
? state.routes.findIndex((r) => r.key === action.source)
|
||||||
|
: state.index;
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return null;
|
return null;
|
||||||
@@ -238,7 +239,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
routes: state.routes.map((route, i) =>
|
routes: state.routes.map((route, i) =>
|
||||||
i === index
|
i === index
|
||||||
? {
|
? {
|
||||||
key: key !== undefined ? key : `${name}-${shortid()}`,
|
key: key !== undefined ? key : `${name}-${nanoid()}`,
|
||||||
name,
|
name,
|
||||||
params:
|
params:
|
||||||
routeParamList[name] !== undefined
|
routeParamList[name] !== undefined
|
||||||
@@ -263,7 +264,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
|||||||
{
|
{
|
||||||
key:
|
key:
|
||||||
action.payload.key === undefined
|
action.payload.key === undefined
|
||||||
? `${action.payload.name}-${shortid()}`
|
? `${action.payload.name}-${nanoid()}`
|
||||||
: action.payload.key,
|
: action.payload.key,
|
||||||
name: action.payload.name,
|
name: action.payload.name,
|
||||||
params:
|
params:
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import shortid from 'shortid';
|
import { nanoid } from 'nanoid/non-secure';
|
||||||
import BaseRouter from './BaseRouter';
|
import BaseRouter from './BaseRouter';
|
||||||
import {
|
import {
|
||||||
NavigationState,
|
NavigationState,
|
||||||
@@ -126,7 +126,7 @@ export default function TabRouter({
|
|||||||
|
|
||||||
const routes = routeNames.map((name) => ({
|
const routes = routeNames.map((name) => ({
|
||||||
name,
|
name,
|
||||||
key: `${name}-${shortid()}`,
|
key: `${name}-${nanoid()}`,
|
||||||
params: routeParamList[name],
|
params: routeParamList[name],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@@ -135,7 +135,7 @@ export default function TabRouter({
|
|||||||
return {
|
return {
|
||||||
stale: false,
|
stale: false,
|
||||||
type: 'tab',
|
type: 'tab',
|
||||||
key: `tab-${shortid()}`,
|
key: `tab-${nanoid()}`,
|
||||||
index,
|
index,
|
||||||
routeNames,
|
routeNames,
|
||||||
history,
|
history,
|
||||||
@@ -161,7 +161,7 @@ export default function TabRouter({
|
|||||||
key:
|
key:
|
||||||
route && route.name === name && route.key
|
route && route.name === name && route.key
|
||||||
? route.key
|
? route.key
|
||||||
: `${name}-${shortid()}`,
|
: `${name}-${nanoid()}`,
|
||||||
params:
|
params:
|
||||||
routeParamList[name] !== undefined
|
routeParamList[name] !== undefined
|
||||||
? {
|
? {
|
||||||
@@ -195,7 +195,7 @@ export default function TabRouter({
|
|||||||
return {
|
return {
|
||||||
stale: false,
|
stale: false,
|
||||||
type: 'tab',
|
type: 'tab',
|
||||||
key: `tab-${shortid()}`,
|
key: `tab-${nanoid()}`,
|
||||||
index,
|
index,
|
||||||
routeNames,
|
routeNames,
|
||||||
history,
|
history,
|
||||||
@@ -208,7 +208,7 @@ export default function TabRouter({
|
|||||||
(name) =>
|
(name) =>
|
||||||
state.routes.find((r) => r.name === name) || {
|
state.routes.find((r) => r.name === name) || {
|
||||||
name,
|
name,
|
||||||
key: `${name}-${shortid()}`,
|
key: `${name}-${nanoid()}`,
|
||||||
params: routeParamList[name],
|
params: routeParamList[name],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import BaseRouter from '../BaseRouter';
|
import BaseRouter from '../BaseRouter';
|
||||||
import * as CommonActions from '../CommonActions';
|
import * as CommonActions from '../CommonActions';
|
||||||
|
|
||||||
jest.mock('shortid', () => () => 'test');
|
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||||
|
|
||||||
const STATE = {
|
const STATE = {
|
||||||
stale: false as const,
|
stale: false as const,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
DrawerNavigationState,
|
DrawerNavigationState,
|
||||||
} from '..';
|
} from '..';
|
||||||
|
|
||||||
jest.mock('shortid', () => () => 'test');
|
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||||
|
|
||||||
it('gets initial state from route names and params with initialRouteName', () => {
|
it('gets initial state from route names and params with initialRouteName', () => {
|
||||||
const router = DrawerRouter({ initialRouteName: 'baz' });
|
const router = DrawerRouter({ initialRouteName: 'baz' });
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CommonActions, StackRouter, StackActions } from '..';
|
import { CommonActions, StackRouter, StackActions } from '..';
|
||||||
|
|
||||||
jest.mock('shortid', () => () => 'test');
|
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||||
|
|
||||||
it('gets initial state from route names and params with initialRouteName', () => {
|
it('gets initial state from route names and params with initialRouteName', () => {
|
||||||
const router = StackRouter({ initialRouteName: 'baz' });
|
const router = StackRouter({ initialRouteName: 'baz' });
|
||||||
@@ -720,7 +720,7 @@ it('replaces focused screen with replace', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('replaces source screen with replace', () => {
|
it('replaces active screen with replace', () => {
|
||||||
const router = StackRouter({});
|
const router = StackRouter({});
|
||||||
const options = {
|
const options = {
|
||||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||||
@@ -754,8 +754,8 @@ it('replaces source screen with replace', () => {
|
|||||||
index: 1,
|
index: 1,
|
||||||
routes: [
|
routes: [
|
||||||
{ key: 'foo', name: 'foo' },
|
{ key: 'foo', name: 'foo' },
|
||||||
{ key: 'bar', name: 'bar', params: { fruit: 'orange' } },
|
|
||||||
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
|
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
|
||||||
|
{ key: 'baz', name: 'baz' },
|
||||||
],
|
],
|
||||||
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
routeNames: ['foo', 'bar', 'baz', 'qux'],
|
||||||
});
|
});
|
||||||
@@ -785,6 +785,7 @@ it("doesn't handle replace if source key isn't present", () => {
|
|||||||
{
|
{
|
||||||
...StackActions.replace('qux', { answer: 42 }),
|
...StackActions.replace('qux', { answer: 42 }),
|
||||||
source: 'magic',
|
source: 'magic',
|
||||||
|
target: 'root',
|
||||||
},
|
},
|
||||||
options
|
options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { CommonActions, TabRouter, TabActions, TabNavigationState } from '..';
|
import { CommonActions, TabRouter, TabActions, TabNavigationState } from '..';
|
||||||
|
|
||||||
jest.mock('shortid', () => () => 'test');
|
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||||
|
|
||||||
it('gets initial state from route names and params with initialRouteName', () => {
|
it('gets initial state from route names and params with initialRouteName', () => {
|
||||||
const router = TabRouter({ initialRouteName: 'baz' });
|
const router = TabRouter({ initialRouteName: 'baz' });
|
||||||
|
|||||||
@@ -4,27 +4,27 @@ export { CommonActions };
|
|||||||
|
|
||||||
export { default as BaseRouter } from './BaseRouter';
|
export { default as BaseRouter } from './BaseRouter';
|
||||||
|
|
||||||
export {
|
export { default as StackRouter, StackActions } from './StackRouter';
|
||||||
default as StackRouter,
|
|
||||||
StackActions,
|
export type {
|
||||||
StackActionHelpers,
|
StackActionHelpers,
|
||||||
StackActionType,
|
StackActionType,
|
||||||
StackRouterOptions,
|
StackRouterOptions,
|
||||||
StackNavigationState,
|
StackNavigationState,
|
||||||
} from './StackRouter';
|
} from './StackRouter';
|
||||||
|
|
||||||
export {
|
export { default as TabRouter, TabActions } from './TabRouter';
|
||||||
default as TabRouter,
|
|
||||||
TabActions,
|
export type {
|
||||||
TabActionHelpers,
|
TabActionHelpers,
|
||||||
TabActionType,
|
TabActionType,
|
||||||
TabRouterOptions,
|
TabRouterOptions,
|
||||||
TabNavigationState,
|
TabNavigationState,
|
||||||
} from './TabRouter';
|
} from './TabRouter';
|
||||||
|
|
||||||
export {
|
export { default as DrawerRouter, DrawerActions } from './DrawerRouter';
|
||||||
default as DrawerRouter,
|
|
||||||
DrawerActions,
|
export type {
|
||||||
DrawerActionHelpers,
|
DrawerActionHelpers,
|
||||||
DrawerActionType,
|
DrawerActionType,
|
||||||
DrawerRouterOptions,
|
DrawerRouterOptions,
|
||||||
|
|||||||
@@ -3,6 +3,74 @@
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||||
|
|
||||||
|
## [5.2.12](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.11...@react-navigation/stack@5.2.12) (2020-04-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* animate card to existing closing state on gesture end ([78485ce](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/78485cea6939b9ffec76e0c4b410bc426ed93402)), closes [#7938](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7938)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.11](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.10...@react-navigation/stack@5.2.11) (2020-04-17)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* disable animation by default on web for stack ([dfdba8d](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/dfdba8d741abb4aa82235688d9f49e26305d2bca))
|
||||||
|
* hide inactive screens for stack on web ([#8010](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8010)) ([82edb25](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/82edb2581bab960f206fd67368a45ad384955c97))
|
||||||
|
* ios presentation modal cuts the topOffset on the bottom ([#7943](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7943)) ([6e51f59](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/6e51f596fa85796c2a3567222f51ff914c1f6c94)), closes [#7856](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7856)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.10](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.9...@react-navigation/stack@5.2.10) (2020-04-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* make color of shadow element same as card color in stack ([f1a8bce](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/f1a8bceba5b736e9f59862a8ae819342209a46f2))
|
||||||
|
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.8...@react-navigation/stack@5.2.9) (2020-03-30)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* dismiss keyboard on screen change for android ([8432e5a](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/8432e5ab25f041af8538ea7fb35e97cfcf1f983e))
|
||||||
|
* finish stack animation on CANCELLED event ([#7898](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7898)) ([d649fbc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/d649fbc6691871f0348076bce185d11a183c02cf)), closes [#7897](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7897)
|
||||||
|
* when comparing changed routes, only check keys ([9a8fea8](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/9a8fea8f2c1bdabfc5dd87e5c3ff4e7b97aef47d))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.6...@react-navigation/stack@5.2.7) (2020-03-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* add pointerEvents=box-none to overlay View ([#7871](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7871)) ([e097df8](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/e097df880adab984aae29f847003d2548cfbdce5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.5...@react-navigation/stack@5.2.6) (2020-03-23)
|
||||||
|
|
||||||
|
**Note:** Version bump only for package @react-navigation/stack
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.4...@react-navigation/stack@5.2.5) (2020-03-23)
|
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.4...@react-navigation/stack@5.2.5) (2020-03-23)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@react-navigation/stack",
|
"name": "@react-navigation/stack",
|
||||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||||
"version": "5.2.5",
|
"version": "5.2.12",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react-native-component",
|
"react-native-component",
|
||||||
"react-component",
|
"react-component",
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/bob": "^0.10.0",
|
"@react-native-community/bob": "^0.10.0",
|
||||||
"@react-native-community/masked-view": "^0.1.7",
|
"@react-native-community/masked-view": "^0.1.7",
|
||||||
"@react-navigation/native": "^5.1.2",
|
"@react-navigation/native": "^5.1.6",
|
||||||
"@types/color": "^3.0.1",
|
"@types/color": "^3.0.1",
|
||||||
"@types/react": "^16.9.23",
|
"@types/react": "^16.9.23",
|
||||||
"@types/react-native": "^0.61.22",
|
"@types/react-native": "^0.61.22",
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
"react-native-gesture-handler": "^1.6.0",
|
"react-native-gesture-handler": "^1.6.0",
|
||||||
"react-native-safe-area-context": "^0.7.3",
|
"react-native-safe-area-context": "^0.7.3",
|
||||||
"react-native-screens": "^2.3.0",
|
"react-native-screens": "^2.3.0",
|
||||||
"typescript": "^3.7.5"
|
"typescript": "^3.8.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@react-native-community/masked-view": ">= 0.1.0",
|
"@react-native-community/masked-view": ">= 0.1.0",
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ export function forModalPresentationIOS({
|
|||||||
borderTopLeftRadius: borderRadius,
|
borderTopLeftRadius: borderRadius,
|
||||||
borderTopRightRadius: borderRadius,
|
borderTopRightRadius: borderRadius,
|
||||||
marginTop: index === 0 ? 0 : statusBarHeight,
|
marginTop: index === 0 ? 0 : statusBarHeight,
|
||||||
|
marginBottom: index === 0 ? 0 : topOffset,
|
||||||
transform: [{ translateY }, { scale }],
|
transform: [{ translateY }, { scale }],
|
||||||
},
|
},
|
||||||
overlayStyle: { opacity: overlayOpacity },
|
overlayStyle: { opacity: overlayOpacity },
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
|
|||||||
/**
|
/**
|
||||||
* Types
|
* Types
|
||||||
*/
|
*/
|
||||||
export {
|
export type {
|
||||||
StackNavigationOptions,
|
StackNavigationOptions,
|
||||||
StackNavigationProp,
|
StackNavigationProp,
|
||||||
StackHeaderProps,
|
StackHeaderProps,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { Platform } from 'react-native';
|
||||||
import {
|
import {
|
||||||
useNavigationBuilder,
|
useNavigationBuilder,
|
||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
@@ -26,6 +27,11 @@ function StackNavigator({
|
|||||||
screenOptions,
|
screenOptions,
|
||||||
...rest
|
...rest
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const defaultOptions = {
|
||||||
|
gestureEnabled: Platform.OS === 'ios',
|
||||||
|
animationEnabled: Platform.OS !== 'web',
|
||||||
|
};
|
||||||
|
|
||||||
const { state, descriptors, navigation } = useNavigationBuilder<
|
const { state, descriptors, navigation } = useNavigationBuilder<
|
||||||
StackNavigationState,
|
StackNavigationState,
|
||||||
StackRouterOptions,
|
StackRouterOptions,
|
||||||
@@ -34,7 +40,16 @@ function StackNavigator({
|
|||||||
>(StackRouter, {
|
>(StackRouter, {
|
||||||
initialRouteName,
|
initialRouteName,
|
||||||
children,
|
children,
|
||||||
screenOptions,
|
screenOptions:
|
||||||
|
typeof screenOptions === 'function'
|
||||||
|
? (...args) => ({
|
||||||
|
...defaultOptions,
|
||||||
|
...screenOptions(...args),
|
||||||
|
})
|
||||||
|
: {
|
||||||
|
...defaultOptions,
|
||||||
|
...screenOptions,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
React.useEffect(
|
React.useEffect(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { TextInput } from 'react-native';
|
import { TextInput, Platform, Keyboard } from 'react-native';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -56,7 +56,9 @@ export default class KeyboardManager extends React.Component<Props> {
|
|||||||
|
|
||||||
const input = this.previouslyFocusedTextInput;
|
const input = this.previouslyFocusedTextInput;
|
||||||
|
|
||||||
if (input) {
|
if (Platform.OS === 'android') {
|
||||||
|
Keyboard.dismiss();
|
||||||
|
} else if (input) {
|
||||||
TextInput.State.blurTextInput(input);
|
TextInput.State.blurTextInput(input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,26 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { UIManager } from 'react-native';
|
import { UIManager } from 'react-native';
|
||||||
import RNCMaskedView from '@react-native-community/masked-view';
|
|
||||||
|
|
||||||
type Props = React.ComponentProps<typeof RNCMaskedView> & {
|
type MaskedViewType = typeof import('@react-native-community/masked-view').default;
|
||||||
|
|
||||||
|
type Props = React.ComponentProps<MaskedViewType> & {
|
||||||
children: React.ReactElement;
|
children: React.ReactElement;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let RNCMaskedView: MaskedViewType | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
RNCMaskedView = require('@react-native-community/masked-view').default;
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
const isMaskedViewAvailable =
|
const isMaskedViewAvailable =
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
UIManager.getViewManagerConfig('RNCMaskedView') != null;
|
UIManager.getViewManagerConfig('RNCMaskedView') != null;
|
||||||
|
|
||||||
export default function MaskedView({ children, ...rest }: Props) {
|
export default function MaskedView({ children, ...rest }: Props) {
|
||||||
if (isMaskedViewAvailable) {
|
if (isMaskedViewAvailable && RNCMaskedView) {
|
||||||
return <RNCMaskedView {...rest}>{children}</RNCMaskedView>;
|
return <RNCMaskedView {...rest}>{children}</RNCMaskedView>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
71
packages/stack/src/views/Screens.tsx
Normal file
71
packages/stack/src/views/Screens.tsx
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { Animated, View, Platform, ViewProps } from 'react-native';
|
||||||
|
|
||||||
|
let Screens: typeof import('react-native-screens') | undefined;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Screens = require('react-native-screens');
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
|
||||||
|
// The web implementation in react-native-screens seems buggy.
|
||||||
|
// The view doesn't become visible after coming back in some cases.
|
||||||
|
// So we use our custom implementation.
|
||||||
|
class WebScreen extends React.Component<
|
||||||
|
ViewProps & {
|
||||||
|
active: number;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
> {
|
||||||
|
render() {
|
||||||
|
const { active, style, ...rest } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
// @ts-ignore
|
||||||
|
hidden={!active}
|
||||||
|
style={[style, { display: active ? 'flex' : 'none' }]}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
|
||||||
|
|
||||||
|
export const MaybeScreenContainer = ({
|
||||||
|
enabled,
|
||||||
|
...rest
|
||||||
|
}: ViewProps & {
|
||||||
|
enabled: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
if (enabled && Platform.OS !== 'web' && Screens && Screens.screensEnabled()) {
|
||||||
|
return <Screens.ScreenContainer {...rest} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <View {...rest} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MaybeScreen = ({
|
||||||
|
enabled,
|
||||||
|
active,
|
||||||
|
...rest
|
||||||
|
}: ViewProps & {
|
||||||
|
enabled: boolean;
|
||||||
|
active: number | Animated.AnimatedInterpolation;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
if (enabled && Platform.OS === 'web') {
|
||||||
|
// @ts-ignore
|
||||||
|
return <AnimatedWebScreen active={active} {...rest} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (enabled && Screens && Screens.screensEnabled()) {
|
||||||
|
// @ts-ignore
|
||||||
|
return <Screens.Screen active={active} {...rest} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <View {...rest} />;
|
||||||
|
};
|
||||||
@@ -246,11 +246,21 @@ export default class Card extends React.Component<Props> {
|
|||||||
this.handleStartInteraction();
|
this.handleStartInteraction();
|
||||||
onGestureBegin?.();
|
onGestureBegin?.();
|
||||||
break;
|
break;
|
||||||
case GestureState.CANCELLED:
|
case GestureState.CANCELLED: {
|
||||||
this.isSwiping.setValue(FALSE);
|
this.isSwiping.setValue(FALSE);
|
||||||
this.handleEndInteraction();
|
this.handleEndInteraction();
|
||||||
|
|
||||||
|
const velocity =
|
||||||
|
gestureDirection === 'vertical' ||
|
||||||
|
gestureDirection === 'vertical-inverted'
|
||||||
|
? nativeEvent.velocityY
|
||||||
|
: nativeEvent.velocityX;
|
||||||
|
|
||||||
|
this.animate({ closing: this.props.closing, velocity });
|
||||||
|
|
||||||
onGestureCanceled?.();
|
onGestureCanceled?.();
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case GestureState.END: {
|
case GestureState.END: {
|
||||||
this.isSwiping.setValue(FALSE);
|
this.isSwiping.setValue(FALSE);
|
||||||
|
|
||||||
@@ -276,7 +286,7 @@ export default class Card extends React.Component<Props> {
|
|||||||
getInvertedMultiplier(gestureDirection) >
|
getInvertedMultiplier(gestureDirection) >
|
||||||
distance / 2
|
distance / 2
|
||||||
? velocity !== 0 || translation !== 0
|
? velocity !== 0 || translation !== 0
|
||||||
: false;
|
: this.props.closing;
|
||||||
|
|
||||||
this.animate({ closing, velocity });
|
this.animate({ closing, velocity });
|
||||||
|
|
||||||
@@ -480,7 +490,7 @@ export default class Card extends React.Component<Props> {
|
|||||||
<CardAnimationContext.Provider value={animationContext}>
|
<CardAnimationContext.Provider value={animationContext}>
|
||||||
<View pointerEvents="box-none" {...rest}>
|
<View pointerEvents="box-none" {...rest}>
|
||||||
{overlayEnabled ? (
|
{overlayEnabled ? (
|
||||||
<View style={StyleSheet.absoluteFill}>
|
<View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
|
||||||
{overlay({ style: overlayStyle })}
|
{overlay({ style: overlayStyle })}
|
||||||
</View>
|
</View>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -507,6 +517,7 @@ export default class Card extends React.Component<Props> {
|
|||||||
: gestureDirection === 'vertical'
|
: gestureDirection === 'vertical'
|
||||||
? [styles.shadowVertical, styles.shadowTop]
|
? [styles.shadowVertical, styles.shadowTop]
|
||||||
: [styles.shadowVertical, styles.shadowBottom],
|
: [styles.shadowVertical, styles.shadowBottom],
|
||||||
|
{ backgroundColor },
|
||||||
shadowStyle,
|
shadowStyle,
|
||||||
]}
|
]}
|
||||||
pointerEvents="none"
|
pointerEvents="none"
|
||||||
@@ -543,7 +554,6 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
shadow: {
|
shadow: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
backgroundColor: '#fff',
|
|
||||||
shadowRadius: 5,
|
shadowRadius: 5,
|
||||||
shadowColor: '#000',
|
shadowColor: '#000',
|
||||||
shadowOpacity: 0.3,
|
shadowOpacity: 0.3,
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
Animated,
|
Animated,
|
||||||
View,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
Platform,
|
Platform,
|
||||||
ViewProps,
|
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
|
||||||
import { ScreenContainer, Screen, screensEnabled } from 'react-native-screens'; // Import with * as to prevent getters being called
|
|
||||||
import { Route, StackNavigationState } from '@react-navigation/native';
|
import { Route, StackNavigationState } from '@react-navigation/native';
|
||||||
|
|
||||||
|
import { MaybeScreenContainer, MaybeScreen } from '../Screens';
|
||||||
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
|
||||||
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
|
||||||
import CardContainer from './CardContainer';
|
import CardContainer from './CardContainer';
|
||||||
@@ -75,37 +72,6 @@ type State = {
|
|||||||
|
|
||||||
const EPSILON = 0.01;
|
const EPSILON = 0.01;
|
||||||
|
|
||||||
const MaybeScreenContainer = ({
|
|
||||||
enabled,
|
|
||||||
...rest
|
|
||||||
}: ViewProps & {
|
|
||||||
enabled: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
if (enabled && screensEnabled()) {
|
|
||||||
return <ScreenContainer {...rest} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <View {...rest} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const MaybeScreen = ({
|
|
||||||
enabled,
|
|
||||||
active,
|
|
||||||
...rest
|
|
||||||
}: ViewProps & {
|
|
||||||
enabled: boolean;
|
|
||||||
active: number | Animated.AnimatedInterpolation;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) => {
|
|
||||||
if (enabled && screensEnabled()) {
|
|
||||||
// @ts-ignore
|
|
||||||
return <Screen active={active} {...rest} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <View {...rest} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
|
||||||
|
|
||||||
const getHeaderHeights = (
|
const getHeaderHeights = (
|
||||||
@@ -415,7 +381,7 @@ export default class CardStack extends React.Component<Props, State> {
|
|||||||
|
|
||||||
// Screens is buggy on iOS and web, so we only enable it on Android
|
// Screens is buggy on iOS and web, so we only enable it on Android
|
||||||
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
||||||
const isScreensEnabled = Platform.OS === 'android' && mode !== 'modal';
|
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
|
|||||||
@@ -46,31 +46,63 @@ type State = {
|
|||||||
|
|
||||||
const GestureHandlerWrapper = GestureHandlerRootView ?? View;
|
const GestureHandlerWrapper = GestureHandlerRootView ?? View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare two arrays with primitive values as the content.
|
||||||
|
* We need to make sure that both values and order match.
|
||||||
|
*/
|
||||||
|
const isArrayEqual = (a: any[], b: any[]) =>
|
||||||
|
a.length === b.length && a.every((it, index) => it === b[index]);
|
||||||
|
|
||||||
export default class StackView extends React.Component<Props, State> {
|
export default class StackView extends React.Component<Props, State> {
|
||||||
static getDerivedStateFromProps(
|
static getDerivedStateFromProps(
|
||||||
props: Readonly<Props>,
|
props: Readonly<Props>,
|
||||||
state: Readonly<State>
|
state: Readonly<State>
|
||||||
) {
|
) {
|
||||||
// If there was no change in routes, we don't need to compute anything
|
// If there was no change in routes, we don't need to compute anything
|
||||||
if (props.state.routes === state.previousRoutes && state.routes.length) {
|
if (
|
||||||
if (props.descriptors !== state.previousDescriptors) {
|
(props.state.routes === state.previousRoutes ||
|
||||||
const descriptors = state.routes.reduce<StackDescriptorMap>(
|
isArrayEqual(
|
||||||
(acc, route) => {
|
props.state.routes.map((r) => r.key),
|
||||||
acc[route.key] =
|
state.previousRoutes.map((r) => r.key)
|
||||||
props.descriptors[route.key] || state.descriptors[route.key];
|
)) &&
|
||||||
|
state.routes.length
|
||||||
|
) {
|
||||||
|
let routes = state.routes;
|
||||||
|
let previousRoutes = state.previousRoutes;
|
||||||
|
let descriptors = props.descriptors;
|
||||||
|
let previousDescriptors = state.previousDescriptors;
|
||||||
|
|
||||||
|
if (props.descriptors !== state.previousDescriptors) {
|
||||||
|
descriptors = state.routes.reduce<StackDescriptorMap>((acc, route) => {
|
||||||
|
acc[route.key] =
|
||||||
|
props.descriptors[route.key] || state.descriptors[route.key];
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
previousDescriptors = props.descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.state.routes !== state.previousRoutes) {
|
||||||
|
// if any route objects have changed, we should update them
|
||||||
|
const map = props.state.routes.reduce<Record<string, Route<string>>>(
|
||||||
|
(acc, route) => {
|
||||||
|
acc[route.key] = route;
|
||||||
return acc;
|
return acc;
|
||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
routes = state.routes.map((route) => map[route.key] || route);
|
||||||
previousDescriptors: props.descriptors,
|
previousRoutes = props.state.routes;
|
||||||
descriptors,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return {
|
||||||
|
routes,
|
||||||
|
previousRoutes,
|
||||||
|
descriptors,
|
||||||
|
previousDescriptors,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we determine which routes were added or removed to animate them
|
// Here we determine which routes were added or removed to animate them
|
||||||
@@ -115,7 +147,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
// We only need to animate routes if the focused route changed
|
// We only need to animate routes if the focused route changed
|
||||||
// Animating previous routes won't be visible coz the focused route is on top of everything
|
// Animating previous routes won't be visible coz the focused route is on top of everything
|
||||||
|
|
||||||
if (!previousRoutes.find((r) => r.key === nextFocusedRoute.key)) {
|
if (!previousRoutes.some((r) => r.key === nextFocusedRoute.key)) {
|
||||||
// A new route has come to the focus, we treat this as a push
|
// A new route has come to the focus, we treat this as a push
|
||||||
// A replace can also trigger this, the animation should look like push
|
// A replace can also trigger this, the animation should look like push
|
||||||
|
|
||||||
@@ -134,7 +166,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
(key) => key !== nextFocusedRoute.key
|
(key) => key !== nextFocusedRoute.key
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
|
if (!routes.some((r) => r.key === previousFocusedRoute.key)) {
|
||||||
// The previous focused route isn't present in state, we treat this as a replace
|
// The previous focused route isn't present in state, we treat this as a replace
|
||||||
|
|
||||||
openingRouteKeys = openingRouteKeys.filter(
|
openingRouteKeys = openingRouteKeys.filter(
|
||||||
@@ -174,7 +206,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
|
} else if (!routes.some((r) => r.key === previousFocusedRoute.key)) {
|
||||||
// The previously focused route was removed, we treat this as a pop
|
// The previously focused route was removed, we treat this as a pop
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -260,9 +292,7 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return gestureEnabled !== undefined
|
return gestureEnabled !== false;
|
||||||
? gestureEnabled
|
|
||||||
: Platform.OS !== 'android';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -298,24 +328,37 @@ export default class StackView extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
||||||
this.setState((state) => ({
|
const { state, navigation } = this.props;
|
||||||
routes: state.replacingRouteKeys.length
|
|
||||||
? state.routes.filter((r) => !state.replacingRouteKeys.includes(r.key))
|
if (
|
||||||
: state.routes,
|
state.routeNames.includes(route.name) &&
|
||||||
openingRouteKeys: state.openingRouteKeys.filter(
|
!state.routes.some((r) => r.key === route.key)
|
||||||
(key) => key !== route.key
|
) {
|
||||||
),
|
// If route isn't present in current state, assume that a close animation was cancelled
|
||||||
closingRouteKeys: state.closingRouteKeys.filter(
|
// So we need to add this route back to the state
|
||||||
(key) => key !== route.key
|
navigation.navigate(route);
|
||||||
),
|
} else {
|
||||||
replacingRouteKeys: [],
|
this.setState((state) => ({
|
||||||
}));
|
routes: state.replacingRouteKeys.length
|
||||||
|
? state.routes.filter(
|
||||||
|
(r) => !state.replacingRouteKeys.includes(r.key)
|
||||||
|
)
|
||||||
|
: state.routes,
|
||||||
|
openingRouteKeys: state.openingRouteKeys.filter(
|
||||||
|
(key) => key !== route.key
|
||||||
|
),
|
||||||
|
closingRouteKeys: state.closingRouteKeys.filter(
|
||||||
|
(key) => key !== route.key
|
||||||
|
),
|
||||||
|
replacingRouteKeys: [],
|
||||||
|
}));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private handleCloseRoute = ({ route }: { route: Route<string> }) => {
|
private handleCloseRoute = ({ route }: { route: Route<string> }) => {
|
||||||
const { state, navigation } = this.props;
|
const { state, navigation } = this.props;
|
||||||
|
|
||||||
if (state.routes.find((r) => r.key === route.key)) {
|
if (state.routes.some((r) => r.key === route.key)) {
|
||||||
// If a route exists in state, trigger a pop
|
// If a route exists in state, trigger a pop
|
||||||
// This will happen in when the route was closed from the card component
|
// This will happen in when the route was closed from the card component
|
||||||
// e.g. When the close animation triggered from a gesture ends
|
// e.g. When the close animation triggered from a gesture ends
|
||||||
|
|||||||
Reference in New Issue
Block a user