refactor(tree): wip

This commit is contained in:
07akioni 2020-02-25 18:47:41 +08:00
parent a4cc14c9ae
commit 1902e394fc
10 changed files with 254 additions and 196 deletions

View File

@ -1,51 +0,0 @@
export default {
props: {
transitionDisabled: {
type: Boolean,
default: false
}
},
methods: {
handleBeforeLeave () {
this.$el.style.maxHeight = this.$el.offsetHeight + 'px'
this.$el.style.height = this.$el.offsetHeight + 'px'
this.$el.getBoundingClientRect()
},
handleLeave () {
// debugger
this.$el.style.maxHeight = 0
this.$el.getBoundingClientRect()
},
handleEnter () {
this.$nextTick().then(() => {
this.$el.style.height = this.$el.offsetHeight + 'px'
this.$el.style.maxHeight = 0
this.$el.getBoundingClientRect()
this.$el.style.maxHeight = this.$el.style.height
})
},
handleAfterEnter () {
this.$el.style.height = null
this.$el.style.maxHeight = null
}
},
beforeDestroy () {
if (this.transitionDisabled) {
const parent = this.$el.parentElement
if (parent) parent.removeChild(this.$el)
}
},
render (h) {
return h('transition', {
props: {
name: 'n-fade-in-height-expand-transition'
},
on: {
beforeLeave: this.handleBeforeLeave,
leave: this.handleLeave,
enter: this.handleEnter,
afterEnter: this.handleAfterEnter
}
}, this.$slots.default)
}
}

View File

