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,
|
||||
);
|
||||
const { icon = slots.icon, ...otherProps } = props;
|
||||
const { icon = slots.icon, blockNode = true, ...otherProps } = props;
|
||||
return (
|
||||
<Tree
|
||||
{...attrs}
|
||||
icon={icon || getIcon}
|
||||
ref={treeRef}
|
||||
blockNode
|
||||
blockNode={blockNode}
|
||||
{...otherProps}
|
||||
prefixCls={prefixCls.value}
|
||||
class={connectClassName}
|
||||
|
|
|
@ -3,7 +3,6 @@ import { ref } from 'vue';
|
|||
import { defineComponent } from 'vue';
|
||||
import classNames from '../_util/classNames';
|
||||
import VcTree, { TreeNode } from '../vc-tree';
|
||||
import animation from '../_util/openAnimation';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { filterEmpty } from '../_util/props-util';
|
||||
import initDefaultProps from '../_util/props-util/initDefaultProps';
|
||||
|
@ -136,10 +135,6 @@ export default defineComponent({
|
|||
checkable: false,
|
||||
selectable: true,
|
||||
showIcon: false,
|
||||
openAnimation: {
|
||||
...animation,
|
||||
appear: false,
|
||||
},
|
||||
blockNode: false,
|
||||
}),
|
||||
slots: ['icon', 'title', 'switcherIcon'],
|
||||
|
|
|
@ -3,9 +3,10 @@ import type { FlattenNode } from './interface';
|
|||
import type { TreeNodeRequiredProps } from './utils/treeUtil';
|
||||
import { getTreeNodeProps } from './utils/treeUtil';
|
||||
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 { treeNodeProps } from './props';
|
||||
import { collapseMotion } from '../_util/transition';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MotionTreeNode',
|
||||
|
@ -25,7 +26,21 @@ export default defineComponent({
|
|||
const visible = ref(true);
|
||||
const context = useInjectTreeContext();
|
||||
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) {
|
||||
props.onMotionEnd();
|
||||
}
|
||||
|
@ -36,9 +51,12 @@ export default defineComponent({
|
|||
() => props.motionNodes,
|
||||
() => {
|
||||
if (props.motionNodes && props.motionType === 'hide' && visible.value) {
|
||||
visible.value = false;
|
||||
nextTick(() => {
|
||||
visible.value = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true, flush: 'post' },
|
||||
);
|
||||
onMounted(() => {
|
||||
props.motionNodes && props.onMotionStart();
|
||||
|
@ -46,18 +64,23 @@ export default defineComponent({
|
|||
onBeforeUnmount(() => {
|
||||
props.motionNodes && onMotionEnd();
|
||||
});
|
||||
|
||||
return () => {
|
||||
const { motion, motionNodes, motionType, active, treeNodeRequiredProps, ...otherProps } =
|
||||
props;
|
||||
if (motionNodes) {
|
||||
return (
|
||||
<Transition
|
||||
{...motion}
|
||||
{...transitionProps.value}
|
||||
appear={motionType === 'show'}
|
||||
onAfterAppear={onMotionEnd}
|
||||
onAfterLeave={onMotionEnd}
|
||||
onAfterAppear={() => onMotionEnd('appear')}
|
||||
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) => {
|
||||
const {
|
||||
data: { ...restProps },
|
||||
|
|
|
@ -118,7 +118,7 @@ export default defineComponent({
|
|||
props.onListChangeEnd();
|
||||
}
|
||||
watch(
|
||||
[() => ({ ...props.expandedKeys }), () => props.data],
|
||||
[() => [...props.expandedKeys], () => props.data],
|
||||
([expandedKeys, data], [prevExpandedKeys, prevData]) => {
|
||||
const diffExpanded = findExpandedKeys(prevExpandedKeys, expandedKeys);
|
||||
|
||||
|
@ -161,9 +161,7 @@ export default defineComponent({
|
|||
transitionData.value = data;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
// We should clean up motion if is changed by dragging
|
||||
watch(
|
||||
() => 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 () => {
|
||||
const {
|
||||
|
|
|
@ -84,12 +84,11 @@ export default defineComponent({
|
|||
// abstract-drag-over-node is the top node
|
||||
dragOverNodeKey: null,
|
||||
});
|
||||
|
||||
warning(
|
||||
!(props.treeData === undefined && props.children),
|
||||
'`children` of Tree is deprecated. Please use `treeData` instead.',
|
||||
);
|
||||
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);
|
||||
});
|
||||
const keyEntities = ref({});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { useInjectTreeContext } from './contextTypes';
|
|||
import { getDataAndAria } from './util';
|
||||
import Indent from './Indent';
|
||||
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 classNames from '../_util/classNames';
|
||||
import { warning } from '../vc-util/warning';
|
||||
|
@ -18,7 +18,7 @@ export default defineComponent({
|
|||
inheritAttrs: false,
|
||||
props: treeNodeProps,
|
||||
isTreeNode: 1,
|
||||
slots: ['title', 'icon', 'switcherIcon', 'checkable'],
|
||||
slots: ['title', 'icon', 'switcherIcon'],
|
||||
setup(props, { attrs, slots, expose }) {
|
||||
warning(
|
||||
!('slots' in props.data),
|
||||
|
|
|
@ -41,7 +41,7 @@ export const treeNodeProps = {
|
|||
dragOverGapTop: { type: Boolean, default: undefined },
|
||||
dragOverGapBottom: { type: Boolean, default: undefined },
|
||||
pos: String,
|
||||
// domRef: React.Ref<HTMLDivElement>,
|
||||
|
||||
/** New added in Tree for easy data access */
|
||||
data: { type: Object as PropType<DataNode> },
|
||||
isStart: { type: Array as PropType<boolean[]> },
|
||||
|
|
|
@ -60,6 +60,20 @@ export function warningWithoutKey(treeData: DataNode[], fieldNames: FieldNames)
|
|||
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.
|
||||
*/
|
||||
|
@ -73,13 +87,37 @@ export function convertTreeToData(rootNodes: VNodeChild): DataNode[] {
|
|||
warning(!treeNode, 'Tree/TreeNode can only accept TreeNode as children.');
|
||||
return null;
|
||||
}
|
||||
|
||||
const slots = (treeNode.children as any) || {};
|
||||
const key = treeNode.key as string | number;
|
||||
const { ...rest } = treeNode.props;
|
||||
const children = (treeNode.children as any)?.default?.();
|
||||
const props: any = {};
|
||||
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 = {
|
||||
...rest,
|
||||
title,
|
||||
icon,
|
||||
switcherIcon,
|
||||
key,
|
||||
isLeaf,
|
||||
...newProps,
|
||||
};
|
||||
|
||||
const parsedChildren = dig(children);
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
v-model:expandedKeys="expandedKeys"
|
||||
v-model:selectedKeys="selectedKeys"
|
||||
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-1" title="leaf 0-1" is-leaf />
|
||||
</a-tree-node>
|
||||
|
@ -16,15 +17,34 @@
|
|||
</a-directory-tree>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { defineComponent, ref, watch } from 'vue';
|
||||
export default defineComponent({
|
||||
setup() {
|
||||
const expandedKeys = ref<string[]>(['0-0', '0-1']);
|
||||
const expandedKeys = 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 {
|
||||
expandedKeys,
|
||||
selectedKeys,
|
||||
onTest: () => {},
|
||||
treeData,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue