diff --git a/examples/NavigationPlayground/js/SimpleTabs.js b/examples/NavigationPlayground/js/SimpleTabs.js
index fb1fa00593..c925963450 100644
--- a/examples/NavigationPlayground/js/SimpleTabs.js
+++ b/examples/NavigationPlayground/js/SimpleTabs.js
@@ -45,35 +45,69 @@ MyHomeScreen.navigationOptions = {
),
};
-const MyPeopleScreen = ({ navigation }) => (
-
-);
-
-MyPeopleScreen.navigationOptions = {
- tabBarLabel: 'People',
- tabBarIcon: ({ tintColor, focused }) => (
-
- ),
-};
-
-const MyChatScreen = ({ navigation }) => (
-
-);
+class MyPeopleScreen extends React.Component {
+ static navigationOptions = {
+ tabBarLabel: 'People',
+ tabBarIcon: ({ tintColor, focused }) => (
+
+ ),
+ };
+ componentDidMount() {
+ this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
+ this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
+ this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
+ this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
+ }
+ componentWillUnmount() {
+ this._s0.remove();
+ this._s1.remove();
+ this._s2.remove();
+ this._s3.remove();
+ }
+ _onEvent = a => {
+ console.log('EVENT ON PEOPLE TAB', a.type, a);
+ };
+ render() {
+ const { navigation } = this.props;
+ return ;
+ }
+}
-MyChatScreen.navigationOptions = {
- tabBarLabel: 'Chat',
- tabBarIcon: ({ tintColor, focused }) => (
-
- ),
-};
+class MyChatScreen extends React.Component {
+ static navigationOptions = {
+ tabBarLabel: 'Chat',
+ tabBarIcon: ({ tintColor, focused }) => (
+
+ ),
+ };
+ componentDidMount() {
+ this._s0 = this.props.navigation.addListener('willFocus', this._onEvent);
+ this._s1 = this.props.navigation.addListener('didFocus', this._onEvent);
+ this._s2 = this.props.navigation.addListener('willBlur', this._onEvent);
+ this._s3 = this.props.navigation.addListener('didBlur', this._onEvent);
+ }
+ componentWillUnmount() {
+ this._s0.remove();
+ this._s1.remove();
+ this._s2.remove();
+ this._s3.remove();
+ }
+ _onEvent = a => {
+ console.log('EVENT ON CHAT TAB', a.type, a);
+ };
+ render() {
+ const { navigation } = this.props;
+ return ;
+ }
+}
const MySettingsScreen = ({ navigation }) => (
@@ -128,10 +162,10 @@ class SimpleTabsContainer extends React.Component {
_s3: EventListener;
componentDidMount() {
- this._s0 = this.props.navigation.addListener('willFocus', this._onWF);
- this._s1 = this.props.navigation.addListener('didFocus', this._onDF);
- this._s2 = this.props.navigation.addListener('willBlur', this._onWB);
- this._s3 = this.props.navigation.addListener('didBlur', this._onDB);
+ this._s0 = this.props.navigation.addListener('willFocus', this._onAction);
+ this._s1 = this.props.navigation.addListener('didFocus', this._onAction);
+ this._s2 = this.props.navigation.addListener('willBlur', this._onAction);
+ this._s3 = this.props.navigation.addListener('didBlur', this._onAction);
}
componentWillUnmount() {
this._s0.remove();
@@ -139,17 +173,8 @@ class SimpleTabsContainer extends React.Component {
this._s2.remove();
this._s3.remove();
}
- _onWF = a => {
- console.log('_onWillFocus tabsExample ', a);
- };
- _onDF = a => {
- console.log('_onDidFocus tabsExample ', a);
- };
- _onWB = a => {
- console.log('_onWillBlur tabsExample ', a);
- };
- _onDB = a => {
- console.log('_onDidBlur tabsExample ', a);
+ _onAction = a => {
+ console.log('TABS EVENT', a.type, a);
};
render() {
return ;
diff --git a/examples/ReduxExample/app.json b/examples/ReduxExample/app.json
index 60df3da525..cf41253aea 100644
--- a/examples/ReduxExample/app.json
+++ b/examples/ReduxExample/app.json
@@ -12,13 +12,10 @@
"icon": "./assets/icons/react-navigation.png",
"hideExponentText": false
},
- "sdkVersion": "23.0.0",
+ "sdkVersion": "24.0.0",
"entryPoint": "./node_modules/react-native-scripts/build/bin/crna-entry.js",
"packagerOpts": {
- "assetExts": [
- "ttf",
- "mp4"
- ]
+ "assetExts": ["ttf", "mp4"]
},
"ios": {
"supportsTablet": true
diff --git a/examples/ReduxExample/package.json b/examples/ReduxExample/package.json
index 6a50356d8f..5ad815afca 100644
--- a/examples/ReduxExample/package.json
+++ b/examples/ReduxExample/package.json
@@ -21,10 +21,10 @@
]
},
"dependencies": {
- "expo": "^23.0.0",
+ "expo": "^24.0.2",
"prop-types": "^15.5.10",
"react": "16.0.0",
- "react-native": "^0.50.3",
+ "react-native": "^0.51.0",
"react-redux": "^5.0.6",
"redux": "^3.7.2",
"react-navigation": "link:../.."
diff --git a/examples/ReduxExample/src/navigators/AppNavigator.js b/examples/ReduxExample/src/navigators/AppNavigator.js
index 3572ef6623..e089289a54 100644
--- a/examples/ReduxExample/src/navigators/AppNavigator.js
+++ b/examples/ReduxExample/src/navigators/AppNavigator.js
@@ -13,17 +13,51 @@ export const AppNavigator = StackNavigator({
Profile: { screen: ProfileScreen },
});
-const AppWithNavigationState = ({ dispatch, nav }) => (
-
-);
+class AppWithNavigationState extends React.Component {
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ nav: PropTypes.object.isRequired,
+ };
-AppWithNavigationState.propTypes = {
- dispatch: PropTypes.func.isRequired,
- nav: PropTypes.object.isRequired,
-};
+ _actionEventSubscribers = new Set();
+
+ _addListener = (eventName, handler) => {
+ eventName === 'action' && this._actionEventSubscribers.add(handler);
+ return {
+ remove: () => {
+ this._actionEventSubscribers.delete(handler);
+ },
+ };
+ };
+
+ componentDidUpdate(lastProps) {
+ const lastState = lastProps.nav;
+ this._actionEventSubscribers.forEach(subscriber => {
+ subscriber({
+ lastState: lastProps.nav,
+ state: this.props.nav,
+ action: this.props.lastAction,
+ });
+ });
+ }
+
+ render() {
+ const { dispatch, nav } = this.props;
+ return (
+
+ );
+ }
+}
const mapStateToProps = state => ({
nav: state.nav,
+ lastAction: state.lastAction,
});
export default connect(mapStateToProps)(AppWithNavigationState);
diff --git a/examples/ReduxExample/src/reducers/index.js b/examples/ReduxExample/src/reducers/index.js
index 1ece9bcd01..2a8ef8c0ac 100644
--- a/examples/ReduxExample/src/reducers/index.js
+++ b/examples/ReduxExample/src/reducers/index.js
@@ -36,6 +36,10 @@ function nav(state = initialNavState, action) {
return nextState || state;
}
+function lastAction(state = null, action) {
+ return action;
+}
+
const initialAuthState = { isLoggedIn: false };
function auth(state = initialAuthState, action) {
@@ -50,6 +54,7 @@ function auth(state = initialAuthState, action) {
}
const AppReducer = combineReducers({
+ lastAction,
nav,
auth,
});
diff --git a/examples/ReduxExample/yarn.lock b/examples/ReduxExample/yarn.lock
index 0e80ea5501..0fb4114e5e 100644
--- a/examples/ReduxExample/yarn.lock
+++ b/examples/ReduxExample/yarn.lock
@@ -57,7 +57,7 @@
dependencies:
cross-spawn "^5.1.0"
-"@expo/vector-icons@^6.2.0":
+"@expo/vector-icons@^6.2.2":
version "6.2.2"
resolved "https://registry.yarnpkg.com/@expo/vector-icons/-/vector-icons-6.2.2.tgz#441edb58a52c0f4e5b4aba1e6f8da1e87cea7e11"
dependencies:
@@ -801,7 +801,7 @@ babel-plugin-transform-es3-property-literals@^6.8.0:
dependencies:
babel-runtime "^6.22.0"
-babel-plugin-transform-exponentiation-operator@^6.24.1:
+babel-plugin-transform-exponentiation-operator@^6.24.1, babel-plugin-transform-exponentiation-operator@^6.5.0:
version "6.24.1"
resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
dependencies:
@@ -1994,11 +1994,11 @@ expect@^21.2.1:
jest-message-util "^21.2.1"
jest-regex-util "^21.2.0"
-expo@^23.0.0:
- version "23.0.6"
- resolved "https://registry.yarnpkg.com/expo/-/expo-23.0.6.tgz#8cb2c3992b385eb5866cc91f25f159420258d19a"
+expo@^24.0.2:
+ version "24.0.2"
+ resolved "https://registry.yarnpkg.com/expo/-/expo-24.0.2.tgz#3ff9784afd9efbb8eb739289aa53290ddf31a5a5"
dependencies:
- "@expo/vector-icons" "^6.2.0"
+ "@expo/vector-icons" "^6.2.2"
babel-preset-expo "^4.0.0"
fbemitter "^2.1.1"
invariant "^2.2.2"
@@ -3768,7 +3768,7 @@ methods@^1.1.1, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
-metro-bundler@^0.20.1:
+metro-bundler@^0.20.0:
version "0.20.3"
resolved "https://registry.yarnpkg.com/metro-bundler/-/metro-bundler-0.20.3.tgz#0ded01b64e8963117017b106f75b83cfc34f3656"
dependencies:
@@ -4717,9 +4717,9 @@ react-native-vector-icons@4.4.2:
prop-types "^15.5.10"
yargs "^8.0.2"
-react-native@^0.50.3:
- version "0.50.4"
- resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.50.4.tgz#194f5da4939087b3acee712a503475f4942dca7e"
+react-native@^0.51.0:
+ version "0.51.0"
+ resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.51.0.tgz#fe25934b3030fd323f3ca1a70f034133465955ed"
dependencies:
absolute-path "^0.0.0"
art "^0.10.0"
@@ -4727,6 +4727,7 @@ react-native@^0.50.3:
babel-plugin-syntax-trailing-function-commas "^6.20.0"
babel-plugin-transform-async-to-generator "6.16.0"
babel-plugin-transform-class-properties "^6.18.0"
+ babel-plugin-transform-exponentiation-operator "^6.5.0"
babel-plugin-transform-flow-strip-types "^6.21.0"
babel-plugin-transform-object-rest-spread "^6.20.2"
babel-register "^6.24.1"
@@ -4747,7 +4748,7 @@ react-native@^0.50.3:
graceful-fs "^4.1.3"
inquirer "^3.0.6"
lodash "^4.16.6"
- metro-bundler "^0.20.1"
+ metro-bundler "^0.20.0"
mime "^1.3.4"
minimist "^1.2.0"
mkdirp "^0.5.1"
@@ -4763,7 +4764,7 @@ react-native@^0.50.3:
react-clone-referenced-element "^1.0.1"
react-devtools-core "^2.5.0"
react-timer-mixin "^0.13.2"
- regenerator-runtime "^0.9.5"
+ regenerator-runtime "^0.11.0"
rimraf "^2.5.4"
semver "^5.0.3"
shell-quote "1.6.1"
@@ -4775,15 +4776,8 @@ react-native@^0.50.3:
yargs "^9.0.0"
"react-navigation@link:../..":
- version "1.0.0-beta.27"
- dependencies:
- babel-plugin-transform-define "^1.3.0"
- clamp "^1.0.1"
- hoist-non-react-statics "^2.2.0"
- path-to-regexp "^1.7.0"
- prop-types "^15.5.10"
- react-native-drawer-layout-polyfill "^1.3.2"
- react-native-tab-view "^0.0.74"
+ version "0.0.0"
+ uid ""
react-proxy@^1.1.7:
version "1.1.8"
@@ -4924,10 +4918,6 @@ regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
-regenerator-runtime@^0.9.5:
- version "0.9.6"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.9.6.tgz#d33eb95d0d2001a4be39659707c51b0cb71ce029"
-
regenerator-transform@^0.10.0:
version "0.10.1"
resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd"
diff --git a/src/__tests__/getChildEventSubscriber-test.js b/src/__tests__/getChildEventSubscriber-test.js
new file mode 100644
index 0000000000..3e25271121
--- /dev/null
+++ b/src/__tests__/getChildEventSubscriber-test.js
@@ -0,0 +1,437 @@
+import getChildEventSubscriber from '../getChildEventSubscriber';
+
+test('child action events only flow when focused', () => {
+ const parentSubscriber = jest.fn();
+ const emitParentAction = payload => {
+ parentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'key1'
+ );
+ const testState = {
+ key: 'foo',
+ routeName: 'FooRoute',
+ routes: [{ key: 'key0' }, { key: 'key1' }],
+ index: 0,
+ isTransitioning: false,
+ };
+ const focusedTestState = {
+ ...testState,
+ index: 1,
+ };
+ const childActionHandler = jest.fn();
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ childEventSubscriber('action', childActionHandler);
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ emitParentAction({
+ type: 'action',
+ state: focusedTestState,
+ lastState: testState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(0);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'action',
+ state: focusedTestState,
+ lastState: focusedTestState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(1);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+});
+
+test('grandchildren subscription', () => {
+ const grandParentSubscriber = jest.fn();
+ const emitGrandParentAction = payload => {
+ grandParentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const parentSubscriber = getChildEventSubscriber(
+ grandParentSubscriber,
+ 'parent'
+ );
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'key1'
+ );
+ const parentBlurState = {
+ key: 'foo',
+ routeName: 'FooRoute',
+ routes: [
+ { key: 'aunt' },
+ {
+ key: 'parent',
+ routes: [{ key: 'key0' }, { key: 'key1' }],
+ index: 1,
+ isTransitioning: false,
+ },
+ ],
+ index: 0,
+ isTransitioning: false,
+ };
+ const parentTransitionState = {
+ ...parentBlurState,
+ index: 1,
+ isTransitioning: true,
+ };
+ const parentFocusState = {
+ ...parentTransitionState,
+ isTransitioning: false,
+ };
+ const childActionHandler = jest.fn();
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ childEventSubscriber('action', childActionHandler);
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ emitGrandParentAction({
+ type: 'action',
+ state: parentTransitionState,
+ lastState: parentBlurState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(0);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(0);
+ emitGrandParentAction({
+ type: 'action',
+ state: parentFocusState,
+ lastState: parentTransitionState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(0);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+});
+
+test('grandchildren transitions', () => {
+ const grandParentSubscriber = jest.fn();
+ const emitGrandParentAction = payload => {
+ grandParentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ grandParentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const parentSubscriber = getChildEventSubscriber(
+ grandParentSubscriber,
+ 'parent'
+ );
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'key1'
+ );
+ const makeFakeState = (childIndex, childIsTransitioning) => ({
+ index: 1,
+ isTransitioning: false,
+ routes: [
+ { key: 'nothing' },
+ {
+ key: 'parent',
+ index: childIndex,
+ isTransitioning: childIsTransitioning,
+ routes: [{ key: 'key0' }, { key: 'key1' }, { key: 'key2' }],
+ },
+ ],
+ });
+ const blurredState = makeFakeState(0, false);
+ const transitionState = makeFakeState(1, true);
+ const focusState = makeFakeState(1, false);
+ const transition2State = makeFakeState(2, true);
+ const blurred2State = makeFakeState(2, false);
+
+ const childActionHandler = jest.fn();
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ const childWillBlurHandler = jest.fn();
+ const childDidBlurHandler = jest.fn();
+ childEventSubscriber('action', childActionHandler);
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ childEventSubscriber('willBlur', childWillBlurHandler);
+ childEventSubscriber('didBlur', childDidBlurHandler);
+ emitGrandParentAction({
+ type: 'action',
+ state: transitionState,
+ lastState: blurredState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(0);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(0);
+ emitGrandParentAction({
+ type: 'action',
+ state: focusState,
+ lastState: transitionState,
+ action: { type: 'FooAction' },
+ });
+ expect(childActionHandler.mock.calls.length).toBe(0);
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+ emitGrandParentAction({
+ type: 'action',
+ state: focusState,
+ lastState: focusState,
+ action: { type: 'TestAction' },
+ });
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+ expect(childActionHandler.mock.calls.length).toBe(1);
+ emitGrandParentAction({
+ type: 'action',
+ state: transition2State,
+ lastState: focusState,
+ action: { type: 'CauseWillBlurAction' },
+ });
+ expect(childWillBlurHandler.mock.calls.length).toBe(1);
+ expect(childDidBlurHandler.mock.calls.length).toBe(0);
+ expect(childActionHandler.mock.calls.length).toBe(2);
+ emitGrandParentAction({
+ type: 'action',
+ state: blurred2State,
+ lastState: transition2State,
+ action: { type: 'CauseDidBlurAction' },
+ });
+ expect(childWillBlurHandler.mock.calls.length).toBe(1);
+ expect(childDidBlurHandler.mock.calls.length).toBe(1);
+ expect(childActionHandler.mock.calls.length).toBe(3);
+});
+
+test('pass through focus', () => {
+ const parentSubscriber = jest.fn();
+ const emitParentAction = payload => {
+ parentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'testKey'
+ );
+ const testRoute = {
+ key: 'foo',
+ routeName: 'FooRoute',
+ routes: [{ key: 'key0' }, { key: 'testKey' }],
+ index: 1,
+ isTransitioning: false,
+ };
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ const childWillBlurHandler = jest.fn();
+ const childDidBlurHandler = jest.fn();
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ childEventSubscriber('willBlur', childWillBlurHandler);
+ childEventSubscriber('didBlur', childDidBlurHandler);
+ emitParentAction({
+ type: 'willFocus',
+ state: testRoute,
+ lastState: testRoute,
+ action: { type: 'FooAction' },
+ });
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'didFocus',
+ state: testRoute,
+ lastState: testRoute,
+ action: { type: 'FooAction' },
+ });
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'willBlur',
+ state: testRoute,
+ lastState: testRoute,
+ action: { type: 'FooAction' },
+ });
+ expect(childWillBlurHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'didBlur',
+ state: testRoute,
+ lastState: testRoute,
+ action: { type: 'FooAction' },
+ });
+ expect(childDidBlurHandler.mock.calls.length).toBe(1);
+});
+
+test('child focus with transition', () => {
+ const parentSubscriber = jest.fn();
+ const emitParentAction = payload => {
+ parentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'key1'
+ );
+ const randomAction = { type: 'FooAction' };
+ const testState = {
+ key: 'foo',
+ routeName: 'FooRoute',
+ routes: [{ key: 'key0' }, { key: 'key1' }],
+ index: 0,
+ isTransitioning: false,
+ };
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ const childWillBlurHandler = jest.fn();
+ const childDidBlurHandler = jest.fn();
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ childEventSubscriber('willBlur', childWillBlurHandler);
+ childEventSubscriber('didBlur', childDidBlurHandler);
+ emitParentAction({
+ type: 'didFocus',
+ action: randomAction,
+ lastState: testState,
+ state: testState,
+ });
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: testState,
+ state: {
+ ...testState,
+ index: 1,
+ isTransitioning: true,
+ },
+ });
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: {
+ ...testState,
+ index: 1,
+ isTransitioning: true,
+ },
+ state: {
+ ...testState,
+ index: 1,
+ isTransitioning: false,
+ },
+ });
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: {
+ ...testState,
+ index: 1,
+ isTransitioning: false,
+ },
+ state: {
+ ...testState,
+ index: 0,
+ isTransitioning: true,
+ },
+ });
+ expect(childWillBlurHandler.mock.calls.length).toBe(1);
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: {
+ ...testState,
+ index: 0,
+ isTransitioning: true,
+ },
+ state: {
+ ...testState,
+ index: 0,
+ isTransitioning: false,
+ },
+ });
+ expect(childDidBlurHandler.mock.calls.length).toBe(1);
+});
+
+test('child focus with immediate transition', () => {
+ const parentSubscriber = jest.fn();
+ const emitParentAction = payload => {
+ parentSubscriber.mock.calls.forEach(subs => {
+ if (subs[0] === payload.type) {
+ subs[1](payload);
+ }
+ });
+ };
+ const subscriptionRemove = () => {};
+ parentSubscriber.mockReturnValueOnce({ remove: subscriptionRemove });
+ const childEventSubscriber = getChildEventSubscriber(
+ parentSubscriber,
+ 'key1'
+ );
+ const randomAction = { type: 'FooAction' };
+ const testState = {
+ key: 'foo',
+ routeName: 'FooRoute',
+ routes: [{ key: 'key0' }, { key: 'key1' }],
+ index: 0,
+ isTransitioning: false,
+ };
+ const childWillFocusHandler = jest.fn();
+ const childDidFocusHandler = jest.fn();
+ const childWillBlurHandler = jest.fn();
+ const childDidBlurHandler = jest.fn();
+ childEventSubscriber('willFocus', childWillFocusHandler);
+ childEventSubscriber('didFocus', childDidFocusHandler);
+ childEventSubscriber('willBlur', childWillBlurHandler);
+ childEventSubscriber('didBlur', childDidBlurHandler);
+ emitParentAction({
+ type: 'didFocus',
+ action: randomAction,
+ lastState: testState,
+ state: testState,
+ });
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: testState,
+ state: {
+ ...testState,
+ index: 1,
+ },
+ });
+ expect(childWillFocusHandler.mock.calls.length).toBe(1);
+ expect(childDidFocusHandler.mock.calls.length).toBe(1);
+
+ emitParentAction({
+ type: 'action',
+ action: randomAction,
+ lastState: {
+ ...testState,
+ index: 1,
+ },
+ state: {
+ ...testState,
+ index: 0,
+ },
+ });
+ expect(childWillBlurHandler.mock.calls.length).toBe(1);
+ expect(childDidBlurHandler.mock.calls.length).toBe(1);
+});
diff --git a/src/getChildEventSubscriber.js b/src/getChildEventSubscriber.js
index fbac4dcd45..e4f784eb32 100644
--- a/src/getChildEventSubscriber.js
+++ b/src/getChildEventSubscriber.js
@@ -32,13 +32,12 @@ export default function getChildEventSubscriber(addListener, key) {
const subscribers = getChildSubscribers(payload.type);
subscribers &&
subscribers.forEach(subs => {
- // $FlowFixMe - Payload should probably understand generic state type
subs(payload);
});
};
- let isSelfFocused = false;
-
+ let isParentFocused = true;
+ let isChildFocused = false;
const cleanup = () => {
upstreamSubscribers.forEach(subs => subs && subs.remove());
};
@@ -54,74 +53,112 @@ export default function getChildEventSubscriber(addListener, key) {
const upstreamSubscribers = upstreamEvents.map(eventName =>
addListener(eventName, payload => {
const { state, lastState, action } = payload;
- const lastFocusKey = lastState && lastState.routes[lastState.index].key;
- const focusKey = state && state.routes[state.index].key;
+ const lastRoutes = lastState && lastState.routes;
+ const routes = state && state.routes;
+ const lastFocusKey =
+ lastState && lastState.routes && lastState.routes[lastState.index].key;
+ const focusKey = routes && routes[state.index].key;
const isFocused = focusKey === key;
const wasFocused = lastFocusKey === key;
const lastRoute =
- lastState && lastState.routes.find(route => route.key === key);
- const newRoute = state && state.routes.find(route => route.key === key);
+ lastRoutes && lastRoutes.find(route => route.key === key);
+ const newRoute = routes && routes.find(route => route.key === key);
+ const eventContext = payload.context || 'Root';
const childPayload = {
+ context: `${key}:${action.type}_${eventContext}`,
state: newRoute,
lastState: lastRoute,
action,
type: eventName,
};
- const didNavigate =
- (lastState && lastState.isTransitioning) !==
- (state && state.isTransitioning);
-
const isTransitioning = !!state && state.isTransitioning;
const wasTransitioning = !!lastState && lastState.isTransitioning;
const didStartTransitioning = !wasTransitioning && isTransitioning;
const didFinishTransitioning = wasTransitioning && !isTransitioning;
-
+ const wasChildFocused = isChildFocused;
if (eventName !== 'action') {
switch (eventName) {
case 'didFocus':
- isSelfFocused = true;
+ isParentFocused = true;
break;
case 'didBlur':
- isSelfFocused = false;
+ isParentFocused = false;
break;
}
- emit(childPayload);
+ if (isFocused && eventName === 'willFocus') {
+ emit(childPayload);
+ }
+ if (isFocused && !isTransitioning && eventName === 'didFocus') {
+ emit(childPayload);
+ isChildFocused = true;
+ }
+ if (isFocused && eventName === 'willBlur') {
+ emit(childPayload);
+ }
+ if (isFocused && !isTransitioning && eventName === 'didBlur') {
+ emit(childPayload);
+ }
return;
}
// now we're exclusively handling the "action" event
+ if (!isParentFocused) {
+ return;
+ }
- if (newRoute) {
- // fire this event to pass navigation events to children subscribers
+ if (isChildFocused && newRoute) {
+ // fire this action event to pass navigation events to children subscribers
emit(childPayload);
}
- if (isFocused && didStartTransitioning && !isSelfFocused) {
+ if (isFocused && didStartTransitioning && !isChildFocused) {
emit({
...childPayload,
type: 'willFocus',
});
}
- if (isFocused && didFinishTransitioning && !isSelfFocused) {
+ if (isFocused && didFinishTransitioning && !isChildFocused) {
+ emit({
+ ...childPayload,
+ type: 'didFocus',
+ });
+ isChildFocused = true;
+ }
+ if (isFocused && !isChildFocused && !didStartTransitioning) {
+ emit({
+ ...childPayload,
+ type: 'willFocus',
+ });
emit({
...childPayload,
type: 'didFocus',
});
- isSelfFocused = true;
+ isChildFocused = true;
}
- if (!isFocused && didStartTransitioning && isSelfFocused) {
+ if (!isFocused && didStartTransitioning && isChildFocused) {
emit({
...childPayload,
type: 'willBlur',
});
}
- if (!isFocused && didFinishTransitioning && isSelfFocused) {
+ if (!isFocused && didFinishTransitioning && isChildFocused) {
+ emit({
+ ...childPayload,
+ type: 'didBlur',
+ });
+ isChildFocused = false;
+ }
+ if (!isFocused && isChildFocused && !didStartTransitioning) {
+ emit({
+ ...childPayload,
+ type: 'willBlur',
+ });
emit({
...childPayload,
type: 'didBlur',
});
- isSelfFocused = false;
+ isChildFocused = false;
}
})
);