Fix outside click in story replies

Co-authored-by: Fedor Indutny <79877362+indutny-signal@users.noreply.github.com>
This commit is contained in:
automated-signal 2022-09-29 14:17:05 -07:00 committed by GitHub
parent fd74595afc
commit 683cb027b1
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; quill: Quill;
outsideClickDestructor: () => void; outsideClickDestructor?: () => void;
constructor(quill: Quill, options: EmojiPickerOptions) { constructor(quill: Quill, options: EmojiPickerOptions) {
this.results = []; this.results = [];
@ -51,18 +51,6 @@ export class EmojiCompletion {
this.root = document.body.appendChild(document.createElement('div')); this.root = document.body.appendChild(document.createElement('div'));
this.quill = quill; 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 = () => { const clearResults = () => {
if (this.results.length) { if (this.results.length) {
this.reset(); this.reset();
@ -108,7 +96,8 @@ export class EmojiCompletion {
} }
destroy(): void { destroy(): void {
this.outsideClickDestructor(); this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.root.remove(); this.root.remove();
} }
@ -277,14 +266,16 @@ export class EmojiCompletion {
} }
onUnmount(): void { onUnmount(): void {
document.body.removeChild(this.root); this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.options.setEmojiPickerElement(null);
} }
render(): void { render(): void {
const { results: emojiResults, index: emojiResultsIndex } = this; const { results: emojiResults, index: emojiResultsIndex } = this;
if (emojiResults.length === 0) { if (emojiResults.length === 0) {
this.options.setEmojiPickerElement(null); this.onUnmount();
return; return;
} }
@ -374,7 +365,21 @@ export class EmojiCompletion {
</div> </div>
)} )}
</Popper>, </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); this.options.setEmojiPickerElement(element);

View File

@ -43,7 +43,7 @@ export class MentionCompletion {
suggestionListRef: RefObject<HTMLDivElement>; suggestionListRef: RefObject<HTMLDivElement>;
outsideClickDestructor: () => void; outsideClickDestructor?: () => void;
constructor(quill: Quill, options: MentionCompletionOptions) { constructor(quill: Quill, options: MentionCompletionOptions) {
this.results = []; this.results = [];
@ -53,18 +53,6 @@ export class MentionCompletion {
this.quill = quill; this.quill = quill;
this.suggestionListRef = React.createRef<HTMLDivElement>(); 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 = () => { const clearResults = () => {
if (this.results.length) { if (this.results.length) {
this.clearResults(); this.clearResults();
@ -92,7 +80,9 @@ export class MentionCompletion {
} }
destroy(): void { destroy(): void {
this.outsideClickDestructor(); this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.root.remove(); this.root.remove();
} }
@ -220,7 +210,9 @@ export class MentionCompletion {
} }
onUnmount(): void { onUnmount(): void {
document.body.removeChild(this.root); this.outsideClickDestructor?.();
this.outsideClickDestructor = undefined;
this.options.setMentionPickerElement(null);
} }
render(): void { render(): void {
@ -228,7 +220,7 @@ export class MentionCompletion {
const { getPreferredBadge, theme } = this.options; const { getPreferredBadge, theme } = this.options;
if (memberResults.length === 0) { if (memberResults.length === 0) {
this.options.setMentionPickerElement(null); this.onUnmount();
return; return;
} }
@ -293,6 +285,20 @@ export class MentionCompletion {
this.root 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); this.options.setMentionPickerElement(element);
} }
} }

View File

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