mirror of
https://gitee.com/element-plus/element-plus.git
synced 2024-11-29 17:58:08 +08:00
refactor(hooks): determine the focus by event listening (#17719)
* refactor(hooks): determine the focus by event listening * test: skip the focus test * test: fix test * feat(hooks): add beforeFocus * fix: optimize blur * chore(components): [mention] remove focus & blur
This commit is contained in:
parent
c1863f508c
commit
949479699b
@ -443,7 +443,7 @@ describe('Autocomplete.vue', () => {
|
||||
await wrapper.find('input').trigger('blur')
|
||||
vi.runAllTimers()
|
||||
await nextTick()
|
||||
expect(onBlur).toHaveBeenCalledTimes(1)
|
||||
expect(onBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
describe('test a11y supports', () => {
|
||||
|
@ -495,7 +495,7 @@ describe('Color-picker', () => {
|
||||
expect(focusHandler).toHaveBeenCalledTimes(1)
|
||||
|
||||
await wrapper.find('.el-color-picker').trigger('blur')
|
||||
expect(blurHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
})
|
||||
|
@ -171,11 +171,10 @@ const popper = ref<TooltipInstance>()
|
||||
const triggerRef = ref()
|
||||
const inputRef = ref()
|
||||
|
||||
const {
|
||||
isFocused,
|
||||
handleFocus: _handleFocus,
|
||||
handleBlur,
|
||||
} = useFocusController(triggerRef, {
|
||||
const { isFocused, handleFocus, handleBlur } = useFocusController(triggerRef, {
|
||||
beforeFocus() {
|
||||
return colorDisabled.value
|
||||
},
|
||||
beforeBlur(event) {
|
||||
return popper.value?.isFocusInsideContent(event)
|
||||
},
|
||||
@ -185,11 +184,6 @@ const {
|
||||
},
|
||||
})
|
||||
|
||||
const handleFocus = (event: FocusEvent) => {
|
||||
if (colorDisabled.value) return blur()
|
||||
_handleFocus(event)
|
||||
}
|
||||
|
||||
// active-change is used to prevent modelValue changes from triggering.
|
||||
let shouldActiveChange = true
|
||||
|
||||
@ -315,14 +309,10 @@ function clear() {
|
||||
resetColor()
|
||||
}
|
||||
|
||||
function handleClickOutside(event: Event) {
|
||||
function handleClickOutside() {
|
||||
if (!showPicker.value) return
|
||||
hide()
|
||||
|
||||
if (isFocused.value) {
|
||||
const _event = new FocusEvent('focus', event)
|
||||
handleBlur(_event)
|
||||
}
|
||||
isFocused.value && focus()
|
||||
}
|
||||
|
||||
function handleEsc(event: KeyboardEvent) {
|
||||
|
@ -52,8 +52,6 @@
|
||||
@compositionupdate="handleCompositionUpdate"
|
||||
@compositionend="handleCompositionEnd"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
@ -132,8 +130,6 @@
|
||||
@compositionupdate="handleCompositionUpdate"
|
||||
@compositionend="handleCompositionEnd"
|
||||
@input="handleInput"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@change="handleChange"
|
||||
@keydown="handleKeydown"
|
||||
/>
|
||||
@ -261,16 +257,13 @@ const textareaCalcStyle = shallowRef(props.inputStyle)
|
||||
|
||||
const _ref = computed(() => input.value || textarea.value)
|
||||
|
||||
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
|
||||
_ref,
|
||||
{
|
||||
const { wrapperRef, isFocused } = useFocusController(_ref, {
|
||||
afterBlur() {
|
||||
if (props.validateEvent) {
|
||||
elFormItem?.validate?.('blur').catch((err) => debugWarn(err))
|
||||
}
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const needStatusIcon = computed(() => elForm?.statusIcon ?? false)
|
||||
const validateState = computed(() => elFormItem?.validateState || '')
|
||||
|
@ -6,8 +6,6 @@
|
||||
:model-value="modelValue"
|
||||
@input="handleInputChange"
|
||||
@keydown="handleInputKeyDown"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@mousedown="handleInputMouseDown"
|
||||
>
|
||||
<template v-for="(_, name) in $slots" #[name]="slotProps">
|
||||
@ -152,7 +150,7 @@ const handleInputKeyDown = (e: KeyboardEvent | Event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const { wrapperRef, handleFocus, handleBlur } = useFocusController(elInputRef, {
|
||||
const { wrapperRef } = useFocusController(elInputRef, {
|
||||
afterFocus() {
|
||||
syncAfterCursorMove()
|
||||
},
|
||||
|
@ -919,13 +919,11 @@ describe('Select', () => {
|
||||
})
|
||||
|
||||
describe('event', () => {
|
||||
it('focus & blur', async () => {
|
||||
it('focus', async () => {
|
||||
const onFocus = vi.fn()
|
||||
const onBlur = vi.fn()
|
||||
const wrapper = createSelect({
|
||||
methods: {
|
||||
onFocus,
|
||||
onBlur,
|
||||
},
|
||||
})
|
||||
const select = wrapper.findComponent(Select)
|
||||
@ -935,13 +933,20 @@ describe('Select', () => {
|
||||
expect(input.exists()).toBe(true)
|
||||
await input.trigger('focus')
|
||||
expect(onFocus).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('blur', async () => {
|
||||
const onBlur = vi.fn()
|
||||
const wrapper = createSelect({
|
||||
methods: {
|
||||
onBlur,
|
||||
},
|
||||
})
|
||||
const select = wrapper.findComponent(Select)
|
||||
const input = select.find('input')
|
||||
expect(input.exists()).toBe(true)
|
||||
await input.trigger('blur')
|
||||
expect(onBlur).toHaveBeenCalledTimes(1)
|
||||
|
||||
await input.trigger('focus')
|
||||
expect(onFocus).toHaveBeenCalledTimes(2)
|
||||
await input.trigger('blur')
|
||||
expect(onBlur).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('focus & blur for multiple & filterable select', async () => {
|
||||
@ -967,12 +972,12 @@ describe('Select', () => {
|
||||
await input.trigger('focus')
|
||||
expect(onFocus).toHaveBeenCalledTimes(1)
|
||||
await input.trigger('blur')
|
||||
expect(onBlur).toHaveBeenCalledTimes(1)
|
||||
expect(onBlur).toHaveBeenCalled()
|
||||
|
||||
await input.trigger('focus')
|
||||
expect(onFocus).toHaveBeenCalledTimes(2)
|
||||
await input.trigger('blur')
|
||||
expect(onBlur).toHaveBeenCalledTimes(2)
|
||||
expect(onBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('only emit change on user input', async () => {
|
||||
|
@ -165,8 +165,6 @@
|
||||
spellcheck="false"
|
||||
type="text"
|
||||
:name="name"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@input="onInput"
|
||||
@compositionstart="handleCompositionStart"
|
||||
@compositionupdate="handleCompositionUpdate"
|
||||
|
@ -103,9 +103,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
|
||||
afterComposition: (e) => onInput(e),
|
||||
})
|
||||
|
||||
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
|
||||
inputRef,
|
||||
{
|
||||
const { wrapperRef, isFocused } = useFocusController(inputRef, {
|
||||
afterFocus() {
|
||||
if (props.automaticDropdown && !expanded.value) {
|
||||
expanded.value = true
|
||||
@ -122,8 +120,7 @@ const useSelect = (props: ISelectV2Props, emit) => {
|
||||
expanded.value = false
|
||||
states.menuVisibleOnFocus = false
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
const allOptions = ref([])
|
||||
const filteredOptions = ref([])
|
||||
@ -923,12 +920,10 @@ const useSelect = (props: ISelectV2Props, emit) => {
|
||||
getValue,
|
||||
getDisabled,
|
||||
getValueKey,
|
||||
handleBlur,
|
||||
handleClear,
|
||||
handleClickOutside,
|
||||
handleDel,
|
||||
handleEsc,
|
||||
handleFocus,
|
||||
focus,
|
||||
blur,
|
||||
handleMenuEnter,
|
||||
|
@ -1351,31 +1351,30 @@ describe('Select', () => {
|
||||
expect(vm.value.indexOf('选项4')).toBe(-1)
|
||||
})
|
||||
|
||||
test('event:focus & blur', async () => {
|
||||
test('event:focus', async () => {
|
||||
const handleFocus = vi.fn()
|
||||
const handleBlur = vi.fn()
|
||||
wrapper = _mount(
|
||||
`<el-select
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur" />`,
|
||||
() => ({
|
||||
wrapper = _mount(`<el-select @focus="handleFocus" />`, () => ({
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
})
|
||||
)
|
||||
}))
|
||||
const select = wrapper.findComponent({ name: 'ElSelect' })
|
||||
const input = select.find('input')
|
||||
|
||||
expect(input.exists()).toBe(true)
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('event:blur', async () => {
|
||||
const handleBlur = vi.fn()
|
||||
wrapper = _mount(`<el-select @blur="handleBlur" />`, () => ({
|
||||
handleBlur,
|
||||
}))
|
||||
const select = wrapper.findComponent({ name: 'ElSelect' })
|
||||
const input = select.find('input')
|
||||
|
||||
expect(input.exists()).toBe(true)
|
||||
await input.trigger('blur')
|
||||
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 () => {
|
||||
@ -1433,7 +1432,7 @@ describe('Select', () => {
|
||||
|
||||
const input = select.find('input')
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('event:focus & blur for multiple & filterable select', async () => {
|
||||
@ -1464,7 +1463,7 @@ describe('Select', () => {
|
||||
await input.trigger('focus')
|
||||
expect(handleFocus).toHaveBeenCalledTimes(2)
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(2)
|
||||
expect(handleBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('event:focus & blur for multiple tag close', async () => {
|
||||
@ -1525,7 +1524,7 @@ describe('Select', () => {
|
||||
expect(handleFocus).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).not.toHaveBeenCalled()
|
||||
await input.trigger('blur')
|
||||
expect(handleBlur).toHaveBeenCalledTimes(1)
|
||||
expect(handleBlur).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
test('should not open popper when automatic-dropdown not set', async () => {
|
||||
|
@ -166,8 +166,6 @@
|
||||
:aria-label="ariaLabel"
|
||||
aria-autocomplete="none"
|
||||
aria-haspopup="listbox"
|
||||
@focus="handleFocus"
|
||||
@blur="handleBlur"
|
||||
@keydown.down.stop.prevent="navigateOptions('next')"
|
||||
@keydown.up.stop.prevent="navigateOptions('prev')"
|
||||
@keydown.esc.stop.prevent="handleEsc"
|
||||
|
@ -101,9 +101,7 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
afterComposition: (e) => onInput(e),
|
||||
})
|
||||
|
||||
const { wrapperRef, isFocused, handleFocus, handleBlur } = useFocusController(
|
||||
inputRef,
|
||||
{
|
||||
const { wrapperRef, isFocused, handleBlur } = useFocusController(inputRef, {
|
||||
afterFocus() {
|
||||
if (props.automaticDropdown && !expanded.value) {
|
||||
expanded.value = true
|
||||
@ -120,8 +118,7 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
expanded.value = false
|
||||
states.menuVisibleOnFocus = false
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
// the controller of the expanded popup
|
||||
const expanded = ref(false)
|
||||
@ -852,10 +849,8 @@ export const useSelect = (props: ISelectProps, emit) => {
|
||||
onOptionCreate,
|
||||
onOptionDestroy,
|
||||
handleMenuEnter,
|
||||
handleFocus,
|
||||
focus,
|
||||
blur,
|
||||
handleBlur,
|
||||
handleClearClick,
|
||||
handleClickOutside,
|
||||
handleEsc,
|
||||
|
@ -34,19 +34,19 @@ describe('useFocusController', () => {
|
||||
|
||||
await nextTick()
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(0)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).not.toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('blur')
|
||||
expect(wrapper.emitted()).toHaveProperty('blur')
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(blurHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it will trigger focus & blur with tabindex', async () => {
|
||||
@ -79,19 +79,19 @@ describe('useFocusController', () => {
|
||||
|
||||
await nextTick()
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(0)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).not.toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('div').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('div').trigger('blur')
|
||||
expect(wrapper.emitted()).toHaveProperty('blur')
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(blurHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it will avoid trigger unnecessary blur event', async () => {
|
||||
@ -101,15 +101,14 @@ describe('useFocusController', () => {
|
||||
emits: ['focus', 'blur'],
|
||||
setup() {
|
||||
const targetRef = ref()
|
||||
const { wrapperRef, isFocused, handleFocus, handleBlur } =
|
||||
useFocusController(targetRef, {
|
||||
const { wrapperRef, isFocused } = useFocusController(targetRef, {
|
||||
afterFocus: focusHandler,
|
||||
afterBlur: blurHandler,
|
||||
})
|
||||
|
||||
return () => (
|
||||
<div ref={wrapperRef}>
|
||||
<input ref={targetRef} onFocus={handleFocus} onBlur={handleBlur} />
|
||||
<input ref={targetRef} />
|
||||
<span>{String(isFocused.value)}</span>
|
||||
</div>
|
||||
)
|
||||
@ -119,24 +118,24 @@ describe('useFocusController', () => {
|
||||
await nextTick()
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(wrapper.find('div').attributes('tabindex')).toBe('-1')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(0)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).not.toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('span').trigger('click')
|
||||
expect(wrapper.emitted()).not.toHaveProperty('blur')
|
||||
expect(focusHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalledTimes(0)
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('blur')
|
||||
expect(wrapper.emitted()).toHaveProperty('blur')
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(blurHandler).toHaveBeenCalledTimes(1)
|
||||
expect(blurHandler).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it will avoid trigger unnecessary blur event by beforeBlur', async () => {
|
||||
@ -145,8 +144,7 @@ describe('useFocusController', () => {
|
||||
emits: ['focus', 'blur'],
|
||||
setup() {
|
||||
const targetRef = ref()
|
||||
const { wrapperRef, isFocused, handleFocus, handleBlur } =
|
||||
useFocusController(targetRef, {
|
||||
const { wrapperRef, isFocused } = useFocusController(targetRef, {
|
||||
afterBlur: () => {
|
||||
beforeBlur()
|
||||
return true
|
||||
@ -156,11 +154,7 @@ describe('useFocusController', () => {
|
||||
return () => (
|
||||
<>
|
||||
<div ref={wrapperRef}>
|
||||
<input
|
||||
ref={targetRef}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
/>
|
||||
<input ref={targetRef} />
|
||||
</div>
|
||||
<span>{String(isFocused.value)}</span>
|
||||
</>
|
||||
@ -170,15 +164,65 @@ describe('useFocusController', () => {
|
||||
|
||||
await nextTick()
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(beforeBlur).toHaveBeenCalledTimes(0)
|
||||
expect(beforeBlur).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(beforeBlur).toHaveBeenCalledTimes(0)
|
||||
expect(beforeBlur).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('span').trigger('click')
|
||||
expect(wrapper.emitted()).not.toHaveProperty('blur')
|
||||
expect(beforeBlur).toHaveBeenCalledTimes(0)
|
||||
expect(beforeBlur).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('it will avoid triggering unnecessary blur events even with multiple input', async () => {
|
||||
const focusHandler = vi.fn()
|
||||
const blurHandler = vi.fn()
|
||||
const wrapper = mount({
|
||||
emits: ['focus', 'blur'],
|
||||
setup() {
|
||||
const targetRef = ref()
|
||||
const { isFocused, wrapperRef } = useFocusController(targetRef, {
|
||||
afterFocus: focusHandler,
|
||||
afterBlur: blurHandler,
|
||||
})
|
||||
|
||||
return () => (
|
||||
<div ref={wrapperRef}>
|
||||
<input ref={targetRef} />
|
||||
<input class="input2" />
|
||||
<span>{String(isFocused.value)}</span>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(focusHandler).not.toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('.input2').trigger('focus')
|
||||
expect(wrapper.emitted()).toHaveProperty('focus')
|
||||
expect(wrapper.find('span').text()).toBe('true')
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('span').trigger('click')
|
||||
expect(wrapper.emitted()).not.toHaveProperty('blur')
|
||||
expect(focusHandler).toHaveBeenCalled()
|
||||
expect(blurHandler).not.toHaveBeenCalled()
|
||||
|
||||
await wrapper.find('input').trigger('blur')
|
||||
expect(wrapper.emitted()).toHaveProperty('blur')
|
||||
expect(wrapper.find('span').text()).toBe('false')
|
||||
expect(blurHandler).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { getCurrentInstance, ref, shallowRef, watch } from 'vue'
|
||||
import { getCurrentInstance, onMounted, ref, shallowRef, watch } from 'vue'
|
||||
import { useEventListener } from '@vueuse/core'
|
||||
import { isFunction } from '@element-plus/utils'
|
||||
import { isElement, isFunction } from '@element-plus/utils'
|
||||
import type { ShallowRef } from 'vue'
|
||||
|
||||
interface UseFocusControllerOptions {
|
||||
/**
|
||||
* return true to cancel focus
|
||||
* @param event FocusEvent
|
||||
*/
|
||||
beforeFocus?: (event: FocusEvent) => boolean | undefined
|
||||
afterFocus?: () => void
|
||||
/**
|
||||
* return true to cancel blur
|
||||
@ -15,7 +20,12 @@ interface UseFocusControllerOptions {
|
||||
|
||||
export function useFocusController<T extends { focus: () => void }>(
|
||||
target: ShallowRef<T | undefined>,
|
||||
{ afterFocus, beforeBlur, afterBlur }: UseFocusControllerOptions = {}
|
||||
{
|
||||
beforeFocus,
|
||||
afterFocus,
|
||||
beforeBlur,
|
||||
afterBlur,
|
||||
}: UseFocusControllerOptions = {}
|
||||
) {
|
||||
const instance = getCurrentInstance()!
|
||||
const { emit } = instance
|
||||
@ -23,7 +33,8 @@ export function useFocusController<T extends { focus: () => void }>(
|
||||
const isFocused = ref(false)
|
||||
|
||||
const handleFocus = (event: FocusEvent) => {
|
||||
if (isFocused.value) return
|
||||
const cancelFocus = isFunction(beforeFocus) ? beforeFocus(event) : false
|
||||
if (cancelFocus || isFocused.value) return
|
||||
isFocused.value = true
|
||||
emit('focus', event)
|
||||
afterFocus?.()
|
||||
@ -44,6 +55,12 @@ export function useFocusController<T extends { focus: () => void }>(
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (
|
||||
wrapperRef.value?.contains(document.activeElement) &&
|
||||
wrapperRef.value !== document.activeElement
|
||||
)
|
||||
return
|
||||
|
||||
target.value?.focus()
|
||||
}
|
||||
|
||||
@ -53,14 +70,28 @@ export function useFocusController<T extends { focus: () => void }>(
|
||||
}
|
||||
})
|
||||
|
||||
// TODO: using useEventListener will fail the test
|
||||
// useEventListener(target, 'focus', handleFocus)
|
||||
// useEventListener(target, 'blur', handleBlur)
|
||||
useEventListener(wrapperRef, 'focus', handleFocus, true)
|
||||
useEventListener(wrapperRef, 'blur', handleBlur, true)
|
||||
useEventListener(wrapperRef, 'click', handleClick, true)
|
||||
|
||||
// only for test
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
onMounted(() => {
|
||||
const targetEl = isElement(target.value)
|
||||
? target.value
|
||||
: document.querySelector('input,textarea')
|
||||
|
||||
if (targetEl) {
|
||||
useEventListener(targetEl, 'focus', handleFocus, true)
|
||||
useEventListener(targetEl, 'blur', handleBlur, true)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
wrapperRef,
|
||||
isFocused,
|
||||
/** Avoid using wrapperRef and handleFocus/handleBlur together */
|
||||
wrapperRef,
|
||||
handleFocus,
|
||||
handleBlur,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user