Compare commits

...

59 Commits

Author SHA1 Message Date
Satyajit Sahoo
5683bebfd6 chore: publish
- @react-navigation/stack@5.2.12
2020-04-22 16:26:11 +02:00
Satyajit Sahoo
78485cea69 fix: animate card to existing closing state on gesture end
fixes #7938
2020-04-22 15:16:39 +02:00
Satyajit Sahoo
1613915669 chore: mark screens and masked view as optional in stack
Needs e54819c4de to work.
2020-04-22 14:02:21 +02:00
Satyajit Sahoo
335a04edc1 chore: add action to check package versions 2020-04-20 14:35:07 +02:00
Satyajit Sahoo
5e0069a896 chore: publish
- @react-navigation/bottom-tabs@5.2.7
 - @react-navigation/compat@5.1.9
 - @react-navigation/core@5.3.4
 - @react-navigation/drawer@5.5.0
 - @react-navigation/material-bottom-tabs@5.1.9
 - @react-navigation/material-top-tabs@5.1.9
 - @react-navigation/native@5.1.6
 - @react-navigation/routers@5.4.0
 - @react-navigation/stack@5.2.11
2020-04-18 01:28:05 +02:00
Satyajit Sahoo
249248e741 chore: update yarn.lock 2020-04-18 01:24:16 +02:00
Evan Bacon
821343fed3 fix: webkit style error in overlay 2020-04-18 01:14:56 +02:00
Satyajit Sahoo
82edb2581b fix: hide inactive screens for stack on web (#8010) 2020-04-18 01:14:11 +02:00
Satyajit Sahoo
cb67530dc5 chore: tweak album example 2020-04-18 01:13:34 +02:00
Satyajit Sahoo
36689e24c2 feat: add openByDefault option to drawer 2020-04-18 01:13:34 +02:00
Gheorghe Pinzaru
6e51f596fa fix: ios presentation modal cuts the topOffset on the bottom (#7943)
* Add padding bottom to ios presentation modal

Because of the translateY moving the screen out to the bottom of view by 10 pt, these 10pt are hidden under the screen, or steal this size from the safe area. To avoid cutting elements, the size of the screen could be decreased by the `topOffset` using padding on the bottom. Fixes #7856

* Update packages/stack/src/TransitionConfigs/CardStyleInterpolators.tsx

Co-Authored-By: Serhii Vecherenko <SDSLeon999@gmail.com>

Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
Co-authored-by: Serhii Vecherenko <SDSLeon999@gmail.com>
2020-04-18 01:13:34 +02:00
Satyajit Sahoo
402df73aa2 chore: add link to how to create minimal repro 2020-04-17 00:24:22 +02:00
Satyajit Sahoo
187aefe9c4 fix: handle initial: false for nested route after first initialization 2020-04-14 17:06:58 +02:00
Satyajit Sahoo
2613a62874 chore: add config for netlify 2020-04-12 22:11:22 +02:00
Satyajit Sahoo
6bdf6ae4ed fix: handle in-page go back when there's no history
fixes #7852
2020-04-10 17:59:40 +02:00
Satyajit Sahoo
e2bcf5168c fix: fix drawer not closing on web
fixes #6759
2020-04-10 17:59:07 +02:00
Satyajit Sahoo
dfdba8d741 fix: disable animation by default on web for stack 2020-04-10 17:02:32 +02:00
Satyajit Sahoo
a3f7a5feba fix: add initial param for actions from deep link 2020-04-10 12:05:16 +02:00
Satyajit Sahoo
004c7d7ab1 fix: add initial option for navigating to nested navigators
By default, params passed to nested navigators is used to initialize the navigator if it's not rendered already. The `initial` option would let the user control this behaviour. By specifying `initial: false`, it'll be possible to acheive the old behaviour of rendering the initial route of the stack before navigating to the new screen.

Example:

```js
navigation.navigate('Account', {
  screen: 'Settings',
  initial: false,
});
```
2020-04-10 11:51:32 +02:00
Satyajit Sahoo
49f658fbc0 chore: publish
- @react-navigation/bottom-tabs@5.2.6
 - @react-navigation/compat@5.1.8
 - @react-navigation/core@5.3.3
 - @react-navigation/drawer@5.4.1
 - @react-navigation/material-bottom-tabs@5.1.8
 - @react-navigation/material-top-tabs@5.1.8
 - @react-navigation/native@5.1.5
 - @react-navigation/routers@5.3.0
 - @react-navigation/stack@5.2.10
2020-04-08 12:17:31 +02:00
Satyajit Sahoo
cb2f157a56 fix: don't hide content from accessibility with permanent drawer
closes #7976
2020-04-08 12:17:09 +02:00
Juang, Yi-Lin
c4acdaa703 docs: fix typo (#7865) 2020-04-08 11:35:45 +02:00
Satyajit Sahoo
f1a8bceba5 fix: make color of shadow element same as card color in stack 2020-04-07 23:34:55 +02:00
Ruben Grimm
44081172d4 fix: use 1 as default in compatibility pop action 2020-04-07 23:33:38 +02:00
Satyajit Sahoo
de5d985f3b chore: upgrade depenendecies 2020-04-07 15:44:58 +02:00
Satyajit Sahoo
b71de6cc79 fix: mark type exports for all packages 2020-04-07 11:22:47 +02:00
raajnadar
303f0b78a5 fix: separate normal exports and type exports 2020-04-07 11:17:06 +02:00
Satyajit Sahoo
ce3994c82c fix: switch order of focus and blur events. closes #7963 2020-04-07 11:07:16 +02:00
Satyajit Sahoo
ba1f405129 feat: make replace bubble up 2020-04-07 00:02:54 +02:00
Satyajit Sahoo
d4fd906915 fix: workaround warning about setState in another component in render 2020-04-06 23:58:25 +02:00
Vinícius Fraga Modesto
b7fa90bf8d docs: fixes typo (#7923)
This PR fixes a typo in activeBackgroundColor's description
2020-03-31 17:56:26 +02:00
Satyajit Sahoo
9556aa9eff chore: publish
- @react-navigation/bottom-tabs@5.2.5
 - @react-navigation/compat@5.1.7
 - @react-navigation/core@5.3.2
 - @react-navigation/drawer@5.4.0
 - @react-navigation/material-bottom-tabs@5.1.7
 - @react-navigation/material-top-tabs@5.1.7
 - @react-navigation/native@5.1.4
 - @react-navigation/routers@5.2.1
 - @react-navigation/stack@5.2.9
2020-03-30 22:22:25 +02:00
Satyajit Sahoo
9a8fea8f2c fix: when comparing changed routes, only check keys 2020-03-30 22:20:16 +02:00
Satyajit Sahoo
9973db86f0 chore: use non-secure nanoid to be able to run in RN 2020-03-30 22:04:53 +02:00
max
8432e5ab25 fix: dismiss keyboard on screen change for android 2020-03-30 21:50:52 +02:00
Satyajit Sahoo
9bb5cfded3 refactor: replace shortid with nanoid. closes #7858 2020-03-30 21:42:58 +02:00
Satyajit Sahoo
4ac40b5c5d chore: update typescript and babel 2020-03-30 21:42:58 +02:00
Wojciech Lewicki
cd47915861 fix: handle no path property and undefined query params (#7911) 2020-03-30 17:11:33 +02:00
Andrius Janauskas
d649fbc669 fix: finish stack animation on CANCELLED event (#7898)
fixes #7897
2020-03-30 14:36:04 +02:00
Satyajit Sahoo
105da6ab2f fix: disable only swipe gesture on safari 2020-03-30 13:56:30 +02:00
Rajendran Nadar
ac7f972e92 feat: add swipeEnabled option to disable swipe gesture in drawer (#7834) 2020-03-30 13:51:32 +02:00
Satyajit Sahoo
babb5027f9 chore: publish
- @react-navigation/stack@5.2.8
2020-03-27 15:01:32 +01:00
Satyajit Sahoo
78d7a66b2b chore: remove detox dep coz we don't use it 2020-03-27 14:54:54 +01:00
osdnk
a248c453ba chore: publish
- @react-navigation/stack@5.2.7
2020-03-26 17:07:40 +01:00
Wojciech Stanisz
e097df880a fix: add pointerEvents=box-none to overlay View (#7871) 2020-03-26 13:38:30 +01:00
Satyajit Sahoo
856449b200 chore: publish
- @react-navigation/bottom-tabs@5.2.4
 - @react-navigation/compat@5.1.6
 - @react-navigation/core@5.3.1
 - @react-navigation/drawer@5.3.4
 - @react-navigation/material-bottom-tabs@5.1.6
 - @react-navigation/material-top-tabs@5.1.6
 - @react-navigation/native@5.1.3
 - @react-navigation/stack@5.2.6
2020-03-23 17:07:43 +01:00
Steven Bell
d94e43c3c8 fix: add info about android launchMode in useLinking error 2020-03-23 16:39:18 +01:00
Satyajit Sahoo
3096de6286 fix: only call listeners for focused screen for global events 2020-03-23 13:43:43 +01:00
Satyajit Sahoo
1c001424b5 fix: don't emit events for screens that don't exist anymore 2020-03-23 13:03:33 +01:00
Satyajit Sahoo
0f2368965c chore: publish
- @react-navigation/stack@5.2.5
2020-03-23 11:42:01 +01:00
Satyajit Sahoo
61f16d3f25 fix: fix swipe gestures requiring a lot of velocity to dismiss 2020-03-23 11:40:37 +01:00
Satyajit Sahoo
853740bfaf chore: publish
- @react-navigation/bottom-tabs@5.2.3
 - @react-navigation/compat@5.1.5
 - @react-navigation/core@5.3.0
 - @react-navigation/drawer@5.3.3
 - @react-navigation/material-bottom-tabs@5.1.5
 - @react-navigation/material-top-tabs@5.1.5
 - @react-navigation/native@5.1.2
 - @react-navigation/routers@5.2.0
 - @react-navigation/stack@5.2.4
2020-03-23 00:00:55 +01:00
Satyajit Sahoo
179b6312fe chore: update prettier 2020-03-22 23:58:06 +01:00
Satyajit Sahoo
043924ca48 fix: fix swipe not dismissing card in RTL
closes #7841
2020-03-22 23:55:16 +01:00
Satyajit Sahoo
813a5903b5 feat: add keys to routes missing keys during reset 2020-03-22 23:38:40 +01:00
Satyajit Sahoo
3709e652f4 feat: support function in listeners prop 2020-03-22 23:33:25 +01:00
Satyajit Sahoo
5b15c7164f fix: return correct value for isFocused after changing screens
fixes #7843
2020-03-22 23:31:04 +01:00
Satyajit Sahoo
e030932497 chore: publish
- @react-navigation/stack@5.2.3
2020-03-19 21:56:36 +01:00
Tien Pham
adbfedcd58 fix: use the correct velocity value in closing animation (#7836)
In this commit f24d3a3461 we modified the `velocity` in inverted gesture, but since we also use this value in the closing animation, the change in that commit also introduced a new bug:
![Mar-20-2020 03-40-05](https://user-images.githubusercontent.com/57227217/77113229-006f0500-6a5d-11ea-97b5-571e8301cd87.gif)

This PR fixes the issue by keeping the original velocity value.
2020-03-19 21:55:03 +01:00
119 changed files with 4836 additions and 3812 deletions

View File

@@ -13,7 +13,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to reproduce the issue with minimal code (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
args: comment "Hey! Thanks for opening the issue. Can you provide more information about the issue? Please fill the issue template when opening the issue without deleting any section. We need all the information we can to be able to help. Make sure to at least provide - Current behaviour, Expected behaviour, A way to [reproduce the issue with minimal code](https://stackoverflow.com/help/minimal-reproducible-example) (link to [snack.expo.io](https://snack.expo.io)) or a repo on GitHub, and the information about your environment (such as the platform of the device, exact versions of all the packages mentioned in the template etc.)."
needs-repro:
runs-on: ubuntu-latest
@@ -24,7 +24,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: comment "Hey! Thanks for opening the issue. Can you provide a minimal repro which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
args: comment "Hey! Thanks for opening the issue. Can you provide a [minimal repro](https://stackoverflow.com/help/minimal-reproducible-example) which demonstrates the issue? Posting a snippet of your code in the issue is useful, but it's not usually straightforward to run. A repro will help us debug the issue faster. Please try to keep the repro as small as possible. The easiest way to provide a repro is on [snack.expo.io](https://snack.expo.io). If it's not possible to repro it on [snack.expo.io](https://snack.expo.io), then you can also provide the repro in a GitHub repository."
question:
runs-on: ubuntu-latest

27
.github/workflows/versions.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Check versions
on:
issues:
types: [opened]
jobs:
check-versions:
runs-on: ubuntu-latest
steps:
- uses: react-navigation/check-versions-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
packages: |
@react-navigation/bottom-tabs
@react-navigation/compat
@react-navigation/core
@react-navigation/drawer
@react-navigation/material-bottom-tabs
@react-navigation/material-top-tabs
@react-navigation/native
@react-navigation/routers
@react-navigation/stack
react-navigation-animated-switch
react-navigation-drawer
react-navigation-material-bottom-tabs
react-navigation-stack
react-navigation-tabs

View File

@@ -7,7 +7,7 @@
"slug": "react-navigation-example",
"description": "Demo app to showcase various functionality of React Navigation",
"privacy": "public",
"sdkVersion": "36.0.0",
"sdkVersion": "37.0.0",
"platforms": [
"ios",
"android",

View File

@@ -1,4 +1,4 @@
module.exports = function(api) {
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],

View File

@@ -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;

View File

@@ -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

View File

@@ -12,31 +12,30 @@
},
"dependencies": {
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.7",
"@types/react-native-restart": "^0.0.0",
"@react-native-community/masked-view": "^0.1.7",
"color": "^3.1.2",
"expo": "^36.0.2",
"expo-asset": "~8.0.0",
"expo-blur": "^8.0.0",
"expo": "^37.0.0",
"expo-asset": "~8.1.3",
"expo-blur": "~8.1.0",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-paper": "^3.6.0",
"react-native-paper": "^3.7.0",
"react-native-reanimated": "^1.7.0",
"react-native-restart": "^0.0.14",
"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-tab-view": "2.14.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.19",
"@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"
"@types/react-native": "^0.60.22",
"babel-preset-expo": "^8.1.0",
"expo-cli": "^3.17.18",
"typescript": "^3.8.3"
}
}

View File

@@ -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"

View File

@@ -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

View File

@@ -0,0 +1,127 @@
import * as React from 'react';
import { Dimensions, ScaledSize } from 'react-native';
import { Appbar } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/native';
import { StackNavigationProp } from '@react-navigation/stack';
import {
createDrawerNavigator,
DrawerNavigationProp,
DrawerContent,
} from '@react-navigation/drawer';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type DrawerParams = {
Article: undefined;
NewsFeed: undefined;
Album: undefined;
};
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
const useIsLargeScreen = () => {
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
React.useEffect(() => {
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', onDimensionsChange);
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
return dimensions.width > 414;
};
const Header = ({
onGoBack,
title,
}: {
onGoBack: () => void;
title: string;
}) => {
const isLargeScreen = useIsLargeScreen();
return (
<Appbar.Header>
{isLargeScreen ? null : <Appbar.BackAction onPress={onGoBack} />}
<Appbar.Content title={title} />
</Appbar.Header>
);
};
const ArticleScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Article" onGoBack={() => navigation.toggleDrawer()} />
<Article />
</>
);
};
const NewsFeedScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Feed" onGoBack={() => navigation.toggleDrawer()} />
<NewsFeed />
</>
);
};
const AlbumsScreen = ({ navigation }: { navigation: DrawerNavigation }) => {
return (
<>
<Header title="Albums" onGoBack={() => navigation.toggleDrawer()} />
<Albums />
</>
);
};
const Drawer = createDrawerNavigator<DrawerParams>();
type Props = Partial<React.ComponentProps<typeof Drawer.Navigator>> & {
navigation: StackNavigationProp<ParamListBase>;
};
export default function DrawerScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
gestureEnabled: false,
});
const isLargeScreen = useIsLargeScreen();
return (
<Drawer.Navigator
openByDefault
drawerType={isLargeScreen ? 'permanent' : 'back'}
drawerStyle={isLargeScreen ? null : { width: '100%' }}
overlayColor="transparent"
drawerContent={(props) => (
<>
<Appbar.Header>
<Appbar.Action icon="close" onPress={() => navigation.goBack()} />
<Appbar.Content title="Pages" />
</Appbar.Header>
<DrawerContent {...props} />
</>
)}
{...rest}
>
<Drawer.Screen name="Article" component={ArticleScreen} />
<Drawer.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
<Drawer.Screen
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</Drawer.Navigator>
);
}

View File

@@ -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"

View File

@@ -9,6 +9,7 @@ import {
ScrollViewProps,
Dimensions,
Platform,
ScaledSize,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
@@ -40,15 +41,38 @@ const COVERS = [
];
export default function Albums(props: Partial<ScrollViewProps>) {
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
React.useEffect(() => {
const onDimensionsChange = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', onDimensionsChange);
return () => Dimensions.removeEventListener('change', onDimensionsChange);
}, []);
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
const itemSize = dimensions.width / Math.floor(dimensions.width / 150);
return (
<ScrollView ref={ref} contentContainerStyle={styles.content} {...props}>
{COVERS.map((source, i) => (
// eslint-disable-next-line react/no-array-index-key
<View key={i} style={styles.item}>
<View
// eslint-disable-next-line react/no-array-index-key
key={i}
style={[
styles.item,
Platform.OS !== 'web' && {
height: itemSize,
width: itemSize,
},
]}
>
<Image source={source} style={styles.photo} />
</View>
))}
@@ -76,10 +100,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
flexWrap: 'wrap',
},
item: {
height: Dimensions.get('window').width / 2,
width: '50%',
},
},
}),
photo: {

View File

@@ -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"
/>

View File

@@ -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>

View File

@@ -43,6 +43,7 @@ import {
} from '@react-navigation/stack';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import StackTransparent from './Screens/StackTransparent';
@@ -53,7 +54,7 @@ import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import DynamicTabs from './Screens/DynamicTabs';
import AuthFlow from './Screens/AuthFlow';
import CompatAPI from './Screens/CompatAPI';
import SettingsItem from './Shared/SettingsItem';
import MasterDetail from './Screens/MasterDetail';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
@@ -97,6 +98,10 @@ const SCREENS = {
title: 'Dynamic Tabs',
component: DynamicTabs,
},
MasterDetail: {
title: 'Master Detail',
component: MasterDetail,
},
AuthFlow: {
title: 'Auth Flow',
component: AuthFlow,
@@ -116,7 +121,7 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>();
const containerRef = React.useRef<NavigationContainerRef>(null);
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
@@ -214,7 +219,7 @@ export default function App() {
return null;
}
const isLargeScreen = dimensions.width > 900;
const isLargeScreen = dimensions.width >= 1024;
return (
<PaperProvider theme={paperTheme}>
@@ -224,7 +229,7 @@ export default function App() {
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
onStateChange={(state) =>
AsyncStorage.setItem(
NAVIGATION_PERSISTENCE_KEY,
JSON.stringify(state)
@@ -298,12 +303,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 +320,7 @@ export default function App() {
)}
</Stack.Screen>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
(name) => (
<Stack.Screen
key={name}
name={name}

View File

@@ -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,

5
netlify.toml Normal file
View File

@@ -0,0 +1,5 @@
[build]
base = "/"
publish = "example/web-build"
command = "yarn example expo build:web"

View File

@@ -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",
"@types/jest": "^25.2.1",
"babel-jest": "^25.2.6",
"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",
"jest": "^25.2.7",
"lerna": "^3.20.2",
"prettier": "^1.19.1",
"typescript": "^3.7.5"
"prettier": "^2.0.4",
"typescript": "^3.8.3"
},
"resolutions": {
"react": "~16.9.0",

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.7](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.6...@react-navigation/bottom-tabs@5.2.7) (2020-04-17)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.5...@react-navigation/bottom-tabs@5.2.6) (2020-04-08)
### Bug Fixes
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.4...@react-navigation/bottom-tabs@5.2.5) (2020-03-30)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.3...@react-navigation/bottom-tabs@5.2.4) (2020-03-23)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
**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)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.2.2",
"version": "5.2.7",
"keywords": [
"react-native-component",
"react-component",
@@ -35,7 +35,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.1",
"@react-navigation/native": "^5.1.6",
"@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",

View File

@@ -12,7 +12,7 @@ export { default as BottomTabBar } from './views/BottomTabBar';
/**
* Types
*/
export {
export type {
BottomTabNavigationOptions,
BottomTabNavigationProp,
BottomTabBarProps,

View File

@@ -138,7 +138,7 @@ export type BottomTabBarOptions = {
*/
inactiveTintColor?: string;
/**
* Background olor for the active tab.
* Background color for the active tab.
*/
activeBackgroundColor?: string;
/**

View File

@@ -121,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 {

View File

@@ -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 }) => {

View File

@@ -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

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.8...@react-navigation/compat@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.7...@react-navigation/compat@5.1.8) (2020-04-08)
### Bug Fixes
* use 1 as default in compatibility pop action ([4408117](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/44081172d440c713ad3543a2d5e1e18ebc8f72a4))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.6...@react-navigation/compat@5.1.7) (2020-03-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.5...@react-navigation/compat@5.1.6) (2020-03-23)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
**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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.1.4",
"version": "5.1.9",
"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.1",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"react": "~16.9.0",
"typescript": "^3.7.5"
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",

View File

@@ -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;

View File

@@ -57,7 +57,7 @@ export function push(routeName: string, params?: object, action?: never) {
});
}
export function pop(n: number) {
export function pop(n: number = 1) {
return StackActions.pop(typeof n === 'number' ? { n } : n);
}

View File

@@ -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>>({});

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -3,6 +3,70 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.3...@react-navigation/core@5.3.4) (2020-04-17)
### Bug Fixes
* add initial option for navigating to nested navigators ([004c7d7](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/004c7d7ab1f80faf04b2a1836ec6b79a5419e45f))
* add initial param for actions from deep link ([a3f7a5f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/a3f7a5feba2e6aa2158aeaea6cde73ae1603173e))
* handle initial: false for nested route after first initialization ([187aefe](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/187aefe9c400b499f920c212bf856414e25c5aaf))
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.2...@react-navigation/core@5.3.3) (2020-04-08)
### Bug Fixes
* switch order of focus and blur events. closes [#7963](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7963) ([ce3994c](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/ce3994c82c28669d5742017eb7627e9adf996933))
* workaround warning about setState in another component in render ([d4fd906](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/d4fd906915cc20d6fb21508384c05a540d8644d8))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.1...@react-navigation/core@5.3.2) (2020-03-30)
### Bug Fixes
* handle no path property and undefined query params ([#7911](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7911)) ([cd47915](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/cd47915861a56cd7eaa9ac79f5139cde56ca95a7))
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.0...@react-navigation/core@5.3.1) (2020-03-23)
### Bug Fixes
* don't emit events for screens that don't exist anymore ([1c00142](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/1c001424b595b40f9db9343096c833f75353b099))
* only call listeners for focused screen for global events ([3096de6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3096de62868a7ed9ed65e529c8ddfa001b9be486))
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
### 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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.2.3",
"version": "5.3.4",
"keywords": [
"react",
"react-native",
@@ -29,24 +29,23 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.1.1",
"@react-navigation/routers": "^5.4.0",
"escape-string-regexp": "^2.0.0",
"query-string": "^6.11.1",
"nanoid": "^3.0.2",
"query-string": "^6.12.0",
"react-is": "^16.13.0",
"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"
"react-test-renderer": "~16.13.1",
"typescript": "^3.8.3"
},
"peerDependencies": {
"react": "*"

View File

@@ -9,14 +9,15 @@ import {
} from '@react-navigation/routers';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import { ScheduleUpdateContext } from './useScheduleUpdate';
import useFocusedListeners from './useFocusedListeners';
import useDevTools from './useDevTools';
import useStateGetters from './useStateGetters';
import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState';
import isSerializable from './isSerializable';
import { NavigationContainerRef, NavigationContainerProps } from './types';
import useEventEmitter from './useEventEmitter';
import useSyncState from './useSyncState';
type State = NavigationState | PartialState<NavigationState> | undefined;
@@ -73,7 +74,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>;
@@ -102,7 +103,7 @@ const BaseNavigationContainer = React.forwardRef(
independent,
children,
}: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
ref?: React.Ref<NavigationContainerRef>
) {
const parent = React.useContext(NavigationStateContext);
@@ -112,7 +113,13 @@ const BaseNavigationContainer = React.forwardRef(
);
}
const [state, getState, setState] = useSyncState<State>(() =>
const [
state,
getState,
setState,
scheduleUpdate,
flushUpdates,
] = useSyncState<State>(() =>
getPartialState(initialState == null ? undefined : initialState)
);
@@ -156,7 +163,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 = () => {
@@ -164,7 +171,7 @@ const BaseNavigationContainer = React.forwardRef(
return false;
}
const { result, handled } = listeners[0](navigation =>
const { result, handled } = listeners[0]((navigation) =>
navigation.canGoBack()
);
@@ -218,6 +225,11 @@ const BaseNavigationContainer = React.forwardRef(
[addFocusedListener, trackAction, addStateGetter]
);
const scheduleContext = React.useMemo(
() => ({ scheduleUpdate, flushUpdates }),
[scheduleUpdate, flushUpdates]
);
const context = React.useMemo(
() => ({
state,
@@ -263,11 +275,13 @@ const BaseNavigationContainer = React.forwardRef(
}, [onStateChange, trackState, getRootState, emitter, state]);
return (
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
<ScheduleUpdateContext.Provider value={scheduleContext}>
<NavigationBuilderContext.Provider value={builderContext}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>{children}</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationBuilderContext.Provider>
</ScheduleUpdateContext.Provider>
);
}
);

View File

@@ -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
),
});

View File

@@ -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>
);
};

View File

@@ -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) {

View File

@@ -35,8 +35,10 @@ it('gets navigate action from state', () => {
author: 'jane',
},
screen: 'qux',
initial: true,
},
screen: 'bar',
initial: true,
},
},
type: 'NAVIGATE',
@@ -70,9 +72,11 @@ it('gets navigate action from state', () => {
payload: {
name: 'foo',
params: {
initial: true,
screen: 'bar',
params: {
screen: 'quz',
initial: false,
},
},
},

View File

@@ -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);
});

View File

@@ -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,
},

View File

@@ -626,7 +626,7 @@ it('updates route params with setParams applied to parent', () => {
});
});
it('handles change in route names', () => {
it('handles change in route names', async () => {
const TestNavigator = (props: any): any => {
useNavigationBuilder(MockRouter, props);
return null;
@@ -635,7 +635,7 @@ it('handles change in route names', () => {
const onStateChange = jest.fn();
const root = render(
<BaseNavigationContainer onStateChange={onStateChange}>
<BaseNavigationContainer>
<TestNavigator initialRouteName="bar">
<Screen name="foo" component={jest.fn()} />
<Screen name="bar" component={jest.fn()} />
@@ -737,6 +737,366 @@ it('navigates to nested child in a navigator', () => {
);
});
it('navigates to nested child in a navigator with initial: false', () => {
const TestRouter: typeof MockRouter = (options) => {
const router = MockRouter(options);
return {
...router,
getStateForAction(state, action, options) {
switch (action.type) {
case 'NAVIGATE': {
if (!options.routeNames.includes(action.payload.name as any)) {
return null;
}
const routes = [
...state.routes,
{
key: String(MockRouterKey.current++),
name: action.payload.name,
params: action.payload.params,
},
];
return {
...state,
index: routes.length - 1,
routes,
};
}
default:
return router.getStateForAction(state, action, options);
}
},
} as typeof router;
};
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(TestRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const TestComponent = ({ route }: any): any =>
`[${route.name}, ${JSON.stringify(route.params)}]`;
const onStateChange = jest.fn();
const navigation = React.createRef<NavigationContainerRef>();
const first = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(first).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 0,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '1',
routeNames: ['foo-a', 'foo-b'],
routes: [
{
key: 'foo-a',
name: 'foo-a',
},
{
key: 'foo-b',
name: 'foo-b',
},
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
})
);
expect(first).toMatchInlineSnapshot(
`"[bar-b, {\\"some\\":\\"stuff\\",\\"test\\":42}]"`
);
expect(navigation.current?.getRootState()).toEqual({
index: 2,
key: '0',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{
key: '2',
name: 'bar',
params: { params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '3',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b',
name: 'bar-b',
params: { some: 'stuff', test: 42 },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
const second = render(
<BaseNavigationContainer ref={navigation} onStateChange={onStateChange}>
<TestNavigator>
<Screen name="foo">
{() => (
<TestNavigator>
<Screen name="foo-a" component={TestComponent} />
<Screen name="foo-b" component={TestComponent} />
</TestNavigator>
)}
</Screen>
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(second).toMatchInlineSnapshot(`"[foo-a, undefined]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 0,
key: '4',
routeNames: ['foo', 'bar'],
routes: [
{
key: 'foo',
name: 'foo',
state: {
index: 0,
key: '5',
routeNames: ['foo-a', 'foo-b'],
routes: [
{ key: 'foo-a', name: 'foo-a' },
{ key: 'foo-b', name: 'foo-b' },
],
stale: false,
type: 'test',
},
},
{ key: 'bar', name: 'bar' },
],
stale: false,
type: 'test',
});
act(
() =>
navigation.current &&
navigation.current.navigate('bar', {
screen: 'bar-b',
params: { test: 42 },
initial: false,
})
);
expect(second).toMatchInlineSnapshot(`"[bar-b, {\\"test\\":42}]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 2,
key: '4',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar' },
{
key: '6',
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 2,
key: '7',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b',
name: 'bar-b',
params: { some: 'stuff' },
},
{ key: '8', name: 'bar-b', params: { test: 42 } },
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
const third = render(
<BaseNavigationContainer
ref={navigation}
initialState={{
index: 1,
routes: [
{ name: 'foo' },
{
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '7',
routes: [
{
name: 'bar-a',
params: { lol: 'why' },
},
{
name: 'bar-b',
params: { some: 'stuff' },
},
],
type: 'test',
},
},
],
type: 'test',
}}
>
<TestNavigator>
<Screen name="foo" component={TestComponent} />
<Screen name="bar">
{() => (
<TestNavigator initialRouteName="bar-a">
<Screen
name="bar-a"
component={TestComponent}
initialParams={{ lol: 'why' }}
/>
<Screen
name="bar-b"
component={TestComponent}
initialParams={{ some: 'stuff' }}
/>
</TestNavigator>
)}
</Screen>
</TestNavigator>
</BaseNavigationContainer>
);
expect(third).toMatchInlineSnapshot(`"[bar-b, {\\"some\\":\\"stuff\\"}]"`);
expect(navigation.current?.getRootState()).toEqual({
index: 1,
key: '11',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo-9', name: 'foo' },
{
key: 'bar-10',
name: 'bar',
params: { initial: false, params: { test: 42 }, screen: 'bar-b' },
state: {
index: 1,
key: '14',
routeNames: ['bar-a', 'bar-b'],
routes: [
{
key: 'bar-a-12',
name: 'bar-a',
params: { lol: 'why' },
},
{
key: 'bar-b-13',
name: 'bar-b',
params: { some: 'stuff' },
},
],
stale: false,
type: 'test',
},
},
],
stale: false,
type: 'test',
});
});
it('gives access to internal state', () => {
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);

View File

@@ -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 (
@@ -262,8 +262,10 @@ it('sets initial options with setOptions', () => {
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
React.useEffect(() => {
navigation.setOptions({
title: 'Hello world',
});
});
return 'Test screen';
@@ -273,7 +275,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>
@@ -315,12 +317,12 @@ it('updates options with setOptions', () => {
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
@@ -338,7 +340,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>

View File

@@ -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();
@@ -97,6 +97,69 @@ it('fires focus and blur events in root navigator', () => {
expect(fourthBlurCallback).toBeCalledTimes(0);
});
it('fires focus event after blur', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
MockRouter,
props
);
React.useImperativeHandle(ref, () => navigation, [navigation]);
return state.routes.map((route) => descriptors[route.key].render());
});
const callback = jest.fn();
const Test = ({ route, navigation }: any) => {
React.useEffect(
() =>
navigation.addListener('focus', () => callback(route.name, 'focus')),
[navigation, route.name]
);
React.useEffect(
() => navigation.addListener('blur', () => callback(route.name, 'blur')),
[navigation, route.name]
);
return null;
};
const navigation = React.createRef<any>();
const element = (
<BaseNavigationContainer>
<TestNavigator ref={navigation}>
<Screen name="first" component={Test} />
<Screen name="second" component={Test} />
</TestNavigator>
</BaseNavigationContainer>
);
render(element);
expect(callback.mock.calls).toEqual([['first', 'focus']]);
act(() => navigation.current.navigate('second'));
expect(callback.mock.calls).toEqual([
['first', 'focus'],
['first', 'blur'],
['second', 'focus'],
]);
act(() => navigation.current.navigate('first'));
expect(callback.mock.calls).toEqual([
['first', 'focus'],
['first', 'blur'],
['second', 'focus'],
['second', 'blur'],
['first', 'focus'],
]);
});
it('fires focus and blur events in nested navigator', () => {
const TestNavigator = React.forwardRef((props: any, ref: any): any => {
const { state, navigation, descriptors } = useNavigationBuilder(
@@ -106,7 +169,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 +439,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 +519,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 +628,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 +685,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 +786,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) => {

View File

@@ -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();

View File

@@ -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 = () => {

View File

@@ -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 = () => {

View File

@@ -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);
});

View File

@@ -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');

View File

@@ -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>
);
};

View File

@@ -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 = () => {

View File

@@ -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,

View File

@@ -3,6 +3,7 @@ import { PartialState, NavigationState } from '@react-navigation/routers';
type NavigateParams = {
screen?: string;
params?: NavigateParams;
initial?: boolean;
};
type NavigateAction = {
@@ -35,6 +36,7 @@ export default function getActionFromState(
}
route = current.routes[current.routes.length - 1];
params.initial = current.routes.length === 1;
params.screen = route.name;
if (route.state) {

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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.
@@ -400,24 +410,19 @@ export type RouteConfig<
}
);
export type NavigationContainerRef =
| (NavigationHelpers<ParamListBase> &
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
/**
* Reset the navigation state of the root navigator to the provided state.
*
* @param state Navigation state object.
*/
resetRoot(
state?: PartialState<NavigationState> | NavigationState
): void;
/**
* Get the rehydrated navigation state of the navigation tree.
*/
getRootState(): NavigationState;
})
| undefined
| null;
export type NavigationContainerRef = NavigationHelpers<ParamListBase> &
EventConsumer<{ state: { data: { state: NavigationState } } }> & {
/**
* Reset the navigation state of the root navigator to the provided state.
*
* @param state Navigation state object.
*/
resetRoot(state?: PartialState<NavigationState> | NavigationState): void;
/**
* Get the rehydrated navigation state of the navigation tree.
*/
getRootState(): NavigationState;
};
export type TypedNavigator<
ParamList extends ParamListBase,

View File

@@ -58,7 +58,7 @@ export default function useDevTools({ name, reset, state, enabled }: Options) {
React.useEffect(
() =>
devTools?.subscribe(message => {
devTools?.subscribe((message) => {
if (message.type === 'DISPATCH' && message.state) {
reset(JSON.parse(message.state));
}

View File

@@ -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;
},

View File

@@ -48,7 +48,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
emitter.emit({ type: 'focus', target: currentFocusedKey });
}
// We should only dispatch events when the focused key changed and navigator is focused
// We should only emit events when the focused key changed and navigator is focused
// When navigator is not focused, screens inside shouldn't receive focused status either
if (
lastFocusedKey === currentFocusedKey ||
@@ -62,7 +62,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
return;
}
emitter.emit({ type: 'focus', target: currentFocusedKey });
emitter.emit({ type: 'blur', target: lastFocusedKey });
emitter.emit({ type: 'focus', target: currentFocusedKey });
}, [currentFocusedKey, emitter, navigation]);
}

View File

@@ -9,6 +9,7 @@ import {
RouterFactory,
PartialState,
NavigationAction,
Route,
} from '@react-navigation/routers';
import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
@@ -31,6 +32,7 @@ import {
} from './types';
import useStateGetters from './useStateGetters';
import useOnGetState from './useOnGetState';
import useScheduleUpdate from './useScheduleUpdate';
// This is to make TypeScript compiler happy
// eslint-disable-next-line babel/no-unused-expressions
@@ -41,6 +43,7 @@ type NavigatorRoute = {
params?: {
screen?: string;
params?: object;
initial?: boolean;
};
};
@@ -103,7 +106,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) {
@@ -174,17 +177,19 @@ export default function useNavigationBuilder<
| NavigatorRoute
| undefined;
const previousRouteRef = React.useRef(route);
const previousNestedParamsRef = React.useRef(route?.params);
React.useEffect(() => {
previousRouteRef.current = route;
previousNestedParamsRef.current = route?.params;
}, [route]);
const { children, ...rest } = options;
const { current: router } = React.useRef<Router<State, any>>(
createRouter({
...((rest as unknown) as RouterOptions),
...(route?.params && typeof route.params.screen === 'string'
...(route?.params &&
route.params.initial !== false &&
typeof route.params.screen === 'string'
? { initialRouteName: route.params.screen }
: null),
})
@@ -212,12 +217,12 @@ 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];
const initialParamsFromParams =
route?.params && route.params.screen === curr
route?.params?.initial !== false && route?.params?.screen === curr
? route.params.params
: undefined;
@@ -241,12 +246,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]
);
@@ -264,6 +269,8 @@ export default function useNavigationBuilder<
>();
const initializedStateRef = React.useRef<State>();
let isFirstStateInitialization = false;
if (
initializedStateRef.current === undefined ||
currentState !== previousStateRef.current
@@ -272,16 +279,21 @@ export default function useNavigationBuilder<
// We also need to re-initialize it if the state passed from parent was changed (maybe due to reset)
// Otherwise assume that the state was provided as initial state
// So we need to rehydrate it to make it usable
initializedStateRef.current =
currentState === undefined || !isStateValid(currentState)
? router.getInitialState({
routeNames,
routeParamList,
})
: router.getRehydratedState(currentState as PartialState<State>, {
routeNames,
routeParamList,
});
if (currentState === undefined || !isStateValid(currentState)) {
isFirstStateInitialization = true;
initializedStateRef.current = router.getInitialState({
routeNames,
routeParamList,
});
} else {
initializedStateRef.current = router.getRehydratedState(
currentState as PartialState<State>,
{
routeNames,
routeParamList,
}
);
}
}
React.useEffect(() => {
@@ -307,16 +319,14 @@ export default function useNavigationBuilder<
}
if (
previousRouteRef.current &&
route &&
route.params &&
typeof route.params.screen === 'string' &&
route.params !== previousRouteRef.current.params
typeof route?.params?.screen === 'string' &&
(route.params !== previousNestedParamsRef.current ||
(route.params.initial === false && isFirstStateInitialization))
) {
// If the route was updated with new name and/or params, we should navigate there
// The update should be limited to current navigator only, so we call the router manually
const updatedState = router.getStateForAction(
state,
nextState,
CommonActions.navigate(route.params.screen, route.params.params),
{
routeNames,
@@ -330,17 +340,17 @@ export default function useNavigationBuilder<
routeNames,
routeParamList,
})
: state;
: nextState;
}
const shouldUpdate = state !== nextState;
React.useEffect(() => {
useScheduleUpdate(() => {
if (shouldUpdate) {
// If the state needs to be updated, we'll schedule an update with React
// If the state needs to be updated, we'll schedule an update
setState(nextState);
}
}, [nextState, setState, shouldUpdate]);
});
// The up-to-date state will come in next render, but we don't need to wait for it
// We can't use the outdated state since the screens have changed, which will cause error due to mismatched config
@@ -367,34 +377,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 });

View File

@@ -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;
}

View File

@@ -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));
});

View File

@@ -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),
})),

