docs: add Decorators concept guide#8696
Conversation
|
Hi @WilliamK112! Thank you for your pull request and welcome to our community. Action RequiredIn order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you. ProcessIn order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA. Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks! |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
potatowagon
left a comment
There was a problem hiding this comment.
Reviewed by Navi (Tater Thoughts Bobblehead) on behalf of @potatowagon.
Assessment: LGTM ✅ — Pure documentation addition, well-structured and accurate.
What I checked:
- Content accuracy: The guide correctly describes
DecoratorNodebehavior —decorate()returning framework-specific values,createDOM()as the host,updateDOM()returning false for stable hosts, inline-by-default semantics. All consistent with the Lexical source. - Code examples: The
VideoNodeexamples use correct APIs ($insertNodes,createCommand,COMMAND_PRIORITY_EDITOR,useLexicalComposerContext). The NodeState example correctly usescreateState,$getState,$setState, and$create— matching the new NodeState/$config protocol from recent PRs. - Cross-references: Links to
/docs/concepts/node-stateand the existing DecoratorNode section innodes.mdxare valid. Sidebar placement (afternodes, beforenode-replacement) is logical. - No library code changes — only
packages/lexical-website/files touched. - No www compat concerns (docs only).
CI status: Vercel previews pass (both website and playground deploy fine). CLA check is failing — author needs to sign the Meta CLA before this can merge.
Minor suggestions (non-blocking):
- The
isInline(): falsereturn type annotation is redundant since the method body already returnsfalse(TypeScript infers it), but it does serve as documentation — fine either way. - Consider adding a brief mention of
setDecoratorClassNames()for styling the host element, since newcomers often struggle with styling decorator boundaries.
Ready to merge once CLA is signed.
|
Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Meta Open Source project. Thanks! |
etrepum
left a comment
There was a problem hiding this comment.
This is a good start, a few additional things to cover (some of these are unreleased but in main):
- Current lexical convention is to put all configuration and behavior in extensions, not directly in an editor config (which also implies using LexicalExtensionComposer)
- DecoratorNode can be framework independent if the decorate method returns null (the default) and all work happens in createDOM/updateDOM and/or managed by DOMRenderExtension hooks or a mutation listener (HorizontalRuleNode from
@lexical/extensionis an example of this) - There's probably other stuff to cover with named slots which adds a lot of potential to DecoratorNode
- DOMImportExtension should be mentioned in preference to importDOM, which will be deprecated eventually because the two import methods can't be used at the same time
- It's an advanced feature, but NodeState can store any kind of data, given a suitable StateValueConfig
- It's also recently possible to use arbitrary DOM inside or around ElementNode with a combination of DOMSlot and something like mutation listeners, like the ReviewExtension and its associated ReviewNode in the playground
|
Thanks for the detailed pointers. I pushed a follow-up commit ( The earlier follow-up already covered the other items you mentioned: extension-first registration, framework-independent decorators via Validation run locally:
|
etrepum
left a comment
There was a problem hiding this comment.
The PR title and description should follow the pull request template
| As with other custom nodes, a decorator node must be registered before it is | ||
| used. In React, pass the node class in the editor config: | ||
|
|
||
| ```tsx | ||
| <LexicalComposer | ||
| initialConfig={{ | ||
| namespace: 'Example', | ||
| nodes: [VideoNode], | ||
| onError(error) { | ||
| throw error; | ||
| }, | ||
| }}> | ||
| {/* editor UI */} | ||
| </LexicalComposer> | ||
| ``` | ||
|
|
||
| A common pattern is to pair the node with a plugin or extension that registers | ||
| commands for inserting it. | ||
|
|
||
| ```ts | ||
| export const INSERT_VIDEO_COMMAND: LexicalCommand<string> = createCommand( | ||
| 'INSERT_VIDEO_COMMAND', | ||
| ); | ||
|
|
||
| function VideoPlugin(): null { | ||
| const [editor] = useLexicalComposerContext(); | ||
|
|
||
| useEffect(() => { | ||
| return editor.registerCommand( | ||
| INSERT_VIDEO_COMMAND, | ||
| videoID => { | ||
| editor.update(() => { | ||
| $insertNodes([$createVideoNode(videoID)]); | ||
| }); | ||
| return true; | ||
| }, | ||
| COMMAND_PRIORITY_EDITOR, | ||
| ); | ||
| }, [editor]); | ||
|
|
||
| return null; | ||
| } |
There was a problem hiding this comment.
This section is still documenting the legacy config + plugin pattern, not extensions. Plugins that return null and have no actual react dependency are also not encouraged because that can be better handled with framework independent extensions. This whole example can be implemented in a single extension rather than the separate legacy config + plugin pattern.
| For newer code, prefer putting the node registration and the behavior that | ||
| goes with it in an [extension](/docs/extensions/intro) instead of spreading it | ||
| across an editor config and separate plugins. React applications can mount that | ||
| root extension with | ||
| [`LexicalExtensionComposer`](/docs/api/modules/lexical_react_LexicalExtensionComposer#lexicalextensioncomposer). | ||
| This keeps node registration, commands, transforms, mutation listeners, and | ||
| framework decorators in one composable unit. |
There was a problem hiding this comment.
We should simply document how you would use decorators with extensions and not even provide an example of how it used to be done.
| `importDOM()` still works, but it shares the same import path as the extension | ||
| pipeline and is expected to be deprecated eventually, so avoid mixing both | ||
| approaches for the same import behavior. |
There was a problem hiding this comment.
"shares the same import path as the extension pipeline" doesn't make sense.
| `decorate()`. | ||
|
|
||
| Decorator nodes also combine well with | ||
| [named slots](https://lexical.dev/docs/concepts/named-slots) when surrounding |
There was a problem hiding this comment.
This can be a relative URL like all of the other links
Addresses part of #2845.
This adds a dedicated Concepts page for Decorators, following the maintainer suggestion in #2845 to bring the old decorator-node guidance into the website docs under Concepts.
The new page explains:
DecoratorNodeinstead ofTextNodeorElementNodecreateDOM()/decorate()rendering modelIt also links the existing DecoratorNode section in
concepts/nodesto the new page and adds the page to the Concepts sidebar.Validation:
pnpm exec prettier --check packages/lexical-website/docs/concepts/decorators.mdx packages/lexical-website/docs/concepts/nodes.mdx packages/lexical-website/sidebars.jspnpm --filter @lexical/website run tscpnpm --filter @lexical/website run buildNote: the website build succeeded. It emitted an existing
/docs/api/HTML minifier warning unrelated to this new Decorators page.