diff --git a/kez-chat/web/src/lib/EmojiButton.svelte b/kez-chat/web/src/lib/EmojiButton.svelte index f47257d..11ffd78 100644 --- a/kez-chat/web/src/lib/EmojiButton.svelte +++ b/kez-chat/web/src/lib/EmojiButton.svelte @@ -13,7 +13,10 @@ let { onpick }: Props = $props(); let open = $state(false); - let mountEl: HTMLDivElement | null = $state(null); + /** Outer wrapper — captures the document-click bounds (must include the button). */ + let wrapEl: HTMLDivElement | null = $state(null); + /** Floating popover container — where the custom element mounts. */ + let popoverEl: HTMLDivElement | null = $state(null); let pickerEl: HTMLElement | null = null; let loading = $state(false); let loadError = $state(null); @@ -33,15 +36,21 @@ pickerEl.addEventListener("emoji-click", onEmojiClick as EventListener); // Match the dark-on-light scheme of the rest of the app. pickerEl.classList.add("light"); - if (mountEl) mountEl.appendChild(pickerEl); } catch (e) { loadError = (e as Error).message; } finally { loading = false; } - } else if (mountEl && !mountEl.contains(pickerEl)) { - mountEl.appendChild(pickerEl); } + // Mount into the floating popover div on every open — the popover + // is conditionally rendered, so it's a fresh DOM node each time. + // Wait a tick so the {#if open} block actually mounts before we + // try to appendChild into it. + queueMicrotask(() => { + if (pickerEl && popoverEl && !popoverEl.contains(pickerEl)) { + popoverEl.appendChild(pickerEl); + } + }); document.addEventListener("click", onDocumentClick); } @@ -58,7 +67,10 @@ function onDocumentClick(ev: MouseEvent) { // Close when the user clicks anywhere outside the popover or trigger. - if (mountEl && !mountEl.contains(ev.target as Node)) close(); + const t = ev.target as Node; + const inside = + (wrapEl && wrapEl.contains(t)) || (popoverEl && popoverEl.contains(t)); + if (!inside) close(); } onDestroy(() => { @@ -67,7 +79,10 @@ }); -
+ +
{/if}