View File

@@ -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) {

View File

@@ -0,0 +1,32 @@
import * as React from 'react';
const MISSING_CONTEXT_ERROR = "Couldn't find a schedule context.";
export const ScheduleUpdateContext = React.createContext<{
scheduleUpdate: (callback: () => void) => void;
flushUpdates: () => void;
}>({
scheduleUpdate() {
throw new Error(MISSING_CONTEXT_ERROR);
},
flushUpdates() {
throw new Error(MISSING_CONTEXT_ERROR);
},
});
/**
* When screen config changes, we want to update the navigator in the same update phase.
* However, navigation state is in the root component and React won't let us update it from a child.
* This is a workaround for that, the scheduled update is stored in the ref without actually calling setState.
* It lets all subsequent updates access the latest state so it stays correct.
* Then we call setState during after the component updates.
*/
export default function useScheduleUpdate(callback: () => void) {
const { scheduleUpdate, flushUpdates } = React.useContext(
ScheduleUpdateContext
);
scheduleUpdate(callback);
React.useEffect(flushUpdates);
}

View File

@@ -2,8 +2,12 @@ import * as React from 'react';
const UNINTIALIZED_STATE = {};
/**
* This is definitely not compatible with concurrent mode, but we don't have a solution for sync state yet.
*/
export default function useSyncState<T>(initialState?: (() => T) | T) {
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
const isSchedulingRef = React.useRef(false);
if (stateRef.current === UNINTIALIZED_STATE) {
stateRef.current =
@@ -11,7 +15,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
typeof initialState === 'function' ? initialState() : initialState;
}
const [state, setTrackingState] = React.useState(stateRef.current);
const [trackingState, setTrackingState] = React.useState(stateRef.current);
const getState = React.useCallback(() => stateRef.current, []);
@@ -21,8 +25,35 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
}
stateRef.current = state;
setTrackingState(state);
if (!isSchedulingRef.current) {
setTrackingState(state);
}
}, []);
return [state, getState, setState] as const;
const scheduleUpdate = React.useCallback((callback: () => void) => {
isSchedulingRef.current = true;
try {
callback();
} finally {
isSchedulingRef.current = false;
}
}, []);
const flushUpdates = React.useCallback(() => {
// Make sure that the tracking state is up-to-date.
// We call it unconditionally, but React should skip the update if state is unchanged.
setTrackingState(stateRef.current);
}, []);
// If we're rendering and the tracking state is out of date, update it immediately
// This will make sure that our updates are applied as early as possible.
if (trackingState !== stateRef.current) {
setTrackingState(stateRef.current);
}
const state = stateRef.current;
return [state, getState, setState, scheduleUpdate, flushUpdates] as const;
}

