-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(richtext-lexical): wrapper blocks #9289
base: main
Are you sure you want to change the base?
Conversation
I find this super interesting and with a lot of potential. But I'm not convinced. For the case of link nodes, I don't see an advantage since it is a feature that is already implemented and as you said, “the DX of having a dedicated link node with strong typing is nicer”. For the case of textColor and backgroundColor this is what I have in mind: Also, how would you implement some things that exist with the features like: On the other hand, this wraps the nodes to accomplish things that could be done with a property on the node, making the serialization of the editor larger. I have an alternative idea. Building on the TextColorFeature I linked above I'm going to experiment with a more generic TextAttributeFeature , I'll keep you posted. |
This is something we still have to solve for our block and inline block nodes, so this problem is not exclusively relevant for wrapper blocks. Might have to expose additional properties for more control over toolbar positioning. We should be able to handle
Features are extremely modular and powerful, but it's not too easy to create custom features. I have tried filming a tutorial for creating custom Lexical features, and it got a lot more complex than I expected. 40 minute video, and that was just half of what I wanted to film. There is a lot you need to do to write a custom feature. 2 different files for client and server feature, register a command in a plugin, create and register a lexical node, etc. And if you then want to attach custom payload fields to a node, that's a lot of work. Setting up the drawer, making sure it receives initial state from the server, handling the form, hooks, etc etc. We need some top-level APIs like wrapper blocks to make it super easy to build custom stuff with lexical. And for the remaining 2% of use-cases, people can build a feature and have full control.
100% agree with that. That's something I'm still conflicted about. When should you wrap a node (=> ElementNode) and when should you modify properties on the TextNode? I think we might need both. If you need to wrap MULTIPLE text nodes, without duplicating the field data saved in the editor JSON, then wrapper blocks will be the right choice. For example, one may want to create something similar to a LinkFeature using wrapper blocks. But instead of displaying the link in the floating toolbar, they want to display some other, custom component. We can't use a property on the TextNode for that. Another example might be a TextBorder Feature. Say someone wants to select some text and wrap it in a border. Needs to be in a wrapped node, otherwise it will be tricky to display it in a way where the multiple text nodes are in a single, connected border. Here's an even better example: Comments! Using WrapperBlocks, you could easily implement a Comment WrapperBlock, where you can select text, and attach a comment to it. The comment could be written in yet another lexical richText field. And using wrapper blocks, you could replace the component of the floating toolbar in order to display the comment editor inline. Wrapper Blocks handle all that for you. You could even add hooks to your comment editor, and customize the lexical features available. We wouldn't be able to use text attributes for this, as the comment data needs to be shared between multiple text nodes it wraps. And building a custom feature would yet again lead to a ton of duplicative code in regards to handling sub-fields, floating toolbars, drawers, form state etc. How do you feel about keeping both wrapper blocks, as well as a |
…ays fetched on the demand when needed. Initial render will never need a state for wrapper blocks
Now that you mention comments, we probably want to use MarkNodes like Lexical's Playground does, because it keeps them as a unit even if it is span across several nodes. This can be useful for displaying them, highlighting them on hover, removing them, etc. So I wonder if we wouldn't need this or another abstraction to allow building sets of MarkNodes, instead of ElementNodes. I'm not against introducing a new abstraction if it can be useful. Just considering that the use cases for this are for things that have already been solved in other ways or that no one is requesting yet, so I think it warrants some deep thought. Currently, the existing or potential APIs for users to customize the editor are:
I'll think about this and revisit it on Monday, just food for though in the meantime. |
…per blocks with and without custom block components
Wrapper Blocks can be used to wrap existing text in a node, called the WrapperBlock node. While Blocks and Inline Blocks are standalone nodes that can not contain children, Wrapper Blocks can contain children.
These can be used to create any kind of node that operates against existing text, e.g. links, color pickers, text font pickers, a comment plugin, new text format, text styles and text references.
This is the last one in the holy trinity of lexical block types. After this, most editor features that one can imagine can be implemented using just the BlocksFeature, without having to worry about creating custom features and interfacing with the lexical API.
I am contemplating about moving the LinkFeature to use wrapper blocks, as 90% of code is duplicative. However, we will probably keep the LinkNodes, as they are used very frequently and the DX of having a dedicated link node with strong typing is a bit nicer.
Canary to test this:
3.2.3-canary.673b4b5
Example - Color Wrapper Block
Result
CleanShot.2024-11-29.at.21.27.34.mp4
Code
Lexical Field
ColorLabelComponent.tsx
createDOM.ts