refactor: tree
parent
3117c2748b
commit
a0f7e8d21f
|
@ -20,6 +20,7 @@ export default (
|
|||
}>;
|
||||
autoInsertSpaceInButton: ComputedRef<Boolean>;
|
||||
renderEmpty?: ComputedRef<(componentName?: string) => VNodeChild | JSX.Element>;
|
||||
virtual: ComputedRef<Boolean>;
|
||||
} => {
|
||||
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
|
||||
'configProvider',
|
||||
|
@ -34,6 +35,7 @@ export default (
|
|||
const form = computed(() => configProvider.form);
|
||||
const size = computed(() => props.size || configProvider.componentSize);
|
||||
const getTargetContainer = computed(() => props.getTargetContainer);
|
||||
const virtual = computed(() => props.virtual);
|
||||
return {
|
||||
configProvider,
|
||||
prefixCls,
|
||||
|
@ -45,5 +47,6 @@ export default (
|
|||
form,
|
||||
autoInsertSpaceInButton,
|
||||
renderEmpty,
|
||||
virtual,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -747,6 +747,7 @@
|
|||
|
||||
// Tree
|
||||
// ---
|
||||
@tree-bg: @component-background;
|
||||
@tree-title-height: 24px;
|
||||
@tree-child-padding: 18px;
|
||||
@tree-directory-selected-color: #fff;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
},
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import type { VNode } from 'vue';
|
||||
import type { ExtractPropTypes, PropType, VNode } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import omit from 'omit.js';
|
||||
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 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 { treeProps } from './Tree';
|
||||
import Tree, { TreeProps } from './Tree';
|
||||
import {
|
||||
calcRangeKeys,
|
||||
|
@ -16,20 +15,11 @@ import {
|
|||
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)[];
|
||||
}
|
||||
export type ExpandAction = false | 'click' | 'doubleClick' | 'dblclick';
|
||||
|
||||
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
|
||||
const { isLeaf, expanded } = props;
|
||||
|
@ -39,213 +29,23 @@ function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
|
|||
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
|
||||
}
|
||||
|
||||
const directoryTreeProps = {
|
||||
...treeProps(),
|
||||
expandAction: { type: [Boolean, String] as PropType<ExpandAction> },
|
||||
};
|
||||
|
||||
export type DirectoryTreeProps = Partial<ExtractPropTypes<typeof directoryTreeProps>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ADirectoryTree',
|
||||
mixins: [BaseMixin],
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(
|
||||
{
|
||||
...TreeProps(),
|
||||
expandAction: PropTypes.oneOf([false, 'click', 'doubleclick', 'dblclick']),
|
||||
},
|
||||
{
|
||||
props: initDefaultProps(directoryTreeProps, {
|
||||
showIcon: true,
|
||||
expandAction: 'click',
|
||||
},
|
||||
),
|
||||
}),
|
||||
setup() {
|
||||
return {
|
||||
children: null,
|
||||
onDebounceExpand: null,
|
||||
tree: null,
|
||||
lastSelectedKey: '',
|
||||
cachedSelectedKeys: [],
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
return () => {
|
||||
return null;
|
||||
};
|
||||
},
|
||||
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>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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} />;
|
||||
},
|
||||
});
|
|
@ -1,290 +1,347 @@
|
|||
import type { VNode, PropType, CSSProperties } from 'vue';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { VNode, PropType, DefineComponent, ExtractPropTypes, ref } from 'vue';
|
||||
import { defineComponent } 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 from '../vc-tree';
|
||||
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 { filterEmpty } from '../_util/props-util';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
import { cloneElement } from '../_util/vnode';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { DataNode, FieldNames, Key } from '../vc-tree/interface';
|
||||
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 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 {
|
||||
export interface AntdTreeNodeAttribute {
|
||||
eventKey: string;
|
||||
prefixCls: string;
|
||||
className: string;
|
||||
expanded: boolean;
|
||||
}
|
||||
|
||||
export interface SelectEvent extends DefaultEvent {
|
||||
event: string;
|
||||
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;
|
||||
expandedKeys: (string | number)[];
|
||||
node: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface DropEvent {
|
||||
dragNode: Record<string, any>;
|
||||
dragNodesKeys: (string | number)[];
|
||||
export interface AntTreeNodeDragEnterEvent extends AntTreeNodeMouseEvent {
|
||||
expandedKeys: Key[];
|
||||
}
|
||||
|
||||
export interface AntTreeNodeDropEvent {
|
||||
node: AntTreeNode;
|
||||
dragNode: AntTreeNode;
|
||||
dragNodesKeys: Key[];
|
||||
dropPosition: number;
|
||||
dropToGap: boolean;
|
||||
event: DragEvent;
|
||||
node: Record<string, any>;
|
||||
dropToGap?: boolean;
|
||||
event: MouseEvent;
|
||||
}
|
||||
|
||||
function TreeProps() {
|
||||
// [Legacy] Compatible for v2
|
||||
export type TreeDataItem = DataNode;
|
||||
|
||||
export const treeProps = () => {
|
||||
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状态下节点选择完全受控(父子节点选中状态不再关联)*/
|
||||
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(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
),
|
||||
defaultExpandedKeys: { type: Array as PropType<Key[]> },
|
||||
/** (受控)展开指定的树节点 */
|
||||
expandedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
||||
expandedKeys: { type: Array as PropType<Key[]> },
|
||||
/** (受控)选中复选框的树节点 */
|
||||
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,
|
||||
]),
|
||||
checkedKeys: {
|
||||
type: [Array, Object] as PropType<Key[] | { checked: Key[]; halfChecked: Key[] }>,
|
||||
},
|
||||
/** 默认选中复选框的树节点 */
|
||||
defaultCheckedKeys: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
),
|
||||
defaultCheckedKeys: { type: Array as PropType<Key[]> },
|
||||
/** (受控)设置选中的树节点 */
|
||||
selectedKeys: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
||||
selectedKeys: { type: Array as PropType<Key[]> },
|
||||
/** 默认选中的树节点 */
|
||||
defaultSelectedKeys: PropTypes.arrayOf(
|
||||
PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
),
|
||||
selectable: PropTypes.looseBool,
|
||||
defaultSelectedKeys: { type: Array as PropType<Key[]> },
|
||||
selectable: { type: Boolean, default: undefined },
|
||||
|
||||
/** 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,
|
||||
filterAntTreeNode: { type: Function as PropType<(node: AntTreeNode) => boolean> },
|
||||
loadedKeys: { type: Array as PropType<Key[]> },
|
||||
draggable: { type: Boolean, default: undefined },
|
||||
showIcon: { type: Boolean, default: undefined },
|
||||
icon: { type: Function as PropType<(nodeProps: AntdTreeNodeAttribute) => any> },
|
||||
switcherIcon: PropTypes.any,
|
||||
prefixCls: PropTypes.string,
|
||||
filterTreeNode: PropTypes.func,
|
||||
openAnimation: PropTypes.any,
|
||||
treeData: {
|
||||
type: Array as PropType<TreeDataItem[]>,
|
||||
},
|
||||
/**
|
||||
* @default{title,key,children}
|
||||
* deprecated, please use `fieldNames` instead
|
||||
* 替换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,
|
||||
replaceFields: { type: Object as PropType<FieldNames> },
|
||||
blockNode: { type: Boolean, default: undefined },
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export { TreeProps };
|
||||
export type TreeProps = Partial<ExtractPropTypes<ReturnType<typeof treeProps>>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ATree',
|
||||
inheritAttrs: false,
|
||||
props: initDefaultProps(TreeProps(), {
|
||||
props: initDefaultProps(treeProps(), {
|
||||
checkable: false,
|
||||
selectable: true,
|
||||
showIcon: false,
|
||||
openAnimation: {
|
||||
...animation,
|
||||
appear: null,
|
||||
appear: false,
|
||||
},
|
||||
blockNode: false,
|
||||
}),
|
||||
setup() {
|
||||
return {
|
||||
tree: null,
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
slots: ['icon', 'title', 'switcherIcon'],
|
||||
emits: [
|
||||
'update:selectedKeys',
|
||||
'update:checkedKeys',
|
||||
'update:expandedKeys',
|
||||
'expand',
|
||||
'select',
|
||||
'check',
|
||||
],
|
||||
TreeNode,
|
||||
methods: {
|
||||
renderSwitcherIcon(prefixCls: string, switcherIcon: VNode, { isLeaf, loading, expanded }) {
|
||||
const { showLine } = this.$props;
|
||||
if (loading) {
|
||||
return <LoadingOutlined class={`${prefixCls}-switcher-loading-icon`} />;
|
||||
}
|
||||
setup(props, { attrs, expose, emit, slots }) {
|
||||
const { prefixCls, direction, virtual } = useConfigInject('tree', props);
|
||||
const tree = ref();
|
||||
expose({
|
||||
tree,
|
||||
});
|
||||
|
||||
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,
|
||||
const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
|
||||
emit('update:checkedKeys', checkedObjOrKeys);
|
||||
emit('check', checkedObjOrKeys, eventObj);
|
||||
};
|
||||
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 = {
|
||||
const handleExpand: TreeProps['onExpand'] = (expandedKeys, eventObj) => {
|
||||
emit('update:expandedKeys', expandedKeys);
|
||||
emit('expand', expandedKeys, eventObj);
|
||||
};
|
||||
const handleSelect: TreeProps['onSelect'] = (selectedKeys, eventObj) => {
|
||||
emit('update:selectedKeys', selectedKeys);
|
||||
emit('select', selectedKeys, eventObj);
|
||||
};
|
||||
return () => {
|
||||
const {
|
||||
showIcon,
|
||||
showLine,
|
||||
switcherIcon = slots.switcherIcon?.(),
|
||||
icon = slots.icon,
|
||||
blockNode,
|
||||
checkable,
|
||||
selectable,
|
||||
fieldNames,
|
||||
replaceFields,
|
||||
} = props;
|
||||
const newProps = {
|
||||
...attrs,
|
||||
...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} __propsSymbol__={[]} />;
|
||||
showLine: Boolean(showLine),
|
||||
dropIndicatorRender,
|
||||
fieldNames: fieldNames || (replaceFields as FieldNames),
|
||||
icon,
|
||||
};
|
||||
|
||||
return (
|
||||
<VcTree
|
||||
itemHeight={20}
|
||||
virtual={virtual.value}
|
||||
{...newProps}
|
||||
ref={tree}
|
||||
prefixCls={prefixCls.value}
|
||||
class={classNames(
|
||||
{
|
||||
[`${prefixCls.value}-icon-hide`]: !showIcon,
|
||||
[`${prefixCls.value}-block-node`]: blockNode,
|
||||
[`${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} />;
|
||||
// },
|
||||
});
|
||||
|
|
|
@ -2,6 +2,21 @@ import type { App, Plugin } from 'vue';
|
|||
import Tree from './Tree';
|
||||
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.DirectoryTree = DirectoryTree;
|
||||
/* istanbul ignore next */
|
||||
|
|
|
@ -2,93 +2,70 @@
|
|||
|
||||
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
||||
|
||||
.@{tree-prefix-cls} {
|
||||
&.@{tree-prefix-cls}-directory {
|
||||
.@{tree-prefix-cls}.@{tree-prefix-cls}-directory {
|
||||
// ================== TreeNode ==================
|
||||
.@{tree-prefix-cls}-treenode {
|
||||
position: relative;
|
||||
|
||||
// Stretch selector width
|
||||
> li,
|
||||
.@{tree-prefix-cls}-child-tree > li {
|
||||
span {
|
||||
&.@{tree-prefix-cls}-switcher {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&.@{tree-prefix-cls}-switcher-noop {
|
||||
// Hover color
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 4px;
|
||||
left: 0;
|
||||
transition: background-color 0.3s;
|
||||
content: '';
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&::before {
|
||||
background: @item-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.@{tree-prefix-cls}-checkbox {
|
||||
position: relative;
|
||||
// Elements
|
||||
> * {
|
||||
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;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
|
||||
&::before {
|
||||
background: @item-hover-bg;
|
||||
}
|
||||
}
|
||||
|
||||
&.@{tree-prefix-cls}-node-selected {
|
||||
color: @tree-directory-selected-color;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: @tree-title-height;
|
||||
transition: all 0.3s;
|
||||
content: '';
|
||||
}
|
||||
|
||||
> span {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.@{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 {
|
||||
// ============= Selected =============
|
||||
&-selected {
|
||||
&:hover::before,
|
||||
&::before {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,276 +5,12 @@
|
|||
@import './directory';
|
||||
|
||||
@tree-prefix-cls: ~'@{ant-prefix}-tree';
|
||||
@tree-showline-icon-color: @text-color-secondary;
|
||||
@tree-node-padding: 4px;
|
||||
@tree-node-prefix-cls: ~'@{tree-prefix-cls}-treenode';
|
||||
|
||||
.antCheckboxFn(@checkbox-prefix-cls: ~'@{ant-prefix}-tree-checkbox');
|
||||
|
||||
.@{tree-prefix-cls} {
|
||||
/* see https://github.com/ant-design/ant-design/issues/16259 */
|
||||
&-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)';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.antTreeFn(@tree-prefix-cls);
|
||||
}
|
||||
|
||||
@import './rtl';
|
||||
|
|
|
@ -1,29 +1,274 @@
|
|||
@import '../../style/mixins/index';
|
||||
|
||||
@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') {
|
||||
.@{tree-prefix-cls}-switcher-icon,
|
||||
.@{tree-select-prefix-cls}-switcher-icon {
|
||||
.iconfont-size-under-12px(10px);
|
||||
|
||||
.@{select-tree-prefix-cls}-switcher-icon {
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
font-size: 10px;
|
||||
vertical-align: baseline;
|
||||
svg {
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.antTreeShowLineIcon(@type) {
|
||||
.@{tree-prefix-cls}-switcher-icon,
|
||||
.@{tree-select-prefix-cls}-switcher-icon {
|
||||
display: inline-block;
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
svg {
|
||||
transition: transform 0.3s;
|
||||
.drop-indicator() {
|
||||
.@{tree-prefix-cls}-drop-indicator {
|
||||
position: absolute;
|
||||
// it should displayed over the following node
|
||||
z-index: 1;
|
||||
height: 2px;
|
||||
background-color: @primary-color;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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`} />;
|
||||
}
|
|
@ -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} />;
|
||||
}
|
|
@ -13,7 +13,7 @@ import UploadList from './UploadList';
|
|||
import { UploadProps } from './interface';
|
||||
import { T, fileToObject, genPercentAdd, getFileItem, removeFileItem } from './utils';
|
||||
import { defineComponent, inject } from 'vue';
|
||||
import { getDataAndAria } from '../vc-tree/src/util';
|
||||
import { getDataAndAriaProps } from '../_util/util';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AUpload',
|
||||
|
@ -280,7 +280,7 @@ export default defineComponent({
|
|||
[`${prefixCls}-disabled`]: disabled,
|
||||
});
|
||||
return (
|
||||
<span class={className} {...getDataAndAria(this.$attrs)}>
|
||||
<span class={className} {...getDataAndAriaProps(this.$attrs)}>
|
||||
<div
|
||||
class={dragCls}
|
||||
onDrop={this.onFileDrop}
|
||||
|
|
|
@ -3,7 +3,7 @@ import {
|
|||
convertDataToTree as vcConvertDataToTree,
|
||||
convertTreeToEntities as vcConvertTreeToEntities,
|
||||
conductCheck as rcConductCheck,
|
||||
} from '../../vc-tree/src/util';
|
||||
} from '../../vc-tree/utils/treeUtil';
|
||||
import { hasClass } from '../../vc-util/Dom/class';
|
||||
import { SHOW_CHILD, SHOW_PARENT } from './strategies';
|
||||
import { getSlot, getPropsData, isEmptyElement } from '../../_util/props-util';
|
||||
|
|
|
@ -18,7 +18,7 @@ export default defineComponent({
|
|||
motionType: String,
|
||||
treeNodeRequiredProps: { type: Object as PropType<TreeNodeRequiredProps> },
|
||||
},
|
||||
slots: ['title', 'icon', 'switcherIcon'],
|
||||
slots: ['title', 'icon', 'switcherIcon', 'checkable'],
|
||||
setup(props, { attrs, slots }) {
|
||||
const visible = ref(true);
|
||||
const context = useInjectTreeContext();
|
||||
|
|
|
@ -94,6 +94,7 @@ export default defineComponent({
|
|||
name: 'NodeList',
|
||||
inheritAttrs: false,
|
||||
props: nodeListProps,
|
||||
slots: ['checkable'],
|
||||
setup(props, { expose, attrs, slots }) {
|
||||
// =============================== Ref ================================
|
||||
const listRef = ref(null);
|
||||
|
@ -275,8 +276,7 @@ export default defineComponent({
|
|||
itemHeight={itemHeight}
|
||||
prefixCls={`${prefixCls}-list`}
|
||||
ref={listRef}
|
||||
>
|
||||
{(treeNode: FlattenNode) => {
|
||||
children={(treeNode: FlattenNode) => {
|
||||
const {
|
||||
pos,
|
||||
data: { ...restProps },
|
||||
|
@ -310,10 +310,11 @@ export default defineComponent({
|
|||
onMousemove={() => {
|
||||
onActiveChange(null);
|
||||
}}
|
||||
v-slots={slots}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</VirtualList>
|
||||
></VirtualList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -32,6 +32,7 @@ import classNames from '../_util/classNames';
|
|||
export default defineComponent({
|
||||
name: 'Tree',
|
||||
inheritAttrs: false,
|
||||
slots: ['checkable'],
|
||||
props: initDefaultProps(treeProps(), {
|
||||
prefixCls: 'vc-tree',
|
||||
showLine: false,
|
||||
|
@ -52,7 +53,7 @@ export default defineComponent({
|
|||
allowDrop: () => true,
|
||||
}),
|
||||
|
||||
setup(props, { attrs }) {
|
||||
setup(props, { attrs, slots }) {
|
||||
const destroyed = ref(false);
|
||||
let delayedDragEnterLogic: Record<Key, number> = {};
|
||||
|
||||
|
@ -91,6 +92,34 @@ export default defineComponent({
|
|||
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
|
||||
});
|
||||
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(() => {
|
||||
if (treeData.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 => {
|
||||
listRef.value.scrollTo(scroll);
|
||||
};
|
||||
|
@ -983,7 +986,7 @@ export default defineComponent({
|
|||
checkable,
|
||||
checkStrictly,
|
||||
disabled,
|
||||
motion,
|
||||
openAnimation,
|
||||
loadData,
|
||||
filterTreeNode,
|
||||
height,
|
||||
|
@ -1059,7 +1062,7 @@ export default defineComponent({
|
|||
disabled={disabled}
|
||||
selectable={selectable}
|
||||
checkable={!!checkable}
|
||||
motion={motion}
|
||||
motion={openAnimation}
|
||||
dragging={dragging}
|
||||
height={height}
|
||||
itemHeight={itemHeight}
|
||||
|
@ -1078,6 +1081,7 @@ export default defineComponent({
|
|||
onScroll={onScroll}
|
||||
{...treeNodeRequiredProps.value}
|
||||
{...domProps}
|
||||
v-slots={slots}
|
||||
/>
|
||||
</div>
|
||||
</TreeContext>
|
||||
|
|
|
@ -16,7 +16,7 @@ export default defineComponent({
|
|||
inheritAttrs: false,
|
||||
props: treeNodeProps,
|
||||
isTreeNode: 1,
|
||||
slots: ['title', 'icon', 'switcherIcon'],
|
||||
slots: ['title', 'icon', 'switcherIcon', 'checkable'],
|
||||
setup(props, { attrs, expose, slots }) {
|
||||
const dragNodeHighlight = ref(false);
|
||||
const context = useInjectTreeContext();
|
||||
|
@ -275,9 +275,6 @@ export default defineComponent({
|
|||
|
||||
if (!checkable) return null;
|
||||
|
||||
// [Legacy] Custom element should be separate with `checkable` in future
|
||||
const $custom = typeof checkable !== 'boolean' ? checkable : null;
|
||||
|
||||
return (
|
||||
<span
|
||||
class={classNames(
|
||||
|
@ -288,7 +285,7 @@ export default defineComponent({
|
|||
)}
|
||||
onClick={onCheck}
|
||||
>
|
||||
{$custom}
|
||||
{slots.checkable?.()}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -84,6 +84,7 @@ export interface TreeContextProps {
|
|||
const TreeContextKey: InjectionKey<ComputedRef<TreeContextProps>> = Symbol('TreeContextKey');
|
||||
|
||||
export const TreeContext = defineComponent({
|
||||
name: 'TreeContext',
|
||||
props: {
|
||||
value: { type: Object as PropType<TreeContextProps> },
|
||||
},
|
||||
|
@ -92,7 +93,7 @@ export const TreeContext = defineComponent({
|
|||
TreeContextKey,
|
||||
computed(() => props.value),
|
||||
);
|
||||
return slots.default?.();
|
||||
return () => slots.default?.();
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import type { TreeProps, TreeNodeProps } from './props';
|
||||
import Tree from './Tree';
|
||||
import TreeNode from './TreeNode';
|
||||
import type { TreeProps } from './Tree';
|
||||
import type { TreeNodeProps } from './TreeNode';
|
||||
|
||||
export { TreeNode };
|
||||
export type { TreeProps, TreeNodeProps };
|
||||
export default Tree;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { VNode } from 'vue';
|
||||
import { CSSProperties, VNode } from 'vue';
|
||||
export type { ScrollTo } from '../vc-virtual-list/List';
|
||||
|
||||
export interface DataNode {
|
||||
|
@ -14,8 +14,8 @@ export interface DataNode {
|
|||
switcherIcon?: IconType;
|
||||
|
||||
/** Set style of TreeNode. This is not recommend if you don't have any force requirement */
|
||||
// className?: string;
|
||||
// style?: CSSProperties;
|
||||
class?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export interface EventDataNode extends DataNode {
|
||||
|
|
|
@ -5,8 +5,15 @@ import type {
|
|||
NodeMouseEventHandler,
|
||||
NodeMouseEventParams,
|
||||
} from './contextTypes';
|
||||
import type { DataNode, Key, FlattenNode, DataEntity, EventDataNode, Direction } from './interface';
|
||||
import { fillFieldNames } from './utils/treeUtil';
|
||||
import type {
|
||||
DataNode,
|
||||
Key,
|
||||
FlattenNode,
|
||||
DataEntity,
|
||||
EventDataNode,
|
||||
Direction,
|
||||
FieldNames,
|
||||
} from './interface';
|
||||
|
||||
export interface CheckInfo {
|
||||
event: 'check';
|
||||
|
@ -103,7 +110,7 @@ export const treeProps = () => ({
|
|||
tabindex: Number,
|
||||
children: PropTypes.VNodeChild,
|
||||
treeData: { type: Array as PropType<DataNode[]> }, // Generate treeNode by children
|
||||
fieldNames: fillFieldNames,
|
||||
fieldNames: { type: Object as PropType<FieldNames> },
|
||||
showLine: { type: Boolean, default: undefined },
|
||||
showIcon: { type: Boolean, default: undefined },
|
||||
icon: PropTypes.any,
|
||||
|
@ -218,7 +225,7 @@ export const treeProps = () => ({
|
|||
*/
|
||||
onActiveChange: { type: Function as PropType<(key: Key) => void> },
|
||||
filterTreeNode: { type: Function as PropType<(treeNode: EventDataNode) => boolean> },
|
||||
motion: PropTypes.any,
|
||||
openAnimation: PropTypes.any,
|
||||
switcherIcon: PropTypes.any,
|
||||
|
||||
// Virtual List
|
||||
|
|
|
@ -1,39 +1,62 @@
|
|||
<template>
|
||||
<div>
|
||||
<demo />
|
||||
</div>
|
||||
<a-tree
|
||||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
checkable
|
||||
:tree-data="treeData"
|
||||
>
|
||||
<template #title0010><span style="color: #1890ff">sss</span></template>
|
||||
</a-tree>
|
||||
</template>
|
||||
<script>
|
||||
import { defineComponent } from 'vue';
|
||||
import demo from '../v2-doc/src/docs/mentions/demo/index.vue';
|
||||
// import Affix from '../components/affix';
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref, watch } from 'vue';
|
||||
import { TreeDataItem } from 'ant-design-vue/es/tree/Tree';
|
||||
|
||||
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({
|
||||
components: {
|
||||
demo,
|
||||
// Affix,
|
||||
},
|
||||
data() {
|
||||
setup() {
|
||||
const expandedKeys = ref<string[]>(['0-0-0', '0-0-1']);
|
||||
const selectedKeys = ref<string[]>(['0-0-0', '0-0-1']);
|
||||
const checkedKeys = ref<string[]>(['0-0-0', '0-0-1']);
|
||||
watch(expandedKeys, () => {
|
||||
console.log('expandedKeys', expandedKeys);
|
||||
});
|
||||
watch(selectedKeys, () => {
|
||||
console.log('selectedKeys', selectedKeys);
|
||||
});
|
||||
watch(checkedKeys, () => {
|
||||
console.log('checkedKeys', checkedKeys);
|
||||
});
|
||||
|
||||
return {
|
||||
visible: false,
|
||||
pStyle: {
|
||||
fontSize: '16px',
|
||||
color: 'rgba(0,0,0,0.85)',
|
||||
lineHeight: '24px',
|
||||
display: 'block',
|
||||
marginBottom: '16px',
|
||||
},
|
||||
pStyle2: {
|
||||
marginBottom: '24px',
|
||||
},
|
||||
treeData,
|
||||
expandedKeys,
|
||||
selectedKeys,
|
||||
checkedKeys,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showDrawer() {
|
||||
this.visible = true;
|
||||
},
|
||||
onClose() {
|
||||
this.visible = false;
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
Loading…
Reference in New Issue