Skip to content

Commit 1ef43eb

Browse files
authored
feat(richtext-lexical): add view override system for custom node rendering (#14244)
Adds support for custom view maps that allow users to override how Lexical nodes are rendered in the editor. This enables full control over node presentation without modifying the underlying data structure or node class. ⚠️ **Experimental**: This API is experimental and may change in minor releases. ## Key Features - **Custom Node Rendering**: Override the visual presentation of any node type (built-in or custom) - **Per-Editor Configuration**: Each editor instance can have its own view overrides. Each editor can toggle between multiple views, with each view rendering nodes differently. - **Dual Usage**: View definitions work in both the admin panel editor and frontend JSX serialization, ensuring visual consistency ## Benefits - **WYSIWYG Editing**: Make the admin editor look exactly like your frontend - **Consistent Rendering**: Single source of truth for both admin and frontend ## Usage Define a view map and pass it to the lexical editor: ```tsx // views.tsx export const myViews: LexicalEditorViewMap = { default: { // Override heading rendering with custom DOM heading: { createDOM() { const h2 = document.createElement('h2') h2.textContent = 'Custom Heading' return h2 }, }, // Override with a React component horizontalRule: { Component: () => <div className="custom-hr">---</div>, }, // Override with HTML string/function link: { html: '<a href="#">Custom Link</a>', }, }, } ``` ```ts // config.ts { fields: [ { name: 'content', type: 'richText', editor: lexicalEditor({ views: '/path/to/views.tsx#myViews', }), }, ] } ```
1 parent 04163ea commit 1ef43eb

69 files changed

Lines changed: 4638 additions & 351 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/e2e.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ export default createE2EConfig([
6969
// TODO: Enable parallel mode again when ensureCompilationIsDone is extracted into a playwright hook. Otherwise,
7070
// it runs multiple times in parallel, for each single test, which causes the tests to fail occasionally in CI.
7171
{ file: 'lexical__collections__LexicalListsFeature', shards: 1, parallel: false },
72+
{ file: 'lexical__collections__LexicalViewsFrontend', shards: 1, parallel: false },
73+
{ file: 'lexical__collections__LexicalViewsProvider', shards: 1, parallel: false },
74+
{ file: 'lexical__collections__LexicalViewsProviderDefault', shards: 1, parallel: false },
75+
{ file: 'lexical__collections__LexicalViewsNested', shards: 1, parallel: false },
76+
7277
{ file: 'lexical__collections__OnDemandForm', shards: 1 },
7378
{ file: 'lexical__collections__Lexical__e2e__main', shards: 2 },
7479
{ file: 'lexical__collections__Lexical__e2e__blocks', shards: 2 },

docs/rich-text/converting-jsx.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const MyComponent = ({ data }: { data: SerializedEditorState }) => {
2222

2323
The `RichText` component includes built-in converters for common Lexical nodes. You can add or override converters via the `converters` prop for custom blocks, custom nodes, or any modifications you need. See the [website template](https://github.com/payloadcms/payload/blob/main/templates/website/src/components/RichText/index.tsx) for a working example.
2424

25+
If you want to share the same node rendering logic between the admin panel and your frontend, consider using [Views](/docs/rich-text/views) instead. Views let you define a single node map that works in both contexts.
26+
2527
<Banner type="default">
2628
When fetching data, ensure your `depth` setting is high enough to fully
2729
populate Lexical nodes such as uploads. The JSX converter requires fully

docs/rich-text/overview.mdx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,30 @@ You can customize the placeholder (the text that appears in the editor when it's
309309
}
310310
```
311311

312+
## Views
313+
314+
Views let you define shared rendering logic for Lexical nodes that works in both the admin panel and JSX converters. You can define multiple named views (like `default`, `preview`, `debug`) and switch between them using the built-in view selector, then pass the same node maps to JSX converters for consistent rendering outside the editor.
315+
316+
```ts
317+
import { lexicalEditor } from '@payloadcms/richtext-lexical'
318+
319+
export const Posts = {
320+
slug: 'posts',
321+
fields: [
322+
{
323+
name: 'content',
324+
type: 'richText',
325+
editor: lexicalEditor({
326+
// Import path with #exportName to specify which view map to use
327+
views: './views.js#postViews',
328+
}),
329+
},
330+
],
331+
}
332+
```
333+
334+
Learn more about [Views](/docs/rich-text/views).
335+
312336
## Detecting empty editor state
313337

314338
When you first type into a rich text field and subsequently delete everything through the admin panel, its value changes from `null` to a JSON object containing an empty paragraph.

0 commit comments

Comments
 (0)