diff --git a/examples/keyPath.js b/examples/keyPath.js
index fa76a1bc..7779e24c 100644
--- a/examples/keyPath.js
+++ b/examples/keyPath.js
@@ -3,14 +3,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
-import createReactClass from 'create-react-class';
import 'rc-menu/assets/index.less';
-const Test = createReactClass({
+class Test extends React.Component {
onClick(info) {
console.log('click ', info);
- },
+ }
getMenu() {
return (
@@ -33,14 +32,14 @@ const Test = createReactClass({
);
- },
+ }
render() {
return (
);
- },
-});
+ }
+}
ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/openKeys.js b/examples/openKeys.js
index 13115bd4..6a27f912 100644
--- a/examples/openKeys.js
+++ b/examples/openKeys.js
@@ -3,27 +3,25 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
-import createReactClass from 'create-react-class';
import 'rc-menu/assets/index.less';
-const Test = createReactClass({
- getInitialState() {
- return {
- openKeys: [],
- };
- },
+class Test extends React.Component {
+ state = {
+ openKeys: [],
+ };
onClick(info) {
console.log('click ', info);
- },
+ }
- onOpenChange(openKeys) {
+ onOpenChange = (openKeys) => {
console.log('onOpenChange', openKeys);
this.setState({
openKeys,
});
- },
+ }
+
getMenu() {
return (
);
- },
+ }
render() {
return ();
- },
-});
-
+ }
+}
ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/examples/selectedKeys.js b/examples/selectedKeys.js
index ba09a076..ae1c1b4c 100644
--- a/examples/selectedKeys.js
+++ b/examples/selectedKeys.js
@@ -3,38 +3,35 @@
import React from 'react';
import ReactDOM from 'react-dom';
import Menu, { SubMenu, Item as MenuItem } from 'rc-menu';
-import createReactClass from 'create-react-class';
import 'rc-menu/assets/index.less';
-const Test = createReactClass({
- getInitialState() {
- return {
- destroyed: false,
- selectedKeys: [],
- openKeys: [],
- };
- },
+class Test extends React.Component {
+ state = {
+ destroyed: false,
+ selectedKeys: [],
+ openKeys: [],
+ };
- onSelect(info) {
+ onSelect = (info) => {
console.log('selected ', info);
this.setState({
selectedKeys: info.selectedKeys,
});
- },
+ };
onDeselect(info) {
console.log('deselect ', info);
- },
+ }
- onOpenChange(openKeys) {
+ onOpenChange = (openKeys) => {
console.log('onOpenChange ', openKeys);
this.setState({
openKeys,
});
- },
+ };
- onCheck(e) {
+ onCheck = (e) => {
const value = e.target.value;
if (e.target.checked) {
this.setState({
@@ -50,9 +47,9 @@ const Test = createReactClass({
selectedKeys,
});
}
- },
+ };
- onOpenCheck(e) {
+ onOpenCheck = (e) => {
const value = e.target.value;
if (e.target.checked) {
this.setState({
@@ -68,7 +65,7 @@ const Test = createReactClass({
openKeys,
});
}
- },
+ };
getMenu() {
return (
@@ -91,13 +88,13 @@ const Test = createReactClass({
);
- },
+ }
destroy() {
this.setState({
destroyed: true,
});
- },
+ }
render() {
if (this.state.destroyed) {
@@ -141,8 +138,7 @@ const Test = createReactClass({
{this.getMenu()}
);
- },
-});
-
+ }
+}
ReactDOM.render(, document.getElementById('__react-content'));
diff --git a/package.json b/package.json
index 18a15bcd..cc4eaf2c 100644
--- a/package.json
+++ b/package.json
@@ -75,9 +75,8 @@
"dependencies": {
"babel-runtime": "6.x",
"classnames": "2.x",
- "create-react-class": "^15.5.2",
"dom-scroll-into-view": "1.x",
- "mini-store": "^1.0.2",
+ "mini-store": "^1.1.0",
"prop-types": "^15.5.6",
"rc-animate": "2.x",
"rc-trigger": "^2.3.0",
diff --git a/src/DOMWrap.jsx b/src/DOMWrap.jsx
index e4efc189..6027cf45 100644
--- a/src/DOMWrap.jsx
+++ b/src/DOMWrap.jsx
@@ -1,22 +1,17 @@
import React from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-const DOMWrap = createReactClass({
- displayName: 'DOMWrap',
-
- propTypes: {
+export default class DOMWrap extends React.Component {
+ static propTypes = {
tag: PropTypes.string,
hiddenClassName: PropTypes.string,
visible: PropTypes.bool,
- },
+ };
- getDefaultProps() {
- return {
- tag: 'div',
- className: '',
- };
- },
+ static defaultProps = {
+ tag: 'div',
+ className: '',
+ };
render() {
const props = { ...this.props };
@@ -28,7 +23,5 @@ const DOMWrap = createReactClass({
delete props.hiddenClassName;
delete props.visible;
return ;
- },
-});
-
-export default DOMWrap;
+ }
+}
diff --git a/src/Divider.jsx b/src/Divider.jsx
index 4dd63357..9360996f 100644
--- a/src/Divider.jsx
+++ b/src/Divider.jsx
@@ -1,24 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-const Divider = createReactClass({
- displayName: 'Divider',
-
- propTypes: {
+export default class Divider extends React.Component {
+ static propTypes = {
className: PropTypes.string,
rootPrefixCls: PropTypes.string,
- },
+ };
- getDefaultProps() {
+ static defaultProps = {
// To fix keyboard UX.
- return { disabled: true };
- },
+ disabled: true,
+ };
render() {
const { className = '', rootPrefixCls } = this.props;
return ;
- },
-});
-
-export default Divider;
+ }
+}
diff --git a/src/Menu.jsx b/src/Menu.jsx
index c8874da8..eb15dc03 100644
--- a/src/Menu.jsx
+++ b/src/Menu.jsx
@@ -1,15 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import { Provider, create } from 'mini-store';
-import { default as MenuMixin, getActiveKey } from './MenuMixin';
+import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu';
import { noop } from './util';
-const Menu = createReactClass({
- displayName: 'Menu',
-
- propTypes: {
+export default class Menu extends React.Component {
+ static propTypes = {
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
+ defaultActiveFirst: PropTypes.bool,
selectedKeys: PropTypes.arrayOf(PropTypes.string),
defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
openKeys: PropTypes.arrayOf(PropTypes.string),
@@ -29,29 +27,34 @@ const Menu = createReactClass({
selectable: PropTypes.bool,
multiple: PropTypes.bool,
children: PropTypes.any,
- },
-
- mixins: [MenuMixin],
+ className: PropTypes.string,
+ style: PropTypes.object,
+ activeKey: PropTypes.string,
+ prefixCls: PropTypes.string,
+ };
+
+ static defaultProps = {
+ selectable: true,
+ onClick: noop,
+ onSelect: noop,
+ onOpenChange: noop,
+ onDeselect: noop,
+ defaultSelectedKeys: [],
+ defaultOpenKeys: [],
+ subMenuOpenDelay: 0.1,
+ subMenuCloseDelay: 0.1,
+ triggerSubMenuAction: 'hover',
+ prefixCls: 'rc-menu',
+ className: '',
+ mode: 'vertical',
+ style: {},
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.isRootMenu = true;
- isRootMenu: true,
-
- getDefaultProps() {
- return {
- selectable: true,
- onClick: noop,
- onSelect: noop,
- onOpenChange: noop,
- onDeselect: noop,
- defaultSelectedKeys: [],
- defaultOpenKeys: [],
- subMenuOpenDelay: 0.1,
- subMenuCloseDelay: 0.1,
- triggerSubMenuAction: 'hover',
- };
- },
-
- getInitialState() {
- const props = this.props;
let selectedKeys = props.defaultSelectedKeys;
let openKeys = props.defaultOpenKeys;
if ('selectedKeys' in props) {
@@ -66,9 +69,7 @@ const Menu = createReactClass({
openKeys,
activeKey: { '0-menu-': getActiveKey(props, props.activeKey) },
});
-
- return {};
- },
+ }
componentWillReceiveProps(nextProps) {
if ('selectedKeys' in nextProps) {
@@ -81,9 +82,9 @@ const Menu = createReactClass({
openKeys: nextProps.openKeys || [],
});
}
- },
+ }
- onSelect(selectInfo) {
+ onSelect = (selectInfo) => {
const props = this.props;
if (props.selectable) {
// root menu
@@ -104,13 +105,20 @@ const Menu = createReactClass({
selectedKeys,
});
}
- },
+ }
- onClick(e) {
+ onClick = (e) => {
this.props.onClick(e);
- },
+ }
- onOpenChange(event) {
+ // onKeyDown needs to be exposed as a instance method
+ // e.g., in rc-select, we need to navigate menu item while
+ // current active item is rc-select input box rather than the menu itself
+ onKeyDown = (e, callback) => {
+ this.innerMenu.getWrappedInstance().onKeyDown(e, callback);
+ }
+
+ onOpenChange = (event) => {
const props = this.props;
const openKeys = this.store.getState().openKeys.concat();
let changed = false;
@@ -142,9 +150,9 @@ const Menu = createReactClass({
}
props.onOpenChange(openKeys);
}
- },
+ }
- onDeselect(selectInfo) {
+ onDeselect = (selectInfo) => {
const props = this.props;
if (props.selectable) {
const selectedKeys = this.store.getState().selectedKeys.concat();
@@ -163,9 +171,9 @@ const Menu = createReactClass({
selectedKeys,
});
}
- },
+ }
- getOpenTransitionName() {
+ getOpenTransitionName = () => {
const props = this.props;
let transitionName = props.openTransitionName;
const animationName = props.openAnimation;
@@ -173,32 +181,24 @@ const Menu = createReactClass({
transitionName = `${props.prefixCls}-open-${animationName}`;
}
return transitionName;
- },
-
- renderMenuItem(c, i, subMenuKey) {
- /* istanbul ignore if */
- if (!c) {
- return null;
- }
- const state = this.store.getState();
- const extraProps = {
- openKeys: state.openKeys,
- selectedKeys: state.selectedKeys,
- triggerSubMenuAction: this.props.triggerSubMenuAction,
- subMenuKey,
- };
- return this.renderCommonMenuItem(c, i, extraProps);
- },
+ }
render() {
- const props = { ...this.props };
+ let { ...props } = this.props;
props.className += ` ${props.prefixCls}-root`;
+ props = {
+ ...props,
+ onClick: this.onClick,
+ onOpenChange: this.onOpenChange,
+ onDeselect: this.onDeselect,
+ onSelect: this.onSelect,
+ openTransitionName: this.getOpenTransitionName(),
+ parentMenu: this,
+ };
return (
- {this.renderRoot(props)}
+ this.innerMenu = c}>{this.props.children}
);
- },
-});
-
-export default Menu;
+ }
+}
diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx
index 48f06b61..3920b7b5 100644
--- a/src/MenuItem.jsx
+++ b/src/MenuItem.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import KeyCode from 'rc-util/lib/KeyCode';
import classNames from 'classnames';
import scrollIntoView from 'dom-scroll-into-view';
@@ -10,10 +9,8 @@ import { noop } from './util';
/* eslint react/no-is-mounted:0 */
-export const MenuItem = createReactClass({
- displayName: 'MenuItem',
-
- propTypes: {
+export class MenuItem extends React.Component {
+ static propTypes = {
rootPrefixCls: PropTypes.string,
eventKey: PropTypes.string,
active: PropTypes.bool,
@@ -29,27 +26,26 @@ export const MenuItem = createReactClass({
onDestroy: PropTypes.func,
onMouseEnter: PropTypes.func,
onMouseLeave: PropTypes.func,
- },
-
- getDefaultProps() {
- return {
- onSelect: noop,
- onMouseEnter: noop,
- onMouseLeave: noop,
- };
- },
-
- componentWillUnmount() {
- const props = this.props;
- if (props.onDestroy) {
- props.onDestroy(props.eventKey);
- }
- },
+ multiple: PropTypes.bool,
+ isSelected: PropTypes.bool,
+ manualRef: PropTypes.func,
+ };
+
+ static defaultProps = {
+ onSelect: noop,
+ onMouseEnter: noop,
+ onMouseLeave: noop,
+ manualRef: noop,
+ };
+
+ constructor(props) {
+ super(props);
+ }
componentDidMount() {
// invoke customized ref to expose component to mixin
this.callRef();
- },
+ }
componentDidUpdate() {
if (this.props.active) {
@@ -59,18 +55,24 @@ export const MenuItem = createReactClass({
}
this.callRef();
- },
+ }
+ componentWillUnmount() {
+ const props = this.props;
+ if (props.onDestroy) {
+ props.onDestroy(props.eventKey);
+ }
+ }
- onKeyDown(e) {
+ onKeyDown = (e) => {
const keyCode = e.keyCode;
if (keyCode === KeyCode.ENTER) {
this.onClick(e);
return true;
}
- },
+ };
- onMouseLeave(e) {
+ onMouseLeave = (e) => {
const { eventKey, onItemHover, onMouseLeave } = this.props;
onItemHover({
key: eventKey,
@@ -80,9 +82,9 @@ export const MenuItem = createReactClass({
key: eventKey,
domEvent: e,
});
- },
+ };
- onMouseEnter(e) {
+ onMouseEnter = (e) => {
const { eventKey, onItemHover, onMouseEnter } = this.props;
onItemHover({
key: eventKey,
@@ -92,9 +94,9 @@ export const MenuItem = createReactClass({
key: eventKey,
domEvent: e,
});
- },
+ };
- onClick(e) {
+ onClick = (e) => {
const { eventKey, multiple, onClick, onSelect, onDeselect, isSelected } = this.props;
const info = {
key: eventKey,
@@ -112,29 +114,29 @@ export const MenuItem = createReactClass({
} else if (!isSelected) {
onSelect(info);
}
- },
+ };
getPrefixCls() {
return `${this.props.rootPrefixCls}-item`;
- },
+ }
getActiveClassName() {
return `${this.getPrefixCls()}-active`;
- },
+ }
getSelectedClassName() {
return `${this.getPrefixCls()}-selected`;
- },
+ }
getDisabledClassName() {
return `${this.getPrefixCls()}-disabled`;
- },
+ }
callRef() {
if (this.props.manualRef) {
this.props.manualRef(this);
}
- },
+ }
render() {
const props = this.props;
@@ -174,12 +176,14 @@ export const MenuItem = createReactClass({
{props.children}
);
- },
-});
-
-MenuItem.isMenuItem = 1;
+ }
+}
-export default connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
+const connected = connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
active: activeKey[subMenuKey] === eventKey,
isSelected: selectedKeys.indexOf(eventKey) !== -1,
}))(MenuItem);
+
+connected.isMenuItem = true;
+
+export default connected;
diff --git a/src/MenuItemGroup.jsx b/src/MenuItemGroup.jsx
index bb52fe2a..55ea7d19 100644
--- a/src/MenuItemGroup.jsx
+++ b/src/MenuItemGroup.jsx
@@ -1,26 +1,23 @@
import React from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-const MenuItemGroup = createReactClass({
- displayName: 'MenuItemGroup',
-
- propTypes: {
+class MenuItemGroup extends React.Component {
+ static propTypes = {
renderMenuItem: PropTypes.func,
index: PropTypes.number,
className: PropTypes.string,
+ subMenuKey: PropTypes.string,
rootPrefixCls: PropTypes.string,
- },
+ };
- getDefaultProps() {
- // To fix keyboard UX.
- return { disabled: true };
- },
+ static defaultProps = {
+ disabled: true,
+ };
- renderInnerMenuItem(item) {
+ renderInnerMenuItem = (item) => {
const { renderMenuItem, index } = this.props;
return renderMenuItem(item, index, this.props.subMenuKey);
- },
+ }
render() {
const props = this.props;
@@ -40,8 +37,8 @@ const MenuItemGroup = createReactClass({
);
- },
-});
+ }
+}
MenuItemGroup.isMenuItemGroup = true;
diff --git a/src/MenuMixin.js b/src/MenuMixin.js
deleted file mode 100644
index d81ad6e7..00000000
--- a/src/MenuMixin.js
+++ /dev/null
@@ -1,269 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import KeyCode from 'rc-util/lib/KeyCode';
-import createChainedFunction from 'rc-util/lib/createChainedFunction';
-import classNames from 'classnames';
-import { getKeyFromChildrenIndex, loopMenuItem } from './util';
-import DOMWrap from './DOMWrap';
-
-function allDisabled(arr) {
- if (!arr.length) {
- return true;
- }
- return arr.every(c => !!c.props.disabled);
-}
-
-function updateActiveKey(store, menuId, activeKey) {
- const state = store.getState();
- store.setState({
- activeKey: {
- ...state.activeKey,
- [menuId]: activeKey,
- },
- });
-}
-
-export function getActiveKey(props, originalActiveKey) {
- let activeKey = originalActiveKey;
- const { children, eventKey } = props;
- if (activeKey) {
- let found;
- loopMenuItem(children, (c, i) => {
- if (c && !c.props.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) {
- found = true;
- }
- });
- if (found) {
- return activeKey;
- }
- }
- activeKey = null;
- if (props.defaultActiveFirst) {
- loopMenuItem(children, (c, i) => {
- if (!activeKey && c && !c.props.disabled) {
- activeKey = getKeyFromChildrenIndex(c, eventKey, i);
- }
- });
- return activeKey;
- }
- return activeKey;
-}
-
-function saveRef(index, c) {
- if (c) {
- this.instanceArray[index] = c;
- }
-}
-
-const MenuMixin = {
- propTypes: {
- focusable: PropTypes.bool,
- multiple: PropTypes.bool,
- style: PropTypes.object,
- defaultActiveFirst: PropTypes.bool,
- visible: PropTypes.bool,
- activeKey: PropTypes.string,
- selectedKeys: PropTypes.arrayOf(PropTypes.string),
- defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
- defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
- openKeys: PropTypes.arrayOf(PropTypes.string),
- children: PropTypes.any,
- },
-
- getDefaultProps() {
- return {
- prefixCls: 'rc-menu',
- className: '',
- mode: 'vertical',
- level: 1,
- inlineIndent: 24,
- visible: true,
- focusable: true,
- style: {},
- };
- },
-
- componentWillReceiveProps(nextProps) {
- const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey :
- this.getStore().getState().activeKey[this.getEventKey()];
- const activeKey = getActiveKey(nextProps, originalActiveKey);
- if (activeKey !== originalActiveKey) {
- updateActiveKey(this.getStore(), this.getEventKey(), activeKey);
- }
- },
-
- shouldComponentUpdate(nextProps) {
- return this.props.visible || nextProps.visible;
- },
-
- componentWillMount() {
- this.instanceArray = [];
- },
-
- // all keyboard events callbacks run from here at first
- // FIXME: callback is currently used by rc-select, should be more explicit
- onKeyDown(e, callback) {
- const keyCode = e.keyCode;
- let handled;
- this.getFlatInstanceArray().forEach((obj) => {
- if (obj && obj.props.active && obj.onKeyDown) {
- handled = obj.onKeyDown(e);
- }
- });
- if (handled) {
- return 1;
- }
- let activeItem = null;
- if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) {
- activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1);
- }
- if (activeItem) {
- e.preventDefault();
- updateActiveKey(this.getStore(), this.getEventKey(), activeItem.props.eventKey);
-
- if (typeof callback === 'function') {
- callback(activeItem);
- }
-
- return 1;
- }
- },
-
- onItemHover(e) {
- const { key, hover } = e;
- updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null);
- },
-
- getEventKey() {
- // when eventKey not available ,it's menu and return menu id '0-menu-'
- return this.props.eventKey || '0-menu-';
- },
-
- getStore() {
- const store = this.store || this.props.store;
-
- return store;
- },
-
- getFlatInstanceArray() {
- return this.instanceArray;
- },
-
- renderCommonMenuItem(child, i, extraProps) {
- const state = this.getStore().getState();
- const props = this.props;
- const key = getKeyFromChildrenIndex(child, props.eventKey, i);
- const childProps = child.props;
- const isActive = key === state.activeKey;
- const newChildProps = {
- mode: props.mode,
- level: props.level,
- inlineIndent: props.inlineIndent,
- renderMenuItem: this.renderMenuItem,
- rootPrefixCls: props.prefixCls,
- index: i,
- parentMenu: this,
- // customized ref function, need to be invoked manually in child's componentDidMount
- manualRef: childProps.disabled ? undefined :
- createChainedFunction(child.ref, saveRef.bind(this, i)),
- eventKey: key,
- active: !childProps.disabled && isActive,
- multiple: props.multiple,
- onClick: this.onClick,
- onItemHover: this.onItemHover,
- openTransitionName: this.getOpenTransitionName(),
- openAnimation: props.openAnimation,
- subMenuOpenDelay: props.subMenuOpenDelay,
- subMenuCloseDelay: props.subMenuCloseDelay,
- forceSubMenuRender: props.forceSubMenuRender,
- onOpenChange: this.onOpenChange,
- onDeselect: this.onDeselect,
- onSelect: this.onSelect,
- ...extraProps,
- };
- if (props.mode === 'inline') {
- newChildProps.triggerSubMenuAction = 'click';
- }
- return React.cloneElement(child, newChildProps);
- },
-
- renderRoot(props) {
- this.instanceArray = [];
- const className = classNames(
- props.prefixCls,
- props.className,
- `${props.prefixCls}-${props.mode}`,
- );
- const domProps = {
- className,
- role: 'menu',
- 'aria-activedescendant': '',
- };
- if (props.id) {
- domProps.id = props.id;
- }
- if (props.focusable) {
- domProps.tabIndex = '0';
- domProps.onKeyDown = this.onKeyDown;
- }
- return (
- // ESLint is not smart enough to know that the type of `children` was checked.
- /* eslint-disable */
-
- {React.Children.map(
- props.children,
- (c, i) => this.renderMenuItem(c, i, props.eventKey || '0-menu-'),
- )}
-
- /*eslint-enable */
- );
- },
-
- step(direction) {
- let children = this.getFlatInstanceArray();
- const activeKey = this.getStore().getState().activeKey[this.getEventKey()];
- const len = children.length;
- if (!len) {
- return null;
- }
- if (direction < 0) {
- children = children.concat().reverse();
- }
- // find current activeIndex
- let activeIndex = -1;
- children.every((c, ci) => {
- if (c && c.props.eventKey === activeKey) {
- activeIndex = ci;
- return false;
- }
- return true;
- });
- if (!this.props.defaultActiveFirst && activeIndex !== -1) {
- if (allDisabled(children.slice(activeIndex, len - 1))) {
- return undefined;
- }
- }
- const start = (activeIndex + 1) % len;
- let i = start;
- for (; ;) {
- const child = children[i];
- if (!child || child.props.disabled) {
- i = (i + 1 + len) % len;
- // complete a loop
- if (i === start) {
- return null;
- }
- } else {
- return child;
- }
- }
- },
-};
-
-export default MenuMixin;
diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx
index 75cc9faf..9c9c6bae 100644
--- a/src/SubMenu.jsx
+++ b/src/SubMenu.jsx
@@ -1,13 +1,13 @@
import React from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
import Trigger from 'rc-trigger';
import KeyCode from 'rc-util/lib/KeyCode';
import classNames from 'classnames';
import { connect } from 'mini-store';
import SubPopupMenu from './SubPopupMenu';
import placements from './placements';
+import Animate from 'rc-animate';
import {
noop,
loopMenuItemRecursively,
@@ -34,10 +34,8 @@ const updateDefaultActiveFirst = (store, eventKey, defaultActiveFirst) => {
});
};
-const SubMenu = createReactClass({
- displayName: 'SubMenu',
-
- propTypes: {
+export class SubMenu extends React.Component {
+ static propTypes = {
parentMenu: PropTypes.object,
title: PropTypes.node,
children: PropTypes.any,
@@ -60,27 +58,30 @@ const SubMenu = createReactClass({
onTitleMouseLeave: PropTypes.func,
onTitleClick: PropTypes.func,
isOpen: PropTypes.bool,
- },
-
- isRootMenu: false,
-
- getDefaultProps() {
- return {
- onMouseEnter: noop,
- onMouseLeave: noop,
- onTitleMouseEnter: noop,
- onTitleMouseLeave: noop,
- onTitleClick: noop,
- title: '',
- };
- },
-
- getInitialState() {
- this.isSubMenu = 1;
- const props = this.props;
+ store: PropTypes.object,
+ mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
+ manualRef: PropTypes.func,
+ };
+
+ static defaultProps = {
+ onMouseEnter: noop,
+ onMouseLeave: noop,
+ onTitleMouseEnter: noop,
+ onTitleMouseLeave: noop,
+ onTitleClick: noop,
+ manualRef: noop,
+ mode: 'vertical',
+ title: '',
+ };
+
+ constructor(props) {
+ super(props);
const store = props.store;
const eventKey = props.eventKey;
const defaultActiveFirst = store.getState().defaultActiveFirst;
+
+ this.isRootMenu = false;
+
let value = false;
if (defaultActiveFirst) {
@@ -88,27 +89,11 @@ const SubMenu = createReactClass({
}
updateDefaultActiveFirst(store, eventKey, value);
-
- return {};
- },
+ }
componentDidMount() {
this.componentDidUpdate();
- },
-
- adjustWidth() {
- /* istanbul ignore if */
- if (!this.subMenuTitle || !this.menuInstance) {
- return;
- }
- const popupMenu = ReactDOM.findDOMNode(this.menuInstance);
- if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) {
- return;
- }
-
- /* istanbul ignore next */
- popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`;
- },
+ }
componentDidUpdate() {
const { mode, parentMenu, manualRef } = this.props;
@@ -123,7 +108,7 @@ const SubMenu = createReactClass({
}
this.minWidthTimeout = setTimeout(() => this.adjustWidth(), 0);
- },
+ }
componentWillUnmount() {
const { onDestroy, eventKey } = this.props;
@@ -140,13 +125,13 @@ const SubMenu = createReactClass({
if (this.mouseenterTimeout) {
clearTimeout(this.mouseenterTimeout);
}
- },
+ }
- onDestroy(key) {
+ onDestroy = (key) => {
this.props.onDestroy(key);
- },
+ };
- onKeyDown(e) {
+ onKeyDown = (e) => {
const keyCode = e.keyCode;
const menu = this.menuInstance;
const {
@@ -187,26 +172,26 @@ const SubMenu = createReactClass({
if (isOpen && (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN)) {
return menu.onKeyDown(e);
}
- },
+ };
- onOpenChange(e) {
+ onOpenChange = (e) => {
this.props.onOpenChange(e);
- },
+ };
- onPopupVisibleChange(visible) {
+ onPopupVisibleChange = (visible) => {
this.triggerOpenChange(visible, visible ? 'mouseenter' : 'mouseleave');
- },
+ };
- onMouseEnter(e) {
+ onMouseEnter = (e) => {
const { eventKey: key, onMouseEnter, store } = this.props;
updateDefaultActiveFirst(store, this.props.eventKey, false);
onMouseEnter({
key,
domEvent: e,
});
- },
+ };
- onMouseLeave(e) {
+ onMouseLeave = (e) => {
const {
parentMenu,
eventKey,
@@ -217,9 +202,9 @@ const SubMenu = createReactClass({
key: eventKey,
domEvent: e,
});
- },
+ };
- onTitleMouseEnter(domEvent) {
+ onTitleMouseEnter = (domEvent) => {
const { eventKey: key, onItemHover, onTitleMouseEnter } = this.props;
onItemHover({
key,
@@ -229,9 +214,9 @@ const SubMenu = createReactClass({
key,
domEvent,
});
- },
+ };
- onTitleMouseLeave(e) {
+ onTitleMouseLeave = (e) => {
const { parentMenu, eventKey, onItemHover, onTitleMouseLeave } = this.props;
parentMenu.subMenuInstance = this;
onItemHover({
@@ -242,9 +227,9 @@ const SubMenu = createReactClass({
key: eventKey,
domEvent: e,
});
- },
+ };
- onTitleClick(e) {
+ onTitleClick = (e) => {
const { props } = this;
props.onTitleClick({
key: props.eventKey,
@@ -255,53 +240,53 @@ const SubMenu = createReactClass({
}
this.triggerOpenChange(!props.isOpen, 'click');
updateDefaultActiveFirst(props.store, this.props.eventKey, false);
- },
+ };
- onSubMenuClick(info) {
+ onSubMenuClick = (info) => {
this.props.onClick(this.addKeyPath(info));
- },
+ };
- onSelect(info) {
+ onSelect = (info) => {
this.props.onSelect(info);
- },
+ };
- onDeselect(info) {
+ onDeselect = (info) => {
this.props.onDeselect(info);
- },
+ };
- getPrefixCls() {
+ getPrefixCls = () => {
return `${this.props.rootPrefixCls}-submenu`;
- },
+ };
- getActiveClassName() {
+ getActiveClassName = () => {
return `${this.getPrefixCls()}-active`;
- },
+ };
- getDisabledClassName() {
+ getDisabledClassName = () => {
return `${this.getPrefixCls()}-disabled`;
- },
+ };
- getSelectedClassName() {
+ getSelectedClassName = () => {
return `${this.getPrefixCls()}-selected`;
- },
+ };
- getOpenClassName() {
+ getOpenClassName = () => {
return `${this.props.rootPrefixCls}-submenu-open`;
- },
+ };
- saveMenuInstance(c) {
+ saveMenuInstance = (c) => {
// children menu instance
this.menuInstance = c;
- },
+ };
- addKeyPath(info) {
+ addKeyPath = (info) => {
return {
...info,
keyPath: (info.keyPath || []).concat(this.props.eventKey),
};
- },
+ };
- triggerOpenChange(open, type) {
+ triggerOpenChange = (open, type) => {
const key = this.props.eventKey;
const openChange = () => {
this.onOpenChange({
@@ -319,17 +304,35 @@ const SubMenu = createReactClass({
} else {
openChange();
}
- },
+ }
- isChildrenSelected() {
+ isChildrenSelected = () => {
const ret = { find: false };
loopMenuItemRecursively(this.props.children, this.props.selectedKeys, ret);
return ret.find;
- },
+ }
- isOpen() {
+ isOpen = () => {
return this.props.openKeys.indexOf(this.props.eventKey) !== -1;
- },
+ }
+
+ adjustWidth = () => {
+ /* istanbul ignore if */
+ if (!this.subMenuTitle || !this.menuInstance) {
+ return;
+ }
+ const popupMenu = ReactDOM.findDOMNode(this.menuInstance);
+ if (popupMenu.offsetWidth >= this.subMenuTitle.offsetWidth) {
+ return;
+ }
+
+ /* istanbul ignore next */
+ popupMenu.style.minWidth = `${this.subMenuTitle.offsetWidth}px`;
+ };
+
+ saveSubMenuTitle = (subMenuTitle) => {
+ this.subMenuTitle = subMenuTitle;
+ }
renderChildren(children) {
const props = this.props;
@@ -350,6 +353,7 @@ const SubMenu = createReactClass({
openAnimation: props.openAnimation,
onOpenChange: this.onOpenChange,
subMenuOpenDelay: props.subMenuOpenDelay,
+ parentMenu: this,
subMenuCloseDelay: props.subMenuCloseDelay,
forceSubMenuRender: props.forceSubMenuRender,
triggerSubMenuAction: props.triggerSubMenuAction,
@@ -360,12 +364,44 @@ const SubMenu = createReactClass({
id: this._menuId,
manualRef: this.saveMenuInstance,
};
- return {children};
- },
- saveSubMenuTitle(subMenuTitle) {
- this.subMenuTitle = subMenuTitle;
- },
+ const haveRendered = this.haveRendered;
+ this.haveRendered = true;
+
+ this.haveOpened = this.haveOpened || baseProps.visible || baseProps.forceSubMenuRender;
+ // never rendered not planning to, don't render
+ if (!this.haveOpened) {
+ return ;
+ }
+
+ // don't show transition on first rendering (no animation for opened menu)
+ // show appear transition if it's not visible (not sure why)
+ // show appear transition if it's not inline mode
+ const transitionAppear = haveRendered || !baseProps.visible || !baseProps.mode === 'inline';
+
+ baseProps.className += ` ${baseProps.prefixCls}-sub`;
+ const animProps = {};
+
+ if (baseProps.openTransitionName) {
+ animProps.transitionName = baseProps.openTransitionName;
+ } else if (typeof baseProps.openAnimation === 'object') {
+ animProps.animation = { ...baseProps.openAnimation };
+ if (!transitionAppear) {
+ delete animProps.animation.appear;
+ }
+ }
+
+ return (
+
+ {children}
+
+ );
+ }
render() {
const props = this.props;
@@ -458,13 +494,15 @@ const SubMenu = createReactClass({
)}
);
- },
-});
+ }
+}
-SubMenu.isSubMenu = 1;
-
-export default connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
+const connected = connect(({ openKeys, activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({
isOpen: openKeys.indexOf(eventKey) > -1,
active: activeKey[subMenuKey] === eventKey,
selectedKeys,
}))(SubMenu);
+
+connected.isSubMenu = true;
+
+export default connected;
diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js
index 92974136..7bcecb5b 100644
--- a/src/SubPopupMenu.js
+++ b/src/SubPopupMenu.js
@@ -1,14 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';
-import createReactClass from 'create-react-class';
-import Animate from 'rc-animate';
import { connect } from 'mini-store';
-import { default as MenuMixin, getActiveKey } from './MenuMixin';
+import KeyCode from 'rc-util/lib/KeyCode';
+import createChainedFunction from 'rc-util/lib/createChainedFunction';
+import classNames from 'classnames';
+import { getKeyFromChildrenIndex, loopMenuItem, noop } from './util';
+import DOMWrap from './DOMWrap';
-const SubPopupMenu = createReactClass({
- displayName: 'SubPopupMenu',
+function allDisabled(arr) {
+ if (!arr.length) {
+ return true;
+ }
+ return arr.every(c => !!c.props.disabled);
+}
- propTypes: {
+function updateActiveKey(store, menuId, activeKey) {
+ const state = store.getState();
+ store.setState({
+ activeKey: {
+ ...state.activeKey,
+ [menuId]: activeKey,
+ },
+ });
+}
+
+export function getActiveKey(props, originalActiveKey) {
+ let activeKey = originalActiveKey;
+ const { children, eventKey } = props;
+ if (activeKey) {
+ let found;
+ loopMenuItem(children, (c, i) => {
+ if (c && !c.props.disabled && activeKey === getKeyFromChildrenIndex(c, eventKey, i)) {
+ found = true;
+ }
+ });
+ if (found) {
+ return activeKey;
+ }
+ }
+ activeKey = null;
+ if (props.defaultActiveFirst) {
+ loopMenuItem(children, (c, i) => {
+ if (!activeKey && c && !c.props.disabled) {
+ activeKey = getKeyFromChildrenIndex(c, eventKey, i);
+ }
+ });
+ return activeKey;
+ }
+ return activeKey;
+}
+
+function saveRef(index, c) {
+ if (c) {
+ this.instanceArray[index] = c;
+ }
+}
+
+export class SubPopupMenu extends React.Component {
+ static propTypes = {
onSelect: PropTypes.func,
onClick: PropTypes.func,
onDeselect: PropTypes.func,
@@ -19,106 +68,279 @@ const SubPopupMenu = createReactClass({
openKeys: PropTypes.arrayOf(PropTypes.string),
visible: PropTypes.bool,
children: PropTypes.any,
- },
+ parentMenu: PropTypes.object,
+ eventKey: PropTypes.string,
+ store: PropTypes.shape({
+ getState: PropTypes.func,
+ setState: PropTypes.func,
+ }),
- mixins: [MenuMixin],
+ // adding in refactor
+ focusable: PropTypes.bool,
+ multiple: PropTypes.bool,
+ style: PropTypes.object,
+ defaultActiveFirst: PropTypes.bool,
+ activeKey: PropTypes.string,
+ selectedKeys: PropTypes.arrayOf(PropTypes.string),
+ defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
+ defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
+ level: PropTypes.number,
+ mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
+ triggerSubMenuAction: PropTypes.oneOf(['click', 'hover']),
+ inlineIndent: PropTypes.oneOfType(PropTypes.number, PropTypes.string),
+ manualRef: PropTypes.func,
+ };
+
+ static defaultProps = {
+ prefixCls: 'rc-menu',
+ className: '',
+ mode: 'vertical',
+ level: 1,
+ inlineIndent: 24,
+ visible: true,
+ focusable: true,
+ style: {},
+ manualRef: noop,
+ };
+
+ constructor(props) {
+ super(props);
- getInitialState() {
- const props = this.props;
props.store.setState({
activeKey: {
...props.store.getState().activeKey,
[props.eventKey]: getActiveKey(props, props.activeKey),
},
});
+ }
- return {};
- },
+ componentWillMount() {
+ this.instanceArray = [];
+ }
componentDidMount() {
// invoke customized ref to expose component to mixin
- this.props.manualRef(this);
- },
+ if (this.props.manualRef) {
+ this.props.manualRef(this);
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ const originalActiveKey = 'activeKey' in nextProps ? nextProps.activeKey :
+ this.getStore().getState().activeKey[this.getEventKey()];
+ const activeKey = getActiveKey(nextProps, originalActiveKey);
+ if (activeKey !== originalActiveKey) {
+ updateActiveKey(this.getStore(), this.getEventKey(), activeKey);
+ }
+ }
- onDeselect(selectInfo) {
+ shouldComponentUpdate(nextProps) {
+ return this.props.visible || nextProps.visible;
+ }
+
+ // all keyboard events callbacks run from here at first
+ onKeyDown = (e, callback) => {
+ const keyCode = e.keyCode;
+ let handled;
+ this.getFlatInstanceArray().forEach((obj) => {
+ if (obj && obj.props.active && obj.onKeyDown) {
+ handled = obj.onKeyDown(e);
+ }
+ });
+ if (handled) {
+ return 1;
+ }
+ let activeItem = null;
+ if (keyCode === KeyCode.UP || keyCode === KeyCode.DOWN) {
+ activeItem = this.step(keyCode === KeyCode.UP ? -1 : 1);
+ }
+ if (activeItem) {
+ e.preventDefault();
+ updateActiveKey(this.getStore(), this.getEventKey(), activeItem.props.eventKey);
+
+ if (typeof callback === 'function') {
+ callback(activeItem);
+ }
+
+ return 1;
+ }
+ };
+
+ onItemHover = (e) => {
+ const { key, hover } = e;
+ updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null);
+ };
+
+ onDeselect = (selectInfo) => {
this.props.onDeselect(selectInfo);
- },
+ };
- onSelect(selectInfo) {
+ onSelect = (selectInfo) => {
this.props.onSelect(selectInfo);
- },
+ }
- onClick(e) {
+ onClick = (e) => {
this.props.onClick(e);
- },
+ };
- onOpenChange(e) {
+ onOpenChange = (e) => {
this.props.onOpenChange(e);
- },
+ };
- onDestroy(key) {
+ onDestroy = (key) => {
/* istanbul ignore next */
this.props.onDestroy(key);
- },
+ };
+
+ getFlatInstanceArray = () => {
+ return this.instanceArray;
+ };
+
+ getStore = () => {
+ return this.props.store;
+ };
- getOpenTransitionName() {
+ getEventKey = () => {
+ // when eventKey not available ,it's menu and return menu id '0-menu-'
+ return this.props.eventKey || '0-menu-';
+ };
+
+ getOpenTransitionName = () => {
return this.props.openTransitionName;
- },
+ };
- renderMenuItem(c, i, subMenuKey) {
- /* istanbul ignore next */
- if (!c) {
+ step = (direction) => {
+ let children = this.getFlatInstanceArray();
+ const activeKey = this.getStore().getState().activeKey[this.getEventKey()];
+ const len = children.length;
+ if (!len) {
return null;
}
+ if (direction < 0) {
+ children = children.concat().reverse();
+ }
+ // find current activeIndex
+ let activeIndex = -1;
+ children.every((c, ci) => {
+ if (c && c.props.eventKey === activeKey) {
+ activeIndex = ci;
+ return false;
+ }
+ return true;
+ });
+ if (
+ !this.props.defaultActiveFirst && activeIndex !== -1
+ &&
+ allDisabled(children.slice(activeIndex, len - 1))
+ ) {
+ return undefined;
+ }
+ const start = (activeIndex + 1) % len;
+ let i = start;
+
+ do {
+ const child = children[i];
+ if (!child || child.props.disabled) {
+ i = (i + 1) % len;
+ } else {
+ return child;
+ }
+ } while (i !== start);
+
+ return null;
+ };
+
+ renderCommonMenuItem = (child, i, extraProps) => {
+ const state = this.getStore().getState();
const props = this.props;
+ const key = getKeyFromChildrenIndex(child, props.eventKey, i);
+ const childProps = child.props;
+ const isActive = key === state.activeKey;
+ const newChildProps = {
+ mode: props.mode,
+ level: props.level,
+ inlineIndent: props.inlineIndent,
+ renderMenuItem: this.renderMenuItem,
+ rootPrefixCls: props.prefixCls,
+ index: i,
+ parentMenu: props.parentMenu,
+ // customized ref function, need to be invoked manually in child's componentDidMount
+ manualRef: childProps.disabled ? undefined :
+ createChainedFunction(child.ref, saveRef.bind(this, i)),
+ eventKey: key,
+ active: !childProps.disabled && isActive,
+ multiple: props.multiple,
+ onClick: this.onClick,
+ onItemHover: this.onItemHover,
+ openTransitionName: this.getOpenTransitionName(),
+ openAnimation: props.openAnimation,
+ subMenuOpenDelay: props.subMenuOpenDelay,
+ subMenuCloseDelay: props.subMenuCloseDelay,
+ forceSubMenuRender: props.forceSubMenuRender,
+ onOpenChange: this.onOpenChange,
+ onDeselect: this.onDeselect,
+ onSelect: this.onSelect,
+ ...extraProps,
+ };
+ if (props.mode === 'inline') {
+ newChildProps.triggerSubMenuAction = 'click';
+ }
+ return React.cloneElement(child, newChildProps);
+ };
+
+ renderMenuItem = (c, i, subMenuKey) => {
+ /* istanbul ignore if */
+ if (!c) {
+ return null;
+ }
+ const state = this.getStore().getState();
const extraProps = {
- openKeys: props.openKeys,
- selectedKeys: props.selectedKeys,
- triggerSubMenuAction: props.triggerSubMenuAction,
+ openKeys: state.openKeys,
+ selectedKeys: state.selectedKeys,
+ triggerSubMenuAction: this.props.triggerSubMenuAction,
subMenuKey,
};
return this.renderCommonMenuItem(c, i, extraProps);
- },
+ };
render() {
- const props = { ...this.props };
- const haveRendered = this.haveRendered;
- this.haveRendered = true;
-
- this.haveOpened = this.haveOpened || props.visible || props.forceSubMenuRender;
- // never rendered not planning to, don't render
- if (!this.haveOpened) {
- return null;
+ const props = this.props;
+ this.instanceArray = [];
+ const className = classNames(
+ props.prefixCls,
+ props.className,
+ `${props.prefixCls}-${props.mode}`,
+ );
+ const domProps = {
+ className,
+ role: 'menu',
+ 'aria-activedescendant': '',
+ };
+ if (props.id) {
+ domProps.id = props.id;
}
-
- // don't show transition on first rendering (no animation for opened menu)
- // show appear transition if it's not visible (not sure why)
- // show appear transition if it's not inline mode
- const transitionAppear = haveRendered || !props.visible || !props.mode === 'inline';
-
- props.className += ` ${props.prefixCls}-sub`;
- const animProps = {};
-
- if (props.openTransitionName) {
- animProps.transitionName = props.openTransitionName;
- } else if (typeof props.openAnimation === 'object') {
- animProps.animation = { ...props.openAnimation };
- if (!transitionAppear) {
- delete animProps.animation.appear;
- }
+ if (props.focusable) {
+ domProps.tabIndex = '0';
+ domProps.onKeyDown = this.onKeyDown;
}
-
return (
-
- {this.renderRoot(props)}
-
+ {React.Children.map(
+ props.children,
+ (c, i) => this.renderMenuItem(c, i, props.eventKey || '0-menu-'),
+ )}
+
+ /*eslint-enable */
);
- },
-});
+ }
+}
export default connect()(SubPopupMenu);
diff --git a/src/placements.jsx b/src/placements.js
similarity index 100%
rename from src/placements.jsx
rename to src/placements.js
diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js
index 8540b4dc..13a14907 100644
--- a/tests/SubMenu.spec.js
+++ b/tests/SubMenu.spec.js
@@ -256,10 +256,19 @@ describe('SubMenu', () => {
describe('horizontal menu', () => {
it('should automatically adjust width', () => {
- const wrapper = mount(createMenu({
+ const props = {
mode: 'horizontal',
openKeys: ['s1'],
- }));
+ };
+
+ const wrapper = mount(
+
+ );
const subMenuInstance = wrapper.find('SubMenu').first().instance();
const adjustWidthSpy = jest.spyOn(subMenuInstance, 'adjustWidth');
diff --git a/tests/__snapshots__/Menu.spec.js.snap b/tests/__snapshots__/Menu.spec.js.snap
index d5de1dad..241f13d0 100644
--- a/tests/__snapshots__/Menu.spec.js.snap
+++ b/tests/__snapshots__/Menu.spec.js.snap
@@ -188,6 +188,7 @@ exports[`Menu render renders inline menu correctly 1`] = `
class="rc-menu-submenu-arrow"
/>
+
`;