From 798ffc058270f007fe6689c281c628fa19d6ca39 Mon Sep 17 00:00:00 2001 From: kolaente Date: Tue, 9 Dec 2025 12:59:01 +0100 Subject: [PATCH] fix: prevent emoji picker from closing when clicking search input (#1959) Resolves https://community.vikunja.io/t/cannot-search-for-reaction-emoji/4207/1 - Fixes the emoji reaction picker closing immediately when clicking inside the search box - The issue occurred because `vuemoji-picker` wraps `emoji-picker-element` which uses Shadow DOM - The `closeWhenClickedOutside` helper was using `parentElement` traversal which doesn't cross shadow boundaries --- frontend/src/components/input/Reactions.vue | 6 +++--- frontend/src/helpers/closeWhenClickedOutside.ts | 17 ++++------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/input/Reactions.vue b/frontend/src/components/input/Reactions.vue index 957e7cf32..7056e9eed 100644 --- a/frontend/src/components/input/Reactions.vue +++ b/frontend/src/components/input/Reactions.vue @@ -96,11 +96,11 @@ function getReactionTooltip(users: IUser[], value: string | number) { } const showEmojiPicker = ref(false) -const emojiPickerRef = ref(null) +const emojiPickerRef = ref | null>(null) function hideEmojiPicker(e: MouseEvent) { - if (showEmojiPicker.value && emojiPickerRef.value) { - closeWhenClickedOutside(e, emojiPickerRef.value, () => showEmojiPicker.value = false) + if (showEmojiPicker.value && emojiPickerRef.value?.$el) { + closeWhenClickedOutside(e, emojiPickerRef.value.$el, () => showEmojiPicker.value = false) } } diff --git a/frontend/src/helpers/closeWhenClickedOutside.ts b/frontend/src/helpers/closeWhenClickedOutside.ts index 56914aeba..7808f4ccd 100644 --- a/frontend/src/helpers/closeWhenClickedOutside.ts +++ b/frontend/src/helpers/closeWhenClickedOutside.ts @@ -6,20 +6,11 @@ * @param closeCallback A closure function to call when the click event happened outside of the rootElement. */ export const closeWhenClickedOutside = (event: MouseEvent, rootElement: HTMLElement, closeCallback: () => void) => { - // We walk up the tree to see if any parent of the clicked element is the root element. - // If it is not, we call the close callback. We're doing all this hassle to only call the - // closing callback when a click happens outside of the rootElement. - let parent = (event.target as HTMLElement)?.parentElement - while (parent !== rootElement) { - if (parent === null || parent.parentElement === null) { - parent = null - break - } + // Use composedPath() to get the full event path including elements inside Shadow DOM. + // This ensures clicks inside shadow roots (like emoji-picker-element) are detected correctly. + const path = event.composedPath() - parent = parent.parentElement - } - - if (parent === rootElement) { + if (path.includes(rootElement)) { return }