react-native#TextInputSelectionChangeEventData TypeScript Examples
The following examples show how to use
react-native#TextInputSelectionChangeEventData.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: mention-input.tsx From react-native-controlled-mentions with MIT License | 4 votes |
MentionInput: 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 {
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) => {
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>
);
return (
<View style={containerStyle}>
{(partTypes
.filter(one => (
isMentionPartType(one)
&& one.renderSuggestions != null
&& !one.isBottomMentionSuggestionsRender
)) as MentionPartType[])
.map(renderMentionSuggestions)
}
<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>
{(partTypes
.filter(one => (
isMentionPartType(one)
&& one.renderSuggestions != null
&& one.isBottomMentionSuggestionsRender
)) as MentionPartType[])
.map(renderMentionSuggestions)
}
</View>
);
}
Example #2
Source File: MentionInput.tsx From lowkey-react-native-mentions-input with MIT License | 4 votes |
MentionsInput = React.forwardRef(
(
{
suggestedUsersComponent,
textInputStyle,
onFocusStateChange = () => {},
onTextChange = () => {},
onMarkdownChange = () => {},
placeholder = 'Write a message...',
placeholderTextColor,
multiline,
textInputTextStyle,
leftComponent = <></>,
rightComponent = <></>,
innerComponent = <></>,
users,
...props
}: Props,
ref
) => {
const [isOpen, SetIsOpen] = useState(false);
const [suggestedUsers, SetSuggesedUsers] = useState<SuggestedUsers[]>([]);
const [matches, SetMatches] = useState<any[]>([]);
const [mentions, SetMentions] = useState<any[]>([]);
const [currentCursorPosition, SetCurrentCursorPosition] = useState(0);
useEffect(() => {
if (props.value === '' && (mentions.length > 0 || matches.length > 0)) {
SetMatches([]);
SetMentions([]);
SetCurrentCursorPosition(1);
}
}, [matches, mentions, props.value]);
const transformTag = useCallback((value: string) => {
return value
.replace(/\s+/g, '')
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '');
}, []);
const handleSuggestionsOpen = useCallback(
(values: RegExpMatchArray[], currentCursorPosition: number) => {
let shouldPresentSuggestions = false;
let newSuggestedUsers: Array<SuggestedUsers> = [];
users.map(
(user, index) =>
(newSuggestedUsers[index] = {
...user,
startPosition: 0,
})
);
values.map((match) => {
if (match === null) {
return;
}
const matchStartPosition = match.index;
if (typeof matchStartPosition === 'undefined') {
return;
}
const matchEndPosition = matchStartPosition + match[0].length;
if (
currentCursorPosition > matchStartPosition &&
currentCursorPosition <= matchEndPosition
) {
shouldPresentSuggestions = true;
newSuggestedUsers = newSuggestedUsers
.filter((user) =>
user.name
.toLowerCase()
.includes(match[0].substring(1).toLowerCase())
)
.map((user) => {
user.startPosition = matchStartPosition;
return user;
});
}
});
const isSameSuggestedUser =
suggestedUsers.length === newSuggestedUsers.length &&
suggestedUsers.every(
(value, index) =>
value.id === newSuggestedUsers[index].id &&
value.startPosition == newSuggestedUsers[index].startPosition
);
SetIsOpen(shouldPresentSuggestions);
if (!isSameSuggestedUser) {
SetSuggesedUsers(newSuggestedUsers);
}
},
[users, suggestedUsers]
);
const formatMarkdown = useCallback(
(markdown: string) => {
let parseHeadIndex = 0;
let markdownArray = [];
if (mentions.length === 0) {
markdownArray.push({
type: 'text',
data: markdown,
});
}
mentions.map((mention, index) => {
let match = matches.find((m) => {
return (
m.index === mention.user.startPosition &&
m[0] === `@${mention.user.name}`
);
});
if (typeof match === 'undefined') {
return;
}
markdownArray.push({
type: 'text',
data: markdown.substring(
parseHeadIndex,
mention.user.startPosition
),
});
markdownArray.push({
type: 'mention',
data: `<@${mention.user.name}::${mention.user.id}>`,
});
parseHeadIndex =
mention.user.startPosition + mention.user.name.length + 1;
if (index === mentions.length - 1) {
markdownArray.push({
type: 'text',
data: markdown.substring(parseHeadIndex, markdown.length),
});
}
});
markdown = '';
markdownArray.map((m) => {
if (m.type === 'text') {
markdown = markdown + encodeURIComponent(m.data);
} else if (m.type === 'mention') {
markdown = markdown + m.data;
}
});
onMarkdownChange(markdown);
},
[onMarkdownChange, mentions, matches]
);
const handleDelete = useCallback(
({ nativeEvent }: NativeSyntheticEvent<TextInputKeyPressEventData>) => {
if (nativeEvent.key === 'Backspace') {
mentions.map((mention, index) => {
const matchStartPosition = mention.user.startPosition;
const matchEndPosition =
matchStartPosition + mention.user.name.length + 1;
if (
currentCursorPosition > matchStartPosition &&
currentCursorPosition <= matchEndPosition
) {
const newMentions = mentions;
newMentions.splice(index, 1);
SetMentions(newMentions);
}
});
}
},
[mentions, currentCursorPosition]
);
const onSelectionChange = useCallback(
({
nativeEvent,
}: NativeSyntheticEvent<TextInputSelectionChangeEventData>) => {
if (nativeEvent.selection.start === nativeEvent.selection.end) {
SetCurrentCursorPosition(nativeEvent.selection.start);
}
},
[]
);
const handleMentions = useCallback(
(newText: string, currentCursorPosition: number) => {
const pattern = PATTERNS.USERNAME_MENTION;
let newMatches = [...matchAll(newText, pattern)];
let newMentions = newText.length > 0 ? mentions : [];
newMentions.map((mention) => {
const matchStartPosition = mention.user.startPosition;
if (decodeURI(newText).length - decodeURI(props.value).length > 0) {
if (
matchStartPosition + (newText.length - props.value.length) >
currentCursorPosition &&
currentCursorPosition !== props.value.length
) {
mention.user.startPosition =
mention.user.startPosition +
(newText.length - props.value.length);
}
} else {
if (matchStartPosition >= currentCursorPosition) {
mention.user.startPosition =
mention.user.startPosition +
(newText.length - props.value.length);
}
}
return mention;
});
onTextChange(newText);
formatMarkdown(newText);
const isSameMatch =
matches.length === newMatches.length &&
matches.every((value, index) => value === newMatches[index]);
SetMentions(newMentions);
if (!isSameMatch) {
SetMatches(newMatches);
}
},
[mentions, onTextChange, formatMarkdown, props.value, matches]
);
const onChangeText = useCallback(
(newText: string) => {
handleMentions(newText, currentCursorPosition);
},
[handleMentions, currentCursorPosition]
);
const handleAddMentions = useCallback(
(user: {
id: number;
name: string;
avatar: string;
startPosition: number;
}) => {
const startPosition = user.startPosition;
const mention = mentions.find(
(m) => m.user.startPosition === startPosition
);
if (mention) {
return;
}
const match = matches.find((m) => m.index === startPosition);
let newMentions = mentions;
const userName = transformTag(user.name);
const newText =
props.value.substring(0, match.index) +
`@${userName} ` +
props.value.substring(
match.index + match[0].length,
props.value.length
);
newMentions.push({
user: {
...user,
name: userName,
startPosition: startPosition,
test: 1000,
},
});
newMentions.sort((a, b) =>
a.user.startPosition > b.user.startPosition
? 1
: b.user.startPosition > a.user.startPosition
? -1
: 0
);
SetMentions(newMentions);
SetIsOpen(false);
const newCursor = match.index + user.name.length + 1;
SetCurrentCursorPosition(newCursor);
setTimeout(() => {
handleMentions(newText, newCursor);
}, 100);
},
[mentions, matches, transformTag, props.value, handleMentions]
);
const onFocus = useCallback(() => {
onFocusStateChange(true);
}, [onFocusStateChange]);
const onBlur = useCallback(() => {
onFocusStateChange(false);
}, [onFocusStateChange]);
useEffect(() => {
formatMarkdown(props.value);
}, [props.value, formatMarkdown]);
useEffect(() => {
let timeout = setTimeout(() => {
handleSuggestionsOpen(matches, currentCursorPosition);
}, 100);
return () => clearTimeout(timeout);
}, [handleSuggestionsOpen, matches, currentCursorPosition]);
return (
<View>
<View>
{isOpen && suggestedUsersComponent(suggestedUsers, handleAddMentions)}
<View style={styles.inputContainerRow}>
<View>{leftComponent}</View>
<View style={[textInputStyle, styles.row]}>
<TextInput
{...props}
onFocus={onFocus}
onBlur={onBlur}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
multiline={multiline}
value={decodeURI(props.value.replace(/%/g, encodeURI('%')))}
onChangeText={onChangeText}
onKeyPress={handleDelete}
style={[
textInputTextStyle,
styles.flex,
{ paddingBottom: multiline ? 5 : 0 },
]}
onSelectionChange={onSelectionChange}
//@ts-ignore
ref={ref}
/>
<View style={styles.innerContainer}>{innerComponent}</View>
</View>
{rightComponent}
</View>
</View>
</View>
);
}
)