diff --git a/src/tree/src/Tree.tsx b/src/tree/src/Tree.tsx index fb412135d..8ed48bf3d 100644 --- a/src/tree/src/Tree.tsx +++ b/src/tree/src/Tree.tsx @@ -132,6 +132,7 @@ const treeProps = { onDragleave: [Function, Array] as PropType void>>, onDragend: [Function, Array] as PropType void>>, onDragstart: [Function, Array] as PropType void>>, + onDragover: [Function, Array] as PropType void>>, onDrop: [Function, Array] as PropType void>>, // eslint-disable-next-line vue/prop-name-casing 'onUpdate:expandedKeys': [Function, Array] as PropType< @@ -257,13 +258,18 @@ export default defineComponent({ treeMateRef.value.getFlattenedNodes(mergedExpandedKeysRef.value) ) - const draggingNodeKeyRef = ref(null) - const draggingNodeRef = ref(null) - const droppingNodeKeyRef = ref(null) const expandTimerIdRef = ref(undefined) const highlightKeysRef = ref([]) const loadingKeysRef = ref([]) + const draggingNodeRef = ref(null) + const droppingNodeRef = ref(null) + const droppingNodeParentRef = computed(() => { + const { value: droppingNode } = droppingNodeRef + if (droppingNode) return droppingNode.parent + return null + }) + watch(toRef(props, 'data'), () => { loadingKeysRef.value = [] expandTimerIdRef.value = undefined @@ -420,14 +426,17 @@ export default defineComponent({ const { onDragstart } = props if (onDragstart) call(onDragstart, info) } + function doDragOver (info: DragInfo): void { + const { onDragover } = props + if (onDragover) call(onDragover, info) + } function doDrop (info: DropInfo): void { const { onDrop } = props if (onDrop) call(onDrop, info) } function resetDragStatus (): void { - draggingNodeKeyRef.value = null draggingNodeRef.value = null - droppingNodeKeyRef.value = null + droppingNodeRef.value = null } function handleCheck (node: TmNode, checked: boolean): void { if (props.disabled || node.disabled) return @@ -482,18 +491,22 @@ export default defineComponent({ } } } + // Dnd function handleDragEnter ({ event, node }: InternalDragInfo): void { // node should be a tmNode if (!props.draggable || props.disabled || node.disabled) return doDragEnter({ event, node: node.rawNode }) if (!props.expandOnDragenter) return - droppingNodeKeyRef.value = node.key - if (node.key === draggingNodeKeyRef.value) return + droppingNodeRef.value = node + const { value: draggingNode } = draggingNodeRef + if (draggingNode && node.key === draggingNode.key) return if (!mergedExpandedKeysRef.value.includes(node.key) && !node.isLeaf) { window.clearTimeout(expandTimerIdRef.value) const expand = (): void => { + const { value: droppingNode } = droppingNodeRef if ( - droppingNodeKeyRef.value === node.key && + droppingNode && + droppingNode.key === node.key && !mergedExpandedKeysRef.value.includes(node.key) ) { doExpandedKeysChange(mergedExpandedKeysRef.value.concat(node.key)) @@ -522,7 +535,7 @@ export default defineComponent({ } function handleDragLeave ({ event, node }: InternalDragInfo): void { if (!props.draggable || props.disabled || node.disabled) return - droppingNodeKeyRef.value = null + droppingNodeRef.value = null doDragLeave({ event, node: node.rawNode }) } function handleDragEnd ({ event, node }: InternalDragInfo): void { @@ -532,10 +545,12 @@ export default defineComponent({ } function handleDragStart ({ event, node }: InternalDragInfo): void { if (!props.draggable || props.disabled || node.disabled) return - draggingNodeKeyRef.value = node.key - draggingNodeRef.value = node.rawNode + draggingNodeRef.value = node doDragStart({ event, node: node.rawNode }) } + function handleDragOver ({ event, node }: InternalDragInfo): void { + doDragOver({ event, node: node.rawNode }) + } function handleDrop ({ event, node, dropPosition }: InternalDropInfo): void { if ( !props.draggable || @@ -548,7 +563,7 @@ export default defineComponent({ doDrop({ event, node: node.rawNode, - dragNode: draggingNodeRef.value, + dragNode: draggingNodeRef.value.rawNode, dropPosition }) resetDragStatus() @@ -571,12 +586,17 @@ export default defineComponent({ onLoadRef: toRef(props, 'onLoad'), draggableRef: toRef(props, 'draggable'), checkableRef: toRef(props, 'checkable'), + blockLineRef: toRef(props, 'blockLine'), + droppingNodeRef, + droppingNodeParentRef, + draggingNodeRef, handleSwitcherClick, handleDragEnd, handleDragEnter, handleDragLeave, handleDragStart, handleDrop, + handleDragOver, handleSelect, handleCheck }) diff --git a/src/tree/src/TreeNode.tsx b/src/tree/src/TreeNode.tsx index eb32c9971..cac8942f3 100644 --- a/src/tree/src/TreeNode.tsx +++ b/src/tree/src/TreeNode.tsx @@ -1,4 +1,4 @@ -import { h, inject, computed, defineComponent, PropType } from 'vue' +import { h, inject, computed, defineComponent, PropType, ref } from 'vue' import { useMemo } from 'vooks' import NTreeNodeSwitcher from './TreeNodeSwitcher' import NTreeNodeCheckbox from './TreeNodeCheckbox' @@ -45,20 +45,47 @@ const TreeNode = defineComponent({ function handleContentClick (e: MouseEvent): void { NTree.handleSelect(props.tmNode) } - function handleDragEnter (e: DragEvent): void { - NTree.handleDragEnter({ - event: e, - node: props.tmNode - }) + + function handleCheck (checked: boolean): void { + NTree.handleCheck(props.tmNode, checked) } + // Dnd + const pendingPositionRef = ref<'top' | 'center' | 'bottom' | null>(null) function handleDragStart (e: DragEvent): void { NTree.handleDragStart({ event: e, node: props.tmNode }) } - function handleDragLeave (e: DragEvent): void { - NTree.handleDragLeave({ + function handleDragEnter (e: DragEvent): void { + if ( + e.currentTarget && + e.relatedTarget && + (e.currentTarget as HTMLElement).contains( + e.relatedTarget as HTMLElement + ) + ) { + return + } + NTree.handleDragEnter({ + event: e, + node: props.tmNode + }) + } + function handleDragOver (e: DragEvent): void { + e.preventDefault() + const el = e.currentTarget as HTMLElement + const elOffsetHeight = el.offsetHeight // dangerous + const elClientTop = el.getBoundingClientRect().top + const eventOffsetY = e.clientY - elClientTop + if (eventOffsetY <= 8) { + pendingPositionRef.value = 'top' + } else if (eventOffsetY >= elOffsetHeight - 8) { + pendingPositionRef.value = 'bottom' + } else { + pendingPositionRef.value = 'center' + } + NTree.handleDragOver({ event: e, node: props.tmNode }) @@ -69,18 +96,35 @@ const TreeNode = defineComponent({ node: props.tmNode }) } - function handleDrop ( - e: DragEvent, - dropPosition: 'bottom' | 'center' | 'top' - ): void { - NTree.handleDrop({ + function handleDragLeave (e: DragEvent): void { + if ( + e.currentTarget && + e.relatedTarget && + (e.currentTarget as HTMLElement).contains( + e.relatedTarget as HTMLElement + ) + ) { + return + } + NTree.handleDragLeave({ event: e, - node: props.tmNode, - dropPosition + node: props.tmNode }) } - function handleCheck (checked: boolean): void { - NTree.handleCheck(props.tmNode, checked) + function handleDrop (e: DragEvent): void { + e.preventDefault() + if (pendingPositionRef.value !== null) { + const dropPosition = ({ + top: 'top', + bottom: 'bottom', + center: 'center' + } as const)[pendingPositionRef.value] + NTree.handleDrop({ + event: e, + node: props.tmNode, + dropPosition + }) + } } return { loading: useMemo(() => @@ -104,10 +148,13 @@ const TreeNode = defineComponent({ icon: computed(() => props.tmNode.rawNode.icon), checkable: NTree.checkableRef, draggable: NTree.draggableRef, + blockLine: NTree.blockLineRef, + pendingPosition: pendingPositionRef, handleCheck, handleDrop, handleDragStart, handleDragEnter, + handleDragOver, handleDragEnd, handleDragLeave, handleContentClick, @@ -115,51 +162,75 @@ const TreeNode = defineComponent({ } }, render () { - const { tmNode, clsPrefix, checkable, selected, highlight } = this + const { + tmNode, + clsPrefix, + checkable, + selected, + highlight, + draggable, + blockLine + } = this + // drag start not inside + // it need to be append to node itself, not wrapper + const dragEventHandlers = draggable + ? { + onDragenter: this.handleDragEnter, + onDragleave: this.handleDragLeave, + onDragend: this.handleDragEnd, + onDrop: this.handleDrop + } + : undefined return ( -
  • - {Array.apply(null, { length: tmNode.level } as any).map(() => ( -
    - ))} - - {checkable ? ( - - ) : null} - - {{ - default: () => tmNode.rawNode.label - }} - - {this.icon ? this.icon() : null} -
  • + {Array.apply(null, { length: tmNode.level } as any).map(() => ( +
    + ))} + + {checkable ? ( + + ) : null} + + {{ + default: () => tmNode.rawNode.label + }} + + {this.icon ? this.icon() : null} + + ) } }) diff --git a/src/tree/src/TreeNodeContent.tsx b/src/tree/src/TreeNodeContent.tsx index d375a1e4d..11f5686fe 100644 --- a/src/tree/src/TreeNodeContent.tsx +++ b/src/tree/src/TreeNodeContent.tsx @@ -12,159 +12,31 @@ export default defineComponent({ default: false }, onClick: Function as PropType<(e: MouseEvent) => void>, - onDragstart: Function as PropType<(e: DragEvent) => void>, - onDragend: Function as PropType<(e: DragEvent) => void>, - onDragenter: Function as PropType<(e: DragEvent) => void>, - onDragover: Function as PropType<(e: DragEvent) => void>, - onDragleave: Function as PropType<(e: DragEvent) => void>, - onDrop: Function as PropType< - (e: DragEvent, dropPosition: 'bottom' | 'center' | 'top') => void - > + onDragstart: Function as PropType<(e: DragEvent) => void> }, setup (props) { - const pendingRef = ref(false) - const pendingPositionRef = ref<'top' | 'center' | 'bottom' | null>(null) const selfRef = ref(null) function doClick (e: MouseEvent): void { const { onClick } = props if (onClick) onClick(e) } - function doDragStart (e: DragEvent): void { - const { onDragstart } = props - if (onDragstart) onDragstart(e) - } - function doDragEnter (e: DragEvent): void { - const { onDragenter } = props - if (onDragenter) onDragenter(e) - } - function doDragEnd (e: DragEvent): void { - const { onDragend } = props - if (onDragend) onDragend(e) - } - function doDragLeave (e: DragEvent): void { - const { onDragleave } = props - if (onDragleave) onDragleave(e) - } - // function doDragOver (e: DragEvent) { - // const { onDragOver } = props - // if (onDragOver) onDragOver(e) - // } - function doDrop ( - e: DragEvent, - dropPosition: 'top' | 'bottom' | 'center' - ): void { - const { onDrop } = props - if (onDrop) onDrop(e, dropPosition) - } function handleClick (e: MouseEvent): void { doClick(e) } - function handleContentDragStart (e: DragEvent): void { - doDragStart(e) - } - function handleContentDragEnter (e: DragEvent): void { - if ( - e.currentTarget && - e.relatedTarget && - (e.currentTarget as HTMLElement).contains( - e.relatedTarget as HTMLElement - ) - ) { - return - } - doDragEnter(e) - } - function handleDragOverContent (e: DragEvent): void { - e.preventDefault() - const el = selfRef.value as HTMLElement - pendingRef.value = true - const elOffsetHeight = el.offsetHeight - const elClientTop = el.getBoundingClientRect().top - const eventOffsetY = e.clientY - elClientTop - if (eventOffsetY <= 8) { - pendingPositionRef.value = 'top' - } else if (eventOffsetY >= elOffsetHeight - 8) { - pendingPositionRef.value = 'bottom' - } else { - pendingPositionRef.value = 'center' - } - } - function handleContentDragEnd (e: DragEvent): void { - doDragEnd(e) - } - function handleContentDragLeave (e: DragEvent): void { - if ( - e.currentTarget && - e.relatedTarget && - (e.currentTarget as HTMLElement).contains( - e.relatedTarget as HTMLElement - ) - ) { - return - } - pendingRef.value = false - doDragLeave(e) - } - function handleContentDrop (e: DragEvent): void { - e.preventDefault() - pendingRef.value = false - if (pendingPositionRef.value !== null) { - const dropPosition = { - top: 'top', - bottom: 'bottom', - center: 'center' - }[pendingPositionRef.value] - doDrop(e, dropPosition as 'top' | 'bottom' | 'center') - } - } return { selfRef, - pending: pendingRef, - pendingPosition: pendingPositionRef, - handleContentDragLeave, - handleContentDragStart, - handleDragOverContent, - handleContentDragEnd, - handleContentDragEnter, - handleContentDrop, handleClick } }, render () { - const { - clsPrefix, - pending, - pendingPosition, - handleContentDragLeave, - handleContentDragStart, - handleDragOverContent, - handleContentDragEnd, - handleContentDragEnter, - handleContentDrop, - handleClick - } = this + const { clsPrefix, handleClick, onDragstart } = this return (
    {this.$slots}
    diff --git a/src/tree/src/interface.ts b/src/tree/src/interface.ts index 26468164f..9a015e7f9 100644 --- a/src/tree/src/interface.ts +++ b/src/tree/src/interface.ts @@ -50,6 +50,10 @@ export interface TreeInjection { checkableRef: Ref mergedThemeRef: Ref> onLoadRef: Ref<((node: TreeOption) => Promise) | undefined> + blockLineRef: Ref + draggingNodeRef: Ref + droppingNodeRef: Ref + droppingNodeParentRef: Ref handleSwitcherClick: (node: TreeNode) => void handleSelect: (node: TreeNode) => void handleCheck: (node: TreeNode, checked: boolean) => void @@ -57,6 +61,7 @@ export interface TreeInjection { handleDragEnter: (info: InternalDragInfo) => void handleDragLeave: (info: InternalDragInfo) => void handleDragEnd: (info: InternalDragInfo) => void + handleDragOver: (info: InternalDragInfo) => void handleDrop: (info: InternalDropInfo) => void } diff --git a/src/tree/src/render-drop-mark.tsx b/src/tree/src/render-drop-mark.tsx new file mode 100644 index 000000000..9ad72a1d8 --- /dev/null +++ b/src/tree/src/render-drop-mark.tsx @@ -0,0 +1,21 @@ +import { CSSProperties, h, VNode } from 'vue' + +export function renderDropMark (position: 'top' | 'center' | 'bottom'): VNode { + const style: CSSProperties = { + position: 'absolute', + boxSizing: 'border-box', + right: 0, + left: 0 + } + if (position === 'center') { + style.top = 0 + style.bottom = 0 + style.borderRadius = 'inherit' + style.border = '2px solid black' + } else { + style[position] = 0 + style.height = '2px' + style.backgroundColor = 'black' + } + return
    +} diff --git a/src/tree/src/styles/index.cssr.ts b/src/tree/src/styles/index.cssr.ts index a71c34792..bb545c02f 100644 --- a/src/tree/src/styles/index.cssr.ts +++ b/src/tree/src/styles/index.cssr.ts @@ -8,21 +8,7 @@ const nodeStateStyle = [ }), c('&:active', { backgroundColor: 'var(--node-color-pressed)' - }), - cM('pending', [ - c('&:hover', { - backgroundColor: '#0000' - }), - cM('pending-bottom', { - borderBottom: '3px solid var(--node-color-hover)' - }), - cM('pending-top', { - borderTop: '3px solid var(--node-color-hover)' - }), - cM('pending-body', { - backgroundColor: 'var(--node-color-hover)' - }) - ]) + }) ] // vars: @@ -67,8 +53,8 @@ export default cB('tree', { }) ]) ]), + cB('tree-node-wrapper', 'padding: 3px 0;'), cB('tree-node', ` - margin: 6px 0 0 0; display: flex; border-radius: var(--node-border-radius); transition: background-color .3s var(--bezier); @@ -196,20 +182,6 @@ export default cB('tree', { }), c('&:active', { backgroundColor: 'var(--node-color-pressed)' - }), - cM('pending', [ - c('&:hover', { - backgroundColor: '#0000' - }), - cM('pending-bottom', { - borderBottom: '3px solid var(--node-color-hover)' - }), - cM('pending-top', { - borderTop: '3px solid var(--node-color-hover)' - }), - cM('pending-body', { - backgroundColor: 'var(--node-color-hover)' - }) - ]) + }) ]) ])