add vc-tree
parent
5ea54b104a
commit
bbfbc8fe4f
|
@ -0,0 +1,110 @@
|
||||||
|
/* eslint no-console:0 */
|
||||||
|
/* eslint no-alert:0 */
|
||||||
|
import PropTypes from '../../_util/vue-types'
|
||||||
|
import Tree, { TreeNode } from '../index'
|
||||||
|
import '../assets/index.less'
|
||||||
|
import './basic.less'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
keys: PropTypes.array.def(['0-0-0-0']),
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
const keys = this.keys
|
||||||
|
return {
|
||||||
|
defaultExpandedKeys: keys,
|
||||||
|
defaultSelectedKeys: keys,
|
||||||
|
defaultCheckedKeys: keys,
|
||||||
|
switchIt: true,
|
||||||
|
showMore: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onExpand (expandedKeys) {
|
||||||
|
console.log('onExpand', expandedKeys, arguments)
|
||||||
|
},
|
||||||
|
onSelect (selectedKeys, info) {
|
||||||
|
console.log('selected', selectedKeys, info)
|
||||||
|
this.selKey = info.node.$options.propsData.eventKey
|
||||||
|
},
|
||||||
|
onCheck (checkedKeys, info) {
|
||||||
|
console.log('onCheck', checkedKeys, info)
|
||||||
|
},
|
||||||
|
onEdit () {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('current key: ', this.selKey)
|
||||||
|
}, 0)
|
||||||
|
},
|
||||||
|
onDel (e) {
|
||||||
|
if (!window.confirm('sure to delete?')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.stopPropagation()
|
||||||
|
},
|
||||||
|
toggleChildren () {
|
||||||
|
this.showMore = !this.showMore
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const customLabel = (<span class='cus-label'>
|
||||||
|
<span>operations: </span>
|
||||||
|
<span style={{ color: 'blue' }} onClick={this.onEdit}>Edit</span>
|
||||||
|
<label onClick={(e) => e.stopPropagation()}><input type='checkbox' /> checked</label>
|
||||||
|
<span style={{ color: 'red' }} onClick={this.onDel}>Delete</span>
|
||||||
|
</span>)
|
||||||
|
return (<div style={{ margin: '0 20px' }}>
|
||||||
|
<h2>simple</h2>
|
||||||
|
{/* <Tree
|
||||||
|
class='myCls' showLine checkable defaultExpandAll
|
||||||
|
defaultExpandedKeys={this.defaultExpandedKeys}
|
||||||
|
onExpand={this.onExpand}
|
||||||
|
defaultSelectedKeys={this.defaultSelectedKeys}
|
||||||
|
defaultCheckedKeys={this.defaultCheckedKeys}
|
||||||
|
onSelect={this.onSelect} onCheck={this.onCheck}
|
||||||
|
>
|
||||||
|
<TreeNode title='parent 1' key='0-0'>
|
||||||
|
<TreeNode title={customLabel} key='0-0-0'>
|
||||||
|
<TreeNode title='leaf' key='0-0-0-0' />
|
||||||
|
<TreeNode title='leaf' key='0-0-0-1' />
|
||||||
|
</TreeNode>
|
||||||
|
<TreeNode title='parent 1-1' key='0-0-1'>
|
||||||
|
<TreeNode title='parent 1-1-0' key='0-0-1-0' disableCheckbox />
|
||||||
|
<TreeNode title='parent 1-1-1' key='0-0-1-1' />
|
||||||
|
</TreeNode>
|
||||||
|
<TreeNode title='parent 1-2' key='0-0-2' disabled>
|
||||||
|
<TreeNode title='parent 1-2-0' key='0-0-2-0' disabled />
|
||||||
|
<TreeNode title='parent 1-2-1' key='0-0-2-1' />
|
||||||
|
</TreeNode>
|
||||||
|
</TreeNode>
|
||||||
|
</Tree> */}
|
||||||
|
|
||||||
|
<h2>Check on Click TreeNode</h2>
|
||||||
|
<button onClick={this.toggleChildren}>toggle children</button>
|
||||||
|
<Tree
|
||||||
|
class='myCls'
|
||||||
|
showLine
|
||||||
|
checkable
|
||||||
|
selectable={ false }
|
||||||
|
defaultExpandAll
|
||||||
|
onExpand={this.onExpand}
|
||||||
|
defaultSelectedKeys={this.defaultSelectedKeys}
|
||||||
|
defaultCheckedKeys={this.defaultCheckedKeys}
|
||||||
|
onSelect={this.onSelect}
|
||||||
|
onCheck={this.onCheck}
|
||||||
|
>
|
||||||
|
<TreeNode title='parent 1' key='0-0'>
|
||||||
|
<TreeNode title='parent 1-1' key='0-0-1'>
|
||||||
|
<TreeNode title='parent 1-1-0' key='0-0-1-0' disableCheckbox />
|
||||||
|
<TreeNode title='parent 1-1-1' key='0-0-1-1' />
|
||||||
|
</TreeNode>
|
||||||
|
{this.showMore ? <TreeNode title='parent 2-1' key='0-0-2'>
|
||||||
|
<TreeNode title='parent 2-1-0' key='0-0-2-0' disableCheckbox />
|
||||||
|
<TreeNode title='parent 2-1-1' key='0-0-2-1' />
|
||||||
|
</TreeNode> : null}
|
||||||
|
</TreeNode>
|
||||||
|
</Tree>
|
||||||
|
</div>)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
.rc-tree li a.rc-tree-node-selected{
|
||||||
|
.cus-label {
|
||||||
|
background-color: white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,9 @@
|
||||||
import PropTypes from '../../_util/vue-types'
|
import PropTypes from '../../_util/vue-types'
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import warning from 'warning'
|
import warning from 'warning'
|
||||||
import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util'
|
import { initDefaultProps, getOptionProps } from '../../_util/props-util'
|
||||||
|
import { cloneElement } from '../../_util/vnode'
|
||||||
|
import BaseMixin from '../../_util/BaseMixin'
|
||||||
import {
|
import {
|
||||||
traverseTreeNodes, getStrictlyValue,
|
traverseTreeNodes, getStrictlyValue,
|
||||||
getFullKeyList, getPosition, getDragNodesKeys,
|
getFullKeyList, getPosition, getDragNodesKeys,
|
||||||
|
@ -54,6 +56,8 @@ export const contextTypes = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const Tree = {
|
const Tree = {
|
||||||
|
name: 'Tree',
|
||||||
|
mixins: [BaseMixin],
|
||||||
props: initDefaultProps({
|
props: initDefaultProps({
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
showLine: PropTypes.bool,
|
showLine: PropTypes.bool,
|
||||||
|
@ -79,9 +83,9 @@ const Tree = {
|
||||||
]),
|
]),
|
||||||
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
|
defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
selectedKeys: PropTypes.arrayOf(PropTypes.string),
|
||||||
//onExpand: PropTypes.func,
|
// onExpand: PropTypes.func,
|
||||||
//onCheck: PropTypes.func,
|
// onCheck: PropTypes.func,
|
||||||
//onSelect: PropTypes.func,
|
// onSelect: PropTypes.func,
|
||||||
loadData: PropTypes.func,
|
loadData: PropTypes.func,
|
||||||
// onMouseEnter: PropTypes.func,
|
// onMouseEnter: PropTypes.func,
|
||||||
// onMouseLeave: PropTypes.func,
|
// onMouseLeave: PropTypes.func,
|
||||||
|
@ -95,6 +99,7 @@ const Tree = {
|
||||||
filterTreeNode: PropTypes.func,
|
filterTreeNode: PropTypes.func,
|
||||||
openTransitionName: PropTypes.string,
|
openTransitionName: PropTypes.string,
|
||||||
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||||
|
children: PropTypes.any,
|
||||||
}, {
|
}, {
|
||||||
prefixCls: 'rc-tree',
|
prefixCls: 'rc-tree',
|
||||||
showLine: false,
|
showLine: false,
|
||||||
|
@ -122,489 +127,476 @@ const Tree = {
|
||||||
defaultCheckedKeys,
|
defaultCheckedKeys,
|
||||||
defaultSelectedKeys,
|
defaultSelectedKeys,
|
||||||
} = props
|
} = props
|
||||||
|
const children = this.$slots.default
|
||||||
// Sync state with props
|
// Sync state with props
|
||||||
const { checkedKeys = [], halfCheckedKeys = [] } =
|
const { checkedKeys = [], halfCheckedKeys = [] } =
|
||||||
calcCheckedKeys(defaultCheckedKeys, props) || {}
|
calcCheckedKeys(defaultCheckedKeys, props, children) || {}
|
||||||
|
|
||||||
// Cache for check status to optimize
|
// Cache for check status to optimize
|
||||||
this.checkedBatch = null
|
this.checkedBatch = null
|
||||||
|
this.propsToStateMap = {
|
||||||
|
expandedKeys: 'sExpandedKeys',
|
||||||
|
selectedKeys: 'sSelectedKeys',
|
||||||
|
checkedKeys: 'sCheckedKeys',
|
||||||
|
halfCheckedKeys: 'sHalfCheckedKeys',
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
sExpandedKeys: defaultExpandAll
|
sExpandedKeys: defaultExpandAll
|
||||||
? getFullKeyList(this.$slots.default)
|
? getFullKeyList(children)
|
||||||
: calcExpandedKeys(defaultExpandedKeys, props),
|
: calcExpandedKeys(defaultExpandedKeys, props, children),
|
||||||
sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props),
|
sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props, children),
|
||||||
sCheckedKeys,
|
sCheckedKeys: checkedKeys,
|
||||||
sHalfCheckedKeys,
|
sHalfCheckedKeys: halfCheckedKeys,
|
||||||
|
|
||||||
...(this.getSyncProps(props) || {}),
|
...(this.getSyncProps(props) || {}),
|
||||||
}
|
dragOverNodeKey: '',
|
||||||
|
dropPosition: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
provide: {
|
provide () {
|
||||||
rcTree: this,
|
return {
|
||||||
},
|
vcTree: this,
|
||||||
|
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
// React 16 will not trigger update if new state is null
|
|
||||||
this.setState(this.getSyncProps(nextProps, this.props))
|
|
||||||
},
|
|
||||||
|
|
||||||
onNodeDragStart (event, node) {
|
|
||||||
const { expandedKeys } = this.state
|
|
||||||
const { onDragStart } = this.props
|
|
||||||
const { eventKey, children } = node.props
|
|
||||||
|
|
||||||
this.dragNode = node
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
dragNodesKeys: getDragNodesKeys(children, node),
|
|
||||||
expandedKeys: arrDel(expandedKeys, eventKey),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (onDragStart) {
|
|
||||||
onDragStart({ event, node })
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
watch: {
|
||||||
* [Legacy] Select handler is less small than node,
|
children (val) {
|
||||||
* so that this will trigger when drag enter node or select handler.
|
const { checkedKeys = [], halfCheckedKeys = [] } = calcCheckedKeys(this.checkedKeys || this.sCheckedKeys, this.$props, this.$slots.default) || {}
|
||||||
* This is a little tricky if customize css without padding.
|
this.sCheckedKeys = checkedKeys
|
||||||
* Better for use mouse move event to refresh drag state.
|
this.sHalfCheckedKeys = halfCheckedKeys
|
||||||
* But let's just keep it to avoid event trigger logic change.
|
},
|
||||||
*/
|
expandedKeys (val) {
|
||||||
onNodeDragEnter = (event, node) => {
|
this.sExpandedKeys = calcExpandedKeys(this.expandedKeys, this.$props, this.$slots.default)
|
||||||
const { expandedKeys } = this.state
|
},
|
||||||
const { onDragEnter } = this.props
|
selectedKeys (val) {
|
||||||
const { pos, eventKey } = node.props
|
this.sSelectedKeys = calcSelectedKeys(this.selectedKeys, this.$props, this.$slots.default)
|
||||||
|
},
|
||||||
|
checkedKeys (val) {
|
||||||
|
const { checkedKeys = [], halfCheckedKeys = [] } = calcCheckedKeys(this.checkedKeys, this.$props, this.$slots.default) || {}
|
||||||
|
this.sCheckedKeys = checkedKeys
|
||||||
|
this.sHalfCheckedKeys = halfCheckedKeys
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
const dropPosition = calcDropPosition(event, node)
|
// componentWillReceiveProps (nextProps) {
|
||||||
|
// // React 16 will not trigger update if new state is null
|
||||||
|
// this.setState(this.getSyncProps(nextProps, this.props))
|
||||||
|
// },
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
onNodeDragStart (event, node) {
|
||||||
|
const { sExpandedKeys } = this
|
||||||
|
const { eventKey, children } = node.props
|
||||||
|
|
||||||
|
this.dragNode = node
|
||||||
|
|
||||||
// Skip if drag node is self
|
|
||||||
if (
|
|
||||||
this.dragNode.props.eventKey === eventKey &&
|
|
||||||
dropPosition === 0
|
|
||||||
) {
|
|
||||||
this.setState({
|
this.setState({
|
||||||
dragOverNodeKey: '',
|
dragNodesKeys: getDragNodesKeys(children, node),
|
||||||
dropPosition: null,
|
sExpandedKeys: arrDel(sExpandedKeys, eventKey),
|
||||||
})
|
})
|
||||||
return
|
this.__emit('dragstart', { event, node })
|
||||||
}
|
},
|
||||||
|
|
||||||
// Ref: https://github.com/react-component/tree/issues/132
|
/**
|
||||||
// Add timeout to let onDragLevel fire before onDragEnter,
|
* [Legacy] Select handler is less small than node,
|
||||||
// so that we can clean drag props for onDragLeave node.
|
* so that this will trigger when drag enter node or select handler.
|
||||||
// Macro task for this:
|
* This is a little tricky if customize css without padding.
|
||||||
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
|
* Better for use mouse move event to refresh drag state.
|
||||||
setTimeout(() => {
|
* But let's just keep it to avoid event trigger logic change.
|
||||||
// Update drag over node
|
*/
|
||||||
this.setState({
|
onNodeDragEnter (event, node) {
|
||||||
dragOverNodeKey: eventKey,
|
const { sExpandedKeys } = this
|
||||||
dropPosition,
|
const { pos, eventKey } = node.props
|
||||||
})
|
|
||||||
|
|
||||||
// Side effect for delay drag
|
const dropPosition = calcDropPosition(event, node)
|
||||||
if (!this.delayedDragEnterLogic) {
|
|
||||||
this.delayedDragEnterLogic = {}
|
// Skip if drag node is self
|
||||||
}
|
if (
|
||||||
Object.keys(this.delayedDragEnterLogic).forEach((key) => {
|
this.dragNode.props.eventKey === eventKey &&
|
||||||
clearTimeout(this.delayedDragEnterLogic[key])
|
dropPosition === 0
|
||||||
})
|
) {
|
||||||
this.delayedDragEnterLogic[pos] = setTimeout(() => {
|
|
||||||
const newExpandedKeys = arrAdd(expandedKeys, eventKey)
|
|
||||||
this.setState({
|
this.setState({
|
||||||
expandedKeys: newExpandedKeys,
|
dragOverNodeKey: '',
|
||||||
|
dropPosition: null,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ref: https://github.com/react-component/tree/issues/132
|
||||||
|
// Add timeout to let onDragLevel fire before onDragEnter,
|
||||||
|
// so that we can clean drag props for onDragLeave node.
|
||||||
|
// Macro task for this:
|
||||||
|
// https://html.spec.whatwg.org/multipage/webappapis.html#clean-up-after-running-script
|
||||||
|
setTimeout(() => {
|
||||||
|
// Update drag over node
|
||||||
|
this.setState({
|
||||||
|
dragOverNodeKey: eventKey,
|
||||||
|
dropPosition,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (onDragEnter) {
|
// Side effect for delay drag
|
||||||
onDragEnter({ event, node, expandedKeys: newExpandedKeys })
|
if (!this.delayedDragEnterLogic) {
|
||||||
}
|
this.delayedDragEnterLogic = {}
|
||||||
}, 400)
|
|
||||||
}, 0)
|
|
||||||
};
|
|
||||||
onNodeDragOver = (event, node) => {
|
|
||||||
const { onDragOver } = this.props
|
|
||||||
if (onDragOver) {
|
|
||||||
onDragOver({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onNodeDragLeave = (event, node) => {
|
|
||||||
const { onDragLeave } = this.props
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
dragOverNodeKey: '',
|
|
||||||
})
|
|
||||||
|
|
||||||
if (onDragLeave) {
|
|
||||||
onDragLeave({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onNodeDragEnd = (event, node) => {
|
|
||||||
const { onDragEnd } = this.props
|
|
||||||
this.setState({
|
|
||||||
dragOverNodeKey: '',
|
|
||||||
})
|
|
||||||
if (onDragEnd) {
|
|
||||||
onDragEnd({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
onNodeDrop = (event, node) => {
|
|
||||||
const { dragNodesKeys, dropPosition } = this.state
|
|
||||||
const { onDrop } = this.props
|
|
||||||
const { eventKey, pos } = node.props
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
dragOverNodeKey: '',
|
|
||||||
dropNodeKey: eventKey,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (dragNodesKeys.indexOf(eventKey) !== -1) {
|
|
||||||
warning(false, 'Can not drop to dragNode(include it\'s children node)')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const posArr = posToArr(pos)
|
|
||||||
|
|
||||||
const dropResult = {
|
|
||||||
event,
|
|
||||||
node,
|
|
||||||
dragNode: this.dragNode,
|
|
||||||
dragNodesKeys: dragNodesKeys.slice(),
|
|
||||||
dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dropPosition !== 0) {
|
|
||||||
dropResult.dropToGap = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onDrop) {
|
|
||||||
onDrop(dropResult)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeSelect = (e, treeNode) => {
|
|
||||||
let { selectedKeys } = this.state
|
|
||||||
const { onSelect, multiple, children } = this.props
|
|
||||||
const { selected, eventKey } = treeNode.props
|
|
||||||
const targetSelected = !selected
|
|
||||||
|
|
||||||
// Update selected keys
|
|
||||||
if (!targetSelected) {
|
|
||||||
selectedKeys = arrDel(selectedKeys, eventKey)
|
|
||||||
} else if (!multiple) {
|
|
||||||
selectedKeys = [eventKey]
|
|
||||||
} else {
|
|
||||||
selectedKeys = arrAdd(selectedKeys, eventKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// [Legacy] Not found related usage in doc or upper libs
|
|
||||||
// [Legacy] TODO: add optimize prop to skip node process
|
|
||||||
const selectedNodes = []
|
|
||||||
if (selectedKeys.length) {
|
|
||||||
traverseTreeNodes(children, ({ node, key }) => {
|
|
||||||
if (selectedKeys.indexOf(key) !== -1) {
|
|
||||||
selectedNodes.push(node)
|
|
||||||
}
|
}
|
||||||
|
Object.keys(this.delayedDragEnterLogic).forEach((key) => {
|
||||||
|
clearTimeout(this.delayedDragEnterLogic[key])
|
||||||
|
})
|
||||||
|
this.delayedDragEnterLogic[pos] = setTimeout(() => {
|
||||||
|
const newExpandedKeys = arrAdd(sExpandedKeys, eventKey)
|
||||||
|
this.setState({
|
||||||
|
sExpandedKeys: newExpandedKeys,
|
||||||
|
})
|
||||||
|
this.__emit('dragenter', { event, node, expandedKeys: newExpandedKeys })
|
||||||
|
}, 400)
|
||||||
|
}, 0)
|
||||||
|
},
|
||||||
|
onNodeDragOver (event, node) {
|
||||||
|
this.__emit('dragover', { event, node })
|
||||||
|
},
|
||||||
|
onNodeDragLeave (event, node) {
|
||||||
|
this.setState({
|
||||||
|
dragOverNodeKey: '',
|
||||||
})
|
})
|
||||||
}
|
this.__emit('dragleave', { event, node })
|
||||||
|
},
|
||||||
|
onNodeDragEnd (event, node) {
|
||||||
|
this.setState({
|
||||||
|
dragOverNodeKey: '',
|
||||||
|
})
|
||||||
|
this.__emit('dragend', { event, node })
|
||||||
|
},
|
||||||
|
onNodeDrop (event, node) {
|
||||||
|
const { dragNodesKeys, dropPosition } = this
|
||||||
|
|
||||||
this.setUncontrolledState({ selectedKeys })
|
const { eventKey, pos } = node.props
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
dragOverNodeKey: '',
|
||||||
|
dropNodeKey: eventKey,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (dragNodesKeys.indexOf(eventKey) !== -1) {
|
||||||
|
warning(false, 'Can not drop to dragNode(include it\'s children node)')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const posArr = posToArr(pos)
|
||||||
|
|
||||||
|
const dropResult = {
|
||||||
|
event,
|
||||||
|
node,
|
||||||
|
dragNode: this.dragNode,
|
||||||
|
dragNodesKeys: dragNodesKeys.slice(),
|
||||||
|
dropPosition: dropPosition + Number(posArr[posArr.length - 1]),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dropPosition !== 0) {
|
||||||
|
dropResult.dropToGap = true
|
||||||
|
}
|
||||||
|
this.__emit('drop', dropResult)
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodeSelect (e, treeNode) {
|
||||||
|
const { sSelectedKeys, multiple, $slots: { default: children }} = this
|
||||||
|
const { selected, eventKey } = getOptionProps(treeNode)
|
||||||
|
const targetSelected = !selected
|
||||||
|
let selectedKeys = sSelectedKeys
|
||||||
|
// Update selected keys
|
||||||
|
if (!targetSelected) {
|
||||||
|
selectedKeys = arrDel(selectedKeys, eventKey)
|
||||||
|
} else if (!multiple) {
|
||||||
|
selectedKeys = [eventKey]
|
||||||
|
} else {
|
||||||
|
selectedKeys = arrAdd(selectedKeys, eventKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// [Legacy] Not found related usage in doc or upper libs
|
||||||
|
// [Legacy] TODO: add optimize prop to skip node process
|
||||||
|
const selectedNodes = []
|
||||||
|
if (selectedKeys.length) {
|
||||||
|
traverseTreeNodes(children, ({ node, key }) => {
|
||||||
|
if (selectedKeys.indexOf(key) !== -1) {
|
||||||
|
selectedNodes.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setUncontrolledState({ selectedKeys })
|
||||||
|
|
||||||
if (onSelect) {
|
|
||||||
const eventObj = {
|
const eventObj = {
|
||||||
event: 'select',
|
event: 'select',
|
||||||
selected: targetSelected,
|
selected: targetSelected,
|
||||||
node: treeNode,
|
node: treeNode,
|
||||||
selectedNodes,
|
selectedNodes,
|
||||||
}
|
}
|
||||||
onSelect(selectedKeys, eventObj)
|
this.__emit('select', selectedKeys, eventObj)
|
||||||
}
|
},
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This will cache node check status to optimize update process.
|
* This will cache node check status to optimize update process.
|
||||||
* When Tree get trigger `onCheckConductFinished` will flush all the update.
|
* When Tree get trigger `onCheckConductFinished` will flush all the update.
|
||||||
*/
|
*/
|
||||||
onBatchNodeCheck = (key, checked, halfChecked, startNode) => {
|
onBatchNodeCheck (key, checked, halfChecked, startNode) {
|
||||||
if (startNode) {
|
if (startNode) {
|
||||||
this.checkedBatch = {
|
this.checkedBatch = {
|
||||||
treeNode: startNode,
|
treeNode: startNode,
|
||||||
checked,
|
checked,
|
||||||
list: [],
|
list: [],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// This code should never called
|
// This code should never called
|
||||||
if (!this.checkedBatch) {
|
if (!this.checkedBatch) {
|
||||||
this.checkedBatch = {
|
this.checkedBatch = {
|
||||||
list: [],
|
list: [],
|
||||||
|
}
|
||||||
|
warning(
|
||||||
|
false,
|
||||||
|
'Checked batch not init. This should be a bug. Please fire a issue.'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkedBatch.list.push({ key, checked, halfChecked })
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When top `onCheckConductFinished` called, will execute all batch update.
|
||||||
|
* And trigger `onCheck` event.
|
||||||
|
*/
|
||||||
|
onCheckConductFinished () {
|
||||||
|
const { sCheckedKeys, sHalfCheckedKeys, checkStrictly, $slots: { default: children }} = this
|
||||||
|
|
||||||
|
// Use map to optimize update speed
|
||||||
|
const checkedKeySet = {}
|
||||||
|
const halfCheckedKeySet = {}
|
||||||
|
|
||||||
|
sCheckedKeys.forEach(key => {
|
||||||
|
checkedKeySet[key] = true
|
||||||
|
})
|
||||||
|
sHalfCheckedKeys.forEach(key => {
|
||||||
|
halfCheckedKeySet[key] = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// Batch process
|
||||||
|
this.checkedBatch.list.forEach(({ key, checked, halfChecked }) => {
|
||||||
|
checkedKeySet[key] = checked
|
||||||
|
halfCheckedKeySet[key] = halfChecked
|
||||||
|
})
|
||||||
|
const newCheckedKeys = Object.keys(checkedKeySet).filter(key => checkedKeySet[key])
|
||||||
|
const newHalfCheckedKeys = Object.keys(halfCheckedKeySet).filter(key => halfCheckedKeySet[key])
|
||||||
|
|
||||||
|
// Trigger onChecked
|
||||||
|
let selectedObj
|
||||||
|
|
||||||
|
const eventObj = {
|
||||||
|
event: 'check',
|
||||||
|
node: this.checkedBatch.treeNode,
|
||||||
|
checked: this.checkedBatch.checked,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (checkStrictly) {
|
||||||
|
selectedObj = getStrictlyValue(newCheckedKeys, newHalfCheckedKeys)
|
||||||
|
|
||||||
|
// [Legacy] TODO: add optimize prop to skip node process
|
||||||
|
eventObj.checkedNodes = []
|
||||||
|
traverseTreeNodes(children, ({ node, key }) => {
|
||||||
|
if (checkedKeySet[key]) {
|
||||||
|
eventObj.checkedNodes.push(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setUncontrolledState({ checkedKeys: newCheckedKeys })
|
||||||
|
} else {
|
||||||
|
selectedObj = newCheckedKeys
|
||||||
|
|
||||||
|
// [Legacy] TODO: add optimize prop to skip node process
|
||||||
|
eventObj.checkedNodes = []
|
||||||
|
eventObj.checkedNodesPositions = [] // [Legacy] TODO: not in API
|
||||||
|
eventObj.halfCheckedKeys = newHalfCheckedKeys // [Legacy] TODO: not in API
|
||||||
|
traverseTreeNodes(children, ({ node, pos, key }) => {
|
||||||
|
if (checkedKeySet[key]) {
|
||||||
|
eventObj.checkedNodes.push(node)
|
||||||
|
eventObj.checkedNodesPositions.push({ node, pos })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setUncontrolledState({
|
||||||
|
checkedKeys: newCheckedKeys,
|
||||||
|
halfCheckedKeys: newHalfCheckedKeys,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
this.__emit('check', selectedObj, eventObj)
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
this.checkedBatch = null
|
||||||
|
},
|
||||||
|
|
||||||
|
onNodeExpand (e, treeNode) {
|
||||||
|
const { sExpandedKeys, loadData } = this
|
||||||
|
let expandedKeys = [...sExpandedKeys]
|
||||||
|
const { eventKey, expanded } = getOptionProps(treeNode)
|
||||||
|
|
||||||
|
// Update selected keys
|
||||||
|
const index = expandedKeys.indexOf(eventKey)
|
||||||
|
const targetExpanded = !expanded
|
||||||
|
|
||||||
warning(
|
warning(
|
||||||
false,
|
(expanded && index !== -1) || (!expanded && index === -1)
|
||||||
'Checked batch not init. This should be a bug. Please fire a issue.'
|
, 'Expand state not sync with index check')
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.checkedBatch.list.push({ key, checked, halfChecked })
|
if (targetExpanded) {
|
||||||
};
|
expandedKeys = arrAdd(expandedKeys, eventKey)
|
||||||
|
} else {
|
||||||
/**
|
expandedKeys = arrDel(expandedKeys, eventKey)
|
||||||
* When top `onCheckConductFinished` called, will execute all batch update.
|
|
||||||
* And trigger `onCheck` event.
|
|
||||||
*/
|
|
||||||
onCheckConductFinished = () => {
|
|
||||||
const { checkedKeys, halfCheckedKeys } = this.state
|
|
||||||
const { onCheck, checkStrictly, children } = this.props
|
|
||||||
|
|
||||||
// Use map to optimize update speed
|
|
||||||
const checkedKeySet = {}
|
|
||||||
const halfCheckedKeySet = {}
|
|
||||||
|
|
||||||
checkedKeys.forEach(key => {
|
|
||||||
checkedKeySet[key] = true
|
|
||||||
})
|
|
||||||
halfCheckedKeys.forEach(key => {
|
|
||||||
halfCheckedKeySet[key] = true
|
|
||||||
})
|
|
||||||
|
|
||||||
// Batch process
|
|
||||||
this.checkedBatch.list.forEach(({ key, checked, halfChecked }) => {
|
|
||||||
checkedKeySet[key] = checked
|
|
||||||
halfCheckedKeySet[key] = halfChecked
|
|
||||||
})
|
|
||||||
const newCheckedKeys = Object.keys(checkedKeySet).filter(key => checkedKeySet[key])
|
|
||||||
const newHalfCheckedKeys = Object.keys(halfCheckedKeySet).filter(key => halfCheckedKeySet[key])
|
|
||||||
|
|
||||||
// Trigger onChecked
|
|
||||||
let selectedObj
|
|
||||||
|
|
||||||
const eventObj = {
|
|
||||||
event: 'check',
|
|
||||||
node: this.checkedBatch.treeNode,
|
|
||||||
checked: this.checkedBatch.checked,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkStrictly) {
|
|
||||||
selectedObj = getStrictlyValue(newCheckedKeys, newHalfCheckedKeys)
|
|
||||||
|
|
||||||
// [Legacy] TODO: add optimize prop to skip node process
|
|
||||||
eventObj.checkedNodes = []
|
|
||||||
traverseTreeNodes(children, ({ node, key }) => {
|
|
||||||
if (checkedKeySet[key]) {
|
|
||||||
eventObj.checkedNodes.push(node)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setUncontrolledState({ checkedKeys: newCheckedKeys })
|
|
||||||
} else {
|
|
||||||
selectedObj = newCheckedKeys
|
|
||||||
|
|
||||||
// [Legacy] TODO: add optimize prop to skip node process
|
|
||||||
eventObj.checkedNodes = []
|
|
||||||
eventObj.checkedNodesPositions = [] // [Legacy] TODO: not in API
|
|
||||||
eventObj.halfCheckedKeys = newHalfCheckedKeys // [Legacy] TODO: not in API
|
|
||||||
traverseTreeNodes(children, ({ node, pos, key }) => {
|
|
||||||
if (checkedKeySet[key]) {
|
|
||||||
eventObj.checkedNodes.push(node)
|
|
||||||
eventObj.checkedNodesPositions.push({ node, pos })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.setUncontrolledState({
|
|
||||||
checkedKeys: newCheckedKeys,
|
|
||||||
halfCheckedKeys: newHalfCheckedKeys,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onCheck) {
|
|
||||||
onCheck(selectedObj, eventObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
this.checkedBatch = null
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeExpand = (e, treeNode) => {
|
|
||||||
let { expandedKeys } = this.state
|
|
||||||
const { onExpand, loadData } = this.props
|
|
||||||
const { eventKey, expanded } = treeNode.props
|
|
||||||
|
|
||||||
// Update selected keys
|
|
||||||
const index = expandedKeys.indexOf(eventKey)
|
|
||||||
const targetExpanded = !expanded
|
|
||||||
|
|
||||||
warning(
|
|
||||||
(expanded && index !== -1) || (!expanded && index === -1)
|
|
||||||
, 'Expand state not sync with index check')
|
|
||||||
|
|
||||||
if (targetExpanded) {
|
|
||||||
expandedKeys = arrAdd(expandedKeys, eventKey)
|
|
||||||
} else {
|
|
||||||
expandedKeys = arrDel(expandedKeys, eventKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setUncontrolledState({ expandedKeys })
|
|
||||||
|
|
||||||
if (onExpand) {
|
|
||||||
onExpand(expandedKeys, { node: treeNode, expanded: targetExpanded })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Async Load data
|
|
||||||
if (targetExpanded && loadData) {
|
|
||||||
return loadData(treeNode).then(() => {
|
|
||||||
// [Legacy] Refresh logic
|
|
||||||
this.setUncontrolledState({ expandedKeys })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeMouseEnter = (event, node) => {
|
|
||||||
const { onMouseEnter } = this.props
|
|
||||||
if (onMouseEnter) {
|
|
||||||
onMouseEnter({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeMouseLeave = (event, node) => {
|
|
||||||
const { onMouseLeave } = this.props
|
|
||||||
if (onMouseLeave) {
|
|
||||||
onMouseLeave({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onNodeContextMenu = (event, node) => {
|
|
||||||
const { onRightClick } = this.props
|
|
||||||
if (onRightClick) {
|
|
||||||
event.preventDefault()
|
|
||||||
onRightClick({ event, node })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sync state with props if needed
|
|
||||||
*/
|
|
||||||
getSyncProps = (props = {}, prevProps) => {
|
|
||||||
let needSync = false
|
|
||||||
const newState = {}
|
|
||||||
const myPrevProps = prevProps || {}
|
|
||||||
|
|
||||||
function checkSync (name) {
|
|
||||||
if (props[name] !== myPrevProps[name]) {
|
|
||||||
needSync = true
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Children change will affect check box status.
|
this.setUncontrolledState({ expandedKeys })
|
||||||
// And no need to check when prev props not provided
|
this.__emit('expand', expandedKeys, { node: treeNode, expanded: targetExpanded })
|
||||||
if (prevProps && checkSync('children')) {
|
|
||||||
const { checkedKeys = [], halfCheckedKeys = [] } =
|
|
||||||
calcCheckedKeys(props.checkedKeys || this.state.checkedKeys, props) || {}
|
|
||||||
newState.checkedKeys = checkedKeys
|
|
||||||
newState.halfCheckedKeys = halfCheckedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
if (checkSync('expandedKeys')) {
|
// Async Load data
|
||||||
newState.expandedKeys = calcExpandedKeys(props.expandedKeys, props)
|
if (targetExpanded && loadData) {
|
||||||
}
|
return loadData(treeNode).then(() => {
|
||||||
|
// [Legacy] Refresh logic
|
||||||
|
this.setUncontrolledState({ expandedKeys })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (checkSync('selectedKeys')) {
|
return null
|
||||||
newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props)
|
},
|
||||||
}
|
|
||||||
|
|
||||||
if (checkSync('checkedKeys')) {
|
onNodeMouseEnter (event, node) {
|
||||||
const { checkedKeys = [], halfCheckedKeys = [] } =
|
this.__emit('mouseenter', { event, node })
|
||||||
calcCheckedKeys(props.checkedKeys, props) || {}
|
},
|
||||||
newState.checkedKeys = checkedKeys
|
|
||||||
newState.halfCheckedKeys = halfCheckedKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
return needSync ? newState : null
|
onNodeMouseLeave (event, node) {
|
||||||
};
|
this.__emit('mouseleave', { event, node })
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
onNodeContextMenu (event, node) {
|
||||||
* Only update the value which is not in props
|
event.preventDefault()
|
||||||
*/
|
this.__emit('rightClick', { event, node })
|
||||||
setUncontrolledState = (state) => {
|
},
|
||||||
let needSync = false
|
|
||||||
const newState = {}
|
|
||||||
|
|
||||||
Object.keys(state).forEach(name => {
|
/**
|
||||||
if (name in this.props) return
|
* Sync state with props if needed
|
||||||
|
*/
|
||||||
|
getSyncProps (props = {}, prevProps) {
|
||||||
|
let needSync = false
|
||||||
|
const newState = {}
|
||||||
|
const myPrevProps = prevProps || {}
|
||||||
|
const children = this.$slots.default
|
||||||
|
function checkSync (name) {
|
||||||
|
if (props[name] !== myPrevProps[name]) {
|
||||||
|
needSync = true
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
needSync = true
|
// Children change will affect check box status.
|
||||||
newState[name] = state[name]
|
// And no need to check when prev props not provided
|
||||||
})
|
if (prevProps && checkSync('children')) {
|
||||||
|
const { checkedKeys = [], halfCheckedKeys = [] } =
|
||||||
|
calcCheckedKeys(props.checkedKeys || this.sCheckedKeys, props, children) || {}
|
||||||
|
newState.sCheckedKeys = checkedKeys
|
||||||
|
newState.sHalfCheckedKeys = halfCheckedKeys
|
||||||
|
}
|
||||||
|
|
||||||
this.setState(needSync ? newState : null)
|
if (checkSync('expandedKeys')) {
|
||||||
};
|
newState.sExpandedKeys = calcExpandedKeys(props.expandedKeys, props, children)
|
||||||
|
}
|
||||||
|
|
||||||
isKeyChecked = (key) => {
|
if (checkSync('selectedKeys')) {
|
||||||
const { checkedKeys = [] } = this.state
|
newState.sSelectedKeys = calcSelectedKeys(props.selectedKeys, props, children)
|
||||||
return checkedKeys.indexOf(key) !== -1
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
if (checkSync('checkedKeys')) {
|
||||||
* [Legacy] Original logic use `key` as tracking clue.
|
const { checkedKeys = [], halfCheckedKeys = [] } =
|
||||||
* We have to use `cloneElement` to pass `key`.
|
calcCheckedKeys(props.checkedKeys, props, children) || {}
|
||||||
*/
|
newState.sCheckedKeys = checkedKeys
|
||||||
renderTreeNode = (child, index, level = 0) => {
|
newState.sHalfCheckedKeys = halfCheckedKeys
|
||||||
const {
|
}
|
||||||
expandedKeys = [], selectedKeys = [], halfCheckedKeys = [],
|
|
||||||
dragOverNodeKey, dropPosition,
|
|
||||||
} = this.state
|
|
||||||
const {} = this.props
|
|
||||||
const pos = getPosition(level, index)
|
|
||||||
const key = child.key || pos
|
|
||||||
|
|
||||||
return React.cloneElement(child, {
|
return needSync ? newState : null
|
||||||
eventKey: key,
|
},
|
||||||
expanded: expandedKeys.indexOf(key) !== -1,
|
|
||||||
selected: selectedKeys.indexOf(key) !== -1,
|
|
||||||
checked: this.isKeyChecked(key),
|
|
||||||
halfChecked: halfCheckedKeys.indexOf(key) !== -1,
|
|
||||||
pos,
|
|
||||||
|
|
||||||
// [Legacy] Drag props
|
/**
|
||||||
dragOver: dragOverNodeKey === key && dropPosition === 0,
|
* Only update the value which is not in props
|
||||||
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
|
*/
|
||||||
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
|
setUncontrolledState (state) {
|
||||||
})
|
let needSync = false
|
||||||
};
|
const newState = {}
|
||||||
|
const props = getOptionProps(this)
|
||||||
|
Object.keys(state).forEach(name => {
|
||||||
|
if (name in props) return
|
||||||
|
|
||||||
|
needSync = true
|
||||||
|
const key = this.propsToStateMap[name]
|
||||||
|
newState[key] = state[name]
|
||||||
|
})
|
||||||
|
|
||||||
|
this.setState(needSync ? newState : null)
|
||||||
|
},
|
||||||
|
|
||||||
|
isKeyChecked (key) {
|
||||||
|
const { sCheckedKeys = [] } = this
|
||||||
|
return sCheckedKeys.indexOf(key) !== -1
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Legacy] Original logic use `key` as tracking clue.
|
||||||
|
* We have to use `cloneElement` to pass `key`.
|
||||||
|
*/
|
||||||
|
renderTreeNode (child, index, level = 0) {
|
||||||
|
const {
|
||||||
|
sExpandedKeys = [], sSelectedKeys = [], sHalfCheckedKeys = [],
|
||||||
|
dragOverNodeKey, dropPosition,
|
||||||
|
} = this
|
||||||
|
const pos = getPosition(level, index)
|
||||||
|
const key = child.key || pos
|
||||||
|
|
||||||
|
return cloneElement(child, {
|
||||||
|
props: {
|
||||||
|
eventKey: key,
|
||||||
|
expanded: sExpandedKeys.indexOf(key) !== -1,
|
||||||
|
selected: sSelectedKeys.indexOf(key) !== -1,
|
||||||
|
checked: this.isKeyChecked(key),
|
||||||
|
halfChecked: sHalfCheckedKeys.indexOf(key) !== -1,
|
||||||
|
pos,
|
||||||
|
|
||||||
|
// [Legacy] Drag props
|
||||||
|
dragOver: dragOverNodeKey === key && dropPosition === 0,
|
||||||
|
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
|
||||||
|
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
prefixCls, className, focusable,
|
prefixCls, focusable,
|
||||||
showLine,
|
showLine,
|
||||||
children,
|
$slots: { default: children = [] },
|
||||||
} = this.props
|
} = this
|
||||||
const domProps = {}
|
const domProps = {}
|
||||||
|
|
||||||
// [Legacy] Commit: 0117f0c9db0e2956e92cb208f51a42387dfcb3d1
|
|
||||||
if (focusable) {
|
|
||||||
domProps.tabIndex = '0'
|
|
||||||
domProps.onKeyDown = this.onKeyDown
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul
|
<ul
|
||||||
{...domProps}
|
{...domProps}
|
||||||
className={classNames(prefixCls, className, {
|
class={classNames(prefixCls, {
|
||||||
[`${prefixCls}-show-line`]: showLine,
|
[`${prefixCls}-show-line`]: showLine,
|
||||||
})}
|
})}
|
||||||
role='tree-node'
|
role='tree-node'
|
||||||
unselectable='on'
|
unselectable='on'
|
||||||
|
tabIndex={focusable ? '0' : null}
|
||||||
|
onKeydown={focusable ? this.onKeydown : () => {}}
|
||||||
>
|
>
|
||||||
{React.Children.map(children, this.renderTreeNode, this)}
|
{children.map(this.renderTreeNode)}
|
||||||
</ul>
|
</ul>
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Tree
|
export default Tree
|
||||||
|
|
|
@ -4,6 +4,8 @@ import warning from 'warning'
|
||||||
import { contextTypes } from './Tree'
|
import { contextTypes } from './Tree'
|
||||||
import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes } from './util'
|
import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes } from './util'
|
||||||
import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util'
|
import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util'
|
||||||
|
import BaseMixin from '../../_util/BaseMixin'
|
||||||
|
import getTransitionProps from '../../_util/getTransitionProps'
|
||||||
|
|
||||||
const ICON_OPEN = 'open'
|
const ICON_OPEN = 'open'
|
||||||
const ICON_CLOSE = 'close'
|
const ICON_CLOSE = 'close'
|
||||||
|
@ -19,12 +21,14 @@ let onlyTreeNodeWarned = false // Only accept TreeNode
|
||||||
|
|
||||||
export const nodeContextTypes = {
|
export const nodeContextTypes = {
|
||||||
...contextTypes,
|
...contextTypes,
|
||||||
rcTreeNode: PropTypes.shape({
|
vcTreeNode: PropTypes.shape({
|
||||||
onUpCheckConduct: PropTypes.func,
|
onUpCheckConduct: PropTypes.func,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
const TreeNode = {
|
const TreeNode = {
|
||||||
|
name: 'TreeNode',
|
||||||
|
mixins: [BaseMixin],
|
||||||
props: initDefaultProps({
|
props: initDefaultProps({
|
||||||
eventKey: PropTypes.string, // Pass by parent `cloneElement`
|
eventKey: PropTypes.string, // Pass by parent `cloneElement`
|
||||||
prefixCls: PropTypes.string,
|
prefixCls: PropTypes.string,
|
||||||
|
@ -37,7 +41,7 @@ const TreeNode = {
|
||||||
selected: PropTypes.bool,
|
selected: PropTypes.bool,
|
||||||
checked: PropTypes.bool,
|
checked: PropTypes.bool,
|
||||||
halfChecked: PropTypes.bool,
|
halfChecked: PropTypes.bool,
|
||||||
title: PropTypes.node,
|
title: PropTypes.any,
|
||||||
pos: PropTypes.string,
|
pos: PropTypes.string,
|
||||||
dragOver: PropTypes.bool,
|
dragOver: PropTypes.bool,
|
||||||
dragOverGapTop: PropTypes.bool,
|
dragOverGapTop: PropTypes.bool,
|
||||||
|
@ -60,500 +64,500 @@ const TreeNode = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
inject: {
|
inject: {
|
||||||
context: { default: {}},
|
vcTree: { default: {}},
|
||||||
},
|
},
|
||||||
provide: {
|
provide () {
|
||||||
...this.context,
|
return {
|
||||||
rcTreeNode: this,
|
vcTree: this.vcTree,
|
||||||
|
vcTreeNode: this,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Isomorphic needn't load data in server side
|
// Isomorphic needn't load data in server side
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$nextTick(() => {
|
this.syncLoadData(this.$props)
|
||||||
this.syncLoadData(this.$props)
|
},
|
||||||
})
|
watch: {
|
||||||
|
expanded (val) {
|
||||||
|
this.syncLoadData({ expanded: val })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillReceiveProps (nextProps) {
|
methods: {
|
||||||
this.syncLoadData(nextProps)
|
onUpCheckConduct (treeNode, nodeChecked, nodeHalfChecked) {
|
||||||
},
|
const { pos: nodePos } = getOptionProps(treeNode)
|
||||||
|
const { eventKey, pos, checked, halfChecked } = this
|
||||||
|
const {
|
||||||
|
vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck, onCheckConductFinished },
|
||||||
|
vcTreeNode: { onUpCheckConduct } = {},
|
||||||
|
} = this
|
||||||
|
|
||||||
onUpCheckConduct (treeNode, nodeChecked, nodeHalfChecked) {
|
// Stop conduct when current node is disabled
|
||||||
const { pos: nodePos } = getOptionProps(treeNode)
|
if (isCheckDisabled(this)) {
|
||||||
const { eventKey, pos, checked, halfChecked } = this
|
onCheckConductFinished()
|
||||||
const {
|
|
||||||
rcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck, onCheckConductFinished },
|
|
||||||
rcTreeNode: { onUpCheckConduct } = {},
|
|
||||||
} = this.context
|
|
||||||
|
|
||||||
// Stop conduct when current node is disabled
|
|
||||||
if (isCheckDisabled(this)) {
|
|
||||||
onCheckConductFinished()
|
|
||||||
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isKeyChecked(node.key || childPos)) {
|
const children = this.getNodeChildren()
|
||||||
checkedCount += 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Static enabled children count
|
let checkedCount = nodeChecked ? 1 : 0
|
||||||
const enabledChildrenCount = children
|
|
||||||
.filter(node => !isCheckDisabled(node))
|
|
||||||
.length
|
|
||||||
|
|
||||||
// checkStrictly will not conduct check status
|
// Statistic checked count
|
||||||
const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount
|
children.forEach((node, index) => {
|
||||||
const nextHalfChecked = checkStrictly // propagated or child checked
|
const childPos = getPosition(pos, index)
|
||||||
? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked))
|
|
||||||
|
|
||||||
// Add into batch update
|
if (nodePos === childPos || isCheckDisabled(node)) {
|
||||||
if (checked !== nextChecked || halfChecked !== nextHalfChecked) {
|
return
|
||||||
onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked)
|
}
|
||||||
|
if (isKeyChecked(node.key || childPos)) {
|
||||||
|
checkedCount += 1
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
if (onUpCheckConduct) {
|
// Static enabled children count
|
||||||
onUpCheckConduct(this, nextChecked, nextHalfChecked)
|
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)
|
||||||
|
} else {
|
||||||
|
// Flush all the update
|
||||||
|
onCheckConductFinished()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Flush all the update
|
// Flush all the update
|
||||||
onCheckConductFinished()
|
onCheckConductFinished()
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
// Flush all the update
|
|
||||||
onCheckConductFinished()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onDownCheckConduct (nodeChecked) {
|
onDownCheckConduct (nodeChecked) {
|
||||||
const { $slots } = this
|
const { $slots } = this
|
||||||
const children = $slots.default || []
|
const children = $slots.default || []
|
||||||
const { rcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this.context
|
const { vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this
|
||||||
if (checkStrictly) return
|
if (checkStrictly) return
|
||||||
|
|
||||||
traverseTreeNodes(children, ({ node, key }) => {
|
traverseTreeNodes(children, ({ node, key }) => {
|
||||||
if (isCheckDisabled(node)) return false
|
if (isCheckDisabled(node)) return false
|
||||||
|
|
||||||
if (nodeChecked !== isKeyChecked(key)) {
|
if (nodeChecked !== isKeyChecked(key)) {
|
||||||
onBatchNodeCheck(key, nodeChecked, false)
|
onBatchNodeCheck(key, nodeChecked, false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
onSelectorClick (e) {
|
||||||
|
if (this.isSelectable()) {
|
||||||
|
this.onSelect(e)
|
||||||
|
} else {
|
||||||
|
this.onCheck(e)
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
},
|
|
||||||
|
|
||||||
onSelectorClick (e) {
|
onSelect (e) {
|
||||||
if (this.isSelectable()) {
|
if (this.isDisabled()) return
|
||||||
this.onSelect(e)
|
|
||||||
} else {
|
|
||||||
this.onCheck(e)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onSelect (e) {
|
const { vcTree: { onNodeSelect }} = this
|
||||||
if (this.isDisabled()) return
|
e.preventDefault()
|
||||||
|
onNodeSelect(e, this)
|
||||||
|
},
|
||||||
|
|
||||||
const { rcTree: { onNodeSelect }} = this.context
|
onCheck (e) {
|
||||||
e.preventDefault()
|
if (this.isDisabled()) return
|
||||||
onNodeSelect(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onCheck (e) {
|
const { disableCheckbox, checked, eventKey } = this
|
||||||
if (this.isDisabled()) return
|
const {
|
||||||
|
vcTree: { checkable, onBatchNodeCheck, onCheckConductFinished },
|
||||||
|
vcTreeNode: { onUpCheckConduct } = {},
|
||||||
|
} = this
|
||||||
|
|
||||||
const { disableCheckbox, checked, eventKey } = this
|
if (!checkable || disableCheckbox) return
|
||||||
const {
|
|
||||||
rcTree: { checkable, onBatchNodeCheck, onCheckConductFinished },
|
|
||||||
rcTreeNode: { onUpCheckConduct } = {},
|
|
||||||
} = this.context
|
|
||||||
|
|
||||||
if (!checkable || disableCheckbox) return
|
e.preventDefault()
|
||||||
|
const targetChecked = !checked
|
||||||
|
onBatchNodeCheck(eventKey, targetChecked, false, this)
|
||||||
|
|
||||||
e.preventDefault()
|
// Children conduct
|
||||||
const targetChecked = !checked
|
this.onDownCheckConduct(targetChecked)
|
||||||
onBatchNodeCheck(eventKey, targetChecked, false, this)
|
|
||||||
|
|
||||||
// Children conduct
|
// Parent conduct
|
||||||
this.onDownCheckConduct(targetChecked)
|
if (onUpCheckConduct) {
|
||||||
|
onUpCheckConduct(this, targetChecked, false)
|
||||||
|
} else {
|
||||||
|
onCheckConductFinished()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// Parent conduct
|
onMouseEnter (e) {
|
||||||
if (onUpCheckConduct) {
|
const { vcTree: { onNodeMouseEnter }} = this
|
||||||
onUpCheckConduct(this, targetChecked, false)
|
onNodeMouseEnter(e, this)
|
||||||
} else {
|
},
|
||||||
onCheckConductFinished()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onMouseEnter (e) {
|
onMouseLeave (e) {
|
||||||
const { rcTree: { onNodeMouseEnter }} = this.context
|
const { vcTree: { onNodeMouseLeave }} = this
|
||||||
onNodeMouseEnter(e, this)
|
onNodeMouseLeave(e, this)
|
||||||
},
|
},
|
||||||
|
|
||||||
onMouseLeave (e) {
|
onContextMenu (e) {
|
||||||
const { rcTree: { onNodeMouseLeave }} = this.context
|
const { vcTree: { onNodeContextMenu }} = this
|
||||||
onNodeMouseLeave(e, this)
|
onNodeContextMenu(e, this)
|
||||||
},
|
},
|
||||||
|
|
||||||
onContextMenu (e) {
|
onDragStart (e) {
|
||||||
const { rcTree: { onNodeContextMenu }} = this.context
|
const { vcTree: { onNodeDragStart }} = this
|
||||||
onNodeContextMenu(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragStart (e) {
|
e.stopPropagation()
|
||||||
const { rcTree: { onNodeDragStart }} = this.context
|
this.setState({
|
||||||
|
dragNodeHighlight: true,
|
||||||
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 { rcTree: { onNodeDragEnter }} = this.context
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
onNodeDragEnter(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragOver (e) {
|
|
||||||
const { rcTree: { onNodeDragOver }} = this.context
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
onNodeDragOver(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragLeave (e) {
|
|
||||||
const { rcTree: { onNodeDragLeave }} = this.context
|
|
||||||
|
|
||||||
e.stopPropagation()
|
|
||||||
onNodeDragLeave(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDragEnd (e) {
|
|
||||||
const { rcTree: { onNodeDragEnd }} = this.context
|
|
||||||
|
|
||||||
e.stopPropagation()
|
|
||||||
this.setState({
|
|
||||||
dragNodeHighlight: false,
|
|
||||||
})
|
|
||||||
onNodeDragEnd(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
onDrop (e) {
|
|
||||||
const { rcTree: { onNodeDrop }} = this.context
|
|
||||||
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
this.setState({
|
|
||||||
dragNodeHighlight: false,
|
|
||||||
})
|
|
||||||
onNodeDrop(e, this)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Disabled item still can be switch
|
|
||||||
onExpand (e) {
|
|
||||||
const { rcTree: { onNodeExpand }} = this.context
|
|
||||||
const callbackPromise = onNodeExpand(e, this)
|
|
||||||
|
|
||||||
// Promise like
|
|
||||||
if (callbackPromise && callbackPromise.then) {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_LOADING })
|
|
||||||
|
|
||||||
callbackPromise.then(() => {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_LOADED })
|
|
||||||
}).catch(() => {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_FAILED })
|
|
||||||
})
|
})
|
||||||
}
|
onNodeDragStart(e, this)
|
||||||
},
|
|
||||||
|
|
||||||
// Drag usage
|
try {
|
||||||
setSelectHandle (node) {
|
// ie throw error
|
||||||
this.selectHandle = node
|
// firefox-need-it
|
||||||
},
|
e.dataTransfer.setData('text/plain', '')
|
||||||
|
} catch (error) {
|
||||||
|
// empty
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
getNodeChildren () {
|
onDragEnter (e) {
|
||||||
const { $slots: { default: children }} = this
|
const { vcTree: { onNodeDragEnter }} = this
|
||||||
const originList = filterEmpty(children)
|
|
||||||
const targetList = getNodeChildren(originList)
|
|
||||||
|
|
||||||
if (originList.length !== targetList.length && !onlyTreeNodeWarned) {
|
e.preventDefault()
|
||||||
onlyTreeNodeWarned = true
|
e.stopPropagation()
|
||||||
warning(false, 'Tree only accept TreeNode as children.')
|
onNodeDragEnter(e, this)
|
||||||
}
|
},
|
||||||
|
|
||||||
return targetList
|
onDragOver (e) {
|
||||||
},
|
const { vcTree: { onNodeDragOver }} = this
|
||||||
|
|
||||||
getNodeState () {
|
e.preventDefault()
|
||||||
const { expanded } = this
|
e.stopPropagation()
|
||||||
|
onNodeDragOver(e, this)
|
||||||
|
},
|
||||||
|
|
||||||
if (this.isLeaf()) {
|
onDragLeave (e) {
|
||||||
return null
|
const { vcTree: { onNodeDragLeave }} = this
|
||||||
}
|
|
||||||
|
|
||||||
return expanded ? ICON_OPEN : ICON_CLOSE
|
e.stopPropagation()
|
||||||
},
|
onNodeDragLeave(e, this)
|
||||||
|
},
|
||||||
|
|
||||||
isLeaf () {
|
onDragEnd (e) {
|
||||||
const { isLeaf, loadStatus } = this
|
const { vcTree: { onNodeDragEnd }} = this
|
||||||
const { rcTree: { loadData }} = this.context
|
|
||||||
|
|
||||||
const hasChildren = this.getNodeChildren().length !== 0
|
e.stopPropagation()
|
||||||
|
this.setState({
|
||||||
return (
|
dragNodeHighlight: false,
|
||||||
isLeaf ||
|
|
||||||
(!loadData && !hasChildren) ||
|
|
||||||
(loadData && loadStatus === LOAD_STATUS_LOADED && !hasChildren)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
isDisabled () {
|
|
||||||
const { disabled } = this
|
|
||||||
const { rcTree: { disabled: treeDisabled }} = this.context
|
|
||||||
|
|
||||||
// Follow the logic of Selectable
|
|
||||||
if (disabled === false) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return !!(treeDisabled || disabled)
|
|
||||||
},
|
|
||||||
|
|
||||||
isSelectable () {
|
|
||||||
const { selectable } = this
|
|
||||||
const { rcTree: { selectable: treeSelectable }} = this.context
|
|
||||||
|
|
||||||
// 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 { loadStatus } = this
|
|
||||||
const { expanded } = props
|
|
||||||
const { rcTree: { loadData }} = this.context
|
|
||||||
|
|
||||||
if (loadData && loadStatus === LOAD_STATUS_NONE && expanded && !this.isLeaf()) {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_LOADING })
|
|
||||||
|
|
||||||
loadData(this).then(() => {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_LOADED })
|
|
||||||
}).catch(() => {
|
|
||||||
this.setState({ loadStatus: LOAD_STATUS_FAILED })
|
|
||||||
})
|
})
|
||||||
}
|
onNodeDragEnd(e, this)
|
||||||
},
|
},
|
||||||
|
|
||||||
// Switcher
|
onDrop (e) {
|
||||||
renderSwitcher () {
|
const { vcTree: { onNodeDrop }} = this
|
||||||
const { expanded } = this
|
|
||||||
const { rcTree: { prefixCls }} = this.context
|
|
||||||
|
|
||||||
if (this.isLeaf()) {
|
e.preventDefault()
|
||||||
return <span class={`${prefixCls}-switcher ${prefixCls}-switcher-noop`} />
|
e.stopPropagation()
|
||||||
}
|
this.setState({
|
||||||
|
dragNodeHighlight: false,
|
||||||
|
})
|
||||||
|
onNodeDrop(e, this)
|
||||||
|
},
|
||||||
|
|
||||||
return (
|
// Disabled item still can be switch
|
||||||
<span
|
onExpand (e) {
|
||||||
class={classNames(
|
const { vcTree: { onNodeExpand }} = this
|
||||||
`${prefixCls}-switcher`,
|
const callbackPromise = onNodeExpand(e, this)
|
||||||
`${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
|
|
||||||
)}
|
|
||||||
onClick={this.onExpand}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Checkbox
|
// Promise like
|
||||||
renderCheckbox () {
|
if (callbackPromise && callbackPromise.then) {
|
||||||
const { checked, halfChecked, disableCheckbox } = this
|
this.setState({ loadStatus: LOAD_STATUS_LOADING })
|
||||||
const { rcTree: { prefixCls, checkable }} = this.context
|
|
||||||
const disabled = this.isDisabled()
|
|
||||||
|
|
||||||
if (!checkable) return null
|
callbackPromise.then(() => {
|
||||||
|
this.setState({ loadStatus: LOAD_STATUS_LOADED })
|
||||||
|
}).catch(() => {
|
||||||
|
this.setState({ loadStatus: LOAD_STATUS_FAILED })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// [Legacy] Custom element should be separate with `checkable` in future
|
// Drag usage
|
||||||
const $custom = typeof checkable !== 'boolean' ? checkable : null
|
setSelectHandle (node) {
|
||||||
|
this.selectHandle = node
|
||||||
|
},
|
||||||
|
|
||||||
return (
|
getNodeChildren () {
|
||||||
<span
|
const { $slots: { default: children }} = this
|
||||||
class={classNames(
|
const originList = filterEmpty(children)
|
||||||
`${prefixCls}-checkbox`,
|
const targetList = getNodeChildren(originList)
|
||||||
checked && `${prefixCls}-checkbox-checked`,
|
|
||||||
!checked && halfChecked && `${prefixCls}-checkbox-indeterminate`,
|
|
||||||
(disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
|
|
||||||
)}
|
|
||||||
onClick={this.onCheck}
|
|
||||||
>
|
|
||||||
{$custom}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
renderIcon () {
|
if (originList.length !== targetList.length && !onlyTreeNodeWarned) {
|
||||||
const { loadStatus } = this
|
onlyTreeNodeWarned = true
|
||||||
const { rcTree: { prefixCls }} = this.context
|
warning(false, 'Tree only accept TreeNode as children.')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return targetList
|
||||||
<span
|
},
|
||||||
class={classNames(
|
|
||||||
`${prefixCls}-iconEle`,
|
|
||||||
`${prefixCls}-icon__${this.getNodeState() || 'docu'}`,
|
|
||||||
(loadStatus === LOAD_STATUS_LOADING) && `${prefixCls}-icon_loading`,
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Icon + Title
|
getNodeState () {
|
||||||
renderSelector () {
|
const { expanded } = this
|
||||||
const { title, selected, icon, loadStatus, dragNodeHighlight } = this
|
|
||||||
const { rcTree: { prefixCls, showIcon, draggable, loadData }} = this.context
|
|
||||||
const disabled = this.isDisabled()
|
|
||||||
|
|
||||||
const wrapClass = `${prefixCls}-node-content-wrapper`
|
if (this.isLeaf2()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// Icon - Still show loading icon when loading without showIcon
|
return expanded ? ICON_OPEN : ICON_CLOSE
|
||||||
let $icon
|
},
|
||||||
|
|
||||||
if (showIcon) {
|
isLeaf2 () {
|
||||||
$icon = icon ? (
|
const { isLeaf, loadStatus } = this
|
||||||
|
const { vcTree: { loadData }} = this
|
||||||
|
|
||||||
|
const hasChildren = this.getNodeChildren().length !== 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
isLeaf ||
|
||||||
|
(!loadData && !hasChildren) ||
|
||||||
|
(loadData && loadStatus === LOAD_STATUS_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 { loadStatus } = this
|
||||||
|
const { expanded } = props
|
||||||
|
const { vcTree: { loadData }} = this
|
||||||
|
|
||||||
|
if (loadData && loadStatus === LOAD_STATUS_NONE && expanded && !this.isLeaf2()) {
|
||||||
|
this.setState({ loadStatus: LOAD_STATUS_LOADING })
|
||||||
|
|
||||||
|
loadData(this).then(() => {
|
||||||
|
this.setState({ loadStatus: LOAD_STATUS_LOADED })
|
||||||
|
}).catch(() => {
|
||||||
|
this.setState({ loadStatus: LOAD_STATUS_FAILED })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Switcher
|
||||||
|
renderSwitcher () {
|
||||||
|
const { expanded } = this
|
||||||
|
const { vcTree: { prefixCls }} = this
|
||||||
|
|
||||||
|
if (this.isLeaf2()) {
|
||||||
|
return <span class={`${prefixCls}-switcher ${prefixCls}-switcher-noop`} />
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
class={classNames(
|
||||||
|
`${prefixCls}-switcher`,
|
||||||
|
`${prefixCls}-switcher_${expanded ? ICON_OPEN : ICON_CLOSE}`,
|
||||||
|
)}
|
||||||
|
onClick={this.onExpand}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<span
|
||||||
|
class={classNames(
|
||||||
|
`${prefixCls}-checkbox`,
|
||||||
|
checked && `${prefixCls}-checkbox-checked`,
|
||||||
|
!checked && halfChecked && `${prefixCls}-checkbox-indeterminate`,
|
||||||
|
(disabled || disableCheckbox) && `${prefixCls}-checkbox-disabled`,
|
||||||
|
)}
|
||||||
|
onClick={this.onCheck}
|
||||||
|
>
|
||||||
|
{$custom}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
renderIcon () {
|
||||||
|
const { loadStatus } = this
|
||||||
|
const { vcTree: { prefixCls }} = this
|
||||||
|
|
||||||
|
return (
|
||||||
<span
|
<span
|
||||||
class={classNames(
|
class={classNames(
|
||||||
`${prefixCls}-iconEle`,
|
`${prefixCls}-iconEle`,
|
||||||
`${prefixCls}-icon__customize`,
|
`${prefixCls}-icon__${this.getNodeState() || 'docu'}`,
|
||||||
|
(loadStatus === LOAD_STATUS_LOADING) && `${prefixCls}-icon_loading`,
|
||||||
)}
|
)}
|
||||||
>
|
/>
|
||||||
{typeof icon === 'function'
|
|
||||||
? icon(this.$props) : icon}
|
|
||||||
</span>
|
|
||||||
) : this.renderIcon()
|
|
||||||
} else if (loadData && loadStatus === LOAD_STATUS_LOADING) {
|
|
||||||
$icon = this.renderIcon()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Title
|
|
||||||
const $title = <span class={`${prefixCls}-title`}>{title}</span>
|
|
||||||
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
ref='selectHandle'
|
|
||||||
title={typeof title === 'string' ? title : ''}
|
|
||||||
class={classNames(
|
|
||||||
`${wrapClass}`,
|
|
||||||
`${wrapClass}-${this.getNodeState() || 'normal'}`,
|
|
||||||
(!disabled && (selected || dragNodeHighlight)) && `${prefixCls}-node-selected`,
|
|
||||||
(!disabled && draggable) && 'draggable'
|
|
||||||
)}
|
|
||||||
draggable={(!disabled && draggable) || undefined}
|
|
||||||
aria-grabbed={(!disabled && draggable) || undefined}
|
|
||||||
|
|
||||||
onMouseenter={this.onMouseEnter}
|
|
||||||
onMouseleave={this.onMouseLeave}
|
|
||||||
onContexmenu={this.onContextMenu}
|
|
||||||
onClick={this.onSelectorClick}
|
|
||||||
onDragstart={this.onDragStart}
|
|
||||||
>
|
|
||||||
{$icon}{$title}
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
// Children list wrapped with `Animation`
|
|
||||||
renderChildren () {
|
|
||||||
const { expanded, pos } = this
|
|
||||||
const { rcTree: {
|
|
||||||
prefixCls,
|
|
||||||
openTransitionName, openAnimation,
|
|
||||||
renderTreeNode,
|
|
||||||
}} = this.context
|
|
||||||
|
|
||||||
// [Legacy] Animation control
|
|
||||||
const renderFirst = this.renderFirst
|
|
||||||
this.renderFirst = 1
|
|
||||||
let transitionAppear = true
|
|
||||||
if (!renderFirst && expanded) {
|
|
||||||
transitionAppear = false
|
|
||||||
}
|
|
||||||
|
|
||||||
const animProps = {}
|
|
||||||
if (openTransitionName) {
|
|
||||||
animProps.transitionName = openTransitionName
|
|
||||||
} else if (typeof openAnimation === 'object') {
|
|
||||||
animProps.animation = { ...openAnimation }
|
|
||||||
if (!transitionAppear) {
|
|
||||||
delete animProps.animation.appear
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Children TreeNode
|
|
||||||
const nodeList = this.getNodeChildren()
|
|
||||||
|
|
||||||
if (nodeList.length === 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
let $children
|
|
||||||
if (expanded) {
|
|
||||||
$children = (
|
|
||||||
<ul
|
|
||||||
class={classNames(
|
|
||||||
`${prefixCls}-child-tree`,
|
|
||||||
expanded && `${prefixCls}-child-tree-open`,
|
|
||||||
)}
|
|
||||||
data-expanded={expanded}
|
|
||||||
>
|
|
||||||
{nodeList.map((node, index) => (
|
|
||||||
renderTreeNode(node, index, pos)
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
|
|
||||||
return (
|
// Icon + Title
|
||||||
<Animate
|
renderSelector () {
|
||||||
{...animProps}
|
const { title, selected, icon, loadStatus, dragNodeHighlight } = this
|
||||||
showProp='data-expanded'
|
const { vcTree: { prefixCls, showIcon, draggable, loadData }} = this
|
||||||
transitionAppear={transitionAppear}
|
const disabled = this.isDisabled()
|
||||||
component=''
|
|
||||||
>
|
const wrapClass = `${prefixCls}-node-content-wrapper`
|
||||||
{$children}
|
|
||||||
</Animate>
|
// Icon - Still show loading icon when loading without showIcon
|
||||||
)
|
let $icon
|
||||||
|
|
||||||
|
if (showIcon) {
|
||||||
|
$icon = icon ? (
|
||||||
|
<span
|
||||||
|
class={classNames(
|
||||||
|
`${prefixCls}-iconEle`,
|
||||||
|
`${prefixCls}-icon__customize`,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{typeof icon === 'function'
|
||||||
|
? icon(this.$props) : icon}
|
||||||
|
</span>
|
||||||
|
) : this.renderIcon()
|
||||||
|
} else if (loadData && loadStatus === LOAD_STATUS_LOADING) {
|
||||||
|
$icon = this.renderIcon()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title
|
||||||
|
const $title = <span class={`${prefixCls}-title`}>{title}</span>
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
ref='selectHandle'
|
||||||
|
title={typeof title === 'string' ? title : ''}
|
||||||
|
class={classNames(
|
||||||
|
`${wrapClass}`,
|
||||||
|
`${wrapClass}-${this.getNodeState() || 'normal'}`,
|
||||||
|
(!disabled && (selected || dragNodeHighlight)) && `${prefixCls}-node-selected`,
|
||||||
|
(!disabled && draggable) && 'draggable'
|
||||||
|
)}
|
||||||
|
draggable={(!disabled && draggable) || undefined}
|
||||||
|
aria-grabbed={(!disabled && draggable) || undefined}
|
||||||
|
|
||||||
|
onMouseenter={this.onMouseEnter}
|
||||||
|
onMouseleave={this.onMouseLeave}
|
||||||
|
onContexmenu={this.onContextMenu}
|
||||||
|
onClick={this.onSelectorClick}
|
||||||
|
onDragstart={this.onDragStart}
|
||||||
|
>
|
||||||
|
{$icon}{$title}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 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 }
|
||||||
|
if (!transitionAppear) {
|
||||||
|
delete animProps.props.appear
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Children TreeNode
|
||||||
|
const nodeList = this.getNodeChildren()
|
||||||
|
|
||||||
|
if (nodeList.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let $children
|
||||||
|
if (expanded) {
|
||||||
|
$children = (
|
||||||
|
<ul
|
||||||
|
v-show={expanded}
|
||||||
|
class={classNames(
|
||||||
|
`${prefixCls}-child-tree`,
|
||||||
|
expanded && `${prefixCls}-child-tree-open`,
|
||||||
|
)}
|
||||||
|
data-expanded={expanded}
|
||||||
|
>
|
||||||
|
{nodeList.map((node, index) => (
|
||||||
|
renderTreeNode(node, index, pos)
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<transition
|
||||||
|
{...animProps}
|
||||||
|
>
|
||||||
|
{$children}
|
||||||
|
</transition>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
dragOver, dragOverGapTop, dragOverGapBottom,
|
dragOver, dragOverGapTop, dragOverGapBottom,
|
||||||
} = this
|
} = this
|
||||||
const { rcTree: {
|
const { vcTree: {
|
||||||
prefixCls,
|
prefixCls,
|
||||||
filterTreeNode,
|
filterTreeNode,
|
||||||
}} = this.context
|
}} = this
|
||||||
const disabled = this.isDisabled()
|
const disabled = this.isDisabled()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,25 @@
|
||||||
|
import { getOptionProps } from '../../_util/props-util'
|
||||||
import Tree from './Tree'
|
import Tree from './Tree'
|
||||||
import TreeNode from './TreeNode'
|
import TreeNode from './TreeNode'
|
||||||
Tree.TreeNode = TreeNode
|
Tree.TreeNode = TreeNode
|
||||||
|
|
||||||
|
//
|
||||||
|
const NewTree = {
|
||||||
|
TreeNode: TreeNode,
|
||||||
|
props: Tree.props,
|
||||||
|
render () {
|
||||||
|
const { $listeners, $slots } = this
|
||||||
|
const treeProps = {
|
||||||
|
props: {
|
||||||
|
...getOptionProps(this),
|
||||||
|
children: $slots.default,
|
||||||
|
},
|
||||||
|
on: $listeners,
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Tree {...treeProps}>{$slots.default}</Tree>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
export { TreeNode }
|
export { TreeNode }
|
||||||
export default Tree
|
export default NewTree
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/* eslint no-loop-func: 0*/
|
/* eslint no-loop-func: 0*/
|
||||||
import { Children } from 'react'
|
|
||||||
import warning from 'warning'
|
import warning from 'warning'
|
||||||
|
import { getSlotOptions, getOptionProps } from '../../_util/props-util'
|
||||||
|
|
||||||
export function arrDel (list, value) {
|
export function arrDel (list, value) {
|
||||||
const clone = list.slice()
|
const clone = list.slice()
|
||||||
|
@ -48,14 +48,13 @@ export function getPosition (level, index) {
|
||||||
return `${level}-${index}`
|
return `${level}-${index}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNodeChildren (children) {
|
export function getNodeChildren (children = []) {
|
||||||
const childList = Array.isArray(children) ? children : [children]
|
return children
|
||||||
return childList
|
.filter(child => getSlotOptions(child).isTreeNode)
|
||||||
.filter(child => child && child.type && child.type.isTreeNode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isCheckDisabled (node) {
|
export function isCheckDisabled (node) {
|
||||||
const { disabled, disableCheckbox } = node.props || {}
|
const { disabled, disableCheckbox } = getOptionProps(node) || {}
|
||||||
return !!(disabled || disableCheckbox)
|
return !!(disabled || disableCheckbox)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +65,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function processNode (node, index, parent) {
|
function processNode (node, index, parent) {
|
||||||
const children = node ? node.props.children : treeNodes
|
const children = node ? node.componentOptions.children : treeNodes
|
||||||
const pos = node ? getPosition(parent.pos, index) : 0
|
const pos = node ? getPosition(parent.pos, index) : 0
|
||||||
|
|
||||||
// Filter children
|
// Filter children
|
||||||
|
@ -86,7 +85,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) {
|
||||||
if (subTreeData) {
|
if (subTreeData) {
|
||||||
// Statistic children
|
// Statistic children
|
||||||
const subNodes = []
|
const subNodes = []
|
||||||
Children.forEach(childList, (subNode, subIndex) => {
|
childList.forEach((subNode, subIndex) => {
|
||||||
// Provide limit snapshot
|
// Provide limit snapshot
|
||||||
const subPos = getPosition(pos, index)
|
const subPos = getPosition(pos, index)
|
||||||
subNodes.push({
|
subNodes.push({
|
||||||
|
@ -106,7 +105,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process children node
|
// Process children node
|
||||||
Children.forEach(childList, (subNode, subIndex) => {
|
childList.forEach((subNode, subIndex) => {
|
||||||
processNode(subNode, subIndex, { node, pos })
|
processNode(subNode, subIndex, { node, pos })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -182,7 +181,7 @@ export function getNodesStatistic (treeNodes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDragNodesKeys (treeNodes, node) {
|
export function getDragNodesKeys (treeNodes, node) {
|
||||||
const { eventKey, pos } = node.props
|
const { eventKey, pos } = getOptionProps(node)
|
||||||
const dragNodesKeys = []
|
const dragNodesKeys = []
|
||||||
|
|
||||||
traverseTreeNodes(treeNodes, ({ pos: nodePos, key }) => {
|
traverseTreeNodes(treeNodes, ({ pos: nodePos, key }) => {
|
||||||
|
@ -214,12 +213,12 @@ export function calcDropPosition (event, treeNode) {
|
||||||
* @param props
|
* @param props
|
||||||
* @returns [string]
|
* @returns [string]
|
||||||
*/
|
*/
|
||||||
export function calcExpandedKeys (keyList, props) {
|
export function calcExpandedKeys (keyList, props, children = []) {
|
||||||
if (!keyList) {
|
if (!keyList) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const { autoExpandParent, children } = props
|
const { autoExpandParent } = props
|
||||||
|
|
||||||
// Do nothing if not auto expand parent
|
// Do nothing if not auto expand parent
|
||||||
if (!autoExpandParent) {
|
if (!autoExpandParent) {
|
||||||
|
@ -363,8 +362,8 @@ export function calcCheckStateConduct (treeNodes, checkedKeys) {
|
||||||
* Calculate the value of checked and halfChecked keys.
|
* Calculate the value of checked and halfChecked keys.
|
||||||
* This should be only run in init or props changed.
|
* This should be only run in init or props changed.
|
||||||
*/
|
*/
|
||||||
export function calcCheckedKeys (keys, props) {
|
export function calcCheckedKeys (keys, props, children = []) {
|
||||||
const { checkable, children, checkStrictly } = props
|
const { checkable, checkStrictly } = props
|
||||||
|
|
||||||
if (!checkable || !keys) {
|
if (!checkable || !keys) {
|
||||||
return null
|
return null
|
||||||
|
|
|
@ -3,7 +3,7 @@ const AsyncComp = () => {
|
||||||
const hashs = window.location.hash.split('/')
|
const hashs = window.location.hash.split('/')
|
||||||
const d = hashs[hashs.length - 1]
|
const d = hashs[hashs.length - 1]
|
||||||
return {
|
return {
|
||||||
component: import(`../components/popconfirm/demo/${d}`),
|
component: import(`../components/vc-tree/demo/${d}`),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export default [
|
export default [
|
||||||
|
|
Loading…
Reference in New Issue