Skip to content

Commit

Permalink
🐛 Fix/use on click outside (#552)
Browse files Browse the repository at this point in the history
* Support missing refs (fixes #531)

* Add support for focus event to `useOnClickOutside` (Fixes: #522)

* Expose `AddEventListenerOptions` in `useOnClickOutside` (Fixes #554 from @metav-drimz)
  • Loading branch information
juliencrn authored Mar 21, 2024
1 parent 59c0b93 commit b14db5b
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 18 deletions.
22 changes: 22 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -1726,8 +1726,30 @@
"avatar_url": "https://avatars.githubusercontent.com/u/10504365?v=4",
"profile": "https://github.com/Newbie012",
"contributions": ["code"]
},
{
"login": "isumix",
"name": "Igor Sukharev",
"avatar_url": "https://avatars.githubusercontent.com/u/16747416?v=4",
"profile": "https://github.com/isumix",
"contributions": ["bug"]
},
{
"login": "pookmish",
"name": "pookmish",
"avatar_url": "https://avatars.githubusercontent.com/u/7185045?v=4",
"profile": "https://github.com/pookmish",
"contributions": ["ideas"]
},
{
"login": "metav-drimz",
"name": "metav-drimz",
"avatar_url": "https://avatars.githubusercontent.com/u/113976282?v=4",
"profile": "https://github.com/metav-drimz",
"contributions": ["ideas"]
}
],
"contributorsPerLine": 7,
"commitType": "docs"
}

5 changes: 5 additions & 0 deletions .changeset/five-impalas-suffer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"usehooks-ts": patch
---

Add support for focus event to `useOnClickOutside` (Fixes: #522)
5 changes: 5 additions & 0 deletions .changeset/poor-forks-end.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"usehooks-ts": patch
---

Expose `AddEventListenerOptions` in `useOnClickOutside` (Fixes #554 from @metav-drimz)
5 changes: 5 additions & 0 deletions .changeset/rare-icons-tickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"usehooks-ts": patch
---

Support missing refs in `useOnClickOutside` (Fixes: #531)
5 changes: 5 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
<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>
<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>
</tr>
<tr>
<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>
<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>
<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>
</tr>
</tbody>
</table>

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

<br />
Expand Down Expand Up @@ -432,6 +432,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
<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>
<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>
</tr>
<tr>
<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>
<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>
<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>
</tr>
</tbody>
</table>

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

<br />
Expand Down Expand Up @@ -432,6 +432,11 @@ Big thanks go to all our contributors! [[Become a contributor](https://github.co
<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>
<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>
</tr>
<tr>
<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>
<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>
<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>
</tr>
</tbody>
</table>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,25 @@ describe('useOnClickOutside(', () => {
expect(handler).toHaveBeenCalledTimes(1)
})

it('should call the handler when a clicking outside the element (multiple refs with a null)', () => {
const containerRef1 = { current: document.createElement('div') }
const containerRef2 = { current: null }
const handler = vitest.fn()

renderHook(() => {
useOnClickOutside([containerRef1, containerRef2], handler)
})

expect(handler).toHaveBeenCalledTimes(0)

// Simulate click outside the containers
act(() => {
fireEvent.mouseDown(document)
})

expect(handler).toHaveBeenCalledTimes(1)
})

it('should NOT call the handler when a clicking inside the element', () => {
const containerRef = { current: document.createElement('div') }
const handler = vitest.fn()
Expand Down
47 changes: 31 additions & 16 deletions packages/usehooks-ts/src/useOnClickOutside/useOnClickOutside.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,21 @@ import type { RefObject } from 'react'
import { useEventListener } from '../useEventListener'

/** Supported event types. */
type EventType = 'mousedown' | 'mouseup' | 'touchstart' | 'touchend'
type EventType =
| 'mousedown'
| 'mouseup'
| 'touchstart'
| 'touchend'
| 'focusin'
| 'focusout'

/**
* Custom hook that handles clicks outside a specified element.
* @template T - The type of the element's reference.
* @param {RefObject<T> | RefObject<T>[]} ref - The React ref object(s) representing the element(s) to watch for outside clicks.
* @param {(event: MouseEvent | TouchEvent) => void} handler - The callback function to be executed when a click outside the element occurs.
* @param {(event: MouseEvent | TouchEvent | FocusEvent) => void} handler - The callback function to be executed when a click outside the element occurs.
* @param {EventType} [eventType] - The mouse event type to listen for (optional, default is 'mousedown').
* @param {?AddEventListenerOptions} [eventListenerOptions] - The options object to be passed to the `addEventListener` method (optional).
* @returns {void}
* @public
* @see [Documentation](https://usehooks-ts.com/react-hook/use-on-click-outside)
Expand All @@ -24,23 +31,31 @@ type EventType = 'mousedown' | 'mouseup' | 'touchstart' | 'touchend'
*/
export function useOnClickOutside<T extends HTMLElement = HTMLElement>(
ref: RefObject<T> | RefObject<T>[],
handler: (event: MouseEvent | TouchEvent) => void,
handler: (event: MouseEvent | TouchEvent | FocusEvent) => void,
eventType: EventType = 'mousedown',
eventListenerOptions: AddEventListenerOptions = {},
): void {
useEventListener(eventType, event => {
const target = event.target as Node
useEventListener(
eventType,
event => {
const target = event.target as Node

// Do nothing if the target is not connected element with document
if (!target || !target.isConnected) {
return
}
// Do nothing if the target is not connected element with document
if (!target || !target.isConnected) {
return
}

const isOutside = Array.isArray(ref)
? ref.every(r => r.current && !r.current.contains(target))
: ref.current && !ref.current.contains(target)
const isOutside = Array.isArray(ref)
? ref
.filter(r => Boolean(r.current))
.every(r => r.current && !r.current.contains(target))
: ref.current && !ref.current.contains(target)

if (isOutside) {
handler(event)
}
})
if (isOutside) {
handler(event)
}
},
undefined,
eventListenerOptions,
)
}

0 comments on commit b14db5b

Please sign in to comment.