// 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 | 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; }>; 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); } }; };