Compare commits

...

11 Commits

Author SHA1 Message Date
Satyajit Sahoo
c8dd70a033 chore: publish
- @react-navigation/bottom-tabs@6.0.0-next.4
 - @react-navigation/core@6.0.0-next.2
 - @react-navigation/devtools@6.0.0-next.2
 - @react-navigation/drawer@6.0.0-next.4
 - @react-navigation/elements@1.0.0-next.4
 - @react-navigation/material-bottom-tabs@6.0.0-next.2
 - @react-navigation/material-top-tabs@6.0.0-next.2
 - @react-navigation/native@6.0.0-next.2
 - @react-navigation/routers@6.0.0-next.2
 - @react-navigation/stack@6.0.0-next.9
2021-04-08 06:19:12 +02:00
Satyajit Sahoo
277fec481b chore: fix dist-tag for publishing 2021-04-08 06:14:41 +02:00
Satyajit Sahoo
3241190b19 fix: fix drawer overlay on web 2021-04-08 05:46:18 +02:00
Satyajit Sahoo
ef7370b215 chore: configure reanimated 2 2021-04-04 06:55:37 +02:00
Satyajit Sahoo
1149e718c1 refactor: add reanimated 2 implementation for drawer 2021-04-04 06:43:12 +02:00
Satyajit Sahoo
d85a4fd8ed chore: upgrade expo to SDK 41 2021-04-04 06:41:26 +02:00
Satyajit Sahoo
b89396888f fix: don't handle back button with permanent drawer 2021-04-04 01:09:05 +02:00
WoLewicki
c38906a7a0 fix: properly resolve initialRouteNames 2021-04-04 01:08:57 +02:00
Janic Duplessis
d87857e5d9 fix: remove calls to removed Keyboard.removeListener in useIsKeyboardShown (#9457) 2021-04-04 01:06:55 +02:00
Satyajit Sahoo
5ae0badc44 fix: only handle back button in drawer when focused 2021-04-02 05:21:32 +02:00
Jemmy Phan
84020a0b27 feat: improve useNavigationState typing (#9464) 2021-03-29 05:42:19 +02:00
42 changed files with 2757 additions and 3241 deletions

View File

@@ -2,5 +2,6 @@ module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: ['react-native-reanimated/plugin'],
};
};

View File

@@ -13,50 +13,51 @@
"test": "jest"
},
"dependencies": {
"@expo/vector-icons": "^12.0.3",
"@react-native-masked-view/masked-view": "0.2.0",
"@expo/vector-icons": "^12.0.0",
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-masked-view/masked-view": "0.2.3",
"color": "^3.1.3",
"expo": "^40.0.1",
"expo-asset": "~8.2.2",
"expo-blur": "~9.0.0",
"expo-linking": "~2.1.1",
"expo-updates": "~0.4.2",
"expo": "^41.0.0-beta.2",
"expo-asset": "~8.3.1",
"expo-blur": "~9.0.3",
"expo-linking": "~2.2.1",
"expo-updates": "~0.5.3",
"koa": "^2.13.0",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "https://github.com/expo/react-native/archive/sdk-40.0.0.tar.gz",
"react-native": "https://github.com/expo/react-native/archive/sdk-41.0.0.tar.gz",
"react-native-appearance": "~0.3.3",
"react-native-gesture-handler": "~1.8.0",
"react-native-pager-view": "^4.2.4",
"react-native-gesture-handler": "~1.10.2",
"react-native-pager-view": "~5.0.12",
"react-native-paper": "^4.7.2",
"react-native-reanimated": "~1.13.0",
"react-native-safe-area-context": "3.1.9",
"react-native-screens": "~2.15.0",
"react-native-tab-view": "^3.0.0",
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"react-native-tab-view": "^3.0.1",
"react-native-vector-icons": "^8.1.0",
"react-native-web": "~0.15.0"
},
"devDependencies": {
"@babel/node": "^7.13.0",
"@expo/webpack-config": "~0.12.60",
"@types/cheerio": "^0.22.24",
"@babel/node": "^7.13.13",
"@expo/webpack-config": "~0.12.63",
"@types/cheerio": "^0.22.28",
"@types/jest-dev-server": "^4.2.0",
"@types/koa": "^2.13.1",
"@types/node-fetch": "^2.5.8",
"@types/node-fetch": "^2.5.9",
"@types/react": "~16.9.35",
"@types/react-dom": "~16.9.8",
"@types/react-native": "~0.63.51",
"@types/react-native": "~0.63.2",
"babel-loader": "^8.2.2",
"babel-plugin-module-resolver": "^4.0.0",
"babel-preset-expo": "8.3.0",
"cheerio": "^1.0.0-rc.3",
"expo-cli": "^4.3.0",
"expo-cli": "^4.3.4",
"jest": "^26.6.3",
"jest-dev-server": "^4.4.0",
"mock-require-assets": "^0.0.1",
"node-fetch": "^2.6.1",
"nodemon": "^2.0.6",
"playwright": "^1.9.1",
"playwright": "^1.10.0",
"serve": "^11.3.0",
"typescript": "~4.2.3"
}

View File

@@ -1,2 +0,0 @@
import * as Linking from 'expo-linking';
export default [Linking.makeUrl('/')];

View File

@@ -1 +0,0 @@
export default ['rne://127.0.0.1:19000/--/'];

View File

