@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
219 lines (197 loc) • 6.94 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.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useLexicalEditable } from '@lexical/react/useLexicalEditable';
import { LexicalBuilder } from '@lexical/extension/LexicalBuilder';
import { ReactProviderExtension } from '@lexical/react/ReactProviderExtension';
import { useMemo, useSyncExternalStore, Suspense, useLayoutEffect, useEffect, useState } from 'react';
import { createPortal } from 'react-dom';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { $canShowPlaceholderCurry } from '@lexical/text';
import { CAN_USE_DOM, mergeRegister } from 'lexical';
import { registerDragonSupport } from '@lexical/dragon';
import { registerRichText } from '@lexical/rich-text';
/**
* 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.
*
*/
function useRootElement(editor) {
const [subscribe, getSnapshot] = useMemo(() => [editor.registerRootListener.bind(editor), editor.getRootElement.bind(editor)], [editor]);
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
}
/**
* 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 useReactDecorators(editor, ErrorBoundary) {
const [subscribe, getSnapshot] = useMemo(() => [cb => editor.registerDecoratorListener(cb), () => editor.getDecorators()], [editor]);
const decorators = useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
const rootElement = useRootElement(editor);
// Return decorators defined as React Portals
return useMemo(() => {
const onError = e => editor._onError(e);
const decoratedPortals = [];
for (const nodeKey in decorators) {
const element = editor.getElementByKey(nodeKey);
if (element !== null) {
const reactDecorator = /*#__PURE__*/jsx(ErrorBoundary, {
onError: onError,
children: /*#__PURE__*/jsx(Suspense, {
fallback: null,
children: decorators[nodeKey]
})
});
decoratedPortals.push(/*#__PURE__*/createPortal(reactDecorator, element, nodeKey));
}
}
return decoratedPortals;
}, [ErrorBoundary, decorators, editor, rootElement]);
}
function isUsingReactExtension(editor) {
const builder = LexicalBuilder.maybeFromEditor(editor);
if (builder && builder.hasExtensionByName(ReactProviderExtension.name)) {
for (const name of ['@lexical/plain-text', '@lexical/rich-text']) {
if (!!builder.hasExtensionByName(name)) {
formatDevErrorMessage(`LexicalBuilder: @lexical/react legacy text plugins conflict with the ${name} extension. Remove the legacy <RichTextPlugin/> or <PlainTextPlugin/> component.`);
}
}
return true;
}
return false;
}
function Decorators({
editor,
ErrorBoundary
}) {
return useReactDecorators(editor, ErrorBoundary);
}
/**
* @internal
*
* When using @lexical/extension, the ReactProvider is expected to handle
* rendering decorators. This component allows RichTextPlugin and
* PlainTextPlugin to be used in extension projects that have not yet
* migrated to use RichTextExtension or PlainTextExtension.
**/
function LegacyDecorators({
editor,
ErrorBoundary
}) {
return isUsingReactExtension(editor) ? null : /*#__PURE__*/jsx(Decorators, {
editor: editor,
ErrorBoundary: ErrorBoundary
});
}
/**
* 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.
*
*/
// This workaround is no longer necessary in React 19,
// but we currently support React >=18.x
// https://github.com/facebook/react/pull/26395
const useLayoutEffectImpl = CAN_USE_DOM ? useLayoutEffect : useEffect;
/**
* 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 canShowPlaceholderFromCurrentEditorState(editor) {
const currentCanShowPlaceholder = editor.read('latest', $canShowPlaceholderCurry(editor.isComposing()));
return currentCanShowPlaceholder;
}
function useCanShowPlaceholder(editor) {
const [canShowPlaceholder, setCanShowPlaceholder] = useState(() => canShowPlaceholderFromCurrentEditorState(editor));
useLayoutEffectImpl(() => {
function resetCanShowPlaceholder() {
const currentCanShowPlaceholder = canShowPlaceholderFromCurrentEditorState(editor);
setCanShowPlaceholder(currentCanShowPlaceholder);
}
resetCanShowPlaceholder();
return mergeRegister(editor.registerUpdateListener(() => {
resetCanShowPlaceholder();
}), editor.registerEditableListener(() => {
resetCanShowPlaceholder();
}));
}, [editor]);
return canShowPlaceholder;
}
/**
* 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 useRichTextSetup(editor) {
useLayoutEffectImpl(() => {
return mergeRegister(registerRichText(editor), registerDragonSupport(editor));
}, [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 RichTextPlugin({
contentEditable,
// TODO Remove. This property is now part of ContentEditable
placeholder = null,
ErrorBoundary
}) {
const [editor] = useLexicalComposerContext();
useRichTextSetup(editor);
return /*#__PURE__*/jsxs(Fragment, {
children: [contentEditable, /*#__PURE__*/jsx(Placeholder, {
content: placeholder
}), /*#__PURE__*/jsx(LegacyDecorators, {
editor: editor,
ErrorBoundary: ErrorBoundary
})]
});
}
// TODO remove
function Placeholder({
content
}) {
const [editor] = useLexicalComposerContext();
const showPlaceholder = useCanShowPlaceholder(editor);
const editable = useLexicalEditable();
if (!showPlaceholder) {
return null;
}
if (typeof content === 'function') {
return content(editable);
} else {
return content;
}
}
export { RichTextPlugin };