Compare commits
45 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da9948bfd8 | ||
|
|
d3f5c55dbf | ||
|
|
f91d16cd5d | ||
|
|
fbadea46f1 | ||
|
|
5614a7cd31 | ||
|
|
d8b88bd83f | ||
|
|
d6d06d07d9 | ||
|
|
65ce20ecbc | ||
|
|
12d90833eb | ||
|
|
edeb2e8ad9 | ||
|
|
133b59cd17 | ||
|
|
a9e584c3b7 | ||
|
|
c46e0a9c14 | ||
|
|
418a858f23 | ||
|
|
b201fd2071 | ||
|
|
c514542305 | ||
|
|
dcc5f99ecd | ||
|
|
adbeb292f5 | ||
|
|
543679f185 | ||
|
|
cbe240eae6 | ||
|
|
7f963a74bb | ||
|
|
572beae41b | ||
|
|
15fe3ebb51 | ||
|
|
31565d5576 | ||
|
|
2c31d1705c | ||
|
|
8f9a250958 | ||
|
|
87d28ca430 | ||
|
|
fa4411a14d | ||
|
|
77b757091c | ||
|
|
6b9b999c5b | ||
|
|
8c5f84094f | ||
|
|
21709f7674 | ||
|
|
22742e4a7f | ||
|
|
3cd1aedcf4 | ||
|
|
797d3a798e | ||
|
|
59803f54d6 | ||
|
|
935659899f | ||
|
|
ef0f5d6567 | ||
|
|
499f66dba4 | ||
|
|
2ef2f1a86f | ||
|
|
0252bdc222 | ||
|
|
282f62c258 | ||
|
|
f462d67270 | ||
|
|
873afec9fe | ||
|
|
878297e52f |
@@ -49,6 +49,7 @@ jobs:
|
||||
at: ~/project
|
||||
- run: |
|
||||
yarn lerna run prepare
|
||||
node scripts/check-types-path.js
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
70
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
|
||||
name: Detox (iOS)
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macOS-latest
|
||||
timeout-minutes: 15
|
||||
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_11.2.app
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
|
||||
- name: Use Node.js 10
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12
|
||||
|
||||
- name: Get Yarn cache
|
||||
id: yarn-cache
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
|
||||
- name: Cache Yarn packages
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: ${{ steps.yarn-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: ${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.yarn-cache.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@v1
|
||||
with:
|
||||
path: example/ios/Pods
|
||||
key: pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
|
||||
- name: Update Pods
|
||||
run: |
|
||||
gem update cocoapods xcodeproj
|
||||
cd example/ios
|
||||
pod install
|
||||
|
||||
- name: Configure dependencies
|
||||
run: |
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
yarn global add detox-cli
|
||||
|
||||
- name: Cache Detox build
|
||||
uses: actions/cache@v1
|
||||
id: detox-cache
|
||||
with:
|
||||
path: example/ios/build
|
||||
key: detox-build-${{ hashFiles('**/yarn.lock') }}-${{ hashFiles('**/Podfile.lock') }}
|
||||
|
||||
- name: Build Detox
|
||||
if: steps.detox-cache.outputs.cache-hit != 'true'
|
||||
run: |
|
||||
cd example
|
||||
detox build --configuration ios.sim.release
|
||||
|
||||
- name: Run Detox tests
|
||||
run: |
|
||||
cd example
|
||||
detox test --configuration ios.sim.release --cleanup --debug-synchronization 200
|
||||
513
README.md
@@ -1,496 +1,12 @@
|
||||
# Rethinking Navigation
|
||||
# React Navigation 5
|
||||
|
||||
[![Build Status][build-badge]][build]
|
||||
[![Code Coverage][coverage-badge]][coverage]
|
||||
[![MIT License][license-badge]][license]
|
||||
|
||||
An exploration of a component-first API for React Navigation for building more dynamic navigation solutions.
|
||||
Routing and navigation for your React Native apps with a component-first API.
|
||||
|
||||
## Considerations
|
||||
|
||||
- Should play well with static type system
|
||||
- Navigation state should be contained in root component (helpful for stuff such as deep linking)
|
||||
- Component-first API
|
||||
|
||||
## Building blocks
|
||||
|
||||
### `NavigationContainer`
|
||||
|
||||
Component which wraps the whole app. It stores the state for the whole navigation tree.
|
||||
|
||||
### `useNavigationBuilder`
|
||||
|
||||
Hook which can access the navigation state from the context. Along with the state, it also provides some helpers to modify the navigation state provided by the router. All state changes are notified to the parent `NavigationContainer`.
|
||||
|
||||
### Router
|
||||
|
||||
The router object provides various helper methods to deal with the state and actions, a reducer to update the state as well as some action creators.
|
||||
|
||||
The router is responsible for handling actions dispatched by calling methods on the `navigation` object. If the router cannot handle an action, it can return `null`, which would propagate the action to other routers until it's handled.
|
||||
|
||||
### Navigator
|
||||
|
||||
Navigators bundle a router and a view which takes the navigation state and decides how to render it.
|
||||
|
||||
A simple navigator could look like this:
|
||||
|
||||
```js
|
||||
import { createNavigatorFactory } from '@react-navigation/native';
|
||||
|
||||
function StackNavigator({ initialRouteName, children, ...rest }) {
|
||||
// The `navigation` object contains the navigation state and some helpers (e.g. push, pop)
|
||||
// The `descriptors` object contains the screen options and a helper for rendering a screen
|
||||
const { state, navigation, descriptors } = useNavigationBuilder(StackRouter, {
|
||||
initialRouteName,
|
||||
children,
|
||||
});
|
||||
|
||||
return (
|
||||
// The view determines how to animate any state changes
|
||||
<StackView
|
||||
state={state}
|
||||
navigation={navigation}
|
||||
descriptors={descriptors}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default createNavigatorFactory(StackNavigator);
|
||||
```
|
||||
|
||||
The navigator can render a screen by calling `descriptors[route.key].render()`. Internally, the descriptor adds appropriate wrappers to handle nested state.
|
||||
|
||||
## Architectural differences
|
||||
|
||||
### Shape of the navigation state
|
||||
|
||||
The shape of the navigation state looks very similar to the current implementation. There are few important differences:
|
||||
|
||||
- The name of the route is in the `route.name` property instead of `route.routeName`.
|
||||
- The state of the child navigator exists on a separate property `route.state`.
|
||||
- The state object contains a `routeNames` property which contains the list of defined route names in an array of strings,
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
{
|
||||
index: 0,
|
||||
key: 'stack-ytwk65',
|
||||
routeNames: ['home', 'profile', 'settings'],
|
||||
routes: [
|
||||
{
|
||||
key: 'home-hjds3b',
|
||||
name: 'home',
|
||||
state: {
|
||||
index: 1,
|
||||
key: 'tab-jhsf6g',
|
||||
routeNames: ['feed', 'recommended'],
|
||||
routes: [
|
||||
{
|
||||
key: 'feed-jv2iud',
|
||||
name: 'feed',
|
||||
},
|
||||
{
|
||||
key: 'recommended-njdh63',
|
||||
name: 'recommended',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
### Deriving initial state
|
||||
|
||||
In the current implementation of React Navigation, the initial state is extracted from the navigator definitions. This is possible because they are defined statically. In our case, it's not possible because the screens are rendered dynamically.
|
||||
|
||||
Turns out we don't really need the initial state in the `NavigationContainer`. This state is the default state, so we can store `undefined` instead, and let the navigators initialize their initial state themselves. Next time an action modifies the state, we update the value in the container.
|
||||
|
||||
If an initial state is specified, e.g. as a result of `Linking.getInitialURL()`, the child navigators will use that state, instead of having to initialize it themselves.
|
||||
|
||||
### Passing state to child navigator
|
||||
|
||||
Navigation state is exposed to children navigators via React context instead of having to pass it down manually. This lets the user nest navigators freely without having to worry about properly passing the state down.
|
||||
|
||||
### Accessing state of other navigators
|
||||
|
||||
Navigators should not access the state of other navigators. It might be tempting to access the state of a child route to perform some checks, but it's not going to work correctly, as the state object may not exist yet.
|
||||
|
||||
Instead of direct state access, navigators should communicate via events. Each navigator should access and modify its own state only.
|
||||
|
||||
### Bubbling of actions
|
||||
|
||||
In the current implementation of React Navigation, routers manually call the child routers to apply any actions. Since we have a component based architecture, this is not really possible.
|
||||
|
||||
Instead, we use an event based system. Child navigators can add listeners to handle actions. If the parent couldn't handle the action, it'll call the listeners. The event system is built into the core and the routers don't need to worry about it.
|
||||
|
||||
When an action can be bubble, the `getStateForAction` method from a router should return `null`, otherwise it should return the state object.
|
||||
|
||||
It's also possible to disable bubbling of actions when dispatching them by adding a `target` key in the action. The `target` key should refer to the key of the navigator that should handle the action.
|
||||
|
||||
## Basic usage
|
||||
|
||||
```js
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
|
||||
const Stack = createStackNavigator();
|
||||
const Tab = createBottomTabNavigator();
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<NavigationContainer>
|
||||
<Stack.Navigator initialRouteName="home">
|
||||
<Stack.Screen name="settings" component={Settings} />
|
||||
<Stack.Screen
|
||||
name="profile"
|
||||
component={Profile}
|
||||
options={{ title: 'John Doe' }}
|
||||
/>
|
||||
<Stack.Screen name="home">
|
||||
{() => (
|
||||
<Tab.Navigator initialRouteName="feed">
|
||||
<Tab.Screen name="feed" component={Feed} />
|
||||
<Tab.Screen name="article" component={Article} />
|
||||
<Tab.Screen name="notifications">
|
||||
{props => <Notifications {...props} />}
|
||||
</Tab.Screen>
|
||||
</Tab.Navigator>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
</Stack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
Navigators need to have `Screen` components as their direct children. These components don't do anything by themselves, but the navigator can extract information from these and determine what to render. Implementation-wise, we use `React.Children` API for this purpose.
|
||||
|
||||
The content to render can be specified in 2 ways:
|
||||
|
||||
1. React component in `component` prop (recommended)
|
||||
2. Render callback as children
|
||||
|
||||
When a React component is specified, the navigator takes care of adding a `React.memo` to prevent unnecessary re-renders. However, it's not possible to pass extra props to the component this way. It's preferable to use the context API for such cases instead of props.
|
||||
|
||||
A render callback which doesn't have such limitation and is easier to use for this purpose. However, performance optimization for the component is left to the user in such case.
|
||||
|
||||
The rendered component will receives a `navigation` prop with various helpers and a `route` prop which represents the route being rendered.
|
||||
|
||||
## Setting screen options
|
||||
|
||||
In React Navigation, screen options can be specified in a static property on the component (`navigationOptions`). This poses few issues:
|
||||
|
||||
- It's not possible to configure options based on props, state or context
|
||||
- To update the props based on an action in the component (such as button press), we need to do it in a hacky way by changing params
|
||||
- It breaks when used with HOCs which don't hoist static props, which is a common source of confusion
|
||||
|
||||
Instead of a static property, we expose a method to configure screen options:
|
||||
|
||||
```js
|
||||
function Selection({ navigation }) {
|
||||
const [selectedIds, setSelectedIds] = React.useState([]);
|
||||
|
||||
navigation.setOptions({
|
||||
title: `${selectedIds.length} items selected`,
|
||||
});
|
||||
|
||||
return <SelectionList onSelect={id => setSelectedIds(ids => [...ids, id])} />;
|
||||
}
|
||||
```
|
||||
|
||||
This allows options to be changed based on props, state or context, and doesn't have the disadvantages of static configuration.
|
||||
|
||||
## Navigation events
|
||||
|
||||
Screens can add listeners on the `navigation` prop like in React Navigation. By default, `focus` and `blur` events are fired when focused screen changes:
|
||||
|
||||
```js
|
||||
function Profile({ navigation }) {
|
||||
React.useEffect(() =>
|
||||
navigation.addListener('focus', () => {
|
||||
// do something
|
||||
})
|
||||
);
|
||||
|
||||
return <ProfileContent />;
|
||||
}
|
||||
```
|
||||
|
||||
The `navigation.addListener` method returns a function to remove the listener which can be returned as the cleanup function in an effect.
|
||||
|
||||
Navigators can also emit custom events using the `emit` method in the `navigation` object passed:
|
||||
|
||||
```js
|
||||
navigation.emit({
|
||||
type: 'transitionStart',
|
||||
data: { blurring: false },
|
||||
target: route.key,
|
||||
});
|
||||
```
|
||||
|
||||
The `data` is available under the `data` property in the `event` object, i.e. `event.data`.
|
||||
|
||||
The `target` property determines the screen that will receive the event. If the `target` property is omitted, the event is dispatched to all screens in the navigator.
|
||||
|
||||
Screens cannot emit events as there is no `emit` method on a screen's `navigation` prop.
|
||||
|
||||
If you don't need to get notified of focus change, but just need to check if the screen is currently focused in a callback, you can use the `navigation.isFocused()` method which returns a boolean. Note that it's not safe to use this in `render`. Only use it in callbacks, event listeners etc.
|
||||
|
||||
## Additional utilities
|
||||
|
||||
### Access navigation anywhere
|
||||
|
||||
Passing the `navigation` prop down can be tedious. The library exports a `useNavigation` hook which can access the `navigation` prop from the parent screen:
|
||||
|
||||
```js
|
||||
const navigation = useNavigation();
|
||||
```
|
||||
|
||||
### Side-effects in focused screen
|
||||
|
||||
Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc. While this can be achieved using `focus` and `blur` events, it's not very ergonomic.
|
||||
|
||||
To make this easier, the library exports a `useFocusEffect` hook:
|
||||
|
||||
```js
|
||||
import { useFocusEffect } from '@react-navigation/native';
|
||||
|
||||
function Profile({ userId }) {
|
||||
const [user, setUser] = React.useState(null);
|
||||
|
||||
const fetchUser = React.useCallback(() => {
|
||||
const request = API.fetchUser(userId).then(
|
||||
data => setUser(data),
|
||||
error => alert(error.message)
|
||||
);
|
||||
|
||||
return () => request.abort();
|
||||
}, [userId]);
|
||||
|
||||
useFocusEffect(fetchUser);
|
||||
|
||||
return <ProfileContent user={user} />;
|
||||
}
|
||||
```
|
||||
|
||||
The `useFocusEffect` is analogous to React's `useEffect` hook. The only difference is that it runs on focus instead of render.
|
||||
|
||||
**NOTE:** To avoid the running the effect too often, it's important to wrap the callback in `useCallback` before passing it to `useFocusEffect` as shown in the example.
|
||||
|
||||
### Render based on focus state
|
||||
|
||||
We might want to render different content based on the current focus state of the screen. The library exports a `useIsFocused` hook to make this easier:
|
||||
|
||||
```js
|
||||
import { useIsFocused } from '@react-navigation/native';
|
||||
|
||||
// ...
|
||||
|
||||
const isFocused = useIsFocused();
|
||||
```
|
||||
|
||||
## React Native
|
||||
|
||||
For proper UX in React Native, we need to respect platform behavior such as the device back button on Android, deep linking etc. The library exports few hooks to make it easier.
|
||||
|
||||
### Back button integration
|
||||
|
||||
When the back button on the device is pressed, we also want to navigate back in the focused navigator. The library exports a `useBackButton` hook to handle this:
|
||||
|
||||
```js
|
||||
import { useBackButton } from '@react-navigation/native';
|
||||
|
||||
// ...
|
||||
|
||||
const ref = React.useRef();
|
||||
|
||||
useBackButton(ref);
|
||||
|
||||
return <NavigationContainer ref={ref}>{/* content */}</NavigationContainer>;
|
||||
```
|
||||
|
||||
### Scroll to top on tab button press
|
||||
|
||||
When there's a scroll view in a tab and the user taps on the already focused tab bar again, we might want to scroll to top in our scroll view. The library exports a `useScrollToTop` hook to handle this:
|
||||
|
||||
```js
|
||||
import { useScrollToTop } from '@react-navigation/native';
|
||||
|
||||
// ...
|
||||
|
||||
const ref = React.useRef();
|
||||
|
||||
useScrollToTop(ref);
|
||||
|
||||
return <ScrollView ref={ref}>{/* content */}</ScrollView>;
|
||||
```
|
||||
|
||||
The hook can accept a ref object to any view that has a `scrollTo` method.
|
||||
|
||||
### Deep-link integration
|
||||
|
||||
To handle incoming links, we need to handle 2 scenarios:
|
||||
|
||||
1. If the app wasn't previously open, we need to set the initial state based on the link
|
||||
2. If the app was already open, we need to update the state to reflect the incoming link
|
||||
|
||||
The current implementation of React Navigation has an advantage in handling deep links and is able to automatically set the state based on the path definitions for each screen. It's possible because it can get the configuration for all screens statically.
|
||||
|
||||
With our dynamic architecture, we can't determine the state automatically. So it's necessary to manually translate a deep link to a navigation state. The library exports a `getStateFromPath` utility to convert a URL to a state object if the path segments translate directly to route names.
|
||||
|
||||
For example, the path `/rooms/chat?user=jane` will be translated to a state object like this:
|
||||
|
||||
```js
|
||||
{
|
||||
routes: [
|
||||
{
|
||||
name: 'rooms',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'chat',
|
||||
params: { user: 'jane' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
The `useLinking` hooks makes it easier to handle incoming links:
|
||||
|
||||
```js
|
||||
import { useLinking } from '@react-navigation/native';
|
||||
|
||||
// ...
|
||||
|
||||
const ref = React.useRef();
|
||||
|
||||
const { getInitialState } = useLinking(ref, {
|
||||
prefixes: ['https://myapp.com', 'myapp://'],
|
||||
});
|
||||
|
||||
const [isReady, setIsReady] = React.useState(false);
|
||||
const [initialState, setInitialState] = React.useState();
|
||||
|
||||
React.useEffect(() => {
|
||||
getInitialState()
|
||||
.catch(() => {})
|
||||
.then(state => {
|
||||
if (state !== undefined) {
|
||||
setInitialState(state);
|
||||
}
|
||||
|
||||
setIsReady(true);
|
||||
});
|
||||
}, [getInitialState]);
|
||||
|
||||
if (!isReady) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<NavigationContainer initialState={initialState} ref={ref}>
|
||||
{/* content */}
|
||||
</NavigationContainer>
|
||||
);
|
||||
```
|
||||
|
||||
The hook also accepts a `getStateFromPath` option where you can provide a custom function to convert the URL to a valid state object for more advanced use cases.
|
||||
|
||||
## Type-checking
|
||||
|
||||
The library is written with TypeScript and provides type definitions for TypeScript projects.
|
||||
|
||||
When building custom navigators, each navigator also need to export a custom type for the `navigation` prop which should contain the actions they provide, .e.g. `push` for stack, `jumpTo` for tab etc. and pass correct type parameters to the helper functions to ensure that it works well with type-checking.
|
||||
|
||||
Currently type checking and intelliSense works for route name and params. The user has to define a type alias with a list of routes along with the type of params they use.
|
||||
|
||||
For our example above, we need 2 separate types for stack and tabs:
|
||||
|
||||
```ts
|
||||
type StackParamList = {
|
||||
settings: undefined;
|
||||
profile: { userId: string };
|
||||
home: undefined;
|
||||
};
|
||||
|
||||
type TabParamList = {
|
||||
feed: undefined;
|
||||
article: undefined;
|
||||
notifications: undefined;
|
||||
};
|
||||
```
|
||||
|
||||
In a component, it's possible to annotate the `navigation` and `route` props using these types:
|
||||
|
||||
```ts
|
||||
type Props = {
|
||||
navigation: StackNavigationProp<StackParamList, 'profile'>;
|
||||
route: RouteProp<StackParamList, 'profile'>;
|
||||
};
|
||||
|
||||
function Profile(props: Props) {
|
||||
// Content
|
||||
}
|
||||
```
|
||||
|
||||
Annotating the `navigation` prop will be enough for provide type-checking for actions such as `navigate` etc. Annotating `route` will provide type-checking for accessing `params` for the current route.
|
||||
|
||||
For nested navigators, the `navigation` prop is a combination of multiple `navigation` props, so we need to combine multiple types to type them. We export a type called `CompositeNavigationProp` to make it easier:
|
||||
|
||||
```ts
|
||||
type FeedScreenNavigationProp = CompositeNavigationProp<
|
||||
TabNavigationProp<TabParamList, 'feed'>,
|
||||
StackNavigationProp<StackParamList>
|
||||
>;
|
||||
```
|
||||
|
||||
The `CompositeNavigationProp` type takes 2 parameters, first parameter is the primary navigation type (type for the navigator that owns this screen) and second parameter is the secondary navigation type (type for a parent navigator). The primary navigation type should always have the screen's route name as it's second parameter.
|
||||
|
||||
For multiple parent navigators, this secondary type should be nested:
|
||||
|
||||
```ts
|
||||
type FeedScreenNavigationProp = CompositeNavigationProp<
|
||||
TabNavigationProp<TabParamList, 'feed'>,
|
||||
CompositeNavigationProp<
|
||||
StackNavigationProp<StackParamList>,
|
||||
DrawerNavigationProp<DrawerParamList>
|
||||
>
|
||||
>;
|
||||
```
|
||||
|
||||
To annotate the `navigation` prop from `useNavigation`, we can use a type parameter:
|
||||
|
||||
```ts
|
||||
const navigation = useNavigation<FeedScreenNavigationProp>();
|
||||
```
|
||||
|
||||
It's also possible to type-check the navigator to some extent. To do this, we need to pass a generic when creating the navigator object:
|
||||
|
||||
```ts
|
||||
const Stack = createStackNavigator<StackParamList>();
|
||||
```
|
||||
|
||||
And then we can use it:
|
||||
|
||||
```js
|
||||
<Stack.Navigator initialRouteName="profile">
|
||||
<Stack.Screen name="settings" component={Settings} />
|
||||
<Stack.Screen
|
||||
name="profile"
|
||||
component={Profile}
|
||||
options={{ title: 'My profile' }}
|
||||
/>
|
||||
<Stack.Screen name="home" component={Home} />
|
||||
</Stack.Navigator>
|
||||
```
|
||||
|
||||
Unfortunately it's not possible to verify that the type of children elements are correct since [TypeScript doesn't support type-checking JSX elements](https://github.com/microsoft/TypeScript/issues/21699).
|
||||
Documentation can be found at [next.reactnavigation.org](https://next.reactnavigation.org/).
|
||||
|
||||
## Contributing
|
||||
|
||||
@@ -519,12 +35,33 @@ To fix formatting errors, run the following:
|
||||
yarn lint --fix
|
||||
```
|
||||
|
||||
Remember to add tests for your change if possible. Run the tests by:
|
||||
Remember to add tests for your change if possible. Run the unit tests by:
|
||||
|
||||
```sh
|
||||
yarn test
|
||||
```
|
||||
|
||||
Running Detox (on iOS) requires the following:
|
||||
|
||||
- Mac with macOS (at least macOS High Sierra 10.13.6)
|
||||
- Xcode 10.1+ with Xcode command line tools
|
||||
|
||||
To run the integration tests, first you need to install `applesimutils` and `detox-cli`:
|
||||
|
||||
```sh
|
||||
brew tap wix/brew
|
||||
brew install applesimutils
|
||||
yarn global add detox-cli
|
||||
```
|
||||
|
||||
Then you can build and run the tests:
|
||||
|
||||
```sh
|
||||
cd example
|
||||
detox build -c ios.sim.debug
|
||||
detox test -c ios.sim.debug
|
||||
```
|
||||
|
||||
## Publishing
|
||||
|
||||
To publish a new version, first we need to export a `GH_TOKEN` environment variable as mentioned [here](https://github.com/lerna/lerna/tree/master/commands/version#--create-release-type). Then run:
|
||||
|
||||
@@ -13,5 +13,8 @@ module.exports = {
|
||||
'@babel/preset-react',
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
plugins: ['@babel/plugin-proposal-class-properties'],
|
||||
plugins: [
|
||||
'@babel/plugin-proposal-class-properties',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
],
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 33 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 900 B |
9
example/e2e/__integration_tests__/index.test.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { by, element, expect, device } from 'detox';
|
||||
|
||||
beforeEach(async () => {
|
||||
await device.reloadReactNative();
|
||||
});
|
||||
|
||||
it('has dark theme toggle', async () => {
|
||||
await expect(element(by.text('Dark theme'))).toBeVisible();
|
||||
});
|
||||
6
example/e2e/config.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"setupFilesAfterEnv": ["./init.js"],
|
||||
"testEnvironment": "node",
|
||||
"reporters": ["detox/runners/jest/streamlineReporter"],
|
||||
"verbose": true
|
||||
}
|
||||
29
example/e2e/init.js
Normal file
@@ -0,0 +1,29 @@
|
||||
/* eslint-disable import/no-commonjs, jest/no-jasmine-globals */
|
||||
/* eslint-env jest, jasmine */
|
||||
|
||||
const detox = require('detox');
|
||||
const config = require('../package.json').detox;
|
||||
const adapter = require('detox/runners/jest/adapter');
|
||||
const specReporter = require('detox/runners/jest/specReporter');
|
||||
|
||||
// Set the default timeout
|
||||
jest.setTimeout(120000);
|
||||
|
||||
jasmine.getEnv().addReporter(adapter);
|
||||
|
||||
// This takes care of generating status logs on a per-spec basis. By default, jest only reports at file-level.
|
||||
// This is strictly optional.
|
||||
jasmine.getEnv().addReporter(specReporter);
|
||||
|
||||
beforeAll(async () => {
|
||||
await detox.init(config);
|
||||
}, 300000);
|
||||
|
||||
beforeEach(async () => {
|
||||
await adapter.beforeEach();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await adapter.afterAll();
|
||||
await detox.cleanup();
|
||||
});
|
||||
@@ -253,7 +253,7 @@ PODS:
|
||||
- React
|
||||
- RNReanimated (1.4.0):
|
||||
- React
|
||||
- RNScreens (2.0.0-alpha.17):
|
||||
- RNScreens (2.0.0-alpha.19):
|
||||
- React
|
||||
- UMBarCodeScannerInterface (5.0.0)
|
||||
- UMCameraInterface (5.0.0)
|
||||
@@ -274,15 +274,15 @@ PODS:
|
||||
|
||||
DEPENDENCIES:
|
||||
- DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`)
|
||||
- EXAppLoaderProvider (from `../node_modules/expo/node_modules/expo-app-loader-provider/ios`)
|
||||
- EXConstants (from `../node_modules/expo/node_modules/expo-constants/ios`)
|
||||
- EXAppLoaderProvider (from `../node_modules/expo-app-loader-provider/ios`)
|
||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||
- EXErrorRecovery (from `../node_modules/expo-error-recovery/ios`)
|
||||
- EXFileSystem (from `../node_modules/expo/node_modules/expo-file-system/ios`)
|
||||
- EXFileSystem (from `../node_modules/expo-file-system/ios`)
|
||||
- EXFont (from `../node_modules/expo-font/ios`)
|
||||
- EXKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- EXLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
|
||||
- EXLocation (from `../node_modules/expo-location/ios`)
|
||||
- EXPermissions (from `../node_modules/expo/node_modules/expo-permissions/ios`)
|
||||
- EXPermissions (from `../node_modules/expo-permissions/ios`)
|
||||
- EXSQLite (from `../node_modules/expo-sqlite/ios`)
|
||||
- EXWebBrowser (from `../node_modules/expo-web-browser/ios`)
|
||||
- FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`)
|
||||
@@ -316,18 +316,18 @@ DEPENDENCIES:
|
||||
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
|
||||
- RNReanimated (from `../node_modules/react-native-reanimated`)
|
||||
- RNScreens (from `../node_modules/react-native-screens`)
|
||||
- UMBarCodeScannerInterface (from `../node_modules/expo/node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||
- UMCameraInterface (from `../node_modules/expo/node_modules/unimodules-camera-interface/ios`)
|
||||
- UMConstantsInterface (from `../node_modules/expo/node_modules/unimodules-constants-interface/ios`)
|
||||
- "UMCore (from `../node_modules/expo/node_modules/@unimodules/core/ios`)"
|
||||
- UMFaceDetectorInterface (from `../node_modules/expo/node_modules/unimodules-face-detector-interface/ios`)
|
||||
- UMFileSystemInterface (from `../node_modules/expo/node_modules/unimodules-file-system-interface/ios`)
|
||||
- UMFontInterface (from `../node_modules/expo/node_modules/unimodules-font-interface/ios`)
|
||||
- UMImageLoaderInterface (from `../node_modules/expo/node_modules/unimodules-image-loader-interface/ios`)
|
||||
- UMPermissionsInterface (from `../node_modules/expo/node_modules/unimodules-permissions-interface/ios`)
|
||||
- "UMReactNativeAdapter (from `../node_modules/expo/node_modules/@unimodules/react-native-adapter/ios`)"
|
||||
- UMSensorsInterface (from `../node_modules/expo/node_modules/unimodules-sensors-interface/ios`)
|
||||
- UMTaskManagerInterface (from `../node_modules/expo/node_modules/unimodules-task-manager-interface/ios`)
|
||||
- UMBarCodeScannerInterface (from `../node_modules/unimodules-barcode-scanner-interface/ios`)
|
||||
- UMCameraInterface (from `../node_modules/unimodules-camera-interface/ios`)
|
||||
- UMConstantsInterface (from `../node_modules/unimodules-constants-interface/ios`)
|
||||
- "UMCore (from `../node_modules/@unimodules/core/ios`)"
|
||||
- UMFaceDetectorInterface (from `../node_modules/unimodules-face-detector-interface/ios`)
|
||||
- UMFileSystemInterface (from `../node_modules/unimodules-file-system-interface/ios`)
|
||||
- UMFontInterface (from `../node_modules/unimodules-font-interface/ios`)
|
||||
- UMImageLoaderInterface (from `../node_modules/unimodules-image-loader-interface/ios`)
|
||||
- UMPermissionsInterface (from `../node_modules/unimodules-permissions-interface/ios`)
|
||||
- "UMReactNativeAdapter (from `../node_modules/@unimodules/react-native-adapter/ios`)"
|
||||
- UMSensorsInterface (from `../node_modules/unimodules-sensors-interface/ios`)
|
||||
- UMTaskManagerInterface (from `../node_modules/unimodules-task-manager-interface/ios`)
|
||||
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -339,16 +339,16 @@ EXTERNAL SOURCES:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec"
|
||||
EXAppLoaderProvider:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/expo-app-loader-provider/ios"
|
||||
path: "../node_modules/expo-app-loader-provider/ios"
|
||||
EXConstants:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/expo-constants/ios"
|
||||
path: "../node_modules/expo-constants/ios"
|
||||
EXErrorRecovery:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo-error-recovery/ios"
|
||||
EXFileSystem:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/expo-file-system/ios"
|
||||
path: "../node_modules/expo-file-system/ios"
|
||||
EXFont:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo-font/ios"
|
||||
@@ -363,7 +363,7 @@ EXTERNAL SOURCES:
|
||||
path: "../node_modules/expo-location/ios"
|
||||
EXPermissions:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/expo-permissions/ios"
|
||||
path: "../node_modules/expo-permissions/ios"
|
||||
EXSQLite:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo-sqlite/ios"
|
||||
@@ -428,40 +428,40 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-screens"
|
||||
UMBarCodeScannerInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-barcode-scanner-interface/ios"
|
||||
path: "../node_modules/unimodules-barcode-scanner-interface/ios"
|
||||
UMCameraInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-camera-interface/ios"
|
||||
path: "../node_modules/unimodules-camera-interface/ios"
|
||||
UMConstantsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-constants-interface/ios"
|
||||
path: "../node_modules/unimodules-constants-interface/ios"
|
||||
UMCore:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/@unimodules/core/ios"
|
||||
path: "../node_modules/@unimodules/core/ios"
|
||||
UMFaceDetectorInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-face-detector-interface/ios"
|
||||
path: "../node_modules/unimodules-face-detector-interface/ios"
|
||||
UMFileSystemInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-file-system-interface/ios"
|
||||
path: "../node_modules/unimodules-file-system-interface/ios"
|
||||
UMFontInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-font-interface/ios"
|
||||
path: "../node_modules/unimodules-font-interface/ios"
|
||||
UMImageLoaderInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-image-loader-interface/ios"
|
||||
path: "../node_modules/unimodules-image-loader-interface/ios"
|
||||
UMPermissionsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-permissions-interface/ios"
|
||||
path: "../node_modules/unimodules-permissions-interface/ios"
|
||||
UMReactNativeAdapter:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/@unimodules/react-native-adapter/ios"
|
||||
path: "../node_modules/@unimodules/react-native-adapter/ios"
|
||||
UMSensorsInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-sensors-interface/ios"
|
||||
path: "../node_modules/unimodules-sensors-interface/ios"
|
||||
UMTaskManagerInterface:
|
||||
:path: !ruby/object:Pathname
|
||||
path: "../node_modules/expo/node_modules/unimodules-task-manager-interface/ios"
|
||||
path: "../node_modules/unimodules-task-manager-interface/ios"
|
||||
Yoga:
|
||||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
@@ -506,7 +506,7 @@ SPEC CHECKSUMS:
|
||||
RNCMaskedView: dd13f9f7b146a9ad82f9b7eb6c9b5548fcf6e990
|
||||
RNGestureHandler: 946a7691e41df61e2c4b1884deab41a4cdc3afff
|
||||
RNReanimated: b2ab0b693dddd2339bd2f300e770f6302d2e960c
|
||||
RNScreens: f5e2ff7ccde2a1bfcf2a48c6fd07fdcf1fd95223
|
||||
RNScreens: 62284de159bbddba268edd282337afdb8771aaed
|
||||
UMBarCodeScannerInterface: 3802c8574ef119c150701d679ab386e2266d6a54
|
||||
UMCameraInterface: 985d301f688ed392f815728f0dd906ca34b7ccb1
|
||||
UMConstantsInterface: bda5f8bd3403ad99e663eb3c4da685d063c5653c
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"react-dom": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "~1.5.0",
|
||||
"react-native-iphone-x-helper": "^1.2.1",
|
||||
"react-native-paper": "^3.3.0",
|
||||
"react-native-reanimated": "^1.4.0",
|
||||
"react-native-safe-area-context": "^0.6.0",
|
||||
@@ -45,7 +46,30 @@
|
||||
"@types/react": "^16.9.16",
|
||||
"@types/react-native": "^0.60.25",
|
||||
"babel-preset-expo": "^8.0.0",
|
||||
"detox": "^15.0.0",
|
||||
"expo-cli": "^3.11.1",
|
||||
"jest": "^24.9.0",
|
||||
"typescript": "^3.7.3"
|
||||
},
|
||||
"detox": {
|
||||
"test-runner": "jest",
|
||||
"configurations": {
|
||||
"ios.sim.debug": {
|
||||
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/ReactNavigationExample.app",
|
||||
"build": "set -o pipefail; xcodebuild -workspace ios/ReactNavigationExample.xcworkspace -scheme ReactNavigationExample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11 Pro"
|
||||
}
|
||||
},
|
||||
"ios.sim.release": {
|
||||
"binaryPath": "ios/build/Build/Products/Release-iphonesimulator/ReactNavigationExample.app",
|
||||
"build": "export RCT_NO_LAUNCH_PACKAGER=true; set -o pipefail; xcodebuild -workspace ios/ReactNavigationExample.xcworkspace -scheme ReactNavigationExample -configuration Release -sdk iphonesimulator -derivedDataPath ios/build",
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 11 Pro"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ const getTabBarIcon = (name: string) => ({
|
||||
}) => <MaterialCommunityIcons name={name} color={color} size={size} />;
|
||||
|
||||
type BottomTabParams = {
|
||||
article: undefined;
|
||||
albums: undefined;
|
||||
contacts: undefined;
|
||||
chat: undefined;
|
||||
Article: undefined;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const BottomTabs = createBottomTabNavigator<BottomTabParams>();
|
||||
@@ -32,7 +32,7 @@ export default function BottomTabsScreen() {
|
||||
}}
|
||||
>
|
||||
<BottomTabs.Screen
|
||||
name="article"
|
||||
name="Article"
|
||||
options={{
|
||||
title: 'Article',
|
||||
tabBarIcon: getTabBarIcon('file-document-box'),
|
||||
@@ -41,7 +41,7 @@ export default function BottomTabsScreen() {
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</BottomTabs.Screen>
|
||||
<BottomTabs.Screen
|
||||
name="chat"
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -49,7 +49,7 @@ export default function BottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="contacts"
|
||||
name="Contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
title: 'Contacts',
|
||||
@@ -57,7 +57,7 @@ export default function BottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<BottomTabs.Screen
|
||||
name="albums"
|
||||
name="Albums"
|
||||
component={Albums}
|
||||
options={{
|
||||
title: 'Albums',
|
||||
|
||||
@@ -7,10 +7,10 @@ import Chat from '../Shared/Chat';
|
||||
import SimpleStackScreen from './SimpleStack';
|
||||
|
||||
type MaterialBottomTabParams = {
|
||||
article: undefined;
|
||||
albums: undefined;
|
||||
contacts: undefined;
|
||||
chat: undefined;
|
||||
Article: undefined;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const MaterialBottomTabs = createMaterialBottomTabNavigator<
|
||||
@@ -21,7 +21,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
return (
|
||||
<MaterialBottomTabs.Navigator barStyle={styles.tabBar}>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="article"
|
||||
name="Article"
|
||||
options={{
|
||||
tabBarLabel: 'Article',
|
||||
tabBarIcon: 'file-document-box',
|
||||
@@ -31,7 +31,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
{props => <SimpleStackScreen {...props} headerMode="none" />}
|
||||
</MaterialBottomTabs.Screen>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="chat"
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
options={{
|
||||
tabBarLabel: 'Chat',
|
||||
@@ -41,7 +41,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="contacts"
|
||||
name="Contacts"
|
||||
component={Contacts}
|
||||
options={{
|
||||
tabBarLabel: 'Contacts',
|
||||
@@ -50,7 +50,7 @@ export default function MaterialBottomTabsScreen() {
|
||||
}}
|
||||
/>
|
||||
<MaterialBottomTabs.Screen
|
||||
name="albums"
|
||||
name="Albums"
|
||||
component={Albums}
|
||||
options={{
|
||||
tabBarLabel: 'Albums',
|
||||
|
||||
@@ -5,9 +5,9 @@ import Contacts from '../Shared/Contacts';
|
||||
import Chat from '../Shared/Chat';
|
||||
|
||||
type MaterialTopTabParams = {
|
||||
albums: undefined;
|
||||
contacts: undefined;
|
||||
chat: undefined;
|
||||
Albums: undefined;
|
||||
Contacts: undefined;
|
||||
Chat: undefined;
|
||||
};
|
||||
|
||||
const MaterialTopTabs = createMaterialTopTabNavigator<MaterialTopTabParams>();
|
||||
@@ -16,17 +16,17 @@ export default function MaterialTopTabsScreen() {
|
||||
return (
|
||||
<MaterialTopTabs.Navigator>
|
||||
<MaterialTopTabs.Screen
|
||||
name="chat"
|
||||
name="Chat"
|
||||
component={Chat}
|
||||
options={{ title: 'Chat' }}
|
||||
/>
|
||||
<MaterialTopTabs.Screen
|
||||
name="contacts"
|
||||
name="Contacts"
|
||||
component={Contacts}
|
||||
options={{ title: 'Contacts' }}
|
||||
/>
|
||||
<MaterialTopTabs.Screen
|
||||
name="albums"
|
||||
name="Albums"
|
||||
component={Albums}
|
||||
options={{ title: 'Albums' }}
|
||||
/>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { View, StyleSheet } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
import { useSafeArea } from 'react-native-safe-area-context';
|
||||
import { RouteProp, ParamListBase } from '@react-navigation/native';
|
||||
import {
|
||||
createStackNavigator,
|
||||
@@ -11,28 +10,26 @@ import {
|
||||
import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type SimpleStackParams = {
|
||||
article: { author: string };
|
||||
album: undefined;
|
||||
type ModalStackParams = {
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
};
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
type ModalStackNavigation = StackNavigationProp<ModalStackParams>;
|
||||
|
||||
const ArticleScreen = ({
|
||||
navigation,
|
||||
route,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
route: RouteProp<SimpleStackParams, 'article'>;
|
||||
navigation: ModalStackNavigation;
|
||||
route: RouteProp<ModalStackParams, 'Article'>;
|
||||
}) => {
|
||||
const insets = useSafeArea();
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={[styles.buttons, { marginTop: insets.top }]}>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('album')}
|
||||
onPress={() => navigation.push('Album')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
@@ -50,19 +47,13 @@ const ArticleScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const AlbumsScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
}) => {
|
||||
const insets = useSafeArea();
|
||||
|
||||
const AlbumsScreen = ({ navigation }: { navigation: ModalStackNavigation }) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={[styles.buttons, { marginTop: insets.top }]}>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('article', { author: 'Babel fish' })}
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
@@ -80,7 +71,7 @@ const AlbumsScreen = ({
|
||||
);
|
||||
};
|
||||
|
||||
const ModalPresentationStack = createStackNavigator<SimpleStackParams>();
|
||||
const ModalPresentationStack = createStackNavigator<ModalStackParams>();
|
||||
|
||||
type Props = {
|
||||
options?: React.ComponentProps<typeof ModalPresentationStack.Navigator>;
|
||||
@@ -95,16 +86,20 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
return (
|
||||
<ModalPresentationStack.Navigator
|
||||
mode="modal"
|
||||
headerMode="none"
|
||||
screenOptions={{
|
||||
headerMode="screen"
|
||||
screenOptions={({ route, navigation }) => ({
|
||||
...TransitionPresets.ModalPresentationIOS,
|
||||
cardOverlayEnabled: true,
|
||||
gestureEnabled: true,
|
||||
}}
|
||||
headerStatusBarHeight:
|
||||
navigation.dangerouslyGetState().routes.indexOf(route) > 0
|
||||
? 0
|
||||
: undefined,
|
||||
})}
|
||||
{...options}
|
||||
>
|
||||
<ModalPresentationStack.Screen
|
||||
name="article"
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params.author}`,
|
||||
@@ -112,7 +107,7 @@ export default function SimpleStackScreen({ navigation, options }: Props) {
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<ModalPresentationStack.Screen
|
||||
name="album"
|
||||
name="Album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type NativeStackParams = {
|
||||
article: { author: string };
|
||||
album: undefined;
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
};
|
||||
|
||||
type NativeStackNavigation = NativeStackNavigationProp<NativeStackParams>;
|
||||
@@ -42,7 +42,7 @@ const ArticleScreen = ({
|
||||
navigation,
|
||||
}: {
|
||||
navigation: NativeStackNavigation;
|
||||
route: RouteProp<NativeStackParams, 'article'>;
|
||||
route: RouteProp<NativeStackParams, 'Article'>;
|
||||
}) => {
|
||||
const { colors } = useTheme();
|
||||
|
||||
@@ -54,7 +54,7 @@ const ArticleScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('album')}
|
||||
onPress={() => navigation.push('Album')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
@@ -141,7 +141,7 @@ const AlbumsScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('article', { author: 'Babel fish' })}
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
@@ -188,7 +188,7 @@ export default function NativeStackScreen({ navigation }: Props) {
|
||||
return (
|
||||
<NativeStack.Navigator>
|
||||
<NativeStack.Screen
|
||||
name="article"
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={{
|
||||
title: 'Lorem Ipsum',
|
||||
@@ -197,7 +197,7 @@ export default function NativeStackScreen({ navigation }: Props) {
|
||||
}}
|
||||
/>
|
||||
<NativeStack.Screen
|
||||
name="album"
|
||||
name="Album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
|
||||
@@ -10,8 +10,8 @@ import Article from '../Shared/Article';
|
||||
import Albums from '../Shared/Albums';
|
||||
|
||||
type SimpleStackParams = {
|
||||
article: { author: string };
|
||||
album: undefined;
|
||||
Article: { author: string };
|
||||
Album: undefined;
|
||||
};
|
||||
|
||||
type SimpleStackNavigation = StackNavigationProp<SimpleStackParams>;
|
||||
@@ -21,14 +21,14 @@ const ArticleScreen = ({
|
||||
route,
|
||||
}: {
|
||||
navigation: SimpleStackNavigation;
|
||||
route: RouteProp<SimpleStackParams, 'article'>;
|
||||
route: RouteProp<SimpleStackParams, 'Article'>;
|
||||
}) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('album')}
|
||||
onPress={() => navigation.push('Album')}
|
||||
style={styles.button}
|
||||
>
|
||||
Push album
|
||||
@@ -56,7 +56,7 @@ const AlbumsScreen = ({
|
||||
<View style={styles.buttons}>
|
||||
<Button
|
||||
mode="contained"
|
||||
onPress={() => navigation.push('article', { author: 'Babel fish' })}
|
||||
onPress={() => navigation.push('Article', { author: 'Babel fish' })}
|
||||
style={styles.button}
|
||||
>
|
||||
Push article
|
||||
@@ -88,7 +88,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
return (
|
||||
<SimpleStack.Navigator {...rest}>
|
||||
<SimpleStack.Screen
|
||||
name="article"
|
||||
name="Article"
|
||||
component={ArticleScreen}
|
||||
options={({ route }) => ({
|
||||
title: `Article by ${route.params.author}`,
|
||||
@@ -96,7 +96,7 @@ export default function SimpleStackScreen({ navigation, ...rest }: Props) {
|
||||
initialParams={{ author: 'Gandalf' }}
|
||||
/>
|
||||
<SimpleStack.Screen
|
||||
name="album"
|
||||
name="Album"
|
||||
component={AlbumsScreen}
|
||||
options={{ title: 'Album' }}
|
||||
/>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* eslint-disable import/no-commonjs */
|
||||
|
||||
import * as React from 'react';
|
||||
import { Image, Dimensions, ScrollView, StyleSheet } from 'react-native';
|
||||
import { useScrollToTop } from '@react-navigation/native';
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
import { Asset } from 'expo-asset';
|
||||
import {
|
||||
InitialState,
|
||||
getStateFromPath,
|
||||
useLinking,
|
||||
NavigationContainerRef,
|
||||
NavigationNativeContainer,
|
||||
@@ -104,20 +103,19 @@ export default function App() {
|
||||
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
|
||||
const { getInitialState } = useLinking(containerRef, {
|
||||
prefixes: LinkingPrefixes,
|
||||
getStateFromPath: path => {
|
||||
const state = getStateFromPath(path);
|
||||
config: {
|
||||
Root: Object.keys(SCREENS).reduce<{ [key: string]: string }>(
|
||||
(acc, name) => {
|
||||
// Convert screen names such as SimpleStack to kebab case (simple-stack)
|
||||
acc[name] = name
|
||||
.replace(/([A-Z]+)/g, '-$1')
|
||||
.replace(/^-/, '')
|
||||
.toLowerCase();
|
||||
|
||||
return {
|
||||
routes: [
|
||||
{
|
||||
name: 'root',
|
||||
state: {
|
||||
...state,
|
||||
routes: [{ name: 'home' }, ...(state ? state.routes : [])],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -252,19 +250,19 @@ export default function App() {
|
||||
/>
|
||||
</View>
|
||||
<Divider />
|
||||
{(Object.keys(SCREENS) as Array<
|
||||
keyof typeof SCREENS
|
||||
>).map(name => (
|
||||
<List.Item
|
||||
key={name}
|
||||
title={SCREENS[name].title}
|
||||
onPress={() => navigation.push(name)}
|
||||
/>
|
||||
))}
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
<List.Item
|
||||
key={name}
|
||||
title={SCREENS[name].title}
|
||||
onPress={() => navigation.push(name)}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</Stack.Screen>
|
||||
{(Object.keys(SCREENS) as Array<keyof typeof SCREENS>).map(
|
||||
{(Object.keys(SCREENS) as (keyof typeof SCREENS)[]).map(
|
||||
name => (
|
||||
<Stack.Screen
|
||||
key={name}
|
||||
|
||||
@@ -4,6 +4,9 @@ const createExpoWebpackConfigAsync = require('@expo/webpack-config');
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
|
||||
|
||||
const node_modules = path.resolve(__dirname, 'node_modules');
|
||||
const packages = path.resolve(__dirname, '..', 'packages');
|
||||
|
||||
module.exports = async function(env, argv) {
|
||||
const config = await createExpoWebpackConfigAsync(env, argv);
|
||||
|
||||
@@ -19,28 +22,15 @@ module.exports = async function(env, argv) {
|
||||
);
|
||||
|
||||
Object.assign(config.resolve.alias, {
|
||||
react: path.resolve(__dirname, 'node_modules', 'react'),
|
||||
'react-native': path.resolve(__dirname, 'node_modules', 'react-native-web'),
|
||||
'react-native-web': path.resolve(
|
||||
__dirname,
|
||||
'node_modules',
|
||||
'react-native-web'
|
||||
),
|
||||
'react-native-reanimated': path.resolve(
|
||||
__dirname,
|
||||
'node_modules',
|
||||
'react-native-reanimated-web'
|
||||
),
|
||||
'@expo/vector-icons/MaterialCommunityIcons': require.resolve(
|
||||
'@expo/vector-icons/MaterialCommunityIcons'
|
||||
),
|
||||
react: path.resolve(node_modules, 'react'),
|
||||
'react-native': path.resolve(node_modules, 'react-native-web'),
|
||||
'react-native-web': path.resolve(node_modules, 'react-native-web'),
|
||||
'@expo/vector-icons': path.resolve(node_modules, '@expo/vector-icons'),
|
||||
});
|
||||
|
||||
fs.readdirSync(path.join(__dirname, '..')).forEach(name => {
|
||||
fs.readdirSync(packages).forEach(name => {
|
||||
config.resolve.alias[`@react-navigation/${name}`] = path.resolve(
|
||||
__dirname,
|
||||
'..',
|
||||
'packages',
|
||||
packages,
|
||||
name,
|
||||
'src'
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ const error = console.error;
|
||||
|
||||
console.error = (...args) =>
|
||||
// Supress error messages regarding error boundary in tests
|
||||
/Consider adding an error boundary to your tree to customize error handling behavior/m.test(
|
||||
/(Consider adding an error boundary to your tree to customize error handling behavior|React will try to recreate this component tree from scratch using the error boundary you provided|Error boundaries should implement getDerivedStateFromError)/m.test(
|
||||
args[0]
|
||||
)
|
||||
? void 0
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
|
||||
"@babel/preset-env": "^7.7.6",
|
||||
"@babel/preset-react": "^7.7.0",
|
||||
"@babel/preset-typescript": "^7.7.2",
|
||||
@@ -34,9 +35,9 @@
|
||||
"commitlint": "^8.2.0",
|
||||
"core-js": "^3.5.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-config-satya164": "^3.1.2",
|
||||
"eslint-config-satya164": "^3.1.5",
|
||||
"husky": "^3.0.9",
|
||||
"jest": "^24.8.0",
|
||||
"jest": "^24.9.0",
|
||||
"lerna": "^3.18.4",
|
||||
"prettier": "^1.19.1",
|
||||
"typescript": "^3.7.3"
|
||||
|
||||
@@ -3,6 +3,41 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.31...@react-navigation/bottom-tabs@5.0.0-alpha.32) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.30...@react-navigation/bottom-tabs@5.0.0-alpha.31) (2020-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.29...@react-navigation/bottom-tabs@5.0.0-alpha.30) (2020-01-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.28...@react-navigation/bottom-tabs@5.0.0-alpha.29) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/bottom-tabs@5.0.0-alpha.27...@react-navigation/bottom-tabs@5.0.0-alpha.28) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/bottom-tabs
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"android",
|
||||
"tab"
|
||||
],
|
||||
"version": "5.0.0-alpha.28",
|
||||
"version": "5.0.0-alpha.32",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,8 +33,9 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18",
|
||||
"color": "^3.1.2"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20",
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
@@ -51,7 +52,7 @@
|
||||
"@react-navigation/native": "^5.0.0-alpha.0",
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-safe-area-context": "^0.3.6"
|
||||
"react-native-safe-area-context": "^0.6.0"
|
||||
},
|
||||
"@react-native-community/bob": {
|
||||
"source": "src",
|
||||
|
||||
@@ -14,7 +14,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled && screensEnabled()) {
|
||||
if (screensEnabled?.()) {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
// @ts-ignore
|
||||
return <Screen active={isVisible ? 1 : 0} {...rest} />;
|
||||
|
||||
@@ -3,6 +3,17 @@ import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaConsumer,
|
||||
} from 'react-native-safe-area-context';
|
||||
import {
|
||||
getStatusBarHeight,
|
||||
getBottomSpace,
|
||||
} from 'react-native-iphone-x-helper';
|
||||
|
||||
const initialSafeAreaInsets = {
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
right: 0,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <SafeAreaProvider>{children}</SafeAreaProvider>;
|
||||
return (
|
||||
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}}
|
||||
</SafeAreaConsumer>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.20...@react-navigation/compat@5.0.0-alpha.21) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.19...@react-navigation/compat@5.0.0-alpha.20) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/compat@5.0.0-alpha.18...@react-navigation/compat@5.0.0-alpha.19) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/compat
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@react-navigation/compat",
|
||||
"description": "Compatibility layer to write navigator definitions in static configuration format",
|
||||
"version": "5.0.0-alpha.19",
|
||||
"version": "5.0.0-alpha.21",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -24,7 +24,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.9.16",
|
||||
|
||||
@@ -25,7 +25,7 @@ export function navigate({
|
||||
}
|
||||
|
||||
export function back(options?: { key?: null | string }) {
|
||||
return options && options.key != null
|
||||
return options?.key != null
|
||||
? (state: NavigationState) => ({
|
||||
...CommonActions.goBack(),
|
||||
source: options.key,
|
||||
|
||||
@@ -109,17 +109,17 @@ export default function createCompatNavigationProp<
|
||||
break;
|
||||
case 'didFocus': {
|
||||
const unsubscribe = focusSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'didBlur': {
|
||||
const unsubscribe = blurSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
case 'refocus': {
|
||||
const unsubscribe = refocusSubscriptions.get(callback);
|
||||
unsubscribe && unsubscribe();
|
||||
unsubscribe?.();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function createCompatNavigatorFactory<
|
||||
>(
|
||||
routeConfig: CompatRouteConfig<NavigationPropType>,
|
||||
navigationConfig: Partial<Omit<NavigationConfig, 'screenOptions'>> & {
|
||||
order?: Array<Extract<keyof ParamList, string>>;
|
||||
order?: Extract<keyof ParamList, string>[];
|
||||
defaultNavigationOptions?: ScreenOptions;
|
||||
navigationOptions?: Record<string, any>;
|
||||
} = {}
|
||||
|
||||
@@ -3,6 +3,18 @@
|
||||
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.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.29...@react-navigation/core@5.0.0-alpha.30) (2020-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cleanup transaction even if action wasn't handled ([f462d67](https://github.com/react-navigation/navigation-ex/commit/f462d672708cabfb0477c3a48505bd194ea626fd))
|
||||
* show error if an action was not handled ([0252bdc](https://github.com/react-navigation/navigation-ex/commit/0252bdc2222ebe7410a0ed593bf03b2bdf5dc7ca))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/core@5.0.0-alpha.28...@react-navigation/core@5.0.0-alpha.29) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/core
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"react-native",
|
||||
"react-navigation"
|
||||
],
|
||||
"version": "5.0.0-alpha.29",
|
||||
"version": "5.0.0-alpha.30",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import { NavigationState, PartialState } from './types';
|
||||
|
||||
export type Action =
|
||||
|
||||
@@ -123,9 +123,11 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
isTransactionActiveRef.current = true;
|
||||
transactionStateRef.current = navigationState;
|
||||
|
||||
callback();
|
||||
|
||||
isTransactionActiveRef.current = false;
|
||||
try {
|
||||
callback();
|
||||
} finally {
|
||||
isTransactionActiveRef.current = false;
|
||||
}
|
||||
|
||||
return transactionStateRef.current;
|
||||
});
|
||||
@@ -202,7 +204,7 @@ const Container = React.forwardRef(function NavigationContainer(
|
||||
}, [getStateForRoute]);
|
||||
|
||||
React.useImperativeHandle(ref, () => ({
|
||||
...(Object.keys(CommonActions) as Array<keyof typeof CommonActions>).reduce<
|
||||
...(Object.keys(CommonActions) as (keyof typeof CommonActions)[]).reduce<
|
||||
any
|
||||
>((acc, name) => {
|
||||
acc[name] = (...args: any[]) =>
|
||||
|
||||
75
packages/core/src/__tests__/getActionFromState.test.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import getActionFromState from '../getActionFromState';
|
||||
|
||||
it('gets navigate action from state', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
params: { answer: 42 },
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: {
|
||||
name: 'foo',
|
||||
params: {
|
||||
params: {
|
||||
answer: 42,
|
||||
params: {
|
||||
author: 'jane',
|
||||
},
|
||||
screen: 'qux',
|
||||
},
|
||||
screen: 'bar',
|
||||
},
|
||||
},
|
||||
type: 'NAVIGATE',
|
||||
});
|
||||
});
|
||||
|
||||
it('gets reset action from state', () => {
|
||||
const state = {
|
||||
routes: [
|
||||
{
|
||||
name: 'foo',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'bar',
|
||||
state: {
|
||||
routes: [
|
||||
{
|
||||
name: 'qux',
|
||||
params: { author: 'jane' },
|
||||
},
|
||||
{ name: 'quz' },
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(getActionFromState(state)).toEqual({
|
||||
payload: state,
|
||||
type: 'RESET_ROOT',
|
||||
});
|
||||
});
|
||||
@@ -357,6 +357,8 @@ it("doesn't update state if action wasn't handled", () => {
|
||||
|
||||
const onStateChange = jest.fn();
|
||||
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
render(
|
||||
<NavigationContainer onStateChange={onStateChange}>
|
||||
<TestNavigator initialRouteName="foo">
|
||||
@@ -367,6 +369,12 @@ it("doesn't update state if action wasn't handled", () => {
|
||||
);
|
||||
|
||||
expect(onStateChange).toBeCalledTimes(0);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'INVALID' with payload 'undefined' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
it('cleans up state when the navigator unmounts', () => {
|
||||
|
||||
@@ -305,8 +305,7 @@ it("action doesn't bubble if target is specified", () => {
|
||||
expect(onStateChange).not.toBeCalled();
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/expect-expect
|
||||
it("doesn't crash if no navigator handled the action", () => {
|
||||
it('logs error if no navigator handled the action', () => {
|
||||
const TestRouter = MockRouter;
|
||||
|
||||
const TestNavigator = (props: any) => {
|
||||
@@ -366,5 +365,13 @@ it("doesn't crash if no navigator handled the action", () => {
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
const spy = jest.spyOn(console, 'error').mockImplementation();
|
||||
|
||||
render(element).update(element);
|
||||
|
||||
expect(spy.mock.calls[0][0]).toMatch(
|
||||
"The action 'UNKNOWN' with payload 'undefined' was not handled by any navigator."
|
||||
);
|
||||
|
||||
spy.mockRestore();
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ it('gets route prop from context', () => {
|
||||
const Test = () => {
|
||||
const route = useRoute<RouteProp<{ sample: { x: string } }, 'sample'>>();
|
||||
|
||||
expect(route && route.params && route.params.x).toEqual(1);
|
||||
expect(route?.params?.x).toEqual(1);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
67
packages/core/src/getActionFromState.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { PartialState, NavigationState } from './types';
|
||||
|
||||
type NavigateParams = {
|
||||
screen?: string;
|
||||
params?: NavigateParams;
|
||||
};
|
||||
|
||||
type Action =
|
||||
| {
|
||||
type: 'NAVIGATE';
|
||||
payload: { name: string; params: NavigateParams };
|
||||
}
|
||||
| {
|
||||
type: 'RESET_ROOT';
|
||||
payload: PartialState<NavigationState>;
|
||||
};
|
||||
|
||||
export default function getActionFromState(
|
||||
state: PartialState<NavigationState>
|
||||
): Action {
|
||||
let payload: { name: string; params: NavigateParams } | undefined;
|
||||
|
||||
if (state.routes.length === 1) {
|
||||
// Try to construct payload for a `NAVIGATE` action from the state
|
||||
// This lets us preserve the navigation state and not lose it
|
||||
let route = state.routes[0];
|
||||
|
||||
payload = {
|
||||
name: route.name,
|
||||
params: { ...route.params },
|
||||
};
|
||||
|
||||
let current = state.routes[0].state;
|
||||
let params = payload.params;
|
||||
|
||||
while (current) {
|
||||
if (current.routes.length === 1) {
|
||||
route = current.routes[0];
|
||||
params.screen = route.name;
|
||||
|
||||
if (route.state) {
|
||||
params.params = { ...route.params };
|
||||
params = params.params;
|
||||
} else {
|
||||
params.params = route.params;
|
||||
}
|
||||
|
||||
current = route.state;
|
||||
} else {
|
||||
payload = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (payload) {
|
||||
return {
|
||||
type: 'NAVIGATE',
|
||||
payload,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'RESET_ROOT',
|
||||
payload: state,
|
||||
};
|
||||
}
|
||||
@@ -60,7 +60,7 @@ export default function getPathFromState(
|
||||
Object.entries(route.params).reduce<{
|
||||
[key: string]: string;
|
||||
}>((acc, [key, value]) => {
|
||||
acc[key] = config && config[key] ? config[key](value) : String(value);
|
||||
acc[key] = config?.[key] ? config[key](value) : String(value);
|
||||
return acc;
|
||||
}, {})
|
||||
: undefined;
|
||||
@@ -80,6 +80,7 @@ export default function getPathFromState(
|
||||
if (params && name in params && p.startsWith(':')) {
|
||||
const value = params[name];
|
||||
// Remove the used value from the params object since we'll use the rest for query string
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete params[name];
|
||||
return encodeURIComponent(value);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ type RouteConfig = {
|
||||
parse: Record<string, (value: string) => any> | undefined;
|
||||
};
|
||||
|
||||
type ResultState = PartialState<NavigationState> & {
|
||||
state?: ResultState;
|
||||
};
|
||||
|
||||
/**
|
||||
* Utility to parse a path string to initial state object accepted by the container.
|
||||
* This is useful for deep linking when we need to handle the incoming URL.
|
||||
@@ -37,7 +41,7 @@ type RouteConfig = {
|
||||
export default function getStateFromPath(
|
||||
path: string,
|
||||
options: Options = {}
|
||||
): PartialState<NavigationState> | undefined {
|
||||
): ResultState | undefined {
|
||||
// Create a normalized configs array which will be easier to use
|
||||
const configs = ([] as RouteConfig[]).concat(
|
||||
...Object.keys(options).map(key => createNormalizedConfigs(key, options))
|
||||
|
||||
@@ -17,5 +17,6 @@ export { default as useIsFocused } from './useIsFocused';
|
||||
|
||||
export { default as getStateFromPath } from './getStateFromPath';
|
||||
export { default as getPathFromState } from './getPathFromState';
|
||||
export { default as getActionFromState } from './getActionFromState';
|
||||
|
||||
export * from './types';
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
import * as CommonActions from './CommonActions';
|
||||
import * as React from 'react';
|
||||
|
||||
@@ -20,9 +19,9 @@ export type NavigationState = {
|
||||
/**
|
||||
* List of rendered routes.
|
||||
*/
|
||||
routes: Array<
|
||||
Route<string> & { state?: NavigationState | PartialState<NavigationState> }
|
||||
>;
|
||||
routes: (Route<string> & {
|
||||
state?: NavigationState | PartialState<NavigationState>;
|
||||
})[];
|
||||
/**
|
||||
* Custom type for the state, whether it's for tab, stack, drawer etc.
|
||||
* During rehydration, the state will be discarded if type doesn't match with router type.
|
||||
@@ -38,7 +37,7 @@ export type NavigationState = {
|
||||
export type InitialState = Partial<
|
||||
Omit<NavigationState, 'stale' | 'routes'>
|
||||
> & {
|
||||
routes: Array<Omit<Route<string>, 'key'> & { state?: InitialState }>;
|
||||
routes: (Omit<Route<string>, 'key'> & { state?: InitialState })[];
|
||||
};
|
||||
|
||||
export type PartialState<State extends NavigationState> = Partial<
|
||||
@@ -46,9 +45,10 @@ export type PartialState<State extends NavigationState> = Partial<
|
||||
> & {
|
||||
stale?: true;
|
||||
type?: string;
|
||||
routes: Array<
|
||||
Omit<Route<string>, 'key'> & { key?: string; state?: InitialState }
|
||||
>;
|
||||
routes: (Omit<Route<string>, 'key'> & {
|
||||
key?: string;
|
||||
state?: InitialState;
|
||||
})[];
|
||||
};
|
||||
|
||||
export type Route<RouteName extends string> = {
|
||||
@@ -71,6 +71,10 @@ export type NavigationAction = {
|
||||
* Type of the action (e.g. `NAVIGATE`)
|
||||
*/
|
||||
type: string;
|
||||
/**
|
||||
* Additional data for the action
|
||||
*/
|
||||
payload?: object;
|
||||
/**
|
||||
* Key of the route which dispatched this action.
|
||||
*/
|
||||
|
||||
@@ -44,16 +44,15 @@ export default function useDevTools({ name, reset, state }: Options) {
|
||||
|
||||
const devTools = devToolsRef.current;
|
||||
const lastStateRef = React.useRef<State>(state);
|
||||
const actions = React.useRef<Array<NavigationAction | string>>([]);
|
||||
const actions = React.useRef<(NavigationAction | string)[]>([]);
|
||||
|
||||
React.useEffect(() => {
|
||||
devTools && devTools.init(lastStateRef.current);
|
||||
devTools?.init(lastStateRef.current);
|
||||
}, [devTools]);
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
devTools &&
|
||||
devTools.subscribe(message => {
|
||||
devTools?.subscribe(message => {
|
||||
if (message.type === 'DISPATCH' && message.state) {
|
||||
reset(JSON.parse(message.state));
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ export type NavigationEventEmitter = EventEmitter<Record<string, any>> & {
|
||||
create: (target: string) => EventConsumer<Record<string, any>>;
|
||||
};
|
||||
|
||||
type Listeners = Array<(data: any) => void>;
|
||||
type Listeners = ((data: any) => void)[];
|
||||
|
||||
/**
|
||||
* Hook to manage the event system used by the navigator to notify screens of various events.
|
||||
@@ -69,7 +69,7 @@ export default function useEventEmitter(): NavigationEventEmitter {
|
||||
},
|
||||
};
|
||||
|
||||
callbacks && callbacks.forEach(cb => cb(event));
|
||||
callbacks?.forEach(cb => cb(event));
|
||||
|
||||
return event;
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import useNavigation from './useNavigation';
|
||||
|
||||
type EffectCallback = (() => void) | (() => () => void);
|
||||
type EffectCallback = (() => undefined) | (() => () => void);
|
||||
|
||||
/**
|
||||
* Hook to run an effect in a focused screen, similar to `React.useEffect`.
|
||||
@@ -15,7 +15,7 @@ export default function useFocusEffect(callback: EffectCallback) {
|
||||
|
||||
React.useEffect(() => {
|
||||
let isFocused = false;
|
||||
let cleanup: (() => void) | void;
|
||||
let cleanup: (() => void) | undefined;
|
||||
|
||||
// We need to run the effect on intial render/dep changes if the screen is focused
|
||||
if (navigation.isFocused()) {
|
||||
@@ -30,19 +30,19 @@ export default function useFocusEffect(callback: EffectCallback) {
|
||||
return;
|
||||
}
|
||||
|
||||
cleanup && cleanup();
|
||||
cleanup?.();
|
||||
cleanup = callback();
|
||||
isFocused = true;
|
||||
});
|
||||
|
||||
const unsubscribeBlur = navigation.addListener('blur', () => {
|
||||
cleanup && cleanup();
|
||||
cleanup?.();
|
||||
cleanup = undefined;
|
||||
isFocused = false;
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanup && cleanup();
|
||||
cleanup?.();
|
||||
unsubscribeFocus();
|
||||
unsubscribeBlur();
|
||||
};
|
||||
|
||||
@@ -21,8 +21,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
||||
// Coz the child screen can't be focused if the parent screen is out of fcous
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigation &&
|
||||
navigation.addListener('focus', () =>
|
||||
navigation?.addListener('focus', () =>
|
||||
emitter.emit({ type: 'focus', target: currentFocusedKey })
|
||||
),
|
||||
[currentFocusedKey, emitter, navigation]
|
||||
@@ -30,8 +29,7 @@ export default function useFocusEvents({ state, emitter }: Options) {
|
||||
|
||||
React.useEffect(
|
||||
() =>
|
||||
navigation &&
|
||||
navigation.addListener('blur', () =>
|
||||
navigation?.addListener('blur', () =>
|
||||
emitter.emit({ type: 'blur', target: currentFocusedKey })
|
||||
),
|
||||
[currentFocusedKey, emitter, navigation]
|
||||
|
||||
@@ -38,7 +38,7 @@ export default function useFocusedListenersChildrenAdapter({
|
||||
[focusedListeners, navigation]
|
||||
);
|
||||
|
||||
React.useEffect(() => addFocusedListener && addFocusedListener(listener), [
|
||||
React.useEffect(() => addFocusedListener?.(listener), [
|
||||
addFocusedListener,
|
||||
listener,
|
||||
]);
|
||||
|
||||
@@ -117,7 +117,7 @@ export default function useNavigationBuilder<
|
||||
const { current: router } = React.useRef<Router<State, any>>(
|
||||
createRouter({
|
||||
...((rest as unknown) as RouterOptions),
|
||||
...(route && route.params && typeof route.params.screen === 'string'
|
||||
...(route?.params && typeof route.params.screen === 'string'
|
||||
? { initialRouteName: route.params.screen }
|
||||
: null),
|
||||
})
|
||||
@@ -141,7 +141,7 @@ export default function useNavigationBuilder<
|
||||
(acc, curr) => {
|
||||
const { initialParams } = screens[curr];
|
||||
const initialParamsFromParams =
|
||||
route && route.params && route.params.screen === curr
|
||||
route?.params && route.params.screen === curr
|
||||
? route.params.params
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -42,13 +42,22 @@ export default function useNavigationHelpers<
|
||||
const { performTransaction } = React.useContext(NavigationStateContext);
|
||||
|
||||
return React.useMemo(() => {
|
||||
const dispatch = (action: Action | ((state: State) => Action)) =>
|
||||
const dispatch = (action: Action | ((state: State) => Action)) => {
|
||||
performTransaction(() => {
|
||||
const payload =
|
||||
typeof action === 'function' ? action(getState()) : action;
|
||||
|
||||
onAction(payload);
|
||||
const handled = onAction(payload);
|
||||
|
||||
if (!handled && process.env.NODE_ENV !== 'production') {
|
||||
console.error(
|
||||
`The action '${payload.type}' with payload '${JSON.stringify(
|
||||
payload.payload
|
||||
)}' was not handled by any navigator.`
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const actions = {
|
||||
...router.actionCreators,
|
||||
@@ -81,7 +90,7 @@ export default function useNavigationHelpers<
|
||||
routeNames: state.routeNames,
|
||||
routeParamList: {},
|
||||
}) !== null ||
|
||||
(parentNavigationHelpers && parentNavigationHelpers.canGoBack()) ||
|
||||
parentNavigationHelpers?.canGoBack() ||
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
@@ -130,10 +130,10 @@ export default function useOnAction({
|
||||
]
|
||||
);
|
||||
|
||||
React.useEffect(
|
||||
() => addActionListenerParent && addActionListenerParent(onAction),
|
||||
[addActionListenerParent, onAction]
|
||||
);
|
||||
React.useEffect(() => addActionListenerParent?.(onAction), [
|
||||
addActionListenerParent,
|
||||
onAction,
|
||||
]);
|
||||
|
||||
return onAction;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,6 @@ export default function useOnGetState({
|
||||
}, [getState, getStateForRoute]);
|
||||
|
||||
React.useEffect(() => {
|
||||
return addStateGetter && addStateGetter(key, getRehydratedState);
|
||||
return addStateGetter?.(key, getRehydratedState);
|
||||
}, [addStateGetter, getRehydratedState, key]);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,49 @@
|
||||
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.34](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.33...@react-navigation/drawer@5.0.0-alpha.34) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.33](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.32...@react-navigation/drawer@5.0.0-alpha.33) (2020-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add interaction handle to drawer ([#239](https://github.com/react-navigation/navigation-ex/issues/239)) ([fa4411a](https://github.com/react-navigation/navigation-ex/commit/fa4411a14dc4aae568794e4b884088e3276a2876))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.32](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.31...@react-navigation/drawer@5.0.0-alpha.32) (2020-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* pass backBehavior to the router in drawer. fixes [#230](https://github.com/react-navigation/navigation-ex/issues/230) ([3cd1aed](https://github.com/react-navigation/navigation-ex/commit/3cd1aedcf490a4c7962b2d36873d714637f3b9b0))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.31](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.30...@react-navigation/drawer@5.0.0-alpha.31) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/drawer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.30](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/drawer@5.0.0-alpha.29...@react-navigation/drawer@5.0.0-alpha.30) (2019-12-19)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"material",
|
||||
"drawer"
|
||||
],
|
||||
"version": "5.0.0-alpha.30",
|
||||
"version": "5.0.0-alpha.34",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,8 +34,9 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18",
|
||||
"color": "^3.1.2"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20",
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
|
||||
@@ -23,6 +23,7 @@ type Props = DefaultNavigatorOptions<DrawerNavigationOptions> &
|
||||
|
||||
function DrawerNavigator({
|
||||
initialRouteName,
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
...rest
|
||||
@@ -34,6 +35,7 @@ function DrawerNavigator({
|
||||
DrawerNavigationEventMap
|
||||
>(DrawerRouter, {
|
||||
initialRouteName,
|
||||
backBehavior,
|
||||
children,
|
||||
screenOptions,
|
||||
});
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
StatusBar,
|
||||
StyleProp,
|
||||
View,
|
||||
InteractionManager,
|
||||
} from 'react-native';
|
||||
import {
|
||||
PanGestureHandler,
|
||||
@@ -161,9 +162,24 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
|
||||
componentWillUnmount() {
|
||||
this.toggleStatusBar(false);
|
||||
this.handleEndInteraction();
|
||||
}
|
||||
|
||||
private handleEndInteraction = () => {
|
||||
if (this.interactionHandle !== undefined) {
|
||||
InteractionManager.clearInteractionHandle(this.interactionHandle);
|
||||
this.interactionHandle = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
private handleStartInteraction = () => {
|
||||
if (this.interactionHandle === undefined) {
|
||||
this.interactionHandle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
};
|
||||
|
||||
private clock = new Clock();
|
||||
private interactionHandle: number | undefined;
|
||||
|
||||
private isDrawerTypeFront = new Value<Binary>(
|
||||
this.props.drawerType === 'front' ? TRUE : FALSE
|
||||
@@ -277,6 +293,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
set(state.velocity, this.velocityX),
|
||||
set(this.isOpen, isOpen),
|
||||
startClock(this.clock),
|
||||
call([], this.handleStartInteraction),
|
||||
set(this.manuallyTriggerSpring, FALSE),
|
||||
]),
|
||||
spring(this.clock, state, { ...SPRING_CONFIG, toValue }),
|
||||
@@ -288,8 +305,9 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
set(this.offsetX, 0),
|
||||
// When the animation finishes, stop the clock
|
||||
stopClock(this.clock),
|
||||
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
|
||||
call([this.isOpen], ([value]: readonly Binary[]) => {
|
||||
const open = Boolean(value);
|
||||
this.handleEndInteraction();
|
||||
|
||||
if (open !== this.props.open) {
|
||||
// Sync drawer's state after animation finished
|
||||
@@ -304,7 +322,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
private dragX = block([
|
||||
onChange(
|
||||
this.isOpen,
|
||||
call([this.isOpen], ([value]: ReadonlyArray<Binary>) => {
|
||||
call([this.isOpen], ([value]: readonly Binary[]) => {
|
||||
const open = Boolean(value);
|
||||
|
||||
this.currentOpenValue = open;
|
||||
@@ -344,7 +362,7 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
// Listen to updates for this value only when it changes
|
||||
// Without `onChange`, this will fire even if the value didn't change
|
||||
// We don't want to call the listeners if the value didn't change
|
||||
call([this.isSwiping], ([value]: ReadonlyArray<Binary>) => {
|
||||
call([this.isSwiping], ([value]: readonly Binary[]) => {
|
||||
const { keyboardDismissMode } = this.props;
|
||||
|
||||
if (value === TRUE) {
|
||||
@@ -358,6 +376,13 @@ export default class DrawerView extends React.PureComponent<Props> {
|
||||
}
|
||||
})
|
||||
),
|
||||
onChange(
|
||||
this.gestureState,
|
||||
cond(
|
||||
eq(this.gestureState, State.ACTIVE),
|
||||
call([], this.handleStartInteraction)
|
||||
)
|
||||
),
|
||||
cond(
|
||||
eq(this.gestureState, State.ACTIVE),
|
||||
[
|
||||
|
||||
@@ -13,7 +13,7 @@ const FAR_FAR_AWAY = 3000; // this should be big enough to move the whole view o
|
||||
|
||||
export default class ResourceSavingScene extends React.Component<Props> {
|
||||
render() {
|
||||
if (screensEnabled && screensEnabled()) {
|
||||
if (screensEnabled?.()) {
|
||||
const { isVisible, ...rest } = this.props;
|
||||
|
||||
// @ts-ignore
|
||||
|
||||
@@ -3,6 +3,17 @@ import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaConsumer,
|
||||
} from 'react-native-safe-area-context';
|
||||
import {
|
||||
getStatusBarHeight,
|
||||
getBottomSpace,
|
||||
} from 'react-native-iphone-x-helper';
|
||||
|
||||
const initialSafeAreaInsets = {
|
||||
top: getStatusBarHeight(true),
|
||||
bottom: getBottomSpace(),
|
||||
right: 0,
|
||||
left: 0,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
@@ -19,7 +30,11 @@ export default function SafeAreaProviderCompat({ children }: Props) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return <SafeAreaProvider>{children}</SafeAreaProvider>;
|
||||
return (
|
||||
<SafeAreaProvider initialSafeAreaInsets={initialSafeAreaInsets}>
|
||||
{children}
|
||||
</SafeAreaProvider>
|
||||
);
|
||||
}}
|
||||
</SafeAreaConsumer>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,22 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.29](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.28...@react-navigation/material-bottom-tabs@5.0.0-alpha.29) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.28](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.27...@react-navigation/material-bottom-tabs@5.0.0-alpha.28) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-bottom-tabs@5.0.0-alpha.26...@react-navigation/material-bottom-tabs@5.0.0-alpha.27) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"material",
|
||||
"tab"
|
||||
],
|
||||
"version": "5.0.0-alpha.27",
|
||||
"version": "5.0.0-alpha.29",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,7 +34,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
|
||||
@@ -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.27](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.26...@react-navigation/material-top-tabs@5.0.0-alpha.27) (2020-01-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for pager component ([dcc5f99](https://github.com/react-navigation/navigation-ex/commit/dcc5f99ecd495ad1903c9e99884e0d4e9b3994f1))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.26](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.25...@react-navigation/material-top-tabs@5.0.0-alpha.26) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/material-top-tabs
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.25](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/material-top-tabs@5.0.0-alpha.24...@react-navigation/material-top-tabs@5.0.0-alpha.25) (2019-12-19)
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"material",
|
||||
"tab"
|
||||
],
|
||||
"version": "5.0.0-alpha.25",
|
||||
"version": "5.0.0-alpha.27",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -34,7 +34,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18",
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20",
|
||||
"color": "^3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -113,9 +113,15 @@ export type MaterialTopTabNavigationConfig = Partial<
|
||||
| 'onSwipeEnd'
|
||||
| 'renderScene'
|
||||
| 'renderTabBar'
|
||||
| 'renderPager'
|
||||
| 'renderLazyPlaceholder'
|
||||
>
|
||||
> & {
|
||||
/**
|
||||
* Function that returns a React element to use as the pager.
|
||||
* The pager handles swipe gestures and page switching.
|
||||
*/
|
||||
pager?: React.ComponentProps<typeof TabView>['renderPager'];
|
||||
/**
|
||||
* Function that returns a React element to render for routes that haven't been rendered yet.
|
||||
* Receives an object containing the route as the prop.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import { TabView, SceneRendererProps } from 'react-native-tab-view';
|
||||
import { Route, useTheme } from '@react-navigation/native';
|
||||
import { useTheme } from '@react-navigation/native';
|
||||
import { TabNavigationState, TabActions } from '@react-navigation/routers';
|
||||
|
||||
import MaterialTopTabBar from './MaterialTopTabBar';
|
||||
@@ -19,6 +19,7 @@ type Props = MaterialTopTabNavigationConfig & {
|
||||
};
|
||||
|
||||
export default function MaterialTopTabView({
|
||||
pager,
|
||||
lazyPlaceholder,
|
||||
tabBar = (props: MaterialTopTabBarProps) => <MaterialTopTabBar {...props} />,
|
||||
tabBarOptions,
|
||||
@@ -30,14 +31,6 @@ export default function MaterialTopTabView({
|
||||
}: Props) {
|
||||
const { colors } = useTheme();
|
||||
|
||||
const renderLazyPlaceholder = (props: { route: Route<string> }) => {
|
||||
if (lazyPlaceholder != null) {
|
||||
return lazyPlaceholder(props);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderTabBar = (props: SceneRendererProps) => {
|
||||
const route = state.routes[state.index];
|
||||
const options = descriptors[route.key].options;
|
||||
@@ -69,7 +62,8 @@ export default function MaterialTopTabView({
|
||||
renderScene={({ route }) => descriptors[route.key].render()}
|
||||
navigationState={state}
|
||||
renderTabBar={renderTabBar}
|
||||
renderLazyPlaceholder={renderLazyPlaceholder}
|
||||
renderPager={pager}
|
||||
renderLazyPlaceholder={lazyPlaceholder}
|
||||
onSwipeStart={() => navigation.emit({ type: 'swipeStart' })}
|
||||
onSwipeEnd={() => navigation.emit({ type: 'swipeEnd' })}
|
||||
sceneContainerStyle={[
|
||||
|
||||
@@ -3,6 +3,30 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
||||
|
||||
# [5.0.0-alpha.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.21...@react-navigation/native-stack@5.0.0-alpha.22) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native-stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.20...@react-navigation/native-stack@5.0.0-alpha.21) (2020-01-03)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native-stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.19...@react-navigation/native-stack@5.0.0-alpha.20) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native-stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native-stack@5.0.0-alpha.18...@react-navigation/native-stack@5.0.0-alpha.19) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native-stack
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"react-native",
|
||||
"react-navigation"
|
||||
],
|
||||
"version": "5.0.0-alpha.19",
|
||||
"version": "5.0.0-alpha.22",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function HeaderConfig(props: Props) {
|
||||
? headerTintColor
|
||||
: colors.text
|
||||
}
|
||||
backTitle={headerBackTitleVisible ? headerBackTitle : ''}
|
||||
backTitle={headerBackTitleVisible ? headerBackTitle : ' '}
|
||||
backTitleFontFamily={headerBackTitleStyle.fontFamily}
|
||||
backTitleFontSize={headerBackTitleStyle.fontSize}
|
||||
color={headerTintColor !== undefined ? headerTintColor : colors.primary}
|
||||
|
||||
@@ -3,6 +3,14 @@
|
||||
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.22](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.21...@react-navigation/native@5.0.0-alpha.22) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.21](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/native@5.0.0-alpha.20...@react-navigation/native@5.0.0-alpha.21) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/native
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"ios",
|
||||
"android"
|
||||
],
|
||||
"version": "5.0.0-alpha.21",
|
||||
"version": "5.0.0-alpha.22",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -30,7 +30,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.0.0-alpha.29"
|
||||
"@react-navigation/core": "^5.0.0-alpha.30"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { Linking } from 'react-native';
|
||||
import {
|
||||
getActionFromState,
|
||||
getStateFromPath as getStateFromPathDefault,
|
||||
NavigationContainerRef,
|
||||
} from '@react-navigation/core';
|
||||
@@ -82,7 +83,13 @@ export default function useLinking(
|
||||
const state = getStateFromPathRef.current(path, configRef.current);
|
||||
|
||||
if (state) {
|
||||
navigation.resetRoot(state);
|
||||
const action = getActionFromState(state);
|
||||
|
||||
if (action.type === 'RESET_ROOT') {
|
||||
navigation.resetRoot(action.payload);
|
||||
} else {
|
||||
navigation.dispatch(action);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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.20](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.19...@react-navigation/routers@5.0.0-alpha.20) (2020-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* preserve focused route in tab on changing screens list ([adbeb29](https://github.com/react-navigation/navigation-ex/commit/adbeb292f522be8d7a58dd3f84e560a6d83d01a8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.19](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.18...@react-navigation/routers@5.0.0-alpha.19) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.18](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/routers@5.0.0-alpha.17...@react-navigation/routers@5.0.0-alpha.18) (2019-12-19)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/routers
|
||||
|
||||
@@ -250,6 +250,89 @@ it('gets state on route names change', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('preserves focused route on route names change', () => {
|
||||
const router = TabRouter({});
|
||||
|
||||
expect(
|
||||
router.getStateForRouteNamesChange(
|
||||
{
|
||||
index: 1,
|
||||
key: 'tab-test',
|
||||
routeKeyHistory: [],
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-test', name: 'bar' },
|
||||
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
|
||||
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
},
|
||||
{
|
||||
routeNames: ['qux', 'foo', 'fiz', 'baz'],
|
||||
routeParamList: {
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
index: 3,
|
||||
key: 'tab-test',
|
||||
routeKeyHistory: [],
|
||||
routeNames: ['qux', 'foo', 'fiz', 'baz'],
|
||||
routes: [
|
||||
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
|
||||
{ key: 'foo-test', name: 'foo' },
|
||||
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
|
||||
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to first route if route is removed on route names change', () => {
|
||||
const router = TabRouter({});
|
||||
|
||||
expect(
|
||||
router.getStateForRouteNamesChange(
|
||||
{
|
||||
index: 1,
|
||||
key: 'tab-test',
|
||||
routeKeyHistory: [],
|
||||
routeNames: ['bar', 'baz', 'qux'],
|
||||
routes: [
|
||||
{ key: 'bar-test', name: 'bar' },
|
||||
{ key: 'baz-test', name: 'baz', params: { answer: 42 } },
|
||||
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
},
|
||||
{
|
||||
routeNames: ['qux', 'foo', 'fiz'],
|
||||
routeParamList: {
|
||||
qux: { name: 'John' },
|
||||
fiz: { fruit: 'apple' },
|
||||
},
|
||||
}
|
||||
)
|
||||
).toEqual({
|
||||
index: 0,
|
||||
key: 'tab-test',
|
||||
routeKeyHistory: [],
|
||||
routeNames: ['qux', 'foo', 'fiz'],
|
||||
routes: [
|
||||
{ key: 'qux-test', name: 'qux', params: { name: 'Jane' } },
|
||||
{ key: 'foo-test', name: 'foo' },
|
||||
{ key: 'fiz-test', name: 'fiz', params: { fruit: 'apple' } },
|
||||
],
|
||||
stale: false,
|
||||
type: 'tab',
|
||||
});
|
||||
});
|
||||
|
||||
it('handles navigate action', () => {
|
||||
const router = TabRouter({});
|
||||
const options = {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"react-native",
|
||||
"react-navigation"
|
||||
],
|
||||
"version": "5.0.0-alpha.18",
|
||||
"version": "5.0.0-alpha.20",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -29,7 +29,7 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/core": "^5.0.0-alpha.29",
|
||||
"@react-navigation/core": "^5.0.0-alpha.30",
|
||||
"shortid": "^2.2.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -147,11 +147,16 @@ export default function TabRouter({
|
||||
}
|
||||
);
|
||||
|
||||
const index = Math.max(
|
||||
0,
|
||||
routeNames.indexOf(state.routes[state.index].name)
|
||||
);
|
||||
|
||||
return {
|
||||
...state,
|
||||
routeNames,
|
||||
routes,
|
||||
index: Math.min(state.index, routes.length - 1),
|
||||
index,
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
@@ -3,6 +3,120 @@
|
||||
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.56](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.55...@react-navigation/stack@5.0.0-alpha.56) (2020-01-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove clamping in extrapolation of progress of stack animation ([d3f5c55](https://github.com/react-navigation/navigation-ex/commit/d3f5c55dbfbbff604c3289a40f3eccd91a60ee2e))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.55](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.54...@react-navigation/stack@5.0.0-alpha.55) (2020-01-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* memoize interpolated style to avoid extra work ([d8b88bd](https://github.com/react-navigation/navigation-ex/commit/d8b88bd83f57f2626d5b66bb157fd8e21a937c28))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.54](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.53...@react-navigation/stack@5.0.0-alpha.54) (2020-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* expose the header height even if not floating ([12d9083](https://github.com/react-navigation/navigation-ex/commit/12d90833eb36e9e7f229384ec8a05823b0a564d1))
|
||||
* use memo for card container ([65ce20e](https://github.com/react-navigation/navigation-ex/commit/65ce20ecbc1e5f2dba9f1004cb29de03a6e5504a))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.53](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.52...@react-navigation/stack@5.0.0-alpha.53) (2020-01-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* compare with correct height when floating header height updates ([a9e584c](https://github.com/react-navigation/navigation-ex/commit/a9e584c3b765ae1e166a3a82b3fa0a40e8e2172a))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* expose header height in context ([133b59c](https://github.com/react-navigation/navigation-ex/commit/133b59cd175ddc899dff3b56bf3a0514c0c91ae6))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.52](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.51...@react-navigation/stack@5.0.0-alpha.52) (2020-01-05)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add headerStatusBarHeight option to stack ([b201fd2](https://github.com/react-navigation/navigation-ex/commit/b201fd20716a2f03cb9373c72281f5d396a9356d))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.51](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.50...@react-navigation/stack@5.0.0-alpha.51) (2020-01-05)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.50](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.49...@react-navigation/stack@5.0.0-alpha.50) (2020-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* keep screens for replace when animation is enabled ([7f963a7](https://github.com/react-navigation/navigation-ex/commit/7f963a74bb4d4b04134e917fe47e38e4d982afed))
|
||||
* use gesture direction when using next screen's animation ([572beae](https://github.com/react-navigation/navigation-ex/commit/572beae41b326f3ef80bc2b790badf123e0071bc))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.49](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.48...@react-navigation/stack@5.0.0-alpha.49) (2020-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* dismiss keyboard on page change ([2c31d17](https://github.com/react-navigation/navigation-ex/commit/2c31d1705c4e5827b19b9cc7f3e5b05207c3238a))
|
||||
* interaction manager in stack ([#237](https://github.com/react-navigation/navigation-ex/issues/237)) ([6b9b999](https://github.com/react-navigation/navigation-ex/commit/6b9b999c5b60a67ed683b84484928700d4260585))
|
||||
* provide initial values for safe area to prevent blank screen ([#238](https://github.com/react-navigation/navigation-ex/issues/238)) ([77b7570](https://github.com/react-navigation/navigation-ex/commit/77b757091c0451e20bca01138629669c7da544a8))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.48](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.47...@react-navigation/stack@5.0.0-alpha.48) (2020-01-01)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* improve gesture performance ([59803f5](https://github.com/react-navigation/navigation-ex/commit/59803f54d64f85c8e46c1ebc70613a70a812f53a))
|
||||
* use native driver for gestures ([9356598](https://github.com/react-navigation/navigation-ex/commit/935659899f1d4084c601fbefea4a935f9b6ce087))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.47](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.46...@react-navigation/stack@5.0.0-alpha.47) (2020-01-01)
|
||||
|
||||
**Note:** Version bump only for package @react-navigation/stack
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# [5.0.0-alpha.46](https://github.com/react-navigation/navigation-ex/compare/@react-navigation/stack@5.0.0-alpha.45...@react-navigation/stack@5.0.0-alpha.46) (2019-12-19)
|
||||
|
||||
|
||||
|
||||
@@ -10,18 +10,18 @@ Open a Terminal in your project's folder and run,
|
||||
yarn add @react-navigation/native @react-navigation/stack @react-native-community/masked-view
|
||||
```
|
||||
|
||||
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-reanimated`](https://github.com/kmagiera/react-native-reanimated), [`react-native-screens`](https://github.com/kmagiera/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
|
||||
Now we need to install [`react-native-gesture-handler`](https://github.com/kmagiera/react-native-gesture-handler), [`react-native-screens`](https://github.com/kmagiera/react-native-screens) and [`react-native-safe-area-context`](https://github.com/th3rdwave/react-native-safe-area-context).
|
||||
|
||||
If you are using Expo, to ensure that you get the compatible versions of the libraries, run:
|
||||
|
||||
```sh
|
||||
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context
|
||||
expo install react-native-gesture-handler react-native-screens react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are not using Expo, run the following:
|
||||
|
||||
```sh
|
||||
yarn add react-native-reanimated react-native-gesture-handler react-native-screens react-native-safe-area-context
|
||||
yarn add react-native-gesture-handler react-native-screens react-native-safe-area-context
|
||||
```
|
||||
|
||||
If you are using Expo, you are done. Otherwise, continue to the next steps.
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"android",
|
||||
"stack"
|
||||
],
|
||||
"version": "5.0.0-alpha.46",
|
||||
"version": "5.0.0-alpha.56",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -33,8 +33,9 @@
|
||||
"clean": "del lib"
|
||||
},
|
||||
"dependencies": {
|
||||
"@react-navigation/routers": "^5.0.0-alpha.18",
|
||||
"color": "^3.1.2"
|
||||
"@react-navigation/routers": "^5.0.0-alpha.20",
|
||||
"color": "^3.1.2",
|
||||
"react-native-iphone-x-helper": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/bob": "^0.7.0",
|
||||
@@ -46,7 +47,6 @@
|
||||
"react": "~16.9.0",
|
||||
"react-native": "~0.61.5",
|
||||
"react-native-gesture-handler": "^1.5.0",
|
||||
"react-native-reanimated": "^1.4.0",
|
||||
"react-native-safe-area-context": "^0.6.0",
|
||||
"react-native-screens": "^2.0.0-alpha.19",
|
||||
"typescript": "^3.7.3"
|
||||
@@ -57,7 +57,6 @@
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "^1.0.0",
|
||||
"react-native-reanimated": "^1.0.0",
|
||||
"react-native-safe-area-context": "^0.6.0",
|
||||
"react-native-screens": "^1.0.0-alpha.0 || ^2.0.0-alpha.0"
|
||||
},
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { Animated } from 'react-native';
|
||||
import conditional from '../utils/conditional';
|
||||
import {
|
||||
StackCardInterpolationProps,
|
||||
StackCardInterpolatedStyle,
|
||||
} from '../types';
|
||||
|
||||
const { cond, add, multiply, interpolate } = Animated;
|
||||
const { add, multiply } = Animated;
|
||||
|
||||
/**
|
||||
* Standard iOS-style slide in from the right.
|
||||
@@ -16,7 +17,7 @@ export function forHorizontalIOS({
|
||||
layouts: { screen },
|
||||
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
|
||||
const translateFocused = multiply(
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [screen.width, 0],
|
||||
}),
|
||||
@@ -25,20 +26,20 @@ export function forHorizontalIOS({
|
||||
|
||||
const translateUnfocused = next
|
||||
? multiply(
|
||||
interpolate(next.progress, {
|
||||
next.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, multiply(screen.width, -0.3)],
|
||||
outputRange: [0, screen.width * -0.3],
|
||||
}),
|
||||
inverted
|
||||
)
|
||||
: 0;
|
||||
|
||||
const overlayOpacity = interpolate(current.progress, {
|
||||
const overlayOpacity = current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 0.07],
|
||||
});
|
||||
|
||||
const shadowOpacity = interpolate(current.progress, {
|
||||
const shadowOpacity = current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 0.3],
|
||||
});
|
||||
@@ -66,7 +67,7 @@ export function forVerticalIOS({
|
||||
layouts: { screen },
|
||||
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
|
||||
const translateY = multiply(
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [screen.height, 0],
|
||||
}),
|
||||
@@ -102,7 +103,7 @@ export function forModalPresentationIOS({
|
||||
const progress = add(current.progress, next ? next.progress : 0);
|
||||
|
||||
const translateY = multiply(
|
||||
interpolate(progress, {
|
||||
progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [
|
||||
screen.height,
|
||||
@@ -113,14 +114,14 @@ export function forModalPresentationIOS({
|
||||
inverted
|
||||
);
|
||||
|
||||
const overlayOpacity = interpolate(progress, {
|
||||
const overlayOpacity = progress.interpolate({
|
||||
inputRange: [0, 1, 1.0001, 2],
|
||||
outputRange: [0, 0.3, 1, 1],
|
||||
});
|
||||
|
||||
const scale = isLandscape
|
||||
? 1
|
||||
: interpolate(progress, {
|
||||
: progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [
|
||||
1,
|
||||
@@ -132,7 +133,7 @@ export function forModalPresentationIOS({
|
||||
const borderRadius = isLandscape
|
||||
? 0
|
||||
: index === 0
|
||||
? interpolate(progress, {
|
||||
? progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 0, 10],
|
||||
})
|
||||
@@ -160,17 +161,17 @@ export function forFadeFromBottomAndroid({
|
||||
closing,
|
||||
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
|
||||
const translateY = multiply(
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [multiply(screen.height, 0.08), 0],
|
||||
outputRange: [screen.height * 0.08, 0],
|
||||
}),
|
||||
inverted
|
||||
);
|
||||
|
||||
const opacity = cond(
|
||||
const opacity = conditional(
|
||||
closing,
|
||||
current.progress,
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 0.5, 0.9, 1],
|
||||
outputRange: [0, 0.25, 0.7, 1],
|
||||
})
|
||||
@@ -194,7 +195,7 @@ export function forRevealFromBottomAndroid({
|
||||
layouts: { screen },
|
||||
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
|
||||
const containerTranslateY = multiply(
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [screen.height, 0],
|
||||
}),
|
||||
@@ -202,24 +203,24 @@ export function forRevealFromBottomAndroid({
|
||||
);
|
||||
|
||||
const cardTranslateYFocused = multiply(
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [multiply(screen.height, 95.9 / 100, -1), 0],
|
||||
outputRange: [screen.height * (95.9 / 100) * -1, 0],
|
||||
}),
|
||||
inverted
|
||||
);
|
||||
|
||||
const cardTranslateYUnfocused = next
|
||||
? multiply(
|
||||
interpolate(next.progress, {
|
||||
next.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, multiply(screen.height, 2 / 100, -1)],
|
||||
outputRange: [0, screen.height * (2 / 100) * -1],
|
||||
}),
|
||||
inverted
|
||||
)
|
||||
: 0;
|
||||
|
||||
const overlayOpacity = interpolate(current.progress, {
|
||||
const overlayOpacity = current.progress.interpolate({
|
||||
inputRange: [0, 0.36, 1],
|
||||
outputRange: [0, 0.1, 0.1],
|
||||
});
|
||||
@@ -249,18 +250,18 @@ export function forScaleFromCenterAndroid({
|
||||
}: StackCardInterpolationProps): StackCardInterpolatedStyle {
|
||||
const progress = add(current.progress, next ? next.progress : 0);
|
||||
|
||||
const opacity = interpolate(progress, {
|
||||
const opacity = progress.interpolate({
|
||||
inputRange: [0, 0.8, 1, 1.2, 2],
|
||||
outputRange: [0, 0.5, 1, 0.33, 0],
|
||||
});
|
||||
|
||||
const scale = cond(
|
||||
const scale = conditional(
|
||||
closing,
|
||||
interpolate(current.progress, {
|
||||
current.progress.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0.9, 1],
|
||||
}),
|
||||
interpolate(progress, {
|
||||
progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0.85, 1, 1.1],
|
||||
})
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { I18nManager } from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { Animated, I18nManager } from 'react-native';
|
||||
import {
|
||||
StackHeaderInterpolationProps,
|
||||
StackHeaderInterpolatedStyle,
|
||||
} from '../types';
|
||||
|
||||
const { interpolate, add } = Animated;
|
||||
const { add } = Animated;
|
||||
|
||||
/**
|
||||
* Standard UIKit style animation for the header where the title fades into the back button label.
|
||||
@@ -38,7 +37,7 @@ export function forUIKit({
|
||||
|
||||
return {
|
||||
leftButtonStyle: {
|
||||
opacity: interpolate(progress, {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0.3, 1, 1.5],
|
||||
outputRange: [0, 1, 0],
|
||||
}),
|
||||
@@ -46,7 +45,7 @@ export function forUIKit({
|
||||
leftLabelStyle: {
|
||||
transform: [
|
||||
{
|
||||
translateX: interpolate(progress, {
|
||||
translateX: progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: I18nManager.isRTL
|
||||
? [-rightOffset, 0, leftLabelOffset]
|
||||
@@ -56,19 +55,19 @@ export function forUIKit({
|
||||
],
|
||||
},
|
||||
rightButtonStyle: {
|
||||
opacity: interpolate(progress, {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0.3, 1, 1.5],
|
||||
outputRange: [0, 1, 0],
|
||||
}),
|
||||
},
|
||||
titleStyle: {
|
||||
opacity: interpolate(progress, {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0, 0.4, 1, 1.5],
|
||||
outputRange: [0, 0.1, 1, 0],
|
||||
}),
|
||||
transform: [
|
||||
{
|
||||
translateX: interpolate(progress, {
|
||||
translateX: progress.interpolate({
|
||||
inputRange: [0.5, 1, 2],
|
||||
outputRange: I18nManager.isRTL
|
||||
? [-titleLeftOffset, 0, rightOffset]
|
||||
@@ -80,7 +79,7 @@ export function forUIKit({
|
||||
backgroundStyle: {
|
||||
transform: [
|
||||
{
|
||||
translateX: interpolate(progress, {
|
||||
translateX: progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: I18nManager.isRTL
|
||||
? [-layouts.screen.width, 0, layouts.screen.width]
|
||||
@@ -100,7 +99,7 @@ export function forFade({
|
||||
next,
|
||||
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
|
||||
const progress = add(current.progress, next ? next.progress : 0);
|
||||
const opacity = interpolate(progress, {
|
||||
const opacity = progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 1, 0],
|
||||
});
|
||||
@@ -110,7 +109,7 @@ export function forFade({
|
||||
rightButtonStyle: { opacity },
|
||||
titleStyle: { opacity },
|
||||
backgroundStyle: {
|
||||
opacity: interpolate(progress, {
|
||||
opacity: progress.interpolate({
|
||||
inputRange: [0, 1, 1.9, 2],
|
||||
outputRange: [0, 1, 1, 0],
|
||||
}),
|
||||
@@ -127,7 +126,7 @@ export function forStatic({
|
||||
layouts: { screen },
|
||||
}: StackHeaderInterpolationProps): StackHeaderInterpolatedStyle {
|
||||
const progress = add(current.progress, next ? next.progress : 0);
|
||||
const translateX = interpolate(progress, {
|
||||
const translateX = progress.interpolate({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: I18nManager.isRTL
|
||||
? [-screen.width, 0, screen.width]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Easing } from 'react-native-reanimated';
|
||||
import { Easing } from 'react-native';
|
||||
import { TransitionSpec } from '../types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,9 @@ import * as TransitionPresets from './TransitionConfigs/TransitionPresets';
|
||||
export { default as createStackNavigator } from './navigators/createStackNavigator';
|
||||
|
||||
export const Assets = [
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
require('./views/assets/back-icon.png'),
|
||||
// eslint-disable-next-line import/no-commonjs
|
||||
require('./views/assets/back-icon-mask.png'),
|
||||
];
|
||||
|
||||
@@ -34,8 +36,13 @@ export {
|
||||
/**
|
||||
* Utilities
|
||||
*/
|
||||
export { default as StackGestureContext } from './utils/StackGestureContext';
|
||||
export { default as StackCardAnimationContext } from './utils/StackCardAnimationContext';
|
||||
export { default as CardAnimationContext } from './utils/CardAnimationContext';
|
||||
export { default as HeaderHeightContext } from './utils/HeaderHeightContext';
|
||||
export { default as GestureHandlerRefContext } from './utils/GestureHandlerRefContext';
|
||||
|
||||
export { default as useCardAnimation } from './utils/useCardAnimation';
|
||||
export { default as useHeaderHeight } from './utils/useHeaderHeight';
|
||||
export { default as useGestureHandlerRef } from './utils/useGestureHandlerRef';
|
||||
|
||||
/**
|
||||
* Types
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import {
|
||||
Animated,
|
||||
EasingFunction,
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
ViewStyle,
|
||||
LayoutChangeEvent,
|
||||
} from 'react-native';
|
||||
import Animated from 'react-native-reanimated';
|
||||
import { EdgeInsets } from 'react-native-safe-area-context';
|
||||
import {
|
||||
NavigationProp,
|
||||
@@ -88,22 +89,24 @@ export type Scene<T> = {
|
||||
/**
|
||||
* Progress value of the current screen.
|
||||
*/
|
||||
current: Animated.Node<number>;
|
||||
current: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* 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>;
|
||||
next?: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Progress value for the screen before this one in the stack.
|
||||
* This can be `undefined` in case the screen animating is the first one.
|
||||
*/
|
||||
previous?: Animated.Node<number>;
|
||||
previous?: Animated.AnimatedInterpolation;
|
||||
};
|
||||
};
|
||||
|
||||
export type StackHeaderMode = 'float' | 'screen' | 'none';
|
||||
|
||||
export type StackCardMode = 'card' | 'modal';
|
||||
|
||||
export type StackHeaderOptions = {
|
||||
/**
|
||||
* String or a function that returns a React Element to be used by the header.
|
||||
@@ -202,6 +205,12 @@ export type StackHeaderOptions = {
|
||||
* This is useful if you want to render a semi-transparent header or a blurred background.
|
||||
*/
|
||||
headerTransparent?: boolean;
|
||||
/**
|
||||
* Extra padding to add at the top of header to account for translucent status bar.
|
||||
* By default, it uses the top value from the safe area insets of the device.
|
||||
* Pass 0 or a custom value to disable the default behaviour, and customize the height.
|
||||
*/
|
||||
headerStatusBarHeight?: number;
|
||||
};
|
||||
|
||||
export type StackHeaderProps = {
|
||||
@@ -322,7 +331,7 @@ export type StackNavigationOptions = StackHeaderOptions &
|
||||
};
|
||||
|
||||
export type StackNavigationConfig = {
|
||||
mode?: 'card' | 'modal';
|
||||
mode?: StackCardMode;
|
||||
headerMode?: StackHeaderMode;
|
||||
/**
|
||||
* If `false`, the keyboard will NOT automatically dismiss when navigating to a new screen.
|
||||
@@ -433,7 +442,7 @@ export type SpringConfig = {
|
||||
|
||||
export type TimingConfig = {
|
||||
duration: number;
|
||||
easing: Animated.EasingFunction;
|
||||
easing: EasingFunction;
|
||||
};
|
||||
|
||||
export type TransitionSpec =
|
||||
@@ -448,7 +457,7 @@ export type StackCardInterpolationProps = {
|
||||
/**
|
||||
* Animated node representing the progress value of the current screen.
|
||||
*/
|
||||
progress: Animated.Node<number>;
|
||||
progress: Animated.AnimatedInterpolation;
|
||||
};
|
||||
/**
|
||||
* Values for the current screen the screen after this one in the stack.
|
||||
@@ -458,24 +467,24 @@ export type StackCardInterpolationProps = {
|
||||
/**
|
||||
* Animated node representing the progress value of the next screen.
|
||||
*/
|
||||
progress: Animated.Node<number>;
|
||||
progress: Animated.AnimatedInterpolation;
|
||||
};
|
||||
/**
|
||||
* The index of the card in the stack.
|
||||
*/
|
||||
index: number;
|
||||
/**
|
||||
* Animated node representing whether the card is closing.
|
||||
* Animated node representing whether the card is closing (1 - closing, 0 - not closing).
|
||||
*/
|
||||
closing: Animated.Node<0 | 1>;
|
||||
closing: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Animated node representing whether the card is being swiped.
|
||||
* Animated node representing whether the card is being swiped (1 - swiping, 0 - not swiping).
|
||||
*/
|
||||
swiping: Animated.Node<0 | 1>;
|
||||
swiping: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Animated node representing multiplier when direction is inverted.
|
||||
* Animated node representing multiplier when direction is inverted (-1 - inverted, 1 - normal).
|
||||
*/
|
||||
inverted: Animated.Node<-1 | 1>;
|
||||
inverted: Animated.AnimatedInterpolation;
|
||||
/**
|
||||
* Layout measurements for various items we use for animation.
|
||||
*/
|
||||
@@ -527,7 +536,7 @@ export type StackHeaderInterpolationProps = {
|
||||
/**
|
||||
* Animated node representing the progress value of the current screen.
|
||||
*/
|
||||
progress: Animated.Node<number>;
|
||||
progress: Animated.AnimatedInterpolation;
|
||||
};
|
||||
/**
|
||||
* Values for the current screen the screen after this one in the stack.
|
||||
@@ -537,7 +546,7 @@ export type StackHeaderInterpolationProps = {
|
||||
/**
|
||||
* Animated node representing the progress value of the next screen.
|
||||
*/
|
||||
progress: Animated.Node<number>;
|
||||
progress: Animated.AnimatedInterpolation;
|
||||
};
|
||||
/**
|
||||
* Layout measurements for various items we use for animation.
|
||||
|
||||
3
packages/stack/src/utils/HeaderHeightContext.tsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export default React.createContext<number | undefined>(undefined);
|
||||
33
packages/stack/src/utils/conditional.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Animated } from 'react-native';
|
||||
|
||||
const { add, multiply } = Animated;
|
||||
|
||||
/**
|
||||
* Use an Animated Node based on a condition. Similar to Reanimated's `cond`.
|
||||
*
|
||||
* @param condition Animated Node representing the condition, must be 0 or 1, 1 means `true`, 0 means `false`
|
||||
* @param main Animated Node to use if the condition is `true`
|
||||
* @param fallback Animated Node to use if the condition is `false`
|
||||
*/
|
||||
export default function conditional(
|
||||
condition: Animated.AnimatedInterpolation,
|
||||
main: Animated.AnimatedInterpolation,
|
||||
fallback: Animated.AnimatedInterpolation
|
||||
) {
|
||||
// To implement this behavior, we multiply the main node with the condition.
|
||||
// So if condition is 0, result will be 0, and if condition is 1, result will be main node.
|
||||
// Then we multiple reverse of the condition (0 if condition is 1) with the fallback.
|
||||
// So if condition is 0, result will be fallback node, and if condition is 1, result will be 0,
|
||||
// This way, one of them will always be 0, and other one will be the value we need.
|
||||
// In the end we add them both together, 0 + value we need = value we need
|
||||
return add(
|
||||
multiply(condition, main),
|
||||
multiply(
|
||||
condition.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [1, 0],
|
||||
}),
|
||||
fallback
|
||||
)
|
||||
);
|
||||
}
|
||||
18
packages/stack/src/utils/getDistanceForDirection.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import getInvertedMultiplier from './getInvertedMultiplier';
|
||||
import { GestureDirection, Layout } from '../types';
|
||||
|
||||
export default function getDistanceForDirection(
|
||||
layout: Layout,
|
||||
gestureDirection: GestureDirection
|
||||
): number {
|
||||
const multiplier = getInvertedMultiplier(gestureDirection);
|
||||
|
||||
switch (gestureDirection) {
|
||||
case 'vertical':
|
||||
case 'vertical-inverted':
|
||||
return layout.height * multiplier;
|
||||
case 'horizontal':
|
||||
case 'horizontal-inverted':
|
||||
return layout.width * multiplier;
|
||||
}
|
||||
}
|
||||
17
packages/stack/src/utils/getInvertedMultiplier.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { I18nManager } from 'react-native';
|
||||
import { GestureDirection } from '../types';
|
||||
|
||||
export default function getInvertedMultiplier(
|
||||
gestureDirection: GestureDirection
|
||||
): 1 | -1 {
|
||||
switch (gestureDirection) {
|
||||
case 'vertical':
|
||||
return 1;
|
||||
case 'vertical-inverted':
|
||||
return -1;
|
||||
case 'horizontal':
|
||||
return I18nManager.isRTL ? -1 : 1;
|
||||
case 'horizontal-inverted':
|
||||
return I18nManager.isRTL ? 1 : -1;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
export default function memoize<Result, Deps extends ReadonlyArray<any>>(
|
||||
export default function memoize<Result, Deps extends readonly any[]>(
|
||||
callback: (...deps: Deps) => Result
|
||||
) {
|
||||
let previous: Deps | undefined;
|
||||
|
||||
14
packages/stack/src/utils/useCardAnimation.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import CardAnimationContext from './CardAnimationContext';
|
||||
|
||||
export default function useCardAnimation() {
|
||||
const animation = React.useContext(CardAnimationContext);
|
||||
|
||||
if (animation === undefined) {
|
||||
throw new Error(
|
||||
"Couldn't find values for card animation. Are you inside a screen in Stack?"
|
||||
);
|
||||
}
|
||||
|
||||
return animation;
|
||||
}
|
||||