@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
188 lines (175 loc) • 6.4 kB
JavaScript
/**
* 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;