diff --git a/components/checkbox/Checkbox.jsx b/components/checkbox/Checkbox.jsx index 722ed86e9..3056fc5c1 100644 --- a/components/checkbox/Checkbox.jsx +++ b/components/checkbox/Checkbox.jsx @@ -20,7 +20,6 @@ export default { }, inject: { checkboxGroupContext: { default: null }, - test: { default: null }, }, data () { const { checkboxGroupContext, checked, defaultChecked, value } = this diff --git a/components/dropdown/index.en-US.md b/components/dropdown/index.en-US.md index 4c872bd8c..e0f7c5b56 100644 --- a/components/dropdown/index.en-US.md +++ b/components/dropdown/index.en-US.md @@ -8,7 +8,7 @@ | getPopupContainer | to set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | `() => document.body` | | overlay(slot) | the dropdown menu | [Menu](#/us/components/menu) | - | | placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` | -| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` | +| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` | | visible(v-model) | whether the dropdown menu is visible | boolean | - | ### events @@ -30,7 +30,7 @@ You should use [Menu](#/us/components/menu/) as `overlay`. The menu items and di | overlay(slot) | the dropdown menu | [Menu](#/us/components/menu) | - | | placement | placement of pop menu: `bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` | | size | size of the button, the same as [Button](#/us/components/button) | string | `default` | -| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` | +| trigger | the trigger mode which executes the drop-down action | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` | | type | type of the button, the same as [Button](#/us/components/button) | string | `default` | | visible | whether the dropdown menu is visible | boolean | - | diff --git a/components/dropdown/index.zh-CN.md b/components/dropdown/index.zh-CN.md index 8c4f839ff..b65b5cf1d 100644 --- a/components/dropdown/index.zh-CN.md +++ b/components/dropdown/index.zh-CN.md @@ -8,7 +8,7 @@ | getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | `() => document.body` | | overlay(slot) | 菜单 | [Menu](#/cn/components/menu) | - | | placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` | -| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` | +| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` | | visible(v-model) | 菜单是否显示 | boolean | - | `overlay` 菜单使用 [Menu](#/cn/components/menu/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`。 @@ -31,7 +31,7 @@ | overlay(slot) | 菜单 | [Menu](#/cn/components/menu/) | - | | placement | 菜单弹出位置:`bottomLeft` `bottomCenter` `bottomRight` `topLeft` `topCenter` `topRight` | String | `bottomLeft` | | size | 按钮大小,和 [Button](#/cn/components/button/) 一致 | string | 'default' | -| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextMenu`> | `['hover']` | +| trigger | 触发下拉的行为 | Array<`click`\|`hover`\|`contextmenu`> | `['hover']` | | type | 按钮类型,和 [Button](#/cn/components/button/) 一致 | string | 'default' | | visible(v-model) | 菜单是否显示 | boolean | - | diff --git a/components/trigger/index.md b/components/trigger/index.md index 69f9cb643..bae1a0f2a 100644 --- a/components/trigger/index.md +++ b/components/trigger/index.md @@ -40,7 +40,7 @@ action string[] ['hover'] - which actions cause popup shown. enum of 'hover','click','focus','contextMenu' + which actions cause popup shown. enum of 'hover','click','focus','contextmenu' mouseEnterDelay diff --git a/components/vc-tree/assets/icons.png b/components/vc-tree/assets/icons.png new file mode 100644 index 000000000..ffda01ef1 Binary files /dev/null and b/components/vc-tree/assets/icons.png differ diff --git a/components/vc-tree/assets/index.less b/components/vc-tree/assets/index.less new file mode 100644 index 000000000..cde71e949 --- /dev/null +++ b/components/vc-tree/assets/index.less @@ -0,0 +1,192 @@ +@treePrefixCls: rc-tree; +.@{treePrefixCls} { + margin: 0; + padding: 5px; + li { + padding: 0; + margin: 0; + list-style: none; + white-space: nowrap; + outline: 0; + .draggable { + color: #333; + -moz-user-select: none; + -khtml-user-select: none; + -webkit-user-select: none; + user-select: none; + /* Required to make elements draggable in old WebKit */ + -khtml-user-drag: element; + -webkit-user-drag: element; + } + &.drag-over { + > .draggable { + background-color: #316ac5; + color: white; + border: 1px #316ac5 solid; + opacity: 0.8; + } + } + &.drag-over-gap-top { + > .draggable { + border-top: 2px blue solid; + } + } + &.drag-over-gap-bottom { + > .draggable { + border-bottom: 2px blue solid; + } + } + &.filter-node { + > .@{treePrefixCls}-node-content-wrapper { + color: #a60000!important; + font-weight: bold!important; + } + } + ul { + margin: 0; + padding: 0 0 0 18px; + } + .@{treePrefixCls}-node-content-wrapper { + display: inline-block; + padding: 1px 3px 0 0; + margin: 0; + cursor: pointer; + height: 17px; + text-decoration: none; + vertical-align: top; + } + span { + &.@{treePrefixCls}-switcher, + &.@{treePrefixCls}-checkbox, + &.@{treePrefixCls}-iconEle { + line-height: 16px; + margin-right: 2px; + width: 16px; + height: 16px; + display: inline-block; + vertical-align: middle; + border: 0 none; + cursor: pointer; + outline: none; + background-color: transparent; + background-repeat: no-repeat; + background-attachment: scroll; + background-image: url('data:image/png;base64,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'); + + &.@{treePrefixCls}-icon__customize { + background-image: none; + } + } + &.@{treePrefixCls}-icon_loading { + margin-right: 2px; + vertical-align: top; + background: url('data:image/gif;base64,R0lGODlhEAAQAKIGAMLY8YSx5HOm4Mjc88/g9Ofw+v///wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAGACwAAAAAEAAQAAADMGi6RbUwGjKIXCAA016PgRBElAVlG/RdLOO0X9nK61W39qvqiwz5Ls/rRqrggsdkAgAh+QQFCgAGACwCAAAABwAFAAADD2hqELAmiFBIYY4MAutdCQAh+QQFCgAGACwGAAAABwAFAAADD1hU1kaDOKMYCGAGEeYFCQAh+QQFCgAGACwKAAIABQAHAAADEFhUZjSkKdZqBQG0IELDQAIAIfkEBQoABgAsCgAGAAUABwAAAxBoVlRKgyjmlAIBqCDCzUoCACH5BAUKAAYALAYACgAHAAUAAAMPaGpFtYYMAgJgLogA610JACH5BAUKAAYALAIACgAHAAUAAAMPCAHWFiI4o1ghZZJB5i0JACH5BAUKAAYALAAABgAFAAcAAAMQCAFmIaEp1motpDQySMNFAgA7') no-repeat scroll 0 0 transparent; + } + &.@{treePrefixCls}-switcher { + &.@{treePrefixCls}-switcher-noop { + cursor: auto; + } + &.@{treePrefixCls}-switcher_open { + background-position: -93px -56px; + } + &.@{treePrefixCls}-switcher_close { + background-position: -75px -56px; + } + } + &.@{treePrefixCls}-checkbox { + width: 13px; + height: 13px; + margin: 0 3px; + background-position: 0 0; + &-checked { + background-position: -14px 0; + } + &-indeterminate { + background-position: -14px -28px; + } + &-disabled { + background-position: 0 -56px; + } + &.@{treePrefixCls}-checkbox-checked.@{treePrefixCls}-checkbox-disabled { + background-position: -14px -56px; + } + &.@{treePrefixCls}-checkbox-indeterminate.@{treePrefixCls}-checkbox-disabled { + position: relative; + background: #ccc; + border-radius: 3px; + &::after { + content: ' '; + -webkit-transform: scale(1); + transform: scale(1); + position: absolute; + left: 3px; + top: 5px; + width: 5px; + height: 0; + border: 2px solid #fff; + border-top: 0; + border-left: 0; + } + } + } + } + } + &:not(.@{treePrefixCls}-show-line) { + .@{treePrefixCls}-switcher-noop { + background: none; + } + } + &.@{treePrefixCls}-show-line { + li:not(:last-child) { + > ul { + background: url('data:image/gif;base64,R0lGODlhCQACAIAAAMzMzP///yH5BAEAAAEALAAAAAAJAAIAAAIEjI9pUAA7') 0 0 repeat-y; + } + > .@{treePrefixCls}-switcher-noop { + background-position: -56px -18px; + } + } + li:last-child { + > .@{treePrefixCls}-switcher-noop { + background-position: -56px -36px; + } + } + } + &-child-tree { + display: none; + &-open { + display: block; + } + } + &-treenode-disabled { + >span:not(.@{treePrefixCls}-switcher), + >a, + >a span { + color: #ccc; + cursor: not-allowed; + } + } + &-node-selected { + background-color: #ffe6b0; + border: 1px #ffb951 solid; + opacity: 0.8; + } + &-icon__open { + margin-right: 2px; + background-position: -110px -16px; + vertical-align: top; + } + &-icon__close { + margin-right: 2px; + background-position: -110px 0; + vertical-align: top; + } + &-icon__docu { + margin-right: 2px; + background-position: -110px -32px; + vertical-align: top; + } + &-icon__customize { + margin-right: 2px; + vertical-align: top; + } +} diff --git a/components/vc-tree/assets/line.gif b/components/vc-tree/assets/line.gif new file mode 100644 index 000000000..d561d36a9 Binary files /dev/null and b/components/vc-tree/assets/line.gif differ diff --git a/components/vc-tree/assets/loading.gif b/components/vc-tree/assets/loading.gif new file mode 100644 index 000000000..e8c289293 Binary files /dev/null and b/components/vc-tree/assets/loading.gif differ diff --git a/components/vc-tree/index.js b/components/vc-tree/index.js new file mode 100644 index 000000000..8f31b413f --- /dev/null +++ b/components/vc-tree/index.js @@ -0,0 +1,3 @@ +'use strict' + +module.exports = require('./src/') diff --git a/components/vc-tree/src/Tree.jsx b/components/vc-tree/src/Tree.jsx new file mode 100644 index 000000000..53a1b9646 --- /dev/null +++ b/components/vc-tree/src/Tree.jsx @@ -0,0 +1,610 @@ +import PropTypes from '../../_util/vue-types' +import classNames from 'classnames' +import warning from 'warning' +import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util' +import { + traverseTreeNodes, getStrictlyValue, + getFullKeyList, getPosition, getDragNodesKeys, + calcExpandedKeys, calcSelectedKeys, + calcCheckedKeys, calcDropPosition, + arrAdd, arrDel, posToArr, +} from './util' + +/** + * Thought we still use `cloneElement` to pass `key`, + * other props can pass with context for future refactor. + */ +export const contextTypes = { + rcTree: PropTypes.shape({ + root: PropTypes.object, + + prefixCls: PropTypes.string, + selectable: PropTypes.bool, + showIcon: PropTypes.bool, + draggable: PropTypes.bool, + checkable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.object, + ]), + checkStrictly: PropTypes.bool, + disabled: PropTypes.bool, + openTransitionName: PropTypes.string, + openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + + loadData: PropTypes.func, + filterTreeNode: PropTypes.func, + renderTreeNode: PropTypes.func, + + isKeyChecked: PropTypes.func, + + // onNodeExpand: PropTypes.func, + // onNodeSelect: PropTypes.func, + // onNodeMouseEnter: PropTypes.func, + // onNodeMouseLeave: PropTypes.func, + // onNodeContextMenu: PropTypes.func, + // onNodeDragStart: PropTypes.func, + // onNodeDragEnter: PropTypes.func, + // onNodeDragOver: PropTypes.func, + // onNodeDragLeave: PropTypes.func, + // onNodeDragEnd: PropTypes.func, + // onNodeDrop: PropTypes.func, + // onBatchNodeCheck: PropTypes.func, + // onCheckConductFinished: PropTypes.func, + }), +} + +const Tree = { + props: initDefaultProps({ + prefixCls: PropTypes.string, + showLine: PropTypes.bool, + showIcon: PropTypes.bool, + focusable: PropTypes.bool, + selectable: PropTypes.bool, + disabled: PropTypes.bool, + multiple: PropTypes.bool, + checkable: PropTypes.oneOfType([ + PropTypes.bool, + PropTypes.node, + ]), + checkStrictly: PropTypes.bool, + draggable: PropTypes.bool, + autoExpandParent: PropTypes.bool, + defaultExpandAll: PropTypes.bool, + defaultExpandedKeys: PropTypes.arrayOf(PropTypes.string), + expandedKeys: PropTypes.arrayOf(PropTypes.string), + defaultCheckedKeys: PropTypes.arrayOf(PropTypes.string), + checkedKeys: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string), + PropTypes.object, + ]), + defaultSelectedKeys: PropTypes.arrayOf(PropTypes.string), + selectedKeys: PropTypes.arrayOf(PropTypes.string), + //onExpand: PropTypes.func, + //onCheck: PropTypes.func, + //onSelect: PropTypes.func, + loadData: PropTypes.func, + // onMouseEnter: PropTypes.func, + // onMouseLeave: PropTypes.func, + // onRightClick: PropTypes.func, + // onDragStart: PropTypes.func, + // onDragEnter: PropTypes.func, + // onDragOver: PropTypes.func, + // onDragLeave: PropTypes.func, + // onDragEnd: PropTypes.func, + // onDrop: PropTypes.func, + filterTreeNode: PropTypes.func, + openTransitionName: PropTypes.string, + openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), + }, { + prefixCls: 'rc-tree', + showLine: false, + showIcon: true, + selectable: true, + multiple: false, + checkable: false, + disabled: false, + checkStrictly: false, + draggable: false, + autoExpandParent: true, + defaultExpandAll: false, + defaultExpandedKeys: [], + defaultCheckedKeys: [], + defaultSelectedKeys: [], + }), + + // static childContextTypes = contextTypes; + + data () { + const props = getOptionProps(this) + const { + defaultExpandAll, + defaultExpandedKeys, + defaultCheckedKeys, + defaultSelectedKeys, + } = props + + // Sync state with props + const { checkedKeys = [], halfCheckedKeys = [] } = + calcCheckedKeys(defaultCheckedKeys, props) || {} + + // Cache for check status to optimize + this.checkedBatch = null + + return { + sExpandedKeys: defaultExpandAll + ? getFullKeyList(this.$slots.default) + : calcExpandedKeys(defaultExpandedKeys, props), + sSelectedKeys: calcSelectedKeys(defaultSelectedKeys, props), + sCheckedKeys, + sHalfCheckedKeys, + + ...(this.getSyncProps(props) || {}), + } + }, + 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 }) + } + }, + + /** + * [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 + + const dropPosition = calcDropPosition(event, node) + + // Skip if drag node is self + if ( + this.dragNode.props.eventKey === eventKey && + dropPosition === 0 + ) { + this.setState({ + 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, + }) + + // 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) + this.setState({ + expandedKeys: newExpandedKeys, + }) + + 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) + } + }) + } + + this.setUncontrolledState({ selectedKeys }) + + if (onSelect) { + const eventObj = { + event: 'select', + selected: targetSelected, + node: treeNode, + selectedNodes, + } + onSelect(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 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 { 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. + // 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 + } + + if (checkSync('expandedKeys')) { + newState.expandedKeys = calcExpandedKeys(props.expandedKeys, props) + } + + if (checkSync('selectedKeys')) { + newState.selectedKeys = calcSelectedKeys(props.selectedKeys, props) + } + + if (checkSync('checkedKeys')) { + const { checkedKeys = [], halfCheckedKeys = [] } = + calcCheckedKeys(props.checkedKeys, props) || {} + newState.checkedKeys = checkedKeys + newState.halfCheckedKeys = halfCheckedKeys + } + + return needSync ? newState : null + }; + + /** + * Only update the value which is not in props + */ + setUncontrolledState = (state) => { + let needSync = false + const newState = {} + + Object.keys(state).forEach(name => { + if (name in this.props) return + + needSync = true + newState[name] = state[name] + }) + + this.setState(needSync ? newState : null) + }; + + isKeyChecked = (key) => { + const { checkedKeys = [] } = this.state + return checkedKeys.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 { + expandedKeys = [], selectedKeys = [], halfCheckedKeys = [], + dragOverNodeKey, dropPosition, + } = this.state + const {} = this.props + const pos = getPosition(level, index) + const key = child.key || pos + + 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, + + // [Legacy] Drag props + dragOver: dragOverNodeKey === key && dropPosition === 0, + dragOverGapTop: dragOverNodeKey === key && dropPosition === -1, + dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1, + }) + }; + + render () { + const { + prefixCls, className, focusable, + showLine, + children, + } = this.props + 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 new file mode 100644 index 000000000..155585404 --- /dev/null +++ b/components/vc-tree/src/TreeNode.jsx @@ -0,0 +1,585 @@ +import PropTypes from '../../_util/vue-types' +import classNames from 'classnames' +import warning from 'warning' +import { contextTypes } from './Tree' +import { getPosition, getNodeChildren, isCheckDisabled, traverseTreeNodes } from './util' +import { initDefaultProps, getOptionProps, filterEmpty } from '../../_util/props-util' + +const ICON_OPEN = 'open' +const ICON_CLOSE = 'close' + +const LOAD_STATUS_NONE = 0 +const LOAD_STATUS_LOADING = 1 +const LOAD_STATUS_LOADED = 2 +const LOAD_STATUS_FAILED = 0 // Action align, let's make failed same as init. + +const defaultTitle = '---' + +let onlyTreeNodeWarned = false // Only accept TreeNode + +export const nodeContextTypes = { + ...contextTypes, + rcTreeNode: PropTypes.shape({ + onUpCheckConduct: PropTypes.func, + }), +} + +const TreeNode = { + props: initDefaultProps({ + eventKey: PropTypes.string, // Pass by parent `cloneElement` + prefixCls: PropTypes.string, + // className: PropTypes.string, + root: PropTypes.object, + // onSelect: PropTypes.func, + + // By parent + expanded: PropTypes.bool, + selected: PropTypes.bool, + checked: PropTypes.bool, + halfChecked: PropTypes.bool, + title: PropTypes.node, + pos: PropTypes.string, + dragOver: PropTypes.bool, + dragOverGapTop: PropTypes.bool, + dragOverGapBottom: PropTypes.bool, + + // By user + isLeaf: PropTypes.bool, + selectable: PropTypes.bool, + disabled: PropTypes.bool, + disableCheckbox: PropTypes.bool, + icon: PropTypes.any, + }, { + title: defaultTitle, + }), + + data () { + return { + loadStatus: LOAD_STATUS_NONE, + dragNodeHighlight: false, + } + }, + inject: { + context: { default: {}}, + }, + provide: { + ...this.context, + rcTreeNode: this, + }, + + // Isomorphic needn't load data in server side + mounted () { + this.$nextTick(() => { + this.syncLoadData(this.$props) + }) + }, + + componentWillReceiveProps (nextProps) { + this.syncLoadData(nextProps) + }, + + 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)) { + return + } + + if (isKeyChecked(node.key || childPos)) { + checkedCount += 1 + } + }) + + // Static enabled children count + const enabledChildrenCount = children + .filter(node => !isCheckDisabled(node)) + .length + + // checkStrictly will not conduct check status + const nextChecked = checkStrictly ? checked : enabledChildrenCount === checkedCount + const nextHalfChecked = checkStrictly // propagated or child checked + ? halfChecked : (nodeHalfChecked || (checkedCount > 0 && !nextChecked)) + + // Add into batch update + if (checked !== nextChecked || halfChecked !== nextHalfChecked) { + onBatchNodeCheck(eventKey, nextChecked, nextHalfChecked) + + if (onUpCheckConduct) { + onUpCheckConduct(this, nextChecked, nextHalfChecked) + } 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 + + traverseTreeNodes(children, ({ node, key }) => { + if (isCheckDisabled(node)) return false + + if (nodeChecked !== isKeyChecked(key)) { + onBatchNodeCheck(key, nodeChecked, false) + } + }) + }, + + onSelectorClick (e) { + if (this.isSelectable()) { + this.onSelect(e) + } else { + this.onCheck(e) + } + }, + + onSelect (e) { + if (this.isDisabled()) return + + const { rcTree: { onNodeSelect }} = this.context + e.preventDefault() + onNodeSelect(e, this) + }, + + onCheck (e) { + if (this.isDisabled()) return + + const { disableCheckbox, checked, eventKey } = this + const { + rcTree: { checkable, onBatchNodeCheck, onCheckConductFinished }, + rcTreeNode: { onUpCheckConduct } = {}, + } = this.context + + if (!checkable || disableCheckbox) return + + e.preventDefault() + const targetChecked = !checked + onBatchNodeCheck(eventKey, targetChecked, false, this) + + // Children conduct + this.onDownCheckConduct(targetChecked) + + // Parent conduct + if (onUpCheckConduct) { + onUpCheckConduct(this, targetChecked, false) + } else { + onCheckConductFinished() + } + }, + + onMouseEnter (e) { + const { rcTree: { onNodeMouseEnter }} = this.context + onNodeMouseEnter(e, this) + }, + + onMouseLeave (e) { + const { rcTree: { onNodeMouseLeave }} = this.context + onNodeMouseLeave(e, this) + }, + + onContextMenu (e) { + const { rcTree: { onNodeContextMenu }} = this.context + onNodeContextMenu(e, 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 }) + }) + } + }, + + // Drag usage + setSelectHandle (node) { + this.selectHandle = node + }, + + getNodeChildren () { + const { $slots: { default: children }} = this + const originList = filterEmpty(children) + const targetList = getNodeChildren(originList) + + if (originList.length !== targetList.length && !onlyTreeNodeWarned) { + onlyTreeNodeWarned = true + warning(false, 'Tree only accept TreeNode as children.') + } + + return targetList + }, + + getNodeState () { + const { expanded } = this + + if (this.isLeaf()) { + return null + } + + return expanded ? ICON_OPEN : ICON_CLOSE + }, + + isLeaf () { + const { isLeaf, loadStatus } = this + const { rcTree: { loadData }} = this.context + + 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 }) + }) + } + }, + + // Switcher + renderSwitcher () { + const { expanded } = this + const { rcTree: { prefixCls }} = this.context + + if (this.isLeaf()) { + return + } + + return ( + + ) + }, + + // Checkbox + renderCheckbox () { + const { checked, halfChecked, disableCheckbox } = this + const { rcTree: { prefixCls, checkable }} = this.context + 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 { rcTree: { prefixCls }} = this.context + + return ( + + ) + }, + + // Icon + Title + renderSelector () { + 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` + + // 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 { 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} + + ) + }, + + render () { + const { + dragOver, dragOverGapTop, dragOverGapBottom, + } = this + const { rcTree: { + prefixCls, + filterTreeNode, + }} = this.context + const disabled = this.isDisabled() + + return ( +
  • + {this.renderSwitcher()} + {this.renderCheckbox()} + {this.renderSelector()} + {this.renderChildren()} +
  • + ) + }, +} + +TreeNode.isTreeNode = 1 + +export default TreeNode diff --git a/components/vc-tree/src/index.js b/components/vc-tree/src/index.js new file mode 100644 index 000000000..d053f4c9f --- /dev/null +++ b/components/vc-tree/src/index.js @@ -0,0 +1,6 @@ +import Tree from './Tree' +import TreeNode from './TreeNode' +Tree.TreeNode = TreeNode + +export { TreeNode } +export default Tree diff --git a/components/vc-tree/src/util.js b/components/vc-tree/src/util.js new file mode 100644 index 000000000..41e39ed08 --- /dev/null +++ b/components/vc-tree/src/util.js @@ -0,0 +1,399 @@ +/* eslint no-loop-func: 0*/ +import { Children } from 'react' +import warning from 'warning' + +export function arrDel (list, value) { + const clone = list.slice() + const index = clone.indexOf(value) + if (index >= 0) { + clone.splice(index, 1) + } + return clone +} + +export function arrAdd (list, value) { + const clone = list.slice() + if (clone.indexOf(value) === -1) { + clone.push(value) + } + return clone +} + +export function posToArr (pos) { + return pos.split('-') +} + +// Only used when drag, not affect SSR. +export function getOffset (ele) { + if (!ele.getClientRects().length) { + return { top: 0, left: 0 } + } + + const rect = ele.getBoundingClientRect() + if (rect.width || rect.height) { + const doc = ele.ownerDocument + const win = doc.defaultView + const docElem = doc.documentElement + + return { + top: rect.top + win.pageYOffset - docElem.clientTop, + left: rect.left + win.pageXOffset - docElem.clientLeft, + } + } + + return rect +} + +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 isCheckDisabled (node) { + const { disabled, disableCheckbox } = node.props || {} + return !!(disabled || disableCheckbox) +} + +export function traverseTreeNodes (treeNodes, subTreeData, callback) { + if (typeof subTreeData === 'function') { + callback = subTreeData + subTreeData = false + } + + function processNode (node, index, parent) { + const children = node ? node.props.children : treeNodes + const pos = node ? getPosition(parent.pos, index) : 0 + + // Filter children + const childList = getNodeChildren(children) + + // Process node if is not root + if (node) { + const data = { + node, + index, + pos, + key: node.key || pos, + parentPos: parent.node ? parent.pos : null, + } + + // Children data is not must have + if (subTreeData) { + // Statistic children + const subNodes = [] + Children.forEach(childList, (subNode, subIndex) => { + // Provide limit snapshot + const subPos = getPosition(pos, index) + subNodes.push({ + node: subNode, + key: subNode.key || subPos, + pos: subPos, + index: subIndex, + }) + }) + data.subNodes = subNodes + } + + // Can break traverse by return false + if (callback(data) === false) { + return + } + } + + // Process children node + Children.forEach(childList, (subNode, subIndex) => { + processNode(subNode, subIndex, { node, pos }) + }) + } + + processNode(null) +} + +/** + * [Legacy] Return halfChecked when it has value. + * @param checkedKeys + * @param halfChecked + * @returns {*} + */ +export function getStrictlyValue (checkedKeys, halfChecked) { + if (halfChecked) { + return { checked: checkedKeys, halfChecked } + } + return checkedKeys +} + +export function getFullKeyList (treeNodes) { + const keyList = [] + traverseTreeNodes(treeNodes, ({ key }) => { + keyList.push(key) + }) + return keyList +} + +/** + * Check position relation. + * @param parentPos + * @param childPos + * @param directly only directly parent can be true + * @returns {boolean} + */ +export function isParent (parentPos, childPos, directly = false) { + if (!parentPos || !childPos || parentPos.length > childPos.length) return false + + const parentPath = posToArr(parentPos) + const childPath = posToArr(childPos) + + // Directly check + if (directly && parentPath.length !== childPath.length - 1) return false + + const len = parentPath.length + for (let i = 0; i < len; i += 1) { + if (parentPath[i] !== childPath[i]) return false + } + + return true +} + +/** + * Statistic TreeNodes info + * @param treeNodes + * @returns {{}} + */ +export function getNodesStatistic (treeNodes) { + const statistic = { + keyNodes: {}, + posNodes: {}, + nodeList: [], + } + + traverseTreeNodes(treeNodes, true, ({ node, index, pos, key, subNodes, parentPos }) => { + const data = { node, index, pos, key, subNodes, parentPos } + statistic.keyNodes[key] = data + statistic.posNodes[pos] = data + statistic.nodeList.push(data) + }) + + return statistic +} + +export function getDragNodesKeys (treeNodes, node) { + const { eventKey, pos } = node.props + const dragNodesKeys = [] + + traverseTreeNodes(treeNodes, ({ pos: nodePos, key }) => { + if (isParent(pos, nodePos)) { + dragNodesKeys.push(key) + } + }) + dragNodesKeys.push(eventKey || pos) + return dragNodesKeys +} + +export function calcDropPosition (event, treeNode) { + const offsetTop = getOffset(treeNode.selectHandle).top + const offsetHeight = treeNode.selectHandle.offsetHeight + const pageY = event.pageY + const gapHeight = 2 // [Legacy] TODO: remove hard code + if (pageY > offsetTop + offsetHeight - gapHeight) { + return 1 + } + if (pageY < offsetTop + gapHeight) { + return -1 + } + return 0 +} + +/** + * Auto expand all related node when sub node is expanded + * @param keyList + * @param props + * @returns [string] + */ +export function calcExpandedKeys (keyList, props) { + if (!keyList) { + return [] + } + + const { autoExpandParent, children } = props + + // Do nothing if not auto expand parent + if (!autoExpandParent) { + return keyList + } + + // Fill parent expanded keys + const { keyNodes, nodeList } = getNodesStatistic(children) + const needExpandKeys = {} + const needExpandPathList = [] + + // Fill expanded nodes + keyList.forEach((key) => { + const node = keyNodes[key] + if (node) { + needExpandKeys[key] = true + needExpandPathList.push(node.pos) + } + }) + + // Match parent by path + nodeList.forEach(({ pos, key }) => { + if (needExpandPathList.some(childPos => isParent(pos, childPos))) { + needExpandKeys[key] = true + } + }) + + const calcExpandedKeyList = Object.keys(needExpandKeys) + + // [Legacy] Return origin keyList if calc list is empty + return calcExpandedKeyList.length ? calcExpandedKeyList : keyList +} + +/** + * Return selectedKeys according with multiple prop + * @param selectedKeys + * @param props + * @returns [string] + */ +export function calcSelectedKeys (selectedKeys, props) { + if (!selectedKeys) { + return undefined + } + + const { multiple } = props + if (multiple) { + return selectedKeys.slice() + } + + if (selectedKeys.length) { + return [selectedKeys[0]] + } + return selectedKeys +} + +/** + * Check conduct is by key level. It pass though up & down. + * When conduct target node is check means already conducted will be skip. + * @param treeNodes + * @param checkedKeys + * @returns {{checkedKeys: Array, halfCheckedKeys: Array}} + */ +export function calcCheckStateConduct (treeNodes, checkedKeys) { + const { keyNodes, posNodes } = getNodesStatistic(treeNodes) + + const tgtCheckedKeys = {} + const tgtHalfCheckedKeys = {} + + // Conduct up + function conductUp (key, halfChecked) { + if (tgtCheckedKeys[key]) return + + const { subNodes = [], parentPos, node } = keyNodes[key] + if (isCheckDisabled(node)) return + + const allSubChecked = !halfChecked && subNodes + .filter(sub => !isCheckDisabled(sub.node)) + .every(sub => tgtCheckedKeys[sub.key]) + + if (allSubChecked) { + tgtCheckedKeys[key] = true + } else { + tgtHalfCheckedKeys[key] = true + } + + if (parentPos !== null) { + conductUp(posNodes[parentPos].key, !allSubChecked) + } + } + + // Conduct down + function conductDown (key) { + if (tgtCheckedKeys[key]) return + const { subNodes = [], node } = keyNodes[key] + + if (isCheckDisabled(node)) return + + tgtCheckedKeys[key] = true + + subNodes.forEach((sub) => { + conductDown(sub.key) + }) + } + + function conduct (key) { + if (!keyNodes[key]) { + warning(false, `'${key}' does not exist in the tree.`) + return + } + + const { subNodes = [], parentPos, node } = keyNodes[key] + if (isCheckDisabled(node)) return + + tgtCheckedKeys[key] = true + + // Conduct down + subNodes + .filter(sub => !isCheckDisabled(sub.node)) + .forEach((sub) => { + conductDown(sub.key) + }) + + // Conduct up + if (parentPos !== null) { + conductUp(posNodes[parentPos].key) + } + } + + checkedKeys.forEach((key) => { + conduct(key) + }) + + return { + checkedKeys: Object.keys(tgtCheckedKeys), + halfCheckedKeys: Object.keys(tgtHalfCheckedKeys) + .filter(key => !tgtCheckedKeys[key]), + } +} + +/** + * 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 + + if (!checkable || !keys) { + return null + } + + // Convert keys to object format + let keyProps + if (Array.isArray(keys)) { + // [Legacy] Follow the api doc + keyProps = { + checkedKeys: keys, + halfCheckedKeys: undefined, + } + } else if (typeof keys === 'object') { + keyProps = { + checkedKeys: keys.checked || undefined, + halfCheckedKeys: keys.halfChecked || undefined, + } + } else { + warning(false, '`CheckedKeys` is not an array or an object') + return null + } + + // Do nothing if is checkStrictly mode + if (checkStrictly) { + return keyProps + } + + // Conduct calculate the check status + const { checkedKeys = [] } = keyProps + return calcCheckStateConduct(children, checkedKeys) +}