Merge branch 'closing-animation'

This commit is contained in:
Stanislav Miklik 2017-05-12 20:57:18 +02:00
commit 4941e1ad3d
12 changed files with 143 additions and 100 deletions

View File

@ -25,5 +25,6 @@
"comma-dangle": 0,
"react/prop-types": 0,
"react/no-did-mount-set-state": 0,
"react/no-deprecated": 0
}
}

View File

@ -10,5 +10,8 @@
"jest": true,
"expect": true,
"jasmine": true
},
"rules": {
"react/jsx-key": 0,
}
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import { View, Text } from 'react-native';
import { render } from './helpers';
import { MenuTrigger, MenuOptions } from '../src/index';

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import { View, Text } from 'react-native';
import { render } from './helpers';
import { MenuOptions, MenuTrigger } from '../src/index';
import MenuOutside from '../src/renderers/MenuOutside';
@ -106,7 +106,7 @@ describe('MenuContext', () => {
);
const { menuRegistry, menuActions } = instance.getChildContext();
menuRegistry.subscribe(menu1);
menuActions.openMenu('menu1');
menuActions.openMenu('menu1').then(() => {
expect(menuActions.isMenuOpen()).toEqual(true);
expect(menu1._getOpened()).toEqual(true);
initOutput.props.onLayout(defaultLayout);
@ -120,6 +120,7 @@ describe('MenuContext', () => {
// on open was called only once
expect(menu1.props.onOpen.calls.count()).toEqual(1);
});
});
it('should close menu', () => {
const { output: initOutput, instance, renderer } = render(
@ -166,7 +167,7 @@ describe('MenuContext', () => {
const { menuRegistry, menuActions } = instance.getChildContext();
initOutput.props.onLayout(defaultLayout);
menuRegistry.subscribe(menu1);
menuActions.openMenu('menu_not_existing');
return menuActions.openMenu('menu_not_existing').then(() => {
expect(menuActions.isMenuOpen()).toEqual(false);
const output = renderer.getRenderOutput();
const [ components, backdrop, options ] = output.props.children;
@ -174,6 +175,7 @@ describe('MenuContext', () => {
expect(backdrop).toBeFalsy();
expect(options).toBeFalsy();
});
});
it('should not open menu if not initialized', () => {
const { output, instance } = render(
@ -181,7 +183,7 @@ describe('MenuContext', () => {
);
const { menuRegistry, menuActions } = instance.getChildContext();
menuRegistry.subscribe(menu1);
menuActions.openMenu('menu1');
menuActions.openMenu('menu1').then(() => {
expect(menuActions.isMenuOpen()).toEqual(true);
const [ components, backdrop, options ] = output.props.children;
// on layout has not been not called
@ -189,6 +191,7 @@ describe('MenuContext', () => {
expect(backdrop).toBeFalsy();
expect(options).toBeFalsy();
});
});
it('should update options layout', () => {
const { output: initOutput, instance, renderer } = render(
@ -197,7 +200,7 @@ describe('MenuContext', () => {
const { menuRegistry, menuActions } = instance.getChildContext();
initOutput.props.onLayout(defaultLayout);
menuRegistry.subscribe(menu1);
menuActions.openMenu('menu1');
menuActions.openMenu('menu1').then(() => {
const output = renderer.getRenderOutput();
expect(output.props.children.length).toEqual(3);
const options = output.props.children[2];
@ -218,6 +221,7 @@ describe('MenuContext', () => {
}
}));
});
});
it('should render backdrop that will trigger onBackdropPress', () => {
const { output: initOutput, instance, renderer } = render(
@ -226,7 +230,7 @@ describe('MenuContext', () => {
const { menuRegistry, menuActions } = instance.getChildContext();
initOutput.props.onLayout(defaultLayout);
menuRegistry.subscribe(menu1);
menuActions.openMenu('menu1');
menuActions.openMenu('menu1').then(() => {
const output = renderer.getRenderOutput();
expect(output.props.children.length).toEqual(3);
const backdrop = output.props.children[1];
@ -234,5 +238,6 @@ describe('MenuContext', () => {
backdrop.props.onPress();
expect(menu1.props.onBackdropPress).toHaveBeenCalled();
});
});
});

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Animated } from 'react-native';
import { Animated, Text } from 'react-native';
import { render } from '../helpers';
jest.dontMock('../../src/renderers/ContextMenu');

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import { View, Text } from 'react-native';
import { render } from '../helpers';
jest.dontMock('../../src/renderers/MenuOutside');

View File

@ -1,5 +1,5 @@
import React from 'react';
import { View } from 'react-native';
import { View, Text } from 'react-native';
import { render } from '../helpers';
jest.dontMock('../../src/renderers/NotAnimatedContextMenu');

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Animated } from 'react-native';
import { Animated, Text } from 'react-native';
import { render } from '../helpers';
jest.dontMock('../../src/renderers/SlideInMenu');

View File

@ -12,19 +12,23 @@ export default class ControlledExample extends Component {
onOptionSelect(value) {
alert(`Selected number: ${value}`);
if (value === 1) {
this.refs.menu.close();
this.menu.close();
}
return false;
}
openMenu() {
this.refs.menu.open();
this.menu.open();
}
onRef = r => {
this.menu = r;
}
render() {
return (
<MenuContext style={{flexDirection: 'column', padding: 30}}>
<Menu onSelect={value => this.onOptionSelect(value)} ref='menu'>
<Menu onSelect={value => this.onOptionSelect(value)} ref={this.onRef}>
<MenuTrigger text='Select option'/>
<MenuOptions>
<MenuOption value={1} text='One' />

View File

@ -46,7 +46,7 @@ const OriginalExample = React.createClass({
});
},
render() {
return (
return ( // eslint-disable-next-line react/no-string-refs
<MenuContext style={{ flex: 1 }} ref="MenuContext">
<View style={styles.topbar}>
<Menu onSelect={this.setMessage}>

View File

@ -21,10 +21,10 @@
},
"homepage": "https://github.com/instea/react-native-popup-menu",
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [
"js"
],
"transform": {
".*": "<rootDir>/node_modules/babel-jest"
},
"testRegex": ".*-test.js",
"moduleFileExtensions": [
"js"
],
@ -41,18 +41,20 @@
},
"devDependencies": {
"babel-core": "^6.8.0",
"babel-eslint": "^6.0.4",
"babel-jest": "^12.0.2",
"babel-eslint": "^7.2.3",
"babel-jest": "^20.0.1",
"babel-polyfill": "^6.23.0",
"babel-preset-react": "^6.5.0",
"babel-preset-react-native": "^1.7.0",
"babel-preset-react-native": "^1.9.2",
"chai": "^3.5.0",
"eslint": "^2.9.0",
"eslint-plugin-react": "^5.1.1",
"eslint": "^3.19.0",
"eslint-plugin-react": "^7.0.0",
"jasmine-reporters": "^2.1.1",
"jest-cli": "^12.0.2",
"mocha": "^2.4.5",
"react": "^15.0.2",
"react-addons-test-utils": "^15.0.2",
"sinon": "^1.17.4"
"jest-cli": "^20.0.1",
"mocha": "^3.3.0",
"react": "^15.5.4",
"react-addons-test-utils": "^15.5.1",
"react-dom": "^15.5.4",
"sinon": "^2.2.0"
}
}

View File

@ -40,37 +40,47 @@ export default class MenuContext extends Component {
openMenu(name) {
const menu = this._menuRegistry.getMenu(name);
if (!menu) {
return console.warn(`menu with name ${name} does not exist`);
console.warn(`menu with name ${name} does not exist`);
return Promise.resolve();
}
debug('open menu', name);
menu.instance._setOpened(true);
this._notify();
return Promise.resolve();
return this._notify();
}
closeMenu() {
closeMenu() { // has no effect on controlled menus
debug('close menu');
const hideMenu = (this.refs.menuOptions
&& this.refs.menuOptions.close
&& this.refs.menuOptions.close()) || Promise.resolve();
const hideBackdrop = this.refs.backdrop && this.refs.backdrop.close();
const closePromise = Promise.all([hideMenu, hideBackdrop]);
return closePromise.then(() => {
this._menuRegistry.getAll().forEach(menu => {
if (menu.instance._getOpened()) {
menu.instance._setOpened(false);
// invalidate trigger layout
this._menuRegistry.updateLayoutInfo(menu.name, { triggerLayout: undefined });
this._menuRegistry.getAll()
.filter(menu => menu.instance._getOpened())
.forEach(menu => menu.instance._setOpened(false));
return this._notify();
}
_invalidateTriggerLayouts() {
// invalidate layouts for closed menus,
// both controlled and uncontrolled menus
this._menuRegistry.getAll()
.filter(menu => !menu.instance._isOpen())
.forEach(menu => {
this._menuRegistry.updateLayoutInfo(menu.name, { triggerLayout: undefined });
});
this._notify();
}).catch(console.error);
}
_beforeClose(menu) {
debug('before close', menu.name);
const hideMenu = (this.optionsRef
&& this.optionsRef.close
&& this.optionsRef.close()) || Promise.resolve();
const hideBackdrop = this.backdropRef && this.backdropRef.close();
this._invalidateTriggerLayouts();
return Promise.all([hideMenu, hideBackdrop]);
}
toggleMenu(name) {
const menu = this._menuRegistry.getMenu(name);
if (!menu) {
return console.warn(`menu with name ${name} does not exist`);
console.warn(`menu with name ${name} does not exist`);
return Promise.resolve();
}
debug('toggle menu', name);
if (menu.instance._getOpened()) {
@ -87,19 +97,25 @@ export default class MenuContext extends Component {
// set newly opened menu before any callbacks are called
this.openedMenu = next === NULL ? undefined : next;
if (!forceUpdate && !this._isRenderNeeded(prev, next)) {
return;
return Promise.resolve();
}
debug('notify: next menu:', next.name, ' prev menu:', prev.name);
let afterSetState = undefined;
let beforeSetState = () => Promise.resolve();
if (prev.name !== next.name) {
prev.instance && prev.instance.props.onClose();
if (next.name) {
if (prev !== NULL && !prev.instance._isOpen()) {
beforeSetState = () => this._beforeClose(prev)
.then(() => prev.instance.props.onClose());
}
if (next !== NULL) {
next.instance.props.onOpen();
afterSetState = () => this._initOpen(next);
}
}
return beforeSetState().then(() => {
this.setState({ openedMenu: this.openedMenu }, afterSetState);
debug('notify ended');
});
}
/**
@ -131,7 +147,11 @@ export default class MenuContext extends Component {
{ this.props.children }
</View>
{shouldRenderMenu &&
<Backdrop onPress={() => this._onBackdropPress()} style={customStyles.backdrop} ref='backdrop' />
<Backdrop
onPress={() => this._onBackdropPress()}
style={customStyles.backdrop}
ref={this.onBackdropRef}
/>
}
{shouldRenderMenu &&
this._makeOptions(this.state.openedMenu)
@ -140,6 +160,14 @@ export default class MenuContext extends Component {
);
}
onBackdropRef = r => {
this.backdropRef = r;
}
onOptionsRef = r => {
this.optionsRef = r;
}
_onBackdropPress() {
debug('on backdrop press');
this.state.openedMenu.instance.props.onBackdropPress();
@ -181,7 +209,7 @@ export default class MenuContext extends Component {
const props = { style, onLayout, layouts };
const optionsType = isOutside ? MenuOutside : renderer;
if (!isFunctional(optionsType)) {
props.ref = 'menuOptions';
props.ref = this.onOptionsRef;
}
return React.createElement(optionsType, props, optionsRenderer(options));
}