@ -1,53 +1,46 @@
import {
treedOptions,
dropIsValid,
applyDrop,
linkedCascaderOptions,
menuOptions
} from '../../_utils/data/menuModel'
import withapp from '../../_mixins/withapp'
import themeable from '../../_mixins/themeable'
import NTreeNode from './TreeNode'
import NTreeChildNodesExpandTransition from './ChildNodesExpandTransition'
import NFadeInHeightExpandTransition from '../../_transition/FadeInHeightExpandTransition'
import { isLeaf, isLoaded } from './utils'
function createNode (node, h, self) {
function createNode (node, h, treeInstance) {
const listeners = {
'switcher-click': self.handleSwitcherClick,
select: self.handleSelect,
dragenter: self.handleDragEnter,
dragstart: self.handleDragStart,
dragleave: self.handleDragLeave,
drop: self.handleDrop,
check: self.handleCheck
'switcher-click': treeInstance.handleSwitcherClick,
select: treeInstance.handleSelect,
dragenter: treeInstance.handleDragEnter,
dragstart: treeInstance.handleDragStart,
dragleave: treeInstance.handleDragLeave,
drop: treeInstance.handleDrop,
check: treeInstance.handleCheck
}
const expanded = self.syntheticExpandedKeys.includes(node.key)
const expanded = treeInstance.syntheticExpandedKeys.includes(node.key)
const props = {
data: node,
expanded,
selected: self.syntheticSelectedKeys.includes(node.key),
draggable: self.draggable,
checkable: self.checkable,
drop: self.drop,
blockNode: self.blockNode,
checked: self.syntheticCheckedKeys.includes(node.key)
selected: treeInstance.syntheticSelectedKeys.includes(node.key),
draggable: treeInstance.draggable,
checkable: treeInstance.checkable,
drop: treeInstance.drop,
blockNode: treeInstance.blockNode,
checked: treeInstance.syntheticCheckedKeys.includes(node.key)
}
return h(NTreeNode, {
props,
on: listeners,
key: node.key
},
[!node.isLeaf
? h(NTreeChildNodesExpandTransition, {
[!isLeaf(node)
? h(NFadeInHeightExpandTransition, {
props: {
transitionDisabled: self.transitionDisabled
transitionDisabled: treeInstance.transitionDisabled
}
},
[
expanded
expanded && node.children
? h('ul', {
staticClass: 'n-tree-children-wrapper'
}, node.children.map(child => createNode(child, h, self)))
}, node.children.map(child => createNode(child, h, treeInstance)))
: null
]
)
@ -55,25 +48,36 @@ function createNode (node, h, self) {
)
}
function convertRootedOptionsToVNodeTree (root, h, self) {
return root.children.map(child => createNode(child, h, self))
function convertOptionsToVNodeTree (options, h, treeInstance) {
return options.map(child => createNode(child, h, treeInstance))
}
export default {
name: 'NTree',
mixins: [ withapp, themeable ],
model: {
prop: 'selected-keys',
event: 'selected-keys-change'
},
provide () {
return { NTree: this }
},
props: {
data: {
type: Array,
default: null
},
autoExpandParent: {
type: Boolean,
default: true
},
checkable: {
type: Boolean,
default: false
},
draggable: {
type: Boolean,
default: true
default: false
},
blockNode: {
type: Boolean,
@ -83,6 +87,10 @@ export default {
type: Array,
default: null
},
disabled: {
type: Boolean,
default: false
},
defaultCheckedKeys: {
type: Array,
default: null
@ -99,18 +107,10 @@ export default {
type: Array,
default: null
},
lazy: {
remote: {
type: Boolean,
default: false
},
allowDrop: {
type: Function,
default: () => true
},
allowDrag: {
type: Function,
default: () => true
},
multiple: {
type: Boolean,
default: false
@ -119,36 +119,15 @@ export default {
type: String,
default: ''
},
onExpand: {
onLoad: {
type: Function,
default: () => {
return (node, next) => {
next()
}
}
},
onSelect: {
type: Function,
default: () => {
return (node, next) => {
next()
}
}
},
onDrop: {
type: Function,
default: () => {
return (node, next) => {
next()
}
}
default: null
}
},
created () {
this.internalCheckedKeys = this.defaultCheckedKeys || this.internalCheckedKeys
this.internalExpandedKeys = this.defaultExpandedKeys || this.internalExpandedKeys
this.internalSelectedKeys = this.defaultSelectedKeys || this.internalSelectedKeys
this.treeData = treedOptions(this.data)
this.internalCheckedKeys = this.defaultCheckedKeys || []
this.internalExpandedKeys = this.defaultExpandedKeys || []
this.internalSelectedKeys = this.defaultSelectedKeys || []
},
data () {
return {
@ -160,18 +139,16 @@ export default {
draggingNode: null,
droppingNodeKey: null,
expandTimerId: null,
transitionDisabled: false
transitionDisabled: false,
loadingKeys: []
}
},
watch: {
data (newData) {
this.treeData = treedOptions(newData)
data () {
this.internalExpandedKeys = []
this.internalCheckedKeys = []
this.internalSelectedKeys = []
this.draggingNodeKey = null
this.draggingNode = null
this.droppingNodeKey = null
this.loadingKeys = []
this.expandTimerId = null
}
},
@ -208,25 +185,33 @@ export default {
}
},
methods: {
getSelectedKeys () {
return this.syntheticSelectedKeys
},
getCheckedKeys () {
return this.syntheticCheckedKeys
},
getExpandedKeys () {
return this.syntheticExpandedKeys
},
disableTransition () {
this.transitionDisabled = true
},
enableTransition () {
this.transitionDisabled = false
},
resetDragStatus () {
this.draggingNodeKey = null
this.draggingNode = null
this.droppingNodeKey = null
},
handleCheck (node, checked) {
if (!this.hasCheckedKeys) {
if (checked) {
if (this.disabled || node.disabled) return
if (checked) {
if (this.hasCheckedKeys) {
this.$emit('checked-keys-change', this.syntheticCheckedKeys.concat([node.key]))
} else {
this.internalCheckedKeys.push(node.key)
}
} else {
if (this.hasCheckedKeys) {
const checkedKeysAfterChange = this.syntheticCheckedKeys
checkedKeysAfterChange.splice(
checkedKeysAfterChange.findIndex(key => key === node.key),
1
)
this.$emit('checked-keys-change', checkedKeysAfterChange)
} else {
this.internalCheckedKeys.splice(
this.internalCheckedKeys.findIndex(key => key === node.key),
@ -235,79 +220,121 @@ export default {
}
}
},
handleDrop (node, dropType) {
const drop = [this.draggingNode, node, dropType]
if (dropIsValid(drop)) {
this.disableTransition()
this.$nextTick().then(() => {
applyDrop(drop)
return this.$nextTick()
}).then(() => {
this.enableTransition()
})
}
this.draggingNodeKey = null
this.draggingNode = null
},
toggleExpand (node) {
const index = this.syntheticExpandedKeys.findIndex(expandNodeId => expandNodeId === node.key)
if (this.disabled) return
const index = this.syntheticExpandedKeys
.findIndex(expandNodeId => expandNodeId === node.key)
if (~index) {
this.$emit('collapse', node)
if (!this.hasExpandedKeys) {
this.internalExpandedKeys.splice(index, 1)
this.$emit('expanded-keys-change', this.internalExpandedKeys)
} else {
const expandedKeysAfterChange = Array.from(this.syntheticExpandedKeys)
expandedKeysAfterChange.splice(index, 1)
this.$emit(
'expanded-keys-change',
expandedKeysAfterChange
)
}
} else {
if (!node.isLeaf) {
if (!isLeaf(node)) {
this.$emit('expand', node)
if (!this.hasExpandedKeys) {
this.internalExpandedKeys.push(node.key)
this.$emit('expanded-keys-change', this.internalExpandedKeys)
} else {
this.$emit(
'expanded-keys-change',
this.syntheticExpandedKeys.concat(node.key)
)
}
}
}
},
handleSwitcherClick (node) {
if (this.disabled || node.disabled) return
this.toggleExpand(node)
},
handleSelect (node) {
if (this.disabled || node.disabled) return
this.$emit('select', node)
if (this.internalSelectedKeys.includes(node.key)) this.internalSelectedKeys = []
else this.internalSelectedKeys = [node.key]
},
handleDragEnter (node) {
this.$emit('dragenter', node)
handleDragEnter ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.$emit('dragenter', { event, node })
if (!this.autoExpandParent) return
this.droppingNodeKey = node.key
if (node.key === this.draggingNodeKey) return
if (!this.syntheticExpandedKeys.includes(node.key) && !node.isLeaf) {
if (
!this.syntheticExpandedKeys.includes(node.key) &&
!isLeaf(node)
) {
window.clearTimeout(this.expandTimerId)
this.expandTimerId = window.setTimeout(() => {
if (this.droppingNodeKey === node.key && !this.syntheticExpandedKeys.includes(node.key)) {
const expand = () => {
if (
this.droppingNodeKey === node.key &&
!this.syntheticExpandedKeys.includes(node.key)
) {
if (!this.hasExpandedKeys) {
this.internalExpandedKeys.push(node.key)
this.$emit('expanded-keys-change', this.internalExpandedKeys)
} else {
this.$emit('expanded-keys-change', this.syntheticExpandedKeys.concat(node.key))
}
this.$emit('expand', node.key)
}
}
if (!isLoaded(node)) {
if (!this.loadingKeys.includes(node.key)) {
this.loadingKeys.push(node.key)
}
this
.onLoad(node)
.then(() => {
this.loadingKeys.splice(
this.loadingKeys.find(key => key === node.key),
1
)
expand()
})
return
}
this.expandTimerId = window.setTimeout(() => {
expand()
this.expandTimerId = null
}, 800)
}
},
handleDragLeave (node) {
handleDragLeave ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.droppingNodeKey = null
this.$emit('dragleave', node)
this.$emit('dragleave', { event, node })
},
handleDragStart (node) {
handleDragStart ({ event, node }) {
if (!this.draggable || this.disabled || node.disabled) return
this.draggingNodeKey = node.key
this.draggingNode = node
this.$emit('dragstart', node)
this.$emit('dragstart', { event, node })
},
handleDrop ({ event, node, dropPosition }) {
if (!this.draggable || this.disabled || node.disabled) return
const drop = {
event,
node,
dragNode: this.draggingNode,
dropPosition
}
this.$emit('drop', drop)
this.resetDragStatus()
}
},
render (h) {
const lOptions = linkedCascaderOptions(this.treeData, 'multiple-all-options')
const mOptions = menuOptions(lOptions)[0]
return h('div', {
staticClass: 'n-tree',
class: {
[`n-${this.syntheticTheme}-theme`]: this.syntheticTheme
}
}, convertRootedOptionsToVNodeTree(mOptions, h, this))
}, convertOptionsToVNodeTree(this.data, h, this))
}
}

View File

@ -1,9 +1,15 @@
import NTreeNodeSwitcher from './TreeNodeSwitcher.vue'
import NTreeNodeCheckbox from './TreeNodeCheckbox.vue'
import NTreeNodeContent from './TreeNodeContent.vue'
import { isLeaf, isLoaded } from './utils'
export default {
name: 'NTreeNode',
inject: {
NTree: {
default: null
}
},
props: {
data: {
type: Object,
@ -38,27 +44,58 @@ export default {
default: null
}
},
computed: {
loading () {
return this.NTree.loadingKeys.includes(this.data.key)
}
},
methods: {
handleSwitcherClick () {
this.$emit('switcher-click', this.data)
const node = this.data
const NTree = this.NTree
if (NTree.remote && !isLeaf(node) && !isLoaded(node)) {
if (!NTree.loadingKeys.includes(node.key)) {
NTree.loadingKeys.push(node.key)
}
NTree.onLoad &&
NTree.onLoad(node)
.then(() => {
NTree.loadingKeys.splice(
NTree.loadingKeys.find(key => key === node.key),
1
)
this.$emit('switcher-click', node)
})
} else {
this.$emit('switcher-click', node)
}
},
handleContentClick () {
this.$emit('select', this.data)
},
handleDragOver () {
this.$emit('dragover', this.data)
handleDragOver (e) {
this.$emit('dragover', { event: e, node: this.data })
},
handleDragEnter () {
this.$emit('dragenter', this.data)
handleDragEnter (e) {
this.$emit('dragenter', { event: e, node: this.data })
},
handleDragStart () {
this.$emit('dragstart', this.data)
handleDragStart (e) {
this.$emit('dragstart', { event: e, node: this.data })
},
handleDragLeave () {
this.$emit('dragleave', this.data)
handleDragLeave (e) {
this.$emit('dragleave', { event: e, node: this.data })
},
handleDragEnd (e) {
this.$emit('dragend', { event: e, node: this.data })
this.resetDragStatus()
},
handleDrop (e, dropPosition) {
this.$emit('drop', this.data, dropPosition)
this.$emit('drop', {
event: e,
node: this.data,
dropPosition
})
this.NTree.resetDragStatus()
},
handleCheck (checked) {
this.$emit('check', this.data, checked)
@ -71,7 +108,8 @@ export default {
h(NTreeNodeSwitcher, {
props: {
expanded: this.expanded,
hide: this.data.isLeaf
loading: this.loading,
hide: isLeaf(this.data)
},
on: {
click: this.handleSwitcherClick

View File

@ -2,7 +2,7 @@
<span class="n-tree-node-checkbox">
<n-checkbox
:checked="value"
@input="handleInput"
@change="handleChange"
/>
</span>
</template>
@ -22,7 +22,7 @@ export default {
}
},
methods: {
handleInput (value) {
handleChange (value) {
this.$emit('check', value)
}
}

View File

@ -88,12 +88,12 @@ export default {
handleContentDrop (e) {
e.preventDefault()
this.pending = false
const actionType = ({
top: 'insertBefore',
bottom: 'insertAfter',
body: 'append'
const dropPosition = ({
top: 'top',
bottom: 'bottom',
body: 'center'
})[this.pendingPosition]
this.$emit('drop', e, actionType)
this.$emit('drop', e, dropPosition)
}
}
}

View File

@ -7,19 +7,33 @@
}"
@click="handleClick"
>
<n-icon>
<md-arrow-dropright />
</n-icon>
<div class="n-tree-node-switcher__icon">
<n-icon-switch-transition>
<n-icon v-if="!loading" key="switcher">
<md-arrow-dropright />
</n-icon>
<n-base-loading v-else key="loading" :theme="NTree.syntheticTheme" />
</n-icon-switch-transition>
</div>
</span>
</template>
<script>
import mdArrowDropright from '../../_icons/md-arrow-dropright'
import NBaseLoading from '../../_base/Loading'
import NIconSwitchTransition from '../../_transition/IconSwitchTransition'
export default {
name: 'NTreeSwitcher',
inject: {
NTree: {
default: null
}
},
components: {
mdArrowDropright
mdArrowDropright,
NBaseLoading,
NIconSwitchTransition
},
props: {
expanded: {
@ -29,6 +43,10 @@ export default {
hide: {
type: Boolean,
default: false
},
loading: {
type: Boolean,
default: false
}
},
methods: {

8
src/Tree/src/utils.js Normal file
View File

@ -0,0 +1,8 @@
export function isLeaf (node) {
if (node.isLeaf !== undefined) return node.isLeaf
return !node.children
}
export function isLoaded (node) {
return !(node.isLeaf === false && !node.children)
}

View File

@ -32,9 +32,25 @@
justify-content: center;
transition: transform .15s $--n-ease-in-out-cubic-bezier;
vertical-align: bottom;
@include b(icon) {
fill: $--n-secondary-text-color;
stroke: $--n-secondary-text-color;
@include e(icon) {
position: relative;
height: 14px;
width: 14px;
display: flex;
@include b(icon) {
height: 14px;
width: 14px;
font-size: 14px;
fill: $--tree-node-switcher-color;
stroke: $--tree-node-switcher-color;
@include icon-switch-transition;
}
@include b(base-loading) {
font-size: 14px;
height: 14px;
width: 14px;
@include icon-switch-transition;
}
}
@include m(hide) {
visibility: hidden

View File

@ -1,7 +1,8 @@
@mixin setup-dark-tree {
$--tree-node-background-color: (
'hover': change-color($--n-primary-color, $alpha: .45),
'active': change-color($--n-primary-color, $alpha: .3),
'selected': change-color($--n-primary-color, $alpha: .3)
'hover': change-color($--n-primary-color, $alpha: .25),
'active': change-color($--n-primary-color, $alpha: .15),
'selected': change-color($--n-primary-color, $alpha: .15)
) !global;
$--tree-node-switcher-color: $--n-tertiary-text-color !global;
}

View File

@ -1,7 +1,8 @@
@mixin setup-light-tree {
$--tree-node-background-color: (
'hover': change-color($--n-primary-color, $alpha: .15),
'active': change-color($--n-primary-color, $alpha: .25),
'selected': change-color($--n-primary-color, $alpha: .25)
'hover': change-color($--n-primary-color, $alpha: .16),
'active': change-color($--n-primary-color, $alpha: .1),
'selected': change-color($--n-primary-color, $alpha: .1)
) !global;
$--tree-node-switcher-color: $--n-tertiary-text-color !global;
}