UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

190 lines (176 loc) • 7.55 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 { CollaborationContext } from '@lexical/react/LexicalCollaborationContext'; import { LexicalComposerContext, createLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { getTransformSetFromKlass, createSharedNodeState, getRegisteredNode } from 'lexical'; import { useRef, useContext, useMemo, useEffect } from 'react'; import { jsx } from '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. * */ /** * Returns a function that logs `message` with `console.warn` the first time it * is called (and never in production). Creating one has no effect, so the * build annotates module-scope calls and a bundler can drop an unused warning. * * @__NO_SIDE_EFFECTS__ */ /*@__INLINE__*/ function warnOnlyOnce(message) { { let run = false; return () => { if (!run) { console.warn(message); } run = true; }; } } const initialNodesWarning = /* @__PURE__ */warnOnlyOnce(`LexicalNestedComposer initialNodes is deprecated and will be removed in v0.32.0, it has never worked correctly.\nYou can configure your editor's nodes with createEditor({nodes: [], parentEditor: $getEditor()})`); const explicitNamespaceWarning = /* @__PURE__ */warnOnlyOnce(`LexicalNestedComposer initialEditor should explicitly initialize its namespace when the node configuration differs from the parentEditor. For backwards compatibility, the namespace will be initialized from parentEditor until v0.32.0, but this has always had incorrect copy/paste behavior when the configuration differed.\nYou can configure your editor's namespace with createEditor({namespace: 'nested-editor-namespace', nodes: [], parentEditor: $getEditor()}).`); /** * Provides a nested {@link LexicalEditor} (for example the editor that backs a * node such as an image caption or table cell) to its `children` through React * context, the same way {@link LexicalComposer} does for a top-level editor. * The nested editor must be created ahead of time (typically inside the owning * decorator node) and passed as `initialEditor`; by default it inherits the * parent's theme, nodes, namespace, and editable state. * * `LexicalNestedComposer` uses the legacy plugin pattern. To build a nested * editor from extensions, create it with {@link NestedEditorExtension} and * render it with {@link LexicalExtensionEditorComposer} instead. * * @returns A context provider wrapping `children`. */ function LexicalNestedComposer({ initialEditor, children, initialNodes, initialTheme, skipCollabChecks, skipEditableListener }) { const wasCollabPreviouslyReadyRef = useRef(false); const parentContext = useContext(LexicalComposerContext); if (parentContext == null) { { formatDevErrorMessage(`Unexpected parent context null on a nested composer`); } } const [parentEditor, { getTheme: getParentTheme }] = parentContext; const composerContext = useMemo(() => { const composerTheme = initialTheme || getParentTheme() || undefined; const context = createLexicalComposerContext(parentContext, composerTheme); if (composerTheme !== undefined) { // eslint-disable-next-line react-hooks/immutability initialEditor._config.theme = composerTheme; } // eslint-disable-next-line react-hooks/immutability initialEditor._parentEditor = initialEditor._parentEditor || parentEditor; const createEditorArgs = initialEditor._createEditorArgs; const explicitNamespace = createEditorArgs && createEditorArgs.namespace; if (!initialNodes) { if (!(createEditorArgs && createEditorArgs.nodes)) { // eslint-disable-next-line react-hooks/immutability const parentNodes = initialEditor._nodes = new Map(parentEditor._nodes); if (!explicitNamespace) { // This is the only safe situation to inherit the parent's namespace // eslint-disable-next-line react-hooks/immutability initialEditor._config.namespace = parentEditor._config.namespace; } for (const [type, entry] of parentNodes) { initialEditor._nodes.set(type, { exportDOM: entry.exportDOM, klass: entry.klass, replace: entry.replace, replaceWithKlass: entry.replaceWithKlass, sharedNodeState: createSharedNodeState(entry.klass), transforms: getTransformSetFromKlass(entry.klass) }); } } else if (!explicitNamespace) { explicitNamespaceWarning(); // eslint-disable-next-line react-hooks/immutability initialEditor._config.namespace = parentEditor._config.namespace; } } else { initialNodesWarning(); if (!explicitNamespace) { explicitNamespaceWarning(); // eslint-disable-next-line react-hooks/immutability initialEditor._config.namespace = parentEditor._config.namespace; } for (let klass of initialNodes) { let replace = null; let replaceWithKlass = null; if (typeof klass !== 'function') { const options = klass; klass = options.replace; replace = options.with; replaceWithKlass = options.withKlass || null; } const registeredKlass = getRegisteredNode(initialEditor, klass.getType()); initialEditor._nodes.set(klass.getType(), { exportDOM: registeredKlass ? registeredKlass.exportDOM : undefined, klass, replace, replaceWithKlass, sharedNodeState: createSharedNodeState(klass), transforms: getTransformSetFromKlass(klass) }); } } return [initialEditor, context]; }, // We only do this for init // eslint-disable-next-line react-hooks/exhaustive-deps []); // If collaboration is enabled, make sure we don't render the children until the collaboration subdocument is ready. const collabContext = useContext(CollaborationContext); const { isCollabActive, yjsDocMap } = collabContext ?? {}; const isCollabReady = skipCollabChecks || wasCollabPreviouslyReadyRef.current || yjsDocMap && yjsDocMap.has(initialEditor.getKey()); useEffect(() => { if (isCollabReady) { wasCollabPreviouslyReadyRef.current = true; } }, [isCollabReady]); // Update `isEditable` state of nested editor in response to the same change on parent editor. useEffect(() => { if (!skipEditableListener) { const editableListener = editable => initialEditor.setEditable(editable); editableListener(parentEditor.isEditable()); return parentEditor.registerEditableListener(editableListener); } }, [initialEditor, parentEditor, skipEditableListener]); return /*#__PURE__*/jsx(LexicalComposerContext.Provider, { value: composerContext, children: !isCollabActive || isCollabReady ? children : null }); } export { LexicalNestedComposer };