react-native#TextInputKeyPressEventData TypeScript Examples

The following examples show how to use react-native#TextInputKeyPressEventData. 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: MentionInput.tsx    From lowkey-react-native-mentions-input with MIT License 4 votes vote down vote up
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>
    );
  }
)