UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

188 lines (175 loc) 6.4 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 LexicalContentEditable = require('@lexical/react/LexicalContentEditable'); var LexicalErrorBoundary = require('@lexical/react/LexicalErrorBoundary'); var ReactProviderExtension = require('@lexical/react/ReactProviderExtension'); var lexical = require('lexical'); var LexicalComposerContext = require('@lexical/react/LexicalComposerContext'); var react = require('react'); var reactDom = require('react-dom'); 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); } /** * 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. * */ /** @internal */ function useReactDecorators(editor, ErrorBoundary) { const [subscribe, getSnapshot] = react.useMemo(() => [cb => editor.registerDecoratorListener(cb), () => editor.getDecorators()], [editor]); const decorators = react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot); // Return decorators defined as React Portals return react.useMemo(() => { const decoratedPortals = []; for (const nodeKey in decorators) { const element = editor.getElementByKey(nodeKey); if (element !== null) { const reactDecorator = /*#__PURE__*/jsxRuntime.jsx(ErrorBoundary, { onError: e => { editor._onError(e); }, children: /*#__PURE__*/jsxRuntime.jsx(react.Suspense, { fallback: null, children: decorators[nodeKey] }) }); decoratedPortals.push(/*#__PURE__*/reactDom.createPortal(reactDecorator, element, nodeKey)); } } return decoratedPortals; }, [ErrorBoundary, decorators, editor]); } /** * 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. * */ function buildEditorComponent(config, context) { const [editor] = context; const rawConfigDecorators = config.decorators.map(El => // eslint-disable-next-line react/jsx-key -- wrapped later typeof El === 'function' ? /*#__PURE__*/jsxRuntime.jsx(El, { context: context }) : El); return function EditorComponent(props) { const { EditorChildrenComponent = config.EditorChildrenComponent, ErrorBoundary = config.ErrorBoundary, contentEditable = config.contentEditable, children } = props; const decorators = useReactDecorators(editor, ErrorBoundary); const configDecorators = react.useMemo(() => rawConfigDecorators.map((decorator, i) => /*#__PURE__*/jsxRuntime.jsx(ErrorBoundary, { onError: e => { editor._onError(e); } // eslint-disable-next-line react/no-array-index-key -- no natural key , children: /*#__PURE__*/jsxRuntime.jsx(react.Suspense, { fallback: null, children: decorator }) }, i)), [ErrorBoundary]); return /*#__PURE__*/jsxRuntime.jsx(LexicalComposerContext.LexicalComposerContext.Provider, { value: context, children: /*#__PURE__*/jsxRuntime.jsxs(EditorChildrenComponent, { context: context, contentEditable: contentEditable, children: [children, configDecorators, decorators] }) }); }; } /** * @example * The default EditorChildrenComponent implementation * ```jsx * return ( * <> * {contentEditable} * {children} * </> * ); * ``` */ function DefaultEditorChildrenComponent({ contentEditable, children }) { return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [contentEditable, children] }); } const initialConfig = { EditorChildrenComponent: DefaultEditorChildrenComponent, ErrorBoundary: LexicalErrorBoundary.LexicalErrorBoundary, contentEditable: /*#__PURE__*/jsxRuntime.jsx(LexicalContentEditable.ContentEditable, {}), decorators: [] }; /** * An extension to use or configure React for use with Lexical. In an editor, you * would typically use {@link LexicalExtensionComposer} (for React projects) or * {@link ReactPluginHostExtension} (to use React Extensions and plug-ins in a non-React * project). * * See {@link ReactConfig} for more detailed exextensionations of how to use * the config for this Extension. * * For an Extension developer, you can defineConfig() override the extension with * decorators to add JSX inside the editor context that is not * location-dependent (e.g. floating UI that does not need to be mounted in * some specific location, or effects that return null). */ const ReactExtension = lexical.defineExtension({ build(editor, config, state) { const providerPeer = state.getPeer(ReactProviderExtension.ReactProviderExtension.name); if (!providerPeer) { { formatDevErrorMessage(`No ReactProviderExtension detected. You must use ReactPluginHostExtension or LexicalExtensionComposer to host React extensions. The following extensions depend on ReactExtension: ${[...state.getDirectDependentNames()].join(' ')}`); } } const context = [editor, { getTheme: () => editor._config.theme }]; const Component = buildEditorComponent(config, context); return { Component, context }; }, config: initialConfig, mergeConfig(a, b) { const config = lexical.shallowMergeConfig(a, b); if (b.decorators) { config.decorators = b.decorators.length > 0 ? [...a.decorators, ...b.decorators] : a.decorators; } return config; }, name: '@lexical/react/React', peerDependencies: [ // We are not trying to avoid the import, just the direct dependency, // so using the extension directly is fine. lexical.declarePeerDependency(ReactProviderExtension.ReactProviderExtension.name)] }); exports.DefaultEditorChildrenComponent = DefaultEditorChildrenComponent; exports.ReactExtension = ReactExtension;