1 | import React from 'react';
|
2 | import ReactDOM from 'react-dom'
|
3 | import classNames from 'classnames';
|
4 | import Animate from 'bee-animate';
|
5 | import {
|
6 | browser,
|
7 | getNodeChildren,
|
8 | toArray,
|
9 | warnOnlyTreeNode
|
10 | } from './util';
|
11 | import PropTypes from 'prop-types';
|
12 | import { KeyCode } from 'tinper-bee-core';
|
13 | const browserUa = typeof window !== 'undefined' ? browser(window.navigator) : '';
|
14 | const ieOrEdge = /.*(IE|Edge).+/.test(browserUa);
|
15 |
|
16 |
|
17 |
|
18 | const defaultTitle = '---';
|
19 |
|
20 | class TreeNode extends React.Component {
|
21 | constructor(props) {
|
22 | super(props);
|
23 | [
|
24 | 'onExpand',
|
25 | 'onCheck',
|
26 | 'onContextMenu',
|
27 | 'onMouseEnter',
|
28 | 'onMouseLeave',
|
29 | 'onDragStart',
|
30 | 'onDragEnter',
|
31 | 'onDragOver',
|
32 | 'onDragLeave',
|
33 | 'onDrop',
|
34 | 'onDragEnd',
|
35 | 'onDoubleClick',
|
36 | 'onKeyDown'
|
37 | ].forEach((m) => {
|
38 | this[m] = this[m].bind(this);
|
39 | });
|
40 | this.state = {
|
41 | dataLoading: false,
|
42 | dragNodeHighlight: false,
|
43 | };
|
44 | }
|
45 |
|
46 | componentDidMount() {
|
47 | if (!this.props.root._treeNodeInstances) {
|
48 | this.props.root._treeNodeInstances = [];
|
49 | }
|
50 | this.props.root._treeNodeInstances.push(this);
|
51 | }
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 | onCheck() {
|
60 |
|
61 | this.props.root.onCheck(this);
|
62 | }
|
63 |
|
64 | onSelect() {
|
65 | clearTimeout(this.doubleClickFlag);
|
66 | let _this = this;
|
67 | if(this.props.onDoubleClick){
|
68 |
|
69 | this.doubleClickFlag = setTimeout(function(){
|
70 |
|
71 | _this.props.root.onSelect(_this);
|
72 | },300);
|
73 | }else{
|
74 | _this.props.root.onSelect(_this);
|
75 | }
|
76 |
|
77 |
|
78 | }
|
79 |
|
80 |
|
81 | onDoubleClick() {
|
82 | clearTimeout(this.doubleClickFlag);
|
83 | this.props.root.onDoubleClick(this);
|
84 | }
|
85 |
|
86 | onMouseEnter(e) {
|
87 | e.preventDefault();
|
88 | this.props.root.onMouseEnter(e, this);
|
89 | }
|
90 |
|
91 | onMouseLeave(e) {
|
92 | e.preventDefault();
|
93 | this.props.root.onMouseLeave(e, this);
|
94 | }
|
95 |
|
96 | onContextMenu(e) {
|
97 | e.preventDefault();
|
98 | this.props.root.onContextMenu(e, this);
|
99 | }
|
100 |
|
101 | onDragStart(e) {
|
102 |
|
103 |
|
104 | e.stopPropagation();
|
105 | this.setState({
|
106 | dragNodeHighlight: true,
|
107 | });
|
108 | this.props.root.onDragStart(e, this);
|
109 | try {
|
110 |
|
111 |
|
112 | e.dataTransfer.setData('text/plain', '');
|
113 | } finally {
|
114 |
|
115 | }
|
116 | }
|
117 |
|
118 | onDragEnter(e) {
|
119 | e.preventDefault();
|
120 | e.stopPropagation();
|
121 | this.props.root.onDragEnter(e, this);
|
122 | }
|
123 |
|
124 | onDragOver(e) {
|
125 |
|
126 | e.preventDefault();
|
127 | e.stopPropagation();
|
128 | this.props.root.onDragOver(e, this);
|
129 | return false;
|
130 | }
|
131 |
|
132 | onDragLeave(e) {
|
133 | e.stopPropagation();
|
134 | this.props.root.onDragLeave(e, this);
|
135 | }
|
136 |
|
137 | onDrop(e) {
|
138 | e.preventDefault();
|
139 | e.stopPropagation();
|
140 | this.setState({
|
141 | dragNodeHighlight: false,
|
142 | });
|
143 | this.props.root.onDrop(e, this);
|
144 | }
|
145 |
|
146 | onDragEnd(e) {
|
147 | e.stopPropagation();
|
148 | this.setState({
|
149 | dragNodeHighlight: false,
|
150 | });
|
151 | this.props.root.onDragEnd(e, this);
|
152 | }
|
153 |
|
154 | onExpand() {
|
155 | const callbackPromise = this.props.root.onExpand(this);
|
156 | if (callbackPromise && typeof callbackPromise === 'object') {
|
157 | const setLoading = (dataLoading) => {
|
158 | this.setState({
|
159 | dataLoading
|
160 | });
|
161 | };
|
162 | setLoading(true);
|
163 | callbackPromise.then(() => {
|
164 | setLoading(false);
|
165 | }, () => {
|
166 | setLoading(false);
|
167 | });
|
168 | }
|
169 | }
|
170 |
|
171 |
|
172 | onKeyDown(e) {
|
173 | this.props.root.onKeyDown(e,this);
|
174 | if(e.keyCode == KeyCode.SPACE || e.keyCode == KeyCode.DOWN || e.keyCode == KeyCode.LEFT || e.keyCode == KeyCode.RIGHT ||e.keyCode == KeyCode.UP){
|
175 | e.preventDefault();
|
176 | }
|
177 |
|
178 | }
|
179 |
|
180 | renderSwitcher(props, expandedState) {
|
181 | let stateIcon;
|
182 | const prefixCls = props.prefixCls;
|
183 | const switcherCls = {
|
184 | [`${prefixCls}-switcher`]: true
|
185 | };
|
186 | if (!props.showLine) {
|
187 | switcherCls[`${prefixCls}-noline_${expandedState}`] = true;
|
188 | } else if (props.pos === '0-0') {
|
189 | switcherCls[`${prefixCls}-roots_${expandedState}`] = true;
|
190 | } else {
|
191 | switcherCls[`${prefixCls}-center_${expandedState}`] = !props.last;
|
192 | switcherCls[`${prefixCls}-bottom_${expandedState}`] = props.last;
|
193 | }
|
194 |
|
195 | if (expandedState === 'open' && props.openIcon) {
|
196 | stateIcon = props.openIcon;
|
197 | switcherCls['icon-none'] = true;
|
198 | }
|
199 | if (expandedState === 'close' && props.closeIcon) {
|
200 | stateIcon = props.closeIcon;
|
201 | switcherCls['icon-none'] = true;
|
202 | }
|
203 |
|
204 | props.switcherClass?switcherCls[`${props.switcherClass}`]=true:'';
|
205 | if (props.disabled && !props.mustExpandable) {
|
206 | switcherCls[`${prefixCls}-switcher-disabled`] = true;
|
207 | return <span className={classNames(switcherCls)} style={props.switcherStyle}>{stateIcon}</span>;
|
208 | }
|
209 | return <span className={classNames(switcherCls)} style={props.switcherStyle} onClick={this.onExpand}>{stateIcon}</span>;
|
210 | }
|
211 |
|
212 | renderCheckbox(props) {
|
213 | const prefixCls = props.prefixCls;
|
214 | const checkboxCls = {
|
215 | [`${prefixCls}-checkbox`]: true,
|
216 | };
|
217 | if (props.checked) {
|
218 | checkboxCls[`${prefixCls}-checkbox-checked`] = true;
|
219 | } else if (props.halfChecked) {
|
220 | checkboxCls[`${prefixCls}-checkbox-indeterminate`] = true;
|
221 | }
|
222 | let customEle = null;
|
223 | if (typeof props.checkable !== 'boolean') {
|
224 | customEle = props.checkable;
|
225 | }
|
226 | if (props.disabled || props.disableCheckbox) {
|
227 | checkboxCls[`${prefixCls}-checkbox-disabled`] = true;
|
228 | return <span className={classNames(checkboxCls)}>{customEle}</span>;
|
229 | }
|
230 | return (
|
231 | <span
|
232 | className={classNames(checkboxCls) }
|
233 | onClick={this.onCheck}
|
234 | >{customEle}</span>);
|
235 | }
|
236 |
|
237 | renderChildren(props) {
|
238 | const renderFirst = this.renderFirst;
|
239 | this.renderFirst = 1;
|
240 | let transitionAppear = true;
|
241 | if (!renderFirst && props.expanded) {
|
242 | transitionAppear = false;
|
243 | }
|
244 | const children = props.children;
|
245 | let newChildren = children;
|
246 |
|
247 | let allTreeNode = false;
|
248 | if (Array.isArray(children)) {
|
249 | for( let index = 0; index < children.length; index++ ){
|
250 | let item = children[index];
|
251 | allTreeNode = item.type.isTreeNode == 1;
|
252 | if( !allTreeNode ){
|
253 |
|
254 | break;
|
255 | }
|
256 | }
|
257 |
|
258 | }else if(children && children.type && children.type.isTreeNode == 1){
|
259 | allTreeNode = true;
|
260 | }
|
261 |
|
262 | if (allTreeNode && React.Children.count(children) ){
|
263 | const cls = {
|
264 | [`${props.prefixCls}-child-tree`]: true,
|
265 | [`${props.prefixCls}-child-tree-open`]: props.expanded,
|
266 | };
|
267 | if (props.showLine) {
|
268 | cls[`${props.prefixCls}-line`] = !props.last;
|
269 | }
|
270 | const animProps = {};
|
271 | if (props.openTransitionName) {
|
272 | animProps.transitionName = props.openTransitionName;
|
273 | } else if (typeof props.openAnimation === 'object') {
|
274 | animProps.animation = Object.assign({}, props.openAnimation);
|
275 | if (!transitionAppear) {
|
276 | delete animProps.animation.appear;
|
277 | }
|
278 | }
|
279 | newChildren = (
|
280 | <Animate {...animProps}
|
281 | showProp="data-expanded"
|
282 | transitionAppear={transitionAppear}
|
283 | component=""
|
284 | >
|
285 | {!props.expanded ? null : <ul className={classNames(cls)} data-expanded={props.expanded}>
|
286 | {React.Children.map(children, (item, index) => {
|
287 | return props.root.renderTreeNode(item, index, props.pos);
|
288 | }, props.root)}
|
289 | </ul>}
|
290 | </Animate>
|
291 | );
|
292 | }
|
293 | return newChildren;
|
294 | }
|
295 |
|
296 | getNodeChildren = () => {
|
297 | const { children } = this.props;
|
298 | const originList = toArray(children).filter(node => node);
|
299 | const targetList = getNodeChildren(originList);
|
300 |
|
301 | if (originList.length !== targetList.length) {
|
302 | warnOnlyTreeNode();
|
303 | }
|
304 |
|
305 | return targetList;
|
306 | };
|
307 | |
308 |
|
309 |
|
310 |
|
311 |
|
312 |
|
313 | checkIsLeaf(){
|
314 | const { isLeaf, loadData } = this.props;
|
315 | let rs = isLeaf
|
316 | if (rs === false || rs === true) {
|
317 | return rs;
|
318 | }else{
|
319 | const hasChildren = this.getNodeChildren().length !== 0;
|
320 | return (!loadData && !hasChildren);
|
321 | }
|
322 |
|
323 |
|
324 | }
|
325 |
|
326 | render() {
|
327 | const props = this.props;
|
328 | const prefixCls = props.prefixCls;
|
329 | const expandedState = props.expanded ? 'open' : 'close';
|
330 | let iconState = expandedState;
|
331 |
|
332 | let canRenderSwitcher = true;
|
333 | const content = props.title;
|
334 | let newChildren = this.renderChildren(props);
|
335 | let openIconCls = false,
|
336 | closeIconCls = false;
|
337 |
|
338 |
|
339 | let timer = 0;
|
340 | let delay = 500;
|
341 | let prevent = false;
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 | if(this.checkIsLeaf()){
|
352 | canRenderSwitcher = false;
|
353 | iconState = 'docu';
|
354 | }
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | const iconEleCls = {
|
361 | [`${prefixCls}-iconEle`]: true,
|
362 | [`${prefixCls}-icon_loading`]: this.state.dataLoading,
|
363 | [`${prefixCls}-icon__${iconState}`]: true
|
364 | };
|
365 | const selectHandle = () => {
|
366 | const titleClass=props.titleClass?prefixCls+'-title'+' '+props.className:prefixCls+'-title';
|
367 |
|
368 |
|
369 | let icon;
|
370 | if(props.showIcon && props.icon){
|
371 | icon = <span
|
372 | className={classNames(
|
373 | `${prefixCls}-iconEle`,
|
374 | `${prefixCls}-icon__customize`,
|
375 | )}
|
376 | >
|
377 | {typeof currentIcon === 'function' ?
|
378 | React.createElement(props.icon, {
|
379 | ...this.props,
|
380 | }) : props.icon}
|
381 | </span>
|
382 | }else if(props.showIcon || props.loadData && this.state.dataLoading){
|
383 | icon = <span className={classNames(iconEleCls)}></span>
|
384 | }
|
385 | const title = <span className={titleClass} style={props.titleStyle} >{content}</span>;
|
386 | const wrap = `${prefixCls}-node-content-wrapper`;
|
387 | const domProps = {
|
388 | className: `${wrap} ${wrap}-${iconState === expandedState ? iconState : 'normal'}`,
|
389 | };
|
390 | if (!props.disabled) {
|
391 | if (props.selected || !props._dropTrigger && this.state.dragNodeHighlight) {
|
392 | domProps.className += ` ${prefixCls}-node-selected`;
|
393 | }
|
394 | domProps.onClick = (e) => {
|
395 | var _this = this;
|
396 | e.preventDefault();
|
397 | if (props.selectable) {
|
398 | _this.onSelect();
|
399 | }
|
400 |
|
401 |
|
402 |
|
403 |
|
404 |
|
405 | };
|
406 |
|
407 | if(props.onDoubleClick){
|
408 | domProps.onDoubleClick = this.onDoubleClick;
|
409 | }
|
410 |
|
411 | if (props.onRightClick) {
|
412 | domProps.onContextMenu = this.onContextMenu;
|
413 | }
|
414 | if (props.onMouseEnter) {
|
415 | domProps.onMouseEnter = this.onMouseEnter;
|
416 | }
|
417 | if (props.onMouseLeave) {
|
418 | domProps.onMouseLeave = this.onMouseLeave;
|
419 | }
|
420 |
|
421 |
|
422 | if (props.draggable) {
|
423 | domProps.className += ' draggable';
|
424 | if (ieOrEdge) {
|
425 |
|
426 | domProps.href = '#';
|
427 | }
|
428 | domProps.draggable = true;
|
429 | domProps['aria-grabbed'] = true;
|
430 | domProps.onDragStart = this.onDragStart;
|
431 | }
|
432 | }
|
433 |
|
434 | if(props.focusable){
|
435 | domProps.onKeyDown = this.onKeyDown;
|
436 | domProps.tabIndex = -1;
|
437 | if(props.tabIndexKey){
|
438 | if(props.eventKey == props.tabIndexKey){
|
439 | domProps.tabIndex = props.tabIndexValue;
|
440 | }
|
441 | }else if(props.pos == '0-0'){
|
442 | domProps.tabIndex = props.tabIndexValue;
|
443 | }
|
444 | }
|
445 |
|
446 |
|
447 |
|
448 | return (
|
449 | <a ref={(el)=>{this.selectHandle=el}} pos={props.pos} title={typeof content === 'string' ? content : ''} {...domProps}>
|
450 | {icon}{title}
|
451 | </a>
|
452 | );
|
453 | };
|
454 |
|
455 | let liProps = {};
|
456 | if(props.liAttr){
|
457 | liProps = Object.assign({},props.liAttr);
|
458 | }
|
459 | if (props.draggable) {
|
460 | liProps.onDragEnter = this.onDragEnter;
|
461 | liProps.onDragOver = this.onDragOver;
|
462 | liProps.onDragLeave = this.onDragLeave;
|
463 | liProps.onDrop = this.onDrop;
|
464 | liProps.onDragEnd = this.onDragEnd;
|
465 | }
|
466 | let disabledCls = '';
|
467 | let dragOverCls = '';
|
468 | if (props.disabled) {
|
469 | disabledCls = `${prefixCls}-treenode-disabled`;
|
470 | } else if (props.dragOver) {
|
471 | dragOverCls = 'drag-over';
|
472 | } else if (props.dragOverGapTop) {
|
473 | dragOverCls = 'drag-over-gap-top';
|
474 | } else if (props.dragOverGapBottom) {
|
475 | dragOverCls = 'drag-over-gap-bottom';
|
476 | }
|
477 |
|
478 | const filterCls = props.filterTreeNode ? (props.filterTreeNode(this) ? 'filter-node' : '') : '';
|
479 |
|
480 | const noopSwitcher = () => {
|
481 | const cls = {
|
482 | [`${prefixCls}-switcher`]: true,
|
483 | [`${prefixCls}-switcher-noop`]: true,
|
484 | };
|
485 | if (props.showLine) {
|
486 |
|
487 | cls[`${prefixCls}-center_docu`] = !props.last;
|
488 | cls[`${prefixCls}-bottom_docu`] = props.last;
|
489 | } else {
|
490 | cls[`${prefixCls}-noline_docu`] = true;
|
491 | }
|
492 | return <span className={classNames(cls)}></span>;
|
493 | };
|
494 | const selectedCls = props.selected?`${prefixCls}-treenode-selected`:'';
|
495 | const focusedCls = props.focused ? `${prefixCls}-treenode-focused` : '';
|
496 | const expandedCls = `${prefixCls}-treenode-${expandedState}`;
|
497 | return (
|
498 | <li {...liProps} style={props.style}
|
499 | className={classNames(props.className, disabledCls, dragOverCls, filterCls,selectedCls,focusedCls,expandedCls) }
|
500 | >
|
501 | {canRenderSwitcher ? this.renderSwitcher(props, expandedState) : noopSwitcher()}
|
502 | {props.checkable ? this.renderCheckbox(props) : null}
|
503 | {selectHandle()}
|
504 | {newChildren}
|
505 | </li>
|
506 | );
|
507 | }
|
508 | }
|
509 |
|
510 | TreeNode.isTreeNode = 1;
|
511 |
|
512 | TreeNode.propTypes = {
|
513 | prefixCls: PropTypes.string,
|
514 | disabled: PropTypes.bool,
|
515 | disableCheckbox: PropTypes.bool,
|
516 | expanded: PropTypes.bool,
|
517 | isLeaf: PropTypes.bool,
|
518 | root: PropTypes.object,
|
519 | onSelect: PropTypes.func,
|
520 | openIcon: PropTypes.element,
|
521 | closeIcon: PropTypes.element,
|
522 | style: PropTypes.object,
|
523 | className: PropTypes.string,
|
524 | titleClass:PropTypes.string,
|
525 | titleStyle:PropTypes.object,
|
526 | switcherClass:PropTypes.string,
|
527 | switcherStyle:PropTypes.object
|
528 | };
|
529 |
|
530 | TreeNode.defaultProps = {
|
531 | title: defaultTitle,
|
532 | tabIndexValue:0,
|
533 | mustExpandable:false
|
534 | };
|
535 |
|
536 | export default TreeNode; |
\ | No newline at end of file |