diff --git a/src/hooks/useClickOutside/useClickOutside.svelte b/src/hooks/useClickOutside/useClickOutside.svelte
new file mode 100644
index 0000000..3ef1b3a
--- /dev/null
+++ b/src/hooks/useClickOutside/useClickOutside.svelte
@@ -0,0 +1,22 @@
+
+
+
diff --git a/src/hooks/useClickOutside/useClickOutside.test.ts b/src/hooks/useClickOutside/useClickOutside.test.ts
new file mode 100644
index 0000000..22eef17
--- /dev/null
+++ b/src/hooks/useClickOutside/useClickOutside.test.ts
@@ -0,0 +1,43 @@
+import { render, screen } from "@testing-library/svelte";
+import userEvent from "@testing-library/user-event";
+
+import UseClickOutside from "@/hooks/useClickOutside/useClickOutside.svelte";
+
+vi.mock("esm-env", async (importOriginal) => {
+ const actual: any = await importOriginal();
+
+ return {
+ ...actual,
+ BROWSER: true,
+ };
+});
+
+describe("Hooks - useClickOutside", () => {
+ test("should work on click outside the element", async () => {
+ const clickMockFn = vi.fn();
+
+ render(UseClickOutside, { onClick: clickMockFn });
+
+ const element = screen.getByText(/hello/i);
+
+ await userEvent.click(element);
+
+ expect(clickMockFn).not.toBeCalled();
+
+ await userEvent.click(document.body);
+
+ expect(clickMockFn).toBeCalled();
+ });
+
+ test("should NOT work after unmount component", async () => {
+ const clickMockFn = vi.fn();
+
+ const { unmount } = render(UseClickOutside, { onClick: clickMockFn });
+
+ await unmount();
+
+ await userEvent.click(document.body);
+
+ expect(clickMockFn).not.toBeCalled();
+ });
+});
diff --git a/src/hooks/useClickOutside/useClickOutside.ts b/src/hooks/useClickOutside/useClickOutside.ts
index 7957061..ee7ca5e 100644
--- a/src/hooks/useClickOutside/useClickOutside.ts
+++ b/src/hooks/useClickOutside/useClickOutside.ts
@@ -1,19 +1,40 @@
+import { onMount } from "svelte";
+
import { useEventListener } from "..";
type Handler = (event: MouseEvent) => void;
-export function useClickOutside(
- ref: T,
- handler: Handler,
- mouseEvent: "mousedown" | "mouseup" = "mousedown",
-): void {
- useEventListener(mouseEvent, (event: MouseEvent) => {
- const el = ref;
+type Options = {
+ handler: Handler;
+ mouseEvent?: "mousedown" | "mouseup";
+ component?: boolean;
+};
+
+export function useClickOutside({
+ handler,
+ mouseEvent = "mousedown",
+ component = false,
+}: Options): { setElementRef: (elm: T) => void; removeListener: () => void } {
+ let element: T;
- if (!el || el.contains(event.target as Node)) {
+ const removeListener = useEventListener(mouseEvent, (event: MouseEvent) => {
+ if (!element || element.contains(event.target as Node)) {
return;
}
handler(event);
});
+
+ if (component) {
+ onMount(() => {
+ return () => {
+ removeListener();
+ };
+ });
+ }
+
+ return {
+ setElementRef: (elm) => (element = elm),
+ removeListener,
+ };
}
diff --git a/src/routes/guide/+layout.server.ts b/src/routes/guide/+layout.server.ts
index e65a248..c1e2e13 100644
--- a/src/routes/guide/+layout.server.ts
+++ b/src/routes/guide/+layout.server.ts
@@ -14,6 +14,10 @@ export async function load({ route }) {
label: "useClickAnyWhere",
link: "/guide/useClickAnyWhere",
},
+ {
+ label: "useClickOutside",
+ link: "/guide/useClickOutside",
+ },
{
label: "useClipboard",
link: "/guide/useClipboard",
diff --git a/src/routes/guide/useClickOutside/+page.svelte b/src/routes/guide/useClickOutside/+page.svelte
new file mode 100644
index 0000000..3bf3aa2
--- /dev/null
+++ b/src/routes/guide/useClickOutside/+page.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
Hook to catch the click on any part of the site
+
+
Related hooks
+
+
+
+
+
+
Visual example
+
+
+ click outside here
+
+
+
+
+
Code example
+
+
+
+
diff --git a/src/routes/guide/useClickOutside/code-snippet.js b/src/routes/guide/useClickOutside/code-snippet.js
new file mode 100644
index 0000000..48896eb
--- /dev/null
+++ b/src/routes/guide/useClickOutside/code-snippet.js
@@ -0,0 +1,24 @@
+export default `
+
+
+
+
+
+ content
+
+`;