Compare commits

...

21 Commits

Author SHA1 Message Date
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
Satyajit Sahoo
bc9b044fb3 chore: publish
- @react-navigation/bottom-tabs@5.2.2
 - @react-navigation/compat@5.1.4
 - @react-navigation/core@5.2.3
 - @react-navigation/drawer@5.3.2
 - @react-navigation/material-bottom-tabs@5.1.4
 - @react-navigation/material-top-tabs@5.1.4
 - @react-navigation/native@5.1.1
 - @react-navigation/stack@5.2.2
2020-03-19 19:48:37 +01:00
Alexey Vlasenko
f24d3a3461 fix: fix closing stack using inverted gesture. (#7824)
Co-authored-by: Satyajit Sahoo <satyajit.happy@gmail.com>
2020-03-19 19:32:29 +01:00
Satyajit Sahoo
3df65e2819 fix: initialize height and width to zero if undefined
closes #6789
2020-03-19 19:03:23 +01:00
Satyajit Sahoo
5c4afc5cb4 fix: close drawer on pressing Esc on web
closes #6745
2020-03-19 18:51:16 +01:00
Satyajit Sahoo
d5bb357053 chore: temporarily disables devtools until we add a public API
closes #7726
2020-03-19 18:39:04 +01:00
Satyajit Sahoo
b1fe73097f fix: only dismiss previously focused input on page change. closes #6918 2020-03-19 18:30:54 +01:00
Satyajit Sahoo
49f6fed6d3 fix: fix blank page if stack was inside display: none before 2020-03-19 18:11:55 +01:00
Satyajit Sahoo
b1a65fc73e fix: don't use react-native-screens on web
seems `react-native-screens` doesn't handle active screens properly and shows a blank page instead on web when a number is specified in the `active` prop.

closes #7485
2020-03-19 17:28:35 +01:00
Noemi Rozpara
3ea8eec432 fix: fix permanent sidebar position (#7830) 2020-03-19 11:44:13 +01:00
Satyajit Sahoo
00e0f05190 chore: publish
- @react-navigation/drawer@5.3.1
2020-03-17 20:13:03 +01:00
Satyajit Sahoo
193c344ba5 refactor: fix useIsDrawerOpen hook 2020-03-17 19:22:12 +01:00
Satyajit Sahoo
358d9e9feb chore: publish
- @react-navigation/bottom-tabs@5.2.1
 - @react-navigation/compat@5.1.3
 - @react-navigation/drawer@5.3.0
 - @react-navigation/material-bottom-tabs@5.1.3
 - @react-navigation/material-top-tabs@5.1.3
 - @react-navigation/native@5.1.0
 - @react-navigation/stack@5.2.1
2020-03-17 14:37:21 +01:00
Satyajit Sahoo
6a5d0a035a feat: add permanent drawer type (#7818)
Co-authored-by: NoemiRozpara <nrozpara@gmail.com>
2020-03-17 14:11:00 +01:00
89 changed files with 898 additions and 376 deletions

View File

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

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

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

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

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

@@ -6,6 +6,8 @@ import {
Platform,
StatusBar,
I18nManager,
Dimensions,
ScaledSize,
} from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens';
@@ -196,10 +198,24 @@ export default function App() {
};
}, [theme.colors, theme.dark]);
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);
}, []);
if (!isReady) {
return null;
}
const isLargeScreen = dimensions.width > 900;
return (
<PaperProvider theme={paperTheme}>
{Platform.OS === 'ios' && (
@@ -208,7 +224,7 @@ export default function App() {
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
onStateChange={(state) =>
AsyncStorage.setItem(
NAVIGATION_PERSISTENCE_KEY,
JSON.stringify(state)
@@ -216,7 +232,7 @@ export default function App() {
}
theme={theme}
>
<Drawer.Navigator>
<Drawer.Navigator drawerType={isLargeScreen ? 'permanent' : undefined}>
<Drawer.Screen
name="Root"
options={{
@@ -240,13 +256,15 @@ export default function App() {
name="Home"
options={{
title: 'Examples',
headerLeft: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
headerLeft: isLargeScreen
? undefined
: () => (
<Appbar.Action
color={theme.colors.text}
icon="menu"
onPress={() => navigation.toggleDrawer()}
/>
),
}}
>
{({
@@ -280,12 +298,12 @@ export default function App() {
theme.dark ? 'light' : 'dark'
);
setTheme(t => (t.dark ? DefaultTheme : DarkTheme));
setTheme((t) => (t.dark ? DefaultTheme : DarkTheme));
}}
/>
<Divider />
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
(name) => (
<List.Item
key={name}
title={SCREENS[name].title}
@@ -297,7 +315,7 @@ export default function App() {
)}
</Stack.Screen>
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
name => (
(name) => (
<Stack.Screen
key={name}
name={name}

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,

View File

@@ -43,7 +43,7 @@
"husky": "^4.2.3",
"jest": "^25.1.0",
"lerna": "^3.20.2",
"prettier": "^1.19.1",
"prettier": "^2.0.1",
"typescript": "^3.7.5"
},
"resolutions": {

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.2...@react-navigation/bottom-tabs@5.2.3) (2020-03-22)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.1...@react-navigation/bottom-tabs@5.2.2) (2020-03-19)
### Bug Fixes
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7485)
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/6789)
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.0...@react-navigation/bottom-tabs@5.2.1) (2020-03-17)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.1.1...@react-navigation/bottom-tabs@5.2.0) (2020-03-16)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.2.0",
"version": "5.2.3",
"keywords": [
"react-native-component",
"react-component",
@@ -35,7 +35,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/color": "^3.0.1",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",

View File

@@ -51,7 +51,12 @@ export default function BottomTabBar({
}: Props) {
const { colors } = useTheme();
const [dimensions, setDimensions] = React.useState(Dimensions.get('window'));
const [dimensions, setDimensions] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
return { height, width };
});
const [layout, setLayout] = React.useState({
height: 0,
width: dimensions.width,
@@ -116,7 +121,7 @@ export default function BottomTabBar({
const handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
setLayout(layout => {
setLayout((layout) => {
if (height === layout.height && width === layout.width) {
return layout;
} else {

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

@@ -1,6 +1,5 @@
import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { Screen, screensEnabled } from 'react-native-screens';
@@ -10,12 +9,14 @@ type Props = {
style?: any;
};
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled?.()) {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;
}
@@ -24,7 +25,13 @@ export default class ResourceSavingScene extends React.Component<Props> {
return (
<View
style={[styles.container, style, { opacity: isVisible ? 1 : 0 }]}
style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused

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,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.4...@react-navigation/compat@5.1.5) (2020-03-22)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.3...@react-navigation/compat@5.1.4) (2020-03-19)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.2...@react-navigation/compat@5.1.3) (2020-03-17)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.1...@react-navigation/compat@5.1.2) (2020-03-16)
**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.2",
"version": "5.1.5",
"license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": {
@@ -26,7 +26,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/react": "^16.9.23",
"react": "~16.9.0",
"typescript": "^3.7.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

@@ -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,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.3...@react-navigation/core@5.3.0) (2020-03-22)
### Bug Fixes
* return correct value for isFocused after changing screens ([5b15c71](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/5b15c7164f5503f2f0d51006a3f23bd0c58fd9b7)), closes [#7843](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/7843)
### Features
* support function in listeners prop ([3709e65](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3709e652f41a16c2c2b05d5dbbe1da2017ba2c3f))
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.2...@react-navigation/core@5.2.3) (2020-03-19)
**Note:** Version bump only for package @react-navigation/core
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.2.1...@react-navigation/core@5.2.2) (2020-03-16)
**Note:** Version bump only for package @react-navigation/core

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.2.2",
"version": "5.3.0",
"keywords": [
"react",
"react-native",
@@ -29,7 +29,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.1.1",
"@react-navigation/routers": "^5.2.0",
"escape-string-regexp": "^2.0.0",
"query-string": "^6.11.1",
"react-is": "^16.13.0",

View File

@@ -73,7 +73,7 @@ const getPartialState = (
return {
...partialState,
stale: true,
routes: state.routes.map(route => {
routes: state.routes.map((route) => {
if (route.state === undefined) {
return route as Route<string> & {
state?: PartialState<NavigationState>;
@@ -136,6 +136,7 @@ const BaseNavigationContainer = React.forwardRef(
);
const { trackState, trackAction } = useDevTools({
enabled: false,
name: '@react-navigation',
reset,
state,
@@ -155,7 +156,7 @@ const BaseNavigationContainer = React.forwardRef(
throw new Error(NOT_INITIALIZED_ERROR);
}
listeners[0](navigation => navigation.dispatch(action));
listeners[0]((navigation) => navigation.dispatch(action));
};
const canGoBack = () => {
@@ -163,7 +164,7 @@ const BaseNavigationContainer = React.forwardRef(
return false;
}
const { result, handled } = listeners[0](navigation =>
const { result, handled } = listeners[0]((navigation) =>
navigation.canGoBack()
);

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

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

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

@@ -119,7 +119,7 @@ it('sets options with screenOptions prop as an object', () => {
return (
<>
{state.routes.map(route => {
{state.routes.map((route) => {
const { render, options } = descriptors[route.key];
return (
@@ -179,7 +179,7 @@ it('sets options with screenOptions prop as a fuction', () => {
return (
<>
{state.routes.map(route => {
{state.routes.map((route) => {
const { render, options } = descriptors[route.key];
return (
@@ -273,7 +273,7 @@ it('sets initial options with setOptions', () => {
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
{(props) => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
@@ -338,7 +338,7 @@ it('updates options with setOptions', () => {
<BaseNavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
{(props) => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>

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();
@@ -106,7 +106,7 @@ it('fires focus and blur events in nested navigator', () => {
React.useImperativeHandle(ref, () => navigation, [navigation]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const firstFocusCallback = jest.fn();
@@ -376,7 +376,7 @@ it('fires custom events added with addListener', () => {
state,
]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const firstCallback = jest.fn();
@@ -456,7 +456,7 @@ it("doesn't call same listener multiple times with addListener", () => {
state,
]);
return state.routes.map(route => descriptors[route.key].render());
return state.routes.map((route) => descriptors[route.key].render());
});
const callback = jest.fn();
@@ -640,7 +640,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

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

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.

View File

@@ -8,6 +8,7 @@ import {
type State = NavigationState | PartialState<NavigationState> | undefined;
type Options = {
enabled: boolean;
name: string;
reset: (state: NavigationState) => void;
state: State;
@@ -35,10 +36,11 @@ declare global {
}
}
export default function useDevTools({ name, reset, state }: Options) {
export default function useDevTools({ name, reset, state, enabled }: Options) {
const devToolsRef = React.useRef<DevTools>();
if (
enabled &&
process.env.NODE_ENV !== 'production' &&
global.__REDUX_DEVTOOLS_EXTENSION__ &&
devToolsRef.current === undefined
@@ -56,7 +58,7 @@ export default function useDevTools({ name, reset, state }: Options) {
React.useEffect(
() =>
devTools?.subscribe(message => {
devTools?.subscribe((message) => {
if (message.type === 'DISPATCH' && message.state) {
reset(JSON.parse(message.state));
}

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

@@ -9,6 +9,7 @@ import {
RouterFactory,
PartialState,
NavigationAction,
Route,
} from '@react-navigation/routers';
import { NavigationStateContext } from './BaseNavigationContainer';
import NavigationRouteContext from './NavigationRouteContext';
@@ -103,7 +104,7 @@ const getRouteConfigsFromChildren = <
}, []);
if (process.env.NODE_ENV !== 'production') {
configs.forEach(config => {
configs.forEach((config) => {
const { name, children, component } = config as any;
if (typeof name !== 'string' || !name) {
@@ -212,7 +213,7 @@ export default function useNavigationBuilder<
return acc;
}, {});
const routeNames = routeConfigs.map(config => config.name);
const routeNames = routeConfigs.map((config) => config.name);
const routeParamList = routeNames.reduce<Record<string, object | undefined>>(
(acc, curr) => {
const { initialParams } = screens[curr];
@@ -241,12 +242,12 @@ export default function useNavigationBuilder<
}
const isStateValid = React.useCallback(
state => state.type === undefined || state.type === router.type,
(state) => state.type === undefined || state.type === router.type,
[router.type]
);
const isStateInitialized = React.useCallback(
state =>
(state) =>
state !== undefined && state.stale === false && isStateValid(state),
[isStateValid]
);
@@ -367,14 +368,16 @@ 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 target: Route<string> | undefined;
if (name) {
routeNames.push(name);
if (e.target) {
target = state.routes.find((route) => route.key === e.target);
if (target?.name) {
routeNames.push(target.name);
}
} else {
routeNames.push(...Object.keys(screens));
@@ -382,19 +385,28 @@ export default function useNavigationBuilder<
const listeners = ([] as (((e: any) => void) | undefined)[])
.concat(
...routeNames.map(name => {
...routeNames.map((name) => {
const { listeners } = screens[name];
return listeners
? Object.keys(listeners)
.filter(type => type === e.type)
.map(type => listeners[type])
.filter((type) => type === e.type)
.map((type) => {
if (typeof listeners === 'function') {
const route = target ?? state.routes[state.index];
const navigation = descriptors[route.key].navigation;
return listeners({ route: route as any, navigation })[type];
}
return listeners[type];
})
: undefined;
})
)
.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

@@ -3,6 +3,47 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.2...@react-navigation/drawer@5.3.3) (2020-03-22)
**Note:** Version bump only for package @react-navigation/drawer
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.1...@react-navigation/drawer@5.3.2) (2020-03-19)
### Bug Fixes
* close drawer on pressing Esc on web ([5c4afc5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/5c4afc5cb40c1206a9d8c40efe3cf947030da48e)), closes [#6745](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6745)
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7485)
* fix permanent sidebar position ([#7830](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7830)) ([3ea8eec](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3ea8eec4324ea82f0ed427f4662e68e1115e60ab))
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/6789)
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.3.0...@react-navigation/drawer@5.3.1) (2020-03-17)
**Note:** Version bump only for package @react-navigation/drawer
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.2.0...@react-navigation/drawer@5.3.0) (2020-03-17)
### Features
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.1.1...@react-navigation/drawer@5.2.0) (2020-03-16)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "5.2.0",
"version": "5.3.3",
"keywords": [
"react-native-component",
"react-component",
@@ -40,7 +40,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",

View File

@@ -27,8 +27,9 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
* - `front`: Traditional drawer which covers the screen with a overlay behind it.
* - `back`: The drawer is revealed behind the screen on swipe.
* - `slide`: Both the screen and the drawer slide on swipe to reveal the drawer.
* - `permanent`: A permanent drawer is shown as a sidebar.
*/
drawerType?: 'front' | 'back' | 'slide';
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
/**
* How far from the edge of the screen the swipe gesture should activate.
*/

View File

@@ -1,44 +1,16 @@
import * as React from 'react';
import { useNavigation, ParamListBase } from '@react-navigation/native';
import { DrawerNavigationProp } from '../types';
import DrawerOpenContext from '../views/DrawerOpenContext';
import DrawerOpenContext from './DrawerOpenContext';
/**
* Hook to detect if the drawer is open in a parent navigator.
*/
export default function useIsDrawerOpen() {
const navigation = useNavigation();
const isDrawerOpen = React.useContext(DrawerOpenContext);
let drawer = navigation as DrawerNavigationProp<ParamListBase>;
const drawerOpenContext = React.useContext(DrawerOpenContext);
// The screen might be inside another navigator such as stack nested in drawer
// We need to find the closest drawer navigator and add the listener there
while (drawer && drawer.dangerouslyGetState().type !== 'drawer') {
drawer = drawer.dangerouslyGetParent();
}
const [isDrawerOpen, setIsDrawerOpen] = React.useState(() =>
drawer
? Boolean(
drawer.dangerouslyGetState().history.find(it => it.type === 'drawer')
)
: false
);
React.useEffect(() => {
const unsubscribe = drawer.addListener('state', e => {
setIsDrawerOpen(
Boolean(e.data.state.history.find(it => it.type === 'drawer'))
);
});
return unsubscribe;
}, [drawer, isDrawerOpen]);
if (drawerOpenContext !== null) {
return drawerOpenContext;
if (typeof isDrawerOpen !== 'boolean') {
throw new Error(
"Couldn't find a drawer. Is your component inside a drawer navigator?"
);
}
return isDrawerOpen;

View File

@@ -18,7 +18,6 @@ import {
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import Overlay from './Overlay';
import DrawerOpenContext from './DrawerOpenContext';
const {
Clock,
@@ -69,6 +68,8 @@ const SPRING_CONFIG = {
restSpeedThreshold: 0.01,
};
const ANIMATED_ONE = new Animated.Value(1);
type Binary = 0 | 1;
type Renderer = (props: { progress: Animated.Node<number> }) => React.ReactNode;
@@ -80,7 +81,7 @@ type Props = {
onGestureRef?: (ref: PanGestureHandler | null) => void;
gestureEnabled: boolean;
drawerPosition: 'left' | 'right';
drawerType: 'front' | 'back' | 'slide';
drawerType: 'front' | 'back' | 'slide' | 'permanent';
keyboardDismissMode: 'none' | 'on-drag';
swipeEdgeWidth: number;
swipeDistanceThreshold?: number;
@@ -126,6 +127,12 @@ export default class DrawerView extends React.PureComponent<Props> {
statusBarAnimation: 'slide',
};
componentDidMount() {
if (Platform.OS === 'web') {
document?.body?.addEventListener?.('keyup', this.handleEscape);
}
}
componentDidUpdate(prevProps: Props) {
const {
open,
@@ -181,8 +188,22 @@ export default class DrawerView extends React.PureComponent<Props> {
componentWillUnmount() {
this.toggleStatusBar(false);
this.handleEndInteraction();
if (Platform.OS === 'web') {
document?.body?.removeEventListener?.('keyup', this.handleEscape);
}
}
private handleEscape = (e: KeyboardEvent) => {
const { open, onClose } = this.props;
if (e.key === 'Escape') {
if (open) {
onClose();
}
}
};
private handleEndInteraction = () => {
if (this.interactionHandle !== undefined) {
InteractionManager.clearInteractionHandle(this.interactionHandle);
@@ -545,6 +566,7 @@ export default class DrawerView extends React.PureComponent<Props> {
gestureHandlerProps,
} = this.props;
const isOpen = drawerType === 'permanent' ? true : open;
const isRight = drawerPosition === 'right';
const contentTranslateX = drawerType === 'front' ? 0 : this.translateX;
@@ -570,8 +592,10 @@ export default class DrawerView extends React.PureComponent<Props> {
const hitSlop = isRight
? // Extend hitSlop to the side of the screen when drawer is closed
// This lets the user drag the drawer from the side of the screen
{ right: 0, width: open ? undefined : swipeEdgeWidth }
: { left: 0, width: open ? undefined : swipeEdgeWidth };
{ right: 0, width: isOpen ? undefined : swipeEdgeWidth }
: { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
const progress = drawerType === 'permanent' ? ANIMATED_ONE : this.progress;
return (
<PanGestureHandler
@@ -581,64 +605,83 @@ export default class DrawerView extends React.PureComponent<Props> {
onGestureEvent={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
hitSlop={hitSlop}
enabled={gestureEnabled}
enabled={drawerType !== 'permanent' && gestureEnabled}
{...gestureHandlerProps}
>
<Animated.View
onLayout={this.handleContainerLayout}
style={styles.main}
style={[
styles.main,
{
flexDirection:
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
},
]}
>
<Animated.View
style={[
styles.content,
{
drawerType !== 'permanent' && {
transform: [{ translateX: contentTranslateX }],
},
sceneContainerStyle as any,
]}
>
<View
accessibilityElementsHidden={open}
importantForAccessibility={open ? 'no-hide-descendants' : 'auto'}
accessibilityElementsHidden={isOpen}
importantForAccessibility={
isOpen ? 'no-hide-descendants' : 'auto'
}
style={styles.content}
>
{renderSceneContent({ progress: this.progress })}
{renderSceneContent({ progress })}
</View>
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={this.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
exec={block([
onChange(this.manuallyTriggerSpring, [
cond(eq(this.manuallyTriggerSpring, TRUE), [
set(this.nextIsOpen, FALSE),
call([], () => (this.currentOpenValue = false)),
{drawerType === 'permanent' ? null : (
<Animated.Code
exec={block([
onChange(this.manuallyTriggerSpring, [
cond(eq(this.manuallyTriggerSpring, TRUE), [
set(this.nextIsOpen, FALSE),
call([], () => (this.currentOpenValue = false)),
]),
]),
]),
])}
/>
])}
/>
)}
<Animated.View
accessibilityViewIsModal={open}
accessibilityViewIsModal={isOpen}
removeClippedSubviews={Platform.OS !== 'ios'}
onLayout={this.handleDrawerLayout}
style={[
styles.container,
isRight ? { right: offset } : { left: offset },
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
zIndex: drawerType === 'back' ? -1 : 0,
},
drawerType === 'permanent'
? // Without this, the `left`/`right` values don't get reset
isRight
? { right: 0 }
: { left: 0 }
: [
styles.nonPermanent,
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
},
isRight ? { right: offset } : { left: offset },
{ zIndex: drawerType === 'back' ? -1 : 0 },
],
drawerStyle as any,
]}
>
<DrawerOpenContext.Provider value={open}>
{renderDrawerContent({ progress: this.progress })}
</DrawerOpenContext.Provider>
{renderDrawerContent({ progress })}
</Animated.View>
</Animated.View>
</PanGestureHandler>
@@ -649,11 +692,13 @@ export default class DrawerView extends React.PureComponent<Props> {
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
maxWidth: '100%',
},
nonPermanent: {
position: 'absolute',
top: 0,
bottom: 0,
width: '80%',
maxWidth: '100%',
},
content: {
flex: 1,

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

@@ -32,6 +32,7 @@ import {
DrawerNavigationHelpers,
DrawerContentComponentProps,
} from '../types';
import DrawerOpenContext from '../utils/DrawerOpenContext';
import DrawerPositionContext from '../utils/DrawerPositionContext';
type Props = DrawerNavigationConfig & {
@@ -88,15 +89,17 @@ export default function DrawerView({
sceneContainerStyle,
}: Props) {
const [loaded, setLoaded] = React.useState([state.index]);
const [drawerWidth, setDrawerWidth] = React.useState(() =>
getDefaultDrawerWidth(Dimensions.get('window'))
);
const [drawerWidth, setDrawerWidth] = React.useState(() => {
const { height = 0, width = 0 } = Dimensions.get('window');
return getDefaultDrawerWidth({ height, width });
});
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
const { colors } = useTheme();
const isDrawerOpen = Boolean(state.history.find(it => it.type === 'drawer'));
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
const handleDrawerOpen = React.useCallback(() => {
navigation.dispatch({
@@ -203,36 +206,48 @@ export default function DrawerView({
<GestureHandlerWrapper style={styles.content}>
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={drawerGestureRef}>
<Drawer
open={isDrawerOpen}
gestureEnabled={gestureEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={ref => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={[
{ backgroundColor: colors.background },
sceneContainerStyle,
]}
drawerStyle={[
{ width: drawerWidth, backgroundColor: colors.card },
drawerStyle,
]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={renderNavigationView}
renderSceneContent={renderContent}
keyboardDismissMode={keyboardDismissMode}
drawerPostion={drawerPosition}
/>
<DrawerOpenContext.Provider value={isDrawerOpen}>
<Drawer
open={isDrawerOpen}
gestureEnabled={gestureEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={(ref) => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
sceneContainerStyle={[
{ backgroundColor: colors.background },
sceneContainerStyle,
]}
drawerStyle={[
{ width: drawerWidth, backgroundColor: colors.card },
drawerType === 'permanent' &&
(drawerPosition === 'left'
? {
borderRightColor: colors.border,
borderRightWidth: StyleSheet.hairlineWidth,
}
: {
borderLeftColor: colors.border,
borderLeftWidth: StyleSheet.hairlineWidth,
}),
drawerStyle,
]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={edgeWidth}
swipeDistanceThreshold={minSwipeDistance}
hideStatusBar={hideStatusBar}
statusBarAnimation={statusBarAnimation}
renderDrawerContent={renderNavigationView}
renderSceneContent={renderContent}
keyboardDismissMode={keyboardDismissMode}
drawerPostion={drawerPosition}
/>
</DrawerOpenContext.Provider>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
</GestureHandlerWrapper>

View File

@@ -9,21 +9,29 @@ type Props = {
style?: any;
};
const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view out of its container
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
export default class ResourceSavingScene extends React.Component<Props> {
render() {
if (screensEnabled?.()) {
// react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props;
// @ts-ignore
return <Screen active={isVisible ? 1 : 0} {...rest} />;
}
const { isVisible, children, style, ...rest } = this.props;
return (
<View
style={[styles.container, style]}
style={[
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: null,
style,
]}
collapsable={false}
removeClippedSubviews={
// On iOS, set removeClippedSubviews to true only when not focused

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,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.4...@react-navigation/material-bottom-tabs@5.1.5) (2020-03-22)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.3...@react-navigation/material-bottom-tabs@5.1.4) (2020-03-19)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.2...@react-navigation/material-bottom-tabs@5.1.3) (2020-03-17)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.1...@react-navigation/material-bottom-tabs@5.1.2) (2020-03-16)
**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.2",
"version": "5.1.5",
"keywords": [
"react-native-component",
"react-component",
@@ -36,7 +36,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"@types/react-native-vector-icons": "^6.4.5",

View File

@@ -3,6 +3,30 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.4...@react-navigation/material-top-tabs@5.1.5) (2020-03-22)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.3...@react-navigation/material-top-tabs@5.1.4) (2020-03-19)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.2...@react-navigation/material-top-tabs@5.1.3) (2020-03-17)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.1...@react-navigation/material-top-tabs@5.1.2) (2020-03-16)
**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.2",
"version": "5.1.5",
"keywords": [
"react-native-component",
"react-component",
@@ -39,7 +39,7 @@
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",

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,33 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.1...@react-navigation/native@5.1.2) (2020-03-22)
**Note:** Version bump only for package @react-navigation/native
## [5.1.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.0...@react-navigation/native@5.1.1) (2020-03-19)
**Note:** Version bump only for package @react-navigation/native
# [5.1.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.10...@react-navigation/native@5.1.0) (2020-03-17)
### Features
* add permanent drawer type ([#7818](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/7818)) ([6a5d0a0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/6a5d0a035afae60d91aef78401ec8826295746fe))
## [5.0.10](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.0.9...@react-navigation/native@5.0.10) (2020-03-16)
**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.0.10",
"version": "5.1.2",
"keywords": [
"react-native",
"react-navigation",
@@ -31,7 +31,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.2.2"
"@react-navigation/core": "^5.3.0"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",

View File

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

View File

@@ -7,7 +7,7 @@ const DefaultTheme: Theme = {
background: 'rgb(242, 242, 242)',
card: 'rgb(255, 255, 255)',
text: 'rgb(28, 28, 30)',
border: 'rgb(199, 199, 204)',
border: 'rgb(224, 224, 224)',
},
};

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [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.2.0",
"keywords": [
"react",
"react-native",

View File

@@ -1,3 +1,4 @@
import shortid from 'shortid';
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,26 @@ 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}-${shortid()}` }
),
};
}
return nextState;
}

View File

@@ -71,7 +71,7 @@ export const DrawerActions = {
const isDrawerOpen = (
state: DrawerNavigationState | PartialState<DrawerNavigationState>
) => Boolean(state.history?.find(it => it.type === 'drawer'));
) => Boolean(state.history?.find((it) => it.type === 'drawer'));
const openDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
if (isDrawerOpen(state)) {
@@ -91,7 +91,7 @@ const closeDrawer = (state: DrawerNavigationState): DrawerNavigationState => {
return {
...state,
history: state.history.filter(it => it.type !== 'drawer'),
history: state.history.filter((it) => it.type !== 'drawer'),
};
};

View File

@@ -134,9 +134,9 @@ 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()}`,
@@ -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)
);
@@ -201,7 +201,7 @@ export default function StackRouter(options: StackRouterOptions) {
},
getStateForRouteFocus(state, key) {
const index = state.routes.findIndex(r => r.key === key);
const index = state.routes.findIndex((r) => r.key === key);
if (index === -1 || index === state.index) {
return state;
@@ -220,7 +220,7 @@ export default function StackRouter(options: StackRouterOptions) {
switch (action.type) {
case 'REPLACE': {
const index = action.source
? state.routes.findIndex(r => r.key === action.source)
? state.routes.findIndex((r) => r.key === action.source)
: state.index;
if (index === -1) {
@@ -283,7 +283,7 @@ export default function StackRouter(options: StackRouterOptions) {
case 'POP': {
const index =
action.target === state.key && action.source
? state.routes.findIndex(r => r.key === action.source)
? state.routes.findIndex((r) => r.key === action.source)
: state.index;
if (index > 0) {

View File

@@ -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,7 +124,7 @@ export default function TabRouter({
? routeNames.indexOf(initialRouteName)
: 0;
const routes = routeNames.map(name => ({
const routes = routeNames.map((name) => ({
name,
key: `${name}-${shortid()}`,
params: routeParamList[name],
@@ -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 {
@@ -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) {
@@ -205,8 +205,8 @@ 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()}`,
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

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

@@ -3,6 +3,51 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.3...@react-navigation/stack@5.2.4) (2020-03-22)
### Bug Fixes
* fix swipe not dismissing card in RTL ([043924c](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/043924ca4843b6f02626532cbf4aafc7cad9fab1)), closes [#7841](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7841)
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.2...@react-navigation/stack@5.2.3) (2020-03-19)
### Bug Fixes
* use the correct velocity value in closing animation ([#7836](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7836)) ([adbfedc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/adbfedcd58d4e3d358c6c9691710bb8e4e0d8afb))
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.1...@react-navigation/stack@5.2.2) (2020-03-19)
### Bug Fixes
* don't use react-native-screens on web ([b1a65fc](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/b1a65fc73e8603ae2c06ef101a74df31e80bb9b2)), closes [#7485](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7485)
* fix blank page if stack was inside display: none before ([49f6fed](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/49f6fed6d3da878e02a9fe9115c05bcf84e332bf))
* fix closing stack using inverted gesture. ([#7824](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7824)) ([f24d3a3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/f24d3a3461ee8a566c25ce7d13f31035b4be2691))
* initialize height and width to zero if undefined ([3df65e2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/3df65e28197db3bb8371059146546d57661c5ba3)), closes [#6789](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/6789)
* only dismiss previously focused input on page change. closes [#6918](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/6918) ([b1fe730](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/b1fe73097f3ad58d3ba4a8a9b875276d1d8d220c))
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.0...@react-navigation/stack@5.2.1) (2020-03-17)
**Note:** Version bump only for package @react-navigation/stack
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.1.1...@react-navigation/stack@5.2.0) (2020-03-16)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/stack",
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
"version": "5.2.0",
"version": "5.2.4",
"keywords": [
"react-native-component",
"react-component",
@@ -40,7 +40,7 @@
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-native-community/masked-view": "^0.1.7",
"@react-navigation/native": "^5.0.10",
"@react-navigation/native": "^5.1.2",
"@types/color": "^3.0.1",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",

View File

@@ -40,7 +40,7 @@ function StackNavigator({
React.useEffect(
() =>
navigation.addListener &&
navigation.addListener('tabPress', e => {
navigation.addListener('tabPress', (e) => {
const isFocused = navigation.isFocused();
// Run the operation in the next frame so we're sure all listeners have been run

View File

@@ -4,7 +4,7 @@ export default function debounce<T extends (...args: any[]) => void>(
): T {
let timeout: NodeJS.Timeout | number | undefined;
return function(this: any, ...args) {
return function (this: any, ...args) {
if (!timeout) {
// eslint-disable-next-line babel/no-invalid-this
func.apply(this, args);

View File

@@ -124,7 +124,7 @@ export default function HeaderContainer({
<View
onLayout={
onContentHeightChange
? e =>
? (e) =>
onContentHeightChange({
route: scene.route,
height: e.nativeEvent.layout.height,

View File

@@ -38,7 +38,7 @@ type State = {
};
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
Object.keys(styles).forEach(styleProp => {
Object.keys(styles).forEach((styleProp) => {
const value = styles[styleProp];
if (styleProp === 'position' && value === 'absolute') {

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { TextInput, Keyboard } from 'react-native';
import { TextInput } from 'react-native';
type Props = {
enabled: boolean;
@@ -54,7 +54,11 @@ export default class KeyboardManager extends React.Component<Props> {
this.clearKeyboardTimeout();
Keyboard.dismiss();
const input = this.previouslyFocusedTextInput;
if (input) {
TextInput.State.blurTextInput(input);
}
// Cleanup the ID on successful page change
this.previouslyFocusedTextInput = null;

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

@@ -272,7 +272,10 @@ export default class Card extends React.Component<Props> {
}
const closing =
translation + velocity * gestureVelocityImpact > distance / 2
(translation + velocity) *
gestureVelocityImpact *
getInvertedMultiplier(gestureDirection) >
distance / 2
? velocity !== 0 || translation !== 0
: false;

View File

@@ -75,9 +75,6 @@ type State = {
const EPSILON = 0.01;
const dimensions = Dimensions.get('window');
const layout = { width: dimensions.width, height: dimensions.height };
const MaybeScreenContainer = ({
enabled,
...rest
@@ -160,7 +157,16 @@ const getProgressFromGesture = (
layout: Layout,
descriptor?: StackDescriptor
) => {
const distance = getDistanceFromOptions(mode, layout, descriptor);
const distance = getDistanceFromOptions(
mode,
{
// Make sure that we have a non-zero distance, otherwise there will be incorrect progress
// This causes blank screen on web if it was previously inside container with display: none
width: Math.max(1, layout.width),
height: Math.max(1, layout.height),
},
descriptor
);
if (distance > 0) {
return gesture.interpolate({
@@ -290,19 +296,25 @@ export default class CardStack extends React.Component<Props, State> {
};
}
state: State = {
routes: [],
scenes: [],
gestures: {},
layout,
descriptors: this.props.descriptors,
// Used when card's header is null and mode is float to make transition
// between screens with headers and those without headers smooth.
// This is not a great heuristic here. We don't know synchronously
// on mount what the header height is so we have just used the most
// common cases here.
headerHeights: {},
};
constructor(props: Props) {
super(props);
const { height = 0, width = 0 } = Dimensions.get('window');
this.state = {
routes: [],
scenes: [],
gestures: {},
layout: { height, width },
descriptors: this.props.descriptors,
// Used when card's header is null and mode is float to make transition
// between screens with headers and those without headers smooth.
// This is not a great heuristic here. We don't know synchronously
// on mount what the header height is so we have just used the most
// common cases here.
headerHeights: {},
};
}
private handleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout;
@@ -401,9 +413,9 @@ export default class CardStack extends React.Component<Props, State> {
left = insets.left,
} = focusedOptions.safeAreaInsets || {};
// Screens is buggy on iOS, so we don't enable it there
// Screens is buggy on iOS and web, so we only enable it on Android
// For modals, usually we want the screen underneath to be visible, so also disable it there
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal';
const isScreensEnabled = Platform.OS === 'android' && mode !== 'modal';
return (
<React.Fragment>

View File

@@ -115,7 +115,7 @@ export default class StackView extends React.Component<Props, State> {
// We only need to animate routes if the focused route changed
// Animating previous routes won't be visible coz the focused route is on top of everything
if (!previousRoutes.find(r => r.key === nextFocusedRoute.key)) {
if (!previousRoutes.find((r) => r.key === nextFocusedRoute.key)) {
// A new route has come to the focus, we treat this as a push
// A replace can also trigger this, the animation should look like push
@@ -128,17 +128,17 @@ export default class StackView extends React.Component<Props, State> {
openingRouteKeys = [...openingRouteKeys, nextFocusedRoute.key];
closingRouteKeys = closingRouteKeys.filter(
key => key !== nextFocusedRoute.key
(key) => key !== nextFocusedRoute.key
);
replacingRouteKeys = replacingRouteKeys.filter(
key => key !== nextFocusedRoute.key
(key) => key !== nextFocusedRoute.key
);
if (!routes.find(r => r.key === previousFocusedRoute.key)) {
if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
// The previous focused route isn't present in state, we treat this as a replace
openingRouteKeys = openingRouteKeys.filter(
key => key !== previousFocusedRoute.key
(key) => key !== previousFocusedRoute.key
);
if (getAnimationTypeForReplace(nextFocusedRoute.key) === 'pop') {
@@ -151,7 +151,7 @@ export default class StackView extends React.Component<Props, State> {
// But since user configured it to animate the old screen like a pop, we need to add this without animation
// So remove it from `openingRouteKeys` which will remove the animation
openingRouteKeys = openingRouteKeys.filter(
key => key !== nextFocusedRoute.key
(key) => key !== nextFocusedRoute.key
);
// Keep the route being removed at the end to animate it out
@@ -163,7 +163,7 @@ export default class StackView extends React.Component<Props, State> {
];
closingRouteKeys = closingRouteKeys.filter(
key => key !== previousFocusedRoute.key
(key) => key !== previousFocusedRoute.key
);
// Keep the old route in the state because it's visible under the new route, and removing it will feel abrupt
@@ -174,7 +174,7 @@ export default class StackView extends React.Component<Props, State> {
}
}
}
} else if (!routes.find(r => r.key === previousFocusedRoute.key)) {
} else if (!routes.find((r) => r.key === previousFocusedRoute.key)) {
// The previously focused route was removed, we treat this as a pop
if (
@@ -186,10 +186,10 @@ export default class StackView extends React.Component<Props, State> {
// Sometimes a route can be closed before the opening animation finishes
// So we also need to remove it from the opening list
openingRouteKeys = openingRouteKeys.filter(
key => key !== previousFocusedRoute.key
(key) => key !== previousFocusedRoute.key
);
replacingRouteKeys = replacingRouteKeys.filter(
key => key !== previousFocusedRoute.key
(key) => key !== previousFocusedRoute.key
);
// Keep a copy of route being removed in the state to be able to animate it
@@ -271,13 +271,13 @@ export default class StackView extends React.Component<Props, State> {
private getPreviousRoute = ({ route }: { route: Route<string> }) => {
const { closingRouteKeys, replacingRouteKeys } = this.state;
const routes = this.state.routes.filter(
r =>
(r) =>
r.key === route.key ||
(!closingRouteKeys.includes(r.key) &&
!replacingRouteKeys.includes(r.key))
);
const index = routes.findIndex(r => r.key === route.key);
const index = routes.findIndex((r) => r.key === route.key);
return routes[index - 1];
};
@@ -298,12 +298,16 @@ export default class StackView extends React.Component<Props, State> {
};
private handleOpenRoute = ({ route }: { route: Route<string> }) => {
this.setState(state => ({
this.setState((state) => ({
routes: state.replacingRouteKeys.length
? state.routes.filter(r => !state.replacingRouteKeys.includes(r.key))
? state.routes.filter((r) => !state.replacingRouteKeys.includes(r.key))
: state.routes,
openingRouteKeys: state.openingRouteKeys.filter(key => key !== route.key),
closingRouteKeys: state.closingRouteKeys.filter(key => key !== route.key),
openingRouteKeys: state.openingRouteKeys.filter(
(key) => key !== route.key
),
closingRouteKeys: state.closingRouteKeys.filter(
(key) => key !== route.key
),
replacingRouteKeys: [],
}));
};
@@ -311,7 +315,7 @@ export default class StackView extends React.Component<Props, State> {
private handleCloseRoute = ({ route }: { route: Route<string> }) => {
const { state, navigation } = this.props;
if (state.routes.find(r => r.key === route.key)) {
if (state.routes.find((r) => r.key === route.key)) {
// If a route exists in state, trigger a pop
// This will happen in when the route was closed from the card component
// e.g. When the close animation triggered from a gesture ends
@@ -322,13 +326,13 @@ export default class StackView extends React.Component<Props, State> {
});
} else {
// We need to clean up any state tracking the route and pop it immediately
this.setState(state => ({
routes: state.routes.filter(r => r.key !== route.key),
this.setState((state) => ({
routes: state.routes.filter((r) => r.key !== route.key),
openingRouteKeys: state.openingRouteKeys.filter(
key => key !== route.key
(key) => key !== route.key
),
closingRouteKeys: state.closingRouteKeys.filter(
key => key !== route.key
(key) => key !== route.key
),
}));
}
@@ -378,9 +382,9 @@ export default class StackView extends React.Component<Props, State> {
<GestureHandlerWrapper style={styles.container}>
<SafeAreaProviderCompat>
<SafeAreaConsumer>
{insets => (
{(insets) => (
<KeyboardManager enabled={keyboardHandlingEnabled !== false}>
{props => (
{(props) => (
<CardStack
mode={mode}
insets={insets as EdgeInsets}

View File

@@ -9,7 +9,7 @@ const packages = path.join(__dirname, '..', 'packages');
const invalid = [];
fs.readdirSync(packages).forEach(name => {
fs.readdirSync(packages).forEach((name) => {
const dir = path.join(packages, name);
if (fs.statSync(path.join(packages, name)).isDirectory()) {
@@ -26,6 +26,6 @@ fs.readdirSync(packages).forEach(name => {
if (invalid.length) {
console.log(
'Found invalid path to type definitions in the following packages:\n',
invalid.map(p => `- ${p.name} (${p.types})`).join('\n')
invalid.map((p) => `- ${p.name} (${p.types})`).join('\n')
);
}

View File

@@ -14637,10 +14637,10 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
prettier@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.1.tgz#3f00ac71263be34684b2b2c8d7e7f63737592dac"
integrity sha512-piXGBcY1zoFOG0MvHpNE5reAGseLmaCRifQ/fmfF49BcYkInEs/naD/unxGNAeOKFA5+JxVrPyMvMlpzcd20UA==
pretty-bytes@^4.0.2:
version "4.0.2"