Compare commits

..

21 Commits

Author SHA1 Message Date
Satyajit Sahoo
9f553c2ad1 chore: publish
- react-navigation-animated-switch@0.6.4
 - react-navigation-drawer@2.7.0
 - react-navigation-material-bottom-tabs@2.3.4
 - @react-navigation/native@3.8.4
 - react-navigation@4.4.4
 - react-navigation-stack@2.10.3
 - react-navigation-tabs@2.11.0
2021-02-21 16:49:38 +01:00
Satyajit Sahoo
10ad9154a2 chore: sync latest stack 2021-02-21 16:46:26 +01:00
Satyajit Sahoo
fb1e8d4a07 fix: address breaking change in react-native for Linking 2021-02-21 15:45:49 +01:00
WoLewicki
5c7f892d77 feat: add activityState to other navigators 2020-12-18 13:49:05 +01:00
Satyajit Sahoo
10c6c3280f chore: publish
- react-navigation-stack@2.10.2
2020-11-22 14:15:14 +01:00
Satyajit Sahoo
e5856dae79 chore: sync latest stack 2020-11-22 14:13:52 +01:00
Satyajit Sahoo
db24639445 chore: sync latest stack 2020-11-10 21:10:31 +01:00
Satyajit Sahoo
f10543f9fc chore: publish
- react-navigation-stack@2.10.1
2020-11-04 22:48:09 +01:00
Satyajit Sahoo
a3e3fa2cfd chore: sync latest stack 2020-11-04 22:46:35 +01:00
otrepanier
ec25edd658 test: add tests to getEventManager 2020-11-04 22:34:38 +01:00
otrepanier
09d4bc24e9 test: add tests for getChildrenNavigationCache 2020-11-04 22:34:25 +01:00
Satyajit Sahoo
8d0e0f48ad chore: publish
- react-navigation-stack@2.10.0
2020-10-30 11:40:12 +01:00
Satyajit Sahoo
8411e6f764 feat: enable react-native-screens in Stack by default on iOS 2020-10-30 01:59:25 +01:00
Satyajit Sahoo
dff6c3ee7c chore: publish
- react-navigation-tabs@2.10.1
2020-10-28 15:04:08 +01:00
Satyajit Sahoo
ffe3bddcb2 fix: add bottom insets to custom height for bottom tabs 2020-10-28 15:02:54 +01:00
Satyajit Sahoo
9b55493e76 chore: publish
- react-navigation-animated-switch@0.6.3
 - @react-navigation/core@3.7.9
 - react-navigation-drawer@2.6.0
 - react-navigation-material-bottom-tabs@2.3.3
 - @react-navigation/native@3.8.3
 - react-navigation@4.4.3
 - react-navigation-stack@2.9.0
 - react-navigation-tabs@2.10.0
2020-10-26 18:19:28 +01:00
Satyajit Sahoo
ebe70f51fb fix: use react-native-iphone-x-helper in bottom-tabs navigator 2020-10-26 18:16:23 +01:00
Satyajit Sahoo
256e5aec2a chore: upgrade depenendecies 2020-10-26 18:16:22 +01:00
Wojciech Lewicki
707bea3cba feat: add detachInactiveScreens prop for bottom tabs and drawer (#8816) 2020-10-26 18:10:35 +01:00
Satyajit Sahoo
6bdc1e9e5f feat: sync latest stack 2020-10-26 16:28:59 +01:00
oltrep
b3db099a00 test: add navigation focus events tests (#8983)
This PR adds a few unit tests on the NavigationFocusEvents. It asserts that events are emitted in the expected order in different cases.
2020-10-25 02:00:19 +02:00
50 changed files with 3144 additions and 1455 deletions

View File

@@ -8,7 +8,7 @@
"ios": "expo start --ios" "ios": "expo start --ios"
}, },
"dependencies": { "dependencies": {
"@babel/runtime": "^7.11.0", "@babel/runtime": "^7.12.1",
"@expo/vector-icons": "^10.0.0", "@expo/vector-icons": "^10.0.0",
"@react-native-community/masked-view": "0.1.10", "@react-native-community/masked-view": "0.1.10",
"expo": "^39.0.0", "expo": "^39.0.0",
@@ -20,17 +20,17 @@
"react": "~16.13.1", "react": "~16.13.1",
"react-native": "~0.63.2", "react-native": "~0.63.2",
"react-native-gesture-handler": "~1.7.0", "react-native-gesture-handler": "~1.7.0",
"react-native-iphone-x-helper": "^1.2.1", "react-native-iphone-x-helper": "^1.3.0",
"react-native-maps": "0.27.1", "react-native-maps": "0.27.1",
"react-native-paper": "^4.0.1", "react-native-paper": "^4.0.1",
"react-native-reanimated": "~1.13.0", "react-native-reanimated": "~1.13.0",
"react-native-safe-area-context": "3.1.4", "react-native-safe-area-context": "3.1.4",
"react-native-screens": "~2.10.1", "react-native-screens": "~2.10.1",
"react-native-webview": "10.7.0", "react-native-webview": "10.7.0",
"react-navigation-header-buttons": "^3.0.5" "react-navigation-header-buttons": "^6.0.0"
}, },
"devDependencies": { "devDependencies": {
"babel-plugin-module-resolver": "^4.0.0", "babel-plugin-module-resolver": "^4.0.0",
"expo-cli": "^3.27.8" "expo-cli": "^3.28.2"
} }
} }

View File

@@ -25,21 +25,21 @@
"example": "yarn --cwd example" "example": "yarn --cwd example"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-class-properties": "^7.10.4", "@babel/plugin-proposal-class-properties": "^7.12.1",
"@babel/plugin-proposal-optional-chaining": "^7.11.0", "@babel/plugin-proposal-optional-chaining": "^7.12.1",
"@babel/preset-env": "^7.11.0", "@babel/preset-env": "^7.12.1",
"@babel/preset-react": "^7.10.4", "@babel/preset-react": "^7.12.1",
"@babel/preset-typescript": "^7.10.4", "@babel/preset-typescript": "^7.12.1",
"@babel/runtime": "^7.11.0", "@babel/runtime": "^7.12.1",
"@commitlint/config-conventional": "^11.0.0", "@commitlint/config-conventional": "^11.0.0",
"@types/jest": "^26.0.8", "@types/jest": "^26.0.15",
"babel-jest": "^26.2.2", "babel-jest": "^26.6.1",
"commitlint": "^11.0.0", "commitlint": "^11.0.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"eslint": "^7.6.0", "eslint": "^7.12.0",
"eslint-config-satya164": "^3.1.8", "eslint-config-satya164": "^3.1.8",
"husky": "^4.2.5", "husky": "^4.2.5",
"jest": "^26.2.2", "jest": "^26.6.1",
"lerna": "^3.22.1", "lerna": "^3.22.1",
"prettier": "^2.1.2", "prettier": "^2.1.2",
"typescript": "^4.0.3" "typescript": "^4.0.3"

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [0.6.4](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.3...react-navigation-animated-switch@0.6.4) (2021-02-21)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.6.3](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.2...react-navigation-animated-switch@0.6.3) (2020-10-26)
**Note:** Version bump only for package react-navigation-animated-switch
## [0.6.2](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.1...react-navigation-animated-switch@0.6.2) (2020-10-02) ## [0.6.2](https://github.com/react-navigation/react-navigation/compare/react-navigation-animated-switch@0.6.1...react-navigation-animated-switch@0.6.2) (2020-10-02)
**Note:** Version bump only for package react-navigation-animated-switch **Note:** Version bump only for package react-navigation-animated-switch

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-animated-switch", "name": "react-navigation-animated-switch",
"version": "0.6.2", "version": "0.6.4",
"description": "Animated switch for React Navigation", "description": "Animated switch for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -22,13 +22,13 @@
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "0.16.2", "@react-native-community/bob": "0.16.2",
"@types/react": "16.9.49", "@types/react": "16.9.53",
"@types/react-native": "0.63.20", "@types/react-native": "0.63.30",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",
"react-native": "~0.63.2", "react-native": "~0.63.2",
"react-native-reanimated": "~1.13.0", "react-native-reanimated": "~1.13.0",
"react-navigation": "^4.4.2", "react-navigation": "^4.4.4",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.7.9](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.8...@react-navigation/core@3.7.9) (2020-10-26)
**Note:** Version bump only for package @react-navigation/core
## [3.7.8](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.7...@react-navigation/core@3.7.8) (2020-10-02) ## [3.7.8](https://github.com/react-navigation/react-navigation-core/compare/@react-navigation/core@3.7.7...@react-navigation/core@3.7.8) (2020-10-02)

View File

@@ -1,6 +1,6 @@
{ {
"name": "@react-navigation/core", "name": "@react-navigation/core",
"version": "3.7.8", "version": "3.7.9",
"description": "Core utilities for the react-navigation framework", "description": "Core utilities for the react-navigation framework",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -37,7 +37,7 @@
"dependencies": { "dependencies": {
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"path-to-regexp": "^1.8.0", "path-to-regexp": "^1.8.0",
"query-string": "^6.13.1", "query-string": "^6.13.6",
"react-is": "^16.13.0" "react-is": "^16.13.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -0,0 +1,60 @@
import getChildrenNavigationCache from '../getChildrenNavigationCache';
it('should return empty table if navigation arg not provided', () => {
expect(getChildrenNavigationCache()._childrenNavigation).toBeUndefined();
});
it('should populate navigation._childrenNavigation as a side-effect', () => {
const navigation = {
state: {
routes: [{ key: 'one' }],
},
};
const result = getChildrenNavigationCache(navigation);
expect(result).toBeDefined();
expect(navigation._childrenNavigation).toBe(result);
});
it('should delete children cache keys that are no longer valid', () => {
const navigation = {
state: {
routes: [{ key: 'one' }, { key: 'two' }, { key: 'three' }],
},
_childrenNavigation: {
one: {},
two: {},
three: {},
four: {},
},
};
const result = getChildrenNavigationCache(navigation);
expect(result).toEqual({
one: {},
two: {},
three: {},
});
});
it('should not delete children cache keys if in transitioning state', () => {
const navigation = {
state: {
routes: [{ key: 'one' }, { key: 'two' }, { key: 'three' }],
isTransitioning: true,
},
_childrenNavigation: {
one: {},
two: {},
three: {},
four: {},
},
};
const result = getChildrenNavigationCache(navigation);
expect(result).toEqual({
one: {},
two: {},
three: {},
four: {},
});
});

View File

@@ -0,0 +1,48 @@
import getEventManager from '../getEventManager';
const TARGET = 'target';
it('calls listeners to emitted event', () => {
const eventManager = getEventManager(TARGET);
const callback = jest.fn();
eventManager.addListener('didFocus', callback);
eventManager.emit('didFocus');
expect(callback).toHaveBeenCalledTimes(1);
});
it('does not call listeners connected to a different event', () => {
const eventManager = getEventManager(TARGET);
const callback = jest.fn();
eventManager.addListener('didFocus', callback);
eventManager.emit('didBlur');
expect(callback).not.toHaveBeenCalled();
});
it('does not call removed listeners', () => {
const eventManager = getEventManager(TARGET);
const callback = jest.fn();
const { remove } = eventManager.addListener('didFocus', callback);
eventManager.emit('didFocus');
expect(callback).toHaveBeenCalled();
remove();
eventManager.emit('didFocus');
expect(callback).toHaveBeenCalledTimes(1);
});
it('calls the listeners with the given payload', () => {
const eventManager = getEventManager(TARGET);
const callback = jest.fn();
eventManager.addListener('didFocus', callback);
const payload = { foo: 0 };
eventManager.emit('didFocus', payload);
expect(callback).toHaveBeenCalledWith(payload);
});

View File

@@ -0,0 +1,307 @@
import * as React from 'react';
import renderer from 'react-test-renderer';
import NavigationFocusEvents from '../NavigationFocusEvents';
import getEventManager from '../../getEventManager';
import { NavigationActions, StackActions } from '@react-navigation/core';
const getNavigationMock = (mock = {}) => {
const { addListener, emit } = getEventManager('target');
return {
state: {
routes: [
{ key: 'a', routeName: 'foo' },
{ key: 'b', routeName: 'bar' },
],
index: 0,
},
isFocused: () => true,
addListener: jest.fn(addListener),
emit: emit,
dangerouslyGetParent: () => null,
...mock,
};
};
it('emits refocus event with current route key on refocus', () => {
const navigation = getNavigationMock();
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
navigation.emit('refocus');
expect(onEvent).toHaveBeenCalledTimes(1);
const key = navigation.state.routes[navigation.state.index].key;
expect(onEvent).toHaveBeenCalledWith(key, 'refocus');
});
describe('on navigation action emitted', () => {
it('does not emit if navigation is not focused', () => {
const navigation = getNavigationMock({
isFocused: () => false,
});
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
navigation.emit('action', {
state: navigation.state,
action: NavigationActions.init(),
type: 'action',
});
expect(onEvent).not.toHaveBeenCalled();
});
it('emits only willFocus and willBlur if state is transitioning', () => {
const state = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 1,
routeKeyHistory: ['First', 'Second'],
isTransitioning: true,
};
const action = NavigationActions.init();
const navigation = getNavigationMock({
state: state,
});
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
const lastState = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 0,
routeKeyHistory: ['First'],
};
navigation.emit('action', {
state,
lastState,
action,
type: 'action',
});
const expectedPayload = {
action,
state: { key: 'Second', routeName: 'Second' },
lastState: { key: 'First', routeName: 'First' },
context: 'Second:Navigation/INIT_Root',
type: 'action',
};
expect(onEvent.mock.calls).toEqual([
['Second', 'willFocus', expectedPayload],
['First', 'willBlur', expectedPayload],
['Second', 'action', expectedPayload],
]);
});
it('emits didFocus after willFocus and didBlur after willBlur if no transitions', () => {
const state = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 1,
routeKeyHistory: ['First', 'Second'],
};
const action = NavigationActions.navigate({
routeName: 'Second',
});
const navigation = getNavigationMock({
state: state,
});
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
const lastState = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 0,
routeKeyHistory: ['First'],
};
navigation.emit('action', {
state,
lastState,
action,
type: 'action',
});
const expectedPayload = {
action,
state: { key: 'Second', routeName: 'Second' },
lastState: { key: 'First', routeName: 'First' },
context: 'Second:Navigation/NAVIGATE_Root',
type: 'action',
};
expect(onEvent.mock.calls).toEqual([
['Second', 'willFocus', expectedPayload],
['Second', 'didFocus', expectedPayload],
['First', 'willBlur', expectedPayload],
['First', 'didBlur', expectedPayload],
['Second', 'action', expectedPayload],
]);
});
it('emits didBlur and didFocus when transition ends', () => {
const initialState = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 0,
routeKeyHistory: ['First'],
isTransitioning: true,
};
const intermediateState = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 1,
routeKeyHistory: ['First', 'Second'],
isTransitioning: true,
};
const finalState = {
routes: [
{ key: 'First', routeName: 'First' },
{ key: 'Second', routeName: 'Second' },
],
index: 1,
routeKeyHistory: ['First', 'Second'],
isTransitioning: false,
};
const actionNavigate = NavigationActions.navigate({ routeName: 'Second' });
const actionEndTransition = StackActions.completeTransition({
key: 'Second',
});
const navigation = getNavigationMock({
state: intermediateState,
});
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
navigation.emit('action', {
state: intermediateState,
lastState: initialState,
action: actionNavigate,
type: 'action',
});
const expectedPayloadNavigate = {
action: actionNavigate,
state: { key: 'Second', routeName: 'Second' },
lastState: { key: 'First', routeName: 'First' },
context: 'Second:Navigation/NAVIGATE_Root',
type: 'action',
};
expect(onEvent.mock.calls).toEqual([
['Second', 'willFocus', expectedPayloadNavigate],
['First', 'willBlur', expectedPayloadNavigate],
['Second', 'action', expectedPayloadNavigate],
]);
onEvent.mockClear();
navigation.emit('action', {
state: finalState,
lastState: intermediateState,
action: actionEndTransition,
type: 'action',
});
const expectedPayloadEndTransition = {
action: actionEndTransition,
state: { key: 'Second', routeName: 'Second' },
lastState: { key: 'Second', routeName: 'Second' },
context: 'Second:Navigation/COMPLETE_TRANSITION_Root',
type: 'action',
};
expect(onEvent.mock.calls).toEqual([
['First', 'didBlur', expectedPayloadEndTransition],
['Second', 'didFocus', expectedPayloadEndTransition],
['Second', 'action', expectedPayloadEndTransition],
]);
});
});
describe('on willFocus emitted', () => {
it('emits didFocus after willFocus if no transition', () => {
const navigation = getNavigationMock({
state: {
routes: [
{ key: 'FirstLanding', routeName: 'FirstLanding' },
{ key: 'Second', routeName: 'Second' },
],
index: 0,
key: 'First',
routeName: 'First',
},
});
const onEvent = jest.fn();
renderer.create(
<NavigationFocusEvents navigation={navigation} onEvent={onEvent} />
);
const lastState = { key: 'Third', routeName: 'Third' };
const action = NavigationActions.navigate({ routeName: 'First' });
navigation.emit('willFocus', {
lastState,
action,
context: 'First:Navigation/NAVIGATE_Root',
type: 'action',
});
const expectedPayload = {
action,
state: { key: 'FirstLanding', routeName: 'FirstLanding' },
context:
'FirstLanding:Navigation/NAVIGATE_First:Navigation/NAVIGATE_Root',
type: 'action',
};
expect(onEvent.mock.calls).toEqual([
['FirstLanding', 'willFocus', expectedPayload],
['FirstLanding', 'didFocus', expectedPayload],
]);
onEvent.mockClear();
// the nested navigator might emit a didFocus that should be ignored
navigation.emit('didFocus', {
lastState,
action,
context: 'First:Navigation/NAVIGATE_Root',
type: 'action',
});
expect(onEvent).not.toHaveBeenCalled();
});
});

