Properly handle closing click events in modals
This commit is contained in:
parent
b348bf9b70
commit
635840cd99
13 changed files with 257 additions and 178 deletions
68
ts/util/handleOutsideClick.ts
Normal file
68
ts/util/handleOutsideClick.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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);
|
||||
}
|
||||
};
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue