diff --git a/.eslintrc.js b/.eslintrc.js
index 314fcffc887..c0cd4c5817e 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -82,7 +82,7 @@ module.exports = {
// @lexical/yjs
'createBinding',
],
- isLexicalProvider: ['updateEditor'],
+ isLexicalProvider: ['updateEditor', 'updateEditorSync'],
isSafeDollarFunction: '$createRootNode',
}),
],
diff --git a/examples/vanilla-js-iframe/README.md b/examples/vanilla-js-iframe/README.md
new file mode 100644
index 00000000000..4069a5b7d5a
--- /dev/null
+++ b/examples/vanilla-js-iframe/README.md
@@ -0,0 +1,8 @@
+# Vanilla JS example in an iframe
+
+Here we have simplest Lexical setup in rich text configuration (`@lexical/rich-text`) with history (`@lexical/history`) and accessibility (`@lexical/dragon`) features enabled using an iframe
+for the contentEditable surface.
+
+**Run it locally:** `npm i && npm run dev`
+
+[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/facebook/lexical/tree/main/examples/vanilla-js-iframe?file=src/main.ts)
diff --git a/examples/vanilla-js-iframe/index.html b/examples/vanilla-js-iframe/index.html
new file mode 100644
index 00000000000..a730616c401
--- /dev/null
+++ b/examples/vanilla-js-iframe/index.html
@@ -0,0 +1,33 @@
+
+
+
+
+
+ Lexical Basic - Vanilla JS iframe
+
+
+
+
+
Lexical Basic - Vanilla JS iframe
+
+
Editor state:
+
+
+
+
+
+
+
diff --git a/examples/vanilla-js-iframe/package-lock.json b/examples/vanilla-js-iframe/package-lock.json
new file mode 100644
index 00000000000..ec39bb13579
--- /dev/null
+++ b/examples/vanilla-js-iframe/package-lock.json
@@ -0,0 +1,960 @@
+{
+ "name": "@lexical/vanilla-js-iframe",
+ "version": "0.23.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "@lexical/vanilla-js-iframe",
+ "version": "0.23.0",
+ "dependencies": {
+ "@lexical/dragon": "0.23.0",
+ "@lexical/history": "0.23.0",
+ "@lexical/rich-text": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.2.11"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@lexical/clipboard": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/clipboard/-/clipboard-0.23.0.tgz",
+ "integrity": "sha512-+MEdOajIXFp/5Q3dS3tj3PD3E6SCzf91E2AkNfN3oeeogDf04WG4e5Gx8NuXSGzpEZ8Rog28QDP6xQ8fCzwaTg==",
+ "dependencies": {
+ "@lexical/html": "0.23.0",
+ "@lexical/list": "0.23.0",
+ "@lexical/selection": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/dragon": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/dragon/-/dragon-0.23.0.tgz",
+ "integrity": "sha512-EIwnH8eZIkYTyb4rY9cPKrzPv7a4t9cip6JBeTsysGB3k2K3nTaWCW4k89kUZ4Jy4olB+d7FDLRjEUMwV7MoDg==",
+ "dependencies": {
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/history": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/history/-/history-0.23.0.tgz",
+ "integrity": "sha512-s76kbrGYw/duLjN3OpPiYtpzl1F9ddbTbFL7KxWG6FHhAXXPF5caY9Ajg+OB6327r2jSxUbZSautd5zbwFxbWA==",
+ "dependencies": {
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/html": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/html/-/html-0.23.0.tgz",
+ "integrity": "sha512-kHCmjATl88CeaeJoWbycHT1XQjwYgscjZSmgSmOahRvCsBee4lJ/h+cuMLVDj9gj21IAnzYd8Gx+EHka/yECgA==",
+ "dependencies": {
+ "@lexical/selection": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/list": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/list/-/list-0.23.0.tgz",
+ "integrity": "sha512-YcvnyqER400XWYtjruIRs1ggMKqQbBupejMx2SHrXRzL/7dByHtmfGL6Bzn/1Y3BRWBYSFHy2LFs+OCFuChEIw==",
+ "dependencies": {
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/rich-text": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/rich-text/-/rich-text-0.23.0.tgz",
+ "integrity": "sha512-X5f+as0dItxo5GGwwExHo7cGgG1erf/02mqhFNbMvOnl+VJVOvy3c+wp2W3JEWRDTaLdqxaw/m4LrfN6m79cEg==",
+ "dependencies": {
+ "@lexical/clipboard": "0.23.0",
+ "@lexical/selection": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/selection": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/selection/-/selection-0.23.0.tgz",
+ "integrity": "sha512-ypyLRkzRiVA8JIlIZu58FepkBxl8ilysigjJefyMEuFUS8/F3d9nujznWi6BhplWmBCd/lNzFjvLvmsvYAK1XQ==",
+ "dependencies": {
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/table": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/table/-/table-0.23.0.tgz",
+ "integrity": "sha512-R8WHuyrefQyrwMkIGuj/2CnQDf5f3yljHABy77URvoBjmVONEM/vqQ9ZLCtDP4fIaxhdf2Fq3Agt6e3tMNs/vQ==",
+ "dependencies": {
+ "@lexical/clipboard": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@lexical/utils": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@lexical/utils/-/utils-0.23.0.tgz",
+ "integrity": "sha512-vhcwR7ymkvXGrnoANxiBR55UlNwR4KcRNTzbbKgtQRdo+ATXbX6/KROVPJ6nkvYah+f6fcqw9Crj7RtzSOYhiQ==",
+ "dependencies": {
+ "@lexical/list": "0.23.0",
+ "@lexical/selection": "0.23.0",
+ "@lexical/table": "0.23.0",
+ "lexical": "0.23.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.30.1.tgz",
+ "integrity": "sha512-pSWY+EVt3rJ9fQ3IqlrEUtXh3cGqGtPDH1FQlNZehO2yYxCHEX1SPsz1M//NXwYfbTlcKr9WObLnJX9FsS9K1Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.30.1.tgz",
+ "integrity": "sha512-/NA2qXxE3D/BRjOJM8wQblmArQq1YoBVJjrjoTSBS09jgUisq7bqxNHJ8kjCHeV21W/9WDGwJEWSN0KQ2mtD/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.30.1.tgz",
+ "integrity": "sha512-r7FQIXD7gB0WJ5mokTUgUWPl0eYIH0wnxqeSAhuIwvnnpjdVB8cRRClyKLQr7lgzjctkbp5KmswWszlwYln03Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.30.1.tgz",
+ "integrity": "sha512-x78BavIwSH6sqfP2xeI1hd1GpHL8J4W2BXcVM/5KYKoAD3nNsfitQhvWSw+TFtQTLZ9OmlF+FEInEHyubut2OA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.30.1.tgz",
+ "integrity": "sha512-HYTlUAjbO1z8ywxsDFWADfTRfTIIy/oUlfIDmlHYmjUP2QRDTzBuWXc9O4CXM+bo9qfiCclmHk1x4ogBjOUpUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.30.1.tgz",
+ "integrity": "sha512-1MEdGqogQLccphhX5myCJqeGNYTNcmTyaic9S7CG3JhwuIByJ7J05vGbZxsizQthP1xpVx7kd3o31eOogfEirw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.30.1.tgz",
+ "integrity": "sha512-PaMRNBSqCx7K3Wc9QZkFx5+CX27WFpAMxJNiYGAXfmMIKC7jstlr32UhTgK6T07OtqR+wYlWm9IxzennjnvdJg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.30.1.tgz",
+ "integrity": "sha512-B8Rcyj9AV7ZlEFqvB5BubG5iO6ANDsRKlhIxySXcF1axXYUyqwBok+XZPgIYGBgs7LDXfWfifxhw0Ik57T0Yug==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.30.1.tgz",
+ "integrity": "sha512-hqVyueGxAj3cBKrAI4aFHLV+h0Lv5VgWZs9CUGqr1z0fZtlADVV1YPOij6AhcK5An33EXaxnDLmJdQikcn5NEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.30.1.tgz",
+ "integrity": "sha512-i4Ab2vnvS1AE1PyOIGp2kXni69gU2DAUVt6FSXeIqUCPIR3ZlheMW3oP2JkukDfu3PsexYRbOiJrY+yVNSk9oA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.30.1.tgz",
+ "integrity": "sha512-fARcF5g296snX0oLGkVxPmysetwUk2zmHcca+e9ObOovBR++9ZPOhqFUM61UUZ2EYpXVPN1redgqVoBB34nTpQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.30.1.tgz",
+ "integrity": "sha512-GLrZraoO3wVT4uFXh67ElpwQY0DIygxdv0BNW9Hkm3X34wu+BkqrDrkcsIapAY+N2ATEbvak0XQ9gxZtCIA5Rw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.30.1.tgz",
+ "integrity": "sha512-0WKLaAUUHKBtll0wvOmh6yh3S0wSU9+yas923JIChfxOaaBarmb/lBKPF0w/+jTVozFnOXJeRGZ8NvOxvk/jcw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.30.1.tgz",
+ "integrity": "sha512-GWFs97Ruxo5Bt+cvVTQkOJ6TIx0xJDD/bMAOXWJg8TCSTEK8RnFeOeiFTxKniTc4vMIaWvCplMAFBt9miGxgkA==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.30.1.tgz",
+ "integrity": "sha512-UtgGb7QGgXDIO+tqqJ5oZRGHsDLO8SlpE4MhqpY9Llpzi5rJMvrK6ZGhsRCST2abZdBqIBeXW6WPD5fGK5SDwg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.30.1.tgz",
+ "integrity": "sha512-V9U8Ey2UqmQsBT+xTOeMzPzwDzyXmnAoO4edZhL7INkwQcaW1Ckv3WJX3qrrp/VHaDkEWIBWhRwP47r8cdrOow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.30.1.tgz",
+ "integrity": "sha512-WabtHWiPaFF47W3PkHnjbmWawnX/aE57K47ZDT1BXTS5GgrBUEpvOzq0FI0V/UYzQJgdb8XlhVNH8/fwV8xDjw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.30.1.tgz",
+ "integrity": "sha512-pxHAU+Zv39hLUTdQQHUVHf4P+0C47y/ZloorHpzs2SXMRqeAWmGghzAhfOlzFHHwjvgokdFAhC4V+6kC1lRRfw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.30.1.tgz",
+ "integrity": "sha512-D6qjsXGcvhTjv0kI4fU8tUuBDF/Ueee4SVX79VfNDXZa64TfCW1Slkb6Z7O1p7vflqZjcmOVdZlqf8gvJxc6og==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/lexical": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/lexical/-/lexical-0.23.0.tgz",
+ "integrity": "sha512-xkRJqPrdcAkUKP9NiJcmOayKpvou9C8H9y2O8fIWM9tW0KAJub1gkuw9q9VexwvqgCZbf2ep2ufFwC1rY7caSw=="
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.8",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+ "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.49",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
+ "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.30.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.30.1.tgz",
+ "integrity": "sha512-mlJ4glW020fPuLi7DkM/lN97mYEZGWeqBnrljzN0gs7GLctqX3lNWxKQ7Gl712UAX+6fog/L3jh4gb7R6aVi3w==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.6"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.30.1",
+ "@rollup/rollup-android-arm64": "4.30.1",
+ "@rollup/rollup-darwin-arm64": "4.30.1",
+ "@rollup/rollup-darwin-x64": "4.30.1",
+ "@rollup/rollup-freebsd-arm64": "4.30.1",
+ "@rollup/rollup-freebsd-x64": "4.30.1",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.30.1",
+ "@rollup/rollup-linux-arm-musleabihf": "4.30.1",
+ "@rollup/rollup-linux-arm64-gnu": "4.30.1",
+ "@rollup/rollup-linux-arm64-musl": "4.30.1",
+ "@rollup/rollup-linux-loongarch64-gnu": "4.30.1",
+ "@rollup/rollup-linux-powerpc64le-gnu": "4.30.1",
+ "@rollup/rollup-linux-riscv64-gnu": "4.30.1",
+ "@rollup/rollup-linux-s390x-gnu": "4.30.1",
+ "@rollup/rollup-linux-x64-gnu": "4.30.1",
+ "@rollup/rollup-linux-x64-musl": "4.30.1",
+ "@rollup/rollup-win32-arm64-msvc": "4.30.1",
+ "@rollup/rollup-win32-ia32-msvc": "4.30.1",
+ "@rollup/rollup-win32-x64-msvc": "4.30.1",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.7.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz",
+ "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
+ "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/examples/vanilla-js-iframe/package.json b/examples/vanilla-js-iframe/package.json
new file mode 100644
index 00000000000..9c2a8775708
--- /dev/null
+++ b/examples/vanilla-js-iframe/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@lexical/vanilla-js-iframe",
+ "private": true,
+ "version": "0.23.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "monorepo:dev": "vite -c vite.monorepo.config.ts",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@lexical/dragon": "0.23.0",
+ "@lexical/history": "0.23.0",
+ "@lexical/rich-text": "0.23.0",
+ "@lexical/utils": "0.23.0",
+ "lexical": "0.23.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.2.2",
+ "vite": "^5.2.11"
+ }
+}
diff --git a/examples/vanilla-js-iframe/src/main.ts b/examples/vanilla-js-iframe/src/main.ts
new file mode 100644
index 00000000000..089ffc6178c
--- /dev/null
+++ b/examples/vanilla-js-iframe/src/main.ts
@@ -0,0 +1,52 @@
+/**
+ * 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 {registerDragonSupport} from '@lexical/dragon';
+import {createEmptyHistoryState, registerHistory} from '@lexical/history';
+import {HeadingNode, QuoteNode, registerRichText} from '@lexical/rich-text';
+import {mergeRegister} from '@lexical/utils';
+import {createEditor} from 'lexical';
+
+import prepopulatedRichText from './prepopulatedRichText';
+
+const template = document.querySelector('#app-template')!;
+const iframe = document.querySelector('#app-iframe')!;
+const iframeDoc = iframe.contentDocument!;
+iframeDoc.body.replaceChildren(iframeDoc.importNode(template.content, true));
+const editorRef = iframeDoc.querySelector('#lexical-editor')!;
+const stateRef =
+ iframeDoc.querySelector('#lexical-state')!;
+
+const initialConfig = {
+ namespace: 'Vanilla JS Demo',
+ // Register nodes specific for @lexical/rich-text
+ nodes: [HeadingNode, QuoteNode],
+ onError: (error: Error) => {
+ throw error;
+ },
+ theme: {
+ // Adding styling to Quote node, see styles.css
+ quote: 'PlaygroundEditorTheme__quote',
+ },
+};
+const editor = createEditor(initialConfig);
+editor.setRootElement(editorRef);
+
+// Registring Plugins
+mergeRegister(
+ registerRichText(editor),
+ registerDragonSupport(editor),
+ registerHistory(editor, createEmptyHistoryState(), 300),
+);
+
+editor.update(prepopulatedRichText, {tag: 'history-merge'});
+
+editor.registerUpdateListener(({editorState}) => {
+ stateRef!.value = JSON.stringify(editorState.toJSON(), undefined, 2);
+});
diff --git a/examples/vanilla-js-iframe/src/prepopulatedRichText.ts b/examples/vanilla-js-iframe/src/prepopulatedRichText.ts
new file mode 100644
index 00000000000..b0ceaf4a1f5
--- /dev/null
+++ b/examples/vanilla-js-iframe/src/prepopulatedRichText.ts
@@ -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.
+ *
+ */
+import {$createHeadingNode, $createQuoteNode} from '@lexical/rich-text';
+import {$createParagraphNode, $createTextNode, $getRoot} from 'lexical';
+
+export default function $prepopulatedRichText() {
+ const root = $getRoot();
+ if (root.getFirstChild() !== null) {
+ return;
+ }
+
+ const heading = $createHeadingNode('h1');
+ heading.append(
+ $createTextNode('Welcome to the IFrame Vanilla JS Lexical Demo!'),
+ );
+ root.append(heading);
+ const quote = $createQuoteNode();
+ quote.append(
+ $createTextNode(
+ `In case you were wondering what the text area at the bottom is – it's the debug view, showing the current state of the editor. `,
+ ),
+ );
+ root.append(quote);
+ const paragraph = $createParagraphNode();
+ paragraph.append(
+ $createTextNode('This is a demo environment built with '),
+ $createTextNode('lexical').toggleFormat('code'),
+ $createTextNode('.'),
+ $createTextNode(' Try typing in '),
+ $createTextNode('some text').toggleFormat('bold'),
+ $createTextNode(' with '),
+ $createTextNode('different').toggleFormat('italic'),
+ $createTextNode(' formats.'),
+ );
+ root.append(paragraph);
+}
diff --git a/examples/vanilla-js-iframe/src/styles.css b/examples/vanilla-js-iframe/src/styles.css
new file mode 100644
index 00000000000..73636e09051
--- /dev/null
+++ b/examples/vanilla-js-iframe/src/styles.css
@@ -0,0 +1,27 @@
+/**
+ * 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.
+ *
+ */
+
+.editor-wrapper {
+ border: 2px solid gray;
+}
+#lexical-state {
+ width: 100%;
+ height: 300px;
+}
+
+.PlaygroundEditorTheme__quote {
+ margin: 0;
+ margin-left: 20px;
+ margin-bottom: 10px;
+ font-size: 15px;
+ color: rgb(101, 103, 107);
+ border-left-color: rgb(206, 208, 212);
+ border-left-width: 4px;
+ border-left-style: solid;
+ padding-left: 16px;
+}
diff --git a/examples/vanilla-js-iframe/src/vite-env.d.ts b/examples/vanilla-js-iframe/src/vite-env.d.ts
new file mode 100644
index 00000000000..11f02fe2a00
--- /dev/null
+++ b/examples/vanilla-js-iframe/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/examples/vanilla-js-iframe/tsconfig.json b/examples/vanilla-js-iframe/tsconfig.json
new file mode 100644
index 00000000000..75abdef2659
--- /dev/null
+++ b/examples/vanilla-js-iframe/tsconfig.json
@@ -0,0 +1,23 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "module": "ESNext",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"]
+}
diff --git a/examples/vanilla-js-iframe/vite.monorepo.config.ts b/examples/vanilla-js-iframe/vite.monorepo.config.ts
new file mode 100644
index 00000000000..c6dbd2b59ac
--- /dev/null
+++ b/examples/vanilla-js-iframe/vite.monorepo.config.ts
@@ -0,0 +1,80 @@
+/**
+ * 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 babel from '@rollup/plugin-babel';
+import {createRequire} from 'node:module';
+import {defineConfig} from 'vite';
+import {replaceCodePlugin} from 'vite-plugin-replace';
+
+import moduleResolution from '../../packages/shared/viteModuleResolution';
+
+const require = createRequire(import.meta.url);
+
+// https://vitejs.dev/config/
+export default defineConfig(({command}) => {
+ return {
+ build: {
+ outDir: 'build',
+ rollupOptions: {
+ input: {
+ main: new URL('./index.html', import.meta.url).pathname,
+ split: new URL('./split/index.html', import.meta.url).pathname,
+ },
+ onwarn(warning, warn) {
+ if (
+ warning.code === 'EVAL' &&
+ warning.id &&
+ /[\\/]node_modules[\\/]@excalidraw\/excalidraw[\\/]/.test(
+ warning.id,
+ )
+ ) {
+ return;
+ }
+ warn(warning);
+ },
+ },
+ },
+ define: {
+ 'process.env.IS_PREACT': process.env.IS_PREACT,
+ },
+ plugins: [
+ replaceCodePlugin({
+ replacements: [
+ {
+ from: /__DEV__/g,
+ to: 'true',
+ },
+ {
+ from: 'process.env.LEXICAL_VERSION',
+ to: JSON.stringify(`${process.env.npm_package_version}+git`),
+ },
+ ],
+ }),
+ babel({
+ babelHelpers: 'bundled',
+ babelrc: false,
+ configFile: false,
+ exclude: '/**/node_modules/**',
+ extensions: ['jsx', 'js', 'ts', 'tsx', 'mjs'],
+ plugins: [
+ '@babel/plugin-transform-flow-strip-types',
+ [
+ require('../../scripts/error-codes/transform-error-messages'),
+ {
+ noMinify: true,
+ },
+ ],
+ ],
+ presets: [['@babel/preset-react', {runtime: 'automatic'}]],
+ }),
+ ],
+ resolve: {
+ alias: moduleResolution(command === 'serve' ? 'source' : 'development'),
+ },
+ };
+});
diff --git a/packages/lexical-clipboard/src/clipboard.ts b/packages/lexical-clipboard/src/clipboard.ts
index 9f9155be596..e33f1d9b27b 100644
--- a/packages/lexical-clipboard/src/clipboard.ts
+++ b/packages/lexical-clipboard/src/clipboard.ts
@@ -420,9 +420,9 @@ export async function copyToClipboard(
}
const rootElement = editor.getRootElement();
- const windowDocument =
- editor._window == null ? window.document : editor._window.document;
- const domSelection = getDOMSelection(editor._window);
+ const editorWindow = editor._window || window;
+ const windowDocument = window.document;
+ const domSelection = getDOMSelection(editorWindow);
if (rootElement === null || domSelection === null) {
return false;
}
diff --git a/packages/lexical/src/LexicalEditor.ts b/packages/lexical/src/LexicalEditor.ts
index e7e4f2048d3..5b6845b4566 100644
--- a/packages/lexical/src/LexicalEditor.ts
+++ b/packages/lexical/src/LexicalEditor.ts
@@ -21,7 +21,7 @@ import {$getRoot, $getSelection, TextNode} from '.';
import {FULL_RECONCILE, NO_DIRTY_NODES} from './LexicalConstants';
import {cloneEditorState, createEmptyEditorState} from './LexicalEditorState';
import {addRootElementEvents, removeRootElementEvents} from './LexicalEvents';
-import {$flushRootMutations, initMutationObserver} from './LexicalMutations';
+import {flushRootMutations, initMutationObserver} from './LexicalMutations';
import {LexicalNode} from './LexicalNode';
import {
$commitPendingUpdates,
@@ -88,6 +88,8 @@ export type EditorUpdateOptions = {
skipTransforms?: true;
tag?: string | Array;
discrete?: true;
+ /** @internal */
+ event?: undefined | UIEvent | Event | null;
};
export type EditorSetOptions = {
@@ -1154,7 +1156,7 @@ export class LexicalEditor {
: null;
}
- $flushRootMutations(this);
+ flushRootMutations(this);
const pendingEditorState = this._pendingEditorState;
const tags = this._updateTags;
const tag = options !== undefined ? options.tag : null;
diff --git a/packages/lexical/src/LexicalEvents.ts b/packages/lexical/src/LexicalEvents.ts
index 0ccd47e2910..4ac619adbeb 100644
--- a/packages/lexical/src/LexicalEvents.ts
+++ b/packages/lexical/src/LexicalEvents.ts
@@ -75,7 +75,7 @@ import {
$internalCreateRangeSelection,
RangeSelection,
} from './LexicalSelection';
-import {getActiveEditor, updateEditor} from './LexicalUpdates';
+import {getActiveEditor, updateEditorSync} from './LexicalUpdates';
import {
$flushMutations,
$getNodeByKey,
@@ -201,7 +201,7 @@ function $shouldPreventDefaultAndInsertText(
const focus = selection.focus;
const anchorNode = anchor.getNode();
const editor = getActiveEditor();
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
const domAnchorNode = domSelection !== null ? domSelection.anchorNode : null;
const anchorKey = anchor.key;
const backingAnchorElement = editor.getElementByKey(anchorKey);
@@ -290,7 +290,7 @@ function onSelectionChange(
return;
}
}
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
// Non-active editor don't need any extra logic for selection, it only needs update
// to reconcile selection (set it to null) to ensure that only one editor has non-null selection.
if (!isActive) {
@@ -417,9 +417,9 @@ function onSelectionChange(
// also help other browsers when selection might "appear" lost, when it
// really isn't.
function onClick(event: PointerEvent, editor: LexicalEditor): void {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
const selection = $getSelection();
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
const lastSelection = $getPreviousSelection();
if (domSelection) {
@@ -483,7 +483,7 @@ function onPointerDown(event: PointerEvent, editor: LexicalEditor) {
const target = event.target;
const pointerType = event.pointerType;
if (isDOMNode(target) && pointerType !== 'touch' && event.button === 0) {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
// Drag & drop should not recompute selection until mouse up; otherwise the initially
// selected content is lost.
if (!$isSelectionCapturedInDecorator(target)) {
@@ -543,7 +543,7 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {
return;
}
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
const selection = $getSelection();
if (inputType === 'deleteContentBackward') {
@@ -571,7 +571,7 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {
lastKeyDownTimeStamp = 0;
// Fixes an Android bug where selection flickers when backspacing
setTimeout(() => {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
$setCompositionKey(null);
});
}, ANDROID_COMPOSITION_LATENCY);
@@ -807,93 +807,103 @@ function onBeforeInput(event: InputEvent, editor: LexicalEditor): void {
}
function onInput(event: InputEvent, editor: LexicalEditor): void {
+ // Note that the MutationObserver may or may not have already fired,
+ // but the the DOM and selection may have already changed.
+ // See also:
+ // - https://github.com/facebook/lexical/issues/7028
+ // - https://github.com/facebook/lexical/pull/794
+
// We don't want the onInput to bubble, in the case of nested editors.
event.stopPropagation();
- updateEditor(editor, () => {
- const selection = $getSelection();
- const data = event.data;
- const targetRange = getTargetRange(event);
+ updateEditorSync(
+ editor,
+ () => {
+ const selection = $getSelection();
+ const data = event.data;
+ const targetRange = getTargetRange(event);
- if (
- data != null &&
- $isRangeSelection(selection) &&
- $shouldPreventDefaultAndInsertText(
- selection,
- targetRange,
- data,
- event.timeStamp,
- false,
- )
- ) {
- // Given we're over-riding the default behavior, we will need
- // to ensure to disable composition before dispatching the
- // insertText command for when changing the sequence for FF.
- if (isFirefoxEndingComposition) {
- $onCompositionEndImpl(editor, data);
- isFirefoxEndingComposition = false;
- }
- const anchor = selection.anchor;
- const anchorNode = anchor.getNode();
- const domSelection = getDOMSelection(editor._window);
- if (domSelection === null) {
- return;
- }
- const isBackward = selection.isBackward();
- const startOffset = isBackward
- ? selection.anchor.offset
- : selection.focus.offset;
- const endOffset = isBackward
- ? selection.focus.offset
- : selection.anchor.offset;
- // If the content is the same as inserted, then don't dispatch an insertion.
- // Given onInput doesn't take the current selection (it uses the previous)
- // we can compare that against what the DOM currently says.
if (
- !CAN_USE_BEFORE_INPUT ||
- selection.isCollapsed() ||
- !$isTextNode(anchorNode) ||
- domSelection.anchorNode === null ||
- anchorNode.getTextContent().slice(0, startOffset) +
- data +
- anchorNode.getTextContent().slice(startOffset + endOffset) !==
- getAnchorTextFromDOM(domSelection.anchorNode)
+ data != null &&
+ $isRangeSelection(selection) &&
+ $shouldPreventDefaultAndInsertText(
+ selection,
+ targetRange,
+ data,
+ event.timeStamp,
+ false,
+ )
) {
- dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
- }
+ // Given we're over-riding the default behavior, we will need
+ // to ensure to disable composition before dispatching the
+ // insertText command for when changing the sequence for FF.
+ if (isFirefoxEndingComposition) {
+ $onCompositionEndImpl(editor, data);
+ isFirefoxEndingComposition = false;
+ }
+ const anchor = selection.anchor;
+ const anchorNode = anchor.getNode();
+ const domSelection = getDOMSelection(getWindow(editor));
+ if (domSelection === null) {
+ return;
+ }
+ const isBackward = selection.isBackward();
+ const startOffset = isBackward
+ ? selection.anchor.offset
+ : selection.focus.offset;
+ const endOffset = isBackward
+ ? selection.focus.offset
+ : selection.anchor.offset;
+ // If the content is the same as inserted, then don't dispatch an insertion.
+ // Given onInput doesn't take the current selection (it uses the previous)
+ // we can compare that against what the DOM currently says.
+ if (
+ !CAN_USE_BEFORE_INPUT ||
+ selection.isCollapsed() ||
+ !$isTextNode(anchorNode) ||
+ domSelection.anchorNode === null ||
+ anchorNode.getTextContent().slice(0, startOffset) +
+ data +
+ anchorNode.getTextContent().slice(startOffset + endOffset) !==
+ getAnchorTextFromDOM(domSelection.anchorNode)
+ ) {
+ dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);
+ }
- const textLength = data.length;
+ const textLength = data.length;
- // Another hack for FF, as it's possible that the IME is still
- // open, even though compositionend has already fired (sigh).
- if (
- IS_FIREFOX &&
- textLength > 1 &&
- event.inputType === 'insertCompositionText' &&
- !editor.isComposing()
- ) {
- selection.anchor.offset -= textLength;
- }
+ // Another hack for FF, as it's possible that the IME is still
+ // open, even though compositionend has already fired (sigh).
+ if (
+ IS_FIREFOX &&
+ textLength > 1 &&
+ event.inputType === 'insertCompositionText' &&
+ !editor.isComposing()
+ ) {
+ selection.anchor.offset -= textLength;
+ }
- // This ensures consistency on Android.
- if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
- lastKeyDownTimeStamp = 0;
- $setCompositionKey(null);
- }
- } else {
- const characterData = data !== null ? data : undefined;
- $updateSelectedTextFromDOM(false, editor, characterData);
+ // This ensures consistency on Android.
+ if (!IS_SAFARI && !IS_IOS && !IS_APPLE_WEBKIT && editor.isComposing()) {
+ lastKeyDownTimeStamp = 0;
+ $setCompositionKey(null);
+ }
+ } else {
+ const characterData = data !== null ? data : undefined;
+ $updateSelectedTextFromDOM(false, editor, characterData);
- // onInput always fires after onCompositionEnd for FF.
- if (isFirefoxEndingComposition) {
- $onCompositionEndImpl(editor, data || undefined);
- isFirefoxEndingComposition = false;
+ // onInput always fires after onCompositionEnd for FF.
+ if (isFirefoxEndingComposition) {
+ $onCompositionEndImpl(editor, data || undefined);
+ isFirefoxEndingComposition = false;
+ }
}
- }
- // Also flush any other mutations that might have occurred
- // since the change.
- $flushMutations();
- });
+ // Also flush any other mutations that might have occurred
+ // since the change.
+ $flushMutations();
+ },
+ {event},
+ );
unprocessedBeforeInputData = null;
}
@@ -901,7 +911,7 @@ function onCompositionStart(
event: CompositionEvent,
editor: LexicalEditor,
): void {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
const selection = $getSelection();
if ($isRangeSelection(selection) && !editor.isComposing()) {
@@ -995,7 +1005,7 @@ function onCompositionEnd(
if (IS_FIREFOX) {
isFirefoxEndingComposition = true;
} else {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
$onCompositionEndImpl(editor, event.data);
});
}
@@ -1144,7 +1154,7 @@ function onDocumentSelectionChange(event: Event): void {
if (isSelectionChangeFromMouseDown) {
isSelectionChangeFromMouseDown = false;
- updateEditor(nextActiveEditor, () => {
+ updateEditorSync(nextActiveEditor, () => {
const lastSelection = $getPreviousSelection();
const domAnchorNode = domSelection.anchorNode;
if (isHTMLElement(domAnchorNode) || isDOMTextNode(domAnchorNode)) {
diff --git a/packages/lexical/src/LexicalMutations.ts b/packages/lexical/src/LexicalMutations.ts
index c14c301edcd..fd02befe218 100644
--- a/packages/lexical/src/LexicalMutations.ts
+++ b/packages/lexical/src/LexicalMutations.ts
@@ -21,7 +21,7 @@ import {
$isTextNode,
$setSelection,
} from '.';
-import {updateEditor} from './LexicalUpdates';
+import {updateEditorSync} from './LexicalUpdates';
import {
$getNodeByKey,
$getNodeFromDOMNode,
@@ -83,7 +83,7 @@ function $handleTextMutation(
node: TextNode,
editor: LexicalEditor,
): void {
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
let anchorOffset = null;
let focusOffset = null;
@@ -141,7 +141,7 @@ function $getNearestManagedNodePairFromDOMNode(
}
}
-export function $flushMutations(
+function flushMutations(
editor: LexicalEditor,
mutations: Array,
observer: MutationObserver,
@@ -149,9 +149,8 @@ export function $flushMutations(
isProcessingMutations = true;
const shouldFlushTextMutations =
performance.now() - lastTextEntryTimeStamp > TEXT_MUTATION_VARIANCE;
-
try {
- updateEditor(editor, () => {
+ updateEditorSync(editor, () => {
const selection = $getSelection() || getLastSelection(editor);
const badDOMTargets = new Map();
const rootElement = editor.getRootElement();
@@ -303,12 +302,12 @@ export function $flushMutations(
}
}
-export function $flushRootMutations(editor: LexicalEditor): void {
+export function flushRootMutations(editor: LexicalEditor): void {
const observer = editor._observer;
if (observer !== null) {
const mutations = observer.takeRecords();
- $flushMutations(editor, mutations, observer);
+ flushMutations(editor, mutations, observer);
}
}
@@ -316,7 +315,7 @@ export function initMutationObserver(editor: LexicalEditor): void {
initTextEntryListener(editor);
editor._observer = new MutationObserver(
(mutations: Array, observer: MutationObserver) => {
- $flushMutations(editor, mutations, observer);
+ flushMutations(editor, mutations, observer);
},
);
}
diff --git a/packages/lexical/src/LexicalSelection.ts b/packages/lexical/src/LexicalSelection.ts
index 85f5996737a..370ec2b3313 100644
--- a/packages/lexical/src/LexicalSelection.ts
+++ b/packages/lexical/src/LexicalSelection.ts
@@ -55,6 +55,7 @@ import {
getDOMTextNode,
getElementByKeyOrThrow,
getTextNodeOffset,
+ getWindow,
INTERNAL_$isBlock,
isHTMLElement,
isSelectionCapturedInDecoratorInput,
@@ -1632,7 +1633,7 @@ export class RangeSelection implements BaseSelection {
}
}
const editor = getActiveEditor();
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
if (!domSelection) {
return;
@@ -2340,9 +2341,6 @@ function $internalResolveSelectionPoints(
if (resolvedAnchorPoint === null) {
return null;
}
- if (__DEV__) {
- $validatePoint(editor, 'anchor', resolvedAnchorPoint);
- }
const resolvedFocusPoint = $internalResolveSelectionPoint(
focusDOM,
focusOffset,
@@ -2353,6 +2351,7 @@ function $internalResolveSelectionPoints(
return null;
}
if (__DEV__) {
+ $validatePoint(editor, 'anchor', resolvedAnchorPoint);
$validatePoint(editor, 'focus', resolvedAnchorPoint);
}
if (
@@ -2421,17 +2420,18 @@ export function $createNodeSelection(): NodeSelection {
export function $internalCreateSelection(
editor: LexicalEditor,
+ event: UIEvent | Event | null,
): null | BaseSelection {
const currentEditorState = editor.getEditorState();
const lastSelection = currentEditorState._selection;
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
if ($isRangeSelection(lastSelection) || lastSelection == null) {
return $internalCreateRangeSelection(
lastSelection,
domSelection,
editor,
- null,
+ event,
);
}
return lastSelection.clone();
diff --git a/packages/lexical/src/LexicalUpdates.ts b/packages/lexical/src/LexicalUpdates.ts
index 7f459312772..542b2cf20bb 100644
--- a/packages/lexical/src/LexicalUpdates.ts
+++ b/packages/lexical/src/LexicalUpdates.ts
@@ -51,6 +51,7 @@ import {
getEditorStateTextContent,
getEditorsToPropagate,
getRegisteredNodeOrThrow,
+ getWindow,
isLexicalEditor,
removeDOMBlockCursorElement,
scheduleMicroTask,
@@ -599,7 +600,9 @@ export function $commitPendingUpdates(
// Reconciliation has finished. Now update selection and trigger listeners.
// ======
- const domSelection = shouldSkipDOM ? null : getDOMSelection(editor._window);
+ const domSelection = shouldSkipDOM
+ ? null
+ : getDOMSelection(getWindow(editor));
// Attempt to update the DOM selection, including focusing of the root element,
// and scroll into view if needed.
@@ -633,10 +636,10 @@ export function $commitPendingUpdates(
);
}
updateDOMBlockCursorElement(editor, rootElement, pendingSelection);
+ } finally {
if (observer !== null) {
observer.observe(rootElement, observerOptions);
}
- } finally {
activeEditor = previousActiveEditor;
activeEditorState = previousActiveEditorState;
}
@@ -909,15 +912,19 @@ function $beginUpdate(
isReadOnlyMode = false;
editor._updating = true;
activeEditor = editor;
+ const headless = editor._headless || editor.getRootElement() === null;
try {
if (editorStateWasCloned) {
- if (editor._headless) {
+ if (headless) {
if (currentEditorState._selection !== null) {
pendingEditorState._selection = currentEditorState._selection.clone();
}
} else {
- pendingEditorState._selection = $internalCreateSelection(editor);
+ pendingEditorState._selection = $internalCreateSelection(
+ editor,
+ (options && options.event) || null,
+ );
}
}
@@ -1030,11 +1037,14 @@ function $beginUpdate(
export function updateEditorSync(
editor: LexicalEditor,
updateFn: () => void,
+ options?: EditorUpdateOptions,
): void {
- if (editor._updating === false || activeEditor !== editor) {
- updateEditor(editor, updateFn);
- } else {
+ if (!editor._updating) {
+ $beginUpdate(editor, updateFn, options);
+ } else if (activeEditor === editor) {
updateFn();
+ } else {
+ editor._updates.push([updateFn, options]);
}
}
diff --git a/packages/lexical/src/LexicalUtils.ts b/packages/lexical/src/LexicalUtils.ts
index f1b2b229d55..695d45d05c2 100644
--- a/packages/lexical/src/LexicalUtils.ts
+++ b/packages/lexical/src/LexicalUtils.ts
@@ -65,7 +65,7 @@ import {
TEXT_TYPE_TO_FORMAT,
} from './LexicalConstants';
import {LexicalEditor} from './LexicalEditor';
-import {$flushRootMutations} from './LexicalMutations';
+import {flushRootMutations} from './LexicalMutations';
import {$normalizeSelection} from './LexicalNormalization';
import {
errorOnInfiniteTransforms,
@@ -590,7 +590,7 @@ export function $setSelection(selection: null | BaseSelection): void {
export function $flushMutations(): void {
errorOnReadOnly();
const editor = getActiveEditor();
- $flushRootMutations(editor);
+ flushRootMutations(editor);
}
export function $getNodeFromDOM(dom: Node): null | LexicalNode {
@@ -662,7 +662,7 @@ export function $updateSelectedTextFromDOM(
data?: string,
): void {
// Update the text content with the latest composition text
- const domSelection = getDOMSelection(editor._window);
+ const domSelection = getDOMSelection(getWindow(editor));
if (domSelection === null) {
return;
}