refactor: directory tree
parent
94aa0df8d7
commit
99f034deac
|
@ -0,0 +1,89 @@
|
||||||
|
// copy from https://github.dev/vueuse/vueuse
|
||||||
|
|
||||||
|
import type { Ref, WatchOptions, WatchStopHandle } from 'vue';
|
||||||
|
import { unref, watch } from 'vue';
|
||||||
|
|
||||||
|
type MaybeRef<T> = T | Ref<T>;
|
||||||
|
|
||||||
|
type Fn = () => void;
|
||||||
|
|
||||||
|
export type FunctionArgs<Args extends any[] = any[], Return = void> = (...args: Args) => Return;
|
||||||
|
|
||||||
|
export interface FunctionWrapperOptions<Args extends any[] = any[], This = any> {
|
||||||
|
fn: FunctionArgs<Args, This>;
|
||||||
|
args: Args;
|
||||||
|
thisArg: This;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventFilter<Args extends any[] = any[], This = any> = (
|
||||||
|
invoke: Fn,
|
||||||
|
options: FunctionWrapperOptions<Args, This>,
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
const bypassFilter: EventFilter = invoke => {
|
||||||
|
return invoke();
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* Create an EventFilter that debounce the events
|
||||||
|
*
|
||||||
|
* @param ms
|
||||||
|
*/
|
||||||
|
export function debounceFilter(ms: MaybeRef<number>) {
|
||||||
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||||
|
|
||||||
|
const filter: EventFilter = invoke => {
|
||||||
|
const duration = unref(ms);
|
||||||
|
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
|
||||||
|
if (duration <= 0) return invoke();
|
||||||
|
|
||||||
|
timer = setTimeout(invoke, duration);
|
||||||
|
};
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
export interface DebouncedWatchOptions<Immediate> extends WatchOptions<Immediate> {
|
||||||
|
debounce?: MaybeRef<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ConfigurableEventFilter {
|
||||||
|
eventFilter?: EventFilter;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
function createFilterWrapper<T extends FunctionArgs>(filter: EventFilter, fn: T) {
|
||||||
|
function wrapper(this: any, ...args: any[]) {
|
||||||
|
filter(() => fn.apply(this, args), { fn, thisArg: this, args });
|
||||||
|
}
|
||||||
|
|
||||||
|
return wrapper as any as T;
|
||||||
|
}
|
||||||
|
export interface WatchWithFilterOptions<Immediate>
|
||||||
|
extends WatchOptions<Immediate>,
|
||||||
|
ConfigurableEventFilter {}
|
||||||
|
// implementation
|
||||||
|
export function watchWithFilter<Immediate extends Readonly<boolean> = false>(
|
||||||
|
source: any,
|
||||||
|
cb: any,
|
||||||
|
options: WatchWithFilterOptions<Immediate> = {},
|
||||||
|
): WatchStopHandle {
|
||||||
|
const { eventFilter = bypassFilter, ...watchOptions } = options;
|
||||||
|
|
||||||
|
return watch(source, createFilterWrapper(eventFilter, cb), watchOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
// implementation
|
||||||
|
export default function debouncedWatch<Immediate extends Readonly<boolean> = false>(
|
||||||
|
source: any,
|
||||||
|
cb: any,
|
||||||
|
options: DebouncedWatchOptions<Immediate> = {},
|
||||||
|
): WatchStopHandle {
|
||||||
|
const { debounce = 0, ...watchOptions } = options;
|
||||||
|
|
||||||
|
return watchWithFilter(source, cb, {
|
||||||
|
...watchOptions,
|
||||||
|
eventFilter: debounceFilter(debounce),
|
||||||
|
});
|
||||||
|
}
|
|
@ -247,13 +247,13 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
attrs.class,
|
attrs.class,
|
||||||
);
|
);
|
||||||
const { icon = slots.icon, ...otherProps } = props;
|
const { icon = slots.icon, blockNode = true, ...otherProps } = props;
|
||||||
return (
|
return (
|
||||||
<Tree
|
<Tree
|
||||||
{...attrs}
|
{...attrs}
|
||||||
icon={icon || getIcon}
|
icon={icon || getIcon}
|
||||||
ref={treeRef}
|
ref={treeRef}
|
||||||
blockNode
|
blockNode={blockNode}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
prefixCls={prefixCls.value}
|
prefixCls={prefixCls.value}
|
||||||
class={connectClassName}
|
class={connectClassName}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import { ref } from 'vue';
|
||||||
import { defineComponent } from 'vue';
|
import { defineComponent } from 'vue';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import VcTree, { TreeNode } from '../vc-tree';
|
import VcTree, { TreeNode } from '../vc-tree';
|
||||||
import animation from '../_util/openAnimation';
|
|
||||||
import PropTypes from '../_util/vue-types';
|
import PropTypes from '../_util/vue-types';
|
||||||
import { filterEmpty } from '../_util/props-util';
|
import { filterEmpty } from '../_util/props-util';
|
||||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||||
|
@ -136,10 +135,6 @@ export default defineComponent({
|
||||||
checkable: false,
|
checkable: false,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showIcon: false,
|
showIcon: false,
|
||||||
openAnimation: {
|
|
||||||
...animation,
|
|
||||||
appear: false,
|
|
||||||
},
|
|
||||||
blockNode: false,
|
blockNode: false,
|
||||||
}),
|
}),
|
||||||
slots: ['icon', 'title', 'switcherIcon'],
|
slots: ['icon', 'title', 'switcherIcon'],
|
||||||
|
|
|
@ -3,9 +3,10 @@ import type { FlattenNode } from './interface';
|
||||||
import type { TreeNodeRequiredProps } from './utils/treeUtil';
|
import type { TreeNodeRequiredProps } from './utils/treeUtil';
|
||||||
import { getTreeNodeProps } from './utils/treeUtil';
|
import { getTreeNodeProps } from './utils/treeUtil';
|
||||||
import { useInjectTreeContext } from './contextTypes';
|
import { useInjectTreeContext } from './contextTypes';
|
||||||
import type { PropType } from 'vue';
|
import { computed, getCurrentInstance, nextTick, PropType } from 'vue';
|
||||||
import { defineComponent, onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue';
|
import { defineComponent, onBeforeUnmount, onMounted, ref, Transition, watch } from 'vue';
|
||||||
import { treeNodeProps } from './props';
|
import { treeNodeProps } from './props';
|
||||||
|
import { collapseMotion } from '../_util/transition';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MotionTreeNode',
|
name: 'MotionTreeNode',
|
||||||
|
@ -25,7 +26,21 @@ export default defineComponent({
|
||||||
const visible = ref(true);
|
const visible = ref(true);
|
||||||
const context = useInjectTreeContext();
|
const context = useInjectTreeContext();
|
||||||
const motionedRef = ref(false);
|
const motionedRef = ref(false);
|
||||||
const onMotionEnd = () => {
|
const transitionClass = ref('');
|
||||||
|
const transitionStyle = ref({});
|
||||||
|
const transitionProps = computed(() => {
|
||||||
|
if (props.motion) {
|
||||||
|
return props.motion;
|
||||||
|
} else {
|
||||||
|
return collapseMotion(transitionStyle, transitionClass);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const onMotionEnd = (type?: 'appear' | 'leave') => {
|
||||||
|
if (type === 'appear') {
|
||||||
|
transitionProps.value?.onAfterAppear?.();
|
||||||
|
} else if (type === 'leave') {
|
||||||
|
transitionProps.value?.onAfterLeave?.();
|
||||||
|
}
|
||||||
if (!motionedRef.value) {
|
if (!motionedRef.value) {
|
||||||
props.onMotionEnd();
|
props.onMotionEnd();
|
||||||
}
|
}
|
||||||
|
@ -36,9 +51,12 @@ export default defineComponent({
|
||||||
() => props.motionNodes,
|
() => props.motionNodes,
|
||||||
() => {
|
() => {
|
||||||
if (props.motionNodes && props.motionType === 'hide' && visible.value) {
|
if (props.motionNodes && props.motionType === 'hide' && visible.value) {
|
||||||
visible.value = false;
|
nextTick(() => {
|
||||||
|
visible.value = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true, flush: 'post' },
|
||||||
);
|
);
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
props.motionNodes && props.onMotionStart();
|
props.motionNodes && props.onMotionStart();
|
||||||
|
@ -46,18 +64,23 @@ export default defineComponent({
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
props.motionNodes && onMotionEnd();
|
props.motionNodes && onMotionEnd();
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const { motion, motionNodes, motionType, active, treeNodeRequiredProps, ...otherProps } =
|
const { motion, motionNodes, motionType, active, treeNodeRequiredProps, ...otherProps } =
|
||||||
props;
|
props;
|
||||||
if (motionNodes) {
|
if (motionNodes) {
|
||||||
return (
|
return (
|
||||||
<Transition
|
<Transition
|
||||||
{...motion}
|
{...transitionProps.value}
|
||||||
appear={motionType === 'show'}
|
appear={motionType === 'show'}
|
||||||
onAfterAppear={onMotionEnd}
|
onAfterAppear={() => onMotionEnd('appear')}
|
||||||
onAfterLeave={onMotionEnd}
|
onAfterLeave={() => onMotionEnd('leave')}
|
||||||
>
|
>
|
||||||
<div v-show={visible.value} class={`${context.value.prefixCls}-treenode-motion`}>
|
<div
|
||||||
|
v-show={visible.value}
|
||||||
|
class={[`${context.value.prefixCls}-treenode-motion`, transitionClass.value]}
|
||||||
|
style={transitionStyle.value}
|
||||||
|
>
|
||||||
{motionNodes.map((treeNode: FlattenNode) => {
|
{motionNodes.map((treeNode: FlattenNode) => {
|
||||||
const {
|
const {
|
||||||
data: { ...restProps },
|
data: { ...restProps },
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default defineComponent({
|
||||||
props.onListChangeEnd();
|
props.onListChangeEnd();
|
||||||
}
|
}
|
||||||
watch(
|
watch(
|
||||||
[() => ({ ...props.expandedKeys }), () => props.data],
|
[() => [...props.expandedKeys], () => props.data],
|
||||||
([expandedKeys, data], [prevExpandedKeys, prevData]) => {
|
([expandedKeys, data], [prevExpandedKeys, prevData]) => {
|
||||||
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
|
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
|
||||||
|
|
||||||
|
@ -161,9 +161,7 @@ export default defineComponent({
|
||||||
transitionData.value = data;
|
transitionData.value = data;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ immediate: true },
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// We should clean up motion if is changed by dragging
|
// We should clean up motion if is changed by dragging
|
||||||
watch(
|
watch(
|
||||||
() => props.dragging,
|
() => props.dragging,
|
||||||
|
@ -174,7 +172,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergedData = computed(() => (props.motion ? transitionData.value : props.data));
|
const mergedData = computed(() =>
|
||||||
|
props.motion === undefined ? transitionData.value : props.data,
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -84,12 +84,11 @@ export default defineComponent({
|
||||||
// abstract-drag-over-node is the top node
|
// abstract-drag-over-node is the top node
|
||||||
dragOverNodeKey: null,
|
dragOverNodeKey: null,
|
||||||
});
|
});
|
||||||
|
warning(
|
||||||
|
!(props.treeData === undefined && props.children),
|
||||||
|
'`children` of Tree is deprecated. Please use `treeData` instead.',
|
||||||
|
);
|
||||||
const treeData = computed(() => {
|
const treeData = computed(() => {
|
||||||
warning(
|
|
||||||
!(props.treeData === undefined && props.children),
|
|
||||||
'`children` of Tree is deprecated. Please use `treeData` instead.',
|
|
||||||
);
|
|
||||||
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
|
return props.treeData !== undefined ? props.treeData : convertTreeToData(props.children);
|
||||||
});
|
});
|
||||||
const keyEntities = ref({});
|
const keyEntities = ref({});
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { useInjectTreeContext } from './contextTypes';
|
||||||
import { getDataAndAria } from './util';
|
import { getDataAndAria } from './util';
|
||||||
import Indent from './Indent';
|
import Indent from './Indent';
|
||||||
import { convertNodePropsToEventData } from './utils/treeUtil';
|
import { convertNodePropsToEventData } from './utils/treeUtil';
|
||||||
import { computed, defineComponent, getCurrentInstance, onMounted, onUpdated, ref } from 'vue';
|
import { computed, defineComponent, onMounted, onUpdated, ref } from 'vue';
|
||||||
import { treeNodeProps } from './props';
|
import { treeNodeProps } from './props';
|
||||||
import classNames from '../_util/classNames';
|
import classNames from '../_util/classNames';
|
||||||
import { warning } from '../vc-util/warning';
|
import { warning } from '../vc-util/warning';
|
||||||
|
@ -18,7 +18,7 @@ export default defineComponent({
|
||||||
inheritAttrs: false,
|
inheritAttrs: false,
|
||||||
props: treeNodeProps,
|
props: treeNodeProps,
|
||||||
isTreeNode: 1,
|
isTreeNode: 1,
|
||||||
slots: ['title', 'icon', 'switcherIcon', 'checkable'],
|
slots: ['title', 'icon', 'switcherIcon'],
|
||||||
setup(props, { attrs, slots, expose }) {
|
setup(props, { attrs, slots, expose }) {
|
||||||
warning(
|
warning(
|
||||||
!('slots' in props.data),
|
!('slots' in props.data),
|
||||||
|
|
|
@ -41,7 +41,7 @@ export const treeNodeProps = {
|
||||||
dragOverGapTop: { type: Boolean, default: undefined },
|
dragOverGapTop: { type: Boolean, default: undefined },
|
||||||
dragOverGapBottom: { type: Boolean, default: undefined },
|
dragOverGapBottom: { type: Boolean, default: undefined },
|
||||||
pos: String,
|
pos: String,
|
||||||
// domRef: React.Ref<HTMLDivElement>,
|
|
||||||
/** New added in Tree for easy data access */
|
/** New added in Tree for easy data access */
|
||||||
data: { type: Object as PropType<DataNode> },
|
data: { type: Object as PropType<DataNode> },
|
||||||
isStart: { type: Array as PropType<boolean[]> },
|
isStart: { type: Array as PropType<boolean[]> },
|
||||||
|
|
|
@ -60,6 +60,20 @@ export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames)
|
||||||
dig(treeData);
|
dig(treeData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const cacheStringFunction = (fn: (s: string) => string) => {
|
||||||
|
const cache = Object.create(null);
|
||||||
|
return (str: string) => {
|
||||||
|
const hit = cache[str];
|
||||||
|
return hit || (cache[str] = fn(str));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const camelizeRE = /-(\w)/g;
|
||||||
|
|
||||||
|
const camelize = cacheStringFunction((str: string) => {
|
||||||
|
return str.replace(camelizeRE, (_, c) => (c ? c.toUpperCase() : ''));
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert `children` of Tree into `treeData` structure.
|
* Convert `children` of Tree into `treeData` structure.
|
||||||
*/
|
*/
|
||||||
|
@ -73,13 +87,37 @@ export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
|
||||||
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
|
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
const slots = (treeNode.children as any) || {};
|
||||||
const key = treeNode.key as string | number;
|
const key = treeNode.key as string | number;
|
||||||
const { ...rest } = treeNode.props;
|
const props: any = {};
|
||||||
const children = (treeNode.children as any)?.default?.();
|
for (const [k, v] of Object.entries(treeNode.props)) {
|
||||||
|
props[camelize(k)] = v;
|
||||||
|
}
|
||||||
|
const { isLeaf, checkable, selectable, disabled, disableCheckbox } = props;
|
||||||
|
// 默认值为 undefined
|
||||||
|
const newProps = {
|
||||||
|
isLeaf: isLeaf || isLeaf === '' || undefined,
|
||||||
|
checkable: checkable || checkable === '' || undefined,
|
||||||
|
selectable: selectable || selectable === '' || undefined,
|
||||||
|
disabled: disabled || disabled === '' || undefined,
|
||||||
|
disableCheckbox: disableCheckbox || disableCheckbox === '' || undefined,
|
||||||
|
};
|
||||||
|
const slotsProps = { ...props, ...newProps };
|
||||||
|
const {
|
||||||
|
title = slots.title?.(slotsProps),
|
||||||
|
icon = slots.icon?.(slotsProps),
|
||||||
|
switcherIcon = slots.switcherIcon?.(slotsProps),
|
||||||
|
...rest
|
||||||
|
} = props;
|
||||||
|
const children = slots.default?.();
|
||||||
const dataNode: DataNode = {
|
const dataNode: DataNode = {
|
||||||
...rest,
|
...rest,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
switcherIcon,
|
||||||
key,
|
key,
|
||||||
|
isLeaf,
|
||||||
|
...newProps,
|
||||||
};
|
};
|
||||||
|
|
||||||
const parsedChildren = dig(children);
|
const parsedChildren = dig(children);
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
v-model:expandedKeys="expandedKeys"
|
v-model:expandedKeys="expandedKeys"
|
||||||
v-model:selectedKeys="selectedKeys"
|
v-model:selectedKeys="selectedKeys"
|
||||||
multiple
|
multiple
|
||||||
@test="onTest"
|
:tree-data1="treeData"
|
||||||
>
|
>
|
||||||
<a-tree-node key="0-0" title="parent 0">
|
<a-tree-node key="0-0" title="ddd" class="test" style="color: red">
|
||||||
|
<template #title="{ title }">{{ title }}</template>
|
||||||
<a-tree-node key="0-0-0" title="leaf 0-0" is-leaf />
|
<a-tree-node key="0-0-0" title="leaf 0-0" is-leaf />
|
||||||
<a-tree-node key="0-0-1" title="leaf 0-1" is-leaf />
|
<a-tree-node key="0-0-1" title="leaf 0-1" is-leaf />
|
||||||
</a-tree-node>
|
</a-tree-node>
|
||||||
|
@ -16,15 +17,34 @@
|
||||||
</a-directory-tree>
|
</a-directory-tree>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref, watch } from 'vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
setup() {
|
setup() {
|
||||||
const expandedKeys = ref<string[]>(['0-0', '0-1']);
|
const expandedKeys = ref<string[]>([]);
|
||||||
const selectedKeys = ref<string[]>([]);
|
const selectedKeys = ref<string[]>([]);
|
||||||
|
const treeData = [
|
||||||
|
{
|
||||||
|
title: 'parent 0',
|
||||||
|
key: '0-0',
|
||||||
|
children: [
|
||||||
|
{ title: 'leaf 0-0', key: '0-0-0', isLeaf: true },
|
||||||
|
// { title: 'leaf 0-1', key: '0-0-1', isLeaf: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'parent 1',
|
||||||
|
key: '0-1',
|
||||||
|
children: [
|
||||||
|
{ title: 'leaf 1-0', key: '0-1-0', isLeaf: true },
|
||||||
|
{ title: 'leaf 1-1', key: '0-1-1', isLeaf: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
return {
|
return {
|
||||||
expandedKeys,
|
expandedKeys,
|
||||||
selectedKeys,
|
selectedKeys,
|
||||||
onTest: () => {},
|
onTest: () => {},
|
||||||
|
treeData,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue