From 2fe499a47d0b634bafa5d99e125faa660a7b3c35 Mon Sep 17 00:00:00 2001 From: Luncode <1370655052@qq.com> Date: Wed, 2 Jul 2025 17:04:33 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20useHover=E4=B8=AD=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E9=95=BF=E6=97=B6=E9=97=B4=E6=82=AC=E5=81=9C=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/useHover/__tests__/index.test.tsx | 59 +++++++++++++++++++ packages/hooks/src/useHover/demo/demo3.tsx | 31 ++++++++++ packages/hooks/src/useHover/index.en-US.md | 4 ++ packages/hooks/src/useHover/index.ts | 17 +++++- packages/hooks/src/useHover/index.zh-CN.md | 4 ++ 5 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 packages/hooks/src/useHover/demo/demo3.tsx diff --git a/packages/hooks/src/useHover/__tests__/index.test.tsx b/packages/hooks/src/useHover/__tests__/index.test.tsx index a9a6e652e8..f39c94e064 100644 --- a/packages/hooks/src/useHover/__tests__/index.test.tsx +++ b/packages/hooks/src/useHover/__tests__/index.test.tsx @@ -30,3 +30,62 @@ describe('useHover', () => { expect(trigger).toBe(2); }); }); + +describe('useHover - onLongHover', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + it('should call onLongHover(true) after longHoverDuration', () => { + const onLongHover = jest.fn(); + const { getByText } = render(); + renderHook(() => + useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) + ); + + act(() => { + fireEvent.mouseEnter(getByText('Hover')); + }); + expect(onLongHover).not.toBeCalled(); + act(() => { + jest.advanceTimersByTime(300); + }); + expect(onLongHover).toHaveBeenCalledWith(true); + }); + + it('should call onLongHover(false) on mouseleave if timer exists', () => { + const onLongHover = jest.fn(); + const { getByText } = render(); + renderHook(() => + useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) + ); + act(() => { + fireEvent.mouseEnter(getByText('Hover')); + }); + act(() => { + fireEvent.mouseLeave(getByText('Hover')); + }); + expect(onLongHover).toHaveBeenCalledWith(false); + }); + + it('should not call onLongHover(true) if mouse leaves before duration', () => { + const onLongHover = jest.fn(); + const { getByText } = render(); + renderHook(() => + useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) + ); + act(() => { + fireEvent.mouseEnter(getByText('Hover')); + }); + act(() => { + jest.advanceTimersByTime(200); + fireEvent.mouseLeave(getByText('Hover')); + }); + expect(onLongHover).toHaveBeenCalledTimes(1); + expect(onLongHover).toHaveBeenCalledWith(false); + }); +}); diff --git a/packages/hooks/src/useHover/demo/demo3.tsx b/packages/hooks/src/useHover/demo/demo3.tsx new file mode 100644 index 0000000000..c4986413dd --- /dev/null +++ b/packages/hooks/src/useHover/demo/demo3.tsx @@ -0,0 +1,31 @@ +/** + * title: Basic usage + * desc: Use ref or Pass in DOM element. + * + * title.zh-CN: 基础用法 + * desc.zh-CN: 使用 ref 或者传入 DOM 元素。 + */ + +import React, { useRef, useState } from "react"; +import { useHover } from "ahooks"; + +export default () => { + const ref = useRef(null); + const [isLongHovering, setIsLongHovering] = useState(false); + const isHovering = useHover(ref, { + longHoverDuration: 1000, + onLongHover: (value) => { + setIsLongHovering(value); + }, + }); + return ( +
+
isHovering: {isHovering ? "hover" : "leave hover"}
+ +
+ isLongHovering: {isLongHovering ? "long Hover" : "leave Hover"}{" "} + (delay:1000ms) +
+
+ ); +}; diff --git a/packages/hooks/src/useHover/index.en-US.md b/packages/hooks/src/useHover/index.en-US.md index b56c4808ee..2ffe0bbfe9 100644 --- a/packages/hooks/src/useHover/index.en-US.md +++ b/packages/hooks/src/useHover/index.en-US.md @@ -17,6 +17,10 @@ A hook that tracks whether the element is being hovered. +### Long term hover event + + + ## API ```javascript diff --git a/packages/hooks/src/useHover/index.ts b/packages/hooks/src/useHover/index.ts index 0e319680d7..53a42d122c 100644 --- a/packages/hooks/src/useHover/index.ts +++ b/packages/hooks/src/useHover/index.ts @@ -1,24 +1,34 @@ import useBoolean from '../useBoolean'; import useEventListener from '../useEventListener'; import type { BasicTarget } from '../utils/domTarget'; +import { useRef } from 'react'; export interface Options { onEnter?: () => void; onLeave?: () => void; onChange?: (isHovering: boolean) => void; + onLongHover?: (isLongHovering: boolean) => void; + longHoverDuration?: number; } export default (target: BasicTarget, options?: Options): boolean => { - const { onEnter, onLeave, onChange } = options || {}; + const { onEnter, onLeave, onChange, onLongHover, longHoverDuration = 500 } = options || {}; const [state, { setTrue, setFalse }] = useBoolean(false); + const timerRef = useRef(null); + useEventListener( 'mouseenter', () => { onEnter?.(); setTrue(); onChange?.(true); + if (onLongHover) { + timerRef.current = window.setTimeout(() => { + onLongHover?.(true); + }, longHoverDuration); + } }, { target, @@ -31,6 +41,11 @@ export default (target: BasicTarget, options?: Options): boolean => { onLeave?.(); setFalse(); onChange?.(false); + if (timerRef.current) { + clearTimeout(timerRef.current); + timerRef.current = null; + onLongHover?.(false); + } }, { target, diff --git a/packages/hooks/src/useHover/index.zh-CN.md b/packages/hooks/src/useHover/index.zh-CN.md index 53435c306f..6c3924b31f 100644 --- a/packages/hooks/src/useHover/index.zh-CN.md +++ b/packages/hooks/src/useHover/index.zh-CN.md @@ -17,6 +17,10 @@ nav: +### 长时间悬停事件 + + + ## API ```javascript From 9b462df8c3bdb9540d5bec512d24992e1cefec4f Mon Sep 17 00:00:00 2001 From: Luncode <1370655052@qq.com> Date: Wed, 2 Jul 2025 17:05:55 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=E6=96=B0=E5=A2=9E=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=92=8C=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/hooks/src/useHover/__tests__/index.test.tsx | 12 +++--------- packages/hooks/src/useHover/demo/demo3.tsx | 11 ++++------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/hooks/src/useHover/__tests__/index.test.tsx b/packages/hooks/src/useHover/__tests__/index.test.tsx index f39c94e064..c21ba3b697 100644 --- a/packages/hooks/src/useHover/__tests__/index.test.tsx +++ b/packages/hooks/src/useHover/__tests__/index.test.tsx @@ -43,9 +43,7 @@ describe('useHover - onLongHover', () => { it('should call onLongHover(true) after longHoverDuration', () => { const onLongHover = jest.fn(); const { getByText } = render(); - renderHook(() => - useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) - ); + renderHook(() => useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 })); act(() => { fireEvent.mouseEnter(getByText('Hover')); @@ -60,9 +58,7 @@ describe('useHover - onLongHover', () => { it('should call onLongHover(false) on mouseleave if timer exists', () => { const onLongHover = jest.fn(); const { getByText } = render(); - renderHook(() => - useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) - ); + renderHook(() => useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 })); act(() => { fireEvent.mouseEnter(getByText('Hover')); }); @@ -75,9 +71,7 @@ describe('useHover - onLongHover', () => { it('should not call onLongHover(true) if mouse leaves before duration', () => { const onLongHover = jest.fn(); const { getByText } = render(); - renderHook(() => - useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 }) - ); + renderHook(() => useHover(getByText('Hover'), { onLongHover, longHoverDuration: 300 })); act(() => { fireEvent.mouseEnter(getByText('Hover')); }); diff --git a/packages/hooks/src/useHover/demo/demo3.tsx b/packages/hooks/src/useHover/demo/demo3.tsx index c4986413dd..a602a6e921 100644 --- a/packages/hooks/src/useHover/demo/demo3.tsx +++ b/packages/hooks/src/useHover/demo/demo3.tsx @@ -6,8 +6,8 @@ * desc.zh-CN: 使用 ref 或者传入 DOM 元素。 */ -import React, { useRef, useState } from "react"; -import { useHover } from "ahooks"; +import React, { useRef, useState } from 'react'; +import { useHover } from 'ahooks'; export default () => { const ref = useRef(null); @@ -20,12 +20,9 @@ export default () => { }); return (
-
isHovering: {isHovering ? "hover" : "leave hover"}
+
isHovering: {isHovering ? 'hover' : 'leave hover'}
-
- isLongHovering: {isLongHovering ? "long Hover" : "leave Hover"}{" "} - (delay:1000ms) -
+
isLongHovering: {isLongHovering ? 'long Hover' : 'leave Hover'} (delay:1000ms)
); };