Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implement Autocomplete in Draft JS but without a "trigger" like "@"

I want to implement something like a tag editor. However, it's meant just for those tags so I want the user to see the autocomplete suggestions popup without having to type something like @ or #, just the text itself.

I have something that kinda works, but the popup displays in strange positions on the screen:

  • when I first type something and the popup appears, it appears somewhere near the top-left corner of the screen
  • after the first entity is created, when press SPACE and start typing again, the popup appears a couple of pixels to the right from it's intuitive position (i.e. under the first letter of the word)

Here is an example of a well-known editor of this kind (although not implemented with Draft), so you can get a better understanding of what I want to implement.

Gmail email composer

First things first, here is the function that triggers the suggestions popup:

private onChange(editorState: EditorState) {
  const content = editorState.getCurrentContent();
  const selection = editorState.getSelection();
  const currentBlock = content.getBlockForKey(selection.getAnchorKey());

  if (selection.isCollapsed()) {
    const blockText = currentBlock.getText();
    const wordMeta = getWordAt(blockText, selection.getAnchorOffset());
    const categoryRegex = /([\w]*)/;
    const matches = wordMeta.word.match(categoryRegex);
    const existingEntity = currentBlock.getEntityAt(wordMeta.begin);

    if (!existingEntity && matches) {
      const categorySearch = matches[1];
      const selection = window.getSelection();
      if (this.state.autoComplete.search !== categorySearch && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        const boundingRect = getRangeBoundingClientRect(range);

        this.setState((prevState: StateType) => {
          let state = {
            autoComplete: {
              active: true,
              search: categorySearch,
              searchMeta: {
                begin: wordMeta.begin,
                end: wordMeta.end,
              },
            },
            selectionRect: prevState.selectionRect,
          };

          if (prevState.autoComplete.active === false) {
            state.selectionRect = boundingRect;
          }

          return state;
        });
      }
    }
  }

  this.props.onChange(editorState);
}

Here is the getWordAt function:

function getWordAt(text: string, pos: number): WordMeta
{
  const left = text.slice(0, pos + 1).search(/\S+$/);
  const right = text.slice(pos).search(/\s/);

  if (right < 0) {
    return {
      word: text.slice(left),
      begin: left,
      end: text.length,
    };
  }

  return {
    word: text.slice(left, right + pos),
    begin: left,
    end: right + pos,
  };
}

What would be a better way of handling the position of the popup and maybe even the strategy for autocompletion of this kind as well? Thank you!

like image 399
Victor Avatar asked Jan 04 '18 10:01

Victor


1 Answers

Instead Of draft-js-typeahead - TypeaheadEditor is a react component that wraps draft's Editor. You can use React-Autosuggest component that meets the requirements. It has custom rendering that works natively with React elements. It's fast and pretty easily customizable. Have full control over suggestions rendering.

We can make it handle JS objects instead of plain strings.

  1. the onSuggestionSelected props is a callback to get the selected suggestion
  2. suggestionRenderer method takes suggestion and returns React markup

Check out React-Autosuggest.

You can use above component by using a custom block renderer, it is possible to introduce complex rich interactions within the frame of your editor.

You have to break your head to achieve what you want to, its not straight forward. This was my suggestion through which you might achieve it but its not that easy.

like image 152
Varsha Avatar answered Oct 11 '22 15:10

Varsha