From fabc511bfbf90149b536ba09c6ba5e18bafd4c3a Mon Sep 17 00:00:00 2001 From: Kael Date: Mon, 6 Jan 2025 15:53:33 +1100 Subject: [PATCH] feat(VVirtualScroll): add itemKey prop closes #20809 --- .../src/locale/en/VVirtualScroll.json | 2 +- .../VAutocomplete/VAutocomplete.tsx | 2 +- .../src/components/VCombobox/VCombobox.tsx | 2 +- .../src/components/VSelect/VSelect.tsx | 2 +- packages/vuetify/src/composables/virtual.ts | 27 ++++++++++++------- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/packages/api-generator/src/locale/en/VVirtualScroll.json b/packages/api-generator/src/locale/en/VVirtualScroll.json index e3fc6099fe0..f2529013bbe 100644 --- a/packages/api-generator/src/locale/en/VVirtualScroll.json +++ b/packages/api-generator/src/locale/en/VVirtualScroll.json @@ -1,7 +1,7 @@ { "props": { "height": "Height of the component as a css value/", - "itemKey": "Required when using **dynamic-item-height** together with object items. Should point to a property with a unique value for each item.", + "itemKey": "Should point to a property with a unique value for each item, if not set then item index will be used as a key which may result in unnecessary re-renders.", "items": "The array of items to display.", "renderless": "Disables default component rendering functionality." }, diff --git a/packages/vuetify/src/components/VAutocomplete/VAutocomplete.tsx b/packages/vuetify/src/components/VAutocomplete/VAutocomplete.tsx index 7980fd6bede..a927163e371 100644 --- a/packages/vuetify/src/components/VAutocomplete/VAutocomplete.tsx +++ b/packages/vuetify/src/components/VAutocomplete/VAutocomplete.tsx @@ -496,7 +496,7 @@ export const VAutocomplete = genericComponent ))} - + { ({ item, index, itemRef }) => { const itemProps = mergeProps(item.props, { ref: itemRef, diff --git a/packages/vuetify/src/components/VCombobox/VCombobox.tsx b/packages/vuetify/src/components/VCombobox/VCombobox.tsx index e1921b8e3c7..3714f10fdd0 100644 --- a/packages/vuetify/src/components/VCombobox/VCombobox.tsx +++ b/packages/vuetify/src/components/VCombobox/VCombobox.tsx @@ -540,7 +540,7 @@ export const VCombobox = genericComponent ))} - + { ({ item, index, itemRef }) => { const itemProps = mergeProps(item.props, { ref: itemRef, diff --git a/packages/vuetify/src/components/VSelect/VSelect.tsx b/packages/vuetify/src/components/VSelect/VSelect.tsx index ad18b3bbf1f..36e59082df5 100644 --- a/packages/vuetify/src/components/VSelect/VSelect.tsx +++ b/packages/vuetify/src/components/VSelect/VSelect.tsx @@ -417,7 +417,7 @@ export const VSelect = genericComponent ))} - + { ({ item, index, itemRef }) => { const itemProps = mergeProps(item.props, { ref: itemRef, diff --git a/packages/vuetify/src/composables/virtual.ts b/packages/vuetify/src/composables/virtual.ts index 4b1d12dc0f1..4cc3344bce4 100644 --- a/packages/vuetify/src/composables/virtual.ts +++ b/packages/vuetify/src/composables/virtual.ts @@ -4,10 +4,11 @@ import { useResizeObserver } from '@/composables/resizeObserver' // Utilities import { computed, nextTick, onScopeDispose, ref, shallowRef, watch, watchEffect } from 'vue' -import { clamp, debounce, IN_BROWSER, isObject, propsFactory } from '@/util' +import { clamp, debounce, getPropertyFromItem, IN_BROWSER, propsFactory } from '@/util' // Types -import type { Ref } from 'vue' +import type { PropType, Ref } from 'vue' +import type { SelectItemKey } from '@/util' const UP = -1 const DOWN = 1 @@ -16,8 +17,9 @@ const DOWN = 1 const BUFFER_PX = 100 type VirtualProps = { - itemHeight?: number | string - height?: number | string + itemHeight: number | string | null | undefined + itemKey: SelectItemKey + height: number | string | undefined } export const makeVirtualProps = propsFactory({ @@ -25,6 +27,10 @@ export const makeVirtualProps = propsFactory({ type: [Number, String], default: null, }, + itemKey: { + type: [String, Array, Function] as PropType, + default: null, + }, height: [Number, String], }, 'virtual') @@ -235,11 +241,14 @@ export function useVirtual (props: VirtualProps, items: Ref) { } const computedItems = computed(() => { - return items.value.slice(first.value, last.value).map((item, index) => ({ - raw: item, - index: index + first.value, - key: (isObject(item) && 'value' in item) ? item.value : index + first.value, - })) + return items.value.slice(first.value, last.value).map((item, index) => { + const _index = index + first.value + return { + raw: item, + index: _index, + key: getPropertyFromItem(item, props.itemKey, _index), + } + }) }) watch(items, () => {