import PropTypes from '../../_util/vue-types' import classNames from 'classnames' import warning from 'warning' import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes, mapChildren } from './util' import { initDefaultProps, getOptionProps, filterEmpty, getComponentFromProp } from '../../_util/props-util' import BaseMixin from '../../_util/BaseMixin' import getTransitionProps from '../../_util/getTransitionProps' function noop () {} const ICON_OPEN = 'open' const ICON_CLOSE = 'close' const defaultTitle = '---' let onlyTreeNodeWarned = false // Only accept TreeNode 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, }, {}), 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) }, watch: { expanded (val) { this.syncLoadData({ expanded: val }) }, }, methods: { onUpCheckConduct (treeNode, nodeChecked, nodeHalfChecked, e) { const { pos: nodePos } = getOptionProps(treeNode) const { eventKey, pos, checked, halfChecked } = this const { vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck, onCheckConductFinished }, vcTreeNode: { onUpCheckConduct } = {}, } = this // Stop conduct when current node is disabled if (isCheckDisabled(this)) { onCheckConductFinished(e) return } const children = this.getNodeChildren() let checkedCount = nodeChecked ? 1 : 0 // Statistic checked count children.forEach((node, index) => { const childPos = getPosition(pos, index) if (nodePos === childPos || isCheckDisabled(node)) { return } if (isKeyChecked(node.key || childPos)) { checkedCount += 1 } }) // Static enabled children count const enabledChildrenCount = children .filter(node => !isCheckDisabled(node)) .length // checkStrictly will not conduct check status const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount const nextHalfChecked = checkStrictly // propagated or child checked ? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked)) // Add into batch update if (checked !== nextChecked || halfChecked !== nextHalfChecked) { onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked) if (onUpCheckConduct) { onUpCheckConduct(this, nextChecked, nextHalfChecked, e) } else { // Flush all the update onCheckConductFinished(e) } } else { // Flush all the update onCheckConductFinished(e) } }, onDownCheckConduct (nodeChecked) { const { $slots } = this const children = $slots.default || [] const { vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this if (checkStrictly) return traverseTreeNodes(children, ({ node, key }) => { if (isCheckDisabled(node)) return false if (nodeChecked !== isKeyChecked(key)) { onBatchNodeCheck(key, nodeChecked, false) } }) }, 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, eventKey } = this const { vcTree: { checkable, onBatchNodeCheck, onCheckConductFinished }, vcTreeNode: { onUpCheckConduct } = {}, } = this if (!checkable || disableCheckbox) return e.preventDefault() const targetChecked = !checked onBatchNodeCheck(eventKey, targetChecked, false, this) // Children conduct this.onDownCheckConduct(targetChecked) // Parent conduct if (onUpCheckConduct) { onUpCheckConduct(this, targetChecked, false, e) } else { onCheckConductFinished(e) } }, 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 && !onlyTreeNodeWarned) { onlyTreeNodeWarned = true warning(false, 'Tree only accept TreeNode as children.') } 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 } = this const { vcTree: { onNodeLoad }} = this // 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) { onNodeLoad(this) } } }, // Switcher renderSwitcher () { const { expanded } = this const { vcTree: { prefixCls }} = this if (this.isLeaf2()) { return } return ( ) }, // 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 // [Legacy] Animation control const renderFirst = this.renderFirst this.renderFirst = 1 let transitionAppear = true if (!renderFirst && expanded) { transitionAppear = false } let animProps = {} if (openTransitionName) { animProps = getTransitionProps(openTransitionName, { appear: transitionAppear }) } else if (typeof openAnimation === 'object') { animProps = { ...openAnimation } animProps.props = { css: false, ...animProps.props } if (!transitionAppear) { delete animProps.props.appear } } // Children TreeNode const nodeList = this.getNodeChildren() if (nodeList.length === 0) { return null } let $children if (expanded) { $children = (
    {mapChildren(nodeList, (node, index) => ( renderTreeNode(node, index, pos) ))}
) } return ( {$children} ) }, }, render () { const { dragOver, dragOverGapTop, dragOverGapBottom, isLeaf, expanded, selected, checked, halfChecked, loading, } = this.$props const { vcTree: { prefixCls, filterTreeNode, draggable, }} = this const disabled = this.isDisabled() return (
  • {this.renderSwitcher()} {this.renderCheckbox()} {this.renderSelector()} {this.renderChildren()}
  • ) }, } TreeNode.isTreeNode = 1 export default TreeNode