Skip to content

Commit 9d5a1d4

Browse files
J-SekKaelWD
andauthored
fix(VTreeview): faster interactions with large trees (#22255)
Co-authored-by: Kael <[email protected]> fixes #21720
1 parent bd4a87b commit 9d5a1d4

File tree

7 files changed

+122
-33
lines changed

7 files changed

+122
-33
lines changed

packages/api-generator/src/locale/en/VList.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"disabled": "Puts all children inputs into a disabled state.",
77
"filterable": "**FOR INTERNAL USE ONLY** Prevents list item selection using [space] key and pass it back to the text input. Used internally for VAutocomplete and VCombobox.",
88
"inactive": "If set, the list tile will not be rendered as a link even if it has to/href prop or @click handler.",
9+
"itemsRegistration": "When set to 'props', skips rendering collapsed items/nodes (for significant performance gains).",
910
"lines": "Designates a **minimum-height** for all children `v-list-item` components. This prop uses [line-clamp](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-line-clamp) and is not supported in all browsers.",
1011
"link": "Applies `v-list-item` hover styles. Useful when using the item is an _activator_.",
1112
"nav": "An alternative styling that reduces `v-list-item` width and rounds the corners. Typically used with **[v-navigation-drawer](/components/navigation-drawers)**.",

packages/docs/src/data/new-in.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,9 @@
206206
},
207207
"VList": {
208208
"props": {
209-
"prependGap": "3.11.0",
210-
"indent": "3.11.0"
209+
"indent": "3.11.0",
210+
"itemsRegistration": "3.11.0",
211+
"prependGap": "3.11.0"
211212
}
212213
},
213214
"VListItem": {
@@ -344,7 +345,8 @@
344345
"hideNoData": "3.10.0",
345346
"noDataText": "3.10.0",
346347
"separateRoots": "3.9.0",
347-
"indentLines": "3.9.0"
348+
"indentLines": "3.9.0",
349+
"itemsRegistration": "3.11.0"
348350
},
349351
"slots": {
350352
"header": "3.10.0",

packages/vuetify/src/components/VList/VList.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,8 @@ export const VList = genericComponent<new <
172172
const { dimensionStyles } = useDimension(props)
173173
const { elevationClasses } = useElevation(props)
174174
const { roundedClasses } = useRounded(props)
175-
const { children, open, parents, select, getPath } = useNested(props)
175+
const { children, open, parents, select, getPath } = useNested(props, items, () => props.returnObject)
176+
176177
const lineClasses = toRef(() => props.lines ? `v-list--${props.lines}-line` : undefined)
177178
const activeColor = toRef(() => props.activeColor)
178179
const baseColor = toRef(() => props.baseColor)

packages/vuetify/src/components/VList/VListGroup.tsx

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import { VDefaultsProvider } from '@/components/VDefaultsProvider'
66
import { useList } from './list'
77
import { makeComponentProps } from '@/composables/component'
88
import { IconValue } from '@/composables/icons'
9-
import { useNestedGroupActivator, useNestedItem } from '@/composables/nested/nested'
9+
import { useNestedGroupActivator, useNestedItem, VNestedSymbol } from '@/composables/nested/nested'
1010
import { useSsrBoot } from '@/composables/ssrBoot'
1111
import { makeTagProps } from '@/composables/tag'
1212
import { MaybeTransition } from '@/composables/transition'
1313

1414
// Utilities
15-
import { computed } from 'vue'
15+
import { computed, inject, toRef } from 'vue'
1616
import { defineComponent, genericComponent, propsFactory, useRender } from '@/util'
1717

1818
export type VListGroupSlots = {
@@ -67,6 +67,9 @@ export const VListGroup = genericComponent<VListGroupSlots>()({
6767
const list = useList()
6868
const { isBooted } = useSsrBoot()
6969

70+
const parent = inject(VNestedSymbol)
71+
const renderWhenClosed = toRef(() => parent?.root?.itemsRegistration.value === 'render')
72+
7073
function onClick (e: Event) {
7174
if (['INPUT', 'TEXTAREA'].includes((e.target as Element)?.tagName)) return
7275
open(!isOpen.value, e)
@@ -114,9 +117,16 @@ export const VListGroup = genericComponent<VListGroupSlots>()({
114117
)}
115118

116119
<MaybeTransition transition={{ component: VExpandTransition }} disabled={ !isBooted.value }>
117-
<div class="v-list-group__items" role="group" aria-labelledby={ id.value } v-show={ isOpen.value }>
118-
{ slots.default?.() }
119-
</div>
120+
{ renderWhenClosed.value
121+
? (
122+
<div class="v-list-group__items" role="group" aria-labelledby={ id.value } v-show={ isOpen.value }>
123+
{ slots.default?.() }
124+
</div>
125+
) : isOpen.value && (
126+
<div class="v-list-group__items" role="group" aria-labelledby={ id.value }>
127+
{ slots.default?.() }
128+
</div>
129+
)}
120130
</MaybeTransition>
121131
</props.tag>
122132
))

packages/vuetify/src/components/VTreeview/__tests__/VTreeview.spec.browser.tsx

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,11 @@ const items = [
6060
]
6161

6262
describe.each([
63-
['plain', items],
64-
['reactive', reactive(items)],
65-
])('VTreeview with %s items', (_, items) => {
63+
['plain', 'render', items],
64+
['reactive', 'render', reactive(items)],
65+
['plain', 'props', items],
66+
['reactive', 'props', reactive(items)],
67+
] as const)('VTreeview with %s items and %s registration', (_, itemsRegistration, items) => {
6668
describe('activate', () => {
6769
it('single-leaf strategy', async () => {
6870
const activated = ref([])
@@ -74,6 +76,7 @@ describe.each([
7476
itemValue="id"
7577
activatable
7678
activeStrategy="single-leaf"
79+
itemsRegistration={ itemsRegistration }
7780
/>
7881
))
7982

@@ -96,6 +99,7 @@ describe.each([
9699
itemValue="id"
97100
activatable
98101
activeStrategy="leaf"
102+
itemsRegistration={ itemsRegistration }
99103
/>
100104
))
101105

@@ -118,6 +122,7 @@ describe.each([
118122
itemValue="id"
119123
activatable
120124
activeStrategy="independent"
125+
itemsRegistration={ itemsRegistration }
121126
/>
122127
))
123128

@@ -143,6 +148,7 @@ describe.each([
143148
itemValue="id"
144149
activatable
145150
activeStrategy="single-independent"
151+
itemsRegistration={ itemsRegistration }
146152
/>
147153
))
148154

@@ -169,6 +175,7 @@ describe.each([
169175
activatable
170176
activeStrategy="independent"
171177
onUpdate:activated={ onActivated }
178+
itemsRegistration={ itemsRegistration }
172179
/>
173180
))
174181

@@ -191,6 +198,7 @@ describe.each([
191198
itemValue="id"
192199
selectable
193200
selectStrategy="single-leaf"
201+
itemsRegistration={ itemsRegistration }
194202
/>
195203
))
196204

@@ -212,6 +220,7 @@ describe.each([
212220
itemValue="id"
213221
selectable
214222
selectStrategy="leaf"
223+
itemsRegistration={ itemsRegistration }
215224
/>
216225
))
217226

@@ -233,6 +242,7 @@ describe.each([
233242
itemValue="id"
234243
selectable
235244
selectStrategy="independent"
245+
itemsRegistration={ itemsRegistration }
236246
/>
237247
))
238248

@@ -257,6 +267,7 @@ describe.each([
257267
itemValue="id"
258268
selectable
259269
selectStrategy="single-independent"
270+
itemsRegistration={ itemsRegistration }
260271
/>
261272
))
262273

@@ -279,6 +290,7 @@ describe.each([
279290
itemValue="id"
280291
selectable
281292
selectStrategy="classic"
293+
itemsRegistration={ itemsRegistration }
282294
/>
283295
))
284296

@@ -301,13 +313,14 @@ describe.each([
301313
items={ items }
302314
itemValue="id"
303315
returnObject
316+
itemsRegistration={ itemsRegistration }
304317
/>
305318
))
306319

307320
await userEvent.click(screen.getByText(/Vuetify/).parentElement!.previousElementSibling!)
308-
await expect.element(screen.getByText(/Core/)).toBeVisible()
321+
await expect.element(screen.getByText(/Core/)).toBeDisplayed()
309322
await userEvent.click(screen.getByText(/Vuetify/).parentElement!.previousElementSibling!)
310-
await expect.element(screen.getByText(/Core/)).not.toBeVisible()
323+
await expect.poll(() => screen.queryByText(/Core/)).not.toBeDisplayed()
311324
})
312325

313326
it('open-all should work', async () => {
@@ -317,6 +330,7 @@ describe.each([
317330
items={ items }
318331
itemValue="id"
319332
returnObject
333+
itemsRegistration={ itemsRegistration }
320334
/>
321335
))
322336

@@ -335,6 +349,7 @@ describe.each([
335349
items={ items }
336350
itemValue="id"
337351
returnObject
352+
itemsRegistration={ itemsRegistration }
338353
/>
339354
))
340355

@@ -389,6 +404,7 @@ describe.each([
389404
activatable
390405
activeStrategy="leaf"
391406
returnObject
407+
itemsRegistration={ itemsRegistration }
392408
/>
393409
))
394410

@@ -416,6 +432,7 @@ describe.each([
416432
activatable
417433
activeStrategy="independent"
418434
returnObject
435+
itemsRegistration={ itemsRegistration }
419436
/>
420437
))
421438

@@ -453,6 +470,7 @@ describe.each([
453470
activatable
454471
activeStrategy="single-independent"
455472
returnObject
473+
itemsRegistration={ itemsRegistration }
456474
/>
457475
))
458476

@@ -487,6 +505,7 @@ describe.each([
487505
returnObject
488506
selectable
489507
selectStrategy="single-leaf"
508+
itemsRegistration={ itemsRegistration }
490509
/>
491510
))
492511

@@ -513,6 +532,7 @@ describe.each([
513532
returnObject
514533
selectable
515534
selectStrategy="leaf"
535+
itemsRegistration={ itemsRegistration }
516536
/>
517537
))
518538

@@ -542,6 +562,7 @@ describe.each([
542562
returnObject
543563
selectable
544564
selectStrategy="independent"
565+
itemsRegistration={ itemsRegistration }
545566
/>
546567
))
547568

@@ -580,6 +601,7 @@ describe.each([
580601
returnObject
581602
selectable
582603
selectStrategy="single-independent"
604+
itemsRegistration={ itemsRegistration }
583605
/>
584606
))
585607

@@ -608,6 +630,7 @@ describe.each([
608630
selectable
609631
returnObject
610632
selectStrategy="classic"
633+
itemsRegistration={ itemsRegistration }
611634
/>
612635
))
613636

@@ -645,6 +668,7 @@ describe.each([
645668
itemValue="id"
646669
openAll
647670
returnObject
671+
itemsRegistration={ itemsRegistration }
648672
/>
649673
))
650674

@@ -667,6 +691,7 @@ describe.each([
667691
openAll
668692
items={ items }
669693
itemValue="id"
694+
itemsRegistration={ itemsRegistration }
670695
/>
671696
))
672697

@@ -685,17 +710,17 @@ describe.each([
685710
itemValue="id"
686711
openOnClick
687712
returnObject
713+
itemsRegistration={ itemsRegistration }
688714
>
689715
{{
690716
prepend: ({ isOpen }) => (<span class="prepend-is-open">{ `${isOpen}` }</span>),
691717
}}
692718
</VTreeview>
693719
))
694720

695-
const itemsPrepend = screen.getAllByCSS('.v-treeview-item .v-list-item__prepend .prepend-is-open')
696-
697721
await userEvent.click(screen.getByText(/Vuetify Human Resources/))
698722
await waitIdle()
723+
const itemsPrepend = screen.getAllByCSS('.v-treeview-item .v-list-item__prepend .prepend-is-open')
699724
expect(itemsPrepend[0]).toHaveTextContent(/^true$/)
700725
expect(itemsPrepend[1]).toHaveTextContent(/^false$/)
701726

@@ -712,7 +737,6 @@ describe.each([
712737
await userEvent.click(screen.getByText(/Vuetify Human Resources/))
713738
await waitIdle()
714739
expect(itemsPrepend[0]).toHaveTextContent(/^false$/)
715-
expect(itemsPrepend[1]).toHaveTextContent(/^false$/)
716740
})
717741
})
718742

0 commit comments

Comments
 (0)