ny
22 小时以前 282fbc6488f4e8ceb5fda759f963ee88fbf7b999
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
import type { Arrayable, MaybeElementRef } from '@vueuse/core';
 
import type { Ref } from 'vue';
 
import { computed, effectScope, onUnmounted, ref, unref, watch } from 'vue';
 
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 延迟更新状态的时间,可以是数字或包含进入/离开延迟的配置对象
 * @returns 返回一个数组,第一个元素是一个 ref,表示鼠标是否在元素内部,第二个元素是一个控制器,可以通过 enable 和 disable 方法来控制监听器的启用和禁用
 */
export function useHoverToggle(
  refElement: Arrayable<MaybeElementRef> | Ref<HTMLElement[] | null>,
  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 value = ref(false);
  const enterTimer = ref<ReturnType<typeof setTimeout> | undefined>();
  const leaveTimer = ref<ReturnType<typeof setTimeout> | undefined>();
  const hoverScopes = ref<ReturnType<typeof effectScope>[]>([]);
 
  // 使用计算属性包装 refElement,使其响应式变化
  const refs = computed(() => {
    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 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) {
      clearTimeout(enterTimer.value);
      enterTimer.value = undefined;
    }
    if (leaveTimer.value) {
      clearTimeout(leaveTimer.value);
      leaveTimer.value = undefined;
    }
  }
 
  function setValueDelay(val: boolean) {
    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 hoverWatcher = watch(
    isOutsideAll,
    (val) => {
      setValueDelay(!val);
    },
    { immediate: true },
  );
 
  const controller = {
    enable() {
      hoverWatcher.resume();
    },
    disable() {
      hoverWatcher.pause();
    },
  };
 
  onUnmounted(() => {
    clearTimers();
    // 停止监听器
    stopWatcher();
    // 停止所有剩余的作用域
    hoverScopes.value.forEach((scope) => scope.stop());
  });
 
  return [value, controller] as [typeof value, typeof controller];
}