Fix DMs input automatically accepting autocomplete suggestion instead of clearing (#7796)
* fix input not clearing when autocomplete suggestion active * fix up storybook * restore web focus hack
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import React from 'react'
|
||||
import {useCallback, useState} from 'react'
|
||||
import {Pressable, TextInput, useWindowDimensions, View} from 'react-native'
|
||||
import {
|
||||
useFocusedInputHandler,
|
||||
@@ -19,7 +19,7 @@ import Graphemer from 'graphemer'
|
||||
import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||
import {useHaptics} from '#/lib/haptics'
|
||||
import {useEmail} from '#/lib/hooks/useEmail'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {isIOS, isWeb} from '#/platform/detection'
|
||||
import {
|
||||
useMessageDraft,
|
||||
useSaveMessageDraft,
|
||||
@@ -58,16 +58,17 @@ export function MessageInput({
|
||||
const isInputScrollable = useSharedValue(false)
|
||||
|
||||
const inputStyles = useSharedInputStyles()
|
||||
const [isFocused, setIsFocused] = React.useState(false)
|
||||
const [message, setMessage] = React.useState(getDraft)
|
||||
const [isFocused, setIsFocused] = useState(false)
|
||||
const [message, setMessage] = useState(getDraft)
|
||||
const inputRef = useAnimatedRef<TextInput>()
|
||||
const [shouldEnforceClear, setShouldEnforceClear] = useState(false)
|
||||
|
||||
const {needsEmailVerification} = useEmail()
|
||||
|
||||
useSaveMessageDraft(message)
|
||||
useExtractEmbedFromFacets(message, setEmbed)
|
||||
|
||||
const onSubmit = React.useCallback(() => {
|
||||
const onSubmit = useCallback(() => {
|
||||
if (needsEmailVerification) {
|
||||
return
|
||||
}
|
||||
@@ -81,14 +82,18 @@ export function MessageInput({
|
||||
clearDraft()
|
||||
onSendMessage(message)
|
||||
playHaptic()
|
||||
setMessage('')
|
||||
setEmbed(undefined)
|
||||
|
||||
// Pressing the send button causes the text input to lose focus, so we need to
|
||||
// re-focus it after sending
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 100)
|
||||
setMessage('')
|
||||
if (isIOS) {
|
||||
setShouldEnforceClear(true)
|
||||
}
|
||||
if (isWeb) {
|
||||
// Pressing the send button causes the text input to lose focus, so we need to
|
||||
// re-focus it after sending
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus()
|
||||
}, 100)
|
||||
}
|
||||
}, [
|
||||
needsEmailVerification,
|
||||
hasEmbed,
|
||||
@@ -97,8 +102,8 @@ export function MessageInput({
|
||||
onSendMessage,
|
||||
playHaptic,
|
||||
setEmbed,
|
||||
_,
|
||||
inputRef,
|
||||
_,
|
||||
])
|
||||
|
||||
useFocusedInputHandler(
|
||||
@@ -149,8 +154,21 @@ export function MessageInput({
|
||||
placeholder={_(msg`Write a message`)}
|
||||
placeholderTextColor={t.palette.contrast_500}
|
||||
value={message}
|
||||
onChange={evt => {
|
||||
// bit of a hack: iOS automatically accepts autocomplete suggestions when you tap anywhere on the screen
|
||||
// including the button we just pressed - and this overrides clearing the input! so we watch for the
|
||||
// next change and double make sure the input is cleared. It should *always* send an onChange event after
|
||||
// clearing via setMessage('') that happens in onSubmit()
|
||||
// -sfn
|
||||
if (isIOS && shouldEnforceClear) {
|
||||
setShouldEnforceClear(false)
|
||||
setMessage('')
|
||||
return
|
||||
}
|
||||
const text = evt.nativeEvent.text
|
||||
setMessage(text)
|
||||
}}
|
||||
multiline={true}
|
||||
onChangeText={setMessage}
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
@@ -160,7 +178,7 @@ export function MessageInput({
|
||||
animatedStyle,
|
||||
]}
|
||||
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
||||
blurOnSubmit={false}
|
||||
submitBehavior="submit"
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
ref={inputRef}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {TextInput, View} from 'react-native'
|
||||
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
@@ -19,6 +19,8 @@ export function Forms() {
|
||||
const [value, setValue] = React.useState('')
|
||||
const [date, setDate] = React.useState('2001-01-01')
|
||||
|
||||
const inputRef = React.useRef<TextInput>(null)
|
||||
|
||||
return (
|
||||
<View style={[a.gap_4xl, a.align_start]}>
|
||||
<H1>Forms</H1>
|
||||
@@ -33,22 +35,23 @@ export function Forms() {
|
||||
/>
|
||||
|
||||
<View style={[a.flex_row, a.align_start, a.gap_sm]}>
|
||||
<View
|
||||
style={[
|
||||
{
|
||||
width: '50%',
|
||||
},
|
||||
]}>
|
||||
<View style={[a.flex_1]}>
|
||||
<TextField.Root>
|
||||
<TextField.Icon icon={Globe} />
|
||||
<TextField.Input
|
||||
inputRef={inputRef}
|
||||
value={value}
|
||||
onChangeText={setValue}
|
||||
label="Text field"
|
||||
/>
|
||||
</TextField.Root>
|
||||
</View>
|
||||
<Button label="Submit" size="large" variant="solid" color="primary">
|
||||
<Button
|
||||
label="Submit"
|
||||
size="large"
|
||||
variant="solid"
|
||||
color="primary"
|
||||
onPress={() => inputRef.current?.clear()}>
|
||||
<ButtonText>Submit</ButtonText>
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
import React from 'react'
|
||||
import {ScrollView, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {useNavigation} from '@react-navigation/native'
|
||||
|
||||
import {NavigationProp} from '#/lib/routes/types'
|
||||
import {isWeb} from '#/platform/detection'
|
||||
import {useSetThemePrefs} from '#/state/shell'
|
||||
import {CenteredView} from '#/view/com/util/Views'
|
||||
import {ListContained} from '#/view/screens/Storybook/ListContained'
|
||||
import {atoms as a, ThemeProvider, useTheme} from '#/alf'
|
||||
import {atoms as a, ThemeProvider} from '#/alf'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
import * as Layout from '#/components/Layout'
|
||||
import {Admonitions} from './Admonitions'
|
||||
@@ -27,25 +25,27 @@ import {Typography} from './Typography'
|
||||
export function Storybook() {
|
||||
return (
|
||||
<Layout.Screen>
|
||||
{isWeb ? (
|
||||
<Layout.Header.Outer>
|
||||
<Layout.Header.BackButton />
|
||||
<Layout.Header.Content>
|
||||
<Layout.Header.TitleText>Storybook</Layout.Header.TitleText>
|
||||
</Layout.Header.Content>
|
||||
<Layout.Header.Slot />
|
||||
</Layout.Header.Outer>
|
||||
<Layout.Content keyboardShouldPersistTaps="handled">
|
||||
<StorybookInner />
|
||||
) : (
|
||||
<ScrollView>
|
||||
<StorybookInner />
|
||||
</ScrollView>
|
||||
)}
|
||||
</Layout.Content>
|
||||
</Layout.Screen>
|
||||
)
|
||||
}
|
||||
|
||||
function StorybookInner() {
|
||||
const t = useTheme()
|
||||
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
||||
const [showContainedList, setShowContainedList] = React.useState(false)
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
|
||||
return (
|
||||
<CenteredView style={[t.atoms.bg]}>
|
||||
<>
|
||||
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 100}]}>
|
||||
{!showContainedList ? (
|
||||
<>
|
||||
@@ -100,10 +100,6 @@ function StorybookInner() {
|
||||
<ButtonText>Open Shared Prefs Tester</ButtonText>
|
||||
</Button>
|
||||
|
||||
<Admonitions />
|
||||
|
||||
<Settings />
|
||||
|
||||
<ThemeProvider theme="light">
|
||||
<Theming />
|
||||
</ThemeProvider>
|
||||
@@ -126,6 +122,8 @@ function StorybookInner() {
|
||||
<Menus />
|
||||
<Breakpoints />
|
||||
<Dialogs />
|
||||
<Admonitions />
|
||||
<Settings />
|
||||
|
||||
<Button
|
||||
variant="solid"
|
||||
@@ -150,6 +148,6 @@ function StorybookInner() {
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</CenteredView>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user