import * as React from "react"; // import PropTypes from "prop-types"; import classNames from "classnames"; // import ArrowRightIcon from "@material-ui/icons/ArrowRight"; // import { withStyles } from "@material-ui/styles"; // @ts-ignore import { TreeContext, TreeContextProps } from "./context-types"; import { getDataAndAria } from "./util"; import { IconType, Key, DataNode } from "./interface"; import { Indent } from "./indent"; import { convertNodePropsToEventData } from "./util/tree-util"; // const styles = { // coTree: { // margin: 0, // border: "1px solid transparent" // }, // coTreeTreenode: { // margin: 0, // padding: 0, // height: "29px", // lineHeight: "20px", // outline: 0, // whiteSpace: "nowrap", // listStyle: "none", // display: "flex", // alignItems: "center", // userSelect: "none", // borderTop: "2px solid transparent", // borderBottom: "2px solid transparent", // "& :hover": { // backgroundColor: "#eee" // }, // dragOver: { // color: "white", // backgroundColor: "#99bcf5", // opacity: 0.7 // }, // dragOverGapTop: { // borderTopColor: "skyblue" // }, // dragOverGapBottom: { // borderBottomColor: "skyblue" // }, // draggable: { // color: "#333", // userSelect: "none" // } // }, // coTreeNodeContentWrapper: { // display: "inline-block", // height: "20px", // margin: 0, // padding: "1px 2px 0 2px", // textDecoration: "none", // verticalAlign: "top", // cursor: "pointer" // }, // coTreeIconEle: { // display: "inline-block", // width: "16px", // height: "16px", // lineHeight: "16px", // verticalAlign: "middle", // border: "none", // outline: "none", // cursor: "pointer" // }, // coTreeSwitcher: { // display: "inline-block", // width: "16px", // height: "16px", // lineHeight: "16px", // verticalAlign: "middle", // border: "none", // outline: "none", // cursor: "pointer", // marginLeft: "20px" // }, // coTreeSwitcher_open: { // background: `url(${"./assets/arrowright.png"}) no-repeat 0 0 transparent`, // transform: "rotate(90deg)", // transition: "0.2s linear" // }, // coTreeSwitcher_close: { // background: `url(${"./assets/arrowright.png"}) no-repeat 0 0 transparent`, // transition: "0.2s linear" // }, // coTreeTreeNodeDisabled: { // span: { // "&:not(coTreeSwitcher)": { // color: "#767676", // cursor: "not-allowed" // } // }, // a: { // color: "#767676", // cursor: "not-allowed", // span: { // color: "#767676", // cursor: "not-allowed" // } // } // }, // coTreeTreenodeActive: { // background: "rgba(19, 18, 18, 0.1)" // }, // coTreeTreenodeSelected: { // backgroundColor: "#e6ecf1", // fontWieght: "bold", // opacity: "0.8" // }, // coTreeIcon__open: { // marginRight: "2px", // marginBottom: "4px", // verticalAlign: "middle", // background: `url(${"./assets/notebookp.png"}) no-repeat 0 0 transparent` // }, // coTreeIcon__close: { // marginRight: "2px", // marginBottom: "4px", // verticalAlign: "middle", // background: `url(${"./assets/notebookp.png"}) no-repeat 0 0 transparent` // }, // coTreeIcon__docu: { // marginRight: "2px", // marginBottom: "4px", // verticalAlign: "middle", // background: `url(${"./assets/notebookc.png"}) no-repeat 0 0 transparent` // }, // coTreeIcon__customize: { // marginRight: "2px", // verticalAlign: "top" // }, // coTreeIndentUnit: { // display: "inline-block", // paddingLeft: "10px" // } // }; const ICON_OPEN = "open"; const ICON_CLOSE = "close"; const defaultTitle = "---"; interface TreeNodeProps { eventKey?: Key; // Pass by parent `cloneElement` prefixCls?: string; className?: string; style?: React.CSSProperties; // By parent expanded?: boolean; selected?: boolean; loaded?: boolean; loading?: boolean; title?: React.ReactNode | ((data: DataNode) => React.ReactNode); dragOver?: boolean; dragOverGapTop?: boolean; dragOverGapBottom?: boolean; pos?: string; domRef?: React.Ref; /** New added in Tree for easy data access */ data?: DataNode; isStart?: boolean[]; isEnd?: boolean[]; active: boolean; onMouseMove?: React.MouseEventHandler; // By user isLeaf?: boolean; selectable?: boolean; disabled?: boolean; icon?: IconType; switcherIcon?: IconType; children?: React.ReactNode; context?: TreeContextProps; } // interface InternalTreeNodeProps extends TreeNodeProps { // context?: TreeContextProps; // } interface TreeNodeState { dragNodeHighlight: boolean; } class TreeNode extends React.Component { // static propTypes = { // prefixCls: PropTypes.string, // className: PropTypes.string, // style: PropTypes.object, // onSelect: PropTypes.func, // // By parent // eventKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), // expanded: PropTypes.bool, // selected: PropTypes.bool, // loaded: PropTypes.bool, // loading: PropTypes.bool, // title: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), // dragOver: PropTypes.bool, // dragOverGapTop: PropTypes.bool, // dragOverGapBottom: PropTypes.bool, // pos: PropTypes.string, // // By user // isLeaf: PropTypes.bool, // selectable: PropTypes.bool, // disabled: PropTypes.bool, // icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), // switcherIcon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]) // }; public state = { dragNodeHighlight: false }; public selectHandle: HTMLSpanElement; // Isomorphic needn't load data in server side componentDidMount() { this.syncLoadData(this.props); } componentDidUpdate() { this.syncLoadData(this.props); } onSelectorClick = e => { // Click trigger before select operation const { context: { onNodeClick } } = this.props; onNodeClick(e, convertNodePropsToEventData(this.props)); if (this.isSelectable()) { this.onSelect(e); } }; onSelectorDoubleClick = e => { const { context: { onNodeDoubleClick } } = this.props; onNodeDoubleClick(e, convertNodePropsToEventData(this.props)); }; onSelect = e => { if (this.isDisabled()) return; const { context: { onNodeSelect } } = this.props; e.preventDefault(); onNodeSelect(e, convertNodePropsToEventData(this.props)); }; onMouseEnter = e => { const { context: { onNodeMouseEnter } } = this.props; onNodeMouseEnter(e, convertNodePropsToEventData(this.props)); }; onMouseLeave = e => { const { context: { onNodeMouseLeave } } = this.props; onNodeMouseLeave(e, convertNodePropsToEventData(this.props)); }; onContextMenu = e => { const { context: { onNodeContextMenu } } = this.props; onNodeContextMenu(e, convertNodePropsToEventData(this.props)); }; onDragStart = e => { const { context: { onNodeDragStart } } = this.props; e.stopPropagation(); this.setState({ dragNodeHighlight: true }); onNodeDragStart(e, this); try { // ie throw error // firefox-need-it e.dataTransfer.setData("text/plain", ""); } catch (error) { // empty } }; onDragEnter = e => { const { context: { onNodeDragEnter } } = this.props; e.preventDefault(); e.stopPropagation(); onNodeDragEnter(e, this); }; onDragOver = e => { const { context: { onNodeDragOver } } = this.props; e.preventDefault(); e.stopPropagation(); onNodeDragOver(e, this); }; onDragLeave = e => { const { context: { onNodeDragLeave } } = this.props; e.stopPropagation(); onNodeDragLeave(e, this); }; onDragEnd = e => { const { context: { onNodeDragEnd } } = this.props; e.stopPropagation(); this.setState({ dragNodeHighlight: false }); onNodeDragEnd(e, this); }; onDrop = e => { const { context: { onNodeDrop } } = this.props; e.preventDefault(); e.stopPropagation(); this.setState({ dragNodeHighlight: false }); onNodeDrop(e, this); }; // Disabled item still can be switch onExpand: React.MouseEventHandler = e => { const { context: { onNodeExpand } } = this.props; onNodeExpand(e, convertNodePropsToEventData(this.props)); }; // Drag usage setSelectHandle = node => { this.selectHandle = node; }; getNodeState = () => { const { expanded } = this.props; if (this.isLeaf()) { return null; } return expanded ? ICON_OPEN : ICON_CLOSE; }; hasChildren = () => { const { eventKey } = this.props; const { context: { keyEntities } } = this.props; const { children } = keyEntities[eventKey] || {}; return !!(children || []).length; }; isLeaf = () => { const { isLeaf, loaded } = this.props; const { context: { loadData } } = this.props; const hasChildren = this.hasChildren(); if (isLeaf === false) { return false; } return ( isLeaf || (!loadData && !hasChildren) || (loadData && loaded && !hasChildren) ); }; isDisabled = () => { const { disabled } = this.props; const { context: { disabled: treeDisabled } } = this.props; return !!(treeDisabled || disabled); }; // Load data to avoid default expanded tree without data syncLoadData = props => { const { expanded, loading, loaded } = props; const { context: { loadData, onNodeLoad } } = this.props; if (loading) return; // read from state to avoid loadData at same time if (loadData && expanded && !this.isLeaf()) { // We needn't reload data when has children in sync logic // It's only needed in node expanded if (!this.hasChildren() && !loaded) { onNodeLoad(convertNodePropsToEventData(this.props)); } } }; isSelectable() { const { selectable } = this.props; const { context: { selectable: treeSelectable } } = this.props; // Ignore when selectable is undefined or null if (typeof selectable === "boolean") { return selectable; } return treeSelectable; } // Switcher renderSwitcher = () => { const { expanded, switcherIcon: switcherIconFromProps } = this.props; const { context: { prefixCls, switcherIcon: switcherIconFromCtx } } = this.props; const switcherIcon = switcherIconFromProps || switcherIconFromCtx; if (this.isLeaf()) { //switcher icon / open * close return (
{typeof switcherIcon === "function" ? switcherIcon({ ...this.props, isLeaf: true }) : switcherIcon}
); } const switcherCls = classNames( `${prefixCls}-switcher`, `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}` ); // return ( // //icon //
// // {typeof switcherIcon === "function" // ? switcherIcon({ ...this.props, isLeaf: false }) // : switcherIcon} //
// ); if (switcherIcon) { return (
{typeof switcherIcon === "function" ? switcherIcon({ ...this.props, isLeaf: false }) : switcherIcon}
); } else { return ( //icon
👉
{typeof switcherIcon === "function" ? switcherIcon({ ...this.props, isLeaf: false }) : switcherIcon}
); } }; renderIcon = () => { const { loading } = this.props; const { context: { prefixCls } } = this.props; //background / file / loading icon !!! // if (this.getNodeState()) { return 📕; } if (loading) { return ; } return 📗; }; // Icon + Title renderSelector = () => { const { dragNodeHighlight } = this.state; const { title, selected, icon, loading, data } = this.props; const { context: { prefixCls, showIcon, icon: treeIcon, draggable, loadData } } = this.props; const disabled = this.isDisabled(); const wrapClass = `${prefixCls}-node-content-wrapper`; // Icon - Still show loading icon when loading without showIcon let $icon; if (showIcon) { const currentIcon = icon || treeIcon; $icon = currentIcon ? ( {typeof currentIcon === "function" ? currentIcon(this.props) : currentIcon} ) : ( this.renderIcon() ); } else if (loadData && loading) { $icon = this.renderIcon(); } // Title const $title = ( {typeof title === "function" ? title(data) : title} ); return ( {$icon} {$title} ); }; render() { const { eventKey, className, style, dragOver, dragOverGapTop, dragOverGapBottom, isLeaf, isStart, isEnd, expanded, selected, loading, domRef, active, onMouseMove, ...otherProps } = this.props; const { context: { prefixCls, filterTreeNode, draggable, keyEntities } } = this.props; const disabled = this.isDisabled(); const dataOrAriaAttributeProps = getDataAndAria(otherProps); const { level } = keyEntities[eventKey] || {}; return (
{this.renderSwitcher()} {this.renderSelector()}
); } } const ContextTreeNode: React.FC | any = props => ( {context => } ); ContextTreeNode.displayName = "TreeNode"; ContextTreeNode.defaultProps = { title: defaultTitle }; (ContextTreeNode as any).isTreeNode = 1; export { TreeNodeProps, TreeNode, TreeNodeState }; export default ContextTreeNode;