Skip to content

Commit 94aa0df

Browse files
committed
refactor: tree
1 parent 5f189f5 commit 94aa0df

File tree

7 files changed

+282
-68
lines changed

7 files changed

+282
-68
lines changed

components/tree/DirectoryTree.tsx

Lines changed: 245 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,21 @@
1-
import type { ExtractPropTypes, PropType, VNode } from 'vue';
2-
import { defineComponent, inject } from 'vue';
3-
import omit from 'omit.js';
1+
import { ExtractPropTypes, nextTick, onUpdated, PropType, ref, watch } from 'vue';
2+
import { defineComponent } from 'vue';
43
import debounce from 'lodash-es/debounce';
54
import FolderOpenOutlined from '@ant-design/icons-vue/FolderOpenOutlined';
65
import FolderOutlined from '@ant-design/icons-vue/FolderOutlined';
76
import FileOutlined from '@ant-design/icons-vue/FileOutlined';
8-
import PropTypes from '../_util/vue-types';
97
import classNames from '../_util/classNames';
10-
import { treeProps } from './Tree';
8+
import { AntdTreeNodeAttribute, treeProps } from './Tree';
119
import Tree, { TreeProps } from './Tree';
12-
import {
13-
calcRangeKeys,
14-
getFullKeyList,
15-
convertDirectoryKeysToNodes,
16-
getFullKeyListByTreeData,
17-
} from './util';
18-
import { getOptionProps, getComponent, getSlot } from '../_util/props-util';
1910
import initDefaultProps from '../_util/props-util/initDefaultProps';
20-
import { defaultConfigProvider } from '../config-provider';
11+
import { convertDataToEntities, convertTreeToData } from '../vc-tree/utils/treeUtil';
12+
import { DataNode, EventDataNode, Key } from '../vc-tree/interface';
13+
import { conductExpandParent } from '../vc-tree/util';
14+
import { calcRangeKeys, convertDirectoryKeysToNodes } from './utils/dictUtil';
15+
import useConfigInject from '../_util/hooks/useConfigInject';
16+
import { filterEmpty } from '../_util/props-util';
2117

22-
export type ExpandAction = false | 'click' | 'doubleClick' | 'dblclick';
23-
24-
function getIcon(props: { isLeaf: boolean; expanded: boolean } & VNode) {
25-
const { isLeaf, expanded } = props;
26-
if (isLeaf) {
27-
return <FileOutlined />;
28-
}
29-
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
30-
}
18+
export type ExpandAction = false | 'click' | 'doubleclick' | 'dblclick';
3119

