initial replacement with new context API - WIP
This commit is contained in:
parent
37d0a0f8e1
commit
1bfab39fa2
|
@ -57,7 +57,7 @@ describe('MenuTrigger', () => {
|
||||||
<MenuTrigger menuName='menu1' />
|
<MenuTrigger menuName='menu1' />
|
||||||
);
|
);
|
||||||
const menuActions = { openMenu: createSpy() };
|
const menuActions = { openMenu: createSpy() };
|
||||||
instance.context = { menuActions };
|
instance.props.ctx = { menuActions };
|
||||||
nthChild(output, 1).props.onPress();
|
nthChild(output, 1).props.onPress();
|
||||||
expect(menuActions.openMenu).toHaveBeenCalledWith('menu1');
|
expect(menuActions.openMenu).toHaveBeenCalledWith('menu1');
|
||||||
expect(menuActions.openMenu.calls.count()).toEqual(1);
|
expect(menuActions.openMenu.calls.count()).toEqual(1);
|
||||||
|
@ -68,7 +68,7 @@ describe('MenuTrigger', () => {
|
||||||
<MenuTrigger menuName='menu1' disabled={true} />
|
<MenuTrigger menuName='menu1' disabled={true} />
|
||||||
);
|
);
|
||||||
const menuActions = { openMenu: createSpy() };
|
const menuActions = { openMenu: createSpy() };
|
||||||
instance.context = { menuActions };
|
instance.props.ctx = { menuActions };
|
||||||
nthChild(output, 1).props.onPress();
|
nthChild(output, 1).props.onPress();
|
||||||
expect(menuActions.openMenu).not.toHaveBeenCalled();
|
expect(menuActions.openMenu).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-native-popup-menu",
|
"name": "react-native-popup-menu",
|
||||||
"version": "0.12.3",
|
"version": "0.13.0-rc",
|
||||||
"description": "extensible popup/context menu for react native",
|
"description": "extensible popup/context menu for react native",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
|
@ -61,5 +61,6 @@
|
||||||
"react-dom": "^15.5.4",
|
"react-dom": "^15.5.4",
|
||||||
"react-test-renderer": "^15.6.1",
|
"react-test-renderer": "^15.6.1",
|
||||||
"sinon": "^2.2.0"
|
"sinon": "^2.2.0"
|
||||||
}
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|
27
src/Menu.js
27
src/Menu.js
|
@ -5,17 +5,19 @@ import { MenuOptions, MenuTrigger } from './index';
|
||||||
import ContextMenu from './renderers/ContextMenu';
|
import ContextMenu from './renderers/ContextMenu';
|
||||||
import { makeName } from './helpers';
|
import { makeName } from './helpers';
|
||||||
import { debug } from './logger';
|
import { debug } from './logger';
|
||||||
|
import { withCtx } from './MenuProvider';
|
||||||
|
|
||||||
const isRegularComponent = c => c.type !== MenuOptions && c.type !== MenuTrigger;
|
const isRegularComponent = c => c.type !== MenuOptions && c.type !== MenuTrigger;
|
||||||
const isTrigger = c => c.type === MenuTrigger;
|
const isTrigger = c => c.type === MenuTrigger;
|
||||||
const isMenuOptions = c => c.type === MenuOptions;
|
const isMenuOptions = c => c.type === MenuOptions;
|
||||||
|
|
||||||
export default class Menu extends Component {
|
class Menu extends Component {
|
||||||
|
|
||||||
constructor(props, ctx) {
|
constructor(props) {
|
||||||
super(props, ctx);
|
super(props);
|
||||||
this._name = this.props.name || makeName();
|
this._name = this.props.name || makeName();
|
||||||
this._forceClose = false;
|
this._forceClose = false;
|
||||||
|
const { ctx } = props;
|
||||||
if(!(ctx && ctx.menuActions)) {
|
if(!(ctx && ctx.menuActions)) {
|
||||||
throw new Error("Menu component must be ancestor of MenuProvider");
|
throw new Error("Menu component must be ancestor of MenuProvider");
|
||||||
}
|
}
|
||||||
|
@ -26,24 +28,24 @@ export default class Menu extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
debug('subscribing menu', this._name);
|
debug('subscribing menu', this._name);
|
||||||
this.context.menuRegistry.subscribe(this);
|
this.props.ctx.menuRegistry.subscribe(this);
|
||||||
this.context.menuActions._notify();
|
this.props.ctx.menuActions._notify();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
// force update if menu is opened as its content might have changed
|
// force update if menu is opened as its content might have changed
|
||||||
const force = this._isOpen();
|
const force = this._isOpen();
|
||||||
debug('component did update', this._name, force);
|
debug('component did update', this._name, force);
|
||||||
this.context.menuActions._notify(force);
|
this.props.ctx.menuActions._notify(force);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
debug('unsubscribing menu', this._name);
|
debug('unsubscribing menu', this._name);
|
||||||
if (this._isOpen()) {
|
if (this._isOpen()) {
|
||||||
this._forceClose = true;
|
this._forceClose = true;
|
||||||
this.context.menuActions._notify();
|
this.props.ctx.menuActions._notify();
|
||||||
}
|
}
|
||||||
this.context.menuRegistry.unsubscribe(this);
|
this.props.ctx.menuRegistry.unsubscribe(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
@ -53,11 +55,11 @@ export default class Menu extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
open() {
|
open() {
|
||||||
return this.context.menuActions.openMenu(this._name);
|
return this.props.ctx.menuActions.openMenu(this._name);
|
||||||
}
|
}
|
||||||
|
|
||||||
close() {
|
close() {
|
||||||
return this.context.menuActions.closeMenu();
|
return this.props.ctx.menuActions.closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
getName() {
|
getName() {
|
||||||
|
@ -156,7 +158,4 @@ Menu.defaultProps = {
|
||||||
onBackdropPress: () => {},
|
onBackdropPress: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
Menu.contextTypes = {
|
export default withCtx(Menu);
|
||||||
menuRegistry: PropTypes.object,
|
|
||||||
menuActions: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
|
@ -3,8 +3,10 @@ import PropTypes from 'prop-types';
|
||||||
import { View, StyleSheet, Text } from 'react-native';
|
import { View, StyleSheet, Text } from 'react-native';
|
||||||
import { debug } from './logger';
|
import { debug } from './logger';
|
||||||
import { makeTouchable } from './helpers';
|
import { makeTouchable } from './helpers';
|
||||||
|
import { withCtx } from './MenuProvider';
|
||||||
|
|
||||||
export default class MenuOption extends Component {
|
|
||||||
|
class MenuOption extends Component {
|
||||||
|
|
||||||
_onSelect() {
|
_onSelect() {
|
||||||
const { value } = this.props;
|
const { value } = this.props;
|
||||||
|
@ -12,17 +14,17 @@ export default class MenuOption extends Component {
|
||||||
const shouldClose = onSelect(value) !== false;
|
const shouldClose = onSelect(value) !== false;
|
||||||
debug('select option', value, shouldClose);
|
debug('select option', value, shouldClose);
|
||||||
if (shouldClose) {
|
if (shouldClose) {
|
||||||
this.context.menuActions.closeMenu();
|
this.props.ctx.menuActions.closeMenu();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_getMenusOnSelect() {
|
_getMenusOnSelect() {
|
||||||
const menu = this.context.menuActions._getOpenedMenu();
|
const menu = this.props.ctx.menuActions._getOpenedMenu();
|
||||||
return menu.instance.props.onSelect;
|
return menu.instance.props.onSelect;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getCustomStyles() {
|
_getCustomStyles() {
|
||||||
const { optionsCustomStyles } = this.context.menuActions._getOpenedMenu();
|
const { optionsCustomStyles } = this.props.ctx.menuActions._getOpenedMenu();
|
||||||
return {
|
return {
|
||||||
...optionsCustomStyles,
|
...optionsCustomStyles,
|
||||||
...this.props.customStyles,
|
...this.props.customStyles,
|
||||||
|
@ -81,10 +83,6 @@ MenuOption.defaultProps = {
|
||||||
customStyles: {},
|
customStyles: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuOption.contextTypes = {
|
|
||||||
menuActions: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
const defaultStyles = StyleSheet.create({
|
const defaultStyles = StyleSheet.create({
|
||||||
option: {
|
option: {
|
||||||
padding: 5,
|
padding: 5,
|
||||||
|
@ -94,3 +92,5 @@ const defaultStyles = StyleSheet.create({
|
||||||
color: '#ccc',
|
color: '#ccc',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default withCtx(MenuOption);
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
|
import { withCtx } from './MenuProvider';
|
||||||
|
|
||||||
class MenuOptions extends React.Component {
|
class _MenuOptions extends React.Component {
|
||||||
|
|
||||||
updateCustomStyles(_props) {
|
updateCustomStyles(_props) {
|
||||||
const { customStyles } = _props
|
const { customStyles } = _props
|
||||||
const menu = this.context.menuActions._getOpenedMenu()
|
const menu = this.props.ctx.menuActions._getOpenedMenu()
|
||||||
const menuName = menu.instance.getName()
|
const menuName = menu.instance.getName()
|
||||||
this.context.menuRegistry.setOptionsCustomStyles(menuName, customStyles)
|
this.props.ctx.menuRegistry.setOptionsCustomStyles(menuName, customStyles)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
componentWillReceiveProps(nextProps) {
|
||||||
|
@ -29,6 +30,8 @@ class MenuOptions extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MenuOptions = withCtx(_MenuOptions)
|
||||||
|
|
||||||
MenuOptions.propTypes = {
|
MenuOptions.propTypes = {
|
||||||
customStyles: PropTypes.object,
|
customStyles: PropTypes.object,
|
||||||
renderOptionsContainer: PropTypes.func,
|
renderOptionsContainer: PropTypes.func,
|
||||||
|
@ -43,9 +46,4 @@ MenuOptions.defaultProps = {
|
||||||
customStyles: {},
|
customStyles: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuOptions.contextTypes = {
|
|
||||||
menuRegistry: PropTypes.object,
|
|
||||||
menuActions: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MenuOptions;
|
export default MenuOptions;
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, { Component } from 'react';
|
import React, { Component, createContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { View, BackHandler } from 'react-native';
|
import { View, BackHandler } from 'react-native';
|
||||||
|
|
||||||
|
import { withContext } from './with-context';
|
||||||
import makeMenuRegistry from './menuRegistry';
|
import makeMenuRegistry from './menuRegistry';
|
||||||
import MenuPlaceholder from './MenuPlaceholder';
|
import MenuPlaceholder from './MenuPlaceholder';
|
||||||
import { measure } from './helpers';
|
import { measure } from './helpers';
|
||||||
|
@ -14,15 +16,18 @@ const layoutsEqual = (a, b) => (
|
||||||
|
|
||||||
const isFunctional = Component => !Component.prototype.render;
|
const isFunctional = Component => !Component.prototype.render;
|
||||||
|
|
||||||
|
if (!createContext) {
|
||||||
|
console.warn('New React context API not available - are you using RN 0.55+ ?')
|
||||||
|
}
|
||||||
|
const PopupMenuContext = createContext({})
|
||||||
|
export const withCtx = withContext(PopupMenuContext, "ctx");
|
||||||
|
|
||||||
export default class MenuProvider extends Component {
|
export default class MenuProvider extends Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._menuRegistry = makeMenuRegistry();
|
this._menuRegistry = makeMenuRegistry();
|
||||||
this._isMenuClosing = false;
|
this._isMenuClosing = false;
|
||||||
}
|
|
||||||
|
|
||||||
getChildContext() {
|
|
||||||
const menuActions = {
|
const menuActions = {
|
||||||
openMenu: name => this.openMenu(name),
|
openMenu: name => this.openMenu(name),
|
||||||
closeMenu: () => this.closeMenu(),
|
closeMenu: () => this.closeMenu(),
|
||||||
|
@ -31,8 +36,7 @@ export default class MenuProvider extends Component {
|
||||||
_getOpenedMenu: () => this._getOpenedMenu(),
|
_getOpenedMenu: () => this._getOpenedMenu(),
|
||||||
_notify: force => this._notify(force),
|
_notify: force => this._notify(force),
|
||||||
};
|
};
|
||||||
const menuRegistry = this._menuRegistry;
|
this.menuCtx = { menuRegistry: this._menuRegistry, menuActions }
|
||||||
return { menuRegistry, menuActions };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_handleBackButton = () => {
|
_handleBackButton = () => {
|
||||||
|
@ -196,6 +200,7 @@ export default class MenuProvider extends Component {
|
||||||
const { style, customStyles } = this.props;
|
const { style, customStyles } = this.props;
|
||||||
debug('render menu', this.isMenuOpen(), this._ownLayout);
|
debug('render menu', this.isMenuOpen(), this._ownLayout);
|
||||||
return (
|
return (
|
||||||
|
<PopupMenuContext.Provider value={this.menuCtx}>
|
||||||
<View style={{flex:1}} onLayout={this._onLayout}>
|
<View style={{flex:1}} onLayout={this._onLayout}>
|
||||||
<View style={[
|
<View style={[
|
||||||
{flex:1},
|
{flex:1},
|
||||||
|
@ -211,6 +216,7 @@ export default class MenuProvider extends Component {
|
||||||
ref={this._onPlaceholderRef}
|
ref={this._onPlaceholderRef}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
</PopupMenuContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +241,6 @@ export default class MenuProvider extends Component {
|
||||||
if (menu) {
|
if (menu) {
|
||||||
menu.instance.props.onBackdropPress();
|
menu.instance.props.onBackdropPress();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -311,8 +316,3 @@ MenuProvider.defaultProps = {
|
||||||
customStyles: {},
|
customStyles: {},
|
||||||
backHandler: false,
|
backHandler: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuProvider.childContextTypes = {
|
|
||||||
menuRegistry: PropTypes.object,
|
|
||||||
menuActions: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ import PropTypes from 'prop-types';
|
||||||
import { View, Text } from 'react-native';
|
import { View, Text } from 'react-native';
|
||||||
import { debug } from './logger.js';
|
import { debug } from './logger.js';
|
||||||
import { makeTouchable } from './helpers';
|
import { makeTouchable } from './helpers';
|
||||||
|
import { withCtx } from './MenuProvider';
|
||||||
|
|
||||||
export default class MenuTrigger extends Component {
|
class MenuTrigger extends Component {
|
||||||
|
|
||||||
_onPress() {
|
_onPress() {
|
||||||
debug('trigger onPress');
|
debug('trigger onPress');
|
||||||
this.props.onPress && this.props.onPress();
|
this.props.onPress && this.props.onPress();
|
||||||
this.context.menuActions.openMenu(this.props.menuName);
|
this.props.ctx.menuActions.openMenu(this.props.menuName);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -45,7 +46,4 @@ MenuTrigger.defaultProps = {
|
||||||
customStyles: {},
|
customStyles: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
MenuTrigger.contextTypes = {
|
export default withCtx(MenuTrigger)
|
||||||
menuActions: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function withContext(Context, propName = "context") {
|
||||||
|
return function wrap(Component) {
|
||||||
|
class EnhanceContext extends React.Component {
|
||||||
|
render() {
|
||||||
|
const { forwardedRef, ...rest } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Consumer>
|
||||||
|
{value => {
|
||||||
|
const custom = {
|
||||||
|
[propName]: value,
|
||||||
|
ref: forwardedRef
|
||||||
|
};
|
||||||
|
return <Component {...custom} {...rest} />;
|
||||||
|
}}
|
||||||
|
</Context.Consumer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = Component.displayName || Component.name || "Component";
|
||||||
|
const consumerName =
|
||||||
|
Context.Consumer.displayName ||
|
||||||
|
Context.Consumer.name ||
|
||||||
|
"Context.Consumer";
|
||||||
|
|
||||||
|
function enhanceForwardRef(props, ref) {
|
||||||
|
return <EnhanceContext {...props} forwardedRef={ref} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
enhanceForwardRef.displayName = `enhanceContext-${consumerName}(${name})`;
|
||||||
|
|
||||||
|
enhanceForwardRef.defaultProps = Component.defaultProps;
|
||||||
|
enhanceForwardRef.propTypes = Component.propTypes;
|
||||||
|
|
||||||
|
return React.forwardRef(enhanceForwardRef);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue