by Andrey Semin
How to create ordered and unordered lists in Draft.js with a shortcut
We at Propeller have encountered many differences between Draft.js and popular text editors. We also found some issues like controlling list depth and multiline items in lists. The biggest difference is the inability to use shortcuts to start a list by default. Surprisingly enough you need to implement this logic yourself.
As always, there is a plugin available to add support for the shortcuts you use. I also want to refer to draft-js-autolist-plugin as a source of inspiration. For some reason, this plugin didn’t work when we tried it. So we’ve come up with our own solution which is now presented in this post.
Open Google Docs, Word365, or whatever editor you use. Try to type
* and then type
space. Boom! You’ve started an unordered list. Nice feature to have, right?
If we try the exact same trick with the default Draft.js configuration, we will get nothing but plain text.
Let’s change it!
To implement this feature, we need to keep track of the last three pressed buttons. Why three? Well, that’s because the longest character combination we need to support is
1. + space which is exactly three presses.
To start, let’s implement the logic to store these presses. Here we would use a simple array named
history. This array will store the value of the key that was pressed. We definitely don’t want to process any key presses with modifiers like
alt and so on. We can use Draft.js built-in
KeyBindingUtil.hasCommandModifier function to perform the check for any modifier.
Draft.js exposes a
keyDown event for us in the
keyBindingFn prop function. We are going to check if we need to start a list here. If so, we need to return a so called
DraftEditorCommand, which is a string. Also, to benefit from OS-level commands we need to add a
getDefaultKeyBinding function call as a fall-through case.
We need to check if the currently pressed key is a
space. If so we would run our checks against the
history array. We check if we have a suitable set of previously pressed keys —
* for an unordered list and
1. for an ordered one. If we find a match, we return a command(string) to be processed later.
Now we need to implement the
handleKeyCommand prop function and pass it to the editor. The logic is pretty simple. If we get one of our custom commands, we check if we should start a list on the current block. So here is a skeleton of the
To check if we are good to start a list, we check if the currently selected block satisfies all three of the following rules:
- The block type is
- The block has a
- the block has
1.as a text
Let’s wrap it up with the code:
Now we’re able to catch the exact case where Draft.js needs to start a list! Now it’s a time to implement the
First of all, we need to map our custom commands to a particular list style. This means we need to start an unordered list for the
We start an ordered list for the
start-ordered-list command. Next, we need to update the styling of the block to the selected type. To do it we would use the
toggleBlockType function of
RichUtils module, which comes as a part of Draft.js.
Next we need to replace the shortcut text we’ve entered with an empty string. To do it we need to call the
replaceText method of the
Modifier module. This method requires a selection range to determine what should be replaced. We need to get the selection out of the block and update it to have
focusOffset value equal to block length. This combination means we want to replace the whole text we’ve entered.
Great! Now we need to update our local editor state with the new state we get from the
startList function. So let’s bring it all together!
OK! We’re almost done! But there is one more moment we need to handle. In some cases when one of our custom commands fire, we should not start a list based on the output of the
shouldStartList function. We need to process the insertion of the space manually.
For implementation details of the
getSelectedBlock method, check out my previous post on this Draft.js topic!
To do this we may want to use a method called
insertText of the
Modifier module. Obviously enough, it is used to build a new content state with the provided text inserted into it. As always, we need to provide the current content state, current selection state and the text we want to insert (a single space in our case).
We need to add a call to this function to our
handleKeyCommand function. So here is the final version of it:
If you’ve read this post all the way through, you may also want to check out my previous post about Draft.js enchantment. You may want to apply it to your project as well.