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 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
+ ![License](https://img.shields.io/npm/l/react-click-guard)
6
+ ![Size](https://img.shields.io/bundlephobia/minzip/react-click-guard)
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
+ }