Add search in v-select (#13867)

* Add search in v-select

* only show search when nested items more than 20

* auto open when there's only one child

* persist search input when item count reduces
This commit is contained in:
Azri Kahar
2022-06-28 03:53:51 +08:00
committed by GitHub
parent 43f27b0943
commit 68eeab8ae2
2 changed files with 56 additions and 9 deletions

View File

@@ -1,5 +1,5 @@
<template>
<v-list-group :clickable="item.selectable" :value="item.value">
<v-list-group :clickable="item.selectable" :open="item.children?.length === 1" :value="item.value">
<template #activator>
<v-list-item-icon v-if="multiple === false && allowOther === false && item.icon">
<v-icon :name="item.icon" />

View File

@@ -48,6 +48,16 @@
<v-divider />
</template>
<v-list-item v-if="internalItemsCount > 20 || search">
<v-list-item-content>
<v-input v-model="search" autofocus small :placeholder="t('search')" @click.stop.prevent>
<template #append>
<v-icon small name="search" />
</template>
</v-input>
</v-list-item-content>
</v-list-item>
<template v-for="(item, index) in internalItems" :key="index">
<select-list-item-group
v-if="item.children"
@@ -117,14 +127,14 @@
</template>
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, PropType, computed, toRefs, Ref } from 'vue';
import { useCustomSelection, useCustomSelectionMultiple } from '@/composables/use-custom-selection';
import { get } from 'lodash';
import { Placement } from '@popperjs/core';
import { debounce, get } from 'lodash';
import { computed, defineComponent, PropType, Ref, ref, toRefs, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import SelectListItemGroup from './select-list-item-group.vue';
import SelectListItem from './select-list-item.vue';
import { Option } from './types';
import { Placement } from '@popperjs/core';
type ItemsRaw = (string | any)[];
type InputValue = string[] | string;
@@ -217,7 +227,7 @@ export default defineComponent({
setup(props, { emit }) {
const { t } = useI18n();
const { internalItems } = useItems();
const { internalItems, internalItemsCount, internalSearch } = useItems();
const { displayValue } = useDisplayValue();
const { modelValue } = toRefs(props);
const { otherValue, usesOtherValue } = useCustomSelection(modelValue as Ref<string>, internalItems, (value) =>
@@ -229,18 +239,30 @@ export default defineComponent({
(value) => emit('update:modelValue', value)
);
const search = ref<string | null>(null);
watch(
search,
debounce((val: string | null) => {
internalSearch.value = val;
}, 250)
);
return {
t,
internalItems,
internalItemsCount,
displayValue,
otherValue,
usesOtherValue,
otherValues,
addOtherValue,
setOtherValue,
search,
};
function useItems() {
const internalSearch = ref<string | null>(null);
const internalItems = computed(() => {
const parseItem = (item: Record<string, any>): Option => {
if (typeof item === 'string') {
@@ -260,16 +282,41 @@ export default defineComponent({
icon: get(item, props.itemIcon),
disabled: get(item, props.itemDisabled),
selectable: get(item, props.itemSelectable),
children,
children: children ? children.filter(filterItem) : children,
};
};
const items = props.items.map(parseItem);
const filterItem = (item: Record<string, any>): boolean => {
if (!internalSearch.value) return true;
const searchValue = internalSearch.value.toLowerCase();
return item?.children
? item.children.some((item: Record<string, any>) => filterItem(item))
: item.text?.toLowerCase().includes(searchValue) || item.value?.toLowerCase().includes(searchValue);
};
const items = internalSearch.value ? props.items.filter(filterItem).map(parseItem) : props.items.map(parseItem);
return items;
});
return { internalItems };
const internalItemsCount = computed<number>(() => {
const countItems = (items: Option[]): number => {
const count = items.reduce((acc, item): number => {
if (item?.children) {
acc += countItems(item.children);
}
return acc + 1;
}, 0);
return count;
};
return countItems(props.items);
});
return { internalItems, internalItemsCount, internalSearch };
}
function useDisplayValue() {