3220
const directoryTreeProps = {
3321
...treeProps(),
@@ -36,16 +24,249 @@ const directoryTreeProps = {
3624

3725
export type DirectoryTreeProps = Partial<ExtractPropTypes<typeof directoryTreeProps>>;
3826

27+
function getIcon(props: AntdTreeNodeAttribute) {
28+
const { isLeaf, expanded } = props;
29+
if (isLeaf) {
30+
return <FileOutlined />;
31+
}
32+
return expanded ? <FolderOpenOutlined /> : <FolderOutlined />;
33+
}
34+
3935
export default defineComponent({
4036
name: 'ADirectoryTree',
4137
inheritAttrs: false,
4238
props: initDefaultProps(directoryTreeProps, {
4339
showIcon: true,
4440
expandAction: 'click',
4541
}),
46-
setup() {
42+
slots: ['icon', 'title', 'switcherIcon'],
43+
emits: [
44+
'update:selectedKeys',
45+
'update:checkedKeys',
46+
'update:expandedKeys',
47+
'expand',
48+
'select',
49+
'check',
50+
'doubleclick',
51+
'dblclick',
52+
'click',
53+
],
54+
setup(props, { attrs, slots, emit }) {
55+
// convertTreeToData 兼容 a-tree-node 历史写法,未来a-tree-node移除后,删除相关代码,不要再render中调用 treeData,否则死循环
56+
const treeData = ref<DataNode[]>(
57+
props.treeData || convertTreeToData(filterEmpty(slots.default?.())),
58+
);
59+
watch(
60+
() => props.treeData,
61+
() => {
62+
treeData.value = props.treeData;
63+
},
64+
);
65+
onUpdated(() => {
66+
nextTick(() => {
67+
if (props.treeData === undefined && slots.default) {
68+
treeData.value = convertTreeToData(filterEmpty(slots.default?.()));
69+
}
70+
});
71+
});
72+
// Shift click usage
73+
const lastSelectedKey = ref<Key>();
74+
75+
const cachedSelectedKeys = ref<Key[]>();
76+
77+
const treeRef = ref();
78+
79+
const getInitExpandedKeys = () => {
80+
const { keyEntities } = convertDataToEntities(treeData.value);
81+
82+
let initExpandedKeys: any;
83+
84+
// Expanded keys
85+
if (props.defaultExpandAll) {
86+
initExpandedKeys = Object.keys(keyEntities);
87+
} else if (props.defaultExpandParent) {
88+
initExpandedKeys = conductExpandParent(
89+
props.expandedKeys || props.defaultExpandedKeys,
90+
keyEntities,
91+
);
92+
} else {
93+
initExpandedKeys = props.expandedKeys || props.defaultExpandedKeys;
94+
}
95+
return initExpandedKeys;
96+
};
97+
98+
const selectedKeys = ref(props.selectedKeys || props.defaultSelectedKeys || []);
99+
100+
const expandedKeys = ref<Key[]>(getInitExpandedKeys());
101+
102+
watch(
103+
() => props.selectedKeys,
104+
() => {
105+
if (props.selectedKeys !== undefined) {
106+
selectedKeys.value = props.selectedKeys;
107+
}
108+
},
109+
{ immediate: true },
110+
);
111+
112+
watch(
113+
() => props.expandedKeys,
114+
() => {
115+
if (props.expandedKeys !== undefined) {
116+
expandedKeys.value = props.expandedKeys;
117+
}
118+
},
119+
{ immediate: true },
120+
);
121+
122+
const expandFolderNode = (event: MouseEvent, node: any) => {
123+
const { isLeaf } = node;
124+
125+
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) {
126+
return;
127+
}
128+
// Call internal rc-tree expand function
129+
// https://github.com/ant-design/ant-design/issues/12567
130+
treeRef.value!.onNodeExpand(event as any, node);
131+
};
132+
const onDebounceExpand = debounce(expandFolderNode, 200, {
133+
leading: true,
134+
});
135+
const onExpand = (
136+
keys: Key[],
137+
info: {
138+
node: EventDataNode;
139+
expanded: boolean;
140+
nativeEvent: MouseEvent;
141+
},
142+
) => {
143+
if (props.expandedKeys === undefined) {
144+
expandedKeys.value = keys;
145+
}
146+
// Call origin function
147+
emit('update:expandedKeys', keys);
148+
emit('expand', keys, info);
149+
};
150+
151+
const onClick = (event: MouseEvent, node: EventDataNode) => {
152+
const { expandAction } = props;
153+
154+
// Expand the tree
155+
if (expandAction === 'click') {
156+
onDebounceExpand(event, node);
157+
}
158+
emit('click', event, node);
159+
};
160+
161+
const onDoubleClick = (event: MouseEvent, node: EventDataNode) => {
162+
const { expandAction } = props;
163+
// Expand the tree
164+
if (expandAction === 'dblclick' || expandAction === 'doubleclick') {
165+
onDebounceExpand(event, node);
166+
}
167+
168+
emit('doubleclick', event, node);
169+
emit('dblclick', event, node);
170+
};
171+
172+
const onSelect = (
173+
keys: Key[],
174+
event: {
175+
event: 'select';
176+
selected: boolean;
177+
node: any;
178+
selectedNodes: DataNode[];
179+
nativeEvent: MouseEvent;
180+
},
181+
) => {
182+
const { multiple } = props;
183+
const { node, nativeEvent } = event;
184+
const { key = '' } = node;
185+
186+
// const newState: DirectoryTreeState = {};
187+
188+
// We need wrap this event since some value is not same
189+
const newEvent: any = {
190+
...event,
191+
selected: true, // Directory selected always true
192+
};
193+
194+
// Windows / Mac single pick
195+
const ctrlPick: boolean = nativeEvent.ctrlKey || nativeEvent.metaKey;
196+
const shiftPick: boolean = nativeEvent.shiftKey;
197+
198+
// Generate new selected keys
199+
let newSelectedKeys: Key[];
200+
if (multiple && ctrlPick) {
201+
// Control click
202+
newSelectedKeys = keys;
203+
lastSelectedKey.value = key;
204+
cachedSelectedKeys.value = newSelectedKeys;
205+
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
206+
} else if (multiple && shiftPick) {
207+
// Shift click
208+
newSelectedKeys = Array.from(
209+
new Set([
210+
...(cachedSelectedKeys.value || []),
211+
...calcRangeKeys({
212+
treeData: treeData.value,
213+
expandedKeys: expandedKeys.value,
214+
startKey: key,
215+
endKey: lastSelectedKey.value,
216+
}),
217+
]),
218+
);
219+
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
220+
} else {
221+
// Single click
222+
newSelectedKeys = [key];
223+
lastSelectedKey.value = key;
224+
cachedSelectedKeys.value = newSelectedKeys;
225+
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData.value, newSelectedKeys);
226+
}
227+
228+
emit('update:selectedKeys', newSelectedKeys);
229+
emit('select', newSelectedKeys, newEvent);
230+
if (props.selectedKeys === undefined) {
231+
selectedKeys.value = newSelectedKeys;
232+
}
233+
};
234+
235+
const onCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
236+
emit('update:checkedKeys', checkedObjOrKeys);
237+
emit('check', checkedObjOrKeys, eventObj);
238+
};
239+
240+
const { prefixCls, direction } = useConfigInject('tree', props);
241+
47242
return () => {
48-
return null;
243+
const connectClassName = classNames(
244+
`${prefixCls.value}-directory`,
245+
{
246+
[`${prefixCls.value}-directory-rtl`]: direction.value === 'rtl',
247+
},
248+
attrs.class,
249+
);
250+
const { icon = slots.icon, ...otherProps } = props;
251+
return (
252+
<Tree
253+
{...attrs}
254+
icon={icon || getIcon}
255+
ref={treeRef}
256+
blockNode
257+
{...otherProps}
258+
prefixCls={prefixCls.value}
259+
class={connectClassName}
260+
expandedKeys={expandedKeys.value}
261+
selectedKeys={selectedKeys.value}
262+
onSelect={onSelect}
263+
onClick={onClick}
264+
onDblclick={onDoubleClick}
265+
onExpand={onExpand}
266+
onCheck={onCheck}
267+
v-slots={slots}
268+
/>
269+
);
49270
};
50271
},
51272
});

