I am trying to build a Rich Text Editor from scratch, Seems like a really big & complex task, I know, The editor should make use of Range
& selection apis instead of execCommand
, be extensible and support basic editing features like paragraph alignment and inline styling.
I've found the SlateJs
library which also allows you to build rich text editor. However I am determined to do this from scratch, even though I don't know where to start.
The problem is that when the user types into my editor the text goes directly into container element with contentEditable
div. However when the user types into SlateJs
editor, The text goes into a three nested spans which are contained inside a paragraph
element , You can inspect this here https://www.slatejs.org/examples/richtext
How can I implement this behavior ?
preventDefault
doesn't work on input but works on keydown
editor.addEventListener("keydown",(e)=> {
e.preventDefault()
// if the key is a printable character
if(event.key.length == 1){
// this always appends to the last of paragraph even if the cursor is somewhere else
this.topLevelParagraph.innerHTML += event.key
}
})
The caret position doesn't change, Text always goes at last.
I learned that to maintain structure, You need to do some things
1 - Initial structure, You must create initial elements, wrappers and formatting inline elements and decide which element should / will take input from the user
2 - A keydown
event on the container with contentEditable
attribute , Now you can preventDefault
on the event and put the key into the exact element inside the container, You must filter out keys that are not printable characters
, You can filter control characters using ascii
codes but I recommend you use unicode
I used e.key.length == 1
and some other filters
3 - Now your input has gone into the box, or maybe not because you preventedDefault
, You must also track user selection range and adjust it after every key, so input is at the right position, for example on each key you must collapse the current range
at endPosition + 1
, so the caret remains right next to the current letter typed
4 - You must handle copy
paste
, You don't have to handle backspace key, However after every user action, You must make sure the structure remains intact
You could go a separate way and create your own data structure, You take all the events from the browser and have a structure in your code that user changes instead of the text field and re render the object that takes the input / has changed.