Focus composer input when editable (#10982)
(cherry picked from commit 51e25435a2)
This commit is contained in:
@@ -615,6 +615,8 @@ export function Outer({
|
|||||||
label?: string
|
label?: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
align?: 'left' | 'right'
|
align?: 'left' | 'right'
|
||||||
|
/** Web only. Native restores focus differently. */
|
||||||
|
onCloseAutoFocus?: (event: Event) => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const context = useContextMenuContext()
|
const context = useContextMenuContext()
|
||||||
|
|||||||
@@ -34,14 +34,16 @@ export function Outer({
|
|||||||
children,
|
children,
|
||||||
label,
|
label,
|
||||||
style,
|
style,
|
||||||
|
onCloseAutoFocus,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
label?: string
|
label?: string
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
|
onCloseAutoFocus?: (event: Event) => void
|
||||||
}) {
|
}) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
return (
|
return (
|
||||||
<Menu.Outer style={style}>
|
<Menu.Outer style={style} onCloseAutoFocus={onCloseAutoFocus}>
|
||||||
{label ? (
|
{label ? (
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
|
|||||||
@@ -60,6 +60,20 @@ export let MessageContextMenu = ({
|
|||||||
const langPrefs = useLanguagePrefs()
|
const langPrefs = useLanguagePrefs()
|
||||||
const translate = useGoogleTranslate()
|
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 isFromSelf = message.sender?.did === currentAccount?.did
|
||||||
const isGroupChatEnabled = !ax.features.enabled(ax.features.GroupChatsDisable)
|
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), {
|
label={l`Sent at ${i18n.date(new Date(message.sentAt), {
|
||||||
timeStyle: 'short',
|
timeStyle: 'short',
|
||||||
})}`}
|
})}`}
|
||||||
style={[isFromSelf && isGroupChatEnabled ? null : a.ml_sm]}>
|
style={[isFromSelf && isGroupChatEnabled ? null : a.ml_sm]}
|
||||||
|
onCloseAutoFocus={onCloseAutoFocus}>
|
||||||
<ContextMenu.Item
|
<ContextMenu.Item
|
||||||
testID="messageDropdownReplyBtn"
|
testID="messageDropdownReplyBtn"
|
||||||
label={l`Reply`}
|
label={l`Reply`}
|
||||||
onPress={() => setReply(message)}>
|
onPress={onReply}>
|
||||||
<ContextMenu.ItemIcon icon={ReplyIcon} position="left" />
|
<ContextMenu.ItemIcon icon={ReplyIcon} position="left" />
|
||||||
<ContextMenu.ItemText>{l`Reply`}</ContextMenu.ItemText>
|
<ContextMenu.ItemText>{l`Reply`}</ContextMenu.ItemText>
|
||||||
</ContextMenu.Item>
|
</ContextMenu.Item>
|
||||||
|
|||||||
@@ -77,6 +77,15 @@ export function MessageComposer({
|
|||||||
composerInternalApiRef.current?.input?.focus()
|
composerInternalApiRef.current?.input?.focus()
|
||||||
}, [replyTo, composerInternalApiRef])
|
}, [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
|
// Android interactive dismiss sometimes doesn't blur the input
|
||||||
const blur = useNonReactiveCallback(() => {
|
const blur = useNonReactiveCallback(() => {
|
||||||
composerInternalApiRef.current?.input?.blur()
|
composerInternalApiRef.current?.input?.blur()
|
||||||
@@ -249,7 +258,6 @@ export function MessageComposer({
|
|||||||
internalApiRef={composerInternalApiRef}
|
internalApiRef={composerInternalApiRef}
|
||||||
defaultValue={text}
|
defaultValue={text}
|
||||||
editable={editable}
|
editable={editable}
|
||||||
autoFocus={IS_WEB}
|
|
||||||
maxRows={12}
|
maxRows={12}
|
||||||
outerStyle={[a.flex_1]}
|
outerStyle={[a.flex_1]}
|
||||||
contentTextStyle={[a.text_md, a.leading_snug]}
|
contentTextStyle={[a.text_md, a.leading_snug]}
|
||||||
|
|||||||
Reference in New Issue
Block a user