components/tree/Tree.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -150,13 +150,18 @@ export default defineComponent({
150150
'expand',
151151
'select',
152152
'check',
153+
'doubleclick',
154+
'dblclick',
153155
],
154156
TreeNode,
155157
setup(props, { attrs, expose, emit, slots }) {
156158
const { prefixCls, direction, virtual } = useConfigInject('tree', props);
157-
const tree = ref();
159+
const treeRef = ref();
158160
expose({
159-
tree,
161+
treeRef,
162+
onNodeExpand: (...args) => {
163+
treeRef.value?.onNodeExpand(...args);
164+
},
160165
});
161166

162167
const handleCheck: TreeProps['onCheck'] = (checkedObjOrKeys, eventObj) => {
@@ -197,7 +202,7 @@ export default defineComponent({
197202
itemHeight={20}
198203
virtual={virtual.value}
199204
{...newProps}
200-
ref={tree}
205+
ref={treeRef}
201206
prefixCls={prefixCls.value}
202207
class={classNames(
203208
{

components/vc-tree/Tree.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export default defineComponent({
5555
allowDrop: () => true,
5656
}),
5757

58-
setup(props, { attrs, slots }) {
58+
setup(props, { attrs, slots, expose }) {
5959
const destroyed = ref(false);
6060
let delayedDragEnterLogic: Record<Key, number> = {};
6161
const indent = ref();
@@ -543,9 +543,9 @@ export default defineComponent({
543543
};
544544

545545
const onNodeDoubleClick: NodeMouseEventHandler = (e, treeNode) => {
546-
const { onDblClick } = props;
547-
if (onDblClick) {
548-
onDblClick(e, treeNode);
546+
const { onDblclick } = props;
547+
if (onDblclick) {
548+
onDblclick(e, treeNode);
549549
}
550550
};
551551

@@ -953,7 +953,9 @@ export default defineComponent({
953953
onKeyDown(event);
954954
}
955955
};
956-
956+
expose({
957+
onNodeExpand,
958+
});
957959
onUnmounted(() => {
958960
window.removeEventListener('dragend', onWindowDragEnd);
959961
destroyed.value = true;

components/vc-tree/interface.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { ComputedRef, CSSProperties, Ref, VNode } from 'vue';
1+
import type { ComputedRef, CSSProperties, DefineComponent, Ref, VNode } from 'vue';
2+
import { TreeNodeProps } from './props';
23
export type { ScrollTo } from '../vc-virtual-list/List';
34

45
export interface DataNode {
@@ -39,7 +40,7 @@ export type IconType = any;
3940

4041
export type Key = string | number;
4142

42-
export type NodeElement = VNode & {
43+
export type NodeElement = VNode<DefineComponent<TreeNodeProps>> & {
4344
type: {
4445
isTreeNode: boolean;
4546
};

components/vc-tree/props.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ export const treeProps = () => ({
149149
onKeyDown: { type: Function as PropType<EventHandlerNonNull> },
150150
onContextmenu: { type: Function as PropType<EventHandlerNonNull> },
151151
onClick: { type: Function as PropType<NodeMouseEventHandler> },
152-
onDblClick: { type: Function as PropType<NodeMouseEventHandler> },
152+
onDblclick: { type: Function as PropType<NodeMouseEventHandler> },
153153
onScroll: { type: Function as PropType<EventHandlerNonNull> },
154154
onExpand: {
155155
type: Function as PropType<

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy