Compare commits

..

1 Commits

Author SHA1 Message Date
Satyajit Sahoo
515e652b37 wip 2020-04-27 17:46:42 +02:00
119 changed files with 3051 additions and 5727 deletions

View File

@@ -1,54 +1,46 @@
version: 2.1
version: 2
executors:
default:
defaults: &defaults
docker:
- image: circleci/node:10
working_directory: ~/project
environment:
YARN_CACHE_FOLDER: "~/.cache/yarn"
commands:
attach_project:
steps:
- attach_workspace:
at: ~/project
jobs:
install-dependencies:
executor: default
<<: *defaults
steps:
- checkout
- attach_project
- attach_workspace:
at: ~/project
- restore_cache:
keys:
- v2-dependencies-{{ checksum "yarn.lock" }}
- v2-dependencies-
- v1-dependencies-{{ checksum "yarn.lock" }}
- v1-dependencies-
- run:
name: Install project dependencies
command: yarn install --frozen-lockfile
- save_cache:
key: v2-dependencies-{{ checksum "yarn.lock" }}
paths: ~/.cache/yarn
key: v1-dependencies-{{ checksum "yarn.lock" }}
paths: node_modules
- persist_to_workspace:
root: .
paths: .
lint-and-typecheck:
executor: default
<<: *defaults
steps:
- attach_project
- attach_workspace:
at: ~/project
- run:
name: Lint files
command: yarn lint
- run:
name: Typecheck files
command: yarn typescript
unit-tests:
executor: default
<<: *defaults
steps:
- attach_project
- attach_workspace:
at: ~/project
- run:
name: Run unit tests
command: yarn test --coverage
@@ -58,11 +50,11 @@ jobs:
- store_artifacts:
path: coverage
destination: coverage
integration-tests:
executor: default
<<: *defaults
steps:
- attach_project
- attach_workspace:
at: ~/project
- run:
name: Install Headless Chrome dependencies
command: |
@@ -78,11 +70,11 @@ jobs:
- run:
name: Run integration tests
command: yarn example test --maxWorkers=2
build-packages:
executor: default
<<: *defaults
steps:
- attach_project
- attach_workspace:
at: ~/project
- run:
name: Build packages in the monorepo
command: yarn lerna run prepare
@@ -91,6 +83,7 @@ jobs:
command: node scripts/check-types-path.js
workflows:
version: 2
build-and-test:
jobs:
- install-dependencies

View File

@@ -8,6 +8,7 @@
"@react-navigation/routers",
"@react-navigation/compat",
"@react-navigation/stack",
"@react-navigation/web-stack",
"@react-navigation/drawer",
"@react-navigation/bottom-tabs",
"@react-navigation/material-top-tabs",

View File

