Fix Emoji picker focus (#7217)

* Only portal the emoji picker where needed

* Add optional portal prop to emoji picker

* Use FocusScope to our advantage

* Pare back, add guards, fix focus trap

* Don't return focus to emoji button

* Set DM input position on emoji insert

* Let the caller determine next focus node

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
Eric Bailey
2024-12-20 13:59:33 -06:00
committed by GitHub
parent 8a3dfcb9d0
commit 8116d12c15
6 changed files with 96 additions and 53 deletions
@@ -3,6 +3,7 @@ import {Pressable, StyleSheet, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import Graphemer from 'graphemer'
import {flushSync} from 'react-dom'
import TextareaAutosize from 'react-textarea-autosize'
import {isSafari, isTouchDevice} from '#/lib/browser'
@@ -106,11 +107,19 @@ export function MessageInput({
const onEmojiInserted = React.useCallback(
(emoji: Emoji) => {
const position = textAreaRef.current?.selectionStart ?? 0
setMessage(
message =>
message.slice(0, position) + emoji.native + message.slice(position),
)
if (!textAreaRef.current) {
return
}
const position = textAreaRef.current.selectionStart ?? 0
textAreaRef.current.focus()
flushSync(() => {
setMessage(
message =>
message.slice(0, position) + emoji.native + message.slice(position),
)
})
textAreaRef.current.selectionStart = position + emoji.native.length
textAreaRef.current.selectionEnd = position + emoji.native.length
},
[setMessage],
)
@@ -148,7 +157,14 @@ export function MessageInput({
<Button
onPress={e => {
e.currentTarget.measure((_fx, _fy, _width, _height, px, py) => {
openEmojiPicker?.({top: py, left: px, right: px, bottom: py})
openEmojiPicker?.({
top: py,
left: px,
right: px,
bottom: py,
nextFocusRef:
textAreaRef as unknown as React.MutableRefObject<HTMLElement>,
})
})
}}
style={[
@@ -101,7 +101,7 @@ export function MessagesList({
const [emojiPickerState, setEmojiPickerState] =
React.useState<EmojiPickerState>({
isOpen: false,
pos: {top: 0, left: 0, right: 0, bottom: 0},
pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null},
})
// We need to keep track of when the scroll offset is at the bottom of the list to know when to scroll as new items