diff --git a/src/components/forms/auto-complete/RuiAutoComplete.spec.ts b/src/components/forms/auto-complete/RuiAutoComplete.spec.ts index d242f81..f818871 100644 --- a/src/components/forms/auto-complete/RuiAutoComplete.spec.ts +++ b/src/components/forms/auto-complete/RuiAutoComplete.spec.ts @@ -62,9 +62,7 @@ describe('autocomplete', () => { it('works with primitive options', () => { const wrapper = createWrapper({ propsData: { - keyAttr: 'id', options: options.map(item => item.label), - textAttr: 'label', value: options[4].label, }, }); diff --git a/src/components/forms/auto-complete/RuiAutoComplete.vue b/src/components/forms/auto-complete/RuiAutoComplete.vue index 550bac3..019864a 100644 --- a/src/components/forms/auto-complete/RuiAutoComplete.vue +++ b/src/components/forms/auto-complete/RuiAutoComplete.vue @@ -8,7 +8,7 @@ import type { Ref } from 'vue'; export type T = any; -export type K = Extract; +export type K = string; export type ModelValue = MV | MV[] | null; @@ -73,17 +73,15 @@ const emit = defineEmits<{ const css = useCssModule(); const attrs = useAttrs(); -const { dense, variant, disabled } = toRefs(props); - -const multiple = computed(() => Array.isArray(props.value)); +const { dense, variant, disabled, options } = toRefs(props); const textInput = ref(); -const { focused: searchInputFocused } = useFocus(textInput); +const activator = ref(); +const menuRef = ref(); -const isPrimitiveOptions = computed(() => !(props.options[0] instanceof Object)); +const multiple = computed(() => Array.isArray(props.value)); -const keyProp = computed(() => props.keyAttr ?? 'key' as K); -const textProp = computed(() => props.textAttr ?? 'label' as K); +const { focused: searchInputFocused } = useFocus(textInput); const internalSearch: Ref = ref(''); const debouncedInternalSearch = refDebounced(internalSearch, 200); @@ -96,28 +94,20 @@ watchImmediate(searchInputModel, (search) => { set(internalSearch, search); }); -const mappedOptions = computed<(T extends string ? T : Record)[]>(() => { - const filtered = props.options; - if (!get(isPrimitiveOptions)) - return filtered; - - return filtered.map(item => ({ - [get(keyProp)]: item, - [get(textProp)]: item, - })); -}); - const filteredOptions = computed(() => { const search = get(debouncedInternalSearch); - const optionsVal = get(mappedOptions); + const optionsVal = get(options); if (props.noFilter || !search) return optionsVal; + const keyAttr = props.keyAttr; + const textAttr = props.textAttr; + const usedFilter = props.filter || ((item, search) => { - const keywords = [item[get(keyProp)]]; + const keywords = [keyAttr ? item[keyAttr] : item.toString()]; - if (!get(isPrimitiveOptions)) - keywords.push(item[get(textProp)]); + if (textAttr && typeof item === 'object') + keywords.push(item[textAttr]); return keywords.some(keyword => getTextToken(keyword).includes(getTextToken(search))); }); @@ -132,15 +122,17 @@ function input(value: ModelValue) { const value = computed<(T extends string ? T : Record)[]>({ get: () => { const value = props.value; + const keyAttr = props.keyAttr; const valueToArray = value ? (Array.isArray(value) ? value : [value]) : []; - if (props.keyAttr || get(isPrimitiveOptions)) - return get(mappedOptions).filter(item => valueToArray.includes(item[get(keyProp)])); + if (keyAttr) + return get(options).filter(item => valueToArray.includes(item[keyAttr])); return valueToArray; }, set: (selected: T[]) => { - const selection = props.keyAttr || get(isPrimitiveOptions) ? selected.map(item => item[get(keyProp)]) : selected; + const keyAttr = props.keyAttr; + const selection = keyAttr ? selected.map(item => item[keyAttr]) : selected; if (get(multiple)) return input(selection); @@ -171,17 +163,17 @@ const { getIdentifier, isActiveItem, itemIndexInValue, - menuRef, highlightedIndex, moveHighlight, applyHighlighted, } = useDropdownMenu({ itemHeight: props.itemHeight ?? (props.dense ? 30 : 48), - keyAttr: get(keyProp), - textAttr: get(textProp), + keyAttr: props.keyAttr, + textAttr: props.textAttr, options: filteredOptions, dense, value, + menuRef, setValue, autoSelectFirst: props.autoSelectFirst, }); @@ -260,7 +252,9 @@ watch(focusedValueIndex, (index) => { return; nextTick(() => { - const data = get(value)[index][get(keyProp)]; + const keyAttr = props.keyAttr; + const entry = get(value)[index]; + const data = keyAttr ? entry[keyAttr] : entry; const activeChip = get(activator).querySelector(`[data-value="${data}"]`); activeChip?.focus(); }); @@ -291,7 +285,6 @@ function moveSelectedValueHighlight(next: boolean) { } } -const activator = ref(); const { focused: activatorFocusedWithin } = useFocusWithin(activator); const { focused: menuFocusedWithin } = useFocusWithin(containerProps.ref); const anyFocused = logicOr(activatorFocusedWithin, menuFocusedWithin); @@ -310,7 +303,7 @@ function onInputFocused() { } function clear() { - emit('input', null); + emit('input', Array.isArray(props.value) ? [] : null); } function onInputDeletePressed() { @@ -399,10 +392,10 @@ function onInputDeletePressed() {