| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 | 
							- import React, { PureComponent } from 'react';
 
- import cls from 'classnames';
 
- import PropTypes from 'prop-types';
 
- import { cssClasses } from '@douyinfe/semi-foundation/tree/constants';
 
- import isEnterPress from '@douyinfe/semi-foundation/utils/isEnterPress';
 
- import { debounce, isFunction, isString, get, isEmpty } from 'lodash';
 
- import { IconTreeTriangleDown, IconFile, IconFolder, IconFolderOpen } from '@douyinfe/semi-icons';
 
- import { Checkbox } from '../checkbox';
 
- import TreeContext, { TreeContextValue } from './treeContext';
 
- import Spin from '../spin';
 
- import { TreeNodeProps, TreeNodeState } from './interface';
 
- import { getHighLightTextHTML } from '../_utils/index';
 
- import Indent from './indent';
 
- const prefixcls = cssClasses.PREFIX_OPTION;
 
- export default class TreeNode extends PureComponent<TreeNodeProps, TreeNodeState> {
 
-     static contextType = TreeContext;
 
-     static propTypes = {
 
-         expanded: PropTypes.bool,
 
-         selected: PropTypes.bool,
 
-         checked: PropTypes.bool,
 
-         halfChecked: PropTypes.bool,
 
-         active: PropTypes.bool,
 
-         disabled: PropTypes.bool,
 
-         loaded: PropTypes.bool,
 
-         loading: PropTypes.bool,
 
-         isLeaf: PropTypes.bool,
 
-         pos: PropTypes.string,
 
-         children: PropTypes.oneOfType([PropTypes.array, PropTypes.object]),
 
-         icon: PropTypes.node,
 
-         directory: PropTypes.bool,
 
-         keyword: PropTypes.string,
 
-         treeNodeFilterProp: PropTypes.string,
 
-         selectedKey: PropTypes.string,
 
-         motionKey: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.string)]),
 
-         isEnd: PropTypes.arrayOf(PropTypes.bool),
 
-         showLine: PropTypes.bool
 
-     };
 
-     static defaultProps = {
 
-         selectedKey: '',
 
-         motionKey: '',
 
-     };
 
-     debounceSelect: any;
 
-     refNode: HTMLElement;
 
-     context: TreeContextValue;
 
-     constructor(props: TreeNodeProps) {
 
-         super(props);
 
-         this.state = {};
 
-         this.debounceSelect = debounce(this.onSelect, 500, {
 
-             leading: true,
 
-             trailing: false
 
-         });
 
-     }
 
-     onSelect = (e: React.MouseEvent | React.KeyboardEvent) => {
 
-         const { onNodeSelect } = this.context;
 
-         onNodeSelect(e, this.props);
 
-     };
 
-     onExpand = (e: React.MouseEvent | React.KeyboardEvent) => {
 
-         const { onNodeExpand } = this.context;
 
-         e && e.stopPropagation();
 
-         e.nativeEvent.stopImmediatePropagation();
 
-         onNodeExpand(e, this.props);
 
-     };
 
-     onCheck = (e: React.MouseEvent | React.KeyboardEvent) => {
 
-         if (this.isDisabled()) {
 
-             return;
 
-         }
 
-         const { onNodeCheck } = this.context;
 
-         e.stopPropagation();
 
-         e.nativeEvent?.stopImmediatePropagation?.();
 
-         onNodeCheck(e, this.props);
 
-     };
 
-     /**
 
-      * A11y: simulate checkbox click
 
-      */
 
-     handleCheckEnterPress = (e: React.KeyboardEvent) => {
 
-         if (isEnterPress(e)) {
 
-             this.onCheck(e);
 
-         }
 
-     }
 
-     onContextMenu = (e: React.MouseEvent) => {
 
-         const { onNodeRightClick } = this.context;
 
-         onNodeRightClick(e, this.props);
 
-     };
 
-     onClick = (e: React.MouseEvent | React.KeyboardEvent) => {
 
-         const { expandAction } = this.context;
 
-         if (expandAction === 'doubleClick') {
 
-             this.debounceSelect(e);
 
-             return;
 
-         }
 
-         this.onSelect(e);
 
-         if (expandAction === 'click') {
 
-             this.onExpand(e);
 
-         }
 
-     };
 
-     /**
 
-      * A11y: simulate li click
 
-      */
 
-     handleliEnterPress = (e: React.KeyboardEvent) => {
 
-         if (isEnterPress(e)) {
 
-             this.onClick(e);
 
-         }
 
-     }
 
