diff --git a/examples/docs/en-US/tree.md b/examples/docs/en-US/tree.md index ad61a76b6..d0148f27d 100644 --- a/examples/docs/en-US/tree.md +++ b/examples/docs/en-US/tree.md @@ -996,6 +996,103 @@ Only one node among the same level can be expanded at one time. ``` ::: +### Draggable + +Tree nodes can be drag and drop. + +:::demo +```html + + + + +``` +::: + ### Attributes | Attribute | Description | Type | Accepted Values | Default | | --------------------- | ---------------------------------------- | --------------------------- | --------------- | ------- | @@ -1018,6 +1115,9 @@ Only one node among the same level can be expanded at one time. | accordion | whether only one node among the same level can be expanded at one time | boolean | — | false | | indent | horizontal indentation of nodes in adjacent levels in pixels | number | — | 16 | | lazy | whether to lazy load leaf node, used with `load` attribute | boolean | — | false | +| draggable | whether enable tree nodes drag and drop | boolean | — | false | +| allow-drag | this function will be executed before dragging a node. if return `false`, the node can not be drag. | Function(node) | — | — | +| allow-drop | this function will be executed when dragging enter a node. if return `false`, dragging node can not be drop at the node. | Function(draggingNode, dropNode) | — | — | ### props | Attribute | Description | Type | Accepted Values | Default | @@ -1060,6 +1160,12 @@ Only one node among the same level can be expanded at one time. | current-change | triggers when current node changes | two parameters: node object corresponding to the current node, `node` property of TreeNode | | node-expand | triggers when current node open | three parameters: node object corresponding to the node opened, `node` property of TreeNode, TreeNode itself | | node-collapse | triggers when current node close | three parameters: node object corresponding to the node closed, `node` property of TreeNode, TreeNode itself | +| node-drag-start | triggers when dragging start | two parameters: node object corresponding to the dragging node、event. | +| node-drag-enter | triggers when dragging node enters a node | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging enter node、event. | +| node-drag-leave | triggers when dragging node leaves a node | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging leave node、event. | +| node-drag-over | triggers when dragging over a node(like browser mouseover event) | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging over node、event. | +| node-drag-end | triggers when dragging end | four parameters: node object corresponding to the dragging node、node object corresponding to the dragging end node、node drop type (before、after、inner)、event. | +| node-drop | triggers after dragging and dropping onto a node | four parameters: node object corresponding to the dragging node、node object corresponding to the dragging end node、node drop type (before、after、inner)、event. | ### Scoped slot | name | Description | diff --git a/examples/docs/es/tree.md b/examples/docs/es/tree.md index f4ae49a72..480f8765b 100644 --- a/examples/docs/es/tree.md +++ b/examples/docs/es/tree.md @@ -996,6 +996,103 @@ Solo puede ser expandido un nodo del mismo nivel a la vez. ``` ::: +### Draggable + +Tree nodes can be drag and drop. + +:::demo +```html + + + + +``` +::: + ### Atributos | Atributo | Descripción | Tipo | Valores aceptados | Por defecto | | --------------------- | ---------------------------------------- | --------------------------------- | ----------------- | ----------- | @@ -1017,6 +1114,9 @@ Solo puede ser expandido un nodo del mismo nivel a la vez. | filter-node-method | Esta función se ejecutará en cada nodo cuando se use el método filtrtar, si devuelve `false` el nodo se oculta | Function(value, data, node) | — | — | | accordion | Si solo un nodo de cada nivel puede expandirse a la vez | boolean | — | false | | indent | Indentación horizontal de los nodos en niveles adyacentes, en pixeles | number | — | 16 | +| draggable | whether enable tree nodes drag and drop | boolean | — | false | +| allow-drag | this function will be executed before dragging a node. if return `false`, the node can not be drag. | Function(node) | — | — | +| allow-drop | this function will be executed when dragging enter a node. if return `false`, dragging node can not be drop at the node. | Function(draggingNode, dropNode) | — | — | ### props | Atributo | Descripción | Tipo | Valores aceptados | Por defecto | @@ -1058,6 +1158,12 @@ Solo puede ser expandido un nodo del mismo nivel a la vez. | current-change | cambia cuando el nodo actual cambia | dos parámetros: objeto nodo que se corresponde al nodo actual y propiedad `node` del TreeNode | | node-expand | se lanza cuando el nodo actual se abre | tres parámetros: el objeto del nodo abierto, propiedad `node` de TreeNode y el TreeNode en si | | node-collapse | se lanza cuando el nodo actual se cierra | tres parámetros: el objeto del nodo cerrado, propiedad `node` de TreeNode y el TreeNode en si | +| node-drag-start | triggers when dragging start | two parameters: node object corresponding to the dragging node、event. | +| node-drag-enter | triggers when dragging node enters a node | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging enter node、event. | +| node-drag-leave | triggers when dragging node leaves a node | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging leave node、event. | +| node-drag-over | triggers when dragging over a node(like browser mouseover event) | three parameters: node object corresponding to the dragging node、node object corresponding to the dragging over node、event. | +| node-drag-end | triggers when dragging end | four parameters: node object corresponding to the dragging node、node object corresponding to the dragging end node、node drop type (before、after、inner)、event. | +| node-drop | triggers after dragging and dropping onto a node | four parameters: node object corresponding to the dragging node、node object corresponding to the dragging end node、node drop type (before、after、inner)、event. | ### Scoped slot | name | Description | diff --git a/examples/docs/zh-CN/tree.md b/examples/docs/zh-CN/tree.md index 14c3e040c..6d84a81d3 100644 --- a/examples/docs/zh-CN/tree.md +++ b/examples/docs/zh-CN/tree.md @@ -1065,7 +1065,7 @@ ### 可拖拽节点 -通过draggable属性可让节点变为可拖拽,节点只能放到相同level节点旁边。 +通过 draggable 属性可让节点变为可拖拽。 :::demo ```html @@ -1076,7 +1076,9 @@ @node-drag-start="handleDragStart" @node-drag-enter="handleDragEnter" @node-drag-leave="handleDragLeave" + @node-drag-over="handleDragOver" @node-drag-end="handleDragEnd" + @node-drop="handleDrop" draggable :allow-drop="allowDrop" :allow-drag="allowDrag"> @@ -1141,24 +1143,28 @@ handleDragStart(node, ev) { console.log('drag start', node); }, - handleDragEnter(node, ev) { - console.log('tree drag enter: ', node.label); + handleDragEnter(draggingNode, dropNode, ev) { + console.log('tree drag enter: ', dropNode.label); }, - handleDragLeave(node, ev) { - console.log('tree drag leave: ', node.label); + handleDragLeave(draggingNode, dropNode, ev) { + console.log('tree drag leave: ', dropNode.label); }, - handleDragEnd(from, target, position, ev) { - console.log('tree drag end: ', target.label); - if (position !== null) { - console.log(`target position: parent node: ${position.parent.label}, index: ${position.index}`); - } + handleDragOver(draggingNode, dropNode, ev) { + console.log('tree drag over: ', dropNode.label); }, - allowDrop(from, target) { - return target.data.label !== '二级 3-1'; + handleDragEnd(draggingNode, dropNode, dropType, ev) { + console.log('tree drag end: ', dropNode.label, dropType); }, - allowDrag(node) { - return node.data.label.indexOf('三级 3-1-1') === -1; + handleDrop(draggingNode, dropNode, dropType, ev) { + console.log('tree drop: ', dropNode.label, dropType); }, + allowDrop(draggingNode, dropNode) { + return dropNode.data.label !== '二级 3-1'; + }, + allowDrag(draggingNode) { + return draggingNode.data.label.indexOf('三级 3-1-1') === -1; + } + } }; ``` @@ -1187,8 +1193,8 @@ | indent | 相邻级节点间的水平缩进,单位为像素 | number | — | 16 | | lazy | 是否懒加载子节点,需与 load 方法结合使用 | boolean | — | false | | draggable | 是否开启拖拽节点功能 | boolean | — | false | -| allow-drag | 判断节点能否被拖拽 | Function(Node) | — | — | -| allow-drop | 拖拽时判定位置能否被放置 | Function(fromNode, toNode) | — | — | +| allow-drag | 判断节点能否被拖拽 | Function(node) | — | — | +| allow-drop | 拖拽时判定位置能否被放置 | Function(draggingNode, dropNode) | — | — | ### props | 参数 | 说明 | 类型 | 可选值 | 默认值 | @@ -1233,10 +1239,12 @@ | current-change | 当前选中节点变化时触发的事件 | 共两个参数,依次为:当前节点的数据,当前节点的 Node 对象 | | node-expand | 节点被展开时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 | | node-collapse | 节点被关闭时触发的事件 | 共三个参数,依次为:传递给 `data` 属性的数组中该节点所对应的对象、节点对应的 Node、节点组件本身。 | -| node-drag-start| 节点开始拖拽时触发的事件 | 共两个参数,依次为:被拖拽节点对应的 Node、Vue传来的drag event。 | -| node-drag-enter| 拖拽进入其他节点时触发的事件 | 共两个参数,依次为:所进入节点对应的 Node、Vue传来的drag event。 | -| node-drag-leave| 拖拽离开某个节点时触发的事件 | 共两个参数,依次为:所离开节点对应的 Node、Vue传来的drag event。(注意:上个节点的leave事件有可能在下个节点enter之后执行) | -| node-drag-end | 拖拽结束时触发的事件 | 共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后指向的节点、被拖拽节点的放置位置{ parent: 位置的父节点, index: 在父节点中的序号 }、Vue传来的drag event。| +| node-drag-start | 节点开始拖拽时触发的事件 | 共两个参数,依次为:被拖拽节点对应的 Node、event。 | +| node-drag-enter | 拖拽进入其他节点时触发的事件 | 共三个参数,依次为:被拖拽节点对应的 Node、所进入节点对应的 Node、event。| +| node-drag-leave | 拖拽离开某个节点时触发的事件 | 共三个参数,依次为:被拖拽节点对应的 Node、所离开节点对应的 Node、event。 | +| node-drag-over | 在拖拽节点时触发的事件(类似浏览器的 mouseover 事件) | 共三个参数,依次为:被拖拽节点对应的 Node、当前进入节点对应的 Node、event。 | +| node-drag-end | 拖拽结束时(可能未成功)触发的事件 | 共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点(可能为空)、被拖拽节点的放置位置(before、after、inner)、event。| +| node-drop | 拖拽成功完成时触发的事件 | 共四个参数,依次为:被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置(before、after、inner)、event。| ### Scoped slot | name | 说明 | diff --git a/packages/theme-chalk/src/tree.scss b/packages/theme-chalk/src/tree.scss index 8893c0d69..ce77c7bcf 100644 --- a/packages/theme-chalk/src/tree.scss +++ b/packages/theme-chalk/src/tree.scss @@ -23,9 +23,10 @@ color: mix($--color-primary, rgb(158, 68, 0), 50%); } - @include e(drag-indicator) { + @include e(drop-indicator) { position: absolute; - width: 100%; + left: 0; + right: 0; height: 1px; background-color: $--color-primary; } @@ -39,6 +40,14 @@ background-color: $--tree-node-hover-color; } } + + @include when(drop-inner) { + > .el-tree-node__content .el-tree-node__label { + background-color: $--color-primary; + color: #fff; + } + } + @include e(content) { display: flex; align-items: center; @@ -55,14 +64,15 @@ background-color: $--tree-node-hover-color; } - .el-tree.dragging & { + .el-tree.is-dragging & { cursor: move; & * { pointer-events: none; } } - .el-tree.dragging.drop-not-allow & { + + .el-tree.is-dragging.is-drop-not-allow & { cursor: not-allowed; } } diff --git a/packages/tree/src/model/node.js b/packages/tree/src/model/node.js index 13023a04e..3e81c0bae 100644 --- a/packages/tree/src/model/node.js +++ b/packages/tree/src/model/node.js @@ -168,12 +168,58 @@ export default class Node { return getPropertyFromData(this, 'disabled'); } + get nextSibling() { + const parent = this.parent; + if (parent) { + const index = parent.childNodes.indexOf(this); + if (index > -1) { + return parent.childNodes[index + 1]; + } + } + return null; + } + + get previousSibling() { + const parent = this.parent; + if (parent) { + const index = parent.childNodes.indexOf(this); + if (index > -1) { + return index > 0 ? parent.childNodes[index - 1] : null; + } + } + return null; + } + + contains(target, deep = true) { + const walk = function(parent) { + const children = parent.childNodes || []; + let result = false; + for (let i = 0, j = children.length; i < j; i++) { + const child = children[i]; + if (child === target || (deep && walk(child))) { + result = true; + break; + } + } + return result; + }; + + return walk(this); + } + + remove() { + const parent = this.parent; + if (parent) { + parent.removeChild(this); + } + } + insertChild(child, index, batch) { if (!child) throw new Error('insertChild error: child is required.'); if (!(child instanceof Node)) { if (!batch) { - const children = this.getChildren() || []; + const children = this.getChildren(true); if (children.indexOf(child.data) === -1) { if (typeof index === 'undefined' || index < 0) { children.push(child.data); @@ -357,7 +403,7 @@ export default class Node { } } - getChildren() { // this is data + getChildren(forceInit = false) { // this is data if (this.level === 0) return this.data; const data = this.data; if (!data) return null; @@ -372,6 +418,10 @@ export default class Node { data[children] = null; } + if (forceInit && !data[children]) { + data[children] = []; + } + return data[children]; } diff --git a/packages/tree/src/model/tree-store.js b/packages/tree/src/model/tree-store.js index f3699e8d1..99a453e70 100644 --- a/packages/tree/src/model/tree-store.js +++ b/packages/tree/src/model/tree-store.js @@ -6,11 +6,6 @@ export default class TreeStore { this.currentNode = null; this.currentNodeKey = null; - this.dragSourceNode = null; - this.dragTargetNode = null; - this.dragTargetDom = null; - this.allowDrop = true; - for (let option in options) { if (options.hasOwnProperty(option)) { this[option] = options[option]; diff --git a/packages/tree/src/model/util.js b/packages/tree/src/model/util.js index 50cc8f942..9f09ea28c 100644 --- a/packages/tree/src/model/util.js +++ b/packages/tree/src/model/util.js @@ -14,3 +14,14 @@ export const getNodeKey = function(key, data) { if (!key) return data[NODE_KEY]; return data[key]; }; + +export const findNearestComponent = (element, componentName) => { + let target = element; + while (target && target.tagName !== 'BODY') { + if (target.__vue__ && target.__vue__.$options.name === componentName) { + return target.__vue__; + } + target = target.parentNode; + } + return null; +}; diff --git a/packages/tree/src/tree-node.vue b/packages/tree/src/tree-node.vue index 04e6f4ab6..03708b09b 100644 --- a/packages/tree/src/tree-node.vue +++ b/packages/tree/src/tree-node.vue @@ -18,8 +18,6 @@ :aria-checked="node.checked" :draggable="tree.draggable" @dragstart.stop="handleDragStart" - @dragenter.stop="handleDragEnter" - @dragleave.stop="handleDragLeave" @dragover.stop="handleDragOver" @dragend.stop="handleDragEnd" @drop.stop="handleDrop" @@ -114,7 +112,7 @@ ? parent.renderContent.call(parent._renderProxy, h, { _self: tree.$vnode.context, node, data, store }) : tree.$scopedSlots.default ? tree.$scopedSlots.default({ node, data }) - : { this.node.label } + : { node.label } ); } } @@ -209,90 +207,21 @@ this.tree.$emit('node-expand', nodeData, node, instance); }, - handleDragStart(ev) { - if (typeof this.tree.allowDrag === 'function' && !this.tree.allowDrag(this.node)) { - ev.preventDefault(); - return false; - } - ev.dataTransfer.effectAllowed = 'move'; - ev.dataTransfer.setData('text/plain', this.node.label); - this.node.store.dragSourceNode = this.node; - this.node.store.dragFromDom = this.$refs.node; - this.node.store.allowDrop = true; - this.tree.$emit('node-drag-start', this.node, ev); + handleDragStart(event) { + this.tree.$emit('tree-node-drag-start', event, this); }, - handleDragEnter(ev) { - ev.preventDefault(); - const store = this.node.store; - const from = store.dragSourceNode; - let node = this.node; - let dom = this.$refs.node; - - if (!from) return; - - while (node.level > from.level && node.level > 1) { - node = node.parent - dom = this.$parent.$refs.node; - } - store.dragTargetNode = node; - store.dragTargetDom = dom; - - if (!this.tree.dropAt) { - ev.dataTransfer.dropEffect = 'none'; - store.allowDrop = false; - } else { - ev.dataTransfer.dropEffect = 'move'; - store.allowDrop = true; - } - - this.tree.$emit('node-drag-enter', this.node, ev); + handleDragOver(event) { + this.tree.$emit('tree-node-drag-over', event, this); + event.preventDefault(); }, - handleDragLeave(ev) { - ev.preventDefault(); - if (!this.node.store.dragSourceNode) return; - this.tree.$emit('node-drag-leave', this.node, ev); + handleDrop(event) { + event.preventDefault(); }, - handleDragOver(ev) { - ev.dataTransfer.dropEffect = this.node.store.allowDrop ? 'move' : 'none'; - ev.preventDefault(); - }, - - handleDrop(ev) { - ev.preventDefault(); - }, - - handleDragEnd(ev) { - const from = this.node.store.dragSourceNode; - const target = this.node.store.dragTargetNode; - let position = this.tree.dropAt; - - if (!from) return; - - if (typeof this.tree.allowDrop === 'function' && !this.tree.allowDrop(from, target)) { - position = null; - } - ev.preventDefault(); - ev.dataTransfer.dropEffect = 'move'; - - if (target && from && from !== target && position) { - const index = from.parent.childNodes.indexOf(from); - from.parent.childNodes.splice(index, 1); - if (from.parent.childNodes.length === 0) { - from.parent.isLeaf = true; - } - position.parent.childNodes.splice(position.index, 0, from); - from.parent = position.parent; - from.parent.isLeaf = false; - } - this.tree.$emit('node-drag-end', from, target, position, ev); - this.node.store.dragTargetNode = null; - this.node.store.dragSourceNode = null; - this.node.store.dragTargetDom = null; - - return false; + handleDragEnd(event) { + this.tree.$emit('tree-node-drag-end', event, this); } }, diff --git a/packages/tree/src/tree.vue b/packages/tree/src/tree.vue index 1346de071..9a6f49e8f 100644 --- a/packages/tree/src/tree.vue +++ b/packages/tree/src/tree.vue @@ -3,8 +3,9 @@ class="el-tree" :class="{ 'el-tree--highlight-current': highlightCurrent, - dragging: !!store.dragSourceNode, - 'drop-not-allow': !store.allowDrop + 'is-dragging': !!dragState.draggingNode, + 'is-drop-not-allow': !dragState.allowDrop, + 'is-drop-inner': dragState.dropType === 'inner' }" role="tree" > @@ -21,20 +22,20 @@ {{ emptyText }}
+ v-show="dragState.showDropIndicator" + class="el-tree__drop-indicator" + ref="dropIndicator">