/** * 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 type {LexicalEditor} from 'lexical'; import type {ForwardedRef, JSX, RefCallback} from 'react'; import * as React from 'react'; import {forwardRef, useCallback, useMemo, useState} from 'react'; import {mergeRefs} from './mergeRefs'; import useLayoutEffect from './useLayoutEffect'; export type ContentEditableElementProps = { editor: LexicalEditor; ariaActiveDescendant?: React.AriaAttributes['aria-activedescendant']; ariaAutoComplete?: React.AriaAttributes['aria-autocomplete']; ariaControls?: React.AriaAttributes['aria-controls']; ariaDescribedBy?: React.AriaAttributes['aria-describedby']; ariaErrorMessage?: React.AriaAttributes['aria-errormessage']; ariaExpanded?: React.AriaAttributes['aria-expanded']; ariaInvalid?: React.AriaAttributes['aria-invalid']; ariaLabel?: React.AriaAttributes['aria-label']; ariaLabelledBy?: React.AriaAttributes['aria-labelledby']; ariaMultiline?: React.AriaAttributes['aria-multiline']; ariaOwns?: React.AriaAttributes['aria-owns']; ariaRequired?: React.AriaAttributes['aria-required']; autoCapitalize?: HTMLDivElement['autocapitalize']; 'data-testid'?: string | null | undefined; } & Omit, 'placeholder'>; function ContentEditableElementImpl( { editor, ariaActiveDescendant, ariaAutoComplete, ariaControls, ariaDescribedBy, ariaErrorMessage, ariaExpanded, ariaInvalid, ariaLabel, ariaLabelledBy, ariaMultiline, ariaOwns, ariaRequired, autoCapitalize, className, id, role = 'textbox', spellCheck = true, style, tabIndex, 'data-testid': testid, ...rest }: ContentEditableElementProps, ref: ForwardedRef, ): JSX.Element { const [isEditable, setEditable] = useState(editor.isEditable()); const handleRef = useCallback>( rootElement => { // defaultView is required for a root element. // In multi-window setups, the defaultView may not exist at certain points. if ( rootElement && rootElement.ownerDocument && rootElement.ownerDocument.defaultView ) { editor.setRootElement(rootElement); } else { editor.setRootElement(null); } }, [editor], ); const mergedRefs = useMemo(() => mergeRefs(ref, handleRef), [handleRef, ref]); useLayoutEffect(() => { setEditable(editor.isEditable()); return editor.registerEditableListener(currentIsEditable => { setEditable(currentIsEditable); }); }, [editor]); return (
); } export const ContentEditableElement = forwardRef(ContentEditableElementImpl);