pref: improve hover and pre-selection state for formkit select (#6607)

#### What type of PR is this?

/kind improvement
/area ui
/milestone 2.20.x

#### What this PR does / why we need it:

优化了 Formkit 中的 selec 组件,使得其使用快捷键与鼠标选中的预选状态始终只有一个。且在多选状态下按回车时,不再预选跳转至第一个选项。

#### How to test it?

在多选状态下测试使用键盘进行预选及使用鼠标 hover 进行预选的状态是否符合预期。

#### Which issue(s) this PR fixes:

Fixes #6600

#### Does this PR introduce a user-facing change?
```release-note
优化 Formkit select 组件在多选状态下的待选中状态。
```
pull/6651/head
Takagi 2024-09-13 10:22:26 +08:00 committed by GitHub
parent 7ed859cefb
commit 25eec1ec4f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 51 additions and 3 deletions

View File

@ -27,6 +27,8 @@ const emit = defineEmits<{
const selectedIndex = ref<number>(0); const selectedIndex = ref<number>(0);
const selectOptionRef = ref<HTMLElement>(); const selectOptionRef = ref<HTMLElement>();
const oldEvent = ref<MouseEvent | undefined>();
const isCursorHidden = ref(false);
const selectedValues = computed(() => const selectedValues = computed(() =>
props.selectedOptions?.map((option) => option.value) props.selectedOptions?.map((option) => option.value)
@ -46,6 +48,9 @@ const getSelectedIndex = () => {
}; };
const handleKeydown = (event: KeyboardEvent) => { const handleKeydown = (event: KeyboardEvent) => {
if (selectOptionRef.value) {
isCursorHidden.value = true;
}
const key = event.key; const key = event.key;
if (key === "ArrowUp") { if (key === "ArrowUp") {
selectedIndex.value = selectedIndex.value =
@ -138,7 +143,7 @@ const handleOptionScroll = (state: UseScrollReturn) => {
}; };
watch( watch(
() => props.options, props.options,
() => { () => {
selectedIndex.value = getSelectedIndex(); selectedIndex.value = getSelectedIndex();
}, },
@ -153,6 +158,48 @@ watch(
handleScrollIntoView(); handleScrollIntoView();
} }
); );
/**
* check if cursor is changed
*
* @param newEvent
* @param oldEvent
*/
const isCursorChanged = (
newEvent: MouseEvent,
oldEvent: MouseEvent | undefined
) => {
if (!oldEvent) {
return true;
}
return (
newEvent.screenX !== oldEvent.screenX ||
newEvent.screenY !== oldEvent.screenY
);
};
const handleMouseover = (event: MouseEvent) => {
if (isCursorHidden.value) {
if (!oldEvent.value) {
oldEvent.value = event;
}
if (isCursorChanged(event, oldEvent.value)) {
isCursorHidden.value = false;
oldEvent.value = undefined;
}
return;
}
const target = event.target as HTMLElement;
if (
target.classList.contains("select-option-item") &&
target instanceof HTMLElement
) {
const parentElement = target.parentElement as HTMLElement;
const index = Array.from(parentElement.children).indexOf(target);
selectedIndex.value = index;
}
};
</script> </script>
<template> <template>
@ -160,17 +207,18 @@ watch(
id="select-option" id="select-option"
ref="selectOptionRef" ref="selectOptionRef"
v-scroll="[handleOptionScroll, { throttle: 10 }]" v-scroll="[handleOptionScroll, { throttle: 10 }]"
class="select max-h-64 cursor-pointer overflow-y-auto p-1.5" class="select max-h-64 overflow-y-auto p-1.5"
:class="[isCursorHidden ? 'cursor-none' : 'cursor-pointer']"
role="list" role="list"
tabindex="-1" tabindex="-1"
@keydown="handleKeydown" @keydown="handleKeydown"
@mouseover="handleMouseover"
> >
<template v-for="(option, index) in options" :key="option.value"> <template v-for="(option, index) in options" :key="option.value">
<SelectOptionItem <SelectOptionItem
class="select-option-item" class="select-option-item"
:option="option" :option="option"
:class="{ :class="{
'hover:bg-zinc-100': !isDisabled(option),
'bg-zinc-100': !isDisabled(option) && selectedIndex === index, 'bg-zinc-100': !isDisabled(option) && selectedIndex === index,
'selected !bg-zinc-200/60': 'selected !bg-zinc-200/60':
selectedValues && selectedValues.includes(option.value), selectedValues && selectedValues.includes(option.value),