Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7982263
[lexical][lexical-html] Feature: Extensible DOM rendering and import …
claude Apr 13, 2026
a643948
Fix remaining resetOnCopyNode references and update tests
claude Apr 13, 2026
1b4d8bb
move $getClipboardDataFromSelection to its original spot
etrepum Apr 13, 2026
396fc24
update example
etrepum Apr 13, 2026
8569d6d
es2022
etrepum Apr 13, 2026
a88c240
Refactor tests to use `using editor = buildEditorFromExtensions` pattern
claude Apr 13, 2026
fde01c7
Restore resetOnCopyNode/resetOnCopyNodeFrom from main
claude Apr 13, 2026
32cba39
unused parseStringEnum
etrepum Apr 13, 2026
ba5df49
Revert nodeStatesAreEquivalent size check that broke text node merging
claude Apr 13, 2026
de28f35
Remove unfinished DOMImportExtension, EmitterState, and related code
claude Apr 13, 2026
e5e15df
Fix trailing newline in types.ts (prettier)
claude Apr 13, 2026
80f9a15
Update dev-node-state-style example dependencies to match monorepo
claude Apr 13, 2026
286ec8f
Update dev-node-state-style example dependencies to match workspace
claude Apr 13, 2026
6c46a91
Update remaining dev-node-state-style deps to latest versions
claude Apr 13, 2026
bd578ec
Sync Flow types for new public exports in lexical and @lexical/html
claude Apr 13, 2026
cd60552
Remove examples/dev-* from npm workspaces
claude Apr 14, 2026
fc700f5
Update error codes for new invariant messages
claude Apr 14, 2026
5e25755
Fix dev-node-state-style integration test build
claude Apr 14, 2026
e947191
Make dev-* examples proper workspace packages, exclude from integrati…
claude Apr 14, 2026
4ec6e1b
Move dev-node-state-style to dev-examples/, add integration test support
claude Apr 14, 2026
479fcad
Restore tsc in dev-node-state-style build, fix tsconfig
claude Apr 14, 2026
4280afa
Add @experimental JSDoc tags to new public exports
claude Apr 14, 2026
bb4864d
Make internal-only functions private, remove @experimental from $isLe…
claude Apr 14, 2026
f51fc5e
Fix flaky Focus e2e test in Firefox by retrying assertion
claude Apr 14, 2026
8b3fec5
Skip Focus tab test on non-Chromium browsers
claude Apr 14, 2026
11efd5e
Add error logging to Focus tab test for CI debugging
claude Apr 14, 2026
a0aa32b
Restore $getEditorDOMRenderConfig in $internalResolveSelectionPoint
claude Apr 14, 2026
193ffe3
Fix playwright cache to update when browsers change
claude Apr 14, 2026
8533bf4
workaround for deferred not being flushed
etrepum Apr 14, 2026
3079ed7
add a test that can reproduce the bug (although it is bad)
etrepum Apr 14, 2026
82a463e
an actual failing test using realistic API calls
etrepum Apr 14, 2026
8050e9c
patch jsdom globally in the test suite
etrepum Apr 14, 2026
6204ac4
Merge branch 'main' into claude/remove-dom-import-code-LGqib
etrepum Apr 14, 2026
7dba6dc
restore error-codes
etrepum Apr 14, 2026
854b063
use workflows from main
etrepum Apr 15, 2026
da1de65
Merge branch 'main' into claude/remove-dom-import-code-LGqib
etrepum Apr 15, 2026
0315d61
fix dev-node-state-style stackblitz urls
etrepum Apr 15, 2026
247cd90
Add more api docs
etrepum Apr 16, 2026
7d47183
prettier
etrepum Apr 16, 2026
acbbbaf
Merge branch 'main' into claude/remove-dom-import-code-LGqib
etrepum Apr 16, 2026
464a4bc
Fix prettier formatting in agent-example
claude Apr 16, 2026
97de501
Use EditorDOMRenderConfig.$getDOMSlot instead of direct .getDOM
etrepum Apr 16, 2026
3567fb1
Add doc string to the extension
etrepum Apr 17, 2026
893aed8
Merge remote-tracking branch 'origin/main' into claude/remove-dom-imp…
etrepum Apr 17, 2026
a5d30f2
Merge remote-tracking branch 'origin/main' into claude/remove-dom-imp…
etrepum Apr 17, 2026
00cef6c
pnpm run prettier:fix
etrepum Apr 17, 2026
cd2a608
Fix prettier formatting in agent-example
claude Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions dev-examples/dev-node-state-style/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# DEV Node State Style example

