From 706a151e1988c02ff6d4a87f81d4c24cac8fcccf Mon Sep 17 00:00:00 2001 From: David Langley Date: Wed, 4 Mar 2026 13:23:41 +0000 Subject: [PATCH] fix(docs): stop LoggedInView from hijacking keypresses in document view MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit LoggedInView has a global keydown handler that redirects any printable character typed outside an input/contenteditable to the send-message composer. When the document editor's outer wrapper div (tabIndex=-1) has focus (i.e. before the first click reaches the contentEditable), this handler fires and dispatches FocusSendMessageComposer, visibly stealing all keystrokes. Fix: skip the redirect when the active element is inside .mx_DocumentView. Same guard added to the paste handler. Also simplify handleContentClick to always call ref.current.focus() on any click inside the content area, not just clicks that miss the editor div — this ensures focus is reliably placed in the contentEditable even when the click lands on a wrapper div. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/components/structures/LoggedInView.tsx | 8 +++++++- .../rooms/wysiwyg_composer/DocumentView.tsx | 17 ++++++----------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/structures/LoggedInView.tsx b/apps/web/src/components/structures/LoggedInView.tsx index 49cabc44e3..185bc7b936 100644 --- a/apps/web/src/components/structures/LoggedInView.tsx +++ b/apps/web/src/components/structures/LoggedInView.tsx @@ -451,6 +451,9 @@ class LoggedInView extends React.Component { const inputableElement = getInputableElement(element); if (inputableElement === document.activeElement) return; // nothing to do + // Don't redirect paste when the document view is active. + if (document.activeElement?.closest(".mx_DocumentView")) return; + if (inputableElement?.focus) { inputableElement.focus(); } else { @@ -670,7 +673,10 @@ class LoggedInView extends React.Component { // If the user is entering a printable character outside of an input field // redirect it to the composer for them. - if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement)) { + // Exception: if the active element is inside the document view, the editor + // should handle typing directly without being redirected. + const inDocumentView = !!document.activeElement?.closest(".mx_DocumentView"); + if (!isClickShortcut && isPrintable && !getInputableElement(ev.target as HTMLElement) && !inDocumentView) { const inThread = !!document.activeElement?.closest(".mx_ThreadView"); // synchronous dispatch so we focus before key generates input dis.dispatch( diff --git a/apps/web/src/components/views/rooms/wysiwyg_composer/DocumentView.tsx b/apps/web/src/components/views/rooms/wysiwyg_composer/DocumentView.tsx index 464d5877f2..14f87845e4 100644 --- a/apps/web/src/components/views/rooms/wysiwyg_composer/DocumentView.tsx +++ b/apps/web/src/components/views/rooms/wysiwyg_composer/DocumentView.tsx @@ -210,17 +210,12 @@ export const DocumentView = memo(function DocumentView({ room }: DocumentViewPro scheduleDeltaSend(); }, [scheduleDeltaSend]); - // Forward clicks anywhere in the content area to the contentEditable so - // the user can click anywhere in the document space to start typing. - const handleContentClick = useCallback( - (ev: React.MouseEvent) => { - // Only forward if the click didn't already land on the editor itself. - if (ev.target !== ref.current && ref.current) { - ref.current.focus(); - } - }, - [ref], - ); + // Forward clicks anywhere in the content area to the contentEditable. + // The click may land on the wrapper divs rather than the contentEditable + // itself, so always ensure the editor has focus after any click in this area. + const handleContentClick = useCallback(() => { + ref.current?.focus(); + }, [ref]); if (!isLoaded) { return
;