refactor: tree

pull/4577/head
tangjinzhou 2021-08-17 22:03:49 +08:00
parent 3117c2748b
commit a0f7e8d21f
26 changed files with 1545 additions and 891 deletions

View File

@ -20,6 +20,7 @@ export default (
}>; }>;
autoInsertSpaceInButton: ComputedRef<Boolean>; autoInsertSpaceInButton: ComputedRef<Boolean>;
renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>; renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>;
virtual: ComputedRef<Boolean>;
} => { } => {
const configProvider = inject<UnwrapRef<ConfigProviderProps>>( const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
'configProvider', 'configProvider',
@ -34,6 +35,7 @@ export default (
const form = computed(() => configProvider.form); const form = computed(() => configProvider.form);
const size = computed(() => props.size || configProvider.componentSize); const size = computed(() => props.size || configProvider.componentSize);
const getTargetContainer = computed(() => props.getTargetContainer); const getTargetContainer = computed(() => props.getTargetContainer);
const virtual = computed(() => props.virtual);
return { return {
configProvider, configProvider,
prefixCls, prefixCls,
@ -45,5 +47,6 @@ export default (
form, form,
autoInsertSpaceInButton, autoInsertSpaceInButton,
renderEmpty, renderEmpty,
virtual,
}; };
}; };

View File

@ -747,6 +747,7 @@
// Tree // Tree
// --- // ---
@tree-bg: @component-background;
@tree-title-height: 24px; @tree-title-height: 24px;
@tree-child-padding: 18px; @tree-child-padding: 18px;
@tree-directory-selected-color: #fff; @tree-directory-selected-color: #fff;

View File

@ -0,0 +1,251 @@
import type { VNode } from 'vue';
import { defineComponent, inject } from 'vue';
import omit from 'omit.js';
import debounce from 'lodash-es/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 classNames from '../_util/classNames';
import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util';
import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree';
import Tree, { TreeProps } from './Tree';
import {
calcRangeKeys,
getFullKeyList,
convertDirectoryKeysToNodes,
getFullKeyListByTreeData,
} from './util';
import BaseMixin from '../_util/BaseMixin';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import { defaultConfigProvider } from '../config-provider';
// export type ExpandAction = false | 'click' | 'dblclick'; export interface
// DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; }
// export interface DirectoryTreeState { expandedKeys?: string[];
// selectedKeys?: string[]; }
export interface DirectoryTreeState {
_expandedKeys?: (string | number)[];
_selectedKeys?: (string | number)[];
}
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
const { isLeaf, expanded } = props;
if (isLeaf) {
return <FileOutlined />;
}
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
}
export default defineComponent({
name: 'ADirectoryTree',
mixins: [BaseMixin],
inheritAttrs: false,
props: initDefaultProps(
{
...TreeProps(),
expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']),
},
{
showIcon: true,
expandAction: 'click',
},
),
setup() {
return {
children: null,
onDebounceExpand: null,
tree: null,
lastSelectedKey: '',
cachedSelectedKeys: [],
configProvider: inject('configProvider', defaultConfigProvider),
};
},
data() {
const props = getOptionProps(this);
const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props;
const children = getSlot(this);
const { keyEntities } = convertTreeToEntities(children);
const state: DirectoryTreeState = {};
// Selected keys
state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || [];
// Expanded keys
if (defaultExpandAll) {
if (props.treeData) {
state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields);
} else {
state._expandedKeys = getFullKeyList(children);
}
} else if (defaultExpandParent) {
state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities);
} else {
state._expandedKeys = expandedKeys || defaultExpandedKeys;
}
return {
_selectedKeys: [],
_expandedKeys: [],
...state,
};
},
watch: {
expandedKeys(val) {
this.setState({ _expandedKeys: val });
},
selectedKeys(val) {
this.setState({ _selectedKeys: val });
},
},
created() {
this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true });
},
methods: {
handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) {
this.setUncontrolledState({ _expandedKeys: expandedKeys });
this.$emit('update:expandedKeys', expandedKeys);
this.$emit('expand', expandedKeys, info);
return undefined;
},
handleClick(event: MouseEvent, node: VNode) {
const { expandAction } = this.$props;
// Expand the tree
if (expandAction === 'click') {
this.onDebounceExpand(event, node);
}
this.$emit('click', event, node);
},
handleDoubleClick(event: MouseEvent, node: VNode) {
const { expandAction } = this.$props;
// Expand the tree
if (expandAction === 'dblclick' || expandAction === 'doubleclick') {
this.onDebounceExpand(event, node);
}
this.$emit('doubleclick', event, node);
this.$emit('dblclick', event, node);
},
hanldeSelect(keys: (string | number)[], event: SelectEvent) {
const { multiple } = this.$props;
const children = this.children || [];
const { _expandedKeys: expandedKeys = [] } = this.$data;
const { node, nativeEvent } = event;
const { eventKey = '' } = node;
const newState: DirectoryTreeState = {};
// We need wrap this event since some value is not same
const newEvent = {
...event,
selected: true, // Directory selected always true
};
// Windows / Mac single pick
const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey;
const shiftPick = nativeEvent.shiftKey;
// Generate new selected keys
let newSelectedKeys: (string | number)[];
if (multiple && ctrlPick) {
// Control click
newSelectedKeys = keys;
this.lastSelectedKey = eventKey;
this.cachedSelectedKeys = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
} else if (multiple && shiftPick) {
// Shift click
newSelectedKeys = Array.from(
new Set([
...(this.cachedSelectedKeys || []),
...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey),
]),
);
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
} else {
// Single click
newSelectedKeys = [eventKey];
this.lastSelectedKey = eventKey;
this.cachedSelectedKeys = newSelectedKeys;
newEvent.selectedNodes = [event.node];
}
newState._selectedKeys = newSelectedKeys;
this.$emit('update:selectedKeys', newSelectedKeys);
this.$emit('select', newSelectedKeys, newEvent);
this.setUncontrolledState(newState);
},
setTreeRef(node: VNode) {
this.tree = node;
},
expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) {
const { isLeaf } = node;
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
return;
}
if (this.tree.tree) {
// Get internal vc-tree
const internalTree = this.tree.tree;
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
internalTree.onNodeExpand(event, node);
}
},
setUncontrolledState(state: unknown) {
const newState = omit(
state,
Object.keys(getOptionProps(this)).map(p => `_${p}`),
);
if (Object.keys(newState).length) {
this.setState(newState);
}
},
handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) {
this.$emit('update:checkedKeys', checkedObj);
this.$emit('check', checkedObj, eventObj);
},
},
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 { class: className, ...restAttrs } = this.$attrs;
const connectClassName = classNames(`${prefixCls}-directory`, className);
const treeProps = {
icon: getIcon,
...restAttrs,
...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']),
prefixCls,
expandedKeys,
selectedKeys,
switcherIcon: getComponent(this, 'switcherIcon'),
ref: this.setTreeRef,
class: connectClassName,
onSelect: this.hanldeSelect,
onClick: this.handleClick,
onDblclick: this.handleDoubleClick,
onExpand: this.handleExpand,
onCheck: this.handleCheck,
};
return (
<Tree {...treeProps} v-slots={omit(this.$slots, ['default'])}>
{this.children}
</Tree>
);
},
});

View File

