diff options
author | Brian Kim <brian@tagg.id> | 2021-05-11 11:46:20 -0700 |
---|---|---|
committer | Brian Kim <brian@tagg.id> | 2021-05-11 11:46:20 -0700 |
commit | fd54544b84cc0f13f57dc481ccb11d3183de79c8 (patch) | |
tree | f53fe35c835ee99e3da13cf22b745e76a1cbd0f3 /src/components/comments | |
parent | 4569240e09ff134d49fa11180c0074329a3ac95b (diff) |
Fixed issue with mentions not collapsing when deleting '@' too quickly
Diffstat (limited to 'src/components/comments')
-rw-r--r-- | src/components/comments/AddComment.tsx | 3 | ||||
-rw-r--r-- | src/components/comments/MentionInputControlled.tsx | 189 |
2 files changed, 191 insertions, 1 deletions
diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 9cf10b5e..28e1a40e 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -20,6 +20,7 @@ import {CommentThreadType, CommentType} from '../../types'; import {SCREEN_HEIGHT, SCREEN_WIDTH} from '../../utils'; import {mentionPartTypes} from '../../utils/comments'; import {Avatar} from '../common'; +import {MentionInputControlled} from './MentionInputControlled'; export interface AddCommentProps { momentId: string; @@ -112,7 +113,7 @@ const AddComment: React.FC<AddCommentProps> = ({momentId, placeholderText}) => { ]}> <View style={styles.textContainer}> <Avatar style={styles.avatar} uri={avatar} /> - <MentionInput + <MentionInputControlled containerStyle={styles.text} placeholder={placeholderText} value={inReplyToMention + comment} diff --git a/src/components/comments/MentionInputControlled.tsx b/src/components/comments/MentionInputControlled.tsx new file mode 100644 index 00000000..642ef64c --- /dev/null +++ b/src/components/comments/MentionInputControlled.tsx @@ -0,0 +1,189 @@ +import React, { FC, MutableRefObject, useMemo, useRef, useState } from 'react'; +import { + NativeSyntheticEvent, + Text, + TextInput, + TextInputSelectionChangeEventData, + View, +} from 'react-native'; + +import { + MentionInputProps, + MentionPartType, + Suggestion +} from 'react-native-controlled-mentions/dist/types'; +import { + defaultMentionTextStyle, + generateValueFromPartsAndChangedText, + generateValueWithAddedSuggestion, + getMentionPartSuggestionKeywords, + isMentionPartType, + parseValue, +} from 'react-native-controlled-mentions/dist/utils'; + +const MentionInputControlled: FC<MentionInputProps> = ( + { + value, + onChange, + + partTypes = [], + + inputRef: propInputRef, + + containerStyle, + + onSelectionChange, + + ...textInputProps + }, +) => { + const textInput = useRef<TextInput | null>(null); + + const [selection, setSelection] = useState({start: 0, end: 0}); + + const [keyboardText, setKeyboardText] = useState<string>(''); + + const validRegex = /.*\@[^ ]*$/; + + const { + plainText, + parts, + } = useMemo(() => parseValue(value, partTypes), [value, partTypes]); + + const handleSelectionChange = (event: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => { + setSelection(event.nativeEvent.selection); + + onSelectionChange && onSelectionChange(event); + }; + + /** + * Callback that trigger on TextInput text change + * + * @param changedText + */ + const onChangeInput = (changedText: string) => { + setKeyboardText(changedText); + onChange(generateValueFromPartsAndChangedText(parts, plainText, changedText)); + }; + + /** + * We memoize the keyword to know should we show mention suggestions or not + */ + const keywordByTrigger = useMemo(() => { + return getMentionPartSuggestionKeywords( + parts, + plainText, + selection, + partTypes, + ); + }, [parts, plainText, selection, partTypes]); + + /** + * Callback on mention suggestion press. We should: + * - Get updated value + * - Trigger onChange callback with new value + */ + const onSuggestionPress = (mentionType: MentionPartType) => (suggestion: Suggestion) => { + const newValue = generateValueWithAddedSuggestion( + parts, + mentionType, + plainText, + selection, + suggestion, + ); + + if (!newValue) { + return; + } + + onChange(newValue); + + /** + * Move cursor to the end of just added mention starting from trigger string and including: + * - Length of trigger string + * - Length of mention name + * - Length of space after mention (1) + * + * Not working now due to the RN bug + */ + // const newCursorPosition = currentPart.position.start + triggerPartIndex + trigger.length + + // suggestion.name.length + 1; + + // textInput.current?.setNativeProps({selection: {start: newCursorPosition, end: newCursorPosition}}); + }; + + const handleTextInputRef = (ref: TextInput) => { + textInput.current = ref as TextInput; + + if (propInputRef) { + if (typeof propInputRef === 'function') { + propInputRef(ref); + } else { + (propInputRef as MutableRefObject<TextInput>).current = ref as TextInput; + } + } + }; + + const renderMentionSuggestions = (mentionType: MentionPartType) => ( + <React.Fragment key={mentionType.trigger}> + {mentionType.renderSuggestions && mentionType.renderSuggestions({ + keyword: keywordByTrigger[mentionType.trigger], + onSuggestionPress: onSuggestionPress(mentionType), + })} + </React.Fragment> + ); + + const validateInput = (testString: string) => { + return validRegex.test(testString); + } + + return ( + <View style={containerStyle}> + {validateInput(keyboardText) ? (partTypes + .filter(one => ( + isMentionPartType(one) + && one.renderSuggestions != null + && !one.isBottomMentionSuggestionsRender + )) as MentionPartType[]) + .map(renderMentionSuggestions) + : null + } + + <TextInput + multiline + + {...textInputProps} + + ref={handleTextInputRef} + + onChangeText={onChangeInput} + onSelectionChange={handleSelectionChange} + > + <Text> + {parts.map(({text, partType, data}, index) => partType ? ( + <Text + key={`${index}-${data?.trigger ?? 'pattern'}`} + style={partType.textStyle ?? defaultMentionTextStyle} + > + {text} + </Text> + ) : ( + <Text key={index}>{text}</Text> + ))} + </Text> + </TextInput> + + {validateInput(keyboardText) ? (partTypes + .filter(one => ( + isMentionPartType(one) + && one.renderSuggestions != null + && one.isBottomMentionSuggestionsRender + )) as MentionPartType[]) + .map(renderMentionSuggestions) + : null + } + </View> + ); +}; + +export { MentionInputControlled };
\ No newline at end of file |