handleOutsideClick should drop events that started inside
This commit is contained in:
parent
96de08cef9
commit
6f1d824c3d
2 changed files with 67 additions and 32 deletions
|
@ -303,6 +303,7 @@ export function ForwardMessagesModal({
|
||||||
useFocusTrap={false}
|
useFocusTrap={false}
|
||||||
padded={false}
|
padded={false}
|
||||||
modalFooter={footer}
|
modalFooter={footer}
|
||||||
|
noMouseClose
|
||||||
>
|
>
|
||||||
{isEditingMessage && lonelyDraft != null ? (
|
{isEditingMessage && lonelyDraft != null ? (
|
||||||
<ForwardMessageEditor
|
<ForwardMessageEditor
|
||||||
|
|
|
@ -2,39 +2,50 @@
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
|
import * as log from '../logging/log';
|
||||||
|
|
||||||
export type ClickHandlerType = (target: Node) => boolean;
|
export type HandlerType = (target: Node) => boolean;
|
||||||
|
export type HandlersType = {
|
||||||
|
name: string;
|
||||||
|
handleClick: HandlerType;
|
||||||
|
handlePointerDown: HandlerType;
|
||||||
|
};
|
||||||
export type ContainerElementType = Node | RefObject<Node> | null | undefined;
|
export type ContainerElementType = Node | RefObject<Node> | null | undefined;
|
||||||
|
|
||||||
// TODO(indutny): DESKTOP-4177
|
// TODO(indutny): DESKTOP-4177
|
||||||
// A stack of handlers. Handlers are executed from the top to the bottom
|
// A stack of handlers. Handlers are executed from the top to the bottom
|
||||||
const fakeClickHandlers = new Array<{
|
const fakeHandlers = new Array<HandlersType>();
|
||||||
name: string;
|
|
||||||
handleEvent: (event: MouseEvent) => boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function runFakeClickHandlers(event: MouseEvent): void {
|
|
||||||
for (const entry of fakeClickHandlers.slice().reverse()) {
|
|
||||||
const { handleEvent } = entry;
|
|
||||||
if (handleEvent(event)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type HandleOutsideClickOptionsType = Readonly<{
|
export type HandleOutsideClickOptionsType = Readonly<{
|
||||||
name: string;
|
name: string;
|
||||||
containerElements: ReadonlyArray<ContainerElementType>;
|
containerElements: ReadonlyArray<ContainerElementType>;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
function handleGlobalPointerDown(event: MouseEvent) {
|
||||||
|
for (const handlers of fakeHandlers) {
|
||||||
|
// continue even if handled, so that we can detect if the click was inside
|
||||||
|
handlers.handlePointerDown(event.target as Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleGlobalClick(event: MouseEvent) {
|
||||||
|
for (const handlers of fakeHandlers.slice().reverse()) {
|
||||||
|
const handled = handlers.handleClick(event.target as Node);
|
||||||
|
if (handled) {
|
||||||
|
log.info(`handleOutsideClick: ${handlers.name} handled click`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventOptions = { capture: true };
|
||||||
|
|
||||||
export const handleOutsideClick = (
|
export const handleOutsideClick = (
|
||||||
handler: ClickHandlerType,
|
handler: HandlerType,
|
||||||
{ name, containerElements }: HandleOutsideClickOptionsType
|
{ name, containerElements }: HandleOutsideClickOptionsType
|
||||||
): (() => void) => {
|
): (() => void) => {
|
||||||
const handleEvent = (event: MouseEvent) => {
|
function isInside(target: Node) {
|
||||||
const target = event.target as Node;
|
return containerElements.some(elem => {
|
||||||
|
|
||||||
const isInside = containerElements.some(elem => {
|
|
||||||
if (!elem) {
|
if (!elem) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -43,30 +54,53 @@ export const handleOutsideClick = (
|
||||||
}
|
}
|
||||||
return elem.current?.contains(target);
|
return elem.current?.contains(target);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let startedInside = false;
|
||||||
|
|
||||||
|
function handlePointerDown(target: Node) {
|
||||||
|
startedInside = isInside(target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick(target: Node) {
|
||||||
|
const endedInside = isInside(target);
|
||||||
// Clicked inside of one of container elements - stop processing
|
// Clicked inside of one of container elements - stop processing
|
||||||
if (isInside) {
|
if (startedInside || endedInside) {
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop processing if requested by handler function
|
// Stop processing if requested by handler function
|
||||||
return handler(target);
|
return handler(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fakeHandler = {
|
||||||
|
name,
|
||||||
|
handleClick,
|
||||||
|
handlePointerDown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const fakeHandler = { name, handleEvent };
|
fakeHandlers.push(fakeHandler);
|
||||||
fakeClickHandlers.push(fakeHandler);
|
|
||||||
if (fakeClickHandlers.length === 1) {
|
if (fakeHandlers.length === 1) {
|
||||||
const useCapture = true;
|
document.addEventListener(
|
||||||
document.addEventListener('click', runFakeClickHandlers, useCapture);
|
'pointerdown',
|
||||||
|
handleGlobalPointerDown,
|
||||||
|
eventOptions
|
||||||
|
);
|
||||||
|
document.addEventListener('click', handleGlobalClick, eventOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
const index = fakeClickHandlers.indexOf(fakeHandler);
|
const index = fakeHandlers.indexOf(fakeHandler);
|
||||||
fakeClickHandlers.splice(index, 1);
|
fakeHandlers.splice(index, 1);
|
||||||
|
|
||||||
if (fakeClickHandlers.length === 0) {
|
if (fakeHandlers.length === 0) {
|
||||||
const useCapture = true;
|
document.removeEventListener(
|
||||||
document.removeEventListener('click', runFakeClickHandlers, useCapture);
|
'pointerdown',
|
||||||
|
handleGlobalPointerDown,
|
||||||
|
eventOptions
|
||||||
|
);
|
||||||
|
document.removeEventListener('click', handleGlobalClick, eventOptions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue