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 {Pressable, TextInput, useWindowDimensions, View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
useFocusedInputHandler,
|
useFocusedInputHandler,
|
||||||
@@ -19,7 +19,7 @@ import Graphemer from 'graphemer'
|
|||||||
import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
|
import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants'
|
||||||
import {useHaptics} from '#/lib/haptics'
|
import {useHaptics} from '#/lib/haptics'
|
||||||
import {useEmail} from '#/lib/hooks/useEmail'
|
import {useEmail} from '#/lib/hooks/useEmail'
|
||||||
import {isIOS} from '#/platform/detection'
|
import {isIOS, isWeb} from '#/platform/detection'
|
||||||
import {
|
import {
|
||||||
useMessageDraft,
|
useMessageDraft,
|
||||||
useSaveMessageDraft,
|
useSaveMessageDraft,
|
||||||
@@ -58,16 +58,17 @@ export function MessageInput({
|
|||||||
const isInputScrollable = useSharedValue(false)
|
const isInputScrollable = useSharedValue(false)
|
||||||
|
|
||||||
const inputStyles = useSharedInputStyles()
|
const inputStyles = useSharedInputStyles()
|
||||||
const [isFocused, setIsFocused] = React.useState(false)
|
const [isFocused, setIsFocused] = useState(false)
|
||||||
const [message, setMessage] = React.useState(getDraft)
|
const [message, setMessage] = useState(getDraft)
|
||||||
const inputRef = useAnimatedRef<TextInput>()
|
const inputRef = useAnimatedRef<TextInput>()
|
||||||
|
const [shouldEnforceClear, setShouldEnforceClear] = useState(false)
|
||||||
|
|
||||||
const {needsEmailVerification} = useEmail()
|
const {needsEmailVerification} = useEmail()
|
||||||
|
|
||||||
useSaveMessageDraft(message)
|
useSaveMessageDraft(message)
|
||||||
useExtractEmbedFromFacets(message, setEmbed)
|
useExtractEmbedFromFacets(message, setEmbed)
|
||||||
|
|
||||||
const onSubmit = React.useCallback(() => {
|
const onSubmit = useCallback(() => {
|
||||||
if (needsEmailVerification) {
|
if (needsEmailVerification) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -81,14 +82,18 @@ export function MessageInput({
|
|||||||
clearDraft()
|
clearDraft()
|
||||||
onSendMessage(message)
|
onSendMessage(message)
|
||||||
playHaptic()
|
playHaptic()
|
||||||
setMessage('')
|
|
||||||
setEmbed(undefined)
|
setEmbed(undefined)
|
||||||
|
setMessage('')
|
||||||
// Pressing the send button causes the text input to lose focus, so we need to
|
if (isIOS) {
|
||||||
// re-focus it after sending
|
setShouldEnforceClear(true)
|
||||||
setTimeout(() => {
|
}
|
||||||
inputRef.current?.focus()
|
if (isWeb) {
|
||||||
}, 100)
|
// 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,
|
needsEmailVerification,
|
||||||
hasEmbed,
|
hasEmbed,
|
||||||
@@ -97,8 +102,8 @@ export function MessageInput({
|
|||||||
onSendMessage,
|
onSendMessage,
|
||||||
playHaptic,
|
playHaptic,
|
||||||
setEmbed,
|
setEmbed,
|
||||||
_,
|
|
||||||
inputRef,
|
inputRef,
|
||||||
|
_,
|
||||||
])
|
])
|
||||||
|
|
||||||
useFocusedInputHandler(
|
useFocusedInputHandler(
|
||||||
@@ -149,8 +154,21 @@ export function MessageInput({
|
|||||||
placeholder={_(msg`Write a message`)}
|
placeholder={_(msg`Write a message`)}
|
||||||
placeholderTextColor={t.palette.contrast_500}
|
placeholderTextColor={t.palette.contrast_500}
|
||||||
value={message}
|
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}
|
multiline={true}
|
||||||
onChangeText={setMessage}
|
|
||||||
style={[
|
style={[
|
||||||
a.flex_1,
|
a.flex_1,
|
||||||
a.text_md,
|
a.text_md,
|
||||||
@@ -160,7 +178,7 @@ export function MessageInput({
|
|||||||
animatedStyle,
|
animatedStyle,
|
||||||
]}
|
]}
|
||||||
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
||||||
blurOnSubmit={false}
|
submitBehavior="submit"
|
||||||
onFocus={() => setIsFocused(true)}
|
onFocus={() => setIsFocused(true)}
|
||||||
onBlur={() => setIsFocused(false)}
|
onBlur={() => setIsFocused(false)}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View} from 'react-native'
|
import {TextInput, View} from 'react-native'
|
||||||
|
|
||||||
import {atoms as a} from '#/alf'
|
import {atoms as a} from '#/alf'
|
||||||
import {Button, ButtonText} from '#/components/Button'
|
import {Button, ButtonText} from '#/components/Button'
|
||||||
@@ -19,6 +19,8 @@ export function Forms() {
|
|||||||
const [value, setValue] = React.useState('')
|
const [value, setValue] = React.useState('')
|
||||||
const [date, setDate] = React.useState('2001-01-01')
|
const [date, setDate] = React.useState('2001-01-01')
|
||||||
|
|
||||||
|
const inputRef = React.useRef<TextInput>(null)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.gap_4xl, a.align_start]}>
|
<View style={[a.gap_4xl, a.align_start]}>
|
||||||
<H1>Forms</H1>
|
<H1>Forms</H1>
|
||||||
@@ -33,22 +35,23 @@ export function Forms() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={[a.flex_row, a.align_start, a.gap_sm]}>
|
<View style={[a.flex_row, a.align_start, a.gap_sm]}>
|
||||||
<View
|
<View style={[a.flex_1]}>
|
||||||
style={[
|
|
||||||
{
|
|
||||||
width: '50%',
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<TextField.Root>
|
<TextField.Root>
|
||||||
<TextField.Icon icon={Globe} />
|
<TextField.Icon icon={Globe} />
|
||||||
<TextField.Input
|
<TextField.Input
|
||||||
|
inputRef={inputRef}
|
||||||
value={value}
|
value={value}
|
||||||
onChangeText={setValue}
|
onChangeText={setValue}
|
||||||
label="Text field"
|
label="Text field"
|
||||||
/>
|
/>
|
||||||
</TextField.Root>
|
</TextField.Root>
|
||||||
</View>
|
</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>
|
<ButtonText>Submit</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {ScrollView, View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {useNavigation} from '@react-navigation/native'
|
import {useNavigation} from '@react-navigation/native'
|
||||||
|
|
||||||
import {NavigationProp} from '#/lib/routes/types'
|
import {NavigationProp} from '#/lib/routes/types'
|
||||||
import {isWeb} from '#/platform/detection'
|
|
||||||
import {useSetThemePrefs} from '#/state/shell'
|
import {useSetThemePrefs} from '#/state/shell'
|
||||||
import {CenteredView} from '#/view/com/util/Views'
|
|
||||||
import {ListContained} from '#/view/screens/Storybook/ListContained'
|
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 {Button, ButtonText} from '#/components/Button'
|
||||||
import * as Layout from '#/components/Layout'
|
import * as Layout from '#/components/Layout'
|
||||||
import {Admonitions} from './Admonitions'
|
import {Admonitions} from './Admonitions'
|
||||||
@@ -27,25 +25,27 @@ import {Typography} from './Typography'
|
|||||||
export function Storybook() {
|
export function Storybook() {
|
||||||
return (
|
return (
|
||||||
<Layout.Screen>
|
<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 />
|
<StorybookInner />
|
||||||
) : (
|
</Layout.Content>
|
||||||
<ScrollView>
|
|
||||||
<StorybookInner />
|
|
||||||
</ScrollView>
|
|
||||||
)}
|
|
||||||
</Layout.Screen>
|
</Layout.Screen>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function StorybookInner() {
|
function StorybookInner() {
|
||||||
const t = useTheme()
|
|
||||||
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
const {setColorMode, setDarkTheme} = useSetThemePrefs()
|
||||||
const [showContainedList, setShowContainedList] = React.useState(false)
|
const [showContainedList, setShowContainedList] = React.useState(false)
|
||||||
const navigation = useNavigation<NavigationProp>()
|
const navigation = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[t.atoms.bg]}>
|
<>
|
||||||
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 100}]}>
|
<View style={[a.p_xl, a.gap_5xl, {paddingBottom: 100}]}>
|
||||||
{!showContainedList ? (
|
{!showContainedList ? (
|
||||||
<>
|
<>
|
||||||
@@ -100,10 +100,6 @@ function StorybookInner() {
|
|||||||
<ButtonText>Open Shared Prefs Tester</ButtonText>
|
<ButtonText>Open Shared Prefs Tester</ButtonText>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Admonitions />
|
|
||||||
|
|
||||||
<Settings />
|
|
||||||
|
|
||||||
<ThemeProvider theme="light">
|
<ThemeProvider theme="light">
|
||||||
<Theming />
|
<Theming />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
@@ -126,6 +122,8 @@ function StorybookInner() {
|
|||||||
<Menus />
|
<Menus />
|
||||||
<Breakpoints />
|
<Breakpoints />
|
||||||
<Dialogs />
|
<Dialogs />
|
||||||
|
<Admonitions />
|
||||||
|
<Settings />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="solid"
|
variant="solid"
|
||||||
@@ -150,6 +148,6 @@ function StorybookInner() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</CenteredView>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user