UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

190 lines (181 loc) 6.38 kB
/** * 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. * */ 'use strict'; var extension = require('@lexical/extension'); var ReactExtension = require('@lexical/react/ReactExtension'); var ReactProviderExtension = require('@lexical/react/ReactProviderExtension'); var utils = require('@lexical/utils'); var lexical = require('lexical'); var react = require('react'); var reactDom = require('react-dom'); var client = require('react-dom/client'); var jsxRuntime = require('react/jsx-runtime'); /** * 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. * */ // Do not require this module directly! Use normal `invariant` calls. function formatDevErrorMessage(message) { throw new Error(message); } function mountReactExtensionComponent(editor, opts) { const { props, extension: extension$1, ...rest } = opts; const { Component } = extension.getExtensionDependencyFromEditor(editor, extension$1).output; const element = props ? /*#__PURE__*/jsxRuntime.jsx(Component, { ...props }) : null; mountReactPluginElement(editor, { ...rest, element }); } function mountReactPluginComponent(editor, opts) { const { Component, props, ...rest } = opts; mountReactPluginElement(editor, { ...rest, element: props ? /*#__PURE__*/jsxRuntime.jsx(Component, { ...props }) : null }); } function mountReactPluginElement(editor, opts) { extension.getExtensionDependencyFromEditor(editor, ReactPluginHostExtension).output.mountReactPlugin(opts); } function mountReactPluginHost(editor, container) { extension.getExtensionDependencyFromEditor(editor, ReactPluginHostExtension).output.mountReactPluginHost(container); } const REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND = lexical.createCommand('REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND'); const REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND = lexical.createCommand('REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND'); function PluginHostDecorator({ context: [editor] }) { const { mountedPluginsStore } = extension.getExtensionDependencyFromEditor(editor, ReactPluginHostExtension).output; const { ErrorBoundary } = extension.getExtensionDependencyFromEditor(editor, ReactExtension.ReactExtension).config; const onError = editor._onError.bind(editor); const [{ plugins }, setMountedPlugins] = react.useState(() => mountedPluginsStore.peek()); react.useEffect(() => extension.effect(() => setMountedPlugins(mountedPluginsStore.value)), [mountedPluginsStore]); const children = []; for (const { key, element, domNode } of plugins.values()) { if (!element) { continue; } const wrapped = /*#__PURE__*/jsxRuntime.jsx(ErrorBoundary, { onError: onError, children: /*#__PURE__*/jsxRuntime.jsx(react.Suspense, { fallback: null, children: element }) }, key); children.push(domNode ? /*#__PURE__*/reactDom.createPortal(wrapped, domNode, key) : wrapped); } return children.length > 0 ? /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, { children: children }) : null; } /** * This extension provides a React host for editors that are not built * with LexicalExtensionComposer (e.g. you are using Vanilla JS or some * other framework). * * You must use {@link mountReactPluginHost} for any React content to work. * Afterwards, you may use {@link mountReactExtensionComponent} to * render UI for a specific React Extension. * {@link mountReactPluginComponent} and * {@link mountReactPluginElement} can be used to render * legacy React plug-ins (or any React content). */ const ReactPluginHostExtension = lexical.defineExtension({ build(editor, config, state) { const mountedPluginsStore = extension.signal({ plugins: new Map() }); return { mountReactPlugin: arg => { editor.dispatchCommand(REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND, arg); }, // Using outputs to wrap commands will give us better error messages // if the mount functions are called on an editor without this extension mountReactPluginHost: container => editor.dispatchCommand(REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND, { root: client.createRoot(container) }), mountedPluginsStore }; }, dependencies: [ReactProviderExtension.ReactProviderExtension, lexical.configExtension(ReactExtension.ReactExtension, { decorators: [PluginHostDecorator] })], name: '@lexical/react/ReactPluginHost', register(editor, _config, state) { let root; const { mountedPluginsStore } = state.getOutput(); const { Component } = state.getDependency(ReactExtension.ReactExtension).output; return utils.mergeRegister(() => { if (root) { root.unmount(); } extension.untracked(() => { mountedPluginsStore.value.plugins.clear(); }); }, editor.registerCommand(REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND, arg => { // This runs before the PluginHost version extension.untracked(() => { const { plugins } = mountedPluginsStore.value; plugins.set(arg.key, arg); mountedPluginsStore.value = { plugins }; }); return false; }, lexical.COMMAND_PRIORITY_CRITICAL), editor.registerCommand(REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND, arg => { if (!(root === undefined)) { formatDevErrorMessage(`ReactPluginHostExtension: Root is already mounted`); } root = arg.root; root.render(/*#__PURE__*/jsxRuntime.jsx(Component, { contentEditable: null })); return true; }, lexical.COMMAND_PRIORITY_EDITOR)); } }); exports.REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND = REACT_PLUGIN_HOST_MOUNT_PLUGIN_COMMAND; exports.REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND = REACT_PLUGIN_HOST_MOUNT_ROOT_COMMAND; exports.ReactPluginHostExtension = ReactPluginHostExtension; exports.mountReactExtensionComponent = mountReactExtensionComponent; exports.mountReactPluginComponent = mountReactPluginComponent; exports.mountReactPluginElement = mountReactPluginElement; exports.mountReactPluginHost = mountReactPluginHost;