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