├── .gitignore
├── jest.config.js
├── renovate.json
├── tsconfig.json
├── __tests__
├── useFocus.test.ts
├── useActive.test.ts
├── useTouch.test.ts
├── useHover.test.ts
├── useWindowResize.test.tsx
├── useMousePosition.test.ts
├── useResizeObserver.test.tsx
└── useClickOutside.test.tsx
├── src
├── index.ts
├── useFocus
│ └── index.tsx
├── useActive
│ └── index.tsx
├── useMousePosition
│ └── index.tsx
├── useTouch
│ └── index.tsx
├── useHover
│ └── index.tsx
├── useWindowResize
│ └── index.tsx
├── useResizeObserver
│ └── index.tsx
└── useClickOutside
│ └── index.tsx
├── docs
├── useActive.mdx
├── useHover.mdx
├── useTouch.mdx
├── useWindowResize.mdx
├── useFocus.mdx
├── useMousePosition.mdx
├── useResizeObserver.mdx
├── intro.mdx
└── useClickOutside.mdx
├── .github
└── workflows
│ └── npmpublish.yml
├── README.md
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | node_modules
3 | pkg
4 | .docz
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | testEnvironment: "jsdom",
3 | };
4 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "config:base"
4 | ],
5 | "automerge": true,
6 | "major": {
7 | "automerge": false
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2018",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "jsx": "react",
7 | "strict": true,
8 | "allowSyntheticDefaultImports": true,
9 | "skipLibCheck": true
10 | },
11 | "include": ["src"]
12 | }
13 |
--------------------------------------------------------------------------------
/__tests__/useFocus.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from "@testing-library/react";
2 | import { useFocus } from "../src";
3 |
4 | test("useFocus should react on focus/blur events", () => {
5 | const { result } = renderHook(() => useFocus());
6 |
7 | expect(result.current[0]).toBeFalsy();
8 | act(() => result.current[1].onFocus({} as React.FocusEvent));
9 | expect(result.current[0]).toBeTruthy();
10 | act(() => result.current[1].onBlur({} as React.FocusEvent));
11 | expect(result.current[0]).toBeFalsy();
12 | });
13 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useActive } from './useActive/index';
2 | export { default as useClickOutside } from './useClickOutside/index';
3 | export { default as useFocus } from './useFocus/index';
4 | export { default as useHover } from './useHover/index';
5 | export { default as useMousePosition } from './useMousePosition/index';
6 | export { default as useResizeObserver } from './useResizeObserver/index';
7 | export { default as useTouch } from './useTouch/index';
8 | export { default as useWindowResize } from './useWindowResize/index';
9 |
--------------------------------------------------------------------------------
/__tests__/useActive.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from "@testing-library/react";
2 | import { useActive } from "../src";
3 |
4 | test("useActive should react on mouseDown/mouseUp events", () => {
5 | const { result } = renderHook(() => useActive());
6 |
7 | expect(result.current[0]).toBeFalsy();
8 | act(() => result.current[1].onMouseDown({} as React.MouseEvent));
9 | expect(result.current[0]).toBeTruthy();
10 | act(() => result.current[1].onMouseUp({} as React.MouseEvent));
11 | expect(result.current[0]).toBeFalsy();
12 | });
13 |
--------------------------------------------------------------------------------
/__tests__/useTouch.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from "@testing-library/react";
2 | import { useTouch } from "../src";
3 |
4 | test("useTouch should react on touchStart/touchEnd events", () => {
5 | const { result } = renderHook(() => useTouch());
6 |
7 | expect(result.current[0]).toBeFalsy();
8 | act(() => result.current[1].onTouchStart({} as React.TouchEvent));
9 | expect(result.current[0]).toBeTruthy();
10 | act(() => result.current[1].onTouchEnd({} as React.TouchEvent));
11 | expect(result.current[0]).toBeFalsy();
12 | });
13 |
--------------------------------------------------------------------------------
/__tests__/useHover.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from "@testing-library/react";
2 | import { useHover } from "../src";
3 |
4 | test("useHover should react on mouseEnter/mouseLeave events", () => {
5 | const { result } = renderHook(() => useHover());
6 |
7 | expect(result.current[0]).toBeFalsy();
8 | act(() => result.current[1].onMouseEnter({} as React.MouseEvent));
9 | expect(result.current[0]).toBeTruthy();
10 | act(() => result.current[1].onMouseLeave({} as React.MouseEvent));
11 | expect(result.current[0]).toBeFalsy();
12 | });
13 |
--------------------------------------------------------------------------------
/src/useFocus/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useFocus(): [
4 | boolean,
5 | {
6 | onFocus: (e: React.FocusEvent) => void;
7 | onBlur: (e: React.FocusEvent) => void;
8 | }
9 | ] {
10 | const [isFocused, setFocused] = React.useState(false);
11 |
12 | const bind = React.useMemo(
13 | () => ({
14 | onFocus: (e: React.FocusEvent) => void setFocused(true),
15 | onBlur: (e: React.FocusEvent) => void setFocused(false),
16 | }),
17 | []
18 | );
19 |
20 | return [isFocused, bind];
21 | }
22 |
23 | export default useFocus;
24 |
--------------------------------------------------------------------------------
/src/useActive/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useActive(): [
4 | boolean,
5 | {
6 | onMouseDown: (e: React.MouseEvent) => void;
7 | onMouseUp: (e: React.MouseEvent) => void;
8 | }
9 | ] {
10 | const [isActive, setActive] = React.useState(false);
11 |
12 | const bind = React.useMemo(
13 | () => ({
14 | onMouseDown: (e: React.MouseEvent) => void setActive(true),
15 | onMouseUp: (e: React.MouseEvent) => void setActive(false),
16 | }),
17 | []
18 | );
19 |
20 | return [isActive, bind];
21 | }
22 |
23 | export default useActive;
24 |
--------------------------------------------------------------------------------
/src/useMousePosition/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useMousePosition(): [
4 | number,
5 | number,
6 | { onMouseMove: (e: React.MouseEvent) => void }
7 | ] {
8 | const [x, setX] = React.useState(0);
9 | const [y, setY] = React.useState(0);
10 |
11 | const bind = React.useMemo(
12 | () => ({
13 | onMouseMove: (e: React.MouseEvent) => {
14 | setX(e.nativeEvent.offsetX);
15 | setY(e.nativeEvent.offsetY);
16 | },
17 | }),
18 | []
19 | );
20 |
21 | return [x, y, bind];
22 | }
23 |
24 | export default useMousePosition;
25 |
--------------------------------------------------------------------------------
/src/useTouch/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useTouch(): [
4 | boolean,
5 | {
6 | onTouchStart: (e: React.TouchEvent) => void;
7 | onTouchEnd: (e: React.TouchEvent) => void;
8 | }
9 | ] {
10 | const [isTouched, setTouched] = React.useState(false);
11 |
12 | const bind = React.useMemo(
13 | () => ({
14 | onTouchStart: (e: React.TouchEvent) => void setTouched(true),
15 | onTouchEnd: (e: React.TouchEvent) => void setTouched(false),
16 | }),
17 | []
18 | );
19 |
20 | return [isTouched, bind];
21 | }
22 |
23 | export default useTouch;
24 |
--------------------------------------------------------------------------------
/src/useHover/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useHover(): [
4 | boolean,
5 | {
6 | onMouseEnter: (e: React.MouseEvent) => void;
7 | onMouseLeave: (e: React.MouseEvent) => void;
8 | }
9 | ] {
10 | const [isHovered, setHovered] = React.useState(false);
11 |
12 | const bind = React.useMemo(
13 | () => ({
14 | onMouseEnter: (e: React.MouseEvent) => void setHovered(true),
15 | onMouseLeave: (e: React.MouseEvent) => void setHovered(false),
16 | }),
17 | []
18 | );
19 |
20 | return [isHovered, bind];
21 | }
22 |
23 | export default useHover;
24 |
--------------------------------------------------------------------------------
/src/useWindowResize/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | function useWindowResize(): [number, number] {
4 | const [width, setWidth] = React.useState(window.innerWidth);
5 | const [height, setHeight] = React.useState(window.innerHeight);
6 |
7 | const resize = React.useCallback(() => {
8 | setWidth(window.innerWidth);
9 | setHeight(window.innerHeight);
10 | }, []);
11 |
12 | React.useEffect(() => {
13 | window.addEventListener('resize', resize);
14 | return () => void window.removeEventListener('resize', resize);
15 | }, [resize]);
16 |
17 | return [width, height];
18 | }
19 |
20 | export default useWindowResize;
21 |
--------------------------------------------------------------------------------
/docs/useActive.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useActive
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useActive } from '../src';
7 |
8 | # useActive
9 |
10 | ```js
11 | import { useActive } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [isActive, bind] = useActive();
17 |
18 | return (
19 |
20 | You are {isActive ? 'clicking' : 'not clicking'} this div
21 |
22 | );
23 | };
24 | ```
25 |
26 |
27 | {() => {
28 | const [isActive, bind] = useActive();
29 | return (
30 |
31 | You are {isActive ? 'clicking' : 'not clicking'} this div
32 |
33 | );
34 | }}
35 |
36 |
--------------------------------------------------------------------------------
/docs/useHover.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useHover
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useHover } from '../src';
7 |
8 | # useHover
9 |
10 | ```js
11 | import { useHover } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [isHovered, bind] = useHover();
17 |
18 | return (
19 |
20 | You are {isHovered ? 'hovering' : 'not hovering'} this div
21 |
22 | );
23 | };
24 | ```
25 |
26 |
27 | {() => {
28 | const [isHovered, bind] = useHover();
29 | return (
30 |
31 | You are {isHovered ? 'hovering' : 'not hovering'} this div
32 |
33 | );
34 | }}
35 |
36 |
--------------------------------------------------------------------------------
/docs/useTouch.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useTouch
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useTouch } from '../src';
7 |
8 | # useTouch
9 |
10 | ```js
11 | import { useTouch } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [isTouched, bind] = useTouch();
17 |
18 | return (
19 |
20 | You are {isTouched ? 'touching' : 'not touching'} this div
21 |
22 | );
23 | };
24 | ```
25 |
26 |
27 | {() => {
28 | const [isTouched, bind] = useTouch();
29 | return (
30 |
31 | You are {isTouched ? 'touching' : 'not touching'} this div
32 |
33 | );
34 | }}
35 |
36 |
--------------------------------------------------------------------------------
/docs/useWindowResize.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useWindowResize
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useWindowResize } from '../src';
7 |
8 | # useWindowResize
9 |
10 | ```js
11 | import { useWindowResize } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [width, height] = useWindowResize();
17 |
18 | return (
19 |
20 | width: {width}
21 | height: {height}
22 |
23 | );
24 | };
25 | ```
26 |
27 |
28 | {() => {
29 | const [width, height] = useWindowResize();
30 | return (
31 |
32 | width: {width}
33 | height: {height}
34 |
35 | );
36 | }}
37 |
38 |
--------------------------------------------------------------------------------
/__tests__/useWindowResize.test.tsx:
--------------------------------------------------------------------------------
1 | import { fireEvent } from "@testing-library/react";
2 | import { act, renderHook } from "@testing-library/react";
3 | import { useWindowResize } from "../src";
4 |
5 | const resize = (width: number, height: number) => {
6 | // @ts-ignore
7 | window.innerWidth = width;
8 | // @ts-ignore
9 | window.innerHeight = height;
10 | fireEvent(window, new Event("resize"));
11 | };
12 |
13 | test("useWindowResize should react on window resize event", () => {
14 | const { result } = renderHook(() => useWindowResize());
15 |
16 | act(() => resize(100, 100));
17 | expect(result.current[0]).toBe(100);
18 | expect(result.current[1]).toBe(100);
19 |
20 | act(() => resize(200, 200));
21 | expect(result.current[0]).toBe(200);
22 | expect(result.current[1]).toBe(200);
23 | });
24 |
--------------------------------------------------------------------------------
/docs/useFocus.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useFocus
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useFocus } from '../src';
7 |
8 | # useFocus
9 |
10 | ```js
11 | import { useFocus } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [isFocused, bind] = useFocus();
17 |
18 | return (
19 |
20 |
21 |
You are {isFocused ? 'focusing' : 'not focusing'} the input
22 |
23 | );
24 | };
25 | ```
26 |
27 |
28 | {() => {
29 | const [isFocused, bind] = useFocus();
30 | return (
31 |
32 |
33 |
You are {isFocused ? 'focusing' : 'not focusing'} the input
34 |
35 | );
36 | }}
37 |
38 |
--------------------------------------------------------------------------------
/__tests__/useMousePosition.test.ts:
--------------------------------------------------------------------------------
1 | import { act, renderHook } from "@testing-library/react";
2 | import { useMousePosition } from "../src";
3 |
4 | test("useTouch should react on mouseMove event", () => {
5 | const { result } = renderHook(() => useMousePosition());
6 |
7 | expect(result.current[0]).toBe(0);
8 | expect(result.current[1]).toBe(0);
9 | act(() => {
10 | return result.current[2].onMouseMove({
11 | nativeEvent: { offsetX: 1, offsetY: 2 },
12 | } as React.MouseEvent);
13 | });
14 | expect(result.current[0]).toBe(1);
15 | expect(result.current[1]).toBe(2);
16 | act(() => {
17 | return result.current[2].onMouseMove({
18 | nativeEvent: { offsetX: 3, offsetY: 4 },
19 | } as React.MouseEvent);
20 | });
21 | expect(result.current[0]).toBe(3);
22 | expect(result.current[1]).toBe(4);
23 | });
24 |
--------------------------------------------------------------------------------
/.github/workflows/npmpublish.yml:
--------------------------------------------------------------------------------
1 | name: NPM publish
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 | push:
8 | branches:
9 | - master
10 |
11 | jobs:
12 | publish-npm:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@master
16 |
17 | - name: Set Node.js 20.x
18 | uses: actions/setup-node@v4
19 |
20 | with:
21 | node-version: 20
22 | registry-url: https://registry.npmjs.org/
23 |
24 | - name: Install dependencies
25 | run: yarn
26 |
27 | - name: Typechecking
28 | run: yarn tsc
29 |
30 | - name: Test
31 | run: yarn test
32 |
33 | - name: Build
34 | run: yarn build
35 |
36 | - name: Publish
37 | run: npm publish
38 | working-directory: ./pkg
39 | env:
40 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}}
41 |
--------------------------------------------------------------------------------
/docs/useMousePosition.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useMousePosition
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useMousePosition } from '../src';
7 |
8 | # useMousePosition
9 |
10 | ```js
11 | import { useMousePosition } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const [x, y, bind] = useMousePosition();
17 |
18 | return (
19 |
20 | {x} - {y}
21 |
22 | );
23 | };
24 | ```
25 |
26 |
27 | {() => {
28 | const [x, y, bind] = useMousePosition();
29 | return (
30 |
42 | x: {x} y: {y}
43 |
44 | );
45 | }}
46 |
47 |
--------------------------------------------------------------------------------
/__tests__/useResizeObserver.test.tsx:
--------------------------------------------------------------------------------
1 | import { render } from "@testing-library/react";
2 | import React from "react";
3 | import ResizeObserver from "resize-observer-polyfill";
4 | import { useResizeObserver } from "../src";
5 |
6 | // @ts-ignore
7 | jest.spyOn(window, "requestAnimationFrame").mockImplementation((cb) => cb());
8 | jest.mock("resize-observer-polyfill");
9 |
10 | const TestComponent = () => {
11 | const ref = React.useRef(null);
12 | const [width, height] = useResizeObserver(ref);
13 | return {width + height}
;
14 | };
15 |
16 | const resize = (width: number, height: number) => {
17 | // @ts-ignore
18 | ResizeObserver.mockReset();
19 | // @ts-ignore
20 | ResizeObserver.mockImplementation((cb) => {
21 | cb([{ contentRect: { width, height } }]);
22 | return { observe: jest.fn, disconnect: jest.fn };
23 | });
24 |
25 | const { container } = render();
26 | return container.textContent;
27 | };
28 |
29 | test("useResizeObserver", () => {
30 | expect(resize(100, 100)).toBe("200");
31 | expect(resize(200, 200)).toBe("400");
32 | });
33 |
--------------------------------------------------------------------------------
/docs/useResizeObserver.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useResizeObserver
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useResizeObserver } from '../src';
7 |
8 | # useResizeObserver
9 |
10 | ```js
11 | import { useResizeObserver } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const ref = React.useRef(null);
17 | const [width, height] = useResizeObserver(ref);
18 |
19 | return (
20 |
21 | width: {width}
22 | height: {height}
23 |
24 | );
25 | };
26 | ```
27 |
28 |
29 | {() => {
30 | const ref = React.useRef(null);
31 | const [width, height] = useResizeObserver(ref);
32 | return (
33 |
44 | width: {width}
45 | height: {height}
46 |
47 | );
48 |
49 | }}
50 |
51 |
52 |
--------------------------------------------------------------------------------
/docs/intro.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Introduction
3 | route: /
4 | order: 1
5 | ---
6 |
7 | # Introduction
8 |
9 | **Use Events** - Events turned into React Hooks.
10 | Read about [Hooks](https://reactjs.org/docs/hooks-intro.html) feature.
11 |
12 | ## Install
13 |
14 | > Note: React 16.8+ is required for Hooks.
15 |
16 | ### With npm
17 |
18 | ```sh
19 | npm i use-events
20 | ```
21 |
22 | ### Or with yarn
23 |
24 | ```sh
25 | yarn add use-events
26 | ```
27 |
28 | exposed as `UseEvents`
29 |
30 | ## Quick Examples
31 |
32 | ```jsx
33 | import { useActive, useHover, useMousePosition } from 'use-events';
34 | ```
35 |
36 | ```jsx
37 | const App = () => {
38 | const [isActive, bind] = useActive();
39 |
40 | return (
41 |
44 | );
45 | };
46 | ```
47 |
48 | ```jsx
49 | const App = () => {
50 | const [isHovered, hoverBind] = useHover();
51 | const [x, y, mousePositionBind] = useMousePosition();
52 |
53 | return (
54 |
55 | {isHovered && (
56 |
57 | {x} - {y}
58 |
59 | )}
60 |
61 | );
62 | };
63 | ```
64 |
--------------------------------------------------------------------------------
/src/useResizeObserver/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ResizeObserver from "resize-observer-polyfill";
3 |
4 | function useResizeObserver(
5 | ref: React.RefObject,
6 | ): [number, number] {
7 | const animationFrameID = React.useRef(0);
8 | const [width, setWidth] = React.useState(0);
9 | const [height, setHeight] = React.useState(0);
10 |
11 | React.useLayoutEffect(() => {
12 | // https://github.com/microsoft/TypeScript/issues/37861
13 | // @ts-ignore
14 | const resizeObserver = new ResizeObserver((entries) => {
15 | if (!Array.isArray(entries) || entries.length === 0) {
16 | return;
17 | }
18 |
19 | const { width, height } = entries[0].contentRect;
20 |
21 | // https://github.com/WICG/resize-observer/issues/38
22 | animationFrameID.current = requestAnimationFrame(() => {
23 | setWidth(width);
24 | setHeight(height);
25 | });
26 | });
27 |
28 | if (ref.current !== null) {
29 | resizeObserver.observe(ref.current);
30 | }
31 |
32 | return () => {
33 | if (animationFrameID.current) {
34 | cancelAnimationFrame(animationFrameID.current);
35 | }
36 |
37 | resizeObserver.disconnect();
38 | };
39 | }, [ref]);
40 |
41 | return [width, height];
42 | }
43 |
44 | export default useResizeObserver;
45 |
--------------------------------------------------------------------------------
/src/useClickOutside/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | function useClickOutside(
4 | refs: React.RefObject[],
5 | onClickOutside: (e: MouseEvent) => void,
6 | ): [boolean] {
7 | const [isActive, setActive] = React.useState(false);
8 |
9 | const isOutside = React.useCallback(
10 | (e: MouseEvent) => {
11 | const test = refs.map((ref) => {
12 | return (
13 | ref.current !== null && !ref.current.contains(e.target as HTMLElement)
14 | );
15 | });
16 |
17 | return test.every(Boolean);
18 | },
19 | [refs],
20 | );
21 |
22 | const mousedown = React.useCallback(
23 | (e: MouseEvent) => {
24 | if (isOutside(e)) {
25 | setActive(true);
26 | onClickOutside(e);
27 | }
28 | },
29 | [isOutside, onClickOutside],
30 | );
31 |
32 | const mouseup = React.useCallback(
33 | (e: MouseEvent) => {
34 | if (isOutside(e)) {
35 | setActive(false);
36 | }
37 | },
38 | [isOutside],
39 | );
40 |
41 | React.useEffect(() => {
42 | document.addEventListener("mousedown", mousedown);
43 | document.addEventListener("mouseup", mouseup);
44 |
45 | return () => {
46 | document.removeEventListener("mousedown", mousedown);
47 | document.removeEventListener("mouseup", mouseup);
48 | };
49 | }, [refs, onClickOutside]);
50 |
51 | return [isActive];
52 | }
53 |
54 | export default useClickOutside;
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 
6 | 
7 | 
8 |
9 | **Use Events** - Events turned into React Hooks.
10 | Read about [Hooks](https://reactjs.org/docs/hooks-intro.html) feature.
11 |
12 | ## Documentation
13 |
14 | https://sandiiarov.github.io/use-events
15 |
16 | ### List of hooks
17 |
18 | - [useActive](https://sandiiarov.github.io/use-events/#/docs-use-active)
19 | - [useClickOutside](https://sandiiarov.github.io/use-events/#/docs-use-click-outside)
20 | - [useFocus](https://sandiiarov.github.io/use-events/#/docs-use-focus)
21 | - [useHover](https://sandiiarov.github.io/use-events/#/docs-use-hover)
22 | - [useMousePosition](https://sandiiarov.github.io/use-events/#/docs-use-mouse-position)
23 | - [useTouch](https://sandiiarov.github.io/use-events/#/docs-use-touch)
24 | - [useWindowResize](https://sandiiarov.github.io/use-events/#/docs-use-window-resize)
25 | - [useResizeObserver](https://sandiiarov.github.io/use-events/#/docs-use-resize-observer)
26 |
27 | ## Installation
28 |
29 | > Note: React 16.8+ is required for Hooks.
30 |
31 | ### With npm
32 |
33 | ```sh
34 | npm i use-events
35 | ```
36 |
37 | ### Or with yarn
38 |
39 | ```sh
40 | yarn add use-events
41 | ```
42 |
--------------------------------------------------------------------------------
/docs/useClickOutside.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: useClickOutside
3 | ---
4 |
5 | import { Playground } from 'docz';
6 | import { useClickOutside } from '../src';
7 |
8 | # useClickOutside
9 |
10 | ```js
11 | import { useClickOutside } from 'use-events';
12 | ```
13 |
14 | ```jsx
15 | const Example = () => {
16 | const ref1 = React.useRef(null);
17 | const ref2 = React.useRef(null);
18 | const [isActive] = useClickOutside([ref1, ref2], event => console.log(event));
19 |
20 | return (
21 |
22 |
23 | You are {isActive ? 'clicking' : 'not clicking'} outside of this div
24 |
25 |
26 |
27 | You are {isActive ? 'clicking' : 'not clicking'} outside of this div
28 |
29 |
30 | );
31 | };
32 | ```
33 |
34 |
35 | {() => {
36 | const ref1 = React.useRef(null);
37 | const ref2 = React.useRef(null);
38 | const [isActive] = useClickOutside([ref1, ref2], event =>
39 | console.log(event)
40 | );
41 | return (
42 |
43 |
44 | You are {isActive ? 'clicking' : 'not clicking'} outside of this div
45 |
46 |
47 |
48 | You are {isActive ? 'clicking' : 'not clicking'} outside of this div
49 |
50 |
51 | );
52 | }}
53 |
54 |
--------------------------------------------------------------------------------
/__tests__/useClickOutside.test.tsx:
--------------------------------------------------------------------------------
1 | import { fireEvent, render } from "@testing-library/react";
2 | import React from "react";
3 | import { useClickOutside } from "../src";
4 |
5 | test("useClickOutside should react on click outside and call callback", () => {
6 | const mockFn = jest.fn();
7 |
8 | const TestComponent = () => {
9 | const ref1 = React.useRef(null);
10 | const ref2 = React.useRef(null);
11 | const [isActive] = useClickOutside([ref1, ref2], mockFn);
12 | return (
13 |
14 |
15 | {isActive ? "Foo" : "Baz"}
16 |
17 |
18 | {isActive ? "Foo" : "Baz"}
19 |
20 |
21 | );
22 | };
23 |
24 | const { container, getByTestId } = render();
25 |
26 | const firstEl = getByTestId("1");
27 | const secondEl = getByTestId("2");
28 |
29 | expect(firstEl.textContent).toBe("Baz");
30 | expect(secondEl.textContent).toBe("Baz");
31 |
32 | fireEvent.mouseDown(container);
33 | expect(firstEl.textContent).toBe("Foo");
34 | expect(secondEl.textContent).toBe("Foo");
35 | fireEvent.mouseUp(container);
36 |
37 | fireEvent.mouseDown(firstEl);
38 | expect(firstEl.textContent).toBe("Baz");
39 | expect(secondEl.textContent).toBe("Baz");
40 | fireEvent.mouseUp(firstEl);
41 |
42 | fireEvent.mouseDown(secondEl);
43 | expect(firstEl.textContent).toBe("Baz");
44 | expect(secondEl.textContent).toBe("Baz");
45 | fireEvent.mouseUp(secondEl);
46 |
47 | expect(mockFn).toBeCalledTimes(1);
48 | });
49 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "use-events",
3 | "version": "1.4.5",
4 | "license": "MIT",
5 | "description": "Event Hooks",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/sandiiarov/use-events.git"
9 | },
10 | "scripts": {
11 | "build": "pika build",
12 | "test": "jest",
13 | "tsc": "tsc --noEmit"
14 | },
15 | "babel": {
16 | "presets": [
17 | "@babel/preset-typescript",
18 | "@babel/preset-react"
19 | ],
20 | "env": {
21 | "test": {
22 | "presets": [
23 | "@babel/preset-env"
24 | ]
25 | }
26 | }
27 | },
28 | "@pika/pack": {
29 | "pipeline": [
30 | [
31 | "@pika/plugin-standard-pkg"
32 | ],
33 | [
34 | "@pika/plugin-build-node"
35 | ],
36 | [
37 | "@pika/plugin-build-web"
38 | ],
39 | [
40 | "@pika/plugin-build-types"
41 | ]
42 | ]
43 | },
44 | "peerDependencies": {
45 | "react": ">=16.8.1"
46 | },
47 | "dependencies": {
48 | "resize-observer-polyfill": "1.5.1"
49 | },
50 | "devDependencies": {
51 | "@babel/core": "7.6.4",
52 | "@babel/preset-env": "7.6.3",
53 | "@babel/preset-react": "7.6.3",
54 | "@babel/preset-typescript": "7.6.0",
55 | "@pika/pack": "0.5.0",
56 | "@pika/plugin-build-node": "0.6.1",
57 | "@pika/plugin-build-types": "0.6.1",
58 | "@pika/plugin-build-web": "0.6.1",
59 | "@pika/plugin-standard-pkg": "0.6.1",
60 | "@testing-library/dom": "10.4.0",
61 | "@testing-library/react": "16.2.0",
62 | "@types/jest": "24.0.18",
63 | "@types/react": "19.0.8",
64 | "@types/react-dom": "19.0.3",
65 | "jest": "29.7.0",
66 | "jest-environment-jsdom": "29.7.0",
67 | "react": "19.0.0",
68 | "react-dom": "19.0.0",
69 | "typescript": "5.7.3"
70 | }
71 | }
72 |
--------------------------------------------------------------------------------