@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
179 lines (159 loc) • 5.62 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 { $canShowPlaceholderCurry } from '@lexical/text';
import { mergeRegister } from '@lexical/utils';
import { useLayoutEffect, useEffect, useState, useMemo, Suspense } from 'react';
import { flushSync, createPortal } from 'react-dom';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { registerDragonSupport } from '@lexical/dragon';
import { registerPlainText } from '@lexical/plain-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.
*
*/
const CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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 >=17.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.getEditorState().read($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 useDecorators(editor, ErrorBoundary) {
const [decorators, setDecorators] = useState(() => editor.getDecorators());
// Subscribe to changes
useLayoutEffectImpl(() => {
return editor.registerDecoratorListener(nextDecorators => {
flushSync(() => {
setDecorators(nextDecorators);
});
});
}, [editor]);
useEffect(() => {
// If the content editable mounts before the subscription is added, then
// nothing will be rendered on initial pass. We can get around that by
// ensuring that we set the value.
setDecorators(editor.getDecorators());
}, [editor]);
// Return decorators defined as React Portals
return useMemo(() => {
const decoratedPortals = [];
const decoratorKeys = Object.keys(decorators);
for (let i = 0; i < decoratorKeys.length; i++) {
const nodeKey = decoratorKeys[i];
const reactDecorator = /*#__PURE__*/jsx(ErrorBoundary, {
onError: e => editor._onError(e),
children: /*#__PURE__*/jsx(Suspense, {
fallback: null,
children: decorators[nodeKey]
})
});
const element = editor.getElementByKey(nodeKey);
if (element !== null) {
decoratedPortals.push(/*#__PURE__*/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 usePlainTextSetup(editor) {
useLayoutEffectImpl(() => {
return mergeRegister(registerPlainText(editor), registerDragonSupport(editor));
// We only do this for init
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [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 PlainTextPlugin({
contentEditable,
// TODO Remove. This property is now part of ContentEditable
placeholder = null,
ErrorBoundary
}) {
const [editor] = useLexicalComposerContext();
const decorators = useDecorators(editor, ErrorBoundary);
usePlainTextSetup(editor);
return /*#__PURE__*/jsxs(Fragment, {
children: [contentEditable, /*#__PURE__*/jsx(Placeholder, {
content: placeholder
}), decorators]
});
}
// 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 { PlainTextPlugin };