69 lines
1.8 KiB
TypeScript
69 lines
1.8 KiB
TypeScript
|
// Copyright 2022 Signal Messenger, LLC
|
||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
|
||
|
import type { RefObject } from 'react';
|
||
|
|
||
|
export type ClickHandlerType = (target: Node) => boolean;
|
||
|
export type ContainerElementType = Node | RefObject<Node> | null | undefined;
|
||
|
|
||
|
// TODO(indutny): DESKTOP-4177
|
||
|
// A stack of handlers. Handlers are executed from the top to the bottom
|
||
|
const fakeClickHandlers = new Array<(event: MouseEvent) => boolean>();
|
||
|
|
||
|
function runFakeClickHandlers(event: MouseEvent): void {
|
||
|
for (const handler of fakeClickHandlers.slice().reverse()) {
|
||
|
if (handler(event)) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
export type HandleOutsideClickOptionsType = Readonly<{
|
||
|
containerElements: ReadonlyArray<ContainerElementType>;
|
||
|
}>;
|
||
|
|
||
|
export const handleOutsideClick = (
|
||
|
handler: ClickHandlerType,
|
||
|
{ containerElements }: HandleOutsideClickOptionsType
|
||
|
): (() => void) => {
|
||
|
const handleEvent = (event: MouseEvent) => {
|
||
|
const target = event.target as Node;
|
||
|
|
||
|
const isInside = containerElements.some(elem => {
|
||
|
if (!elem) {
|
||
|
return false;
|
||
|
}
|
||
|
if (elem instanceof Node) {
|
||
|
return elem.contains(target);
|
||
|
}
|
||
|
return elem.current?.contains(target);
|
||
|
});
|
||
|
if (isInside) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
const isHandled = handler(target);
|
||
|
if (!isHandled) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
fakeClickHandlers.push(handleEvent);
|
||
|
if (fakeClickHandlers.length === 1) {
|
||
|
const useCapture = true;
|
||
|
document.addEventListener('click', runFakeClickHandlers, useCapture);
|
||
|
}
|
||
|
|
||
|
return () => {
|
||
|
const index = fakeClickHandlers.indexOf(handleEvent);
|
||
|
fakeClickHandlers.splice(index, 1);
|
||
|
|
||
|
if (fakeClickHandlers.length === 0) {
|
||
|
const useCapture = true;
|
||
|
document.removeEventListener('click', handleEvent, useCapture);
|
||
|
}
|
||
|
};
|
||
|
};
|