import PropTypes from '../../_util/vue-types' import classNames from 'classnames' import { getNodeChildren, mapChildren, warnOnlyTreeNode } from './util' import { initDefaultProps, filterEmpty, getComponentFromProp } from '../../_util/props-util' import BaseMixin from '../../_util/BaseMixin' import getTransitionProps from '../../_util/getTransitionProps' import { cloneElement } from '../../_util/vnode' function noop () {} const ICON_OPEN = 'open' const ICON_CLOSE = 'close' const defaultTitle = '---' const TreeNode = { name: 'TreeNode', mixins: [BaseMixin], __ANT_TREE_NODE: true, props: initDefaultProps({ eventKey: PropTypes.string, // Pass by parent `cloneElement` prefixCls: PropTypes.string, // className: PropTypes.string, root: PropTypes.object, // onSelect: PropTypes.func, // By parent expanded: PropTypes.bool, selected: PropTypes.bool, checked: PropTypes.bool, loaded: PropTypes.bool, loading: PropTypes.bool, halfChecked: PropTypes.bool, title: PropTypes.any, pos: PropTypes.string, dragOver: PropTypes.bool, dragOverGapTop: PropTypes.bool, dragOverGapBottom: PropTypes.bool, // By user isLeaf: PropTypes.bool, selectable: PropTypes.bool, disabled: PropTypes.bool, disableCheckbox: PropTypes.bool, icon: PropTypes.any, dataRef: PropTypes.object, switcherIcon: PropTypes.any, }, {}), data () { return { dragNodeHighlight: false, } }, inject: { vcTree: { default: {}}, vcTreeNode: { default: {}}, }, provide () { return { vcTreeNode: this, } }, // Isomorphic needn't load data in server side mounted () { this.syncLoadData(this.$props) }, updated () { this.syncLoadData(this.$props) }, methods: { onSelectorClick (e) { // Click trigger before select/check operation const { vcTree: { onNodeClick }} = this onNodeClick(e, this) if (this.isSelectable()) { this.onSelect(e) } else { this.onCheck(e) } }, onSelectorDoubleClick (e) { const { vcTree: { onNodeDoubleClick }} = this onNodeDoubleClick(e, this) }, onSelect (e) { if (this.isDisabled()) return const { vcTree: { onNodeSelect }} = this e.preventDefault() onNodeSelect(e, this) }, onCheck (e) { if (this.isDisabled()) return const { disableCheckbox, checked } = this const { vcTree: { checkable, onNodeCheck }, } = this if (!checkable || disableCheckbox) return e.preventDefault() const targetChecked = !checked onNodeCheck(e, this, targetChecked) }, onMouseEnter (e) { const { vcTree: { onNodeMouseEnter }} = this onNodeMouseEnter(e, this) }, onMouseLeave (e) { const { vcTree: { onNodeMouseLeave }} = this onNodeMouseLeave(e, this) }, onContextMenu (e) { const { vcTree: { onNodeContextMenu }} = this onNodeContextMenu(e, this) }, onDragStart (e) { const { vcTree: { onNodeDragStart }} = this 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 { vcTree: { onNodeDragEnter }} = this e.preventDefault() e.stopPropagation() onNodeDragEnter(e, this) }, onDragOver (e) { const { vcTree: { onNodeDragOver }} = this e.preventDefault() e.stopPropagation() onNodeDragOver(e, this) }, onDragLeave (e) { const { vcTree: { onNodeDragLeave }} = this e.stopPropagation() onNodeDragLeave(e, this) }, onDragEnd (e) { const { vcTree: { onNodeDragEnd }} = this e.stopPropagation() this.setState({ dragNodeHighlight: false, }) onNodeDragEnd(e, this) }, onDrop (e) { const { vcTree: { onNodeDrop }} = this e.preventDefault() e.stopPropagation() this.setState({ dragNodeHighlight: false, }) onNodeDrop(e, this) }, // Disabled item still can be switch onExpand (e) { const { vcTree: { onNodeExpand }} = this onNodeExpand(e, this) }, getNodeChildren () { const { $slots: { default: children }} = this const originList = filterEmpty(children) const targetList = getNodeChildren(originList) if (originList.length !== targetList.length) { warnOnlyTreeNode() } return targetList }, getNodeState () { const { expanded } = this if (this.isLeaf2()) { return null } return expanded ? ICON_OPEN : ICON_CLOSE }, isLeaf2 () { const { isLeaf, loaded } = this const { vcTree: { loadData }} = this const hasChildren = this.getNodeChildren().length !== 0 if (isLeaf === false) { return false } return ( isLeaf || (!loadData && !hasChildren) || (loadData && loaded && !hasChildren) ) }, isDisabled () { const { disabled } = this const { vcTree: { disabled: treeDisabled }} = this // Follow the logic of Selectable if (disabled === false) { return false } return !!(treeDisabled || disabled) }, isSelectable () { const { selectable } = this const { vcTree: { selectable: treeSelectable }} = this // Ignore when selectable is undefined or null if (typeof selectable === 'boolean') { return selectable } return treeSelectable }, // Load data to avoid default expanded tree without data syncLoadData (props) { const { expanded, loading, loaded } = props const { vcTree: { onNodeLoad }} = this if (loading) return // read from state to avoid loadData at same time if (expanded && !this.isLeaf2()) { // We needn't reload data when has children in sync logic // It's only needed in node expanded const hasChildren = this.getNodeChildren().length !== 0 if (!hasChildren && !loaded) { onNodeLoad(this) } } }, // Switcher renderSwitcher () { const { expanded } = this const { vcTree: { prefixCls }} = this const switcherIcon = getComponentFromProp(this, 'switcherIcon') || getComponentFromProp(this.vcTree, 'switcherIcon') if (this.isLeaf2()) { return ( {typeof switcherIcon === 'function' ? cloneElement(switcherIcon, { props: { ...this.$props, isLeaf: true }}) : switcherIcon} ) } const switcherCls = classNames(`${prefixCls}-switcher`, `${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`) return ( {typeof switcherIcon === 'function' ? cloneElement(switcherIcon, { props: { ...this.$props, isLeaf: false }}) : switcherIcon} ) }, // Checkbox renderCheckbox () { const { checked, halfChecked, disableCheckbox } = this const { vcTree: { prefixCls, checkable }} = this const disabled = this.isDisabled() if (!checkable) return null // [Legacy] Custom element should be separate with `checkable` in future const $custom = typeof checkable !== 'boolean' ? checkable : null return ( {$custom} ) }, renderIcon () { const { loading } = this const { vcTree: { prefixCls }} = this return ( ) }, // Icon + Title renderSelector () { const { selected, icon, loading, dragNodeHighlight, $scopedSlots } = this const { vcTree: { prefixCls, showIcon, draggable, loadData }} = this const disabled = this.isDisabled() const title = getComponentFromProp(this, 'title') || defaultTitle const treeIcon = getComponentFromProp(this, 'icon') || $scopedSlots.icon 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 = {title} return ( {$icon}{$title} ) }, // Children list wrapped with `Animation` renderChildren () { const { expanded, pos } = this const { vcTree: { prefixCls, openTransitionName, openAnimation, renderTreeNode, }} = this let animProps = {} if (openTransitionName) { animProps = getTransitionProps(openTransitionName) } else if (typeof openAnimation === 'object') { animProps = { ...openAnimation } animProps.props = { css: false, ...animProps.props } } // Children TreeNode const nodeList = this.getNodeChildren() if (nodeList.length === 0) { return null } let $children if (expanded) { $children = (