diff --git a/ui/playground/src/pages/components/tree.vue b/ui/playground/src/pages/components/tree.vue
index d74308922c6..26394d26f70 100644
--- a/ui/playground/src/pages/components/tree.vue
+++ b/ui/playground/src/pages/components/tree.vue
@@ -19,6 +19,7 @@
+
@@ -36,7 +37,7 @@
-
+
@@ -45,6 +46,7 @@
this.$q.notify('Tapped on node 1.3')
return {
+ virtualScroll: true,
noConnectors: false,
noTransition: false,
selected: null,
diff --git a/ui/src/components/tree/QTree.js b/ui/src/components/tree/QTree.js
index a9266198957..2323d41bb0a 100644
--- a/ui/src/components/tree/QTree.js
+++ b/ui/src/components/tree/QTree.js
@@ -1,6 +1,7 @@
import {
h, ref, computed, watch,
- withDirectives, vShow, nextTick, getCurrentInstance, onBeforeUpdate
+ withDirectives, vShow, nextTick, getCurrentInstance, onBeforeUpdate,
+ withMemo
} from 'vue'
import QIcon from '../icon/QIcon.js'
@@ -14,6 +15,7 @@ import { createComponent } from '../../utils/private.create/create.js'
import { stopAndPrevent } from '../../utils/event/event.js'
import { shouldIgnoreKey } from '../../utils/private.keyboard/key-composition.js'
import { injectProp } from '../../utils/private.inject-obj-prop/inject-obj-prop.js'
+import QVirtualScroll from '../virtual-scroll/QVirtualScroll.js'
const tickStrategyOptions = [ 'none', 'strict', 'leaf', 'leaf-filtered' ]
@@ -41,6 +43,7 @@ export default createComponent({
},
dense: Boolean,
+ virtualScroll: Boolean,
color: String,
controlColor: String,
@@ -83,21 +86,18 @@ export default createComponent({
'afterHide'
],
- setup (props, { slots, emit }) {
+ setup (props, { slots, emit, attrs }) {
const { proxy } = getCurrentInstance()
const { $q } = proxy
const isDark = useDark(props, $q)
const lazy = ref({})
- const innerTicked = ref(props.ticked || [])
- const innerExpanded = ref(props.expanded || [])
+ const innerTicked = ref(new Set(props.ticked || []))
+ const innerExpanded = ref(new Set(props.expanded || []))
+ const innerNodes = ref(new Map(getNodesPairs(props.nodes)))
- let blurTargets = {}
-
- onBeforeUpdate(() => {
- blurTargets = {}
- })
+ const blurTargets = {}
const classes = computed(() =>
`q-tree q-tree--${ props.dense === true ? 'dense' : 'standard' }`
@@ -132,6 +132,35 @@ export default createComponent({
&& node[ props.labelKey ].toLowerCase().indexOf(filt) !== -1
}
))
+ const virtSlots = {
+ default: props => {
+ console.log(props)
+ const key = getNodeKey(props.item.node)
+ const m = meta.value[key]
+
+ const node = getNode(props.item.node, true)
+
+ if (props.item.level > 0) {
+ let level = node;
+ for (let index = 0; index < props.item.level; index++) {
+ level = h('div', {
+ class: 'q-tree__node-collapsible' + textColorClass.value,
+ key: `${ key }__q`
+ }, [
+ h('div', {
+ class: 'q-tree__children'
+ + (m.disabled === true ? ' q-tree__node--disabled' : ''),
+ role: 'group'
+ }, [level])
+ ]);
+ }
+
+ return level;
+ }
+
+ return node
+ }
+ }
const meta = computed(() => {
const meta = {}
@@ -139,7 +168,7 @@ export default createComponent({
const travel = (node, parent) => {
const tickStrategy = node.tickStrategy || (parent ? parent.tickStrategy : props.tickStrategy)
const
- key = node[ props.nodeKey ],
+ key = getNodeKey(node),
isParent = node[ props.childrenKey ] && Array.isArray(node[ props.childrenKey ]) && node[ props.childrenKey ].length !== 0,
selectable = node.disabled !== true && hasSelection.value === true && node.selectable !== false,
expandable = node.disabled !== true && node.expandable !== false,
@@ -174,7 +203,7 @@ export default createComponent({
selected: key === props.selected && selectable === true,
selectable,
- expanded: isParent === true ? innerExpanded.value.includes(key) : false,
+ expanded: isParent === true ? innerExpanded.value.has(key) : false,
expandable,
noTick: node.noTick === true || (strictTicking !== true && localLazy && localLazy !== 'loaded'),
tickable,
@@ -184,8 +213,8 @@ export default createComponent({
leafFilteredTicking,
leafTicking,
ticked: strictTicking === true
- ? innerTicked.value.includes(key)
- : (isParent === true ? false : innerTicked.value.includes(key))
+ ? innerTicked.value.has(key)
+ : (isParent === true ? false : innerTicked.value.has(key))
}
meta[ key ] = m
@@ -242,36 +271,44 @@ export default createComponent({
}
props.nodes.forEach(node => travel(node, null))
+
return meta
})
watch(() => props.ticked, val => {
- innerTicked.value = val
+ innerTicked.value = new Set(val)
})
watch(() => props.expanded, val => {
- innerExpanded.value = val
+ innerExpanded.value = new Set(val)
})
- function getNodeByKey (key) {
- const reduce = [].reduce
+ watch(() => props.nodes, val => {
+ innerNodes.value = new Map(getNodesPairs(val))
+ })
- const find = (result, node) => {
- if (result || !node) {
- return result
- }
- if (Array.isArray(node) === true) {
- return reduce.call(Object(node), find, result)
- }
- if (node[ props.nodeKey ] === key) {
- return node
- }
- if (node[ props.childrenKey ]) {
- return find(null, node[ props.childrenKey ])
+ function getNodesPairs (nodes) {
+ const nodePairs = []
+
+ const travel = (node) => {
+ if (Array.isArray(node[ props.childrenKey ])) {
+ node[ props.childrenKey ].forEach(travel)
}
+
+ nodePairs.push([ getNodeKey(node), node ])
}
- return find(null, props.nodes)
+ nodes.forEach(travel)
+
+ return nodePairs
+ }
+
+ function getNodeKey (node) {
+ return node[ props.nodeKey ]
+ }
+
+ function getNodeByKey (key) {
+ return innerNodes.value.get(key) ?? null
}
function getTickedNodes () {
@@ -293,28 +330,20 @@ export default createComponent({
emit('update:expanded', [])
}
else {
- innerExpanded.value = []
+ innerExpanded.value = new Set([])
}
}
function expandAll () {
- const expanded = []
- const travel = node => {
- if (node[ props.childrenKey ] && node[ props.childrenKey ].length !== 0) {
- if (node.expandable !== false && node.disabled !== true) {
- expanded.push(node[ props.nodeKey ])
- node[ props.childrenKey ].forEach(travel)
- }
- }
- }
+ const expanded = [ ...innerNodes.value.keys() ]
- props.nodes.forEach(travel)
+ const shouldEmit = props.expanded !== void 0
- if (props.expanded !== void 0) {
+ if (shouldEmit) {
emit('update:expanded', expanded)
}
else {
- innerExpanded.value = expanded
+ innerExpanded.value = new Set(expanded)
}
}
@@ -353,13 +382,8 @@ export default createComponent({
}
function localSetExpanded (key, state) {
- let target = innerExpanded.value
const shouldEmit = props.expanded !== void 0
- if (shouldEmit === true) {
- target = target.slice()
- }
-
if (state) {
if (props.accordion) {
if (meta.value[ key ]) {
@@ -373,30 +397,25 @@ export default createComponent({
}
else {
props.nodes.forEach(node => {
- const k = node[ props.nodeKey ]
+ const k = getNodeKey(node)
if (k !== key) {
collapse.push(k)
}
})
}
if (collapse.length !== 0) {
- target = target.filter(k => collapse.includes(k) === false)
+ collapse.forEach(key => innerExpanded.value.delete(key))
}
}
}
-
- target = target.concat([ key ])
- .filter((key, index, self) => self.indexOf(key) === index)
+ innerExpanded.value.add(key)
}
else {
- target = target.filter(k => k !== key)
+ innerExpanded.value.delete(key)
}
if (shouldEmit === true) {
- emit('update:expanded', target)
- }
- else {
- innerExpanded.value = target
+ emit('update:expanded', [ ...innerExpanded.value ])
}
}
@@ -407,23 +426,17 @@ export default createComponent({
}
function setTicked (keys, state) {
- let target = innerTicked.value
const shouldEmit = props.ticked !== void 0
- if (shouldEmit === true) {
- target = target.slice()
- }
-
if (state) {
- target = target.concat(keys)
- .filter((key, index, self) => self.indexOf(key) === index)
+ keys.forEach(key => innerTicked.value.add(key))
}
else {
- target = target.filter(k => keys.includes(k) === false)
+ keys.forEach(key => innerTicked.value.delete(key))
}
if (shouldEmit === true) {
- emit('update:ticked', target)
+ emit('update:ticked', [ ...innerTicked.value ])
}
}
@@ -448,11 +461,30 @@ export default createComponent({
}
function getChildren (nodes) {
+ if (props.virtualScroll) {
+ const virtualChildren = []
+
+ const travel = (node, level = 0) => {
+ const key = getNodeKey(node)
+ const m = meta.value[key]
+
+ virtualChildren.push({node, level})
+
+ if (m.expanded && Array.isArray(node[ props.childrenKey ])) {
+ node[ props.childrenKey ].forEach(child => travel(child, level + 1))
+ }
+ }
+
+ nodes.forEach(node => travel(node, 0))
+
+ return h(QVirtualScroll, {style: attrs.style, separator: true, type: 'list', items: virtualChildren}, virtSlots)
+ }
+
return (
props.filter
? nodes.filter(n => meta.value[ n[ props.nodeKey ] ].matchesFilter)
: nodes
- ).map(child => getNode(child))
+ ).map((child) => getNode(child))
}
function getNodeMedia (node) {
@@ -480,19 +512,21 @@ export default createComponent({
emit('afterHide')
}
- function getNode (node) {
+ function getNode (node, omitChildren) {
const
- key = node[ props.nodeKey ],
+ key = getNodeKey(node),
m = meta.value[ key ],
header = node.header
? slots[ `header-${ node.header }` ] || slots[ 'default-header' ]
: slots[ 'default-header' ]
- const children = m.isParent === true
- ? getChildren(node[ props.childrenKey ])
+ const childrenMeta = node[props.childrenKey] ?? [];
+
+ const children = m.isParent === true
+ ? getChildren(childrenMeta)
: []
- const isParent = children.length !== 0 || (m.lazy && m.lazy !== 'loaded')
+ const isParent = childrenMeta.length !== 0 || (m.lazy && m.lazy !== 'loaded')
let body = node.body
? slots[ `body-${ node.body }` ] || slots[ 'default-body' ]
@@ -584,7 +618,7 @@ export default createComponent({
])
]),
- isParent === true
+ isParent === true && !omitChildren
? (
props.noTransition === true
? (
@@ -705,7 +739,7 @@ export default createComponent({
})
return () => {
- const children = getChildren(props.nodes)
+ const children = getChildren(props.nodes);
return h(
'div', {