Skip to content
This repository was archived by the owner on Sep 30, 2024. It is now read-only.

Commit a03f5a5

Browse files
committed
fix(Autocomplete): prevent input to be updated while menu still opened
1 parent ffd698f commit a03f5a5

File tree

2 files changed

+78
-70
lines changed

2 files changed

+78
-70
lines changed

src/components/forms/auto-complete/RuiAutoComplete.spec.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,14 @@ describe('autocomplete', () => {
164164
// Even if the options changed, the search value should not be touch as long as the focus still there, so the UX is not breaking
165165
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Greece');
166166

167-
// Only after the search value is blurred, the search value can be reset
167+
// Still not supposed to change the search value
168+
const menu = document.body.querySelector('div[role=menu]') as HTMLDivElement;
169+
menu.focus();
170+
await nextTick();
171+
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Greece');
172+
173+
// Only after nothing is focused anymore, the search value can be reset
174+
menu.blur();
168175
(wrapper.find('input').element as HTMLInputElement).blur();
169176
await nextTick();
170177
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('');

src/components/forms/auto-complete/RuiAutoComplete.vue

Lines changed: 70 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,12 @@ const {
106106
107107
const textInput = ref();
108108
const activator = ref();
109-
const noDataContainer = ref();
110109
const menuRef = ref();
110+
const menuWrapperRef = ref();
111+
112+
const { focused: activatorFocusedWithin } = useFocusWithin(activator);
113+
const { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);
114+
const anyFocused = logicOr(activatorFocusedWithin, menuWrapperFocusedWithin);
111115
112116
const multiple = computed(() => Array.isArray(props.value));
113117
@@ -186,14 +190,14 @@ const value = computed<(T extends string ? T : Record<K, T>)[]>({
186190
});
187191
188192
if (multipleVal || filtered.length === 0) {
189-
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused))
193+
if (get(shouldApplyValueAsSearch) && !get(anyFocused))
190194
updateInternalSearch();
191195
192196
return filtered;
193197
}
194198
else {
195199
const val = filtered[0];
196-
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused))
200+
if (get(shouldApplyValueAsSearch) && !get(anyFocused))
197201
updateInternalSearch(getText(val));
198202
199203
return [val];
@@ -210,7 +214,7 @@ const value = computed<(T extends string ? T : Record<K, T>)[]>({
210214
return filtered.push(val);
211215
});
212216
213-
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused)) {
217+
if (get(shouldApplyValueAsSearch) && !get(anyFocused)) {
214218
if (filtered.length > 0)
215219
updateInternalSearch(filtered[0]);
216220
else
@@ -380,11 +384,6 @@ function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean) {
380384
}
381385
}
382386
383-
const { focused: activatorFocusedWithin } = useFocusWithin(activator);
384-
const { focused: noDataContainerFocusedWithin } = useFocusWithin(noDataContainer);
385-
const { focused: menuFocusedWithin } = useFocusWithin(containerProps.ref);
386-
const anyFocused = logicOr(activatorFocusedWithin, noDataContainerFocusedWithin, menuFocusedWithin);
387-
388387
function textValueToProperValue(val: any): T {
389388
const keyAttr = props.keyAttr;
390389
if (!keyAttr)
@@ -428,7 +427,8 @@ function onInputFocused() {
428427
if (get(shouldApplyValueAsSearch))
429428
get(textInput)?.select();
430429
431-
set(justOpened, true);
430+
if (!get(isOpen))
431+
set(justOpened, true);
432432
}
433433
434434
function clear() {
@@ -523,7 +523,7 @@ function arrowClicked(event: any) {
523523
const renderedOptions = ref([]);
524524
525525
const menuMinHeight: ComputedRef<number> = computed(() => {
526-
const renderedOptionsData = get(renderedOptions).slice(0, 5);
526+
const renderedOptionsData = get(renderedOptions).slice(0, Math.min(5, get(renderedData).length));
527527
return renderedOptionsData.reduce((currentValue, item: typeof RuiButton) => currentValue + item.$el.offsetHeight, 0);
528528
});
529529
@@ -714,72 +714,73 @@ defineExpose({
714714
</slot>
715715
</template>
716716
<template #default="{ width }">
717-
<div
718-
v-if="optionsWithSelectedHidden.length > 0"
719-
:class="[css.menu, menuClass]"
720-
:style="{ width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }"
721-
v-bind="virtualContainerProps"
722-
@scroll="containerProps.onScroll"
723-
@keydown.up.prevent="moveHighlight(true)"
724-
@keydown.down.prevent="moveHighlight(false)"
725-
>
717+
<div ref="menuWrapperRef">
726718
<div
727-
v-bind="wrapperProps"
728-
ref="menuRef"
719+
v-if="optionsWithSelectedHidden.length > 0"
720+
:class="[css.menu, menuClass]"
721+
:style="{ width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }"
722+
v-bind="virtualContainerProps"
723+
@scroll="containerProps.onScroll"
724+
@keydown.up.prevent="moveHighlight(true)"
725+
@keydown.down.prevent="moveHighlight(false)"
729726
>
730-
<RuiButton
731-
v-for="({ item, index }) in renderedData"
732-
ref="renderedOptions"
733-
:key="getIdentifier(item)"
734-
:active="isActiveItem(item)"
735-
:size="dense ? 'sm' : undefined"
736-
:value="getIdentifier(item)"
737-
tabindex="0"
738-
variant="list"
739-
:class="{
740-
highlighted: highlightedIndex === index,
741-
[css.highlighted]: highlightedIndex === index,
742-
[css.active]: isActiveItem(item),
743-
}"
744-
@input="setValue(item, index)"
745-
@mousedown="highlightedIndex = index"
727+
<div
728+
v-bind="wrapperProps"
729+
ref="menuRef"
746730
>
747-
<template #prepend>
748-
<slot
749-
name="item.prepend"
750-
v-bind="{ disabled, item, active: isActiveItem(item) }"
751-
/>
752-
</template>
753-
<slot
754-
name="item"
755-
v-bind="{ disabled, item, active: isActiveItem(item) }"
731+
<RuiButton
732+
v-for="({ item, index }) in renderedData"
733+
ref="renderedOptions"
734+
:key="getIdentifier(item)"
735+
:active="isActiveItem(item)"
736+
:size="dense ? 'sm' : undefined"
737+
:value="getIdentifier(item)"
738+
tabindex="0"
739+
variant="list"
740+
:class="{
741+
highlighted: highlightedIndex === index,
742+
[css.highlighted]: highlightedIndex === index,
743+
[css.active]: isActiveItem(item),
744+
}"
745+
@input="setValue(item, index)"
746+
@mousedown="highlightedIndex = index"
756747
>
757-
{{ getText(item) }}
758-
</slot>
759-
<template #append>
748+
<template #prepend>
749+
<slot
750+
name="item.prepend"
751+
v-bind="{ disabled, item, active: isActiveItem(item) }"
752+
/>
753+
</template>
760754
<slot
761-
name="item.append"
755+
name="item"
762756
v-bind="{ disabled, item, active: isActiveItem(item) }"
763-
/>
764-
</template>
765-
</RuiButton>
757+
>
758+
{{ getText(item) }}
759+
</slot>
760+
<template #append>
761+
<slot
762+
name="item.append"
763+
v-bind="{ disabled, item, active: isActiveItem(item) }"
764+
/>
765+
</template>
766+
</RuiButton>
767+
</div>
766768
</div>
767-
</div>
768769

769-
<div
770-
v-else-if="!hideNoData"
771-
ref="noDataContainer"
772-
:style="{ width: `${width}px`, minWidth: menuWidth }"
773-
:class="menuClass"
774-
>
775-
<slot name="no-data">
776-
<div
777-
v-if="!customValue"
778-
class="p-4"
779-
>
780-
{{ noDataText }}
781-
</div>
782-
</slot>
770+
<div
771+
v-else-if="!hideNoData"
772+
:style="{ width: `${width}px`, minWidth: menuWidth }"
773+
:class="menuClass"
774+
>
775+
<slot name="no-data">
776+
<div
777+
v-if="!customValue"
778+
class="p-4"
779+
>
780+
{{ noDataText }}
781+
</div>
782+
</slot>
783+
</div>
783784
</div>
784785
</template>
785786
</RuiMenu>

0 commit comments

Comments
 (0)