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

Commit

Permalink
fix(Autocomplete): prevent input to be updated while menu still opened
Browse files Browse the repository at this point in the history
  • Loading branch information
lukicenturi authored and kelsos committed Jul 23, 2024
1 parent ffd698f commit 5ce8379
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 70 deletions.
9 changes: 8 additions & 1 deletion src/components/forms/auto-complete/RuiAutoComplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,14 @@ describe('autocomplete', () => {
// 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
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Greece');

// Only after the search value is blurred, the search value can be reset
// Still not supposed to change the search value
const menu = document.body.querySelector('div[role=menu]') as HTMLDivElement;
menu.focus();
await nextTick();
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('Greece');

// Only after nothing is focused anymore, the search value can be reset
menu.blur();
(wrapper.find('input').element as HTMLInputElement).blur();
await nextTick();
expect((wrapper.find('input').element as HTMLInputElement).value).toBe('');
Expand Down
139 changes: 70 additions & 69 deletions src/components/forms/auto-complete/RuiAutoComplete.vue
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,12 @@ const {
const textInput = ref();
const activator = ref();
const noDataContainer = ref();
const menuRef = ref();
const menuWrapperRef = ref();
const { focused: activatorFocusedWithin } = useFocusWithin(activator);
const { focused: menuWrapperFocusedWithin } = useFocusWithin(menuWrapperRef);
const anyFocused = logicOr(activatorFocusedWithin, menuWrapperFocusedWithin);
const multiple = computed(() => Array.isArray(props.value));
Expand Down Expand Up @@ -186,14 +190,14 @@ const value = computed<(T extends string ? T : Record<K, T>)[]>({
});
if (multipleVal || filtered.length === 0) {
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused))
if (get(shouldApplyValueAsSearch) && !get(anyFocused))
updateInternalSearch();
return filtered;
}
else {
const val = filtered[0];
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused))
if (get(shouldApplyValueAsSearch) && !get(anyFocused))
updateInternalSearch(getText(val));
return [val];
Expand All @@ -210,7 +214,7 @@ const value = computed<(T extends string ? T : Record<K, T>)[]>({
return filtered.push(val);
});
if (get(shouldApplyValueAsSearch) && !get(searchInputFocused)) {
if (get(shouldApplyValueAsSearch) && !get(anyFocused)) {
if (filtered.length > 0)
updateInternalSearch(filtered[0]);
else
Expand Down Expand Up @@ -380,11 +384,6 @@ function moveSelectedValueHighlight(event: KeyboardEvent, next: boolean) {
}
}
const { focused: activatorFocusedWithin } = useFocusWithin(activator);
const { focused: noDataContainerFocusedWithin } = useFocusWithin(noDataContainer);
const { focused: menuFocusedWithin } = useFocusWithin(containerProps.ref);
const anyFocused = logicOr(activatorFocusedWithin, noDataContainerFocusedWithin, menuFocusedWithin);
function textValueToProperValue(val: any): T {
const keyAttr = props.keyAttr;
if (!keyAttr)
Expand Down Expand Up @@ -428,7 +427,8 @@ function onInputFocused() {
if (get(shouldApplyValueAsSearch))
get(textInput)?.select();
set(justOpened, true);
if (!get(isOpen))
set(justOpened, true);
}
function clear() {
Expand Down Expand Up @@ -523,7 +523,7 @@ function arrowClicked(event: any) {
const renderedOptions = ref([]);
const menuMinHeight: ComputedRef<number> = computed(() => {
const renderedOptionsData = get(renderedOptions).slice(0, 5);
const renderedOptionsData = get(renderedOptions).slice(0, Math.min(5, get(renderedData).length));
return renderedOptionsData.reduce((currentValue, item: typeof RuiButton) => currentValue + item.$el.offsetHeight, 0);
});
Expand Down Expand Up @@ -714,72 +714,73 @@ defineExpose({
</slot>
</template>
<template #default="{ width }">
<div
v-if="optionsWithSelectedHidden.length > 0"
:class="[css.menu, menuClass]"
:style="{ width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }"
v-bind="virtualContainerProps"
@scroll="containerProps.onScroll"
@keydown.up.prevent="moveHighlight(true)"
@keydown.down.prevent="moveHighlight(false)"
>
<div ref="menuWrapperRef">
<div
v-bind="wrapperProps"
ref="menuRef"
v-if="optionsWithSelectedHidden.length > 0"
:class="[css.menu, menuClass]"
:style="{ width: `${width}px`, minWidth: menuWidth, minHeight: `${menuMinHeight}px` }"
v-bind="virtualContainerProps"
@scroll="containerProps.onScroll"
@keydown.up.prevent="moveHighlight(true)"
@keydown.down.prevent="moveHighlight(false)"
>
<RuiButton
v-for="({ item, index }) in renderedData"
ref="renderedOptions"
:key="getIdentifier(item)"
:active="isActiveItem(item)"
:size="dense ? 'sm' : undefined"
:value="getIdentifier(item)"
tabindex="0"
variant="list"
:class="{
highlighted: highlightedIndex === index,
[css.highlighted]: highlightedIndex === index,
[css.active]: isActiveItem(item),
}"
@input="setValue(item, index)"
@mousedown="highlightedIndex = index"
<div
v-bind="wrapperProps"
ref="menuRef"
>
<template #prepend>
<slot
name="item.prepend"
v-bind="{ disabled, item, active: isActiveItem(item) }"
/>
</template>
<slot
name="item"
v-bind="{ disabled, item, active: isActiveItem(item) }"
<RuiButton
v-for="({ item, index }) in renderedData"
ref="renderedOptions"
:key="getIdentifier(item)"
:active="isActiveItem(item)"
:size="dense ? 'sm' : undefined"
:value="getIdentifier(item)"
tabindex="0"
variant="list"
:class="{
highlighted: highlightedIndex === index,
[css.highlighted]: highlightedIndex === index,
[css.active]: isActiveItem(item),
}"
@input="setValue(item, index)"
@mousedown="highlightedIndex = index"
>
{{ getText(item) }}
</slot>
<template #append>
<template #prepend>
<slot
name="item.prepend"
v-bind="{ disabled, item, active: isActiveItem(item) }"
/>
</template>
<slot
name="item.append"
name="item"
v-bind="{ disabled, item, active: isActiveItem(item) }"
/>
</template>
</RuiButton>
>
{{ getText(item) }}
</slot>
<template #append>
<slot
name="item.append"
v-bind="{ disabled, item, active: isActiveItem(item) }"
/>
</template>
</RuiButton>
</div>
</div>
</div>

<div
v-else-if="!hideNoData"
ref="noDataContainer"
:style="{ width: `${width}px`, minWidth: menuWidth }"
:class="menuClass"
>
<slot name="no-data">
<div
v-if="!customValue"
class="p-4"
>
{{ noDataText }}
</div>
</slot>
<div
v-else-if="!hideNoData"
:style="{ width: `${width}px`, minWidth: menuWidth }"
:class="menuClass"
>
<slot name="no-data">
<div
v-if="!customValue"
class="p-4"
>
{{ noDataText }}
</div>
</slot>
</div>
</div>
</template>
</RuiMenu>
Expand Down

0 comments on commit 5ce8379

Please sign in to comment.