-     onDoubleClick = (e: React.MouseEvent) => {
 
-         const { expandAction, onNodeDoubleClick } = this.context;
 
-         e.stopPropagation();
 
-         e.nativeEvent.stopImmediatePropagation();
 
-         if (isFunction(onNodeDoubleClick)) {
 
-             onNodeDoubleClick(e, this.props);
 
-         }
 
-         if (expandAction === 'doubleClick') {
 
-             this.onExpand(e);
 
-         }
 
-     };
 
-     onDragStart = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDragStart } = this.context;
 
-         e.stopPropagation();
 
-         onNodeDragStart(e, { ...this.props, nodeInstance: this.refNode });
 
-         try {
 
-             // ie throw error
 
-             // firefox-need-it
 
-             e.dataTransfer.setData('text/plain', '');
 
-         } catch (error) {
 
-             // empty
 
-         }
 
-     };
 
-     onDragEnter = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDragEnter } = this.context;
 
-         e.preventDefault();
 
-         e.stopPropagation();
 
-         onNodeDragEnter(e, { ...this.props, nodeInstance: this.refNode });
 
-     };
 
-     onDragOver = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDragOver } = this.context;
 
-         e.preventDefault();
 
-         e.stopPropagation();
 
-         onNodeDragOver(e, { ...this.props, nodeInstance: this.refNode });
 
-     };
 
-     onDragLeave = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDragLeave } = this.context;
 
-         e.stopPropagation();
 
-         onNodeDragLeave(e, { ...this.props, nodeInstance: this.refNode });
 
-     };
 
-     onDragEnd = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDragEnd } = this.context;
 
-         e.stopPropagation();
 
-         onNodeDragEnd(e, { ...this.props, nodeInstance: this.refNode });
 
-     };
 
-     onDrop = (e: React.DragEvent<HTMLLIElement>) => {
 
-         const { onNodeDrop } = this.context;
 
-         e.preventDefault();
 
-         e.stopPropagation();
 
-         onNodeDrop(e, { ...this.props, nodeInstance: this.refNode });
 
-     };
 
-     getNodeChildren = () => {
 
-         const { children } = this.props;
 
-         return children || [];
 
-     };
 
-     isLeaf = () => {
 
-         const { isLeaf, loaded } = this.props;
 
-         const { loadData } = this.context;
 
-         const hasChildren = this.getNodeChildren().length !== 0;
 
-         if (isLeaf === false) {
 
-             return false;
 
-         }
 
-         return isLeaf || (!loadData && !hasChildren) || (loadData && loaded && !hasChildren);
 
-     };
 
-     isDisabled = () => {
 
-         const { disabled } = this.props;
 
-         const { treeDisabled } = this.context;
 
-         if (disabled === false) {
 
-             return false;
 
-         }
 
-         return Boolean(treeDisabled || disabled);
 
-     };
 
-     renderArrow() {
 
-         const showIcon = !this.isLeaf();
 
-         const { loading, expanded, showLine } = this.props;
 
-         if (loading) {
 
-             return <Spin wrapperClassName={`${prefixcls}-spin-icon`} />;
 
-         }
 
-         if (showIcon) {
 
-             return (
 
-                 <IconTreeTriangleDown
 
-                     role='button'
 
-                     aria-label={`${expanded ? 'Expand' : 'Collapse'} the tree item`}
 
-                     className={`${prefixcls}-expand-icon`}
 
-                     size="small"
 
-                     onClick={this.onExpand}
 
-                 />
 
-             );
 
-         }
 
-         if (showLine) {
 
-             return this.renderSwitcher();
 
-         }
 
-         return (
 
-             <span className={`${prefixcls}-empty-icon`} />
 
-         );
 
-     }
 
-     renderCheckbox() {
 
-         const { checked, halfChecked, eventKey } = this.props;
 
-         const disabled = this.isDisabled();
 
-         return (
 
-             <div
 
-                 role='none'
 
-                 onClick={this.onCheck}
 
-                 onKeyPress={this.handleCheckEnterPress}
 
-             >
 
-                 <Checkbox
 
-                     aria-label='Toggle the checked state of checkbox'
 
-                     value={eventKey}
 
-                     indeterminate={halfChecked}
 
-                     checked={checked}
 
-                     disabled={Boolean(disabled)}
 
-                 />
 
-             </div>
 
-         );
 
-     }
 
