Skip to content

Commit b14db5b

Browse files
authored
🐛 Fix/use on click outside (#552)
* Support missing refs (fixes #531) * Add support for focus event to `useOnClickOutside` (Fixes: #522) * Expose `AddEventListenerOptions` in `useOnClickOutside` (Fixes #554 from @metav-drimz)
1 parent 59c0b93 commit b14db5b

File tree

9 files changed

+104
-18
lines changed

9 files changed

+104
-18
lines changed

.all-contributorsrc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1726,8 +1726,30 @@
17261726
"avatar_url": "https://avatars.githubusercontent.com/u/10504365?v=4",
17271727
"profile": "https://github.com/Newbie012",
17281728
"contributions": ["code"]
1729+
},
1730+
{
1731+
"login": "isumix",
1732+
"name": "Igor Sukharev",
1733+
"avatar_url": "https://avatars.githubusercontent.com/u/16747416?v=4",
1734+
"profile": "https://github.com/isumix",
1735+
"contributions": ["bug"]
1736+
},
1737+
{
1738+
"login": "pookmish",
1739+
"name": "pookmish",
1740+
"avatar_url": "https://avatars.githubusercontent.com/u/7185045?v=4",
1741+
"profile": "https://github.com/pookmish",
1742+
"contributions": ["ideas"]
1743+
},
1744+
{
1745+
"login": "metav-drimz",
1746+
"name": "metav-drimz",
1747+
"avatar_url": "https://avatars.githubusercontent.com/u/113976282?v=4",
1748+
"profile": "https://github.com/metav-drimz",
1749+
"contributions": ["ideas"]
17291750
}
17301751
],
17311752
"contributorsPerLine": 7,
17321753
"commitType": "docs"
17331754
}
1755+

.changeset/five-impalas-suffer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"usehooks-ts": patch
3+
---
4+
5+
Add support for focus event to `useOnClickOutside` (Fixes: #522)

.changeset/poor-forks-end.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"usehooks-ts": patch
3+
---
4+
5+
Expose `AddEventListenerOptions` in `useOnClickOutside` (Fixes #554 from @metav-drimz)

.changeset/rare-icons-tickle.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"usehooks-ts": patch
3+
---
4+
5+
Support missing refs in `useOnClickOutside` (Fixes: #531)

.github/CONTRIBUTING.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
420420
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LumaKernel"><img src="https://avatars.githubusercontent.com/u/29811106?v=4?s=64" width="64px;" alt="Luma"/><br /><sub><b>Luma</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=LumaKernel" title="Code">💻</a></td>
421421
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Newbie012"><img src="https://avatars.githubusercontent.com/u/10504365?v=4?s=64" width="64px;" alt="Eliya Cohen"/><br /><sub><b>Eliya Cohen</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=Newbie012" title="Code">💻</a></td>
422422
</tr>
423+
<tr>
424+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/isumix"><img src="https://avatars.githubusercontent.com/u/16747416?v=4?s=64" width="64px;" alt="Igor Sukharev"/><br /><sub><b>Igor Sukharev</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/issues?q=author%3Aisumix" title="Bug reports">🐛</a></td>
425+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pookmish"><img src="https://avatars.githubusercontent.com/u/7185045?v=4?s=64" width="64px;" alt="pookmish"/><br /><sub><b>pookmish</b></sub></a><br /><a href="#ideas-pookmish" title="Ideas, Planning, & Feedback">🤔</a></td>
426+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/metav-drimz"><img src="https://avatars.githubusercontent.com/u/113976282?v=4?s=64" width="64px;" alt="metav-drimz"/><br /><sub><b>metav-drimz</b></sub></a><br /><a href="#ideas-metav-drimz" title="Ideas, Planning, & Feedback">🤔</a></td>
427+
</tr>
423428
</tbody>
424429
</table>
425430

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[![License](https://badgen.net/badge/License/MIT/blue)](https://github.com/juliencrn/usehooks-ts/blob/master/LICENSE)
1818
![npm bundle size](https://img.shields.io/bundlephobia/minzip/usehooks-ts)
1919
![npm](https://img.shields.io/npm/v/usehooks-ts)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
20-
[![All Contributors](https://img.shields.io/badge/all_contributors-245-orange.svg?style=flat-square)](#contributors-)
20+
[![All Contributors](https://img.shields.io/badge/all_contributors-248-orange.svg?style=flat-square)](#contributors-)
2121
<!-- ALL-CONTRIBUTORS-BADGE:END -->
2222

2323
<br />
@@ -432,6 +432,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
432432
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LumaKernel"><img src="https://avatars.githubusercontent.com/u/29811106?v=4?s=64" width="64px;" alt="Luma"/><br /><sub><b>Luma</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=LumaKernel" title="Code">💻</a></td>
433433
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Newbie012"><img src="https://avatars.githubusercontent.com/u/10504365?v=4?s=64" width="64px;" alt="Eliya Cohen"/><br /><sub><b>Eliya Cohen</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=Newbie012" title="Code">💻</a></td>
434434
</tr>
435+
<tr>
436+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/isumix"><img src="https://avatars.githubusercontent.com/u/16747416?v=4?s=64" width="64px;" alt="Igor Sukharev"/><br /><sub><b>Igor Sukharev</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/issues?q=author%3Aisumix" title="Bug reports">🐛</a></td>
437+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pookmish"><img src="https://avatars.githubusercontent.com/u/7185045?v=4?s=64" width="64px;" alt="pookmish"/><br /><sub><b>pookmish</b></sub></a><br /><a href="#ideas-pookmish" title="Ideas, Planning, & Feedback">🤔</a></td>
438+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/metav-drimz"><img src="https://avatars.githubusercontent.com/u/113976282?v=4?s=64" width="64px;" alt="metav-drimz"/><br /><sub><b>metav-drimz</b></sub></a><br /><a href="#ideas-metav-drimz" title="Ideas, Planning, & Feedback">🤔</a></td>
439+
</tr>
435440
</tbody>
436441
</table>
437442

packages/usehooks-ts/README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
[![License](https://badgen.net/badge/License/MIT/blue)](https://github.com/juliencrn/usehooks-ts/blob/master/LICENSE)
1818
![npm bundle size](https://img.shields.io/bundlephobia/minzip/usehooks-ts)
1919
![npm](https://img.shields.io/npm/v/usehooks-ts)<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
20-
[![All Contributors](https://img.shields.io/badge/all_contributors-245-orange.svg?style=flat-square)](#contributors-)
20+
[![All Contributors](https://img.shields.io/badge/all_contributors-248-orange.svg?style=flat-square)](#contributors-)
2121
<!-- ALL-CONTRIBUTORS-BADGE:END -->
2222

2323
<br />
@@ -432,6 +432,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
432432
<td align="center" valign="top" width="14.28%"><a href="https://github.com/LumaKernel"><img src="https://avatars.githubusercontent.com/u/29811106?v=4?s=64" width="64px;" alt="Luma"/><br /><sub><b>Luma</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=LumaKernel" title="Code">💻</a></td>
433433
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Newbie012"><img src="https://avatars.githubusercontent.com/u/10504365?v=4?s=64" width="64px;" alt="Eliya Cohen"/><br /><sub><b>Eliya Cohen</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/commits?author=Newbie012" title="Code">💻</a></td>
434434
</tr>
435+
<tr>
436+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/isumix"><img src="https://avatars.githubusercontent.com/u/16747416?v=4?s=64" width="64px;" alt="Igor Sukharev"/><br /><sub><b>Igor Sukharev</b></sub></a><br /><a href="https://github.com/juliencrn/usehooks-ts/issues?q=author%3Aisumix" title="Bug reports">🐛</a></td>
437+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/pookmish"><img src="https://avatars.githubusercontent.com/u/7185045?v=4?s=64" width="64px;" alt="pookmish"/><br /><sub><b>pookmish</b></sub></a><br /><a href="#ideas-pookmish" title="Ideas, Planning, & Feedback">🤔</a></td>
438+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/metav-drimz"><img src="https://avatars.githubusercontent.com/u/113976282?v=4?s=64" width="64px;" alt="metav-drimz"/><br /><sub><b>metav-drimz</b></sub></a><br /><a href="#ideas-metav-drimz" title="Ideas, Planning, & Feedback">🤔</a></td>
439+
</tr>
435440
</tbody>
436441
</table>
437442

packages/usehooks-ts/src/useOnClickOutside/useOnClickOuside.test.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,25 @@ describe('useOnClickOutside(', () => {
4040
expect(handler).toHaveBeenCalledTimes(1)
4141
})
4242

43+
it('should call the handler when a clicking outside the element (multiple refs with a null)', () => {
44+
const containerRef1 = { current: document.createElement('div') }
45+
const containerRef2 = { current: null }
46+
const handler = vitest.fn()
47+
48+
renderHook(() => {
49+
useOnClickOutside([containerRef1, containerRef2], handler)
50+
})
51+
52+
expect(handler).toHaveBeenCalledTimes(0)
53+
54+
// Simulate click outside the containers
55+
act(() => {
56+
fireEvent.mouseDown(document)
57+
})
58+
59+
expect(handler).toHaveBeenCalledTimes(1)
60+
})
61+
4362
it('should NOT call the handler when a clicking inside the element', () => {
4463
const containerRef = { current: document.createElement('div') }
4564
const handler = vitest.fn()

packages/usehooks-ts/src/useOnClickOutside/useOnClickOutside.ts

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@ import type { RefObject } from 'react'
33
import { useEventListener } from '../useEventListener'
44

55
/** Supported event types. */
6-
type EventType = 'mousedown' | 'mouseup' | 'touchstart' | 'touchend'
6+
type EventType =
7+
| 'mousedown'
8+
| 'mouseup'
9+
| 'touchstart'
10+
| 'touchend'
11+
| 'focusin'
12+
| 'focusout'
713

814
/**
915
* Custom hook that handles clicks outside a specified element.
1016
* @template T - The type of the element's reference.
1117
* @param {RefObject<T> | RefObject<T>[]} ref - The React ref object(s) representing the element(s) to watch for outside clicks.
12-
* @param {(event: MouseEvent | TouchEvent) => void} handler - The callback function to be executed when a click outside the element occurs.
18+
* @param {(event: MouseEvent | TouchEvent | FocusEvent) => void} handler - The callback function to be executed when a click outside the element occurs.
1319
* @param {EventType} [eventType] - The mouse event type to listen for (optional, default is 'mousedown').
20+
* @param {?AddEventListenerOptions} [eventListenerOptions] - The options object to be passed to the `addEventListener` method (optional).
1421
* @returns {void}
1522
* @public
1623
* @see [Documentation](https://usehooks-ts.com/react-hook/use-on-click-outside)
@@ -24,23 +31,31 @@ type EventType = 'mousedown' | 'mouseup' | 'touchstart' | 'touchend'
2431
*/
2532
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
2633
ref: RefObject<T> | RefObject<T>[],
27-
handler: (event: MouseEvent | TouchEvent) => void,
34+
handler: (event: MouseEvent | TouchEvent | FocusEvent) => void,
2835
eventType: EventType = 'mousedown',
36+
eventListenerOptions: AddEventListenerOptions = {},
2937
): void {
30-
useEventListener(eventType, event => {
31-
const target = event.target as Node
38+
useEventListener(
39+
eventType,
40+
event => {
41+
const target = event.target as Node
3242

33-
// Do nothing if the target is not connected element with document
34-
if (!target || !target.isConnected) {
35-
return
36-
}
43+
// Do nothing if the target is not connected element with document
44+
if (!target || !target.isConnected) {
45+
return
46+
}
3747

38-
const isOutside = Array.isArray(ref)
39-
? ref.every(r => r.current && !r.current.contains(target))
40-
: ref.current && !ref.current.contains(target)
48+
const isOutside = Array.isArray(ref)
49+
? ref
50+
.filter(r => Boolean(r.current))
51+
.every(r => r.current && !r.current.contains(target))
52+
: ref.current && !ref.current.contains(target)
4153

42-
if (isOutside) {
43-
handler(event)
44-
}
45-
})
54+
if (isOutside) {
55+
handler(event)
56+
}
57+
},
58+
undefined,
59+
eventListenerOptions,
60+
)
4661
}

0 commit comments

Comments
 (0)