Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Samuel Newman and committed by
GitHub
7c14b54a 9c739912

+59 -40
+33 -15
src/screens/Messages/components/MessageInput.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useState} from 'react' 2 2 import {Pressable, TextInput, useWindowDimensions, View} from 'react-native' 3 3 import { 4 4 useFocusedInputHandler, ··· 19 19 import {HITSLOP_10, MAX_DM_GRAPHEME_LENGTH} from '#/lib/constants' 20 20 import {useHaptics} from '#/lib/haptics' 21 21 import {useEmail} from '#/lib/hooks/useEmail' 22 - import {isIOS} from '#/platform/detection' 22 + import {isIOS, isWeb} from '#/platform/detection' 23 23 import { 24 24 useMessageDraft, 25 25 useSaveMessageDraft, ··· 58 58 const isInputScrollable = useSharedValue(false) 59 59 60 60 const inputStyles = useSharedInputStyles() 61 - const [isFocused, setIsFocused] = React.useState(false) 62 - const [message, setMessage] = React.useState(getDraft) 61 + const [isFocused, setIsFocused] = useState(false) 62 + const [message, setMessage] = useState(getDraft) 63 63 const inputRef = useAnimatedRef<TextInput>() 64 + const [shouldEnforceClear, setShouldEnforceClear] = useState(false) 64 65 65 66 const {needsEmailVerification} = useEmail() 66 67 67 68 useSaveMessageDraft(message) 68 69 useExtractEmbedFromFacets(message, setEmbed) 69 70 70 - const onSubmit = React.useCallback(() => { 71 + const onSubmit = useCallback(() => { 71 72 if (needsEmailVerification) { 72 73 return 73 74 } ··· 81 82 clearDraft() 82 83 onSendMessage(message) 83 84 playHaptic() 84 - setMessage('') 85 85 setEmbed(undefined) 86 - 87 - // Pressing the send button causes the text input to lose focus, so we need to 88 - // re-focus it after sending 89 - setTimeout(() => { 90 - inputRef.current?.focus() 91 - }, 100) 86 + setMessage('') 87 + if (isIOS) { 88 + setShouldEnforceClear(true) 89 + } 90 + if (isWeb) { 91 + // Pressing the send button causes the text input to lose focus, so we need to 92 + // re-focus it after sending 93 + setTimeout(() => { 94 + inputRef.current?.focus() 95 + }, 100) 96 + } 92 97 }, [ 93 98 needsEmailVerification, 94 99 hasEmbed, ··· 97 102 onSendMessage, 98 103 playHaptic, 99 104 setEmbed, 100 - _, 101 105 inputRef, 106 + _, 102 107 ]) 103 108 104 109 useFocusedInputHandler( ··· 149 154 placeholder={_(msg`Write a message`)} 150 155 placeholderTextColor={t.palette.contrast_500} 151 156 value={message} 157 + onChange={evt => { 158 + // bit of a hack: iOS automatically accepts autocomplete suggestions when you tap anywhere on the screen 159 + // including the button we just pressed - and this overrides clearing the input! so we watch for the 160 + // next change and double make sure the input is cleared. It should *always* send an onChange event after 161 + // clearing via setMessage('') that happens in onSubmit() 162 + // -sfn 163 + if (isIOS && shouldEnforceClear) { 164 + setShouldEnforceClear(false) 165 + setMessage('') 166 + return 167 + } 168 + const text = evt.nativeEvent.text 169 + setMessage(text) 170 + }} 152 171 multiline={true} 153 - onChangeText={setMessage} 154 172 style={[ 155 173 a.flex_1, 156 174 a.text_md, ··· 160 178 animatedStyle, 161 179 ]} 162 180 keyboardAppearance={t.name === 'light' ? 'light' : 'dark'} 163 - blurOnSubmit={false} 181 + submitBehavior="submit" 164 182 onFocus={() => setIsFocused(true)} 165 183 onBlur={() => setIsFocused(false)} 166 184 ref={inputRef}
+11 -8
src/view/screens/Storybook/Forms.tsx
··· 1 1 import React from 'react' 2 - import {View} from 'react-native' 2 + import {TextInput, View} from 'react-native' 3 3 4 4 import {atoms as a} from '#/alf' 5 5 import {Button, ButtonText} from '#/components/Button' ··· 19 19 const [value, setValue] = React.useState('') 20 20 const [date, setDate] = React.useState('2001-01-01') 21 21 22 + const inputRef = React.useRef<TextInput>(null) 23 + 22 24 return ( 23 25 <View style={[a.gap_4xl, a.align_start]}> 24 26 <H1>Forms</H1> ··· 33 35 /> 34 36 35 37 <View style={[a.flex_row, a.align_start, a.gap_sm]}> 36 - <View 37 - style={[ 38 - { 39 - width: '50%', 40 - }, 41 - ]}> 38 + <View style={[a.flex_1]}> 42 39 <TextField.Root> 43 40 <TextField.Icon icon={Globe} /> 44 41 <TextField.Input 42 + inputRef={inputRef} 45 43 value={value} 46 44 onChangeText={setValue} 47 45 label="Text field" 48 46 /> 49 47 </TextField.Root> 50 48 </View> 51 - <Button label="Submit" size="large" variant="solid" color="primary"> 49 + <Button 50 + label="Submit" 51 + size="large" 52 + variant="solid" 53 + color="primary" 54 + onPress={() => inputRef.current?.clear()}> 52 55 <ButtonText>Submit</ButtonText> 53 56 </Button> 54 57 </View>
+15 -17
src/view/screens/Storybook/index.tsx
··· 1 1 import React from 'react' 2 - import {ScrollView, View} from 'react-native' 2 + import {View} from 'react-native' 3 3 import {useNavigation} from '@react-navigation/native' 4 4 5 5 import {NavigationProp} from '#/lib/routes/types' 6 - import {isWeb} from '#/platform/detection' 7 6 import {useSetThemePrefs} from '#/state/shell' 8 - import {CenteredView} from '#/view/com/util/Views' 9 7 import {ListContained} from '#/view/screens/Storybook/ListContained' 10 - import {atoms as a, ThemeProvider, useTheme} from '#/alf' 8 + import {atoms as a, ThemeProvider} from '#/alf' 11 9 import {Button, ButtonText} from '#/components/Button' 12 10 import * as Layout from '#/components/Layout' 13 11 import {Admonitions} from './Admonitions' ··· 27 25 export function Storybook() { 28 26 return ( 29 27 <Layout.Screen> 30 - {isWeb ? ( 28 + <Layout.Header.Outer> 29 + <Layout.Header.BackButton /> 30 + <Layout.Header.Content> 31 + <Layout.Header.TitleText>Storybook</Layout.Header.TitleText> 32 + </Layout.Header.Content> 33 + <Layout.Header.Slot /> 34 + </Layout.Header.Outer> 35 + <Layout.Content keyboardShouldPersistTaps="handled"> 31 36 <StorybookInner /> 32 - ) : ( 33 - <ScrollView> 34 - <StorybookInner /> 35 - </ScrollView> 36 - )} 37 + </Layout.Content> 37 38 </Layout.Screen> 38 39 ) 39 40 } 40 41 41 42 function StorybookInner() { 42 - const t = useTheme() 43 43 const {setColorMode, setDarkTheme} = useSetThemePrefs() 44 44 const [showContainedList, setShowContainedList] = React.useState(false) 45 45 const navigation = useNavigation<NavigationProp>() 46 46 47 47 return ( 48 - <CenteredView style={[t.atoms.bg]}> 48 + <> 49 49 <View style={[a.p_xl, a.gap_5xl, {paddingBottom: 100}]}> 50 50 {!showContainedList ? ( 51 51 <> ··· 100 100 <ButtonText>Open Shared Prefs Tester</ButtonText> 101 101 </Button> 102 102 103 - <Admonitions /> 104 - 105 - <Settings /> 106 - 107 103 <ThemeProvider theme="light"> 108 104 <Theming /> 109 105 </ThemeProvider> ··· 126 122 <Menus /> 127 123 <Breakpoints /> 128 124 <Dialogs /> 125 + <Admonitions /> 126 + <Settings /> 129 127 130 128 <Button 131 129 variant="solid" ··· 150 148 </> 151 149 )} 152 150 </View> 153 - </CenteredView> 151 + </> 154 152 ) 155 153 }