Here we have an example that demonstrates how NodeState can be used with a
DOMRenderExtension to override create and export behavior of any node.

This example currently depends on unreleased features (v0.44+) and will not
work outside of the monorepo.

**Run it locally:** `pnpm i && pnpm run dev`

[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/facebook/lexical/tree/main?file=dev-examples/dev-node-state-style/src/main.tsx&startCommand=pnpm%20run%20start:example%20dev-node-state-style)

[![Open in StackBlitz-PR](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/etrepum/lexical/tree/claude%2fremove-dom-import-code-LGqib?file=dev-examples/dev-node-state-style/src/main.tsx&startCommand=pnpm%20run%20start:example%20dev-node-state-style)
12 changes: 12 additions & 0 deletions dev-examples/dev-node-state-style/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Lexical Node State Example</title>
</head>
<body>
<div id="root"></div>
<script src="/src/main.tsx" type="module"></script>
</body>
</html>
43 changes: 43 additions & 0 deletions dev-examples/dev-node-state-style/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@lexical/dev-node-state-style-example",
"private": true,
"version": "0.43.0",
"type": "module",
"scripts": {
"dev": "vite -c vite.config.monorepo.ts",
"monorepo:dev": "vite -c vite.config.monorepo.ts",
"build": "tsc && vite build -c vite.config.monorepo.ts",
"preview": "vite preview"
},
"dependencies": {
"@ark-ui/react": "^5.36.0",
"@lexical/clipboard": "workspace:*",
"@lexical/extension": "workspace:*",
"@lexical/history": "workspace:*",
"@lexical/html": "workspace:*",
"@lexical/react": "workspace:*",
"@lexical/rich-text": "workspace:*",
"@lexical/selection": "workspace:*",
"@lexical/utils": "workspace:*",
"@zag-js/combobox": "^1.40.0",
"@shikijs/langs": "^4.0.2",
"@shikijs/themes": "^4.0.2",
"inline-style-parser": "^0.2.7",
"lexical": "workspace:*",
"lucide-react": "^1.8.0",
"prettier": "^3.8.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"shiki": "^4.0.2"
},
"devDependencies": {
"@types/node": "^25.6.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"cross-env": "^10.1.0",
"csstype": "^3.2.3",
"typescript": "^6.0.2",
"vite": "^8.0.8"
}
}
82 changes: 82 additions & 0 deletions dev-examples/dev-node-state-style/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {Tabs} from '@ark-ui/react/tabs';
import {AutoFocusExtension} from '@lexical/extension';
import {HistoryExtension} from '@lexical/history';
import {ContentEditable} from '@lexical/react/LexicalContentEditable';
import {LexicalExtensionComposer} from '@lexical/react/LexicalExtensionComposer';
import {RichTextExtension} from '@lexical/rich-text';
import {defineExtension, ParagraphNode, TextNode} from 'lexical';

import ExampleTheme from './ExampleTheme';
import {ShikiViewPlugin} from './plugins/ShikiViewPlugin';
import {StyleViewPlugin} from './plugins/StyleViewPlugin';
import {ToolbarPlugin} from './plugins/ToolbarPlugin';
import {StyleStateExtension} from './styleState';

const placeholder = 'Enter some rich text...';

const editorExtension = defineExtension({
dependencies: [
RichTextExtension,
HistoryExtension,
AutoFocusExtension,
StyleStateExtension,
],
name: '@lexical/examples/node-state-style',
namespace: 'NodeState Demo',
nodes: [ParagraphNode, TextNode],
onError(error: Error) {
throw error;
},
theme: ExampleTheme,
});

export default function App() {
return (
<LexicalExtensionComposer
extension={editorExtension}
contentEditable={null}>
<div className="editor-container">
<ToolbarPlugin />
<div className="editor-inner">
<ContentEditable
className="editor-input"
aria-placeholder={placeholder}
placeholder={
<div className="editor-placeholder">{placeholder}</div>
}
/>
</div>
</div>
<Tabs.Root
lazyMount={true}
unmountOnExit={true}
defaultValue="style"
style={{margin: '0 10px'}}>
<Tabs.List
style={{display: 'flex', justifyContent: 'center', margin: '10px 0'}}>
<Tabs.Trigger value="style">Style Tree</Tabs.Trigger>
<Tabs.Trigger value="html">HTML</Tabs.Trigger>
<Tabs.Trigger value="json">JSON</Tabs.Trigger>
<Tabs.Indicator />
</Tabs.List>
<Tabs.Content value="style">
<StyleViewPlugin />
</Tabs.Content>
<Tabs.Content value="html">
<ShikiViewPlugin lang="html" />
</Tabs.Content>
<Tabs.Content value="json">
<ShikiViewPlugin lang="json" />
</Tabs.Content>
</Tabs.Root>
</LexicalExtensionComposer>
);
}
41 changes: 41 additions & 0 deletions dev-examples/dev-node-state-style/src/ExampleTheme.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

