UNPKG

41.6 kBJavaScriptView Raw
1/* eslint no-console:0 */
2import React from 'react';
3import TreeNode from './TreeNode';
4import InfiniteScroll from './infiniteScroll';
5import classNames from 'classnames';
6import {
7 loopAllChildren,
8 isInclude,
9 getOffset,
10 filterParentPosition,
11 handleCheckState,
12 getCheck,
13 getStrictlyValue,
14 arraysEqual,
15 closest,
16 convertListToTree,
17} from './util';
18import PropTypes from 'prop-types';
19import { KeyCode } from 'tinper-bee-core';
20import CONFIG from './config';
21import createStore from './createStore';
22import omit from 'omit.js';
23
24function noop() {}
25
26class Tree extends React.Component {
27 constructor(props) {
28 super(props);
29 ['onKeyDown', 'onCheck',"onUlFocus","_focusDom","onUlMouseEnter","onUlMouseLeave"].forEach((m) => {
30 this[m] = this[m].bind(this);
31 });
32 this.contextmenuKeys = [];
33 this.checkedKeysChange = true;
34 this.selectKeyDomPos = '0-0';
35 this.state = {
36 expandedKeys: this.getDefaultExpandedKeys(props),
37 checkedKeys: this.getDefaultCheckedKeys(props),
38 selectedKeys: this.getDefaultSelectedKeys(props),
39 dragNodesKeys: '',
40 dragOverNodeKey: '',
41 dropNodeKey: '',
42 focusKey: '', //上下箭头选择树节点时,用于标识focus状态
43 treeData: [], //Tree结构数组(全量)
44 flatTreeData: [], //一维数组(全量)
45 prevProps: null
46 };
47 //默认显示20条,rowsInView根据定高算的。在非固定高下,这个只是一个大概的值。
48 this.rowsInView = CONFIG.defaultRowsInView;
49 //一次加载多少数据
50 this.loadCount = CONFIG.loadBuffer ? this.rowsInView + CONFIG.loadBuffer * 2 : 16;
51 this.flatTreeKeysMap = {}; //存储所有 key-value 的映射,方便获取各节点信息
52 this.startIndex = 0;
53 this.endIndex = this.startIndex + this.loadCount;
54 this.cacheTreeNodes = []; //缓存 treenode 节点数组
55 this.store = createStore({ rowHeight: 24 }); //rowHeight 树节点的高度,此变量在滚动加载场景很关键
56 }
57
58 /**
59 * 在 lazyload 情况下,需要获取树节点的真实高度
60 */
61 componentDidMount() {
62 const { lazyLoad } = this.props;
63 // 此处为了区分数据是不是异步渲染的,prevProps 作为标识
64 if(this.hasTreeNode()){
65 this.setState({
66 prevProps: this.props
67 })
68 }
69 // ncc制造,树参照包含下级需求,checkStrictly 动态改变后,拿到组件内部属性 this.checkedKeys
70 if(this.props._getTreeObj){
71 this.props._getTreeObj(this);
72 }
73 // 启用懒加载,计算树节点真实高度
74 if(!lazyLoad) return;
75 const treenodes = this.tree.querySelectorAll('.u-tree-treenode-close')[0];
76 if(!treenodes) return;
77 let rowHeight = treenodes.getBoundingClientRect().height;
78 this.store.setState({
79 rowHeight: rowHeight
80 });
81 }
82
83 // 判断初始化挂载时,有没有渲染树节点
84 hasTreeNode = () => {
85 const { children, treeData } = this.props;
86 let noTreeNode = children === null || typeof children === 'undefined' || (typeof children === 'object' && children.length === 0) || (typeof treeData === 'object' && treeData.length === 0);
87 return !noTreeNode;
88 }
89
90 componentWillMount() {
91 const { treeData,lazyLoad } = this.props;
92 let sliceTreeList = [];
93 //启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
94 if(lazyLoad) {
95 let flatTreeData = this.deepTraversal(treeData);
96 flatTreeData.forEach((element) => {
97 if(sliceTreeList.length >= this.loadCount) return;
98 sliceTreeList.push(element);
99 });
100 this.handleTreeListChange(sliceTreeList);
101 this.setState({
102 flatTreeData
103 })
104 } else {
105 this.setState({
106 treeData
107 })
108 }
109 }
110
111 componentWillReceiveProps(nextProps) {
112 const {startIndex,endIndex,props,state} = this;
113 const {prevProps} = state;
114 const expandedKeys = this.getDefaultExpandedKeys(nextProps, true);
115 const checkedKeys = this.getDefaultCheckedKeys(nextProps, true);
116 const selectedKeys = this.getDefaultSelectedKeys(nextProps, true);
117 const st = {
118 prevProps:nextProps
119 };
120 // 用于记录这次data内容有没有变化
121 this.dataChange = false;
122 function needSync(name) {
123 return (!prevProps && name in nextProps) || (prevProps && prevProps[name] !== nextProps[name]);
124 }
125 // ================ expandedKeys =================
126 if (needSync('expandedKeys') || (prevProps && needSync('autoExpandParent')) || (prevProps && prevProps['expandedKeys'] !== expandedKeys)) {
127 st.expandedKeys = expandedKeys;
128 } else if ((!prevProps && props.defaultExpandAll) || (!prevProps && props.defaultExpandedKeys)) {
129 st.expandedKeys = this.getDefaultExpandedKeys(nextProps);
130 }
131 if(st.expandedKeys){
132 //缓存 expandedKeys
133 this.cacheExpandedKeys = new Set(expandedKeys);
134 if(nextProps.lazyLoad) {
135 let flatTreeData = this.deepTraversal(nextProps.treeData);
136 this.cacheExpandedKeys = null;
137 st.flatTreeData = flatTreeData;
138 let newTreeList = flatTreeData.slice(startIndex,endIndex);
139 this.handleTreeListChange(newTreeList, startIndex, endIndex);
140 }
141 }
142
143 // ================ checkedKeys =================
144 if (checkedKeys) {
145 if (nextProps.checkedKeys === this.props.checkedKeys) {
146 this.checkedKeysChange = false;
147 } else {
148 this.checkedKeysChange = true;
149 }
150 st.checkedKeys = checkedKeys;
151 }
152
153 // ================ selectedKeys =================
154 if (selectedKeys) {
155 st.selectedKeys = selectedKeys;
156 }
157
158 // ================ treeData =================
159 if(nextProps.hasOwnProperty('treeData') && nextProps.treeData !== this.props.treeData){
160 this.dataChange = true;
161 //treeData更新时,需要重新处理一次数据
162 if(nextProps.lazyLoad) {
163 let flatTreeData = this.deepTraversal(nextProps.treeData);
164 st.flatTreeData = flatTreeData;
165 let newTreeList = flatTreeData.slice(startIndex,endIndex);
166 this.handleTreeListChange(newTreeList, startIndex, endIndex);
167 } else {
168 st.treeData = nextProps.treeData;
169 }
170 }
171
172 // ================ children =================
173 if(nextProps.children !== this.props.children){
174 this.dataChange = true;
175 }
176 this.setState(st);
177 }
178
179 onDragStart(e, treeNode) {
180 this.dragNode = treeNode;
181 this.dragNodesKeys = this.getDragNodes(treeNode);
182 const st = {
183 dragNodesKeys: this.dragNodesKeys,
184 };
185 const expandedKeys = this.getExpandedKeys(treeNode, false);
186 if (expandedKeys) {
187 // Controlled expand, save and then reset
188 this.getRawExpandedKeys();
189 st.expandedKeys = expandedKeys;
190 }
191 this.setState(st);
192 this.props.onDragStart({
193 event: e,
194 node: treeNode,
195 });
196 this._dropTrigger = false;
197 }
198
199 onDragEnterGap(e, treeNode) {
200 const offsetTop = (0, getOffset)(treeNode.selectHandle).top;
201 const offsetHeight = treeNode.selectHandle.offsetHeight;
202 const pageY = e.pageY;
203 const gapHeight = 2;
204 if (pageY > offsetTop + offsetHeight - gapHeight) {
205 this.dropPosition = 1;
206 return 1;
207 }
208 if (pageY < offsetTop + gapHeight) {
209 this.dropPosition = -1;
210 return -1;
211 }
212 this.dropPosition = 0;
213 return 0;
214 }
215
216 onDragEnter(e, treeNode) {
217 const enterGap = this.onDragEnterGap(e, treeNode);
218 if (this.dragNode.props.eventKey === treeNode.props.eventKey && enterGap === 0) {
219 this.setState({
220 dragOverNodeKey: '',
221 });
222 return;
223 }
224 const st = {
225 dragOverNodeKey: treeNode.props.eventKey,
226 };
227 const expandedKeys = this.getExpandedKeys(treeNode, true);
228 if (expandedKeys) {
229 this.getRawExpandedKeys();
230 st.expandedKeys = expandedKeys;
231 }
232 this.setState(st);
233 this.props.onDragEnter({
234 event: e,
235 node: treeNode,
236 expandedKeys: expandedKeys && [...expandedKeys] || [...this.state.expandedKeys],
237 });
238 }
239
240 onDragOver(e, treeNode) {
241 this.props.onDragOver({
242 event: e,
243 node: treeNode
244 });
245 }
246
247 onDragLeave(e, treeNode) {
248 this.props.onDragLeave({
249 event: e,
250 node: treeNode
251 });
252 }
253
254 onDrop(e, treeNode) {
255 const key = treeNode.props.eventKey;
256 this.setState({
257 dragOverNodeKey: '',
258 dropNodeKey: key,
259 });
260 if (this.dragNodesKeys.indexOf(key) > -1) {
261 if (console.warn) {
262 console.warn('can not drop to dragNode(include it\'s children node)');
263 }
264 return false;
265 }
266
267 const posArr = treeNode.props.pos.split('-');
268 const res = {
269 event: e,
270 node: treeNode,
271 dragNode: this.dragNode,
272 dragNodesKeys: [...this.dragNodesKeys],
273 dropPosition: this.dropPosition + Number(posArr[posArr.length - 1]),
274 };
275 if (this.dropPosition !== 0) {
276 res.dropToGap = true;
277 }
278 if ('expandedKeys' in this.props) {
279 res.rawExpandedKeys = [...this._rawExpandedKeys] || [...this.state.expandedKeys];
280 }
281 this.props.onDrop(res);
282 this._dropTrigger = true;
283 }
284
285 onDragEnd(e, treeNode) {
286 this.setState({
287 dragOverNodeKey: '',
288 });
289 this.props.onDragEnd({
290 event: e,
291 node: treeNode
292 });
293 }
294/**
295 *
296 *
297 * @param {*} treeNode 当前操作的节点
298 * @param {*} keyType 键盘事件通用的key类型 left 为收起,right为展开
299 * @returns
300 * @memberof Tree
301 */
302onExpand(treeNode,keyType) {
303 const { treeData,lazyLoad } = this.props;
304 let expanded = !treeNode.props.expanded;
305 const controlled = 'expandedKeys' in this.props;
306 const expandedKeys = [...this.state.expandedKeys];
307 const index = expandedKeys.indexOf(treeNode.props.eventKey);
308
309 if(keyType == 'left'){
310 expanded = false;
311 }else if(keyType == 'right'){
312 expanded = true;
313 }
314
315 if (expanded && index === -1) {
316 expandedKeys.push(treeNode.props.eventKey);
317 } else if (!expanded && index > -1) {
318 expandedKeys.splice(index, 1);
319 }
320 if (!controlled) {
321 this.setState({
322 expandedKeys
323 });
324 }
325 this.props.onExpand(expandedKeys, {
326 node: treeNode,
327 expanded
328 });
329
330 // after data loaded, need set new expandedKeys
331 if (expanded && this.props.loadData) {
332 return this.props.loadData(treeNode).then(() => {
333 if (!controlled) {
334 this.setState({
335 expandedKeys
336 });
337 }
338 });
339 }
340 //收起和展开时,缓存 expandedKeys
341 this.cacheExpandedKeys = new Set(expandedKeys);
342 //启用懒加载,把 Tree 结构拍平,为后续动态截取数据做准备
343 if(lazyLoad) {
344 let flatTreeData = this.deepTraversal(treeData);
345 this.cacheExpandedKeys = null;
346 this.setState({
347 flatTreeData
348 })
349 }
350 }
351
352 onCheck(treeNode) {
353 let checked = !treeNode.props.checked;
354 if (treeNode.props.halfChecked) {
355 checked = true;
356 }
357 const key = treeNode.props.eventKey;
358 let checkedKeys = [...this.state.checkedKeys];
359 const index = checkedKeys.indexOf(key);
360
361 const newSt = {
362 event: 'check',
363 node: treeNode,
364 checked,
365 };
366
367
368 if (this.props.checkStrictly) {
369 let rsCheckedKeys = [];
370 if (checked && index === -1) {
371 checkedKeys.push(key);
372 rsCheckedKeys.push(key);//onCheck第一个参数的key不对
373 }
374 if (!checked && index > -1) {
375 checkedKeys.splice(index, 1);
376 }
377 this.treeNodesStates[treeNode.props.pos].checked = checked;
378 newSt.checkedNodes = [];
379 loopAllChildren(this.props.children, (item, ind, pos, keyOrPos) => {
380 if (checkedKeys.indexOf(keyOrPos) !== -1) {
381 newSt.checkedNodes.push(item);
382 rsCheckedKeys.push(keyOrPos);
383 }
384 });
385 if (!('checkedKeys' in this.props)) {
386 this.setState({
387 checkedKeys:rsCheckedKeys
388 });
389 }
390 const halfChecked = this.props.checkedKeys ? this.props.checkedKeys.halfChecked : [];
391 this.props.onCheck(getStrictlyValue(rsCheckedKeys, halfChecked), newSt);
392 } else {
393 if (checked && index === -1) {
394 this.treeNodesStates[treeNode.props.pos].checked = true;
395 const checkedPositions = [];
396 Object.keys(this.treeNodesStates).forEach(i => {
397 if (this.treeNodesStates[i].checked) {
398 checkedPositions.push(i);
399 }
400 });
401 handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true);
402 }
403 if (!checked) {
404 this.treeNodesStates[treeNode.props.pos].checked = false;
405 this.treeNodesStates[treeNode.props.pos].halfChecked = false;
406 handleCheckState(this.treeNodesStates, [treeNode.props.pos], false);
407 }
408 const checkKeys = getCheck(this.treeNodesStates);
409 newSt.checkedNodes = checkKeys.checkedNodes;
410 newSt.checkedNodesPositions = checkKeys.checkedNodesPositions;
411 newSt.halfCheckedKeys = checkKeys.halfCheckedKeys;
412 this.checkKeys = checkKeys;
413
414 this._checkedKeys = checkedKeys = checkKeys.checkedKeys;
415 if (!('checkedKeys' in this.props)) {
416 this.setState({
417 checkedKeys,
418 });
419 }
420 this.props.onCheck(checkedKeys, newSt);
421 }
422 }
423
424 onSelect(treeNode) {
425 const props = this.props;
426 const selectedKeys = [...this.state.selectedKeys];
427 const eventKey = treeNode.props.eventKey || treeNode.key;
428 const index = selectedKeys.indexOf(eventKey);
429 let selected;
430 //cancelUnSelect为true时第二次点击时不取消选中
431 if(props.cancelUnSelect){
432 if (index == -1) {
433 selected = true;
434 if (!props.multiple) {
435 selectedKeys.length = 0;
436 }
437 selectedKeys.push(eventKey);
438 }
439 }else{
440 if (index !== -1) {
441 selected = false;
442 selectedKeys.splice(index, 1);
443 } else {
444 selected = true;
445 if (!props.multiple) {
446 selectedKeys.length = 0;
447 }
448 selectedKeys.push(eventKey);
449 }
450 }
451
452 const selectedNodes = [];
453 if (selectedKeys.length) {
454 loopAllChildren(this.props.children, (item) => {
455 if (selectedKeys.indexOf(item.key) !== -1) {
456 selectedNodes.push(item);
457 }
458 });
459 }
460 const newSt = {
461 event: 'select',
462 node: treeNode,
463 selected,
464 selectedNodes,
465 };
466 if (!('selectedKeys' in this.props)) {
467 this.setState({
468 selectedKeys,
469 });
470 }
471 props.onSelect(selectedKeys, newSt);
472 }
473
474
475 onDoubleClick(treeNode){
476 const props = this.props;
477 const eventKey = treeNode.props.eventKey;
478 const newSt = {
479 event: 'dblclick',
480 node: treeNode
481 };
482 if(props.expandWhenDoubleClick) {
483 this.onExpand(treeNode);
484 }
485 props.onDoubleClick(eventKey,newSt);
486 }
487
488 onMouseEnter(e, treeNode) {
489 this.props.onMouseEnter({
490 event: e,
491 node: treeNode
492 });
493 }
494
495 onMouseLeave(e, treeNode) {
496 this.props.onMouseLeave({
497 event: e,
498 node: treeNode
499 });
500 }
501
502
503
504 onContextMenu(e, treeNode) {
505 const selectedKeys = [...this.state.selectedKeys];
506 const eventKey = treeNode.props.eventKey;
507 if (this.contextmenuKeys.indexOf(eventKey) === -1) {
508 this.contextmenuKeys.push(eventKey);
509 }
510 this.contextmenuKeys.forEach((key) => {
511 const index = selectedKeys.indexOf(key);
512 if (index !== -1) {
513 selectedKeys.splice(index, 1);
514 }
515 });
516 if (selectedKeys.indexOf(eventKey) === -1) {
517 selectedKeys.push(eventKey);
518 }
519 this.setState({
520 selectedKeys,
521 });
522 this.props.onRightClick({
523 event: e,
524 node: treeNode
525 });
526 }
527
528 getTreeNode(){
529 const props = this.props;
530
531 }
532
533 goDown(currentPos,currentIndex,e,treeNode){
534 const props = this.props;
535 const state = this.state;
536 let treeChildren = props.children ? props.children : this.cacheTreeNodes; //最终渲染在 Tree 标签中的子节点
537 const nextIndex = parseInt(currentIndex) + 1;
538
539 let nextPos,backNextPos;
540 let nextTreeNode,backNextTreeNode;
541 const backNextPosArr=[],backNextTreeNodeArr = [],tempBackNextPosArr=[];
542 //是否为展开的节点,如果展开获取第一个子节点的信息,如果没有取相邻节点,若也没有相邻节点则获取父节点的下一个节点
543 if(state.expandedKeys.indexOf(treeNode.props.eventKey)>-1){
544 nextPos = currentPos + '-0';
545 }else{
546 nextPos = currentPos.substr(0,currentPos.lastIndexOf('-')+1)+nextIndex;
547
548
549 }
550 //若向下的节点没有了,找到父级相邻节点
551 let tempPosArr = currentPos.split('-');
552 let tempPosArrLength = tempPosArr.length;
553 //将可能是下一个节点的的位置都备份一遍
554 while(tempPosArrLength>1){
555 backNextPos = tempPosArrLength>1 && tempPosArr.slice(0,tempPosArrLength-1).join('-')+'-' + (parseInt(tempPosArr[tempPosArrLength-1])+1)
556 tempBackNextPosArr.push(backNextPos);
557 tempPosArr = tempPosArr.slice(0,tempPosArrLength-1)
558 tempPosArrLength = tempPosArr.length;
559 }
560 //选中下一个相邻的节点
561 loopAllChildren(treeChildren,function(itemNode,index,pos,newKey){
562 if(pos == nextPos){
563 nextTreeNode = itemNode;
564 }
565 tempBackNextPosArr.forEach(item=>{
566 if(item && item == pos){
567 // backNextTreeNode = item;
568 backNextTreeNodeArr.push(itemNode);
569 backNextPosArr.push(pos);
570 }
571 })
572
573 })
574 //如果没有下一个节点,则获取父节点的下一个节点
575 if(!nextTreeNode){
576 for(let i=0;i<backNextTreeNodeArr.length;i++){
577 if(backNextTreeNodeArr[i]){
578 nextTreeNode = backNextTreeNodeArr[i];
579 nextPos = backNextPosArr[i];
580 break;
581 }
582 }
583
584
585 }
586
587 //查询的下一个节点不为空的话,则选中
588 if(nextTreeNode){
589 const queryInfo = `a[pos="${nextPos}"]`;
590 const parentEle = closest(e.target,".u-tree")
591 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
592 focusEle && focusEle.focus()
593 const eventKey = nextTreeNode.props.eventKey || nextTreeNode.key;
594 this.setState({
595 focusKey: eventKey
596 })
597 if(props.autoSelectWhenFocus){
598 this.onSelect(nextTreeNode);
599 }
600 }
601 }
602
603 goUp(currentPos,currentIndex,e,treeNode){
604 const props = this.props;
605 const state = this.state;
606 if(currentIndex == 0 && currentPos.length === 3){
607 return
608 }
609 // 向上键Up
610 const preIndex = parseInt(currentIndex) - 1;
611 let prePos;
612 if(preIndex>= 0){
613 prePos = currentPos.substr(0,currentPos.lastIndexOf('-')+1)+preIndex;
614 }else{
615 prePos = currentPos.substr(0,currentPos.lastIndexOf('-'));
616 }
617
618 let prevTreeNode,preElement;
619 //选中上一个相邻的节点
620 loopAllChildren(props.children,function(item,index,pos,newKey){
621 if(pos == prePos){
622 prevTreeNode = item;
623 }
624 })
625 //查询的上一个节点不为空的话,则选中
626 if(prevTreeNode){
627 if(preIndex >=0){
628 //如果上面的节点展开则默认选择最后一个子节点
629 if(state.expandedKeys.indexOf(prevTreeNode.key)>-1){
630 const preElementArr = e.target.parentElement.previousElementSibling.querySelectorAll('a');
631 preElement = preElementArr[preElementArr.length-1];
632 prePos = preElement.getAttribute('pos');
633 loopAllChildren(props.children,function(item,index,pos,newKey){
634 if(pos == prePos){
635 prevTreeNode = item;
636 }
637 })
638 }else{
639 //上一个节点没有展开
640 preElement = e.target.parentElement.previousElementSibling.querySelector('a')
641 }
642 }else{
643 // 不存在上一个节点时,选中它的父节点
644 preElement = e.target.parentElement.parentElement.parentElement.querySelector('a')
645 }
646
647
648 }
649 preElement && preElement.focus();
650 const eventKey = prevTreeNode.props.eventKey || prevTreeNode.key;
651 this.setState({
652 focusKey: eventKey
653 })
654 if(props.autoSelectWhenFocus){
655 this.onSelect(prevTreeNode);
656 }
657 }
658 // all keyboard events callbacks run from here at first
659 onKeyDown(e,treeNode) {
660 // e.stopPropagation();
661
662 const props = this.props;
663 const currentPos = treeNode.props.pos;
664 const selectable = treeNode.props.selectable;
665 const currentIndex = currentPos.substr(currentPos.lastIndexOf('-')+1);
666 //向下键down
667 if(e.keyCode == KeyCode.DOWN){
668 this.goDown(currentPos,currentIndex,e,treeNode);
669 }else if(e.keyCode == KeyCode.UP){
670 this.goUp(currentPos,currentIndex,e,treeNode);
671 }else if(e.keyCode == KeyCode.LEFT && !treeNode.props.isLeaf){
672 // 收起树节点
673 this.onExpand(treeNode,'left');
674 }else if (e.keyCode == KeyCode.RIGHT && !treeNode.props.isLeaf){
675 // 展开树节点
676 this.onExpand(treeNode,'right');
677 }else if (e.keyCode == KeyCode.SPACE){
678 this.onSelect(treeNode);
679 // 如果是多选tree则进行选中或者反选该节点
680 props.checkable && this.onCheck(treeNode);
681 }else if(e.keyCode == KeyCode.ENTER){
682 if(props.onDoubleClick) {
683 this.onDoubleClick(treeNode);
684 } else {
685 selectable && this.onSelect(treeNode);
686 props.checkable && this.onCheck(treeNode);
687 }
688 }
689 this.props.keyFun && this.props.keyFun(e,treeNode);
690 // e.preventDefault();
691
692 }
693
694 _focusDom(selectKeyDomPos,targetDom){
695 const queryInfo = `a[pos="${selectKeyDomPos}"]`;
696 const parentEle = closest(targetDom,".u-tree")
697 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
698 if(document.activeElement !== focusEle){
699 focusEle && focusEle.focus();
700 }
701 }
702
703 /**
704 * 此方法为了解决树快捷键,当有的元素隐藏,按tab键也要显示的问题
705 * @param {*} e
706 */
707 onUlFocus(e){
708 const targetDom = e.target;
709
710 // 如果当前tree节点不包括上一个焦点节点会触发此方法
711 if(this.tree == targetDom && !this.isIn && !this.tree.contains(e.relatedTarget)){
712 const {onFocus, children} = this.props;
713 const {selectedKeys=[]} = this.state;
714 let tabIndexKey = selectedKeys[0]
715 let isExist = false;
716 const treeNode = children.length && children[0];
717 let eventKey = treeNode.props.eventKey || treeNode.key;
718 if((this.selectKeyDomExist && tabIndexKey) || !tabIndexKey){
719 isExist = true;
720 const queryInfo = `a[pos="${this.selectKeyDomPos}"]`;
721 const parentEle = closest(e.target,".u-tree")
722 const focusEle = parentEle?parentEle.querySelector(queryInfo):null;
723 focusEle && focusEle.focus();
724 // TAB键选中树后,默认聚焦在第一个(已选中)节点,并显示 focus 状态。
725 this.setState({
726 focusKey: tabIndexKey || eventKey
727 })
728 }
729 let onFocusRes = onFocus && onFocus(isExist);
730 if(onFocusRes instanceof Promise){
731 onFocusRes.then(()=>{
732 this._focusDom(this.selectKeyDomPos,targetDom);
733 });
734 }else{
735 this._focusDom(this.selectKeyDomPos,targetDom);
736 }
737 }
738 }
739
740
741 onUlMouseEnter(e){
742 this.isIn = true;
743 // console.log('onUlMouseEnter----isIn-----',this.isIn);
744 }
745 onUlMouseLeave(e){
746 this.isIn = false;
747 // console.log('onUlMouseLeave----isIn-----',this.isIn);
748
749 }
750
751 getFilterExpandedKeys(props, expandKeyProp, expandAll) {
752 const keys = props[expandKeyProp];
753 if (!expandAll && !props.autoExpandParent) {
754 return keys || [];
755 }
756 const expandedPositionArr = [];
757 if (props.autoExpandParent) {
758 loopAllChildren(props.children, (item, index, pos, newKey) => {
759 if (keys.indexOf(newKey) > -1) {
760 expandedPositionArr.push(pos);
761 }
762 });
763 }
764 const filterExpandedKeys = [];
765 loopAllChildren(props.children, (item, index, pos, newKey) => {
766 if (expandAll) {
767 filterExpandedKeys.push(newKey);
768 } else if (props.autoExpandParent) {
769 expandedPositionArr.forEach(p => {
770 if ((p.split('-').length > pos.split('-').length && isInclude(pos.split('-'), p.split('-')) || pos === p) && filterExpandedKeys.indexOf(newKey) === -1) {
771 filterExpandedKeys.push(newKey);
772 }
773 });
774 }
775 });
776 return filterExpandedKeys.length ? filterExpandedKeys : keys;
777 }
778
779 getDefaultExpandedKeys(props, willReceiveProps) {
780 let expandedKeys = willReceiveProps ? undefined :
781 this.getFilterExpandedKeys(props, 'defaultExpandedKeys',
782 props.defaultExpandedKeys.length ? false : props.defaultExpandAll);
783 if ('expandedKeys' in props) {
784 expandedKeys = (props.autoExpandParent ?
785 this.getFilterExpandedKeys(props, 'expandedKeys', false) :
786 props.expandedKeys) || [];
787 }
788 return expandedKeys;
789 }
790
791 getDefaultCheckedKeys(props, willReceiveProps) {
792 let checkedKeys = willReceiveProps ? undefined : props.defaultCheckedKeys;
793 if ('checkedKeys' in props) {
794 checkedKeys = props.checkedKeys || [];
795 if (props.checkStrictly) {
796 if (props.checkedKeys.checked) {
797 checkedKeys = props.checkedKeys.checked;
798 } else if (!Array.isArray(props.checkedKeys)) {
799 checkedKeys = [];
800 }
801 }
802 }
803 return checkedKeys;
804 }
805
806 getDefaultSelectedKeys(props, willReceiveProps) {
807 const getKeys = (keys) => {
808 if (props.multiple) {
809 return [...keys];
810 }
811 if (keys.length) {
812 return [keys[0]];
813 }
814 return keys;
815 };
816 let selectedKeys = willReceiveProps ? undefined : getKeys(props.defaultSelectedKeys);
817 if ('selectedKeys' in props) {
818 selectedKeys = getKeys(props.selectedKeys);
819 }
820 return selectedKeys;
821 }
822
823 getRawExpandedKeys() {
824 if (!this._rawExpandedKeys && ('expandedKeys' in this.props)) {
825 this._rawExpandedKeys = [...this.state.expandedKeys];
826 }
827 }
828
829 getOpenTransitionName() {
830 const props = this.props;
831 let transitionName = props.openTransitionName;
832 const animationName = props.openAnimation;
833 if (!transitionName && typeof animationName === 'string') {
834 transitionName = `${props.prefixCls}-open-${animationName}`;
835 }
836 return transitionName;
837 }
838
839 getDragNodes(treeNode) {
840 const dragNodesKeys = [];
841 const tPArr = treeNode.props.pos.split('-');
842 loopAllChildren(this.props.children, (item, index, pos, newKey) => {
843 const pArr = pos.split('-');
844 if (treeNode.props.pos === pos || tPArr.length < pArr.length && isInclude(tPArr, pArr)) {
845 dragNodesKeys.push(newKey);
846 }
847 });
848 return dragNodesKeys;
849 }
850
851 getExpandedKeys(treeNode, expand) {
852 const key = treeNode.props.eventKey;
853 const expandedKeys = this.state.expandedKeys;
854 const expandedIndex = expandedKeys.indexOf(key);
855 let exKeys;
856 if (expandedIndex > -1 && !expand) {
857 exKeys = [...expandedKeys];
858 exKeys.splice(expandedIndex, 1);
859 return exKeys;
860 }
861 if (expand && expandedKeys.indexOf(key) === -1) {
862 return expandedKeys.concat([key]);
863 }
864 }
865
866 filterTreeNode(treeNode) {
867 const filterTreeNode = this.props.filterTreeNode;
868 if (typeof filterTreeNode !== 'function' || treeNode.props.disabled) {
869 return false;
870 }
871 return filterTreeNode.call(this, treeNode);
872 }
873
874 /**
875 * 将截取后的 List 数组转换为 Tree 结构,并更新 state
876 */
877 handleTreeListChange = (treeList, startIndex, endIndex) => {
878 // 属性配置设置
879 let attr = {
880 id: 'key',
881 parendId: 'parentKey',
882 name: 'title',
883 rootId: null,
884 isLeaf: 'isLeaf'
885 };
886 let treeData = convertListToTree(treeList, attr, this.flatTreeKeysMap);
887
888 this.startIndex = typeof(startIndex) !== "undefined" ? startIndex : this.startIndex;
889 this.endIndex = typeof(endIndex) !== "undefined" ? endIndex : this.endIndex;
890 this.setState({
891 treeData : treeData
892 })
893 }
894
895 /**
896 * 深度遍历 treeData,把Tree数据拍平,变为一维数组
897 * @param {*} treeData
898 * @param {*} parentKey 标识父节点
899 * @param {*} isShown 该节点是否显示在页面中,当节点的父节点是展开状态 或 该节点是根节点时,该值为 true
900 */
901 deepTraversal = (treeData, parentKey=null, isShown) => {
902 let {expandedKeys} = this.state,
903 expandedKeysSet = this.cacheExpandedKeys ? this.cacheExpandedKeys : new Set(expandedKeys),
904 flatTreeData = [],
905 flatTreeKeysMap = this.flatTreeKeysMap, //存储所有 key-value 的映射,方便获取各节点信息
906 dataCopy = treeData;
907 if(Array.isArray(dataCopy)){
908 for (let i=0, l=dataCopy.length; i<l; i++) {
909 let { key, title, children, ...props } = dataCopy[i],
910 dataCopyI = new Object(),
911 isLeaf = children ? false : true;
912 //如果父节点是收起状态,则子节点的展开状态无意义。(一级节点或根节点直接判断自身状态即可)
913 let isExpanded = (parentKey === null || expandedKeysSet.has(parentKey)) ? expandedKeysSet.has(key) : false;
914 dataCopyI = Object.assign(dataCopyI,{
915 key,
916 title,
917 isExpanded,
918 parentKey : parentKey || null,
919 isShown,
920 isLeaf
921 },{...props});
922 //该节点的父节点是展开状态 或 该节点是根节点
923 if(isShown || parentKey === null){
924 flatTreeData.push(dataCopyI); // 取每项数据放入一个新数组
925 flatTreeKeysMap[key] = dataCopyI;
926 }
927 if (Array.isArray(children) && children.length > 0){
928 // 若存在children则递归调用,把数据拼接到新数组中,并且删除该children
929 flatTreeData = flatTreeData.concat(this.deepTraversal(children, key, isExpanded));
930 }
931 }
932 }
933 return flatTreeData;
934 }
935
936 /**
937 * 根据 treeData 渲染树节点
938 * @param data 树形结构的数组
939 * @param preHeight 前置占位高度
940 * @param sufHeight 后置占位高度
941 */
942 renderTreefromData = (data) => {
943 let {renderTitle,renderTreeNodes} = this.props;
944 if(renderTreeNodes) {
945 return renderTreeNodes(data);
946 }
947 const loop = data => data.map((item) => {
948 if (item.children) {
949 return (
950 <TreeNode key={item.key} title={renderTitle ? renderTitle(item) : item.key} isLeaf={item.isLeaf}>
951 {loop(item.children)}
952 </TreeNode>
953 );
954 }
955 return <TreeNode key={item.key} title={renderTitle ? renderTitle(item) : item.key} isLeaf={true}/>;
956 });
957 return loop(data);
958 }
959
960 /**
961 * @description 计算懒加载时的前置占位和后置占位
962 * @param start {Number} 开始截取数据的位置
963 * @param end {Number} 结束截取数据的位置
964 * @return sumHeight {Number} 空白占位的高度
965 */
966 getSumHeight = (start, end) => {
967 let sumHeight = 0;
968 if(start > end) {
969 return sumHeight;
970 }
971 let span = Math.abs(end - start);
972 if(span) {
973 sumHeight = span * this.store.getState().rowHeight;
974 }
975 return sumHeight;
976 }
977
978 renderTreeNode(child, index, level = 0) {
979 const pos = `${level}-${index}`;
980 const key = child.key || pos;
981
982 const state = this.state;
983 const props = this.props;
984 const {selectedKeys=[]} = this.state;
985 let tabIndexKey = selectedKeys[0]
986 if(tabIndexKey && key == tabIndexKey){
987 this.selectKeyDomExist = true;
988 this.selectKeyDomPos = pos;
989 }
990 // prefer to child's own selectable property if passed
991 let selectable = props.selectable;
992 if (child.props.hasOwnProperty('selectable')) {
993 selectable = child.props.selectable;
994 }
995 let draggable = props.draggable;
996 if(child.props.hasOwnProperty('draggable')){
997 draggable = child.props.draggable;
998 }
999 let isLeaf = null;
1000 if(child.props.hasOwnProperty('isLeaf')){
1001 isLeaf = child.props.isLeaf;
1002 }
1003
1004 const cloneProps = {
1005 root: this,
1006 eventKey: key,
1007 pos,
1008 selectable,
1009 loadData: props.loadData,
1010 onMouseEnter: props.onMouseEnter,
1011 onMouseLeave: props.onMouseLeave,
1012 onRightClick: props.onRightClick,
1013 onDoubleClick:props.onDoubleClick,
1014 onKeyDown:props.onKeyDown,
1015 prefixCls: props.prefixCls,
1016 showLine: props.showLine,
1017 showIcon: props.showIcon,
1018 draggable,
1019 dragOver: state.dragOverNodeKey === key && this.dropPosition === 0,
1020 dragOverGapTop: state.dragOverNodeKey === key && this.dropPosition === -1,
1021 dragOverGapBottom: state.dragOverNodeKey === key && this.dropPosition === 1,
1022 _dropTrigger: this._dropTrigger,
1023 expanded: state.expandedKeys.indexOf(key) !== -1,
1024 selected: state.selectedKeys.indexOf(key) !== -1,
1025 focused: state.focusKey === key,
1026 openTransitionName: this.getOpenTransitionName(),
1027 openAnimation: props.openAnimation,
1028 filterTreeNode: this.filterTreeNode.bind(this),
1029 openIcon: props.openIcon,
1030 closeIcon: props.closeIcon,
1031 focusable:props.focusable,
1032 tabIndexKey: state.selectedKeys[0],
1033 tabIndexValue:props.tabIndexValue,
1034 ext:child.props.ext,
1035 mustExpandable:props.mustExpandable,
1036 isLeaf
1037 };
1038 if (props.checkable) {
1039 cloneProps.checkable = props.checkable;
1040 if (props.checkStrictly) {
1041 if (state.checkedKeys) {
1042 cloneProps.checked = state.checkedKeys.indexOf(key) !== -1 || false;
1043 }
1044 if (props.checkedKeys && props.checkedKeys.halfChecked) {
1045 cloneProps.halfChecked = props.checkedKeys.halfChecked.indexOf(key) !== -1 || false;
1046 } else {
1047 cloneProps.halfChecked = false;
1048 }
1049 } else {
1050 if (this.checkedKeys) {
1051 cloneProps.checked = this.checkedKeys.indexOf(key) !== -1 || false;
1052 }
1053 cloneProps.halfChecked = this.halfCheckedKeys.indexOf(key) !== -1;
1054 }
1055 }
1056 if (this.treeNodesStates && this.treeNodesStates[pos]) {
1057 Object.assign(cloneProps, this.treeNodesStates[pos].siblingPosition);
1058 }
1059 return React.cloneElement(child, cloneProps);
1060 }
1061
1062 render() {
1063 const props = this.props;
1064 const {
1065 showLine, prefixCls, className, focusable, checkable, loadData, checkStrictly, tabIndexValue, lazyLoad, getScrollContainer,
1066 defaultExpandedKeys, defaultSelectedKeys, defaultCheckedKeys, openAnimation, draggable,
1067 ...others
1068 } = this.props;
1069 const customProps = {...omit(others, [
1070 'showIcon',
1071 'cancelUnSelect',
1072 'onCheck',
1073 'selectable',
1074 'autoExpandParent',
1075 'defaultExpandAll',
1076 'onExpand',
1077 'autoSelectWhenFocus',
1078 'expandWhenDoubleClick',
1079 'expandedKeys',
1080 'keyFun',
1081 'openIcon',
1082 'closeIcon',
1083 'treeData',
1084 'checkedKeys',
1085 'selectedKeys',
1086 'renderTreeNodes',
1087 'mustExpandable',
1088 'onMouseEnter',
1089 'onMouseLeave',
1090 'onDoubleClick'
1091 ])}
1092 const { treeData,flatTreeData } = this.state;
1093 let { startIndex, endIndex } = this, //数据截取的开始位置和结束位置
1094 preHeight = 0, //前置占位高度
1095 sufHeight = 0, //后置占位高度
1096 treeNode = [], //根据传入的 treeData 生成的 treeNode 节点数组
1097 treeChildren = props.children; //最终渲染在 Tree 标签中的子节点
1098 if(lazyLoad){
1099 preHeight = this.getSumHeight(0, startIndex);
1100 sufHeight = this.getSumHeight(endIndex, flatTreeData.length);
1101 }
1102 if(!props.children && treeData) { //传入json数据
1103 treeNode = this.renderTreefromData(treeData);
1104 this.cacheTreeNodes = treeNode;
1105 treeChildren = treeNode;
1106 }
1107 let showLineCls = "";
1108 if (showLine) {
1109 showLineCls = `${prefixCls}-show-line`;
1110 }
1111 const domProps = {
1112 className: classNames(className, prefixCls, showLineCls),
1113 role: 'tree-node',
1114 };
1115
1116 if (focusable) {
1117 domProps.onFocus = this.onUlFocus;
1118 domProps.onMouseEnter = this.onUlMouseEnter;
1119 domProps.onMouseLeave = this.onUlMouseLeave;
1120 }
1121
1122 // if (props.focusable) {
1123 // // domProps.tabIndex = '0';//需求改成了默认选择第一个节点或者选中的节点
1124 // // domProps.onKeyDown = this.onKeyDown;//添加到具体的treeNode上了
1125 // }
1126 const getTreeNodesStates = () => {
1127 this.treeNodesStates = {};
1128 loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
1129 this.treeNodesStates[pos] = {
1130 siblingPosition,
1131 };
1132 });
1133 };
1134 if (showLine && !checkable ) {
1135 getTreeNodesStates();
1136 }
1137 if (checkable && (this.checkedKeysChange || loadData || this.dataChange)) {
1138 if (checkStrictly) {
1139 getTreeNodesStates();
1140 } else if (props._treeNodesStates) {
1141 this.treeNodesStates = props._treeNodesStates.treeNodesStates;
1142 this.halfCheckedKeys = props._treeNodesStates.halfCheckedKeys;
1143 this.checkedKeys = props._treeNodesStates.checkedKeys;
1144 } else {
1145 const checkedKeys = this.state.checkedKeys;
1146 let checkKeys;
1147 if (!loadData && this.checkKeys && this._checkedKeys &&
1148 arraysEqual(this._checkedKeys, checkedKeys) && !this.dataChange) {
1149 // if checkedKeys the same as _checkedKeys from onCheck, use _checkedKeys.
1150 checkKeys = this.checkKeys;
1151 } else {
1152 const checkedPositions = [];
1153 this.treeNodesStates = {};
1154 loopAllChildren(treeChildren, (item, index, pos, keyOrPos, siblingPosition) => {
1155 this.treeNodesStates[pos] = {
1156 node: item,
1157 key: keyOrPos,
1158 checked: false,
1159 halfChecked: false,
1160 siblingPosition,
1161 };
1162 if (checkedKeys.indexOf(keyOrPos) !== -1) {
1163 this.treeNodesStates[pos].checked = true;
1164 checkedPositions.push(pos);
1165 }
1166 });
1167 // if the parent node's key exists, it all children node will be checked
1168 handleCheckState(this.treeNodesStates, filterParentPosition(checkedPositions), true);
1169 checkKeys = getCheck(this.treeNodesStates);
1170 }
1171 this.halfCheckedKeys = checkKeys.halfCheckedKeys;
1172 this.checkedKeys = checkKeys.checkedKeys;
1173 }
1174 }
1175 this.selectKeyDomExist = false;
1176 return (
1177 lazyLoad ?
1178 <InfiniteScroll
1179 className="u-tree-infinite-scroll"
1180 treeList={flatTreeData}
1181 handleTreeListChange={this.handleTreeListChange}
1182 getScrollParent={getScrollContainer}
1183 store={this.store}
1184 >
1185 <ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue} {...customProps}>
1186 <li style={{height : preHeight}} className='u-treenode-start' key={'tree_node_start'}></li>
1187 { React.Children.map(treeChildren, this.renderTreeNode, this) }
1188 <li style={{height : sufHeight}} className='u-treenode-end' key={'tree_node_end'}></li>
1189 </ul>
1190 </InfiniteScroll>
1191 :
1192 <ul {...domProps} unselectable="true" ref={(el)=>{this.tree = el}} tabIndex={focusable && tabIndexValue} {...customProps}>
1193 { React.Children.map(treeChildren, this.renderTreeNode, this) }
1194 </ul>
1195 );
1196 }
1197}
1198
1199Tree.propTypes = {
1200 prefixCls: PropTypes.string,
1201 children: PropTypes.any,
1202 showLine: PropTypes.bool,
1203 showIcon: PropTypes.bool,
1204 selectable: PropTypes.bool,
1205 multiple: PropTypes.bool,
1206 checkable: PropTypes.oneOfType([
1207 PropTypes.bool,
1208 PropTypes.node,
1209 ]),
1210 _treeNodesStates: PropTypes.object,
1211 checkStrictly: PropTypes.bool,
1212 draggable: PropTypes.bool,
1213 autoExpandParent: PropTypes.bool,
1214 defaultExpandAll: PropTypes.bool,
1215 defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string),
1216 expandedKeys: PropTypes.arrayOf(PropTypes.string),
1217 defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string),
1218 checkedKeys: PropTypes.oneOfType([
1219 PropTypes.arrayOf(PropTypes.string),
1220 PropTypes.object,
1221 ]),
1222 defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
1223 selectedKeys: PropTypes.arrayOf(PropTypes.string),
1224 onExpand: PropTypes.func,
1225 onCheck: PropTypes.func,
1226 onSelect: PropTypes.func,
1227 loadData: PropTypes.func,
1228 onMouseEnter: PropTypes.func,
1229 onMouseLeave: PropTypes.func,
1230 onRightClick: PropTypes.func,
1231 onDragStart: PropTypes.func,
1232 onDragEnter: PropTypes.func,
1233 onDragOver: PropTypes.func,
1234 onDragLeave: PropTypes.func,
1235 onDrop: PropTypes.func,
1236 onDragEnd: PropTypes.func,
1237 filterTreeNode: PropTypes.func,
1238 openTransitionName: PropTypes.string,
1239 focusable: PropTypes.bool,
1240 openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
1241 lazyLoad: PropTypes.bool,
1242 treeData: PropTypes.array,
1243 renderTreeNodes: PropTypes.func,
1244 autoSelectWhenFocus: PropTypes.bool,
1245 getScrollContainer: PropTypes.func,
1246 expandWhenDoubleClick: PropTypes.bool
1247};
1248
1249Tree.defaultProps = {
1250 prefixCls: 'rc-tree',
1251 showLine: false,
1252 showIcon: true,
1253 selectable: true,
1254 multiple: false,
1255 checkable: false,
1256 checkStrictly: false,
1257 draggable: false,
1258 autoExpandParent: true,
1259 defaultExpandAll: false,
1260 defaultExpandedKeys: [],
1261 defaultCheckedKeys: [],
1262 defaultSelectedKeys: [],
1263 onExpand: noop,
1264 onCheck: noop,
1265 onSelect: noop,
1266 onDragStart: noop,
1267 onDragEnter: noop,
1268 onDragOver: noop,
1269 onDragLeave: noop,
1270 onDrop: noop,
1271 onDragEnd: noop,
1272 tabIndexValue:0,
1273 lazyLoad: false,
1274 autoSelectWhenFocus: false,
1275 getScrollContainer: noop,
1276 expandWhenDoubleClick: false
1277};
1278
1279export default Tree;
\No newline at end of file