Compare commits

..

42 Commits

Author SHA1 Message Date
satyajit.happy
44020452fb chore: publish
- @react-navigation/core@5.0.0-alpha.8
 - @react-navigation/stack@5.0.0-alpha.15
2019-09-04 11:56:16 +02:00
satyajit.happy
ceaf18edd6 chore: remove commented out code 2019-09-04 11:55:37 +02:00
satyajit.happy
faaf855717 refactor: separate navigation and route context 2019-09-04 11:54:20 +02:00
satyajit.happy
196cce0803 feat: add approximate android Q transition 2019-09-04 11:36:00 +02:00
satyajit.happy
a022860317 chore: publish
- @react-navigation/stack@5.0.0-alpha.14
2019-09-03 16:43:26 +02:00
satyajit.happy
167d58ce27 fix: change order of attaching nodes in card exec 2019-09-03 16:41:11 +02:00
Michal Osadnik
798a905d0a chore: publish
- @react-navigation/stack@5.0.0-alpha.13
2019-09-01 16:56:21 +01:00
Michal Osadnik
aa6313c0e9 feat: useForeground if possible in stack header backButton 2019-09-01 16:56:00 +01:00
Michal Osadnik
2563924f53 chore: publish
- @react-navigation/stack@5.0.0-alpha.12
2019-09-01 15:43:59 +01:00
Michal Osadnik
55ec815247 fix: stack with gesture enabled 2019-09-01 12:46:55 +01:00
satyajit.happy
c7a79a62fa fix: defer running the animation to next frame 2019-09-01 12:43:03 +02:00
Michal Osadnik
5c6e85cec8 chore: publish
- @react-navigation/stack@5.0.0-alpha.11
2019-09-01 02:17:06 +01:00
Michal Osadnik
3f853d458f feat: optimizations in stack 2019-09-01 02:09:48 +01:00
satyajit.happy
06b3e6bda7 chore: publish
- @react-navigation/core@5.0.0-alpha.7
 - @react-navigation/example@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.7
2019-08-31 23:23:10 +02:00
satyajit.happy
3c840bbae3 fix: fix navigation object changing too often 2019-08-31 23:21:27 +02:00
satyajit.happy
d4ad9d48f9 refactor: make descriptor.options a getter 2019-08-31 23:10:30 +02:00
satyajit.happy
a0e9784d98 chore: publish
- @react-navigation/core@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.5
 - @react-navigation/native@5.0.0-alpha.6
