diff --git a/packages/hooks/src/useHover/__tests__/index.test.tsx b/packages/hooks/src/useHover/__tests__/index.test.tsx index a9a6e652e8..c21ba3b697 100644 --- a/packages/hooks/src/useHover/__tests__/index.test.tsx +++ b/packages/hooks/src/useHover/__tests__/index.test.tsx @@ -30,3 +30,56 @@ 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..a602a6e921 --- /dev/null +++ b/packages/hooks/src/useHover/demo/demo3.tsx @@ -0,0 +1,28 @@ +/** + * 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