Files
element-plus/packages/hooks/use-focus-controller/index.ts
sea 1f7cdc3915 chore: only show warnings in dev mode (#22675)
* chore: only show warnings in dev mode

* chore: test

* chore: test2

* chore: test3

* chore: test3

* chore: update 4

* chore: update

* chore: update

* chore: update

* chore: update

Co-authored-by: btea <2356281422@qq.com>

* chore: add security checks to all process

---------

Co-authored-by: btea <2356281422@qq.com>
2025-11-10 09:25:01 +08:00

117 lines
2.9 KiB
TypeScript

import {
getCurrentInstance,
onMounted,
ref,
shallowRef,
unref,
watch,
} from 'vue'
import { useEventListener } from '@vueuse/core'
import { isElement, isFocusable, isFunction } from '@element-plus/utils'
import type { ShallowRef } from 'vue'
import type { MaybeRef } from '@vueuse/core'
interface UseFocusControllerOptions {
disabled?: MaybeRef<boolean>
/**
* return true to cancel focus
* @param event FocusEvent
*/
beforeFocus?: (event: FocusEvent) => boolean | undefined
afterFocus?: () => void
/**
* return true to cancel blur
* @param event FocusEvent
*/
beforeBlur?: (event: FocusEvent) => boolean | undefined
afterBlur?: () => void
}
export function useFocusController<T extends { focus: () => void }>(
target: ShallowRef<T | undefined>,
{
disabled,
beforeFocus,
afterFocus,
beforeBlur,
afterBlur,
}: UseFocusControllerOptions = {}
) {
const instance = getCurrentInstance()!
const { emit } = instance
const wrapperRef = shallowRef<HTMLElement>()
const isFocused = ref(false)
const handleFocus = (event: FocusEvent) => {
const cancelFocus = isFunction(beforeFocus) ? beforeFocus(event) : false
if (unref(disabled) || isFocused.value || cancelFocus) return
isFocused.value = true
emit('focus', event)
afterFocus?.()
}
const handleBlur = (event: FocusEvent) => {
const cancelBlur = isFunction(beforeBlur) ? beforeBlur(event) : false
if (
unref(disabled) ||
(event.relatedTarget &&
wrapperRef.value?.contains(event.relatedTarget as Node)) ||
cancelBlur
)
return
isFocused.value = false
emit('blur', event)
afterBlur?.()
}
const handleClick = (event: Event) => {
if (
unref(disabled) ||
isFocusable(event.target as HTMLElement) ||
(wrapperRef.value?.contains(document.activeElement) &&
wrapperRef.value !== document.activeElement)
)
return
target.value?.focus()
}
watch([wrapperRef, () => unref(disabled)], ([el, disabled]) => {
if (!el) return
if (disabled) {
el.removeAttribute('tabindex')
} else {
el.setAttribute('tabindex', '-1')
}
})
useEventListener(wrapperRef, 'focus', handleFocus, true)
useEventListener(wrapperRef, 'blur', handleBlur, true)
useEventListener(wrapperRef, 'click', handleClick, true)
// only for test
if (typeof process !== 'undefined' && 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 {
isFocused,
/** Avoid using wrapperRef and handleFocus/handleBlur together */
wrapperRef,
handleFocus,
handleBlur,
}
}