UNPKG

6.67 kBJavaScriptView Raw
1import React from 'react';
2import ReactDOM from 'react-dom';
3import PropTypes from 'prop-types';
4import { Provider, create } from 'mini-store';
5import { default as SubPopupMenu, getActiveKey } from './SubPopupMenu';
6import { noop,fireKeyEvent } from './util';
7
8class Menu extends React.Component {
9 static propTypes = {
10 defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
11 defaultActiveFirst: PropTypes.bool,
12 selectedKeys: PropTypes.arrayOf(PropTypes.string),
13 defaultOpenKeys: PropTypes.arrayOf(PropTypes.string),
14 openKeys: PropTypes.arrayOf(PropTypes.string),
15 mode: PropTypes.oneOf(['horizontal', 'vertical', 'vertical-left', 'vertical-right', 'inline']),
16 getPopupContainer: PropTypes.func,
17 onClick: PropTypes.func,
18 onSelect: PropTypes.func,
19 onDeselect: PropTypes.func,
20 onDestroy: PropTypes.func,
21 openTransitionName: PropTypes.string,
22 openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
23 subMenuOpenDelay: PropTypes.number,
24 subMenuCloseDelay: PropTypes.number,
25 forceSubMenuRender: PropTypes.bool,
26 triggerSubMenuAction: PropTypes.string,
27 level: PropTypes.number,
28 selectable: PropTypes.bool,
29 multiple: PropTypes.bool,
30 children: PropTypes.any,
31 className: PropTypes.string,
32 style: PropTypes.object,
33 activeKey: PropTypes.string,
34 prefixCls: PropTypes.string,
35 builtinPlacements: PropTypes.object,
36 itemIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
37 expandIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
38 overflowedIndicator: PropTypes.node,
39 keyboard: PropTypes.bool,
40 };
41
42 static defaultProps = {
43 selectable: true,
44 onClick: noop,
45 onSelect: noop,
46 onOpenChange: noop,
47 onDeselect: noop,
48 defaultSelectedKeys: [],
49 defaultOpenKeys: [],
50 subMenuOpenDelay: 0.1,
51 subMenuCloseDelay: 0.1,
52 triggerSubMenuAction: 'hover',
53 prefixCls: 'rc-menu',
54 className: '',
55 mode: 'vertical',
56 style: {},
57 builtinPlacements: {},
58 overflowedIndicator: <span>···</span>,
59 keyboard:false
60 };
61
62 constructor(props) {
63 super(props);
64
65 this.isRootMenu = true;
66
67 let selectedKeys = props.defaultSelectedKeys;
68 let openKeys = props.defaultOpenKeys;
69 if ('selectedKeys' in props) {
70 selectedKeys = props.selectedKeys || [];
71 }
72 if ('openKeys' in props) {
73 openKeys = props.openKeys || [];
74 }
75
76 this.store = create({
77 selectedKeys,
78 openKeys,
79 activeKey: { '0-menu-': getActiveKey(props, props.activeKey) },
80 });
81 }
82
83 componentDidMount() {
84 this.updateMiniStore();
85 }
86
87 componentDidUpdate() {
88 this.updateMiniStore();
89 }
90
91 onSelect = (selectInfo) => {
92 const props = this.props;
93 if (props.selectable) {
94 // root menu
95 let selectedKeys = this.store.getState().selectedKeys;
96 const selectedKey = selectInfo.key;
97 if (props.multiple) {
98 selectedKeys = selectedKeys.concat([selectedKey]);
99 } else {
100 selectedKeys = [selectedKey];
101 }
102 if (!('selectedKeys' in props)) {
103 this.store.setState({
104 selectedKeys,
105 });
106 }
107 props.onSelect({
108 ...selectInfo,
109 selectedKeys,
110 });
111 }
112 }
113
114 onClick = (e) => {
115 this.props.onClick(e);
116 }
117
118 // onKeyDown needs to be exposed as a instance method
119 // e.g., in rc-select, we need to navigate menu item while
120 // current active item is rc-select input box rather than the menu itself
121 onKeyDown = (e, callback) => {
122 this.innerMenu.getWrappedInstance().onKeyDown(e, callback);
123 }
124
125 onOpenChange = (event) => {
126 const props = this.props;
127 const openKeys = this.store.getState().openKeys.concat();
128 let changed = false;
129 const processSingle = (e) => {
130 let oneChanged = false;
131 if (e.open) {
132 oneChanged = openKeys.indexOf(e.key) === -1;
133 if (oneChanged) {
134 openKeys.push(e.key);
135 }
136 } else {
137 const index = openKeys.indexOf(e.key);
138 oneChanged = index !== -1;
139 if (oneChanged) {
140 openKeys.splice(index, 1);
141 }
142 }
143 changed = changed || oneChanged;
144 };
145 if (Array.isArray(event)) {
146 // batch change call
147 event.forEach(processSingle);
148 } else {
149 processSingle(event);
150 }
151 if (changed) {
152 if (!('openKeys' in this.props)) {
153 this.store.setState({ openKeys });
154 }
155 props.onOpenChange(openKeys);
156 }
157 }
158
159 onDeselect = (selectInfo) => {
160 const props = this.props;
161 if (props.selectable) {
162 const selectedKeys = this.store.getState().selectedKeys.concat();
163 const selectedKey = selectInfo.key;
164 const index = selectedKeys.indexOf(selectedKey);
165 if (index !== -1) {
166 selectedKeys.splice(index, 1);
167 }
168 if (!('selectedKeys' in props)) {
169 this.store.setState({
170 selectedKeys,
171 });
172 }
173 props.onDeselect({
174 ...selectInfo,
175 selectedKeys,
176 });
177 }
178 }
179
180 getOpenTransitionName = () => {
181 const props = this.props;
182 let transitionName = props.openTransitionName;
183 const animationName = props.openAnimation;
184 if (!transitionName && typeof animationName === 'string') {
185 transitionName = `${props.prefixCls}-open-${animationName}`;
186 }
187 return transitionName;
188 }
189
190 updateMiniStore() {
191 if ('selectedKeys' in this.props) {
192 this.store.setState({
193 selectedKeys: this.props.selectedKeys || [],
194 keyboard:this.props.keyboard||false
195 });
196 }
197 if ('openKeys' in this.props) {
198 this.store.setState({
199 openKeys: this.props.openKeys || [],
200 keyboard:this.props.keyboard||false
201 });
202 }
203 }
204 focus=()=>{
205 fireKeyEvent(ReactDOM.findDOMNode(this.innerMenu),'keydown',40);
206 this.props.onFocus&&this.props.onFocus();
207 }
208
209 render() {
210 let { ...props } = this.props;
211 props.className += ` ${props.prefixCls}-root`;
212 props = {
213 ...props,
214 onClick: this.onClick,
215 onOpenChange: this.onOpenChange,
216 onDeselect: this.onDeselect,
217 onSelect: this.onSelect,
218 openTransitionName: this.getOpenTransitionName(),
219 parentMenu: this,
220 };
221 return (
222 <Provider store={this.store}>
223 <SubPopupMenu {...props} tabIndex='0' onFocus={this.focus} ref={c => this.innerMenu = c}>{this.props.children}</SubPopupMenu>
224 </Provider>
225 );
226 }
227}
228
229export default Menu;