Skip to content

Commit d5b4670

Browse files
VaishnaviD5900Vaishnavi DeshpandeJ-Sek
authored
fix(VTreeview,VList): wire value-comparator into selection logic (#22841)
Co-authored-by: Vaishnavi Deshpande <VDeshpande7@slb.com> Co-authored-by: J-Sek <J-Sek@users.noreply.github.com> fixes #22013
1 parent d355443 commit d5b4670

4 files changed

Lines changed: 165 additions & 3 deletions

File tree

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ export const VList = genericComponent<new <S, A, O, T extends readonly any[]>(
182182
items,
183183
returnObject: toRef(() => props.returnObject),
184184
scrollToActive: toRef(() => props.navigationStrategy === 'track'),
185+
valueComparator: toRef(() => props.valueComparator),
185186
})
186187

187188
const lineClasses = toRef(() => props.lines ? `v-list--${props.lines}-line` : undefined)

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

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,5 +153,67 @@ describe('VList', () => {
153153
expect(selectedItem.value).toEqual([items[1]])
154154
})
155155

156+
describe('value-comparator', () => {
157+
it('should apply to initial selected', async () => {
158+
const caseItems = [
159+
{
160+
title: 'Group A',
161+
value: 'GROUP_A',
162+
children: [
163+
{ title: 'Alpha', value: 'ALPHA' },
164+
{ title: 'Beta', value: 'BETA' },
165+
],
166+
},
167+
{
168+
title: 'Group B',
169+
value: 'GROUP_B',
170+
children: [
171+
{ title: 'Gamma', value: 'GAMMA' },
172+
{ title: 'Delta', value: 'DELTA' },
173+
],
174+
},
175+
]
176+
const selected = ref<string[]>(['beta', 'delta'])
177+
178+
render(() => (
179+
<VList
180+
v-model:selected={ selected.value }
181+
items={ caseItems }
182+
opened={['GROUP_A', 'GROUP_B']}
183+
selectStrategy="independent"
184+
valueComparator={ (a: string, b: string) => a.toLowerCase() === b.toLowerCase() }
185+
/>
186+
))
187+
188+
expect(screen.getByText('Beta').closest('.v-list-item')).toHaveClass('v-list-item--active')
189+
expect(screen.getByText('Delta').closest('.v-list-item')).toHaveClass('v-list-item--active')
190+
expect(screen.getByText('Alpha').closest('.v-list-item')).not.toHaveClass('v-list-item--active')
191+
expect(screen.getByText('Gamma').closest('.v-list-item')).not.toHaveClass('v-list-item--active')
192+
})
193+
194+
it('should apply to initial activated', async () => {
195+
const caseItems = [
196+
{ title: 'Alpha', value: 'ALPHA' },
197+
{ title: 'Beta', value: 'BETA' },
198+
{ title: 'Gamma', value: 'GAMMA' },
199+
]
200+
const activated = ref<string[]>(['beta'])
201+
202+
render(() => (
203+
<VList
204+
v-model:activated={ activated.value }
205+
items={ caseItems }
206+
activatable
207+
activeStrategy="independent"
208+
valueComparator={ (a: string, b: string) => a.toLowerCase() === b.toLowerCase() }
209+
/>
210+
))
211+
212+
expect(screen.getByText('Beta').closest('.v-list-item')).toHaveClass('v-list-item--active')
213+
expect(screen.getByText('Alpha').closest('.v-list-item')).not.toHaveClass('v-list-item--active')
214+
expect(screen.getByText('Gamma').closest('.v-list-item')).not.toHaveClass('v-list-item--active')
215+
})
216+
})
217+
156218
showcase({ stories })
157219
})

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,35 @@ describe.each([
185185
await userEvent.click(screen.getByText(/Human Resources/))
186186
expect(onActivated).toHaveBeenCalledTimes(2)
187187
})
188+
189+
it('should apply value-comparator to initial activated', async () => {
190+
const caseItems = [
191+
{
192+
title: 'Group A',
193+
value: 'GROUP_A',
194+
children: [
195+
{ title: 'Alpha', value: 'ALPHA' },
196+
{ title: 'Beta', value: 'BETA' },
197+
],
198+
},
199+
]
200+
const activated = ref<string[]>(['beta'])
201+
202+
render(() => (
203+
<VTreeview
204+
v-model:activated={ activated.value }
205+
items={ caseItems }
206+
activatable
207+
openAll
208+
activeStrategy="independent"
209+
valueComparator={ (a: string, b: string) => a.toLowerCase() === b.toLowerCase() }
210+
itemsRegistration={ itemsRegistration }
211+
/>
212+
))
213+
214+
expect(screen.getByText('Beta').closest('.v-list-item')).toHaveClass('v-list-item--active')
215+
expect(screen.getByText('Alpha').closest('.v-list-item')).not.toHaveClass('v-list-item--active')
216+
})
188217
})
189218

