diff --git a/README.md b/README.md index 6bc6a16..535906e 100644 --- a/README.md +++ b/README.md @@ -343,6 +343,17 @@ mobiledoc-specific props: - `name`: The name of this card. - `onTeardown`: A callback that can be called when the rendered content is torn down. +### React 18 Support + + To use custom card & atom components in React 18 without warnings, you can pass a specific ReactDOM version as a prop on the Container: + + ```js + import ReactDOM from 'react-dom/client'; + + ...; + ``` + + Notice the React 18 specific import path. Internally, components will render with the new `createRoot` API if available and fallback to the legacy `render` method. ## Development diff --git a/demo/index.js b/demo/index.js index a771fba..2c818c3 100644 --- a/demo/index.js +++ b/demo/index.js @@ -1,5 +1,5 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import ReactDOM from 'react-dom/client'; import '../node_modules/mobiledoc-kit/dist/mobiledoc.css'; import * as ReactMobiledoc from 'react-mobiledoc-editor'; @@ -46,6 +46,7 @@ const App = () => { {...config} mobiledoc={state} onChange={setState} + ReactDOM={ReactDOM} > @@ -57,4 +58,5 @@ const App = () => { ); }; -ReactDOM.render(, document.getElementById('root')); +const root = ReactDOM.createRoot(document.getElementById('root')); +root.render(); diff --git a/karma.conf.js b/karma.conf.js index b5e7a3a..9f295c6 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -25,6 +25,12 @@ module.exports = function (config) { resolve: { alias: { 'react-mobiledoc-editor': __dirname, + ...(process.env.REACT_17 && { + react: 'react-17', + 'react-dom': 'react-dom-17', + '@cfaester/enzyme-adapter-react-18': + '@wojtekmaj/enzyme-adapter-react-17', + }), }, }, module: { diff --git a/package-lock.json b/package-lock.json index 854d9eb..1251948 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "react-mobiledoc-editor", - "version": "0.13.1", + "version": "0.14.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "react-mobiledoc-editor", - "version": "0.13.1", + "version": "0.14.0", "license": "BSD-3-Clause", "dependencies": { - "prop-types": "^15.7.2" + "prop-types": "^15.8.1" }, "devDependencies": { "@babel/core": "^7.18.10", @@ -19,6 +19,7 @@ "@babel/plugin-transform-runtime": "^7.18.10", "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.18.6", + "@cfaester/enzyme-adapter-react-18": "^0.5.1", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-commonjs": "^22.0.1", "@rollup/plugin-node-resolve": "^13.3.0", @@ -48,8 +49,10 @@ "mobiledoc-kit": "^0.14.0", "mocha": "^10.0.0", "prettier": "^2.7.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.2.0", + "react-17": "npm:react@^17", + "react-dom": "^18.2.0", + "react-dom-17": "npm:react-dom@^17", "rimraf": "^3.0.2", "rollup": "^2.77.2", "rollup-plugin-auto-external": "^2.0.0", @@ -63,8 +66,8 @@ }, "peerDependencies": { "mobiledoc-kit": "^0.12.2 || ^0.13.0 || ^0.14.0", - "react": "^16.3.0 || ^17.0.0", - "react-dom": "^16.3.0 || ^17.0.0" + "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" } }, "node_modules/@ampproject/remapping": { @@ -1865,6 +1868,28 @@ "node": ">=6.9.0" } }, + "node_modules/@cfaester/enzyme-adapter-react-18": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.5.1.tgz", + "integrity": "sha512-re27mknhfQsNoBjgn+kD9egD9hCIfb3i6ygzjFcSM2fBKPZFtzBKvtWkGAnzJ7W2fNR8LrCoYtrZtPZc+aUtvg==", + "dev": true, + "dependencies": { + "enzyme-shallow-equal": "^1.0.0", + "react-is": "^18.0.0", + "react-test-renderer": "^18.0.0" + }, + "peerDependencies": { + "enzyme": "^3.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@cfaester/enzyme-adapter-react-18/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2610,18 +2635,18 @@ "dev": true }, "node_modules/@wojtekmaj/enzyme-adapter-react-17/node_modules/react-test-renderer": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz", - "integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", "dev": true, "dependencies": { "object-assign": "^4.1.1", - "react-is": "^17.0.1", + "react-is": "^17.0.2", "react-shallow-renderer": "^16.13.1", - "scheduler": "^0.20.1" + "scheduler": "^0.20.2" }, "peerDependencies": { - "react": "17.0.1" + "react": "17.0.2" } }, "node_modules/@wojtekmaj/enzyme-adapter-utils": { @@ -4813,17 +4838,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react/node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/eslint-plugin-react/node_modules/resolve": { "version": "2.0.0-next.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", @@ -8307,13 +8321,13 @@ "dev": true }, "node_modules/prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "node_modules/proxy-addr": { @@ -8508,9 +8522,22 @@ } }, "node_modules/react": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", - "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-17": { + "name": "react", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "dev": true, "dependencies": { "loose-envify": "^1.1.0", @@ -8521,17 +8548,40 @@ } }, "node_modules/react-dom": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", - "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-dom-17": { + "name": "react-dom", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "scheduler": "^0.20.1" + "scheduler": "^0.20.2" }, "peerDependencies": { - "react": "17.0.1" + "react": "17.0.2" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" } }, "node_modules/react-is": { @@ -8540,16 +8590,45 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/react-shallow-renderer": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", - "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", "dev": true, "dependencies": { "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-test-renderer": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "dev": true, + "dependencies": { + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" }, "peerDependencies": { - "react": "^16.0.0 || ^17.0.0" + "react": "^18.2.0" + } + }, + "node_modules/react-test-renderer/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/react-test-renderer/node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "dependencies": { + "loose-envify": "^1.1.0" } }, "node_modules/read-pkg": { @@ -8950,9 +9029,9 @@ "dev": true }, "node_modules/scheduler": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", - "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "dev": true, "dependencies": { "loose-envify": "^1.1.0", @@ -11958,6 +12037,25 @@ "to-fast-properties": "^2.0.0" } }, + "@cfaester/enzyme-adapter-react-18": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.5.1.tgz", + "integrity": "sha512-re27mknhfQsNoBjgn+kD9egD9hCIfb3i6ygzjFcSM2fBKPZFtzBKvtWkGAnzJ7W2fNR8LrCoYtrZtPZc+aUtvg==", + "dev": true, + "requires": { + "enzyme-shallow-equal": "^1.0.0", + "react-is": "^18.0.0", + "react-test-renderer": "^18.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + } + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -12610,15 +12708,15 @@ "dev": true }, "react-test-renderer": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.1.tgz", - "integrity": "sha512-/dRae3mj6aObwkjCcxZPlxDFh73XZLgvwhhyON2haZGUEhiaY5EjfAdw+d/rQmlcFwdTpMXCSGVk374QbCTlrA==", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-17.0.2.tgz", + "integrity": "sha512-yaQ9cB89c17PUb0x6UfWRs7kQCorVdHlutU1boVPEsB8IDZH6n9tHxMacc3y0JoXOJUsZb/t/Mb8FUWMKaM7iQ==", "dev": true, "requires": { "object-assign": "^4.1.1", - "react-is": "^17.0.1", + "react-is": "^17.0.2", "react-shallow-renderer": "^16.13.1", - "scheduler": "^0.20.1" + "scheduler": "^0.20.2" } } } @@ -14446,17 +14544,6 @@ "es-abstract": "^1.19.1" } }, - "prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, - "requires": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "resolve": { "version": "2.0.0-next.3", "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", @@ -16882,13 +16969,13 @@ "dev": true }, "prop-types": { - "version": "15.7.2", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", - "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", - "react-is": "^16.8.1" + "react-is": "^16.13.1" } }, "proxy-addr": { @@ -17034,9 +17121,18 @@ } }, "react": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react/-/react-17.0.1.tgz", - "integrity": "sha512-lG9c9UuMHdcAexXtigOZLX8exLWkW0Ku29qPRU8uhF2R9BN96dLCt0psvzPLlHc5OWkgymP3qwTRgbnw5BKx3w==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, + "react-17": { + "version": "npm:react@17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "dev": true, "requires": { "loose-envify": "^1.1.0", @@ -17044,14 +17140,35 @@ } }, "react-dom": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.1.tgz", - "integrity": "sha512-6eV150oJZ9U2t9svnsspTMrWNyHc6chX0KzDeAOXftRa8bNeOKTTfCJ7KorIwenkHd2xqVTBTCZd79yk/lx/Ug==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "dependencies": { + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + } + } + }, + "react-dom-17": { + "version": "npm:react-dom@17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "scheduler": "^0.20.1" + "scheduler": "^0.20.2" } }, "react-is": { @@ -17060,13 +17177,41 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "react-shallow-renderer": { - "version": "16.14.1", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz", - "integrity": "sha512-rkIMcQi01/+kxiTE9D3fdS959U1g7gs+/rborw++42m1O9FAQiNI/UNRZExVUoAOprn4umcXf+pFRou8i4zuBg==", + "version": "16.15.0", + "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", + "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", "dev": true, "requires": { "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0" + "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" + } + }, + "react-test-renderer": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz", + "integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==", + "dev": true, + "requires": { + "react-is": "^18.2.0", + "react-shallow-renderer": "^16.15.0", + "scheduler": "^0.23.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "dev": true, + "requires": { + "loose-envify": "^1.1.0" + } + } } }, "read-pkg": { @@ -17372,9 +17517,9 @@ "dev": true }, "scheduler": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.1.tgz", - "integrity": "sha512-LKTe+2xNJBNxu/QhHvDR14wUXHRQbVY5ZOYpOGWRzhydZUqrLb2JBvLPY7cAqFmqrWuDED0Mjk7013SZiOz6Bw==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "dev": true, "requires": { "loose-envify": "^1.1.0", diff --git a/package.json b/package.json index 9ad4b9f..b727dd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-mobiledoc-editor", - "version": "0.13.1", + "version": "0.14.0", "description": "A Mobiledoc editor for React apps", "repository": "joshfrench/react-mobiledoc-editor", "homepage": "https://github.com/joshfrench/react-mobiledoc-editor", @@ -28,7 +28,7 @@ "start": "concurrently \"npm:start:demo\" \"npm:build:watch\"", "start:demo": "webpack-dev-server --mode development --hot --config webpack.demo.config.js", "lint": "eslint src", - "test": "npm run test:build && karma start --single-run", + "test": "npm run test:build && karma start --single-run && REACT_17=true karma start --single-run", "test:watch": "concurrently \"karma start\" \"npm:build:watch\"", "test:build": "npm run build && mocha test/build", "posttest": "npm run lint && npm run format:check", @@ -40,12 +40,12 @@ "prepublishOnly": "npm run build" }, "dependencies": { - "prop-types": "^15.7.2" + "prop-types": "^15.8.1" }, "peerDependencies": { "mobiledoc-kit": "^0.12.2 || ^0.13.0 || ^0.14.0", - "react": "^16.3.0 || ^17.0.0", - "react-dom": "^16.3.0 || ^17.0.0" + "react": "^16.3.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.3.0 || ^17.0.0 || ^18.0.0" }, "devDependencies": { "@babel/core": "^7.18.10", @@ -55,6 +55,7 @@ "@babel/plugin-transform-runtime": "^7.18.10", "@babel/preset-env": "^7.18.10", "@babel/preset-react": "^7.18.6", + "@cfaester/enzyme-adapter-react-18": "^0.5.1", "@rollup/plugin-babel": "^5.3.1", "@rollup/plugin-commonjs": "^22.0.1", "@rollup/plugin-node-resolve": "^13.3.0", @@ -84,8 +85,10 @@ "mobiledoc-kit": "^0.14.0", "mocha": "^10.0.0", "prettier": "^2.7.1", - "react": "^17.0.1", - "react-dom": "^17.0.1", + "react": "^18.2.0", + "react-17": "npm:react@^17", + "react-dom": "^18.2.0", + "react-dom-17": "npm:react-dom@^17", "rimraf": "^3.0.2", "rollup": "^2.77.2", "rollup-plugin-auto-external": "^2.0.0", diff --git a/src/components/Container.js b/src/components/Container.js index 79328e5..c844d41 100644 --- a/src/components/Container.js +++ b/src/components/Container.js @@ -31,6 +31,7 @@ class Container extends React.Component { placeholder, serializeVersion, spellcheck, + ReactDOM, } = this.props; const mobiledoc = this.props.mobiledoc || (html ? undefined : EMPTY_MOBILEDOC); @@ -39,7 +40,7 @@ class Container extends React.Component { ...this.props.options, atoms, autofocus, - cardOptions: { cardProps }, + cardOptions: { cardProps, ReactDOM }, cards, html, mobiledoc, @@ -87,6 +88,7 @@ class Container extends React.Component { spellcheck, willCreateEditor, onChange, + ReactDOM, ...componentProps } = this.props; /* eslint-enable no-unused-vars */ diff --git a/src/utils/classToAtom.js b/src/utils/classToAtom.js index 5f2fde2..e9781bc 100644 --- a/src/utils/classToAtom.js +++ b/src/utils/classToAtom.js @@ -1,10 +1,11 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { reactDomRender, reactDomUnmount } from './react'; const atomRenderer = (component) => ({ env, options, payload, value }) => { const { onTeardown } = env; + const { ReactDOM } = options; const element = React.createElement(component, { ...env, @@ -14,9 +15,9 @@ const atomRenderer = }); const targetNode = document.createElement('span'); - ReactDOM.render(element, targetNode); + const root = reactDomRender(ReactDOM, element, targetNode); - onTeardown(() => ReactDOM.unmountComponentAtNode(targetNode)); + onTeardown(() => reactDomUnmount(ReactDOM, root, targetNode)); return targetNode; }; diff --git a/src/utils/classToCard.js b/src/utils/classToCard.js index 06d490a..392d3ae 100644 --- a/src/utils/classToCard.js +++ b/src/utils/classToCard.js @@ -1,25 +1,26 @@ import React from 'react'; -import ReactDOM from 'react-dom'; +import { reactDomRender, reactDomUnmount } from './react'; const cardRenderer = (component, isEditing = false) => ({ env, options, payload }) => { const targetNode = document.createElement('div'); const { didRender, onTeardown } = env; + const { cardProps, ReactDOM } = options; + let root; didRender(() => { payload = { ...payload }; // deref payload - const { cardProps } = options; const element = React.createElement(component, { ...env, ...cardProps, payload, isEditing, }); - ReactDOM.render(element, targetNode); + root = reactDomRender(ReactDOM, element, targetNode); }); - onTeardown(() => ReactDOM.unmountComponentAtNode(targetNode)); + onTeardown(() => reactDomUnmount(ReactDOM, root, targetNode)); return targetNode; }; diff --git a/src/utils/react.js b/src/utils/react.js new file mode 100644 index 0000000..fd5af8d --- /dev/null +++ b/src/utils/react.js @@ -0,0 +1,18 @@ +import ReactDOM from 'react-dom'; + +export function reactDomRender(CustomReactDOM, element, target) { + const ResolvedReactDOM = CustomReactDOM || ReactDOM; + const createRoot = ResolvedReactDOM.createRoot; // React 18+ + if (createRoot) { + const root = createRoot(target); + root.render(element); + return root; + } else { + ResolvedReactDOM.render(element, target); + } +} + +export function reactDomUnmount(CustomReactDOM, root, target) { + if (root) root.unmount(); + else (CustomReactDOM || ReactDOM).unmountComponentAtNode(target); +} diff --git a/test/setup.js b/test/setup.js index d4f0be1..6c87cbe 100644 --- a/test/setup.js +++ b/test/setup.js @@ -1,8 +1,11 @@ import chai from 'chai'; import chaiEnzyme from 'chai-enzyme'; import Enzyme from 'enzyme'; -import Adapter from '@wojtekmaj/enzyme-adapter-react-17'; import sinonChai from 'sinon-chai'; +import React from 'react'; +import Adapter from '@cfaester/enzyme-adapter-react-18'; + +console.log('Testing with React version:', React.version); // eslint-disable-line no-console Enzyme.configure({ adapter: new Adapter() });