2019-08-31 23:01:28 +02:00
satyajit.happy
eff0c0464f feat: support function in screenOptions 2019-08-31 23:00:27 +02:00
satyajit.happy
fe9ba2bf71 refactor: rename NativeContainer -> NavigationNativeContainer 2019-08-31 21:02:48 +02:00
Michał Osadnik
b0a3756d18 feat: add useRoute (#89) 2019-08-31 15:56:04 +01:00
satyajit.happy
4e0ebb05f9 chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.7
 - @react-navigation/drawer@5.0.0-alpha.8
 - @react-navigation/example@5.0.0-alpha.4
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.7
 - @react-navigation/material-top-tabs@5.0.0-alpha.7
 - @react-navigation/routers@5.0.0-alpha.7
 - @react-navigation/stack@5.0.0-alpha.10
2019-08-31 10:22:57 +02:00
satyajit.happy
bf0b408238 refactor: change signature of interpolation props 2019-08-31 10:21:36 +02:00
Satyajit Sahoo
1b2983eaa9 fix: handle route names change when all routes are removed (#86) 2019-08-30 19:15:54 +01:00
satyajit.happy
b38ee1c162 chore: publish
- @react-navigation/core@5.0.0-alpha.5
 - @react-navigation/drawer@5.0.0-alpha.7
 - @react-navigation/stack@5.0.0-alpha.9
2019-08-30 15:34:34 +02:00
satyajit.happy
fdc24d2523 fix: rename contentContainerStyle to sceneContainerStyle for drawer 2019-08-30 15:30:18 +02:00
satyajit.happy
6a8242c31a fix: properly set animated node on gestureEnabled change 2019-08-29 19:55:42 +02:00
satyajit.happy
3ad2e6ebcf fix: change interpolated style when idle to avoid messing up reanimated 2019-08-29 16:12:22 +02:00
satyajit.happy
5c8d183d68 chore: rename BaseActions -> CommonActions 2019-08-29 13:10:36 +02:00
Michal Osadnik
f1b976b68c chore: publish
- @react-navigation/bottom-tabs@5.0.0-alpha.6
 - @react-navigation/core@5.0.0-alpha.4
 - @react-navigation/drawer@5.0.0-alpha.6
 - @react-navigation/example@5.0.0-alpha.3
 - @react-navigation/material-bottom-tabs@5.0.0-alpha.6
 - @react-navigation/material-top-tabs@5.0.0-alpha.6
 - @react-navigation/native@5.0.0-alpha.5
 - @react-navigation/routers@5.0.0-alpha.6
 - @react-navigation/stack@5.0.0-alpha.8
2019-08-29 10:40:13 +01:00
Michał Osadnik
6b75cbaaa6 feat: handle navigating with both with both key and name (#83) 2019-08-29 09:46:51 +02:00
satyajit.happy
c951027ebb fix: handle both null and undefined in useScrollToTop 2019-08-29 08:33:42 +02:00
Janic Duplessis
cdbf1e97f9 feat: handle animated component wrappers in useScrollToTop (#81) 2019-08-28 23:13:00 +01:00
Janic Duplessis
56a2ee99f9 chore: add prettier config to package.json so it is picked up by editors (#82) 2019-08-28 23:12:29 +01:00
satyajit.happy
a9d4813b47 fix: allow making params optional. fixes #80 2019-08-28 22:05:48 +02:00
satyajit.happy
74c3377b63 chore: fix typo in example 2019-08-28 21:48:58 +02:00
satyajit.happy
8c1acc33c6 fix: fix gestures not working in stack 2019-08-28 21:41:15 +02:00
satyajit.happy
d72a96d1ef chore: add example for pop to top on tab press 2019-08-28 21:28:18 +02:00
satyajit.happy
f9e8c7e80f feat: handle more methods in useScrollToTop 2019-08-28 21:17:48 +02:00
satyajit.happy
9245c79990 feat: export NavigationContext 2019-08-28 15:52:48 +02:00
satyajit.happy
ea4c753d0a refactor: rename 'timing' option to 'animation' in transition spec 2019-08-28 15:48:17 +02:00
satyajit.happy
01196d7b48 docs: add JSDoc for transition configurations 2019-08-28 15:41:11 +02:00
Janic Duplessis
b4a5c3c35e fix: types path (#75) 2019-08-28 11:36:37 +01:00
58 changed files with 1709 additions and 576 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,68 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.7...@react-navigation/core@5.0.0-alpha.8) (2019-09-04)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.7) (2019-08-31)
### Bug Fixes
* fix navigation object changing too often ([3c840bb](https://github.com/react-navigation/navigation-ex/commit/3c840bb))
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.5...@react-navigation/core@5.0.0-alpha.6) (2019-08-31)
### Features
* add useRoute ([#89](https://github.com/react-navigation/navigation-ex/issues/89)) ([b0a3756](https://github.com/react-navigation/navigation-ex/commit/b0a3756))
* support function in screenOptions ([eff0c04](https://github.com/react-navigation/navigation-ex/commit/eff0c04))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.4...@react-navigation/core@5.0.0-alpha.5) (2019-08-30)
**Note:** Version bump only for package @react-navigation/core
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.3...@react-navigation/core@5.0.0-alpha.4) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
### Features
* export NavigationContext ([9245c79](https://github.com/react-navigation/navigation-ex/commit/9245c79))
* handle navigating with both with both key and name ([#83](https://github.com/react-navigation/navigation-ex/issues/83)) ([6b75cba](https://github.com/react-navigation/navigation-ex/commit/6b75cba))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.2...@react-navigation/core@5.0.0-alpha.3) (2019-08-27)

View File

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

View File

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

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
import { Route } from './types';
/**
* Context which holds the route prop for a screen.
*/
const NavigationContext = React.createContext<Route<string> | undefined>(
undefined
);
export default NavigationContext;

View File

@@ -1,6 +1,7 @@
import * as React from 'react';
import { NavigationStateContext } from './NavigationContainer';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import StaticContainer from './StaticContainer';
import EnsureSingleNavigator from './EnsureSingleNavigator';
import {
@@ -56,7 +57,7 @@ export default function SceneView<
),
});
},
[getState, route, setState]
[getState, route.key, setState]
);
const context = React.useMemo(
@@ -78,23 +79,25 @@ export default function SceneView<
return (
<NavigationContext.Provider value={navigation}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>
<StaticContainer
name={screen.name}
// @ts-ignore
render={screen.component || screen.children}
navigation={navigation}
route={route}
>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation, route })
) : null}
</StaticContainer>
</EnsureSingleNavigator>
</NavigationStateContext.Provider>
<NavigationRouteContext.Provider value={route}>
<NavigationStateContext.Provider value={context}>
<EnsureSingleNavigator>
<StaticContainer
name={screen.name}
// @ts-ignore
render={screen.component || screen.children}
navigation={navigation}
route={route}
>
{'component' in screen && screen.component !== undefined ? (
<screen.component navigation={navigation} route={route} />
) : 'children' in screen && screen.children !== undefined ? (
screen.children({ navigation, route })
) : null}
</StaticContainer>
</EnsureSingleNavigator>
</NavigationStateContext.Provider>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
}

View File

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

View File

@@ -46,11 +46,320 @@ it('sets options with options prop as an object', () => {
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets options with screenOptions prop as an object', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator screenOptions={{ title: 'Hello world' }}>
<Screen name="foo" component={TestScreenA} />
<Screen name="bar" component={TestScreenB} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
Hello world
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
Hello world
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets options with screenOptions prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
return (
<>
{state.routes.map(route => {
const { render, options } = descriptors[route.key];
return (
<main key={route.key}>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
})}
</>
);
};
const TestScreenA = (): any => 'Test screen A';
const TestScreenB = (): any => 'Test screen B';
const root = render(
<NavigationContainer>
<TestNavigator
screenOptions={({ route }: any) => ({
title: `${route.name}: ${route.params.author || route.params.fruit}`,
})}
>
<Screen
name="foo"
component={TestScreenA}
initialParams={{ author: 'Jane' }}
/>
<Screen
name="bar"
component={TestScreenB}
initialParams={{ fruit: 'Apple' }}
/>
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
Array [
<main>
<h1>
foo: Jane
</h1>
<div>
Test screen A
</div>
</main>,
<main>
<h1>
bar: Apple
</h1>
<div>
Test screen B
</div>
</main>,
]
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Hello world
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
@@ -295,180 +604,3 @@ it('returns true for canGoBack when parent router handles GO_BACK', () => {
expect(result).toBe(false);
});
it('sets options with options prop as a fuction', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{ title?: string },
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = (): any => 'Test screen';
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen
name="foo"
component={TestScreen}
options={({ route }: any) => ({ title: route.params.author })}
initialParams={{ author: 'Jane' }}
/>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1>
Jane
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('sets initial options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
{
title?: string;
color?: string;
},
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
});
return 'Test screen';
};
const root = render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello world
</h1>
<div>
Test screen
</div>
</main>
`);
});
it('updates options with setOptions', () => {
const TestNavigator = (props: any) => {
const { state, descriptors } = useNavigationBuilder<
NavigationState,
any,
any,
any
>(MockRouter, props);
const { render, options } = descriptors[state.routes[state.index].key];
return (
<main>
<h1 color={options.color}>{options.title}</h1>
<p>{options.description}</p>
<caption>{options.author}</caption>
<div>{render()}</div>
</main>
);
};
const TestScreen = ({ navigation }: any): any => {
navigation.setOptions({
title: 'Hello world',
description: 'Something here',
});
React.useEffect(() => {
const timer = setTimeout(() =>
navigation.setOptions({
title: 'Hello again',
author: 'Jane',
})
);
return () => clearTimeout(timer);
});
return 'Test screen';
};
const element = (
<NavigationContainer>
<TestNavigator>
<Screen name="foo" options={{ color: 'blue' }}>
{props => <TestScreen {...props} />}
</Screen>
<Screen name="bar" component={jest.fn()} />
</TestNavigator>
</NavigationContainer>
);
const root = render(element);
act(() => jest.runAllTimers());
root.update(element);
expect(root).toMatchInlineSnapshot(`
<main>
<h1
color="blue"
>
Hello again
</h1>
<p>
Something here
</p>
<caption>
Jane
</caption>
<div>
Test screen
</div>
</main>
`);
});

View File

@@ -0,0 +1,34 @@
import * as React from 'react';
import { render } from 'react-native-testing-library';
import useNavigationBuilder from '../useNavigationBuilder';
import useRoute from '../useRoute';
import NavigationContainer from '../NavigationContainer';
import Screen from '../Screen';
import MockRouter from './__fixtures__/MockRouter';
import { RouteProp } from '../types';
it('gets route prop from context', () => {
expect.assertions(1);
const TestNavigator = (props: any): any => {
const { state, descriptors } = useNavigationBuilder(MockRouter, props);
return state.routes.map(route => descriptors[route.key].render());
};
const Test = () => {
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
expect(route && route.params && route.params.x).toEqual(1);
return null;
};
render(
<NavigationContainer>
<TestNavigator>
<Screen name="foo" component={Test} initialParams={{ x: 1 }} />
</TestNavigator>
</NavigationContainer>
);
});

View File

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

View File

@@ -96,7 +96,12 @@ export type DefaultNavigatorOptions<
/**
* Default options for all screens under this navigator.
*/
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
};
export type RouterFactory<
@@ -274,8 +279,8 @@ type NavigationHelpersCommon<
* @param [params] Params object for the route.
*/
navigate<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -287,7 +292,7 @@ type NavigationHelpersCommon<
navigate<RouteName extends keyof ParamList>(
route:
| { key: string; params?: ParamList[RouteName] }
| { name: RouteName; params: ParamList[RouteName] }
| { name: RouteName; key?: string; params: ParamList[RouteName] }
): void;
/**
@@ -298,7 +303,7 @@ type NavigationHelpersCommon<
*/
replace<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends undefined
? [RouteName] | [RouteName, undefined]
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -534,11 +539,24 @@ export type TypedNavigator<
* Navigator component which manages the child screens.
*/
Navigator: React.ComponentType<
React.ComponentProps<Navigator> & {
Omit<
React.ComponentProps<Navigator>,
'initialRouteName' | 'screenOptions'
> & {
/**
* Route to focus on initial render.
* Name of the route to focus by on initial render.
* If not specified, usually the first route is used.
*/
initialRouteName?: keyof ParamList;
/**
* Default options for all screens under this navigator.
*/
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamList, keyof ParamList>;
navigation: any;
}) => ScreenOptions);
}
>;
/**

View File

@@ -13,6 +13,7 @@ import {
NavigationState,
ParamListBase,
RouteConfig,
RouteProp,
Router,
} from './types';
@@ -20,7 +21,12 @@ type Options<ScreenOptions extends object> = {
state: NavigationState;
screens: { [key: string]: RouteConfig<ParamListBase, string, ScreenOptions> };
navigation: NavigationHelpers<ParamListBase>;
screenOptions?: ScreenOptions;
screenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase, string>;
navigation: any;
}) => ScreenOptions);
onAction: (
action: NavigationAction,
visitedNavigators?: Set<string>
@@ -93,13 +99,15 @@ export default function useDescriptors<
return state.routes.reduce(
(acc, route) => {
const screen = screens[route.name];
const navigation = navigations[route.key];
acc[route.key] = {
navigation,
render() {
return (
<NavigationBuilderContext.Provider key={route.key} value={context}>
<SceneView
navigation={navigations[route.key]}
navigation={navigation}
route={route}
screen={screen}
getState={getState}
@@ -108,22 +116,30 @@ export default function useDescriptors<
</NavigationBuilderContext.Provider>
);
},
options: {
// The default `screenOptions` passed to the navigator
...screenOptions,
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation: navigations[route.key],
})),
// The options set via `navigation.setOptions`
...options[route.key],
get options() {
return {
// The default `screenOptions` passed to the navigator
...(typeof screenOptions === 'object' || screenOptions == null
? screenOptions
: screenOptions({
// @ts-ignore
route,
navigation,
})),
// The `options` prop passed to `Screen` elements
...(typeof screen.options === 'object' || screen.options == null
? screen.options
: screen.options({
// @ts-ignore
route,
navigation,
})),
// The options set via `navigation.setOptions`
...options[route.key],
};
},
navigation: navigations[route.key],
};
return acc;
},
{} as {

View File

@@ -0,0 +1,22 @@
import * as React from 'react';
import NavigationRouteContext from './NavigationRouteContext';
import { ParamListBase, RouteProp } from './types';
/**
* Hook to access the route prop of the parent screen anywhere.
*
* @returns Route prop of the parent screen.
*/
export default function useRoute<
T extends RouteProp<ParamListBase, string>
>(): T {
const route = React.useContext(NavigationRouteContext);
if (route === undefined) {
throw new Error(
"We couldn't find a route object. Is your component inside a navigator?"
);
}
return route as T;
}

View File

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

View File

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

View File

@@ -75,7 +75,7 @@ export type DrawerNavigationConfig = {
lazy: boolean;
/**
* Whether a screen should be unmounted when navigating away from it.
* Defaults to `false`..
* Defaults to `false`.
*/
unmountInactiveRoutes?: boolean;
/**
@@ -87,7 +87,10 @@ export type DrawerNavigationConfig = {
* Options for the content component which will be passed as props.
*/
contentOptions?: object;
contentContainerStyle?: StyleProp<ViewStyle>;
/**
* Style object for the component wrapping the screen content.
*/
sceneContainerStyle?: StyleProp<ViewStyle>;
style?: StyleProp<ViewStyle>;
};

View File

@@ -88,7 +88,7 @@ type Props = {
statusBarAnimation: 'slide' | 'none' | 'fade';
overlayStyle?: StyleProp<ViewStyle>;
drawerStyle?: StyleProp<ViewStyle>;
contentContainerStyle?: StyleProp<ViewStyle>;
sceneContainerStyle?: StyleProp<ViewStyle>;
renderDrawerContent: Renderer;
renderSceneContent: Renderer;
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
@@ -484,7 +484,7 @@ export default class DrawerView extends React.PureComponent<Props> {
drawerPosition,
drawerType,
swipeEdgeWidth,
contentContainerStyle,
sceneContainerStyle,
drawerStyle,
overlayStyle,
onGestureRef,
@@ -534,7 +534,7 @@ export default class DrawerView extends React.PureComponent<Props> {
{
transform: [{ translateX: contentTranslateX }],
},
contentContainerStyle as any,
sceneContainerStyle as any,
]}
>
{renderSceneContent({ progress: this.progress })}

View File

@@ -190,7 +190,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
drawerPosition,
drawerBackgroundColor,
overlayColor,
contentContainerStyle,
sceneContainerStyle,
edgeWidth,
minSwipeDistance,
hideStatusBar,
@@ -222,7 +222,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
gestureHandlerProps={gestureHandlerProps}
drawerType={drawerType}
drawerPosition={drawerPosition}
contentContainerStyle={contentContainerStyle}
sceneContainerStyle={sceneContainerStyle}
drawerStyle={{
backgroundColor: drawerBackgroundColor || 'white',
width: this.state.drawerWidth,

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.6](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.5](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.4...@react-navigation/example@5.0.0-alpha.5) (2019-08-31)
**Note:** Version bump only for package @react-navigation/example
# [5.0.0-alpha.4](https://github.com/satya164/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.3...@react-navigation/example@5.0.0-alpha.4) (2019-08-31)
### Bug Fixes
* handle route names change when all routes are removed ([#86](https://github.com/satya164/navigation-ex/issues/86)) ([1b2983e](https://github.com/satya164/navigation-ex/commit/1b2983e))
# [5.0.0-alpha.3](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.2...@react-navigation/example@5.0.0-alpha.3) (2019-08-29)
### Features
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.2](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/example@5.0.0-alpha.1...@react-navigation/example@5.0.0-alpha.2) (2019-08-27)

View File

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

View File

@@ -0,0 +1,128 @@
import * as React from 'react';
import { View, TextInput, ActivityIndicator, StyleSheet } from 'react-native';
import { Title, Button } from 'react-native-paper';
import { ParamListBase } from '@react-navigation/core';
import {
createStackNavigator,
HeaderBackButton,
StackNavigationProp,
} from '@react-navigation/stack';
type AuthStackParams = {
splash: undefined;
home: undefined;
'sign-in': undefined;
};
const SplashScreen = () => (
<View style={styles.content}>
<ActivityIndicator />
</View>
);
const SignInScreen = ({
setUserToken,
}: {
setUserToken: (token: string) => void;
}) => {
return (
<View style={styles.content}>
<TextInput placeholder="Username" style={styles.input} />
<TextInput placeholder="Password" secureTextEntry style={styles.input} />
<Button
mode="contained"
onPress={() => setUserToken('token')}
style={styles.button}
>
Sign in
</Button>
</View>
);
};
const HomeScreen = ({
setUserToken,
}: {
setUserToken: (token: undefined) => void;
}) => {
return (
<View style={styles.content}>
<Title style={styles.text}>Signed in successfully 🎉</Title>
<Button onPress={() => setUserToken(undefined)} style={styles.button}>
Sign out
</Button>
</View>
);
};
const SimpleStack = createStackNavigator<AuthStackParams>();
type Props = {
navigation: StackNavigationProp<ParamListBase>;
};
export default function SimpleStackScreen({ navigation }: Props) {
const [isLoading, setIsLoading] = React.useState(true);
const [userToken, setUserToken] = React.useState<string | undefined>(
undefined
);
React.useEffect(() => {
const timer = setTimeout(() => setIsLoading(false), 1000);
return () => clearTimeout(timer);
}, []);
navigation.setOptions({
header: null,
});
return (
<SimpleStack.Navigator
screenOptions={{
headerLeft: () => (
<HeaderBackButton onPress={() => navigation.goBack()} />
),
}}
>
{isLoading ? (
<SimpleStack.Screen
name="splash"
component={SplashScreen}
options={{ title: `Auth Flow` }}
/>
) : userToken === undefined ? (
<SimpleStack.Screen name="sign-in" options={{ title: `Sign in` }}>
{() => <SignInScreen setUserToken={setUserToken} />}
</SimpleStack.Screen>
) : (
<SimpleStack.Screen name="home" options={{ title: 'Home' }}>
{() => <HomeScreen setUserToken={setUserToken} />}
</SimpleStack.Screen>
)}
</SimpleStack.Navigator>
);
}
const styles = StyleSheet.create({
content: {
flex: 1,
padding: 16,
justifyContent: 'center',
},
input: {
margin: 8,
padding: 10,
backgroundColor: 'white',
borderRadius: 3,
borderWidth: StyleSheet.hairlineWidth,
borderColor: 'rgba(0, 0, 0, 0.08)',
},
button: {
margin: 8,
},
text: {
textAlign: 'center',
margin: 8,
},
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -8,7 +8,10 @@ import {
getStateFromPath,
NavigationContainerRef,
} from '@react-navigation/core';
import { useLinking, NativeContainer } from '@react-navigation/native';
import {
useLinking,
NavigationNativeContainer,
} from '@react-navigation/native';
import {
createDrawerNavigator,
DrawerNavigationProp,
@@ -23,6 +26,7 @@ import SimpleStackScreen from './Screens/SimpleStack';
import BottomTabsScreen from './Screens/BottomTabs';
import MaterialTopTabsScreen from './Screens/MaterialTopTabs';
import MaterialBottomTabs from './Screens/MaterialBottomTabs';
import AuthFlow from './Screens/AuthFlow';
YellowBox.ignoreWarnings(['Require cycle:', 'Warning: Async Storage']);
@@ -47,6 +51,10 @@ const SCREENS = {
title: 'Material Bottom Tabs',
component: MaterialBottomTabs,
},
'auth-flow': {
title: 'Auth Flow',
component: AuthFlow,
},
};
const Drawer = createDrawerNavigator<RootDrawerParamList>();
@@ -113,7 +121,7 @@ export default function App() {
}
return (
<NativeContainer
<NavigationNativeContainer
ref={containerRef}
initialState={initialState}
onStateChange={state =>
@@ -172,6 +180,6 @@ export default function App() {
)}
</Drawer.Screen>
</Drawer.Navigator>
</NativeContainer>
</NavigationNativeContainer>
);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -50,8 +50,8 @@ export type MaterialTopTabNavigationProp<
* @param [params] Params object for the route.
*/
jumpTo<RouteName extends Extract<keyof ParamList, string>>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
};
@@ -125,7 +125,7 @@ export type MaterialTopTabNavigationConfig = Partial<
*
* This view is usually only shown for a split second. Keep it lightweight.
*
* By default, this renders null..
* By default, this renders null.
*/
lazyPlaceholderComponent?: React.ComponentType<{ route: Route<string> }>;
/**

View File

@@ -3,6 +3,39 @@
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/native@5.0.0-alpha.5...@react-navigation/native@5.0.0-alpha.7) (2019-08-31)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.5...@react-navigation/native@5.0.0-alpha.6) (2019-08-31)
**Note:** Version bump only for package @react-navigation/native
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.4...@react-navigation/native@5.0.0-alpha.5) (2019-08-29)
### Bug Fixes
* handle both null and undefined in useScrollToTop ([c951027](https://github.com/react-navigation/navigation-ex/commit/c951027))
### Features
* handle animated component wrappers in `useScrollToTop` ([#81](https://github.com/react-navigation/navigation-ex/issues/81)) ([cdbf1e9](https://github.com/react-navigation/navigation-ex/commit/cdbf1e9))
* handle more methods in useScrollToTop ([f9e8c7e](https://github.com/react-navigation/navigation-ex/commit/f9e8c7e))
# [5.0.0-alpha.4](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.3...@react-navigation/native@5.0.0-alpha.4) (2019-08-28)

View File

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

View File

@@ -16,14 +16,14 @@ import useBackButton from './useBackButton';
* @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(
const NavigationNativeContainer = React.forwardRef(function NativeContainer(
props: NavigationContainerProps,
ref: React.Ref<NavigationContainerRef>
) {
const refContainer = React.useRef<NavigationContainerRef>(null);
useBackButton(refContainer);
React.useImperativeHandle(ref, () => refContainer.current);
return (
@@ -35,4 +35,4 @@ const NativeContainer = React.forwardRef(function NativeContainer(
);
});
export default NativeContainer;
export default NavigationNativeContainer;

View File

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

View File

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

View File

@@ -3,6 +3,28 @@
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/routers@5.0.0-alpha.6...@react-navigation/routers@5.0.0-alpha.7) (2019-08-31)
### Bug Fixes
* handle route names change when all routes are removed ([#86](https://github.com/react-navigation/navigation-ex/issues/86)) ([1b2983e](https://github.com/react-navigation/navigation-ex/commit/1b2983e))
# [5.0.0-alpha.6](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.5...@react-navigation/routers@5.0.0-alpha.6) (2019-08-29)
### Features
* handle navigating with both with both key and name ([#83](https://github.com/react-navigation/navigation-ex/issues/83)) ([6b75cba](https://github.com/react-navigation/navigation-ex/commit/6b75cba))
# [5.0.0-alpha.5](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.4...@react-navigation/routers@5.0.0-alpha.5) (2019-08-28)
**Note:** Version bump only for package @react-navigation/routers

View File

@@ -138,7 +138,7 @@ it('gets state on route names change', () => {
expect(
router.getStateForRouteNamesChange(
{
index: 0,
index: 2,
key: 'stack-test',
routeNames: ['bar', 'baz', 'qux'],
routes: [
@@ -157,7 +157,7 @@ it('gets state on route names change', () => {
}
)
).toEqual({
index: 0,
index: 1,
key: 'stack-test',
routeNames: ['qux', 'baz', 'foo', 'fiz'],
routes: [
@@ -166,6 +166,64 @@ it('gets state on route names change', () => {
],
stale: false,
});
expect(
router.getStateForRouteNamesChange(
{
index: 1,
key: 'stack-test',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo-test', name: 'foo' },
{ key: 'bar-test', name: 'bar' },
],
stale: false,
},
{
routeNames: ['baz', 'qux'],
routeParamList: {
baz: { name: 'John' },
},
}
)
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['baz', 'qux'],
routes: [{ key: 'baz-test', name: 'baz', params: { name: 'John' } }],
stale: false,
});
});
it('gets state on route names change with initialRouteName', () => {
const router = StackRouter({ initialRouteName: 'qux' });
expect(
router.getStateForRouteNamesChange(
{
index: 1,
key: 'stack-test',
routeNames: ['foo', 'bar'],
routes: [
{ key: 'foo-test', name: 'foo' },
{ key: 'bar-test', name: 'bar' },
],
stale: false,
},
{
routeNames: ['baz', 'qux'],
routeParamList: {
baz: { name: 'John' },
},
}
)
).toEqual({
index: 0,
key: 'stack-test',
routeNames: ['baz', 'qux'],
routes: [{ key: 'qux-test', name: 'qux' }],
stale: false,
});
});
it('handles navigate action', () => {
@@ -267,6 +325,51 @@ it('handles navigate action', () => {
CommonActions.navigate({ key: 'unknown' })
)
).toBe(null);
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
{
type: 'NAVIGATE',
payload: { key: 'baz-0', name: 'baz' },
}
)
).toEqual({
stale: false,
key: 'root',
index: 0,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }],
});
expect(
router.getStateForAction(
{
stale: false,
key: 'root',
index: 1,
routeNames: ['baz', 'bar', 'qux'],
routes: [{ key: 'baz-0', name: 'baz' }, { key: 'bar', name: 'bar' }],
},
CommonActions.navigate({ key: 'baz-1', name: 'baz' })
)
).toEqual({
stale: false,
key: 'root',
index: 2,
routeNames: ['baz', 'bar', 'qux'],
routes: [
{ key: 'baz-0', name: 'baz' },
{ key: 'bar', name: 'bar' },
{ key: 'baz-1', name: 'baz' },
],
});
});
it('handles go back action', () => {

View File

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

View File

@@ -11,7 +11,7 @@ import {
export type StackActionType =
| {
type: 'PUSH';
payload: { name: string; params?: object };
payload: { name: string; key?: string | undefined; params?: object };
source?: string;
target?: string;
}
@@ -114,11 +114,25 @@ export default function StackRouter(options: StackRouterOptions) {
};
},
getStateForRouteNamesChange(state, { routeNames }) {
getStateForRouteNamesChange(state, { routeNames, routeParamList }) {
const routes = state.routes.filter(route =>
routeNames.includes(route.name)
);
if (routes.length === 0) {
const initialRouteName =
options.initialRouteName !== undefined &&
routeNames.includes(options.initialRouteName)
? options.initialRouteName
: routeNames[0];
routes.push({
key: `${initialRouteName}-${shortid()}`,
name: initialRouteName,
params: routeParamList[initialRouteName],
});
}
return {
...state,
routeNames,
@@ -151,7 +165,10 @@ export default function StackRouter(options: StackRouterOptions) {
routes: [
...state.routes,
{
key: `${action.payload.name}-${shortid()}`,
key:
action.payload.key === undefined
? `${action.payload.name}-${shortid()}`
: action.payload.key,
name: action.payload.name,
params: action.payload.params,
},
@@ -199,14 +216,16 @@ export default function StackRouter(options: StackRouterOptions) {
let index = -1;
if (
state.routes[state.index].name === action.payload.name ||
(state.routes[state.index].name === action.payload.name &&
action.payload.key === undefined) ||
state.routes[state.index].key === action.payload.key
) {
index = state.index;
} else {
for (let i = state.routes.length - 1; i >= 0; i--) {
if (
state.routes[i].name === action.payload.name ||
(state.routes[i].name === action.payload.name &&
action.payload.key === undefined) ||
state.routes[i].key === action.payload.key
) {
index = i;
@@ -215,7 +234,11 @@ export default function StackRouter(options: StackRouterOptions) {
}
}
if (index === -1 && action.payload.key) {
if (
index === -1 &&
action.payload.key &&
action.payload.name === undefined
) {
return null;
}
@@ -223,6 +246,7 @@ export default function StackRouter(options: StackRouterOptions) {
return router.getStateForAction(state, {
type: 'PUSH',
payload: {
key: action.payload.key,
name: action.payload.name,
params: action.payload.params,
},

View File

@@ -3,6 +3,94 @@
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.15](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.14...@react-navigation/stack@5.0.0-alpha.15) (2019-09-04)
### Features
* add approximate android Q transition ([196cce0](https://github.com/react-navigation/navigation-ex/commit/196cce0))
# [5.0.0-alpha.14](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.13...@react-navigation/stack@5.0.0-alpha.14) (2019-09-03)
### Bug Fixes
* change order of attaching nodes in card exec ([167d58c](https://github.com/react-navigation/navigation-ex/commit/167d58c))
# [5.0.0-alpha.13](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.12...@react-navigation/stack@5.0.0-alpha.13) (2019-09-01)
### Features
* useForeground if possible in stack header backButton ([aa6313c](https://github.com/react-navigation/navigation-ex/commit/aa6313c))
# [5.0.0-alpha.12](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.11...@react-navigation/stack@5.0.0-alpha.12) (2019-09-01)
### Bug Fixes
* defer running the animation to next frame ([c7a79a6](https://github.com/react-navigation/navigation-ex/commit/c7a79a6))
* stack with gesture enabled ([55ec815](https://github.com/react-navigation/navigation-ex/commit/55ec815))
# [5.0.0-alpha.11](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.10...@react-navigation/stack@5.0.0-alpha.11) (2019-09-01)
### Features
* optimizations in stack ([3f853d4](https://github.com/react-navigation/navigation-ex/commit/3f853d4))
# [5.0.0-alpha.10](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.9...@react-navigation/stack@5.0.0-alpha.10) (2019-08-31)
**Note:** Version bump only for package @react-navigation/stack
# [5.0.0-alpha.9](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.8...@react-navigation/stack@5.0.0-alpha.9) (2019-08-30)
### Bug Fixes
* change interpolated style when idle to avoid messing up reanimated ([3ad2e6e](https://github.com/react-navigation/navigation-ex/commit/3ad2e6e))
* properly set animated node on gestureEnabled change ([6a8242c](https://github.com/react-navigation/navigation-ex/commit/6a8242c))
# [5.0.0-alpha.8](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.7...@react-navigation/stack@5.0.0-alpha.8) (2019-08-29)
### Bug Fixes
* allow making params optional. fixes [#80](https://github.com/react-navigation/navigation-ex/issues/80) ([a9d4813](https://github.com/react-navigation/navigation-ex/commit/a9d4813))
* fix gestures not working in stack ([8c1acc3](https://github.com/react-navigation/navigation-ex/commit/8c1acc3))
# [5.0.0-alpha.7](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.6...@react-navigation/stack@5.0.0-alpha.7) (2019-08-28)

View File

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

View File

@@ -9,15 +9,16 @@ const { cond, add, multiply, interpolate } = Animated;
* Standard iOS-style slide in from the right.
*/
export function forHorizontalIOS({
progress: { current, next },
current,
next,
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const translateFocused = interpolate(current, {
const translateFocused = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [I18nManager.isRTL ? -screen.width : screen.width, 0],
});
const translateUnfocused = next
? interpolate(next, {
? interpolate(next.progress, {
inputRange: [0, 1],
outputRange: [
0,
@@ -26,12 +27,12 @@ export function forHorizontalIOS({
})
: 0;
const overlayOpacity = interpolate(current, {
const overlayOpacity = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [0, 0.07],
});
const shadowOpacity = interpolate(current, {
const shadowOpacity = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [0, 0.3],
});
@@ -54,10 +55,10 @@ export function forHorizontalIOS({
* Standard iOS-style slide in from the bottom (used for modals).
*/
export function forVerticalIOS({
progress: { current },
current,
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
const translateY = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [screen.height, 0],
});
@@ -77,14 +78,15 @@ export function forVerticalIOS({
*/
export function forModalPresentationIOS({
index,
progress: { current, next },
current,
next,
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const topOffset = 10;
const statusBarHeight = getStatusBarHeight(screen.width > screen.height);
const aspectRatio = screen.height / screen.width;
const progress = add(current, next ? next : 0);
const progress = add(current.progress, next ? next.progress : 0);
const translateY = interpolate(progress, {
inputRange: [0, 1, 2],
@@ -129,19 +131,19 @@ export function forModalPresentationIOS({
* Standard Android-style fade in from the bottom for Android Oreo.
*/
export function forFadeFromBottomAndroid({
progress: { current },
current,
layouts: { screen },
closing,
}: CardInterpolationProps): CardInterpolatedStyle {
const translateY = interpolate(current, {
const translateY = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [multiply(screen.height, 0.08), 0],
});
const opacity = cond(
closing,
current,
interpolate(current, {
current.progress,
interpolate(current.progress, {
inputRange: [0, 0.5, 0.9, 1],
outputRange: [0, 0.25, 0.7, 1],
})
@@ -156,27 +158,28 @@ export function forFadeFromBottomAndroid({
}
/**
* Standard Android-style wipe from the bottom for Android Pie.
* Standard Android-style reveal from the bottom for Android Pie.
*/
export function forRevealFromBottomAndroid({
progress: { current, next },
current,
next,
layouts: { screen },
}: CardInterpolationProps): CardInterpolatedStyle {
const containerTranslateY = interpolate(current, {
const containerTranslateY = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [screen.height, 0],
});
const cardTranslateYFocused = interpolate(current, {
const cardTranslateYFocused = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [multiply(screen.height, 95.9 / 100, -1), 0],
});
const cardTranslateYUnfocused = next
? interpolate(next, {
? interpolate(next.progress, {
inputRange: [0, 1],
outputRange: [0, multiply(screen.height, 2 / 100, -1)],
})
: 0;
const overlayOpacity = interpolate(current, {
const overlayOpacity = interpolate(current.progress, {
inputRange: [0, 0.36, 1],
outputRange: [0, 0.1, 0.1],
});
@@ -195,3 +198,34 @@ export function forRevealFromBottomAndroid({
overlayStyle: { opacity: overlayOpacity },
};
}
/**
* Standard Android-style reveal from the bottom for Android Q.
* TODO: Update this with correct values when AOSP is updated.
*/
export function forScaleFromCenterAndroid({
current,
next,
}: CardInterpolationProps): CardInterpolatedStyle {
const cardOpacityFocused = interpolate(current.progress, {
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 1],
});
const cardScaleFocused = interpolate(current.progress, {
inputRange: [0, 1],
outputRange: [0.9, 1],
});
const cardScaleUnFocused = next
? interpolate(next.progress, {
inputRange: [0, 1],
outputRange: [1, 1.1],
})
: 1;
return {
containerStyle: {
opacity: cardOpacityFocused,
transform: [{ scale: cardScaleFocused }, { scale: cardScaleUnFocused }],
},
};
}

View File

@@ -4,8 +4,12 @@ import { HeaderInterpolationProps, HeaderInterpolatedStyle } from '../types';
const { interpolate, add } = Animated;
/**
* Standard UIKit style animation for the header where the title fades into the back button label.
*/
export function forUIKit({
progress: { current, next },
current,
next,
layouts,
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
const defaultOffset = 100;
@@ -27,7 +31,7 @@ export function forUIKit({
// The back title also animates in from this position
const rightOffset = layouts.screen.width / 4;
const progress = add(current, next ? next : 0);
const progress = add(current.progress, next ? next.progress : 0);
return {
leftButtonStyle: {
@@ -85,10 +89,14 @@ export function forUIKit({
};
}
/**
* Simple fade animation for the header elements.
*/
export function forFade({
progress: { current, next },
current,
next,
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
const progress = add(current, next ? next : 0);
const progress = add(current.progress, next ? next.progress : 0);
const opacity = interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: [0, 1, 0],
@@ -102,11 +110,15 @@ export function forFade({
};
}
/**
* Simple translate animation to translate the header along with the sliding screen.
*/
export function forStatic({
progress: { current, next },
current,
next,
layouts: { screen },
}: HeaderInterpolationProps): HeaderInterpolatedStyle {
const progress = add(current, next ? next : 0);
const progress = add(current.progress, next ? next.progress : 0);
const translateX = interpolate(progress, {
inputRange: [0, 1, 2],
outputRange: I18nManager.isRTL

View File

@@ -1,6 +1,7 @@
import {
forHorizontalIOS,
forVerticalIOS,
forScaleFromCenterAndroid,
forRevealFromBottomAndroid,
forFadeFromBottomAndroid,
forModalPresentationIOS,
@@ -8,6 +9,7 @@ import {
import { forNoAnimation, forFade } from './HeaderStyleInterpolators';
import {
TransitionIOSSpec,
ScaleFromCenterAndroidSpec,
RevealFromBottomAndroidSpec,
FadeOutToBottomAndroidSpec,
FadeInFromBottomAndroidSpec,
@@ -17,7 +19,9 @@ import { Platform } from 'react-native';
const ANDROID_VERSION_PIE = 28;
// Standard iOS navigation transition
/**
* Standard iOS navigation transition.
*/
export const SlideFromRightIOS: TransitionPreset = {
gestureDirection: 'horizontal',
transitionSpec: {
@@ -28,7 +32,9 @@ export const SlideFromRightIOS: TransitionPreset = {
headerStyleInterpolator: forFade,
};
// Standard iOS navigation transition for modals
/**
* Standard iOS navigation transition for modals.
*/
export const ModalSlideFromBottomIOS: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -39,7 +45,9 @@ export const ModalSlideFromBottomIOS: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard iOS modal presentation style (introduced in iOS 13)
/**
* Standard iOS modal presentation style (introduced in iOS 13).
*/
export const ModalPresentationIOS: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -50,7 +58,9 @@ export const ModalPresentationIOS: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard Android navigation transition when opening or closing an Activity on Android < 9
/**
* Standard Android navigation transition when opening or closing an Activity on Android < 9 (Oreo).
*/
export const FadeFromBottomAndroid: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -61,7 +71,9 @@ export const FadeFromBottomAndroid: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
// Standard Android navigation transition when opening or closing an Activity on Android >= 9
/**
* Standard Android navigation transition when opening or closing an Activity on Android 9 (Pie).
*/
export const RevealFromBottomAndroid: TransitionPreset = {
gestureDirection: 'vertical',
transitionSpec: {
@@ -72,6 +84,22 @@ export const RevealFromBottomAndroid: TransitionPreset = {
headerStyleInterpolator: forNoAnimation,
};
/**
* Standard Android navigation transition when opening or closing an Activity on Android 10 (Q).
*/
export const ScaleFromCenterAndroid: TransitionPreset = {
gestureDirection: 'horizontal',
transitionSpec: {
open: ScaleFromCenterAndroidSpec,
close: ScaleFromCenterAndroidSpec,
},
cardStyleInterpolator: forScaleFromCenterAndroid,
headerStyleInterpolator: forNoAnimation,
};
/**
* Default navigation transition for the current platform.
*/
export const DefaultTransition = Platform.select({
ios: SlideFromRightIOS,
default:
@@ -80,6 +108,9 @@ export const DefaultTransition = Platform.select({
: RevealFromBottomAndroid,
});
/**
* Default modal transition for the current platform.
*/
export const ModalTransition = Platform.select({
ios: ModalSlideFromBottomIOS,
default: DefaultTransition,

View File

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

View File

@@ -47,8 +47,8 @@ export type StackNavigationProp<
* @param [params] Params object for the route.
*/
push<RouteName extends keyof ParamList>(
...args: ParamList[RouteName] extends void
? [RouteName]
...args: ParamList[RouteName] extends (undefined | any)
? [RouteName] | [RouteName, ParamList[RouteName]]
: [RouteName, ParamList[RouteName]]
): void;
@@ -405,28 +405,33 @@ export type TimingConfig = {
};
export type TransitionSpec =
| { timing: 'spring'; config: SpringConfig }
| { timing: 'timing'; config: TimingConfig };
| { animation: 'spring'; config: SpringConfig }
| { animation: 'timing'; config: TimingConfig };
export type CardInterpolationProps = {
/**
* Values for the current screen.
*/
current: {
/**
* Animated node representing the progress value of the current screen.
*/
progress: Animated.Node<number>;
};
/**
* Values for the current screen the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: {
/**
* Animated node representing the progress value of the next screen.
*/
progress: Animated.Node<number>;
};
/**
* The index of the card in the stack.
*/
index: number;
/**
* Animated nodes representing the progress of the animation.
*/
progress: {
/**
* Progress value of the current screen.
*/
current: Animated.Node<number>;
/**
* Progress value for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: Animated.Node<number>;
};
/**
* Animated node representing whether the card is closing.
*/
@@ -467,18 +472,23 @@ export type CardStyleInterpolator = (
export type HeaderInterpolationProps = {
/**
* Animated nodes representing the progress of the animation.
* Values for the current screen (the screen which owns this header).
*/
progress: {
current: {
/**
* Progress value of the current screen (the screen which owns this header).
* Animated node representing the progress value of the current screen.
*/
current: Animated.Node<number>;
progress: Animated.Node<number>;
};
/**
* Values for the current screen the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
*/
next?: {
/**
* Progress value for the screen after this one in the stack.
* This can be `undefined` in case the screen animating is the last one.
* Animated node representing the progress value of the next screen.
*/
next?: Animated.Node<number>;
progress: Animated.Node<number>;
};
/**
* Layout measurements for various items we use for animation.

View File

@@ -121,10 +121,8 @@ export default class HeaderSegment extends React.Component<Props, State> {
leftLabelLayout: Layout | undefined
) =>
styleInterpolator({
progress: {
current,
next,
},
current: { progress: current },
next: next && { progress: next },
layouts: {
screen: layout,
title: titleLayout,

View File

@@ -13,7 +13,13 @@ import {
PanGestureHandler,
State as GestureState,
} from 'react-native-gesture-handler';
import { TransitionSpec, CardStyleInterpolator, Layout } from '../../types';
import {
TransitionSpec,
CardStyleInterpolator,
Layout,
SpringConfig,
TimingConfig,
} from '../../types';
import memoize from '../../utils/memoize';
import StackGestureContext from '../../utils/StackGestureContext';
import PointerEventsView from './PointerEventsView';
@@ -50,12 +56,31 @@ type Props = ViewProps & {
contentStyle?: StyleProp<ViewStyle>;
};
type AnimatedSpringConfig = {
damping: Animated.Value<number>;
mass: Animated.Value<number>;
stiffness: Animated.Value<number>;
restSpeedThreshold: Animated.Value<number>;
restDisplacementThreshold: Animated.Value<number>;
overshootClamping: Animated.Value<boolean>;
};
export type AnimatedTimingConfig = {
duration: Animated.Value<number>;
easing: Animated.EasingFunction;
};
type Binary = 0 | 1;
const TRUE = 1;
const TRUE_NODE = new Animated.Value(TRUE);
const FALSE = 0;
const NOOP = 0;
const FALSE_NODE = new Animated.Value(FALSE);
const NOOP_NODE = FALSE_NODE;
const UNSET = -1;
const UNSET_NODE = new Animated.Value(UNSET);
const MINUS_ONE_NODE = UNSET_NODE;
const DIRECTION_VERTICAL = -1;
const DIRECTION_HORIZONTAL = 1;
@@ -110,7 +135,7 @@ if (Animated.proc) {
damping: Animated.Adaptable<number>,
mass: Animated.Adaptable<number>,
stiffness: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<number>,
overshootClamping: Animated.Adaptable<boolean>,
restSpeedThreshold: Animated.Adaptable<number>,
restDisplacementThreshold: Animated.Adaptable<number>,
clock: Animated.Clock
@@ -174,6 +199,30 @@ if (Animated.proc) {
};
}
function transformSpringConfigToAnimatedValues(
config: SpringConfig
): AnimatedSpringConfig {
return {
damping: new Animated.Value(config.damping),
stiffness: new Animated.Value(config.stiffness),
mass: new Animated.Value(config.mass),
restDisplacementThreshold: new Animated.Value(
config.restDisplacementThreshold
),
restSpeedThreshold: new Animated.Value(config.restSpeedThreshold),
overshootClamping: new Animated.Value(config.overshootClamping),
};
}
function transformTimingConfigToAnimatedValues(
config: TimingConfig
): AnimatedTimingConfig {
return {
duration: new Animated.Value(config.duration),
easing: config.easing,
};
}
export default class Card extends React.Component<Props> {
static defaultProps = {
overlayEnabled: Platform.OS !== 'ios',
@@ -202,7 +251,13 @@ export default class Card extends React.Component<Props> {
}
if (closing !== prevProps.closing) {
this.isClosing.setValue(closing ? TRUE : FALSE);
// If the style updates during render, setting the value here doesn't work
// We need to defer it a bit so the animation starts properly
requestAnimationFrame(() =>
requestAnimationFrame(() =>
this.isClosing.setValue(closing ? TRUE : FALSE)
)
);
}
}
@@ -240,14 +295,34 @@ export default class Card extends React.Component<Props> {
height: new Value(this.props.layout.height),
};
openingSpecConfig =
this.props.transitionSpec.open.animation === 'timing'
? transformTimingConfigToAnimatedValues(
this.props.transitionSpec.open.config
)
: transformSpringConfigToAnimatedValues(
this.props.transitionSpec.open.config
);
closingSpecConfig =
this.props.transitionSpec.close.animation === 'timing'
? transformTimingConfigToAnimatedValues(
this.props.transitionSpec.close.config
)
: transformSpringConfigToAnimatedValues(
this.props.transitionSpec.close.config
);
private distance = cond(
eq(this.direction, DIRECTION_VERTICAL),
this.layout.height,
this.layout.width
);
private gestureUntraversed = new Value(0);
private gesture = new Value(0);
private offset = new Value(0);
private velocityUntraversed = new Value(0);
private velocity = new Value(0);
private gestureState = new Value(0);
@@ -267,11 +342,22 @@ export default class Card extends React.Component<Props> {
finished: new Value(FALSE),
};
private handleTransitionEnd = () => {
this.isRunningAnimation = false;
this.interpolatedStyle = this.getInterpolatedStyle(
this.props.styleInterpolator,
this.props.index,
this.props.current,
this.props.next,
this.props.layout
);
};
private runTransition = (isVisible: Binary | Animated.Node<number>) => {
const { open: openingSpec, close: closingSpec } = this.props.transitionSpec;
return cond(eq(this.props.current, isVisible), NOOP, [
cond(clockRunning(this.clock), NOOP, [
return cond(eq(this.props.current, isVisible), NOOP_NODE, [
cond(clockRunning(this.clock), NOOP_NODE, [
// Animation wasn't running before
// Set the initial values and start the clock
set(this.toValue, isVisible),
@@ -280,13 +366,17 @@ export default class Card extends React.Component<Props> {
set(
this.transitionVelocity,
multiply(
cond(this.distance, divide(this.velocity, this.distance), 0),
cond(
this.distance,
divide(this.velocity, this.distance),
FALSE_NODE
),
-1
)
),
set(this.frameTime, 0),
set(this.transitionState.time, 0),
set(this.transitionState.finished, FALSE),
set(this.frameTime, FALSE_NODE),
set(this.transitionState.time, FALSE_NODE),
set(this.transitionState.finished, FALSE_NODE),
set(this.isVisible, isVisible),
startClock(this.clock),
call([this.isVisible], ([value]: ReadonlyArray<Binary>) => {
@@ -297,41 +387,57 @@ export default class Card extends React.Component<Props> {
}),
]),
cond(
eq(isVisible, 1),
openingSpec.timing === 'spring'
eq(isVisible, TRUE_NODE),
openingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...openingSpec.config, toValue: this.toValue }
// @ts-ignore
{
...(this.openingSpecConfig as AnimatedSpringConfig),
toValue: this.toValue,
}
)
: timing(
this.clock,
{ ...this.transitionState, frameTime: this.frameTime },
{ ...openingSpec.config, toValue: this.toValue }
{
...(this.openingSpecConfig as AnimatedTimingConfig),
toValue: this.toValue,
}
),
closingSpec.timing === 'spring'
closingSpec.animation === 'spring'
? memoizedSpring(
this.clock,
{ ...this.transitionState, velocity: this.transitionVelocity },
{ ...closingSpec.config, toValue: this.toValue }
// @ts-ignore
{
...(this.closingSpecConfig as AnimatedSpringConfig),
toValue: this.toValue,
}
)
: timing(
this.clock,
{ ...this.transitionState, frameTime: this.frameTime },
{ ...closingSpec.config, toValue: this.toValue }
{
...(this.closingSpecConfig as AnimatedTimingConfig),
toValue: this.toValue,
}
)
),
cond(this.transitionState.finished, [
// Reset values
set(this.isSwipeGesture, FALSE),
set(this.gesture, 0),
set(this.velocity, 0),
set(this.isSwipeGesture, FALSE_NODE),
set(this.gesture, FALSE_NODE),
set(this.velocity, FALSE_NODE),
// When the animation finishes, stop the clock
stopClock(this.clock),
call([this.isVisible], ([value]: ReadonlyArray<Binary>) => {
const isOpen = Boolean(value);
const { onOpen, onClose } = this.props;
this.isRunningAnimation = false;
this.handleTransitionEnd();
if (isOpen) {
onOpen(true);
} else {
@@ -347,34 +453,58 @@ export default class Card extends React.Component<Props> {
multiply(this.velocity, SWIPE_VELOCITY_IMPACT)
);
private exec = block([
private exec = [
set(
this.gesture,
multiply(
this.gestureUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
)
),
set(
this.velocity,
multiply(
this.velocityUntraversed,
I18nManager.isRTL ? MINUS_ONE_NODE : TRUE_NODE
)
),
onChange(
this.isClosing,
cond(this.isClosing, set(this.nextIsVisible, FALSE))
cond(this.isClosing, set(this.nextIsVisible, FALSE_NODE))
),
onChange(
this.nextIsVisible,
cond(neq(this.nextIsVisible, UNSET), [
cond(neq(this.nextIsVisible, UNSET_NODE), [
// Stop any running animations
cond(clockRunning(this.clock), [
call([], () => (this.isRunningAnimation = false)),
call([], this.handleTransitionEnd),
stopClock(this.clock),
]),
set(this.gesture, 0),
set(this.gesture, FALSE_NODE),
// Update the index to trigger the transition
set(this.isVisible, this.nextIsVisible),
set(this.nextIsVisible, UNSET),
set(this.nextIsVisible, UNSET_NODE),
])
),
];
private changeVisiblityExec = onChange(
this.isVisible,
call([this.isVisible], ([isVisible]) => (this.isVisibleValue = isVisible))
);
private execNoGesture = block([
...this.exec,
this.runTransition(this.isVisible),
onChange(
this.isVisible,
call([this.isVisible], ([isVisible]) => (this.isVisibleValue = isVisible))
),
this.changeVisiblityExec,
]);
private execNoGesture = this.runTransition(this.isVisible);
private execWithGesture = block([
...this.exec,
onChange(
this.isSwiping,
call(
@@ -401,11 +531,11 @@ export default class Card extends React.Component<Props> {
cond(
eq(this.gestureState, GestureState.ACTIVE),
[
cond(this.isSwiping, NOOP, [
cond(this.isSwiping, NOOP_NODE, [
// We weren't dragging before, set it to true
set(this.isSwipeCancelled, FALSE),
set(this.isSwiping, TRUE),
set(this.isSwipeGesture, TRUE),
set(this.isSwipeCancelled, FALSE_NODE),
set(this.isSwiping, TRUE_NODE),
set(this.isSwipeGesture, TRUE_NODE),
// Also update the drag offset to the last position
set(this.offset, this.props.current),
]),
@@ -416,11 +546,15 @@ export default class Card extends React.Component<Props> {
max(
sub(
this.offset,
cond(this.distance, divide(this.gesture, this.distance), 1)
cond(
this.distance,
divide(this.gesture, this.distance),
TRUE_NODE
)
),
0
FALSE_NODE
),
1
TRUE_NODE
)
),
// Stop animations while we're dragging
@@ -443,7 +577,7 @@ export default class Card extends React.Component<Props> {
this.isSwipeCancelled,
eq(this.gestureState, GestureState.CANCELLED)
),
set(this.isSwiping, FALSE),
set(this.isSwiping, FALSE_NODE),
this.runTransition(
cond(
greaterThan(
@@ -452,26 +586,29 @@ export default class Card extends React.Component<Props> {
),
cond(
lessThan(
cond(eq(this.velocity, 0), this.gesture, this.velocity),
0
cond(
eq(this.velocity, FALSE_NODE),
this.gesture,
this.velocity
),
FALSE_NODE
),
TRUE,
FALSE
TRUE_NODE,
FALSE_NODE
),
this.isVisible
)
),
]
),
this.changeVisiblityExec,
]);
private handleGestureEventHorizontal = Animated.event([
{
nativeEvent: {
translationX: (x: Animated.Adaptable<number>) =>
set(this.gesture, multiply(x, I18nManager.isRTL ? -1 : 1)),
velocityX: (x: Animated.Adaptable<number>) =>
set(this.velocity, multiply(x, I18nManager.isRTL ? -1 : 1)),
translationX: this.gestureUntraversed,
velocityX: this.velocityUntraversed,
state: this.gestureState,
},
},
@@ -500,10 +637,8 @@ export default class Card extends React.Component<Props> {
) =>
styleInterpolator({
index,
progress: {
current,
next,
},
current: { progress: current },
next: next && { progress: next },
closing: this.isClosing,
layouts: {
screen: layout,
@@ -511,6 +646,18 @@ export default class Card extends React.Component<Props> {
})
);
// Keep track of the style in a property to avoid changing the animated node when deps change
// The style shouldn't change in the middle of the animation and should refer to what was there at the start of it
// Which will be the last value when just before the render which started the animation
// We need to make sure to update this when the running animation ends
private interpolatedStyle = this.getInterpolatedStyle(
this.props.styleInterpolator,
this.props.index,
this.props.current,
this.props.next,
this.props.layout
);
private gestureActivationCriteria() {
const { layout, gestureDirection, gestureResponseDistance } = this.props;
@@ -551,35 +698,39 @@ export default class Card extends React.Component<Props> {
render() {
const {
index,
active,
transparent,
layout,
styleInterpolator,
index,
current,
next,
layout,
overlayEnabled,
shadowEnabled,
gestureEnabled,
gestureDirection,
children,
styleInterpolator,
containerStyle: customContainerStyle,
contentStyle,
...rest
} = this.props;
if (!this.isRunningAnimation) {
this.interpolatedStyle = this.getInterpolatedStyle(
styleInterpolator,
index,
current,
next,
layout
);
}
const {
containerStyle,
cardStyle,
overlayStyle,
shadowStyle,
} = this.getInterpolatedStyle(
styleInterpolator,
index,
current,
next,
layout
);
} = this.interpolatedStyle;
const handleGestureEvent = gestureEnabled
? gestureDirection === 'vertical'
@@ -590,12 +741,10 @@ export default class Card extends React.Component<Props> {
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} />
)}
<Animated.Code
key={gestureEnabled ? 'gesture-code' : 'no-gesture-code'}
exec={gestureEnabled ? this.execWithGesture : this.execNoGesture}
/>
{overlayEnabled && overlayStyle ? (
<Animated.View
pointerEvents="none"

View File

@@ -167,11 +167,14 @@ export default class Stack extends React.Component<Props, State> {
: undefined;
const next = nextRoute ? progress[nextRoute.key] : undefined;
const oldScene = state.scenes[index];
const scene = {
route,
previous: previousRoute,
descriptor:
props.descriptors[route.key] || state.descriptors[route.key],
props.descriptors[route.key] ||
state.descriptors[route.key] ||
(oldScene ? oldScene.descriptor : { options: {} }),
progress: {
current,
next,
@@ -179,8 +182,6 @@ export default class Stack extends React.Component<Props, State> {
},
};
const oldScene = state.scenes[index];
if (
oldScene &&
scene.route === oldScene.route &&

View File

@@ -52,6 +52,7 @@ export default class TouchableItem extends React.Component<Props> {
return (
<TouchableNativeFeedback
{...rest}
useForeground={TouchableNativeFeedback.canUseNativeForeground()}
background={TouchableNativeFeedback.Ripple(pressColor, borderless)}
>
<View style={style}>{React.Children.only(children)}</View>