Fix outside click in story replies

This commit is contained in:
Fedor Indutny 2022-09-29 13:13:45 -07:00 committed by GitHub
parent b449450098
commit f64426fbe0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 56 additions and 40 deletions

View File

@ -42,7 +42,7 @@ export class EmojiCompletion {
quill: Quill;
outsideClickDestructor: () => void;
outsideClickDestructor?: () => void;
constructor(quill: Quill, options: EmojiPickerOptions) {
this.results = [];
@ -51,18 +51,6 @@ export class EmojiCompletion {
this.root = document.body.appendChild(document.createElement('div'));
this.quill = quill;
// Just to make sure that we don't propagate outside clicks until this
// is closed.
this.outsideClickDestructor = handleOutsideClick(
() => {
return true;
},
{
name: 'quill.emoji.completion',
containerElements: [this.root],
}
);
const clearResults = () => {
if (this.results.length) {
this.reset();
@ -108,7 +96,8 @@ export class EmojiCompletion {
}
destroy(): void {
this.outsideClickDestructor();
this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.root.remove();
}
@ -277,14 +266,16 @@ export class EmojiCompletion {
}
onUnmount(): void {
document.body.removeChild(this.root);
this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.options.setEmojiPickerElement(null);
}
render(): void {
const { results: emojiResults, index: emojiResultsIndex } = this;
if (emojiResults.length === 0) {
this.options.setEmojiPickerElement(null);
this.onUnmount();
return;
}
@ -374,7 +365,21 @@ export class EmojiCompletion {
</div>
)}
</Popper>,
document.body
this.root
);
// Just to make sure that we don't propagate outside clicks until this
// is closed.
this.outsideClickDestructor?.();
this.outsideClickDestructor = handleOutsideClick(
() => {
this.onUnmount();
return true;
},
{
name: 'quill.emoji.completion',
containerElements: [this.root],
}
);
this.options.setEmojiPickerElement(element);

View File

@ -43,7 +43,7 @@ export class MentionCompletion {
suggestionListRef: RefObject<HTMLDivElement>;
outsideClickDestructor: () => void;
outsideClickDestructor?: () => void;
constructor(quill: Quill, options: MentionCompletionOptions) {
this.results = [];
@ -53,18 +53,6 @@ export class MentionCompletion {
this.quill = quill;
this.suggestionListRef = React.createRef<HTMLDivElement>();
// Just to make sure that we don't propagate outside clicks until this
// is closed.
this.outsideClickDestructor = handleOutsideClick(
() => {
return true;
},
{
name: 'quill.emoji.completion',
containerElements: [this.root],
}
);
const clearResults = () => {
if (this.results.length) {
this.clearResults();
@ -92,7 +80,9 @@ export class MentionCompletion {
}
destroy(): void {
this.outsideClickDestructor();
this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.root.remove();
}
@ -220,7 +210,9 @@ export class MentionCompletion {
}
onUnmount(): void {
document.body.removeChild(this.root);
this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.options.setMentionPickerElement(null);
}
render(): void {
@ -228,7 +220,7 @@ export class MentionCompletion {
const { getPreferredBadge, theme } = this.options;
if (memberResults.length === 0) {
this.options.setMentionPickerElement(null);
this.onUnmount();
return;
}
@ -293,6 +285,20 @@ export class MentionCompletion {
this.root
);
// Just to make sure that we don't propagate outside clicks until this
// is closed.
this.outsideClickDestructor?.();
this.outsideClickDestructor = handleOutsideClick(
() => {
this.onUnmount();
return true;
},
{
name: 'quill.mentions.completion',
containerElements: [this.root],
}
);
this.options.setMentionPickerElement(element);
}
}

View File

@ -8,11 +8,15 @@ 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>();
const fakeClickHandlers = new Array<{
name: string;
handleEvent: (event: MouseEvent) => boolean;
}>();
function runFakeClickHandlers(event: MouseEvent): void {
for (const handler of fakeClickHandlers.slice().reverse()) {
if (handler(event)) {
for (const entry of fakeClickHandlers.slice().reverse()) {
const { handleEvent } = entry;
if (handleEvent(event)) {
break;
}
}
@ -25,7 +29,7 @@ export type HandleOutsideClickOptionsType = Readonly<{
export const handleOutsideClick = (
handler: ClickHandlerType,
{ containerElements }: HandleOutsideClickOptionsType
{ name, containerElements }: HandleOutsideClickOptionsType
): (() => void) => {
const handleEvent = (event: MouseEvent) => {
const target = event.target as Node;
@ -49,19 +53,20 @@ export const handleOutsideClick = (
return handler(target);
};
fakeClickHandlers.push(handleEvent);
const fakeHandler = { name, handleEvent };
fakeClickHandlers.push(fakeHandler);
if (fakeClickHandlers.length === 1) {
const useCapture = true;
document.addEventListener('click', runFakeClickHandlers, useCapture);
}
return () => {
const index = fakeClickHandlers.indexOf(handleEvent);
const index = fakeClickHandlers.indexOf(fakeHandler);
fakeClickHandlers.splice(index, 1);
if (fakeClickHandlers.length === 0) {
const useCapture = true;
document.removeEventListener('click', handleEvent, useCapture);
document.removeEventListener('click', runFakeClickHandlers, useCapture);
}
};
};