@ -1,4 +1,4 @@
import type { VNode } from 'vue'; import type { ExtractPropTypes, PropType, VNode } from 'vue';
import { defineComponent, inject } from 'vue'; import { defineComponent, inject } from 'vue';
import omit from 'omit.js'; import omit from 'omit.js';
import debounce from 'lodash-es/debounce'; import debounce from 'lodash-es/debounce';
@ -7,8 +7,7 @@ import FolderOutlined from '@ant-design/icons-vue/FolderOutlined';
import FileOutlined from '@ant-design/icons-vue/FileOutlined'; import FileOutlined from '@ant-design/icons-vue/FileOutlined';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import { conductExpandParent, convertTreeToEntities } from '../vc-tree/src/util'; import { treeProps } from './Tree';
import type { CheckEvent, ExpendEvent, SelectEvent } from './Tree';
import Tree, { TreeProps } from './Tree'; import Tree, { TreeProps } from './Tree';
import { import {
calcRangeKeys, calcRangeKeys,
@ -16,20 +15,11 @@ import {
convertDirectoryKeysToNodes, convertDirectoryKeysToNodes,
getFullKeyListByTreeData, getFullKeyListByTreeData,
} from './util'; } from './util';
import BaseMixin from '../_util/BaseMixin';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import { defaultConfigProvider } from '../config-provider'; import { defaultConfigProvider } from '../config-provider';
// export type ExpandAction = false | 'click' | 'dblclick'; export interface export type ExpandAction = false | 'click' | 'doubleClick' | 'dblclick';
// DirectoryTreeProps extends TreeProps { expandAction?: ExpandAction; }
// export interface DirectoryTreeState { expandedKeys?: string[];
// selectedKeys?: string[]; }
export interface DirectoryTreeState {
_expandedKeys?: (string | number)[];
_selectedKeys?: (string | number)[];
}
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) { function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
const { isLeaf, expanded } = props; const { isLeaf, expanded } = props;
@ -39,213 +29,23 @@ function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />; return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
} }
const directoryTreeProps = {
...treeProps(),
expandAction: { type: [Boolean, String] as PropType<ExpandAction> },
};
export type DirectoryTreeProps = Partial<ExtractPropTypes<typeof directoryTreeProps>>;
export default defineComponent({ export default defineComponent({
name: 'ADirectoryTree', name: 'ADirectoryTree',
mixins: [BaseMixin],
inheritAttrs: false, inheritAttrs: false,
props: initDefaultProps( props: initDefaultProps(directoryTreeProps, {
{
...TreeProps(),
expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']),
},
{
showIcon: true, showIcon: true,
expandAction: 'click', expandAction: 'click',
}, }),
),
setup() { setup() {
return { return () => {
children: null, return null;
onDebounceExpand: null,
tree: null,
lastSelectedKey: '',
cachedSelectedKeys: [],
configProvider: inject('configProvider', defaultConfigProvider),
}; };
}, },
data() {
const props = getOptionProps(this);
const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props;
const children = getSlot(this);
const { keyEntities } = convertTreeToEntities(children);
const state: DirectoryTreeState = {};
// Selected keys
state._selectedKeys = props.selectedKeys || props.defaultSelectedKeys || [];
// Expanded keys
if (defaultExpandAll) {
if (props.treeData) {
state._expandedKeys = getFullKeyListByTreeData(props.treeData, props.replaceFields);
} else {
state._expandedKeys = getFullKeyList(children);
}
} else if (defaultExpandParent) {
state._expandedKeys = conductExpandParent(expandedKeys || defaultExpandedKeys, keyEntities);
} else {
state._expandedKeys = expandedKeys || defaultExpandedKeys;
}
return {
_selectedKeys: [],
_expandedKeys: [],
...state,
};
},
watch: {
expandedKeys(val) {
this.setState({ _expandedKeys: val });
},
selectedKeys(val) {
this.setState({ _selectedKeys: val });
},
},
created() {
this.onDebounceExpand = debounce(this.expandFolderNode, 200, { leading: true });
},
methods: {
handleExpand(expandedKeys: (string | number)[], info: ExpendEvent) {
this.setUncontrolledState({ _expandedKeys: expandedKeys });
this.$emit('update:expandedKeys', expandedKeys);
this.$emit('expand', expandedKeys, info);
return undefined;
},
handleClick(event: MouseEvent, node: VNode) {
const { expandAction } = this.$props;
// Expand the tree
if (expandAction === 'click') {
this.onDebounceExpand(event, node);
}
this.$emit('click', event, node);
},
handleDoubleClick(event: MouseEvent, node: VNode) {
const { expandAction } = this.$props;
// Expand the tree
if (expandAction === 'dblclick' || expandAction === 'doubleclick') {
this.onDebounceExpand(event, node);
}
this.$emit('doubleclick', event, node);
this.$emit('dblclick', event, node);
},
hanldeSelect(keys: (string | number)[], event: SelectEvent) {
const { multiple } = this.$props;
const children = this.children || [];
const { _expandedKeys: expandedKeys = [] } = this.$data;
const { node, nativeEvent } = event;
const { eventKey = '' } = node;
const newState: DirectoryTreeState = {};
// We need wrap this event since some value is not same
const newEvent = {
...event,
selected: true, // Directory selected always true
};
// Windows / Mac single pick
const ctrlPick = nativeEvent.ctrlKey || nativeEvent.metaKey;
const shiftPick = nativeEvent.shiftKey;
// Generate new selected keys
let newSelectedKeys: (string | number)[];
if (multiple && ctrlPick) {
// Control click
newSelectedKeys = keys;
this.lastSelectedKey = eventKey;
this.cachedSelectedKeys = newSelectedKeys;
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
} else if (multiple && shiftPick) {
// Shift click
newSelectedKeys = Array.from(
new Set([
...(this.cachedSelectedKeys || []),
...calcRangeKeys(children, expandedKeys, eventKey, this.lastSelectedKey),
]),
);
newEvent.selectedNodes = convertDirectoryKeysToNodes(children, newSelectedKeys);
} else {
// Single click
newSelectedKeys = [eventKey];
this.lastSelectedKey = eventKey;
this.cachedSelectedKeys = newSelectedKeys;
newEvent.selectedNodes = [event.node];
}
newState._selectedKeys = newSelectedKeys;
this.$emit('update:selectedKeys', newSelectedKeys);
this.$emit('select', newSelectedKeys, newEvent);
this.setUncontrolledState(newState);
},
setTreeRef(node: VNode) {
this.tree = node;
},
expandFolderNode(event: MouseEvent, node: { isLeaf: boolean } & VNode) {
const { isLeaf } = node;
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
return;
}
if (this.tree.tree) {
// Get internal vc-tree
const internalTree = this.tree.tree;
// Call internal rc-tree expand function
// https://github.com/ant-design/ant-design/issues/12567
internalTree.onNodeExpand(event, node);
}
},
setUncontrolledState(state: unknown) {
const newState = omit(
state,
Object.keys(getOptionProps(this)).map(p => `_${p}`),
);
if (Object.keys(newState).length) {
this.setState(newState);
}
},
handleCheck(checkedObj: (string | number)[], eventObj: CheckEvent) {
this.$emit('update:checkedKeys', checkedObj);
this.$emit('check', checkedObj, eventObj);
},
},
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 { class: className, ...restAttrs } = this.$attrs;
const connectClassName = classNames(`${prefixCls}-directory`, className);
const treeProps = {
icon: getIcon,
...restAttrs,
...omit(props, ['onUpdate:selectedKeys', 'onUpdate:checkedKeys', 'onUpdate:expandedKeys']),
prefixCls,
expandedKeys,
selectedKeys,
switcherIcon: getComponent(this, 'switcherIcon'),
ref: this.setTreeRef,
class: connectClassName,
onSelect: this.hanldeSelect,
onClick: this.handleClick,
onDblclick: this.handleDoubleClick,
onExpand: this.handleExpand,
onCheck: this.handleCheck,
};
return (
<Tree {...treeProps} v-slots={omit(this.$slots, ['default'])}>
{this.children}
</Tree>
);
},
}); });

View File

@ -0,0 +1,289 @@
import type { VNode, PropType, CSSProperties } from 'vue';
import { defineComponent, inject } from 'vue';
import classNames from '../_util/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';
import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined';
import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined';
import VcTree, { TreeNode } from '../vc-tree';
import animation from '../_util/openAnimation';
import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps';
import { cloneElement } from '../_util/vnode';
import { defaultConfigProvider } from '../config-provider';
export interface TreeDataItem {
key?: string | number;
title?: string;
isLeaf?: boolean;
selectable?: boolean;
children?: TreeDataItem[];
disableCheckbox?: boolean;
disabled?: boolean;
class?: string;
style?: CSSProperties;
checkable?: boolean;
icon?: VNode;
slots?: Record<string, string>;
switcherIcon?: VNode;
// support custom field
[key: string]: any;
}
interface DefaultEvent {
nativeEvent: MouseEvent;
node: Record<string, any>;
}
export interface CheckEvent extends DefaultEvent {
checked: boolean;
checkedNodes: Array<Record<string, any>>;
checkedNodesPositions: { node: Record<string, any>; pos: string | number }[];
event: string;
halfCheckedKeys: (string | number)[];
}
export interface ExpendEvent extends DefaultEvent {
expanded: boolean;
}
export interface SelectEvent extends DefaultEvent {
event: string;
selected: boolean;
selectedNodes: Array<Record<string, any>>;
}
export interface TreeDragEvent {
event: DragEvent;
expandedKeys: (string | number)[];
node: Record<string, any>;
}
export interface DropEvent {
dragNode: Record<string, any>;
dragNodesKeys: (string | number)[];
dropPosition: number;
dropToGap: boolean;
event: DragEvent;
node: Record<string, any>;
}
function TreeProps() {
return {
showLine: PropTypes.looseBool,
/** 是否支持多选 */
multiple: PropTypes.looseBool,
/** 是否自动展开父节点 */
autoExpandParent: PropTypes.looseBool,
/** checkable状态下节点选择完全受控父子节点选中状态不再关联*/
checkStrictly: PropTypes.looseBool,
/** 是否支持选中 */
checkable: PropTypes.looseBool,
/** 是否禁用树 */
disabled: PropTypes.looseBool,
/** 默认展开所有树节点 */
defaultExpandAll: PropTypes.looseBool,
/** 默认展开对应树节点 */
defaultExpandParent: PropTypes.looseBool,
/** 默认展开指定的树节点 */
defaultExpandedKeys: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
/** (受控)展开指定的树节点 */
expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
/** (受控)选中复选框的树节点 */
checkedKeys: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
PropTypes.shape({
checked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
halfChecked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
}).loose,
]),
/** 默认选中复选框的树节点 */
defaultCheckedKeys: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
/** (受控)设置选中的树节点 */
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
/** 默认选中的树节点 */
defaultSelectedKeys: PropTypes.arrayOf(
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
selectable: PropTypes.looseBool,
/** filter some AntTreeNodes as you need. it should return true */
filterAntTreeNode: PropTypes.func,
/** 异步加载数据 */
loadData: PropTypes.func,
loadedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
// onLoaded: (loadedKeys: string[], info: { event: 'load', node: AntTreeNode; }) => void,
/** 响应右键点击 */
// onRightClick: (options: AntTreeNodeMouseEvent) => void,
/** 设置节点可拖拽IE>8*/
draggable: PropTypes.looseBool,
// /** */
// onDragStart: (options: AntTreeNodeMouseEvent) => void,
// /** dragenter */
// onDragEnter: (options: AntTreeNodeMouseEvent) => void,
// /** dragover */
// onDragOver: (options: AntTreeNodeMouseEvent) => void,
// /** dragleave */
// onDragLeave: (options: AntTreeNodeMouseEvent) => void,
// /** drop */
// onDrop: (options: AntTreeNodeMouseEvent) => void,
showIcon: PropTypes.looseBool,
icon: PropTypes.func,
switcherIcon: PropTypes.any,
prefixCls: PropTypes.string,
filterTreeNode: PropTypes.func,
openAnimation: PropTypes.any,
treeData: {
type: Array as PropType<TreeDataItem[]>,
},
/**
* @default{title,key,children}
* 替换treeNode中 title,key,children字段为treeData中对应的字段
*/
replaceFields: PropTypes.object,
blockNode: PropTypes.looseBool,
/** 展开/收起节点时触发 */
onExpand: PropTypes.func,
/** 点击复选框触发 */
onCheck: PropTypes.func,
/** 点击树节点触发 */
onSelect: PropTypes.func,
/** 单击树节点触发 */
onClick: PropTypes.func,
/** 双击树节点触发 */
onDoubleclick: PropTypes.func,
onDblclick: PropTypes.func,
'onUpdate:selectedKeys': PropTypes.func,
'onUpdate:checkedKeys': PropTypes.func,
'onUpdate:expandedKeys': PropTypes.func,
};
}
export { TreeProps };
export default defineComponent({
name: 'ATree',
inheritAttrs: false,
props: initDefaultProps(TreeProps(), {
checkable: false,
showIcon: false,
openAnimation: {
...animation,
appear: null,
},
blockNode: false,
}),
setup() {
return {
tree: null,
configProvider: inject('configProvider', defaultConfigProvider),
};
},
TreeNode,
methods: {
renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) {
const { showLine } = this.$props;
if (loading) {
return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />;
}
if (isLeaf) {
return showLine ? <FileOutlined class={`${prefixCls}-switcher-line-icon`} /> : null;
}
const switcherCls = `${prefixCls}-switcher-icon`;
if (switcherIcon) {
return cloneElement(switcherIcon, {
class: switcherCls,
});
}
return showLine ? (
expanded ? (
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
) : (
<PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
)
) : (
<CaretDownFilled class={switcherCls} />
);
},
updateTreeData(treeData: TreeDataItem[]) {
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 { slots = {}, class: cls, style, ...restProps } = item;
const treeNodeProps = {
...restProps,
icon: $slots[slots.icon] || restProps.icon,
switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon,
title: $slots[slots.title] || $slots.title || restProps[replaceFields.title],
dataRef: item,
key,
class: cls,
style,
};
if (children) {
return { ...treeNodeProps, children: this.updateTreeData(children) };
}
return treeNodeProps;
});
},
setTreeRef(node: VNode) {
this.tree = node;
},
handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) {
this.$emit('update:checkedKeys', checkedObj);
this.$emit('check', checkedObj, eventObj);
},
handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) {
this.$emit('update:expandedKeys', expandedKeys);
this.$emit('expand', expandedKeys, eventObj);
},
handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) {
this.$emit('update:selectedKeys', selectedKeys);
this.$emit('select', selectedKeys, eventObj);
},
},
render() {
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
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,
prefixCls,
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
children: getSlot(this),
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps),
ref: this.setTreeRef,
...restAttrs,
class: classNames(className, {
[`${prefixCls}-icon-hide`]: !showIcon,
[`${prefixCls}-block-node`]: blockNode,
}),
onCheck: this.handleCheck,
onExpand: this.handleExpand,
onSelect: this.handleSelect,
} as Record<string, any>;
if (treeData) {
vcTreeProps.treeData = treeData;
}
return <VcTree {...vcTreeProps} />;
},
});

View File

@ -1,290 +1,347 @@
import type { VNode, PropType, CSSProperties } from 'vue'; import { VNode, PropType, DefineComponent, ExtractPropTypes, ref } from 'vue';
import { defineComponent, inject } from 'vue'; import { defineComponent } from 'vue';
import classNames from '../_util/classNames'; import classNames from '../_util/classNames';
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined'; import VcTree, { TreeNode } from '../vc-tree';
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled';
import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined';
import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined';
import VcTree from '../vc-tree';
import animation from '../_util/openAnimation'; import animation from '../_util/openAnimation';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import { getOptionProps, getComponent, getSlot } from '../_util/props-util'; import { filterEmpty } from '../_util/props-util';
import initDefaultProps from '../_util/props-util/initDefaultProps'; import initDefaultProps from '../_util/props-util/initDefaultProps';
import { cloneElement } from '../_util/vnode'; import { DataNode, FieldNames, Key } from '../vc-tree/interface';
import { defaultConfigProvider } from '../config-provider'; import { treeProps as vcTreeProps } from '../vc-tree/props';
import useConfigInject from '../_util/hooks/useConfigInject';
import renderSwitcherIcon from './utils/iconUtil';
import dropIndicatorRender from './utils/dropIndicator';
const TreeNode = VcTree.TreeNode; export interface AntdTreeNodeAttribute {
eventKey: string;
export interface TreeDataItem { prefixCls: string;
key?: string | number; className: string;
title?: string;
isLeaf?: boolean;
selectable?: boolean;
children?: TreeDataItem[];
disableCheckbox?: boolean;
disabled?: boolean;
class?: string;
style?: CSSProperties;
checkable?: boolean;
icon?: VNode;
slots?: Record<string, string>;
switcherIcon?: VNode;
// support custom field
[key: string]: any;
}
interface DefaultEvent {
nativeEvent: MouseEvent;
node: Record<string, any>;
}
export interface CheckEvent extends DefaultEvent {
checked: boolean;
checkedNodes: Array<Record<string, any>>;
checkedNodesPositions: { node: Record<string, any>; pos: string | number }[];
event: string;
halfCheckedKeys: (string | number)[];
}
export interface ExpendEvent extends DefaultEvent {
expanded: boolean; expanded: boolean;
}
export interface SelectEvent extends DefaultEvent {
event: string;
selected: boolean; selected: boolean;
selectedNodes: Array<Record<string, any>>; checked: boolean;
halfChecked: boolean;
children: any;
title: any;
pos: string;
dragOver: boolean;
dragOverGapTop: boolean;
dragOverGapBottom: boolean;
isLeaf: boolean;
selectable: boolean;
disabled: boolean;
disableCheckbox: boolean;
} }
export interface TreeDragEvent { export interface AntTreeNodeProps {
className?: string;
checkable?: boolean;
disabled?: boolean;
disableCheckbox?: boolean;
title?: string | any;
key?: Key;
eventKey?: string;
isLeaf?: boolean;
checked?: boolean;
expanded?: boolean;
loading?: boolean;
selected?: boolean;
selectable?: boolean;
icon?: ((treeNode: AntdTreeNodeAttribute) => any) | VNode;
children?: any;
[customProp: string]: any;
}
export interface AntTreeNode extends DefineComponent<AntTreeNodeProps, {}> {}
export interface AntTreeNodeBaseEvent {
node: AntTreeNode;
nativeEvent: MouseEvent;
}
export interface AntTreeNodeCheckedEvent extends AntTreeNodeBaseEvent {
event: 'check';
checked?: boolean;
checkedNodes?: AntTreeNode[];
}
export interface AntTreeNodeSelectedEvent extends AntTreeNodeBaseEvent {
event: 'select';
selected?: boolean;
selectedNodes?: DataNode[];
}
export interface AntTreeNodeExpandedEvent extends AntTreeNodeBaseEvent {
expanded?: boolean;
}
export interface AntTreeNodeMouseEvent {
node: AntTreeNode;
event: DragEvent; event: DragEvent;
expandedKeys: (string | number)[];
node: Record<string, any>;
} }
export interface DropEvent { export interface AntTreeNodeDragEnterEvent extends AntTreeNodeMouseEvent {
dragNode: Record<string, any>; expandedKeys: Key[];
dragNodesKeys: (string | number)[]; }
export interface AntTreeNodeDropEvent {
node: AntTreeNode;
dragNode: AntTreeNode;
dragNodesKeys: Key[];
dropPosition: number; dropPosition: number;
dropToGap: boolean; dropToGap?: boolean;
event: DragEvent; event: MouseEvent;
node: Record<string, any>;
} }
function TreeProps() { // [Legacy] Compatible for v2
export type TreeDataItem = DataNode;
export const treeProps = () => {
return { return {
showLine: PropTypes.looseBool, ...vcTreeProps(),
showLine: { type: Boolean, default: undefined },
/** 是否支持多选 */ /** 是否支持多选 */
multiple: PropTypes.looseBool, multiple: { type: Boolean, default: undefined },
/** 是否自动展开父节点 */ /** 是否自动展开父节点 */
autoExpandParent: PropTypes.looseBool, autoExpandParent: { type: Boolean, default: undefined },
/** checkable状态下节点选择完全受控父子节点选中状态不再关联*/ /** checkable状态下节点选择完全受控父子节点选中状态不再关联*/
checkStrictly: PropTypes.looseBool, checkStrictly: { type: Boolean, default: undefined },
/** 是否支持选中 */ /** 是否支持选中 */
checkable: PropTypes.looseBool, checkable: { type: Boolean, default: undefined },
/** 是否禁用树 */ /** 是否禁用树 */
disabled: PropTypes.looseBool, disabled: { type: Boolean, default: undefined },
/** 默认展开所有树节点 */ /** 默认展开所有树节点 */
defaultExpandAll: PropTypes.looseBool, defaultExpandAll: { type: Boolean, default: undefined },
/** 默认展开对应树节点 */ /** 默认展开对应树节点 */
defaultExpandParent: PropTypes.looseBool, defaultExpandParent: { type: Boolean, default: undefined },
/** 默认展开指定的树节点 */ /** 默认展开指定的树节点 */
defaultExpandedKeys: PropTypes.arrayOf( defaultExpandedKeys: { type: Array as PropType<Key[]> },
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
/** (受控)展开指定的树节点 */ /** (受控)展开指定的树节点 */
expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), expandedKeys: { type: Array as PropType<Key[]> },
/** (受控)选中复选框的树节点 */ /** (受控)选中复选框的树节点 */
checkedKeys: PropTypes.oneOfType([ checkedKeys: {
PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), type: [Array, Object] as PropType<Key[] | { checked: Key[]; halfChecked: Key[] }>,
PropTypes.shape({ },
checked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
halfChecked: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
}).loose,
]),
/** 默认选中复选框的树节点 */ /** 默认选中复选框的树节点 */
defaultCheckedKeys: PropTypes.arrayOf( defaultCheckedKeys: { type: Array as PropType<Key[]> },
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
),
/** (受控)设置选中的树节点 */ /** (受控)设置选中的树节点 */
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), selectedKeys: { type: Array as PropType<Key[]> },
/** 默认选中的树节点 */ /** 默认选中的树节点 */
defaultSelectedKeys: PropTypes.arrayOf( defaultSelectedKeys: { type: Array as PropType<Key[]> },
PropTypes.oneOfType([PropTypes.string, PropTypes.number]), selectable: { type: Boolean, default: undefined },
),
selectable: PropTypes.looseBool,
/** filter some AntTreeNodes as you need. it should return true */ /** filter some AntTreeNodes as you need. it should return true */
filterAntTreeNode: PropTypes.func, filterAntTreeNode: { type: Function as PropType<(node: AntTreeNode) => boolean> },
/** 异步加载数据 */ loadedKeys: { type: Array as PropType<Key[]> },
loadData: PropTypes.func, draggable: { type: Boolean, default: undefined },
loadedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])), showIcon: { type: Boolean, default: undefined },
// onLoaded: (loadedKeys: string[], info: { event: 'load', node: AntTreeNode; }) => void, icon: { type: Function as PropType<(nodeProps: AntdTreeNodeAttribute) => any> },
/** 响应右键点击 */
// onRightClick: (options: AntTreeNodeMouseEvent) => void,
/** 设置节点可拖拽IE>8*/
draggable: PropTypes.looseBool,
// /** */
// onDragStart: (options: AntTreeNodeMouseEvent) => void,
// /** dragenter */
// onDragEnter: (options: AntTreeNodeMouseEvent) => void,
// /** dragover */
// onDragOver: (options: AntTreeNodeMouseEvent) => void,
// /** dragleave */
// onDragLeave: (options: AntTreeNodeMouseEvent) => void,
// /** drop */
// onDrop: (options: AntTreeNodeMouseEvent) => void,
showIcon: PropTypes.looseBool,
icon: PropTypes.func,
switcherIcon: PropTypes.any, switcherIcon: PropTypes.any,
prefixCls: PropTypes.string, prefixCls: PropTypes.string,
filterTreeNode: PropTypes.func,
openAnimation: PropTypes.any,
treeData: {
type: Array as PropType<TreeDataItem[]>,
},
/** /**
* @default{title,key,children} * @default{title,key,children}
* deprecated, please use `fieldNames` instead
* 替换treeNode中 title,key,children字段为treeData中对应的字段 * 替换treeNode中 title,key,children字段为treeData中对应的字段
*/ */
replaceFields: PropTypes.object, replaceFields: { type: Object as PropType<FieldNames> },
blockNode: PropTypes.looseBool, blockNode: { type: Boolean, default: undefined },
/** 展开/收起节点时触发 */
onExpand: PropTypes.func,
/** 点击复选框触发 */
onCheck: PropTypes.func,
/** 点击树节点触发 */
onSelect: PropTypes.func,
/** 单击树节点触发 */
onClick: PropTypes.func,
/** 双击树节点触发 */
onDoubleclick: PropTypes.func,
onDblclick: PropTypes.func,
'onUpdate:selectedKeys': PropTypes.func,
'onUpdate:checkedKeys': PropTypes.func,
'onUpdate:expandedKeys': PropTypes.func,
}; };
} };
export { TreeProps }; export type TreeProps = Partial<ExtractPropTypes<ReturnType<typeof treeProps>>>;
export default defineComponent({ export default defineComponent({
name: 'ATree', name: 'ATree',
inheritAttrs: false, inheritAttrs: false,
props: initDefaultProps(TreeProps(), { props: initDefaultProps(treeProps(), {
checkable: false, checkable: false,
selectable: true,
showIcon: false, showIcon: false,
openAnimation: { openAnimation: {
...animation, ...animation,
appear: null, appear: false,
}, },
blockNode: false, blockNode: false,
}), }),
setup() { slots: ['icon', 'title', 'switcherIcon'],
return { emits: [
tree: null, 'update:selectedKeys',
configProvider: inject('configProvider', defaultConfigProvider), 'update:checkedKeys',
}; 'update:expandedKeys',
}, 'expand',
'select',
'check',
],
TreeNode, TreeNode,
methods: { setup(props, { attrs, expose, emit, slots }) {
renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) { const { prefixCls, direction, virtual } = useConfigInject('tree', props);
const { showLine } = this.$props; const tree = ref();
if (loading) { expose({
return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />; tree,
} });
if (isLeaf) { const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
return showLine ? <FileOutlined class={`${prefixCls}-switcher-line-icon`} /> : null; emit('update:checkedKeys', checkedObjOrKeys);
} emit('check', checkedObjOrKeys, eventObj);
const switcherCls = `${prefixCls}-switcher-icon`;
if (switcherIcon) {
return cloneElement(switcherIcon, {
class: switcherCls,
});
}
return showLine ? (
expanded ? (
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
) : (
<PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
)
) : (
<CaretDownFilled class={switcherCls} />
);
},
updateTreeData(treeData: TreeDataItem[]) {
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 { slots = {}, class: cls, style, ...restProps } = item;
const treeNodeProps = {
...restProps,
icon: $slots[slots.icon] || restProps.icon,
switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon,
title: $slots[slots.title] || $slots.title || restProps[replaceFields.title],
dataRef: item,
key,
class: cls,
style,
}; };
if (children) { const handleExpand: TreeProps['onExpand'] = (expandedKeys, eventObj) => {
return { ...treeNodeProps, children: this.updateTreeData(children) }; emit('update:expandedKeys', expandedKeys);
} emit('expand', expandedKeys, eventObj);
return treeNodeProps; };
}); const handleSelect: TreeProps['onSelect'] = (selectedKeys, eventObj) => {
}, emit('update:selectedKeys', selectedKeys);
setTreeRef(node: VNode) { emit('select', selectedKeys, eventObj);
this.tree = node; };
}, return () => {
handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) { const {
this.$emit('update:checkedKeys', checkedObj); showIcon,
this.$emit('check', checkedObj, eventObj); showLine,
}, switcherIcon = slots.switcherIcon?.(),
handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) { icon = slots.icon,
this.$emit('update:expandedKeys', expandedKeys); blockNode,
this.$emit('expand', expandedKeys, eventObj); checkable,
}, selectable,
handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) { fieldNames,
this.$emit('update:selectedKeys', selectedKeys); replaceFields,
this.$emit('select', selectedKeys, eventObj); } = props;
}, const newProps = {
}, ...attrs,
render() {
const props = getOptionProps(this);
const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props;
const getPrefixCls = this.configProvider.getPrefixCls;
const prefixCls = getPrefixCls('tree', customizePrefixCls);
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, showLine: Boolean(showLine),
checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable, dropIndicatorRender,
children: getSlot(this), fieldNames: fieldNames || (replaceFields as FieldNames),
switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps), icon,
ref: this.setTreeRef, };
...restAttrs,
class: classNames(className, { return (
[`${prefixCls}-icon-hide`]: !showIcon, <VcTree
[`${prefixCls}-block-node`]: blockNode, itemHeight={20}
}), virtual={virtual.value}
onCheck: this.handleCheck, {...newProps}
onExpand: this.handleExpand, ref={tree}
onSelect: this.handleSelect, prefixCls={prefixCls.value}
} as Record<string, any>; class={classNames(
if (treeData) { {
vcTreeProps.treeData = treeData; [`${prefixCls.value}-icon-hide`]: !showIcon,
} [`${prefixCls.value}-block-node`]: blockNode,
return <VcTree {...vcTreeProps} __propsSymbol__={[]} />; [`${prefixCls.value}-unselectable`]: !selectable,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}, },
attrs.class,
)}
direction={direction.value}
checkable={checkable}
selectable={selectable}
switcherIcon={(nodeProps: AntTreeNodeProps) =>
renderSwitcherIcon(prefixCls.value, switcherIcon, showLine, nodeProps)
}
onCheck={handleCheck}
onExpand={handleExpand}
onSelect={handleSelect}
v-slots={{
checkable: () => <span class={`${prefixCls.value}-checkbox-inner`} />,
}}
children={filterEmpty(slots.default?.())}
></VcTree>
);
};
},
// methods: {
// renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) {
// const { showLine } = this.$props;
// if (loading) {
// return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />;
// }
// if (isLeaf) {
// return showLine ? <FileOutlined class={`${prefixCls}-switcher-line-icon`} /> : null;
// }
// const switcherCls = `${prefixCls}-switcher-icon`;
// if (switcherIcon) {
// return cloneElement(switcherIcon, {
// class: switcherCls,
// });
// }
// return showLine ? (
// expanded ? (
// <MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
// ) : (
// <PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
// )
// ) : (
// <CaretDownFilled class={switcherCls} />
// );
// },
// updateTreeData(treeData: TreeDataItem[]) {
// 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 { slots = {}, class: cls, style, ...restProps } = item;
// const treeNodeProps = {
// ...restProps,
// icon: $slots[slots.icon] || restProps.icon,
// switcherIcon: $slots[slots.switcherIcon] || restProps.switcherIcon,
// title: $slots[slots.title] || $slots.title || restProps[replaceFields.title],
// dataRef: item,
// key,
// class: cls,
// style,
// };
// if (children) {
// return { ...treeNodeProps, children: this.updateTreeData(children) };
// }
// return treeNodeProps;
// });
// },
// setTreeRef(node: VNode) {
// this.tree = node;
// },
// handleCheck(checkedObj: (number | string)[], eventObj: CheckEvent) {
// this.$emit('update:checkedKeys', checkedObj);
// this.$emit('check', checkedObj, eventObj);
// },
// handleExpand(expandedKeys: (number | string)[], eventObj: ExpendEvent) {
// this.$emit('update:expandedKeys', expandedKeys);
// this.$emit('expand', expandedKeys, eventObj);
// },
// handleSelect(selectedKeys: (number | string)[], eventObj: SelectEvent) {
// this.$emit('update:selectedKeys', selectedKeys);
// this.$emit('select', selectedKeys, eventObj);
// },
// },
// render() {
// const props = getOptionProps(this);
// const { prefixCls: customizePrefixCls, showIcon, treeNodes, blockNode } = props;
// const getPrefixCls = this.configProvider.getPrefixCls;
// const prefixCls = getPrefixCls('tree', customizePrefixCls);
// 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,
// prefixCls,
// checkable: checkable ? <span class={`${prefixCls}-checkbox-inner`} /> : checkable,
// children: getSlot(this),
// switcherIcon: nodeProps => this.renderSwitcherIcon(prefixCls, switcherIcon, nodeProps),
// ref: this.setTreeRef,
// ...restAttrs,
// class: classNames(className, {
// [`${prefixCls}-icon-hide`]: !showIcon,
// [`${prefixCls}-block-node`]: blockNode,
// }),
// onCheck: this.handleCheck,
// onExpand: this.handleExpand,
// onSelect: this.handleSelect,
// } as Record<string, any>;
// if (treeData) {
// vcTreeProps.treeData = treeData;
// }
// return <VcTree {...vcTreeProps} />;
// },
}); });

