From b69320c0707e416286660ce2e402713e5e894f96 Mon Sep 17 00:00:00 2001 From: broBinChen <139344558+broBinChen@users.noreply.github.com> Date: Sun, 8 Jun 2025 17:53:29 +0800 Subject: [PATCH] feat(hooks): support separate enter/leave delays in useHoverToggle (#6325) Co-authored-by: xiaobin --- .../effects/hooks/src/use-hover-toggle.ts | 77 ++++++++++++++++--- 1 file changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/effects/hooks/src/use-hover-toggle.ts b/packages/effects/hooks/src/use-hover-toggle.ts index 491b1f58..8b1addeb 100644 --- a/packages/effects/hooks/src/use-hover-toggle.ts +++ b/packages/effects/hooks/src/use-hover-toggle.ts @@ -8,19 +8,40 @@ import { isFunction } from '@vben/utils'; import { useElementHover } from '@vueuse/core'; +interface HoverDelayOptions { + /** 鼠标进入延迟时间 */ + enterDelay?: (() => number) | number; + /** 鼠标离开延迟时间 */ + leaveDelay?: (() => number) | number; +} + +const DEFAULT_LEAVE_DELAY = 500; // 鼠标离开延迟时间,默认为 500ms +const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立即响应) + /** * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true - * @param delay 延迟更新状态的时间 + * @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 */ export function useHoverToggle( refElement: Arrayable, - delay: (() => number) | number = 500, + delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, ) { + // 兼容旧版本API + const normalizedOptions: HoverDelayOptions = + typeof delay === 'number' || isFunction(delay) + ? { enterDelay: DEFAULT_ENTER_DELAY, leaveDelay: delay } + : { + enterDelay: DEFAULT_ENTER_DELAY, + leaveDelay: DEFAULT_LEAVE_DELAY, + ...delay, + }; + const isHovers: Array> = []; const value = ref(false); - const timer = ref | undefined>(); + const enterTimer = ref | undefined>(); + const leaveTimer = ref | undefined>(); const refs = Array.isArray(refElement) ? refElement : [refElement]; refs.forEach((refEle) => { const eleRef = computed(() => { @@ -32,15 +53,47 @@ export function useHoverToggle( }); const isOutsideAll = computed(() => isHovers.every((v) => !v.value)); + function clearTimers() { + if (enterTimer.value) { + clearTimeout(enterTimer.value); + enterTimer.value = undefined; + } + if (leaveTimer.value) { + clearTimeout(leaveTimer.value); + leaveTimer.value = undefined; + } + } + function setValueDelay(val: boolean) { - timer.value && clearTimeout(timer.value); - timer.value = setTimeout( - () => { - value.value = val; - timer.value = undefined; - }, - isFunction(delay) ? delay() : delay, - ); + clearTimers(); + + if (val) { + // 鼠标进入 + const enterDelay = normalizedOptions.enterDelay ?? DEFAULT_ENTER_DELAY; + const delayTime = isFunction(enterDelay) ? enterDelay() : enterDelay; + + if (delayTime <= 0) { + value.value = true; + } else { + enterTimer.value = setTimeout(() => { + value.value = true; + enterTimer.value = undefined; + }, delayTime); + } + } else { + // 鼠标离开 + const leaveDelay = normalizedOptions.leaveDelay ?? DEFAULT_LEAVE_DELAY; + const delayTime = isFunction(leaveDelay) ? leaveDelay() : leaveDelay; + + if (delayTime <= 0) { + value.value = false; + } else { + leaveTimer.value = setTimeout(() => { + value.value = false; + leaveTimer.value = undefined; + }, delayTime); + } + } } const watcher = watch( @@ -61,7 +114,7 @@ export function useHoverToggle( }; onUnmounted(() => { - timer.value && clearTimeout(timer.value); + clearTimers(); }); return [value, controller] as [typeof value, typeof controller];