Skip to content

Commit

Permalink
feat(hooks): adding useEventListener, useIsomorphicLayoutEffect, useO…
Browse files Browse the repository at this point in the history
…nClickOutside
  • Loading branch information
biswarup35 committed May 6, 2022
1 parent b4d699c commit 35d0b0f
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
3 changes: 3 additions & 0 deletions packages/material-meeu/src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as useIsomorphicLayoutEffect } from "./useIsomorphicLayoutEffect";
export { default as useEventListener } from "./useEventListener";
export { default as useOnClickOutside } from "./useOnClickOutside";
51 changes: 51 additions & 0 deletions packages/material-meeu/src/hooks/useEventListener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { RefObject, useEffect, useRef } from "react";
import useIsomorphicLayoutEffect from "./useIsomorphicLayoutEffect";

function useEventListener<T extends keyof WindowEventMap>(
eventName: T,
handler: (event: WindowEventMap[T]) => void
): void;
function useEventListener<
T extends keyof HTMLElementEventMap,
U extends HTMLElement = HTMLDivElement
>(
eventName: T,
handler: (event: HTMLElementEventMap[T]) => void,
element: RefObject<U>
): void;

function useEventListener<
TW extends keyof WindowEventMap,
UW extends keyof HTMLElementEventMap,
K extends HTMLElement | void = void
>(
eventName: TW | UW,
handler: (
event: WindowEventMap[TW] | HTMLElementEventMap[UW] | Event
) => void,
element?: RefObject<K>
) {
// create a ref that store handler
const _refHandler = useRef(handler);
useIsomorphicLayoutEffect(() => {
_refHandler.current = handler;
}, [handler]);

useEffect(() => {
// define the listing target
const targetElem: K | Window = element?.current ?? window;
if (!(targetElem && targetElem.addEventListener)) {
return;
}
// create event listener that calls handler function stored in ref
const eventListener: typeof handler = (event) => _refHandler.current(event);
targetElem.addEventListener(eventName, eventListener);

// Remove event on cleanup
return () => {
targetElem.removeEventListener(eventName, eventListener);
};
}, [eventName, element]);
}

export default useEventListener;
6 changes: 6 additions & 0 deletions packages/material-meeu/src/hooks/useIsomorphicLayoutEffect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { useEffect, useLayoutEffect } from "react";

const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect;

export default useIsomorphicLayoutEffect;
23 changes: 23 additions & 0 deletions packages/material-meeu/src/hooks/useOnClickOutside.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import useEventListener from "./useEventListener";

type Handler = (event: MouseEvent | TouchEvent) => void;

function useOnClickOutside(
element: HTMLElement | null,
handler: Handler,
mouseEvent:
| "mouseup"
| "mousedown"
| "click"
| "touchstart"
| "touchend" = "mousedown"
): void {
useEventListener(mouseEvent, (event) => {
// Do nothing if clicking ref's element or descendent elements.
if (!element || element.contains(event.target as Node)) {
return;
}
handler(event);
});
}
export default useOnClickOutside;

0 comments on commit 35d0b0f

Please sign in to comment.