-     // Switcher
 
-     renderSwitcher = () => {
 
-         if (this.isLeaf()) {
 
-             // if switcherIconDom is null, no render switcher span
 
-             return (<span className={cls(`${prefixcls}-switcher`)} >
 
-                 <span className={`${prefixcls}-switcher-leaf-line`} />
 
-             </span>);
 
-         }
 
-         return null;
 
-     };
 
-     renderIcon() {
 
-         const {
 
-             directory,
 
-             treeIcon
 
-         } = this.context;
 
-         const { expanded, icon, data } = this.props;
 
-         if (icon) {
 
-             return icon;
 
-         }
 
-         if (treeIcon) {
 
-             return typeof treeIcon === 'function' ? treeIcon(this.props) : treeIcon;
 
-         }
 
-         if (directory) {
 
-             const hasChild = !this.isLeaf();
 
-             if (!hasChild) {
 
-                 return <IconFile className={`${prefixcls}-item-icon`} />;
 
-             } else {
 
-                 return expanded ? <IconFolderOpen className={`${prefixcls}-item-icon`} /> : <IconFolder className={`${prefixcls}-item-icon`} />;
 
-             }
 
-         }
 
-         return null;
 
-     }
 
-     renderEmptyNode() {
 
-         const { emptyContent } = this.props;
 
-         const wrapperCls = cls(prefixcls, {
 
-             [`${prefixcls}-empty`]: true,
 
-         });
 
-         return (
 
-             <ul className={wrapperCls}>
 
-                 <li className={`${prefixcls}-label ${prefixcls}-label-empty`} x-semi-prop="emptyContent">
 
-                     {emptyContent}
 
-                 </li>
 
-             </ul>
 
-         );
 
-     }
 
-     renderRealLabel = () => {
 
-         const { renderLabel } = this.context;
 
-         const { label, keyword, data, filtered, treeNodeFilterProp } = this.props;
 
-         if (isFunction(renderLabel)) {
 
-             return renderLabel(label, data, keyword);
 
-         } else if (isString(label) && filtered && keyword) {
 
-             return getHighLightTextHTML({
 
-                 sourceString: label,
 
-                 searchWords: [keyword],
 
-                 option: {
 
-                     highlightTag: 'span',
 
-                     highlightClassName: `${prefixcls}-highlight`,
 
-                 },
 
-             } as any);
 
-         } else {
 
-             return label;
 
-         }
 
-     };
 
-     setRef = (node: HTMLElement) => {
 
-         this.refNode = node;
 
-     };
 
