facebookarchive / draft-js

A React framework for building text editors.
https://draftjs.org/
MIT License
22.58k stars 2.64k forks source link

Out of sync selectionState within decorator strategy #484

Open bryceosterhaus opened 8 years ago

bryceosterhaus commented 8 years ago

It would be nice for developers to be able to get the selectionState within a decorator strategy. When trying to do this with this.state.editorState.getSelection() inside of the decorator, the data seems to be out of sync.

Try creating a decorator with this strategy

strategy: (contentBlock, callback) =>  {
     const editorStateText = this.state.editorState.getCurrentContent().getPlainText();
     const contentBlockText = contentBlock.getText();

     console.log(editorStateText);
     console.log(contentBlockText);

     //...
},

You will notice that contentBlockText will be one character(or keystroke) ahead of editorStateText when typing.

Does getting an up-to-date selectionState within the decorator strategy seem like a good idea?

hellendag commented 8 years ago

This has come up a handful of times. I think it would be reasonable to provide SelectionState to decorator strategies, especially since people have requested being able to decorate selected ranges.

butchmarshall commented 8 years ago

I'd love this as well.

I want to apply a decorator /only/ when the cursor is within the block of text. The problem I'm encountering is it's always a keystroke behind. Certain corner cases like backspacing (index is 0 when its actually 1, 1 when its actually 0) when I attempt to use getSelection() are impossible to figure out.

Is there a workaround?

lukesmurray commented 4 years ago

I know its been 3 years. Did you find any decent work arounds? @butchmarshall or @hellendag.

I have a similar use case to @butchmarshall and ran into similar issues.

lukesmurray commented 4 years ago

Alright I solved this with patch-package. Here is the relevant diff. (It is a little messy but its not on master yet 😅.

The general idea is anywhere we call decorator.getDecorations we want to pass in the new SelectionState as an extra parameter. I thought of setting the new SelectionState on the contentState as selectionAfter but I think that would potentially break things.

getDecorations is called in regenerateTreeForNewDecorator, regenerateTreeForNewBlocks, and generateNewTreeMap. I modified each of those functions to accept a new parameter newSelection.

Those functions are called in two places. EditorState.set and EditorState. create. In EditorState.create we don't have an existing selection so I just call generateNewTreeMap with SelectionState.createEmpty(). In EditorState.set we either have a new selection in put.selection or we can use the existing selection from EditorState.getSelection(). I get the new or existing selection using var newSelection = put.selection || editorState.getSelection(); then pass that to the three methods I mentioned.

Finally the CompositeDraftDecorator needs to be modified to pass the newSelection parameter to the getDecorations method. That is straightforward.

If you are using DraftJS-Plugins you also need to modify the MultiDecorator to pass the newSelection parameter to the composed decorators.

This change enables a lot of really cool decorators which really on the position of the selection. Unfortunately since decorators are only called when content changes these are mostly useless. I don't think this should be added to draft-js but wanted to document the solution just in case anyone else got stuck here.