diff --git a/components/vc-tree/demo/basic.jsx b/components/vc-tree/demo/basic.jsx new file mode 100644 index 000000000..322d26d96 --- /dev/null +++ b/components/vc-tree/demo/basic.jsx @@ -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 = ( + operations: + Edit  +   + Delete + ) + return (
+

simple

+ {/* + + + + + + + + + + + + + + + */} + +

Check on Click TreeNode

+ + + + + + + + {this.showMore ? + + + : null} + + +
) + }, +} + diff --git a/components/vc-tree/demo/basic.less b/components/vc-tree/demo/basic.less new file mode 100644 index 000000000..cca0a1860 --- /dev/null +++ b/components/vc-tree/demo/basic.less @@ -0,0 +1,6 @@ +.rc-tree li a.rc-tree-node-selected{ + .cus-label { + background-color: white; + border: none; + } +} diff --git a/components/vc-tree/src/Tree.jsx b/components/vc-tree/src/Tree.jsx index 53a1b9646..6c042a530 100644 --- a/components/vc-tree/src/Tree.jsx +++ b/components/vc-tree/src/Tree.jsx @@ -1,7 +1,9 @@ import PropTypes from '../../_util/vue-types' import classNames from 'classnames' 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 { traverseTreeNodes, getStrictlyValue, getFullKeyList, getPosition, getDragNodesKeys, @@ -54,6 +56,8 @@ export const contextTypes = { } const Tree = { + name: 'Tree', + mixins: [BaseMixin], props: initDefaultProps({ prefixCls: PropTypes.string, showLine: PropTypes.bool, @@ -79,9 +83,9 @@ const Tree = { ]), defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), selectedKeys: PropTypes.arrayOf(PropTypes.string), - //onExpand: PropTypes.func, - //onCheck: PropTypes.func, - //onSelect: PropTypes.func, + // onExpand: PropTypes.func, + // onCheck: PropTypes.func, + // onSelect: PropTypes.func, loadData: PropTypes.func, // onMouseEnter: PropTypes.func, // onMouseLeave: PropTypes.func, @@ -95,6 +99,7 @@ const Tree = { filterTreeNode: PropTypes.func, openTransitionName: PropTypes.string, openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + children: PropTypes.any, }, { prefixCls: 'rc-tree', showLine: false, @@ -122,489 +127,476 @@ const Tree = { defaultCheckedKeys, defaultSelectedKeys, } = props - + const children = this.$slots.default // Sync state with props const { checkedKeys = [], halfCheckedKeys = [] } = - calcCheckedKeys(defaultCheckedKeys, props) || {} + calcCheckedKeys(defaultCheckedKeys, props, children) || {} // Cache for check status to optimize this.checkedBatch = null - + this.propsToStateMap = { + expandedKeys: 'sExpandedKeys', + selectedKeys: 'sSelectedKeys', + checkedKeys: 'sCheckedKeys', + halfCheckedKeys: 'sHalfCheckedKeys', + } return { sExpandedKeys: defaultExpandAll - ? getFullKeyList(this.$slots.default) - : calcExpandedKeys(defaultExpandedKeys, props), - sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props), - sCheckedKeys, - sHalfCheckedKeys, + ? getFullKeyList(children) + : calcExpandedKeys(defaultExpandedKeys, props, children), + sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props, children), + sCheckedKeys: checkedKeys, + sHalfCheckedKeys: halfCheckedKeys, ...(this.getSyncProps(props) || {}), - } + dragOverNodeKey: '', + dropPosition: null, + } }, - provide: { - rcTree: 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 }) + provide () { + return { + vcTree: this, } }, - /** - * [Legacy] Select handler is less small than node, - * so that this will trigger when drag enter node or select handler. - * This is a little tricky if customize css without padding. - * Better for use mouse move event to refresh drag state. - * But let's just keep it to avoid event trigger logic change. - */ - onNodeDragEnter = (event, node) => { - const { expandedKeys } = this.state - const { onDragEnter } = this.props - const { pos, eventKey } = node.props + watch: { + children (val) { + const { checkedKeys = [], halfCheckedKeys = [] } = calcCheckedKeys(this.checkedKeys || this.sCheckedKeys, this.$props, this.$slots.default) || {} + this.sCheckedKeys = checkedKeys + this.sHalfCheckedKeys = halfCheckedKeys + }, + expandedKeys (val) { + this.sExpandedKeys = calcExpandedKeys(this.expandedKeys, this.$props, this.$slots.default) + }, + selectedKeys (val) { + 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({ - dragOverNodeKey: '', - dropPosition: null, + dragNodesKeys: getDragNodesKeys(children, node), + 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, - // 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, - }) + /** + * [Legacy] Select handler is less small than node, + * so that this will trigger when drag enter node or select handler. + * This is a little tricky if customize css without padding. + * Better for use mouse move event to refresh drag state. + * But let's just keep it to avoid event trigger logic change. + */ + onNodeDragEnter (event, node) { + const { sExpandedKeys } = this + const { pos, eventKey } = node.props - // Side effect for delay drag - if (!this.delayedDragEnterLogic) { - this.delayedDragEnterLogic = {} - } - Object.keys(this.delayedDragEnterLogic).forEach((key) => { - clearTimeout(this.delayedDragEnterLogic[key]) - }) - this.delayedDragEnterLogic[pos] = setTimeout(() => { - const newExpandedKeys = arrAdd(expandedKeys, eventKey) + const dropPosition = calcDropPosition(event, node) + + // Skip if drag node is self + if ( + this.dragNode.props.eventKey === eventKey && + dropPosition === 0 + ) { 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) { - onDragEnter({ event, node, expandedKeys: newExpandedKeys }) - } - }, 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) + // Side effect for delay drag + if (!this.delayedDragEnterLogic) { + this.delayedDragEnterLogic = {} } + 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 = { event: 'select', selected: targetSelected, node: treeNode, selectedNodes, } - onSelect(selectedKeys, eventObj) - } - }; + this.__emit('select', selectedKeys, eventObj) + }, - /** - * This will cache node check status to optimize update process. - * When Tree get trigger `onCheckConductFinished` will flush all the update. - */ - onBatchNodeCheck = (key, checked, halfChecked, startNode) => { - if (startNode) { - this.checkedBatch = { - treeNode: startNode, - checked, - list: [], + /** + * This will cache node check status to optimize update process. + * When Tree get trigger `onCheckConductFinished` will flush all the update. + */ + onBatchNodeCheck (key, checked, halfChecked, startNode) { + if (startNode) { + this.checkedBatch = { + treeNode: startNode, + checked, + list: [], + } } - } - // This code should never called - if (!this.checkedBatch) { - this.checkedBatch = { - list: [], + // This code should never called + if (!this.checkedBatch) { + this.checkedBatch = { + 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( - false, - 'Checked batch not init. This should be a bug. Please fire a issue.' - ) - } + (expanded && index !== -1) || (!expanded && index === -1) + , 'Expand state not sync with index check') - this.checkedBatch.list.push({ key, checked, halfChecked }) - }; - - /** - * 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 + if (targetExpanded) { + expandedKeys = arrAdd(expandedKeys, eventKey) + } else { + expandedKeys = arrDel(expandedKeys, eventKey) } - return false - } - // Children change will affect check box status. - // And no need to check when prev props not provided - if (prevProps && checkSync('children')) { - const { checkedKeys = [], halfCheckedKeys = [] } = - calcCheckedKeys(props.checkedKeys || this.state.checkedKeys, props) || {} - newState.checkedKeys = checkedKeys - newState.halfCheckedKeys = halfCheckedKeys - } + this.setUncontrolledState({ expandedKeys }) + this.__emit('expand', expandedKeys, { node: treeNode, expanded: targetExpanded }) - if (checkSync('expandedKeys')) { - newState.expandedKeys = calcExpandedKeys(props.expandedKeys, props) - } + // Async Load data + if (targetExpanded && loadData) { + return loadData(treeNode).then(() => { + // [Legacy] Refresh logic + this.setUncontrolledState({ expandedKeys }) + }) + } - if (checkSync('selectedKeys')) { - newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props) - } + return null + }, - if (checkSync('checkedKeys')) { - const { checkedKeys = [], halfCheckedKeys = [] } = - calcCheckedKeys(props.checkedKeys, props) || {} - newState.checkedKeys = checkedKeys - newState.halfCheckedKeys = halfCheckedKeys - } + onNodeMouseEnter (event, node) { + this.__emit('mouseenter', { event, node }) + }, - return needSync ? newState : null - }; + onNodeMouseLeave (event, node) { + this.__emit('mouseleave', { event, node }) + }, - /** - * Only update the value which is not in props - */ - setUncontrolledState = (state) => { - let needSync = false - const newState = {} + onNodeContextMenu (event, node) { + event.preventDefault() + this.__emit('rightClick', { event, node }) + }, - 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 - newState[name] = state[name] - }) + // Children change will affect check box status. + // 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) => { - const { checkedKeys = [] } = this.state - return checkedKeys.indexOf(key) !== -1 - }; + if (checkSync('selectedKeys')) { + newState.sSelectedKeys = calcSelectedKeys(props.selectedKeys, props, children) + } - /** - * [Legacy] Original logic use `key` as tracking clue. - * We have to use `cloneElement` to pass `key`. - */ - renderTreeNode = (child, index, level = 0) => { - const { - expandedKeys = [], selectedKeys = [], halfCheckedKeys = [], - dragOverNodeKey, dropPosition, - } = this.state - const {} = this.props - const pos = getPosition(level, index) - const key = child.key || pos + if (checkSync('checkedKeys')) { + const { checkedKeys = [], halfCheckedKeys = [] } = + calcCheckedKeys(props.checkedKeys, props, children) || {} + newState.sCheckedKeys = checkedKeys + newState.sHalfCheckedKeys = halfCheckedKeys + } - return React.cloneElement(child, { - eventKey: key, - expanded: expandedKeys.indexOf(key) !== -1, - selected: selectedKeys.indexOf(key) !== -1, - checked: this.isKeyChecked(key), - halfChecked: halfCheckedKeys.indexOf(key) !== -1, - pos, + return needSync ? newState : null + }, - // [Legacy] Drag props - dragOver: dragOverNodeKey === key && dropPosition === 0, - dragOverGapTop: dragOverNodeKey === key && dropPosition === -1, - dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1, - }) - }; + /** + * Only update the value which is not in props + */ + 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 () { const { - prefixCls, className, focusable, + prefixCls, focusable, showLine, - children, - } = this.props + $slots: { default: children = [] }, + } = this const domProps = {} - // [Legacy] Commit: 0117f0c9db0e2956e92cb208f51a42387dfcb3d1 - if (focusable) { - domProps.tabIndex = '0' - domProps.onKeyDown = this.onKeyDown - } - return ( ) - } + }, } export default Tree diff --git a/components/vc-tree/src/TreeNode.jsx b/components/vc-tree/src/TreeNode.jsx index 155585404..fb5ce8a86 100644 --- a/components/vc-tree/src/TreeNode.jsx +++ b/components/vc-tree/src/TreeNode.jsx @@ -4,6 +4,8 @@ import warning from 'warning' import { contextTypes } from './Tree' import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes } from './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_CLOSE = 'close' @@ -19,12 +21,14 @@ let onlyTreeNodeWarned = false // Only accept TreeNode export const nodeContextTypes = { ...contextTypes, - rcTreeNode: PropTypes.shape({ + vcTreeNode: PropTypes.shape({ onUpCheckConduct: PropTypes.func, }), } const TreeNode = { + name: 'TreeNode', + mixins: [BaseMixin], props: initDefaultProps({ eventKey: PropTypes.string, // Pass by parent `cloneElement` prefixCls: PropTypes.string, @@ -37,7 +41,7 @@ const TreeNode = { selected: PropTypes.bool, checked: PropTypes.bool, halfChecked: PropTypes.bool, - title: PropTypes.node, + title: PropTypes.any, pos: PropTypes.string, dragOver: PropTypes.bool, dragOverGapTop: PropTypes.bool, @@ -60,500 +64,500 @@ const TreeNode = { } }, inject: { - context: { default: {}}, + vcTree: { default: {}}, }, - provide: { - ...this.context, - rcTreeNode: this, + provide () { + return { + vcTree: this.vcTree, + vcTreeNode: this, + } }, // Isomorphic needn't load data in server side mounted () { - this.$nextTick(() => { - this.syncLoadData(this.$props) - }) + this.syncLoadData(this.$props) + }, + watch: { + expanded (val) { + this.syncLoadData({ expanded: val }) + }, }, - componentWillReceiveProps (nextProps) { - this.syncLoadData(nextProps) - }, + methods: { + 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) { - const { pos: nodePos } = getOptionProps(treeNode) - const { eventKey, pos, checked, halfChecked } = this - 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)) { + // Stop conduct when current node is disabled + if (isCheckDisabled(this)) { + onCheckConductFinished() return } - if (isKeyChecked(node.key || childPos)) { - checkedCount += 1 - } - }) + const children = this.getNodeChildren() - // Static enabled children count - const enabledChildrenCount = children - .filter(node => !isCheckDisabled(node)) - .length + let checkedCount = nodeChecked ? 1 : 0 - // checkStrictly will not conduct check status - const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount - const nextHalfChecked = checkStrictly // propagated or child checked - ? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked)) + // Statistic checked count + children.forEach((node, index) => { + const childPos = getPosition(pos, index) - // Add into batch update - if (checked !== nextChecked || halfChecked !== nextHalfChecked) { - onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked) + if (nodePos === childPos || isCheckDisabled(node)) { + return + } + if (isKeyChecked(node.key || childPos)) { + checkedCount += 1 + } + }) - if (onUpCheckConduct) { - onUpCheckConduct(this, nextChecked, nextHalfChecked) + // Static enabled children count + const enabledChildrenCount = children + .filter(node => !isCheckDisabled(node)) + .length + + // checkStrictly will not conduct check status + const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount + const nextHalfChecked = checkStrictly // propagated or child checked + ? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked)) + + // Add into batch update + if (checked !== nextChecked || halfChecked !== nextHalfChecked) { + onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked) + + if (onUpCheckConduct) { + onUpCheckConduct(this, nextChecked, nextHalfChecked) + } else { + // Flush all the update + onCheckConductFinished() + } } else { // Flush all the update onCheckConductFinished() } - } else { - // Flush all the update - onCheckConductFinished() - } - }, + }, - onDownCheckConduct (nodeChecked) { - const { $slots } = this - const children = $slots.default || [] - const { rcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this.context - if (checkStrictly) return + onDownCheckConduct (nodeChecked) { + const { $slots } = this + const children = $slots.default || [] + const { vcTree: { checkStrictly, isKeyChecked, onBatchNodeCheck }} = this + if (checkStrictly) return - traverseTreeNodes(children, ({ node, key }) => { - if (isCheckDisabled(node)) return false + traverseTreeNodes(children, ({ node, key }) => { + if (isCheckDisabled(node)) return false - if (nodeChecked !== isKeyChecked(key)) { - onBatchNodeCheck(key, nodeChecked, false) + if (nodeChecked !== isKeyChecked(key)) { + onBatchNodeCheck(key, nodeChecked, false) + } + }) + }, + + onSelectorClick (e) { + if (this.isSelectable()) { + this.onSelect(e) + } else { + this.onCheck(e) } - }) - }, + }, - onSelectorClick (e) { - if (this.isSelectable()) { - this.onSelect(e) - } else { - this.onCheck(e) - } - }, + onSelect (e) { + if (this.isDisabled()) return - onSelect (e) { - if (this.isDisabled()) return + const { vcTree: { onNodeSelect }} = this + e.preventDefault() + onNodeSelect(e, this) + }, - const { rcTree: { onNodeSelect }} = this.context - e.preventDefault() - onNodeSelect(e, this) - }, + onCheck (e) { + if (this.isDisabled()) return - onCheck (e) { - if (this.isDisabled()) return + const { disableCheckbox, checked, eventKey } = this + const { + vcTree: { checkable, onBatchNodeCheck, onCheckConductFinished }, + vcTreeNode: { onUpCheckConduct } = {}, + } = this - const { disableCheckbox, checked, eventKey } = this - const { - rcTree: { checkable, onBatchNodeCheck, onCheckConductFinished }, - rcTreeNode: { onUpCheckConduct } = {}, - } = this.context + if (!checkable || disableCheckbox) return - if (!checkable || disableCheckbox) return + e.preventDefault() + const targetChecked = !checked + onBatchNodeCheck(eventKey, targetChecked, false, this) - e.preventDefault() - const targetChecked = !checked - onBatchNodeCheck(eventKey, targetChecked, false, this) + // Children conduct + this.onDownCheckConduct(targetChecked) - // Children conduct - this.onDownCheckConduct(targetChecked) + // Parent conduct + if (onUpCheckConduct) { + onUpCheckConduct(this, targetChecked, false) + } else { + onCheckConductFinished() + } + }, - // Parent conduct - if (onUpCheckConduct) { - onUpCheckConduct(this, targetChecked, false) - } else { - onCheckConductFinished() - } - }, + onMouseEnter (e) { + const { vcTree: { onNodeMouseEnter }} = this + onNodeMouseEnter(e, this) + }, - onMouseEnter (e) { - const { rcTree: { onNodeMouseEnter }} = this.context - onNodeMouseEnter(e, this) - }, + onMouseLeave (e) { + const { vcTree: { onNodeMouseLeave }} = this + onNodeMouseLeave(e, this) + }, - onMouseLeave (e) { - const { rcTree: { onNodeMouseLeave }} = this.context - onNodeMouseLeave(e, this) - }, + onContextMenu (e) { + const { vcTree: { onNodeContextMenu }} = this + onNodeContextMenu(e, this) + }, - onContextMenu (e) { - const { rcTree: { onNodeContextMenu }} = this.context - onNodeContextMenu(e, this) - }, + onDragStart (e) { + const { vcTree: { onNodeDragStart }} = this - onDragStart (e) { - const { rcTree: { onNodeDragStart }} = this.context - - 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 }) + e.stopPropagation() + this.setState({ + dragNodeHighlight: true, }) - } - }, + onNodeDragStart(e, this) - // Drag usage - setSelectHandle (node) { - this.selectHandle = node - }, + try { + // ie throw error + // firefox-need-it + e.dataTransfer.setData('text/plain', '') + } catch (error) { + // empty + } + }, - getNodeChildren () { - const { $slots: { default: children }} = this - const originList = filterEmpty(children) - const targetList = getNodeChildren(originList) + onDragEnter (e) { + const { vcTree: { onNodeDragEnter }} = this - if (originList.length !== targetList.length && !onlyTreeNodeWarned) { - onlyTreeNodeWarned = true - warning(false, 'Tree only accept TreeNode as children.') - } + e.preventDefault() + e.stopPropagation() + onNodeDragEnter(e, this) + }, - return targetList - }, + onDragOver (e) { + const { vcTree: { onNodeDragOver }} = this - getNodeState () { - const { expanded } = this + e.preventDefault() + e.stopPropagation() + onNodeDragOver(e, this) + }, - if (this.isLeaf()) { - return null - } + onDragLeave (e) { + const { vcTree: { onNodeDragLeave }} = this - return expanded ? ICON_OPEN : ICON_CLOSE - }, + e.stopPropagation() + onNodeDragLeave(e, this) + }, - isLeaf () { - const { isLeaf, loadStatus } = this - const { rcTree: { loadData }} = this.context + onDragEnd (e) { + const { vcTree: { onNodeDragEnd }} = this - const hasChildren = this.getNodeChildren().length !== 0 - - return ( - 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 }) + e.stopPropagation() + this.setState({ + dragNodeHighlight: false, }) - } - }, + onNodeDragEnd(e, this) + }, - // Switcher - renderSwitcher () { - const { expanded } = this - const { rcTree: { prefixCls }} = this.context + onDrop (e) { + const { vcTree: { onNodeDrop }} = this - if (this.isLeaf()) { - return - } + e.preventDefault() + e.stopPropagation() + this.setState({ + dragNodeHighlight: false, + }) + onNodeDrop(e, this) + }, - return ( - - ) - }, + // Disabled item still can be switch + onExpand (e) { + const { vcTree: { onNodeExpand }} = this + const callbackPromise = onNodeExpand(e, this) - // Checkbox - renderCheckbox () { - const { checked, halfChecked, disableCheckbox } = this - const { rcTree: { prefixCls, checkable }} = this.context - const disabled = this.isDisabled() + // Promise like + if (callbackPromise && callbackPromise.then) { + this.setState({ loadStatus: LOAD_STATUS_LOADING }) - 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 - const $custom = typeof checkable !== 'boolean' ? checkable : null + // Drag usage + setSelectHandle (node) { + this.selectHandle = node + }, - return ( - - {$custom} - - ) - }, + getNodeChildren () { + const { $slots: { default: children }} = this + const originList = filterEmpty(children) + const targetList = getNodeChildren(originList) - renderIcon () { - const { loadStatus } = this - const { rcTree: { prefixCls }} = this.context + if (originList.length !== targetList.length && !onlyTreeNodeWarned) { + onlyTreeNodeWarned = true + warning(false, 'Tree only accept TreeNode as children.') + } - return ( - - ) - }, + return targetList + }, - // Icon + Title - renderSelector () { - const { title, selected, icon, loadStatus, dragNodeHighlight } = this - const { rcTree: { prefixCls, showIcon, draggable, loadData }} = this.context - const disabled = this.isDisabled() + getNodeState () { + const { expanded } = this - const wrapClass = `${prefixCls}-node-content-wrapper` + if (this.isLeaf2()) { + return null + } - // Icon - Still show loading icon when loading without showIcon - let $icon + return expanded ? ICON_OPEN : ICON_CLOSE + }, - if (showIcon) { - $icon = icon ? ( + isLeaf2 () { + 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 + } + + return ( + + ) + }, + + // Checkbox + renderCheckbox () { + const { checked, halfChecked, disableCheckbox } = this + const { vcTree: { prefixCls, checkable }} = this + const disabled = this.isDisabled() + + if (!checkable) return null + + // [Legacy] Custom element should be separate with `checkable` in future + const $custom = typeof checkable !== 'boolean' ? checkable : null + + return ( + + {$custom} + + ) + }, + + renderIcon () { + const { loadStatus } = this + const { vcTree: { prefixCls }} = this + + return ( - {typeof icon === 'function' - ? icon(this.$props) : icon} - - ) : this.renderIcon() - } else if (loadData && loadStatus === LOAD_STATUS_LOADING) { - $icon = this.renderIcon() - } - - // Title - const $title = {title} - - return ( - - {$icon}{$title} - - ) - }, - - // 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 = ( -
    - {nodeList.map((node, index) => ( - renderTreeNode(node, index, pos) - ))} -
+ /> ) - } + }, - return ( - - {$children} - - ) + // Icon + Title + renderSelector () { + const { title, selected, icon, loadStatus, dragNodeHighlight } = this + const { vcTree: { prefixCls, showIcon, draggable, loadData }} = this + const disabled = this.isDisabled() + + const wrapClass = `${prefixCls}-node-content-wrapper` + + // Icon - Still show loading icon when loading without showIcon + let $icon + + if (showIcon) { + $icon = icon ? ( + + {typeof icon === 'function' + ? icon(this.$props) : icon} + + ) : this.renderIcon() + } else if (loadData && loadStatus === LOAD_STATUS_LOADING) { + $icon = this.renderIcon() + } + + // Title + const $title = {title} + + return ( + + {$icon}{$title} + + ) + }, + + // Children list wrapped with `Animation` + renderChildren () { + const { expanded, pos } = this + const { vcTree: { + prefixCls, + openTransitionName, openAnimation, + renderTreeNode, + }} = this + + // [Legacy] Animation control + const renderFirst = this.renderFirst + this.renderFirst = 1 + let transitionAppear = true + if (!renderFirst && expanded) { + transitionAppear = false + } + + let animProps = {} + if (openTransitionName) { + animProps = getTransitionProps(openTransitionName, { appear: transitionAppear }) + } else if (typeof openAnimation === 'object') { + animProps = { ...openAnimation } + if (!transitionAppear) { + delete animProps.props.appear + } + } + + // Children TreeNode + const nodeList = this.getNodeChildren() + + if (nodeList.length === 0) { + return null + } + + let $children + if (expanded) { + $children = ( +
    + {nodeList.map((node, index) => ( + renderTreeNode(node, index, pos) + ))} +
