From 90e751141f7eeadb397f31dbd98e843dc5e830dc Mon Sep 17 00:00:00 2001 From: trevor-signal <131492920+trevor-signal@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:00:18 -0500 Subject: [PATCH] Ensure composition input is focused when clicking to edit or reply to message --- ts/axo/AxoContextMenu.dom.tsx | 1 + ts/axo/AxoDropdownMenu.dom.tsx | 4 +++ ts/axo/_internal/AxoBaseMenu.dom.tsx | 1 + .../conversation/MessageContextMenu.dom.tsx | 30 ++++++++++++++++--- ts/util/lint/exceptions.json | 7 +++++ 5 files changed, 39 insertions(+), 4 deletions(-) diff --git a/ts/axo/AxoContextMenu.dom.tsx b/ts/axo/AxoContextMenu.dom.tsx index acbec52106..70f6fe0464 100644 --- a/ts/axo/AxoContextMenu.dom.tsx +++ b/ts/axo/AxoContextMenu.dom.tsx @@ -238,6 +238,7 @@ export namespace AxoContextMenu { className={AxoBaseMenu.menuContentStyles} alignOffset={-6} collisionPadding={6} + onCloseAutoFocus={props.onCloseAutoFocus} > {props.children} diff --git a/ts/axo/AxoDropdownMenu.dom.tsx b/ts/axo/AxoDropdownMenu.dom.tsx index 30e0123b4c..584013e99e 100644 --- a/ts/axo/AxoDropdownMenu.dom.tsx +++ b/ts/axo/AxoDropdownMenu.dom.tsx @@ -190,6 +190,7 @@ export namespace AxoDropdownMenu { */ export const Content: FC = memo(props => { const { context, labelId, descriptionId } = useCreateAriaLabellingContext(); + const { open } = useStrictContext(RootContext); return ( @@ -200,6 +201,9 @@ export namespace AxoDropdownMenu { className={AxoBaseMenu.menuContentStyles} aria-labelledby={labelId} aria-describedby={descriptionId} + onCloseAutoFocus={props.onCloseAutoFocus} + // @ts-expect-error -- React/TS doesn't know about inert + inert={open ? undefined : 'true'} > {props.children} diff --git a/ts/axo/_internal/AxoBaseMenu.dom.tsx b/ts/axo/_internal/AxoBaseMenu.dom.tsx index a56c93acf1..47a7fb9ff5 100644 --- a/ts/axo/_internal/AxoBaseMenu.dom.tsx +++ b/ts/axo/_internal/AxoBaseMenu.dom.tsx @@ -201,6 +201,7 @@ export namespace AxoBaseMenu { */ export type MenuContentProps = Readonly<{ + onCloseAutoFocus?: (e: Event) => void; children: ReactNode; }>; diff --git a/ts/components/conversation/MessageContextMenu.dom.tsx b/ts/components/conversation/MessageContextMenu.dom.tsx index 041410a6a5..26c9142257 100644 --- a/ts/components/conversation/MessageContextMenu.dom.tsx +++ b/ts/components/conversation/MessageContextMenu.dom.tsx @@ -1,7 +1,7 @@ // Copyright 2023 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import React, { type ReactNode } from 'react'; +import React, { useRef, type ReactNode } from 'react'; import type { LocalizerType } from '../../types/I18N.std.js'; import { AxoMenuBuilder } from '../../axo/AxoMenuBuilder.dom.js'; @@ -50,12 +50,20 @@ export function MessageContextMenu({ onUnpinMessage, children, }: MessageContextMenuProps): JSX.Element { + const shouldReturnFocusToTrigger = useRef(true); + return ( {children} - + { + if (!shouldReturnFocusToTrigger.current) { + e.preventDefault(); + } + }} + > {shouldShowAdditional && ( <> {onDownload && ( @@ -64,7 +72,14 @@ export function MessageContextMenu({ )} {onReplyToMessage && ( - + { + // onReplyToMessage will focus the quill input + shouldReturnFocusToTrigger.current = false; + onReplyToMessage(); + }} + > {i18n('icu:MessageContextMenu__reply')} )} @@ -86,7 +101,14 @@ export function MessageContextMenu({ )} {onEdit && ( - + { + // onEdit will focus the quill input + shouldReturnFocusToTrigger.current = false; + onEdit(); + }} + > {i18n('icu:edit')} )} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index efe54e67f5..b5d0ba3be8 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -2342,5 +2342,12 @@ "line": " const listenerRef = useRef(listener);", "reasonCategory": "usageTrusted", "updated": "2025-12-09T15:37:49.757Z" + }, + { + "rule": "React-useRef", + "path": "ts/components/conversation/MessageContextMenu.dom.tsx", + "line": " const shouldReturnFocusToTrigger = useRef(true);", + "reasonCategory": "usageTrusted", + "updated": "2025-12-19T16:03:53.849Z" } ]