mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-01-13 09:30:30 +08:00
Compare commits
39 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9556aa9eff | ||
|
|
9a8fea8f2c | ||
|
|
9973db86f0 | ||
|
|
8432e5ab25 | ||
|
|
9bb5cfded3 | ||
|
|
4ac40b5c5d | ||
|
|
cd47915861 | ||
|
|
d649fbc669 | ||
|
|
105da6ab2f | ||
|
|
ac7f972e92 | ||
|
|
babb5027f9 | ||
|
|
78d7a66b2b | ||
|
|
a248c453ba | ||
|
|
e097df880a | ||
|
|
856449b200 | ||
|
|
d94e43c3c8 | ||
|
|
3096de6286 | ||
|
|
1c001424b5 | ||
|
|
0f2368965c | ||
|
|
61f16d3f25 | ||
|
|
853740bfaf | ||
|
|
179b6312fe | ||
|
|
043924ca48 | ||
|
|
813a5903b5 | ||
|
|
3709e652f4 | ||
|
|
5b15c7164f | ||
|
|
e030932497 | ||
|
|
adbfedcd58 | ||
|
|
bc9b044fb3 | ||
|
|
f24d3a3461 | ||
|
|
3df65e2819 | ||
|
|
5c4afc5cb4 | ||
|
|
d5bb357053 | ||
|
|
b1fe73097f | ||
|
|
49f6fed6d3 | ||
|
|
b1a65fc73e | ||
|
|
3ea8eec432 | ||
|
|
00e0f05190 | ||
|
|
193c344ba5 |
@@ -1,4 +1,4 @@
|
||||
module.exports = function(api) {
|
||||
module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable jest/no-jasmine-globals, import/no-commonjs */
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
const detox = require('detox');
|
||||
const config = require('../../package.json').detox;
|
||||
|
||||
@@ -15,8 +15,8 @@ const modules = ['@expo/vector-icons']
|
||||
// List all packages under `packages/`
|
||||
.readdirSync(packages)
|
||||
// Ignore hidden files such as .DS_Store
|
||||
.filter(p => !p.startsWith('.'))
|
||||
.map(p => {
|
||||
.filter((p) => !p.startsWith('.'))
|
||||
.map((p) => {
|
||||
const pak = JSON.parse(
|
||||
fs.readFileSync(path.join(packages, p, 'package.json'), 'utf8')
|
||||
);
|
||||
@@ -50,9 +50,9 @@ module.exports = {
|
||||
blacklistRE: blacklist(
|
||||
fs
|
||||
.readdirSync(packages)
|
||||
.map(p => path.join(packages, p))
|
||||
.map((p) => path.join(packages, p))
|
||||
.map(
|
||||
it => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
||||
(it) => new RegExp(`^${escape(path.join(it, 'node_modules'))}\\/.*$`)
|
||||
)
|
||||
),
|
||||
|
||||
@@ -65,7 +65,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
server: {
|
||||
enhanceMiddleware: middleware => {
|
||||
enhanceMiddleware: (middleware) => {
|
||||
return (req, res, next) => {
|
||||
// When an asset is imported outside the project root, it has wrong path on Android
|
||||
// This happens for the back button in stack, so we fix the path to correct one
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
"@types/react-native-restart": "^0.0.0",
|
||||
"color": "^3.1.2",
|
||||
"expo": "^36.0.2",
|
||||
"expo-asset": "~8.0.0",
|
||||
"expo-blur": "^8.0.0",
|
||||
"expo-asset": "~8.1.1",
|
||||
"expo-blur": "~8.1.0",
|
||||
"react": "~16.9.0",
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
@@ -28,15 +28,15 @@
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"react-native-tab-view": "2.13.0",
|
||||
"react-native-unimodules": "^0.7.0",
|
||||
"react-native-unimodules": "^0.8.1",
|
||||
"react-native-web": "^0.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@expo/webpack-config": "^0.11.7",
|
||||
"@expo/webpack-config": "^0.11.9",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"babel-preset-expo": "^8.0.0",
|
||||
"expo-cli": "^3.13.8",
|
||||
"typescript": "^3.7.5"
|
||||
"babel-preset-expo": "^8.1.0",
|
||||
"expo-cli": "^3.15.5",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function BottomTabsScreen() {
|
||||
return (
|
||||
<BottomTabs.Navigator
|
||||
screenOptions={{
|
||||
tabBarButton: props => <TouchableBounce {...props} />,
|
||||
tabBarButton: (props) => <TouchableBounce {...props} />,
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
@@ -38,7 +38,7 @@ export default function BottomTabsScreen() {
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
}}
|
||||
>
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</BottomTabs.Screen>
|
||||
<BottomTabs.Screen
|
||||
name="Chat"
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function BottomTabsScreen() {
|
||||
|
||||
return (
|
||||
<BottomTabs.Navigator>
|
||||
{tabs.map(i => (
|
||||
{tabs.map((i) => (
|
||||
<BottomTabs.Screen
|
||||
key={i}
|
||||
name={`tab-${i}`}
|
||||
@@ -29,12 +29,14 @@ export default function BottomTabsScreen() {
|
||||
{() => (
|
||||
<View style={styles.container}>
|
||||
<Title>Tab {i}</Title>
|
||||
<Button onPress={() => setTabs(tabs => [...tabs, tabs.length])}>
|
||||
<Button onPress={() => setTabs((tabs) => [...tabs, tabs.length])}>
|
||||
Add a tab
|
||||
</Button>
|
||||
<Button
|
||||
onPress={() =>
|
||||
setTabs(tabs => (tabs.length > 1 ? tabs.slice(0, -1) : tabs))
|
||||
setTabs((tabs) =>
|
||||
tabs.length > 1 ? tabs.slice(0, -1) : tabs
|
||||
)
|
||||
}
|
||||
>
|
||||
Remove a tab
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
tabBarColor: '#C9E7F8',
|
||||
}}
|
||||
>
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
{(props) => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</MaterialBottomTabs.Screen>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="Chat"
|
||||
|
||||
@@ -68,10 +68,7 @@ export default function Chat(props: Partial<ScrollViewProps>) {
|
||||
styles.input,
|
||||
{ backgroundColor: colors.card, color: colors.text },
|
||||
]}
|
||||
placeholderTextColor={Color(colors.text)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string()}
|
||||
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||
placeholder="Write a message"
|
||||
underlineColorAndroid="transparent"
|
||||
/>
|
||||
|
||||
@@ -51,10 +51,7 @@ export default function NewsFeed(props: Props) {
|
||||
<Card style={styles.card}>
|
||||
<TextInput
|
||||
placeholder="What's on your mind?"
|
||||
placeholderTextColor={Color(colors.text)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string()}
|
||||
placeholderTextColor={Color(colors.text).alpha(0.5).rgb().string()}
|
||||
style={styles.input}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
@@ -224,7 +224,7 @@ export default function App() {
|
||||
<NavigationContainer
|
||||
ref={containerRef}
|
||||
initialState={initialState}
|
||||
onStateChange={state =>
|
||||
onStateChange={(state) =>
|
||||
AsyncStorage.setItem(
|
||||
NAVIGATION_PERSISTENCE_KEY,
|
||||
JSON.stringify(state)
|
||||
@@ -298,12 +298,12 @@ export default function App() {
|
||||
theme.dark ? 'light' : 'dark'
|
||||
);
|
||||
|
||||
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
|
||||
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
(name) => (
|
||||
<List.Item
|
||||
key={name}
|
||||
title={SCREENS[name].title}
|
||||
@@ -315,7 +315,7 @@ export default function App() {
|
||||
)}
|
||||
</Stack.Screen>
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
(name) => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
name={name}
|
||||
|
||||
@@ -7,7 +7,7 @@ const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
const node_modules = path.resolve(__dirname, '..', 'node_modules');
|
||||
const packages = path.resolve(__dirname, '..', 'packages');
|
||||
|
||||
module.exports = async function(env, argv) {
|
||||
module.exports = async function (env, argv) {
|
||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||
|
||||
config.context = path.resolve(__dirname, '..');
|
||||
@@ -20,7 +20,7 @@ module.exports = async function(env, argv) {
|
||||
});
|
||||
|
||||
config.resolve.plugins = config.resolve.plugins.filter(
|
||||
p => !(p instanceof ModuleScopePlugin)
|
||||
(p) => !(p instanceof ModuleScopePlugin)
|
||||
);
|
||||
|
||||
Object.assign(config.resolve.alias, {
|
||||
@@ -30,7 +30,7 @@ module.exports = async function(env, argv) {
|
||||
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
||||
});
|
||||
|
||||
fs.readdirSync(packages).forEach(name => {
|
||||
fs.readdirSync(packages).forEach((name) => {
|
||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||
packages,
|
||||
name,
|
||||
|
||||
22
package.json
22
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/)",
|
||||
"scripts": {
|
||||
"lint": "eslint --ext '.js,.ts,.tsx' .",
|
||||
"typescript": "tsc --noEmit",
|
||||
"typescript": "tsc --noEmit --composite false",
|
||||
"test": "jest",
|
||||
"prerelease": "lerna run clean",
|
||||
"release": "lerna publish",
|
||||
@@ -26,25 +26,25 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.8.3",
|
||||
"@babel/preset-env": "^7.8.7",
|
||||
"@babel/preset-flow": "^7.8.3",
|
||||
"@babel/preset-react": "^7.8.3",
|
||||
"@babel/preset-typescript": "^7.8.3",
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
|
||||
"@babel/preset-env": "^7.9.0",
|
||||
"@babel/preset-flow": "^7.9.0",
|
||||
"@babel/preset-react": "^7.9.4",
|
||||
"@babel/preset-typescript": "^7.9.0",
|
||||
"@babel/runtime": "^7.9.2",
|
||||
"@commitlint/config-conventional": "^8.3.4",
|
||||
"@types/jest": "^25.1.4",
|
||||
"babel-jest": "^25.2.1",
|
||||
"codecov": "^3.6.5",
|
||||
"commitlint": "^8.3.5",
|
||||
"core-js": "^3.6.4",
|
||||
"detox": "^16.0.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-config-satya164": "^3.1.5",
|
||||
"eslint-config-satya164": "^3.1.6",
|
||||
"husky": "^4.2.3",
|
||||
"jest": "^25.1.0",
|
||||
"lerna": "^3.20.2",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.7.5"
|
||||
"prettier": "^2.0.2",
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "~16.9.0",
|
||||
|
||||
@@ -3,6 +3,42 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.1...@react-navigation/bottom-tabs@5.2.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7485)
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.0...@react-navigation/bottom-tabs@5.2.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/bottom-tabs",
|
||||
"description": "Bottom tab navigator following iOS design guidelines",
|
||||
"version": "5.2.1",
|
||||
"version": "5.2.5",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -35,7 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
@@ -44,7 +44,7 @@
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
|
||||
@@ -51,7 +51,12 @@ export default function BottomTabBar({
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
|
||||
const [dimensions, setDimensions] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return { height, width };
|
||||
});
|
||||
|
||||
const [layout, setLayout] = React.useState({
|
||||
height: 0,
|
||||
width: dimensions.width,
|
||||
@@ -116,7 +121,7 @@ export default function BottomTabBar({
|
||||
const handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
|
||||
setLayout(layout => {
|
||||
setLayout((layout) => {
|
||||
if (height === layout.height && width === layout.width) {
|
||||
return layout;
|
||||
} else {
|
||||
|
||||
@@ -133,9 +133,7 @@ export default function BottomTabBarItem({
|
||||
|
||||
const inactiveTintColor =
|
||||
customInactiveTintColor === undefined
|
||||
? Color(colors.text)
|
||||
.mix(Color(colors.card), 0.5)
|
||||
.hex()
|
||||
? Color(colors.text).mix(Color(colors.card), 0.5).hex()
|
||||
: customInactiveTintColor;
|
||||
|
||||
const renderLabel = ({ focused }: { focused: boolean }) => {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Platform, StyleSheet, View } from 'react-native';
|
||||
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import { Screen, screensEnabled } from 'react-native-screens';
|
||||
|
||||
@@ -10,12 +9,14 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -30,7 +30,7 @@ type Props = {
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => {
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.1.3",
|
||||
"version": "5.1.7",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
|
||||
"bugs": {
|
||||
@@ -26,10 +26,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/react": "^16.9.23",
|
||||
"react": "~16.9.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function createCompatNavigatorFactory<
|
||||
function Navigator({ screenProps }: { screenProps?: unknown }) {
|
||||
const screens = React.useMemo(
|
||||
() =>
|
||||
routeNames.map(name => {
|
||||
routeNames.map((name) => {
|
||||
let getScreenComponent: () => CompatScreenType<NavigationPropType>;
|
||||
|
||||
let initialParams;
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function useCompatNavigation<
|
||||
const route = useRoute();
|
||||
|
||||
const isFirstRouteInParent = useNavigationState(
|
||||
state => state.routes[0].key === route.key
|
||||
(state) => state.routes[0].key === route.key
|
||||
);
|
||||
|
||||
const context = React.useRef<Record<string, any>>({});
|
||||
|
||||
@@ -26,8 +26,9 @@ export default function withNavigation<
|
||||
return <Comp ref={onRef} navigation={navigation} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigation(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
WrappedComponent.displayName = `withNavigation(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,9 @@ export default function withNavigationFocus<
|
||||
return <Comp ref={onRef} isFocused={isFocused} {...rest} />;
|
||||
};
|
||||
|
||||
WrappedComponent.displayName = `withNavigationFocus(${Comp.displayName ||
|
||||
Comp.name})`;
|
||||
WrappedComponent.displayName = `withNavigationFocus(${
|
||||
Comp.displayName || Comp.name
|
||||
})`;
|
||||
|
||||
return WrappedComponent;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,53 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* return correct value for isFocused after changing screens ([5b15c71](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5b15c7164f5503f2f0d51006a3f23bd0c58fd9b7)), closes [#7843](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7843)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* support function in listeners prop ([3709e65](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3709e652f41a16c2c2b05d5dbbe1da2017ba2c3f))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.2...@react-navigation/core@5.2.3) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.1...@react-navigation/core@5.2.2) (2020-03-16)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/core",
|
||||
"description": "Core utilities for building navigators",
|
||||
"version": "5.2.2",
|
||||
"version": "5.3.2",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -29,24 +29,23 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.1.1",
|
||||
"@react-navigation/routers": "^5.2.1",
|
||||
"escape-string-regexp": "^2.0.0",
|
||||
"nanoid": "^3.0.2",
|
||||
"query-string": "^6.11.1",
|
||||
"react-is": "^16.13.0",
|
||||
"shortid": "^2.2.15",
|
||||
"use-subscription": "^1.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-is": "^16.7.1",
|
||||
"@types/shortid": "^0.0.29",
|
||||
"@types/use-subscription": "^1.0.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"react": "~16.9.0",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"react-test-renderer": "~16.9.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
|
||||
@@ -73,7 +73,7 @@ const getPartialState = (
|
||||
return {
|
||||
...partialState,
|
||||
stale: true,
|
||||
routes: state.routes.map(route => {
|
||||
routes: state.routes.map((route) => {
|
||||
if (route.state === undefined) {
|
||||
return route as Route<string> & {
|
||||
state?: PartialState<NavigationState>;
|
||||
@@ -136,6 +136,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
);
|
||||
|
||||
const { trackState, trackAction } = useDevTools({
|
||||
enabled: false,
|
||||
name: '@react-navigation',
|
||||
reset,
|
||||
state,
|
||||
@@ -155,7 +156,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
throw new Error(NOT_INITIALIZED_ERROR);
|
||||
}
|
||||
|
||||
listeners[0](navigation => navigation.dispatch(action));
|
||||
listeners[0]((navigation) => navigation.dispatch(action));
|
||||
};
|
||||
|
||||
const canGoBack = () => {
|
||||
@@ -163,7 +164,7 @@ const BaseNavigationContainer = React.forwardRef(
|
||||
return false;
|
||||
}
|
||||
|
||||
const { result, handled } = listeners[0](navigation =>
|
||||
const { result, handled } = listeners[0]((navigation) =>
|
||||
navigation.canGoBack()
|
||||
);
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ export default function SceneView<
|
||||
|
||||
const getCurrentState = React.useCallback(() => {
|
||||
const state = getState();
|
||||
const currentRoute = state.routes.find(r => r.key === route.key);
|
||||
const currentRoute = state.routes.find((r) => r.key === route.key);
|
||||
|
||||
return currentRoute ? currentRoute.state : undefined;
|
||||
}, [getState, route.key]);
|
||||
@@ -62,7 +62,7 @@ export default function SceneView<
|
||||
|
||||
setState({
|
||||
...state,
|
||||
routes: state.routes.map(r =>
|
||||
routes: state.routes.map((r) =>
|
||||
r.key === route.key ? { ...r, state: child } : r
|
||||
),
|
||||
});
|
||||
|
||||
@@ -122,7 +122,7 @@ it('handle dispatching with ref', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -220,7 +220,7 @@ it('handle resetting state with ref', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -371,7 +371,7 @@ it('emits state events when the state changes', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
key: String(MockRouterKey.current++),
|
||||
index,
|
||||
routeNames,
|
||||
routes: routeNames.map(name => ({
|
||||
routes: routeNames.map((name) => ({
|
||||
name,
|
||||
key: name,
|
||||
params: routeParamList[name],
|
||||
@@ -43,9 +43,9 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
}
|
||||
|
||||
const routes = state.routes
|
||||
.filter(route => routeNames.includes(route.name))
|
||||
.filter((route) => routeNames.includes(route.name))
|
||||
.map(
|
||||
route =>
|
||||
(route) =>
|
||||
({
|
||||
...route,
|
||||
key: route.key || `${route.name}-${MockRouterKey.current++}`,
|
||||
@@ -73,7 +73,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames }) {
|
||||
const routes = state.routes.filter(route =>
|
||||
const routes = state.routes.filter((route) =>
|
||||
routeNames.includes(route.name)
|
||||
);
|
||||
|
||||
@@ -86,7 +86,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteFocus(state, key) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
const index = state.routes.findIndex((r) => r.key === key);
|
||||
|
||||
if (index === -1 || index === state.index) {
|
||||
return state;
|
||||
@@ -105,7 +105,7 @@ export default function MockRouter(options: DefaultRouterOptions) {
|
||||
|
||||
case 'NAVIGATE': {
|
||||
const index = state.routes.findIndex(
|
||||
route => route.name === action.payload.name
|
||||
(route) => route.name === action.payload.name
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
|
||||
@@ -42,7 +42,8 @@ it('converts state to path string with config', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
id: (id: string) => Number(id.replace(/^x/, '')),
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -128,7 +129,8 @@ it('handles state with config with nested screens', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -192,12 +194,14 @@ it('handles state with config with nested screens and unused configs', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
stringify: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
unknown: (_: unknown) => 'x',
|
||||
},
|
||||
},
|
||||
@@ -255,11 +259,11 @@ it('handles nested object with stringify in it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -517,3 +521,209 @@ it('returns "/" for empty path', () => {
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
@@ -42,7 +42,8 @@ it('converts path string to initial state with config', () => {
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -153,7 +154,8 @@ it('converts path string to initial state with config with nested screens', () =
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -217,7 +219,8 @@ it('converts path string to initial state with config with nested screens and un
|
||||
Baz: {
|
||||
path: 'baz/:author',
|
||||
parse: {
|
||||
author: (author: string) => author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
id: Boolean,
|
||||
@@ -277,11 +280,11 @@ it('handles nested object with unused configs and with parse in it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -528,11 +531,11 @@ it('handles two initialRouteNames', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
@@ -610,11 +613,11 @@ it('accepts initialRouteName without config for it', () => {
|
||||
path: 'bis/:author',
|
||||
stringify: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toLowerCase()),
|
||||
author.replace(/^\w/, (c) => c.toLowerCase()),
|
||||
},
|
||||
parse: {
|
||||
author: (author: string) =>
|
||||
author.replace(/^\w/, c => c.toUpperCase()),
|
||||
author.replace(/^\w/, (c) => c.toUpperCase()),
|
||||
count: Number,
|
||||
valid: Boolean,
|
||||
},
|
||||
|
||||
@@ -119,7 +119,7 @@ it('sets options with screenOptions prop as an object', () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
{state.routes.map((route) => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
@@ -179,7 +179,7 @@ it('sets options with screenOptions prop as a fuction', () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{state.routes.map(route => {
|
||||
{state.routes.map((route) => {
|
||||
const { render, options } = descriptors[route.key];
|
||||
|
||||
return (
|
||||
@@ -273,7 +273,7 @@ it('sets initial options with setOptions', () => {
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
{(props) => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
@@ -338,7 +338,7 @@ it('updates options with setOptions', () => {
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="foo" options={{ color: 'blue' }}>
|
||||
{props => <TestScreen {...props} />}
|
||||
{(props) => <TestScreen {...props} />}
|
||||
</Screen>
|
||||
<Screen name="bar" component={jest.fn()} />
|
||||
</TestNavigator>
|
||||
|
||||
@@ -15,7 +15,7 @@ it('fires focus and blur events in root navigator', () => {
|
||||
|
||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
@@ -106,7 +106,7 @@ it('fires focus and blur events in nested navigator', () => {
|
||||
|
||||
React.useImperativeHandle(ref, () => navigation, [navigation]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstFocusCallback = jest.fn();
|
||||
@@ -376,7 +376,7 @@ it('fires custom events added with addListener', () => {
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const firstCallback = jest.fn();
|
||||
@@ -456,7 +456,7 @@ it("doesn't call same listener multiple times with addListener", () => {
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const callback = jest.fn();
|
||||
@@ -565,12 +565,10 @@ it('fires custom events added with listeners prop', () => {
|
||||
});
|
||||
|
||||
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(secondCallback).toBeCalledTimes(1);
|
||||
expect(thirdCallback).toBeCalledTimes(2);
|
||||
expect(secondCallback).toBeCalledTimes(0);
|
||||
expect(thirdCallback).toBeCalledTimes(1);
|
||||
});
|
||||
|
||||
it("doesn't call same listener multiple times with listeners", () => {
|
||||
@@ -624,6 +622,91 @@ it("doesn't call same listener multiple times with listeners", () => {
|
||||
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', () => {
|
||||
expect.assertions(5);
|
||||
|
||||
@@ -640,7 +723,7 @@ it('has option to prevent default', () => {
|
||||
state,
|
||||
]);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
});
|
||||
|
||||
const callback = (e: any) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ it('runs focus effect on focus change', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const focusEffect = jest.fn();
|
||||
@@ -107,7 +107,7 @@ it('runs focus effect when initial state is given', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const focusEffect = jest.fn();
|
||||
|
||||
@@ -10,7 +10,7 @@ it('renders correct focus state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
|
||||
@@ -12,7 +12,7 @@ it('gets navigation prop from context', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
@@ -38,7 +38,7 @@ it("gets navigation's parent from context", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
@@ -70,7 +70,7 @@ it("gets navigation's parent's parent from context", () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { render } from 'react-native-testing-library';
|
||||
import { render, act } from 'react-native-testing-library';
|
||||
import useEventEmitter from '../useEventEmitter';
|
||||
import useNavigationCache from '../useNavigationCache';
|
||||
import useNavigationBuilder from '../useNavigationBuilder';
|
||||
import BaseNavigationContainer from '../BaseNavigationContainer';
|
||||
import Screen from '../Screen';
|
||||
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
|
||||
|
||||
beforeEach(() => (MockRouterKey.current = 0));
|
||||
@@ -40,7 +43,7 @@ it('preserves reference for navigation objects', () => {
|
||||
});
|
||||
|
||||
if (previous.current) {
|
||||
Object.keys(navigations).forEach(key => {
|
||||
Object.keys(navigations).forEach((key) => {
|
||||
expect(navigations[key]).toBe(previous.current[key]);
|
||||
});
|
||||
}
|
||||
@@ -56,3 +59,136 @@ it('preserves reference for navigation objects', () => {
|
||||
|
||||
root.update(<Test />);
|
||||
});
|
||||
|
||||
it('returns correct value for isFocused', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
let navigation: any;
|
||||
|
||||
const Test = (props: any) => {
|
||||
navigation = props.navigation;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
act(() => navigation.navigate('second'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
act(() => navigation.navigate('third'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
act(() => navigation.navigate('second'));
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
});
|
||||
|
||||
it('returns correct value for isFocused after changing screens', () => {
|
||||
const TestRouter = (
|
||||
options: Parameters<typeof MockRouter>[0]
|
||||
): ReturnType<typeof MockRouter> => {
|
||||
const router = MockRouter(options);
|
||||
|
||||
return {
|
||||
...router,
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames }) {
|
||||
const routes = routeNames.map(
|
||||
(name) =>
|
||||
state.routes.find((r) => r.name === name) || {
|
||||
name,
|
||||
key: name,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
routeNames,
|
||||
routes,
|
||||
index: routes.length - 1,
|
||||
};
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
|
||||
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
let navigation: any;
|
||||
|
||||
const Test = (props: any) => {
|
||||
navigation = props.navigation;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const root = render(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="fourth">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(true);
|
||||
|
||||
root.update(
|
||||
<BaseNavigationContainer>
|
||||
<TestNavigator>
|
||||
<Screen name="first">{() => null}</Screen>
|
||||
<Screen name="third">{() => null}</Screen>
|
||||
<Screen name="second" component={Test} />
|
||||
<Screen name="fourth">{() => null}</Screen>
|
||||
</TestNavigator>
|
||||
</BaseNavigationContainer>
|
||||
);
|
||||
|
||||
expect(navigation.isFocused()).toBe(false);
|
||||
});
|
||||
|
||||
@@ -11,13 +11,13 @@ it('gets the current navigation state', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = () => {
|
||||
const state = useNavigationState(state => state);
|
||||
const state = useNavigationState((state) => state);
|
||||
|
||||
callback(state);
|
||||
|
||||
@@ -62,13 +62,13 @@ it('gets the current navigation state with selector', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
|
||||
const Test = () => {
|
||||
const index = useNavigationState(state => state.index);
|
||||
const index = useNavigationState((state) => state.index);
|
||||
|
||||
callback(index);
|
||||
|
||||
@@ -112,7 +112,7 @@ it('gets the correct value if selector changes', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const callback = jest.fn();
|
||||
@@ -144,12 +144,12 @@ it('gets the correct value if selector changes', () => {
|
||||
);
|
||||
};
|
||||
|
||||
const root = render(<App selector={state => state.index} />);
|
||||
const root = render(<App selector={(state) => state.index} />);
|
||||
|
||||
expect(callback).toBeCalledTimes(1);
|
||||
expect(callback.mock.calls[0][0]).toBe(0);
|
||||
|
||||
root.update(<App selector={state => state.routes[state.index].name} />);
|
||||
root.update(<App selector={(state) => state.routes[state.index].name} />);
|
||||
|
||||
expect(callback).toBeCalledTimes(2);
|
||||
expect(callback.mock.calls[1][0]).toBe('first');
|
||||
|
||||
@@ -137,7 +137,7 @@ it("lets children handle the action if parent didn't", () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -270,7 +270,7 @@ it("action doesn't bubble if target is specified", () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
@@ -317,7 +317,7 @@ it('logs error if no navigator handled the action', () => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{state.routes.map(route => descriptors[route.key].render())}
|
||||
{state.routes.map((route) => descriptors[route.key].render())}
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ it('gets route prop from context', () => {
|
||||
const TestNavigator = (props: any): any => {
|
||||
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
|
||||
|
||||
return state.routes.map(route => descriptors[route.key].render());
|
||||
return state.routes.map((route) => descriptors[route.key].render());
|
||||
};
|
||||
|
||||
const Test = () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ export default function createNavigatorFactory<
|
||||
EventMap extends EventMapBase,
|
||||
NavigatorComponent extends React.ComponentType<any>
|
||||
>(Navigator: NavigatorComponent) {
|
||||
return function<ParamList extends ParamListBase>(): TypedNavigator<
|
||||
return function <ParamList extends ParamListBase>(): TypedNavigator<
|
||||
ParamList,
|
||||
State,
|
||||
ScreenOptions,
|
||||
|
||||
@@ -64,6 +64,8 @@ export default function getPathFromState(
|
||||
};
|
||||
let currentOptions = options;
|
||||
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) {
|
||||
if (typeof currentOptions[route.name] === 'string') {
|
||||
@@ -77,11 +79,13 @@ export default function getPathFromState(
|
||||
}).screens
|
||||
) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
break;
|
||||
} else {
|
||||
// if it is the end of state, we return pattern
|
||||
if (route.state === undefined) {
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
break;
|
||||
} else {
|
||||
index =
|
||||
@@ -92,11 +96,13 @@ export default function getPathFromState(
|
||||
}).screens;
|
||||
// if there is config for next route name, we go deeper
|
||||
if (nextRoute.name in deeperConfig) {
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
route = nextRoute as Route<string> & { state?: State };
|
||||
currentOptions = deeperConfig;
|
||||
} else {
|
||||
// if not, there is no sense in going deeper in config
|
||||
pattern = (currentOptions[route.name] as { path: string }).path;
|
||||
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
|
||||
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
|
||||
if (pattern !== '') {
|
||||
const config =
|
||||
@@ -125,7 +136,7 @@ export default function getPathFromState(
|
||||
if (currentOptions[route.name] !== undefined) {
|
||||
path += pattern
|
||||
.split('/')
|
||||
.map(p => {
|
||||
.map((p) => {
|
||||
const name = p.replace(/^:/, '');
|
||||
|
||||
// If the path has a pattern for a param, put the param in the path
|
||||
@@ -147,6 +158,12 @@ export default function getPathFromState(
|
||||
if (route.state) {
|
||||
path += '/';
|
||||
} 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);
|
||||
|
||||
if (query) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export default function getStateFromPath(
|
||||
let initialRoutes: InitialRouteConfig[] = [];
|
||||
// Create a normalized configs array which will be easier to use
|
||||
const configs = ([] as RouteConfig[]).concat(
|
||||
...Object.keys(options).map(key =>
|
||||
...Object.keys(options).map((key) =>
|
||||
createNormalizedConfigs(key, options, [], initialRoutes)
|
||||
)
|
||||
);
|
||||
@@ -91,7 +91,7 @@ export default function getStateFromPath(
|
||||
|
||||
const paramPatterns = config.pattern
|
||||
.split('/')
|
||||
.filter(p => p.startsWith(':'));
|
||||
.filter((p) => p.startsWith(':'));
|
||||
|
||||
if (paramPatterns.length) {
|
||||
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
|
||||
@@ -188,7 +188,7 @@ export default function getStateFromPath(
|
||||
const parseFunction = findParseConfigForRoute(route.name, configs);
|
||||
|
||||
if (parseFunction) {
|
||||
Object.keys(params).forEach(name => {
|
||||
Object.keys(params).forEach((name) => {
|
||||
if (parseFunction[name] && typeof params[name] === 'string') {
|
||||
params[name] = parseFunction[name](params[name] as string);
|
||||
}
|
||||
@@ -233,7 +233,7 @@ function createNormalizedConfigs(
|
||||
connectedRoutes: Object.keys(value.screens),
|
||||
});
|
||||
}
|
||||
Object.keys(value.screens).forEach(nestedConfig => {
|
||||
Object.keys(value.screens).forEach((nestedConfig) => {
|
||||
const result = createNormalizedConfigs(
|
||||
nestedConfig,
|
||||
value.screens as Options,
|
||||
|
||||
@@ -346,6 +346,16 @@ export type Descriptor<
|
||||
>;
|
||||
};
|
||||
|
||||
export type ScreenListeners<
|
||||
State extends NavigationState,
|
||||
EventMap extends EventMapBase
|
||||
> = Partial<
|
||||
{
|
||||
[EventName in keyof (EventMap &
|
||||
EventMapCore<State>)]: EventListenerCallback<EventMap, EventName>;
|
||||
}
|
||||
>;
|
||||
|
||||
export type RouteConfig<
|
||||
ParamList extends ParamListBase,
|
||||
RouteName extends keyof ParamList,
|
||||
@@ -371,12 +381,12 @@ export type RouteConfig<
|
||||
/**
|
||||
* Event listeners for this screen.
|
||||
*/
|
||||
listeners?: Partial<
|
||||
{
|
||||
[EventName in keyof (EventMap &
|
||||
EventMapCore<State>)]: EventListenerCallback<EventMap, EventName>;
|
||||
}
|
||||
>;
|
||||
listeners?:
|
||||
| ScreenListeners<State, EventMap>
|
||||
| ((props: {
|
||||
route: RouteProp<ParamList, RouteName>;
|
||||
navigation: any;
|
||||
}) => ScreenListeners<State, EventMap>);
|
||||
|
||||
/**
|
||||
* Initial params object for the route.
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
type State = NavigationState | PartialState<NavigationState> | undefined;
|
||||
|
||||
type Options = {
|
||||
enabled: boolean;
|
||||
name: string;
|
||||
reset: (state: NavigationState) => void;
|
||||
state: State;
|
||||
@@ -35,10 +36,11 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
export default function useDevTools({ name, reset, state }: Options) {
|
||||
export default function useDevTools({ name, reset, state, enabled }: Options) {
|
||||
const devToolsRef = React.useRef<DevTools>();
|
||||
|
||||
if (
|
||||
enabled &&
|
||||
process.env.NODE_ENV !== 'production' &&
|
||||
global.__REDUX_DEVTOOLS_EXTENSION__ &&
|
||||
devToolsRef.current === undefined
|
||||
@@ -56,7 +58,7 @@ export default function useDevTools({ name, reset, state }: Options) {
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
devTools?.subscribe(message => {
|
||||
devTools?.subscribe((message) => {
|
||||
if (message.type === 'DISPATCH' && message.state) {
|
||||
reset(JSON.parse(message.state));
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function useEventEmitter(
|
||||
target !== undefined
|
||||
? items[target] && items[target].slice()
|
||||
: ([] as Listeners)
|
||||
.concat(...Object.keys(items).map(t => items[t]))
|
||||
.concat(...Object.keys(items).map((t) => items[t]))
|
||||
.filter((cb, i, self) => self.lastIndexOf(cb) === i);
|
||||
|
||||
const event: EventArg<any, any, any> = {
|
||||
@@ -117,7 +117,7 @@ export default function useEventEmitter(
|
||||
|
||||
listenRef.current?.(event);
|
||||
|
||||
callbacks?.forEach(cb => cb(event));
|
||||
callbacks?.forEach((cb) => cb(event));
|
||||
|
||||
return event as any;
|
||||
},
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
RouterFactory,
|
||||
PartialState,
|
||||
NavigationAction,
|
||||
Route,
|
||||
} from '@react-navigation/routers';
|
||||
import { NavigationStateContext } from './BaseNavigationContainer';
|
||||
import NavigationRouteContext from './NavigationRouteContext';
|
||||
@@ -103,7 +104,7 @@ const getRouteConfigsFromChildren = <
|
||||
}, []);
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
configs.forEach(config => {
|
||||
configs.forEach((config) => {
|
||||
const { name, children, component } = config as any;
|
||||
|
||||
if (typeof name !== 'string' || !name) {
|
||||
@@ -212,7 +213,7 @@ export default function useNavigationBuilder<
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const routeNames = routeConfigs.map(config => config.name);
|
||||
const routeNames = routeConfigs.map((config) => config.name);
|
||||
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
@@ -241,12 +242,12 @@ export default function useNavigationBuilder<
|
||||
}
|
||||
|
||||
const isStateValid = React.useCallback(
|
||||
state => state.type === undefined || state.type === router.type,
|
||||
(state) => state.type === undefined || state.type === router.type,
|
||||
[router.type]
|
||||
);
|
||||
|
||||
const isStateInitialized = React.useCallback(
|
||||
state =>
|
||||
(state) =>
|
||||
state !== undefined && state.stale === false && isStateValid(state),
|
||||
[isStateValid]
|
||||
);
|
||||
@@ -367,34 +368,49 @@ export default function useNavigationBuilder<
|
||||
: (initializedStateRef.current as State);
|
||||
}, [getCurrentState, isStateInitialized]);
|
||||
|
||||
const emitter = useEventEmitter(e => {
|
||||
const emitter = useEventEmitter((e) => {
|
||||
let routeNames = [];
|
||||
|
||||
if (e.target) {
|
||||
const name = state.routes.find(route => route.key === e.target)?.name;
|
||||
let route: Route<string> | undefined;
|
||||
|
||||
if (name) {
|
||||
routeNames.push(name);
|
||||
if (e.target) {
|
||||
route = state.routes.find((route) => route.key === e.target);
|
||||
|
||||
if (route?.name) {
|
||||
routeNames.push(route.name);
|
||||
}
|
||||
} 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)[])
|
||||
.concat(
|
||||
...routeNames.map(name => {
|
||||
...routeNames.map((name) => {
|
||||
const { listeners } = screens[name];
|
||||
const map =
|
||||
typeof listeners === 'function'
|
||||
? listeners({ route: route as any, navigation })
|
||||
: listeners;
|
||||
|
||||
return listeners
|
||||
? Object.keys(listeners)
|
||||
.filter(type => type === e.type)
|
||||
.map(type => listeners[type])
|
||||
return map
|
||||
? Object.keys(map)
|
||||
.filter((type) => type === e.type)
|
||||
.map((type) => map?.[type])
|
||||
: undefined;
|
||||
})
|
||||
)
|
||||
.filter((cb, i, self) => cb && self.lastIndexOf(cb) === i);
|
||||
|
||||
listeners.forEach(listener => listener?.(e));
|
||||
listeners.forEach((listener) => listener?.(e));
|
||||
});
|
||||
|
||||
useFocusEvents({ state, emitter });
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function useNavigationCache<
|
||||
};
|
||||
|
||||
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(
|
||||
(acc, route, index) => {
|
||||
(acc, route) => {
|
||||
const previous = cache.current[route.key];
|
||||
|
||||
if (previous) {
|
||||
@@ -103,14 +103,14 @@ export default function useNavigationCache<
|
||||
dangerouslyGetState: getState,
|
||||
dispatch,
|
||||
setOptions: (options: object) =>
|
||||
setOptions(o => ({
|
||||
setOptions((o) => ({
|
||||
...o,
|
||||
[route.key]: { ...o[route.key], ...options },
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
|
||||
if (index !== state.index) {
|
||||
if (state.routes[state.index].key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export default function useNavigationState<T>(selector: Selector<T>): T {
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = navigation.addListener('state', e => {
|
||||
const unsubscribe = navigation.addListener('state', (e) => {
|
||||
setResult(selectorRef.current(e.data.state));
|
||||
});
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ export default function useOnGetState({
|
||||
const state = getState();
|
||||
return {
|
||||
...state,
|
||||
routes: state.routes.map(route => ({
|
||||
routes: state.routes.map((route) => ({
|
||||
...route,
|
||||
state: getStateForRoute(route.key),
|
||||
})),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import shortid from 'shortid';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
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.
|
||||
*/
|
||||
export default function useRegisterNavigator() {
|
||||
const [key] = React.useState(() => shortid());
|
||||
const [key] = React.useState(() => nanoid());
|
||||
const container = React.useContext(SingleNavigatorContext);
|
||||
|
||||
if (container === undefined) {
|
||||
|
||||
@@ -3,6 +3,60 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.1...@react-navigation/drawer@5.3.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* close drawer on pressing Esc on web ([5c4afc5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/5c4afc5cb40c1206a9d8c40efe3cf947030da48e)), closes [#6745](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6745)
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7485)
|
||||
* fix permanent sidebar position ([#7830](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7830)) ([3ea8eec](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3ea8eec4324ea82f0ed427f4662e68e1115e60ab))
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6789)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.0...@react-navigation/drawer@5.3.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.2.0...@react-navigation/drawer@5.3.0) (2020-03-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/drawer",
|
||||
"description": "Drawer navigator component with animated transitions and gesturess",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,7 +40,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
|
||||
@@ -111,9 +111,18 @@ export type DrawerNavigationOptions = {
|
||||
|
||||
/**
|
||||
* 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`
|
||||
*/
|
||||
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.
|
||||
* Defaults to `false`.
|
||||
|
||||
@@ -1,44 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { useNavigation, ParamListBase } from '@react-navigation/native';
|
||||
import { DrawerNavigationProp } from '../types';
|
||||
import DrawerOpenContext from '../views/DrawerOpenContext';
|
||||
import DrawerOpenContext from './DrawerOpenContext';
|
||||
|
||||
/**
|
||||
* Hook to detect if the drawer is open in a parent navigator.
|
||||
*/
|
||||
export default function useIsDrawerOpen() {
|
||||
const navigation = useNavigation();
|
||||
const isDrawerOpen = React.useContext(DrawerOpenContext);
|
||||
|
||||
let drawer = navigation as DrawerNavigationProp<ParamListBase>;
|
||||
|
||||
const drawerOpenContext = React.useContext(DrawerOpenContext);
|
||||
|
||||
// The screen might be inside another navigator such as stack nested in drawer
|
||||
// We need to find the closest drawer navigator and add the listener there
|
||||
while (drawer && drawer.dangerouslyGetState().type !== 'drawer') {
|
||||
drawer = drawer.dangerouslyGetParent();
|
||||
}
|
||||
|
||||
const [isDrawerOpen, setIsDrawerOpen] = React.useState(() =>
|
||||
drawer
|
||||
? Boolean(
|
||||
drawer.dangerouslyGetState().history.find(it => it.type === 'drawer')
|
||||
)
|
||||
: false
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const unsubscribe = drawer.addListener('state', e => {
|
||||
setIsDrawerOpen(
|
||||
Boolean(e.data.state.history.find(it => it.type === 'drawer'))
|
||||
);
|
||||
});
|
||||
|
||||
return unsubscribe;
|
||||
}, [drawer, isDrawerOpen]);
|
||||
|
||||
if (drawerOpenContext !== null) {
|
||||
return drawerOpenContext;
|
||||
if (typeof isDrawerOpen !== 'boolean') {
|
||||
throw new Error(
|
||||
"Couldn't find a drawer. Is your component inside a drawer navigator?"
|
||||
);
|
||||
}
|
||||
|
||||
return isDrawerOpen;
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from 'react-native-gesture-handler';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import Overlay from './Overlay';
|
||||
import DrawerOpenContext from './DrawerOpenContext';
|
||||
|
||||
const {
|
||||
Clock,
|
||||
@@ -81,6 +80,7 @@ type Props = {
|
||||
onClose: () => void;
|
||||
onGestureRef?: (ref: PanGestureHandler | null) => void;
|
||||
gestureEnabled: boolean;
|
||||
swipeEnabled: boolean;
|
||||
drawerPosition: 'left' | 'right';
|
||||
drawerType: 'front' | 'back' | 'slide' | 'permanent';
|
||||
keyboardDismissMode: 'none' | 'on-drag';
|
||||
@@ -101,7 +101,7 @@ type Props = {
|
||||
* Disables the pan gesture by default on Apple devices in the browser.
|
||||
* https://stackoverflow.com/a/9039885
|
||||
*/
|
||||
function shouldEnableGesture(): boolean {
|
||||
function shouldEnableSwipeGesture(): boolean {
|
||||
if (
|
||||
Platform.OS === 'web' &&
|
||||
typeof navigator !== 'undefined' &&
|
||||
@@ -116,11 +116,12 @@ function shouldEnableGesture(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
export default class DrawerView extends React.PureComponent<Props> {
|
||||
export default class DrawerView extends React.Component<Props> {
|
||||
static defaultProps = {
|
||||
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
|
||||
drawerType: 'front',
|
||||
gestureEnabled: shouldEnableGesture(),
|
||||
gestureEnabled: true,
|
||||
swipeEnabled: shouldEnableSwipeGesture(),
|
||||
swipeEdgeWidth: 32,
|
||||
swipeVelocityThreshold: 500,
|
||||
keyboardDismissMode: 'on-drag',
|
||||
@@ -128,21 +129,22 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
statusBarAnimation: 'slide',
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.addEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: Props) {
|
||||
const {
|
||||
open,
|
||||
drawerPosition,
|
||||
drawerType,
|
||||
gestureEnabled,
|
||||
swipeDistanceThreshold,
|
||||
swipeVelocityThreshold,
|
||||
hideStatusBar,
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.gestureEnabled !== gestureEnabled) {
|
||||
this.isGestureEnabled.setValue(gestureEnabled ? TRUE : FALSE);
|
||||
}
|
||||
|
||||
if (
|
||||
// If we're not in the middle of a transition, sync the drawer's open state
|
||||
typeof this.pendingOpenValue !== 'boolean' ||
|
||||
@@ -183,8 +185,22 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
componentWillUnmount() {
|
||||
this.toggleStatusBar(false);
|
||||
this.handleEndInteraction();
|
||||
|
||||
if (Platform.OS === 'web') {
|
||||
document?.body?.removeEventListener?.('keyup', this.handleEscape);
|
||||
}
|
||||
}
|
||||
|
||||
private handleEscape = (e: KeyboardEvent) => {
|
||||
const { open, onClose } = this.props;
|
||||
|
||||
if (e.key === 'Escape') {
|
||||
if (open) {
|
||||
onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleEndInteraction = () => {
|
||||
if (this.interactionHandle !== undefined) {
|
||||
InteractionManager.clearInteractionHandle(this.interactionHandle);
|
||||
@@ -204,9 +220,6 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
private isDrawerTypeFront = new Value<Binary>(
|
||||
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 nextIsOpen = new Value<Binary | -1>(UNSET);
|
||||
@@ -535,6 +548,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
const {
|
||||
open,
|
||||
gestureEnabled,
|
||||
swipeEnabled,
|
||||
drawerPosition,
|
||||
drawerType,
|
||||
swipeEdgeWidth,
|
||||
@@ -586,15 +600,16 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
onGestureEvent={this.handleGestureEvent}
|
||||
onHandlerStateChange={this.handleGestureStateChange}
|
||||
hitSlop={hitSlop}
|
||||
enabled={drawerType !== 'permanent' && gestureEnabled}
|
||||
enabled={drawerType !== 'permanent' && gestureEnabled && swipeEnabled}
|
||||
{...gestureHandlerProps}
|
||||
>
|
||||
<Animated.View
|
||||
onLayout={this.handleContainerLayout}
|
||||
style={[
|
||||
styles.main,
|
||||
drawerType === 'permanent' && {
|
||||
flexDirection: 'row-reverse',
|
||||
{
|
||||
flexDirection:
|
||||
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
|
||||
},
|
||||
]}
|
||||
>
|
||||
@@ -661,9 +676,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
drawerStyle as any,
|
||||
]}
|
||||
>
|
||||
<DrawerOpenContext.Provider value={isOpen}>
|
||||
{renderDrawerContent({ progress })}
|
||||
</DrawerOpenContext.Provider>
|
||||
{renderDrawerContent({ progress })}
|
||||
</Animated.View>
|
||||
</Animated.View>
|
||||
</PanGestureHandler>
|
||||
|
||||
@@ -72,14 +72,8 @@ export default function DrawerItem(props: Props) {
|
||||
labelStyle,
|
||||
focused = false,
|
||||
activeTintColor = colors.primary,
|
||||
inactiveTintColor = Color(colors.text)
|
||||
.alpha(0.68)
|
||||
.rgb()
|
||||
.string(),
|
||||
activeBackgroundColor = Color(activeTintColor)
|
||||
.alpha(0.12)
|
||||
.rgb()
|
||||
.string(),
|
||||
inactiveTintColor = Color(colors.text).alpha(0.68).rgb().string(),
|
||||
activeBackgroundColor = Color(activeTintColor).alpha(0.12).rgb().string(),
|
||||
inactiveBackgroundColor = 'transparent',
|
||||
style,
|
||||
onPress,
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
DrawerNavigationHelpers,
|
||||
DrawerContentComponentProps,
|
||||
} from '../types';
|
||||
import DrawerOpenContext from '../utils/DrawerOpenContext';
|
||||
import DrawerPositionContext from '../utils/DrawerPositionContext';
|
||||
|
||||
type Props = DrawerNavigationConfig & {
|
||||
@@ -88,15 +89,17 @@ export default function DrawerView({
|
||||
sceneContainerStyle,
|
||||
}: Props) {
|
||||
const [loaded, setLoaded] = React.useState([state.index]);
|
||||
const [drawerWidth, setDrawerWidth] = React.useState(() =>
|
||||
getDefaultDrawerWidth(Dimensions.get('window'))
|
||||
);
|
||||
const [drawerWidth, setDrawerWidth] = React.useState(() => {
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
return getDefaultDrawerWidth({ height, width });
|
||||
});
|
||||
|
||||
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
|
||||
|
||||
const { colors } = useTheme();
|
||||
|
||||
const isDrawerOpen = Boolean(state.history.find(it => it.type === 'drawer'));
|
||||
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
|
||||
|
||||
const handleDrawerOpen = React.useCallback(() => {
|
||||
navigation.dispatch({
|
||||
@@ -197,46 +200,55 @@ export default function DrawerView({
|
||||
};
|
||||
|
||||
const activeKey = state.routes[state.index].key;
|
||||
const { gestureEnabled } = descriptors[activeKey].options;
|
||||
const { gestureEnabled, swipeEnabled } = descriptors[activeKey].options;
|
||||
|
||||
return (
|
||||
<GestureHandlerWrapper style={styles.content}>
|
||||
<SafeAreaProviderCompat>
|
||||
<DrawerGestureContext.Provider value={drawerGestureRef}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
gestureEnabled={gestureEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={ref => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
drawerStyle={[
|
||||
{ width: drawerWidth, backgroundColor: colors.card },
|
||||
drawerType === 'permanent' && {
|
||||
borderRightColor: colors.border,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
},
|
||||
drawerStyle,
|
||||
]}
|
||||
overlayStyle={{ backgroundColor: overlayColor }}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={renderNavigationView}
|
||||
renderSceneContent={renderContent}
|
||||
keyboardDismissMode={keyboardDismissMode}
|
||||
drawerPostion={drawerPosition}
|
||||
/>
|
||||
<DrawerOpenContext.Provider value={isDrawerOpen}>
|
||||
<Drawer
|
||||
open={isDrawerOpen}
|
||||
gestureEnabled={gestureEnabled}
|
||||
swipeEnabled={swipeEnabled}
|
||||
onOpen={handleDrawerOpen}
|
||||
onClose={handleDrawerClose}
|
||||
onGestureRef={(ref) => {
|
||||
// @ts-ignore
|
||||
drawerGestureRef.current = ref;
|
||||
}}
|
||||
gestureHandlerProps={gestureHandlerProps}
|
||||
drawerType={drawerType}
|
||||
drawerPosition={drawerPosition}
|
||||
sceneContainerStyle={[
|
||||
{ backgroundColor: colors.background },
|
||||
sceneContainerStyle,
|
||||
]}
|
||||
drawerStyle={[
|
||||
{ width: drawerWidth, backgroundColor: colors.card },
|
||||
drawerType === 'permanent' &&
|
||||
(drawerPosition === 'left'
|
||||
? {
|
||||
borderRightColor: colors.border,
|
||||
borderRightWidth: StyleSheet.hairlineWidth,
|
||||
}
|
||||
: {
|
||||
borderLeftColor: colors.border,
|
||||
borderLeftWidth: StyleSheet.hairlineWidth,
|
||||
}),
|
||||
drawerStyle,
|
||||
]}
|
||||
overlayStyle={{ backgroundColor: overlayColor }}
|
||||
swipeEdgeWidth={edgeWidth}
|
||||
swipeDistanceThreshold={minSwipeDistance}
|
||||
hideStatusBar={hideStatusBar}
|
||||
statusBarAnimation={statusBarAnimation}
|
||||
renderDrawerContent={renderNavigationView}
|
||||
renderSceneContent={renderContent}
|
||||
keyboardDismissMode={keyboardDismissMode}
|
||||
drawerPostion={drawerPosition}
|
||||
/>
|
||||
</DrawerOpenContext.Provider>
|
||||
</DrawerGestureContext.Provider>
|
||||
</SafeAreaProviderCompat>
|
||||
</GestureHandlerWrapper>
|
||||
|
||||
@@ -9,21 +9,29 @@ type Props = {
|
||||
style?: any;
|
||||
};
|
||||
|
||||
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
|
||||
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled?.()) {
|
||||
// react-native-screens is buggy on web
|
||||
if (screensEnabled?.() && Platform.OS !== 'web') {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
}
|
||||
|
||||
const { isVisible, children, style, ...rest } = this.props;
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style]}
|
||||
style={[
|
||||
styles.container,
|
||||
Platform.OS === 'web'
|
||||
? { display: isVisible ? 'flex' : 'none' }
|
||||
: null,
|
||||
style,
|
||||
]}
|
||||
collapsable={false}
|
||||
removeClippedSubviews={
|
||||
// On iOS, set removeClippedSubviews to true only when not focused
|
||||
|
||||
@@ -30,7 +30,7 @@ type Props = {
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => {
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.3...@react-navigation/material-bottom-tabs@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.2...@react-navigation/material-bottom-tabs@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-bottom-tabs",
|
||||
"description": "Integration for bottom navigation component from react-native-paper",
|
||||
"version": "5.1.3",
|
||||
"version": "5.1.7",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"@types/react-native-vector-icons": "^6.4.5",
|
||||
@@ -45,7 +45,7 @@
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-paper": "^3.6.0",
|
||||
"react-native-vector-icons": "^6.6.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
|
||||
@@ -3,6 +3,38 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.3...@react-navigation/material-top-tabs@5.1.4) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.2...@react-navigation/material-top-tabs@5.1.3) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/material-top-tabs",
|
||||
"description": "Integration for the animated tab view component from react-native-tab-view",
|
||||
"version": "5.1.3",
|
||||
"version": "5.1.7",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -39,7 +39,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
"del-cli": "^3.0.0",
|
||||
@@ -48,7 +48,7 @@
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-reanimated": "^1.7.0",
|
||||
"react-native-tab-view": "^2.13.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-navigation/native": "^5.0.5",
|
||||
|
||||
@@ -14,17 +14,11 @@ export default function TabBarTop(props: MaterialTopTabBarProps) {
|
||||
navigation,
|
||||
descriptors,
|
||||
activeTintColor = colors.text,
|
||||
inactiveTintColor = Color(activeTintColor)
|
||||
.alpha(0.5)
|
||||
.rgb()
|
||||
.string(),
|
||||
inactiveTintColor = Color(activeTintColor).alpha(0.5).rgb().string(),
|
||||
allowFontScaling = true,
|
||||
showIcon = false,
|
||||
showLabel = true,
|
||||
pressColor = Color(activeTintColor)
|
||||
.alpha(0.08)
|
||||
.rgb()
|
||||
.string(),
|
||||
pressColor = Color(activeTintColor).alpha(0.08).rgb().string(),
|
||||
iconStyle,
|
||||
labelStyle,
|
||||
indicatorStyle,
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function MaterialTopTabView({
|
||||
return (
|
||||
<TabView
|
||||
{...rest}
|
||||
onIndexChange={index =>
|
||||
onIndexChange={(index) =>
|
||||
navigation.dispatch({
|
||||
...TabActions.jumpTo(state.routes[index].name),
|
||||
target: state.key,
|
||||
|
||||
@@ -3,6 +3,41 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.0...@react-navigation/native@5.1.1) (2020-03-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.10...@react-navigation/native@5.1.0) (2020-03-17)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/native",
|
||||
"description": "React Native integration for React Navigation",
|
||||
"version": "5.1.0",
|
||||
"version": "5.1.4",
|
||||
"keywords": [
|
||||
"react-native",
|
||||
"react-navigation",
|
||||
@@ -31,7 +31,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.2.2"
|
||||
"@react-navigation/core": "^5.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
@@ -41,7 +41,7 @@
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-testing-library": "^1.12.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function() {
|
||||
export default function () {
|
||||
throw new Error(
|
||||
"'NavigationNativeContainer' has been renamed to 'NavigationContainer"
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import { Linking, Platform } from 'react-native';
|
||||
import {
|
||||
getActionFromState,
|
||||
getStateFromPath as getStateFromPathDefault,
|
||||
@@ -20,7 +20,10 @@ export default function useLinking(
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
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 {
|
||||
isUsingLinking = true;
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function useLinking(
|
||||
React.useEffect(() => {
|
||||
if (isUsingLinking) {
|
||||
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 {
|
||||
isUsingLinking = true;
|
||||
|
||||
@@ -3,6 +3,25 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add keys to routes missing keys during reset ([813a590](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/813a5903b5f44506b9097538ed85229e565b855e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.0...@react-navigation/routers@5.1.1) (2020-03-16)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/routers",
|
||||
"description": "Routers to help build custom navigators",
|
||||
"version": "5.1.1",
|
||||
"version": "5.2.1",
|
||||
"keywords": [
|
||||
"react",
|
||||
"react-native",
|
||||
@@ -30,12 +30,12 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"shortid": "^2.2.15"
|
||||
"nanoid": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"del-cli": "^3.0.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import { CommonNavigationAction, NavigationState, PartialState } from './types';
|
||||
|
||||
/**
|
||||
@@ -12,7 +13,7 @@ const BaseRouter = {
|
||||
switch (action.type) {
|
||||
case 'SET_PARAMS': {
|
||||
const index = action.source
|
||||
? state.routes.findIndex(r => r.key === action.source)
|
||||
? state.routes.findIndex((r) => r.key === action.source)
|
||||
: state.index;
|
||||
|
||||
if (index === -1) {
|
||||
@@ -32,15 +33,6 @@ const BaseRouter = {
|
||||
case 'RESET': {
|
||||
const nextState = action.payload as State | PartialState<State>;
|
||||
|
||||
if (nextState.stale === false) {
|
||||
if (
|
||||
state.routeNames.length !== nextState.routeNames.length ||
|
||||
nextState.routeNames.some(name => !state.routeNames.includes(name))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
nextState.routes.length === 0 ||
|
||||
nextState.routes.some(
|
||||
@@ -50,6 +42,24 @@ const BaseRouter = {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (nextState.stale === false) {
|
||||
if (
|
||||
state.routeNames.length !== nextState.routeNames.length ||
|
||||
nextState.routeNames.some(
|
||||
(name) => !state.routeNames.includes(name)
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...nextState,
|
||||
routes: nextState.routes.map((route) =>
|
||||
route.key ? route : { ...route, key: `${route.name}-${nanoid()}` }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
return nextState;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import shortid from 'shortid';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import {
|
||||
PartialState,
|
||||
CommonNavigationAction,
|
||||
@@ -71,7 +71,7 @@ export const DrawerActions = {
|
||||
|
||||
const isDrawerOpen = (
|
||||
state: DrawerNavigationState | PartialState<DrawerNavigationState>
|
||||
) => Boolean(state.history?.find(it => it.type === 'drawer'));
|
||||
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
|
||||
|
||||
const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
if (isDrawerOpen(state)) {
|
||||
@@ -91,7 +91,7 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
history: state.history.filter(it => it.type !== 'drawer'),
|
||||
history: state.history.filter((it) => it.type !== 'drawer'),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -115,7 +115,7 @@ export default function DrawerRouter(
|
||||
...state,
|
||||
stale: false,
|
||||
type: 'drawer',
|
||||
key: `drawer-${shortid()}`,
|
||||
key: `drawer-${nanoid()}`,
|
||||
};
|
||||
},
|
||||
|
||||
@@ -136,7 +136,7 @@ export default function DrawerRouter(
|
||||
return {
|
||||
...state,
|
||||
type: 'drawer',
|
||||
key: `drawer-${shortid()}`,
|
||||
key: `drawer-${nanoid()}`,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import shortid from 'shortid';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import BaseRouter from './BaseRouter';
|
||||
import {
|
||||
NavigationState,
|
||||
@@ -113,12 +113,12 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
return {
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: `stack-${shortid()}`,
|
||||
key: `stack-${nanoid()}`,
|
||||
index: 0,
|
||||
routeNames,
|
||||
routes: [
|
||||
{
|
||||
key: `${initialRouteName}-${shortid()}`,
|
||||
key: `${initialRouteName}-${nanoid()}`,
|
||||
name: initialRouteName,
|
||||
params: routeParamList[initialRouteName],
|
||||
},
|
||||
@@ -134,12 +134,12 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
}
|
||||
|
||||
const routes = state.routes
|
||||
.filter(route => routeNames.includes(route.name))
|
||||
.filter((route) => routeNames.includes(route.name))
|
||||
.map(
|
||||
route =>
|
||||
(route) =>
|
||||
({
|
||||
...route,
|
||||
key: route.key || `${route.name}-${shortid()}`,
|
||||
key: route.key || `${route.name}-${nanoid()}`,
|
||||
params:
|
||||
routeParamList[route.name] !== undefined
|
||||
? {
|
||||
@@ -157,7 +157,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
: routeNames[0];
|
||||
|
||||
routes.push({
|
||||
key: `${initialRouteName}-${shortid()}`,
|
||||
key: `${initialRouteName}-${nanoid()}`,
|
||||
name: initialRouteName,
|
||||
params: routeParamList[initialRouteName],
|
||||
});
|
||||
@@ -166,7 +166,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
return {
|
||||
stale: false,
|
||||
type: 'stack',
|
||||
key: `stack-${shortid()}`,
|
||||
key: `stack-${nanoid()}`,
|
||||
index: routes.length - 1,
|
||||
routeNames,
|
||||
routes,
|
||||
@@ -174,7 +174,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames, routeParamList }) {
|
||||
const routes = state.routes.filter(route =>
|
||||
const routes = state.routes.filter((route) =>
|
||||
routeNames.includes(route.name)
|
||||
);
|
||||
|
||||
@@ -186,7 +186,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
: routeNames[0];
|
||||
|
||||
routes.push({
|
||||
key: `${initialRouteName}-${shortid()}`,
|
||||
key: `${initialRouteName}-${nanoid()}`,
|
||||
name: initialRouteName,
|
||||
params: routeParamList[initialRouteName],
|
||||
});
|
||||
@@ -201,7 +201,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
},
|
||||
|
||||
getStateForRouteFocus(state, key) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
const index = state.routes.findIndex((r) => r.key === key);
|
||||
|
||||
if (index === -1 || index === state.index) {
|
||||
return state;
|
||||
@@ -220,7 +220,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
switch (action.type) {
|
||||
case 'REPLACE': {
|
||||
const index = action.source
|
||||
? state.routes.findIndex(r => r.key === action.source)
|
||||
? state.routes.findIndex((r) => r.key === action.source)
|
||||
: state.index;
|
||||
|
||||
if (index === -1) {
|
||||
@@ -238,7 +238,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
routes: state.routes.map((route, i) =>
|
||||
i === index
|
||||
? {
|
||||
key: key !== undefined ? key : `${name}-${shortid()}`,
|
||||
key: key !== undefined ? key : `${name}-${nanoid()}`,
|
||||
name,
|
||||
params:
|
||||
routeParamList[name] !== undefined
|
||||
@@ -263,7 +263,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
{
|
||||
key:
|
||||
action.payload.key === undefined
|
||||
? `${action.payload.name}-${shortid()}`
|
||||
? `${action.payload.name}-${nanoid()}`
|
||||
: action.payload.key,
|
||||
name: action.payload.name,
|
||||
params:
|
||||
@@ -283,7 +283,7 @@ export default function StackRouter(options: StackRouterOptions) {
|
||||
case 'POP': {
|
||||
const index =
|
||||
action.target === state.key && action.source
|
||||
? state.routes.findIndex(r => r.key === action.source)
|
||||
? state.routes.findIndex((r) => r.key === action.source)
|
||||
: state.index;
|
||||
|
||||
if (index > 0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import shortid from 'shortid';
|
||||
import { nanoid } from 'nanoid/non-secure';
|
||||
import BaseRouter from './BaseRouter';
|
||||
import {
|
||||
NavigationState,
|
||||
@@ -93,7 +93,7 @@ const changeIndex = (
|
||||
const currentKey = state.routes[index].key;
|
||||
|
||||
history = state.history
|
||||
.filter(it => (it.type === 'route' ? it.key !== currentKey : false))
|
||||
.filter((it) => (it.type === 'route' ? it.key !== currentKey : false))
|
||||
.concat({ type: TYPE_ROUTE, key: currentKey });
|
||||
} else {
|
||||
history = getRouteHistory(state.routes, index, backBehavior);
|
||||
@@ -124,9 +124,9 @@ export default function TabRouter({
|
||||
? routeNames.indexOf(initialRouteName)
|
||||
: 0;
|
||||
|
||||
const routes = routeNames.map(name => ({
|
||||
const routes = routeNames.map((name) => ({
|
||||
name,
|
||||
key: `${name}-${shortid()}`,
|
||||
key: `${name}-${nanoid()}`,
|
||||
params: routeParamList[name],
|
||||
}));
|
||||
|
||||
@@ -135,7 +135,7 @@ export default function TabRouter({
|
||||
return {
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
key: `tab-${shortid()}`,
|
||||
key: `tab-${nanoid()}`,
|
||||
index,
|
||||
routeNames,
|
||||
history,
|
||||
@@ -150,9 +150,9 @@ export default function TabRouter({
|
||||
return state;
|
||||
}
|
||||
|
||||
const routes = routeNames.map(name => {
|
||||
const routes = routeNames.map((name) => {
|
||||
const route = (state as PartialState<TabNavigationState>).routes.find(
|
||||
r => r.name === name
|
||||
(r) => r.name === name
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -161,7 +161,7 @@ export default function TabRouter({
|
||||
key:
|
||||
route && route.name === name && route.key
|
||||
? route.key
|
||||
: `${name}-${shortid()}`,
|
||||
: `${name}-${nanoid()}`,
|
||||
params:
|
||||
routeParamList[name] !== undefined
|
||||
? {
|
||||
@@ -184,8 +184,8 @@ export default function TabRouter({
|
||||
routes.length - 1
|
||||
);
|
||||
|
||||
let history = state.history?.filter(it =>
|
||||
routes.find(r => r.key === it.key)
|
||||
let history = state.history?.filter((it) =>
|
||||
routes.find((r) => r.key === it.key)
|
||||
);
|
||||
|
||||
if (!history?.length) {
|
||||
@@ -195,7 +195,7 @@ export default function TabRouter({
|
||||
return {
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
key: `tab-${shortid()}`,
|
||||
key: `tab-${nanoid()}`,
|
||||
index,
|
||||
routeNames,
|
||||
history,
|
||||
@@ -205,10 +205,10 @@ export default function TabRouter({
|
||||
|
||||
getStateForRouteNamesChange(state, { routeNames, routeParamList }) {
|
||||
const routes = routeNames.map(
|
||||
name =>
|
||||
state.routes.find(r => r.name === name) || {
|
||||
(name) =>
|
||||
state.routes.find((r) => r.name === name) || {
|
||||
name,
|
||||
key: `${name}-${shortid()}`,
|
||||
key: `${name}-${nanoid()}`,
|
||||
params: routeParamList[name],
|
||||
}
|
||||
);
|
||||
@@ -218,8 +218,8 @@ export default function TabRouter({
|
||||
routeNames.indexOf(state.routes[state.index].name)
|
||||
);
|
||||
|
||||
let history = state.history.filter(it =>
|
||||
routes.find(r => r.key === it.key)
|
||||
let history = state.history.filter((it) =>
|
||||
routes.find((r) => r.key === it.key)
|
||||
);
|
||||
|
||||
if (!history.length) {
|
||||
@@ -236,7 +236,7 @@ export default function TabRouter({
|
||||
},
|
||||
|
||||
getStateForRouteFocus(state, key) {
|
||||
const index = state.routes.findIndex(r => r.key === key);
|
||||
const index = state.routes.findIndex((r) => r.key === key);
|
||||
|
||||
if (index === -1 || index === state.index) {
|
||||
return state;
|
||||
@@ -253,11 +253,11 @@ export default function TabRouter({
|
||||
|
||||
if (action.type === 'NAVIGATE' && action.payload.key) {
|
||||
index = state.routes.findIndex(
|
||||
route => route.key === action.payload.key
|
||||
(route) => route.key === action.payload.key
|
||||
);
|
||||
} else {
|
||||
index = state.routes.findIndex(
|
||||
route => route.name === action.payload.name
|
||||
(route) => route.name === action.payload.name
|
||||
);
|
||||
}
|
||||
|
||||
@@ -295,7 +295,7 @@ export default function TabRouter({
|
||||
|
||||
const previousKey = state.history[state.history.length - 2].key;
|
||||
const index = state.routes.findIndex(
|
||||
route => route.key === previousKey
|
||||
(route) => route.key === previousKey
|
||||
);
|
||||
|
||||
if (index === -1) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import BaseRouter from '../BaseRouter';
|
||||
import * as CommonActions from '../CommonActions';
|
||||
|
||||
jest.mock('shortid', () => () => 'test');
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
|
||||
const STATE = {
|
||||
stale: false as const,
|
||||
@@ -84,6 +84,22 @@ it('resets state to new state with RESET', () => {
|
||||
expect(result).toEqual({ index: 0, routes });
|
||||
});
|
||||
|
||||
it('adds keys to routes missing keys during RESET', () => {
|
||||
const result = BaseRouter.getStateForAction(
|
||||
STATE,
|
||||
// @ts-ignore
|
||||
CommonActions.reset({
|
||||
...STATE,
|
||||
routes: [...STATE.routes, { name: 'qux' }],
|
||||
})
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
...STATE,
|
||||
routes: [...STATE.routes, { key: 'qux-test', name: 'qux' }],
|
||||
});
|
||||
});
|
||||
|
||||
it("doesn't handle RESET if routes don't match routeNames", () => {
|
||||
const routes = [
|
||||
{ key: 'bar', name: 'bar', params: { fruit: 'orange' } },
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
DrawerNavigationState,
|
||||
} from '..';
|
||||
|
||||
jest.mock('shortid', () => () => 'test');
|
||||
jest.mock('nanoid/non-secure', () => ({ nanoid: () => 'test' }));
|
||||
|
||||
it('gets initial state from route names and params with initialRouteName', () => {
|
||||
const router = DrawerRouter({ initialRouteName: 'baz' });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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', () => {
|
||||
const router = StackRouter({ initialRouteName: 'baz' });
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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', () => {
|
||||
const router = TabRouter({ initialRouteName: 'baz' });
|
||||
|
||||
@@ -3,6 +3,86 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix swipe gestures requiring a lot of velocity to dismiss ([61f16d3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/61f16d3f25cbbcc00d699dd09c5f4abde9b17d5a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.3...@react-navigation/stack@5.2.4) (2020-03-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix swipe not dismissing card in RTL ([043924c](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/043924ca4843b6f02626532cbf4aafc7cad9fab1)), closes [#7841](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7841)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.2...@react-navigation/stack@5.2.3) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* use the correct velocity value in closing animation ([#7836](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7836)) ([adbfedc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/adbfedcd58d4e3d358c6c9691710bb8e4e0d8afb))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.1...@react-navigation/stack@5.2.2) (2020-03-19)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7485)
|
||||
* fix blank page if stack was inside display: none before ([49f6fed](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/49f6fed6d3da878e02a9fe9115c05bcf84e332bf))
|
||||
* fix closing stack using inverted gesture. ([#7824](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7824)) ([f24d3a3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/f24d3a3461ee8a566c25ce7d13f31035b4be2691))
|
||||
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/6789)
|
||||
* only dismiss previously focused input on page change. closes [#6918](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/6918) ([b1fe730](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/b1fe73097f3ad58d3ba4a8a9b875276d1d8d220c))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.0...@react-navigation/stack@5.2.1) (2020-03-17)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/stack",
|
||||
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
|
||||
"version": "5.2.1",
|
||||
"version": "5.2.9",
|
||||
"keywords": [
|
||||
"react-native-component",
|
||||
"react-component",
|
||||
@@ -40,7 +40,7 @@
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.10.0",
|
||||
"@react-native-community/masked-view": "^0.1.7",
|
||||
"@react-navigation/native": "^5.1.0",
|
||||
"@react-navigation/native": "^5.1.4",
|
||||
"@types/color": "^3.0.1",
|
||||
"@types/react": "^16.9.23",
|
||||
"@types/react-native": "^0.61.22",
|
||||
@@ -50,7 +50,7 @@
|
||||
"react-native-gesture-handler": "^1.6.0",
|
||||
"react-native-safe-area-context": "^0.7.3",
|
||||
"react-native-screens": "^2.3.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-community/masked-view": ">= 0.1.0",
|
||||
|
||||
@@ -40,7 +40,7 @@ function StackNavigator({
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigation.addListener &&
|
||||
navigation.addListener('tabPress', e => {
|
||||
navigation.addListener('tabPress', (e) => {
|
||||
const isFocused = navigation.isFocused();
|
||||
|
||||
// Run the operation in the next frame so we're sure all listeners have been run
|
||||
|
||||
@@ -4,7 +4,7 @@ export default function debounce<T extends (...args: any[]) => void>(
|
||||
): T {
|
||||
let timeout: NodeJS.Timeout | number | undefined;
|
||||
|
||||
return function(this: any, ...args) {
|
||||
return function (this: any, ...args) {
|
||||
if (!timeout) {
|
||||
// eslint-disable-next-line babel/no-invalid-this
|
||||
func.apply(this, args);
|
||||
|
||||
@@ -124,7 +124,7 @@ export default function HeaderContainer({
|
||||
<View
|
||||
onLayout={
|
||||
onContentHeightChange
|
||||
? e =>
|
||||
? (e) =>
|
||||
onContentHeightChange({
|
||||
route: scene.route,
|
||||
height: e.nativeEvent.layout.height,
|
||||
|
||||
@@ -38,7 +38,7 @@ type State = {
|
||||
};
|
||||
|
||||
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
|
||||
Object.keys(styles).forEach(styleProp => {
|
||||
Object.keys(styles).forEach((styleProp) => {
|
||||
const value = styles[styleProp];
|
||||
|
||||
if (styleProp === 'position' && value === 'absolute') {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as React from 'react';
|
||||
import { TextInput, Keyboard } from 'react-native';
|
||||
import { TextInput, Platform, Keyboard } from 'react-native';
|
||||
|
||||
type Props = {
|
||||
enabled: boolean;
|
||||
@@ -54,7 +54,13 @@ export default class KeyboardManager extends React.Component<Props> {
|
||||
|
||||
this.clearKeyboardTimeout();
|
||||
|
||||
Keyboard.dismiss();
|
||||
const input = this.previouslyFocusedTextInput;
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
Keyboard.dismiss();
|
||||
} else if (input) {
|
||||
TextInput.State.blurTextInput(input);
|
||||
}
|
||||
|
||||
// Cleanup the ID on successful page change
|
||||
this.previouslyFocusedTextInput = null;
|
||||
|
||||
@@ -30,7 +30,7 @@ type Props = {
|
||||
export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return (
|
||||
<SafeAreaConsumer>
|
||||
{insets => {
|
||||
{(insets) => {
|
||||
if (insets) {
|
||||
// If we already have insets, don't wrap the stack in another safe area provider
|
||||
// This avoids an issue with updates at the cost of potentially incorrect values
|
||||
|
||||
@@ -246,11 +246,21 @@ export default class Card extends React.Component<Props> {
|
||||
this.handleStartInteraction();
|
||||
onGestureBegin?.();
|
||||
break;
|
||||
case GestureState.CANCELLED:
|
||||
case GestureState.CANCELLED: {
|
||||
this.isSwiping.setValue(FALSE);
|
||||
this.handleEndInteraction();
|
||||
|
||||
const velocity =
|
||||
gestureDirection === 'vertical' ||
|
||||
gestureDirection === 'vertical-inverted'
|
||||
? nativeEvent.velocityY
|
||||
: nativeEvent.velocityX;
|
||||
|
||||
this.animate({ closing: this.props.closing, velocity });
|
||||
|
||||
onGestureCanceled?.();
|
||||
break;
|
||||
}
|
||||
case GestureState.END: {
|
||||
this.isSwiping.setValue(FALSE);
|
||||
|
||||
@@ -272,7 +282,9 @@ export default class Card extends React.Component<Props> {
|
||||
}
|
||||
|
||||
const closing =
|
||||
translation + velocity * gestureVelocityImpact > distance / 2
|
||||
(translation + velocity * gestureVelocityImpact) *
|
||||
getInvertedMultiplier(gestureDirection) >
|
||||
distance / 2
|
||||
? velocity !== 0 || translation !== 0
|
||||
: false;
|
||||
|
||||
@@ -478,7 +490,7 @@ export default class Card extends React.Component<Props> {
|
||||
<CardAnimationContext.Provider value={animationContext}>
|
||||
<View pointerEvents="box-none" {...rest}>
|
||||
{overlayEnabled ? (
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
<View pointerEvents="box-none" style={StyleSheet.absoluteFill}>
|
||||
{overlay({ style: overlayStyle })}
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
@@ -75,9 +75,6 @@ type State = {
|
||||
|
||||
const EPSILON = 0.01;
|
||||
|
||||
const dimensions = Dimensions.get('window');
|
||||
const layout = { width: dimensions.width, height: dimensions.height };
|
||||
|
||||
const MaybeScreenContainer = ({
|
||||
enabled,
|
||||
...rest
|
||||
@@ -160,7 +157,16 @@ const getProgressFromGesture = (
|
||||
layout: Layout,
|
||||
descriptor?: StackDescriptor
|
||||
) => {
|
||||
const distance = getDistanceFromOptions(mode, layout, descriptor);
|
||||
const distance = getDistanceFromOptions(
|
||||
mode,
|
||||
{
|
||||
// Make sure that we have a non-zero distance, otherwise there will be incorrect progress
|
||||
// This causes blank screen on web if it was previously inside container with display: none
|
||||
width: Math.max(1, layout.width),
|
||||
height: Math.max(1, layout.height),
|
||||
},
|
||||
descriptor
|
||||
);
|
||||
|
||||
if (distance > 0) {
|
||||
return gesture.interpolate({
|
||||
@@ -290,19 +296,25 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
state: State = {
|
||||
routes: [],
|
||||
scenes: [],
|
||||
gestures: {},
|
||||
layout,
|
||||
descriptors: this.props.descriptors,
|
||||
// Used when card's header is null and mode is float to make transition
|
||||
// between screens with headers and those without headers smooth.
|
||||
// This is not a great heuristic here. We don't know synchronously
|
||||
// on mount what the header height is so we have just used the most
|
||||
// common cases here.
|
||||
headerHeights: {},
|
||||
};
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
const { height = 0, width = 0 } = Dimensions.get('window');
|
||||
|
||||
this.state = {
|
||||
routes: [],
|
||||
scenes: [],
|
||||
gestures: {},
|
||||
layout: { height, width },
|
||||
descriptors: this.props.descriptors,
|
||||
// Used when card's header is null and mode is float to make transition
|
||||
// between screens with headers and those without headers smooth.
|
||||
// This is not a great heuristic here. We don't know synchronously
|
||||
// on mount what the header height is so we have just used the most
|
||||
// common cases here.
|
||||
headerHeights: {},
|
||||
};
|
||||
}
|
||||
|
||||
private handleLayout = (e: LayoutChangeEvent) => {
|
||||
const { height, width } = e.nativeEvent.layout;
|
||||
@@ -401,9 +413,9 @@ export default class CardStack extends React.Component<Props, State> {
|
||||
left = insets.left,
|
||||
} = focusedOptions.safeAreaInsets || {};
|
||||
|
||||
// Screens is buggy on iOS, so we don't enable it there
|
||||
// Screens is buggy on iOS and web, so we only enable it on Android
|
||||
// For modals, usually we want the screen underneath to be visible, so also disable it there
|
||||
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
|
||||
const isScreensEnabled = Platform.OS === 'android' && mode !== 'modal';
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
||||
@@ -46,31 +46,63 @@ type State = {
|
||||
|
||||
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> {
|
||||
static getDerivedStateFromProps(
|
||||
props: Readonly<Props>,
|
||||
state: Readonly<State>
|
||||
) {
|
||||
// If there was no change in routes, we don't need to compute anything
|
||||
if (props.state.routes === state.previousRoutes && state.routes.length) {
|
||||
if (props.descriptors !== state.previousDescriptors) {
|
||||
const descriptors = state.routes.reduce<StackDescriptorMap>(
|
||||
(acc, route) => {
|
||||
acc[route.key] =
|
||||
props.descriptors[route.key] || state.descriptors[route.key];
|
||||
if (
|
||||
(props.state.routes === state.previousRoutes ||
|
||||
isArrayEqual(
|
||||
props.state.routes.map((r) => r.key),
|
||||
state.previousRoutes.map((r) => r.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 {
|
||||
previousDescriptors: props.descriptors,
|
||||
descriptors,
|
||||
};
|
||||
routes = state.routes.map((route) => map[route.key] || route);
|
||||
previousRoutes = props.state.routes;
|
||||
}
|
||||
|
||||
return null;
|
||||
return {
|
||||
routes,
|
||||
previousRoutes,
|
||||
descriptors,
|
||||
previousDescriptors,
|
||||
};
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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.find((r) => r.key === nextFocusedRoute.key)) {
|
||||
// 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
|
||||
|
||||
@@ -128,17 +160,17 @@ export default class StackView extends React.Component<Props, State> {
|
||||
openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
|
||||
|
||||
closingRouteKeys = closingRouteKeys.filter(
|
||||
key => key !== nextFocusedRoute.key
|
||||
(key) => key !== nextFocusedRoute.key
|
||||
);
|
||||
replacingRouteKeys = replacingRouteKeys.filter(
|
||||
key => key !== nextFocusedRoute.key
|
||||
(key) => key !== nextFocusedRoute.key
|
||||
);
|
||||
|
||||
if (!routes.find(r => r.key === previousFocusedRoute.key)) {
|
||||
if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
|
||||
// The previous focused route isn't present in state, we treat this as a replace
|
||||
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
(key) => key !== previousFocusedRoute.key
|
||||
);
|
||||
|
||||
if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') {
|
||||
@@ -151,7 +183,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
// But since user configured it to animate the old screen like a pop, we need to add this without animation
|
||||
// So remove it from `openingRouteKeys` which will remove the animation
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== nextFocusedRoute.key
|
||||
(key) => key !== nextFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep the route being removed at the end to animate it out
|
||||
@@ -163,7 +195,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
];
|
||||
|
||||
closingRouteKeys = closingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
(key) => key !== previousFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt
|
||||
@@ -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.find((r) => r.key === previousFocusedRoute.key)) {
|
||||
// The previously focused route was removed, we treat this as a pop
|
||||
|
||||
if (
|
||||
@@ -186,10 +218,10 @@ export default class StackView extends React.Component<Props, State> {
|
||||
// Sometimes a route can be closed before the opening animation finishes
|
||||
// So we also need to remove it from the opening list
|
||||
openingRouteKeys = openingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
(key) => key !== previousFocusedRoute.key
|
||||
);
|
||||
replacingRouteKeys = replacingRouteKeys.filter(
|
||||
key => key !== previousFocusedRoute.key
|
||||
(key) => key !== previousFocusedRoute.key
|
||||
);
|
||||
|
||||
// Keep a copy of route being removed in the state to be able to animate it
|
||||
@@ -271,13 +303,13 @@ export default class StackView extends React.Component<Props, State> {
|
||||
private getPreviousRoute = ({ route }: { route: Route<string> }) => {
|
||||
const { closingRouteKeys, replacingRouteKeys } = this.state;
|
||||
const routes = this.state.routes.filter(
|
||||
r =>
|
||||
(r) =>
|
||||
r.key === route.key ||
|
||||
(!closingRouteKeys.includes(r.key) &&
|
||||
!replacingRouteKeys.includes(r.key))
|
||||
);
|
||||
|
||||
const index = routes.findIndex(r => r.key === route.key);
|
||||
const index = routes.findIndex((r) => r.key === route.key);
|
||||
|
||||
return routes[index - 1];
|
||||
};
|
||||
@@ -298,12 +330,16 @@ export default class StackView extends React.Component<Props, State> {
|
||||
};
|
||||
|
||||
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
|
||||
this.setState(state => ({
|
||||
this.setState((state) => ({
|
||||
routes: state.replacingRouteKeys.length
|
||||
? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key))
|
||||
? 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),
|
||||
openingRouteKeys: state.openingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
closingRouteKeys: state.closingRouteKeys.filter(
|
||||
(key) => key !== route.key
|
||||
),
|
||||
replacingRouteKeys: [],
|
||||
}));
|
||||
};
|
||||
@@ -311,7 +347,7 @@ export default class StackView extends React.Component<Props, State> {
|
||||
private handleCloseRoute = ({ route }: { route: Route<string> }) => {
|
||||
const { state, navigation } = this.props;
|
||||
|
||||
if (state.routes.find(r => r.key === route.key)) {
|
||||
if (state.routes.find((r) => r.key === route.key)) {
|
||||
// If a route exists in state, trigger a pop
|
||||
// This will happen in when the route was closed from the card component
|
||||
// e.g. When the close animation triggered from a gesture ends
|
||||
@@ -322,13 +358,13 @@ export default class StackView extends React.Component<Props, State> {
|
||||
});
|
||||
} else {
|
||||
// We need to clean up any state tracking the route and pop it immediately
|
||||
this.setState(state => ({
|
||||
routes: state.routes.filter(r => r.key !== route.key),
|
||||
this.setState((state) => ({
|
||||
routes: state.routes.filter((r) => r.key !== route.key),
|
||||
openingRouteKeys: state.openingRouteKeys.filter(
|
||||
key => key !== route.key
|
||||
(key) => key !== route.key
|
||||
),
|
||||
closingRouteKeys: state.closingRouteKeys.filter(
|
||||
key => key !== route.key
|
||||
(key) => key !== route.key
|
||||
),
|
||||
}));
|
||||
}
|
||||
@@ -378,9 +414,9 @@ export default class StackView extends React.Component<Props, State> {
|
||||
<GestureHandlerWrapper style={styles.container}>
|
||||
<SafeAreaProviderCompat>
|
||||
<SafeAreaConsumer>
|
||||
{insets => (
|
||||
{(insets) => (
|
||||
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
|
||||
{props => (
|
||||
{(props) => (
|
||||
<CardStack
|
||||
mode={mode}
|
||||
insets={insets as EdgeInsets}
|
||||
|
||||
@@ -9,7 +9,7 @@ const packages = path.join(__dirname, '..', 'packages');
|
||||
|
||||
const invalid = [];
|
||||
|
||||
fs.readdirSync(packages).forEach(name => {
|
||||
fs.readdirSync(packages).forEach((name) => {
|
||||
const dir = path.join(packages, name);
|
||||
|
||||
if (fs.statSync(path.join(packages, name)).isDirectory()) {
|
||||
@@ -26,6 +26,6 @@ fs.readdirSync(packages).forEach(name => {
|
||||
if (invalid.length) {
|
||||
console.log(
|
||||
'Found invalid path to type definitions in the following packages:\n',
|
||||
invalid.map(p => `- ${p.name} (${p.types})`).join('\n')
|
||||
invalid.map((p) => `- ${p.name} (${p.types})`).join('\n')
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user