+ ) + } + + return ( + + {$children} + + ) + }, }, render () { const { dragOver, dragOverGapTop, dragOverGapBottom, } = this - const { rcTree: { + const { vcTree: { prefixCls, filterTreeNode, - }} = this.context + }} = this const disabled = this.isDisabled() return ( diff --git a/components/vc-tree/src/index.js b/components/vc-tree/src/index.js index d053f4c9f..e8c0e81b5 100644 --- a/components/vc-tree/src/index.js +++ b/components/vc-tree/src/index.js @@ -1,6 +1,25 @@ +import { getOptionProps } from '../../_util/props-util' import Tree from './Tree' import TreeNode from './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 ( + {$slots.default} + ) + }, +} export { TreeNode } -export default Tree +export default NewTree diff --git a/components/vc-tree/src/util.js b/components/vc-tree/src/util.js index 41e39ed08..712853249 100644 --- a/components/vc-tree/src/util.js +++ b/components/vc-tree/src/util.js @@ -1,6 +1,6 @@ /* eslint no-loop-func: 0*/ -import { Children } from 'react' import warning from 'warning' +import { getSlotOptions, getOptionProps } from '../../_util/props-util' export function arrDel (list, value) { const clone = list.slice() @@ -48,14 +48,13 @@ export function getPosition (level, index) { return `${level}-${index}` } -export function getNodeChildren (children) { - const childList = Array.isArray(children) ? children : [children] - return childList - .filter(child => child && child.type && child.type.isTreeNode) +export function getNodeChildren (children = []) { + return children + .filter(child => getSlotOptions(child).isTreeNode) } export function isCheckDisabled (node) { - const { disabled, disableCheckbox } = node.props || {} + const { disabled, disableCheckbox } = getOptionProps(node) || {} return !!(disabled || disableCheckbox) } @@ -66,7 +65,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) { } 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 // Filter children @@ -86,7 +85,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) { if (subTreeData) { // Statistic children const subNodes = [] - Children.forEach(childList, (subNode, subIndex) => { + childList.forEach((subNode, subIndex) => { // Provide limit snapshot const subPos = getPosition(pos, index) subNodes.push({ @@ -106,7 +105,7 @@ export function traverseTreeNodes (treeNodes, subTreeData, callback) { } // Process children node - Children.forEach(childList, (subNode, subIndex) => { + childList.forEach((subNode, subIndex) => { processNode(subNode, subIndex, { node, pos }) }) } @@ -182,7 +181,7 @@ export function getNodesStatistic (treeNodes) { } export function getDragNodesKeys (treeNodes, node) { - const { eventKey, pos } = node.props + const { eventKey, pos } = getOptionProps(node) const dragNodesKeys = [] traverseTreeNodes(treeNodes, ({ pos: nodePos, key }) => { @@ -214,12 +213,12 @@ export function calcDropPosition (event, treeNode) { * @param props * @returns [string] */ -export function calcExpandedKeys (keyList, props) { +export function calcExpandedKeys (keyList, props, children = []) { if (!keyList) { return [] } - const { autoExpandParent, children } = props + const { autoExpandParent } = props // Do nothing if not auto expand parent if (!autoExpandParent) { @@ -363,8 +362,8 @@ export function calcCheckStateConduct (treeNodes, checkedKeys) { * Calculate the value of checked and halfChecked keys. * This should be only run in init or props changed. */ -export function calcCheckedKeys (keys, props) { - const { checkable, children, checkStrictly } = props +export function calcCheckedKeys (keys, props, children = []) { + const { checkable, checkStrictly } = props if (!checkable || !keys) { return null diff --git a/examples/routes.js b/examples/routes.js index 73e054488..41b6a33fc 100644 --- a/examples/routes.js +++ b/examples/routes.js @@ -3,7 +3,7 @@ const AsyncComp = () => { const hashs = window.location.hash.split('/') const d = hashs[hashs.length - 1] return { - component: import(`../components/popconfirm/demo/${d}`), + component: import(`../components/vc-tree/demo/${d}`), } } export default [