react-click-guard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +104 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# react-click-guard
|
|
2
|
+
|
|
3
|
+
A lightweight, dependency-free React utility to detect clicks outside an element. Perfect for closing modals, dropdowns, tooltips, and popovers.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## Why?
|
|
9
|
+
|
|
10
|
+
- 📦 **Zero Dependencies**: Pure React & JS.
|
|
11
|
+
- 🪶 **Tiny**: ~500 bytes gzipped.
|
|
12
|
+
- 🖱️ **Smart**: Handles `mousedown` and `touchstart` by default (works on mobile).
|
|
13
|
+
- ⚛️ **Flexible**: Provides both a **Hook** and a **Component**.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install react-click-guard
|
|
19
|
+
# or
|
|
20
|
+
yarn add react-click-guard
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### 1. Using the Component (`ClickGuard`)
|
|
26
|
+
|
|
27
|
+
The easiest way to wrap a section of your UI.
|
|
28
|
+
|
|
29
|
+
```jsx
|
|
30
|
+
import { useState } from 'react';
|
|
31
|
+
import { ClickGuard } from 'react-click-guard';
|
|
32
|
+
|
|
33
|
+
function Dropdown() {
|
|
34
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className="relative">
|
|
38
|
+
<button onClick={() => setIsOpen(!isOpen)}>Toggle Menu</button>
|
|
39
|
+
|
|
40
|
+
{isOpen && (
|
|
41
|
+
<ClickGuard onClickOutside={() => setIsOpen(false)} className="menu-container">
|
|
42
|
+
<div className="menu">
|
|
43
|
+
<p>Menu Item 1</p>
|
|
44
|
+
<p>Menu Item 2</p>
|
|
45
|
+
</div>
|
|
46
|
+
</ClickGuard>
|
|
47
|
+
)}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Using the Hook (`useClickOutside`)
|
|
54
|
+
|
|
55
|
+
For when you need direct control over the `ref`.
|
|
56
|
+
|
|
57
|
+
```jsx
|
|
58
|
+
import { useRef, useState } from 'react';
|
|
59
|
+
import { useClickOutside } from 'react-click-guard';
|
|
60
|
+
|
|
61
|
+
function Modal() {
|
|
62
|
+
const modalRef = useRef(null);
|
|
63
|
+
const [isOpen, setIsOpen] = useState(true);
|
|
64
|
+
|
|
65
|
+
// Close when clicking outside the modal content
|
|
66
|
+
useClickOutside(modalRef, () => {
|
|
67
|
+
setIsOpen(false);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (!isOpen) return null;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div className="overlay">
|
|
74
|
+
<div ref={modalRef} className="modal-content">
|
|
75
|
+
<h2>Modal Title</h2>
|
|
76
|
+
<p>Click outside this box to close me.</p>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## API
|
|
84
|
+
|
|
85
|
+
### `<ClickGuard />`
|
|
86
|
+
|
|
87
|
+
| Prop | Type | Default | Description |
|
|
88
|
+
|------|------|---------|-------------|
|
|
89
|
+
| `onClickOutside` | `() => void` | **required** | Callback fired when a click happens outside. |
|
|
90
|
+
| `as` | `string \| Component` | `'div'` | The element to render as the wrapper. |
|
|
91
|
+
| `enabled` | `boolean` | `true` | Enable/disable the listener conditionally. |
|
|
92
|
+
| `events` | `string[]` | `['mousedown', 'touchstart']` | DOM events to listen for. |
|
|
93
|
+
|
|
94
|
+
### `useClickOutside(ref, callback, options)`
|
|
95
|
+
|
|
96
|
+
| Argument | Type | Description |
|
|
97
|
+
|----------|------|-------------|
|
|
98
|
+
| `ref` | `React.RefObject` | The element ref to detect clicks *outside* of. |
|
|
99
|
+
| `callback` | `(event) => void` | Function to call on outside click. |
|
|
100
|
+
| `options` | `{ enabled?: boolean, events?: string[] }` | Configuration object. |
|
|
101
|
+
|
|
102
|
+
## License
|
|
103
|
+
|
|
104
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var a=Object.create;var s=Object.defineProperty;var l=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var k=Object.getPrototypeOf,E=Object.prototype.hasOwnProperty;var x=(r,t)=>{for(var e in t)s(r,e,{get:t[e],enumerable:!0})},p=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of C(t))!E.call(r,o)&&o!==e&&s(r,o,{get:()=>t[o],enumerable:!(n=l(t,o))||n.enumerable});return r};var O=(r,t,e)=>(e=r!=null?a(k(r)):{},p(t||!r||!r.__esModule?s(e,"default",{value:r,enumerable:!0}):e,r)),R=r=>p(s({},"__esModule",{value:!0}),r);var g={};x(g,{ClickGuard:()=>d,useClickOutside:()=>f});module.exports=R(g);var c=require("react");function f(r,t,{enabled:e=!0,events:n=["mousedown","touchstart"]}={}){let o=(0,c.useRef)(t);(0,c.useEffect)(()=>{o.current=t},[t]),(0,c.useEffect)(()=>{if(!e)return;let i=u=>{!r.current||r.current.contains(u.target)||o.current(u)};return n.forEach(u=>{document.addEventListener(u,i)}),()=>{n.forEach(u=>{document.removeEventListener(u,i)})}},[r,e,JSON.stringify(n)])}var m=O(require("react"));var d=({children:r,onClickOutside:t,as:e="div",enabled:n=!0,events:o,...i})=>{let u=(0,m.useRef)(null);return f(u,t,{enabled:n,events:o}),m.default.createElement(e,{ref:u,...i},r)};0&&(module.exports={ClickGuard,useClickOutside});
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.js","../src/useClickOutside.js","../src/ClickGuard.jsx"],"sourcesContent":["export { useClickOutside } from './useClickOutside';\nexport { ClickGuard } from './ClickGuard';\n","import { useEffect, useRef } from 'react';\n\n/**\n * Hook that triggers a callback when a click/touch is detected outside of the target ref.\n * \n * @param {React.RefObject} ref - The ref of the element to detect clicks outside of.\n * @param {Function} handler - The callback function to execute on outside click.\n * @param {Object} options - Optional configuration.\n * @param {boolean} [options.enabled=true] - Whether the listener is active.\n * @param {Array<string>} [options.events=['mousedown', 'touchstart']] - Events to listen for.\n */\nexport function useClickOutside(ref, handler, { enabled = true, events = ['mousedown', 'touchstart'] } = {}) {\n const handlerRef = useRef(handler);\n\n // Keep handler fresh without restarting effects\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const listener = (event) => {\n // Do nothing if clicking ref's element or descendent elements\n if (!ref.current || ref.current.contains(event.target)) {\n return;\n }\n\n handlerRef.current(event);\n };\n\n events.forEach((event) => {\n document.addEventListener(event, listener);\n });\n\n return () => {\n events.forEach((event) => {\n document.removeEventListener(event, listener);\n });\n };\n }, [ref, enabled, JSON.stringify(events)]);\n}\n","import React, { useRef } from 'react';\nimport { useClickOutside } from './useClickOutside';\n\n/**\n * Component that detects clicks outside of its children.\n * Renders a wrapper element (default: div).\n * \n * @param {Object} props\n * @param {Function} props.onClickOutside - Function called when a click outside occurs.\n * @param {React.ReactNode} props.children - Content to wrap.\n * @param {string|React.ElementType} [props.as='div'] - The HTML tag or component to render as the wrapper.\n * @param {boolean} [props.enabled=true] - Whether to enable the listener.\n * @param {Array<string>} [props.events] - Custom event types to listen for.\n * @param {Object} [props...rest] - Any other props are passed to the wrapper element.\n */\nexport const ClickGuard = ({\n children,\n onClickOutside,\n as: Component = 'div',\n enabled = true,\n events,\n ...rest\n}) => {\n const wrapperRef = useRef(null);\n\n useClickOutside(wrapperRef, onClickOutside, { enabled, events });\n\n return (\n <Component ref={wrapperRef} {...rest}>\n {children}\n </Component>\n );\n};\n"],"mappings":"6iBAAA,IAAAA,EAAA,GAAAC,EAAAD,EAAA,gBAAAE,EAAA,oBAAAC,IAAA,eAAAC,EAAAJ,GCAA,IAAAK,EAAkC,iBAW3B,SAASC,EAAgBC,EAAKC,EAAS,CAAE,QAAAC,EAAU,GAAM,OAAAC,EAAS,CAAC,YAAa,YAAY,CAAE,EAAI,CAAC,EAAG,CACzG,IAAMC,KAAa,UAAOH,CAAO,KAGjC,aAAU,IAAM,CACZG,EAAW,QAAUH,CACzB,EAAG,CAACA,CAAO,CAAC,KAEZ,aAAU,IAAM,CACZ,GAAI,CAACC,EAAS,OAEd,IAAMG,EAAYC,GAAU,CAEpB,CAACN,EAAI,SAAWA,EAAI,QAAQ,SAASM,EAAM,MAAM,GAIrDF,EAAW,QAAQE,CAAK,CAC5B,EAEA,OAAAH,EAAO,QAASG,GAAU,CACtB,SAAS,iBAAiBA,EAAOD,CAAQ,CAC7C,CAAC,EAEM,IAAM,CACTF,EAAO,QAASG,GAAU,CACtB,SAAS,oBAAoBA,EAAOD,CAAQ,CAChD,CAAC,CACL,CACJ,EAAG,CAACL,EAAKE,EAAS,KAAK,UAAUC,CAAM,CAAC,CAAC,CAC7C,CCzCA,IAAAI,EAA8B,oBAevB,IAAMC,EAAa,CAAC,CACvB,SAAAC,EACA,eAAAC,EACA,GAAIC,EAAY,MAChB,QAAAC,EAAU,GACV,OAAAC,EACA,GAAGC,CACP,IAAM,CACF,IAAMC,KAAa,UAAO,IAAI,EAE9B,OAAAC,EAAgBD,EAAYL,EAAgB,CAAE,QAAAE,EAAS,OAAAC,CAAO,CAAC,EAG3D,EAAAI,QAAA,cAACN,EAAA,CAAU,IAAKI,EAAa,GAAGD,GAC3BL,CACL,CAER","names":["index_exports","__export","ClickGuard","useClickOutside","__toCommonJS","import_react","useClickOutside","ref","handler","enabled","events","handlerRef","listener","event","import_react","ClickGuard","children","onClickOutside","Component","enabled","events","rest","wrapperRef","useClickOutside","React"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useEffect as s,useRef as f}from"react";function i(t,e,{enabled:u=!0,events:o=["mousedown","touchstart"]}={}){let n=f(e);s(()=>{n.current=e},[e]),s(()=>{if(!u)return;let c=r=>{!t.current||t.current.contains(r.target)||n.current(r)};return o.forEach(r=>{document.addEventListener(r,c)}),()=>{o.forEach(r=>{document.removeEventListener(r,c)})}},[t,u,JSON.stringify(o)])}import m,{useRef as p}from"react";var d=({children:t,onClickOutside:e,as:u="div",enabled:o=!0,events:n,...c})=>{let r=p(null);return i(r,e,{enabled:o,events:n}),m.createElement(u,{ref:r,...c},t)};export{d as ClickGuard,i as useClickOutside};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/useClickOutside.js","../src/ClickGuard.jsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\n\n/**\n * Hook that triggers a callback when a click/touch is detected outside of the target ref.\n * \n * @param {React.RefObject} ref - The ref of the element to detect clicks outside of.\n * @param {Function} handler - The callback function to execute on outside click.\n * @param {Object} options - Optional configuration.\n * @param {boolean} [options.enabled=true] - Whether the listener is active.\n * @param {Array<string>} [options.events=['mousedown', 'touchstart']] - Events to listen for.\n */\nexport function useClickOutside(ref, handler, { enabled = true, events = ['mousedown', 'touchstart'] } = {}) {\n const handlerRef = useRef(handler);\n\n // Keep handler fresh without restarting effects\n useEffect(() => {\n handlerRef.current = handler;\n }, [handler]);\n\n useEffect(() => {\n if (!enabled) return;\n\n const listener = (event) => {\n // Do nothing if clicking ref's element or descendent elements\n if (!ref.current || ref.current.contains(event.target)) {\n return;\n }\n\n handlerRef.current(event);\n };\n\n events.forEach((event) => {\n document.addEventListener(event, listener);\n });\n\n return () => {\n events.forEach((event) => {\n document.removeEventListener(event, listener);\n });\n };\n }, [ref, enabled, JSON.stringify(events)]);\n}\n","import React, { useRef } from 'react';\nimport { useClickOutside } from './useClickOutside';\n\n/**\n * Component that detects clicks outside of its children.\n * Renders a wrapper element (default: div).\n * \n * @param {Object} props\n * @param {Function} props.onClickOutside - Function called when a click outside occurs.\n * @param {React.ReactNode} props.children - Content to wrap.\n * @param {string|React.ElementType} [props.as='div'] - The HTML tag or component to render as the wrapper.\n * @param {boolean} [props.enabled=true] - Whether to enable the listener.\n * @param {Array<string>} [props.events] - Custom event types to listen for.\n * @param {Object} [props...rest] - Any other props are passed to the wrapper element.\n */\nexport const ClickGuard = ({\n children,\n onClickOutside,\n as: Component = 'div',\n enabled = true,\n events,\n ...rest\n}) => {\n const wrapperRef = useRef(null);\n\n useClickOutside(wrapperRef, onClickOutside, { enabled, events });\n\n return (\n <Component ref={wrapperRef} {...rest}>\n {children}\n </Component>\n );\n};\n"],"mappings":"AAAA,OAAS,aAAAA,EAAW,UAAAC,MAAc,QAW3B,SAASC,EAAgBC,EAAKC,EAAS,CAAE,QAAAC,EAAU,GAAM,OAAAC,EAAS,CAAC,YAAa,YAAY,CAAE,EAAI,CAAC,EAAG,CACzG,IAAMC,EAAaN,EAAOG,CAAO,EAGjCJ,EAAU,IAAM,CACZO,EAAW,QAAUH,CACzB,EAAG,CAACA,CAAO,CAAC,EAEZJ,EAAU,IAAM,CACZ,GAAI,CAACK,EAAS,OAEd,IAAMG,EAAYC,GAAU,CAEpB,CAACN,EAAI,SAAWA,EAAI,QAAQ,SAASM,EAAM,MAAM,GAIrDF,EAAW,QAAQE,CAAK,CAC5B,EAEA,OAAAH,EAAO,QAASG,GAAU,CACtB,SAAS,iBAAiBA,EAAOD,CAAQ,CAC7C,CAAC,EAEM,IAAM,CACTF,EAAO,QAASG,GAAU,CACtB,SAAS,oBAAoBA,EAAOD,CAAQ,CAChD,CAAC,CACL,CACJ,EAAG,CAACL,EAAKE,EAAS,KAAK,UAAUC,CAAM,CAAC,CAAC,CAC7C,CCzCA,OAAOI,GAAS,UAAAC,MAAc,QAevB,IAAMC,EAAa,CAAC,CACvB,SAAAC,EACA,eAAAC,EACA,GAAIC,EAAY,MAChB,QAAAC,EAAU,GACV,OAAAC,EACA,GAAGC,CACP,IAAM,CACF,IAAMC,EAAaC,EAAO,IAAI,EAE9B,OAAAC,EAAgBF,EAAYL,EAAgB,CAAE,QAAAE,EAAS,OAAAC,CAAO,CAAC,EAG3DK,EAAA,cAACP,EAAA,CAAU,IAAKI,EAAa,GAAGD,GAC3BL,CACL,CAER","names":["useEffect","useRef","useClickOutside","ref","handler","enabled","events","handlerRef","listener","event","React","useRef","ClickGuard","children","onClickOutside","Component","enabled","events","rest","wrapperRef","useRef","useClickOutside","React"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-click-guard",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A lightweight, dependency-free React utility to detect clicks outside an element. Perfect for modals, dropdowns, and popovers.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup",
|
|
17
|
+
"dev": "tsup --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"react",
|
|
22
|
+
"click-outside",
|
|
23
|
+
"hook",
|
|
24
|
+
"click-away",
|
|
25
|
+
"detect-click",
|
|
26
|
+
"modal",
|
|
27
|
+
"dropdown",
|
|
28
|
+
"ui"
|
|
29
|
+
],
|
|
30
|
+
"author": "Your Name",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"react": ">=16.8.0",
|
|
34
|
+
"react-dom": ">=16.8.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"react": "^18.2.0",
|
|
38
|
+
"react-dom": "^18.2.0",
|
|
39
|
+
"tsup": "^8.0.0",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"README.md",
|
|
45
|
+
"LICENSE"
|
|
46
|
+
]
|
|
47
|
+
}
|