diff --git a/examples/react-rich/src/plugins/ToolbarPlugin.tsx b/examples/react-rich/src/plugins/ToolbarPlugin.tsx
index 69061072306..1ace522d99f 100644
--- a/examples/react-rich/src/plugins/ToolbarPlugin.tsx
+++ b/examples/react-rich/src/plugins/ToolbarPlugin.tsx
@@ -37,7 +37,7 @@ export default function ToolbarPlugin() {
const [isUnderline, setIsUnderline] = useState(false);
const [isStrikethrough, setIsStrikethrough] = useState(false);
- const updateToolbar = useCallback(() => {
+ const $updateToolbar = useCallback(() => {
const selection = $getSelection();
if ($isRangeSelection(selection)) {
// Update text format
@@ -52,13 +52,13 @@ export default function ToolbarPlugin() {
return mergeRegister(
editor.registerUpdateListener(({editorState}) => {
editorState.read(() => {
- updateToolbar();
+ $updateToolbar();
});
}),
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload, newEditor) => {
- updateToolbar();
+ $updateToolbar();
return false;
},
LowPriority,
@@ -80,7 +80,7 @@ export default function ToolbarPlugin() {
LowPriority,
),
);
- }, [editor, updateToolbar]);
+ }, [editor, $updateToolbar]);
return (
diff --git a/package-lock.json b/package-lock.json
index 5bf364b35e9..1a3d1c6260e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1072,12 +1072,12 @@
}
},
"node_modules/@babel/plugin-syntax-flow": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz",
- "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz",
+ "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -1504,13 +1504,13 @@
}
},
"node_modules/@babel/plugin-transform-flow-strip-types": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz",
- "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.6.tgz",
+ "integrity": "sha512-wE0xtA7csz+hw4fKPwxmu5jnzAsXPIO57XnRwzXP3T19jWh1BODnPGoG9xKYwvAwusP7iUktHayRFbMPGtODaQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0",
- "@babel/plugin-syntax-flow": "^7.24.1"
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/plugin-syntax-flow": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -2264,14 +2264,14 @@
}
},
"node_modules/@babel/preset-flow": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.1.tgz",
- "integrity": "sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz",
+ "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.24.0",
- "@babel/helper-validator-option": "^7.23.5",
- "@babel/plugin-transform-flow-strip-types": "^7.24.1"
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/helper-validator-option": "^7.18.6",
+ "@babel/plugin-transform-flow-strip-types": "^7.18.6"
},
"engines": {
"node": ">=6.9.0"
@@ -3562,18 +3562,6 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
- "node_modules/@eduardoac-skimlinks/webext-redux": {
- "version": "3.0.1-release-candidate",
- "resolved": "https://registry.npmjs.org/@eduardoac-skimlinks/webext-redux/-/webext-redux-3.0.1-release-candidate.tgz",
- "integrity": "sha512-Gv7LOjiqye+umkR4v0FqcWo8goqP333Lr2kqtMIWJMs5S12T5b3G87PoxuVqZByJk2Ebc6mJ/v7f3HMVzgFT+w==",
- "dependencies": {
- "lodash.assignin": "^4.2.0",
- "lodash.clonedeep": "^4.5.0"
- },
- "peerDependencies": {
- "redux": ">= 3 <= 4"
- }
- },
"node_modules/@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@@ -6800,8 +6788,7 @@
"node_modules/@types/webextension-polyfill": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.10.7.tgz",
- "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==",
- "dev": true
+ "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw=="
},
"node_modules/@types/ws": {
"version": "8.5.10",
@@ -7327,6 +7314,61 @@
"integrity": "sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==",
"dev": true
},
+ "node_modules/@webext-pegasus/rpc": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/rpc/-/rpc-0.0.4.tgz",
+ "integrity": "sha512-PjfJ1vQgwKS/XJBZC3xvbx/i1cljm2x3pb1osFbAbPs4Tnmv2FewdVC2hhBTBNy9i27KyHTsMvd42rttZQvRoA==",
+ "dependencies": {
+ "@webext-pegasus/transport": "^0.0.4",
+ "type-fest": "^0.21.3"
+ }
+ },
+ "node_modules/@webext-pegasus/store": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/store/-/store-0.0.4.tgz",
+ "integrity": "sha512-nvfwxlglcGDie0b3pd2WZVLnYbll24tzBVSQ+0XgKa5tL6ntAMjRPqQWO3QNlUJANbmcXabzAd5R+7dxCTrTyw==",
+ "dependencies": {
+ "@webext-pegasus/rpc": "^0.0.4",
+ "@webext-pegasus/transport": "^0.0.4",
+ "type-fest": "^0.21.3"
+ }
+ },
+ "node_modules/@webext-pegasus/store-zustand": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/store-zustand/-/store-zustand-0.0.4.tgz",
+ "integrity": "sha512-5M/ZRwSzno8ebWZeu+A1GplVTVc/ruU/whWCyOkJNtKkCyuKwFZBWLBUfzKKIgNhCqSeGqxwi4F/ggvS42glPQ==",
+ "dependencies": {
+ "@webext-pegasus/store": "^0.0.4",
+ "type-fest": "^0.21.3"
+ },
+ "peerDependencies": {
+ "zustand": "^4"
+ }
+ },
+ "node_modules/@webext-pegasus/transport": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/transport/-/transport-0.0.4.tgz",
+ "integrity": "sha512-s6iWeo3vpoEa7d3UZtNVDWFQjT3OAdgxN064Z7P0fJNbfIMVa/Twvo8dJtcsSg5VGxxoxIqwLJ7FX6LgmBAmpg==",
+ "dependencies": {
+ "@types/webextension-polyfill": "^0.10.7",
+ "nanoevents": "^6.0.2",
+ "serialize-error": "^9.0.0",
+ "tiny-uid": "^1.1.1",
+ "type-fest": "^2.11.1",
+ "webextension-polyfill": "0.10.0"
+ }
+ },
+ "node_modules/@webext-pegasus/transport/node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -18274,22 +18316,12 @@
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
- "node_modules/lodash.assignin": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
- "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg=="
- },
"node_modules/lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"dev": true
},
- "node_modules/lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
- },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -22476,9 +22508,9 @@
}
},
"node_modules/pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
+ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
"dev": true,
"engines": {
"node": ">= 6"
@@ -27750,7 +27782,6 @@
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true,
"engines": {
"node": ">=10"
},
@@ -28995,33 +29026,10 @@
"url": "https://github.com/sponsors/wooorm"
}
},
- "node_modules/webext-bridge": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/webext-bridge/-/webext-bridge-6.0.1.tgz",
- "integrity": "sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==",
- "dependencies": {
- "@types/webextension-polyfill": "^0.8.3",
- "nanoevents": "^6.0.2",
- "serialize-error": "^9.0.0",
- "tiny-uid": "^1.1.1",
- "webextension-polyfill": "^0.9.0"
- }
- },
- "node_modules/webext-bridge/node_modules/@types/webextension-polyfill": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.8.3.tgz",
- "integrity": "sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg=="
- },
- "node_modules/webext-bridge/node_modules/webextension-polyfill": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz",
- "integrity": "sha512-LTtHb0yR49xa9irkstDxba4GATDAcDw3ncnFH9RImoFwDlW47U95ME5sn5IiQX2ghfaECaf6xyXM8yvClIBkkw=="
- },
"node_modules/webextension-polyfill": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
- "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==",
- "dev": true
+ "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g=="
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
@@ -31165,10 +31173,11 @@
"version": "0.14.5",
"hasInstallScript": true,
"dependencies": {
- "@eduardoac-skimlinks/webext-redux": "3.0.1-release-candidate",
+ "@webext-pegasus/rpc": "^0.0.4",
+ "@webext-pegasus/store-zustand": "^0.0.4",
+ "@webext-pegasus/transport": "^0.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "webext-bridge": "~6.0.1",
"zustand": "^4.5.1"
},
"devDependencies": {
@@ -32471,12 +32480,12 @@
}
},
"@babel/plugin-syntax-flow": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.1.tgz",
- "integrity": "sha512-sxi2kLTI5DeW5vDtMUsk4mTPwvlUDbjOnoWayhynCwrw4QXRld4QEYwqzY8JmQXaJUtgUuCIurtSRH5sn4c7mA==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz",
+ "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.24.0"
+ "@babel/helper-plugin-utils": "^7.18.6"
}
},
"@babel/plugin-syntax-import-assertions": {
@@ -32741,13 +32750,13 @@
}
},
"@babel/plugin-transform-flow-strip-types": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.24.1.tgz",
- "integrity": "sha512-iIYPIWt3dUmUKKE10s3W+jsQ3icFkw0JyRVyY1B7G4yK/nngAOHLVx8xlhA6b/Jzl/Y0nis8gjqhqKtRDQqHWQ==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.18.6.tgz",
+ "integrity": "sha512-wE0xtA7csz+hw4fKPwxmu5jnzAsXPIO57XnRwzXP3T19jWh1BODnPGoG9xKYwvAwusP7iUktHayRFbMPGtODaQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.24.0",
- "@babel/plugin-syntax-flow": "^7.24.1"
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/plugin-syntax-flow": "^7.18.6"
}
},
"@babel/plugin-transform-for-of": {
@@ -33235,14 +33244,14 @@
}
},
"@babel/preset-flow": {
- "version": "7.24.1",
- "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.1.tgz",
- "integrity": "sha512-sWCV2G9pcqZf+JHyv/RyqEIpFypxdCSxWIxQjpdaQxenNog7cN1pr76hg8u0Fz8Qgg0H4ETkGcJnXL8d4j0PPA==",
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz",
+ "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==",
"dev": true,
"requires": {
- "@babel/helper-plugin-utils": "^7.24.0",
- "@babel/helper-validator-option": "^7.23.5",
- "@babel/plugin-transform-flow-strip-types": "^7.24.1"
+ "@babel/helper-plugin-utils": "^7.18.6",
+ "@babel/helper-validator-option": "^7.18.6",
+ "@babel/plugin-transform-flow-strip-types": "^7.18.6"
}
},
"@babel/preset-modules": {
@@ -34255,15 +34264,6 @@
}
}
},
- "@eduardoac-skimlinks/webext-redux": {
- "version": "3.0.1-release-candidate",
- "resolved": "https://registry.npmjs.org/@eduardoac-skimlinks/webext-redux/-/webext-redux-3.0.1-release-candidate.tgz",
- "integrity": "sha512-Gv7LOjiqye+umkR4v0FqcWo8goqP333Lr2kqtMIWJMs5S12T5b3G87PoxuVqZByJk2Ebc6mJ/v7f3HMVzgFT+w==",
- "requires": {
- "lodash.assignin": "^4.2.0",
- "lodash.clonedeep": "^4.5.0"
- }
- },
"@esbuild/aix-ppc64": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
@@ -35142,18 +35142,19 @@
"@lexical/devtools": {
"version": "file:packages/lexical-devtools",
"requires": {
- "@eduardoac-skimlinks/webext-redux": "3.0.1-release-candidate",
"@lexical/devtools-core": "0.14.5",
"@rollup/plugin-babel": "^6.0.4",
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.2.1",
+ "@webext-pegasus/rpc": "^0.0.4",
+ "@webext-pegasus/store-zustand": "^0.0.4",
+ "@webext-pegasus/transport": "^0.0.4",
"lexical": "0.14.5",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^5.3.3",
"vite": "^5.2.2",
- "webext-bridge": "~6.0.1",
"wxt": "^0.17.0",
"zustand": "^4.5.1"
},
@@ -36792,8 +36793,7 @@
"@types/webextension-polyfill": {
"version": "0.10.7",
"resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.10.7.tgz",
- "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw==",
- "dev": true
+ "integrity": "sha512-10ql7A0qzBmFB+F+qAke/nP1PIonS0TXZAOMVOxEUsm+lGSW6uwVcISFNa0I4Oyj0884TZVWGGMIWeXOVSNFHw=="
},
"@types/ws": {
"version": "8.5.10",
@@ -37182,6 +37182,54 @@
"integrity": "sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==",
"dev": true
},
+ "@webext-pegasus/rpc": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/rpc/-/rpc-0.0.4.tgz",
+ "integrity": "sha512-PjfJ1vQgwKS/XJBZC3xvbx/i1cljm2x3pb1osFbAbPs4Tnmv2FewdVC2hhBTBNy9i27KyHTsMvd42rttZQvRoA==",
+ "requires": {
+ "@webext-pegasus/transport": "^0.0.4",
+ "type-fest": "^0.21.3"
+ }
+ },
+ "@webext-pegasus/store": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/store/-/store-0.0.4.tgz",
+ "integrity": "sha512-nvfwxlglcGDie0b3pd2WZVLnYbll24tzBVSQ+0XgKa5tL6ntAMjRPqQWO3QNlUJANbmcXabzAd5R+7dxCTrTyw==",
+ "requires": {
+ "@webext-pegasus/rpc": "^0.0.4",
+ "@webext-pegasus/transport": "^0.0.4",
+ "type-fest": "^0.21.3"
+ }
+ },
+ "@webext-pegasus/store-zustand": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/store-zustand/-/store-zustand-0.0.4.tgz",
+ "integrity": "sha512-5M/ZRwSzno8ebWZeu+A1GplVTVc/ruU/whWCyOkJNtKkCyuKwFZBWLBUfzKKIgNhCqSeGqxwi4F/ggvS42glPQ==",
+ "requires": {
+ "@webext-pegasus/store": "^0.0.4",
+ "type-fest": "^0.21.3"
+ }
+ },
+ "@webext-pegasus/transport": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/@webext-pegasus/transport/-/transport-0.0.4.tgz",
+ "integrity": "sha512-s6iWeo3vpoEa7d3UZtNVDWFQjT3OAdgxN064Z7P0fJNbfIMVa/Twvo8dJtcsSg5VGxxoxIqwLJ7FX6LgmBAmpg==",
+ "requires": {
+ "@types/webextension-polyfill": "^0.10.7",
+ "nanoevents": "^6.0.2",
+ "serialize-error": "^9.0.0",
+ "tiny-uid": "^1.1.1",
+ "type-fest": "^2.11.1",
+ "webextension-polyfill": "0.10.0"
+ },
+ "dependencies": {
+ "type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA=="
+ }
+ }
+ },
"@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -45060,22 +45108,12 @@
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
},
- "lodash.assignin": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz",
- "integrity": "sha512-yX/rx6d/UTVh7sSVWVSIMjfnz95evAgDFdb1ZozC35I9mSFCkmzptOzevxjgbQUsc78NR44LVHWjsoMQXy9FDg=="
- },
"lodash.camelcase": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
"dev": true
},
- "lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
- },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -47843,9 +47881,9 @@
"dev": true
},
"pirates": {
- "version": "4.0.6",
- "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
- "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz",
+ "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==",
"dev": true
},
"pkg-dir": {
@@ -51603,8 +51641,7 @@
"type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
- "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
- "dev": true
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="
},
"type-is": {
"version": "1.6.18",
@@ -52498,35 +52535,10 @@
"resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz",
"integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="
},
- "webext-bridge": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/webext-bridge/-/webext-bridge-6.0.1.tgz",
- "integrity": "sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==",
- "requires": {
- "@types/webextension-polyfill": "^0.8.3",
- "nanoevents": "^6.0.2",
- "serialize-error": "^9.0.0",
- "tiny-uid": "^1.1.1",
- "webextension-polyfill": "^0.9.0"
- },
- "dependencies": {
- "@types/webextension-polyfill": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/@types/webextension-polyfill/-/webextension-polyfill-0.8.3.tgz",
- "integrity": "sha512-GN+Hjzy9mXjWoXKmaicTegv3FJ0WFZ3aYz77Wk8TMp1IY3vEzvzj1vnsa0ggV7vMI1i+PUxe4qqnIJKCzf9aTg=="
- },
- "webextension-polyfill": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.9.0.tgz",
- "integrity": "sha512-LTtHb0yR49xa9irkstDxba4GATDAcDw3ncnFH9RImoFwDlW47U95ME5sn5IiQX2ghfaECaf6xyXM8yvClIBkkw=="
- }
- }
- },
"webextension-polyfill": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz",
- "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==",
- "dev": true
+ "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g=="
},
"webidl-conversions": {
"version": "7.0.0",
diff --git a/packages/lexical-devtools/package.json b/packages/lexical-devtools/package.json
index cdf69bd060f..89e08125c57 100644
--- a/packages/lexical-devtools/package.json
+++ b/packages/lexical-devtools/package.json
@@ -7,6 +7,7 @@
"scripts": {
"dev": "wxt",
"dev:firefox": "wxt -b firefox",
+ "dev:edge": "wxt -b edge",
"build": "wxt build",
"build:firefox": "wxt build -b firefox",
"zip": "wxt zip",
@@ -15,21 +16,23 @@
"postinstall": "wxt prepare"
},
"dependencies": {
- "@eduardoac-skimlinks/webext-redux": "3.0.1-release-candidate",
+ "@webext-pegasus/rpc": "^0.0.4",
+ "@webext-pegasus/store-zustand": "^0.0.4",
+ "@webext-pegasus/transport": "^0.0.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "webext-bridge": "~6.0.1",
"zustand": "^4.5.1"
},
"devDependencies": {
- "@lexical/devtools-core": "0.14.5",
- "@rollup/plugin-babel": "^6.0.4",
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"@vitejs/plugin-react": "^4.2.1",
- "lexical": "0.14.5",
"typescript": "^5.3.3",
+ "lexical": "0.14.5",
+ "@lexical/devtools-core": "0.14.5",
+ "wxt": "^0.17.0",
"vite": "^5.2.2",
- "wxt": "^0.17.0"
- }
+ "@rollup/plugin-babel": "^6.0.4"
+ },
+ "sideEffects": false
}
diff --git a/packages/lexical-devtools/src/entrypoints/devtools/main.ts b/packages/lexical-devtools/src/entrypoints/devtools/main.ts
index f8b60cb0cf7..34a845df7f0 100644
--- a/packages/lexical-devtools/src/entrypoints/devtools/main.ts
+++ b/packages/lexical-devtools/src/entrypoints/devtools/main.ts
@@ -9,6 +9,6 @@
// Create the panel which appears within the browser devtools
browser.devtools.panels.create(
'Lexical',
- 'icon/128.png',
+ '/icon/logo.svg',
'devtools-panel.html',
);
diff --git a/packages/lexical-devtools/src/public/icon/logo.svg b/packages/lexical-devtools/src/public/icon/logo.svg
new file mode 100644
index 00000000000..74ddb15d8a9
--- /dev/null
+++ b/packages/lexical-devtools/src/public/icon/logo.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/lexical-devtools/wxt.config.ts b/packages/lexical-devtools/wxt.config.ts
index 383cb658650..40916d1fd82 100644
--- a/packages/lexical-devtools/wxt.config.ts
+++ b/packages/lexical-devtools/wxt.config.ts
@@ -53,6 +53,9 @@ export default defineConfig({
return manifestConf;
},
runner: {
+ binaries: {
+ edge: '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',
+ },
chromiumArgs: [
'--auto-open-devtools-for-tabs',
// Open chrome://version to validate it works
diff --git a/packages/lexical-playground/__tests__/e2e/Extensions.spec.mjs b/packages/lexical-playground/__tests__/e2e/Extensions.spec.mjs
index e054da712e3..ba54a73aae1 100644
--- a/packages/lexical-playground/__tests__/e2e/Extensions.spec.mjs
+++ b/packages/lexical-playground/__tests__/e2e/Extensions.spec.mjs
@@ -6,6 +6,10 @@
*
*/
+import {
+ moveToEditorBeginning,
+ moveToEditorEnd,
+} from '../keyboardShortcuts/index.mjs';
import {
assertHTML,
assertSelection,
@@ -255,4 +259,90 @@ test.describe('Extensions', () => {
focusPath: [1, 0, 0],
});
});
+
+ test('document.execCommand("insertText") with all text backward selection', async ({
+ page,
+ }) => {
+ await focusEditor(page);
+
+ await page.keyboard.type('Paragraph 1');
+ await moveToEditorEnd(page);
+ await page.keyboard.down('Shift');
+ await moveToEditorBeginning(page);
+ await page.keyboard.up('Shift');
+
+ await assertSelection(page, {
+ anchorOffset: 11,
+ anchorPath: [0, 0, 0],
+ focusOffset: 0,
+ focusPath: [0, 0, 0],
+ });
+
+ await evaluate(
+ page,
+ () => {
+ document.execCommand('insertText', false, 'New text');
+ },
+ [],
+ );
+ await assertHTML(
+ page,
+ html`
+
+ New text
+
+ `,
+ );
+ await assertSelection(page, {
+ anchorOffset: 8,
+ anchorPath: [0, 0, 0],
+ focusOffset: 8,
+ focusPath: [0, 0, 0],
+ });
+ });
+
+ test('document.execCommand("insertText") with all text forward selection', async ({
+ page,
+ }) => {
+ await focusEditor(page);
+
+ await page.keyboard.type('Paragraph 1');
+ await moveToEditorBeginning(page);
+ await page.keyboard.down('Shift');
+ await moveToEditorEnd(page);
+ await page.keyboard.up('Shift');
+
+ await assertSelection(page, {
+ anchorOffset: 0,
+ anchorPath: [0, 0, 0],
+ focusOffset: 11,
+ focusPath: [0, 0, 0],
+ });
+
+ await evaluate(
+ page,
+ () => {
+ document.execCommand('insertText', false, 'New text');
+ },
+ [],
+ );
+ await assertHTML(
+ page,
+ html`
+
+ New text
+
+ `,
+ );
+ await assertSelection(page, {
+ anchorOffset: 8,
+ anchorPath: [0, 0, 0],
+ focusOffset: 8,
+ focusPath: [0, 0, 0],
+ });
+ });
});
diff --git a/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts b/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts
index b8f3b3ce976..3feb82b9f96 100644
--- a/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts
+++ b/packages/lexical-playground/src/plugins/ListMaxIndentLevelPlugin/index.ts
@@ -6,7 +6,7 @@
*
*/
-import type {RangeSelection} from 'lexical';
+import type {ElementNode, RangeSelection} from 'lexical';
import {$getListDepth, $isListItemNode, $isListNode} from '@lexical/list';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
@@ -15,15 +15,10 @@ import {
$isElementNode,
$isRangeSelection,
COMMAND_PRIORITY_CRITICAL,
- ElementNode,
INDENT_CONTENT_COMMAND,
} from 'lexical';
import {useEffect} from 'react';
-type Props = Readonly<{
- maxDepth: number | null | undefined;
-}>;
-
function getElementNodesInSelection(
selection: RangeSelection,
): Set
{
@@ -41,7 +36,7 @@ function getElementNodesInSelection(
);
}
-function isIndentPermitted(maxDepth: number): boolean {
+function shouldPreventIndent(maxDepth: number): boolean {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
@@ -69,16 +64,20 @@ function isIndentPermitted(maxDepth: number): boolean {
}
}
- return totalDepth <= maxDepth;
+ return totalDepth > maxDepth;
}
-export default function ListMaxIndentLevelPlugin({maxDepth}: Props): null {
+export default function ListMaxIndentLevelPlugin({
+ maxDepth = 7,
+}: {
+ maxDepth?: number;
+}): null {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return editor.registerCommand(
INDENT_CONTENT_COMMAND,
- () => !isIndentPermitted(maxDepth ?? 7),
+ () => shouldPreventIndent(maxDepth),
COMMAND_PRIORITY_CRITICAL,
);
}, [editor, maxDepth]);
diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts
index 3363d6e6423..e237e67b339 100644
--- a/packages/lexical-react/src/LexicalTablePlugin.ts
+++ b/packages/lexical-react/src/LexicalTablePlugin.ts
@@ -16,6 +16,7 @@ import type {NodeKey} from 'lexical';
import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {
$computeTableMap,
+ $computeTableMapSkipCellCheck,
$createTableCellNode,
$createTableNodeWithDimensions,
$getNodeTriplet,
@@ -28,8 +29,13 @@ import {
TableNode,
TableRowNode,
} from '@lexical/table';
-import {$insertFirst, $insertNodeToNearestRoot} from '@lexical/utils';
import {
+ $insertFirst,
+ $insertNodeToNearestRoot,
+ mergeRegister,
+} from '@lexical/utils';
+import {
+ $createParagraphNode,
$getNodeByKey,
$isTextNode,
$nodesOfType,
@@ -57,24 +63,50 @@ export function TablePlugin({
);
}
- return editor.registerCommand(
- INSERT_TABLE_COMMAND,
- ({columns, rows, includeHeaders}) => {
- const tableNode = $createTableNodeWithDimensions(
- Number(rows),
- Number(columns),
- includeHeaders,
- );
- $insertNodeToNearestRoot(tableNode);
+ return mergeRegister(
+ editor.registerCommand(
+ INSERT_TABLE_COMMAND,
+ ({columns, rows, includeHeaders}) => {
+ const tableNode = $createTableNodeWithDimensions(
+ Number(rows),
+ Number(columns),
+ includeHeaders,
+ );
+ $insertNodeToNearestRoot(tableNode);
+
+ const firstDescendant = tableNode.getFirstDescendant();
+ if ($isTextNode(firstDescendant)) {
+ firstDescendant.select();
+ }
- const firstDescendant = tableNode.getFirstDescendant();
- if ($isTextNode(firstDescendant)) {
- firstDescendant.select();
+ return true;
+ },
+ COMMAND_PRIORITY_EDITOR,
+ ),
+ editor.registerNodeTransform(TableNode, (node) => {
+ const [gridMap] = $computeTableMapSkipCellCheck(node, null, null);
+ const maxRowLength = gridMap.reduce((curLength, row) => {
+ return Math.max(curLength, row.length);
+ }, 0);
+ for (let i = 0; i < gridMap.length; ++i) {
+ const rowLength = gridMap[i].length;
+ if (rowLength === maxRowLength) {
+ continue;
+ }
+ const lastCellMap = gridMap[i][rowLength - 1];
+ const lastRowCell = lastCellMap.cell;
+ for (let j = rowLength; j < maxRowLength; ++j) {
+ // TODO: inherit header state from another header or body
+ const newCell = $createTableCellNode(0);
+ newCell.append($createParagraphNode());
+ if (lastRowCell !== null) {
+ lastRowCell.insertAfter(newCell);
+ } else {
+ $insertFirst(lastRowCell, newCell);
+ }
+ }
}
-
- return true;
- },
- COMMAND_PRIORITY_EDITOR,
+ }),
);
}, [editor]);
diff --git a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
index cf6db409f0b..ed443fc3f17 100644
--- a/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
+++ b/packages/lexical-table/src/LexicalTableSelectionHelpers.ts
@@ -102,15 +102,18 @@ export function applyTableHandlers(
};
const onMouseMove = (moveEvent: MouseEvent) => {
- const focusCell = getDOMCellFromTarget(moveEvent.target as Node);
- if (
- focusCell !== null &&
- (tableObserver.anchorX !== focusCell.x ||
- tableObserver.anchorY !== focusCell.y)
- ) {
- moveEvent.preventDefault();
- tableObserver.setFocusCellForSelection(focusCell);
- }
+ // delaying mousemove handler to allow selectionchange handler from LexicalEvents.ts to be executed first
+ setTimeout(() => {
+ const focusCell = getDOMCellFromTarget(moveEvent.target as Node);
+ if (
+ focusCell !== null &&
+ (tableObserver.anchorX !== focusCell.x ||
+ tableObserver.anchorY !== focusCell.y)
+ ) {
+ moveEvent.preventDefault();
+ tableObserver.setFocusCellForSelection(focusCell);
+ }
+ }, 0);
};
return {onMouseMove: onMouseMove, onMouseUp: onMouseUp};
};
diff --git a/packages/lexical-table/src/LexicalTableUtils.ts b/packages/lexical-table/src/LexicalTableUtils.ts
index e6e507ba16c..07f366982d8 100644
--- a/packages/lexical-table/src/LexicalTableUtils.ts
+++ b/packages/lexical-table/src/LexicalTableUtils.ts
@@ -720,6 +720,21 @@ export function $computeTableMap(
cellA: TableCellNode,
cellB: TableCellNode,
): [TableMapType, TableMapValueType, TableMapValueType] {
+ const [tableMap, cellAValue, cellBValue] = $computeTableMapSkipCellCheck(
+ grid,
+ cellA,
+ cellB,
+ );
+ invariant(cellAValue !== null, 'Anchor not found in Grid');
+ invariant(cellBValue !== null, 'Focus not found in Grid');
+ return [tableMap, cellAValue, cellBValue];
+}
+
+export function $computeTableMapSkipCellCheck(
+ grid: TableNode,
+ cellA: null | TableCellNode,
+ cellB: null | TableCellNode,
+): [TableMapType, TableMapValueType | null, TableMapValueType | null] {
const tableMap: TableMapType = [];
let cellAValue: null | TableMapValueType = null;
let cellBValue: null | TableMapValueType = null;
@@ -739,10 +754,10 @@ export function $computeTableMap(
tableMap[startRow + i][startColumn + j] = value;
}
}
- if (cellA.is(cell)) {
+ if (cellA !== null && cellA.is(cell)) {
cellAValue = value;
}
- if (cellB.is(cell)) {
+ if (cellB !== null && cellB.is(cell)) {
cellBValue = value;
}
}
@@ -771,8 +786,6 @@ export function $computeTableMap(
j += cell.__colSpan;
}
}
- invariant(cellAValue !== null, 'Anchor not found in Grid');
- invariant(cellBValue !== null, 'Focus not found in Grid');
return [tableMap, cellAValue, cellBValue];
}
diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts
index 9c723b483e7..c9f3964cf07 100644
--- a/packages/lexical-table/src/index.ts
+++ b/packages/lexical-table/src/index.ts
@@ -49,6 +49,7 @@ export {
} from './LexicalTableSelectionHelpers';
export {
$computeTableMap,
+ $computeTableMapSkipCellCheck,
$createTableNodeWithDimensions,
$deleteTableColumn,
$deleteTableColumn__EXPERIMENTAL,
diff --git a/packages/lexical/src/LexicalEvents.ts b/packages/lexical/src/LexicalEvents.ts
index edc9dca080b..96cf9ad7c86 100644
--- a/packages/lexical/src/LexicalEvents.ts
+++ b/packages/lexical/src/LexicalEvents.ts
@@ -826,7 +826,13 @@ function onInput(event: InputEvent, editor: LexicalEditor): void {
if (domSelection === null) {
return;
}
- const offset = anchor.offset;
+ 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.
@@ -835,9 +841,9 @@ function onInput(event: InputEvent, editor: LexicalEditor): void {
selection.isCollapsed() ||
!$isTextNode(anchorNode) ||
domSelection.anchorNode === null ||
- anchorNode.getTextContent().slice(0, offset) +
+ anchorNode.getTextContent().slice(0, startOffset) +
data +
- anchorNode.getTextContent().slice(offset + selection.focus.offset) !==
+ anchorNode.getTextContent().slice(startOffset + endOffset) !==
getAnchorTextFromDOM(domSelection.anchorNode)
) {
dispatchCommand(editor, CONTROLLED_TEXT_INSERTION_COMMAND, data);