From de5d05aff008a3d7a90f01550cbf59f35249b38c Mon Sep 17 00:00:00 2001 From: nicholas Date: Mon, 1 Apr 2024 22:09:52 -0400 Subject: [PATCH 1/5] Add useClickOutside composition --- src/useClickOutside/README.md | 35 ++++++++++++ src/useClickOutside/index.ts | 1 + src/useClickOutside/useClickOutside.ts | 74 ++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100644 src/useClickOutside/README.md create mode 100644 src/useClickOutside/index.ts create mode 100644 src/useClickOutside/useClickOutside.ts diff --git a/src/useClickOutside/README.md b/src/useClickOutside/README.md new file mode 100644 index 0000000..3bcaecd --- /dev/null +++ b/src/useClickOutside/README.md @@ -0,0 +1,35 @@ +# useClickOutside +The `useClickOutside` composition is designed to detect and react to clicks outside a specified element. It takes an element and a callback function as arguments. The composition returns `on` and `off` functions, allowing for manual control over the event listener if needed, although automatic cleanup is handled as part of the component's lifecycle. + +## Example +```vue + + + +``` + +## Arguments +| Name | Type | +|----------|-----------------------------------| +| element | `MaybeRefOrGetter | +| callback | `() => void` | + +## Returns + +| Name | Type | Description | +|--------|-------------|---------------------------------------------------| +| on | `() => void` | Manually turn on the event listener (note: this isn't needed unless `off` has been called) | +| off | `() => void` | Manually turn off the event listener (note: this isn't needed for cleanup - the event listener will be removed automatically as part of the component lifecycle) | diff --git a/src/useClickOutside/index.ts b/src/useClickOutside/index.ts new file mode 100644 index 0000000..f00103b --- /dev/null +++ b/src/useClickOutside/index.ts @@ -0,0 +1 @@ +export * from './useClickOutside' \ No newline at end of file diff --git a/src/useClickOutside/useClickOutside.ts b/src/useClickOutside/useClickOutside.ts new file mode 100644 index 0000000..c05b821 --- /dev/null +++ b/src/useClickOutside/useClickOutside.ts @@ -0,0 +1,74 @@ +import { onScopeDispose } from 'vue' +import { useGlobalEventListener } from '..' +import { MaybeRefOrGetter } from '@/types/maybe' +import { toValue } from '@/utilities/vue' + +type ClickOutsideEntry = { + element: MaybeRefOrGetter, + callback: () => void, +} + +const callbacks = new Map() + +function handleClick(event: MouseEvent): void { + for (const { element, callback } of callbacks.values()) { + const elementValue = toValue(element) + + if (!elementValue.contains(event.target as Node)) { + callback() + } + } +} + +const { add, remove } = useGlobalEventListener('click', handleClick, { capture: true }) + +function tryTeardownEventListener(): void { + if (callbacks.size > 0) { + return + } + + remove() +} + +function tryAddEventListener(): void { + if (callbacks.size > 0) { + return + } + + add() +} + +export type UseClickOutsideCallbackFunction = () => void + +export type UseClickOutside = { + off: () => void, + on: () => void, +} + +export function useClickOutside(element: MaybeRefOrGetter, callback: UseClickOutsideCallbackFunction): UseClickOutside { + const id = Symbol('useClickOutside') + + callbacks.set(id, { element, callback }) + + tryAddEventListener() + + onScopeDispose(() => { + off() + tryTeardownEventListener() + }) + + function off(): void { + callbacks.delete(id) + tryTeardownEventListener() + } + + function on(): void { + callbacks.set(id, { element, callback }) + tryAddEventListener() + } + + return { + off, + on, + } +} \ No newline at end of file From eac29960c862dc0c5c02d30fe187e12c1b9c0686 Mon Sep 17 00:00:00 2001 From: nicholas Date: Mon, 1 Apr 2024 22:11:28 -0400 Subject: [PATCH 2/5] Change order of onscopedispose invocation --- src/useClickOutside/useClickOutside.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/useClickOutside/useClickOutside.ts b/src/useClickOutside/useClickOutside.ts index c05b821..3748890 100644 --- a/src/useClickOutside/useClickOutside.ts +++ b/src/useClickOutside/useClickOutside.ts @@ -52,11 +52,6 @@ export function useClickOutside(element: MaybeRefOrGetter, callback: Us tryAddEventListener() - onScopeDispose(() => { - off() - tryTeardownEventListener() - }) - function off(): void { callbacks.delete(id) tryTeardownEventListener() @@ -67,6 +62,11 @@ export function useClickOutside(element: MaybeRefOrGetter, callback: Us tryAddEventListener() } + onScopeDispose(() => { + off() + tryTeardownEventListener() + }) + return { off, on, From 83b626683308fe4ea4a9eefd9927c75386b4a2ee Mon Sep 17 00:00:00 2001 From: nicholas Date: Mon, 1 Apr 2024 22:12:01 -0400 Subject: [PATCH 3/5] Add useClickOutside to bucket exports --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index 6bd750e..1769d06 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ export * from './useBoolean' export * from './useChildrenAreWrapped' +export * from './useClickOutside' export * from './useComputedStyle' export * from './useDebouncedRef' export * from './useElementRect' From ffee704499cc4084f1ededf34a2b9fc3d6892d28 Mon Sep 17 00:00:00 2001 From: nicholas Date: Mon, 1 Apr 2024 22:12:50 -0400 Subject: [PATCH 4/5] Update global readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 1b633cc..de64dfc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ npm i --save @prefecthq/vue-compositions - [useBoolean](https://github.com/prefecthq/vue-compositions/tree/main/src/useBoolean) - [useChildrenAreWrapped](https://github.com/prefecthq/vue-compositions/tree/main/src/useChildrenAreWrapped) +- [useClickOutside](https://github.com/prefecthq/vue-compositions/tree/main/src/useClickOutside) - [useComputedStyle](https://github.com/prefecthq/vue-compositions/tree/main/src/useComputedStyle) - [useDebouncedRef](https://github.com/prefecthq/vue-compositions/tree/main/src/useDebouncedRef) - [useElementRect](https://github.com/prefecthq/vue-compositions/tree/main/src/useElementRect) From 2215e92c2aaea62bc0cc8b9ff6ed3d9c855dece5 Mon Sep 17 00:00:00 2001 From: nicholas Date: Mon, 1 Apr 2024 22:14:23 -0400 Subject: [PATCH 5/5] Fix ref in readme --- src/useClickOutside/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/useClickOutside/README.md b/src/useClickOutside/README.md index 3bcaecd..0cd3ca1 100644 --- a/src/useClickOutside/README.md +++ b/src/useClickOutside/README.md @@ -16,7 +16,7 @@ function handleClickOutside(): void { } const el = ref() -const { on, off } = useClickOutside(children, handleClickOutside) +const { on, off } = useClickOutside(el, handleClickOutside) // Don't need to do anything with on/off unless your handler needs to be paused for some reason ```