-     render() {
 
-         const {
 
-             eventKey,
 
-             expanded,
 
-             selected,
 
-             checked,
 
-             halfChecked,
 
-             loading,
 
-             active,
 
-             level,
 
-             empty,
 
-             filtered,
 
-             treeNodeFilterProp,
 
-             display,
 
-             style,
 
-             isEnd,
 
-             showLine,
 
-             ...rest
 
-         } = this.props;
 
-         if (empty) {
 
-             return this.renderEmptyNode();
 
-         }
 
-         const {
 
-             multiple,
 
-             draggable,
 
-             renderFullLabel,
 
-             dragOverNodeKey,
 
-             dropPosition,
 
-             labelEllipsis
 
-         } = this.context;
 
-         const isEndNode = isEnd[isEnd.length - 1];
 
-         const disabled = this.isDisabled();
 
-         const dragOver = dragOverNodeKey === eventKey && dropPosition === 0;
 
-         const dragOverGapTop = dragOverNodeKey === eventKey && dropPosition === -1;
 
-         const dragOverGapBottom = dragOverNodeKey === eventKey && dropPosition === 1;
 
-         const nodeCls = cls(prefixcls, {
 
-             [`${prefixcls}-level-${level + 1}`]: true,
 
-             [`${prefixcls}-fullLabel-level-${level + 1}`]: renderFullLabel,
 
-             [`${prefixcls}-collapsed`]: !expanded,
 
-             [`${prefixcls}-disabled`]: Boolean(disabled),
 
-             [`${prefixcls}-selected`]: selected,
 
-             [`${prefixcls}-active`]: !multiple && active,
 
-             [`${prefixcls}-ellipsis`]: labelEllipsis,
 
-             [`${prefixcls}-drag-over`]: !disabled && dragOver,
 
-             [`${prefixcls}-draggable`]: !disabled && draggable && !renderFullLabel,
 
-             // When draggable + renderFullLabel is enabled, the default style
 
-             [`${prefixcls}-fullLabel-draggable`]: !disabled && draggable && renderFullLabel,
 
-             // When draggable + renderFullLabel is turned on, the style of dragover
 
-             [`${prefixcls}-fullLabel-drag-over-gap-top`]: !disabled && dragOverGapTop && renderFullLabel,
 
-             [`${prefixcls}-fullLabel-drag-over-gap-bottom`]: !disabled && dragOverGapBottom && renderFullLabel,
 
-             [`${prefixcls}-tree-node-last-leaf`]: isEndNode,
 
-         });
 
-         const labelProps = {
 
-             onClick: this.onClick,
 
-             onContextMenu: this.onContextMenu,
 
-             onDoubleClick: this.onDoubleClick,
 
-             className: nodeCls,
 
-             onExpand: this.onExpand,
 
-             data: rest.data,
 
-             level,
 
-             onCheck: this.onCheck,
 
-             style,
 
-             expandIcon: this.renderArrow(),
 
-             checkStatus: {
 
-                 checked,
 
-                 halfChecked,
 
-             },
 
-             expandStatus: {
 
-                 expanded,
 
-                 loading,
 
-             },
 
-             filtered,
 
-             searchWord: rest.keyword,
 
-         };
 
-         const dragProps = {
 
-             onDoubleClick: this.onDoubleClick,
 
-             onDragStart: draggable ? this.onDragStart : undefined,
 
-             onDragEnter: draggable ? this.onDragEnter : undefined,
 
-             onDragOver: draggable ? this.onDragOver : undefined,
 
-             onDragLeave: draggable ? this.onDragLeave : undefined,
 
-             onDrop: draggable ? this.onDrop : undefined,
 
-             onDragEnd: draggable ? this.onDragEnd : undefined,
 
-             draggable: (!disabled && draggable) || undefined,
 
-         };
 
-         if (renderFullLabel) {
 
-             const customLabel = renderFullLabel({ ...labelProps });
 
-             if (draggable) {
 
-                 // @ts-ignore skip cloneElement type check
 
-                 return React.cloneElement(customLabel, {
 
-                     ref: this.setRef,
 
-                     ...dragProps
 
-                 });
 
-             } else {
 
-                 if (isEmpty(style)) {
 
-                     return customLabel;
 
-                 } else {
 
-                     // In virtualization, props.style will contain location information
 
-                     // @ts-ignore skip cloneElement type check
 
-                     return React.cloneElement(customLabel, {
 
-                         style: { ...get(customLabel, ['props', 'style']), ...style }
 
-                     });
 
-                 }
 
-             }
 
-         }
 
-         const labelCls = cls(`${prefixcls}-label`, {
 
-             [`${prefixcls}-drag-over-gap-top`]: !disabled && dragOverGapTop,
 
-             [`${prefixcls}-drag-over-gap-bottom`]: !disabled && dragOverGapBottom,
 
-         });
 
-         const setsize = get(rest, ['data', 'children', 'length']);
 
-         const posinset = isString(rest.pos) ? Number(rest.pos.split('-')[level + 1]) + 1 : 1;
 
-         return (
 
-             <li
 
-                 className={nodeCls}
 
-                 role="treeitem"
 
-                 aria-disabled={disabled}
 
-                 aria-checked={checked}
 
-                 aria-selected={selected}
 
-                 aria-setsize={setsize}
 
-                 aria-posinset={posinset}
 
-                 aria-expanded={expanded}
 
-                 aria-level={level + 1}
 
-                 data-key={eventKey}
 
-                 onClick={this.onClick}
 
-                 onKeyPress={this.handleliEnterPress}
 
-                 onContextMenu={this.onContextMenu}
 
-                 onDoubleClick={this.onDoubleClick}
 
-                 ref={this.setRef}
 
-                 style={style}
 
-                 {...dragProps}
 
-             >
 
-                 <Indent showLine={showLine} prefixcls={prefixcls} level={level} isEnd={isEnd} />
 
-                 {this.renderArrow()}
 
-                 <span
 
-                     className={labelCls}
 
-                 >
 
-                     {multiple ? this.renderCheckbox() : null}
 
-                     {this.renderIcon()}
 
-                     <span className={`${prefixcls}-label-text`}>{this.renderRealLabel()}</span>
 
-                 </span>
 
-             </li>
 
-         );
 
-     }
 
- }
 
 
  |