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 + + + + + + + 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; }