payload
payload copied to clipboard
feat(richtext-lexical): text classes
This PR allows adding arbitrary classes to TextNodes, without the need to extend the node. My idea is to do the same with the rest of the nodes.
Extending a node is quite complex and has a lot of footguns. Also, we should make extending nodes the last resort to customize the editor, since 2 plugins extending the same node would not work.
Most of the time when someone extends a node, what they want is only to add a class to the node under certain conditions, so this property would allow them to achieve their goals in a simpler way.
As an example, this PR shows how a TextColorFeature could be developed in fewer lines of code and in a safer way since it would not involve breaking the code if used with another plugin that also extends TextNode (run pnpm dev _community
to try it).
The TextColorFeature is a work in progress. It still has bugs and room for improvement. Let's keep the discussion of this PR only about the new TextNode with classes. The TextColorFeature will be removed from here and incorporated in a later PR.
How does this work?
The __classes
property is an object where the key-value has the form prefix-suffix
if the suffix is a string, or prefix
if the value is of type boolean.
For example, this is how the following object would be rendered:
{
bg-color: 'red', // the node is rendered with class `bg-color-red`
color: 'green', // node is rendered with class `color-green`,
collapsed: true, // node is rendered with class `collapsed`,
hidden: false, // this does not add any class to the node
}
Also, in order to make the serialization as lightweight as possible, I filter out values that are undefined or false, or ignore the classes property if it has no entries.
Now, the million dollar question is: What if a user is already extending and replacing TextNode? Or what if they are importing TextNode to do other things?
Well, to solve this problem we have 2 options:
Option 1: we implement this feature in the Lexical repository. I know the Lexical team has often expressed interest in reducing the complexity of customizing nodes, and I think they might be interested in this API. If this is the case, it would be the simplest option.
Option 2: we do not re-export Lexical's TextNode, but our own TextNode with the same name.
We are currently using a wildcard export, and as far as I know you can't exclude a module, so we would have to explicitly mention all modules except the nodes, something complex to maintain.
An important point is that if there is a user who (a) is not using our re-export from Lexical as he/she should and (b) is extending the nodes he imports directly from Lexical, then there would be a breaking change. Actually, this is acceptable because the error is theirs, and we are re-exporting Lexical for a reason. But still to take into account.
If we want typescript not to complain if someone tries to use our TextNode in a Lexical function (example $isTextNode
), we would have to make the node extension structurally equivalent (__classes
should be optional, and the getClasses
and mutateClasses
methods should be functions external to the class).
By way of clarification, I have made sure to test that a node replacement API string works well. See this discussion.