Compare commits

..

25 Commits

Author SHA1 Message Date
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
Michal Osadnik
3703ab6353 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.4
 - @react-navigation/core@5.0.0-alpha.3
 - @react-navigation/drawer@5.0.0-alpha.4
 - @react-navigation/example@5.0.0-alpha.2
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.4
 - @react-navigation/material-top-tabs@5.0.0-alpha.4
 - @react-navigation/native@5.0.0-alpha.3
 - @react-navigation/routers@5.0.0-alpha.4
 - @react-navigation/stack@5.0.0-alpha.5
2019-08-27 11:06:59 +01:00
Michal Osadnik
7990cf2575 feat: add memoization of spring for stack 2019-08-27 10:51:26 +01:00
satyajit.happy
fb9d1837a1 chore: configure jest to resolve source files for packages 2019-08-27 08:58:29 +02:00
satyajit.happy
935c588000 refactor: rename BaseActions tCommonActions 2019-08-27 08:45:37 +02:00
Michal Osadnik
7d526e5881 chore: add tests for drawer 2019-08-26 13:14:03 +01:00
Michal Osadnik
2adccdef1d chore: add tests for tabs 2019-08-26 13:07:12 +01:00
Michal Osadnik
dee25057e8 chore: add tests for stack 2019-08-26 12:52:00 +01:00
satyajit.happy
9e1104c31f feat: add hook to scroll to top on tab press 2019-08-24 12:14:49 +05:30
Michal Osadnik
469ec31cc5 fix: link proper descriptor for StackView 2019-08-24 02:12:48 +01:00
Michal Osadnik
d26b77f9c9 feat: add native container 2019-08-24 06:06:05 +05:30
Michal Osadnik
1bbd6ac422 fix: set correct pointer events when active prop changes 2019-08-22 16:08:51 +05:30
satyajit.happy
1a8281d37d refactor: change order of type parameters 2019-08-22 11:37:17 +05:30
satyajit.happy
b0a0857b0a refactor: typecheck events in navigation helpers 2019-08-22 11:33:15 +05:30
satyajit.happy
4e07461526 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.3
 - @react-navigation/core@5.0.0-alpha.2
 - @react-navigation/drawer@5.0.0-alpha.3
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.3
 - @react-navigation/material-top-tabs@5.0.0-alpha.3
 - @react-navigation/native@5.0.0-alpha.2
 - @react-navigation/routers@5.0.0-alpha.3
 - @react-navigation/stack@5.0.0-alpha.4
2019-08-22 10:10:06 +05:30
satyajit.happy
f18231541b fix: fix path to typescript definitions 2019-08-22 10:09:16 +05:30
satyajit.happy
e70f5273ac chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.2
 - @react-navigation/drawer@5.0.0-alpha.2
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.2
 - @react-navigation/material-top-tabs@5.0.0-alpha.2
 - @react-navigation/routers@5.0.0-alpha.2
 - @react-navigation/stack@5.0.0-alpha.3
2019-08-22 07:40:13 +05:30
satyajit.happy
61dce7ae1b fix: properly handle pop action from stack 2019-08-22 07:39:06 +05:30
satyajit.happy
90f014b78a chore: publish
- @react-navigation/stack@5.0.0-alpha.2
2019-08-22 05:08:18 +05:30
satyajit.happy
8645e361f1 fix: check if left button is truthy to add a left offset 2019-08-22 05:07:48 +05:30
62 changed files with 2293 additions and 220 deletions

View File

