mirror of
https://github.com/zhigang1992/react-navigation.git
synced 2026-02-15 22:35:20 +08:00
Compare commits
11 Commits
@react-nav
...
@react-nav
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8dd70a033 | ||
|
|
277fec481b | ||
|
|
3241190b19 | ||
|
|
ef7370b215 | ||
|
|
1149e718c1 | ||
|
|
d85a4fd8ed | ||
|
|
b89396888f | ||
|
|
c38906a7a0 | ||
|
|
d87857e5d9 | ||
|
|
5ae0badc44 | ||
|
|
84020a0b27 |
@@ -2,5 +2,6 @@ module.exports = function (api) {
|
||||
api.cache(true);
|
||||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: ['react-native-reanimated/plugin'],
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
import * as Linking from 'expo-linking';
|
||||
export default [Linking.makeUrl('/')];
|
||||
@@ -1 +0,0 @@
|
||||
export default ['rne://127.0.0.1:19000/--/'];
|
||||
@@ -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>(
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"allowBranch": "main",
|
||||
"conventionalCommits": true,
|
||||
"createRelease": "github",
|
||||
"distTag": "next",
|
||||
"message": "chore: publish",
|
||||
"ignoreChanges": [
|
||||
"**/__fixtures__/**",
|
||||
|
||||
13
package.json
13
package.json
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
6
packages/drawer/src/utils/DrawerProgressContext.tsx
Normal file
6
packages/drawer/src/utils/DrawerProgressContext.tsx
Normal 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);
|
||||
17
packages/drawer/src/utils/useDrawerProgress.tsx
Normal file
17
packages/drawer/src/utils/useDrawerProgress.tsx
Normal 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;
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
379
packages/drawer/src/views/modern/Drawer.tsx
Normal file
379
packages/drawer/src/views/modern/Drawer.tsx
Normal 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' },
|
||||
}),
|
||||
},
|
||||
});
|
||||
56
packages/drawer/src/views/modern/Overlay.tsx
Normal file
56
packages/drawer/src/views/modern/Overlay.tsx
Normal 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;
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user