export default {
code: 'editor-code',
heading: {
h1: 'editor-heading-h1',
h2: 'editor-heading-h2',
h3: 'editor-heading-h3',
h4: 'editor-heading-h4',
h5: 'editor-heading-h5',
},
image: 'editor-image',
link: 'editor-link',
list: {
listitem: 'editor-listitem',
nested: {
listitem: 'editor-nested-listitem',
},
ol: 'editor-list-ol',
ul: 'editor-list-ul',
},
paragraph: 'editor-paragraph',
placeholder: 'editor-placeholder',
quote: 'editor-quote',
text: {
bold: 'editor-text-bold',
code: 'editor-text-code',
hashtag: 'editor-text-hashtag',
italic: 'editor-text-italic',
overflowed: 'editor-text-overflowed',
strikethrough: 'editor-text-strikethrough',
underline: 'editor-text-underline',
underlineStrikethrough: 'editor-text-underlineStrikethrough',
},
};
5 changes: 5 additions & 0 deletions dev-examples/dev-node-state-style/src/icons/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Bootstrap Icons
https://icons.getbootstrap.com

Licensed under MIT license
https://github.com/twbs/icons/blob/main/LICENSE.md
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions dev-examples/dev-node-state-style/src/icons/type-bold.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions dev-examples/dev-node-state-style/src/icons/type-italic.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions dev-examples/dev-node-state-style/src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import './styles.css';

import React from 'react';
import ReactDOM from 'react-dom/client';

import App from './App.tsx';

ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<div className="App">
<h1>NodeState Style Example</h1>
<App />
</div>
</React.StrictMode>,
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.shiki-view-plugin > pre.shiki {
margin: 0;
padding: 10px;
white-space: pre-wrap;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import './ShikiViewPlugin.css';

import {$generateHtmlFromNodes} from '@lexical/html';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {EditorState, LexicalEditor} from 'lexical';
import * as prettier from 'prettier';
import {useEffect, useMemo, useState} from 'react';
import {createHighlighterCore} from 'shiki/core';
import {createJavaScriptRegexEngine} from 'shiki/engine/javascript';

const jsEngine = createJavaScriptRegexEngine({target: 'ES2024'});

const shikiPromise = createHighlighterCore({
engine: jsEngine,
langs: [import('@shikijs/langs/html'), import('@shikijs/langs/json')],
themes: [import('@shikijs/themes/nord')],
});
const prettierPlugins = [
import('prettier/plugins/babel'),
import('prettier/plugins/estree'),
import('prettier/plugins/html'),
];

function editorHTML(editor: LexicalEditor, editorState: EditorState): string {
return editorState.read(() => $generateHtmlFromNodes(editor, null), {editor});
}

function editorJSON(_editor: LexicalEditor, editorState: EditorState): string {
return JSON.stringify(editorState.toJSON(), null, 2);
}

const langs = {
html: editorHTML,
json: editorJSON,
} as const;

export interface ShikiViewPluginProps {
lang: keyof typeof langs;
}

export function ShikiViewPlugin({lang}: ShikiViewPluginProps) {
const [editor] = useLexicalComposerContext();
const [editorState, setEditorState] = useState(() => editor.getEditorState());
useEffect(
() =>
editor.registerUpdateListener((payload) =>
setEditorState(payload.editorState),
),
[editor],
);
const rawCode = useMemo(
() => langs[lang](editor, editorState),
[lang, editor, editorState],
);
const htmlPromise = useMemo(
() =>
(async () => {
const prettified = await prettier.format(rawCode, {
parser: lang,
plugins: (await Promise.all(prettierPlugins)).map(
(mod) => mod.default,
),
});
return (await shikiPromise).codeToHtml(prettified, {
lang,
theme: 'nord',
});
})(),
[lang, rawCode],
);
const [html, setHtml] = useState('');
useEffect(() => {
let canceled = false;
htmlPromise.then((formatted) => canceled || setHtml(formatted));
return () => {
canceled = true;
};
}, [htmlPromise]);
return (
<div
className="shiki-view-plugin"
dangerouslySetInnerHTML={{__html: html}}
/>
);
}
Loading