@@ -42,6 +42,13 @@ jobs:
- store_artifacts:
path: coverage
destination: coverage
build-packages:
<<: *defaults
steps:
- attach_workspace:
at: ~/project
- run: |
yarn lerna run prepare
workflows:
version: 2
@@ -54,3 +61,6 @@ workflows:
- unit-test:
requires:
- install-dependencies
- build-packages:
requires:
- install-dependencies

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
@@ -31,6 +35,8 @@ Navigators bundle a router and a view which takes the navigation state and decid
A simple navigator could look like this:
```js
import { createNavigator } from '@react-navigation/core';
function StackNavigator({ initialRouteName, children, ...rest }) {
// The `navigation` object contains the navigation state and some helpers (e.g. push, pop)
// The `descriptors` object contains the screen options and a helper for rendering a screen
@@ -127,8 +133,11 @@ It's also possible to disable bubbling of actions when dispatching them by addin
## Basic usage
```js
import { createStackNavigator } from '@react-navigation/stack';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Stack = createStackNavigator();
const Tab = createTabNavigator();
const Tab = createBottomTabNavigator();
function App() {
return (
@@ -210,6 +219,8 @@ function Profile({ navigation }) {
}
```
The `navigation.addListener` method returns a function to remove the listener which can be returned as the cleanup function in an effect.
Navigators can also emit custom events using the `emit` method in the `navigation` object passed:
```js
@@ -245,6 +256,8 @@ Sometimes we want to run side-effects when a screen is focused. A side effect ma
To make this easier, the library exports a `useFocusEffect` hook:
```js
import { useFocusEffect } from '@react-navigation/core';
function Profile({ userId }) {
const [user, setUser] = React.useState(null);
@@ -272,6 +285,10 @@ The `useFocusEffect` is analogous to React's `useEffect` hook. The only differen
We might want to render different content based on the current focus state of the screen. The library exports a `useIsFocused` hook to make this easier:
```js
import { useIsFocused } from '@react-navigation/core';
// ...
const isFocused = useIsFocused();
```
@@ -284,6 +301,10 @@ For proper UX in React Native, we need to respect platform behavior such as the
When the back button on the device is pressed, we also want to navigate back in the focused navigator. The library exports a `useBackButton` hook to handle this:
```js
import { useBackButton } from '@react-navigation/native';
// ...
const ref = React.useRef();
useBackButton(ref);
@@ -291,6 +312,24 @@ useBackButton(ref);
return <NavigationContainer ref={ref}>{/* content */}</NavigationContainer>;
```
### Scroll to top on tab button press
When there's a scroll view in a tab and the user taps on the already focused tab bar again, we might want to scroll to top in our scroll view. The library exports a `useScrollToTop` hook to handle this:
```js
import { useScrollToTop } from '@react-navigation/native';
// ...
const ref = React.useRef();
useScrollToTop(ref);
return <ScrollView ref={ref}>{/* content */}</ScrollView>;
```
The hook can accept a ref object to any view that has a `scrollTo` method.
### Deep-link integration
To handle incoming links, we need to handle 2 scenarios:
@@ -325,6 +364,10 @@ For example, the path `/rooms/chat?user=jane` will be translated to a state obje
The `useLinking` hooks makes it easier to handle incoming links:
```js
import { useLinking } from '@react-navigation/native';
// ...
const ref = React.useRef();
const { getInitialState } = useLinking(ref, {
@@ -451,10 +494,10 @@ Unfortunately it's not possible to verify that the type of children elements are
## Contributing
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `lerna bootstrap` to install the required dependencies for each package:
The project uses a monorepo structure for the packages managed by [yarn workspaces](https://yarnpkg.com/lang/en/docs/workspaces/) and [lerna](https://lerna.js.org). To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
```sh
lerna bootstrap
yarn
```
While developing, you can run the [example app](/example/) with [Expo](https://expo.io/) to test your changes:
@@ -476,13 +519,7 @@ To fix formatting errors, run the following:
yarn lint --fix
```
Remember to add tests for your change if possible. To run tests, first build all the files:
```sh
lerna run prepare
```
Then run the tests:
Remember to add tests for your change if possible. Run the tests by:
```sh
yarn test
@@ -493,7 +530,16 @@ yarn test
To publish a new version, first we need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
```sh
lerna publish
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

@@ -14,7 +14,7 @@
"type": "git",
"url": "git+https://github.com/satya164/react-navigation.git"
},
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/)",
"author": "Satyajit Sahoo <satyajit.happy@gmail.com> (https://github.com/satya164/), Michał Osadnik <micosa97@gmail.com> (https://github.com/osdnk/)",
"scripts": {
"lint": "eslint --ext '.js,.ts,.tsx' .",
"typescript": "tsc --noEmit",
@@ -59,7 +59,10 @@
},
"setupFiles": [
"<rootDir>/jest/setup.js"
]
],
"moduleNameMapper": {
"@react-navigation/([^/]+)": "<rootDir>/packages/$1/src"
}
},
"name": "react-navigation"
}

View File

@@ -3,6 +3,44 @@
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/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)
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.2...@react-navigation/bottom-tabs@5.0.0-alpha.3) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.1...@react-navigation/bottom-tabs@5.0.0-alpha.2) (2019-08-22)
**Note:** Version bump only for package @react-navigation/bottom-tabs
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -10,7 +10,7 @@
"android",
"tab"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.5",
"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/src/index.d.ts",
"types": "lib/typescript/bottom-tabssrc/index.d.ts",
"files": [
"src",
"lib"
@@ -33,7 +33,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.1",
"@react-navigation/routers": "^5.0.0-alpha.5",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -13,6 +13,7 @@ import BottomTabView from '../views/BottomTabView';
import {
BottomTabNavigationConfig,
BottomTabNavigationOptions,
BottomTabNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<BottomTabNavigationOptions> &
@@ -28,8 +29,9 @@ function BottomTabNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
TabNavigationState,
TabRouterOptions,
BottomTabNavigationOptions,
TabRouterOptions
BottomTabNavigationEventMap
>(TabRouter, {
initialRouteName,
backBehavior,

View File

@@ -17,10 +17,6 @@ import {
import { TabNavigationState } from '@react-navigation/routers';
export type BottomTabNavigationEventMap = {
/**
* Event which fires on tapping on the tab for an already focused screen.
*/
refocus: undefined;
/**
* Event which fires on tapping on the tab in the tab bar.
*/
@@ -35,6 +31,11 @@ export type Orientation = 'horizontal' | 'vertical';
export type LabelPosition = 'beside-icon' | 'below-icon';
export type BottomTabNavigationHelpers = NavigationHelpers<
ParamListBase,
BottomTabNavigationEventMap
>;
export type BottomTabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string

View File

@@ -5,23 +5,22 @@ import {
AccessibilityRole,
AccessibilityStates,
} from 'react-native';
import {
NavigationHelpers,
ParamListBase,
Route,
BaseActions,
} from '@react-navigation/core';
import { Route, CommonActions } from '@react-navigation/core';
import { TabNavigationState } from '@react-navigation/routers';
// eslint-disable-next-line import/no-unresolved
import { ScreenContainer } from 'react-native-screens';
import BottomTabBar from './BottomTabBar';
import { BottomTabNavigationConfig, BottomTabDescriptorMap } from '../types';
import {
BottomTabNavigationConfig,
BottomTabDescriptorMap,
BottomTabNavigationHelpers,
} from '../types';
import ResourceSavingScene from './ResourceSavingScene';
type Props = BottomTabNavigationConfig & {
state: TabNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: BottomTabNavigationHelpers;
descriptors: BottomTabDescriptorMap;
};
@@ -139,14 +138,12 @@ export default class BottomTabView extends React.Component<Props, State> {
target: route.key,
});
if (state.routes[state.index].key === route.key) {
navigation.emit({
type: 'refocus',
target: route.key,
});
} else if (!event.defaultPrevented) {
if (
state.routes[state.index].key !== route.key &&
!event.defaultPrevented
) {
navigation.dispatch({
...BaseActions.navigate(route.name),
...CommonActions.navigate(route.name),
target: state.key,
});
}

View File

@@ -3,6 +3,29 @@
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/core@5.0.0-alpha.2...@react-navigation/core@5.0.0-alpha.3) (2019-08-27)
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
* add native container ([d26b77f](https://github.com/react-navigation/navigation-ex/commit/d26b77f))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.1...@react-navigation/core@5.0.0-alpha.2) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -6,7 +6,7 @@
"react-native",
"react-navigation"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.3",
"license": "MIT",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/index.d.ts",
"files": [
"src",
"lib"

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import * as BaseActions from './BaseActions';
import * as CommonActions from './CommonActions';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import NavigationBuilderContext from './NavigationBuilderContext';
import useFocusedListeners from './useFocusedListeners';
@@ -12,16 +12,11 @@ import {
PartialState,
NavigationAction,
NavigationContainerRef,
NavigationContainerProps,
} from './types';
type State = NavigationState | PartialState<NavigationState> | undefined;
type Props = {
initialState?: InitialState;
onStateChange?: (state: State) => void;
children: React.ReactNode;
};
const MISSING_CONTEXT_ERROR =
"We couldn't find a navigation context. Have you wrapped your app with 'NavigationContainer'?";
@@ -84,7 +79,7 @@ const getPartialState = (
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const Container = React.forwardRef(function NavigationContainer(
{ initialState, onStateChange, children }: Props,
{ initialState, onStateChange, children }: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const [state, setNavigationState] = React.useState<State>(() =>
@@ -110,13 +105,13 @@ const Container = React.forwardRef(function NavigationContainer(
};
React.useImperativeHandle(ref, () => ({
...(Object.keys(BaseActions) as Array<keyof typeof BaseActions>).reduce<
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
any
>((acc, name) => {
acc[name] = (...args: any[]) =>
dispatch(
// eslint-disable-next-line import/namespace
BaseActions[name](
CommonActions[name](
// @ts-ignore
...args
)

View File

@@ -1,5 +1,5 @@
import BaseRouter from '../BaseRouter';
import * as BaseActions from '../BaseActions';
import * as CommonActions from '../CommonActions';
jest.mock('shortid', () => () => 'test');
@@ -18,7 +18,7 @@ const STATE = {
it('replaces focused screen with REPLACE', () => {
const result = BaseRouter.getStateForAction(
STATE,
BaseActions.replace('qux', { answer: 42 })
CommonActions.replace('qux', { answer: 42 })
);
expect(result).toEqual({
@@ -36,7 +36,7 @@ it('replaces focused screen with REPLACE', () => {
it('replaces source screen with REPLACE', () => {
const result = BaseRouter.getStateForAction(STATE, {
...BaseActions.replace('qux', { answer: 42 }),
...CommonActions.replace('qux', { answer: 42 }),
source: 'baz',
});
@@ -55,7 +55,7 @@ it('replaces source screen with REPLACE', () => {
it("doesn't handle REPLACE if source key isn't present", () => {
const result = BaseRouter.getStateForAction(STATE, {
...BaseActions.replace('qux', { answer: 42 }),
...CommonActions.replace('qux', { answer: 42 }),
source: 'magic',
});
@@ -65,7 +65,7 @@ it("doesn't handle REPLACE if source key isn't present", () => {
it('sets params for the focused screen with SET_PARAMS', () => {
const result = BaseRouter.getStateForAction(
STATE,
BaseActions.setParams({ answer: 42 })
CommonActions.setParams({ answer: 42 })
);
expect(result).toEqual({
@@ -83,7 +83,7 @@ it('sets params for the focused screen with SET_PARAMS', () => {
it('sets params for the source screen with SET_PARAMS', () => {
const result = BaseRouter.getStateForAction(STATE, {
...BaseActions.setParams({ answer: 42 }),
...CommonActions.setParams({ answer: 42 }),
source: 'foo',
});
@@ -102,7 +102,7 @@ it('sets params for the source screen with SET_PARAMS', () => {
it("doesn't handle SET_PARAMS if source key isn't present", () => {
const result = BaseRouter.getStateForAction(STATE, {
...BaseActions.setParams({ answer: 42 }),
...CommonActions.setParams({ answer: 42 }),
source: 'magic',
});
@@ -119,7 +119,7 @@ it('resets state to new state with RESET', () => {
const result = BaseRouter.getStateForAction(
STATE,
BaseActions.reset({
CommonActions.reset({
index: 0,
routes,
})
@@ -132,7 +132,7 @@ it('ignores key and routeNames when resetting with RESET', () => {
const result = BaseRouter.getStateForAction(
STATE,
// @ts-ignore
BaseActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
CommonActions.reset({ index: 2, key: 'foo', routeNames: ['test'] })
);
expect(result).toEqual({ ...STATE, index: 2 });

View File

@@ -3,8 +3,8 @@ import { render, act } from 'react-native-testing-library';
import Screen from '../Screen';
import NavigationContainer from '../NavigationContainer';
import useNavigationBuilder from '../useNavigationBuilder';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import useNavigation from '../useNavigation';
import MockRouter, { MockRouterKey } from './__fixtures__/MockRouter';
import { NavigationState } from '../types';
beforeEach(() => (MockRouterKey.current = 0));

View File

@@ -17,6 +17,7 @@ it('sets options with options prop as an object', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
@@ -61,6 +62,7 @@ it("returns correct value for canGoBack when it's not overridden", () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
@@ -123,6 +125,7 @@ it(`returns false for canGoBack when current router doesn't handle GO_BACK`, ()
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(TestRouter, props);
@@ -172,6 +175,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
const ParentNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(ParentRouter, props);
@@ -181,6 +185,7 @@ it('returns true for canGoBack when current router handles GO_BACK', () => {
const ChildNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
@@ -237,6 +242,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const OverrodeNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(OverrodeRouter, props);
@@ -246,6 +252,7 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
@@ -293,6 +300,7 @@ it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
@@ -338,6 +346,7 @@ it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
@@ -392,6 +401,7 @@ it('updates options with setOptions', () => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];

View File

@@ -1,6 +1,6 @@
import * as BaseActions from './BaseActions';
import * as CommonActions from './CommonActions';
export { BaseActions };
export { CommonActions };
export { default as BaseRouter } from './BaseRouter';
export { default as NavigationContainer } from './NavigationContainer';

View File

@@ -1,6 +1,7 @@
import * as BaseActions from './BaseActions';
import * as CommonActions from './CommonActions';
import * as React from 'react';
export type CommonAction = BaseActions.Action;
export type CommonAction = CommonActions.Action;
export type NavigationState = {
/**
@@ -186,7 +187,7 @@ export type EventMapBase = {
blur: undefined;
};
export type EventArg<EventName extends string, Data> = {
export type EventArg<EventName extends string, Data = undefined> = {
/**
* Type of the event (e.g. `focus`, `blur`)
*/
@@ -231,11 +232,14 @@ export type EventEmitter<EventMap extends { [key: string]: any }> = {
* @param [options.target] Key of the target route which should receive the event.
* If not specified, all routes receive the event.
*/
emit<EventName extends Extract<keyof EventMap, string>>(options: {
type: EventName;
data?: EventMap[EventName];
target?: string;
}): EventArg<EventName, EventMap[EventName]>;
emit<EventName extends Extract<keyof EventMap, string>>(
options: {
type: EventName;
target?: string;
} & (EventMap[EventName] extends undefined
? {}
: { data: EventMap[EventName] })
): EventArg<EventName, EventMap[EventName]>;
};
export class PrivateValueStore<A, B, C> {
@@ -326,9 +330,10 @@ type NavigationHelpersCommon<
} & PrivateValueStore<ParamList, keyof ParamList, {}>;
export type NavigationHelpers<
ParamList extends ParamListBase
ParamList extends ParamListBase,
EventMap extends { [key: string]: any } = {}
> = NavigationHelpersCommon<ParamList> &
EventEmitter<{ [key: string]: any }> & {
EventEmitter<EventMap> & {
/**
* Update the param object for the route.
* The new params will be shallow merged with the old one.
@@ -340,6 +345,14 @@ export type NavigationHelpers<
): void;
};
export type NavigationContainerProps = {
initialState?: InitialState;
onStateChange?: (
state: NavigationState | PartialState<NavigationState> | undefined
) => void;
children: React.ReactNode;
};
export type NavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string,

View File

@@ -21,6 +21,7 @@ import {
RouterFactory,
PartialState,
PrivateValueStore,
NavigationAction,
} from './types';
// This is to make TypeScript compiler happy
@@ -84,8 +85,9 @@ const getRouteConfigsFromChildren = <ScreenOptions extends object>(
*/
export default function useNavigationBuilder<
State extends NavigationState,
RouterOptions extends DefaultRouterOptions,
ScreenOptions extends object,
RouterOptions extends DefaultRouterOptions
EventMap extends { [key: string]: any }
>(
createRouter: RouterFactory<State, any, RouterOptions>,
options: DefaultNavigatorOptions<ScreenOptions> & RouterOptions
@@ -240,7 +242,7 @@ export default function useNavigationBuilder<
setState,
});
const navigation = useNavigationHelpers({
const navigation = useNavigationHelpers<State, NavigationAction, EventMap>({
onAction,
getState,
setState,

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import * as BaseActions from './BaseActions';
import * as CommonActions from './CommonActions';
import { NavigationEventEmitter } from './useEventEmitter';
import NavigationContext from './NavigationContext';
@@ -53,7 +53,7 @@ export default function useNavigationCache<
const actions = {
...router.actionCreators,
...BaseActions,
...CommonActions,
};
cache.current = state.routes.reduce<NavigationCache<State, ScreenOptions>>(

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import * as BaseActions from './BaseActions';
import * as CommonActions from './CommonActions';
import NavigationContext from './NavigationContext';
import { NavigationStateContext } from './NavigationContainer';
import { NavigationEventEmitter } from './useEventEmitter';
@@ -34,7 +34,8 @@ type Options<State extends NavigationState, Action extends NavigationAction> = {
*/
export default function useNavigationHelpers<
State extends NavigationState,
Action extends NavigationAction
Action extends NavigationAction,
EventMap extends { [key: string]: any }
>({ onAction, getState, setState, emitter, router }: Options<State, Action>) {
const parentNavigationHelpers = React.useContext(NavigationContext);
const { performTransaction } = React.useContext(NavigationStateContext);
@@ -51,7 +52,7 @@ export default function useNavigationHelpers<
const actions = {
...router.actionCreators,
...BaseActions,
...CommonActions,
};
const helpers = Object.keys(actions).reduce(
@@ -72,11 +73,13 @@ export default function useNavigationHelpers<
? parentNavigationHelpers.isFocused
: () => true,
canGoBack: () =>
router.getStateForAction(getState(), BaseActions.goBack() as Action) !==
null ||
router.getStateForAction(
getState(),
CommonActions.goBack() as Action
) !== null ||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
false,
} as NavigationHelpers<ParamListBase> &
} as NavigationHelpers<ParamListBase, EventMap> &
(NavigationProp<ParamListBase, string, any, any, any> | undefined);
}, [
router,

View File

@@ -3,6 +3,41 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.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
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.2...@react-navigation/drawer@5.0.0-alpha.3) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.1...@react-navigation/drawer@5.0.0-alpha.2) (2019-08-22)
**Note:** Version bump only for package @react-navigation/drawer
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -11,7 +11,7 @@
"material",
"drawer"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -21,7 +21,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/drawer/src/index.d.ts",
"files": [
"src",
"lib"
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.1",
"@react-navigation/routers": "^5.0.0-alpha.5",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -11,7 +11,11 @@ import {
} from '@react-navigation/routers';
import DrawerView from '../views/DrawerView';
import { DrawerNavigationOptions, DrawerNavigationConfig } from '../types';
import {
DrawerNavigationOptions,
DrawerNavigationConfig,
DrawerNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
DrawerRouterOptions &
@@ -25,8 +29,9 @@ function DrawerNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
DrawerNavigationState,
DrawerRouterOptions,
DrawerNavigationOptions,
DrawerRouterOptions
DrawerNavigationEventMap
>(DrawerRouter, {
initialRouteName,
children,

View File

@@ -182,6 +182,11 @@ export type DrawerNavigationEventMap = {
drawerClose: undefined;
};
export type DrawerNavigationHelpers = NavigationHelpers<
ParamListBase,
DrawerNavigationEventMap
>;
export type DrawerNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string

View File

@@ -1,24 +1,24 @@
import * as React from 'react';
import { StyleSheet, View, ViewStyle, StyleProp } from 'react-native';
import Animated from 'react-native-reanimated';
import {
NavigationHelpers,
ParamListBase,
Route,
BaseActions,
} from '@react-navigation/core';
import { Route, CommonActions } from '@react-navigation/core';
import {
DrawerActions,
DrawerNavigationState,
} from '@react-navigation/routers';
import { Scene, ContentComponentProps, DrawerDescriptorMap } from '../types';
import {
Scene,
ContentComponentProps,
DrawerDescriptorMap,
DrawerNavigationHelpers,
} from '../types';
type Props = {
contentComponent?: React.ComponentType<ContentComponentProps>;
contentOptions?: object;
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
drawerOpenProgress: Animated.Node<number>;
drawerPosition: 'left' | 'right';
@@ -78,7 +78,7 @@ class DrawerSidebar extends React.PureComponent<Props> {
navigation.dispatch({
...(focused
? DrawerActions.closeDrawer()
: BaseActions.navigate(route.name)),
: CommonActions.navigate(route.name)),
target: state.key,
});
};

View File

@@ -4,7 +4,6 @@ import { Dimensions, StyleSheet, I18nManager, Platform } from 'react-native';
import { ScreenContainer } from 'react-native-screens';
import SafeAreaView from 'react-native-safe-area-view';
import { PanGestureHandler, ScrollView } from 'react-native-gesture-handler';
import { ParamListBase, NavigationHelpers } from '@react-navigation/core';
import {
DrawerNavigationState,
DrawerActions,
@@ -19,11 +18,12 @@ import {
DrawerDescriptorMap,
DrawerNavigationConfig,
ContentComponentProps,
DrawerNavigationHelpers,
} from '../types';
type Props = DrawerNavigationConfig & {
state: DrawerNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
};

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.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)
### Features
* add native container ([d26b77f](https://github.com/react-navigation/navigation-ex/commit/d26b77f))
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -2,6 +2,6 @@
If you want to run the example from the repo,
- Clone the repository and run `lerna bootstrap` in the root
- Clone the repository and run `yarn` in the project root
- Run `yarn example start` to start the packager
- Follow the instructions to open it with the [Expo app](https://expo.io/)

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.1",
"version": "5.0.0-alpha.2",
"private": true,
"workspaces": {
"nohoist": [

View File

@@ -4,12 +4,11 @@ import { Linking } from 'expo';
import { Appbar, List } from 'react-native-paper';
import { Asset } from 'expo-asset';
import {
NavigationContainer,
InitialState,
getStateFromPath,
NavigationContainerRef,
} from '@react-navigation/core';
import { useBackButton, useLinking } from '@react-navigation/native';
import { useLinking, NativeContainer } from '@react-navigation/native';
import {
createDrawerNavigator,
DrawerNavigationProp,
@@ -60,8 +59,6 @@ Asset.loadAsync(StackAssets);
export default function App() {
const containerRef = React.useRef<NavigationContainerRef>();
useBackButton(containerRef);
// To test deep linking on, run the following in the Terminal:
// Android: adb shell am start -a android.intent.action.VIEW -d "exp://127.0.0.1:19000/--/simple-stack"
// iOS: xcrun simctl openurl booted exp://127.0.0.1:19000/--/simple-stack
@@ -116,7 +113,7 @@ export default function App() {
}
return (
<NavigationContainer
<NativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
@@ -175,6 +172,6 @@ export default function App() {
)}
</Drawer.Screen>
</Drawer.Navigator>
</NavigationContainer>
</NativeContainer>
);
}

View File

@@ -3,6 +3,44 @@
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/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)
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.2...@react-navigation/material-bottom-tabs@5.0.0-alpha.3) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.1...@react-navigation/material-bottom-tabs@5.0.0-alpha.2) (2019-08-22)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -21,7 +21,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/material-bottom-tabs/src/index.d.ts",
"files": [
"src",
"lib"
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.1"
"@react-navigation/routers": "^5.0.0-alpha.5"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -14,6 +14,7 @@ import MaterialBottomTabView from '../views/MaterialBottomTabView';
import {
MaterialBottomTabNavigationConfig,
MaterialBottomTabNavigationOptions,
MaterialBottomTabNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<MaterialBottomTabNavigationOptions> &
@@ -29,8 +30,9 @@ function MaterialBottomTabNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
TabNavigationState,
TabRouterOptions,
MaterialBottomTabNavigationOptions,
TabRouterOptions
MaterialBottomTabNavigationEventMap
>(TabRouter, {
initialRouteName,
backBehavior,

View File

@@ -3,14 +3,22 @@ import {
ParamListBase,
Descriptor,
NavigationProp,
NavigationHelpers,
} from '@react-navigation/core';
import { TabNavigationState } from '@react-navigation/routers';
export type MaterialBottomTabNavigationEventMap = {
refocus: undefined;
/**
* Event which fires on tapping on the tab in the tab bar.
*/
tabPress: undefined;
};
export type MaterialBottomTabNavigationHelpers = NavigationHelpers<
ParamListBase,
MaterialBottomTabNavigationEventMap
>;
export type MaterialBottomTabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string

View File

@@ -2,21 +2,18 @@ import * as React from 'react';
import { StyleSheet } from 'react-native';
import { BottomNavigation } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialIcons';
import {
NavigationHelpers,
ParamListBase,
Route,
} from '@react-navigation/core';
import { Route } from '@react-navigation/core';
import { TabNavigationState, TabActions } from '@react-navigation/routers';
import {
MaterialBottomTabDescriptorMap,
MaterialBottomTabNavigationConfig,
MaterialBottomTabNavigationHelpers,
} from '../types';
type Props = MaterialBottomTabNavigationConfig & {
state: TabNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: MaterialBottomTabNavigationHelpers;
descriptors: MaterialBottomTabDescriptorMap;
};
@@ -65,19 +62,12 @@ export default class MaterialBottomTabView extends React.PureComponent<Props> {
};
private handleTabPress = ({ route }: Scene) => {
const { state, navigation } = this.props;
const { navigation } = this.props;
navigation.emit({
type: 'tabPress',
target: route.key,
});
if (state.routes[state.index].key === route.key) {
navigation.emit({
type: 'refocus',
target: route.key,
});
}
};
private renderIcon = ({

View File

@@ -3,6 +3,44 @@
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/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)
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.2...@react-navigation/material-top-tabs@5.0.0-alpha.3) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.1...@react-navigation/material-top-tabs@5.0.0-alpha.2) (2019-08-22)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -11,7 +11,7 @@
"material",
"tab"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -21,7 +21,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/material-top-tabs/src/index.d.ts",
"files": [
"src",
"lib"
@@ -34,7 +34,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.1"
"@react-navigation/routers": "^5.0.0-alpha.5"
},
"devDependencies": {
"@react-native-community/bob": "^0.7.0",

View File

@@ -13,6 +13,7 @@ import MaterialTopTabView from '../views/MaterialTopTabView';
import {
MaterialTopTabNavigationConfig,
MaterialTopTabNavigationOptions,
MaterialTopTabNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<MaterialTopTabNavigationOptions> &
@@ -28,8 +29,9 @@ function MaterialTopTabNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
TabNavigationState,
TabRouterOptions,
MaterialTopTabNavigationOptions,
TabRouterOptions
MaterialTopTabNavigationEventMap
>(TabRouter, {
initialRouteName,
backBehavior,

View File

@@ -10,10 +10,6 @@ import {
import { TabNavigationState } from '@react-navigation/routers';
export type MaterialTopTabNavigationEventMap = {
/**
* Event which fires on tapping on the tab for an already focused screen.
*/
refocus: undefined;
/**
* Event which fires on tapping on the tab in the tab bar.
*/
@@ -32,6 +28,11 @@ export type MaterialTopTabNavigationEventMap = {
swipeEnd: undefined;
};
export type MaterialTopTabNavigationHelpers = NavigationHelpers<
ParamListBase,
MaterialTopTabNavigationEventMap
>;
export type MaterialTopTabNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string

View File

@@ -1,21 +1,18 @@
import * as React from 'react';
import { TabView, SceneRendererProps } from 'react-native-tab-view';
import {
NavigationHelpers,
ParamListBase,
Route,
} from '@react-navigation/core';
import { Route } from '@react-navigation/core';
import { TabNavigationState, TabActions } from '@react-navigation/routers';
import MaterialTopTabBar from './MaterialTopTabBar';
import {
MaterialTopTabDescriptorMap,
MaterialTopTabNavigationConfig,
MaterialTopTabNavigationHelpers,
} from '../types';
type Props = MaterialTopTabNavigationConfig & {
state: TabNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: MaterialTopTabNavigationHelpers;
descriptors: MaterialTopTabDescriptorMap;
tabBarPosition: 'top' | 'bottom';
};
@@ -76,7 +73,6 @@ export default class MaterialTopTabView extends React.PureComponent<Props> {
route: Route<string>;
preventDefault: () => void;
}) => {
const { state, navigation } = this.props;
const event = this.props.navigation.emit({
type: 'tabPress',
target: route.key,
@@ -85,13 +81,6 @@ export default class MaterialTopTabView extends React.PureComponent<Props> {
if (event.defaultPrevented) {
preventDefault();
}
if (state.routes[state.index].key === route.key) {
navigation.emit({
type: 'refocus',
target: route.key,
});
}
};
private handleTabLongPress = ({ route }: { route: Route<string> }) => {

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.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)
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
* add native container ([d26b77f](https://github.com/react-navigation/navigation-ex/commit/d26b77f))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.1...@react-navigation/native@5.0.0-alpha.2) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -7,7 +7,7 @@
"ios",
"android"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.4",
"license": "MIT",
"repository": {
"type": "git",
@@ -17,7 +17,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/native/src/index.d.ts",
"files": [
"src",
"lib"

View File

@@ -0,0 +1,38 @@
import * as React from 'react';
import {
NavigationContainer,
NavigationContainerProps,
NavigationContainerRef,
} from '@react-navigation/core';
import useBackButton from './useBackButton';
/**
* Container component which holds the navigation state
* designed for mobile apps.
* This should be rendered at the root wrapping the whole app.
*
* @param props.initialState Initial state object for the navigation tree.
* @param props.onStateChange Callback which is called with the latest navigation state when it changes.
* @param props.children Child elements to render the content.
* @param props.ref Ref object which refers to the navigation object containing helper methods.
*/
const NativeContainer = React.forwardRef(function NativeContainer(
props: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const refContainer = React.useRef<NavigationContainerRef>(null);
useBackButton(refContainer);
React.useImperativeHandle(ref, () => refContainer.current);
return (
<NavigationContainer
{...props}
ref={refContainer}
children={props.children}
/>
);
});
export default NativeContainer;

View File

@@ -1,2 +1,4 @@
export { default as NativeContainer } from './NativeContainer';
export { default as useBackButton } from './useBackButton';
export { default as useLinking } from './useLinking';
export { default as useScrollToTop } from './useScrollToTop';

View File

@@ -0,0 +1,30 @@
import * as React from 'react';
import { useNavigation, EventArg } from '@react-navigation/core';
type ScrollableView = {
scrollTo(options: { x?: number; y?: number; animated?: boolean }): void;
};
export default function useScrollToTop(ref: React.RefObject<ScrollableView>) {
const navigation = useNavigation();
React.useEffect(
() =>
// @ts-ignore
// 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 (isFocused && !e.defaultPrevented && ref.current) {
// When user taps on already focused tab, scroll to top
ref.current.scrollTo({ y: 0 });
}
});
}),
[navigation, ref]
);
}

View File

@@ -3,6 +3,44 @@
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/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
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.2...@react-navigation/routers@5.0.0-alpha.3) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.1...@react-navigation/routers@5.0.0-alpha.2) (2019-08-22)
### Bug Fixes
* properly handle pop action from stack ([61dce7a](https://github.com/react-navigation/navigation-ex/commit/61dce7a))
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -0,0 +1,440 @@
import { CommonActions } from '@react-navigation/core';
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({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('baz', { answer: 42 })
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz', params: { answer: 42 } },
{ key: 'bar', name: 'bar' },
],
});
});
it('handles open drawer action', () => {
const router = DrawerRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
DrawerActions.openDrawer()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
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', () => {
const router = DrawerRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: true,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
DrawerActions.closeDrawer()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
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', () => {
const router = DrawerRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: true,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
DrawerActions.toggleDrawer()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: false,
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
isDrawerOpen: false,
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
DrawerActions.toggleDrawer()
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
isDrawerOpen: true,
routeKeyHistory: [],
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

@@ -0,0 +1,496 @@
import { CommonActions } from '@react-navigation/core';
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({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('qux', { answer: 42 })
)
).toEqual({
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz', name: 'baz' },
{ key: 'bar', name: 'bar' },
{
key: 'qux-test',
name: 'qux',
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);
});
it('handles go back action', () => {
const router = StackRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toEqual({
stale: false,
key: 'root',
index: 0,
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', () => {
const router = StackRouter({});
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()
)
).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,
key: 'root',
index: 0,
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', () => {
const router = StackRouter({});
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.popToTop()
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz', name: 'baz' }],
});
});
it('handles push action', () => {
const router = StackRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'bar', name: 'bar' }],
},
StackActions.push('baz')
)
).toEqual({
stale: false,
key: 'root',
index: 3,
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

@@ -0,0 +1,526 @@
import { CommonActions } from '@react-navigation/core';
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(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate('baz', { answer: 42 })
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar'],
routes: [
{ key: 'baz', name: 'baz', params: { answer: 42 } },
{ 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', () => {
const router = TabRouter({});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
TabActions.jumpTo('bar')
)
).toEqual({
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['baz'],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
});
});
it('handles back action with backBehavior: history', () => {
const router = TabRouter({ backBehavior: 'history' });
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
routeKeyHistory: ['bar'],
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(
{
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 back action with backBehavior: order', () => {
const router = TabRouter({ backBehavior: 'order' });
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar'],
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 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(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar'],
routeKeyHistory: [],
routes: [{ key: 'baz', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.goBack()
)
).toBe(null);
});
it('handles back action with backBehavior: none', () => {
const router = TabRouter({ backBehavior: 'none' });
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(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.1",
"version": "5.0.0-alpha.5",
"license": "MIT",
"repository": {
"type": "git",
@@ -16,7 +16,7 @@
"main": "lib/commonjs/index.js",
"react-native": "src/index.tsx",
"module": "lib/module/index.js",
"types": "lib/typescript/src/index.d.ts",
"types": "lib/typescript/routers/src/index.d.ts",
"files": [
"src",
"lib"

View File

@@ -161,21 +161,27 @@ export default function StackRouter(options: StackRouterOptions) {
return null;
case 'POP':
if (state.index > 0) {
const index = state.routes.length - 1;
case 'POP': {
const index =
action.target === state.key && action.source
? state.routes.findIndex(r => r.key === action.source)
: state.index;
if (index > 0) {
const routes = state.routes.slice(
0,
Math.max(index - action.payload.count + 1, 1)
);
return {
...state,
index: Math.max(0, state.index - action.payload.count),
routes: state.routes.slice(
0,
Math.max(index - action.payload.count + 1, 1)
),
index: routes.length - 1,
routes,
};
}
return null;
}
case 'POP_TO_TOP':
return router.getStateForAction(state, {
@@ -248,6 +254,8 @@ export default function StackRouter(options: StackRouterOptions) {
return router.getStateForAction(state, {
type: 'POP',
payload: { count: 1 },
target: action.target,
source: action.source,
});
}

View File

@@ -3,6 +3,76 @@
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.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)
### Bug Fixes
* link proper descriptor for StackView ([469ec31](https://github.com/react-navigation/navigation-ex/commit/469ec31))
* set correct pointer events when active prop changes ([1bbd6ac](https://github.com/react-navigation/navigation-ex/commit/1bbd6ac))
### Features
* add hook to scroll to top on tab press ([9e1104c](https://github.com/react-navigation/navigation-ex/commit/9e1104c))
* add memoization of spring for stack ([7990cf2](https://github.com/react-navigation/navigation-ex/commit/7990cf2))
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.3...@react-navigation/stack@5.0.0-alpha.4) (2019-08-22)
### Bug Fixes
* fix path to typescript definitions ([f182315](https://github.com/react-navigation/navigation-ex/commit/f182315))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.2...@react-navigation/stack@5.0.0-alpha.3) (2019-08-22)
**Note:** Version bump only for package @react-navigation/stack
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.1...@react-navigation/stack@5.0.0-alpha.2) (2019-08-21)
### Bug Fixes
* check if left button is truthy to add a left offset ([8645e36](https://github.com/react-navigation/navigation-ex/commit/8645e36))
# 5.0.0-alpha.1 (2019-08-21)

View File

@@ -10,7 +10,7 @@
"android",
"stack"
],
"version": "5.0.0-alpha.1",
"version": "5.0.0-alpha.7",
"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/src/index.d.ts",
"types": "lib/typescript/stack/src/index.d.ts",
"files": [
"src",
"lib"
@@ -33,7 +33,7 @@
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^5.0.0-alpha.1",
"@react-navigation/routers": "^5.0.0-alpha.5",
"react-native-safe-area-view": "^0.14.6"
},
"devDependencies": {

View File

@@ -13,7 +13,11 @@ import {
} from '@react-navigation/routers';
import KeyboardManager from '../views/KeyboardManager';
import StackView from '../views/Stack/StackView';
import { StackNavigationConfig, StackNavigationOptions } from '../types';
import {
StackNavigationConfig,
StackNavigationOptions,
StackNavigationEventMap,
} from '../types';
type Props = DefaultNavigatorOptions<StackNavigationOptions> &
StackRouterOptions &
@@ -28,8 +32,9 @@ function StackNavigator({
}: Props) {
const { state, descriptors, navigation } = useNavigationBuilder<
StackNavigationState,
StackRouterOptions,
StackNavigationOptions,
StackRouterOptions
StackNavigationEventMap
>(StackRouter, {
initialRouteName,
children,
@@ -39,13 +44,21 @@ function StackNavigator({
React.useEffect(
() =>
navigation.addListener &&
navigation.addListener('refocus', (e: EventArg<'refocus', undefined>) => {
if (state.index > 0 && !e.defaultPrevented) {
navigation.dispatch({
...StackActions.popToTop(),
target: state.key,
});
}
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 && 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({
...StackActions.popToTop(),
target: state.key,
});
}
});
}),
[navigation, state.index, state.key]
);

View File

@@ -10,6 +10,7 @@ import {
ParamListBase,
Descriptor,
Route,
NavigationHelpers,
} from '@react-navigation/core';
import { StackNavigationState } from '@react-navigation/routers';
@@ -24,6 +25,11 @@ export type StackNavigationEventMap = {
transitionEnd: { closing: boolean };
};
export type StackNavigationHelpers = NavigationHelpers<
ParamListBase,
StackNavigationEventMap
>;
export type StackNavigationProp<
ParamList extends ParamListBase,
RouteName extends keyof ParamList = string

View File

@@ -274,6 +274,24 @@ export default class HeaderSegment extends React.Component<Props, State> {
}
}
const leftButton = left
? left({
backImage,
pressColorAndroid,
allowFontScaling: backAllowFontScaling,
onPress: onGoBack,
labelVisible: headerBackTitleVisible,
label: leftLabel !== undefined ? leftLabel : previousTitle,
truncatedLabel,
labelStyle: [leftLabelStyle, customLeftLabelStyle],
onLabelLayout: this.handleLeftLabelLayout,
screenLayout: layout,
titleLayout,
tintColor: headerTintColor,
canGoBack: Boolean(onGoBack),
})
: null;
return (
<React.Fragment>
<Animated.View
@@ -295,26 +313,12 @@ export default class HeaderSegment extends React.Component<Props, State> {
style={{ height: headerStatusBarHeight }}
/>
<View pointerEvents="box-none" style={styles.content}>
{left ? (
{leftButton ? (
<Animated.View
pointerEvents="box-none"
style={[styles.left, leftButtonStyle, leftContainerStyle]}
>
{left({
backImage,
pressColorAndroid,
allowFontScaling: backAllowFontScaling,
onPress: onGoBack,
labelVisible: headerBackTitleVisible,
label: leftLabel !== undefined ? leftLabel : previousTitle,
truncatedLabel,
labelStyle: [leftLabelStyle, customLeftLabelStyle],
onLabelLayout: this.handleLeftLabelLayout,
screenLayout: layout,
titleLayout,
tintColor: headerTintColor,
canGoBack: Boolean(onGoBack),
})}
{leftButton}
</Animated.View>
) : null}
<Animated.View
@@ -322,7 +326,7 @@ export default class HeaderSegment extends React.Component<Props, State> {
style={[
Platform.select({
ios: null,
default: { left: left ? 72 : 16 },
default: { left: leftButton ? 72 : 16 },
}),
styles.title,
titleStyle,

View File

@@ -94,6 +94,86 @@ const {
Value,
} = Animated;
// We need to be prepared for both version of reanimated. With and w/out proc
let memoizedSpring = spring;
// @ts-ignore
if (Animated.proc) {
// @ts-ignore
const springHelper = Animated.proc(
(
finished: Animated.Value<number>,
velocity: Animated.Value<number>,
position: Animated.Value<number>,
time: Animated.Value<number>,
prevPosition: Animated.Value<number>,
toValue: Animated.Adaptable<number>,
damping: Animated.Adaptable<number>,
mass: Animated.Adaptable<number>,
stiffness: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<number>,
restSpeedThreshold: Animated.Adaptable<number>,
restDisplacementThreshold: Animated.Adaptable<number>,
clock: Animated.Clock
) =>
spring(
clock,
{
finished,
velocity,
position,
time,
// @ts-ignore
prevPosition,
},
{
toValue,
damping,
mass,
stiffness,
overshootClamping,
restDisplacementThreshold,
restSpeedThreshold,
}
)
);
// @ts-ignore
memoizedSpring = function(
clock: Animated.Clock,
state: {
finished: Animated.Value<number>;
velocity: Animated.Value<number>;
position: Animated.Value<number>;
time: Animated.Value<number>;
},
config: {
toValue: Animated.Adaptable<number>;
damping: Animated.Adaptable<number>;
mass: Animated.Adaptable<number>;
stiffness: Animated.Adaptable<number>;
overshootClamping: Animated.Adaptable<boolean>;
restSpeedThreshold: Animated.Adaptable<number>;
restDisplacementThreshold: Animated.Adaptable<number>;
}
) {
return springHelper(
state.finished,
state.velocity,
state.position,
state.time,
new Value(0),
config.toValue,
config.damping,
config.mass,
config.stiffness,
config.overshootClamping,
config.restSpeedThreshold,
config.restDisplacementThreshold,
clock
);
};
}
export default class Card extends React.Component<Props> {
static defaultProps = {
overlayEnabled: Platform.OS !== 'ios',
@@ -219,7 +299,7 @@ export default class Card extends React.Component<Props> {
cond(
eq(isVisible, 1),
openingSpec.timing === 'spring'
? spring(
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...openingSpec.config, toValue: this.toValue }
@@ -230,7 +310,7 @@ export default class Card extends React.Component<Props> {
{ ...openingSpec.config, toValue: this.toValue }
),
closingSpec.timing === 'spring'
? spring(
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...closingSpec.config, toValue: this.toValue }
@@ -286,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(
@@ -374,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([
@@ -496,15 +581,21 @@ 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} />
{this.props.gestureEnabled ? (
<Animated.Code exec={this.execNoGesture} />
) : (
<Animated.Code exec={this.execWithGesture} />
)}
{overlayEnabled && overlayStyle ? (
<Animated.View
pointerEvents="none"

View File

@@ -23,6 +23,13 @@ const { block, greaterThan, cond, set, call, onChange } = Animated;
* whenever position changes.
*/
export default class PointerEventsView extends React.Component<Props> {
componentDidUpdate(prevProps: Props) {
if (this.props.active !== prevProps.active) {
this.pointerEventsEnabled.setValue(this.props.active ? TRUE : FALSE);
this.setPointerEventsEnabled(this.props.active);
}
}
private pointerEventsEnabled = new Animated.Value<Binary>(
this.props.active ? TRUE : FALSE
);
@@ -40,13 +47,17 @@ export default class PointerEventsView extends React.Component<Props> {
onChange(
this.pointerEventsEnabled,
call([this.pointerEventsEnabled], ([value]) => {
const pointerEvents = this.props.active && value ? 'box-none' : 'none';
this.root && this.root.setNativeProps({ pointerEvents });
this.setPointerEventsEnabled(Boolean(this.props.active && value));
})
),
]);
private setPointerEventsEnabled = (enabled: boolean) => {
const pointerEvents = enabled ? 'box-none' : 'none';
this.root && this.root.setNativeProps({ pointerEvents });
};
private root: View | null = null;
render() {

View File

@@ -10,11 +10,7 @@ import {
import Animated from 'react-native-reanimated';
// eslint-disable-next-line import/no-unresolved
import * as Screens from 'react-native-screens'; // Import with * as to prevent getters being called
import {
Route,
NavigationHelpers,
ParamListBase,
} from '@react-navigation/core';
import { Route } from '@react-navigation/core';
import { StackNavigationState } from '@react-navigation/routers';
import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
@@ -31,6 +27,7 @@ import {
HeaderScene,
StackDescriptorMap,
StackNavigationOptions,
StackNavigationHelpers,
} from '../../types';
type ProgressValues = {
@@ -40,7 +37,7 @@ type ProgressValues = {
type Props = {
mode: 'card' | 'modal';
state: StackNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: StackNavigationHelpers;
descriptors: StackDescriptorMap;
routes: Route<string>[];
openingRoutes: string[];
@@ -331,8 +328,8 @@ export default class Stack extends React.Component<Props, State> {
{routes.map((route, index, self) => {
const focused = focusedRoute.key === route.key;
const current = progress[route.key];
const descriptor = descriptors[route.key];
const scene = scenes[index];
const descriptor = scene.descriptor;
const next = self[index + 1]
? progress[self[index + 1].key]
: ANIMATED_ONE;

View File

@@ -2,14 +2,16 @@ import * as React from 'react';
import { StyleSheet, Platform, StyleProp, ViewStyle } from 'react-native';
import Animated from 'react-native-reanimated';
import { StackNavigationState } from '@react-navigation/routers';
import {
Route,
NavigationHelpers,
ParamListBase,
} from '@react-navigation/core';
import { Route } from '@react-navigation/core';
import { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import Card from './Card';
import { HeaderScene, Layout, HeaderMode, TransitionPreset } from '../../types';
import {
StackNavigationHelpers,
HeaderScene,
Layout,
HeaderMode,
TransitionPreset,
} from '../../types';
type Props = TransitionPreset & {
index: number;
@@ -21,7 +23,7 @@ type Props = TransitionPreset & {
previousScene?: HeaderScene<Route<string>>;
scene: HeaderScene<Route<string>>;
state: StackNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: StackNavigationHelpers;
cardTransparent?: boolean;
cardOverlayEnabled?: boolean;
cardShadowEnabled?: boolean;

View File

@@ -1,21 +1,21 @@
import * as React from 'react';
import { Platform } from 'react-native';
import {
ParamListBase,
Route,
NavigationHelpers,
} from '@react-navigation/core';
import { Route } from '@react-navigation/core';
import { StackActions, StackNavigationState } from '@react-navigation/routers';
import Stack from './Stack';
import HeaderContainer, {
Props as HeaderContainerProps,
} from '../Header/HeaderContainer';
import { StackNavigationConfig, StackDescriptorMap } from '../../types';
import {
StackNavigationHelpers,
StackNavigationConfig,
StackDescriptorMap,
} from '../../types';
type Props = StackNavigationConfig & {
state: StackNavigationState;
navigation: NavigationHelpers<ParamListBase>;
navigation: StackNavigationHelpers;
descriptors: StackDescriptorMap;
onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void;

View File

@@ -2,7 +2,10 @@
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@react-navigation/*": ["./packages/*/src", "./packages/*/lib/typescript"],
"@react-navigation/*": [
"./packages/*/src",
"./packages/*/lib/typescript"
],
"use-subscription": ["./typings/use-subscription.d"]
},
"allowUnreachableCode": false,