mirror of
https://github.com/element-plus/element-plus.git
synced 2025-02-17 11:49:41 +08:00
fix(components): [select] abnormal focus when click tags (#13699)
* fix(components): [select] abnormal focus when click tags closed #13665 * fix(components): [select] clearable * chore(components): [select] remove console * fix: the setTimeout function may bring some side effects * fix: remove role * test(components): [select] add some test
This commit is contained in:
parent
3ba7babc74
commit
0109ab6195
@ -7,7 +7,6 @@ import { ArrowDown, CaretTop, CircleClose } from '@element-plus/icons-vue'
|
||||
import { usePopperContainerId } from '@element-plus/hooks'
|
||||
import { hasClass } from '@element-plus/utils'
|
||||
import { ElFormItem } from '@element-plus/components/form'
|
||||
import sleep from '@element-plus/test-utils/sleep'
|
||||
import Select from '../src/select.vue'
|
||||
import Group from '../src/option-group.vue'
|
||||
import Option from '../src/option.vue'
|
||||
@ -1222,10 +1221,72 @@ describe('Select', () => {
|
||||
|
||||
expect(input.exists()).toBe(true)
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalled()
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
await input.trigger('blur')
|
||||
await sleep(0)
|
||||
expect(handleBlur).toHaveBeenCalled()
|
||||
expect(handleBlur).toHaveBeenCalledTimes(1)
|
||||
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(2)
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('event:focus & blur for clearable & filterable', async () => {
|
||||
const handleFocus = vi.fn()
|
||||
const handleBlur = vi.fn()
|
||||
wrapper = _mount(
|
||||
`<el-select
|
||||
v-model="value"
|
||||
clearable
|
||||
filterable
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>`,
|
||||
() => ({
|
||||
options: [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
],
|
||||
value: '选项1',
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
})
|
||||
)
|
||||
|
||||
const select = wrapper.findComponent({ name: 'ElSelect' })
|
||||
const vm = wrapper.vm as any
|
||||
const selectVm = select.vm as any
|
||||
selectVm.inputHovering = true
|
||||
await selectVm.$nextTick()
|
||||
|
||||
const iconClear = wrapper.findComponent(CircleClose)
|
||||
expect(iconClear.exists()).toBe(true)
|
||||
await iconClear.trigger('click')
|
||||
expect(vm.value).toBe('')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).not.toHaveBeenCalled()
|
||||
|
||||
const options = getOptions()
|
||||
options[0].click()
|
||||
await nextTick()
|
||||
expect(vm.value).toBe('选项1')
|
||||
selectVm.inputHovering = true
|
||||
await iconClear.trigger('click')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).not.toHaveBeenCalled()
|
||||
|
||||
const input = select.find('input')
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('event:focus & blur for multiple & filterable select', async () => {
|
||||
@ -1251,8 +1312,73 @@ describe('Select', () => {
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalled()
|
||||
await input.trigger('blur')
|
||||
await sleep(0)
|
||||
expect(handleBlur).toHaveBeenCalled()
|
||||
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(2)
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('event:focus & blur for multiple tag close', async () => {
|
||||
const handleFocus = vi.fn()
|
||||
const handleBlur = vi.fn()
|
||||
wrapper = _mount(
|
||||
`<el-select
|
||||
v-model="value"
|
||||
multiple
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:label="item.label"
|
||||
:key="item.value"
|
||||
:value="item.value">
|
||||
<p>{{item.label}} {{item.value}}</p>
|
||||
</el-option>
|
||||
</el-select>`,
|
||||
() => ({
|
||||
options: [
|
||||
{
|
||||
value: '选项1',
|
||||
label: '黄金糕',
|
||||
},
|
||||
{
|
||||
value: '选项2',
|
||||
label: '双皮奶',
|
||||
},
|
||||
{
|
||||
value: '选项3',
|
||||
label: '蚵仔煎',
|
||||
},
|
||||
{
|
||||
value: '选项4',
|
||||
label: '龙须面',
|
||||
},
|
||||
{
|
||||
value: '选项5',
|
||||
label: '北京烤鸭',
|
||||
},
|
||||
],
|
||||
value: ['选项1', '选项2'],
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
})
|
||||
)
|
||||
|
||||
const select = wrapper.findComponent({ name: 'ElSelect' })
|
||||
const input = select.find('input')
|
||||
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
const tagCloseIcons = wrapper.findAll('.el-tag__close')
|
||||
await tagCloseIcons[1].trigger('click')
|
||||
await tagCloseIcons[0].trigger('click')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).not.toHaveBeenCalled()
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('should not open popper when automatic-dropdown not set', async () => {
|
||||
|
@ -33,8 +33,10 @@
|
||||
<div
|
||||
v-if="multiple"
|
||||
ref="tags"
|
||||
tabindex="-1"
|
||||
:class="tagsKls"
|
||||
:style="selectTagsStyle"
|
||||
@click="focus"
|
||||
>
|
||||
<transition
|
||||
v-if="collapseTags && selected.length"
|
||||
@ -279,7 +281,7 @@ import {
|
||||
import { useResizeObserver } from '@vueuse/core'
|
||||
import { placements } from '@popperjs/core'
|
||||
import { ClickOutside } from '@element-plus/directives'
|
||||
import { useFocus, useLocale, useNamespace } from '@element-plus/hooks'
|
||||
import { useLocale, useNamespace } from '@element-plus/hooks'
|
||||
import ElInput from '@element-plus/components/input'
|
||||
import ElTooltip, {
|
||||
useTooltipContentProps,
|
||||
@ -462,6 +464,7 @@ export default defineComponent({
|
||||
onOptionDestroy,
|
||||
handleMenuEnter,
|
||||
handleFocus,
|
||||
focus,
|
||||
blur,
|
||||
handleBlur,
|
||||
handleClearClick,
|
||||
@ -490,8 +493,6 @@ export default defineComponent({
|
||||
collapseTagList,
|
||||
} = useSelect(props, states, ctx)
|
||||
|
||||
const { focus } = useFocus(reference)
|
||||
|
||||
const {
|
||||
inputWidth,
|
||||
selected,
|
||||
@ -682,6 +683,7 @@ export default defineComponent({
|
||||
handleComposition,
|
||||
handleMenuEnter,
|
||||
handleFocus,
|
||||
focus,
|
||||
blur,
|
||||
handleBlur,
|
||||
handleClearClick,
|
||||
@ -692,7 +694,6 @@ export default defineComponent({
|
||||
getValueKey,
|
||||
navigateOptions,
|
||||
dropMenuVisible,
|
||||
focus,
|
||||
|
||||
reference,
|
||||
input,
|
||||
|
@ -57,9 +57,9 @@ export function useSelectStates(props) {
|
||||
isOnComposition: false,
|
||||
prefixWidth: 11,
|
||||
mouseEnter: false,
|
||||
focused: false,
|
||||
})
|
||||
}
|
||||
let ignoreFocusEvent = false
|
||||
|
||||
type States = ReturnType<typeof useSelectStates>
|
||||
|
||||
@ -270,7 +270,6 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
props.remoteMethod('')
|
||||
}
|
||||
}
|
||||
input.value && input.value.blur()
|
||||
states.query = ''
|
||||
states.previousQuery = null
|
||||
states.selectedLabel = ''
|
||||
@ -637,6 +636,7 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
ctx.emit('remove-tag', tag.value)
|
||||
}
|
||||
event.stopPropagation()
|
||||
focus()
|
||||
}
|
||||
|
||||
const deleteSelected = (event) => {
|
||||
@ -652,6 +652,7 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
states.hoverIndex = -1
|
||||
states.visible = false
|
||||
ctx.emit('clear')
|
||||
focus()
|
||||
}
|
||||
|
||||
const handleOptionSelect = (option) => {
|
||||
@ -784,16 +785,23 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
}
|
||||
|
||||
const handleFocus = (event: FocusEvent) => {
|
||||
if (!ignoreFocusEvent) {
|
||||
if (!states.focused) {
|
||||
if (props.automaticDropdown || props.filterable) {
|
||||
if (props.filterable && !states.visible) {
|
||||
states.menuVisibleOnFocus = true
|
||||
}
|
||||
states.visible = true
|
||||
}
|
||||
states.focused = true
|
||||
ctx.emit('focus', event)
|
||||
}
|
||||
}
|
||||
|
||||
const focus = () => {
|
||||
if (states.visible) {
|
||||
;(input.value || reference.value)?.focus()
|
||||
} else {
|
||||
ignoreFocusEvent = false
|
||||
reference.value?.focus()
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,19 +812,19 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
}
|
||||
|
||||
const handleBlur = (event: FocusEvent) => {
|
||||
setTimeout(() => {
|
||||
// validate current focus event is inside el-tooltip-content or el-select
|
||||
// if so, ignore the blur event and the next focus event
|
||||
if (
|
||||
tooltipRef.value?.isFocusInsideContent() ||
|
||||
selectWrapper.value?.contains(event.relatedTarget)
|
||||
) {
|
||||
ignoreFocusEvent = true
|
||||
return
|
||||
}
|
||||
states.visible && handleClose()
|
||||
ctx.emit('blur', event)
|
||||
})
|
||||
// validate current focus event is inside el-tooltip-content or el-select
|
||||
// if so, ignore the blur event.
|
||||
if (
|
||||
tooltipRef.value?.isFocusInsideContent(event) ||
|
||||
tagTooltipRef.value?.isFocusInsideContent(event) ||
|
||||
selectWrapper.value?.contains(event.relatedTarget)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
states.visible && handleClose()
|
||||
states.focused = false
|
||||
ctx.emit('blur', event)
|
||||
}
|
||||
|
||||
const handleClearClick = (event: Event) => {
|
||||
@ -847,9 +855,7 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
states.visible = !states.visible
|
||||
}
|
||||
}
|
||||
if (states.visible) {
|
||||
;(input.value || reference.value)?.focus()
|
||||
}
|
||||
focus()
|
||||
}
|
||||
}
|
||||
|
||||
@ -954,6 +960,7 @@ export const useSelect = (props, states: States, ctx) => {
|
||||
onOptionDestroy,
|
||||
handleMenuEnter,
|
||||
handleFocus,
|
||||
focus,
|
||||
blur,
|
||||
handleBlur,
|
||||
handleClearClick,
|
||||
|
@ -154,10 +154,12 @@ watch(
|
||||
}
|
||||
)
|
||||
|
||||
const isFocusInsideContent = () => {
|
||||
const isFocusInsideContent = (event?: FocusEvent) => {
|
||||
const popperContent: HTMLElement | undefined =
|
||||
contentRef.value?.contentRef?.popperContentRef
|
||||
return popperContent && popperContent.contains(document.activeElement)
|
||||
const activeElement = (event?.relatedTarget as Node) || document.activeElement
|
||||
|
||||
return popperContent && popperContent.contains(activeElement)
|
||||
}
|
||||
|
||||
onDeactivated(() => open.value && hide())
|
||||
|
Loading…
Reference in New Issue
Block a user