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.
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!
getRangeBoundingClientRect
but that is not the only problem. the problem is that I somehow need to trigger the autocompletion for the text that is not included in any entity so I can get its bounding rectangle and display the autocompletion – Milomilon