View File

@@ -3,6 +3,28 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.7.0](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.6.0...react-navigation-drawer@2.7.0) (2021-02-21)
### Features
* add activityState to other navigators ([5c7f892](https://github.com/react-navigation/drawer/commit/5c7f892d77298f5c89534fa78a1a6a59c7f35a60))
# [2.6.0](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.2...react-navigation-drawer@2.6.0) (2020-10-26)
### Features
* add detachInactiveScreens prop for bottom tabs and drawer ([#8816](https://github.com/react-navigation/drawer/issues/8816)) ([707bea3](https://github.com/react-navigation/drawer/commit/707bea3cba1aa1f7b1eb5884dd1d129dcc2bbdc2))
## [2.5.2](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.1...react-navigation-drawer@2.5.2) (2020-10-02) ## [2.5.2](https://github.com/react-navigation/drawer/compare/react-navigation-drawer@2.5.1...react-navigation-drawer@2.5.2) (2020-10-02)
**Note:** Version bump only for package react-navigation-drawer **Note:** Version bump only for package react-navigation-drawer

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-drawer", "name": "react-navigation-drawer",
"version": "2.5.2", "version": "2.7.0",
"description": "Drawer navigator component for React Navigation", "description": "Drawer navigator component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -39,8 +39,8 @@
"homepage": "https://github.com/react-navigation/drawer#readme", "homepage": "https://github.com/react-navigation/drawer#readme",
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.16.2", "@react-native-community/bob": "^0.16.2",
"@types/react": "^16.9.44", "@types/react": "^16.9.53",
"@types/react-native": "^0.63.4", "@types/react-native": "^0.63.30",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
@@ -49,7 +49,7 @@
"react-native-reanimated": "~1.13.0", "react-native-reanimated": "~1.13.0",
"react-native-screens": "~2.10.1", "react-native-screens": "~2.10.1",
"react-native-testing-library": "^6.0.0", "react-native-testing-library": "^6.0.0",
"react-navigation": "^4.4.2", "react-navigation": "^4.4.4",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -69,6 +69,7 @@ export type NavigationDrawerConfig = {
drawerBackgroundColor?: ThemedColor; drawerBackgroundColor?: ThemedColor;
overlayColor?: ThemedColor; overlayColor?: ThemedColor;
screenContainerStyle?: StyleProp<ViewStyle>; screenContainerStyle?: StyleProp<ViewStyle>;
detachInactiveScreens?: boolean;
}; };
export type NavigationDrawerRouterConfig = { export type NavigationDrawerRouterConfig = {

View File

@@ -49,6 +49,7 @@ type Props = {
contentOptions?: object; contentOptions?: object;
}; };
screenProps: unknown; screenProps: unknown;
detachInactiveScreens: boolean;
}; };
type State = { type State = {
@@ -166,7 +167,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
}; };
private renderContent = () => { private renderContent = () => {
let { lazy, navigation } = this.props; let { lazy, navigation, detachInactiveScreens = true } = this.props;
let { loaded } = this.state; let { loaded } = this.state;
let { routes } = navigation.state; let { routes } = navigation.state;
@@ -183,7 +184,8 @@ export default class DrawerView extends React.PureComponent<Props, State> {
); );
} else { } else {
return ( return (
<ScreenContainer style={styles.content}> // @ts-ignore
<ScreenContainer enabled={detachInactiveScreens} style={styles.content}>
{routes.map((route, index) => { {routes.map((route, index) => {
if (lazy && !loaded.includes(index)) { if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it // Don't render a screen if we've never navigated to it
@@ -201,6 +203,7 @@ export default class DrawerView extends React.PureComponent<Props, State> {
{ opacity: isFocused ? 1 : 0 }, { opacity: isFocused ? 1 : 0 },
]} ]}
isVisible={isFocused} isVisible={isFocused}
enabled={detachInactiveScreens}
> >
<SceneView <SceneView
navigation={descriptor.navigation} navigation={descriptor.navigation}

View File

@@ -1,11 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native'; import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens'; import {
Screen,
screensEnabled,
// @ts-ignore
shouldUseActivityState,
} from 'react-native-screens';
type Props = { type Props = {
isVisible: boolean; isVisible: boolean;
children: React.ReactNode; children: React.ReactNode;
style?: any; style?: any;
enabled: boolean;
}; };
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
@@ -13,11 +19,20 @@ const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
// react-native-screens is buggy on web // react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') { if (screensEnabled?.() && this.props.enabled && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore if (shouldUseActivityState) {
return <Screen active={isVisible ? 1 : 0} {...rest} />; return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screen activityState={isVisible ? 2 : 0} {...rest} />
);
} else {
return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screen active={isVisible ? 1 : 0} {...rest} />
);
}
} }
const { isVisible, children, style, ...rest } = this.props; const { isVisible, children, style, ...rest } = this.props;

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.3.4](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.3...react-navigation-material-bottom-tabs@2.3.4) (2021-02-21)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.3.3](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.2...react-navigation-material-bottom-tabs@2.3.3) (2020-10-26)
**Note:** Version bump only for package react-navigation-material-bottom-tabs
## [2.3.2](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.1...react-navigation-material-bottom-tabs@2.3.2) (2020-10-02) ## [2.3.2](https://github.com/react-navigation/react-navigation-material-bottom-tabs/compare/react-navigation-material-bottom-tabs@2.3.1...react-navigation-material-bottom-tabs@2.3.2) (2020-10-02)
**Note:** Version bump only for package react-navigation-material-bottom-tabs **Note:** Version bump only for package react-navigation-material-bottom-tabs

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-material-bottom-tabs", "name": "react-navigation-material-bottom-tabs",
"version": "2.3.2", "version": "2.3.4",
"description": "Material Bottom Tab Navigation component for React Navigation", "description": "Material Bottom Tab Navigation component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -40,13 +40,13 @@
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.16.2", "@react-native-community/bob": "^0.16.2",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
"@types/react": "^16.9.44", "@types/react": "^16.9.53",
"@types/react-native": "^0.63.4", "@types/react-native": "^0.63.30",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",
"react-native": "~0.63.2", "react-native": "~0.63.2",
"react-native-paper": "^4.2.0", "react-native-paper": "^4.2.0",
"react-navigation": "^4.4.2", "react-navigation": "^4.4.4",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -3,6 +3,25 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [3.8.4](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.3...@react-navigation/native@3.8.4) (2021-02-21)
### Bug Fixes
* address breaking change in react-native for Linking ([fb1e8d4](https://github.com/react-navigation/react-navigation-native/commit/fb1e8d4a077cece663633408d279459df66033a4))
## [3.8.3](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.2...@react-navigation/native@3.8.3) (2020-10-26)
**Note:** Version bump only for package @react-navigation/native
## [3.8.2](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.1...@react-navigation/native@3.8.2) (2020-10-02) ## [3.8.2](https://github.com/react-navigation/react-navigation-native/compare/@react-navigation/native@3.8.1...@react-navigation/native@3.8.2) (2020-10-02)
**Note:** Version bump only for package @react-navigation/native **Note:** Version bump only for package @react-navigation/native

View File

@@ -1,6 +1,6 @@
{ {
"name": "@react-navigation/native", "name": "@react-navigation/native",
"version": "3.8.2", "version": "3.8.4",
"description": "React Native support for React Navigation", "description": "React Native support for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"react-native": "lib/module/index.js", "react-native": "lib/module/index.js",
@@ -40,7 +40,7 @@
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.16.2", "@react-native-community/bob": "^0.16.2",
"@react-navigation/core": "^3.7.8", "@react-navigation/core": "^3.7.9",
"@types/react-test-renderer": "^16.9.3", "@types/react-test-renderer": "^16.9.3",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",

View File

@@ -214,7 +214,7 @@ export default function createNavigationContainer(Component) {
} }
} }
_statefulContainerCount++; _statefulContainerCount++;
Linking.addEventListener('url', this._handleOpenURL); this._linkingSub = Linking.addEventListener('url', this._handleOpenURL);
// Pull out anything that can impact state // Pull out anything that can impact state
let parsedUrl = null; let parsedUrl = null;
@@ -331,7 +331,14 @@ export default function createNavigationContainer(Component) {
componentWillUnmount() { componentWillUnmount() {
this._isMounted = false; this._isMounted = false;
Linking.removeEventListener('url', this._handleOpenURL);
// https://github.com/facebook/react-native/commit/6d1aca806cee86ad76de771ed3a1cc62982ebcd7
if (this._linkingSub?.remove) {
this._linkingSub?.remove();
} else {
Linking.removeEventListener('url', this._handleOpenURL);
}
this.subs && this.subs.remove(); this.subs && this.subs.remove();
if (this._isStateful()) { if (this._isStateful()) {

View File

@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [4.4.4](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.3...react-navigation@4.4.4) (2021-02-21)
**Note:** Version bump only for package react-navigation
## [4.4.3](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.2...react-navigation@4.4.3) (2020-10-26)
**Note:** Version bump only for package react-navigation
## [4.4.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.1...react-navigation@4.4.2) (2020-10-02) ## [4.4.2](https://github.com/react-navigation/react-navigation/compare/react-navigation@4.4.1...react-navigation@4.4.2) (2020-10-02)
**Note:** Version bump only for package react-navigation **Note:** Version bump only for package react-navigation

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation", "name": "react-navigation",
"version": "4.4.2", "version": "4.4.4",
"description": "Routing and navigation for your React Native apps", "description": "Routing and navigation for your React Native apps",
"main": "src/index.js", "main": "src/index.js",
"types": "typescript/react-navigation.d.ts", "types": "typescript/react-navigation.d.ts",
@@ -24,12 +24,12 @@
"react-native": "*" "react-native": "*"
}, },
"dependencies": { "dependencies": {
"@react-navigation/core": "^3.7.8", "@react-navigation/core": "^3.7.9",
"@react-navigation/native": "^3.8.2" "@react-navigation/native": "^3.8.4"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^16.9.44", "@types/react": "^16.9.53",
"@types/react-native": "^0.63.4", "@types/react-native": "^0.63.30",
"@types/react-test-renderer": "^16.9.3", "@types/react-test-renderer": "^16.9.3",
"react": "~16.13.1", "react": "~16.13.1",
"react-native": "~0.63.2", "react-native": "~0.63.2",

View File

@@ -3,6 +3,52 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
## [2.10.3](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.10.2...react-navigation-stack@2.10.3) (2021-02-21)
**Note:** Version bump only for package react-navigation-stack
## [2.10.2](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.10.1...react-navigation-stack@2.10.2) (2020-11-22)
**Note:** Version bump only for package react-navigation-stack
## [2.10.1](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.10.0...react-navigation-stack@2.10.1) (2020-11-04)
**Note:** Version bump only for package react-navigation-stack
# [2.10.0](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.9.0...react-navigation-stack@2.10.0) (2020-10-30)
### Features
* enable react-native-screens in Stack by default on iOS ([8411e6f](https://github.com/react-navigation/react-navigation-stack/commit/8411e6f764b86341e747cb7ca1ff4a775b4a827a))
# [2.9.0](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.4...react-navigation-stack@2.9.0) (2020-10-26)
### Features
* sync latest stack ([6bdc1e9](https://github.com/react-navigation/react-navigation-stack/commit/6bdc1e9e5f6ca05e0494f6b9a1f7b9b60764628a))
## [2.8.4](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.3...react-navigation-stack@2.8.4) (2020-10-02) ## [2.8.4](https://github.com/react-navigation/react-navigation-stack/compare/react-navigation-stack@2.8.3...react-navigation-stack@2.8.4) (2020-10-02)
**Note:** Version bump only for package react-navigation-stack **Note:** Version bump only for package react-navigation-stack

View File

@@ -49,7 +49,7 @@ yarn example start
The code in this repo uses the source from [`@react-navigation/stack`](https://github.com/react-navigation/navigation-ex/tree/master/packages/stack) and patches it to make it usable in React Navigation 4. If you need to make changes, please send a pull request there. The code in this repo uses the source from [`@react-navigation/stack`](https://github.com/react-navigation/navigation-ex/tree/master/packages/stack) and patches it to make it usable in React Navigation 4. If you need to make changes, please send a pull request there.
If the change is specifically related to React Navigation 4 integration, first run `yarn sync`, then change the files in `src/vendor` and then run `yarn patch` to update the patch file with the latest changes. If the change is specifically related to React Navigation 4 integration, first run `yarn patch:apply`, then change the files in `src/vendor` to resolve any conflicts and then run `yarn patch:create` to update the patch file with the latest changes.
Make sure your code passes TypeScript and ESLint. Run the following to verify: Make sure your code passes TypeScript and ESLint. Run the following to verify:

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-stack", "name": "react-navigation-stack",
"version": "2.8.4", "version": "2.10.3",
"description": "Stack navigator component for React Navigation", "description": "Stack navigator component for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -13,8 +13,8 @@
"scripts": { "scripts": {
"prepare": "bob build", "prepare": "bob build",
"clean": "del lib", "clean": "del lib",
"sync": "bash scripts/sync-stack.sh", "patch:apply": "bash scripts/sync-stack.sh",
"patch": "diff -Naur ../../node_modules/@react-navigation/stack/src src/vendor > scripts/stack.patch; printf ''" "patch:create": "diff -Naur ../../node_modules/@react-navigation/stack/src src/vendor > scripts/stack.patch; printf ''"
}, },
"sideEffects": false, "sideEffects": false,
"publishConfig": { "publishConfig": {
@@ -39,16 +39,16 @@
}, },
"homepage": "https://github.com/react-navigation/react-navigation-stack#readme", "homepage": "https://github.com/react-navigation/react-navigation-stack#readme",
"dependencies": { "dependencies": {
"color": "^3.1.2", "color": "^3.1.3",
"react-native-iphone-x-helper": "^1.2.1" "react-native-iphone-x-helper": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.16.2", "@react-native-community/bob": "^0.16.2",
"@react-native-community/masked-view": "0.1.10", "@react-native-community/masked-view": "0.1.10",
"@react-navigation/stack": "^5.9.0", "@react-navigation/stack": "^5.14.3",
"@types/color": "^3.0.1", "@types/color": "^3.0.1",
"@types/react": "^16.9.44", "@types/react": "^16.9.53",
"@types/react-native": "^0.63.4", "@types/react-native": "^0.63.30",
"@types/react-test-renderer": "^16.9.3", "@types/react-test-renderer": "^16.9.3",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",
@@ -56,7 +56,7 @@
"react-native-gesture-handler": "~1.7.0", "react-native-gesture-handler": "~1.7.0",
"react-native-safe-area-context": "3.1.4", "react-native-safe-area-context": "3.1.4",
"react-native-screens": "~2.10.1", "react-native-screens": "~2.10.1",
"react-navigation": "^4.4.2", "react-navigation": "^4.4.4",
"react-test-renderer": "~16.13.1", "react-test-renderer": "~16.13.1",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },

View File

@@ -1,10 +1,10 @@
diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/index.tsx
--- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/index.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/index.tsx 2020-06-24 23:16:17.000000000 +0200 +++ src/vendor/index.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -3,11 +3,6 @@ @@ -3,11 +3,6 @@
import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs'; import * as TransitionSpecs from './TransitionConfigs/TransitionSpecs';
import * as TransitionPresets from './TransitionConfigs/TransitionPresets'; import * as TransitionPresets from './TransitionConfigs/TransitionPresets';
-/** -/**
- * Navigators - * Navigators
- */ - */
@@ -28,9 +28,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/index.tsx src/vendor/i
StackHeaderLeftButtonProps, StackHeaderLeftButtonProps,
StackHeaderTitleProps, StackHeaderTitleProps,
diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx src/vendor/navigators/createStackNavigator.tsx
--- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/navigators/createStackNavigator.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100 +++ src/vendor/navigators/createStackNavigator.tsx 1970-01-01 01:00:00.000000000 +0100
@@ -1,96 +0,0 @@ @@ -1,101 +0,0 @@
-import * as React from 'react'; -import * as React from 'react';
-import { Platform } from 'react-native'; -import { Platform } from 'react-native';
-import { -import {
@@ -42,6 +42,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
- StackRouterOptions, - StackRouterOptions,
- StackNavigationState, - StackNavigationState,
- StackActions, - StackActions,
- ParamListBase,
- StackActionHelpers,
-} from '@react-navigation/native'; -} from '@react-navigation/native';
-import StackView from '../views/Stack/StackView'; -import StackView from '../views/Stack/StackView';
-import type { -import type {
@@ -62,12 +64,16 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
-}: Props) { -}: Props) {
- const defaultOptions = { - const defaultOptions = {
- gestureEnabled: Platform.OS === 'ios', - gestureEnabled: Platform.OS === 'ios',
- animationEnabled: Platform.OS !== 'web', - animationEnabled:
- Platform.OS !== 'web' &&
- Platform.OS !== 'windows' &&
- Platform.OS !== 'macos',
- }; - };
- -
- const { state, descriptors, navigation } = useNavigationBuilder< - const { state, descriptors, navigation } = useNavigationBuilder<
- StackNavigationState, - StackNavigationState<ParamListBase>,
- StackRouterOptions, - StackRouterOptions,
- StackActionHelpers<ParamListBase>,
- StackNavigationOptions, - StackNavigationOptions,
- StackNavigationEventMap - StackNavigationEventMap
- >(StackRouter, { - >(StackRouter, {
@@ -87,8 +93,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
- -
- React.useEffect( - React.useEffect(
- () => - () =>
- navigation.addListener && - navigation.addListener?.('tabPress', (e) => {
- navigation.addListener('tabPress', (e) => {
- const isFocused = navigation.isFocused(); - const isFocused = navigation.isFocused();
- -
- // Run the operation in the next frame so we're sure all listeners have been run - // Run the operation in the next frame so we're sure all listeners have been run
@@ -122,15 +127,15 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/navigators/createStack
-} -}
- -
-export default createNavigatorFactory< -export default createNavigatorFactory<
- StackNavigationState, - StackNavigationState<ParamListBase>,
- StackNavigationOptions, - StackNavigationOptions,
- StackNavigationEventMap, - StackNavigationEventMap,
- typeof StackNavigator - typeof StackNavigator
->(StackNavigator); ->(StackNavigator);
diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/types.tsx
--- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/types.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/types.tsx 2020-06-24 23:18:40.000000000 +0200 +++ src/vendor/types.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -8,15 +8,28 @@ @@ -8,15 +8,29 @@
} from 'react-native'; } from 'react-native';
import type { EdgeInsets } from 'react-native-safe-area-context'; import type { EdgeInsets } from 'react-native-safe-area-context';
import type { import type {
@@ -155,6 +160,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
+} from 'react-navigation'; +} from 'react-navigation';
+ +
+// @ts-ignore +// @ts-ignore
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
+export type Route<T extends string> = NavigationRoute; +export type Route<T extends string> = NavigationRoute;
+ +
+export type NavigationStackState = NavigationState; +export type NavigationStackState = NavigationState;
@@ -164,26 +170,27 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
+ | 'didFocus' + | 'didFocus'
+ | 'willBlur' + | 'willBlur'
+ | 'didBlur'; + | 'didBlur';
export type StackNavigationEventMap = { export type StackNavigationEventMap = {
/** /**
@@ -29,29 +42,28 @@ @@ -41,30 +55,29 @@
transitionEnd: { data: { closing: boolean } }; gestureCancel: { data: undefined };
}; };
-export type StackNavigationHelpers = NavigationHelpers< -export type StackNavigationHelpers = NavigationHelpers<
- ParamListBase, - ParamListBase,
- StackNavigationEventMap - StackNavigationEventMap
->; -> &
- - StackActionHelpers<ParamListBase>;
+export type StackNavigationHelpers = NavigationProp<NavigationStackState>; +export type StackNavigationHelpers = NavigationProp<NavigationStackState>;
export type StackNavigationProp< export type StackNavigationProp<
- ParamList extends ParamListBase, - ParamList extends ParamListBase,
- RouteName extends keyof ParamList = string - RouteName extends keyof ParamList = string
-> = NavigationProp< -> = NavigationProp<
- ParamList, - ParamList,
- RouteName, - RouteName,
- StackNavigationState, - StackNavigationState<ParamList>,
- StackNavigationOptions, - StackNavigationOptions,
- StackNavigationEventMap - StackNavigationEventMap
-> & -> &
@@ -216,9 +223,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
+ callback: NavigationEventCallback + callback: NavigationEventCallback
+ ) => NavigationEventSubscription; + ) => NavigationEventSubscription;
}; };
export type Layout = { width: number; height: number }; export type Layout = { width: number; height: number };
@@ -228,24 +240,27 @@ @@ -245,24 +258,27 @@
/** /**
* Navigation prop for the header. * Navigation prop for the header.
*/ */
@@ -229,22 +236,22 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
*/ */
styleInterpolator: StackHeaderStyleInterpolator; styleInterpolator: StackHeaderStyleInterpolator;
}; };
-export type StackDescriptor = Descriptor< -export type StackDescriptor = Descriptor<
- ParamListBase, - ParamListBase,
- string, - string,
- StackNavigationState, - StackNavigationState<ParamListBase>,
- StackNavigationOptions - StackNavigationOptions
+export type StackDescriptor = NavigationDescriptor< +export type StackDescriptor = NavigationDescriptor<
+ NavigationParams, + NavigationParams,
+ StackNavigationOptions, + StackNavigationOptions,
+ StackNavigationProp + StackNavigationProp
>; >;
export type StackDescriptorMap = { export type StackDescriptorMap = {
[key: string]: StackDescriptor; [key: string]: StackDescriptor;
}; };
+export type TransitionCallbackProps = { +export type TransitionCallbackProps = {
+ closing: boolean; + closing: boolean;
+}; +};
@@ -252,29 +259,29 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/types.tsx src/vendor/t
export type StackNavigationOptions = StackHeaderOptions & export type StackNavigationOptions = StackHeaderOptions &
Partial<TransitionPreset> & { Partial<TransitionPreset> & {
/** /**
@@ -330,6 +345,8 @@ @@ -356,6 +372,8 @@
bottom?: number; * Defaults to `false` for the last screen when mode='modal', otherwise `true`.
left?: number; */
}; detachPreviousScreen?: boolean;
+ onTransitionStart?: (props: TransitionCallbackProps) => void; + onTransitionStart?: (props: TransitionCallbackProps) => void;
+ onTransitionEnd?: (props: TransitionCallbackProps) => void; + onTransitionEnd?: (props: TransitionCallbackProps) => void;
}; };
export type StackNavigationConfig = { export type StackNavigationConfig = {
diff -Naur ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx src/vendor/utils/PreviousSceneContext.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx src/vendor/utils/PreviousSceneContext.tsx
--- ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/utils/PreviousSceneContext.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/utils/PreviousSceneContext.tsx 2020-06-24 23:26:38.000000000 +0200 +++ src/vendor/utils/PreviousSceneContext.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
import * as React from 'react'; import * as React from 'react';
-import type { Route } from '@react-navigation/native'; -import type { Route } from '@react-navigation/native';
-import type { Scene } from '../types'; -import type { Scene } from '../types';
+import type { Route, Scene } from '../types'; +import type { Route, Scene } from '../types';
const PreviousSceneContext = React.createContext< const PreviousSceneContext = React.createContext<
Scene<Route<string>> | undefined Scene<Route<string>> | undefined
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx src/vendor/views/Header/Header.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/Header.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/Header.tsx 2020-06-25 03:01:13.000000000 +0200 +++ src/vendor/views/Header/Header.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -1,12 +1,15 @@ @@ -1,12 +1,15 @@
import * as React from 'react'; import * as React from 'react';
-import { StackActions } from '@react-navigation/native'; -import { StackActions } from '@react-navigation/native';
@@ -283,12 +290,12 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
+import { getStatusBarHeight } from 'react-native-iphone-x-helper'; +import { getStatusBarHeight } from 'react-native-iphone-x-helper';
+ +
+import HeaderSegment, { getDefaultHeaderHeight } from './HeaderSegment'; +import HeaderSegment, { getDefaultHeaderHeight } from './HeaderSegment';
-import HeaderSegment from './HeaderSegment'; -import HeaderSegment from './HeaderSegment';
import HeaderTitle from './HeaderTitle'; import HeaderTitle from './HeaderTitle';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import type { StackHeaderProps, StackHeaderTitleProps } from '../../types'; import type { StackHeaderProps, StackHeaderTitleProps } from '../../types';
-export default React.memo(function Header(props: StackHeaderProps) { -export default React.memo(function Header(props: StackHeaderProps) {
+const Header = React.memo(function Header(props: StackHeaderProps) { +const Header = React.memo(function Header(props: StackHeaderProps) {
const { const {
@@ -300,9 +307,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
? options.title ? options.title
- : scene.route.name; - : scene.route.name;
+ : scene.route.routeName; + : scene.route.routeName;
let leftLabel; let leftLabel;
@@ -38,17 +41,20 @@ @@ -38,17 +41,20 @@
? o.headerTitle ? o.headerTitle
: o.title !== undefined : o.title !== undefined
@@ -310,7 +317,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
- : previous.route.name; - : previous.route.name;
+ : previous.route.routeName; + : previous.route.routeName;
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const goBack = React.useCallback( const goBack = React.useCallback(
debounce(() => { debounce(() => {
@@ -362,37 +369,37 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/Header.ts
+ +
+export default Header; +export default Header;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx src/vendor/views/Header/HeaderBackButton.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackButton.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/HeaderBackButton.tsx 2020-06-24 23:21:49.000000000 +0200 +++ src/vendor/views/Header/HeaderBackButton.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -8,9 +8,9 @@ @@ -8,9 +8,9 @@
StyleSheet, StyleSheet,
LayoutChangeEvent, LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
-import { useTheme } from '@react-navigation/native'; -import { useTheme } from '@react-navigation/native';
import MaskedView from '../MaskedView'; import MaskedView from '../MaskedView';
import { TouchableItem } from '../TouchableItem'; import TouchableItem from '../TouchableItem';
+import useTheme from '../../../utils/useTheme'; +import useTheme from '../../../utils/useTheme';
import type { StackHeaderLeftButtonProps } from '../../types'; import type { StackHeaderLeftButtonProps } from '../../types';
type Props = StackHeaderLeftButtonProps; type Props = StackHeaderLeftButtonProps;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx src/vendor/views/Header/HeaderBackground.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderBackground.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/HeaderBackground.tsx 2020-06-24 23:16:23.000000000 +0200 +++ src/vendor/views/Header/HeaderBackground.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -7,7 +7,7 @@ @@ -7,7 +7,7 @@
StyleProp, StyleProp,
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
-import { useTheme } from '@react-navigation/native'; -import { useTheme } from '@react-navigation/native';
+import useTheme from '../../../utils/useTheme'; +import useTheme from '../../../utils/useTheme';
type Props = ViewProps & { type Props = ViewProps & {
style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>; style?: Animated.WithAnimatedValue<StyleProp<ViewStyle>>;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx src/vendor/views/Header/HeaderContainer.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderContainer.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/HeaderContainer.tsx 2020-06-24 23:21:29.000000000 +0200 +++ src/vendor/views/Header/HeaderContainer.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -1,11 +1,6 @@ @@ -1,11 +1,6 @@
import * as React from 'react'; import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
-import { -import {
- NavigationContext, - NavigationContext,
- NavigationRouteContext, - NavigationRouteContext,
@@ -401,9 +408,9 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
-} from '@react-navigation/native'; -} from '@react-navigation/native';
+import { NavigationContext } from 'react-navigation'; +import { NavigationContext } from 'react-navigation';
import type { EdgeInsets } from 'react-native-safe-area-context'; import type { EdgeInsets } from 'react-native-safe-area-context';
import Header from './Header'; import Header from './Header';
@@ -19,6 +14,7 @@ @@ -18,6 +13,7 @@
import PreviousSceneContext from '../../utils/PreviousSceneContext'; import PreviousSceneContext from '../../utils/PreviousSceneContext';
import type { import type {
Layout, Layout,
@@ -411,7 +418,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
Scene, Scene,
StackHeaderStyleInterpolator, StackHeaderStyleInterpolator,
StackNavigationProp, StackNavigationProp,
@@ -109,9 +105,7 @@ @@ -99,9 +95,7 @@
insets, insets,
scene, scene,
previous, previous,
@@ -422,7 +429,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
styleInterpolator: styleInterpolator:
mode === 'float' mode === 'float'
? isHeaderStatic ? isHeaderStatic
@@ -130,7 +124,7 @@ @@ -120,7 +114,7 @@
key={scene.route.key} key={scene.route.key}
value={scene.descriptor.navigation} value={scene.descriptor.navigation}
> >
@@ -431,7 +438,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
<View <View
onLayout={ onLayout={
onContentHeightChange onContentHeightChange
@@ -156,7 +150,7 @@ @@ -149,7 +143,7 @@
> >
{header !== undefined ? header(props) : <Header {...props} />} {header !== undefined ? header(props) : <Header {...props} />}
</View> </View>
@@ -441,8 +448,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderCon
); );
})} })}
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx src/vendor/views/Header/HeaderSegment.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSegment.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/HeaderSegment.tsx 2020-06-24 23:20:58.000000000 +0200 +++ src/vendor/views/Header/HeaderSegment.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
@@ -451,8 +458,8 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSeg
+import type { NavigationRoute } from 'react-navigation'; +import type { NavigationRoute } from 'react-navigation';
import HeaderBackButton from './HeaderBackButton'; import HeaderBackButton from './HeaderBackButton';
import HeaderBackground from './HeaderBackground'; import HeaderBackground from './HeaderBackground';
import memoize from '../../utils/memoize'; import HeaderShownContext from '../../utils/HeaderShownContext';
@@ -28,7 +28,7 @@ @@ -29,7 +29,7 @@
onGoBack?: () => void; onGoBack?: () => void;
title?: string; title?: string;
leftLabel?: string; leftLabel?: string;
@@ -460,33 +467,34 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderSeg
+ scene: Scene<NavigationRoute>; + scene: Scene<NavigationRoute>;
styleInterpolator: StackHeaderStyleInterpolator; styleInterpolator: StackHeaderStyleInterpolator;
}; };
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx src/vendor/views/Header/HeaderTitle.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Header/HeaderTitle.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Header/HeaderTitle.tsx 2020-06-24 23:16:23.000000000 +0200 +++ src/vendor/views/Header/HeaderTitle.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -1,6 +1,6 @@ @@ -7,7 +7,7 @@
import * as React from 'react'; StyleProp,
import { Animated, StyleSheet, Platform } from 'react-native'; TextStyle,
} from 'react-native';
-import { useTheme } from '@react-navigation/native'; -import { useTheme } from '@react-navigation/native';
+import useTheme from '../../../utils/useTheme'; +import useTheme from '../../../utils/useTheme';
type Props = Omit<React.ComponentProps<typeof Animated.Text>, 'key'> & { type Props = Omit<TextProps, 'style'> & {
tintColor?: string; tintColor?: string;
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx src/vendor/views/Stack/Card.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Stack/Card.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Stack/Card.tsx 2020-06-24 23:16:23.000000000 +0200 +++ src/vendor/views/Stack/Card.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -146,7 +146,7 @@ @@ -162,7 +162,7 @@
private interactionHandle: number | undefined; private interactionHandle: number | undefined;
- private pendingGestureCallback: number | undefined; - private pendingGestureCallback: number | undefined;
+ private pendingGestureCallback: any; + private pendingGestureCallback: any;
private animate = ({ private lastToValue: number | undefined;
closing,
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx src/vendor/views/Stack/CardContainer.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardContainer.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Stack/CardContainer.tsx 2020-06-24 23:20:38.000000000 +0200 +++ src/vendor/views/Stack/CardContainer.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -1,12 +1,13 @@ @@ -1,12 +1,13 @@
import * as React from 'react'; import * as React from 'react';
import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
@@ -503,18 +511,22 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardContai
Layout, Layout,
StackHeaderMode, StackHeaderMode,
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx src/vendor/views/Stack/CardStack.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Stack/CardStack.tsx 2020-06-24 23:20:16.000000000 +0200 +++ src/vendor/views/Stack/CardStack.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -7,7 +7,7 @@ @@ -6,11 +6,7 @@
Platform, Dimensions,
} from 'react-native'; } from 'react-native';
import type { EdgeInsets } from 'react-native-safe-area-context'; import type { EdgeInsets } from 'react-native-safe-area-context';
-import type { Route, StackNavigationState } from '@react-navigation/native'; -import type {
- ParamListBase,
- Route,
- StackNavigationState,
-} from '@react-navigation/native';
+import type { NavigationState as StackNavigationState } from 'react-navigation'; +import type { NavigationState as StackNavigationState } from 'react-navigation';
import { MaybeScreenContainer, MaybeScreen } from '../Screens'; import {
import { getDefaultHeaderHeight } from '../Header/HeaderSegment'; MaybeScreenContainer,
@@ -25,6 +25,7 @@ @@ -31,6 +27,7 @@
Layout, Layout,
StackHeaderMode, StackHeaderMode,
StackCardMode, StackCardMode,
@@ -522,10 +534,19 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/CardStack.
Scene, Scene,
StackDescriptorMap, StackDescriptorMap,
StackNavigationOptions, StackNavigationOptions,
@@ -44,7 +41,7 @@
type Props = {
mode: StackCardMode;
insets: EdgeInsets;
- state: StackNavigationState<ParamListBase>;
+ state: StackNavigationState;
descriptors: StackDescriptorMap;
routes: Route<string>[];
openingRouteKeys: string[];
diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx src/vendor/views/Stack/StackView.tsx
--- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-06-25 01:58:50.000000000 +0200 --- ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.tsx 2020-11-10 21:02:55.000000000 +0100
+++ src/vendor/views/Stack/StackView.tsx 2020-06-24 23:19:46.000000000 +0200 +++ src/vendor/views/Stack/StackView.tsx 2020-11-10 21:04:07.000000000 +0100
@@ -2,11 +2,11 @@ @@ -2,12 +2,11 @@
import { View, Platform, StyleSheet } from 'react-native'; import { View, Platform, StyleSheet } from 'react-native';
import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context'; import { SafeAreaConsumer, EdgeInsets } from 'react-native-safe-area-context';
import { import {
@@ -533,15 +554,16 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
StackActions, StackActions,
- StackNavigationState, - StackNavigationState,
- Route, - Route,
- ParamListBase,
-} from '@react-navigation/native'; -} from '@react-navigation/native';
+ NavigationState as StackNavigationState, + NavigationState as StackNavigationState,
+ NavigationActions, + NavigationActions,
+ SceneView, + SceneView,
+} from 'react-navigation'; +} from 'react-navigation';
import { GestureHandlerRootView } from '../GestureHandler'; import { GestureHandlerRootView } from '../GestureHandler';
import CardStack from './CardStack'; import CardStack from './CardStack';
@@ -16,6 +16,7 @@ @@ -17,6 +16,7 @@
} from '../Header/HeaderContainer'; } from '../Header/HeaderContainer';
import SafeAreaProviderCompat from '../SafeAreaProviderCompat'; import SafeAreaProviderCompat from '../SafeAreaProviderCompat';
import type { import type {
@@ -549,29 +571,33 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
StackNavigationHelpers, StackNavigationHelpers,
StackNavigationConfig, StackNavigationConfig,
StackDescriptorMap, StackDescriptorMap,
@@ -25,6 +26,7 @@ @@ -24,9 +24,10 @@
state: StackNavigationState; import HeaderShownContext from '../../utils/HeaderShownContext';
type Props = StackNavigationConfig & {
- state: StackNavigationState<ParamListBase>;
+ state: StackNavigationState;
navigation: StackNavigationHelpers; navigation: StackNavigationHelpers;
descriptors: StackDescriptorMap; descriptors: StackDescriptorMap;
+ screenProps: unknown; + screenProps: unknown;
}; };
type State = { type State = {
@@ -293,7 +295,9 @@ @@ -295,7 +296,9 @@
return false; return false;
} }
- return gestureEnabled !== false; - return gestureEnabled !== false;
+ return gestureEnabled !== undefined + return gestureEnabled !== undefined
+ ? gestureEnabled + ? gestureEnabled
+ : Platform.OS !== 'android'; + : Platform.OS !== 'android';
} }
return false; return false;
@@ -321,26 +325,49 @@ @@ -323,26 +326,49 @@
return null; return null;
} }
- return descriptor.render(); - return descriptor.render();
+ const { navigation, getComponent } = descriptor; + const { navigation, getComponent } = descriptor;
+ const SceneComponent = getComponent(); + const SceneComponent = getComponent();
@@ -584,11 +610,11 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
+ /> + />
+ ); + );
}; };
private renderHeader = (props: HeaderContainerProps) => { private renderHeader = (props: HeaderContainerProps) => {
return <HeaderContainer {...props} />; return <HeaderContainer {...props} />;
}; };
+ private handleTransitionComplete = () => { + private handleTransitionComplete = () => {
+ const { state, navigation } = this.props; + const { state, navigation } = this.props;
+ +
@@ -605,7 +631,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
private handleOpenRoute = ({ route }: { route: Route<string> }) => { private handleOpenRoute = ({ route }: { route: Route<string> }) => {
const { state, navigation } = this.props; const { state, navigation } = this.props;
const { closingRouteKeys, replacingRouteKeys } = this.state; const { closingRouteKeys, replacingRouteKeys } = this.state;
+ this.handleTransitionComplete(); + this.handleTransitionComplete();
+ +
if ( if (
@@ -621,7 +647,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
} else { } else {
this.setState((state) => ({ this.setState((state) => ({
routes: state.replacingRouteKeys.length routes: state.replacingRouteKeys.length
@@ -366,12 +393,11 @@ @@ -368,12 +394,11 @@
// If a route exists in state, trigger a pop // If a route exists in state, trigger a pop
// This will happen in when the route was closed from the card component // This will happen in when the route was closed from the card component
// e.g. When the close animation triggered from a gesture ends // e.g. When the close animation triggered from a gesture ends
@@ -638,7 +664,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
// We need to clean up any state tracking the route and pop it immediately // We need to clean up any state tracking the route and pop it immediately
this.setState((state) => ({ this.setState((state) => ({
routes: state.routes.filter((r) => r.key !== route.key), routes: state.routes.filter((r) => r.key !== route.key),
@@ -388,26 +414,29 @@ @@ -390,47 +415,41 @@
private handleTransitionStart = ( private handleTransitionStart = (
{ route }: { route: Route<string> }, { route }: { route: Route<string> },
closing: boolean closing: boolean
@@ -655,7 +681,7 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
+ +
+ descriptor?.options.onTransitionStart?.({ closing }); + descriptor?.options.onTransitionStart?.({ closing });
+ }; + };
private handleTransitionEnd = ( private handleTransitionEnd = (
{ route }: { route: Route<string> }, { route }: { route: Route<string> },
closing: boolean closing: boolean
@@ -665,14 +691,46 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
- data: { closing }, - data: { closing },
- target: route.key, - target: route.key,
- }); - });
-
- private handleGestureStart = ({ route }: { route: Route<string> }) => {
- this.props.navigation.emit({
- type: 'gestureStart',
- target: route.key,
- });
- };
-
- private handleGestureEnd = ({ route }: { route: Route<string> }) => {
- this.props.navigation.emit({
- type: 'gestureEnd',
- target: route.key,
- });
- };
-
- private handleGestureCancel = ({ route }: { route: Route<string> }) => {
- this.props.navigation.emit({
- type: 'gestureCancel',
- target: route.key,
- });
+ ) => { + ) => {
+ const { descriptors } = this.props; + const { descriptors } = this.props;
+ const descriptor = + const descriptor =
+ descriptors[route.key] || this.state.descriptors[route.key]; + descriptors[route.key] || this.state.descriptors[route.key];
+ +
+ descriptor?.options.onTransitionEnd?.({ closing }); + descriptor?.options.onTransitionStart?.({ closing });
+ }; + };
+
+ private handleGestureStart = () => {
+ // Do nothing
+ };
+
+ private handleGestureEnd = () => {
+ // Do nothing
+ };
+
+ private handleGestureCancel = () => {
+ // Do nothing
};
render() { render() {
const { const {
state, state,
@@ -680,16 +738,16 @@ diff -Naur ../../node_modules/@react-navigation/stack/src/views/Stack/StackView.
navigation, navigation,
keyboardHandlingEnabled, keyboardHandlingEnabled,
mode = 'card', mode = 'card',
@@ -427,7 +456,7 @@ @@ -450,7 +469,7 @@
} = this.state; } = this.state;
return ( return (
- <NavigationHelpersContext.Provider value={navigation}> - <NavigationHelpersContext.Provider value={navigation}>
+ <> + <>
<GestureHandlerWrapper style={styles.container}> <GestureHandlerWrapper style={styles.container}>
<SafeAreaProviderCompat> <SafeAreaProviderCompat>
<SafeAreaConsumer> <SafeAreaConsumer>
@@ -460,7 +489,7 @@ @@ -491,7 +510,7 @@
</SafeAreaConsumer> </SafeAreaConsumer>
</SafeAreaProviderCompat> </SafeAreaProviderCompat>
</GestureHandlerWrapper> </GestureHandlerWrapper>

View File

@@ -11,12 +11,9 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
<View <View
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Array [ Object {
Object { "zIndex": 1,
"zIndex": 1, }
},
false,
]
} }
> >
<View <View
@@ -155,16 +152,21 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
onGestureCanceled={[Function]} onGestureCanceled={[Function]}
onGestureEnd={[Function]} onGestureEnd={[Function]}
onOpen={[Function]} onOpen={[Function]}
onTransitionStart={[Function]} onTransition={[Function]}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Array [
"bottom": 0, Object {
"left": 0, "overflow": undefined,
"position": "absolute", },
"right": 0, Object {
"top": 0, "bottom": 0,
} "left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
]
} }
transitionSpec={ transitionSpec={
Object { Object {
@@ -194,6 +196,7 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
} }
> >
<View <View
needsOffscreenAlphaCompositing={false}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Object {
@@ -285,6 +288,112 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
} }
} }
> >
<View
pointerEvents="box-none"
style={
Object {
"zIndex": 1,
}
}
>
<View
accessibilityElementsHidden={false}
importantForAccessibility="auto"
onLayout={[Function]}
pointerEvents="box-none"
style={null}
>
<View
pointerEvents="box-none"
style={
Object {
"bottom": 0,
"left": 0,
"opacity": 1,
"position": "absolute",
"right": 0,
"top": 0,
"zIndex": 0,
}
}
>
<View
style={
Object {
"backgroundColor": "#fff",
"borderBottomColor": "#a7a7aa",
"flex": 1,
"shadowColor": "#a7a7aa",
"shadowOffset": Object {
"height": 0.5,
"width": 0,
},
"shadowOpacity": 0.85,
"shadowRadius": 0,
}
}
/>
</View>
<View
pointerEvents="box-none"
style={
Object {
"height": 44,
"maxHeight": undefined,
"minHeight": undefined,
"opacity": undefined,
"transform": undefined,
}
}
>
<View
pointerEvents="none"
style={
Object {
"height": 0,
}
}
/>
<View
pointerEvents="box-none"
style={
Object {
"alignItems": "center",
"flex": 1,
"flexDirection": "row",
"justifyContent": "center",
}
}
>
<View
pointerEvents="box-none"
style={
Object {
"marginHorizontal": 16,
"opacity": 1,
}
}
>
<Text
accessibilityRole="header"
aria-level="1"
numberOfLines={1}
onLayout={[Function]}
style={
Object {
"color": "rgba(0, 0, 0, 0.9)",
"fontSize": 17,
"fontWeight": "600",
}
}
>
Home
</Text>
</View>
</View>
</View>
</View>
</View>
<View <View
onLayout={[Function]} onLayout={[Function]}
style={ style={
@@ -323,16 +432,21 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
onGestureCanceled={[Function]} onGestureCanceled={[Function]}
onGestureEnd={[Function]} onGestureEnd={[Function]}
onOpen={[Function]} onOpen={[Function]}
onTransitionStart={[Function]} onTransition={[Function]}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Array [
"bottom": 0, Object {
"left": 0, "overflow": undefined,
"position": "absolute", },
"right": 0, Object {
"top": 0, "bottom": 0,
} "left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
]
} }
transitionSpec={ transitionSpec={
Object { Object {
@@ -362,11 +476,11 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
} }
> >
<View <View
needsOffscreenAlphaCompositing={false}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Object {
"flex": 1, "flex": 1,
"marginTop": 0,
} }
} }
> >
@@ -455,22 +569,6 @@ exports[`Nested navigators renders succesfully as direct child 1`] = `
</View> </View>
</View> </View>
</View> </View>
<View
pointerEvents="box-none"
style={
Array [
Object {
"zIndex": 1,
},
Object {
"left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
]
}
/>
</View> </View>
</View> </View>
</View> </View>

View File

@@ -11,12 +11,9 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
<View <View
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Array [ Object {
Object { "zIndex": 1,
"zIndex": 1, }
},
false,
]
} }
> >
<View <View
@@ -172,16 +169,21 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
onGestureCanceled={[Function]} onGestureCanceled={[Function]}
onGestureEnd={[Function]} onGestureEnd={[Function]}
onOpen={[Function]} onOpen={[Function]}
onTransitionStart={[Function]} onTransition={[Function]}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Array [
"bottom": 0, Object {
"left": 0, "overflow": undefined,
"position": "absolute", },
"right": 0, Object {
"top": 0, "bottom": 0,
} "left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
]
} }
transitionSpec={ transitionSpec={
Object { Object {
@@ -211,6 +213,7 @@ exports[`StackNavigator applies correct values when headerRight is present 1`] =
} }
> >
<View <View
needsOffscreenAlphaCompositing={false}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Object {
@@ -317,12 +320,9 @@ exports[`StackNavigator renders successfully 1`] = `
<View <View
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Array [ Object {
Object { "zIndex": 1,
"zIndex": 1, }
},
false,
]
} }
> >
<View <View
@@ -462,16 +462,21 @@ exports[`StackNavigator renders successfully 1`] = `
onGestureCanceled={[Function]} onGestureCanceled={[Function]}
onGestureEnd={[Function]} onGestureEnd={[Function]}
onOpen={[Function]} onOpen={[Function]}
onTransitionStart={[Function]} onTransition={[Function]}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Array [
"bottom": 0, Object {
"left": 0, "overflow": undefined,
"position": "absolute", },
"right": 0, Object {
"top": 0, "bottom": 0,
} "left": 0,
"position": "absolute",
"right": 0,
"top": 0,
},
]
} }
transitionSpec={ transitionSpec={
Object { Object {
@@ -501,6 +506,7 @@ exports[`StackNavigator renders successfully 1`] = `
} }
> >
<View <View
needsOffscreenAlphaCompositing={false}
pointerEvents="box-none" pointerEvents="box-none"
style={ style={
Object { Object {

View File

@@ -289,8 +289,8 @@ export function forScaleFromCenterAndroid({
); );
const opacity = progress.interpolate({ const opacity = progress.interpolate({
inputRange: [0, 0.8, 1, 1.2, 2], inputRange: [0, 0.75, 0.875, 1, 1.0825, 1.2075, 2],
outputRange: [0, 0.5, 1, 0.33, 0], outputRange: [0, 0, 1, 1, 1, 1, 0],
}); });
const scale = conditional( const scale = conditional(

View File

@@ -18,6 +18,7 @@ import {
import type { TransitionPreset } from '../types'; import type { TransitionPreset } from '../types';
const ANDROID_VERSION_PIE = 28; const ANDROID_VERSION_PIE = 28;
const ANDROID_VERSION_10 = 29;
/** /**
* Standard iOS navigation transition. * Standard iOS navigation transition.
@@ -102,10 +103,13 @@ export const ScaleFromCenterAndroid: TransitionPreset = {
*/ */
export const DefaultTransition = Platform.select({ export const DefaultTransition = Platform.select({
ios: SlideFromRightIOS, ios: SlideFromRightIOS,
default: android:
Platform.OS === 'android' && Platform.Version >= ANDROID_VERSION_PIE Platform.Version >= ANDROID_VERSION_10
? ScaleFromCenterAndroid
: Platform.Version >= ANDROID_VERSION_PIE
? RevealFromBottomAndroid ? RevealFromBottomAndroid
: FadeFromBottomAndroid, : FadeFromBottomAndroid,
default: ScaleFromCenterAndroid,
}); });
/** /**

View File

@@ -57,4 +57,5 @@ export type {
StackHeaderInterpolatedStyle, StackHeaderInterpolatedStyle,
StackHeaderInterpolationProps, StackHeaderInterpolationProps,
StackHeaderStyleInterpolator, StackHeaderStyleInterpolator,
TransitionPreset,
} from './types'; } from './types';

View File

@@ -56,6 +56,7 @@ export type StackNavigationEventMap = {
}; };
export type StackNavigationHelpers = NavigationProp<NavigationStackState>; export type StackNavigationHelpers = NavigationProp<NavigationStackState>;
export type StackNavigationProp< export type StackNavigationProp<
State = NavigationRoute, State = NavigationRoute,
Params = NavigationParams Params = NavigationParams
@@ -157,6 +158,10 @@ export type StackHeaderOptions = {
* Whether back button title font should scale to respect Text Size accessibility settings. Defaults to `false`. * Whether back button title font should scale to respect Text Size accessibility settings. Defaults to `false`.
*/ */
headerBackAllowFontScaling?: boolean; headerBackAllowFontScaling?: boolean;
/**
* Accessibility label for the header back button.
*/
headerBackAccessibilityLabel?: string;
/** /**
* Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`. * Title string used by the back button on iOS. Defaults to the previous scene's `headerTitle`.
* Use `headerBackTitleVisible: false` to hide it. * Use `headerBackTitleVisible: false` to hide it.
@@ -336,11 +341,11 @@ export type StackNavigationOptions = StackHeaderOptions &
*/ */
gestureResponseDistance?: { gestureResponseDistance?: {
/** /**
* Distance for horizontal direction. Defaults to 25. * Distance for vertical direction. Defaults to 135.
*/ */
vertical?: number; vertical?: number;
/** /**
* Distance for vertical direction. Defaults to 135. * Distance for horizontal direction. Defaults to 25.
*/ */
horizontal?: number; horizontal?: number;
}; };
@@ -360,6 +365,13 @@ export type StackNavigationOptions = StackHeaderOptions &
bottom?: number; bottom?: number;
left?: number; left?: number;
}; };
/**
* Whether to detach the previous screen from the view hierarchy to save memory.
* Set it to `false` if you need the previous screen to be seen through the active screen.
* Only applicable if `detachInactiveScreens` isn't set to `false`.
* Defaults to `false` for the last screen when mode='modal', otherwise `true`.
*/
detachPreviousScreen?: boolean;
onTransitionStart?: (props: TransitionCallbackProps) => void; onTransitionStart?: (props: TransitionCallbackProps) => void;
onTransitionEnd?: (props: TransitionCallbackProps) => void; onTransitionEnd?: (props: TransitionCallbackProps) => void;
}; };
@@ -372,6 +384,12 @@ export type StackNavigationConfig = {
* Defaults to `true`. * Defaults to `true`.
*/ */
keyboardHandlingEnabled?: boolean; keyboardHandlingEnabled?: boolean;
/**
* Whether inactive screens should be detached from the view hierarchy to save memory.
* Make sure to call `enableScreens` from `react-native-screens` to make it work.
* Defaults to `true` on Android, depends on the version of `react-native-screens` on iOS.
*/
detachInactiveScreens?: boolean;
}; };
export type StackHeaderLeftButtonProps = { export type StackHeaderLeftButtonProps = {

View File

@@ -5,7 +5,7 @@ import { BaseButton } from 'react-native-gesture-handler';
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton); const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
type Props = React.ComponentProps<typeof BaseButton> & { type Props = React.ComponentProps<typeof BaseButton> & {
activeOpacity: number; pressOpacity: number;
}; };
const useNativeDriver = Platform.OS !== 'web'; const useNativeDriver = Platform.OS !== 'web';
@@ -27,7 +27,7 @@ export default class BorderlessButton extends React.Component<Props> {
overshootClamping: true, overshootClamping: true,
restDisplacementThreshold: 0.01, restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01, restSpeedThreshold: 0.01,
toValue: active ? this.props.activeOpacity : 1, toValue: active ? this.props.pressOpacity : 1,
useNativeDriver, useNativeDriver,
}).start(); }).start();
} }

View File

@@ -8,9 +8,9 @@ import {
StyleSheet, StyleSheet,
LayoutChangeEvent, LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
import useTheme from '../../../utils/useTheme';
import MaskedView from '../MaskedView'; import MaskedView from '../MaskedView';
import TouchableItem from '../TouchableItem'; import TouchableItem from '../TouchableItem';
import useTheme from '../../../utils/useTheme';
import type { StackHeaderLeftButtonProps } from '../../types'; import type { StackHeaderLeftButtonProps } from '../../types';
type Props = StackHeaderLeftButtonProps; type Props = StackHeaderLeftButtonProps;

View File

@@ -1,5 +1,5 @@
import * as React from 'react'; import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native'; import { Animated, View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import { NavigationContext } from 'react-navigation'; import { NavigationContext } from 'react-navigation';
import type { EdgeInsets } from 'react-native-safe-area-context'; import type { EdgeInsets } from 'react-native-safe-area-context';
@@ -10,7 +10,6 @@ import {
forNoAnimation, forNoAnimation,
forSlideRight, forSlideRight,
} from '../../TransitionConfigs/HeaderStyleInterpolators'; } from '../../TransitionConfigs/HeaderStyleInterpolators';
import HeaderShownContext from '../../utils/HeaderShownContext';
import PreviousSceneContext from '../../utils/PreviousSceneContext'; import PreviousSceneContext from '../../utils/PreviousSceneContext';
import type { import type {
Layout, Layout,
@@ -52,21 +51,17 @@ export default function HeaderContainer({
style, style,
}: Props) { }: Props) {
const focusedRoute = getFocusedRoute(); const focusedRoute = getFocusedRoute();
const isParentHeaderShown = React.useContext(HeaderShownContext);
const parentPreviousScene = React.useContext(PreviousSceneContext); const parentPreviousScene = React.useContext(PreviousSceneContext);
return ( return (
<View pointerEvents="box-none" style={style}> <Animated.View pointerEvents="box-none" style={style}>
{scenes.slice(-3).map((scene, i, self) => { {scenes.slice(-3).map((scene, i, self) => {
if ((mode === 'screen' && i !== self.length - 1) || !scene) { if ((mode === 'screen' && i !== self.length - 1) || !scene) {
return null; return null;
} }
const { const { header, headerShown = true, headerTransparent } =
header, scene.descriptor.options || {};
headerShown = isParentHeaderShown === false,
headerTransparent,
} = scene.descriptor.options || {};
if (!headerShown) { if (!headerShown) {
return null; return null;
@@ -81,11 +76,10 @@ export default function HeaderContainer({
const previousScene = self[i - 1]; const previousScene = self[i - 1];
const nextScene = self[i + 1]; const nextScene = self[i + 1];
const { const { headerShown: previousHeaderShown = true } =
headerShown: previousHeaderShown = isParentHeaderShown === false, previousScene?.descriptor.options || {};
} = previousScene?.descriptor.options || {};
const { headerShown: nextHeaderShown = isParentHeaderShown === false } = const { headerShown: nextHeaderShown = true } =
nextScene?.descriptor.options || {}; nextScene?.descriptor.options || {};
const isHeaderStatic = const isHeaderStatic =
@@ -124,11 +118,14 @@ export default function HeaderContainer({
<View <View
onLayout={ onLayout={
onContentHeightChange onContentHeightChange
? (e) => ? (e) => {
const { height } = e.nativeEvent.layout;
onContentHeightChange({ onContentHeightChange({
route: scene.route, route: scene.route,
height: e.nativeEvent.layout.height, height,
}) });
}
: undefined : undefined
} }
pointerEvents={isFocused ? 'box-none' : 'none'} pointerEvents={isFocused ? 'box-none' : 'none'}
@@ -150,7 +147,7 @@ export default function HeaderContainer({
</NavigationContext.Provider> </NavigationContext.Provider>
); );
})} })}
</View> </Animated.View>
); );
} }

View File

@@ -1,170 +0,0 @@
import * as React from 'react';
import { View, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import {
NavigationContext,
NavigationRouteContext,
Route,
ParamListBase,
} from '@react-navigation/native';
import type { EdgeInsets } from 'react-native-safe-area-context';
import Header from './Header';
import {
forSlideLeft,
forSlideUp,
forNoAnimation,
forSlideRight,
} from '../../TransitionConfigs/HeaderStyleInterpolators';
import HeaderShownContext from '../../utils/HeaderShownContext';
import PreviousSceneContext from '../../utils/PreviousSceneContext';
import type {
Layout,
Scene,
StackHeaderStyleInterpolator,
StackNavigationProp,
GestureDirection,
} from '../../types';
export type Props = {
mode: 'float' | 'screen';
layout: Layout;
insets: EdgeInsets;
scenes: (Scene<Route<string>> | undefined)[];
getPreviousScene: (props: {
route: Route<string>;
}) => Scene<Route<string>> | undefined;
getFocusedRoute: () => Route<string>;
onContentHeightChange?: (props: {
route: Route<string>;
height: number;
}) => void;
styleInterpolator: StackHeaderStyleInterpolator;
gestureDirection: GestureDirection;
style?: StyleProp<ViewStyle>;
};
export default function HeaderContainer({
mode,
scenes,
layout,
insets,
getPreviousScene,
getFocusedRoute,
onContentHeightChange,
gestureDirection,
styleInterpolator,
style,
}: Props) {
const focusedRoute = getFocusedRoute();
const isParentHeaderShown = React.useContext(HeaderShownContext);
const parentPreviousScene = React.useContext(PreviousSceneContext);
return (
<View pointerEvents="box-none" style={style}>
{scenes.slice(-3).map((scene, i, self) => {
if ((mode === 'screen' && i !== self.length - 1) || !scene) {
return null;
}
const {
header,
headerShown = isParentHeaderShown === false,
headerTransparent,
} = scene.descriptor.options || {};
if (!headerShown) {
return null;
}
const isFocused = focusedRoute.key === scene.route.key;
const previous =
getPreviousScene({ route: scene.route }) ?? parentPreviousScene;
// If the screen is next to a headerless screen, we need to make the header appear static
// This makes the header look like it's moving with the screen
const previousScene = self[i - 1];
const nextScene = self[i + 1];
const {
headerShown: previousHeaderShown = isParentHeaderShown === false,
} = previousScene?.descriptor.options || {};
const { headerShown: nextHeaderShown = isParentHeaderShown === false } =
nextScene?.descriptor.options || {};
const isHeaderStatic =
(previousHeaderShown === false &&
// We still need to animate when coming back from next scene
// A hacky way to check this is if the next scene exists
!nextScene) ||
nextHeaderShown === false;
const props = {
mode,
layout,
insets,
scene,
previous,
navigation: scene.descriptor.navigation as StackNavigationProp<
ParamListBase
>,
styleInterpolator:
mode === 'float'
? isHeaderStatic
? gestureDirection === 'vertical' ||
gestureDirection === 'vertical-inverted'
? forSlideUp
: gestureDirection === 'horizontal-inverted'
? forSlideRight
: forSlideLeft
: styleInterpolator
: forNoAnimation,
};
return (
<NavigationContext.Provider
key={scene.route.key}
value={scene.descriptor.navigation}
>
<NavigationRouteContext.Provider value={scene.route}>
<View
onLayout={
onContentHeightChange
? (e) =>
onContentHeightChange({
route: scene.route,
height: e.nativeEvent.layout.height,
})
: undefined
}
pointerEvents={isFocused ? 'box-none' : 'none'}
accessibilityElementsHidden={!isFocused}
importantForAccessibility={
isFocused ? 'auto' : 'no-hide-descendants'
}
style={
// Avoid positioning the focused header absolutely
// Otherwise accessibility tools don't seem to be able to find it
(mode === 'float' && !isFocused) || headerTransparent
? styles.header
: null
}
>
{header !== undefined ? header(props) : <Header {...props} />}
</View>
</NavigationRouteContext.Provider>
</NavigationContext.Provider>
);
})}
</View>
);
}
const styles = StyleSheet.create({
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
});

View File

@@ -11,6 +11,7 @@ import type { EdgeInsets } from 'react-native-safe-area-context';
import type { NavigationRoute } from 'react-navigation'; import type { NavigationRoute } from 'react-navigation';
import HeaderBackButton from './HeaderBackButton'; import HeaderBackButton from './HeaderBackButton';
import HeaderBackground from './HeaderBackground'; import HeaderBackground from './HeaderBackground';
import HeaderShownContext from '../../utils/HeaderShownContext';
import memoize from '../../utils/memoize'; import memoize from '../../utils/memoize';
import type { import type {
Layout, Layout,
@@ -32,11 +33,6 @@ type Props = StackHeaderOptions & {
styleInterpolator: StackHeaderStyleInterpolator; styleInterpolator: StackHeaderStyleInterpolator;
}; };
type State = {
titleLayout?: Layout;
leftLabelLayout?: Layout;
};
const warnIfHeaderStylesDefined = (styles: Record<string, any>) => { const warnIfHeaderStylesDefined = (styles: Record<string, any>) => {
Object.keys(styles).forEach((styleProp) => { Object.keys(styles).forEach((styleProp) => {
const value = styles[styleProp]; const value = styles[styleProp];
@@ -76,30 +72,35 @@ export const getDefaultHeaderHeight = (
return headerHeight + statusBarHeight; return headerHeight + statusBarHeight;
}; };
export default class HeaderSegment extends React.Component<Props, State> { export default function HeaderSegment(props: Props) {
state: State = {}; const [leftLabelLayout, setLeftLabelLayout] = React.useState<
Layout | undefined
>(undefined);
private handleTitleLayout = (e: LayoutChangeEvent) => { const [titleLayout, setTitleLayout] = React.useState<Layout | undefined>(
undefined
);
const isParentHeaderShown = React.useContext(HeaderShownContext);
const handleTitleLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout; const { height, width } = e.nativeEvent.layout;
this.setState(({ titleLayout }) => { setTitleLayout((titleLayout) => {
if ( if (
titleLayout && titleLayout &&
height === titleLayout.height && height === titleLayout.height &&
width === titleLayout.width width === titleLayout.width
) { ) {
return null; return titleLayout;
} }
return { return { height, width };
titleLayout: { height, width },
};
}); });
}; };
private handleLeftLabelLayout = (e: LayoutChangeEvent) => { const handleLeftLabelLayout = (e: LayoutChangeEvent) => {
const { height, width } = e.nativeEvent.layout; const { height, width } = e.nativeEvent.layout;
const { leftLabelLayout } = this.state;
if ( if (
leftLabelLayout && leftLabelLayout &&
@@ -109,10 +110,10 @@ export default class HeaderSegment extends React.Component<Props, State> {
return; return;
} }
this.setState({ leftLabelLayout: { height, width } }); setLeftLabelLayout({ height, width });
}; };
private getInterpolatedStyle = memoize( const getInterpolatedStyle = memoize(
( (
styleInterpolator: StackHeaderStyleInterpolator, styleInterpolator: StackHeaderStyleInterpolator,
layout: Layout, layout: Layout,
@@ -137,258 +138,253 @@ export default class HeaderSegment extends React.Component<Props, State> {
}) })
); );
render() { const {
const { scene,
scene, layout,
layout, insets,
insets, title: currentTitle,
title: currentTitle, leftLabel: previousTitle,
leftLabel: previousTitle, onGoBack,
onGoBack, headerTitle,
headerTitle, headerTitleAlign = Platform.select({
headerTitleAlign = Platform.select({ ios: 'center',
ios: 'center', default: 'left',
default: 'left', }),
}), headerLeft: left = onGoBack
headerLeft: left = onGoBack ? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} />
? (props: StackHeaderLeftButtonProps) => <HeaderBackButton {...props} /> : undefined,
: undefined, headerTransparent,
headerTransparent, headerTintColor,
headerTintColor, headerBackground,
headerBackground, headerRight: right,
headerRight: right, headerBackImage: backImage,
headerBackImage: backImage, headerBackTitle: leftLabel,
headerBackTitle: leftLabel, headerBackTitleVisible,
headerBackTitleVisible, headerTruncatedBackTitle: truncatedLabel,
headerTruncatedBackTitle: truncatedLabel, headerPressColorAndroid: pressColorAndroid,
headerPressColorAndroid: pressColorAndroid, headerBackAccessibilityLabel: backAccessibilityLabel,
headerBackAllowFontScaling: backAllowFontScaling, headerBackAllowFontScaling: backAllowFontScaling,
headerTitleAllowFontScaling: titleAllowFontScaling, headerTitleAllowFontScaling: titleAllowFontScaling,
headerTitleStyle: customTitleStyle, headerTitleStyle: customTitleStyle,
headerBackTitleStyle: customLeftLabelStyle, headerBackTitleStyle: customLeftLabelStyle,
headerLeftContainerStyle: leftContainerStyle, headerLeftContainerStyle: leftContainerStyle,
headerRightContainerStyle: rightContainerStyle, headerRightContainerStyle: rightContainerStyle,
headerTitleContainerStyle: titleContainerStyle, headerTitleContainerStyle: titleContainerStyle,
headerStyle: customHeaderStyle, headerStyle: customHeaderStyle,
headerStatusBarHeight = insets.top, headerStatusBarHeight = isParentHeaderShown ? 0 : insets.top,
styleInterpolator, styleInterpolator,
} = this.props; } = props;
const { leftLabelLayout, titleLayout } = this.state; const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight);
const defaultHeight = getDefaultHeaderHeight(layout, headerStatusBarHeight); const {
height = defaultHeight,
minHeight,
maxHeight,
backgroundColor,
borderBottomColor,
borderBottomEndRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
borderBottomStartRadius,
borderBottomWidth,
borderColor,
borderEndColor,
borderEndWidth,
borderLeftColor,
borderLeftWidth,
borderRadius,
borderRightColor,
borderRightWidth,
borderStartColor,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
// @ts-expect-error: web support for shadow
boxShadow,
elevation,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
opacity,
transform,
...unsafeStyles
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
const { if (process.env.NODE_ENV !== 'production') {
height = defaultHeight, warnIfHeaderStylesDefined(unsafeStyles);
minHeight, }
maxHeight,
backgroundColor,
borderBottomColor,
borderBottomEndRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
borderBottomStartRadius,
borderBottomWidth,
borderColor,
borderEndColor,
borderEndWidth,
borderLeftColor,
borderLeftWidth,
borderRadius,
borderRightColor,
borderRightWidth,
borderStartColor,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
// @ts-expect-error: web support for shadow
boxShadow,
elevation,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
opacity,
transform,
...unsafeStyles
} = StyleSheet.flatten(customHeaderStyle || {}) as ViewStyle;
if (process.env.NODE_ENV !== 'production') { const safeStyles: ViewStyle = {
warnIfHeaderStylesDefined(unsafeStyles); backgroundColor,
borderBottomColor,
borderBottomEndRadius,
borderBottomLeftRadius,
borderBottomRightRadius,
borderBottomStartRadius,
borderBottomWidth,
borderColor,
borderEndColor,
borderEndWidth,
borderLeftColor,
borderLeftWidth,
borderRadius,
borderRightColor,
borderRightWidth,
borderStartColor,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
// @ts-expect-error: boxShadow is only for Web
boxShadow,
elevation,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
opacity,
transform,
};
// Setting a property to undefined triggers default style
// So we need to filter them out
// Users can use `null` instead
for (const styleProp in safeStyles) {
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles
if (safeStyles[styleProp] === undefined) {
// @ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete safeStyles[styleProp];
} }
}
const safeStyles: ViewStyle = { const {
backgroundColor, titleStyle,
borderBottomColor, leftButtonStyle,
borderBottomEndRadius, leftLabelStyle,
borderBottomLeftRadius, rightButtonStyle,
borderBottomRightRadius, backgroundStyle,
borderBottomStartRadius, } = getInterpolatedStyle(
borderBottomWidth, styleInterpolator,
borderColor, layout,
borderEndColor, scene.progress.current,
borderEndWidth, scene.progress.next,
borderLeftColor, titleLayout,
borderLeftWidth, previousTitle ? leftLabelLayout : undefined,
borderRadius, typeof height === 'number' ? height : defaultHeight
borderRightColor, );
borderRightWidth,
borderStartColor,
borderStartWidth,
borderStyle,
borderTopColor,
borderTopEndRadius,
borderTopLeftRadius,
borderTopRightRadius,
borderTopStartRadius,
borderTopWidth,
borderWidth,
// @ts-expect-error: boxShadow is only for Web
boxShadow,
elevation,
shadowColor,
shadowOffset,
shadowOpacity,
shadowRadius,
opacity,
transform,
};
// Setting a property to undefined triggers default style const leftButton = left
// So we need to filter them out ? left({
// Users can use `null` instead backImage,
for (const styleProp in safeStyles) { pressColorAndroid,
// @ts-expect-error: typescript wrongly complains that styleProp cannot be used to index safeStyles accessibilityLabel: backAccessibilityLabel,
if (safeStyles[styleProp] === undefined) { allowFontScaling: backAllowFontScaling,
// @ts-expect-error onPress: onGoBack,
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete labelVisible: headerBackTitleVisible,
delete safeStyles[styleProp]; label: leftLabel !== undefined ? leftLabel : previousTitle,
} truncatedLabel,
} labelStyle: [leftLabelStyle, customLeftLabelStyle],
onLabelLayout: handleLeftLabelLayout,
screenLayout: layout,
titleLayout,
tintColor: headerTintColor,
canGoBack: Boolean(onGoBack),
})
: null;
const { const rightButton = right ? right({ tintColor: headerTintColor }) : null;
titleStyle,
leftButtonStyle,
leftLabelStyle,
rightButtonStyle,
backgroundStyle,
} = this.getInterpolatedStyle(
styleInterpolator,
layout,
scene.progress.current,
scene.progress.next,
titleLayout,
previousTitle ? leftLabelLayout : undefined,
typeof height === 'number' ? height : defaultHeight
);
const leftButton = left return (
? left({ <React.Fragment>
backImage, <Animated.View
pressColorAndroid, pointerEvents="box-none"
allowFontScaling: backAllowFontScaling, style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
onPress: onGoBack, >
labelVisible: headerBackTitleVisible, {headerBackground ? (
label: leftLabel !== undefined ? leftLabel : previousTitle, headerBackground({ style: safeStyles })
truncatedLabel, ) : headerTransparent ? null : (
labelStyle: [leftLabelStyle, customLeftLabelStyle], <HeaderBackground style={safeStyles} />
onLabelLayout: this.handleLeftLabelLayout, )}
screenLayout: layout, </Animated.View>
titleLayout, <Animated.View
tintColor: headerTintColor, pointerEvents="box-none"
canGoBack: Boolean(onGoBack), style={[{ height, minHeight, maxHeight, opacity, transform }]}
}) >
: null; <View pointerEvents="none" style={{ height: headerStatusBarHeight }} />
<View pointerEvents="box-none" style={styles.content}>
const rightButton = right ? right({ tintColor: headerTintColor }) : null; {leftButton ? (
return (
<React.Fragment>
<Animated.View
pointerEvents="box-none"
style={[StyleSheet.absoluteFill, { zIndex: 0 }, backgroundStyle]}
>
{headerBackground ? (
headerBackground({ style: safeStyles })
) : headerTransparent ? null : (
<HeaderBackground style={safeStyles} />
)}
</Animated.View>
<Animated.View
pointerEvents="box-none"
style={[{ height, minHeight, maxHeight, opacity, transform }]}
>
<View
pointerEvents="none"
style={{ height: headerStatusBarHeight }}
/>
<View pointerEvents="box-none" style={styles.content}>
{leftButton ? (
<Animated.View
pointerEvents="box-none"
style={[
styles.left,
{ left: insets.left },
leftButtonStyle,
leftContainerStyle,
]}
>
{leftButton}
</Animated.View>
) : null}
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[ style={[
headerTitleAlign === 'left' styles.left,
? { { left: insets.left },
position: 'absolute', leftButtonStyle,
left: (leftButton ? 72 : 16) + insets.left, leftContainerStyle,
right: (rightButton ? 72 : 16) + insets.right,
}
: {
marginHorizontal:
(leftButton ? 32 : 16) +
(leftButton && headerBackTitleVisible !== false
? 40
: 0) +
Math.max(insets.left, insets.right),
},
titleStyle,
titleContainerStyle,
]} ]}
> >
{headerTitle({ {leftButton}
children: currentTitle,
onLayout: this.handleTitleLayout,
allowFontScaling: titleAllowFontScaling,
tintColor: headerTintColor,
style: customTitleStyle,
})}
</Animated.View> </Animated.View>
{rightButton ? ( ) : null}
<Animated.View <Animated.View
pointerEvents="box-none" pointerEvents="box-none"
style={[ style={[
styles.right, headerTitleAlign === 'left'
{ right: insets.right }, ? {
rightButtonStyle, position: 'absolute',
rightContainerStyle, left: (leftButton ? 72 : 16) + insets.left,
]} right: (rightButton ? 72 : 16) + insets.right,
> }
{rightButton} : {
</Animated.View> marginHorizontal:
) : null} (leftButton ? 32 : 16) +
</View> (leftButton && headerBackTitleVisible !== false
</Animated.View> ? 40
</React.Fragment> : 0) +
); Math.max(insets.left, insets.right),
} },
titleStyle,
titleContainerStyle,
]}
>
{headerTitle({
children: currentTitle,
onLayout: handleTitleLayout,
allowFontScaling: titleAllowFontScaling,
tintColor: headerTintColor,
style: customTitleStyle,
})}
</Animated.View>
{rightButton ? (
<Animated.View
pointerEvents="box-none"
style={[
styles.right,
{ right: insets.right },
rightButtonStyle,
rightContainerStyle,
]}
>
{rightButton}
</Animated.View>
) : null}
</View>
</Animated.View>
</React.Fragment>
);
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({

View File

@@ -1,15 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import { TextInput, Platform, Keyboard } from 'react-native'; import { TextInput, Keyboard, HostComponent } from 'react-native';
type Props = { type Props = {
enabled: boolean; enabled: boolean;
children: (props: { children: (props: {
onPageChangeStart: () => void; onPageChangeStart: () => void;
onPageChangeConfirm: () => void; onPageChangeConfirm: (force: boolean) => void;
onPageChangeCancel: () => void; onPageChangeCancel: () => void;
}) => React.ReactNode; }) => React.ReactNode;
}; };
type InputRef = React.ElementRef<HostComponent<unknown>> | undefined;
export default class KeyboardManager extends React.Component<Props> { export default class KeyboardManager extends React.Component<Props> {
componentWillUnmount() { componentWillUnmount() {
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
@@ -17,7 +19,7 @@ export default class KeyboardManager extends React.Component<Props> {
// Numeric id of the previously focused text input // Numeric id of the previously focused text input
// When a gesture didn't change the tab, we can restore the focused input with this // When a gesture didn't change the tab, we can restore the focused input with this
private previouslyFocusedTextInput: any | null = null; private previouslyFocusedTextInput: InputRef = undefined;
private startTimestamp: number = 0; private startTimestamp: number = 0;
private keyboardTimeout: any; private keyboardTimeout: any;
@@ -35,7 +37,8 @@ export default class KeyboardManager extends React.Component<Props> {
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
const input: any = TextInput.State.currentlyFocusedInput // @ts-expect-error: blurTextInput accepts both number and ref, but types say only ref
const input: InputRef = TextInput.State.currentlyFocusedInput
? TextInput.State.currentlyFocusedInput() ? TextInput.State.currentlyFocusedInput()
: TextInput.State.currentlyFocusedField(); : TextInput.State.currentlyFocusedField();
@@ -49,23 +52,30 @@ export default class KeyboardManager extends React.Component<Props> {
this.startTimestamp = Date.now(); this.startTimestamp = Date.now();
}; };
private handlePageChangeConfirm = () => { private handlePageChangeConfirm = (force: boolean) => {
if (!this.props.enabled) { if (!this.props.enabled) {
return; return;
} }
this.clearKeyboardTimeout(); this.clearKeyboardTimeout();
const input = this.previouslyFocusedTextInput; if (force) {
// Always dismiss input, even if we don't have a ref to it
if (Platform.OS === 'android') { // We might not have the ref if onPageChangeStart was never called
// This can happen if page change was not from a gesture
Keyboard.dismiss(); Keyboard.dismiss();
} else if (input) { } else {
TextInput.State.blurTextInput(input); const input = this.previouslyFocusedTextInput;
if (input) {
// Dismiss the keyboard only if an input was a focused before
// This makes sure we don't dismiss input on going back and focusing an input
TextInput.State.blurTextInput(input);
}
} }
// Cleanup the ID on successful page change // Cleanup the ID on successful page change
this.previouslyFocusedTextInput = null; this.previouslyFocusedTextInput = undefined;
}; };
private handlePageChangeCancel = () => { private handlePageChangeCancel = () => {
@@ -89,11 +99,11 @@ export default class KeyboardManager extends React.Component<Props> {
if (Date.now() - this.startTimestamp < 100) { if (Date.now() - this.startTimestamp < 100) {
this.keyboardTimeout = setTimeout(() => { this.keyboardTimeout = setTimeout(() => {
TextInput.State.focusTextInput(input); TextInput.State.focusTextInput(input);
this.previouslyFocusedTextInput = null; this.previouslyFocusedTextInput = undefined;
}, 100); }, 100);
} else { } else {
TextInput.State.focusTextInput(input); TextInput.State.focusTextInput(input);
this.previouslyFocusedTextInput = null; this.previouslyFocusedTextInput = undefined;
} }
} }
}; };

View File

@@ -34,6 +34,9 @@ class WebScreen extends React.Component<
const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen); const AnimatedWebScreen = Animated.createAnimatedComponent(WebScreen);
// @ts-ignore
export const shouldUseActivityState = Screens?.shouldUseActivityState;
export const MaybeScreenContainer = ({ export const MaybeScreenContainer = ({
enabled, enabled,
...rest ...rest
@@ -41,8 +44,11 @@ export const MaybeScreenContainer = ({
enabled: boolean; enabled: boolean;
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
if (enabled && Platform.OS !== 'web' && Screens && Screens.screensEnabled()) { if (enabled && Platform.OS !== 'web' && Screens?.screensEnabled()) {
return <Screens.ScreenContainer {...rest} />; return (
// @ts-ignore
<Screens.ScreenContainer enabled={enabled} {...rest} />
);
} }
return <View {...rest} />; return <View {...rest} />;
@@ -54,16 +60,25 @@ export const MaybeScreen = ({
...rest ...rest
}: ViewProps & { }: ViewProps & {
enabled: boolean; enabled: boolean;
active: 0 | 1 | Animated.AnimatedInterpolation; active: 0 | 1 | 2 | Animated.AnimatedInterpolation;
children: React.ReactNode; children: React.ReactNode;
}) => { }) => {
if (enabled && Platform.OS === 'web') { if (enabled && Platform.OS === 'web') {
return <AnimatedWebScreen active={active} {...rest} />; return <AnimatedWebScreen active={active} {...rest} />;
} }
if (enabled && Screens && Screens.screensEnabled()) { if (enabled && Screens?.screensEnabled()) {
// @ts-expect-error: stackPresentation is incorrectly marked as required if (shouldUseActivityState) {
return <Screens.Screen active={active} {...rest} />; return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screens.Screen enabled={enabled} activityState={active} {...rest} />
);
} else {
return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screens.Screen enabled={enabled} active={active} {...rest} />
);
}
} }
return <View {...rest} />; return <View {...rest} />;

View File

@@ -41,7 +41,7 @@ type Props = ViewProps & {
gestureDirection: GestureDirection; gestureDirection: GestureDirection;
onOpen: () => void; onOpen: () => void;
onClose: () => void; onClose: () => void;
onTransitionStart?: (props: { closing: boolean }) => void; onTransition?: (props: { closing: boolean; gesture: boolean }) => void;
onGestureBegin?: () => void; onGestureBegin?: () => void;
onGestureCanceled?: () => void; onGestureCanceled?: () => void;
onGestureEnd?: () => void; onGestureEnd?: () => void;
@@ -79,6 +79,15 @@ const GESTURE_RESPONSE_DISTANCE_VERTICAL = 135;
const useNativeDriver = Platform.OS !== 'web'; const useNativeDriver = Platform.OS !== 'web';
const hasOpacityStyle = (style: any) => {
if (style) {
const flattenedStyle = StyleSheet.flatten(style);
return flattenedStyle.opacity != null;
}
return false;
};
export default class Card extends React.Component<Props> { export default class Card extends React.Component<Props> {
static defaultProps = { static defaultProps = {
overlayEnabled: Platform.OS !== 'ios', overlayEnabled: Platform.OS !== 'ios',
@@ -169,7 +178,7 @@ export default class Card extends React.Component<Props> {
transitionSpec, transitionSpec,
onOpen, onOpen,
onClose, onClose,
onTransitionStart, onTransition,
} = this.props; } = this.props;
const toValue = this.getAnimateToValue({ const toValue = this.getAnimateToValue({
@@ -189,7 +198,7 @@ export default class Card extends React.Component<Props> {
clearTimeout(this.pendingGestureCallback); clearTimeout(this.pendingGestureCallback);
onTransitionStart?.({ closing }); onTransition?.({ closing, gesture: velocity !== undefined });
animation(gesture, { animation(gesture, {
...spec.config, ...spec.config,
velocity, velocity,
@@ -533,6 +542,7 @@ export default class Card extends React.Component<Props> {
</View> </View>
) : null} ) : null}
<Animated.View <Animated.View
needsOffscreenAlphaCompositing={hasOpacityStyle(containerStyle)}
style={[styles.container, containerStyle, customContainerStyle]} style={[styles.container, containerStyle, customContainerStyle]}
pointerEvents="box-none" pointerEvents="box-none"
> >

View File

@@ -47,7 +47,7 @@ type Props = TransitionPreset & {
) => void; ) => void;
onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void; onTransitionEnd?: (props: { route: Route<string> }, closing: boolean) => void;
onPageChangeStart?: () => void; onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void; onPageChangeConfirm?: (force: boolean) => void;
onPageChangeCancel?: () => void; onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void; onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void; onGestureEnd?: (props: { route: Route<string> }) => void;
@@ -67,6 +67,7 @@ type Props = TransitionPreset & {
route: Route<string>; route: Route<string>;
height: number; height: number;
}) => void; }) => void;
isParentHeaderShown: boolean;
}; };
const EPSILON = 0.1; const EPSILON = 0.1;
@@ -94,6 +95,7 @@ function CardContainer({
hasAbsoluteHeader, hasAbsoluteHeader,
headerHeight, headerHeight,
onHeaderHeightChange, onHeaderHeightChange,
isParentHeaderShown,
index, index,
layout, layout,
onCloseRoute, onCloseRoute,
@@ -115,42 +117,58 @@ function CardContainer({
scene, scene,
transitionSpec, transitionSpec,
}: Props) { }: Props) {
React.useEffect(() => {
onPageChangeConfirm?.();
}, [active, onPageChangeConfirm]);
const handleOpen = () => { const handleOpen = () => {
onTransitionEnd?.({ route: scene.route }, false); const { route } = scene;
onOpenRoute({ route: scene.route });
onTransitionEnd?.({ route }, false);
onOpenRoute({ route });
}; };
const handleClose = () => { const handleClose = () => {
onTransitionEnd?.({ route: scene.route }, true); const { route } = scene;
onCloseRoute({ route: scene.route });
onTransitionEnd?.({ route }, true);
onCloseRoute({ route });
}; };
const handleGestureBegin = () => { const handleGestureBegin = () => {
const { route } = scene;
onPageChangeStart?.(); onPageChangeStart?.();
onGestureStart?.({ route: scene.route }); onGestureStart?.({ route });
}; };
const handleGestureCanceled = () => { const handleGestureCanceled = () => {
const { route } = scene;
onPageChangeCancel?.(); onPageChangeCancel?.();
onGestureCancel?.({ route: scene.route }); onGestureCancel?.({ route });
}; };
const handleGestureEnd = () => { const handleGestureEnd = () => {
onGestureEnd?.({ route: scene.route }); const { route } = scene;
onGestureEnd?.({ route });
}; };
const handleTransitionStart = ({ closing }: { closing: boolean }) => { const handleTransition = ({
if (active && closing) { closing,
onPageChangeConfirm?.(); gesture,
}: {
closing: boolean;
gesture: boolean;
}) => {
const { route } = scene;
if (!gesture) {
onPageChangeConfirm?.(true);
} else if (active && closing) {
onPageChangeConfirm?.(false);
} else { } else {
onPageChangeCancel?.(); onPageChangeCancel?.();
} }
onTransitionStart?.({ route: scene.route }, closing); onTransitionStart?.({ route }, closing);
}; };
const insets = { const insets = {
@@ -182,7 +200,6 @@ function CardContainer({
}; };
}, [pointerEvents, scene.progress.next]); }, [pointerEvents, scene.progress.next]);
const isParentHeaderShown = React.useContext(HeaderShownContext);
const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false; const isCurrentHeaderShown = headerMode !== 'none' && headerShown !== false;
const previousScene = getPreviousScene({ route: scene.route }); const previousScene = getPreviousScene({ route: scene.route });
@@ -201,7 +218,7 @@ function CardContainer({
overlay={cardOverlay} overlay={cardOverlay}
overlayEnabled={cardOverlayEnabled} overlayEnabled={cardOverlayEnabled}
shadowEnabled={cardShadowEnabled} shadowEnabled={cardShadowEnabled}
onTransitionStart={handleTransitionStart} onTransition={handleTransition}
onGestureBegin={handleGestureBegin} onGestureBegin={handleGestureBegin}
onGestureCanceled={handleGestureCanceled} onGestureCanceled={handleGestureCanceled}
onGestureEnd={handleGestureEnd} onGestureEnd={handleGestureEnd}
@@ -216,7 +233,14 @@ function CardContainer({
pageOverflowEnabled={headerMode === 'screen' && mode === 'card'} pageOverflowEnabled={headerMode === 'screen' && mode === 'card'}
containerStyle={hasAbsoluteHeader ? { marginTop: headerHeight } : null} containerStyle={hasAbsoluteHeader ? { marginTop: headerHeight } : null}
contentStyle={[{ backgroundColor: colors.background }, cardStyle]} contentStyle={[{ backgroundColor: colors.background }, cardStyle]}
style={StyleSheet.absoluteFill} style={[
{
// This is necessary to avoid unfocused larger pages increasing scroll area
// The issue can be seen on the web when a smaller screen is pushed over a larger one
overflow: active ? undefined : 'hidden',
},
StyleSheet.absoluteFill,
]}
> >
<View style={styles.container}> <View style={styles.container}>
<View style={styles.scene}> <View style={styles.scene}>

View File

@@ -9,7 +9,11 @@ import {
import type { EdgeInsets } from 'react-native-safe-area-context'; import type { EdgeInsets } from 'react-native-safe-area-context';
import type { NavigationState as StackNavigationState } from 'react-navigation'; import type { NavigationState as StackNavigationState } from 'react-navigation';
import { MaybeScreenContainer, MaybeScreen } from '../Screens'; import {
MaybeScreenContainer,
MaybeScreen,
shouldUseActivityState,
} from '../Screens';
import { getDefaultHeaderHeight } from '../Header/HeaderSegment'; import { getDefaultHeaderHeight } from '../Header/HeaderSegment';
import type { Props as HeaderContainerProps } from '../Header/HeaderContainer'; import type { Props as HeaderContainerProps } from '../Header/HeaderContainer';
import CardContainer from './CardContainer'; import CardContainer from './CardContainer';
@@ -19,7 +23,6 @@ import {
} from '../../TransitionConfigs/TransitionPresets'; } from '../../TransitionConfigs/TransitionPresets';
import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators'; import { forNoAnimation as forNoAnimationHeader } from '../../TransitionConfigs/HeaderStyleInterpolators';
import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators'; import { forNoAnimation as forNoAnimationCard } from '../../TransitionConfigs/CardStyleInterpolators';
import HeaderShownContext from '../../utils/HeaderShownContext';
import getDistanceForDirection from '../../utils/getDistanceForDirection'; import getDistanceForDirection from '../../utils/getDistanceForDirection';
import type { import type {
Layout, Layout,
@@ -53,17 +56,19 @@ type Props = {
renderHeader: (props: HeaderContainerProps) => React.ReactNode; renderHeader: (props: HeaderContainerProps) => React.ReactNode;
renderScene: (props: { route: Route<string> }) => React.ReactNode; renderScene: (props: { route: Route<string> }) => React.ReactNode;
headerMode: StackHeaderMode; headerMode: StackHeaderMode;
isParentHeaderShown: boolean;
onTransitionStart: ( onTransitionStart: (
props: { route: Route<string> }, props: { route: Route<string> },
closing: boolean closing: boolean
) => void; ) => void;
onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void; onTransitionEnd: (props: { route: Route<string> }, closing: boolean) => void;
onPageChangeStart?: () => void; onPageChangeStart?: () => void;
onPageChangeConfirm?: () => void; onPageChangeConfirm?: (force: boolean) => void;
onPageChangeCancel?: () => void; onPageChangeCancel?: () => void;
onGestureStart?: (props: { route: Route<string> }) => void; onGestureStart?: (props: { route: Route<string> }) => void;
onGestureEnd?: (props: { route: Route<string> }) => void; onGestureEnd?: (props: { route: Route<string> }) => void;
onGestureCancel?: (props: { route: Route<string> }) => void; onGestureCancel?: (props: { route: Route<string> }) => void;
detachInactiveScreens?: boolean;
}; };
type State = { type State = {
@@ -77,11 +82,16 @@ type State = {
const EPSILON = 0.01; const EPSILON = 0.01;
const STATE_INACTIVE = 0;
const STATE_TRANSITIONING_OR_BELOW_TOP = 1;
const STATE_ON_TOP = 2;
const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} }); const FALLBACK_DESCRIPTOR = Object.freeze({ options: {} });
const getHeaderHeights = ( const getHeaderHeights = (
routes: Route<string>[], routes: Route<string>[],
insets: EdgeInsets, insets: EdgeInsets,
isParentHeaderShown: boolean,
descriptors: StackDescriptorMap, descriptors: StackDescriptorMap,
layout: Layout, layout: Layout,
previous: Record<string, number> previous: Record<string, number>
@@ -98,7 +108,9 @@ const getHeaderHeights = (
...options.safeAreaInsets, ...options.safeAreaInsets,
}; };
const { headerStatusBarHeight = safeAreaInsets.top } = options; const {
headerStatusBarHeight = isParentHeaderShown ? 0 : safeAreaInsets.top,
} = options;
acc[curr.key] = acc[curr.key] =
typeof height === 'number' typeof height === 'number'
@@ -261,6 +273,7 @@ export default class CardStack extends React.Component<Props, State> {
headerHeights: getHeaderHeights( headerHeights: getHeaderHeights(
props.routes, props.routes,
props.insets, props.insets,
props.isParentHeaderShown,
state.descriptors, state.descriptors,
state.layout, state.layout,
state.headerHeights state.headerHeights
@@ -303,6 +316,7 @@ export default class CardStack extends React.Component<Props, State> {
headerHeights: getHeaderHeights( headerHeights: getHeaderHeights(
props.routes, props.routes,
props.insets, props.insets,
props.isParentHeaderShown,
state.descriptors, state.descriptors,
layout, layout,
state.headerHeights state.headerHeights
@@ -371,6 +385,7 @@ export default class CardStack extends React.Component<Props, State> {
renderHeader, renderHeader,
renderScene, renderScene,
headerMode, headerMode,
isParentHeaderShown,
onTransitionStart, onTransitionStart,
onTransitionEnd, onTransitionEnd,
onPageChangeStart, onPageChangeStart,
@@ -379,6 +394,9 @@ export default class CardStack extends React.Component<Props, State> {
onGestureStart, onGestureStart,
onGestureEnd, onGestureEnd,
onGestureCancel, onGestureCancel,
// Enable on new versions of `react-native-screens`
// On older versions of `react-native-screens`, there's an issue with screens not being responsive to user interaction.
detachInactiveScreens = Platform.OS === 'web' || shouldUseActivityState,
} = this.props; } = this.props;
const { scenes, layout, gestures, headerHeights } = this.state; const { scenes, layout, gestures, headerHeights } = this.state;
@@ -386,6 +404,7 @@ export default class CardStack extends React.Component<Props, State> {
const focusedRoute = state.routes[state.index]; const focusedRoute = state.routes[state.index];
const focusedDescriptor = descriptors[focusedRoute.key]; const focusedDescriptor = descriptors[focusedRoute.key];
const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {}; const focusedOptions = focusedDescriptor ? focusedDescriptor.options : {};
const focusedHeaderHeight = headerHeights[focusedRoute.key];
let defaultTransitionPreset = let defaultTransitionPreset =
mode === 'modal' ? ModalTransition : DefaultTransition; mode === 'modal' ? ModalTransition : DefaultTransition;
@@ -404,212 +423,247 @@ export default class CardStack extends React.Component<Props, State> {
left = insets.left, left = insets.left,
} = focusedOptions.safeAreaInsets || {}; } = focusedOptions.safeAreaInsets || {};
// Screens is buggy on iOS and web, so we only enable it on Android let activeScreensLimit = 1;
// For modals, usually we want the screen underneath to be visible, so also disable it there
const isScreensEnabled = Platform.OS !== 'ios' && mode !== 'modal'; for (let i = scenes.length - 1; i >= 0; i--) {
const {
// By default, we don't want to detach the previous screen of the active one for modals
detachPreviousScreen = mode === 'modal'
? i !== scenes.length - 1
: true,
} = scenes[i].descriptor.options;
if (detachPreviousScreen === false) {
activeScreensLimit++;
} else {
break;
}
}
const isFloatHeaderAbsolute =
headerMode === 'float'
? this.state.scenes.slice(-2).some((scene) => {
const { descriptor } = scene;
const options = descriptor ? descriptor.options : {};
const { headerTransparent, headerShown = true } = options;
if (headerTransparent || headerShown === false) {
return true;
}
return false;
})
: false;
const floatingHeader =
headerMode === 'float' ? (
<React.Fragment key="header">
{renderHeader({
mode: 'float',
layout,
insets: { top, right, bottom, left },
scenes,
getPreviousScene: this.getPreviousScene,
getFocusedRoute: this.getFocusedRoute,
onContentHeightChange: this.handleHeaderLayout,
gestureDirection:
focusedOptions.gestureDirection !== undefined
? focusedOptions.gestureDirection
: defaultTransitionPreset.gestureDirection,
styleInterpolator:
focusedOptions.headerStyleInterpolator !== undefined
? focusedOptions.headerStyleInterpolator
: defaultTransitionPreset.headerStyleInterpolator,
style: [
styles.floating,
isFloatHeaderAbsolute && [
// Without this, the header buttons won't be touchable on Android when headerTransparent: true
{ height: focusedHeaderHeight },
styles.absolute,
],
],
})}
</React.Fragment>
) : null;
return ( return (
<HeaderShownContext.Consumer> <React.Fragment>
{(isParentHeaderShown) => { {isFloatHeaderAbsolute ? null : floatingHeader}
const isFloatHeaderAbsolute = <MaybeScreenContainer
headerMode === 'float' enabled={detachInactiveScreens}
? this.state.scenes.slice(-2).some((scene) => { style={styles.container}
const { descriptor } = scene; onLayout={this.handleLayout}
const options = descriptor ? descriptor.options : {}; >
const { {routes.map((route, index, self) => {
headerTransparent, const focused = focusedRoute.key === route.key;
headerShown = isParentHeaderShown === false, const gesture = gestures[route.key];
} = options; const scene = scenes[index];
if (headerTransparent || headerShown === false) { // For the screens that shouldn't be active, the value is 0
return true; // For those that should be active, but are not the top screen, the value is 1
} // For those on top of the stack and with interaction enabled, the value is 2
// For the old implementation, it stays the same it was
let isScreenActive: Animated.AnimatedInterpolation | 2 | 1 | 0 = 1;
return false; if (shouldUseActivityState || Platform.OS === 'web') {
}) if (index < self.length - activeScreensLimit - 1) {
: false; // screen should be inactive because it is too deep in the stack
isScreenActive = STATE_INACTIVE;
} else {
const sceneForActivity = scenes[self.length - 1];
const outputValue =
index === self.length - 1
? STATE_ON_TOP // the screen is on top after the transition
: index >= self.length - activeScreensLimit
? STATE_TRANSITIONING_OR_BELOW_TOP // the screen should stay active after the transition, it is not on top but is in activeLimit
: STATE_INACTIVE; // the screen should be active only during the transition, it is at the edge of activeLimit
isScreenActive = sceneForActivity
? sceneForActivity.progress.current.interpolate({
inputRange: [0, 1 - EPSILON, 1],
outputRange: [1, 1, outputValue],
extrapolate: 'clamp',
})
: STATE_TRANSITIONING_OR_BELOW_TOP;
}
} else {
isScreenActive = scene.progress.next
? scene.progress.next.interpolate({
inputRange: [0, 1 - EPSILON, 1],
outputRange: [1, 1, 0],
extrapolate: 'clamp',
})
: 1;
}
const floatingHeader = const {
headerMode === 'float' ? ( safeAreaInsets,
<React.Fragment key="header"> headerShown = true,
{renderHeader({ headerTransparent,
mode: 'float', cardShadowEnabled,
layout, cardOverlayEnabled,
insets: { top, right, bottom, left }, cardOverlay,
scenes, cardStyle,
getPreviousScene: this.getPreviousScene, animationEnabled,
getFocusedRoute: this.getFocusedRoute, gestureResponseDistance,
onContentHeightChange: this.handleHeaderLayout, gestureVelocityImpact,
gestureDirection: gestureDirection = defaultTransitionPreset.gestureDirection,
focusedOptions.gestureDirection !== undefined transitionSpec = defaultTransitionPreset.transitionSpec,
? focusedOptions.gestureDirection cardStyleInterpolator = animationEnabled === false
: defaultTransitionPreset.gestureDirection, ? forNoAnimationCard
styleInterpolator: : defaultTransitionPreset.cardStyleInterpolator,
focusedOptions.headerStyleInterpolator !== undefined headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
? focusedOptions.headerStyleInterpolator } = scene.descriptor
: defaultTransitionPreset.headerStyleInterpolator, ? scene.descriptor.options
style: [ : ({} as StackNavigationOptions);
styles.floating,
isFloatHeaderAbsolute && styles.absolute,
],
})}
</React.Fragment>
) : null;
return ( let transitionConfig = {
<React.Fragment> gestureDirection,
{isFloatHeaderAbsolute ? null : floatingHeader} transitionSpec,
<MaybeScreenContainer cardStyleInterpolator,
enabled={isScreensEnabled} headerStyleInterpolator,
style={styles.container} };
onLayout={this.handleLayout}
// When a screen is not the last, it should use next screen's transition config
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
// For example combining a slide and a modal transition would look wrong otherwise
// With this approach, combining different transition styles in the same navigator mostly looks right
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
// but majority of the transitions look alright
if (index !== self.length - 1) {
const nextScene = scenes[index + 1];
if (nextScene) {
const {
animationEnabled,
gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = animationEnabled === false
? forNoAnimationCard
: defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
} = nextScene.descriptor
? nextScene.descriptor.options
: ({} as StackNavigationOptions);
transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
};
}
}
const {
top: safeAreaInsetTop = insets.top,
right: safeAreaInsetRight = insets.right,
bottom: safeAreaInsetBottom = insets.bottom,
left: safeAreaInsetLeft = insets.left,
} = safeAreaInsets || {};
const headerHeight =
headerMode !== 'none' && headerShown !== false
? headerHeights[route.key]
: 0;
return (
<MaybeScreen
key={route.key}
style={StyleSheet.absoluteFill}
enabled={detachInactiveScreens}
active={isScreenActive}
pointerEvents="box-none"
> >
{routes.map((route, index, self) => { <CardContainer
const focused = focusedRoute.key === route.key; index={index}
const gesture = gestures[route.key]; active={index === self.length - 1}
const scene = scenes[index]; focused={focused}
closing={closingRouteKeys.includes(route.key)}
const isScreenActive = scene.progress.next layout={layout}
? scene.progress.next.interpolate({ gesture={gesture}
inputRange: [0, 1 - EPSILON, 1], scene={scene}
outputRange: [1, 1, 0], safeAreaInsetTop={safeAreaInsetTop}
extrapolate: 'clamp', safeAreaInsetRight={safeAreaInsetRight}
}) safeAreaInsetBottom={safeAreaInsetBottom}
: 1; safeAreaInsetLeft={safeAreaInsetLeft}
cardOverlay={cardOverlay}
const { cardOverlayEnabled={cardOverlayEnabled}
safeAreaInsets, cardShadowEnabled={cardShadowEnabled}
headerShown = isParentHeaderShown === false, cardStyle={cardStyle}
headerTransparent, onPageChangeStart={onPageChangeStart}
cardShadowEnabled, onPageChangeConfirm={onPageChangeConfirm}
cardOverlayEnabled, onPageChangeCancel={onPageChangeCancel}
cardOverlay, onGestureStart={onGestureStart}
cardStyle, onGestureCancel={onGestureCancel}
animationEnabled, onGestureEnd={onGestureEnd}
gestureResponseDistance, gestureResponseDistance={gestureResponseDistance}
gestureVelocityImpact, headerHeight={headerHeight}
gestureDirection = defaultTransitionPreset.gestureDirection, isParentHeaderShown={isParentHeaderShown}
transitionSpec = defaultTransitionPreset.transitionSpec, onHeaderHeightChange={this.handleHeaderLayout}
cardStyleInterpolator = animationEnabled === false getPreviousScene={this.getPreviousScene}
? forNoAnimationCard getFocusedRoute={this.getFocusedRoute}
: defaultTransitionPreset.cardStyleInterpolator, mode={mode}
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator, headerMode={headerMode}
} = scene.descriptor headerShown={headerShown}
? scene.descriptor.options hasAbsoluteHeader={
: ({} as StackNavigationOptions); isFloatHeaderAbsolute && !headerTransparent
let transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
};
// When a screen is not the last, it should use next screen's transition config
// Many transitions also animate the previous screen, so using 2 different transitions doesn't look right
// For example combining a slide and a modal transition would look wrong otherwise
// With this approach, combining different transition styles in the same navigator mostly looks right
// This will still be broken when 2 transitions have different idle state (e.g. modal presentation),
// but majority of the transitions look alright
if (index !== self.length - 1) {
const nextScene = scenes[index + 1];
if (nextScene) {
const {
animationEnabled,
gestureDirection = defaultTransitionPreset.gestureDirection,
transitionSpec = defaultTransitionPreset.transitionSpec,
cardStyleInterpolator = animationEnabled === false
? forNoAnimationCard
: defaultTransitionPreset.cardStyleInterpolator,
headerStyleInterpolator = defaultTransitionPreset.headerStyleInterpolator,
} = nextScene.descriptor
? nextScene.descriptor.options
: ({} as StackNavigationOptions);
transitionConfig = {
gestureDirection,
transitionSpec,
cardStyleInterpolator,
headerStyleInterpolator,
};
}
} }
renderHeader={renderHeader}
const { renderScene={renderScene}
top: safeAreaInsetTop = insets.top, onOpenRoute={onOpenRoute}
right: safeAreaInsetRight = insets.right, onCloseRoute={onCloseRoute}
bottom: safeAreaInsetBottom = insets.bottom, onTransitionStart={onTransitionStart}
left: safeAreaInsetLeft = insets.left, onTransitionEnd={onTransitionEnd}
} = safeAreaInsets || {}; gestureEnabled={index !== 0 && getGesturesEnabled({ route })}
gestureVelocityImpact={gestureVelocityImpact}
const headerHeight = {...transitionConfig}
headerMode !== 'none' && headerShown !== false />
? headerHeights[route.key] </MaybeScreen>
: 0; );
})}
return ( </MaybeScreenContainer>
<MaybeScreen {isFloatHeaderAbsolute ? floatingHeader : null}
key={route.key} </React.Fragment>
style={StyleSheet.absoluteFill}
enabled={isScreensEnabled}
active={isScreenActive}
pointerEvents="box-none"
>
<CardContainer
index={index}
active={index === self.length - 1}
focused={focused}
closing={closingRouteKeys.includes(route.key)}
layout={layout}
gesture={gesture}
scene={scene}
safeAreaInsetTop={safeAreaInsetTop}
safeAreaInsetRight={safeAreaInsetRight}
safeAreaInsetBottom={safeAreaInsetBottom}
safeAreaInsetLeft={safeAreaInsetLeft}
cardOverlay={cardOverlay}
cardOverlayEnabled={cardOverlayEnabled}
cardShadowEnabled={cardShadowEnabled}
cardStyle={cardStyle}
onPageChangeStart={onPageChangeStart}
onPageChangeConfirm={onPageChangeConfirm}
onPageChangeCancel={onPageChangeCancel}
onGestureStart={onGestureStart}
onGestureCancel={onGestureCancel}
onGestureEnd={onGestureEnd}
gestureResponseDistance={gestureResponseDistance}
headerHeight={headerHeight}
onHeaderHeightChange={this.handleHeaderLayout}
getPreviousScene={this.getPreviousScene}
getFocusedRoute={this.getFocusedRoute}
mode={mode}
headerMode={headerMode}
headerShown={headerShown}
hasAbsoluteHeader={
isFloatHeaderAbsolute && !headerTransparent
}
renderHeader={renderHeader}
renderScene={renderScene}
onOpenRoute={onOpenRoute}
onCloseRoute={onCloseRoute}
onTransitionStart={onTransitionStart}
onTransitionEnd={onTransitionEnd}
gestureEnabled={
index !== 0 && getGesturesEnabled({ route })
}
gestureVelocityImpact={gestureVelocityImpact}
{...transitionConfig}
/>
</MaybeScreen>
);
})}
</MaybeScreenContainer>
{isFloatHeaderAbsolute ? floatingHeader : null}
</React.Fragment>
);
}}
</HeaderShownContext.Consumer>
); );
} }
} }

View File

@@ -21,6 +21,7 @@ import type {
StackNavigationConfig, StackNavigationConfig,
StackDescriptorMap, StackDescriptorMap,
} from '../../types'; } from '../../types';
import HeaderShownContext from '../../utils/HeaderShownContext';
type Props = StackNavigationConfig & { type Props = StackNavigationConfig & {
state: StackNavigationState; state: StackNavigationState;
@@ -430,7 +431,7 @@ export default class StackView extends React.Component<Props, State> {
const descriptor = const descriptor =
descriptors[route.key] || this.state.descriptors[route.key]; descriptors[route.key] || this.state.descriptors[route.key];
descriptor?.options.onTransitionEnd?.({ closing }); descriptor?.options.onTransitionStart?.({ closing });
}; };
private handleGestureStart = () => { private handleGestureStart = () => {
@@ -475,29 +476,34 @@ export default class StackView extends React.Component<Props, State> {
{(insets) => ( {(insets) => (
<KeyboardManager enabled={keyboardHandlingEnabled !== false}> <KeyboardManager enabled={keyboardHandlingEnabled !== false}>
{(props) => ( {(props) => (
<CardStack <HeaderShownContext.Consumer>
mode={mode} {(isParentHeaderShown) => (
insets={insets as EdgeInsets} <CardStack
getPreviousRoute={this.getPreviousRoute} mode={mode}
getGesturesEnabled={this.getGesturesEnabled} insets={insets as EdgeInsets}
routes={routes} isParentHeaderShown={isParentHeaderShown}
openingRouteKeys={openingRouteKeys} getPreviousRoute={this.getPreviousRoute}
closingRouteKeys={closingRouteKeys} getGesturesEnabled={this.getGesturesEnabled}
onOpenRoute={this.handleOpenRoute} routes={routes}
onCloseRoute={this.handleCloseRoute} openingRouteKeys={openingRouteKeys}
onTransitionStart={this.handleTransitionStart} closingRouteKeys={closingRouteKeys}
onTransitionEnd={this.handleTransitionEnd} onOpenRoute={this.handleOpenRoute}
renderHeader={this.renderHeader} onCloseRoute={this.handleCloseRoute}
renderScene={this.renderScene} onTransitionStart={this.handleTransitionStart}
headerMode={headerMode} onTransitionEnd={this.handleTransitionEnd}
state={state} renderHeader={this.renderHeader}
descriptors={descriptors} renderScene={this.renderScene}
onGestureStart={this.handleGestureStart} headerMode={headerMode}
onGestureEnd={this.handleGestureEnd} state={state}
onGestureCancel={this.handleGestureCancel} descriptors={descriptors}
{...rest} onGestureStart={this.handleGestureStart}
{...props} onGestureEnd={this.handleGestureEnd}
/> onGestureCancel={this.handleGestureCancel}
{...rest}
{...props}
/>
)}
</HeaderShownContext.Consumer>
)} )}
</KeyboardManager> </KeyboardManager>
)} )}

View File

@@ -5,7 +5,7 @@ import { BaseButton } from 'react-native-gesture-handler';
const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton); const AnimatedBaseButton = Animated.createAnimatedComponent(BaseButton);
type Props = React.ComponentProps<typeof BaseButton> & { type Props = React.ComponentProps<typeof BaseButton> & {
activeOpacity: number; pressOpacity: number;
}; };
const useNativeDriver = Platform.OS !== 'web'; const useNativeDriver = Platform.OS !== 'web';
@@ -27,7 +27,7 @@ export default class TouchableItem extends React.Component<Props> {
overshootClamping: true, overshootClamping: true,
restDisplacementThreshold: 0.01, restDisplacementThreshold: 0.01,
restSpeedThreshold: 0.01, restSpeedThreshold: 0.01,
toValue: active ? this.props.activeOpacity : 1, toValue: active ? this.props.pressOpacity : 1,
useNativeDriver, useNativeDriver,
}).start(); }).start();

View File

@@ -3,6 +3,44 @@
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
# [2.11.0](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.10.1...react-navigation-tabs@2.11.0) (2021-02-21)
### Features
* add activityState to other navigators ([5c7f892](https://github.com/react-navigation/tabs/commit/5c7f892d77298f5c89534fa78a1a6a59c7f35a60))
## [2.10.1](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.10.0...react-navigation-tabs@2.10.1) (2020-10-28)
### Bug Fixes
* add bottom insets to custom height for bottom tabs ([ffe3bdd](https://github.com/react-navigation/tabs/commit/ffe3bddcb27ecb3d663b13b433263d18f792ad4d))
# [2.10.0](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.2...react-navigation-tabs@2.10.0) (2020-10-26)
### Bug Fixes
* use react-native-iphone-x-helper in bottom-tabs navigator ([ebe70f5](https://github.com/react-navigation/tabs/commit/ebe70f51fbb8d892be9c44b0c226947684c24338))
### Features
* add detachInactiveScreens prop for bottom tabs and drawer ([#8816](https://github.com/react-navigation/tabs/issues/8816)) ([707bea3](https://github.com/react-navigation/tabs/commit/707bea3cba1aa1f7b1eb5884dd1d129dcc2bbdc2))
## [2.9.2](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.1...react-navigation-tabs@2.9.2) (2020-10-02) ## [2.9.2](https://github.com/react-navigation/tabs/compare/react-navigation-tabs@2.9.1...react-navigation-tabs@2.9.2) (2020-10-02)
**Note:** Version bump only for package react-navigation-tabs **Note:** Version bump only for package react-navigation-tabs

View File

@@ -1,6 +1,6 @@
{ {
"name": "react-navigation-tabs", "name": "react-navigation-tabs",
"version": "2.9.2", "version": "2.11.0",
"description": "Tab Navigation components for React Navigation", "description": "Tab Navigation components for React Navigation",
"main": "lib/commonjs/index.js", "main": "lib/commonjs/index.js",
"module": "lib/module/index.js", "module": "lib/module/index.js",
@@ -42,21 +42,21 @@
"dependencies": { "dependencies": {
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"react-lifecycles-compat": "^3.0.4", "react-lifecycles-compat": "^3.0.4",
"react-native-safe-area-view": "^0.14.9", "react-native-iphone-x-helper": "^1.3.0",
"react-native-tab-view": "^2.15.1" "react-native-tab-view": "^2.15.2"
}, },
"devDependencies": { "devDependencies": {
"@react-native-community/bob": "^0.16.2", "@react-native-community/bob": "^0.16.2",
"@types/hoist-non-react-statics": "^3.3.1", "@types/hoist-non-react-statics": "^3.3.1",
"@types/react": "^16.9.44", "@types/react": "^16.9.53",
"@types/react-native": "^0.63.4", "@types/react-native": "^0.63.30",
"del-cli": "^3.0.1", "del-cli": "^3.0.1",
"react": "~16.13.1", "react": "~16.13.1",
"react-native": "~0.63.2", "react-native": "~0.63.2",
"react-native-gesture-handler": "~1.7.0", "react-native-gesture-handler": "~1.7.0",
"react-native-reanimated": "~1.13.0", "react-native-reanimated": "~1.13.0",
"react-native-tab-view": "^2.13.0", "react-native-tab-view": "^2.13.0",
"react-navigation": "^4.4.2", "react-navigation": "^4.4.4",
"typescript": "^4.0.3" "typescript": "^4.0.3"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -20,6 +20,7 @@ type Config = {
lazy?: boolean; lazy?: boolean;
tabBarComponent?: React.ComponentType<any>; tabBarComponent?: React.ComponentType<any>;
tabBarOptions?: BottomTabBarOptions; tabBarOptions?: BottomTabBarOptions;
detachInactiveScreens?: boolean;
}; };
type Props = NavigationViewProps & type Props = NavigationViewProps &
@@ -131,13 +132,19 @@ class TabNavigationView extends React.PureComponent<Props, State> {
}; };
render() { render() {
const { navigation, renderScene, lazy } = this.props; const {
navigation,
renderScene,
lazy,
detachInactiveScreens = true,
} = this.props;
const { routes } = navigation.state; const { routes } = navigation.state;
const { loaded } = this.state; const { loaded } = this.state;
return ( return (
<View style={styles.container}> <View style={styles.container}>
<ScreenContainer style={styles.pages}> {/* @ts-ignore */}
<ScreenContainer enabled={detachInactiveScreens} style={styles.pages}>
{routes.map((route, index) => { {routes.map((route, index) => {
if (lazy && !loaded.includes(index)) { if (lazy && !loaded.includes(index)) {
// Don't render a screen if we've never navigated to it // Don't render a screen if we've never navigated to it
@@ -151,6 +158,7 @@ class TabNavigationView extends React.PureComponent<Props, State> {
key={route.key} key={route.key}
style={StyleSheet.absoluteFill} style={StyleSheet.absoluteFill}
isVisible={isFocused} isVisible={isFocused}
enabled={detachInactiveScreens}
> >
{renderScene({ route })} {renderScene({ route })}
</ResourceSavingScene> </ResourceSavingScene>

View File

@@ -6,7 +6,6 @@ import {
TextStyle, TextStyle,
ViewStyle, ViewStyle,
} from 'react-native'; } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view';
import Animated from 'react-native-reanimated'; import Animated from 'react-native-reanimated';
import { import {
NavigationRoute, NavigationRoute,
@@ -94,7 +93,12 @@ export type BottomTabBarOptions = {
| LabelPosition | LabelPosition
| ((options: { deviceOrientation: Orientation }) => LabelPosition); | ((options: { deviceOrientation: Orientation }) => LabelPosition);
adaptive?: boolean; adaptive?: boolean;
safeAreaInset?: React.ComponentProps<typeof SafeAreaView>['forceInset']; safeAreaInset?: {
top?: 'always' | 'never' | number;
right?: 'always' | 'never' | number;
bottom?: 'always' | 'never' | number;
left?: 'always' | 'never' | number;
};
style?: StyleProp<ViewStyle>; style?: StyleProp<ViewStyle>;
}; };
@@ -149,6 +153,7 @@ export type BottomTabBarProps = BottomTabBarOptions & {
isLandscape: boolean; isLandscape: boolean;
jumpTo: (key: string) => void; jumpTo: (key: string) => void;
screenProps: unknown; screenProps: unknown;
detachInactiveScreens?: boolean;
}; };
export type MaterialTabBarOptions = { export type MaterialTabBarOptions = {

View File

@@ -8,7 +8,10 @@ import {
Platform, Platform,
LayoutChangeEvent, LayoutChangeEvent,
} from 'react-native'; } from 'react-native';
import SafeAreaView from 'react-native-safe-area-view'; import {
getStatusBarHeight,
getBottomSpace,
} from 'react-native-iphone-x-helper';
import { ThemeColors, ThemeContext, NavigationRoute } from 'react-navigation'; import { ThemeColors, ThemeContext, NavigationRoute } from 'react-navigation';
import CrossFadeIcon from './CrossFadeIcon'; import CrossFadeIcon from './CrossFadeIcon';
@@ -30,6 +33,9 @@ const majorVersion = parseInt(Platform.Version as string, 10);
const isIos = Platform.OS === 'ios'; const isIos = Platform.OS === 'ios';
const isIOS11 = majorVersion >= 11 && isIos; const isIOS11 = majorVersion >= 11 && isIos;
const DEFAULT_HEIGHT = 49;
const COMPACT_HEIGHT = 29;
const DEFAULT_MAX_TAB_ITEM_WIDTH = 125; const DEFAULT_MAX_TAB_ITEM_WIDTH = 125;
const DEFAULT_KEYBOARD_ANIMATION_CONFIG: KeyboardHidesTabBarAnimationConfig = { const DEFAULT_KEYBOARD_ANIMATION_CONFIG: KeyboardHidesTabBarAnimationConfig = {
show: { show: {
@@ -103,9 +109,7 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
showIcon: true, showIcon: true,
allowFontScaling: true, allowFontScaling: true,
adaptive: isIOS11, adaptive: isIOS11,
safeAreaInset: { bottom: 'always', top: 'never' } as React.ComponentProps< safeAreaInset: { bottom: 'always', top: 'never' } as const,
typeof SafeAreaView
>['forceInset'],
}; };
// eslint-disable-next-line react/sort-comp // eslint-disable-next-line react/sort-comp
@@ -394,6 +398,7 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
keyboardHidesTabBar, keyboardHidesTabBar,
onTabPress, onTabPress,
onTabLongPress, onTabLongPress,
isLandscape,
safeAreaInset, safeAreaInset,
style, style,
tabStyle, tabStyle,
@@ -418,6 +423,7 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
marginRight, marginRight,
marginHorizontal, marginHorizontal,
marginVertical, marginVertical,
height,
...innerStyle ...innerStyle
} = StyleSheet.flatten(style || {}); } = StyleSheet.flatten(style || {});
@@ -436,13 +442,46 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
marginVertical, marginVertical,
}; };
const statusBarHeight = getStatusBarHeight(true);
const horizontalInset = isLandscape ? statusBarHeight : 0;
const insets = {
bottom:
typeof safeAreaInset?.bottom === 'number'
? safeAreaInset.bottom
: safeAreaInset?.bottom === 'never'
? 0
: getBottomSpace(),
left:
typeof safeAreaInset?.left === 'number'
? safeAreaInset.left
: safeAreaInset?.left === 'never'
? 0
: horizontalInset,
right:
typeof safeAreaInset?.right === 'number'
? safeAreaInset.right
: safeAreaInset?.right === 'never'
? 0
: horizontalInset,
};
const tabBarStyle = [ const tabBarStyle = [
{
height:
height != null
? typeof height === 'number'
? height + insets.bottom
: height
: // @ts-ignore: isPad exists in runtime but not available in type defs
(this._shouldUseHorizontalLabels() && !Platform.isPad
? COMPACT_HEIGHT
: DEFAULT_HEIGHT) + insets.bottom,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right,
},
styles.tabBar, styles.tabBar,
isDark ? styles.tabBarDark : styles.tabBarLight, isDark ? styles.tabBarDark : styles.tabBarLight,
// @ts-ignore
this._shouldUseHorizontalLabels() && !Platform.isPad
? styles.tabBarCompact
: styles.tabBarRegular,
innerStyle, innerStyle,
]; ];
@@ -473,7 +512,7 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
} }
onLayout={this._handleLayout} onLayout={this._handleLayout}
> >
<SafeAreaView style={tabBarStyle} forceInset={safeAreaInset}> <View style={tabBarStyle}>
{routes.map((route, index) => { {routes.map((route, index) => {
const focused = index === navigation.state.index; const focused = index === navigation.state.index;
const scene = { route, focused }; const scene = { route, focused };
@@ -524,15 +563,12 @@ class TabBarBottom extends React.Component<BottomTabBarProps, State> {
</ButtonComponent> </ButtonComponent>
); );
})} })}
</SafeAreaView> </View>
</Animated.View> </Animated.View>
); );
} }
} }
const DEFAULT_HEIGHT = 49;
const COMPACT_HEIGHT = 29;
const styles = StyleSheet.create({ const styles = StyleSheet.create({
tabBar: { tabBar: {
borderTopWidth: StyleSheet.hairlineWidth, borderTopWidth: StyleSheet.hairlineWidth,
@@ -549,12 +585,6 @@ const styles = StyleSheet.create({
container: { container: {
elevation: 8, elevation: 8,
}, },
tabBarCompact: {
height: COMPACT_HEIGHT,
},
tabBarRegular: {
height: DEFAULT_HEIGHT,
},
tab: { tab: {
flex: 1, flex: 1,
alignItems: isIos ? 'center' : 'stretch', alignItems: isIos ? 'center' : 'stretch',

View File

@@ -1,11 +1,17 @@
import * as React from 'react'; import * as React from 'react';
import { Platform, StyleSheet, View } from 'react-native'; import { Platform, StyleSheet, View } from 'react-native';
import { Screen, screensEnabled } from 'react-native-screens'; import {
Screen,
screensEnabled,
// @ts-ignore
shouldUseActivityState,
} from 'react-native-screens';
type Props = { type Props = {
isVisible: boolean; isVisible: boolean;
children: React.ReactNode; children: React.ReactNode;
style?: any; style?: any;
enabled: boolean;
}; };
const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view out of its container
@@ -13,11 +19,20 @@ const FAR_FAR_AWAY = 30000; // this should be big enough to move the whole view
export default class ResourceSavingScene extends React.Component<Props> { export default class ResourceSavingScene extends React.Component<Props> {
render() { render() {
// react-native-screens is buggy on web // react-native-screens is buggy on web
if (screensEnabled?.() && Platform.OS !== 'web') { if (screensEnabled?.() && this.props.enabled && Platform.OS !== 'web') {
const { isVisible, ...rest } = this.props; const { isVisible, ...rest } = this.props;
// @ts-ignore if (shouldUseActivityState) {
return <Screen active={isVisible ? 1 : 0} {...rest} />; return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screen activityState={isVisible ? 2 : 0} {...rest} />
);
} else {
return (
// @ts-expect-error: there was an `active` prop and no `activityState` in older version and stackPresentation was required
<Screen active={isVisible ? 1 : 0} {...rest} />
);
}
} }
const { isVisible, children, style, ...rest } = this.props; const { isVisible, children, style, ...rest } = this.props;

1826
yarn.lock

File diff suppressed because it is too large Load Diff