View File

@@ -3,6 +3,67 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.1...@react-navigation/drawer@5.5.0) (2020-04-17)
### Bug Fixes
* fix drawer not closing on web ([e2bcf51](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/e2bcf5168c389833eaaeadb4b8794aaea4a66d17)), closes [#6759](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6759)
* webkit style error in overlay ([821343f](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/821343fed38577cfdc87a78f13f991d5760bf8f5))
### Features
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.4.0...@react-navigation/drawer@5.4.1) (2020-04-08)
### Bug Fixes
* don't hide content from accessibility with permanent drawer ([cb2f157](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/cb2f157a561a2ce3f073eb4ccb567532c77bd869)), closes [#7976](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7976)
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.4...@react-navigation/drawer@5.4.0) (2020-03-30)
### Bug Fixes
* disable only swipe gesture on safari ([105da6a](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/105da6ab2fe69847b676c4d4117638212cda1f9a))
### Features
* add swipeEnabled option to disable swipe gesture in drawer ([#7834](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7834)) ([ac7f972](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/ac7f972e922a82cd32d943356941d100b68bd8b0))
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.3...@react-navigation/drawer@5.3.4) (2020-03-23)
**Note:** Version bump only for package @react-navigation/drawer
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.2...@react-navigation/drawer@5.3.3) (2020-03-22)
**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)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "5.3.2",
"version": "5.5.0",
"keywords": [
"react-native-component",
"react-component",
@@ -40,7 +40,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.1",
"@react-navigation/native": "^5.1.6",
"@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",

View File

@@ -22,7 +22,7 @@ export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
/**
* Types
*/
export {
export type {
DrawerNavigationOptions,
DrawerNavigationProp,
DrawerContentOptions,

View File

@@ -21,6 +21,7 @@ type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
function DrawerNavigator({
initialRouteName,
openByDefault,
backBehavior,
children,
screenOptions,
@@ -33,6 +34,7 @@ function DrawerNavigator({
DrawerNavigationEventMap
>(DrawerRouter, {
initialRouteName,
openByDefault,
backBehavior,
children,
screenOptions,

View File

@@ -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`.

View File

@@ -14,7 +14,8 @@ import {
import {
PanGestureHandler,
TapGestureHandler,
State,
State as GestureState,
TapGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import Overlay from './Overlay';
@@ -80,6 +81,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';
@@ -94,32 +96,15 @@ type Props = {
renderDrawerContent: Renderer;
renderSceneContent: Renderer;
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
dimensions: { width: number; height: number };
};
/**
* Disables the pan gesture by default on Apple devices in the browser.
* https://stackoverflow.com/a/9039885
*/
function shouldEnableGesture(): boolean {
if (
Platform.OS === 'web' &&
typeof navigator !== 'undefined' &&
typeof window !== 'undefined'
) {
const isWebAppleDevice =
/iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
return !isWebAppleDevice;
}
return true;
}
export default class DrawerView extends React.PureComponent<Props> {
export default class DrawerView extends React.Component<Props> {
static defaultProps = {
drawerPostion: I18nManager.isRTL ? 'left' : 'right',
drawerType: 'front',
gestureEnabled: shouldEnableGesture(),
gestureEnabled: true,
swipeEnabled: Platform.OS !== 'web',
swipeEdgeWidth: 32,
swipeVelocityThreshold: 500,
keyboardDismissMode: 'on-drag',
@@ -138,16 +123,11 @@ export default class DrawerView extends React.PureComponent<Props> {
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' ||
@@ -217,30 +197,54 @@ export default class DrawerView extends React.PureComponent<Props> {
}
};
private getDrawerWidth = (): number => {
const { drawerStyle, dimensions } = this.props;
const { width } = StyleSheet.flatten(drawerStyle);
if (typeof width === 'string' && width.endsWith('%')) {
// Try to calculate width if a percentage is given
const percentage = Number(width.replace(/%$/, ''));
if (Number.isFinite(percentage)) {
return dimensions.width * (percentage / 100);
}
}
return typeof width === 'number' ? width : 0;
};
private clock = new Clock();
private interactionHandle: number | undefined;
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);
private isSwiping = new Value<Binary>(FALSE);
private gestureState = new Value<number>(State.UNDETERMINED);
private initialDrawerWidth = this.getDrawerWidth();
private gestureState = new Value<number>(GestureState.UNDETERMINED);
private touchX = new Value<number>(0);
private velocityX = new Value<number>(0);
private gestureX = new Value<number>(0);
private offsetX = new Value<number>(0);
private position = new Value<number>(0);
private position = new Value<number>(
this.props.open
? this.initialDrawerWidth *
(this.props.drawerPosition === 'right'
? DIRECTION_RIGHT
: DIRECTION_LEFT)
: 0
);
private containerWidth = new Value<number>(0);
private drawerWidth = new Value<number>(0);
private drawerOpacity = new Value<number>(0);
private containerWidth = new Value<number>(this.props.dimensions.width);
private drawerWidth = new Value<number>(this.initialDrawerWidth);
private drawerOpacity = new Value<number>(
this.initialDrawerWidth || this.props.drawerType === 'permanent' ? 1 : 0
);
private drawerPosition = new Value<number>(
this.props.drawerPosition === 'right' ? DIRECTION_RIGHT : DIRECTION_LEFT
);
@@ -418,12 +422,12 @@ export default class DrawerView extends React.PureComponent<Props> {
onChange(
this.gestureState,
cond(
eq(this.gestureState, State.ACTIVE),
eq(this.gestureState, GestureState.ACTIVE),
call([], this.handleStartInteraction)
)
),
cond(
eq(this.gestureState, State.ACTIVE),
eq(this.gestureState, GestureState.ACTIVE),
[
cond(this.isSwiping, NOOP, [
// We weren't dragging before, set it to true
@@ -507,14 +511,28 @@ export default class DrawerView extends React.PureComponent<Props> {
},
]);
private handleTapStateChange = event([
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(eq(s, State.ACTIVE), set(this.manuallyTriggerSpring, TRUE)),
},
},
]);
private handleTapStateChange =
Platform.OS === 'web'
? // FIXME: Drawer doesn't close on Web with the same code that we use for native
({ nativeEvent }: TapGestureHandlerStateChangeEvent) => {
if (
nativeEvent.state === GestureState.END &&
nativeEvent.oldState === GestureState.ACTIVE
) {
this.toggleDrawer(false);
}
}
: event([
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(
eq(s, GestureState.ACTIVE),
set(this.manuallyTriggerSpring, TRUE)
),
},
},
]);
private handleContainerLayout = (e: LayoutChangeEvent) =>
this.containerWidth.setValue(e.nativeEvent.layout.width);
@@ -554,6 +572,7 @@ export default class DrawerView extends React.PureComponent<Props> {
const {
open,
gestureEnabled,
swipeEnabled,
drawerPosition,
drawerType,
swipeEdgeWidth,
@@ -569,9 +588,15 @@ export default class DrawerView extends React.PureComponent<Props> {
const isOpen = drawerType === 'permanent' ? true : open;
const isRight = drawerPosition === 'right';
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
const contentTranslateX =
drawerType === 'front' || drawerType === 'permanent'
? 0
: this.translateX;
const drawerTranslateX =
drawerType === 'back'
drawerType === 'permanent'
? 0
: drawerType === 'back'
? I18nManager.isRTL
? multiply(
sub(this.containerWidth, this.drawerWidth),
@@ -605,7 +630,7 @@ 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
@@ -621,31 +646,38 @@ export default class DrawerView extends React.PureComponent<Props> {
<Animated.View
style={[
styles.content,
drawerType !== 'permanent' && {
transform: [{ translateX: contentTranslateX }],
},
{ transform: [{ translateX: contentTranslateX }] },
sceneContainerStyle as any,
]}
>
<View
accessibilityElementsHidden={isOpen}
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
importantForAccessibility={
isOpen ? 'no-hide-descendants' : 'auto'
isOpen && drawerType !== 'permanent'
? 'no-hide-descendants'
: 'auto'
}
style={styles.content}
>
{renderSceneContent({ progress })}
</View>
{// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : (
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={progress} style={overlayStyle} />
</TapGestureHandler>
)}
{
// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : (
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={progress} style={overlayStyle} />
</TapGestureHandler>
)
}
</Animated.View>
<Animated.Code
// This is needed to make sure that container width updates with `setValue`
// Without this, it won't update when not used in styles
exec={this.containerWidth}
/>
{drawerType === 'permanent' ? null : (
<Animated.Code
exec={block([
@@ -659,11 +691,15 @@ export default class DrawerView extends React.PureComponent<Props> {
/>
)}
<Animated.View
accessibilityViewIsModal={isOpen}
accessibilityViewIsModal={isOpen && drawerType !== 'permanent'}
removeClippedSubviews={Platform.OS !== 'ios'}
onLayout={this.handleDrawerLayout}
style={[
styles.container,
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
},
drawerType === 'permanent'
? // Without this, the `left`/`right` values don't get reset
isRight
@@ -671,10 +707,6 @@ export default class DrawerView extends React.PureComponent<Props> {
: { left: 0 }
: [
styles.nonPermanent,
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
},
isRight ? { right: offset } : { left: offset },
{ zIndex: drawerType === 'back' ? -1 : 0 },
],

View File

@@ -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,

View File

@@ -89,17 +89,15 @@ export default function DrawerView({
sceneContainerStyle,
}: Props) {
const [loaded, setLoaded] = React.useState([state.index]);
const [drawerWidth, setDrawerWidth] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
return getDefaultDrawerWidth({ height, width });
});
const [dimensions, setDimensions] = React.useState(() =>
Dimensions.get('window')
);
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
const { colors } = useTheme();
const isDrawerOpen = state.history.some(it => it.type === 'drawer');
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
const handleDrawerOpen = React.useCallback(() => {
navigation.dispatch({
@@ -141,13 +139,13 @@ export default function DrawerView({
}, [handleDrawerClose, isDrawerOpen, navigation, state.key]);
React.useEffect(() => {
const updateWidth = ({ window }: { window: ScaledSize }) => {
setDrawerWidth(getDefaultDrawerWidth(window));
const updateDimensions = ({ window }: { window: ScaledSize }) => {
setDimensions(window);
};
Dimensions.addEventListener('change', updateWidth);
Dimensions.addEventListener('change', updateDimensions);
return () => Dimensions.removeEventListener('change', updateWidth);
return () => Dimensions.removeEventListener('change', updateDimensions);
}, []);
if (!loaded.includes(state.index)) {
@@ -200,7 +198,7 @@ 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}>
@@ -210,9 +208,10 @@ export default function DrawerView({
<Drawer
open={isDrawerOpen}
gestureEnabled={gestureEnabled}
swipeEnabled={swipeEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={ref => {
onGestureRef={(ref) => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
@@ -224,7 +223,10 @@ export default function DrawerView({
sceneContainerStyle,
]}
drawerStyle={[
{ width: drawerWidth, backgroundColor: colors.card },
{
width: getDefaultDrawerWidth(dimensions),
backgroundColor: colors.card,
},
drawerType === 'permanent' &&
(drawerPosition === 'left'
? {
@@ -246,6 +248,7 @@ export default function DrawerView({
renderSceneContent={renderContent}
keyboardDismissMode={keyboardDismissMode}
drawerPostion={drawerPosition}
dimensions={dimensions}
/>
</DrawerOpenContext.Provider>
</DrawerGestureContext.Provider>

View File

@@ -29,22 +29,24 @@ const Overlay = React.forwardRef(function Overlay(
<Animated.View
{...props}
ref={ref}
style={[styles.overlay, animatedStyle, style]}
style={[styles.overlay, overlayStyle, animatedStyle, style]}
/>
);
});
const overlayStyle = Platform.select<Record<string, string>>({
web: {
// Disable touch highlight on mobile Safari.
// WebkitTapHighlightColor must be used outside of StyleSheet.create because react-native-web will omit the property.
WebkitTapHighlightColor: 'transparent',
},
default: {},
});
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
...Platform.select({
web: {
// Disable touch highlight on mobile Safari.
WebkitTapHighlightColor: 'transparent',
},
default: {},
}),
},
});

View File

@@ -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

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.8...@react-navigation/material-bottom-tabs@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.7...@react-navigation/material-bottom-tabs@5.1.8) (2020-04-08)
### Bug Fixes
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.6...@react-navigation/material-bottom-tabs@5.1.7) (2020-03-30)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.5...@react-navigation/material-bottom-tabs@5.1.6) (2020-03-23)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.4...@react-navigation/material-bottom-tabs@5.1.5) (2020-03-22)
**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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-bottom-tabs",
"description": "Integration for bottom navigation component from react-native-paper",
"version": "5.1.4",
"version": "5.1.9",
"keywords": [
"react-native-component",
"react-component",
@@ -36,16 +36,16 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.1",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@types/react-native-vector-icons": "^6.4.5",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-paper": "^3.6.0",
"react-native-paper": "^3.7.0",
"react-native-vector-icons": "^6.6.0",
"typescript": "^3.7.5"
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",

View File

@@ -11,7 +11,7 @@ export { default as MaterialBottomTabView } from './views/MaterialBottomTabView'
/**
* Types
*/
export {
export type {
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationProp,
} from './types';

View File

@@ -3,6 +3,49 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.9](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.8...@react-navigation/material-top-tabs@5.1.9) (2020-04-17)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.8](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.7...@react-navigation/material-top-tabs@5.1.8) (2020-04-08)
### Bug Fixes
* mark type exports for all packages ([b71de6c](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/b71de6cc799143f1d0e8a0cfcc34f0a2381f9840))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.6...@react-navigation/material-top-tabs@5.1.7) (2020-03-30)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.5...@react-navigation/material-top-tabs@5.1.6) (2020-03-23)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.4...@react-navigation/material-top-tabs@5.1.5) (2020-03-22)
**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

View File

@@ -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.4",
"version": "5.1.9",
"keywords": [
"react-native-component",
"react-component",
@@ -39,7 +39,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.1",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
@@ -47,8 +47,8 @@
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.7.0",
"react-native-tab-view": "^2.13.0",
"typescript": "^3.7.5"
"react-native-tab-view": "^2.14.0",
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",

View File

@@ -12,7 +12,7 @@ export { default as MaterialTopTabBar } from './views/MaterialTopTabBar';
/**
* Types
*/
export {
export type {
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationProp,
MaterialTopTabBarProps,

View File

@@ -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,

View File

@@ -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,

View File

@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.5...@react-navigation/native@5.1.6) (2020-04-17)
### Bug Fixes
* handle in-page go back when there's no history ([6bdf6ae](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/6bdf6ae4ed0f83ac1deb3172d9075a6a2adbbe11)), closes [#7852](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/7852)
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.4...@react-navigation/native@5.1.5) (2020-04-08)
**Note:** Version bump only for package @react-navigation/native
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.3...@react-navigation/native@5.1.4) (2020-03-30)
**Note:** Version bump only for package @react-navigation/native
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.2...@react-navigation/native@5.1.3) (2020-03-23)
### Bug Fixes
* add info about android launchMode in useLinking error ([d94e43c](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d94e43c3c8625b209a5c883b8cb560496d07fda7))
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.1...@react-navigation/native@5.1.2) (2020-03-22)
**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

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/native",
"description": "React Native integration for React Navigation",
"version": "5.1.1",
"version": "5.1.6",
"keywords": [
"react-native",
"react-navigation",
@@ -31,7 +31,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.2.3"
"@react-navigation/core": "^5.3.4"
},
"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": "*",

View File

@@ -26,7 +26,7 @@ type Props = NavigationContainerProps & {
*/
const NavigationContainer = React.forwardRef(function NavigationContainer(
{ theme = DefaultTheme, ...rest }: Props,
ref: React.Ref<NavigationContainerRef>
ref?: React.Ref<NavigationContainerRef | null>
) {
const refContainer = React.useRef<NavigationContainerRef>(null);

View File

@@ -1,4 +1,4 @@
export default function() {
export default function () {
throw new Error(
"'NavigationNativeContainer' has been renamed to 'NavigationContainer"
);

View File

@@ -41,7 +41,7 @@ export type LinkingOptions = {
*/
getStateFromPath?: typeof getStateFromPathDefault;
/**
* Custom function to conver the state object to a valid URL (advanced).
* Custom function to convert the state object to a valid URL (advanced).
*/
getPathFromState?: typeof getPathFromStateDefault;
};

View File

@@ -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;

View File

@@ -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;
@@ -224,14 +224,14 @@ export default function useLinking(
let index = history.state?.index ?? 0;
if (previousStateLength === stateLength) {
// If no new enrties were added to history in our navigation state, we want to replaceState
// If no new entries were added to history in our navigation state, we want to replaceState
if (location.pathname + location.search !== path) {
history.replaceState({ index }, '', path);
previousHistoryIndexRef.current = index;
}
} else if (stateLength > previousStateLength) {
// If new enrties were added, pushState until we have same length
// This won't be accurate if multiple enrties were added at once, but that's the best we can do
// If new entries were added, pushState until we have same length
// This won't be accurate if multiple entries were added at once, but that's the best we can do
for (let i = 0, l = stateLength - previousStateLength; i < l; i++) {
index++;
history.pushState({ index }, '', path);
@@ -239,13 +239,27 @@ export default function useLinking(
previousHistoryIndexRef.current = index;
} else if (previousStateLength > stateLength) {
const delta = previousStateLength - stateLength;
const delta = Math.min(
previousStateLength - stateLength,
// We need to keep at least one item in the history
// Otherwise we'll exit the page
previousHistoryIndexRef.current - 1
);
// We need to set this to ignore the `popstate` event
pendingIndexChangeRef.current = index - delta;
if (delta > 0) {
// We need to set this to ignore the `popstate` event
pendingIndexChangeRef.current = index - delta;
// If new enrties were removed, go back so that we have same length
history.go(-delta);
// If new entries were removed, go back so that we have same length
history.go(-delta);
} else {
// We're not going back in history, but the navigation state changed
// The URL probably also changed, so we need to re-sync the URL
if (location.pathname + location.search !== path) {
history.replaceState({ index }, '', path);
previousHistoryIndexRef.current = index;
}
}
}
});

View File

@@ -3,6 +3,52 @@
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/routers/compare/@react-navigation/routers@5.3.0...@react-navigation/routers@5.4.0) (2020-04-17)
### Features
* add openByDefault option to drawer ([36689e2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/36689e24c21b474692bb7ecd0b901c8afbbe9a20))
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.2.1...@react-navigation/routers@5.3.0) (2020-04-08)
### Bug Fixes
* separate normal exports and type exports ([303f0b7](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/303f0b78a5ab717b2d606cd9c8a22f3dae051f0f))
### Features
* make replace bubble up ([ba1f405](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/ba1f4051299ad86001592b8d3601c16fece159df))
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.2.0...@react-navigation/routers@5.2.1) (2020-03-30)
**Note:** Version bump only for package @react-navigation/routers
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.1.1...@react-navigation/routers@5.2.0) (2020-03-22)
### 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)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/routers",
"description": "Routers to help build custom navigators",
"version": "5.1.1",
"version": "5.4.0",
"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",

View File

@@ -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;
}

View File

@@ -1,4 +1,4 @@
import shortid from 'shortid';
import { nanoid } from 'nanoid/non-secure';
import {
PartialState,
CommonNavigationAction,
@@ -21,7 +21,9 @@ export type DrawerActionType =
target?: string;
};
export type DrawerRouterOptions = TabRouterOptions;
export type DrawerRouterOptions = TabRouterOptions & {
openByDefault?: boolean;
};
export type DrawerNavigationState = Omit<
TabNavigationState,
@@ -71,7 +73,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,14 +93,18 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
return {
...state,
history: state.history.filter(it => it.type !== 'drawer'),
history: state.history.filter((it) => it.type !== 'drawer'),
};
};
export default function DrawerRouter(
options: DrawerRouterOptions
): Router<DrawerNavigationState, DrawerActionType | CommonNavigationAction> {
const router = (TabRouter(options) as unknown) as Router<
export default function DrawerRouter({
openByDefault,
...rest
}: DrawerRouterOptions): Router<
DrawerNavigationState,
DrawerActionType | CommonNavigationAction
> {
const router = (TabRouter(rest) as unknown) as Router<
DrawerNavigationState,
TabActionType | CommonNavigationAction
>;
@@ -109,13 +115,17 @@ export default function DrawerRouter(
type: 'drawer',
getInitialState({ routeNames, routeParamList }) {
const state = router.getInitialState({ routeNames, routeParamList });
let state = router.getInitialState({ routeNames, routeParamList });
if (openByDefault) {
state = openDrawer(state);
}
return {
...state,
stale: false,
type: 'drawer',
key: `drawer-${shortid()}`,
key: `drawer-${nanoid()}`,
};
},
@@ -136,7 +146,7 @@ export default function DrawerRouter(
return {
...state,
type: 'drawer',
key: `drawer-${shortid()}`,
key: `drawer-${nanoid()}`,
};
},
@@ -162,8 +172,14 @@ export default function DrawerRouter(
return openDrawer(state);
case 'GO_BACK':
if (isDrawerOpen(state)) {
return closeDrawer(state);
if (openByDefault) {
if (!isDrawerOpen(state)) {
return openDrawer(state);
}
} else {
if (isDrawerOpen(state)) {
return closeDrawer(state);
}
}
return router.getStateForAction(state, action, options);

View File

@@ -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;
@@ -219,9 +219,10 @@ export default function StackRouter(options: StackRouterOptions) {
switch (action.type) {
case 'REPLACE': {
const index = action.source
? state.routes.findIndex(r => r.key === action.source)
: state.index;
const index =
action.target === state.key && action.source
? state.routes.findIndex((r) => r.key === action.source)
: state.index;
if (index === -1) {
return null;
@@ -238,7 +239,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 +264,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 +284,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) {

View File

@@ -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) {

View File

@@ -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' } },

View File

@@ -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' });

View File

@@ -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' });
@@ -720,7 +720,7 @@ it('replaces focused screen with replace', () => {
});
});
it('replaces source screen with replace', () => {
it('replaces active screen with replace', () => {
const router = StackRouter({});
const options = {
routeNames: ['foo', 'bar', 'baz', 'qux'],
@@ -754,8 +754,8 @@ it('replaces source screen with replace', () => {
index: 1,
routes: [
{ key: 'foo', name: 'foo' },
{ key: 'bar', name: 'bar', params: { fruit: 'orange' } },
{ key: 'qux-test', name: 'qux', params: { answer: 42 } },
{ key: 'baz', name: 'baz' },
],
routeNames: ['foo', 'bar', 'baz', 'qux'],
});
@@ -785,6 +785,7 @@ it("doesn't handle replace if source key isn't present", () => {
{
...StackActions.replace('qux', { answer: 42 }),
source: 'magic',
target: 'root',
},
options
)

Some files were not shown because too many files have changed in this diff Show More