From fd54544b84cc0f13f57dc481ccb11d3183de79c8 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Tue, 11 May 2021 11:46:20 -0700 Subject: Fixed issue with mentions not collapsing when deleting '@' too quickly --- src/components/comments/AddComment.tsx | 3 +- src/components/comments/MentionInputControlled.tsx | 189 +++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 src/components/comments/MentionInputControlled.tsx (limited to 'src') 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 = ({momentId, placeholderText}) => { ]}> - = ( + { + value, + onChange, + + partTypes = [], + + inputRef: propInputRef, + + containerStyle, + + onSelectionChange, + + ...textInputProps + }, +) => { + const textInput = useRef(null); + + const [selection, setSelection] = useState({start: 0, end: 0}); + + const [keyboardText, setKeyboardText] = useState(''); + + const validRegex = /.*\@[^ ]*$/; + + const { + plainText, + parts, + } = useMemo(() => parseValue(value, partTypes), [value, partTypes]); + + const handleSelectionChange = (event: NativeSyntheticEvent) => { + 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).current = ref as TextInput; + } + } + }; + + const renderMentionSuggestions = (mentionType: MentionPartType) => ( + + {mentionType.renderSuggestions && mentionType.renderSuggestions({ + keyword: keywordByTrigger[mentionType.trigger], + onSuggestionPress: onSuggestionPress(mentionType), + })} + + ); + + const validateInput = (testString: string) => { + return validRegex.test(testString); + } + + return ( + + {validateInput(keyboardText) ? (partTypes + .filter(one => ( + isMentionPartType(one) + && one.renderSuggestions != null + && !one.isBottomMentionSuggestionsRender + )) as MentionPartType[]) + .map(renderMentionSuggestions) + : null + } + + + + {parts.map(({text, partType, data}, index) => partType ? ( + + {text} + + ) : ( + {text} + ))} + + + + {validateInput(keyboardText) ? (partTypes + .filter(one => ( + isMentionPartType(one) + && one.renderSuggestions != null + && one.isBottomMentionSuggestionsRender + )) as MentionPartType[]) + .map(renderMentionSuggestions) + : null + } + + ); +}; + +export { MentionInputControlled }; \ No newline at end of file -- cgit v1.2.3-70-g09d2 From d2ea82d2d568a0d94bf516736da976bf0ba09e21 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Tue, 11 May 2021 12:10:39 -0700 Subject: Improved regex test --- src/components/comments/MentionInputControlled.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/components/comments/MentionInputControlled.tsx b/src/components/comments/MentionInputControlled.tsx index 642ef64c..892c9564 100644 --- a/src/components/comments/MentionInputControlled.tsx +++ b/src/components/comments/MentionInputControlled.tsx @@ -43,7 +43,13 @@ const MentionInputControlled: FC = ( const [keyboardText, setKeyboardText] = useState(''); - const validRegex = /.*\@[^ ]*$/; + const validRegex = () => { + if (partTypes.length === 0) { + return /.*\@[^ ]*$/; + } else { + return new RegExp(`.*\@${keywordByTrigger[partTypes[0].trigger]}.*$`); + } + }; const { plainText, @@ -134,7 +140,7 @@ const MentionInputControlled: FC = ( ); const validateInput = (testString: string) => { - return validRegex.test(testString); + return validRegex().test(testString); } return ( -- cgit v1.2.3-70-g09d2 From d760f15aa6971c39f028e18a007a5c56f08b6149 Mon Sep 17 00:00:00 2001 From: Brian Kim Date: Tue, 11 May 2021 12:49:30 -0700 Subject: Fixed linter --- src/components/comments/AddComment.tsx | 1 - src/components/comments/MentionInputControlled.tsx | 130 ++++++++++----------- 2 files changed, 65 insertions(+), 66 deletions(-) (limited to 'src') diff --git a/src/components/comments/AddComment.tsx b/src/components/comments/AddComment.tsx index 28e1a40e..befaa8fe 100644 --- a/src/components/comments/AddComment.tsx +++ b/src/components/comments/AddComment.tsx @@ -7,7 +7,6 @@ import { TextInput, View, } from 'react-native'; -import {MentionInput} from 'react-native-controlled-mentions'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {useDispatch, useSelector} from 'react-redux'; import UpArrowIcon from '../../assets/icons/up_arrow.svg'; diff --git a/src/components/comments/MentionInputControlled.tsx b/src/components/comments/MentionInputControlled.tsx index 892c9564..6abcb566 100644 --- a/src/components/comments/MentionInputControlled.tsx +++ b/src/components/comments/MentionInputControlled.tsx @@ -1,4 +1,4 @@ -import React, { FC, MutableRefObject, useMemo, useRef, useState } from 'react'; +import React, {FC, MutableRefObject, useMemo, useRef, useState} from 'react'; import { NativeSyntheticEvent, Text, @@ -7,10 +7,10 @@ import { View, } from 'react-native'; -import { - MentionInputProps, - MentionPartType, - Suggestion +import { + MentionInputProps, + MentionPartType, + Suggestion, } from 'react-native-controlled-mentions/dist/types'; import { defaultMentionTextStyle, @@ -21,22 +21,20 @@ import { parseValue, } from 'react-native-controlled-mentions/dist/utils'; -const MentionInputControlled: FC = ( - { - value, - onChange, +const MentionInputControlled: FC = ({ + value, + onChange, - partTypes = [], + partTypes = [], - inputRef: propInputRef, + inputRef: propInputRef, - containerStyle, + containerStyle, - onSelectionChange, + onSelectionChange, - ...textInputProps - }, -) => { + ...textInputProps +}) => { const textInput = useRef(null); const [selection, setSelection] = useState({start: 0, end: 0}); @@ -45,18 +43,20 @@ const MentionInputControlled: FC = ( const validRegex = () => { if (partTypes.length === 0) { - return /.*\@[^ ]*$/; + return /.*\@[^ ]*$/; } else { - return new RegExp(`.*\@${keywordByTrigger[partTypes[0].trigger]}.*$`); + return new RegExp(`.*\@${keywordByTrigger[partTypes[0].trigger]}.*$`); } }; - const { - plainText, - parts, - } = useMemo(() => parseValue(value, partTypes), [value, partTypes]); + const {plainText, parts} = useMemo(() => parseValue(value, partTypes), [ + value, + partTypes, + ]); - const handleSelectionChange = (event: NativeSyntheticEvent) => { + const handleSelectionChange = ( + event: NativeSyntheticEvent, + ) => { setSelection(event.nativeEvent.selection); onSelectionChange && onSelectionChange(event); @@ -69,7 +69,9 @@ const MentionInputControlled: FC = ( */ const onChangeInput = (changedText: string) => { setKeyboardText(changedText); - onChange(generateValueFromPartsAndChangedText(parts, plainText, changedText)); + onChange( + generateValueFromPartsAndChangedText(parts, plainText, changedText), + ); }; /** @@ -89,7 +91,9 @@ const MentionInputControlled: FC = ( * - Get updated value * - Trigger onChange callback with new value */ - const onSuggestionPress = (mentionType: MentionPartType) => (suggestion: Suggestion) => { + const onSuggestionPress = (mentionType: MentionPartType) => ( + suggestion: Suggestion, + ) => { const newValue = generateValueWithAddedSuggestion( parts, mentionType, @@ -132,64 +136,60 @@ const MentionInputControlled: FC = ( const renderMentionSuggestions = (mentionType: MentionPartType) => ( - {mentionType.renderSuggestions && mentionType.renderSuggestions({ - keyword: keywordByTrigger[mentionType.trigger], - onSuggestionPress: onSuggestionPress(mentionType), - })} + {mentionType.renderSuggestions && + mentionType.renderSuggestions({ + keyword: keywordByTrigger[mentionType.trigger], + onSuggestionPress: onSuggestionPress(mentionType), + })} ); const validateInput = (testString: string) => { - return validRegex().test(testString); - } + return validRegex().test(testString); + }; return ( - {validateInput(keyboardText) ? (partTypes - .filter(one => ( - isMentionPartType(one) - && one.renderSuggestions != null - && !one.isBottomMentionSuggestionsRender - )) as MentionPartType[]) - .map(renderMentionSuggestions) - : null - } + {validateInput(keyboardText) + ? (partTypes.filter( + (one) => + isMentionPartType(one) && + one.renderSuggestions != null && + !one.isBottomMentionSuggestionsRender, + ) as MentionPartType[]).map(renderMentionSuggestions) + : null} + onSelectionChange={handleSelectionChange}> - {parts.map(({text, partType, data}, index) => partType ? ( - - {text} - - ) : ( - {text} - ))} + {parts.map(({text, partType, data}, index) => + partType ? ( + + {text} + + ) : ( + {text} + ), + )} - {validateInput(keyboardText) ? (partTypes - .filter(one => ( - isMentionPartType(one) - && one.renderSuggestions != null - && one.isBottomMentionSuggestionsRender - )) as MentionPartType[]) - .map(renderMentionSuggestions) - : null - } + {validateInput(keyboardText) + ? (partTypes.filter( + (one) => + isMentionPartType(one) && + one.renderSuggestions != null && + one.isBottomMentionSuggestionsRender, + ) as MentionPartType[]).map(renderMentionSuggestions) + : null} ); }; -export { MentionInputControlled }; \ No newline at end of file +export {MentionInputControlled}; -- cgit v1.2.3-70-g09d2