add vc-tree

pull/165/head
tjz 2018-03-24 17:12:22 +08:00
parent 5ea54b104a
commit bbfbc8fe4f
7 changed files with 987 additions and 857 deletions

View File

@ -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>&nbsp;
<label onClick={(e) => e.stopPropagation()}><input type='checkbox' /> checked</label> &nbsp;
<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>)
},
}

View File

@ -0,0 +1,6 @@
.rc-tree li a.rc-tree-node-selected{
.cus-label {
background-color: white;
border: none;
}
}

View File

@ -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

View File

@ -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 (

View File

@ -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

View File

@ -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

View File

@ -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 [