diff --git a/packages/effects/hooks/src/use-hover-toggle.ts b/packages/effects/hooks/src/use-hover-toggle.ts index 8b1addeb9..0bed41dd3 100644 --- a/packages/effects/hooks/src/use-hover-toggle.ts +++ b/packages/effects/hooks/src/use-hover-toggle.ts @@ -2,7 +2,7 @@ import type { Arrayable, MaybeElementRef } from '@vueuse/core'; import type { Ref } from 'vue'; -import { computed, onUnmounted, ref, unref, watch } from 'vue'; +import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue'; import { isFunction } from '@vben/utils'; @@ -20,12 +20,12 @@ const DEFAULT_ENTER_DELAY = 0; // 鼠标进入延迟时间,默认为 0(立 /** * 监测鼠标是否在元素内部,如果在元素内部则返回 true,否则返回 false - * @param refElement 所有需要检测的元素。如果提供了一个数组,那么鼠标在任何一个元素内部都会返回 true + * @param refElement 所有需要检测的元素。支持单个元素、元素数组或响应式引用的元素数组。如果鼠标在任何一个元素内部都会返回 true * @param delay 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象 * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用 */ export function useHoverToggle( - refElement: Arrayable, + refElement: Arrayable | Ref, delay: (() => number) | HoverDelayOptions | number = DEFAULT_LEAVE_DELAY, ) { // 兼容旧版本API @@ -38,20 +38,58 @@ export function useHoverToggle( ...delay, }; - const isHovers: Array> = []; const value = ref(false); const enterTimer = ref | undefined>(); const leaveTimer = ref | undefined>(); - const refs = Array.isArray(refElement) ? refElement : [refElement]; - refs.forEach((refEle) => { - const eleRef = computed(() => { - const ele = unref(refEle); - return ele instanceof Element ? ele : (ele?.$el as Element); - }); - const isHover = useElementHover(eleRef); - isHovers.push(isHover); + const hoverScopes = ref[]>([]); + + // 使用计算属性包装 refElement,使其响应式变化 + const refs = computed(() => { + const raw = unref(refElement); + if (raw === null) return []; + return Array.isArray(raw) ? raw : [raw]; }); - const isOutsideAll = computed(() => isHovers.every((v) => !v.value)); + // 存储所有 hover 状态 + const isHovers = ref>>([]); + + // 更新 hover 监听的函数 + function updateHovers() { + // 停止并清理之前的作用域 + hoverScopes.value.forEach((scope) => scope.stop()); + hoverScopes.value = []; + + isHovers.value = refs.value.map((refEle) => { + if (!refEle) { + return ref(false); + } + const eleRef = computed(() => { + const ele = unref(refEle); + return ele instanceof Element ? ele : (ele?.$el as Element); + }); + + // 为每个元素创建独立的作用域 + const scope = effectScope(); + const hoverRef = scope.run(() => useElementHover(eleRef)) || ref(false); + hoverScopes.value.push(scope); + + return hoverRef; + }); + } + + // 监听元素数量变化,避免过度执行 + const elementsCount = computed(() => { + const raw = unref(refElement); + if (raw === null) return 0; + return Array.isArray(raw) ? raw.length : 1; + }); + + // 初始设置 + updateHovers(); + + // 只在元素数量变化时重新设置监听器 + const stopWatcher = watch(elementsCount, updateHovers, { deep: false }); + + const isOutsideAll = computed(() => isHovers.value.every((v) => !v.value)); function clearTimers() { if (enterTimer.value) { @@ -96,7 +134,7 @@ export function useHoverToggle( } } - const watcher = watch( + const hoverWatcher = watch( isOutsideAll, (val) => { setValueDelay(!val); @@ -106,15 +144,19 @@ export function useHoverToggle( const controller = { enable() { - watcher.resume(); + hoverWatcher.resume(); }, disable() { - watcher.pause(); + hoverWatcher.pause(); }, }; onUnmounted(() => { clearTimers(); + // 停止监听器 + stopWatcher(); + // 停止所有剩余的作用域 + hoverScopes.value.forEach((scope) => scope.stop()); }); return [value, controller] as [typeof value, typeof controller];