@@ -6,7 +6,7 @@ beforeEach(async () => {
it('loads the article page', async () => {
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/article/gandalf'
'/link-component/Article?author=Gandalf'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
@@ -16,24 +16,24 @@ it('loads the article page', async () => {
});
it('goes to the album page and goes back', async () => {
await page.click('[href="/link-component/music"]');
await page.click('[href="/link-component/Album"]');
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/music'
'/link-component/Album'
);
expect(
((await page.accessibility.snapshot()) as any)?.children?.find(
(it: any) => it.role === 'heading'
)?.name
).toBe('Albums');
).toBe('Album');
await page.click('[aria-label="Article by Gandalf, back"]');
await page.waitForNavigation();
expect(await page.evaluate(() => location.pathname + location.search)).toBe(
'/link-component/article/gandalf'
'/link-component/Article?author=Gandalf'
);
expect(

View File

@@ -12,33 +12,33 @@
"test": "jest"
},
"dependencies": {
"@expo/vector-icons": "^10.2.0",
"@react-native-community/masked-view": "^0.1.10",
"@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "^0.1.7",
"color": "^3.1.2",
"expo": "^37.0.8",
"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.10.1",
"react-native-reanimated": "^1.8.0",
"react-native-restart": "^0.0.15",
"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.7.0",
"react-native-screens": "^2.3.0",
"react-native-tab-view": "2.14.0",
"react-native-unimodules": "~0.9.1",
"react-native-unimodules": "~0.8.1",
"react-native-web": "^0.11.7"
},
"devDependencies": {
"@expo/webpack-config": "^0.11.19",
"@types/jest-dev-server": "^4.2.0",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@types/react": "^16.9.23",
"@types/react-native": "^0.60.22",
"babel-preset-expo": "^8.1.0",
"expo-cli": "^3.20.1",
"jest": "^26.0.1",
"expo-cli": "^3.17.18",
"jest": "^25.2.7",
"jest-dev-server": "^4.4.0",
"playwright": "^0.14.0",
"serve": "^11.3.0",

View File

@@ -1,3 +0,0 @@
import { AsyncStorage } from 'react-native';
export default AsyncStorage;

View File

@@ -1,14 +0,0 @@
export default {
getItem(key: string) {
return Promise.resolve(localStorage.getItem(key));
},
setItem(key: string, value: string) {
return Promise.resolve(localStorage.setItem(key, value));
},
removeItem(key: string) {
return Promise.resolve(localStorage.removeItem(key));
},
clear() {
return Promise.resolve(localStorage.clear());
},
};

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, ScrollView, StyleSheet, Platform } from 'react-native';
import { View, ScrollView, StyleSheet } from 'react-native';
import { Button } from 'react-native-paper';
import {
createCompatNavigatorFactory,
@@ -23,8 +23,6 @@ type NestedStackParams = {
Article: { author: string };
};
const scrollEnabled = Platform.select({ web: true, default: false });
const AlbumsScreen: CompatScreenType<StackNavigationProp<
CompatStackParams
>> = ({ navigation }) => {
@@ -46,7 +44,7 @@ const AlbumsScreen: CompatScreenType<StackNavigationProp<
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -72,7 +70,7 @@ const FeedScreen: CompatScreenType<StackNavigationProp<NestedStackParams>> = ({
Go back
</Button>
</View>
<NewsFeed scrollEnabled={scrollEnabled} />
<NewsFeed scrollEnabled={false} />
</ScrollView>
);
};
@@ -102,7 +100,7 @@ const ArticleScreen: CompatScreenType<StackNavigationProp<
</View>
<Article
author={{ name: navigation.getParam('author') }}
scrollEnabled={scrollEnabled}
scrollEnabled={false}
/>
</ScrollView>
);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import {
Link,
@@ -17,29 +17,18 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = {
Article: { author: string };
Albums: undefined;
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const LinkButton = ({
to,
...rest
}: React.ComponentProps<typeof Button> & { to: string }) => {
const { onPress, ...props } = useLinkProps({ to });
const props = useLinkProps({ to });
return (
<Button
{...props}
{...rest}
{...Platform.select({
web: { onClick: onPress } as any,
default: { onPress },
})}
/>
);
return <Button {...props} {...rest} />;
};
const ArticleScreen = ({
@@ -53,24 +42,24 @@ const ArticleScreen = ({
<ScrollView>
<View style={styles.buttons}>
<Link
to="/link-component/music"
to="/link-component/Album"
style={[styles.button, { padding: 8 }]}
>
Go to /link-component/music
Go to /link-component/Album
</Link>
<Link
to="/link-component/music"
action={StackActions.replace('Albums')}
to="/link-component/Album"
action={StackActions.replace('Album')}
style={[styles.button, { padding: 8 }]}
>
Replace with /link-component/music
Replace with /link-component/Album
</Link>
<LinkButton
to="/link-component/music"
to="/link-component/Album"
mode="contained"
style={styles.button}
>
Go to /link-component/music
Go to /link-component/Album
</LinkButton>
<Button
mode="outlined"
@@ -80,10 +69,7 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
@@ -97,17 +83,17 @@ const AlbumsScreen = ({
<ScrollView>
<View style={styles.buttons}>
<Link
to="/link-component/article/babel"
to="/link-component/Article?author=Babel"
style={[styles.button, { padding: 8 }]}
>
Go to /link-component/article
Go to /link-component/Article
</Link>
<LinkButton
to="/link-component/article/babel"
to="/link-component/Article?author=Babel"
mode="contained"
style={styles.button}
>
Go to /link-component/article
Go to /link-component/Article
</LinkButton>
<Button
mode="outlined"
@@ -117,7 +103,7 @@ const AlbumsScreen = ({
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -144,9 +130,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="Albums"
name="Album"
component={AlbumsScreen}
options={{ title: 'Albums' }}
options={{ title: 'Album' }}
/>
</SimpleStack.Navigator>
);

View File

@@ -15,7 +15,7 @@ import NewsFeed from '../Shared/NewsFeed';
type DrawerParams = {
Article: undefined;
NewsFeed: undefined;
Albums: undefined;
Album: undefined;
};
type DrawerNavigation = DrawerNavigationProp<DrawerParams>;
@@ -118,9 +118,9 @@ export default function DrawerScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }}
/>
<Drawer.Screen
name="Albums"
name="Album"
component={AlbumsScreen}
options={{ title: 'Albums' }}
options={{ title: 'Album' }}
/>
</Drawer.Navigator>
);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -12,13 +12,11 @@ import Albums from '../Shared/Albums';
type ModalStackParams = {
Article: { author: string };
Albums: undefined;
Album: undefined;
};
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -31,7 +29,7 @@ const ArticleScreen = ({
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
@@ -44,10 +42,7 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
@@ -71,7 +66,7 @@ const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -112,9 +107,9 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
initialParams={{ author: 'Gandalf' }}
/>
<ModalPresentationStack.Screen
name="Albums"
name="Album"
component={AlbumsScreen}
options={{ title: 'Albums' }}
options={{ title: 'Album' }}
/>
</ModalPresentationStack.Navigator>
);

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, Platform, StyleSheet, ScrollView } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
@@ -13,13 +13,11 @@ import NewsFeed from '../Shared/NewsFeed';
type SimpleStackParams = {
Article: { author: string };
NewsFeed: undefined;
Albums: undefined;
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -45,10 +43,7 @@ const ArticleScreen = ({
Pop screen
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
@@ -63,7 +58,7 @@ const NewsFeedScreen = ({
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.navigate('Albums')}
onPress={() => navigation.navigate('Album')}
style={styles.button}
>
Navigate to album
@@ -76,7 +71,7 @@ const NewsFeedScreen = ({
Go back
</Button>
</View>
<NewsFeed scrollEnabled={scrollEnabled} />
<NewsFeed scrollEnabled={false} />
</ScrollView>
);
};
@@ -104,7 +99,7 @@ const AlbumsScreen = ({
Pop by 2
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -136,9 +131,9 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
options={{ title: 'Feed' }}
/>
<SimpleStack.Screen
name="Albums"
name="Album"
component={AlbumsScreen}
options={{ title: 'Albums' }}
options={{ title: 'Album' }}
/>
</SimpleStack.Navigator>
);

View File

@@ -15,13 +15,11 @@ import Albums from '../Shared/Albums';
type SimpleStackParams = {
Article: { author: string };
Albums: undefined;
Album: undefined;
};
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -34,7 +32,7 @@ const ArticleScreen = ({
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Albums')}
onPress={() => navigation.push('Album')}
style={styles.button}
>
Push album
@@ -47,10 +45,7 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
@@ -80,7 +75,7 @@ const AlbumsScreen = ({
Go back
</Button>
</View>
<Albums scrollEnabled={scrollEnabled} />
<Albums scrollEnabled={false} />
</ScrollView>
);
};
@@ -131,10 +126,10 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
initialParams={{ author: 'Gandalf' }}
/>
<SimpleStack.Screen
name="Albums"
name="Album"
component={AlbumsScreen}
options={{
title: 'Albums',
title: 'Album',
headerBackTitle: 'Back',
headerTransparent: true,
headerBackground: () => (

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView, Platform } from 'react-native';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button, Paragraph } from 'react-native-paper';
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
import {
@@ -15,8 +15,6 @@ type SimpleStackParams = {
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
const scrollEnabled = Platform.select({ web: true, default: false });
const ArticleScreen = ({
navigation,
route,
@@ -42,10 +40,7 @@ const ArticleScreen = ({
Go back
</Button>
</View>
<Article
author={{ name: route.params.author }}
scrollEnabled={scrollEnabled}
/>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};

View File

@@ -0,0 +1,3 @@
export default function WebStack() {
return null;
}

View File

@@ -0,0 +1,142 @@
import * as React from 'react';
import { View, StyleSheet, ScrollView } from 'react-native';
import { Button } from 'react-native-paper';
import { RouteProp, ParamListBase } from '@react-navigation/native';
import {
createWebStackNavigator,
WebStackNavigationProp,
} from '@react-navigation/web-stack';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import NewsFeed from '../Shared/NewsFeed';
type WebStackParams = {
Article: { author: string };
NewsFeed: undefined;
Album: undefined;
};
type WebStackNavigation = WebStackNavigationProp<WebStackParams>;
const ArticleScreen = ({
navigation,
route,
}: {
navigation: WebStackNavigation;
route: RouteProp<WebStackParams, 'Article'>;
}) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.replace('NewsFeed')}
style={styles.button}
>
Replace with feed
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop()}
style={styles.button}
>
Pop screen
</Button>
</View>
<Article author={{ name: route.params.author }} scrollEnabled={false} />
</ScrollView>
);
};
const NewsFeedScreen = ({ navigation }: { navigation: WebStackNavigation }) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.navigate('Album')}
style={styles.button}
>
Navigate to album
</Button>
<Button
mode="outlined"
onPress={() => navigation.goBack()}
style={styles.button}
>
Go back
</Button>
</View>
<NewsFeed scrollEnabled={false} />
</ScrollView>
);
};
const AlbumsScreen = ({ navigation }: { navigation: WebStackNavigation }) => {
return (
<ScrollView>
<View style={styles.buttons}>
<Button
mode="contained"
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
style={styles.button}
>
Push article
</Button>
<Button
mode="outlined"
onPress={() => navigation.pop(2)}
style={styles.button}
>
Pop by 2
</Button>
</View>
<Albums scrollEnabled={false} />
</ScrollView>
);
};
const WebStack = createWebStackNavigator<WebStackParams>();
type Props = Partial<React.ComponentProps<typeof WebStack.Navigator>> & {
navigation: WebStackNavigationProp<ParamListBase>;
};
export default function WebStackScreen({ navigation, ...rest }: Props) {
navigation.setOptions({
headerShown: false,
});
return (
<WebStack.Navigator {...rest}>
<WebStack.Screen
name="Article"
component={ArticleScreen}
options={({ route }) => ({
title: `Article by ${route.params.author}`,
})}
initialParams={{ author: 'Gandalf' }}
/>
<WebStack.Screen
name="NewsFeed"
component={NewsFeedScreen}
options={{ title: 'Feed' }}
/>
<WebStack.Screen
name="Album"
component={AlbumsScreen}
options={{ title: 'Album' }}
/>
</WebStack.Navigator>
);
}
const styles = StyleSheet.create({
buttons: {
flexDirection: 'row',
padding: 8,
},
button: {
margin: 8,
},
});

View File

@@ -1,13 +1,13 @@
import * as React from 'react';
import {
ScrollView,
AsyncStorage,
YellowBox,
Platform,
StatusBar,
I18nManager,
Dimensions,
ScaledSize,
Linking,
} from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { enableScreens } from 'react-native-screens';
@@ -26,6 +26,7 @@ import {
} from 'react-native-paper';
import {
InitialState,
NavigationContainerRef,
NavigationContainer,
DefaultTheme,
DarkTheme,
@@ -41,9 +42,9 @@ import {
HeaderStyleInterpolators,
} from '@react-navigation/stack';
import AsyncStorage from './AsyncStorage';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import WebStack from './Screens/WebStack';
import SimpleStack from './Screens/SimpleStack';
import ModalPresentationStack from './Screens/ModalPresentationStack';
import StackTransparent from './Screens/StackTransparent';
@@ -76,6 +77,11 @@ type RootStackParamList = {
};
const SCREENS = {
...(Platform.OS === 'web'
? {
WebStack: { title: 'Web Stack', component: WebStack },
}
: null),
SimpleStack: { title: 'Simple Stack', component: SimpleStack },
ModalPresentationStack: {
title: 'Modal Presentation Stack',
@@ -129,6 +135,8 @@ const THEME_PERSISTENCE_KEY = 'THEME_TYPE';
Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>(null);
const [theme, setTheme] = React.useState(DefaultTheme);
const [isReady, setIsReady] = React.useState(false);
@@ -139,19 +147,19 @@ export default function App() {
React.useEffect(() => {
const restoreState = async () => {
try {
const initialUrl = await Linking.getInitialURL();
let state;
if (Platform.OS !== 'web' || initialUrl === null) {
if (Platform.OS !== 'web' && state === undefined) {
const savedState = await AsyncStorage.getItem(
NAVIGATION_PERSISTENCE_KEY
);
const state = savedState ? JSON.parse(savedState) : undefined;
state = savedState ? JSON.parse(savedState) : undefined;
}
if (state !== undefined) {
setInitialState(state);
}
}
} finally {
try {
const themeName = await AsyncStorage.getItem(THEME_PERSISTENCE_KEY);
@@ -206,6 +214,7 @@ export default function App() {
<StatusBar barStyle={theme.dark ? 'light-content' : 'dark-content'} />
)}
<NavigationContainer
ref={containerRef}
initialState={initialState}
onStateChange={(state) =>
AsyncStorage.setItem(
@@ -239,22 +248,6 @@ export default function App() {
{ Home: '' }
),
},
Article: {
path: 'article/:author?',
parse: {
author: (author) =>
author.charAt(0).toUpperCase() +
author.slice(1).replace(/-/g, ' '),
},
stringify: {
author: (author: string) =>
author.toLowerCase().replace(/\s/g, '-'),
},
},
Albums: 'music',
Chat: 'chat',
Contacts: 'people',
NewsFeed: 'feed',
},
}}
fallback={<Text>Loading</Text>}

View File

@@ -1 +0,0 @@
/* /index.html 200

View File

@@ -24,19 +24,17 @@ module.exports = async function (env, argv) {
);
Object.assign(config.resolve.alias, {
'react': path.resolve(node_modules, 'react'),
react: path.resolve(node_modules, 'react'),
'react-native': path.resolve(node_modules, 'react-native-web'),
'react-native-web': path.resolve(node_modules, 'react-native-web'),
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
});
fs.readdirSync(packages)
.filter((name) => !name.startsWith('.'))
.forEach((name) => {
fs.readdirSync(packages).forEach((name) => {
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
packages,
name,
require(`../packages/${name}/package.json`).source
'src'
);
});

View File

@@ -13,7 +13,7 @@
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/react-navigation/react-navigation.git"
"url": "git+https://github.com/satya164/react-navigation.git"
},
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
"scripts": {
@@ -27,23 +27,23 @@
"devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
"@babel/preset-env": "^7.9.6",
"@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.6",
"@babel/runtime": "^7.9.2",
"@commitlint/config-conventional": "^8.3.4",
"@types/jest": "^25.2.1",
"babel-jest": "^26.0.1",
"babel-jest": "^25.2.6",
"codecov": "^3.6.5",
"commitlint": "^8.3.5",
"core-js": "^3.6.5",
"core-js": "^3.6.4",
"eslint": "^6.8.0",
"eslint-config-satya164": "^3.1.7",
"husky": "^4.2.5",
"jest": "^26.0.1",
"eslint-config-satya164": "^3.1.6",
"husky": "^4.2.3",
"jest": "^25.2.7",
"lerna": "^3.20.2",
"prettier": "^2.0.5",
"prettier": "^2.0.4",
"typescript": "^3.8.3"
},
"resolutions": {

View File

@@ -3,104 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.5](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.4...@react-navigation/bottom-tabs@5.4.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.3...@react-navigation/bottom-tabs@5.4.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.2...@react-navigation/bottom-tabs@5.4.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.1...@react-navigation/bottom-tabs@5.4.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.4.0...@react-navigation/bottom-tabs@5.4.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.4...@react-navigation/bottom-tabs@5.4.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/issues/7971)
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.3...@react-navigation/bottom-tabs@5.3.4) (2020-05-05)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.2...@react-navigation/bottom-tabs@5.3.3) (2020-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.1...@react-navigation/bottom-tabs@5.3.2) (2020-05-01)
**Note:** Version bump only for package @react-navigation/bottom-tabs
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.3.0...@react-navigation/bottom-tabs@5.3.1) (2020-04-30)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.8...@react-navigation/bottom-tabs@5.3.0) (2020-04-30)
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/commit/942d2be2c72720469475ce12ec8df23825994dbf))
## [5.2.8](https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs/compare/@react-navigation/bottom-tabs@5.2.7...@react-navigation/bottom-tabs@5.2.8) (2020-04-27)
**Note:** Version bump only for package @react-navigation/bottom-tabs

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "5.4.5",
"version": "5.2.8",
"keywords": [
"react-native-component",
"react-component",
@@ -15,7 +15,6 @@
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/bottom-tabs",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -35,16 +34,16 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.4.0",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.7",
"@types/color": "^3.0.1",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.7.0",
"react-native-screens": "^2.3.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -15,7 +15,6 @@ export { default as BottomTabBar } from './views/BottomTabBar';
export type {
BottomTabNavigationOptions,
BottomTabNavigationProp,
BottomTabScreenProps,
BottomTabBarProps,
BottomTabBarOptions,
} from './types';

View File

@@ -13,7 +13,6 @@ import {
Descriptor,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type BottomTabNavigationEventMap = {
@@ -46,14 +45,6 @@ export type BottomTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type BottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: BottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type BottomTabNavigationOptions = {
/**
* Title text for the screen.

View File

@@ -3,97 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.1.21](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.20...@react-navigation/compat@5.1.21) (2020-05-16)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.20](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.19...@react-navigation/compat@5.1.20) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.19](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.18...@react-navigation/compat@5.1.19) (2020-05-14)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.18](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.17...@react-navigation/compat@5.1.18) (2020-05-10)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.17](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.16...@react-navigation/compat@5.1.17) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.1.16](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.15...@react-navigation/compat@5.1.16) (2020-05-08)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.14...@react-navigation/compat@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.13...@react-navigation/compat@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.12...@react-navigation/compat@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.11...@react-navigation/compat@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.10...@react-navigation/compat@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/compat
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/compat/compare/@react-navigation/compat@5.1.9...@react-navigation/compat@5.1.10) (2020-04-27)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/compat",
"description": "Compatibility layer to write navigator definitions in static configuration format",
"version": "5.1.21",
"version": "5.1.10",
"license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/compat",
"bugs": {
@@ -10,7 +10,6 @@
"homepage": "https://reactnavigation.org/docs/compatibility.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -26,9 +25,9 @@
"clean": "del lib"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.4.0",
"@types/react": "^16.9.34",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.7",
"@types/react": "^16.9.23",
"react": "~16.9.0",
"typescript": "^3.8.3"
},

View File

@@ -3,101 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.1...@react-navigation/core@5.7.0) (2020-05-16)
### Bug Fixes
* don't use Object.fromEntries ([51f4d11](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/51f4d11fdf4bd2bb06f8cd4094f051816590e62c))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.6.0...@react-navigation/core@5.6.1) (2020-05-14)
### Bug Fixes
* don't use flat since it's not supported in node ([21b397f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/21b397f0d6b96ec4875d3172f47533130bb08009))
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.2...@react-navigation/core@5.6.0) (2020-05-14)
### Bug Fixes
* ignore extra slashes in the pattern ([3c47716](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3c47716826d0dfa69dfa6112141c116723372ea1))
* ignore state updates when we're not mounted ([0149e85](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/0149e85a95b90c6a9d487fa753ddbf5d01c03e3d)), closes [#8226](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8226)
### Features
* merge path patterns for nested screens ([#8253](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8253)) ([acc9646](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/acc9646426fee53558d686dfbe5fd0e35361d8c0))
## [5.5.2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.1...@react-navigation/core@5.5.2) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.5.0...@react-navigation/core@5.5.1) (2020-05-08)
### Bug Fixes
* avoid cleaning up state when a new navigator is mounted. fixes [#8195](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8195) ([f6d0676](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/f6d06768d3c36d1f5beaffcb660f3c259209f2e7))
# [5.5.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.4.0...@react-navigation/core@5.5.0) (2020-05-05)
### Features
* add support for optional params to linking ([#8196](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8196)) ([fcd1cc6](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/fcd1cc64c151e4941f3f544a54b5048d853821f6))
* support params anywhere in path segement ([#8184](https://github.com/react-navigation/react-navigation/tree/master/packages/core/issues/8184)) ([3999fc2](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/3999fc28365c3a06a17d963c7be7fb7e897f99e0))
# [5.4.0](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.5...@react-navigation/core@5.4.0) (2020-04-30)
### Bug Fixes
* handle empty paths when parsing ([c3fa83e](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/c3fa83efe0d73db76365f8be3d6a8ca1d1289b71))
* parsing url ([bd35b4f](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/bd35b4fc202c3868fb75c3675b62de67557089e1))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/core/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/core/compare/@react-navigation/core@5.3.4...@react-navigation/core@5.3.5) (2020-04-27)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "5.7.0",
"version": "5.3.5",
"keywords": [
"react",
"react-native",
@@ -15,7 +15,6 @@
"homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -30,21 +29,21 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.4.4",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.5",
"query-string": "^6.12.1",
"@react-navigation/routers": "^5.4.1",
"escape-string-regexp": "^2.0.0",
"nanoid": "^3.0.2",
"query-string": "^6.12.0",
"react-is": "^16.13.0",
"use-subscription": "^1.4.0"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@types/react": "^16.9.34",
"@react-native-community/bob": "^0.10.0",
"@types/react": "^16.9.23",
"@types/react-is": "^16.7.1",
"@types/use-subscription": "^1.0.0",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native-testing-library": "^1.13.2",
"react-native-testing-library": "^1.12.0",
"react-test-renderer": "~16.13.1",
"typescript": "^3.8.3"
},

View File

@@ -219,8 +219,6 @@ const BaseNavigationContainer = React.forwardRef(
dispatch,
canGoBack,
getRootState,
dangerouslyGetState: () => state,
dangerouslyGetParent: () => undefined,
}));
const builderContext = React.useMemo(

View File

@@ -117,82 +117,12 @@ it("doesn't add query param for empty params", () => {
});
it('handles state with config with nested screens', () => {
const path =
'/foo/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) => author.toLowerCase(),
id: (id: number) => `x${id}`,
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Bar',
params: { fruit: 'apple', type: 'sweet' },
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: '10',
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles state with config with nested screens and exact', () => {
const path = '/foe/bar/sweet/apple/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
Foe: 'foe',
},
},
Bar: 'bar/:type/:fruit',
@@ -253,72 +183,12 @@ it('handles state with config with nested screens and exact', () => {
});
it('handles state with config with nested screens and unused configs', () => {
const path = '/foo/foe/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
},
},
Baz: {
path: 'baz/:author',
parse: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toUpperCase()),
count: Number,
valid: Boolean,
},
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
unknown: (_: unknown) => 'x',
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Foe',
state: {
routes: [
{
name: 'Baz',
params: {
author: 'Jane',
count: 10,
answer: '42',
valid: true,
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles state with config with nested screens and unused configs with exact', () => {
const path = '/foe/baz/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
exact: true,
},
Foe: 'foe',
},
},
Baz: {
@@ -370,78 +240,6 @@ it('handles state with config with nested screens and unused configs with exact'
});
it('handles nested object with stringify in it', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: {
path: 'foe',
},
},
},
Bar: 'bar/:type/:fruit',
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: '42',
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object with stringify in it with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?answer=42&count=10&valid=true';
const config = {
Foo: {
@@ -459,7 +257,6 @@ it('handles nested object with stringify in it with exact', () => {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -516,45 +313,6 @@ it('handles nested object with stringify in it with exact', () => {
});
it('handles nested object for second route depth', () => {
const path = '/foo/bar/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Foe: 'foe',
Bar: {
path: 'bar',
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth with exact', () => {
const path = '/baz';
const config = {
Foo: {
@@ -564,10 +322,7 @@ it('handles nested object for second route depth with exact', () => {
Bar: {
path: 'bar',
screens: {
Baz: {
path: 'baz',
exact: true,
},
Baz: 'baz',
},
},
},
@@ -596,53 +351,7 @@ it('handles nested object for second route depth with exact', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots', () => {
const path = '/foo/dathomir/bar/42/baz';
const config = {
Foo: {
path: 'foo/:planet',
stringify: {
id: (id: number) => `planet=${id}`,
},
screens: {
Foe: 'foe',
Bar: {
path: 'bar/:id',
parse: {
id: Number,
},
screens: {
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
params: { planet: 'dathomir' },
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz', params: { id: 42 } }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles nested object for second route depth and path and stringify in roots with exact', () => {
it('handles nested object for second route depth and and path and stringify in roots', () => {
const path = '/baz';
const config = {
Foo: {
@@ -661,10 +370,7 @@ it('handles nested object for second route depth and path and stringify in roots
id: Number,
},
screens: {
Baz: {
path: 'baz',
exact: true,
},
Baz: 'baz',
},
},
},
@@ -720,51 +426,8 @@ it('ignores empty string paths', () => {
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('keeps query params if path is empty', () => {
const path = '/?foo=42';
const config = {
Foo: {
screens: {
Foe: 'foe',
Bar: {
screens: {
Qux: {
path: '',
parse: { foo: Number },
},
Baz: 'baz',
},
},
},
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Qux', params: { foo: 42 } }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toEqual(
path
);
});
it('cuts nested configs too', () => {
const path = '/foo/baz';
const path = '/baz';
const config = {
Foo: {
path: 'foo',
@@ -772,48 +435,7 @@ it('cuts nested configs too', () => {
Bar: '',
},
},
Baz: {
path: 'baz',
},
};
const state = {
routes: [
{
name: 'Foo',
state: {
routes: [
{
name: 'Bar',
state: {
routes: [{ name: 'Baz' }],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('cuts nested configs too with exact', () => {
const path = '/baz';
const config = {
Foo: {
path: 'foo',
screens: {
Bar: {
path: '',
exact: true,
},
},
},
Baz: {
path: 'baz',
},
Baz: { path: 'baz' },
};
const state = {
@@ -839,7 +461,7 @@ it('cuts nested configs too with exact', () => {
});
it('handles empty path at the end', () => {
const path = '/foo/bar';
const path = '/bar';
const config = {
Foo: {
path: 'foo',
@@ -873,8 +495,6 @@ it('handles empty path at the end', () => {
});
it('returns "/" for empty path', () => {
const path = '/';
const config = {
Foo: {
path: '',
@@ -899,8 +519,7 @@ it('returns "/" for empty path', () => {
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
expect(getPathFromState(state, config)).toBe('/');
});
it('parses no path specified', () => {
@@ -964,77 +583,6 @@ it('parses no path specified in nested config', () => {
});
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: {
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,
valid: true,
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('strips undefined query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane?count=10&valid=true';
const config = {
Foo: {
@@ -1052,7 +600,6 @@ it('strips undefined query params with exact', () => {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -1088,6 +635,7 @@ it('strips undefined query params with exact', () => {
params: {
author: 'Jane',
count: 10,
answer: undefined,
valid: true,
},
},
@@ -1108,75 +656,6 @@ it('strips undefined query params with exact', () => {
});
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: {
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',
},
},
],
},
},
],
},
},
],
},
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('handles stripping all query params with exact', () => {
const path = '/bar/sweet/apple/foo/bis/jane';
const config = {
Foo: {
@@ -1194,7 +673,6 @@ it('handles stripping all query params with exact', () => {
Bos: 'bos',
Bis: {
path: 'bis/:author',
exact: true,
stringify: {
author: (author: string) =>
author.replace(/^\w/, (c) => c.toLowerCase()),
@@ -1229,6 +707,9 @@ it('handles stripping all query params with exact', () => {
name: 'Bis',
params: {
author: 'Jane',
count: undefined,
answer: undefined,
valid: undefined,
},
},
],
@@ -1246,22 +727,3 @@ it('handles stripping all query params with exact', () => {
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});
it('replaces undefined query params', () => {
const path = '/bar/undefined/apple';
const config = {
Bar: 'bar/:type/:fruit',
};
const state = {
routes: [
{
name: 'Bar',
params: { fruit: 'apple' },
},
],
};
expect(getPathFromState(state, config)).toBe(path);
expect(getPathFromState(getStateFromPath(path, config), config)).toBe(path);
});

File diff suppressed because it is too large Load Diff

View File

@@ -379,8 +379,6 @@ it("doesn't update state if action wasn't handled", () => {
});
it('cleans up state when the navigator unmounts', () => {
jest.useFakeTimers();
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -428,8 +426,6 @@ it('cleans up state when the navigator unmounts', () => {
<BaseNavigationContainer onStateChange={onStateChange} children={null} />
);
act(() => jest.runAllTimers());
expect(onStateChange).toBeCalledTimes(2);
expect(onStateChange).lastCalledWith(undefined);
});

View File

@@ -4,18 +4,19 @@ import {
PartialState,
Route,
} from '@react-navigation/routers';
import { PathConfig } from './types';
type State = NavigationState | Omit<PartialState<NavigationState>, 'stale'>;
type StringifyConfig = Record<string, (value: any) => string>;
type OptionsItem = PathConfig[string];
type ConfigItem = {
pattern?: string;
type Options = {
[routeName: string]:
| string
| {
path?: string;
stringify?: StringifyConfig;
screens?: Record<string, ConfigItem>;
screens?: Options;
};
};
/**
@@ -47,104 +48,103 @@ type ConfigItem = {
*/
export default function getPathFromState(
state?: State,
options: PathConfig = {}
options: Options = {}
): string {
if (state === undefined) {
throw Error('NavigationState not passed');
}
// Create a normalized configs array which will be easier to use
const configs = createNormalizedConfigs(options);
let path = '/';
let current: State | undefined = state;
const allParams: Record<string, any> = {};
let current: State | undefined = state;
while (current) {
let index = typeof current.index === 'number' ? current.index : 0;
let route = current.routes[index] as Route<string> & {
state?: State;
};
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 = '';
let pattern: string | undefined;
let currentParams: Record<string, any> = { ...route.params };
let currentOptions = configs;
// Keep all the route names that appeared during going deeper in config in case the pattern is resolved to undefined
let nestedRouteNames = [];
let hasNext = true;
while (route.name in currentOptions && hasNext) {
pattern = currentOptions[route.name].pattern;
nestedRouteNames.push(route.name);
if (route.params) {
const stringify = currentOptions[route.name]?.stringify;
currentParams = fromEntries(
Object.entries(route.params).map(([key, value]) => [
key,
stringify?.[key] ? stringify[key](value) : String(value),
])
);
if (pattern) {
Object.assign(allParams, currentParams);
}
}
// If there is no `screens` property or no nested state, we return pattern
if (!currentOptions[route.name].screens || route.state === undefined) {
hasNext = false;
while (route.name in currentOptions) {
if (typeof currentOptions[route.name] === 'string') {
pattern = currentOptions[route.name] as string;
break;
} else if (typeof currentOptions[route.name] === 'object') {
// if there is no `screens` property, we return pattern
if (
!(currentOptions[route.name] as {
screens: Options;
}).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 =
typeof route.state.index === 'number'
? route.state.index
: route.state.routes.length - 1;
typeof route.state.index === 'number' ? route.state.index : 0;
const nextRoute = route.state.routes[index];
const nestedConfig = currentOptions[route.name].screens;
const deeperConfig = (currentOptions[route.name] as {
screens: Options;
}).screens;
// if there is config for next route name, we go deeper
if (nestedConfig && nextRoute.name in nestedConfig) {
if (nextRoute.name in deeperConfig) {
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
route = nextRoute as Route<string> & { state?: State };
currentOptions = nestedConfig;
currentOptions = deeperConfig;
} else {
// If not, there is no sense in going deeper in config
hasNext = false;
// if not, there is no sense in going deeper in config
pattern = (currentOptions[route.name] as { path: string }).path;
nestedRouteNames = `${nestedRouteNames}/${route.name}`;
break;
}
}
}
}
}
if (pattern === undefined) {
pattern = nestedRouteNames.join('/');
// cut the first `/`
pattern = nestedRouteNames.substring(1);
}
// we don't add empty path strings to path
if (pattern !== '') {
const config =
currentOptions[route.name] !== undefined
? (currentOptions[route.name] as { stringify?: StringifyConfig })
.stringify
: undefined;
const params = route.params
? // Stringify all of the param values before we use them
Object.entries(route.params).reduce<{
[key: string]: string;
}>((acc, [key, value]) => {
acc[key] = config?.[key] ? config[key](value) : String(value);
return acc;
}, {})
: undefined;
if (currentOptions[route.name] !== undefined) {
path += pattern
.split('/')
.map((p) => {
const name = p.replace(/^:/, '').replace(/\?$/, '');
const name = p.replace(/^:/, '');
// If the path has a pattern for a param, put the param in the path
if (p.startsWith(':')) {
const value = allParams[name];
if (params && name in params && p.startsWith(':')) {
const value = params[name];
// Remove the used value from the params object since we'll use the rest for query string
if (currentParams) {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete currentParams[name];
}
if (value === undefined && p.endsWith('?')) {
// Optional params without value assigned in route.params should be ignored
return '';
}
delete params[name];
return encodeURIComponent(value);
}
@@ -157,82 +157,28 @@ export default function getPathFromState(
if (route.state) {
path += '/';
} else if (currentParams) {
for (let param in currentParams) {
if (currentParams[param] === 'undefined') {
} else if (params) {
for (let param in params) {
if (params[param] === 'undefined') {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete currentParams[param];
delete params[param];
}
}
const query = queryString.stringify(currentParams);
const query = queryString.stringify(params);
if (query) {
path += `?${query}`;
}
}
}
current = route.state;
}
// Remove multiple as well as trailing slashes
path = path.replace(/\/+/g, '/');
path = path.length > 1 ? path.replace(/\/$/, '') : path;
path =
path !== '/' && path.slice(path.length - 1) === '/'
? path.slice(0, -1)
: path;
return path;
}
// Object.fromEntries is not available in older iOS versions
const fromEntries = <K extends string, V>(entries: (readonly [K, V])[]) =>
entries.reduce((acc, [k, v]) => {
acc[k] = v;
return acc;
}, {} as Record<K, V>);
const joinPaths = (...paths: string[]): string =>
([] as string[])
.concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createConfigItem = (
config: OptionsItem | string,
parentPattern?: string
): ConfigItem => {
if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
return { pattern };
}
// If an object is specified as the value (e.g. Foo: { ... }),
// It can have `path` property and `screens` prop which has nested configs
const pattern =
config.exact !== true && parentPattern && config.path
? joinPaths(parentPattern, config.path)
: config.path;
const screens = config.screens
? createNormalizedConfigs(config.screens, pattern)
: undefined;
return {
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
pattern: pattern?.split('/').filter(Boolean).join('/'),
stringify: config.stringify,
screens,
};
};
const createNormalizedConfigs = (
options: PathConfig,
pattern?: string
): Record<string, ConfigItem> =>
fromEntries(
Object.entries(options).map(([name, c]) => {
const result = createConfigItem(c, pattern);
return [name, result];
})
);

View File

@@ -5,17 +5,25 @@ import {
PartialState,
InitialState,
} from '@react-navigation/routers';
import { PathConfig } from './types';
type ParseConfig = Record<string, (value: string) => any>;
type Options = {
[routeName: string]:
| string
| {
path?: string;
parse?: ParseConfig;
screens?: Options;
initialRouteName?: string;
};
};
type RouteConfig = {
screen: string;
regex?: RegExp;
path: string;
match: RegExp;
pattern: string;
routeNames: string[];
parse?: ParseConfig;
parse: ParseConfig | undefined;
};
type InitialRouteConfig = {
@@ -48,73 +56,34 @@ type ResultState = PartialState<NavigationState> & {
*/
export default function getStateFromPath(
path: string,
options: PathConfig = {}
options: Options = {}
): ResultState | undefined {
if (path === '') {
return undefined;
}
let initialRoutes: InitialRouteConfig[] = [];
// Create a normalized configs array which will be easier to use
const configs = ([] as RouteConfig[])
.concat(
const configs = ([] as RouteConfig[]).concat(
...Object.keys(options).map((key) =>
createNormalizedConfigs(key, options, [], initialRoutes)
)
)
.sort(
(a, b) =>
// Sort configs so the most exhaustive is always first to be chosen
b.pattern.split('/').length - a.pattern.split('/').length
);
let remaining = path
.replace(/\/+/g, '/') // Replace multiple slash (//) with single ones
.replace(/^\//, '') // Remove extra leading slash
.replace(/\?.*$/, ''); // Remove query params which we will handle later
// Make sure there is a trailing slash
remaining = remaining.endsWith('/') ? remaining : `${remaining}/`;
if (remaining === '/') {
// We need to add special handling of empty path so navigation to empty path also works
// When handling empty path, we should only look at the root level config
const match = configs.find(
(config) =>
config.path === '' &&
config.routeNames.every(
// Make sure that none of the parent configs have a non-empty path defined
(name) => !configs.find((c) => c.screen === name)?.path
)
);
if (match) {
return createNestedStateObject(
match.routeNames.map((name, i, self) => {
if (i === self.length - 1) {
return { name, params: parseQueryParams(path, match.parse) };
}
return { name };
}),
initialRoutes
);
}
return undefined;
}
let result: PartialState<NavigationState> | undefined;
let current: PartialState<NavigationState> | undefined;
let remaining = path
.replace(/[/]+/, '/') // Replace multiple slash (//) with single ones
.replace(/^\//, '') // Remove extra leading slash
.replace(/\?.*/, ''); // Remove query params which we will handle later
while (remaining) {
let routeNames: string[] | undefined;
let allParams: Record<string, any> | undefined;
let params: Record<string, any> | undefined;
// Go through all configs, and see if the next path segment matches our regex
for (const config of configs) {
if (!config.regex) {
continue;
}
const match = remaining.match(config.regex);
const match = remaining.match(config.match);
// If our regex matches, we need to extract params from the path
if (match) {
@@ -125,16 +94,21 @@ export default function getStateFromPath(
.filter((p) => p.startsWith(':'));
if (paramPatterns.length) {
allParams = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
const value = match![(i + 1) * 2].replace(/\//, ''); // The param segments appear every second item starting from 2 in the regex match result
params = paramPatterns.reduce<Record<string, any>>((acc, p, i) => {
const key = p.replace(/^:/, '');
const value = match[i + 1]; // The param segments start from index 1 in the regex match result
acc[p] = value;
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
return acc;
}, {});
}
remaining = remaining.replace(match[1], '');
// Remove the matched segment from the remaining path
remaining = remaining.replace(match[0], '');
break;
}
@@ -149,47 +123,35 @@ export default function getStateFromPath(
remaining = segments.join('/');
}
const state = createNestedStateObject(
routeNames.map((name) => {
const config = configs.find((c) => c.screen === name);
let state: InitialState;
let routeName = routeNames.shift() as string;
let initialRoute = findInitialRoute(routeName, initialRoutes);
let params: object | undefined;
if (allParams && config?.path) {
const pattern = config.path;
if (pattern) {
const paramPatterns = pattern
.split('/')
.filter((p) => p.startsWith(':'));
if (paramPatterns.length) {
params = paramPatterns.reduce<Record<string, any>>((acc, p) => {
const key = p.replace(/^:/, '').replace(/\?$/, '');
const value = allParams![p];
if (value) {
acc[key] =
config.parse && config.parse[key]
? config.parse[key](value)
: value;
}
return acc;
}, {});
}
}
}
if (params && Object.keys(params).length) {
return { name, params };
}
return { name };
}),
initialRoutes
state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
let nestedState = state;
while ((routeName = routeNames.shift() as string)) {
initialRoute = findInitialRoute(routeName, initialRoutes);
nestedState.routes[nestedState.index || 0].state = createNestedState(
initialRoute,
routeName,
routeNames.length === 0,
params
);
if (routeNames.length > 0) {
nestedState = nestedState.routes[nestedState.index || 0]
.state as InitialState;
}
}
}
if (current) {
// The state should be nested inside the deepest route we parsed before
while (current?.routes[current.index || 0].state) {
@@ -210,232 +172,9 @@ export default function getStateFromPath(
return undefined;
}
const route = findFocusedRoute(current);
const params = parseQueryParams(
path,
findParseConfigForRoute(route.name, configs)
);
if (params) {
route.params = { ...route.params, ...params };
}
return result;
}
const joinPaths = (...paths: string[]): string =>
([] as string[])
.concat(...paths.map((p) => p.split('/')))
.filter(Boolean)
.join('/');
const createNormalizedConfigs = (
screen: string,
routeConfig: PathConfig,
routeNames: string[] = [],
initials: InitialRouteConfig[],
parentPattern?: string
): RouteConfig[] => {
const configs: RouteConfig[] = [];
routeNames.push(screen);
const config = routeConfig[screen];
if (typeof config === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
const pattern = parentPattern ? joinPaths(parentPattern, config) : config;
configs.push(createConfigItem(screen, routeNames, pattern, config));
} else if (typeof config === 'object') {
let pattern: string | undefined;
// if an object is specified as the value (e.g. Foo: { ... }),
// it can have `path` property and
// it could have `screens` prop which has nested configs
if (typeof config.path === 'string') {
pattern =
config.exact !== true && parentPattern
? joinPaths(parentPattern, config.path)
: config.path;
configs.push(
createConfigItem(screen, routeNames, pattern, config.path, config.parse)
);
}
if (config.screens) {
// property `initialRouteName` without `screens` has no purpose
if (config.initialRouteName) {
initials.push({
initialRouteName: config.initialRouteName,
connectedRoutes: Object.keys(config.screens),
});
}
Object.keys(config.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs(
nestedConfig,
config.screens as PathConfig,
routeNames,
initials,
pattern
);
configs.push(...result);
});
}
}
routeNames.pop();
return configs;
};
const createConfigItem = (
screen: string,
routeNames: string[],
pattern: string,
path: string,
parse?: ParseConfig
): RouteConfig => {
// Normalize pattern to remove any leading, trailing slashes, duplicate slashes etc.
pattern = pattern.split('/').filter(Boolean).join('/');
const regex = pattern
? new RegExp(
`^(${pattern
.split('/')
.map((it) => {
if (it.startsWith(':')) {
return `(([^/]+\\/)${it.endsWith('?') ? '?' : ''})`;
}
return `${escape(it)}\\/`;
})
.join('')})`
)
: undefined;
return {
screen,
regex,
pattern,
path,
// The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames],
parse,
};
};
const findParseConfigForRoute = (
routeName: string,
flatConfig: RouteConfig[]
): ParseConfig | undefined => {
for (const config of flatConfig) {
if (routeName === config.routeNames[config.routeNames.length - 1]) {
return config.parse;
}
}
return undefined;
};
// Try to find an initial route connected with the one passed
const findInitialRoute = (
routeName: string,
initialRoutes: InitialRouteConfig[]
): string | undefined => {
for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName
? undefined
: config.initialRouteName;
}
}
return undefined;
};
// returns state object with values depending on whether
// it is the end of state and if there is initialRoute for this level
const createStateObject = (
initialRoute: string | undefined,
routeName: string,
params: Record<string, any> | undefined,
isEmpty: boolean
): InitialState => {
if (isEmpty) {
if (initialRoute) {
return {
index: 1,
routes: [{ name: initialRoute }, { name: routeName as string, params }],
};
} else {
return {
routes: [{ name: routeName as string, params }],
};
}
} else {
if (initialRoute) {
return {
index: 1,
routes: [
{ name: initialRoute },
{ name: routeName as string, params, state: { routes: [] } },
],
};
} else {
return {
routes: [{ name: routeName as string, params, state: { routes: [] } }],
};
}
}
};
const createNestedStateObject = (
routes: { name: string; params?: object }[],
initialRoutes: InitialRouteConfig[]
) => {
let state: InitialState;
let route = routes.shift() as { name: string; params?: object };
let initialRoute = findInitialRoute(route.name, initialRoutes);
state = createStateObject(
initialRoute,
route.name,
route.params,
routes.length === 0
);
if (routes.length > 0) {
let nestedState = state;
while ((route = routes.shift() as { name: string; params?: object })) {
initialRoute = findInitialRoute(route.name, initialRoutes);
const nestedStateIndex =
nestedState.index || nestedState.routes.length - 1;
nestedState.routes[nestedStateIndex].state = createStateObject(
initialRoute,
route.name,
route.params,
routes.length === 0
);
if (routes.length > 0) {
nestedState = nestedState.routes[nestedStateIndex]
.state as InitialState;
}
}
}
return state;
};
const findFocusedRoute = (state: InitialState) => {
let current: InitialState | undefined = state;
const query = path.split('?')[1];
if (query) {
while (current?.routes[current.index || 0].state) {
// The query params apply to the deepest route
current = current.routes[current.index || 0].state;
@@ -445,23 +184,150 @@ const findFocusedRoute = (state: InitialState) => {
current?.index || 0
];
return route;
};
const parseQueryParams = (
path: string,
parseConfig?: Record<string, (value: string) => any>
) => {
const query = path.split('?')[1];
const params = queryString.parse(query);
const parseFunction = findParseConfigForRoute(route.name, configs);
if (parseConfig) {
if (parseFunction) {
Object.keys(params).forEach((name) => {
if (parseConfig[name] && typeof params[name] === 'string') {
params[name] = parseConfig[name](params[name] as string);
if (parseFunction[name] && typeof params[name] === 'string') {
params[name] = parseFunction[name](params[name] as string);
}
});
}
return Object.keys(params).length ? params : undefined;
};
route.params = { ...route.params, ...params };
}
return result;
}
function createNormalizedConfigs(
key: string,
routeConfig: Options,
routeNames: string[] = [],
initials: InitialRouteConfig[]
): RouteConfig[] {
const configs: RouteConfig[] = [];
routeNames.push(key);
const value = routeConfig[key];
if (typeof value === 'string') {
// If a string is specified as the value of the key(e.g. Foo: '/path'), use it as the pattern
if (value !== '') {
configs.push(createConfigItem(routeNames, value));
}
} else if (typeof value === 'object') {
// if an object is specified as the value (e.g. Foo: { ... }),
// it can have `path` property and
// it could have `screens` prop which has nested configs
if (value.path && value.path !== '') {
configs.push(createConfigItem(routeNames, value.path, value.parse));
}
if (value.screens) {
// property `initialRouteName` without `screens` has no purpose
if (value.initialRouteName) {
initials.push({
initialRouteName: value.initialRouteName,
connectedRoutes: Object.keys(value.screens),
});
}
Object.keys(value.screens).forEach((nestedConfig) => {
const result = createNormalizedConfigs(
nestedConfig,
value.screens as Options,
routeNames,
initials
);
configs.push(...result);
});
}
}
routeNames.pop();
return configs;
}
function createConfigItem(
routeNames: string[],
pattern: string,
parse?: ParseConfig
): RouteConfig {
const match = new RegExp(
'^' + escape(pattern).replace(/:[a-z0-9]+/gi, '([^/]+)') + '/?'
);
return {
match,
pattern,
// The routeNames array is mutated, so copy it to keep the current state
routeNames: [...routeNames],
parse,
};
}
function findParseConfigForRoute(
routeName: string,
flatConfig: RouteConfig[]
): ParseConfig | undefined {
for (const config of flatConfig) {
if (routeName === config.routeNames[config.routeNames.length - 1]) {
return config.parse;
}
}
return undefined;
}
// tries to find an initial route connected with the one passed
function findInitialRoute(
routeName: string,
initialRoutes: InitialRouteConfig[]
): string | undefined {
for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName
? undefined
: config.initialRouteName;
}
}
return undefined;
}
// returns nested state object with values depending on whether
// it is the end of state and if there is initialRoute for this level
function createNestedState(
initialRoute: string | undefined,
routeName: string,
isEmpty: boolean,
params?: Record<string, any> | undefined
): InitialState {
if (isEmpty) {
if (initialRoute) {
return {
index: 1,
routes: [
{ name: initialRoute },
{ name: routeName as string, ...(params && { params }) },
],
};
} else {
return {
routes: [{ name: routeName as string, ...(params && { params }) }],
};
}
} else {
if (initialRoute) {
return {
index: 1,
routes: [
{ name: initialRoute },
{ name: routeName as string, state: { routes: [] } },
],
};
} else {
return { routes: [{ name: routeName as string, state: { routes: [] } }] };
}
}
}

View File

@@ -462,16 +462,3 @@ export type TypedNavigator<
_: RouteConfig<ParamList, RouteName, State, ScreenOptions, EventMap>
) => null;
};
export type PathConfig = {
[routeName: string]:
| string
| {
path?: string;
exact?: boolean;
parse?: Record<string, (value: string) => any>;
stringify?: Record<string, (value: any) => string>;
screens?: PathConfig;
initialRouteName?: string;
};
};

View File

@@ -9,7 +9,6 @@ import useNavigation from './useNavigation';
*/
export default function useIsFocused(): boolean {
const navigation = useNavigation();
// eslint-disable-next-line react-hooks/exhaustive-deps
const getCurrentValue = React.useCallback(navigation.isFocused, [navigation]);
const subscribe = React.useCallback(
(callback: (value: boolean) => void) => {

View File

@@ -362,14 +362,9 @@ export default function useNavigationBuilder<
return () => {
// We need to clean up state for this navigator on unmount
// We do it in a timeout because we need to detect if another navigator mounted in the meantime
// For example, if another navigator has started rendering, we should skip cleanup
// Otherwise, our cleanup step will cleanup state for the other navigator and re-initialize it
setTimeout(() => {
if (getCurrentState() !== undefined && getKey() === navigatorKey) {
setState(undefined);
}
}, 0);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

View File

@@ -8,15 +8,6 @@ const UNINTIALIZED_STATE = {};
export default function useSyncState<T>(initialState?: (() => T) | T) {
const stateRef = React.useRef<T>(UNINTIALIZED_STATE as any);
const isSchedulingRef = React.useRef(false);
const isMountedRef = React.useRef(true);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
if (stateRef.current === UNINTIALIZED_STATE) {
stateRef.current =
@@ -29,7 +20,7 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
const getState = React.useCallback(() => stateRef.current, []);
const setState = React.useCallback((state: T) => {
if (state === stateRef.current || !isMountedRef.current) {
if (state === stateRef.current) {
return;
}
@@ -51,10 +42,6 @@ export default function useSyncState<T>(initialState?: (() => T) | T) {
}, []);
const flushUpdates = React.useCallback(() => {
if (!isMountedRef.current) {
return;
}
// 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);

View File

@@ -3,110 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.7.5](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.4...@react-navigation/drawer@5.7.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.3...@react-navigation/drawer@5.7.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.2...@react-navigation/drawer@5.7.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.1...@react-navigation/drawer@5.7.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/drawer
## [5.7.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.7.0...@react-navigation/drawer@5.7.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.7.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.4...@react-navigation/drawer@5.7.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/issues/7971)
## [5.6.4](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.3...@react-navigation/drawer@5.6.4) (2020-05-05)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.3](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.2...@react-navigation/drawer@5.6.3) (2020-05-01)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.2](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.1...@react-navigation/drawer@5.6.2) (2020-05-01)
**Note:** Version bump only for package @react-navigation/drawer
## [5.6.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.6.0...@react-navigation/drawer@5.6.1) (2020-04-30)
**Note:** Version bump only for package @react-navigation/drawer
# [5.6.0](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.1...@react-navigation/drawer@5.6.0) (2020-04-30)
### Bug Fixes
* fix closing drawer on web with tap on overlay ([70be3f6](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/70be3f6d863c56211e2f90bdf743bd8526338248))
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/commit/942d2be2c72720469475ce12ec8df23825994dbf))
## [5.5.1](https://github.com/react-navigation/react-navigation/tree/master/packages/drawer/compare/@react-navigation/drawer@5.5.0...@react-navigation/drawer@5.5.1) (2020-04-27)
**Note:** Version bump only for package @react-navigation/drawer

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "5.7.5",
"version": "5.5.1",
"keywords": [
"react-native-component",
"react-component",
@@ -20,7 +20,6 @@
"homepage": "https://reactnavigation.org/docs/drawer-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -40,17 +39,17 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.4.0",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.7",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.8.0",
"react-native-reanimated": "^1.7.0",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.7.0",
"react-native-screens": "^2.3.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -25,7 +25,6 @@ export { default as useIsDrawerOpen } from './utils/useIsDrawerOpen';
export type {
DrawerNavigationOptions,
DrawerNavigationProp,
DrawerScreenProps,
DrawerContentOptions,
DrawerContentComponentProps,
} from './types';

View File

@@ -8,9 +8,8 @@ import {
NavigationHelpers,
DrawerNavigationState,
DrawerActionHelpers,
RouteProp,
} from '@react-navigation/native';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
import { PanGestureHandler } from 'react-native-gesture-handler';
export type Scene = {
route: Route<string>;
@@ -33,7 +32,6 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
drawerType?: 'front' | 'back' | 'slide' | 'permanent';
/**
* How far from the edge of the screen the swipe gesture should activate.
* Not supported on Web.
*/
edgeWidth?: number;
/**
@@ -60,9 +58,8 @@ export type DrawerNavigationConfig<T = DrawerContentOptions> = {
statusBarAnimation?: 'slide' | 'none' | 'fade';
/**
* Props to pass to the underlying pan gesture handler.
* Not supported on Web.
*/
gestureHandlerProps?: PanGestureHandlerProperties;
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
/**
* Whether the screens should render the first time they are accessed. Defaults to `true`.
* Set it to `false` if you want to render all screens on initial render.
@@ -116,15 +113,13 @@ 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`.
* Not supported on Web.
* Defaults to `true`
*/
gestureEnabled?: boolean;
/**
* Whether you can use swipe gestures to open or close the drawer.
* Defaults to `true`.
* Not supported on Web.
* Defaults to `true`
*/
swipeEnabled?: boolean;
@@ -209,14 +204,6 @@ export type DrawerNavigationProp<
> &
DrawerActionHelpers<ParamList>;
export type DrawerScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: DrawerNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type DrawerDescriptor = Descriptor<
ParamListBase,
string,

View File

@@ -10,14 +10,14 @@ import {
StyleProp,
View,
InteractionManager,
TouchableWithoutFeedback,
} from 'react-native';
import Animated from 'react-native-reanimated';
import {
PanGestureHandler,
TapGestureHandler,
GestureState,
} from './GestureHandler';
State as GestureState,
TapGestureHandlerStateChangeEvent,
} from 'react-native-gesture-handler';
import Animated from 'react-native-reanimated';
import Overlay from './Overlay';
const {
@@ -79,6 +79,7 @@ type Props = {
open: boolean;
onOpen: () => void;
onClose: () => void;
onGestureRef?: (ref: PanGestureHandler | null) => void;
gestureEnabled: boolean;
swipeEnabled: boolean;
drawerPosition: 'left' | 'right';
@@ -510,7 +511,18 @@ export default class DrawerView extends React.Component<Props> {
},
]);
private handleTapStateChange = event([
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>) =>
@@ -567,6 +579,7 @@ export default class DrawerView extends React.Component<Props> {
sceneContainerStyle,
drawerStyle,
overlayStyle,
onGestureRef,
renderDrawerContent,
renderSceneContent,
gestureHandlerProps,
@@ -611,6 +624,7 @@ export default class DrawerView extends React.Component<Props> {
return (
<PanGestureHandler
ref={onGestureRef}
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
@@ -649,20 +663,12 @@ export default class DrawerView extends React.Component<Props> {
</View>
{
// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : Platform.OS === 'web' ? (
<TouchableWithoutFeedback
onPress={
gestureEnabled ? () => this.toggleDrawer(false) : undefined
}
>
<Overlay progress={progress} style={overlayStyle as any} />
</TouchableWithoutFeedback>
) : (
drawerType === 'permanent' ? null : (
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={progress} style={overlayStyle as any} />
<Overlay progress={progress} style={overlayStyle} />
</TapGestureHandler>
)
}
@@ -731,11 +737,6 @@ const styles = StyleSheet.create({
},
main: {
flex: 1,
...Platform.select({
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
web: null,
default: { overflow: 'hidden' },
}),
overflow: 'hidden',
},
});

View File

@@ -11,6 +11,10 @@ import {
} from 'react-native';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import {
PanGestureHandler,
GestureHandlerRootView,
} from 'react-native-gesture-handler';
import {
NavigationHelpersContext,
DrawerNavigationState,
@@ -18,7 +22,7 @@ import {
useTheme,
} from '@react-navigation/native';
import { GestureHandlerRootView } from './GestureHandler';
import DrawerGestureContext from '../utils/DrawerGestureContext';
import SafeAreaProviderCompat from './SafeAreaProviderCompat';
import ResourceSavingScene from './ResourceSavingScene';
import DrawerContent from './DrawerContent';
@@ -90,6 +94,8 @@ export default function DrawerView({
Dimensions.get('window')
);
const drawerGestureRef = React.useRef<PanGestureHandler>(null);
const { colors } = useTheme();
const isDrawerOpen = state.history.some((it) => it.type === 'drawer');
@@ -199,6 +205,7 @@ export default function DrawerView({
<NavigationHelpersContext.Provider value={navigation}>
<GestureHandlerWrapper style={styles.content}>
<SafeAreaProviderCompat>
<DrawerGestureContext.Provider value={drawerGestureRef}>
<DrawerOpenContext.Provider value={isDrawerOpen}>
<Drawer
open={isDrawerOpen}
@@ -206,6 +213,10 @@ export default function DrawerView({
swipeEnabled={swipeEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
onGestureRef={(ref) => {
// @ts-ignore
drawerGestureRef.current = ref;
}}
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
@@ -242,6 +253,7 @@ export default function DrawerView({
dimensions={dimensions}
/>
</DrawerOpenContext.Provider>
</DrawerGestureContext.Provider>
</SafeAreaProviderCompat>
</GestureHandlerWrapper>
</NavigationHelpersContext.Provider>

View File

@@ -1,23 +0,0 @@
import * as React from 'react';
import {
PanGestureHandler as PanGestureHandlerNative,
PanGestureHandlerProperties,
} from 'react-native-gesture-handler';
import DrawerGestureContext from '../utils/DrawerGestureContext';
export function PanGestureHandler(props: PanGestureHandlerProperties) {
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
return (
<DrawerGestureContext.Provider value={gestureRef}>
<PanGestureHandlerNative {...props} />
</DrawerGestureContext.Provider>
);
}
export {
GestureHandlerRootView,
TapGestureHandler,
State as GestureState,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';

View File

@@ -1,31 +0,0 @@
import * as React from 'react';
import { View } from 'react-native';
import type {
PanGestureHandlerProperties,
TapGestureHandlerProperties,
} from 'react-native-gesture-handler';
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
<>{children}</>
);
export const PanGestureHandler = Dummy as React.ComponentType<
PanGestureHandlerProperties
>;
export const TapGestureHandler = Dummy as React.ComponentType<
TapGestureHandlerProperties
>;
export const GestureHandlerRootView = View;
export const GestureState = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
};
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';

View File

@@ -29,7 +29,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
styles.container,
Platform.OS === 'web'
? { display: isVisible ? 'flex' : 'none' }
: { overflow: 'hidden' },
: null,
style,
]}
collapsable={false}
@@ -52,6 +52,7 @@ export default class ResourceSavingScene extends React.Component<Props> {
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
attached: {
flex: 1,

View File

@@ -3,104 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.4...@react-navigation/material-bottom-tabs@5.2.5) (2020-05-16)
### Bug Fixes
* center icons in material tab bar. fixes [#8248](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8248) ([51b4087](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/51b40879bdb9cea5462a2291955513a88eb87340))
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.3...@react-navigation/material-bottom-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.2...@react-navigation/material-bottom-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.1...@react-navigation/material-bottom-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.2.0...@react-navigation/material-bottom-tabs@5.2.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.15...@react-navigation/material-bottom-tabs@5.2.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/issues/7971)
* use links in bottom navigation tabs ([f384706](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/commit/f384706741f7e2422c284b65da10425f7af680c0))
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.14...@react-navigation/material-bottom-tabs@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.13...@react-navigation/material-bottom-tabs@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.12...@react-navigation/material-bottom-tabs@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.11...@react-navigation/material-bottom-tabs@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.10...@react-navigation/material-bottom-tabs@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-bottom-tabs/compare/@react-navigation/material-bottom-tabs@5.1.9...@react-navigation/material-bottom-tabs@5.1.10) (2020-04-27)
**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.2.5",
"version": "5.1.10",
"keywords": [
"react-native-component",
"react-component",
@@ -20,7 +20,6 @@
"homepage": "https://reactnavigation.org/docs/material-bottom-tab-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -36,15 +35,15 @@
"clean": "del lib"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.4.0",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.7",
"@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.10.1",
"react-native-paper": "^3.7.0",
"react-native-vector-icons": "^6.6.0",
"typescript": "^3.8.3"
},

View File

@@ -14,5 +14,4 @@ export { default as MaterialBottomTabView } from './views/MaterialBottomTabView'
export type {
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationProp,
MaterialBottomTabScreenProps,
} from './types';

View File

@@ -6,7 +6,6 @@ import {
NavigationHelpers,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type MaterialBottomTabNavigationEventMap = {
@@ -33,14 +32,6 @@ export type MaterialBottomTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type MaterialBottomTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: MaterialBottomTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type MaterialBottomTabNavigationOptions = {
/**
* Title text for the screen.

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { StyleSheet, Platform } from 'react-native';
import { StyleSheet } from 'react-native';
import { BottomNavigation, DefaultTheme, DarkTheme } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import {
@@ -8,8 +8,6 @@ import {
TabNavigationState,
TabActions,
useTheme,
useLinkBuilder,
Link,
} from '@react-navigation/native';
import {
@@ -26,14 +24,13 @@ type Props = MaterialBottomTabNavigationConfig & {
type Scene = { route: { key: string } };
function MaterialBottomTabViewInner({
export default function MaterialBottomTabView({
state,
navigation,
descriptors,
...rest
}: Props) {
const { dark, colors } = useTheme();
const buildLink = useLinkBuilder();
const theme = React.useMemo(() => {
const t = dark ? DarkTheme : DefaultTheme;
@@ -49,6 +46,7 @@ function MaterialBottomTabViewInner({
}, [colors, dark]);
return (
<NavigationHelpersContext.Provider value={navigation}>
<BottomNavigation
{...rest}
theme={theme}
@@ -60,39 +58,6 @@ function MaterialBottomTabViewInner({
})
}
renderScene={({ route }) => descriptors[route.key].render()}
renderTouchable={
Platform.OS === 'web'
? ({
onPress,
route,
accessibilityRole: _0,
borderless: _1,
centered: _2,
rippleColor: _3,
style,
...rest
}) => {
return (
<Link
{...rest}
// @ts-ignore
to={buildLink(route.name, route.params)}
accessibilityRole="link"
onPress={(e: any) => {
if (
!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) && // ignore clicks with modifier keys
(e.button == null || e.button === 0) // ignore everything but left clicks
) {
e.preventDefault();
onPress?.(e);
}
}}
style={[styles.touchable, style]}
/>
);
}
: undefined
}
renderIcon={({ route, focused, color }) => {
const { options } = descriptors[route.key];
@@ -103,6 +68,8 @@ function MaterialBottomTabViewInner({
color={color}
size={24}
style={styles.icon}
importantForAccessibility="no-hide-descendants"
accessibilityElementsHidden
/>
);
}
@@ -140,13 +107,6 @@ function MaterialBottomTabViewInner({
}
}}
/>
);
}
export default function MaterialBottomTabView(props: Props) {
return (
<NavigationHelpersContext.Provider value={props.navigation}>
<MaterialBottomTabViewInner {...props} />
</NavigationHelpersContext.Provider>
);
}
@@ -155,8 +115,4 @@ const styles = StyleSheet.create({
icon: {
backgroundColor: 'transparent',
},
touchable: {
display: 'flex',
justifyContent: 'center',
},
});

View File

@@ -3,100 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.4...@react-navigation/material-top-tabs@5.2.5) (2020-05-16)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.3...@react-navigation/material-top-tabs@5.2.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.2...@react-navigation/material-top-tabs@5.2.3) (2020-05-14)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.1...@react-navigation/material-top-tabs@5.2.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.2.0...@react-navigation/material-top-tabs@5.2.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.15...@react-navigation/material-top-tabs@5.2.0) (2020-05-08)
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/issues/7971)
## [5.1.15](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.14...@react-navigation/material-top-tabs@5.1.15) (2020-05-05)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.14](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.13...@react-navigation/material-top-tabs@5.1.14) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.13](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.12...@react-navigation/material-top-tabs@5.1.13) (2020-05-01)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.12](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.11...@react-navigation/material-top-tabs@5.1.12) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.11](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.10...@react-navigation/material-top-tabs@5.1.11) (2020-04-30)
**Note:** Version bump only for package @react-navigation/material-top-tabs
## [5.1.10](https://github.com/react-navigation/react-navigation/tree/master/packages/material-top-tabs/compare/@react-navigation/material-top-tabs@5.1.9...@react-navigation/material-top-tabs@5.1.10) (2020-04-27)
**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.2.5",
"version": "5.1.10",
"keywords": [
"react-native-component",
"react-component",
@@ -20,7 +20,6 @@
"homepage": "https://reactnavigation.org/docs/material-top-tab-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -39,15 +38,15 @@
"color": "^3.1.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-navigation/native": "^5.4.0",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.7",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-reanimated": "^1.8.0",
"react-native-reanimated": "^1.7.0",
"react-native-tab-view": "^2.14.0",
"typescript": "^3.8.3"
},

View File

@@ -15,7 +15,6 @@ export { default as MaterialTopTabBar } from './views/MaterialTopTabBar';
export type {
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationProp,
MaterialTopTabScreenProps,
MaterialTopTabBarProps,
MaterialTopTabBarOptions,
} from './types';

View File

@@ -8,7 +8,6 @@ import {
NavigationProp,
TabNavigationState,
TabActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type MaterialTopTabNavigationEventMap = {
@@ -47,14 +46,6 @@ export type MaterialTopTabNavigationProp<
> &
TabActionHelpers<ParamList>;
export type MaterialTopTabScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: MaterialTopTabNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type MaterialTopTabNavigationOptions = {
/**
* Title text for the screen.

View File

@@ -3,135 +3,6 @@
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/native/compare/@react-navigation/native@5.3.2...@react-navigation/native@5.4.0) (2020-05-16)
### Bug Fixes
* fix types for linking options ([d14f38b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d14f38b80ad569d5828c1919cea426c659173924))
### Features
* add a PathConfig type ([60cb3c9](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/60cb3c9ba76d7ef166c9fe8b55f23728975b5b6e))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.1...@react-navigation/native@5.3.2) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.3.0...@react-navigation/native@5.3.1) (2020-05-14)
**Note:** Version bump only for package @react-navigation/native
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.6...@react-navigation/native@5.3.0) (2020-05-10)
### Features
* initialState should take priority over deep link ([039017b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/039017bc2af69120d2d10e8f2c8a62919c37eb65))
## [5.2.6](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.5...@react-navigation/native@5.2.6) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.2.5](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.4...@react-navigation/native@5.2.5) (2020-05-08)
### Bug Fixes
* return a promise-like from getInitialState ([#8210](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8210)) ([85ae378](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/85ae378d8cb1073895b281e13ebccee881d4c062))
## [5.2.4](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.3...@react-navigation/native@5.2.4) (2020-05-05)
### Bug Fixes
* return undefined for buildLink if linking is not enabled ([9fd2635](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/9fd2635756362c8da79656b4d9b101bebaaf7003))
## [5.2.3](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.2...@react-navigation/native@5.2.3) (2020-05-01)
### Bug Fixes
* default linking enabled to true ([c7b8e2e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/c7b8e2e9666733143eef156b27f3e4995c36b856))
## [5.2.2](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.1...@react-navigation/native@5.2.2) (2020-05-01)
### Bug Fixes
* don't throw when using 'useLinking'. fixes [#8171](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8171) ([10eca8b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/10eca8b92edbce6dbef8abaf189e4b59a29b3748))
## [5.2.1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.2.0...@react-navigation/native@5.2.1) (2020-04-30)
### Bug Fixes
* render fallback only if linking is enabled. closes [#8161](https://github.com/react-navigation/react-navigation/tree/master/packages/native/issues/8161) ([1c075ff](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/1c075ffb169d233ed0515efea264a5a69b4de52e))
# [5.2.0](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.7...@react-navigation/native@5.2.0) (2020-04-30)
### Bug Fixes
* add catch to thenable returned by getInitialState ([d6fa279](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/d6fa279d9371c7a6403d10d209a2a64147891c63))
* return onPress instead of onClick for useLinkProps ([ae5442e](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/ae5442ebe812b91fa1f12164f27d1aeed918ab0e))
### Features
* add `useLinkBuilder` hook to build links ([2792f43](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2792f438fe45428fe193e3708fee7ad61966cbf4))
* add a useLinkProps hook ([f2291d1](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/f2291d110faa2aa8e10c9133c1c0c28d54af7917))
* add action prop to Link ([942d2be](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/942d2be2c72720469475ce12ec8df23825994dbf))
* add Link component as useLinkTo hook for navigating to links ([2573b5b](https://github.com/react-navigation/react-navigation/tree/master/packages/native/commit/2573b5beaac1240434e52f3f57bb29da2f541c88))
## [5.1.7](https://github.com/react-navigation/react-navigation/tree/master/packages/native/compare/@react-navigation/native@5.1.6...@react-navigation/native@5.1.7) (2020-04-27)
**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.4.0",
"version": "5.1.7",
"keywords": [
"react-native",
"react-navigation",
@@ -16,7 +16,6 @@
"homepage": "https://reactnavigation.org",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -32,16 +31,16 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^5.7.0"
"@react-navigation/core": "^5.3.5"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@react-native-community/bob": "^0.10.0",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-testing-library": "^1.13.2",
"react-native-testing-library": "^1.12.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Text, TextProps, GestureResponderEvent, Platform } from 'react-native';
import { Text, TextProps, GestureResponderEvent } from 'react-native';
import { NavigationAction } from '@react-navigation/core';
import useLinkProps from './useLinkProps';
@@ -28,15 +28,16 @@ export default function Link({ to, action, ...rest }: Props) {
rest.onPress?.(e);
}
if (props.onClick) {
props.onClick(e);
} else {
props.onPress(e);
}
};
return React.createElement(Text, {
...props,
...rest,
...Platform.select({
web: { onClick: onPress } as any,
default: { onPress },
}),
...(props.onClick ? { onClick: onPress } : { onPress }),
});
}

View File

@@ -22,7 +22,7 @@ type Props = NavigationContainerProps & {
* Container component which holds the navigation state designed for React Native apps.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will override deep links when specified. Make sure that you don't specify an `initialState` when there's a deep link (`Linking.getInitialURL()`).
* @param props.initialState Initial state object for the navigation tree. When deep link handling is enabled, this will be ignored if there's an incoming link.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.theme Theme object for the navigators.
* @param props.linking Options for deep linking. Deep link handling is enabled when this prop is provided, unless `linking.enabled` is `false`.
@@ -46,13 +46,15 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
...linking,
});
const [isReady, initialState] = useThenable(getInitialState);
const [isReady, initialState = rest.initialState] = useThenable(
getInitialState
);
React.useImperativeHandle(ref, () => refContainer.current);
const linkingContext = React.useMemo(() => ({ options: linking }), [linking]);
if (rest.initialState == null && isLinkingEnabled && !isReady) {
if (!isReady) {
// This is temporary until we have Suspense for data-fetching
// Then the fallback will be handled by a parent `Suspense` component
return fallback as React.ReactElement;
@@ -63,9 +65,7 @@ const NavigationContainer = React.forwardRef(function NavigationContainer(
<ThemeProvider value={theme}>
<BaseNavigationContainer
{...rest}
initialState={
rest.initialState == null ? initialState : rest.initialState
}
initialState={initialState}
ref={refContainer}
/>
</ThemeProvider>

View File

@@ -17,7 +17,7 @@ it('throws if multiple instances of useLinking are used', () => {
let element;
expect(() => (element = render(<Sample />))).toThrowError(
'Looks like you have configured linking in multiple places.'
"Looks like you are using 'useLinking' in multiple components."
);
// @ts-ignore
@@ -41,7 +41,9 @@ it('throws if multiple instances of useLinking are used', () => {
<B />
</>
))
).toThrowError('Looks like you have configured linking in multiple places.');
).toThrowError(
"Looks like you are using 'useLinking' in multiple components."
);
// @ts-ignore
element?.unmount();
@@ -55,19 +57,5 @@ it('throws if multiple instances of useLinking are used', () => {
render(wrapper2).unmount();
expect(() => (element = render(wrapper2))).not.toThrow();
// @ts-ignore
element?.unmount();
function Sample3() {
useLinking(ref, options);
useLinking(ref, { ...options, enabled: false });
return null;
}
expect(() => (element = render(<Sample3 />))).not.toThrowError();
// @ts-ignore
element?.unmount();
expect(() => render(wrapper2)).not.toThrow();
});

View File

@@ -1,7 +1,6 @@
import {
getStateFromPath as getStateFromPathDefault,
getPathFromState as getPathFromStateDefault,
PathConfig,
} from '@react-navigation/core';
export type Theme = {
@@ -40,7 +39,7 @@ export type LinkingOptions = {
* }
* ```
*/
config?: PathConfig;
config?: Parameters<typeof getStateFromPathDefault>[1];
/**
* Custom function to parse the URL to a valid navigation state (advanced).
* Only applicable on Web.

View File

@@ -52,18 +52,14 @@ export default function useLinkBuilder() {
(name: string, params?: object) => {
const { options } = linking;
if (options?.enabled === false) {
return undefined;
}
// If we couldn't find a navigation object in context, we're at root
// So we'll construct a basic state object to use
const state = navigation
? getRootStateForNavigate(navigation, {
index: 0,
routes: [{ name, params }],
})
: // If we couldn't find a navigation object in context, we're at root
// So we'll construct a basic state object to use
{
: {
index: 0,
routes: [{ name, params }],
};

View File

@@ -49,14 +49,6 @@ export default function useLinkProps({ to, action }: Props) {
throw new Error("Couldn't find a navigation object.");
}
} else {
if (typeof to !== 'string') {
throw new Error(
`To 'to' option is invalid (found '${String(
to
)}'. It must be a valid string for navigation.`
);
}
linkTo(to);
}
}
@@ -65,6 +57,9 @@ export default function useLinkProps({ to, action }: Props) {
return {
href: to,
accessibilityRole: 'link' as const,
onPress,
...Platform.select({
web: { onClick: onPress } as any,
default: { onPress },
}),
};
}

View File

@@ -12,28 +12,22 @@ let isUsingLinking = false;
export default function useLinking(
ref: React.RefObject<NavigationContainerRef>,
{
enabled = true,
enabled,
prefixes,
config,
getStateFromPath = getStateFromPathDefault,
}: LinkingOptions
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {
if (isUsingLinking) {
throw new Error(
[
'Looks like you have configured linking in multiple places. This is likely an error since deep links should only be handled in one place to avoid conflicts. Make sure that:',
"- You are not using both 'linking' prop and 'useLinking'",
"- You don't have 'useLinking' in multiple components",
Platform.OS === 'android'
? "- You have set 'android:launchMode=singleTask' in the '<activity />' section of the 'AndroidManifest.xml' file to avoid launching multiple instances"
: '',
]
.join('\n')
.trim()
"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 = enabled !== false;
isUsingLinking = true;
}
return () => {

View File

@@ -10,15 +10,6 @@ import { LinkingOptions } from './types';
type ResultState = ReturnType<typeof getStateFromPathDefault>;
type HistoryState = { index: number };
declare const history: {
state?: HistoryState;
go(delta: number): void;
pushState(state: HistoryState, title: string, url: string): void;
replaceState(state: HistoryState, title: string, url: string): void;
};
const getStateLength = (state: NavigationState) => {
let length = 0;
@@ -43,25 +34,19 @@ let isUsingLinking = false;
export default function useLinking(
ref: React.RefObject<NavigationContainerRef>,
{
enabled = true,
enabled,
config,
getStateFromPath = getStateFromPathDefault,
getPathFromState = getPathFromStateDefault,
}: LinkingOptions
) {
React.useEffect(() => {
if (enabled !== false && isUsingLinking) {
if (isUsingLinking) {
throw new Error(
[
'Looks like you have configured linking in multiple places. This is likely an error since URL integration should only be handled in one place to avoid conflicts. Make sure that:',
"- You are not using both 'linking' prop and 'useLinking'",
"- You don't have 'useLinking' in multiple components",
]
.join('\n')
.trim()
"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 = enabled !== false;
isUsingLinking = true;
}
return () => {
@@ -96,16 +81,10 @@ export default function useLinking(
}
// Make it a thenable to keep consistent with the native impl
const thenable = {
then(onfulfilled?: (state: ResultState | undefined) => void) {
return Promise.resolve(onfulfilled ? onfulfilled(value) : value);
},
catch() {
return thenable;
},
return {
then: (callback: (state: ResultState | undefined) => void) =>
callback(value),
};
return thenable as PromiseLike<ResultState | undefined>;
}, []);
const previousStateLengthRef = React.useRef<number | undefined>(undefined);

View File

@@ -1,6 +1,10 @@
import * as React from 'react';
export default function useThenable<T>(create: () => PromiseLike<T>) {
export default function useThenable<T>(
create: () => {
then(success: (result: T) => void, error?: (error: any) => void): void;
}
) {
const [promise] = React.useState(create);
// Check if our thenable is synchronous
@@ -20,20 +24,20 @@ export default function useThenable<T>(create: () => PromiseLike<T>) {
React.useEffect(() => {
let cancelled = false;
const resolve = async () => {
let result;
try {
result = await promise;
} finally {
if (!resolved) {
promise.then(
(result) => {
if (!cancelled) {
setState([true, result]);
}
},
(error) => {
if (!cancelled) {
console.error(error);
setState([true, undefined]);
}
};
if (!resolved) {
resolve();
}
);
}
return () => {

View File

@@ -3,36 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.4.4](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.3...@react-navigation/routers@5.4.4) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
## [5.4.3](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.2...@react-navigation/routers@5.4.3) (2020-05-08)
**Note:** Version bump only for package @react-navigation/routers
## [5.4.2](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.1...@react-navigation/routers@5.4.2) (2020-04-30)
### Bug Fixes
* fix backBehavior with initialRoute ([#8110](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/issues/8110)) ([420f692](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/commit/420f6926e111d32c2388c44ff0bee2b8ea238a57))
## [5.4.1](https://github.com/react-navigation/react-navigation/tree/master/packages/routers/compare/@react-navigation/routers@5.4.0...@react-navigation/routers@5.4.1) (2020-04-27)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/routers",
"description": "Routers to help build custom navigators",
"version": "5.4.4",
"version": "5.4.1",
"keywords": [
"react",
"react-native",
@@ -15,7 +15,6 @@
"homepage": "https://reactnavigation.org/docs/custom-routers.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -31,10 +30,10 @@
"clean": "del lib"
},
"dependencies": {
"nanoid": "^3.1.5"
"nanoid": "^3.0.2"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-native-community/bob": "^0.10.0",
"del-cli": "^3.0.0",
"typescript": "^3.8.3"
},

View File

@@ -59,31 +59,21 @@ export const TabActions = {
const getRouteHistory = (
routes: Route<string>[],
index: number,
backBehavior: BackBehavior,
initialRouteName: string | undefined
backBehavior: BackBehavior
) => {
const history = [{ type: TYPE_ROUTE, key: routes[index].key }];
let initialRouteIndex;
switch (backBehavior) {
case 'initialRoute':
if (index !== 0) {
history.unshift({ type: TYPE_ROUTE, key: routes[0].key });
}
break;
case 'order':
for (let i = index; i > 0; i--) {
history.unshift({ type: TYPE_ROUTE, key: routes[i - 1].key });
}
break;
case 'initialRoute':
initialRouteIndex = routes.findIndex(
(route) => route.name === initialRouteName
);
initialRouteIndex = initialRouteIndex === -1 ? 0 : initialRouteIndex;
if (initialRouteIndex !== index) {
history.unshift({
type: TYPE_ROUTE,
key: routes[initialRouteIndex].key,
});
}
break;
case 'history':
// The history will fill up on navigation
break;
@@ -95,8 +85,7 @@ const getRouteHistory = (
const changeIndex = (
state: TabNavigationState,
index: number,
backBehavior: BackBehavior,
initialRouteName: string | undefined
backBehavior: BackBehavior
) => {
let history;
@@ -107,12 +96,7 @@ const changeIndex = (
.filter((it) => (it.type === 'route' ? it.key !== currentKey : false))
.concat({ type: TYPE_ROUTE, key: currentKey });
} else {
history = getRouteHistory(
state.routes,
index,
backBehavior,
initialRouteName
);
history = getRouteHistory(state.routes, index, backBehavior);
}
return {
@@ -146,12 +130,7 @@ export default function TabRouter({
params: routeParamList[name],
}));
const history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
const history = getRouteHistory(routes, index, backBehavior);
return {
stale: false,
@@ -210,12 +189,7 @@ export default function TabRouter({
);
if (!history?.length) {
history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
history = getRouteHistory(routes, index, backBehavior);
}
return {
@@ -249,12 +223,7 @@ export default function TabRouter({
);
if (!history.length) {
history = getRouteHistory(
routes,
index,
backBehavior,
initialRouteName
);
history = getRouteHistory(routes, index, backBehavior);
}
return {
@@ -273,7 +242,7 @@ export default function TabRouter({
return state;
}
return changeIndex(state, index, backBehavior, initialRouteName);
return changeIndex(state, index, backBehavior);
},
getStateForAction(state, action) {
@@ -315,8 +284,7 @@ export default function TabRouter({
: state.routes,
},
index,
backBehavior,
initialRouteName
backBehavior
);
}

View File

@@ -882,78 +882,6 @@ it('handles back action with backBehavior: initialRoute', () => {
).toEqual(null);
});
it('handles back action with backBehavior: initialRoute and initialRouteName', () => {
const router = TabRouter({
backBehavior: 'initialRoute',
initialRouteName: 'baz',
});
const options = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {},
};
let state = router.getInitialState(options);
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'baz-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('bar'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual({
stale: false,
type: 'tab',
key: 'tab-test',
index: 1,
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz' },
{ key: 'qux-test', name: 'qux' },
],
history: [{ type: 'route', key: 'baz-test' }],
});
state = router.getStateForAction(
state,
TabActions.jumpTo('baz'),
options
) as TabNavigationState;
expect(
router.getStateForAction(state, CommonActions.goBack(), options)
).toEqual(null);
});
it('handles back action with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
const options = {

View File

@@ -3,131 +3,6 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [5.3.7](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.6...@react-navigation/stack@5.3.7) (2020-05-16)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.6](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.5...@react-navigation/stack@5.3.6) (2020-05-15)
### Bug Fixes
* reduce header title margin. fixes [#8267](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8267) ([d45dbe9](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/d45dbe97dc6625c6a8e80b5e658ab5a95045e5e8))
## [5.3.5](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.4...@react-navigation/stack@5.3.5) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.4](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.3...@react-navigation/stack@5.3.4) (2020-05-14)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.3](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.2...@react-navigation/stack@5.3.3) (2020-05-11)
### Bug Fixes
* fix ios transitionspec settle time ([#8028](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8028)) ([dd7cff2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/dd7cff201608365a80f1d50a006df3e0d18e94a1))
## [5.3.2](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.1...@react-navigation/stack@5.3.2) (2020-05-10)
**Note:** Version bump only for package @react-navigation/stack
## [5.3.1](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.3.0...@react-navigation/stack@5.3.1) (2020-05-08)
### Bug Fixes
* fix building typescript definitions. closes [#8216](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/8216) ([47a1229](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/47a12298378747edd2d22e54dc1c8677f98c49b4))
# [5.3.0](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.19...@react-navigation/stack@5.3.0) (2020-05-08)
### Bug Fixes
* add proper margins to the header title ([f07cd13](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/f07cd135619d635e8841aa0df0b6e687636e7408))
* include safe are insets in title's margins ([4d1e102](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/4d1e102f8c3ffab116d0195fbab3086f6345a077))
### Features
* add generic type aliases for screen props ([bea14aa](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/bea14aa26fd5cbfebc7973733c5cf1f44fd323aa)), closes [#7971](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/issues/7971)
## [5.2.19](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.18...@react-navigation/stack@5.2.19) (2020-05-05)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.18](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.17...@react-navigation/stack@5.2.18) (2020-05-01)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.17](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.16...@react-navigation/stack@5.2.17) (2020-05-01)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.16](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.15...@react-navigation/stack@5.2.16) (2020-04-30)
**Note:** Version bump only for package @react-navigation/stack
## [5.2.15](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.14...@react-navigation/stack@5.2.15) (2020-04-30)
### Bug Fixes
* make sure the address bar hides when scrolling on web ([0a19e94](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/commit/0a19e94b23a4d2b5f22d1d9deb0544f586f475ee))
## [5.2.14](https://github.com/react-navigation/react-navigation/tree/master/packages/stack/compare/@react-navigation/stack@5.2.13...@react-navigation/stack@5.2.14) (2020-04-27)

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.3.7",
"version": "5.2.14",
"keywords": [
"react-native-component",
"react-component",
@@ -19,7 +19,6 @@
"homepage": "https://reactnavigation.org/docs/stack-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"source": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
@@ -39,18 +38,18 @@
"react-native-iphone-x-helper": "^1.2.1"
},
"devDependencies": {
"@react-native-community/bob": "^0.13.1",
"@react-native-community/masked-view": "^0.1.10",
"@react-navigation/native": "^5.4.0",
"@react-native-community/bob": "^0.10.0",
"@react-native-community/masked-view": "^0.1.7",
"@react-navigation/native": "^5.1.7",
"@types/color": "^3.0.1",
"@types/react": "^16.9.34",
"@types/react-native": "^0.62.7",
"@types/react": "^16.9.23",
"@types/react-native": "^0.61.22",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-native": "~0.61.5",
"react-native-gesture-handler": "^1.6.0",
"react-native-safe-area-context": "^0.7.3",
"react-native-screens": "^2.7.0",
"react-native-screens": "^2.3.0",
"typescript": "^3.8.3"
},
"peerDependencies": {

View File

@@ -11,8 +11,8 @@ export const TransitionIOSSpec: TransitionSpec = {
damping: 500,
mass: 3,
overshootClamping: true,
restDisplacementThreshold: 10,
restSpeedThreshold: 10,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
},
};

View File

@@ -51,7 +51,6 @@ export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
export type {
StackNavigationOptions,
StackNavigationProp,
StackScreenProps,
StackHeaderProps,
StackHeaderLeftButtonProps,
StackHeaderTitleProps,

View File

@@ -15,7 +15,6 @@ import {
NavigationHelpers,
StackNavigationState,
StackActionHelpers,
RouteProp,
} from '@react-navigation/native';
export type StackNavigationEventMap = {
@@ -46,14 +45,6 @@ export type StackNavigationProp<
> &
StackActionHelpers<ParamList>;
export type StackScreenProps<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = {
navigation: StackNavigationProp<ParamList, RouteName>;
route: RouteProp<ParamList, RouteName>;
};
export type Layout = { width: number; height: number };
export type GestureDirection =
@@ -285,8 +276,7 @@ export type StackNavigationOptions = StackHeaderOptions &
cardStyle?: StyleProp<ViewStyle>;
/**
* Whether transition animation should be enabled the screen.
* If you set it to `false`, the screen won't animate when pushing or popping.
* Defaults to `true` on Android and iOS, `false` on Web.
* If you set it to `false`, the screen won't animate when pushing or popping. Defaults to `true`.
*/
animationEnabled?: boolean;
/**
@@ -296,12 +286,10 @@ export type StackNavigationOptions = StackHeaderOptions &
animationTypeForReplace?: 'push' | 'pop';
/**
* Whether you can use gestures to dismiss this screen. Defaults to `true` on iOS, `false` on Android.
* Not supported on Web.
*/
gestureEnabled?: boolean;
/**
* Object to override the distance of touch start from the edge of the screen to recognize gestures.
* Not supported on Web.
*/
gestureResponseDistance?: {
/**
@@ -314,8 +302,8 @@ export type StackNavigationOptions = StackHeaderOptions &
horizontal?: number;
};
/**
* Number which determines the relevance of velocity for the gesture. Defaults to 0.3.
* Not supported on Web.
* Number which determines the relevance of velocity for the gesture.
* Defaults to 0.3.
*/
gestureVelocityImpact?: number;
/**

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { PanGestureHandler } from 'react-native-gesture-handler';
export default React.createContext<React.Ref<
import('react-native-gesture-handler').PanGestureHandler
> | null>(null);
export default React.createContext<React.Ref<PanGestureHandler> | undefined>(
undefined
);

View File

@@ -1,22 +0,0 @@
import * as React from 'react';
import {
PanGestureHandler as PanGestureHandlerNative,
PanGestureHandlerProperties,
} from 'react-native-gesture-handler';
import GestureHandlerRefContext from '../utils/GestureHandlerRefContext';
export function PanGestureHandler(props: PanGestureHandlerProperties) {
const gestureRef = React.useRef<PanGestureHandlerNative>(null);
return (
<GestureHandlerRefContext.Provider value={gestureRef}>
<PanGestureHandlerNative {...props} />
</GestureHandlerRefContext.Provider>
);
}
export {
GestureHandlerRootView,
State as GestureState,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';

View File

@@ -1,24 +0,0 @@
import * as React from 'react';
import { View } from 'react-native';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
const Dummy: any = ({ children }: { children: React.ReactNode }) => (
<>{children}</>
);
export const PanGestureHandler = Dummy as React.ComponentType<
PanGestureHandlerProperties
>;
export const GestureHandlerRootView = View;
export const GestureState = {
UNDETERMINED: 0,
FAILED: 1,
BEGAN: 2,
CANCELLED: 3,
ACTIVE: 4,
END: 5,
};
export type { PanGestureHandlerGestureEvent } from 'react-native-gesture-handler';

View File

@@ -41,7 +41,6 @@ export default React.memo(function Header(props: StackHeaderProps) {
: previous.route.name;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
const goBack = React.useCallback(
debounce(() => {
if (navigation.isFocused() && navigation.canGoBack()) {

View File

@@ -10,7 +10,7 @@ import {
} from 'react-native';
import { useTheme } from '@react-navigation/native';
import MaskedView from '../MaskedView';
import { TouchableItem } from '../TouchableItem';
import TouchableItem from '../TouchableItem';
import { StackHeaderLeftButtonProps } from '../../types';
type Props = StackHeaderLeftButtonProps;

View File

@@ -307,8 +307,6 @@ export default class HeaderSegment extends React.Component<Props, State> {
})
: null;
const rightButton = right ? right({ tintColor: headerTintColor }) : null;
return (
<React.Fragment>
<Animated.View
@@ -347,19 +345,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
pointerEvents="box-none"
style={[
headerTitleAlign === 'left'
? {
position: 'absolute',
left: (leftButton ? 72 : 16) + insets.left,
right: (rightButton ? 72 : 16) + insets.right,
}
: {
marginHorizontal:
(leftButton ? 32 : 16) +
(leftButton && headerBackTitleVisible !== false
? 40
: 0) +
Math.max(insets.left, insets.right),
},
? { position: 'absolute', left: leftButton ? 72 : 16 }
: { marginHorizontal: 18 },
titleStyle,
titleContainerStyle,
]}
@@ -372,7 +359,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
style: customTitleStyle,
})}
</Animated.View>
{rightButton ? (
{right ? (
<Animated.View
pointerEvents="box-none"
style={[
@@ -382,7 +369,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
rightContainerStyle,
]}
>
{rightButton}
{right({ tintColor: headerTintColor })}
</Animated.View>
) : null}
</View>

View File

@@ -2,7 +2,7 @@ import * as React from 'react';
import { Animated, StyleSheet, Platform } from 'react-native';
import { useTheme } from '@react-navigation/native';
type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & {
type Props = React.ComponentProps<typeof Animated.Text> & {
tintColor?: string;
children?: string;
};

View File

@@ -9,15 +9,14 @@ import {
Platform,
InteractionManager,
} from 'react-native';
import { EdgeInsets } from 'react-native-safe-area-context';
import Color from 'color';
import CardSheet from './CardSheet';
import {
PanGestureHandler,
GestureState,
State as GestureState,
PanGestureHandlerGestureEvent,
} from '../GestureHandler';
} from 'react-native-gesture-handler';
import { EdgeInsets } from 'react-native-safe-area-context';
import Color from 'color';
import StackGestureRefContext from '../../utils/GestureHandlerRefContext';
import CardAnimationContext from '../../utils/CardAnimationContext';
import getDistanceForDirection from '../../utils/getDistanceForDirection';
import getInvertedMultiplier from '../../utils/getInvertedMultiplier';
@@ -37,7 +36,6 @@ type Props = ViewProps & {
gesture: Animated.Value;
layout: Layout;
insets: EdgeInsets;
pageOverflowEnabled: boolean;
gestureDirection: GestureDirection;
onOpen: () => void;
onClose: () => void;
@@ -414,6 +412,8 @@ export default class Card extends React.Component<Props> {
}
}
private gestureRef = React.createRef<PanGestureHandler>();
private contentRef = React.createRef<View>();
render() {
@@ -430,7 +430,6 @@ export default class Card extends React.Component<Props> {
shadowEnabled,
gestureEnabled,
gestureDirection,
pageOverflowEnabled,
children,
containerStyle: customContainerStyle,
contentStyle,
@@ -500,6 +499,7 @@ export default class Card extends React.Component<Props> {
pointerEvents="box-none"
>
<PanGestureHandler
ref={this.gestureRef}
enabled={layout.width !== 0 && gestureEnabled}
onGestureEvent={handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
@@ -523,14 +523,14 @@ export default class Card extends React.Component<Props> {
pointerEvents="none"
/>
) : null}
<CardSheet
<View
ref={this.contentRef}
enabled={pageOverflowEnabled}
layout={layout}
style={contentStyle}
style={[styles.content, contentStyle]}
>
<StackGestureRefContext.Provider value={this.gestureRef}>
{children}
</CardSheet>
</StackGestureRefContext.Provider>
</View>
</Animated.View>
</PanGestureHandler>
</Animated.View>
@@ -544,6 +544,10 @@ const styles = StyleSheet.create({
container: {
flex: 1,
},
content: {
flex: 1,
overflow: 'hidden',
},
overlay: {
flex: 1,
backgroundColor: '#000',

View File

@@ -4,13 +4,7 @@ import { Route, useTheme } from '@react-navigation/native';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card';
import HeaderHeightContext from '../../utils/HeaderHeightContext';
import {
Scene,
Layout,
StackHeaderMode,
StackCardMode,
TransitionPreset,
} from '../../types';
import { Scene, Layout, StackHeaderMode, TransitionPreset } from '../../types';
type Props = TransitionPreset & {
index: number;
@@ -51,7 +45,6 @@ type Props = TransitionPreset & {
horizontal?: number;
};
gestureVelocityImpact?: number;
mode: StackCardMode;
headerMode: StackHeaderMode;
headerShown?: boolean;
headerTransparent?: boolean;
@@ -80,7 +73,6 @@ function CardContainer({
gestureVelocityImpact,
getPreviousRoute,
getFocusedRoute,
mode,
headerMode,
headerShown,
headerStyleInterpolator,
@@ -186,7 +178,6 @@ function CardContainer({
accessibilityElementsHidden={!focused}
importantForAccessibility={focused ? 'auto' : 'no-hide-descendants'}
pointerEvents={active ? 'box-none' : pointerEvents}
pageOverflowEnabled={headerMode === 'screen' && mode === 'card'}
containerStyle={
headerMode === 'float' && !headerTransparent && headerShown !== false
? { marginTop: headerHeight }

View File

@@ -1,49 +0,0 @@
import * as React from 'react';
import { View, ViewProps, StyleSheet } from 'react-native';
type Props = ViewProps & {
enabled: boolean;
layout: { width: number; height: number };
children: React.ReactNode;
};
// This component will render a page which overflows the screen
// if the container fills the body by comparing the size
// This lets the document.body handle scrolling of the content
// It's necessary for mobile browsers to be able to hide address bar on scroll
export default React.forwardRef<View, Props>(function CardSheet(
{ enabled, layout, style, ...rest },
ref
) {
const [fill, setFill] = React.useState(false);
React.useEffect(() => {
if (typeof document === 'undefined' || !document.body) {
// Only run when DOM is available
return;
}
const width = document.body.clientWidth;
const height = document.body.clientHeight;
setFill(width === layout.width && height === layout.height);
}, [layout.height, layout.width]);
return (
<View
{...rest}
ref={ref}
style={[enabled && fill ? styles.page : styles.card, style]}
/>
);
});
const styles = StyleSheet.create({
page: {
minHeight: '100%',
},
card: {
flex: 1,
overflow: 'hidden',
},
});

View File

@@ -519,7 +519,6 @@ export default class CardStack extends React.Component<Props, State> {
onHeaderHeightChange={this.handleHeaderLayout}
getPreviousRoute={getPreviousRoute}
getFocusedRoute={this.getFocusedRoute}
mode={mode}
headerMode={headerMode}
headerShown={headerShown}
headerTransparent={headerTransparent}
@@ -565,6 +564,7 @@ export default class CardStack extends React.Component<Props, State> {
const styles = StyleSheet.create({
container: {
flex: 1,
overflow: 'hidden',
},
floating: {
position: 'absolute',

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { View, Platform, StyleSheet } from 'react-native';
import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import {
NavigationHelpersContext,
StackActions,
@@ -8,7 +9,6 @@ import {
Route,
} from '@react-navigation/native';
import { GestureHandlerRootView } from '../GestureHandler';
import CardStack from './CardStack';
import KeyboardManager from '../KeyboardManager';
import HeaderContainer, {
@@ -420,7 +420,7 @@ export default class StackView extends React.Component<Props, State> {
} = this.state;
const headerMode =
mode === 'card' && Platform.OS === 'ios' ? 'float' : 'screen';
mode !== 'modal' && Platform.OS === 'ios' ? 'float' : 'screen';
return (
<NavigationHelpersContext.Provider value={navigation}>

View File

@@ -1,81 +0,0 @@
/**
* TouchableItem renders a touchable that looks native on both iOS and Android.
*
* It provides an abstraction on top of TouchableNativeFeedback and
* TouchableOpacity.
*
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
* of TouchableNativeFeedback.
*/
import * as React from 'react';
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
View,
ViewProps,
} from 'react-native';
import BorderlessButton from './BorderlessButton';
export type Props = ViewProps & {
pressColor: string;
disabled?: boolean;
borderless?: boolean;
delayPressIn?: number;
onPress?: () => void;
children: React.ReactNode;
};
const ANDROID_VERSION_LOLLIPOP = 21;
export class TouchableItem extends React.Component<Props> {
static defaultProps = {
borderless: false,
pressColor: 'rgba(0, 0, 0, .32)',
};
render() {
/*
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
* therefore only enable it on Android Lollipop and above.
*
* All touchables on Android should have the ripple effect according to
* platform design guidelines.
* We need to pass the background prop to specify a borderless ripple effect.
*/
if (
Platform.OS === 'android' &&
Platform.Version >= ANDROID_VERSION_LOLLIPOP
) {
const { style, pressColor, borderless, children, ...rest } = this.props;
return (
<TouchableNativeFeedback
{...rest}
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
>
<View style={style}>{React.Children.only(children)}</View>
</TouchableNativeFeedback>
);
} else if (Platform.OS === 'ios') {
return (
<BorderlessButton
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
disallowInterruption
enabled={!this.props.disabled}
{...this.props}
>
{this.props.children}
</BorderlessButton>
);
} else {
return (
<TouchableOpacity {...this.props}>
{this.props.children}
</TouchableOpacity>
);
}
}
}

View File

@@ -1,3 +1,80 @@
import { TouchableOpacity } from 'react-native';
/**
* TouchableItem renders a touchable that looks native on both iOS and Android.
*
* It provides an abstraction on top of TouchableNativeFeedback and
* TouchableOpacity.
*
* On iOS you can pass the props of TouchableOpacity, on Android pass the props
* of TouchableNativeFeedback.
*/
import * as React from 'react';
import {
Platform,
TouchableNativeFeedback,
TouchableOpacity,
View,
ViewProps,
} from 'react-native';
export const TouchableItem = (TouchableOpacity as any) as typeof import('./TouchableItem.native').TouchableItem;
import BorderlessButton from './BorderlessButton';
type Props = ViewProps & {
pressColor: string;
disabled?: boolean;
borderless?: boolean;
delayPressIn?: number;
onPress?: () => void;
};
const ANDROID_VERSION_LOLLIPOP = 21;
export default class TouchableItem extends React.Component<Props> {
static defaultProps = {
borderless: false,
pressColor: 'rgba(0, 0, 0, .32)',
};
render() {
/*
* TouchableNativeFeedback.Ripple causes a crash on old Android versions,
* therefore only enable it on Android Lollipop and above.
*
* All touchables on Android should have the ripple effect according to
* platform design guidelines.
* We need to pass the background prop to specify a borderless ripple effect.
*/
if (
Platform.OS === 'android' &&
Platform.Version >= ANDROID_VERSION_LOLLIPOP
) {
const { style, pressColor, borderless, children, ...rest } = this.props;
return (
<TouchableNativeFeedback
{...rest}
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
>
<View style={style}>{React.Children.only(children)}</View>
</TouchableNativeFeedback>
);
} else if (Platform.OS === 'ios') {
return (
<BorderlessButton
hitSlop={{ top: 10, bottom: 10, right: 10, left: 10 }}
disallowInterruption
enabled={!this.props.disabled}
{...this.props}
>
{this.props.children}
</BorderlessButton>
);
} else {
return (
<TouchableOpacity {...this.props}>
{this.props.children}
</TouchableOpacity>
);
}
}
}

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2017 React Navigation Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,5 @@
# `@react-navigation/web-stack`
Stack navigator for React Navigation on Web.
Installation instructions and documentation can be found on the [React Navigation website](https://reactnavigation.org/docs/web-stack-navigator.html).

View File

@@ -0,0 +1,62 @@
{
"name": "@react-navigation/webstack",
"description": "Stack navigator component for Web",
"version": "5.0.0",
"keywords": [
"react",
"react-native",
"react-component",
"react-navigation",
"web",
"stack"
],
"license": "MIT",
"repository": "https://github.com/react-navigation/react-navigation/tree/master/packages/web-stack",
"bugs": {
"url": "https://github.com/react-navigation/react-navigation/issues"
},
"homepage": "https://reactnavigation.org/docs/web-stack-navigator.html",
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"files": [
"src",
"lib"
],
"sideEffects": false,
"publishConfig": {
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"devDependencies": {
"@react-native-community/bob": "^0.10.0",
"@react-navigation/native": "^5.1.6",
"@types/react": "^16.9.23",
"del-cli": "^3.0.0",
"react": "~16.9.0",
"react-dom": "~16.9.0",
"typescript": "^3.8.3"
},
"peerDependencies": {
"@react-navigation/native": "^5.0.5",
"react": "*"
},
"@react-native-community/bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
}
}

View File

@@ -0,0 +1,23 @@
/**
* Navigators
*/
export { default as createWebStackNavigator } from './navigators/createWebStackNavigator';
/**
* Views
*/
export { default as WebStackView } from './views/Stack/WebStackView';
export { default as Header } from './views/Header/Header';
export { default as HeaderTitle } from './views/Header/HeaderTitle';
export { default as HeaderBackButton } from './views/Header/HeaderBackButton';
/**
* Types
*/
export type {
WebStackNavigationOptions,
WebStackNavigationProp,
WebStackHeaderProps,
WebStackHeaderLeftButtonProps,
WebStackHeaderTitleProps,
} from './types';

View File

@@ -0,0 +1,81 @@
import * as React from 'react';
import {
useNavigationBuilder,
createNavigatorFactory,
DefaultNavigatorOptions,
EventArg,
StackRouter,
StackRouterOptions,
StackNavigationState,
StackActions,
} from '@react-navigation/native';
import WebStackView from '../views/Stack/WebStackView';
import {
WebStackNavigationConfig,
WebStackNavigationOptions,
WebStackNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<WebStackNavigationOptions> &
StackRouterOptions &
WebStackNavigationConfig;
function StackNavigator({
initialRouteName,
children,
screenOptions,
...rest
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
StackNavigationState,
StackRouterOptions,
WebStackNavigationOptions,
WebStackNavigationEventMap
>(StackRouter, {
initialRouteName,
children,
screenOptions,
});
React.useEffect(
() =>
navigation.addListener &&
navigation.addListener('tabPress', (e) => {
const isFocused = navigation.isFocused();
// Run the operation in the next frame so we're sure all listeners have been run
// This is necessary to know if preventDefault() has been called
requestAnimationFrame(() => {
if (
state.index > 0 &&
isFocused &&
!(e as EventArg<'tabPress', true>).defaultPrevented
) {
// When user taps on already focused tab and we're inside the tab,
// reset the stack to replicate native behaviour
navigation.dispatch({
...StackActions.popToTop(),
target: state.key,
});
}
});
}),
[navigation, state.index, state.key]
);
return (
<WebStackView
{...rest}
state={state}
descriptors={descriptors}
navigation={navigation}
/>
);
}
export default createNavigatorFactory<
StackNavigationState,
WebStackNavigationOptions,
WebStackNavigationEventMap,
typeof StackNavigator
>(StackNavigator);

View File

@@ -0,0 +1,166 @@
import * as React from 'react';
import {
NavigationProp,
ParamListBase,
Descriptor,
RouteProp,
NavigationHelpers,
StackNavigationState,
StackActionHelpers,
} from '@react-navigation/native';
export type WebStackNavigationEventMap = {};
export type WebStackNavigationHelpers = NavigationHelpers<
ParamListBase,
WebStackNavigationEventMap
>;
export type WebStackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string
> = NavigationProp<
ParamList,
RouteName,
StackNavigationState,
WebStackNavigationOptions,
WebStackNavigationEventMap
> &
StackActionHelpers<ParamList>;
export type WebStackHeaderOptions = {
/**
* String or a function that returns a React Element to be used by the header.
* Defaults to scene `title`.
* It receives `allowFontScaling`, `onLayout`, `style` and `children` in the options object as an argument.
* The title string is passed in `children`.
*/
headerTitle?: string | ((props: WebStackHeaderTitleProps) => React.ReactNode);
/**
* How to align the the header title.
* Defaults to `center` on iOS and `left` on Android.
*/
headerTitleAlign?: 'left' | 'center';
/**
* Style object for the title component.
*/
headerTitleStyle?: React.CSSProperties;
/**
* Tint color for the header.
*/
headerTintColor?: string;
/**
* Function which returns a React Element to display on the left side of the header.
* It receives a number of arguments when rendered (`onPress`, `label`, `labelStyle` and more.
*/
headerLeft?: (props: WebStackHeaderLeftButtonProps) => React.ReactNode;
/**
* Function which returns a React Element to display on the right side of the header.
*/
headerRight?: (props: { tintColor?: string }) => React.ReactNode;
/**
* Style object for the header. You can specify a custom background color here, for example.
*/
headerStyle?: React.CSSProperties;
};
export type WebStackHeaderProps = {
/**
* Navigation prop for the header.
*/
route: RouteProp<ParamListBase, string>;
/**
* Navigation prop for the header.
*/
navigation: WebStackNavigationProp<ParamListBase>;
/**
* Descriptors for the header.
*/
descriptor: WebStackDescriptor;
/**
* If header should display back button
*/
canGoBack: boolean;
};
export type WebStackDescriptor = Descriptor<
ParamListBase,
string,
StackNavigationState,
WebStackNavigationOptions
>;
export type WebStackDescriptorMap = {
[key: string]: WebStackDescriptor;
};
export type WebStackNavigationOptions = WebStackHeaderOptions & {
/**
* String that can be displayed in the header as a fallback for `headerTitle`.
*/
title?: string;
/**
* Function that given `HeaderProps` returns a React Element to display as a header.
*/
header?: (props: WebStackHeaderProps) => React.ReactNode;
/**
* Whether to show the header. The header is shown by default unless `headerMode` was set to `none`.
* Setting this to `false` hides the header.
*/
headerShown?: boolean;
/**
* Style object for the card in stack.
* You can provide a custom background color to use instead of the default background here.
*
* You can also specify `{ backgroundColor: 'transparent' }` to make the previous screen visible underneath.
* This is useful to implement things like modal dialogs..
*/
cardStyle?: React.CSSProperties;
/**
* Whether transition animation should be enabled the screen.
* If you set it to `false`, the screen won't animate when pushing or popping. Defaults to `true`.
*/
animationEnabled?: boolean;
/**
* The type of animation to use when this screen replaces another screen. Defaults to `push`.
* When `pop` is used, the `pop` animation is applied to the screen being replaced.
*/
animationTypeForReplace?: 'push' | 'pop';
};
export type WebStackNavigationConfig = {};
export type WebStackHeaderLeftButtonProps = {
/**
* Whether the button is disabled.
*/
disabled?: boolean;
/**
* Callback to call when the button is clicked.
* By default, this triggers `goBack`.
*/
onClick?: () => void;
/**
* Style object for the button
*/
style?: React.CSSProperties;
/**
* Whether it's possible to navigate back in stack.
*/
canGoBack?: boolean;
};
export type WebStackHeaderTitleProps = {
/**
* Tint color for the header.
*/
tintColor?: string;
/**
* Content of the title element. Usually the title string.
*/
children?: string;
/**
* Style object for the title element.
*/
style?: React.CSSProperties;
};

View File

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

View File

@@ -0,0 +1,49 @@
import * as React from 'react';
import { StackActions } from '@react-navigation/native';
import HeaderSegment from './HeaderSegment';
import HeaderTitle from './HeaderTitle';
import debounce from '../../utils/debounce';
import { WebStackHeaderProps, WebStackHeaderTitleProps } from '../../types';
export default React.memo(function Header({
navigation,
route,
descriptor,
}: WebStackHeaderProps) {
const { options } = descriptor;
const title =
typeof options.headerTitle !== 'function' &&
options.headerTitle !== undefined
? options.headerTitle
: options.title !== undefined
? options.title
: route.name;
const goBack = React.useCallback(
debounce(() => {
if (navigation.isFocused() && navigation.canGoBack()) {
navigation.dispatch({
...StackActions.pop(),
source: route.key,
});
}
}, 50),
[navigation, route.key]
);
return (
<HeaderSegment
{...options}
route={route}
descriptor={descriptor}
title={title}
headerTitle={
typeof options.headerTitle !== 'function'
? (props: WebStackHeaderTitleProps) => <HeaderTitle {...props} />
: options.headerTitle
}
onGoBack={goBack}
/>
);
});

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import { useTheme } from '@react-navigation/native';
import { WebStackHeaderLeftButtonProps } from '../../types';
type Props = WebStackHeaderLeftButtonProps;
export default function HeaderBackButton({ disabled, onClick, style }: Props) {
const { colors } = useTheme();
/* TODO: styling */
return (
<button
type="button"
disabled={disabled}
style={{
color: style?.color !== undefined ? style.color : colors.text,
...styles.button,
...style,
}}
onClick={onClick}
>
Back
{/* TODO: SVG image for back button */}
</button>
);
}
const styles = {
button: {},
};

View File

@@ -0,0 +1,74 @@
import * as React from 'react';
import {
NavigationContext,
NavigationRouteContext,
Route,
ParamListBase,
RouteProp,
} from '@react-navigation/native';
import Header from './Header';
import { WebStackNavigationProp, WebStackDescriptor } from '../../types';
export type Props = {
scenes: {
route: RouteProp<ParamListBase, string>;
descriptor: WebStackDescriptor;
}[];
getPreviousRoute: (props: {
route: Route<string>;
}) => Route<string> | undefined;
getFocusedRoute: () => Route<string>;
style?: React.CSSProperties;
};
export default function HeaderContainer({
scenes,
getFocusedRoute,
getPreviousRoute,
style,
}: Props) {
const focusedRoute = getFocusedRoute();
return (
<div style={style}>
{scenes.slice(-3).map((scene, i, self) => {
if (i !== self.length - 1 || !scene) {
return null;
}
const { options } = scene.descriptor;
const isFocused = focusedRoute.key === scene.route.key;
const previousRoute = getPreviousRoute({ route: scene.route });
const props = {
canGoBack: !previousRoute,
route: scene.route,
descriptor: scene.descriptor,
navigation: scene.descriptor.navigation as WebStackNavigationProp<
ParamListBase
>,
};
return (
<NavigationContext.Provider
key={scene.route.key}
value={scene.descriptor.navigation}
>
<NavigationRouteContext.Provider value={scene.route}>
<div aria-hidden={isFocused ? true : false}>
{options.headerShown !== false ? (
options.header !== undefined ? (
options.header(props)
) : (
<Header {...props} />
)
) : null}
</div>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
})}
</div>
);
}

View File

@@ -0,0 +1,72 @@
import * as React from 'react';
import { RouteProp, ParamListBase, useTheme } from '@react-navigation/native';
import HeaderBackButton from './HeaderBackButton';
import {
WebStackHeaderLeftButtonProps,
WebStackHeaderTitleProps,
WebStackHeaderOptions,
WebStackDescriptor,
} from '../../types';
type Props = WebStackHeaderOptions & {
headerTitle: (props: WebStackHeaderTitleProps) => React.ReactNode;
onGoBack?: () => void;
title?: string;
route: RouteProp<ParamListBase, string>;
descriptor: WebStackDescriptor;
};
export default function HeaderSegment(props: Props) {
const {
title: currentTitle,
onGoBack,
headerTitle,
headerLeft: left = onGoBack
? (props: WebStackHeaderLeftButtonProps) => (
<HeaderBackButton {...props} />
)
: undefined,
headerTintColor,
headerRight: right,
headerTitleStyle,
headerStyle,
} = props;
const { colors } = useTheme();
const leftButton = left
? left({
onClick: onGoBack,
canGoBack: Boolean(onGoBack),
})
: null;
return (
<div
style={{
backgroundColor: colors.card,
borderBottomColor: colors.border,
...styles.header,
...headerStyle,
}}
>
{leftButton}
{headerTitle({
children: currentTitle,
style: { marginLeft: 18, marginRight: 16, ...headerTitleStyle },
})}
{right ? right({ tintColor: headerTintColor }) : null}
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
header: {
display: 'flex',
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
borderBottomWidth: 1,
},
};

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