Focus composer input when editable (#10982)

This commit is contained in:
DS Boyce
2026-06-24 16:06:02 -07:00
committed by GitHub
parent da1f637160
commit 51e25435a2
4 changed files with 31 additions and 4 deletions
+2
View File
@@ -615,6 +615,8 @@ export function Outer({
label?: string
style?: StyleProp<ViewStyle>
align?: 'left' | 'right'
/** Web only. Native restores focus differently. */
onCloseAutoFocus?: (event: Event) => void
}) {
const t = useTheme()
const context = useContextMenuContext()
+3 -1
View File
@@ -34,14 +34,16 @@ export function Outer({
children,
label,
style,
onCloseAutoFocus,
}: {
children: React.ReactNode
label?: string
style?: StyleProp<ViewStyle>
onCloseAutoFocus?: (event: Event) => void
}) {
const t = useTheme()
return (
<Menu.Outer style={style}>
<Menu.Outer style={style} onCloseAutoFocus={onCloseAutoFocus}>
{label ? (
<Text
numberOfLines={1}
+17 -2
View File
@@ -60,6 +60,20 @@ export let MessageContextMenu = ({
const langPrefs = useLanguagePrefs()
const translate = useGoogleTranslate()
const onReply = useCallback(() => {
setReply(message)
}, [setReply, message])
// On web, the menu is a Radix dropdown that restores focus to the trigger on
// close. When Reply moves focus to the composer, don't let Radix steal it
// back. Checking activeElement (rather than tracking reply intent) also
// handles re-replying to the same message, where the composer's focus effect
// bails on an unchanged reply target and focus should stay on the trigger.
const onCloseAutoFocus = useCallback((event: Event) => {
if (document.activeElement && document.activeElement !== document.body) {
event.preventDefault()
}
}, [])
const isFromSelf = message.sender?.did === currentAccount?.did
const isGroupChatEnabled = !ax.features.enabled(ax.features.GroupChatsDisable)
@@ -161,11 +175,12 @@ export let MessageContextMenu = ({
label={l`Sent at ${i18n.date(new Date(message.sentAt), {
timeStyle: 'short',
})}`}
style={[isFromSelf && isGroupChatEnabled ? null : a.ml_sm]}>
style={[isFromSelf && isGroupChatEnabled ? null : a.ml_sm]}
onCloseAutoFocus={onCloseAutoFocus}>
<ContextMenu.Item
testID="messageDropdownReplyBtn"
label={l`Reply`}
onPress={() => setReply(message)}>
onPress={onReply}>
<ContextMenu.ItemIcon icon={ReplyIcon} position="left" />
<ContextMenu.ItemText>{l`Reply`}</ContextMenu.ItemText>
</ContextMenu.Item>
@@ -77,6 +77,15 @@ export function MessageComposer({
composerInternalApiRef.current?.input?.focus()
}, [replyTo, composerInternalApiRef])
// On web, focus the input once the conversation is ready. The composer also
// mounts during the loading state (when it isn't editable), so a mount-time
// autoFocus would fire too early to land focus.
useEffect(() => {
if (IS_WEB && editable) {
composerInternalApiRef.current?.input?.focus()
}
}, [editable, composerInternalApiRef])
// Android interactive dismiss sometimes doesn't blur the input
const blur = useNonReactiveCallback(() => {
composerInternalApiRef.current?.input?.blur()
@@ -249,7 +258,6 @@ export function MessageComposer({
internalApiRef={composerInternalApiRef}
defaultValue={text}
editable={editable}
autoFocus={IS_WEB}
maxRows={12}
outerStyle={[a.flex_1]}
contentTextStyle={[a.text_md, a.leading_snug]}