From 6a57c13c5688b1a0e9abdff6c2cfefa2e7fcb8f3 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 12 Jun 2026 21:31:07 -0400 Subject: [PATCH] fix: fall back to DOM traversal in targetFromEvent when composedPath is empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit composedPath() returns an empty array once the native event is no longer dispatching. In React 19, events from a portal-within-portal (EmojiBoard inside #portalContainer, which already hosts the Settings Overlay) can reach the synthetic event handler after the native dispatch window closes. The fallback walks up from evt.target so handleGroupItemClick still finds the emoji button and calls onEmojiSelect — fixing emoji selection in the status-message editor in Settings > Account > Profile. Co-Authored-By: Claude Sonnet 4.6 --- src/app/utils/dom.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/app/utils/dom.ts b/src/app/utils/dom.ts index 3ced8f57f..81f4a8087 100644 --- a/src/app/utils/dom.ts +++ b/src/app/utils/dom.ts @@ -1,6 +1,16 @@ export const targetFromEvent = (evt: Event, selector: string): Element | undefined => { const targets = evt.composedPath() as Element[]; - return targets.find((target) => target.matches?.(selector)); + if (targets.length > 0) { + return targets.find((target) => target.matches?.(selector)); + } + // composedPath() is empty when the event is no longer dispatching (e.g. inside a + // portal-within-portal in React 19). Walk up the DOM from evt.target instead. + let el = evt.target instanceof Element ? evt.target : null; + while (el) { + if (el.matches(selector)) return el; + el = el.parentElement; + } + return undefined; }; export const editableActiveElement = (): boolean =>