@@ -1,6 +1,5 @@
import * as React from 'react';
import {
AsyncStorage,
ScrollView,
Platform,
StatusBar,
@@ -21,6 +20,8 @@ import {
Divider,
Text,
} from 'react-native-paper';
import { createURL } from 'expo-linking';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {
InitialState,
NavigationContainer,
@@ -38,7 +39,6 @@ import {
import { useReduxDevToolsExtension } from '@react-navigation/devtools';
import { restartApp } from './Restart';
import LinkingPrefixes from './LinkingPrefixes';
import SettingsItem from './Shared/SettingsItem';
import SimpleStack from './Screens/SimpleStack';
import ModalStack from './Screens/ModalStack';
@@ -225,7 +225,7 @@ export default function App() {
// Android (bare): adb shell am start -a android.intent.action.VIEW -d "rne://127.0.0.1:19000/--/simple-stack"
// iOS (bare): xcrun simctl openurl booted rne://127.0.0.1:19000/--/simple-stack
// The first segment of the link is the the scheme + host (returned by `Linking.makeUrl`)
prefixes: LinkingPrefixes,
prefixes: [createURL('/')],
config: {
initialRouteName: 'Home',
screens: Object.keys(SCREENS).reduce<PathConfigMap>(

View File

@@ -11,6 +11,7 @@
"allowBranch": "main",
"conventionalCommits": true,
"createRelease": "github",
"distTag": "next",
"message": "chore: publish",
"ignoreChanges": [
"**/__fixtures__/**",

View File

@@ -8,8 +8,7 @@
]
},
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"license": "MIT",
"repository": {
@@ -26,13 +25,13 @@
"example": "yarn --cwd example"
},
"devDependencies": {
"@commitlint/config-conventional": "^12.0.1",
"@types/jest": "^26.0.19",
"@commitlint/config-conventional": "^12.1.1",
"@types/jest": "^26.0.22",
"babel-jest": "^26.6.3",
"codecov": "^3.8.1",
"commitlint": "^12.0.1",
"eslint": "^7.21.0",
"eslint-config-satya164": "^3.1.9",
"commitlint": "^12.1.1",
"eslint": "^7.23.0",
"eslint-config-satya164": "^3.1.10",
"husky": "^4.3.6",
"jest": "^26.6.3",
"lerna": "^4.0.0",

View File

@@ -3,6 +3,17 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.3...@react-navigation/bottom-tabs@6.0.0-next.4) (2021-04-08)
### Bug Fixes
* remove calls to removed Keyboard.removeListener in useIsKeyboardShown ([#9457](https://github.com/react-navigation/react-navigation/issues/9457)) ([d87857e](https://github.com/react-navigation/react-navigation/commit/d87857e5d93c19ebee2fd84eb4910e36001ec2a3))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/bottom-tabs@6.0.0-next.2...@react-navigation/bottom-tabs@6.0.0-next.3) (2021-03-22)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/bottom-tabs",
"description": "Bottom tab navigator following iOS design guidelines",
"version": "6.0.0-next.3",
"version": "6.0.0-next.4",
"keywords": [
"react-native-component",
"react-component",
@@ -29,30 +29,29 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.3",
"@react-navigation/elements": "^1.0.0-next.4",
"color": "^3.1.3",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.1",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-safe-area-context": "3.1.9",
"react-native-screens": "~2.15.0",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"typescript": "^4.2.3"
},
"peerDependencies": {
@@ -60,7 +59,7 @@
"react": "*",
"react-native": "*",
"react-native-safe-area-context": ">= 3.0.0",
"react-native-screens": ">= 2.15.0"
"react-native-screens": ">= 3.0.0"
},
"react-native-builder-bob": {
"source": "src",

View File

@@ -1,5 +1,5 @@
import * as React from 'react';
import { Keyboard, Platform } from 'react-native';
import { Keyboard, Platform, EmitterSubscription } from 'react-native';
export default function useIsKeyboardShown() {
const [isKeyboardShown, setIsKeyboardShown] = React.useState(false);
@@ -8,22 +8,22 @@ export default function useIsKeyboardShown() {
const handleKeyboardShow = () => setIsKeyboardShown(true);
const handleKeyboardHide = () => setIsKeyboardShown(false);
let subscriptions: EmitterSubscription[];
if (Platform.OS === 'ios') {
Keyboard.addListener('keyboardWillShow', handleKeyboardShow);
Keyboard.addListener('keyboardWillHide', handleKeyboardHide);
subscriptions = [
Keyboard.addListener('keyboardWillShow', handleKeyboardShow),
Keyboard.addListener('keyboardWillHide', handleKeyboardHide),
];
} else {
Keyboard.addListener('keyboardDidShow', handleKeyboardShow);
Keyboard.addListener('keyboardDidHide', handleKeyboardHide);
subscriptions = [
Keyboard.addListener('keyboardDidShow', handleKeyboardShow),
Keyboard.addListener('keyboardDidHide', handleKeyboardHide),
];
}
return () => {
if (Platform.OS === 'ios') {
Keyboard.removeListener('keyboardWillShow', handleKeyboardShow);
Keyboard.removeListener('keyboardWillHide', handleKeyboardHide);
} else {
Keyboard.removeListener('keyboardDidShow', handleKeyboardShow);
Keyboard.removeListener('keyboardDidHide', handleKeyboardHide);
}
subscriptions.forEach((s) => s.remove());
};
}, []);

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0-next.1...@react-navigation/core@6.0.0-next.2) (2021-04-08)
### Bug Fixes
* properly resolve initialRouteNames ([c38906a](https://github.com/react-navigation/react-navigation/commit/c38906a7a09b997f37ce56734ea823c75ea744db))
### Features
* improve useNavigationState typing ([#9464](https://github.com/react-navigation/react-navigation/issues/9464)) ([84020a0](https://github.com/react-navigation/react-navigation/commit/84020a0b27ebae50d3037438a51d95eb31b02424))
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/core@6.0.0...@react-navigation/core@6.0.0-next.1) (2021-03-10)
**Note:** Version bump only for package @react-navigation/core

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/core",
"description": "Core utilities for building navigators",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react",
"react-native",
@@ -28,18 +28,17 @@
"!**/__tests__"
],
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/routers": "^6.0.0-next.1",
"@react-navigation/routers": "^6.0.0-next.2",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.20",
"query-string": "^6.14.1",
"nanoid": "^3.1.22",
"query-string": "^7.0.0",
"react-is": "^16.13.0"
},
"devDependencies": {
@@ -47,7 +46,7 @@
"@types/react": "^16.9.53",
"@types/react-is": "^16.7.1",
"del-cli": "^3.0.1",
"immer": "^8.0.1",
"immer": "^9.0.1",
"react": "~16.13.1",
"react-native-builder-bob": "^0.18.1",
"react-test-renderer": "~16.13.1",

View File

@@ -2303,3 +2303,119 @@ it('throws if two screens map to the same pattern', () => {
})
).not.toThrow();
});
it('correctly applies initialRouteName for config with similar route names', () => {
const path = '/weekly-earnings';
const config = {
screens: {
RootTabs: {
screens: {
HomeTab: {
screens: {
Home: '',
WeeklyEarnings: 'weekly-earnings',
EventDetails: 'event-details/:eventId',
},
},
EarningsTab: {
initialRouteName: 'Earnings',
path: 'earnings',
screens: {
Earnings: '',
WeeklyEarnings: 'weekly-earnings',
},
},
},
},
},
};
const state = {
routes: [
{
name: 'RootTabs',
state: {
routes: [
{
name: 'HomeTab',
state: {
routes: [
{
name: 'WeeklyEarnings',
path,
},
],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});
it('correctly applies initialRouteName for config with similar route names v2', () => {
const path = '/earnings/weekly-earnings';
const config = {
screens: {
RootTabs: {
screens: {
HomeTab: {
initialRouteName: 'Home',
screens: {
Home: '',
WeeklyEarnings: 'weekly-earnings',
},
},
EarningsTab: {
initialRouteName: 'Earnings',
path: 'earnings',
screens: {
Earnings: '',
WeeklyEarnings: 'weekly-earnings',
},
},
},
},
},
};
const state = {
routes: [
{
name: 'RootTabs',
state: {
routes: [
{
name: 'EarningsTab',
state: {
index: 1,
routes: [
{
name: 'Earnings',
},
{
name: 'WeeklyEarnings',
path,
},
],
},
},
],
},
},
],
};
expect(getStateFromPath(path, config)).toEqual(state);
expect(getStateFromPath(getPathFromState(state, config), config)).toEqual(
state
);
});

View File

@@ -26,7 +26,7 @@ type RouteConfig = {
type InitialRouteConfig = {
initialRouteName: string;
connectedRoutes: string[];
parentScreens: string[];
};
type ResultState = PartialState<NavigationState> & {
@@ -69,7 +69,7 @@ export default function getStateFromPath(
if (options?.initialRouteName) {
initialRoutes.push({
initialRouteName: options.initialRouteName,
connectedRoutes: Object.keys(options.screens),
parentScreens: [],
});
}
@@ -108,7 +108,8 @@ export default function getStateFromPath(
key,
screens as PathConfigMap,
[],
initialRoutes
initialRoutes,
[]
)
)
)
@@ -309,12 +310,15 @@ const createNormalizedConfigs = (
routeConfig: PathConfigMap,
routeNames: string[] = [],
initials: InitialRouteConfig[],
parentScreens: string[],
parentPattern?: string
): RouteConfig[] => {
const configs: RouteConfig[] = [];
routeNames.push(screen);
parentScreens.push(screen);
const config = routeConfig[screen];
if (typeof config === 'string') {
@@ -350,7 +354,7 @@ const createNormalizedConfigs = (
if (config.initialRouteName) {
initials.push({
initialRouteName: config.initialRouteName,
connectedRoutes: Object.keys(config.screens),
parentScreens,
});
}
@@ -360,6 +364,7 @@ const createNormalizedConfigs = (
config.screens as PathConfigMap,
routeNames,
initials,
[...parentScreens],
pattern ?? parentPattern
);
@@ -425,13 +430,23 @@ const findParseConfigForRoute = (
// Try to find an initial route connected with the one passed
const findInitialRoute = (
routeName: string,
parentScreens: string[],
initialRoutes: InitialRouteConfig[]
): string | undefined => {
for (const config of initialRoutes) {
if (config.connectedRoutes.includes(routeName)) {
return config.initialRouteName === routeName
? undefined
: config.initialRouteName;
if (parentScreens.length === config.parentScreens.length) {
let sameParents = true;
for (let i = 0; i < parentScreens.length; i++) {
if (parentScreens[i].localeCompare(config.parentScreens[i]) !== 0) {
sameParents = false;
break;
}
}
if (sameParents) {
return routeName !== config.initialRouteName
? config.initialRouteName
: undefined;
}
}
}
return undefined;
@@ -477,7 +492,11 @@ const createNestedStateObject = (
) => {
let state: InitialState;
let route = routes.shift() as ParsedRoute;
let initialRoute = findInitialRoute(route.name, initialRoutes);
const parentScreens: string[] = [];
let initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
parentScreens.push(route.name);
state = createStateObject(initialRoute, route, routes.length === 0);
@@ -485,7 +504,7 @@ const createNestedStateObject = (
let nestedState = state;
while ((route = routes.shift() as ParsedRoute)) {
initialRoute = findInitialRoute(route.name, initialRoutes);
initialRoute = findInitialRoute(route.name, parentScreens, initialRoutes);
const nestedStateIndex =
nestedState.index || nestedState.routes.length - 1;
@@ -500,6 +519,8 @@ const createNestedStateObject = (
nestedState = nestedState.routes[nestedStateIndex]
.state as InitialState;
}
parentScreens.push(route.name);
}
}

View File

@@ -1,16 +1,21 @@
import * as React from 'react';
import type { NavigationState } from '@react-navigation/routers';
import type { NavigationState, ParamListBase } from '@react-navigation/routers';
import useNavigation from './useNavigation';
import type { NavigationProp } from './types';
type Selector<T> = (state: NavigationState) => T;
type Selector<ParamList extends ParamListBase, T> = (
state: NavigationState<ParamList>
) => T;
/**
* Hook to get a value from the current navigation state using a selector.
*
* @param selector Selector function to get a value from the state.
*/
export default function useNavigationState<T>(selector: Selector<T>): T {
const navigation = useNavigation();
export default function useNavigationState<ParamList extends ParamListBase, T>(
selector: Selector<ParamList, T>
): T {
const navigation = useNavigation<NavigationProp<ParamList>>();
// We don't care about the state value, we run the selector again at the end
// The state is only to make sure that there's a re-render when we have a new value

View File

@@ -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.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0-next.1...@react-navigation/devtools@6.0.0-next.2) (2021-04-08)
**Note:** Version bump only for package @react-navigation/devtools
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/devtools@6.0.0...@react-navigation/devtools@6.0.0-next.1) (2021-03-10)
**Note:** Version bump only for package @react-navigation/devtools

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/devtools",
"description": "Developer tools for React Navigation",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react",
"react-native",
@@ -29,15 +29,14 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^6.0.0-next.1",
"@react-navigation/core": "^6.0.0-next.2",
"deep-equal": "^2.0.5"
},
"devDependencies": {

View File

@@ -3,6 +3,19 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [6.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.3...@react-navigation/drawer@6.0.0-next.4) (2021-04-08)
### Bug Fixes
* don't handle back button with permanent drawer ([b893968](https://github.com/react-navigation/react-navigation/commit/b89396888f46ba79af3cfd84be55fba79d8387d2))
* fix drawer overlay on web ([3241190](https://github.com/react-navigation/react-navigation/commit/3241190b19946c1cd0a744fb09a19d79ba683d74))
* only handle back button in drawer when focused ([5ae0bad](https://github.com/react-navigation/react-navigation/commit/5ae0badc44b576d464f8841822a911b18a698403))
# [6.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/drawer@6.0.0-next.2...@react-navigation/drawer@6.0.0-next.3) (2021-03-22)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/drawer",
"description": "Drawer navigator component with animated transitions and gesturess",
"version": "6.0.0-next.3",
"version": "6.0.0-next.4",
"keywords": [
"react-native-component",
"react-component",
@@ -34,31 +34,30 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.3",
"@react-navigation/elements": "^1.0.0-next.4",
"color": "^3.1.3",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.1",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-gesture-handler": "~1.8.0",
"react-native-reanimated": "~1.13.0",
"react-native-safe-area-context": "3.1.9",
"react-native-screens": "~2.15.0",
"react-native-gesture-handler": "~1.10.2",
"react-native-reanimated": "~2.1.0",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"typescript": "^4.2.3"
},
"peerDependencies": {
@@ -68,7 +67,7 @@
"react-native-gesture-handler": ">= 1.0.0",
"react-native-reanimated": ">= 1.0.0",
"react-native-safe-area-context": ">= 3.0.0",
"react-native-screens": ">= 2.15.0"
"react-native-screens": ">= 3.0.0"
},
"react-native-builder-bob": {
"source": "src",

View File

@@ -18,6 +18,9 @@ export { default as DrawerToggleButton } from './views/DrawerToggleButton';
*/
export { default as DrawerGestureContext } from './utils/DrawerGestureContext';
export { default as DrawerProgressContext } from './utils/DrawerProgressContext';
export { default as useDrawerProgress } from './utils/useDrawerProgress';
export { default as getDrawerStatusFromState } from './utils/getDrawerStatusFromState';
export { default as useDrawerStatus } from './utils/useDrawerStatus';

View File

@@ -1,6 +1,8 @@
import type { StyleProp, ViewStyle, TextStyle } from 'react-native';
import type Animated from 'react-native-reanimated';
import type { PanGestureHandlerProperties } from 'react-native-gesture-handler';
import type {
PanGestureHandler,
PanGestureHandlerProperties,
} from 'react-native-gesture-handler';
import type {
Route,
ParamListBase,
@@ -33,6 +35,18 @@ export type DrawerNavigationConfig = {
* Defaults to `true`.
*/
detachInactiveScreens?: boolean;
/**
* Whether to use the legacy implementation based on Reanimated 1.
* The new implementation based on Reanimated 2 will perform better,
* but you need additional configuration and need to use Hermes with Flipper to debug.
*
* This defaults to `true` in following cases:
* - Reanimated 2 is not configured
* - App is connected to Chrome debugger (Reanimated 2 cannot be used with Chrome debugger)
*
* Otherwise, it defaults to `false`
*/
useLegacyImplementation?: boolean;
};
export type DrawerNavigationOptions = HeaderOptions & {
@@ -207,11 +221,6 @@ export type DrawerContentComponentProps = {
state: DrawerNavigationState<ParamListBase>;
navigation: DrawerNavigationHelpers;
descriptors: DrawerDescriptorMap;
/**
* Animated node which represents the current progress of the drawer's open state.
* `0` is closed, `1` is open.
*/
progress: Animated.Node<number>;
};
export type DrawerHeaderProps = {
@@ -268,3 +277,24 @@ export type DrawerDescriptor = Descriptor<
>;
export type DrawerDescriptorMap = Record<string, DrawerDescriptor>;
export type DrawerProps = {
dimensions: { width: number; height: number };
drawerPosition: 'left' | 'right';
drawerStyle?: StyleProp<ViewStyle>;
drawerType: 'front' | 'back' | 'slide' | 'permanent';
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
hideStatusBarOnOpen: boolean;
keyboardDismissMode: 'none' | 'on-drag';
onClose: () => void;
onOpen: () => void;
open: boolean;
overlayStyle?: StyleProp<ViewStyle>;
renderDrawerContent: () => React.ReactNode;
renderSceneContent: () => React.ReactNode;
statusBarAnimation: 'slide' | 'none' | 'fade';
swipeDistanceThreshold: number;
swipeEdgeWidth: number;
swipeEnabled: boolean;
swipeVelocityThreshold: number;
};

View File

@@ -0,0 +1,6 @@
import * as React from 'react';
import type Animated from 'react-native-reanimated';
export default React.createContext<
Readonly<Animated.SharedValue<number>> | Animated.Node<number> | undefined
>(undefined);

View File

@@ -0,0 +1,17 @@
import * as React from 'react';
import type Animated from 'react-native-reanimated';
import DrawerProgressContext from './DrawerProgressContext';
export default function useDrawerProgress():
| Readonly<Animated.SharedValue<number>>
| Animated.Node<number> {
const progress = React.useContext(DrawerProgressContext);
if (progress === undefined) {
throw new Error(
"Couldn't find a drawer. Is your component inside a drawer navigator?"
);
}
return progress;
}

View File

@@ -5,10 +5,10 @@ import {
I18nManager,
Platform,
BackHandler,
NativeEventSubscription,
} from 'react-native';
import { ScreenContainer } from 'react-native-screens';
import { useSafeAreaFrame } from 'react-native-safe-area-context';
import Animated from 'react-native-reanimated';
import {
NavigationHelpersContext,
DrawerNavigationState,
@@ -27,7 +27,6 @@ import { GestureHandlerRootView } from './GestureHandler';
import ScreenFallback from './ScreenFallback';
import DrawerToggleButton from './DrawerToggleButton';
import DrawerContent from './DrawerContent';
import Drawer from './Drawer';
import DrawerStatusContext from '../utils/DrawerStatusContext';
import DrawerPositionContext from '../utils/DrawerPositionContext';
import getDrawerStatusFromState from '../utils/getDrawerStatusFromState';
@@ -38,6 +37,7 @@ import type {
DrawerContentComponentProps,
DrawerHeaderProps,
DrawerNavigationProp,
DrawerProps,
} from '../types';
type Props = DrawerNavigationConfig & {
@@ -77,7 +77,17 @@ function DrawerViewBase({
<DrawerContent {...props} />
),
detachInactiveScreens = true,
// Running in chrome debugger
// @ts-expect-error
useLegacyImplementation = !global.nativeCallSyncHook ||
// Reanimated 2 is not configured
// @ts-expect-error: the type definitions are incomplete
!Animated.isConfigured?.(),
}: Props) {
const Drawer: React.ComponentType<DrawerProps> = useLegacyImplementation
? require('./legacy/Drawer').default
: require('./modern/Drawer').default;
const focusedRouteKey = state.routes[state.index].key;
const {
drawerHideStatusBarOnOpen = false,
@@ -85,13 +95,14 @@ function DrawerViewBase({
drawerStatusBarAnimation = 'slide',
drawerStyle,
drawerType = Platform.select({ ios: 'slide', default: 'front' }),
gestureEnabled,
gestureHandlerProps,
keyboardDismissMode = 'on-drag',
overlayColor = 'rgba(0, 0, 0, 0.5)',
swipeEdgeWidth,
swipeEnabled,
swipeMinDistance,
swipeEdgeWidth = 32,
swipeEnabled = Platform.OS !== 'web' &&
Platform.OS !== 'windows' &&
Platform.OS !== 'macos',
swipeMinDistance = 60,
} = descriptors[focusedRouteKey].options;
const [loaded, setLoaded] = React.useState([focusedRouteKey]);
@@ -121,27 +132,53 @@ function DrawerViewBase({
}, [navigation, state.key]);
React.useEffect(() => {
let subscription: NativeEventSubscription | undefined;
if (drawerStatus === 'open') {
// We only add the subscription when drawer opens
// This way we can make sure that the subscription is added as late as possible
// This will make sure that our handler will run first when back button is pressed
subscription = BackHandler.addEventListener('hardwareBackPress', () => {
handleDrawerClose();
return true;
});
if (drawerStatus !== 'open' || drawerType === 'permanent') {
return;
}
return () => subscription?.remove();
}, [handleDrawerClose, drawerStatus, navigation, state.key]);
const handleClose = () => {
// We shouldn't handle the back button if the parent screen isn't focused
// This will avoid the drawer overriding event listeners from a focused screen
if (!navigation.isFocused()) {
return false;
}
const renderDrawerContent = ({ progress }: any) => {
handleDrawerClose();
return true;
};
const handleEscape = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
handleClose();
}
};
// We only add the listeners when drawer opens
// This way we can make sure that the listener is added as late as possible
// This will make sure that our handler will run first when back button is pressed
const subscription = BackHandler.addEventListener(
'hardwareBackPress',
handleClose
);
if (Platform.OS === 'web') {
document?.body?.addEventListener?.('keyup', handleEscape);
}
return () => {
subscription.remove();
if (Platform.OS === 'web') {
document?.body?.removeEventListener?.('keyup', handleEscape);
}
};
}, [drawerStatus, drawerType, handleDrawerClose, navigation]);
const renderDrawerContent = () => {
return (
<DrawerPositionContext.Provider value={drawerPosition}>
{drawerContent({
progress: progress,
state: state,
navigation: navigation,
descriptors: descriptors,
@@ -217,11 +254,16 @@ function DrawerViewBase({
<DrawerStatusContext.Provider value={drawerStatus}>
<Drawer
open={drawerStatus !== 'closed'}
gestureEnabled={gestureEnabled}
swipeEnabled={swipeEnabled}
onOpen={handleDrawerOpen}
onClose={handleDrawerClose}
gestureHandlerProps={gestureHandlerProps}
swipeEnabled={swipeEnabled}
swipeEdgeWidth={swipeEdgeWidth}
swipeVelocityThreshold={500}
swipeDistanceThreshold={swipeMinDistance}
hideStatusBarOnOpen={drawerHideStatusBarOnOpen}
statusBarAnimation={drawerStatusBarAnimation}
keyboardDismissMode={keyboardDismissMode}
drawerType={drawerType}
drawerPosition={drawerPosition}
drawerStyle={[
@@ -242,13 +284,8 @@ function DrawerViewBase({
drawerStyle,
]}
overlayStyle={{ backgroundColor: overlayColor }}
swipeEdgeWidth={swipeEdgeWidth}
swipeDistanceThreshold={swipeMinDistance}
hideStatusBarOnOpen={drawerHideStatusBarOnOpen}
statusBarAnimation={drawerStatusBarAnimation}
renderDrawerContent={renderDrawerContent}
renderSceneContent={renderSceneContent}
keyboardDismissMode={keyboardDismissMode}
dimensions={dimensions}
/>
</DrawerStatusContext.Provider>

View File

@@ -1,24 +1,19 @@
import * as React from 'react';
import {
StyleSheet,
ViewStyle,
LayoutChangeEvent,
I18nManager,
Platform,
Keyboard,
StatusBar,
StyleProp,
View,
InteractionManager,
Pressable,
} from 'react-native';
import Animated from 'react-native-reanimated';
import {
PanGestureHandler,
TapGestureHandler,
GestureState,
} from './GestureHandler';
import { PanGestureHandler, GestureState } from '../GestureHandler';
import Overlay from './Overlay';
import DrawerProgressContext from '../../utils/DrawerProgressContext';
import type { DrawerProps } from '../../types';
const {
Clock,
@@ -56,7 +51,6 @@ const UNSET = -1;
const DIRECTION_LEFT = 1;
const DIRECTION_RIGHT = -1;
const SWIPE_DISTANCE_THRESHOLD_DEFAULT = 60;
const SWIPE_DISTANCE_MINIMUM = 5;
const DEFAULT_DRAWER_WIDTH = '80%';
@@ -75,53 +69,8 @@ const ANIMATED_ONE = new Animated.Value(1);
type Binary = 0 | 1;
type Renderer = (props: { progress: Animated.Node<number> }) => React.ReactNode;
type Props = {
open: boolean;
onOpen: () => void;
onClose: () => void;
gestureEnabled: boolean;
swipeEnabled: boolean;
drawerPosition: 'left' | 'right';
drawerType: 'front' | 'back' | 'slide' | 'permanent';
keyboardDismissMode: 'none' | 'on-drag';
swipeEdgeWidth: number;
swipeDistanceThreshold?: number;
swipeVelocityThreshold: number;
hideStatusBarOnOpen: boolean;
statusBarAnimation: 'slide' | 'none' | 'fade';
overlayStyle?: StyleProp<ViewStyle>;
drawerStyle?: StyleProp<ViewStyle>;
renderDrawerContent: Renderer;
renderSceneContent: Renderer;
gestureHandlerProps?: React.ComponentProps<typeof PanGestureHandler>;
dimensions: { width: number; height: number };
};
export default class DrawerView extends React.Component<Props> {
static defaultProps = {
drawerPosition: I18nManager.isRTL ? 'left' : 'right',
drawerType: 'front',
gestureEnabled: true,
swipeEnabled:
Platform.OS !== 'web' &&
Platform.OS !== 'windows' &&
Platform.OS !== 'macos',
swipeEdgeWidth: 32,
swipeVelocityThreshold: 500,
keyboardDismissMode: 'on-drag',
hideStatusBarOnOpen: false,
statusBarAnimation: 'slide',
};
componentDidMount() {
if (Platform.OS === 'web') {
document?.body?.addEventListener?.('keyup', this.handleEscape);
}
}
componentDidUpdate(prevProps: Props) {
export default class DrawerView extends React.Component<DrawerProps> {
componentDidUpdate(prevProps: DrawerProps) {
const {
open,
drawerPosition,
@@ -156,11 +105,7 @@ export default class DrawerView extends React.Component<Props> {
}
if (prevProps.swipeDistanceThreshold !== swipeDistanceThreshold) {
this.swipeDistanceThreshold.setValue(
swipeDistanceThreshold !== undefined
? swipeDistanceThreshold
: SWIPE_DISTANCE_THRESHOLD_DEFAULT
);
this.swipeDistanceThreshold.setValue(swipeDistanceThreshold);
}
if (prevProps.swipeVelocityThreshold !== swipeVelocityThreshold) {
@@ -171,22 +116,8 @@ export default class DrawerView extends React.Component<Props> {
componentWillUnmount() {
this.toggleStatusBar(false);
this.handleEndInteraction();
if (Platform.OS === 'web') {
document?.body?.removeEventListener?.('keyup', this.handleEscape);
}
}
private handleEscape = (e: KeyboardEvent) => {
const { open, onClose } = this.props;
if (e.key === 'Escape') {
if (open) {
onClose();
}
}
};
private handleEndInteraction = () => {
if (this.interactionHandle !== undefined) {
InteractionManager.clearInteractionHandle(this.interactionHandle);
@@ -303,9 +234,7 @@ export default class DrawerView extends React.Component<Props> {
);
private swipeDistanceThreshold = new Value<number>(
this.props.swipeDistanceThreshold !== undefined
? this.props.swipeDistanceThreshold
: SWIPE_DISTANCE_THRESHOLD_DEFAULT
this.props.swipeDistanceThreshold
);
private swipeVelocityThreshold = new Value<number>(
this.props.swipeVelocityThreshold
@@ -515,18 +444,6 @@ export default class DrawerView extends React.Component<Props> {
},
]);
private handleTapStateChange = event([
{
nativeEvent: {
oldState: (s: Animated.Value<number>) =>
cond(
eq(s, GestureState.ACTIVE),
set(this.manuallyTriggerSpring, TRUE)
),
},
},
]);
private handleContainerLayout = (e: LayoutChangeEvent) =>
this.containerWidth.setValue(e.nativeEvent.layout.width);
@@ -567,7 +484,6 @@ export default class DrawerView extends React.Component<Props> {
render() {
const {
open,
gestureEnabled,
swipeEnabled,
drawerPosition,
drawerType,
@@ -617,108 +533,103 @@ export default class DrawerView extends React.Component<Props> {
const progress = drawerType === 'permanent' ? ANIMATED_ONE : this.progress;
return (
<PanGestureHandler
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
hitSlop={hitSlop}
enabled={drawerType !== 'permanent' && gestureEnabled && swipeEnabled}
{...gestureHandlerProps}
>
<Animated.View
onLayout={this.handleContainerLayout}
style={[
styles.main,
{
flexDirection:
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
},
]}
<DrawerProgressContext.Provider value={progress}>
<PanGestureHandler
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
onGestureEvent={this.handleGestureEvent}
onHandlerStateChange={this.handleGestureStateChange}
hitSlop={hitSlop}
enabled={drawerType !== 'permanent' && swipeEnabled}
{...gestureHandlerProps}
>
<Animated.View
onLayout={this.handleContainerLayout}
style={[
styles.content,
{ transform: [{ translateX: contentTranslateX }] },
]}
>
<View
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
importantForAccessibility={
isOpen && drawerType !== 'permanent'
? 'no-hide-descendants'
: 'auto'
}
style={styles.content}
>
{renderSceneContent({ progress })}
</View>
{
// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : Platform.OS === 'web' ||
Platform.OS === 'windows' ||
Platform.OS === 'macos' ? (
<Pressable
onPress={
gestureEnabled ? () => this.toggleDrawer(false) : undefined
}
>
<Overlay progress={progress} style={overlayStyle as any} />
</Pressable>
) : (
<TapGestureHandler
enabled={gestureEnabled}
onHandlerStateChange={this.handleTapStateChange}
>
<Overlay progress={progress} style={overlayStyle as any} />
</TapGestureHandler>
)
}
</Animated.View>
<Animated.Code
// This is needed to make sure that container width updates with `setValue`
// Without this, it won't update when not used in styles
exec={this.containerWidth}
/>
{drawerType === 'permanent' ? null : (
<Animated.Code
exec={block([
onChange(this.manuallyTriggerSpring, [
cond(eq(this.manuallyTriggerSpring, TRUE), [
set(this.nextIsOpen, FALSE),
call([], () => (this.currentOpenValue = false)),
]),
]),
])}
/>
)}
<Animated.View
accessibilityViewIsModal={isOpen && drawerType !== 'permanent'}
removeClippedSubviews={Platform.OS !== 'ios'}
onLayout={this.handleDrawerLayout}
style={[
styles.container,
styles.main,
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
flexDirection:
drawerType === 'permanent' && !isRight
? 'row-reverse'
: 'row',
},
drawerType === 'permanent'
? // Without this, the `left`/`right` values don't get reset
isRight
? { right: 0 }
: { left: 0 }
: [
styles.nonPermanent,
isRight ? { right: offset } : { left: offset },
{ zIndex: drawerType === 'back' ? -1 : 0 },
],
drawerStyle as any,
]}
>
{renderDrawerContent({ progress })}
<Animated.View
style={[
styles.content,
{ transform: [{ translateX: contentTranslateX }] },
]}
>
<View
accessibilityElementsHidden={
isOpen && drawerType !== 'permanent'
}
importantForAccessibility={
isOpen && drawerType !== 'permanent'
? 'no-hide-descendants'
: 'auto'
}
style={styles.content}
>
{renderSceneContent()}
</View>
{
// Disable overlay if sidebar is permanent
drawerType === 'permanent' ? null : (
<Overlay
progress={progress}
onPress={() => this.toggleDrawer(false)}
style={overlayStyle as any}
/>
)
}
</Animated.View>
<Animated.Code
// This is needed to make sure that container width updates with `setValue`
// Without this, it won't update when not used in styles
exec={this.containerWidth}
/>
{drawerType === 'permanent' ? null : (
<Animated.Code
exec={block([
onChange(this.manuallyTriggerSpring, [
cond(eq(this.manuallyTriggerSpring, TRUE), [
set(this.nextIsOpen, FALSE),
call([], () => (this.currentOpenValue = false)),
]),
]),
])}
/>
)}
<Animated.View
accessibilityViewIsModal={isOpen && drawerType !== 'permanent'}
removeClippedSubviews={Platform.OS !== 'ios'}
onLayout={this.handleDrawerLayout}
style={[
styles.container,
{
transform: [{ translateX: drawerTranslateX }],
opacity: this.drawerOpacity,
},
drawerType === 'permanent'
? // Without this, the `left`/`right` values don't get reset
isRight
? { right: 0 }
: { left: 0 }
: [
styles.nonPermanent,
isRight ? { right: offset } : { left: offset },
{ zIndex: drawerType === 'back' ? -1 : 0 },
],
drawerStyle as any,
]}
>
{renderDrawerContent()}
</Animated.View>
</Animated.View>
</Animated.View>
</PanGestureHandler>
</PanGestureHandler>
</DrawerProgressContext.Provider>
);
}
}

View File

@@ -1,26 +1,26 @@
import * as React from 'react';
import { Platform, StyleSheet } from 'react-native';
import { Pressable, Platform, StyleSheet } from 'react-native';
import Animated from 'react-native-reanimated';
const {
interpolate: interpolateDeprecated,
// @ts-expect-error: this property is only present in Reanimated 2
interpolateNode,
cond,
greaterThan,
} = Animated;
const interpolate: typeof interpolateDeprecated =
const interpolate: typeof interpolateNode =
interpolateNode ?? interpolateDeprecated;
const PROGRESS_EPSILON = 0.05;
type Props = React.ComponentProps<typeof Animated.View> & {
progress: Animated.Node<number>;
onPress: () => void;
};
const Overlay = React.forwardRef(function Overlay(
{ progress, style, ...props }: Props,
{ progress, onPress, style, ...props }: Props,
ref: React.Ref<Animated.View>
) {
const animatedStyle = {
@@ -46,7 +46,9 @@ const Overlay = React.forwardRef(function Overlay(
{...props}
ref={ref}
style={[styles.overlay, overlayStyle, animatedStyle, style]}
/>
>
<Pressable onPress={onPress} style={styles.pressable} />
</Animated.View>
);
});
@@ -64,6 +66,9 @@ const styles = StyleSheet.create({
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
pressable: {
flex: 1,
},
});
export default Overlay;

View File

@@ -0,0 +1,379 @@
import * as React from 'react';
import {
InteractionManager,
Keyboard,
Platform,
StatusBar,
StyleSheet,
View,
} from 'react-native';
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
State as GestureState,
} from 'react-native-gesture-handler';
import Animated, {
interpolate,
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
withSpring,
} from 'react-native-reanimated';
import type { DrawerProps } from '../../types';
import DrawerProgressContext from '../../utils/DrawerProgressContext';
import Overlay from './Overlay';
const SWIPE_DISTANCE_MINIMUM = 5;
const DEFAULT_DRAWER_WIDTH = '80%';
const minmax = (value: number, start: number, end: number) => {
'worklet';
return Math.min(Math.max(value, start), end);
};
export default function Drawer({
dimensions,
drawerPosition,
drawerStyle,
drawerType,
gestureHandlerProps,
hideStatusBarOnOpen,
keyboardDismissMode,
onClose,
onOpen,
open,
overlayStyle,
renderDrawerContent,
renderSceneContent,
statusBarAnimation,
swipeDistanceThreshold,
swipeEdgeWidth,
swipeEnabled,
swipeVelocityThreshold,
}: DrawerProps) {
const getDrawerWidth = (): number => {
const { width = DEFAULT_DRAWER_WIDTH } =
StyleSheet.flatten(drawerStyle) || {};
if (typeof width === 'string' && width.endsWith('%')) {
// Try to calculate width if a percentage is given
const percentage = Number(width.replace(/%$/, ''));
if (Number.isFinite(percentage)) {
return dimensions.width * (percentage / 100);
}
}
return typeof width === 'number' ? width : 0;
};
const drawerWidth = getDrawerWidth();
const isOpen = drawerType === 'permanent' ? true : open;
const isRight = drawerPosition === 'right';
const getDrawerTranslationX = React.useCallback(
(open: boolean) => {
'worklet';
if (drawerPosition === 'left') {
return open ? 0 : -drawerWidth;
}
return open ? 0 : drawerWidth;
},
[drawerPosition, drawerWidth]
);
const hideStatusBar = React.useCallback(
(hide: boolean) => {
if (hideStatusBarOnOpen) {
StatusBar.setHidden(hide, statusBarAnimation);
}
},
[hideStatusBarOnOpen, statusBarAnimation]
);
React.useEffect(() => {
hideStatusBar(isOpen);
return () => hideStatusBar(false);
}, [isOpen, hideStatusBarOnOpen, statusBarAnimation, hideStatusBar]);
const interactionHandleRef = React.useRef<number | null>(null);
const startInteraction = () => {
interactionHandleRef.current = InteractionManager.createInteractionHandle();
};
const endInteraction = () => {
if (interactionHandleRef.current != null) {
InteractionManager.clearInteractionHandle(interactionHandleRef.current);
interactionHandleRef.current = null;
}
};
const hideKeyboard = () => {
if (keyboardDismissMode === 'on-drag') {
Keyboard.dismiss();
}
};
const onGestureStart = () => {
startInteraction();
hideKeyboard();
hideStatusBar(true);
};
const onGestureEnd = () => {
endInteraction();
};
// FIXME: Currently hitSlop is broken when on Android when drawer is on right
// https://github.com/kmagiera/react-native-gesture-handler/issues/569
const hitSlop = isRight
? // Extend hitSlop to the side of the screen when drawer is closed
// This lets the user drag the drawer from the side of the screen
{ right: 0, width: isOpen ? undefined : swipeEdgeWidth }
: { left: 0, width: isOpen ? undefined : swipeEdgeWidth };
const touchStartX = useSharedValue(0);
const touchX = useSharedValue(0);
const translationX = useSharedValue(getDrawerTranslationX(open));
const gestureState = useSharedValue<GestureState>(GestureState.UNDETERMINED);
const toggleDrawer = React.useCallback(
(open: boolean, velocity?: number) => {
'worklet';
const translateX = getDrawerTranslationX(open);
touchStartX.value = 0;
touchX.value = 0;
translationX.value = withSpring(
translateX,
{
velocity,
stiffness: 1000,
damping: 500,
mass: 3,
overshootClamping: true,
restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01,
},
() => {
if (translationX.value === getDrawerTranslationX(true)) {
runOnJS(onOpen)();
} else if (translationX.value === getDrawerTranslationX(false)) {
runOnJS(onClose)();
}
}
);
},
[getDrawerTranslationX, onClose, onOpen, touchStartX, touchX, translationX]
);
React.useEffect(() => toggleDrawer(open), [open, toggleDrawer]);
const onGestureEvent = useAnimatedGestureHandler<
PanGestureHandlerGestureEvent,
{ startX: number }
>({
onStart: (event, ctx) => {
ctx.startX = translationX.value;
gestureState.value = event.state;
touchStartX.value = event.x;
runOnJS(onGestureStart)();
},
onActive: (event, ctx) => {
touchX.value = event.x;
translationX.value = ctx.startX + event.translationX;
gestureState.value = event.state;
},
onEnd: (event) => {
gestureState.value = event.state;
const nextOpen =
(Math.abs(event.translationX) > SWIPE_DISTANCE_MINIMUM &&
Math.abs(event.translationX) > swipeVelocityThreshold) ||
Math.abs(event.translationX) > swipeDistanceThreshold
? drawerPosition === 'left'
? // If swiped to right, open the drawer, otherwise close it
(event.velocityX === 0 ? event.translationX : event.velocityX) > 0
: // If swiped to left, open the drawer, otherwise close it
(event.velocityX === 0 ? event.translationX : event.velocityX) < 0
: open;
toggleDrawer(nextOpen, event.velocityX);
runOnJS(onGestureEnd)();
},
});
const translateX = useDerivedValue(() => {
// Comment stolen from react-native-gesture-handler/DrawerLayout
//
// While closing the drawer when user starts gesture outside of its area (in greyed
// out part of the window), we want the drawer to follow only once finger reaches the
// edge of the drawer.
// E.g. on the diagram below drawer is illustrate by X signs and the greyed out area by
// dots. The touch gesture starts at '*' and moves left, touch path is indicated by
// an arrow pointing left
// 1) +---------------+ 2) +---------------+ 3) +---------------+ 4) +---------------+
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|.<-*..| |XXXXXXXX|<--*..| |XXXXX|<-----*..|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// |XXXXXXXX|......| |XXXXXXXX|......| |XXXXXXXX|......| |XXXXX|.........|
// +---------------+ +---------------+ +---------------+ +---------------+
//
// For the above to work properly we define animated value that will keep start position
// of the gesture. Then we use that value to calculate how much we need to subtract from
// the translationX. If the gesture started on the greyed out area we take the distance from the
// edge of the drawer to the start position. Otherwise we don't subtract at all and the
// drawer be pulled back as soon as you start the pan.
//
// This is used only when drawerType is "front"
const touchDistance =
drawerType === 'front' && gestureState.value === GestureState.ACTIVE
? minmax(
drawerPosition === 'left'
? touchStartX.value - drawerWidth
: dimensions.width - drawerWidth - touchStartX.value,
0,
dimensions.width
)
: 0;
const translateX =
drawerPosition === 'left'
? minmax(translationX.value + touchDistance, -drawerWidth, 0)
: minmax(translationX.value - touchDistance, 0, drawerWidth);
return translateX;
});
const drawerAnimatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX:
drawerType === 'permanent' || drawerType === 'back'
? 0
: translateX.value,
},
],
};
});
const contentAnimatedStyle = useAnimatedStyle(() => {
return {
transform: [
{
translateX:
drawerType === 'permanent' || drawerType === 'front'
? 0
: drawerPosition === 'left'
? drawerWidth + translateX.value
: translateX.value - drawerWidth,
},
],
};
});
const progress = useDerivedValue(() => {
return drawerType === 'permanent'
? 1
: interpolate(
translateX.value,
[getDrawerTranslationX(false), getDrawerTranslationX(true)],
[0, 1]
);
});
return (
<DrawerProgressContext.Provider value={progress}>
<PanGestureHandler
activeOffsetX={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
failOffsetY={[-SWIPE_DISTANCE_MINIMUM, SWIPE_DISTANCE_MINIMUM]}
hitSlop={hitSlop}
enabled={drawerType !== 'permanent' && swipeEnabled}
onGestureEvent={onGestureEvent}
{...gestureHandlerProps}
>
{/* Immediate child of gesture handler needs to be an Animated.View */}
<Animated.View
style={[
styles.main,
{
flexDirection:
drawerType === 'permanent' && !isRight ? 'row-reverse' : 'row',
},
]}
>
<Animated.View style={[styles.content, contentAnimatedStyle]}>
<View
accessibilityElementsHidden={isOpen && drawerType !== 'permanent'}
importantForAccessibility={
isOpen && drawerType !== 'permanent'
? 'no-hide-descendants'
: 'auto'
}
style={styles.content}
>
{renderSceneContent()}
</View>
{drawerType !== 'permanent' ? (
<Overlay
progress={progress}
onPress={() => toggleDrawer(false)}
style={overlayStyle}
/>
) : null}
</Animated.View>
<Animated.View
accessibilityViewIsModal={isOpen && drawerType !== 'permanent'}
removeClippedSubviews={Platform.OS !== 'ios'}
style={[
styles.container,
{
position: drawerType === 'permanent' ? 'relative' : 'absolute',
zIndex: drawerType === 'back' ? -1 : 0,
},
drawerAnimatedStyle,
drawerStyle as any,
]}
>
{renderDrawerContent()}
</Animated.View>
</Animated.View>
</PanGestureHandler>
</DrawerProgressContext.Provider>
);
}
const styles = StyleSheet.create({
container: {
top: 0,
bottom: 0,
maxWidth: '100%',
width: DEFAULT_DRAWER_WIDTH,
},
content: {
flex: 1,
},
main: {
flex: 1,
...Platform.select({
// FIXME: We need to hide `overflowX` on Web so the translated content doesn't show offscreen.
// But adding `overflowX: 'hidden'` prevents content from collapsing the URL bar.
web: null,
default: { overflow: 'hidden' },
}),
},
});

View File

@@ -0,0 +1,56 @@
import * as React from 'react';
import { Pressable, Platform, StyleSheet } from 'react-native';
import Animated, { useAnimatedStyle } from 'react-native-reanimated';
const PROGRESS_EPSILON = 0.05;
type Props = React.ComponentProps<typeof Animated.View> & {
progress: Animated.SharedValue<number>;
onPress: () => void;
};
const Overlay = React.forwardRef(function Overlay(
{ progress, onPress, style, ...props }: Props,
ref: React.Ref<Animated.View>
) {
const animatedStyle = useAnimatedStyle(() => {
return {
opacity: progress.value,
// We don't want the user to be able to press through the overlay when drawer is open
// One approach is to adjust the pointerEvents based on the progress
// But we can also send the overlay behind the screen
zIndex: progress.value > PROGRESS_EPSILON ? 0 : -1,
};
});
return (
<Animated.View
{...props}
ref={ref}
style={[styles.overlay, overlayStyle, animatedStyle, style]}
>
<Pressable onPress={onPress} style={styles.pressable} />
</Animated.View>
);
});
const overlayStyle = Platform.select<Record<string, string>>({
web: {
// Disable touch highlight on mobile Safari.
// WebkitTapHighlightColor must be used outside of StyleSheet.create because react-native-web will omit the property.
WebkitTapHighlightColor: 'transparent',
},
default: {},
});
const styles = StyleSheet.create({
overlay: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
},
pressable: {
flex: 1,
},
});
export default Overlay;

View File

@@ -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.
# [1.0.0-next.4](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.3...@react-navigation/elements@1.0.0-next.4) (2021-04-08)
**Note:** Version bump only for package @react-navigation/elements
# [1.0.0-next.3](https://github.com/react-navigation/react-navigation/compare/@react-navigation/elements@1.0.0-next.2...@react-navigation/elements@1.0.0-next.3) (2021-03-22)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/elements",
"description": "UI Components for React Navigation",
"version": "1.0.0-next.3",
"version": "1.0.0-next.4",
"keywords": [
"react-native",
"react-navigation",
@@ -30,22 +30,21 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"devDependencies": {
"@react-native-masked-view/masked-view": "^0.2.2",
"@react-navigation/native": "^6.0.0-next.1",
"@react-native-masked-view/masked-view": "^0.2.3",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"typescript": "^4.2.3"
},

View File

@@ -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.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0-next.1...@react-navigation/material-bottom-tabs@6.0.0-next.2) (2021-04-08)
**Note:** Version bump only for package @react-navigation/material-bottom-tabs
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-bottom-tabs@6.0.0...@react-navigation/material-bottom-tabs@6.0.0-next.1) (2021-03-10)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-bottom-tabs",
"description": "Integration for bottom navigation component from react-native-paper",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react-native-component",
"react-component",
@@ -34,22 +34,21 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.1",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"@types/react-native-vector-icons": "^6.4.6",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-paper": "^4.7.2",
"react-native-vector-icons": "^8.1.0",

View File

@@ -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.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0-next.1...@react-navigation/material-top-tabs@6.0.0-next.2) (2021-04-08)
**Note:** Version bump only for package @react-navigation/material-top-tabs
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/material-top-tabs@6.0.0...@react-navigation/material-top-tabs@6.0.0-next.1) (2021-03-10)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/material-top-tabs",
"description": "Integration for the animated tab view component from react-native-tab-view",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react-native-component",
"react-component",
@@ -34,8 +34,7 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
@@ -46,16 +45,16 @@
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.1",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-pager-view": "^4.2.4",
"react-native-tab-view": "^3.0.0",
"react-native-pager-view": "^5.0.12",
"react-native-tab-view": "^3.0.1",
"typescript": "^4.2.3"
},
"peerDependencies": {

View File

@@ -26,9 +26,7 @@ function MaterialTopTabNavigator({
backBehavior,
children,
screenOptions,
// @ts-expect-error: lazy is deprecated
lazy,
// @ts-expect-error: tabBarOptions is deprecated
tabBarOptions,
...rest
}: Props) {

View File

@@ -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.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0-next.1...@react-navigation/native@6.0.0-next.2) (2021-04-08)
**Note:** Version bump only for package @react-navigation/native
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/native@6.0.0...@react-navigation/native@6.0.0-next.1) (2021-03-10)
**Note:** Version bump only for package @react-navigation/native

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/native",
"description": "React Native integration for React Navigation",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react-native",
"react-navigation",
@@ -30,17 +30,16 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/core": "^6.0.0-next.1",
"@react-navigation/core": "^6.0.0-next.2",
"escape-string-regexp": "^4.0.0",
"nanoid": "^3.1.20"
"nanoid": "^3.1.22"
},
"devDependencies": {
"@testing-library/react-native": "^7.2.0",
@@ -50,7 +49,7 @@
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-dom": "^16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"typescript": "^4.2.3"
},

View File

@@ -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.
# [6.0.0-next.2](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@6.0.0-next.1...@react-navigation/routers@6.0.0-next.2) (2021-04-08)
**Note:** Version bump only for package @react-navigation/routers
# [6.0.0-next.1](https://github.com/react-navigation/react-navigation/compare/@react-navigation/routers@6.0.0...@react-navigation/routers@6.0.0-next.1) (2021-03-10)
**Note:** Version bump only for package @react-navigation/routers

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/routers",
"description": "Routers to help build custom navigators",
"version": "6.0.0-next.1",
"version": "6.0.0-next.2",
"keywords": [
"react",
"react-native",
@@ -29,15 +29,14 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"nanoid": "^3.1.20"
"nanoid": "^3.1.22"
},
"devDependencies": {
"del-cli": "^3.0.1",

View File

@@ -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.
# [6.0.0-next.9](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.8...@react-navigation/stack@6.0.0-next.9) (2021-04-08)
**Note:** Version bump only for package @react-navigation/stack
# [6.0.0-next.8](https://github.com/react-navigation/react-navigation/compare/@react-navigation/stack@6.0.0-next.7...@react-navigation/stack@6.0.0-next.8) (2021-03-22)

View File

@@ -1,7 +1,7 @@
{
"name": "@react-navigation/stack",
"description": "Stack navigator component for iOS and Android with animated transitions and gestures",
"version": "6.0.0-next.8",
"version": "6.0.0-next.9",
"keywords": [
"react-native-component",
"react-component",
@@ -33,32 +33,31 @@
],
"sideEffects": false,
"publishConfig": {
"access": "public",
"tag": "alpha"
"access": "public"
},
"scripts": {
"prepare": "bob build",
"clean": "del lib"
},
"dependencies": {
"@react-navigation/elements": "^1.0.0-next.3",
"@react-navigation/elements": "^1.0.0-next.4",
"color": "^3.1.3",
"react-native-iphone-x-helper": "^1.3.0",
"warn-once": "^0.0.1"
},
"devDependencies": {
"@react-navigation/native": "^6.0.0-next.1",
"@react-navigation/native": "^6.0.0-next.2",
"@testing-library/react-native": "^7.2.0",
"@types/color": "^3.0.1",
"@types/react": "^16.9.53",
"@types/react-native": "~0.63.51",
"del-cli": "^3.0.1",
"react": "~16.13.1",
"react-native": "~0.63.2",
"react-native": "~0.63.4",
"react-native-builder-bob": "^0.18.1",
"react-native-gesture-handler": "~1.8.0",
"react-native-safe-area-context": "3.1.9",
"react-native-screens": "~2.15.0",
"react-native-gesture-handler": "~1.10.2",
"react-native-safe-area-context": "~3.2.0",
"react-native-screens": "~3.0.0",
"typescript": "^4.2.3"
},
"peerDependencies": {
@@ -67,7 +66,7 @@
"react-native": "*",
"react-native-gesture-handler": ">= 1.0.0",
"react-native-safe-area-context": ">= 3.0.0",
"react-native-screens": ">= 2.15.0"
"react-native-screens": ">= 3.0.0"
},
"react-native-builder-bob": {
"source": "src",

4617
yarn.lock

File diff suppressed because it is too large Load Diff