Compare commits

...

20 Commits

Author SHA1 Message Date
Michal Osadnik
f1b976b68c chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.4
 - @react-navigation/drawer@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.3
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.6
 - @react-navigation/material-top-tabs@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.6
 - @react-navigation/stack@5.0.0-alpha.8
2019-08-29 10:40:13 +01:00
Michał Osadnik
6b75cbaaa6 feat: handle navigating with both with both key and name (#83) 2019-08-29 09:46:51 +02:00
satyajit.happy
c951027ebb fix: handle both null and undefined in useScrollToTop 2019-08-29 08:33:42 +02:00
Janic Duplessis
cdbf1e97f9 feat: handle animated component wrappers in useScrollToTop (#81) 2019-08-28 23:13:00 +01:00
Janic Duplessis
56a2ee99f9 chore: add prettier config to package.json so it is picked up by editors (#82) 2019-08-28 23:12:29 +01:00
satyajit.happy
a9d4813b47 fix: allow making params optional. fixes #80 2019-08-28 22:05:48 +02:00
satyajit.happy
74c3377b63 chore: fix typo in example 2019-08-28 21:48:58 +02:00
satyajit.happy
8c1acc33c6 fix: fix gestures not working in stack 2019-08-28 21:41:15 +02:00
satyajit.happy
d72a96d1ef chore: add example for pop to top on tab press 2019-08-28 21:28:18 +02:00
satyajit.happy
f9e8c7e80f feat: handle more methods in useScrollToTop 2019-08-28 21:17:48 +02:00
satyajit.happy
9245c79990 feat: export NavigationContext 2019-08-28 15:52:48 +02:00
satyajit.happy
ea4c753d0a refactor: rename 'timing' option to 'animation' in transition spec 2019-08-28 15:48:17 +02:00
satyajit.happy
01196d7b48 docs: add JSDoc for transition configurations 2019-08-28 15:41:11 +02:00
Janic Duplessis
b4a5c3c35e fix: types path (#75) 2019-08-28 11:36:37 +01:00
Michal Osadnik
f302416631 chore: publish
- @react-navigation/native@5.0.0-alpha.4
 - @react-navigation/stack@5.0.0-alpha.7
2019-08-28 11:24:44 +01:00
satyajit.happy
dead4e826a fix: fix stack nested in tab always getting reset 2019-08-28 12:23:15 +02:00
Michal Osadnik
d5b4210eb2 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.5
 - @react-navigation/drawer@5.0.0-alpha.5
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.5
 - @react-navigation/material-top-tabs@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.5
 - @react-navigation/stack@5.0.0-alpha.6
2019-08-28 11:22:18 +01:00
Michal Osadnik
38336b0290 feat: disable gesture logic when no gesture stack 2019-08-28 11:22:04 +01:00
satyajit.happy
fc37e93b5b chore: add some badges 2019-08-27 17:09:59 +02:00
satyajit.happy
093858b68b test: improve coverage for router tests 2019-08-27 16:58:43 +02:00
46 changed files with 1644 additions and 272 deletions

View File

@@ -1,5 +1,9 @@
# Rethinking Navigation
[![Build Status][build-badge]][build]
[![Code Coverage][coverage-badge]][coverage]
[![MIT License][license-badge]][license]
An exploration of a component-first API for React Navigation for building more dynamic navigation solutions.
## Considerations
@@ -530,3 +534,12 @@ yarn lerna publish
```
This will automatically bump the version and publish the packages. It'll also publish the changelogs on GitHub for each package.
<!-- badges -->
[build-badge]: https://img.shields.io/circleci/project/github/react-navigation/navigation-ex/master.svg?style=flat-square
[build]: https://circleci.com/gh/react-navigation/navigation-ex
[coverage-badge]: https://img.shields.io/codecov/c/github/react-navigation/navigation-ex.svg?style=flat-square
[coverage]: https://codecov.io/github/react-navigation/navigation-ex
[license-badge]: https://img.shields.io/npm/l/@react-navigation/core.svg?style=flat-square
[license]: https://opensource.org/licenses/MIT

View File

@@ -64,5 +64,11 @@
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
}
},
"prettier": {
"tabWidth": 2,
"useTabs": false,
"singleQuote": true,
"trailingComma": "es5"
},
"name": "react-navigation"
}

View File

@@ -3,6 +3,26 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.5...@react-navigation/bottom-tabs@5.0.0-alpha.6) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
* types path ([#75](https://github.com/react-navigation/navigation-ex/issues/75)) ([b4a5c3c](https://github.com/react-navigation/navigation-ex/commit/b4a5c3c))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.4...@react-navigation/bottom-tabs@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.3...@react-navigation/bottom-tabs@5.0.0-alpha.4) (2019-08-27)

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -20,7 +20,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/bottom-tabssrc/index.d.ts",
"types": "lib/typescript/bottom-tabs/src/index.d.ts",
"files": [
"src",
"lib"
@@ -33,7 +33,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4",
"@react-navigation/routers": "^5.0.0-alpha.6",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -53,8 +53,8 @@ export type BottomTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};

View File

@@ -3,6 +3,23 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.3...@react-navigation/core@5.0.0-alpha.4) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
### Features
* export NavigationContext ([9245c79](https://github.com/react-navigation/navigation-ex/commit/9245c79))
* handle navigating with both with both key and name ([#83](https://github.com/react-navigation/navigation-ex/issues/83)) ([6b75cba](https://github.com/react-navigation/navigation-ex/commit/6b75cba))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.2...@react-navigation/core@5.0.0-alpha.3) (2019-08-27)

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.3",
"version": "5.0.0-alpha.4",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -10,7 +10,8 @@ export type Action =
type: 'NAVIGATE';
payload:
| { name: string; key?: undefined; params?: object }
| { key: string; name?: undefined; params?: object };
| { key: string; name?: undefined; params?: object }
| { key: string; name: string; params?: object };
source?: string;
target?: string;
}
@@ -38,7 +39,10 @@ export function goBack(): Action {
}
export function navigate(
route: { key: string; params?: object } | { name: string; params?: object }
route:
| { key: string; params?: object }
| { name: string; params?: object }
| { name: string; key: string; params?: object }
): Action;
export function navigate(name: string, params?: object): Action;
export function navigate(...args: any): Action {
@@ -47,12 +51,9 @@ export function navigate(...args: any): Action {
} else {
const payload = args[0];
if (
(payload.hasOwnProperty('key') && payload.hasOwnProperty('name')) ||
(!payload.hasOwnProperty('key') && !payload.hasOwnProperty('name'))
) {
if (!payload.hasOwnProperty('key') && !payload.hasOwnProperty('name')) {
throw new Error(
'While calling navigate with an object as the argument, you need to specify either name or key'
'While calling navigate with an object as the argument, you need to specify name or key'
);
}

View File

@@ -7,42 +7,7 @@ import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
beforeEach(() => (MockRouterKey.current = 0));
it('throws if NAVIGATE dispatched with both key and name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return descriptors[state.routes[state.index].key].render();
};
const FooScreen = (props: any) => {
React.useEffect(() => {
props.navigation.navigate({ key: '1', name: '2' });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return null;
};
const onStateChange = jest.fn();
const element = (
<NavigationContainer onStateChange={onStateChange}>
<TestNavigator initialRouteName="foo">
<Screen
name="foo"
component={FooScreen}
initialParams={{ count: 10 }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(() => render(element).update(element)).toThrowError(
'While calling navigate with an object as the argument, you need to specify either name or key'
);
});
it('throws if NAVIGATE dispatched neither both key nor name', () => {
it('throws if NAVIGATE dispatched neither key nor name', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
@@ -73,6 +38,6 @@ it('throws if NAVIGATE dispatched neither both key nor name', () => {
);
expect(() => render(element).update(element)).toThrowError(
'While calling navigate with an object as the argument, you need to specify either name or key'
'While calling navigate with an object as the argument, you need to specify name or key'
);
});

View File

@@ -6,6 +6,8 @@ export { default as BaseRouter } from './BaseRouter';
export { default as NavigationContainer } from './NavigationContainer';
export { default as createNavigator } from './createNavigator';
export { default as NavigationContext } from './NavigationContext';
export { default as useNavigationBuilder } from './useNavigationBuilder';
export { default as useNavigation } from './useNavigation';
export { default as useFocusEffect } from './useFocusEffect';

View File

@@ -274,8 +274,8 @@ type NavigationHelpersCommon<
* @param [params] Params object for the route.
*/
navigate<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -287,7 +287,7 @@ type NavigationHelpersCommon<
navigate<RouteName extends keyof ParamList>(
route:
| { key: string; params?: ParamList[RouteName] }
| { name: RouteName; params: ParamList[RouteName] }
| { name: RouteName; key?: string; params: ParamList[RouteName] }
): void;
/**
@@ -298,7 +298,7 @@ type NavigationHelpersCommon<
*/
replace<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.5...@react-navigation/drawer@5.0.0-alpha.6) (2019-08-29)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.4...@react-navigation/drawer@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/drawer
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.3...@react-navigation/drawer@5.0.0-alpha.4) (2019-08-27)
**Note:** Version bump only for package @react-navigation/drawer

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4",
"@react-navigation/routers": "^5.0.0-alpha.6",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.2...@react-navigation/example@5.0.0-alpha.3) (2019-08-29)
### Features
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.1...@react-navigation/example@5.0.0-alpha.2) (2019-08-27)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/example",
"description": "Demo app to showcase various functionality of React Navigation",
"version": "5.0.0-alpha.2",
"version": "5.0.0-alpha.3",
"private": true,
"workspaces": {
"nohoist": [

View File

@@ -8,6 +8,7 @@ import TouchableBounce from 'react-native/Libraries/Components/Touchable/Touchab
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
const getTabBarIcon = (name: string) => ({
tintColor,
@@ -20,6 +21,7 @@ const getTabBarIcon = (name: string) => ({
);
type BottomTabParams = {
article: undefined;
albums: undefined;
contacts: undefined;
chat: undefined;
@@ -30,6 +32,18 @@ const BottomTabs = createBottomTabNavigator<BottomTabParams>();
export default function BottomTabsScreen() {
return (
<BottomTabs.Navigator>
<BottomTabs.Screen
name="article"
options={{
title: 'Article',
tabBarIcon: getTabBarIcon('chrome-reader-mode'),
tabBarButtonComponent: TouchableBounce,
}}
>
{props => (
<SimpleStackScreen {...props} options={{ headerMode: 'none' }} />
)}
</BottomTabs.Screen>
<BottomTabs.Screen
name="chat"
component={Chat}

View File

@@ -1,10 +1,10 @@
import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import Article from '../Shared/Article';
import Albums from '../Shared/Albums';
import Contacts from '../Shared/Contacts';
import Chat from '../Shared/Chat';
import SimpleStackScreen from './SimpleStack';
type MaterialBottomTabParams = {
article: undefined;
@@ -22,13 +22,16 @@ export default function MaterialBottomTabsScreen() {
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
<MaterialBottomTabs.Screen
name="article"
component={Article}
options={{
tabBarLabel: 'Article',
tabBarIcon: 'chrome-reader-mode',
tabBarColor: '#C9E7F8',
}}
/>
>
{props => (
<SimpleStackScreen {...props} options={{ headerMode: 'none' }} />
)}
</MaterialBottomTabs.Screen>
<MaterialBottomTabs.Screen
name="chat"
component={Chat}

View File

@@ -72,17 +72,18 @@ const AlbumsScreen = ({
const SimpleStack = createStackNavigator<SimpleStackParams>();
export default function SimpleStackScreen({
navigation,
}: {
type Props = {
options?: React.ComponentProps<typeof SimpleStack.Navigator>;
navigation: StackNavigationProp<ParamListBase>;
}) {
};
export default function SimpleStackScreen({ navigation, options }: Props) {
navigation.setOptions({
header: null,
});
return (
<SimpleStack.Navigator>
<SimpleStack.Navigator {...options}>
<SimpleStack.Screen
name="article"
component={ArticleScreen}

View File

@@ -1,5 +1,6 @@
import * as React from 'react';
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
const COVERS = [
require('../../assets/album-art-1.jpg'),
@@ -12,19 +13,22 @@ const COVERS = [
require('../../assets/album-art-8.jpg'),
];
export default class Albums extends React.Component {
render() {
return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.content}
>
{COVERS.map((source, i) => (
<Image key={i} source={source} style={styles.cover} />
))}
</ScrollView>
);
}
export default function Albums() {
const ref = React.useRef<ScrollView>(null);
useScrollToTop(ref);
return (
<ScrollView
ref={ref}
style={styles.container}
contentContainerStyle={styles.content}
>
{COVERS.map((source, i) => (
<Image key={i} source={source} style={styles.cover} />
))}
</ScrollView>
);
}
const styles = StyleSheet.create({

View File

@@ -1,63 +1,63 @@
import * as React from 'react';
import { View, Text, Image, ScrollView, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
type Props = {
date: string;
author: {
date?: string;
author?: {
name: string;
};
};
export default class Article extends React.Component<Props> {
static defaultProps = {
date: '1st Jan 2025',
author: {
name: 'Knowledge Bot',
},
};
export default function Article({
date = '1st Jan 2025',
author = {
name: 'Knowledge Bot',
},
}: Props) {
const ref = React.useRef<ScrollView>(null);
render() {
const { date, author } = this.props;
useScrollToTop(ref);
return (
<ScrollView
style={styles.container}
contentContainerStyle={styles.content}
>
<View style={styles.author}>
<Image
style={styles.avatar}
source={require('../../assets/avatar-1.png')}
/>
<View style={styles.meta}>
<Text style={styles.name}>{author.name}</Text>
<Text style={styles.timestamp}>{date}</Text>
</View>
return (
<ScrollView
ref={ref}
style={styles.container}
contentContainerStyle={styles.content}
>
<View style={styles.author}>
<Image
style={styles.avatar}
source={require('../../assets/avatar-1.png')}
/>
<View style={styles.meta}>
<Text style={styles.name}>{author.name}</Text>
<Text style={styles.timestamp}>{date}</Text>
</View>
<Text style={styles.title}>Lorem Ipsum</Text>
<Text style={styles.paragraph}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making
it over 2000 years old.
</Text>
<Image style={styles.image} source={require('../../assets/book.jpg')} />
<Text style={styles.paragraph}>
Richard McClintock, a Latin professor at Hampden-Sydney College in
Virginia, looked up one of the more obscure Latin words, consectetur,
from a Lorem Ipsum passage, and going through the cites of the word in
classical literature, discovered the undoubtable source.
</Text>
<Text style={styles.paragraph}>
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de
Finibus Bonorum et Malorum&quot; (The Extremes of Good and Evil) by
Cicero, written in 45 BC. This book is a treatise on the theory of
ethics, very popular during the Renaissance. The first line of Lorem
Ipsum, &quot;Lorem ipsum dolor sit amet..&quot;, comes from a line in
section 1.10.32.
</Text>
</ScrollView>
);
}
</View>
<Text style={styles.title}>Lorem Ipsum</Text>
<Text style={styles.paragraph}>
Contrary to popular belief, Lorem Ipsum is not simply random text. It
has roots in a piece of classical Latin literature from 45 BC, making it
over 2000 years old.
</Text>
<Image style={styles.image} source={require('../../assets/book.jpg')} />
<Text style={styles.paragraph}>
Richard McClintock, a Latin professor at Hampden-Sydney College in
Virginia, looked up one of the more obscure Latin words, consectetur,
from a Lorem Ipsum passage, and going through the cites of the word in
classical literature, discovered the undoubtable source.
</Text>
<Text style={styles.paragraph}>
Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of &quot;de Finibus
Bonorum et Malorum&quot; (The Extremes of Good and Evil) by Cicero,
written in 45 BC. This book is a treatise on the theory of ethics, very
popular during the Renaissance. The first line of Lorem Ipsum,
&quot;Lorem ipsum dolor sit amet..&quot;, comes from a line in section
1.10.32.
</Text>
</ScrollView>
);
}
const styles = StyleSheet.create({

View File

@@ -7,6 +7,7 @@ import {
ScrollView,
StyleSheet,
} from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
const MESSAGES = [
'okay',
@@ -15,49 +16,51 @@ const MESSAGES = [
'make me a sandwich',
];
export default class Chat extends React.Component {
render() {
return (
<View style={styles.container}>
<ScrollView
style={styles.inverted}
contentContainerStyle={styles.content}
>
{MESSAGES.map((text, i) => {
const odd = i % 2;
export default function Chat() {
const ref = React.useRef<ScrollView>(null);
return (
useScrollToTop(ref);
return (
<View style={styles.container}>
<ScrollView
style={styles.inverted}
contentContainerStyle={styles.content}
>
{MESSAGES.map((text, i) => {
const odd = i % 2;
return (
<View
key={i}
style={[odd ? styles.odd : styles.even, styles.inverted]}
>
<Image
style={styles.avatar}
source={
odd
? require('../../assets/avatar-2.png')
: require('../../assets/avatar-1.png')
}
/>
<View
key={i}
style={[odd ? styles.odd : styles.even, styles.inverted]}
style={[styles.bubble, odd ? styles.received : styles.sent]}
>
<Image
style={styles.avatar}
source={
odd
? require('../../assets/avatar-2.png')
: require('../../assets/avatar-1.png')
}
/>
<View
style={[styles.bubble, odd ? styles.received : styles.sent]}
>
<Text style={odd ? styles.receivedText : styles.sentText}>
{text}
</Text>
</View>
<Text style={odd ? styles.receivedText : styles.sentText}>
{text}
</Text>
</View>
);
})}
</ScrollView>
<TextInput
style={styles.input}
placeholder="Write a message"
underlineColorAndroid="transparent"
/>
</View>
);
}
</View>
);
})}
</ScrollView>
<TextInput
style={styles.input}
placeholder="Write a message"
underlineColorAndroid="transparent"
/>
</View>
);
}
const styles = StyleSheet.create({

View File

@@ -1,6 +1,6 @@
import * as React from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import { View, Text, FlatList, StyleSheet } from 'react-native';
import { useScrollToTop } from '@react-navigation/native';
type Item = { name: string; number: number };
@@ -79,23 +79,24 @@ class ContactItem extends React.PureComponent<{
}
}
export default class Contacts extends React.Component {
private renderItem = ({ item }: { item: Item }) => (
<ContactItem item={item} />
export default function Contacts() {
const ref = React.useRef<FlatList<Item>>(null);
useScrollToTop(ref);
const renderItem = ({ item }: { item: Item }) => <ContactItem item={item} />;
const ItemSeparator = () => <View style={styles.separator} />;
return (
<FlatList
ref={ref}
data={CONTACTS}
keyExtractor={(_, i) => String(i)}
renderItem={renderItem}
ItemSeparatorComponent={ItemSeparator}
/>
);
private ItemSeparator = () => <View style={styles.separator} />;
render() {
return (
<FlatList
data={CONTACTS}
keyExtractor={(_, i) => String(i)}
renderItem={this.renderItem}
ItemSeparatorComponent={this.ItemSeparator}
/>
);
}
}
const styles = StyleSheet.create({

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.5...@react-navigation/material-bottom-tabs@5.0.0-alpha.6) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.4...@react-navigation/material-bottom-tabs@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.3...@react-navigation/material-bottom-tabs@5.0.0-alpha.4) (2019-08-27)

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4"
"@react-navigation/routers": "^5.0.0-alpha.6"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -36,8 +36,8 @@ export type MaterialBottomTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.5...@react-navigation/material-top-tabs@5.0.0-alpha.6) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.4...@react-navigation/material-top-tabs@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.3...@react-navigation/material-top-tabs@5.0.0-alpha.4) (2019-08-27)

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"license": "MIT",
"repository": {
"type": "git",
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4"
"@react-navigation/routers": "^5.0.0-alpha.6"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -50,8 +50,8 @@ export type MaterialTopTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};

View File

@@ -3,6 +3,34 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.4...@react-navigation/native@5.0.0-alpha.5) (2019-08-29)
### Bug Fixes
* handle both null and undefined in useScrollToTop ([c951027](https://github.com/react-navigation/navigation-ex/commit/c951027))
### Features
* handle animated component wrappers in `useScrollToTop` ([#81](https://github.com/react-navigation/navigation-ex/issues/81)) ([cdbf1e9](https://github.com/react-navigation/navigation-ex/commit/cdbf1e9))
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.3...@react-navigation/native@5.0.0-alpha.4) (2019-08-28)
### Bug Fixes
* fix stack nested in tab always getting reset ([dead4e8](https://github.com/react-navigation/navigation-ex/commit/dead4e8))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.2...@react-navigation/native@5.0.0-alpha.3) (2019-08-27)

View File

@@ -7,7 +7,7 @@
"ios",
"android"
],
"version": "5.0.0-alpha.3",
"version": "5.0.0-alpha.5",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -1,11 +1,40 @@
import * as React from 'react';
import { useNavigation, EventArg } from '@react-navigation/core';
type ScrollableView = {
scrollTo(options: { x?: number; y?: number; animated?: boolean }): void;
};
type ScrollOptions = { y?: number; animated?: boolean };
export default function useScrollToTop(ref: React.RefObject<ScrollableView>) {
type ScrollableView =
| { scrollToTop(): void }
| { scrollTo(options: ScrollOptions): void }
| { scrollToOffset(options: ScrollOptions): void }
| { scrollResponderScrollTo(options: ScrollOptions): void };
type ScrollableWrapper =
| { getScrollResponder(): ScrollableView }
| { getNode(): ScrollableView }
| ScrollableView;
function getScrollableNode(ref: React.RefObject<ScrollableWrapper>) {
if (ref.current == null) {
return null;
}
if ('getScrollResponder' in ref.current) {
// If the view is a wrapper like FlatList, SectionList etc.
// We need to use `getScrollResponder` to get access to the scroll responder
return ref.current.getScrollResponder();
} else if ('getNode' in ref.current) {
// When a `ScrollView` is wraped in `Animated.createAnimatedComponent`
// we need to use `getNode` to get the ref to the actual scrollview
return ref.current.getNode();
} else {
return ref.current;
}
}
export default function useScrollToTop(
ref: React.RefObject<ScrollableWrapper>
) {
const navigation = useNavigation();
React.useEffect(
@@ -14,12 +43,24 @@ export default function useScrollToTop(ref: React.RefObject<ScrollableView>) {
// We don't wanna import tab types here to avoid extra deps
// in addition, there are multiple tab implementations
navigation.addListener('tabPress', (e: EventArg<'tabPress'>) => {
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 (navigation.isFocused() && !e.defaultPrevented && ref.current) {
const scrollable = getScrollableNode(ref);
if (isFocused && !e.defaultPrevented && scrollable) {
// When user taps on already focused tab, scroll to top
ref.current.scrollTo({ y: 0 });
if ('scrollToTop' in scrollable) {
scrollable.scrollToTop();
} else if ('scrollTo' in scrollable) {
scrollable.scrollTo({ y: 0, animated: true });
} else if ('scrollToOffset' in scrollable) {
scrollable.scrollToOffset({ y: 0, animated: true });
} else if ('scrollResponderScrollTo' in scrollable) {
scrollable.scrollResponderScrollTo({ y: 0, animated: true });
}
}
});
}),

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.5...@react-navigation/routers@5.0.0-alpha.6) (2019-08-29)
### Features
* handle navigating with both with both key and name ([#83](https://github.com/react-navigation/navigation-ex/issues/83)) ([6b75cba](https://github.com/react-navigation/navigation-ex/commit/6b75cba))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.4...@react-navigation/routers@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/routers
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.3...@react-navigation/routers@5.0.0-alpha.4) (2019-08-27)
**Note:** Version bump only for package @react-navigation/routers

View File

@@ -1,8 +1,190 @@
import { CommonActions } from '@react-navigation/core';
import { DrawerRouter } from '../src';
import { DrawerRouter, DrawerActions } from '../src';
jest.mock('shortid', () => () => 'test');
it('gets initial state from route names and params with initialRouteName', () => {
const router = DrawerRouter({ initialRouteName: 'baz' });
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 1,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it('gets initial state from route names and params without initialRouteName', () => {
const router = DrawerRouter({});
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it('gets rehydrated state from partial state', () => {
const router = DrawerRouter({});
const options = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
};
expect(
router.getRehydratedState(
{
routes: [{ key: 'bar-0', name: 'bar' }, { key: 'qux-1', name: 'qux' }],
},
options
)
).toEqual({
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-1', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz' },
{ key: 'qux-2', name: 'qux' },
],
},
options
)
).toEqual({
index: 2,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz', params: { answer: 42 } },
{ key: 'qux-2', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 4,
routes: [],
},
options
)
).toEqual({
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 1,
isDrawerOpen: true,
routeKeyHistory: ['bar-test', 'qux-test', 'foo-test'],
routes: [],
},
options
)
).toEqual({
index: 1,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: ['bar-test', 'qux-test'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it("doesn't rehydrate state if it's not stale", () => {
const router = DrawerRouter({});
const state = {
index: 0,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
};
expect(
router.getRehydratedState(state, {
routeNames: [],
routeParamList: {},
})
).toBe(state);
});
it('handles navigate action', () => {
const router = DrawerRouter({});
@@ -47,7 +229,7 @@ it('handles open drawer action', () => {
isDrawerOpen: false,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'OPEN_DRAWER' }
DrawerActions.openDrawer()
)
).toEqual({
stale: false,
@@ -58,6 +240,20 @@ it('handles open drawer action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
const state = {
stale: false as const,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
};
expect(router.getStateForAction(state, DrawerActions.openDrawer())).toBe(
state
);
});
it('handles close drawer action', () => {
@@ -74,7 +270,7 @@ it('handles close drawer action', () => {
isDrawerOpen: true,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'CLOSE_DRAWER' }
DrawerActions.closeDrawer()
)
).toEqual({
stale: false,
@@ -85,6 +281,20 @@ it('handles close drawer action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
const state = {
stale: false as const,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
};
expect(router.getStateForAction(state, DrawerActions.closeDrawer())).toBe(
state
);
});
it('handles toggle drawer action', () => {
@@ -101,7 +311,7 @@ it('handles toggle drawer action', () => {
isDrawerOpen: true,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'TOGGLE_DRAWER' }
DrawerActions.toggleDrawer()
)
).toEqual({
stale: false,
@@ -124,7 +334,7 @@ it('handles toggle drawer action', () => {
isDrawerOpen: false,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'TOGGLE_DRAWER' }
DrawerActions.toggleDrawer()
)
).toEqual({
stale: false,
@@ -136,3 +346,95 @@ it('handles toggle drawer action', () => {
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
});
it('updates route key history on focus change', () => {
const router = DrawerRouter({ backBehavior: 'history' });
const state = {
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-0', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
};
expect(router.getStateForRouteFocus(state, 'bar-0').routeKeyHistory).toEqual(
[]
);
expect(router.getStateForRouteFocus(state, 'baz-0').routeKeyHistory).toEqual([
'bar-0',
]);
});
it('closes drawer on focus change', () => {
const router = DrawerRouter({ backBehavior: 'history' });
expect(
router.getStateForRouteFocus(
{
index: 0,
key: 'drawer-test',
isDrawerOpen: false,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
stale: false,
},
'baz-0'
)
).toEqual({
index: 1,
isDrawerOpen: false,
key: 'drawer-test',
routeKeyHistory: ['bar-0'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
stale: false,
});
expect(
router.getStateForRouteFocus(
{
index: 0,
key: 'drawer-test',
isDrawerOpen: true,
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
stale: false,
},
'baz-0'
)
).toEqual({
index: 1,
isDrawerOpen: false,
key: 'drawer-test',
routeKeyHistory: ['bar-0'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
stale: false,
});
});

View File

@@ -1,8 +1,173 @@
import { CommonActions } from '@react-navigation/core';
import { StackRouter } from '../src';
import { StackRouter, StackActions } from '../src';
jest.mock('shortid', () => () => 'test');
it('gets initial state from route names and params with initialRouteName', () => {
const router = StackRouter({ initialRouteName: 'baz' });
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'baz-test', name: 'baz', params: { answer: 42 } }],
stale: false,
});
});
it('gets initial state from route names and params without initialRouteName', () => {
const router = StackRouter({});
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'bar-test', name: 'bar' }],
stale: false,
});
});
it('gets rehydrated state from partial state', () => {
const router = StackRouter({});
const options = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
};
expect(
router.getRehydratedState(
{
routes: [{ key: 'bar-0', name: 'bar' }, { key: 'qux-1', name: 'qux' }],
},
options
)
).toEqual({
index: 1,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'qux-1', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz' },
{ key: 'qux-2', name: 'qux' },
],
},
options
)
).toEqual({
index: 2,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz', params: { answer: 42 } },
{ key: 'qux-2', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 4,
routes: [],
},
options
)
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'bar-test', name: 'bar' }],
stale: false,
});
});
it("doesn't rehydrate state if it's not stale", () => {
const router = StackRouter({});
const state = {
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'bar-test', name: 'bar' }],
stale: false as const,
};
expect(
router.getRehydratedState(state, {
routeNames: [],
routeParamList: {},
})
).toBe(state);
});
it('gets state on route names change', () => {
const router = StackRouter({});
expect(
router.getStateForRouteNamesChange(
{
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
},
{
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routeParamList: {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
}
)
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routes: [
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it('handles navigate action', () => {
const router = StackRouter({});
@@ -28,12 +193,125 @@ it('handles navigate action', () => {
{
key: 'qux-test',
name: 'qux',
params: {
answer: 42,
},
params: { answer: 42 },
},
],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('baz', { answer: 42 })
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz', params: { answer: 42 } }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar', params: { answer: 42 } },
],
},
CommonActions.navigate('bar', { answer: 96 })
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar', params: { answer: 96 } },
],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('unknown')
)
).toBe(null);
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }, { key: 'bar-0', name: 'bar' }],
},
CommonActions.navigate({ key: 'unknown' })
)
).toBe(null);
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{
type: 'NAVIGATE',
payload: { key: 'baz-0', name: 'baz' },
}
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate({ key: 'baz-1', name: 'baz' })
)
).toEqual({
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz-0', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'baz-1', name: 'baz' },
],
});
});
it('handles go back action', () => {
@@ -48,7 +326,7 @@ it('handles go back action', () => {
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'GO_BACK' }
CommonActions.goBack()
)
).toEqual({
stale: false,
@@ -57,6 +335,19 @@ it('handles go back action', () => {
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }],
},
CommonActions.goBack()
)
).toBe(null);
});
it('handles pop action', () => {
@@ -75,7 +366,30 @@ it('handles pop action', () => {
{ key: 'qux', name: 'qux' },
],
},
{ type: 'POP', payload: { count: 2 } }
StackActions.pop()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'qux', name: 'qux' },
],
},
StackActions.pop(2)
)
).toEqual({
stale: false,
@@ -84,6 +398,46 @@ it('handles pop action', () => {
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz-0', name: 'baz' },
{ key: 'bar-0', name: 'bar' },
{ key: 'qux-0', name: 'qux' },
],
},
{
...StackActions.pop(),
target: 'root',
source: 'bar-0',
}
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }],
},
StackActions.pop()
)
).toBe(null);
});
it('handles pop to top action', () => {
@@ -102,7 +456,7 @@ it('handles pop to top action', () => {
{ key: 'qux', name: 'qux' },
],
},
{ type: 'POP_TO_TOP' }
StackActions.popToTop()
)
).toEqual({
stale: false,
@@ -125,7 +479,7 @@ it('handles push action', () => {
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'bar', name: 'bar' }],
},
{ type: 'PUSH', payload: { name: 'baz' } }
StackActions.push('baz')
)
).toEqual({
stale: false,
@@ -134,4 +488,54 @@ it('handles push action', () => {
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'bar', name: 'bar' }, { key: 'baz-test', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'bar', name: 'bar' }],
},
StackActions.push('unknown')
)
).toBe(null);
});
it('changes index on focus change', () => {
const router = StackRouter({});
expect(
router.getStateForRouteFocus(
{
index: 2,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz' },
{ key: 'qux-0', name: 'qux' },
],
stale: false,
},
'baz-0'
)
).toEqual({
index: 1,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'bar-0', name: 'bar' }, { key: 'baz-0', name: 'baz' }],
stale: false,
});
const state = {
index: 0,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [{ key: 'bar-0', name: 'bar' }, { key: 'baz-0', name: 'baz' }],
stale: false as const,
};
expect(router.getStateForRouteFocus(state, 'qux-0')).toEqual(state);
});

View File

@@ -1,11 +1,249 @@
import { CommonActions } from '@react-navigation/core';
import { TabRouter } from '../src';
import { TabRouter, TabActions, TabNavigationState } from '../src';
jest.mock('shortid', () => () => 'test');
it('gets initial state from route names and params with initialRouteName', () => {
const router = TabRouter({ initialRouteName: 'baz' });
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it('gets initial state from route names and params without initialRouteName', () => {
const router = TabRouter({});
expect(
router.getInitialState({
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
})
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it('gets rehydrated state from partial state', () => {
const router = TabRouter({});
const options = {
routeNames: ['bar', 'baz', 'qux'],
routeParamList: {
baz: { answer: 42 },
qux: { name: 'Jane' },
},
};
expect(
router.getRehydratedState(
{
routes: [{ key: 'bar-0', name: 'bar' }, { key: 'qux-1', name: 'qux' }],
},
options
)
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-1', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 2,
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz' },
{ key: 'qux-2', name: 'qux' },
],
},
options
)
).toEqual({
index: 2,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-1', name: 'baz', params: { answer: 42 } },
{ key: 'qux-2', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 4,
routes: [],
},
options
)
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
expect(
router.getRehydratedState(
{
index: 1,
routeKeyHistory: ['bar-test', 'qux-test', 'foo-test'],
routes: [],
},
options
)
).toEqual({
index: 1,
key: 'tab-test',
routeKeyHistory: ['bar-test', 'qux-test'],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
});
});
it("doesn't rehydrate state if it's not stale", () => {
const router = TabRouter({});
const state = {
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
};
expect(
router.getRehydratedState(state, {
routeNames: [],
routeParamList: {},
})
).toBe(state);
});
it('gets state on route names change', () => {
const router = TabRouter({});
expect(
router.getStateForRouteNamesChange(
{
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-test', name: 'bar' },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
],
stale: false,
},
{
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routeParamList: {
qux: { name: 'John' },
fiz: { fruit: 'apple' },
},
}
)
).toEqual({
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routes: [
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
{ key: 'foo-test', name: 'foo' },
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
],
stale: false,
});
});
it('handles navigate action', () => {
const router = TabRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz-1', name: 'baz' }, { key: 'bar-1', name: 'bar' }],
},
CommonActions.navigate({ key: 'bar-1', params: { answer: 42 } })
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar-1'],
routes: [
{ key: 'baz-1', name: 'baz' },
{ key: 'bar-1', name: 'bar', params: { answer: 42 } },
],
});
expect(
router.getStateForAction(
{
@@ -29,6 +267,20 @@ it('handles navigate action', () => {
{ key: 'bar', name: 'bar' },
],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('non-existent')
)
).toBe(null);
});
it('handles jump to action', () => {
@@ -44,7 +296,7 @@ it('handles jump to action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'JUMP_TO', payload: { name: 'bar' } }
TabActions.jumpTo('bar')
)
).toEqual({
stale: false,
@@ -56,8 +308,8 @@ it('handles jump to action', () => {
});
});
it('handles history back action', () => {
const router = TabRouter({});
it('handles back action with backBehavior: history', () => {
const router = TabRouter({ backBehavior: 'history' });
expect(
router.getStateForAction(
@@ -69,7 +321,7 @@ it('handles history back action', () => {
routeKeyHistory: ['bar'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'GO_BACK' }
CommonActions.goBack()
)
).toEqual({
stale: false,
@@ -79,9 +331,23 @@ it('handles history back action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toBe(null);
});
it('handles order back action', () => {
it('handles back action with backBehavior: order', () => {
const router = TabRouter({ backBehavior: 'order' });
expect(
@@ -94,7 +360,7 @@ it('handles order back action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'GO_BACK' }
CommonActions.goBack()
)
).toEqual({
stale: false,
@@ -104,10 +370,48 @@ it('handles order back action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toBe(null);
});
it('handles initialRoute back action', () => {
const router = TabRouter({ backBehavior: 'initialRoute' });
it('handles back action with backBehavior: initialRoute', () => {
const router = TabRouter({
backBehavior: 'initialRoute',
initialRouteName: 'bar',
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
expect(
router.getStateForAction(
@@ -119,19 +423,12 @@ it('handles initialRoute back action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'GO_BACK' }
CommonActions.goBack()
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
).toBe(null);
});
it('handles none back action', () => {
it('handles back action with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
expect(
@@ -144,7 +441,86 @@ it('handles none back action', () => {
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{ type: 'GO_BACK' }
CommonActions.goBack()
)
).toEqual(null);
});
it('updates route key history on navigate and jump to', () => {
const router = TabRouter({ backBehavior: 'history' });
let state: TabNavigationState = {
index: 1,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-0', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
};
expect(state.routeKeyHistory).toEqual([]);
state = router.getStateForAction(
state,
TabActions.jumpTo('qux')
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['baz-0']);
state = router.getStateForAction(
state,
CommonActions.navigate('bar')
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['baz-0', 'qux-0']);
state = router.getStateForAction(
state,
TabActions.jumpTo('baz')
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['qux-0', 'bar-0']);
state = router.getStateForAction(
state,
CommonActions.goBack()
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual(['qux-0']);
state = router.getStateForAction(
state,
CommonActions.goBack()
) as TabNavigationState;
expect(state.routeKeyHistory).toEqual([]);
});
it('updates route key history on focus change', () => {
const router = TabRouter({ backBehavior: 'history' });
const state = {
index: 0,
key: 'tab-test',
routeKeyHistory: [],
routeNames: ['bar', 'baz', 'qux'],
routes: [
{ key: 'bar-0', name: 'bar' },
{ key: 'baz-0', name: 'baz', params: { answer: 42 } },
{ key: 'qux-0', name: 'qux', params: { name: 'Jane' } },
],
stale: false as const,
};
expect(router.getStateForRouteFocus(state, 'bar-0').routeKeyHistory).toEqual(
[]
);
expect(router.getStateForRouteFocus(state, 'baz-0').routeKeyHistory).toEqual([
'bar-0',
]);
});

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.4",
"version": "5.0.0-alpha.6",
"license": "MIT",
"repository": {
"type": "git",

View File

@@ -11,7 +11,7 @@ import {
export type StackActionType =
| {
type: 'PUSH';
payload: { name: string; params?: object };
payload: { name: string; key?: string | undefined; params?: object };
source?: string;
target?: string;
}
@@ -151,7 +151,10 @@ export default function StackRouter(options: StackRouterOptions) {
routes: [
...state.routes,
{
key: `${action.payload.name}-${shortid()}`,
key:
action.payload.key === undefined
? `${action.payload.name}-${shortid()}`
: action.payload.key,
name: action.payload.name,
params: action.payload.params,
},
@@ -199,14 +202,16 @@ export default function StackRouter(options: StackRouterOptions) {
let index = -1;
if (
state.routes[state.index].name === action.payload.name ||
(state.routes[state.index].name === action.payload.name &&
action.payload.key === undefined) ||
state.routes[state.index].key === action.payload.key
) {
index = state.index;
} else {
for (let i = state.routes.length - 1; i >= 0; i--) {
if (
state.routes[i].name === action.payload.name ||
(state.routes[i].name === action.payload.name &&
action.payload.key === undefined) ||
state.routes[i].key === action.payload.key
) {
index = i;
@@ -215,7 +220,11 @@ export default function StackRouter(options: StackRouterOptions) {
}
}
if (index === -1 && action.payload.key) {
if (
index === -1 &&
action.payload.key &&
action.payload.name === undefined
) {
return null;
}
@@ -223,6 +232,7 @@ export default function StackRouter(options: StackRouterOptions) {
return router.getStateForAction(state, {
type: 'PUSH',
payload: {
key: action.payload.key,
name: action.payload.name,
params: action.payload.params,
},

View File

@@ -3,6 +3,40 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.7...@react-navigation/stack@5.0.0-alpha.8) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
* fix gestures not working in stack ([8c1acc3](https://github.com/react-navigation/navigation-ex/commit/8c1acc3))
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.6...@react-navigation/stack@5.0.0-alpha.7) (2019-08-28)
### Bug Fixes
* fix stack nested in tab always getting reset ([dead4e8](https://github.com/react-navigation/navigation-ex/commit/dead4e8))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.5...@react-navigation/stack@5.0.0-alpha.6) (2019-08-28)
### Features
* disable gesture logic when no gesture stack ([38336b0](https://github.com/react-navigation/navigation-ex/commit/38336b0))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.4...@react-navigation/stack@5.0.0-alpha.5) (2019-08-27)

View File

@@ -10,7 +10,7 @@
"android",
"stack"
],
"version": "5.0.0-alpha.5",
"version": "5.0.0-alpha.8",
"license": "MIT",
"repository": {
"type": "git",
@@ -33,7 +33,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.4",
"@react-navigation/routers": "^5.0.0-alpha.6",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -156,7 +156,7 @@ export function forFadeFromBottomAndroid({
}
/**
* Standard Android-style wipe from the bottom for Android Pie.
* Standard Android-style reveal from the bottom for Android Pie.
*/
export function forRevealFromBottomAndroid({
progress: { current, next },

View File

@@ -4,6 +4,9 @@ import { HeaderInterpolationProps, HeaderInterpolatedStyle } from '../types';
const { interpolate, add } = Animated;
/**
* Standard UIKit style animation for the header where the title fades into the back button label.
*/
export function forUIKit({
progress: { current, next },
layouts,
@@ -85,6 +88,9 @@ export function forUIKit({
};
}
/**
* Simple fade animation for the header elements.
*/
export function forFade({
progress: { current, next },
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
@@ -102,6 +108,9 @@ export function forFade({
};
}
/**
* Simple translate animation to translate the header along with the sliding screen.
*/
export function forStatic({
progress: { current, next },
layouts: { screen },

View File

@@ -17,7 +17,9 @@ import { Platform } from 'react-native';
const ANDROID_VERSION_PIE = 28;
// Standard iOS navigation transition
/**
* Standard iOS navigation transition.
*/
export const SlideFromRightIOS: TransitionPreset = {
gestureDirection: 'horizontal',
transitionSpec: {
@@ -28,7 +30,9 @@ export const SlideFromRightIOS: TransitionPreset = {
headerStyleInterpolator: forFade,
};
// Standard iOS navigation transition for modals
/**
* Standard iOS navigation transition for modals.
*/
export const ModalSlideFromBottomIOS: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -39,7 +43,9 @@ export const ModalSlideFromBottomIOS: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard iOS modal presentation style (introduced in iOS 13)
/**
* Standard iOS modal presentation style (introduced in iOS 13).
*/
export const ModalPresentationIOS: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -50,7 +56,9 @@ export const ModalPresentationIOS: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard Android navigation transition when opening or closing an Activity on Android < 9
/**
* Standard Android navigation transition when opening or closing an Activity on Android < 9 (Oreo).
*/
export const FadeFromBottomAndroid: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -61,7 +69,9 @@ export const FadeFromBottomAndroid: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard Android navigation transition when opening or closing an Activity on Android >= 9
/**
* Standard Android navigation transition when opening or closing an Activity on Android >= 9 (Pie).
*/
export const RevealFromBottomAndroid: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -72,6 +82,9 @@ export const RevealFromBottomAndroid: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
/**
* Default navigation transition for the current platform.
*/
export const DefaultTransition = Platform.select({
ios: SlideFromRightIOS,
default:
@@ -80,6 +93,9 @@ export const DefaultTransition = Platform.select({
: RevealFromBottomAndroid,
});
/**
* Default modal transition for the current platform.
*/
export const ModalTransition = Platform.select({
ios: ModalSlideFromBottomIOS,
default: DefaultTransition,

View File

@@ -1,9 +1,11 @@
import { Easing } from 'react-native-reanimated';
import { TransitionSpec } from '../types';
// These are the exact values from UINavigationController's animation configuration
/**
* Exact values from UINavigationController's animation configuration.
*/
export const TransitionIOSSpec: TransitionSpec = {
timing: 'spring',
animation: 'spring',
config: {
stiffness: 1000,
damping: 500,
@@ -14,27 +16,36 @@ export const TransitionIOSSpec: TransitionSpec = {
},
};
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
/**
* Configuration for activity open animation from Android Nougat.
* See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
*/
export const FadeInFromBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
animation: 'timing',
config: {
duration: 350,
easing: Easing.out(Easing.poly(5)),
},
};
// See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
/**
* Configuration for activity close animation from Android Nougat.
* See http://androidxref.com/7.1.1_r6/xref/frameworks/base/core/res/res/anim/activity_close_exit.xml
*/
export const FadeOutToBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
animation: 'timing',
config: {
duration: 150,
easing: Easing.in(Easing.linear),
},
};
// See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
/**
* Approximate configuration for activity open animation from Android Pie.
* See http://androidxref.com/9.0.0_r3/xref/frameworks/base/core/res/res/anim/activity_open_enter.xml
*/
export const RevealFromBottomAndroidSpec: TransitionSpec = {
timing: 'timing',
animation: 'timing',
config: {
duration: 425,
// This is super rough approximation of the path used for the curve by android

View File

@@ -45,14 +45,12 @@ function StackNavigator({
() =>
navigation.addListener &&
navigation.addListener('tabPress', (e: EventArg<'tabPress'>) => {
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 &&
navigation.isFocused() &&
!e.defaultPrevented
) {
if (state.index > 0 && isFocused && !e.defaultPrevented) {
// When user taps on already focused tab and we're inside the tab,
// reset the stack to replicate native behaviour
navigation.dispatch({

View File

@@ -47,8 +47,8 @@ export type StackNavigationProp<
* @param [params] Params object for the route.
*/
push<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -405,8 +405,8 @@ export type TimingConfig = {
};
export type TransitionSpec =
| { timing: 'spring'; config: SpringConfig }
| { timing: 'timing'; config: TimingConfig };
| { animation: 'spring'; config: SpringConfig }
| { animation: 'timing'; config: TimingConfig };
export type CardInterpolationProps = {
/**

View File

@@ -298,7 +298,7 @@ export default class Card extends React.Component<Props> {
]),
cond(
eq(isVisible, 1),
openingSpec.timing === 'spring'
openingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
@@ -309,7 +309,7 @@ export default class Card extends React.Component<Props> {
{ ...this.transitionState, frameTime: this.frameTime },
{ ...openingSpec.config, toValue: this.toValue }
),
closingSpec.timing === 'spring'
closingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
@@ -366,6 +366,15 @@ export default class Card extends React.Component<Props> {
set(this.nextIsVisible, UNSET),
])
),
onChange(
this.isVisible,
call([this.isVisible], ([isVisible]) => (this.isVisibleValue = isVisible))
),
]);
private execNoGesture = this.runTransition(this.isVisible);
private execWithGesture = block([
onChange(
this.isSwiping,
call(
@@ -454,10 +463,6 @@ export default class Card extends React.Component<Props> {
),
]
),
onChange(
this.isVisible,
call([this.isVisible], ([isVisible]) => (this.isVisibleValue = isVisible))
),
]);
private handleGestureEventHorizontal = Animated.event([
@@ -576,15 +581,19 @@ export default class Card extends React.Component<Props> {
layout
);
const handleGestureEvent =
gestureDirection === 'vertical'
const handleGestureEvent = gestureEnabled
? gestureDirection === 'vertical'
? this.handleGestureEventVertical
: this.handleGestureEventHorizontal;
: this.handleGestureEventHorizontal
: undefined;
return (
<StackGestureContext.Provider value={this.gestureRef}>
<View pointerEvents="box-none" {...rest}>
<Animated.Code exec={this.exec} />
<Animated.Code
exec={gestureEnabled ? this.execWithGesture : this.execNoGesture}
/>
{overlayEnabled && overlayStyle ? (
<Animated.View
pointerEvents="none"