Files
Mastodon/app/javascript/mastodon/hooks/useOnClickOutside.ts
T

54 lines
1.6 KiB
TypeScript

/**
* Handle clicks that occur outside of the element(s) provided in the first parameter
*/
import type { RefObject } from 'react';
import { useEffect, useEffectEvent } from 'react';
type PlainElement = Element | null;
type ElementOrRef = PlainElement | RefObject<PlainElement>;
export function useOnClickOutside(
excludedElement: ElementOrRef | ElementOrRef[],
onClick: (e: MouseEvent) => void,
enabled = true,
) {
const handleClickOutside = useEffectEvent((event: MouseEvent) => {
const excludedRefs = Array.isArray(excludedElement)
? excludedElement
: [excludedElement];
for (const ref of excludedRefs) {
const excludedElement = ref instanceof Element ? ref : ref?.current;
// Bail out if the clicked element or the currently focused element
// is inside of excludedElement. We're also checking the focused element
// to prevent an issue in Chrome where initiating a drag inside of an
// input (to select the text inside of it) and ending that drag outside
// of the input fires a click event, breaking our excludedElement rule.
if (
excludedElement &&
(excludedElement === event.target ||
excludedElement === document.activeElement ||
excludedElement.contains(event.target as Node) ||
excludedElement.contains(document.activeElement))
) {
return;
}
}
onClick(event);
});
useEffect(() => {
if (enabled) {
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}
return () => null;
}, [enabled]);
}