Merge branch 'closing-animation'
This commit is contained in:
commit
4941e1ad3d
|
@ -25,5 +25,6 @@
|
||||||
"comma-dangle": 0,
|
"comma-dangle": 0,
|
||||||
"react/prop-types": 0,
|
"react/prop-types": 0,
|
||||||
"react/no-did-mount-set-state": 0,
|
"react/no-did-mount-set-state": 0,
|
||||||
|
"react/no-deprecated": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,5 +10,8 @@
|
||||||
"jest": true,
|
"jest": true,
|
||||||
"expect": true,
|
"expect": true,
|
||||||
"jasmine": true
|
"jasmine": true
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"react/jsx-key": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { render } from './helpers';
|
import { render } from './helpers';
|
||||||
|
|
||||||
import { MenuTrigger, MenuOptions } from '../src/index';
|
import { MenuTrigger, MenuOptions } from '../src/index';
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { render } from './helpers';
|
import { render } from './helpers';
|
||||||
import { MenuOptions, MenuTrigger } from '../src/index';
|
import { MenuOptions, MenuTrigger } from '../src/index';
|
||||||
import MenuOutside from '../src/renderers/MenuOutside';
|
import MenuOutside from '../src/renderers/MenuOutside';
|
||||||
|
@ -106,7 +106,7 @@ describe('MenuContext', () => {
|
||||||
);
|
);
|
||||||
const { menuRegistry, menuActions } = instance.getChildContext();
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
||||||
menuRegistry.subscribe(menu1);
|
menuRegistry.subscribe(menu1);
|
||||||
menuActions.openMenu('menu1');
|
menuActions.openMenu('menu1').then(() => {
|
||||||
expect(menuActions.isMenuOpen()).toEqual(true);
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
||||||
expect(menu1._getOpened()).toEqual(true);
|
expect(menu1._getOpened()).toEqual(true);
|
||||||
initOutput.props.onLayout(defaultLayout);
|
initOutput.props.onLayout(defaultLayout);
|
||||||
|
@ -120,6 +120,7 @@ describe('MenuContext', () => {
|
||||||
// on open was called only once
|
// on open was called only once
|
||||||
expect(menu1.props.onOpen.calls.count()).toEqual(1);
|
expect(menu1.props.onOpen.calls.count()).toEqual(1);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should close menu', () => {
|
it('should close menu', () => {
|
||||||
const { output: initOutput, instance, renderer } = render(
|
const { output: initOutput, instance, renderer } = render(
|
||||||
|
@ -166,7 +167,7 @@ describe('MenuContext', () => {
|
||||||
const { menuRegistry, menuActions } = instance.getChildContext();
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
||||||
initOutput.props.onLayout(defaultLayout);
|
initOutput.props.onLayout(defaultLayout);
|
||||||
menuRegistry.subscribe(menu1);
|
menuRegistry.subscribe(menu1);
|
||||||
menuActions.openMenu('menu_not_existing');
|
return menuActions.openMenu('menu_not_existing').then(() => {
|
||||||
expect(menuActions.isMenuOpen()).toEqual(false);
|
expect(menuActions.isMenuOpen()).toEqual(false);
|
||||||
const output = renderer.getRenderOutput();
|
const output = renderer.getRenderOutput();
|
||||||
const [ components, backdrop, options ] = output.props.children;
|
const [ components, backdrop, options ] = output.props.children;
|
||||||
|
@ -174,6 +175,7 @@ describe('MenuContext', () => {
|
||||||
expect(backdrop).toBeFalsy();
|
expect(backdrop).toBeFalsy();
|
||||||
expect(options).toBeFalsy();
|
expect(options).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should not open menu if not initialized', () => {
|
it('should not open menu if not initialized', () => {
|
||||||
const { output, instance } = render(
|
const { output, instance } = render(
|
||||||
|
@ -181,7 +183,7 @@ describe('MenuContext', () => {
|
||||||
);
|
);
|
||||||
const { menuRegistry, menuActions } = instance.getChildContext();
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
||||||
menuRegistry.subscribe(menu1);
|
menuRegistry.subscribe(menu1);
|
||||||
menuActions.openMenu('menu1');
|
menuActions.openMenu('menu1').then(() => {
|
||||||
expect(menuActions.isMenuOpen()).toEqual(true);
|
expect(menuActions.isMenuOpen()).toEqual(true);
|
||||||
const [ components, backdrop, options ] = output.props.children;
|
const [ components, backdrop, options ] = output.props.children;
|
||||||
// on layout has not been not called
|
// on layout has not been not called
|
||||||
|
@ -189,6 +191,7 @@ describe('MenuContext', () => {
|
||||||
expect(backdrop).toBeFalsy();
|
expect(backdrop).toBeFalsy();
|
||||||
expect(options).toBeFalsy();
|
expect(options).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should update options layout', () => {
|
it('should update options layout', () => {
|
||||||
const { output: initOutput, instance, renderer } = render(
|
const { output: initOutput, instance, renderer } = render(
|
||||||
|
@ -197,7 +200,7 @@ describe('MenuContext', () => {
|
||||||
const { menuRegistry, menuActions } = instance.getChildContext();
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
||||||
initOutput.props.onLayout(defaultLayout);
|
initOutput.props.onLayout(defaultLayout);
|
||||||
menuRegistry.subscribe(menu1);
|
menuRegistry.subscribe(menu1);
|
||||||
menuActions.openMenu('menu1');
|
menuActions.openMenu('menu1').then(() => {
|
||||||
const output = renderer.getRenderOutput();
|
const output = renderer.getRenderOutput();
|
||||||
expect(output.props.children.length).toEqual(3);
|
expect(output.props.children.length).toEqual(3);
|
||||||
const options = output.props.children[2];
|
const options = output.props.children[2];
|
||||||
|
@ -218,6 +221,7 @@ describe('MenuContext', () => {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should render backdrop that will trigger onBackdropPress', () => {
|
it('should render backdrop that will trigger onBackdropPress', () => {
|
||||||
const { output: initOutput, instance, renderer } = render(
|
const { output: initOutput, instance, renderer } = render(
|
||||||
|
@ -226,7 +230,7 @@ describe('MenuContext', () => {
|
||||||
const { menuRegistry, menuActions } = instance.getChildContext();
|
const { menuRegistry, menuActions } = instance.getChildContext();
|
||||||
initOutput.props.onLayout(defaultLayout);
|
initOutput.props.onLayout(defaultLayout);
|
||||||
menuRegistry.subscribe(menu1);
|
menuRegistry.subscribe(menu1);
|
||||||
menuActions.openMenu('menu1');
|
menuActions.openMenu('menu1').then(() => {
|
||||||
const output = renderer.getRenderOutput();
|
const output = renderer.getRenderOutput();
|
||||||
expect(output.props.children.length).toEqual(3);
|
expect(output.props.children.length).toEqual(3);
|
||||||
const backdrop = output.props.children[1];
|
const backdrop = output.props.children[1];
|
||||||
|
@ -234,5 +238,6 @@ describe('MenuContext', () => {
|
||||||
backdrop.props.onPress();
|
backdrop.props.onPress();
|
||||||
expect(menu1.props.onBackdropPress).toHaveBeenCalled();
|
expect(menu1.props.onBackdropPress).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated } from 'react-native';
|
import { Animated, Text } from 'react-native';
|
||||||
import { render } from '../helpers';
|
import { render } from '../helpers';
|
||||||
|
|
||||||
jest.dontMock('../../src/renderers/ContextMenu');
|
jest.dontMock('../../src/renderers/ContextMenu');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { render } from '../helpers';
|
import { render } from '../helpers';
|
||||||
|
|
||||||
jest.dontMock('../../src/renderers/MenuOutside');
|
jest.dontMock('../../src/renderers/MenuOutside');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { render } from '../helpers';
|
import { render } from '../helpers';
|
||||||
|
|
||||||
jest.dontMock('../../src/renderers/NotAnimatedContextMenu');
|
jest.dontMock('../../src/renderers/NotAnimatedContextMenu');
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Animated } from 'react-native';
|
import { Animated, Text } from 'react-native';
|
||||||
import { render } from '../helpers';
|
import { render } from '../helpers';
|
||||||
|
|
||||||
jest.dontMock('../../src/renderers/SlideInMenu');
|
jest.dontMock('../../src/renderers/SlideInMenu');
|
||||||
|
|
|
@ -12,19 +12,23 @@ export default class ControlledExample extends Component {
|
||||||
onOptionSelect(value) {
|
onOptionSelect(value) {
|
||||||
alert(`Selected number: ${value}`);
|
alert(`Selected number: ${value}`);
|
||||||
if (value === 1) {
|
if (value === 1) {
|
||||||
this.refs.menu.close();
|
this.menu.close();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
openMenu() {
|
openMenu() {
|
||||||
this.refs.menu.open();
|
this.menu.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
onRef = r => {
|
||||||
|
this.menu = r;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<MenuContext style={{flexDirection: 'column', padding: 30}}>
|
<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'/>
|
<MenuTrigger text='Select option'/>
|
||||||
<MenuOptions>
|
<MenuOptions>
|
||||||
<MenuOption value={1} text='One' />
|
<MenuOption value={1} text='One' />
|
||||||
|
|
|
@ -46,7 +46,7 @@ const OriginalExample = React.createClass({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return (
|
return ( // eslint-disable-next-line react/no-string-refs
|
||||||
<MenuContext style={{ flex: 1 }} ref="MenuContext">
|
<MenuContext style={{ flex: 1 }} ref="MenuContext">
|
||||||
<View style={styles.topbar}>
|
<View style={styles.topbar}>
|
||||||
<Menu onSelect={this.setMessage}>
|
<Menu onSelect={this.setMessage}>
|
||||||
|
|
30
package.json
30
package.json
|
@ -21,10 +21,10 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/instea/react-native-popup-menu",
|
"homepage": "https://github.com/instea/react-native-popup-menu",
|
||||||
"jest": {
|
"jest": {
|
||||||
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
|
"transform": {
|
||||||
"testFileExtensions": [
|
".*": "<rootDir>/node_modules/babel-jest"
|
||||||
"js"
|
},
|
||||||
],
|
"testRegex": ".*-test.js",
|
||||||
"moduleFileExtensions": [
|
"moduleFileExtensions": [
|
||||||
"js"
|
"js"
|
||||||
],
|
],
|
||||||
|
@ -41,18 +41,20 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-core": "^6.8.0",
|
"babel-core": "^6.8.0",
|
||||||
"babel-eslint": "^6.0.4",
|
"babel-eslint": "^7.2.3",
|
||||||
"babel-jest": "^12.0.2",
|
"babel-jest": "^20.0.1",
|
||||||
|
"babel-polyfill": "^6.23.0",
|
||||||
"babel-preset-react": "^6.5.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",
|
"chai": "^3.5.0",
|
||||||
"eslint": "^2.9.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-plugin-react": "^5.1.1",
|
"eslint-plugin-react": "^7.0.0",
|
||||||
"jasmine-reporters": "^2.1.1",
|
"jasmine-reporters": "^2.1.1",
|
||||||
"jest-cli": "^12.0.2",
|
"jest-cli": "^20.0.1",
|
||||||
"mocha": "^2.4.5",
|
"mocha": "^3.3.0",
|
||||||
"react": "^15.0.2",
|
"react": "^15.5.4",
|
||||||
"react-addons-test-utils": "^15.0.2",
|
"react-addons-test-utils": "^15.5.1",
|
||||||
"sinon": "^1.17.4"
|
"react-dom": "^15.5.4",
|
||||||
|
"sinon": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,37 +40,47 @@ export default class MenuContext extends Component {
|
||||||
openMenu(name) {
|
openMenu(name) {
|
||||||
const menu = this._menuRegistry.getMenu(name);
|
const menu = this._menuRegistry.getMenu(name);
|
||||||
if (!menu) {
|
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);
|
debug('open menu', name);
|
||||||
menu.instance._setOpened(true);
|
menu.instance._setOpened(true);
|
||||||
this._notify();
|
return this._notify();
|
||||||
return Promise.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
closeMenu() {
|
closeMenu() { // has no effect on controlled menus
|
||||||
debug('close menu');
|
debug('close menu');
|
||||||
const hideMenu = (this.refs.menuOptions
|
this._menuRegistry.getAll()
|
||||||
&& this.refs.menuOptions.close
|
.filter(menu => menu.instance._getOpened())
|
||||||
&& this.refs.menuOptions.close()) || Promise.resolve();
|
.forEach(menu => menu.instance._setOpened(false));
|
||||||
const hideBackdrop = this.refs.backdrop && this.refs.backdrop.close();
|
return this._notify();
|
||||||
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 });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_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) {
|
toggleMenu(name) {
|
||||||
const menu = this._menuRegistry.getMenu(name);
|
const menu = this._menuRegistry.getMenu(name);
|
||||||
if (!menu) {
|
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);
|
debug('toggle menu', name);
|
||||||
if (menu.instance._getOpened()) {
|
if (menu.instance._getOpened()) {
|
||||||
|
@ -87,19 +97,25 @@ export default class MenuContext extends Component {
|
||||||
// set newly opened menu before any callbacks are called
|
// set newly opened menu before any callbacks are called
|
||||||
this.openedMenu = next === NULL ? undefined : next;
|
this.openedMenu = next === NULL ? undefined : next;
|
||||||
if (!forceUpdate && !this._isRenderNeeded(prev, next)) {
|
if (!forceUpdate && !this._isRenderNeeded(prev, next)) {
|
||||||
return;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
debug('notify: next menu:', next.name, ' prev menu:', prev.name);
|
debug('notify: next menu:', next.name, ' prev menu:', prev.name);
|
||||||
let afterSetState = undefined;
|
let afterSetState = undefined;
|
||||||
|
let beforeSetState = () => Promise.resolve();
|
||||||
if (prev.name !== next.name) {
|
if (prev.name !== next.name) {
|
||||||
prev.instance && prev.instance.props.onClose();
|
if (prev !== NULL && !prev.instance._isOpen()) {
|
||||||
if (next.name) {
|
beforeSetState = () => this._beforeClose(prev)
|
||||||
|
.then(() => prev.instance.props.onClose());
|
||||||
|
}
|
||||||
|
if (next !== NULL) {
|
||||||
next.instance.props.onOpen();
|
next.instance.props.onOpen();
|
||||||
afterSetState = () => this._initOpen(next);
|
afterSetState = () => this._initOpen(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return beforeSetState().then(() => {
|
||||||
this.setState({ openedMenu: this.openedMenu }, afterSetState);
|
this.setState({ openedMenu: this.openedMenu }, afterSetState);
|
||||||
debug('notify ended');
|
debug('notify ended');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,7 +147,11 @@ export default class MenuContext extends Component {
|
||||||
{ this.props.children }
|
{ this.props.children }
|
||||||
</View>
|
</View>
|
||||||
{shouldRenderMenu &&
|
{shouldRenderMenu &&
|
||||||
<Backdrop onPress={() => this._onBackdropPress()} style={customStyles.backdrop} ref='backdrop' />
|
<Backdrop
|
||||||
|
onPress={() => this._onBackdropPress()}
|
||||||
|
style={customStyles.backdrop}
|
||||||
|
ref={this.onBackdropRef}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
{shouldRenderMenu &&
|
{shouldRenderMenu &&
|
||||||
this._makeOptions(this.state.openedMenu)
|
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() {
|
_onBackdropPress() {
|
||||||
debug('on backdrop press');
|
debug('on backdrop press');
|
||||||
this.state.openedMenu.instance.props.onBackdropPress();
|
this.state.openedMenu.instance.props.onBackdropPress();
|
||||||
|
@ -181,7 +209,7 @@ export default class MenuContext extends Component {
|
||||||
const props = { style, onLayout, layouts };
|
const props = { style, onLayout, layouts };
|
||||||
const optionsType = isOutside ? MenuOutside : renderer;
|
const optionsType = isOutside ? MenuOutside : renderer;
|
||||||
if (!isFunctional(optionsType)) {
|
if (!isFunctional(optionsType)) {
|
||||||
props.ref = 'menuOptions';
|
props.ref = this.onOptionsRef;
|
||||||
}
|
}
|
||||||
return React.createElement(optionsType, props, optionsRenderer(options));
|
return React.createElement(optionsType, props, optionsRenderer(options));
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue