UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

219 lines (197 loc) • 6.94 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. * */ 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 };