View File

@ -2,6 +2,21 @@ import type { App, Plugin } from 'vue';
import Tree from './Tree'; import Tree from './Tree';
import DirectoryTree from './DirectoryTree'; import DirectoryTree from './DirectoryTree';
export { EventDataNode, DataNode } from '../vc-tree/interface';
export {
TreeProps,
AntTreeNode,
AntTreeNodeMouseEvent,
AntTreeNodeExpandedEvent,
AntTreeNodeCheckedEvent,
AntTreeNodeSelectedEvent,
AntdTreeNodeAttribute,
AntTreeNodeProps,
} from './Tree';
export { ExpandAction as DirectoryTreeExpandAction, DirectoryTreeProps } from './DirectoryTree';
Tree.TreeNode.name = 'ATreeNode'; Tree.TreeNode.name = 'ATreeNode';
Tree.DirectoryTree = DirectoryTree; Tree.DirectoryTree = DirectoryTree;
/* istanbul ignore next */ /* istanbul ignore next */

View File

@ -2,93 +2,70 @@
@tree-prefix-cls: ~'@{ant-prefix}-tree'; @tree-prefix-cls: ~'@{ant-prefix}-tree';
.@{tree-prefix-cls} { .@{tree-prefix-cls}.@{tree-prefix-cls}-directory {
&.@{tree-prefix-cls}-directory { // ================== TreeNode ==================
.@{tree-prefix-cls}-treenode {
position: relative; position: relative;
// Stretch selector width // Hover color
> li, &::before {
.@{tree-prefix-cls}-child-tree > li { position: absolute;
span { top: 0;
&.@{tree-prefix-cls}-switcher { right: 0;
position: relative; bottom: 4px;
z-index: 1; left: 0;
transition: background-color 0.3s;
&.@{tree-prefix-cls}-switcher-noop { content: '';
pointer-events: none; pointer-events: none;
} }
&:hover {
&::before {
background: @item-hover-bg;
}
} }
&.@{tree-prefix-cls}-checkbox { // Elements
position: relative; > * {
z-index: 1; z-index: 1;
} }
&.@{tree-prefix-cls}-node-content-wrapper { // >>> Switcher
.@{tree-prefix-cls}-switcher {
transition: color 0.3s;
}
// >>> Title
.@{tree-prefix-cls}-node-content-wrapper {
border-radius: 0; border-radius: 0;
user-select: none; user-select: none;
&:hover { &:hover {
background: transparent; background: transparent;
&::before {
background: @item-hover-bg;
}
} }
&.@{tree-prefix-cls}-node-selected { &.@{tree-prefix-cls}-node-selected {
color: @tree-directory-selected-color; color: @tree-directory-selected-color;
background: transparent; background: transparent;
} }
&::before {
position: absolute;
right: 0;
left: 0;
height: @tree-title-height;
transition: all 0.3s;
content: '';
} }
> span { // ============= Selected =============
position: relative; &-selected {
z-index: 1; &:hover::before,
}
}
}
&.@{tree-prefix-cls}-treenode-selected {
> span {
&.@{tree-prefix-cls}-switcher {
color: @tree-directory-selected-color;
}
&.@{tree-prefix-cls}-checkbox {
.@{tree-prefix-cls}-checkbox-inner {
border-color: @primary-color;
}
&.@{tree-prefix-cls}-checkbox-checked {
&::after {
border-color: @checkbox-check-color;
}
.@{tree-prefix-cls}-checkbox-inner {
background: @checkbox-check-color;
&::after {
border-color: @primary-color;
}
}
}
}
&.@{tree-prefix-cls}-node-content-wrapper {
&::before { &::before {
background: @tree-directory-selected-bg; background: @tree-directory-selected-bg;
} }
// >>> Switcher
.@{tree-prefix-cls}-switcher {
color: @tree-directory-selected-color;
} }
}
// >>> Title
.@{tree-prefix-cls}-node-content-wrapper {
color: @tree-directory-selected-color;
background: transparent;
} }
} }
} }

View File

@ -5,276 +5,12 @@
@import './directory'; @import './directory';
@tree-prefix-cls: ~'@{ant-prefix}-tree'; @tree-prefix-cls: ~'@{ant-prefix}-tree';
@tree-showline-icon-color: @text-color-secondary; @tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
@tree-node-padding: 4px;
.antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox'); .antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox');
.@{tree-prefix-cls} { .@{tree-prefix-cls} {
/* see https://github.com/ant-design/ant-design/issues/16259 */ .antTreeFn(@tree-prefix-cls);
&-checkbox-checked::after {
position: absolute;
top: 16.67%;
left: 0;
width: 100%;
height: 66.67%;
}
.reset-component();
margin: 0;
padding: 0;
ol,
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
margin: 0;
padding: @tree-node-padding 0;
white-space: nowrap;
list-style: none;
outline: 0;
span[draggable],
span[draggable='true'] {
line-height: @tree-title-height - 4px;
border-top: 2px transparent solid;
border-bottom: 2px transparent solid;
user-select: none;
/* Required to make elements draggable in old WebKit */
-khtml-user-drag: element;
-webkit-user-drag: element;
}
&.drag-over {
> span[draggable] {
color: white;
background-color: @primary-color;
opacity: 0.8;
}
}
&.drag-over-gap-top {
> span[draggable] {
border-top-color: @primary-color;
}
}
&.drag-over-gap-bottom {
> span[draggable] {
border-bottom-color: @primary-color;
}
}
&.filter-node {
> span {
color: @highlight-color !important;
font-weight: 500 !important;
}
}
// When node is loading
&.@{tree-prefix-cls}-treenode-loading {
span {
&.@{tree-prefix-cls}-switcher {
&.@{tree-prefix-cls}-switcher_open,
&.@{tree-prefix-cls}-switcher_close {
.@{tree-prefix-cls}-switcher-loading-icon {
position: absolute;
left: 0;
display: inline-block;
width: 24px;
height: @tree-title-height;
color: @primary-color;
font-size: 14px;
transform: none;
svg {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
}
:root &::after {
opacity: 0;
}
}
}
}
}
ul {
margin: 0;
padding: 0 0 0 @tree-child-padding;
}
.@{tree-prefix-cls}-node-content-wrapper {
display: inline-block;
height: @tree-title-height;
margin: 0;
padding: 0 5px;
color: @text-color;
line-height: @tree-title-height;
text-decoration: none;
vertical-align: top;
border-radius: @border-radius-sm;
cursor: pointer;
transition: all 0.3s;
&:hover {
background-color: @tree-node-hover-bg;
}
&.@{tree-prefix-cls}-node-selected {
background-color: @tree-node-selected-bg;
}
}
span {
&.@{tree-prefix-cls}-checkbox {
top: initial;
height: @tree-title-height;
margin: 0 4px 0 2px;
padding: ((@tree-title-height - 16px) / 2) 0;
}
&.@{tree-prefix-cls}-switcher,
&.@{tree-prefix-cls}-iconEle {
display: inline-block;
width: 24px;
height: @tree-title-height;
margin: 0;
line-height: @tree-title-height;
text-align: center;
vertical-align: top;
border: 0 none;
outline: none;
cursor: pointer;
}
&.@{tree-prefix-cls}-iconEle:empty {
display: none;
}
&.@{tree-prefix-cls}-switcher {
position: relative;
&.@{tree-prefix-cls}-switcher-noop {
cursor: default;
}
&.@{tree-prefix-cls}-switcher_open {
.antTreeSwitcherIcon();
}
&.@{tree-prefix-cls}-switcher_close {
.antTreeSwitcherIcon();
.@{tree-prefix-cls}-switcher-icon {
svg {
transform: rotate(-90deg);
}
}
}
}
}
&:last-child > span {
&.@{tree-prefix-cls}-switcher,
&.@{tree-prefix-cls}-iconEle {
&::before {
display: none;
}
}
}
}
> li {
&:first-child {
padding-top: 7px;
}
&:last-child {
padding-bottom: 7px;
}
}
&-child-tree {
// https://github.com/ant-design/ant-design/issues/14958
> li {
// Provide additional padding between top child node and parent node
&:first-child {
padding-top: 2 * @tree-node-padding;
}
// Hide additional padding between last child node and next parent node
&:last-child {
padding-bottom: 0;
}
}
}
li&-treenode-disabled {
> span:not(.@{tree-prefix-cls}-switcher),
> .@{tree-prefix-cls}-node-content-wrapper,
> .@{tree-prefix-cls}-node-content-wrapper span {
color: @disabled-color;
cursor: not-allowed;
}
> .@{tree-prefix-cls}-node-content-wrapper:hover {
background: transparent;
}
}
&-icon__open {
margin-right: 2px;
vertical-align: top;
}
&-icon__close {
margin-right: 2px;
vertical-align: top;
}
// Tree with line
&&-show-line {
li {
position: relative;
span {
&.@{tree-prefix-cls}-switcher {
color: @tree-showline-icon-color;
background: @component-background;
&.@{tree-prefix-cls}-switcher-noop {
.antTreeShowLineIcon('tree-doc-icon');
}
&.@{tree-prefix-cls}-switcher_open {
.antTreeShowLineIcon('tree-showline-open-icon');
}
&.@{tree-prefix-cls}-switcher_close {
.antTreeShowLineIcon('tree-showline-close-icon');
}
}
}
}
li:not(:last-child)::before {
position: absolute;
left: 12px;
width: 1px;
height: 100%;
height: calc(100% - 22px); // Remove additional height if support
margin: 22px 0 0;
border-left: 1px solid @border-color-base;
content: ' ';
}
}
&.@{tree-prefix-cls}-icon-hide {
.@{tree-prefix-cls}-treenode-loading {
.@{tree-prefix-cls}-iconEle {
display: none;
}
}
}
&.@{tree-prefix-cls}-block-node {
li {
.@{tree-prefix-cls}-node-content-wrapper {
width: ~'calc(100% - 24px)';
}
span {
&.@{tree-prefix-cls}-checkbox {
+ .@{tree-prefix-cls}-node-content-wrapper {
width: ~'calc(100% - 46px)';
}
}
}
}
}
} }
@import './rtl';

View File

@ -1,29 +1,274 @@
@import '../../style/mixins/index'; @import '../../style/mixins/index';
@tree-prefix-cls: ~'@{ant-prefix}-tree'; @tree-prefix-cls: ~'@{ant-prefix}-tree';
@tree-select-prefix-cls: ~'@{ant-prefix}-select'; @tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree';
@tree-motion: ~'@{ant-prefix}-motion-collapse';
@tree-node-padding: (@padding-xs / 2);
@tree-node-hightlight-color: inherit;
.antTreeSwitcherIcon(@type: 'tree-default-open-icon') { .antTreeSwitcherIcon(@type: 'tree-default-open-icon') {
.@{tree-prefix-cls}-switcher-icon, .@{tree-prefix-cls}-switcher-icon,
.@{tree-select-prefix-cls}-switcher-icon { .@{select-tree-prefix-cls}-switcher-icon {
.iconfont-size-under-12px(10px);
display: inline-block; display: inline-block;
font-weight: bold; font-size: 10px;
vertical-align: baseline;
svg { svg {
transition: transform 0.3s; transition: transform 0.3s;
} }
} }
} }
.antTreeShowLineIcon(@type) { .drop-indicator() {
.@{tree-prefix-cls}-switcher-icon, .@{tree-prefix-cls}-drop-indicator {
.@{tree-select-prefix-cls}-switcher-icon { position: absolute;
display: inline-block; // it should displayed over the following node
font-weight: normal; z-index: 1;
font-size: 12px; height: 2px;
svg { background-color: @primary-color;
transition: transform 0.3s; border-radius: 1px;
pointer-events: none;
&::after {
position: absolute;
top: -3px;
left: -6px;
width: 8px;
height: 8px;
background-color: transparent;
border: 2px solid @primary-color;
border-radius: 50%;
content: '';
}
}
}
.antTreeFn(@custom-tree-prefix-cls) {
@custom-tree-node-prefix-cls: ~'@{custom-tree-prefix-cls}-treenode';
.reset-component();
background: @tree-bg;
border-radius: @border-radius-base;
transition: background-color 0.3s;
&-focused:not(:hover):not(&-active-focused) {
background: @primary-1;
}
// =================== Virtual List ===================
&-list-holder-inner {
align-items: flex-start;
}
&.@{custom-tree-prefix-cls}-block-node {
.@{custom-tree-prefix-cls}-list-holder-inner {
align-items: stretch;
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
flex: auto;
}
}
}
// ===================== TreeNode =====================
.@{custom-tree-node-prefix-cls} {
display: flex;
align-items: flex-start;
padding: 0 0 @tree-node-padding 0;
outline: none;
// Disabled
&-disabled {
// >>> Title
.@{custom-tree-prefix-cls}-node-content-wrapper {
color: @disabled-color;
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
&-active .@{custom-tree-prefix-cls}-node-content-wrapper {
background: @tree-node-hover-bg;
}
&:not(&-disabled).filter-node .@{custom-tree-prefix-cls}-title {
color: @tree-node-hightlight-color;
font-weight: 500;
}
}
// >>> Indent
&-indent {
align-self: stretch;
white-space: nowrap;
user-select: none;
&-unit {
display: inline-block;
width: @tree-title-height;
}
}
// >>> Switcher
&-switcher {
.antTreeSwitcherIcon();
position: relative;
flex: none;
align-self: stretch;
width: @tree-title-height;
margin: 0;
line-height: @tree-title-height;
text-align: center;
cursor: pointer;
user-select: none;
&-noop {
cursor: default;
}
&_close {
.@{custom-tree-prefix-cls}-switcher-icon {
svg {
transform: rotate(-90deg);
}
}
}
&-loading-icon {
color: @primary-color;
}
&-leaf-line {
position: relative;
z-index: 1;
display: inline-block;
width: 100%;
height: 100%;
&::before {
position: absolute;
top: 0;
bottom: -@tree-node-padding;
margin-left: -1px;
border-left: 1px solid @normal-color;
content: ' ';
}
&::after {
position: absolute;
width: @tree-title-height - 14px;
height: @tree-title-height - 10px;
margin-left: -1px;
border-bottom: 1px solid @normal-color;
content: ' ';
}
}
}
// >>> Checkbox
&-checkbox {
top: initial;
margin: ((@tree-title-height - @checkbox-size) / 2) 8px 0 0;
}
// >>> Title
& &-node-content-wrapper {
position: relative;
z-index: auto;
min-height: @tree-title-height;
margin: 0;
padding: 0 4px;
color: inherit;
line-height: @tree-title-height;
background: transparent;
border-radius: @border-radius-base;
cursor: pointer;
transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s;
&:hover {
background-color: @tree-node-hover-bg;
}
&.@{custom-tree-prefix-cls}-node-selected {
background-color: @tree-node-selected-bg;
}
// Icon
.@{custom-tree-prefix-cls}-iconEle {
display: inline-block;
width: @tree-title-height;
height: @tree-title-height;
line-height: @tree-title-height;
text-align: center;
vertical-align: top;
&:empty {
display: none;
}
}
}
// https://github.com/ant-design/ant-design/issues/28217
&-unselectable &-node-content-wrapper:hover {
background-color: transparent;
}
// ==================== Draggable =====================
&-node-content-wrapper[draggable='true'] {
line-height: @tree-title-height;
user-select: none;
.drop-indicator();
}
.@{custom-tree-node-prefix-cls}.drop-container {
> [draggable] {
box-shadow: 0 0 0 2px @primary-color;
}
}
// ==================== Show Line =====================
&-show-line {
// ================ Indent lines ================
.@{custom-tree-prefix-cls}-indent {
&-unit {
position: relative;
height: 100%;
&::before {
position: absolute;
top: 0;
right: (@tree-title-height / 2);
bottom: -@tree-node-padding;
border-right: 1px solid @border-color-base;
content: '';
}
&-end {
&::before {
display: none;
}
}
}
}
// ============== Cover Background ==============
.@{custom-tree-prefix-cls}-switcher {
background: @component-background;
&-line-icon {
vertical-align: -0.225em;
}
}
}
}
.@{tree-node-prefix-cls}-leaf-last {
.@{tree-prefix-cls}-switcher {
&-leaf-line {
&::before {
top: auto !important;
bottom: auto !important;
height: @tree-title-height - 10px !important;
}
} }
} }
} }

View File

@ -0,0 +1,72 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import '../../checkbox/style/mixin';
@tree-prefix-cls: ~'@{ant-prefix}-tree';
@select-tree-prefix-cls: ~'@{ant-prefix}-select-tree';
@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
.@{tree-prefix-cls} {
&-rtl {
direction: rtl;
.@{tree-prefix-cls}-node-content-wrapper[draggable='true'] {
.@{tree-prefix-cls}-drop-indicator {
&::after {
right: -6px;
left: unset;
}
}
}
}
// ===================== TreeNode =====================
.@{tree-node-prefix-cls} {
&-rtl {
direction: rtl;
}
}
// >>> Switcher
&-switcher {
&_close {
.@{tree-prefix-cls}-switcher-icon {
svg {
.@{tree-prefix-cls}-rtl & {
transform: rotate(90deg);
}
}
}
}
}
// ==================== Show Line =====================
&-show-line {
// ================ Indent lines ================
.@{tree-prefix-cls}-indent {
&-unit {
&::before {
.@{tree-prefix-cls}-rtl& {
right: auto;
left: -(@tree-title-height / 2) - 1px;
border-right: none;
border-left: 1px solid @border-color-base;
}
}
}
}
}
// >>> Checkbox
&-checkbox {
.@{tree-prefix-cls}-rtl& {
margin: ((@tree-title-height - @checkbox-size) / 2) 0 0 8px;
}
}
}
.@{select-tree-prefix-cls} {
// >>> Checkbox
&-checkbox {
.@{tree-prefix-cls}-select-dropdown-rtl & {
margin: ((@tree-title-height - @checkbox-size) / 2) 0 0 8px;
}
}
}

View File

@ -0,0 +1,92 @@
import type { DataNode, Key } from 'ant-design-vue/es/vc-tree/interface';
enum Record {
None,
Start,
End,
}
function traverseNodesKey(
treeData: DataNode[],
callback: (key: Key | number | null, node: DataNode) => boolean,
) {
function processNode(dataNode: DataNode) {
const { key, children } = dataNode;
if (callback(key, dataNode) !== false) {
traverseNodesKey(children || [], callback);
}
}
treeData.forEach(processNode);
}
/** 计算选中范围只考虑expanded情况以优化性能 */
export function calcRangeKeys({
treeData,
expandedKeys,
startKey,
endKey,
}: {
treeData: DataNode[];
expandedKeys: Key[];
startKey?: Key;
endKey?: Key;
}): Key[] {
const keys: Key[] = [];
let record: Record = Record.None;
if (startKey && startKey === endKey) {
return [startKey];
}
if (!startKey || !endKey) {
return [];
}
function matchKey(key: Key) {
return key === startKey || key === endKey;
}
traverseNodesKey(treeData, (key: Key) => {
if (record === Record.End) {
return false;
}
if (matchKey(key)) {
// Match test
keys.push(key);
if (record === Record.None) {
record = Record.Start;
} else if (record === Record.Start) {
record = Record.End;
return false;
}
} else if (record === Record.Start) {
// Append selection
keys.push(key);
}
if (expandedKeys.indexOf(key) === -1) {
return false;
}
return true;
});
return keys;
}
export function convertDirectoryKeysToNodes(treeData: DataNode[], keys: Key[]) {
const restKeys: Key[] = [...keys];
const nodes: DataNode[] = [];
traverseNodesKey(treeData, (key: Key, node: DataNode) => {
const index = restKeys.indexOf(key);
if (index !== -1) {
nodes.push(node);
restKeys.splice(index, 1);
}
return !!restKeys.length;
});
return nodes;
}

View File

@ -0,0 +1,33 @@
import { CSSProperties } from 'vue';
export const offset = 4;
export default function dropIndicatorRender(props: {
dropPosition: -1 | 0 | 1;
dropLevelOffset: number;
indent: number;
prefixCls: string;
direction: 'ltr' | 'rtl';
}) {
const { dropPosition, dropLevelOffset, prefixCls, indent, direction = 'ltr' } = props;
const startPosition = direction === 'ltr' ? 'left' : 'right';
const endPosition = direction === 'ltr' ? 'right' : 'left';
const style: CSSProperties = {
[startPosition]: `${-dropLevelOffset * indent + offset}px`,
[endPosition]: 0,
};
switch (dropPosition) {
case -1:
style.top = `${-3}px`;
break;
case 1:
style.bottom = `${-3}px`;
break;
default:
// dropPosition === 0
style.bottom = `${-3}px`;
style[startPosition] = `${indent + offset}px`;
break;
}
return <div style={style} class={`${prefixCls}-drop-indicator`} />;
}

View File

@ -0,0 +1,52 @@
import LoadingOutlined from '@ant-design/icons-vue/LoadingOutlined';
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
import MinusSquareOutlined from '@ant-design/icons-vue/MinusSquareOutlined';
import PlusSquareOutlined from '@ant-design/icons-vue/PlusSquareOutlined';
import CaretDownFilled from '@ant-design/icons-vue/CaretDownFilled';
import { AntTreeNodeProps } from '../Tree';
import { isValidElement } from 'ant-design-vue/es/_util/props-util';
import { cloneVNode } from 'vue';
export default function renderSwitcherIcon(
prefixCls: string,
switcherIcon: any,
showLine: boolean | { showLeafIcon: boolean } | undefined,
{ isLeaf, expanded, loading }: AntTreeNodeProps,
) {
if (loading) {
return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />;
}
let showLeafIcon;
if (showLine && typeof showLine === 'object') {
showLeafIcon = showLine.showLeafIcon;
}
if (isLeaf) {
if (showLine) {
if (typeof showLine === 'object' && !showLeafIcon) {
return <span class={`${prefixCls}-switcher-leaf-line`} />;
}
return <FileOutlined class={`${prefixCls}-switcher-line-icon`} />;
}
return null;
}
const switcherCls = `${prefixCls}-switcher-icon`;
if (isValidElement(switcherIcon)) {
return cloneVNode(switcherIcon, {
class: switcherCls,
});
}
if (switcherIcon) {
return switcherIcon;
}
if (showLine) {
return expanded ? (
<MinusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
) : (
<PlusSquareOutlined class={`${prefixCls}-switcher-line-icon`} />
);
}
return <CaretDownFilled class={switcherCls} />;
}

View File

@ -13,7 +13,7 @@ import UploadList from './UploadList';
import { UploadProps } from './interface'; import { UploadProps } from './interface';
import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils'; import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils';
import { defineComponent, inject } from 'vue'; import { defineComponent, inject } from 'vue';
import { getDataAndAria } from '../vc-tree/src/util'; import { getDataAndAriaProps } from '../_util/util';
export default defineComponent({ export default defineComponent({
name: 'AUpload', name: 'AUpload',
@ -280,7 +280,7 @@ export default defineComponent({
[`${prefixCls}-disabled`]: disabled, [`${prefixCls}-disabled`]: disabled,
}); });
return ( return (
<span class={className} {...getDataAndAria(this.$attrs)}> <span class={className} {...getDataAndAriaProps(this.$attrs)}>
<div <div
class={dragCls} class={dragCls}
onDrop={this.onFileDrop} onDrop={this.onFileDrop}

View File

@ -3,7 +3,7 @@ import {
convertDataToTree as vcConvertDataToTree, convertDataToTree as vcConvertDataToTree,
convertTreeToEntities as vcConvertTreeToEntities, convertTreeToEntities as vcConvertTreeToEntities,
conductCheck as rcConductCheck, conductCheck as rcConductCheck,
} from '../../vc-tree/src/util'; } from '../../vc-tree/utils/treeUtil';
import { hasClass } from '../../vc-util/Dom/class'; import { hasClass } from '../../vc-util/Dom/class';
import { SHOW_CHILD, SHOW_PARENT } from './strategies'; import { SHOW_CHILD, SHOW_PARENT } from './strategies';
import { getSlot, getPropsData, isEmptyElement } from '../../_util/props-util'; import { getSlot, getPropsData, isEmptyElement } from '../../_util/props-util';

View File

@ -18,7 +18,7 @@ export default defineComponent({
motionType: String, motionType: String,
treeNodeRequiredProps: { type: Object as PropType<TreeNodeRequiredProps> }, treeNodeRequiredProps: { type: Object as PropType<TreeNodeRequiredProps> },
}, },
slots: ['title', 'icon', 'switcherIcon'], slots: ['title', 'icon', 'switcherIcon', 'checkable'],
setup(props, { attrs, slots }) { setup(props, { attrs, slots }) {
const visible = ref(true); const visible = ref(true);
const context = useInjectTreeContext(); const context = useInjectTreeContext();

View File

@ -94,6 +94,7 @@ export default defineComponent({
name: 'NodeList', name: 'NodeList',
inheritAttrs: false, inheritAttrs: false,
props: nodeListProps, props: nodeListProps,
slots: ['checkable'],
setup(props, { expose, attrs, slots }) { setup(props, { expose, attrs, slots }) {
// =============================== Ref ================================ // =============================== Ref ================================
const listRef = ref(null); const listRef = ref(null);
@ -275,8 +276,7 @@ export default defineComponent({
itemHeight={itemHeight} itemHeight={itemHeight}
prefixCls={`${prefixCls}-list`} prefixCls={`${prefixCls}-list`}
ref={listRef} ref={listRef}
> children={(treeNode: FlattenNode) => {
{(treeNode: FlattenNode) => {
const { const {
pos, pos,
data: { ...restProps }, data: { ...restProps },
@ -310,10 +310,11 @@ export default defineComponent({
onMousemove={() => { onMousemove={() => {
onActiveChange(null); onActiveChange(null);
}} }}
v-slots={slots}
/> />
); );
}} }}
</VirtualList> ></VirtualList>
</> </>
); );
}; };

View File

@ -32,6 +32,7 @@ import classNames from '../_util/classNames';
export default defineComponent({ export default defineComponent({
name: 'Tree', name: 'Tree',
inheritAttrs: false, inheritAttrs: false,
slots: ['checkable'],
props: initDefaultProps(treeProps(), { props: initDefaultProps(treeProps(), {
prefixCls: 'vc-tree', prefixCls: 'vc-tree',
showLine: false, showLine: false,
@ -52,7 +53,7 @@ export default defineComponent({
allowDrop: () => true, allowDrop: () => true,
}), }),
setup(props, { attrs }) { setup(props, { attrs, slots }) {
const destroyed = ref(false); const destroyed = ref(false);
let delayedDragEnterLogic: Record<Key, number> = {}; let delayedDragEnterLogic: Record<Key, number> = {};
@ -91,6 +92,34 @@ export default defineComponent({
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children); return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
}); });
const keyEntities = ref({}); const keyEntities = ref({});
const focused = ref(false);
const activeKey = ref<Key>(null);
const listChanging = ref(false);
const fieldNames = computed(() => fillFieldNames(props.fieldNames));
const listRef = ref();
let dragStartMousePosition = null;
let dragNode = null;
const treeNodeRequiredProps = computed(() => {
return {
expandedKeys: expandedKeys.value || [],
selectedKeys: selectedKeys.value || [],
loadedKeys: loadedKeys.value || [],
loadingKeys: loadingKeys.value || [],
checkedKeys: checkedKeys.value || [],
halfCheckedKeys: halfCheckedKeys.value || [],
dragOverNodeKey: dragState.dragOverNodeKey,
dropPosition: dragState.dropPosition,
keyEntities: keyEntities.value,
};
});
watchEffect(() => { watchEffect(() => {
if (treeData.value) { if (treeData.value) {
const entitiesMap = convertDataToEntities(treeData.value, { fieldNames: fieldNames.value }); const entitiesMap = convertDataToEntities(treeData.value, { fieldNames: fieldNames.value });
@ -186,32 +215,6 @@ export default defineComponent({
} }
}); });
const focused = ref(false);
const activeKey = ref<Key>(null);
const listChanging = ref(false);
const fieldNames = computed(() => fillFieldNames(props.fieldNames));
const listRef = ref();
let dragStartMousePosition = null;
let dragNode = null;
const treeNodeRequiredProps = computed(() => {
return {
expandedKeys: expandedKeys.value || [],
selectedKeys: selectedKeys.value || [],
loadedKeys: loadedKeys.value || [],
loadingKeys: loadingKeys.value || [],
checkedKeys: checkedKeys.value || [],
halfCheckedKeys: halfCheckedKeys.value || [],
dragOverNodeKey: dragState.dragOverNodeKey,
dropPosition: dragState.dropPosition,
keyEntities: keyEntities.value,
};
});
const scrollTo: ScrollTo = scroll => { const scrollTo: ScrollTo = scroll => {
listRef.value.scrollTo(scroll); listRef.value.scrollTo(scroll);
}; };
@ -983,7 +986,7 @@ export default defineComponent({
checkable, checkable,
checkStrictly, checkStrictly,
disabled, disabled,
motion, openAnimation,
loadData, loadData,
filterTreeNode, filterTreeNode,
height, height,
@ -1059,7 +1062,7 @@ export default defineComponent({
disabled={disabled} disabled={disabled}
selectable={selectable} selectable={selectable}
checkable={!!checkable} checkable={!!checkable}
motion={motion} motion={openAnimation}
dragging={dragging} dragging={dragging}
height={height} height={height}
itemHeight={itemHeight} itemHeight={itemHeight}
@ -1078,6 +1081,7 @@ export default defineComponent({
onScroll={onScroll} onScroll={onScroll}
{...treeNodeRequiredProps.value} {...treeNodeRequiredProps.value}
{...domProps} {...domProps}
v-slots={slots}
/> />
</div> </div>
</TreeContext> </TreeContext>

View File

@ -16,7 +16,7 @@ export default defineComponent({
inheritAttrs: false, inheritAttrs: false,
props: treeNodeProps, props: treeNodeProps,
isTreeNode: 1, isTreeNode: 1,
slots: ['title', 'icon', 'switcherIcon'], slots: ['title', 'icon', 'switcherIcon', 'checkable'],
setup(props, { attrs, expose, slots }) { setup(props, { attrs, expose, slots }) {
const dragNodeHighlight = ref(false); const dragNodeHighlight = ref(false);
const context = useInjectTreeContext(); const context = useInjectTreeContext();
@ -275,9 +275,6 @@ export default defineComponent({
if (!checkable) return null; if (!checkable) return null;
// [Legacy] Custom element should be separate with `checkable` in future
const $custom = typeof checkable !== 'boolean' ? checkable : null;
return ( return (
<span <span
class={classNames( class={classNames(
@ -288,7 +285,7 @@ export default defineComponent({
)} )}
onClick={onCheck} onClick={onCheck}
> >
{$custom} {slots.checkable?.()}
</span> </span>
); );
}; };

View File

@ -84,6 +84,7 @@ export interface TreeContextProps {
const TreeContextKey: InjectionKey<ComputedRef<TreeContextProps>> = Symbol('TreeContextKey'); const TreeContextKey: InjectionKey<ComputedRef<TreeContextProps>> = Symbol('TreeContextKey');
export const TreeContext = defineComponent({ export const TreeContext = defineComponent({
name: 'TreeContext',
props: { props: {
value: { type: Object as PropType<TreeContextProps> }, value: { type: Object as PropType<TreeContextProps> },
}, },
@ -92,7 +93,7 @@ export const TreeContext = defineComponent({
TreeContextKey, TreeContextKey,
computed(() => props.value), computed(() => props.value),
); );
return slots.default?.(); return () => slots.default?.();
}, },
}); });

View File

@ -1,8 +1,6 @@
import type { TreeProps, TreeNodeProps } from './props';
import Tree from './Tree'; import Tree from './Tree';
import TreeNode from './TreeNode'; import TreeNode from './TreeNode';
import type { TreeProps } from './Tree';
import type { TreeNodeProps } from './TreeNode';
export { TreeNode }; export { TreeNode };
export type { TreeProps, TreeNodeProps }; export type { TreeProps, TreeNodeProps };
export default Tree; export default Tree;

View File

@ -1,4 +1,4 @@
import { VNode } from 'vue'; import { CSSProperties, VNode } from 'vue';
export type { ScrollTo } from '../vc-virtual-list/List'; export type { ScrollTo } from '../vc-virtual-list/List';
export interface DataNode { export interface DataNode {
@ -14,8 +14,8 @@ export interface DataNode {
switcherIcon?: IconType; switcherIcon?: IconType;
/** Set style of TreeNode. This is not recommend if you don't have any force requirement */ /** Set style of TreeNode. This is not recommend if you don't have any force requirement */
// className?: string; class?: string;
// style?: CSSProperties; style?: CSSProperties;
} }
export interface EventDataNode extends DataNode { export interface EventDataNode extends DataNode {

View File

@ -5,8 +5,15 @@ import type {
NodeMouseEventHandler, NodeMouseEventHandler,
NodeMouseEventParams, NodeMouseEventParams,
} from './contextTypes'; } from './contextTypes';
import type { DataNode, Key, FlattenNode, DataEntity, EventDataNode, Direction } from './interface'; import type {
import { fillFieldNames } from './utils/treeUtil'; DataNode,
Key,
FlattenNode,
DataEntity,
EventDataNode,
Direction,
FieldNames,
} from './interface';
export interface CheckInfo { export interface CheckInfo {
event: 'check'; event: 'check';
@ -103,7 +110,7 @@ export const treeProps = () => ({
tabindex: Number, tabindex: Number,
children: PropTypes.VNodeChild, children: PropTypes.VNodeChild,
treeData: { type: Array as PropType<DataNode[]> }, // Generate treeNode by children treeData: { type: Array as PropType<DataNode[]> }, // Generate treeNode by children
fieldNames: fillFieldNames, fieldNames: { type: Object as PropType<FieldNames> },
showLine: { type: Boolean, default: undefined }, showLine: { type: Boolean, default: undefined },
showIcon: { type: Boolean, default: undefined }, showIcon: { type: Boolean, default: undefined },
icon: PropTypes.any, icon: PropTypes.any,
@ -218,7 +225,7 @@ export const treeProps = () => ({
*/ */
onActiveChange: { type: Function as PropType<(key: Key) => void> }, onActiveChange: { type: Function as PropType<(key: Key) => void> },
filterTreeNode: { type: Function as PropType<(treeNode: EventDataNode) => boolean> }, filterTreeNode: { type: Function as PropType<(treeNode: EventDataNode) => boolean> },
motion: PropTypes.any, openAnimation: PropTypes.any,
switcherIcon: PropTypes.any, switcherIcon: PropTypes.any,
// Virtual List // Virtual List

View File

@ -1,39 +1,62 @@
<template> <template>
<div> <a-tree
<demo /> v-model:expandedKeys="expandedKeys"
</div> v-model:selectedKeys="selectedKeys"
v-model:checkedKeys="checkedKeys"
checkable
:tree-data="treeData"
>
<template #title0010><span style="color: #1890ff">sss</span></template>
</a-tree>
</template> </template>
<script> <script lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref, watch } from 'vue';
import demo from '../v2-doc/src/docs/mentions/demo/index.vue'; import { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
// import Affix from '../components/affix';
const treeData: TreeDataItem[] = [
{
title: 'parent 1',
key: '0-0',
children: [
{
title: 'parent 1-0',
key: '0-0-0',
disabled: true,
children: [
{ title: 'leaf', key: '0-0-0-0', disableCheckbox: true },
{ title: 'leaf', key: '0-0-0-1' },
],
},
{
title: 'parent 1-1',
key: '0-0-1',
children: [{ key: '0-0-1-0', slots: { title: 'title0010' } }],
},
],
},
];
export default defineComponent({ export default defineComponent({
components: { setup() {
demo, const expandedKeys = ref<string[]>(['0-0-0', '0-0-1']);
// Affix, const selectedKeys = ref<string[]>(['0-0-0', '0-0-1']);
}, const checkedKeys = ref<string[]>(['0-0-0', '0-0-1']);
data() { watch(expandedKeys, () => {
console.log('expandedKeys', expandedKeys);
});
watch(selectedKeys, () => {
console.log('selectedKeys', selectedKeys);
});
watch(checkedKeys, () => {
console.log('checkedKeys', checkedKeys);
});
return { return {
visible: false, treeData,
pStyle: { expandedKeys,
fontSize: '16px', selectedKeys,
color: 'rgba(0,0,0,0.85)', checkedKeys,
lineHeight: '24px',
display: 'block',
marginBottom: '16px',
},
pStyle2: {
marginBottom: '24px',
},
}; };
}, },
methods: {
showDrawer() {
this.visible = true;
},
onClose() {
this.visible = false;
},
},
}); });
</script> </script>