190219
describe('select', () => {
@@ -303,6 +332,43 @@ describe.each([
303332
await userEvent.click(screen.getByText(/Vuetify/).parentElement!.previousElementSibling!)
304333
expect(selected.value).toStrictEqual([4, 201, 202, 203, 204, 205, 301, 302])
305334
})
335+
336+
it('should apply value-comparator to initial selected', async () => {
337+
const caseItems = [
338+
{
339+
title: 'Group A',
340+
value: 'GROUP_A',
341+
children: [
342+
{ title: 'Alpha', value: 'ALPHA' },
343+
{ title: 'Beta', value: 'BETA' },
344+
],
345+
},
346+
{
347+
title: 'Group B',
348+
value: 'GROUP_B',
349+
children: [
350+
{ title: 'Gamma', value: 'GAMMA' },
351+
{ title: 'Delta', value: 'DELTA' },
352+
],
353+
},
354+
]
355+
const selected = ref<string[]>(['beta', 'delta'])
356+
357+
render(() => (
358+
<VTreeview
359+
v-model:selected={ selected.value }
360+
items={ caseItems }
361+
selectable
362+
openAll
363+
selectStrategy="independent"
364+
valueComparator={ (a: string, b: string) => a.toLowerCase() === b.toLowerCase() }
365+
itemsRegistration={ itemsRegistration }
366+
/>
367+
))
368+
369+
const inputs = screen.getAllByCSS('.v-checkbox-btn input') as HTMLInputElement[]
370+
expect(inputs.filter(el => el.checked)).toHaveLength(2)
371+
})
306372
})
307373

308374
describe('return-object', () => {

packages/vuetify/src/composables/nested/nested.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ import type { ActiveStrategy } from './activeStrategies'
4040
import type { OpenStrategy } from './openStrategies'
4141
import type { SelectStrategy } from './selectStrategies'
4242
import type { ListItem } from '@/composables/list-items'
43-
import type { EventProp } from '@/util'
43+
import type { EventProp, ValueComparator } from '@/util'
4444

4545
export type ActiveStrategyProp =
4646
| 'single-leaf'
@@ -153,10 +153,12 @@ export const useNested = (
153153
items,
154154
returnObject,
155155
scrollToActive,
156+
valueComparator,
156157
}: {
157158
items: Ref<ListItem[]>
158159
returnObject: MaybeRefOrGetter<boolean>
159160
scrollToActive: MaybeRefOrGetter<boolean>
161+
valueComparator?: MaybeRefOrGetter<ValueComparator | undefined>
160162
},
161163
) => {
162164
let isUnmounted = false
@@ -212,18 +214,49 @@ export const useNested = (
212214
}
213215
})
214216

217+
const flatItems = computed(() => {
218+
const flat: ListItem[] = []
219+
const stack = [...items.value]
220+
while (stack.length) {
221+
const item = stack.pop()!
222+
flat.push(item)
223+
if (item.children) stack.push(...item.children)
224+
}
225+
return flat
226+
})
227+
228+
function resolveValue (value: unknown): unknown {
229+
const comparator = toValue(valueComparator)
230+
if (!comparator) return value
231+
const _returnObject = toValue(returnObject)
232+
for (const item of flatItems.value) {
233+
const itemVal = _returnObject ? toRaw(item.raw) : item.value
234+
if (comparator(value, itemVal)) return itemVal
235+
}
236+
return value
237+
}
238+
215239
const activated = useProxiedModel(
216240
props,
217241
'activated',
218242
props.activated,
219-
v => activeStrategy.value.in(v, children.value, parents.value),
243+
v => activeStrategy.value.in(
244+
Array.isArray(v) ? v.map(resolveValue) : v,
245+
children.value,
246+
parents.value,
247+
),
220248
v => activeStrategy.value.out(v, children.value, parents.value),
221249
)
222250
const selected = useProxiedModel(
223251
props,
224252
'selected',
225253
props.selected,
226-
v => selectStrategy.value.in(v, children.value, parents.value, disabled.value),
254+
v => selectStrategy.value.in(
255+
Array.isArray(v) ? v.map(resolveValue) : v,
256+
children.value,
257+
parents.value,
258+
disabled.value,
259+
),
227260
v => selectStrategy.value.out(v, children.value, parents.value),
228261
)
229262

0 commit comments

Comments
 (0)