Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 47 additions & 36 deletions packages/component/src/virtual-tree/VirtualTree.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@ import {
TreeOptionsEnum,
treeProps,
} from 'element-plus/es/components/tree-v2/src/virtual-tree'
import {
definePropType,
mutable,
type Nullable,
} from 'element-plus/es/utils/index.mjs'
import { definePropType, mutable } from 'element-plus/es/utils/index.mjs'
import { computed, getCurrentInstance, provide, ref, useSlots } from 'vue'
import { useDragNodeHandler } from './composables/useDragNode'
import { useTree } from './composables/useTree'
Expand All @@ -36,10 +32,10 @@ const props = defineProps({
mutable({
children: TreeOptionsEnum.CHILDREN,
label: TreeOptionsEnum.LABEL,
disabled: TreeOptionsEnum.DISABLED,
value: TreeOptionsEnum.KEY,
isLeaf: undefined,
} as const),
disabled: TreeOptionsEnum.DISABLED,
class: TreeOptionsEnum.CLASS,
}),
},
draggable: {
type: Boolean,
Expand All @@ -49,50 +45,58 @@ const props = defineProps({
allowDrop: Function,
})

// const props = defineProps(treeProps)

const emit = defineEmits(treeEmits)

const emit = defineEmits({
...treeEmits,
'node-drag-start': (node: any, event: DragEvent) => Boolean(node && event),
'node-drag-enter': (draggingNode: any, dropNode: any, event: DragEvent) =>
Boolean(draggingNode && dropNode && event),
'node-drag-leave': (draggingNode: any, dropNode: any, event: DragEvent) =>
Boolean(draggingNode && dropNode && event),
'node-drag-over': (draggingNode: any, dropNode: any, event: DragEvent) =>
Boolean(draggingNode && dropNode && event),
'node-drag-end': (
draggingNode: any,
_dropNode: any,
_dropType: any,
event: DragEvent,
) => Boolean(draggingNode && event),
'node-drop': (
draggingNode: any,
_dropNode: any,
_dropType: any,
event: DragEvent,
) => Boolean(draggingNode && _dropNode && _dropType && event),
})
const slots = useSlots()

const treeNodeSize = computed(() => props.itemSize)

const el$ = ref<Nullable<HTMLElement>>(null)
const dropIndicator$ = ref<Nullable<HTMLElement>>(null)
const nodeRef = ref<Nullable<HTMLElement>>(null)
const el$ = ref<HTMLElement | null>(null)
const dropIndicator$ = ref<HTMLElement | null>(null)

provide(ROOT_TREE_INJECTION_KEY, {
ctx: {
emit,
emit: emit as any,
slots,
},
props,
instance: getCurrentInstance()!,
})
provide('RootTree', {
ctx: {
emit,
slots,
},
props,
instance: getCurrentInstance(),
} as any)
provide(formItemContextKey, undefined)

const { t } = useLocale()
const ns = useNamespace('tree')
const {
flattenTree,
isNotEmpty,
listRef,
toggleExpand,
isExpanded,
isIndeterminate,
isChecked,
isDisabled,
isCurrent,
isForceHiddenExpandIcon,
handleNodeClick,
handleNodeDrop,
handleNodeCheck,
// expose
toggleCheckbox,
getCurrentNode,
getCurrentKey,
Expand All @@ -109,7 +113,9 @@ const {
expandNode,
collapseNode,
setExpandedKeys,
} = useTree(props, emit)
scrollToNode,
scrollTo,
} = useTree(props, emit as any)

defineExpose({
toggleCheckbox,
Expand All @@ -128,12 +134,14 @@ defineExpose({
expandNode,
collapseNode,
setExpandedKeys,
scrollToNode,
scrollTo,
})

const { dragState } = useDragNodeHandler({
props,
ctx: {
emit,
emit: emit as any,
slots,
},
el$,
Expand All @@ -149,20 +157,21 @@ const { dragState } = useDragNodeHandler({
>
<fixed-size-list
v-if="isNotEmpty"
ref="listRef"
:class-name="ns.b('virtual-list')"
:data="flattenTree"
:total="flattenTree.length"
:height="height"
:item-size="treeNodeSize"
:perf-mode="perfMode"
:scrollbar-always-on="scrollbarAlwaysOn"
>
<template #default="{ data, index, style }">
<VirtualTreeNode
ref="nodeRef"
:key="data[index].key"
:style="style"
:node="data[index]"
:expanded="isExpanded(data[index])"
:expanded="data[index].expanded"
:show-checkbox="showCheckbox"
:checked="isChecked(data[index])"
:indeterminate="isIndeterminate(data[index])"
Expand All @@ -173,15 +182,17 @@ const { dragState } = useDragNodeHandler({
@click="handleNodeClick"
@toggle="toggleExpand"
@check="handleNodeCheck"
@drop="handleNodeDrop"
/>
</template>
</fixed-size-list>
<div v-else :class="ns.e('empty-block')">
<span :class="ns.e('empty-text')">{{
emptyText ?? t('el.tree.emptyText')
}}</span>
<slot name="empty">
<span :class="ns.e('empty-text')">{{
emptyText ?? t('el.tree.emptyText')
}}</span>
</slot>
</div>

<div
v-show="dragState.showDropIndicator"
ref="dropIndicator$"
Expand Down
127 changes: 65 additions & 62 deletions packages/component/src/virtual-tree/VirtualTreeNode.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import { CaretRight, Loading } from '@element-plus/icons-vue'
import { CaretRight } from '@element-plus/icons-vue'
import {
ElCheckbox,
ElIcon,
Expand All @@ -12,9 +12,8 @@ import {
treeNodeEmits,
treeNodeProps,
} from 'element-plus/es/components/tree-v2/src/virtual-tree'
// import ElNodeContent from 'element-plus/es/components/tree-v2/src/tree-node-content'
import ElNodeContent from 'element-plus/es/components/tree/src/tree-node-content.mjs'
import { computed, getCurrentInstance, inject, provide, ref } from 'vue'
import ElNodeContent from 'element-plus/es/components/tree-v2/src/tree-node-content.mjs'
import { computed, inject, ref } from 'vue'
import { dragEventsKey } from './composables/useDragNode'

defineOptions({
Expand All @@ -23,70 +22,96 @@ defineOptions({

const props = defineProps(treeNodeProps)
const emit = defineEmits(treeNodeEmits)

const tree = inject(ROOT_TREE_INJECTION_KEY)
const ns = useNamespace('tree')
const instance = getCurrentInstance()
const node$ = ref<Nullable<HTMLElement>>(null)
const node$ = ref<HTMLElement | null>(null)
const dragEvents = inject(dragEventsKey)

provide('NodeInstance', instance)
const indent = computed(() => tree?.props.indent ?? 16)
const icon = computed(() => tree?.props.icon ?? CaretRight)
const draggable = computed(() => Boolean((tree?.props as any)?.draggable))

const loading = ref(false)
const getNodeClass = (node: any) => {
const nodeClassFunc = (tree?.props as any)?.props?.class
if (!nodeClassFunc) return {}

const indent = computed(() => {
return tree?.props.indent ?? 16
})
const className =
typeof nodeClassFunc === 'function'
? nodeClassFunc(node.data, node)
: nodeClassFunc
return typeof className === 'string' ? { [className]: true } : className
}

const icon = computed(() => {
return tree?.props.icon ?? CaretRight
})
const handleClick = (event: MouseEvent) => {
emit('click', props.node, event)
}

const handleClick = (e: MouseEvent) => {
emit('click', props.node, e)
const handleDrop = (event: DragEvent) => {
emit('drop', props.node, event)
dragEvents?.treeNodeDrop({
event,
treeNode: {
node: props.node,
$el: node$.value,
},
})
}

const handleExpandIconClick = () => {
emit('toggle', props.node, instance)
emit('toggle', props.node)
}

const handleCheckChange = (value: CheckboxValueType) => {
emit('check', props.node, value)
}

const handleContextMenu = (event: Event) => {
if (tree?.instance?.vnode?.props?.onNodeContextmenu) {
if (tree?.instance?.vnode?.props?.['onNodeContextmenu']) {
event.stopPropagation()
event.preventDefault()
}
tree?.ctx.emit(NODE_CONTEXTMENU, event, props.node?.data, props.node)
}

const dragEvents = inject(dragEventsKey)

const handleDragStart = (event: DragEvent) => {
console.log('handleDragStart', event)
if (!tree.props.draggable) return
dragEvents.treeNodeDragStart({ event, treeNode: props })
if (!draggable.value) return
dragEvents?.treeNodeDragStart({
event,
treeNode: {
node: props.node,
$el: node$.value,
},
})
}

const handleDragOver = (event: DragEvent) => {
if (!draggable.value) return
event.preventDefault()
if (!tree.props.draggable) return
dragEvents.treeNodeDragOver({
dragEvents?.treeNodeDragOver({
event,
treeNode: { $el: node$.value, node: props.node },
treeNode: {
node: props.node,
$el: node$.value,
},
})
}

const handleDrop = (event: DragEvent) => {
const handleDragEnter = (event: DragEvent) => {
if (!draggable.value) return
event.preventDefault()
dragEvents?.treeNodeDragOver({
event,
treeNode: {
node: props.node,
$el: node$.value,
},
})
}

const handleDragEnd = (event: DragEvent) => {
if (!tree.props.draggable) return
dragEvents.treeNodeDragEnd(event)
if (!draggable.value) return
dragEvents?.treeNodeDragEnd(event)
}

defineExpose({
loading,
})
</script>

<template>
Expand All @@ -98,10 +123,11 @@ defineExpose({
ns.is('current', current),
ns.is('focusable', !disabled),
ns.is('checked', !disabled && checked),
getNodeClass(node),
]"
role="treeitem"
tabindex="-1"
:draggable="tree.props.draggable"
:draggable="draggable"
:aria-expanded="expanded"
:aria-disabled="disabled"
:aria-checked="checked"
Expand All @@ -110,6 +136,7 @@ defineExpose({
@contextmenu="handleContextMenu"
@dragstart.stop="handleDragStart"
@dragover.stop="handleDragOver"
@dragenter.stop="handleDragEnter"
@dragend.stop="handleDragEnd"
@drop.stop="handleDrop"
>
Expand All @@ -121,13 +148,7 @@ defineExpose({
}"
>
<el-icon
v-if="loading"
:class="[ns.be('node', 'loading-icon'), ns.is('loading')]"
>
<Loading />
</el-icon>
<el-icon
v-else-if="icon"
v-if="icon"
:class="[
ns.is('leaf', !!node?.isLeaf),
ns.is('hidden', hiddenExpandIcon),
Expand All @@ -148,27 +169,9 @@ defineExpose({
@change="handleCheckChange"
@click.stop
/>
<el-node-content
:node="node"
:render-content="tree?.props?.renderContent"
/>
<el-node-content :node="{ ...node, expanded }" />
</div>
</div>
</template>

<style lang="scss" scoped>
$namespace: 'el';

.#{$namespace}-tree-node {
.el-tree-node__loading-icon {
margin-right: 0;
width: 24px;
height: 24px;
line-height: 24px;
text-align: center;
}
.el-tree-node__content {
position: relative;
}
}
</style>
<style scoped lang="scss"></style>
Loading
Loading