feat: update tree

pull/2682/head
tanjinzhou 2020-07-16 18:31:20 +08:00
parent 10b25b0ec7
commit 61500001df
10 changed files with 146 additions and 152 deletions

View File

@ -113,6 +113,7 @@ const getAllChildren = ele => {
return ele.children || componentOptions.children || [];
};
const getSlotOptions = ele => {
throw Error('使用 .type 直接取值');
if (ele.fnOptions) {
// 函数式组件
return ele.fnOptions;

View File

@ -1,10 +1,11 @@
import { inject } from 'vue';
import omit from 'omit.js';
import debounce from 'lodash/debounce';
import FolderOpenOutlined from '@ant-design/icons-vue/FolderOpenOutlined';
import FolderOutlined from '@ant-design/icons-vue/FolderOutlined';
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
import PropTypes from '../_util/vue-types';
import warning from '../_util/warning';
import classNames from 'classnames';
import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util';
import Tree, { TreeProps } from './Tree';
import {
@ -14,12 +15,7 @@ import {
getFullKeyListByTreeData,
} from './util';
import BaseMixin from '../_util/BaseMixin';
import {
initDefaultProps,
getOptionProps,
getListeners,
getComponentFromProp,
} from '../_util/props-util';
import { initDefaultProps, getOptionProps, getComponent, getSlot } from '../_util/props-util';
import { ConfigConsumerProps } from '../config-provider';
// export type ExpandAction = false | 'click' | 'dblclick'; export interface
@ -27,7 +23,7 @@ import { ConfigConsumerProps } from '../config-provider';
// export interface DirectoryTreeState { expandedKeys?: string[];
// selectedKeys?: string[]; }
function getIcon(props, h) {
function getIcon(props) {
const { isLeaf, expanded } = props;
if (isLeaf) {
return <FileOutlined />;
@ -38,10 +34,11 @@ function getIcon(props, h) {
export default {
name: 'ADirectoryTree',
mixins: [BaseMixin],
model: {
prop: 'checkedKeys',
event: 'check',
},
inheritAttrs: false,
// model: {
// prop: 'checkedKeys',
// event: 'check',
// },
props: initDefaultProps(
{
...TreeProps(),
@ -52,14 +49,10 @@ export default {
expandAction: 'click',
},
),
// state: DirectoryTreeState; onDebounceExpand: (event, node: AntTreeNode) =>
// void; // Shift click usage lastSelectedKey?: string; cachedSelectedKeys?:
// string[];
inject: {
configProvider: {
default: () => ConfigConsumerProps,
},
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
},
data() {
const props = getOptionProps(this);
@ -83,6 +76,7 @@ export default {
}
this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true });
this.children = null;
return {
_selectedKeys: [],
_expandedKeys: [],
@ -130,7 +124,7 @@ export default {
onSelect(keys, event) {
const { multiple } = this.$props;
const children = this.$slots.default || [];
const children = this.children || [];
const { _expandedKeys: expandedKeys = [] } = this.$data;
const { node, nativeEvent } = event;
const { eventKey = '' } = node;
@ -178,6 +172,9 @@ export default {
this.setUncontrolledState(newState);
},
setTreeRef(node) {
this.tree = node;
},
expandFolderNode(event, node) {
const { isLeaf } = node;
@ -186,9 +183,9 @@ export default {
return;
}
if (this.$refs.tree.$refs.tree) {
if (this.tree.tree) {
// Get internal vc-tree
const internalTree = this.$refs.tree.$refs.tree;
const internalTree = this.tree.tree;
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
@ -208,31 +205,28 @@ export default {
},
render() {
this.children = getSlot(this);
const { prefixCls: customizePrefixCls, ...props } = getOptionProps(this);
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
const { _expandedKeys: expandedKeys, _selectedKeys: selectedKeys } = this.$data;
const listeners = getListeners(this);
warning(!listeners.doubleclick, '`doubleclick` is deprecated. please use `dblclick` instead.');
const { class: className, ...restAttrs } = this.$attrs;
const connectClassName = classNames(`${prefixCls}-directory`, className);
const treeProps = {
props: {
icon: getIcon,
...props,
prefixCls,
expandedKeys,
selectedKeys,
switcherIcon: getComponentFromProp(this, 'switcherIcon'),
},
ref: 'tree',
class: `${prefixCls}-directory`,
on: {
...omit(listeners, ['update:selectedKeys']),
select: this.onSelect,
click: this.onClick,
dblclick: this.onDoubleClick,
expand: this.onExpand,
},
icon: getIcon,
...props,
...omit(restAttrs, ['onUpdate:selectedKeys']),
prefixCls,
expandedKeys,
selectedKeys,
switcherIcon: getComponent(this, 'switcherIcon'),
ref: this.setTreeRef,
class: connectClassName,
onSelect: this.onSelect,
onClick: this.onClick,
onDblclick: this.onDoubleClick,
onExpand: this.onExpand,
};
return <Tree {...treeProps}>{this.$slots.default}</Tree>;
return <Tree {...treeProps}>{this.children}</Tree>;
},
};

View File

@ -1,4 +1,5 @@
import warning from 'warning';
import { inject } from 'vue';
import classNames from 'classnames';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled';
@ -7,13 +8,7 @@ import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined';
import { Tree as VcTree, TreeNode } from '../vc-tree';
import animation from '../_util/openAnimation';
import PropTypes from '../_util/vue-types';
import {
initDefaultProps,
getOptionProps,
filterEmpty,
getComponentFromProp,
getListeners,
} from '../_util/props-util';
import { initDefaultProps, getOptionProps, getComponent, getSlot } from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
import { ConfigConsumerProps } from '../config-provider';
@ -89,7 +84,6 @@ function TreeProps() {
prefixCls: PropTypes.string,
filterTreeNode: PropTypes.func,
openAnimation: PropTypes.any,
treeNodes: PropTypes.array,
treeData: PropTypes.array,
/**
* @default{title,key,children}
@ -104,27 +98,20 @@ export { TreeProps };
export default {
name: 'ATree',
model: {
prop: 'checkedKeys',
event: 'check',
},
inheritAttrs: false,
props: initDefaultProps(TreeProps(), {
checkable: false,
showIcon: false,
openAnimation: {
on: animation,
props: { appear: null },
...animation,
appear: null,
},
blockNode: false,
}),
inject: {
configProvider: { default: () => ConfigConsumerProps },
},
created() {
warning(
!('treeNodes' in getOptionProps(this)),
'`treeNodes` is deprecated. please use treeData instead.',
);
setup() {
return {
configProvider: inject('configProvider', ConfigConsumerProps),
};
},
TreeNode,
methods: {
@ -146,34 +133,32 @@ export default {
});
}
return showLine ? (
expanded ?
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} /> :
expanded ? (
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
) : (
<PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
)
) : (
<CaretDownFilled class={switcherCls} />
);
},
updateTreeData(treeData) {
const { $slots, $scopedSlots } = this;
const { $slots } = this;
const defaultFields = { children: 'children', title: 'title', key: 'key' };
const replaceFields = { ...defaultFields, ...this.$props.replaceFields };
return treeData.map(item => {
const key = item[replaceFields.key];
const children = item[replaceFields.children];
const { on = {}, slots = {}, scopedSlots = {}, class: cls, style, ...restProps } = item;
const { slots = {}, scopedSlots = {}, class: cls, style, ...restProps } = item;
const treeNodeProps = {
...restProps,
icon: $scopedSlots[scopedSlots.icon] || $slots[slots.icon] || restProps.icon,
icon: $slots[scopedSlots.icon] || $slots[slots.icon] || restProps.icon,
switcherIcon:
$scopedSlots[scopedSlots.switcherIcon] ||
$slots[scopedSlots.switcherIcon] ||
$slots[slots.switcherIcon] ||
restProps.switcherIcon,
title:
$scopedSlots[scopedSlots.title] ||
$slots[slots.title] ||
restProps[replaceFields.title],
title: $slots[scopedSlots.title] || $slots[slots.title] || restProps[replaceFields.title],
dataRef: item,
on,
key,
class: cls,
style,
@ -184,37 +169,38 @@ export default {
return treeNodeProps;
});
},
setTreeRef(node) {
this.tree = node;
},
},
render() {
const props = getOptionProps(this);
const { $slots, $scopedSlots } = this;
const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
const switcherIcon = getComponentFromProp(this, 'switcherIcon');
const switcherIcon = getComponent(this, 'switcherIcon');
const checkable = props.checkable;
let treeData = props.treeData || treeNodes;
if (treeData) {
treeData = this.updateTreeData(treeData);
}
const { class: className, ...restAttrs } = this.$attrs;
const vcTreeProps = {
props: {
...props,
prefixCls,
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
children: filterEmpty($scopedSlots.default ? $scopedSlots.default() : $slots.default),
__propsSymbol__: Symbol(),
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps),
},
on: getListeners(this),
ref: 'tree',
class: {
...props,
prefixCls,
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
children: getSlot(this),
__propsSymbol__: Symbol(),
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps),
ref: this.setTreeRef,
...restAttrs,
class: classNames(className, {
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
},
}),
};
if (treeData) {
vcTreeProps.props.treeData = treeData;
vcTreeProps.treeData = treeData;
}
return <VcTree {...vcTreeProps} />;
},

View File

@ -1,15 +1,13 @@
import Tree from './Tree';
import DirectoryTree from './DirectoryTree';
import Base from '../base';
Tree.TreeNode.name = 'ATreeNode';
Tree.DirectoryTree = DirectoryTree;
/* istanbul ignore next */
Tree.install = function(Vue) {
Vue.use(Base);
Vue.component(Tree.name, Tree);
Vue.component(Tree.TreeNode.name, Tree.TreeNode);
Vue.component(DirectoryTree.name, DirectoryTree);
Tree.install = function(app) {
app.component(Tree.name, Tree);
app.component(Tree.TreeNode.name, Tree.TreeNode);
app.component(DirectoryTree.name, DirectoryTree);
};
export default Tree;

View File

@ -13,7 +13,7 @@ function traverseNodesKey(rootChildren, callback) {
function processNode(node) {
const { key } = node;
const children = getSlots(node).default;
const children = getSlots(node);
if (callback(key, node) !== false) {
traverseNodesKey(typeof children === 'function' ? children() : children, callback);
}

View File

@ -4,7 +4,6 @@ import warning from 'warning';
import { hasProp, initDefaultProps, getOptionProps, getSlots } from '../../_util/props-util';
import { cloneElement } from '../../_util/vnode';
import BaseMixin from '../../_util/BaseMixin';
import proxyComponent from '../../_util/proxyComponent';
import {
convertTreeToEntities,
convertDataToTree,
@ -20,6 +19,7 @@ import {
mapChildren,
conductCheck,
warnOnlyTreeNode,
getDataAndAria,
} from './util';
/**
@ -39,6 +39,7 @@ function getWatch(keys = []) {
const Tree = {
name: 'Tree',
inheritAttrs: false,
mixins: [BaseMixin],
props: initDefaultProps(
{
@ -85,7 +86,7 @@ const Tree = {
openTransitionName: PropTypes.string,
openAnimation: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
switcherIcon: PropTypes.any,
_propsSymbol: PropTypes.any,
__propsSymbol__: PropTypes.any,
},
{
prefixCls: 'rc-tree',
@ -169,7 +170,7 @@ const Tree = {
// Check if `treeData` or `children` changed and save into the state.
if (needSync('treeData')) {
treeNode = convertDataToTree(this.$createElement, props.treeData);
treeNode = convertDataToTree(props.treeData);
} else if (needSync('children')) {
treeNode = props.children;
}
@ -248,7 +249,7 @@ const Tree = {
onNodeDragStart(event, node) {
const { _expandedKeys } = this.$data;
const { eventKey } = node;
const children = getSlots(node).default;
const children = getSlots(node);
this.dragNode = node;
this.setState({
@ -272,7 +273,7 @@ const Tree = {
const { _expandedKeys: expandedKeys } = this.$data;
const { pos, eventKey } = node;
if (!this.dragNode || !node.$refs.selectHandle) return;
if (!this.dragNode || !node.selectHandle) return;
const dropPosition = calcDropPosition(event, node);
@ -319,7 +320,7 @@ const Tree = {
const { eventKey } = node;
const { _dragOverNodeKey, _dropPosition } = this.$data;
// Update drag position
if (this.dragNode && eventKey === _dragOverNodeKey && node.$refs.selectHandle) {
if (this.dragNode && eventKey === _dragOverNodeKey && node.selectHandle) {
const dropPosition = calcDropPosition(event, node);
if (dropPosition === _dropPosition) return;
@ -479,6 +480,7 @@ const Tree = {
_halfCheckedKeys: halfCheckedKeys,
});
}
this.$emit('update:checkedKeys', checkedObj);
this.__emit('check', checkedObj, eventObj);
},
onNodeLoad(treeNode) {
@ -637,21 +639,19 @@ const Tree = {
}
return cloneElement(child, {
props: {
eventKey: key,
expanded: expandedKeys.indexOf(key) !== -1,
selected: selectedKeys.indexOf(key) !== -1,
loaded: loadedKeys.indexOf(key) !== -1,
loading: loadingKeys.indexOf(key) !== -1,
checked: this.isKeyChecked(key),
halfChecked: halfCheckedKeys.indexOf(key) !== -1,
pos,
eventKey: key,
expanded: expandedKeys.indexOf(key) !== -1,
selected: selectedKeys.indexOf(key) !== -1,
loaded: loadedKeys.indexOf(key) !== -1,
loading: loadingKeys.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,
},
// [Legacy] Drag props
dragOver: dragOverNodeKey === key && dropPosition === 0,
dragOverGapTop: dragOverNodeKey === key && dropPosition === -1,
dragOverGapBottom: dragOverNodeKey === key && dropPosition === 1,
key,
});
},
@ -660,12 +660,15 @@ const Tree = {
render() {
const { _treeNode: treeNode } = this.$data;
const { prefixCls, focusable, showLine, tabIndex = 0 } = this.$props;
const domProps = getDataAndAria({ ...this.$props, ...this.$attrs });
const { class: className, style } = this.$attrs;
return (
<ul
class={classNames(prefixCls, {
{...domProps}
class={classNames(prefixCls, className, {
[`${prefixCls}-show-line`]: showLine,
})}
style={style}
role="tree"
unselectable="on"
tabIndex={focusable ? tabIndex : null}
@ -678,4 +681,4 @@ const Tree = {
export { Tree };
export default proxyComponent(Tree);
export default Tree;

View File

@ -1,7 +1,8 @@
import { inject, provide } from 'vue';
import PropTypes from '../../_util/vue-types';
import classNames from 'classnames';
import { getNodeChildren, mapChildren, warnOnlyTreeNode } from './util';
import { initDefaultProps, filterEmpty, getComponentFromProp } from '../../_util/props-util';
import { getNodeChildren, mapChildren, warnOnlyTreeNode, getDataAndAria } from './util';
import { initDefaultProps, filterEmpty, getComponent } from '../../_util/props-util';
import BaseMixin from '../../_util/BaseMixin';
import getTransitionProps from '../../_util/getTransitionProps';
@ -13,6 +14,7 @@ const defaultTitle = '---';
const TreeNode = {
name: 'TreeNode',
inheritAttrs: false,
mixins: [BaseMixin],
__ANT_TREE_NODE: true,
props: initDefaultProps(
@ -56,16 +58,19 @@ const TreeNode = {
dragNodeHighlight: false,
};
},
setup() {
return {
vcTree: inject('vcTree', {}),
vcTreeNode: inject('vcTreeNode', {}),
};
},
inject: {
vcTree: { default: () => ({}) },
vcTreeNode: { default: () => ({}) },
},
provide() {
return {
vcTreeNode: this,
};
created() {
provide('vcTreeNode', this);
},
// Isomorphic needn't load data in server side
mounted() {
const {
@ -234,6 +239,10 @@ const TreeNode = {
} = this;
onNodeExpand(e, this);
},
// Drag usage
setSelectHandle(node) {
this.selectHandle = node;
},
getNodeChildren() {
const {
@ -336,8 +345,8 @@ const TreeNode = {
vcTree: { prefixCls },
} = this;
const switcherIcon =
getComponentFromProp(this, 'switcherIcon', {}, false) ||
getComponentFromProp(this.vcTree, 'switcherIcon', {}, false);
getComponent(this, 'switcherIcon', {}, false) ||
getComponent(this.vcTree, 'switcherIcon', {}, false);
if (this.isLeaf2()) {
return (
<span
@ -413,14 +422,14 @@ const TreeNode = {
},
// Icon + Title
renderSelector(h) {
renderSelector() {
const { selected, loading, dragNodeHighlight } = this;
const icon = getComponentFromProp(this, 'icon', {}, false);
const icon = getComponent(this, 'icon', {}, false);
const {
vcTree: { prefixCls, showIcon, icon: treeIcon, draggable, loadData },
} = this;
const disabled = this.isDisabled();
const title = getComponentFromProp(this, 'title', {}, false);
const title = getComponent(this, 'title', {}, false);
const wrapClass = `${prefixCls}-node-content-wrapper`;
// Icon - Still show loading icon when loading without showIcon
@ -431,7 +440,7 @@ const TreeNode = {
$icon = currentIcon ? (
<span class={classNames(`${prefixCls}-iconEle`, `${prefixCls}-icon__customize`)}>
{typeof currentIcon === 'function'
? currentIcon({ ...this.$props, ...this.$props.dataRef }, h)
? currentIcon({ ...this.$props, ...this.$props.dataRef })
: currentIcon}
</span>
) : (
@ -445,17 +454,16 @@ const TreeNode = {
let $title = currentTitle ? (
<span class={`${prefixCls}-title`}>
{typeof currentTitle === 'function'
? currentTitle({ ...this.$props, ...this.$props.dataRef }, h)
? currentTitle({ ...this.$props, ...this.$props.dataRef })
: currentTitle}
</span>
) : (
<span class={`${prefixCls}-title`}>{defaultTitle}</span>
);
return (
<span
key="selector"
ref="selectHandle"
ref={this.setSelectHandle}
title={typeof title === 'string' ? title : ''}
class={classNames(
`${wrapClass}`,
@ -489,8 +497,7 @@ const TreeNode = {
if (openTransitionName) {
animProps = getTransitionProps(openTransitionName);
} else if (typeof openAnimation === 'object') {
animProps = { ...openAnimation };
animProps.props = { css: false, ...animProps.props };
animProps = { ...openAnimation, css: false, ...animProps };
}
// Children TreeNode
@ -520,7 +527,7 @@ const TreeNode = {
},
},
render(h) {
render() {
const {
dragOver,
dragOverGapTop,
@ -536,9 +543,12 @@ const TreeNode = {
vcTree: { prefixCls, filterTreeNode, draggable },
} = this;
const disabled = this.isDisabled();
const dataOrAriaAttributeProps = getDataAndAria({ ...this.$props, ...this.$attrs });
const { class: className, style } = this.$attrs;
return (
<li
class={{
className,
[`${prefixCls}-treenode-disabled`]: disabled,
[`${prefixCls}-treenode-switcher-${expanded ? 'open' : 'close'}`]: !isLeaf,
[`${prefixCls}-treenode-checkbox-checked`]: checked,
@ -550,16 +560,18 @@ const TreeNode = {
'drag-over-gap-bottom': !disabled && dragOverGapBottom,
'filter-node': filterTreeNode && filterTreeNode(this),
}}
style={style}
role="treeitem"
onDragenter={draggable ? this.onDragEnter : noop}
onDragover={draggable ? this.onDragOver : noop}
onDragleave={draggable ? this.onDragLeave : noop}
onDrop={draggable ? this.onDrop : noop}
onDragend={draggable ? this.onDragEnd : noop}
{...dataOrAriaAttributeProps}
>
{this.renderSwitcher()}
{this.renderCheckbox()}
{this.renderSelector(h)}
{this.renderSelector()}
{this.renderChildren()}
</li>
);

View File

@ -1,8 +1,7 @@
/* eslint no-loop-func: 0*/
import warning from 'warning';
import omit from 'omit.js';
import TreeNode from './TreeNode';
import { getSlotOptions, getOptionProps } from '../../_util/props-util';
import { getOptionProps } from '../../_util/props-util';
const DRAG_SIDE_RANGE = 0.25;
const DRAG_MIN_GAP = 2;
@ -41,7 +40,7 @@ export function getPosition(level, index) {
}
export function isTreeNode(node) {
return getSlotOptions(node).isTreeNode;
return typeof node.type === 'object' && node.type.isTreeNode;
}
export function getNodeChildren(children = []) {
@ -55,7 +54,7 @@ export function isCheckDisabled(node) {
export function traverseTreeNodes(treeNodes, callback) {
function processNode(node, index, parent) {
const children = node ? node.componentOptions.children : treeNodes;
const children = node ? node.children?.default() : treeNodes;
const pos = node ? getPosition(parent.pos, index) : 0;
// Filter children
@ -111,7 +110,7 @@ export function getDragNodesKeys(treeNodes, node) {
export function calcDropPosition(event, treeNode) {
const { clientY } = event;
const { top, bottom, height } = treeNode.$refs.selectHandle.getBoundingClientRect();
const { top, bottom, height } = treeNode.selectHandle.getBoundingClientRect();
const des = Math.max(height * DRAG_SIDE_RANGE, DRAG_MIN_GAP);
if (clientY <= top + des) {
@ -156,20 +155,19 @@ export function calcSelectedKeys(selectedKeys, props) {
const internalProcessProps = (props = {}) => {
return {
props: omit(props, ['on', 'key', 'class', 'className', 'style']),
on: props.on || {},
...props,
class: props.class || props.className,
style: props.style,
key: props.key,
};
};
export function convertDataToTree(h, treeData, processor) {
export function convertDataToTree(treeData, processor) {
if (!treeData) return [];
const { processProps = internalProcessProps } = processor || {};
const list = Array.isArray(treeData) ? treeData : [treeData];
return list.map(({ children, ...props }) => {
const childrenNodes = convertDataToTree(h, children, processor);
const childrenNodes = convertDataToTree(children, processor);
return <TreeNode {...processProps(props)}>{childrenNodes}</TreeNode>;
});
}

View File

@ -4,7 +4,7 @@
</div>
</template>
<script>
import demo from '../antdv-demo/docs/list/demo/grid';
import demo from '../antdv-demo/docs/tree/demo/basic';
export default {
components: {

View File

@ -27,6 +27,7 @@ import {
Collapse,
Card,
Avatar,
Tree,
notification,
message,
} from 'ant-design-vue';
@ -74,4 +75,5 @@ app
.use(Collapse)
.use(Avatar)
.use(Card)
.use(Tree)
.mount('#app');