UNPKG

@lexical/react

Version:

This package provides Lexical components and hooks for React applications.

244 lines (225 loc) 7.53 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. * */ 'use strict'; var LexicalComposerContext = require('@lexical/react/LexicalComposerContext'); var react = require('react'); var jsxRuntime = require('react/jsx-runtime'); var text = require('@lexical/text'); var utils = require('@lexical/utils'); /** * 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 ? react.useLayoutEffect : react.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. * */ // Source: https://github.com/gregberge/react-merge-refs/blob/main/src/index.tsx function mergeRefs(...refs) { return value => { refs.forEach(ref => { if (typeof ref === 'function') { ref(value); } else if (ref != null) { ref.current = value; } }); }; } /** * 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 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 }, ref) { const [isEditable, setEditable] = react.useState(editor.isEditable()); const handleRef = react.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 = react.useMemo(() => mergeRefs(ref, handleRef), [handleRef, ref]); useLayoutEffectImpl(() => { setEditable(editor.isEditable()); return editor.registerEditableListener(currentIsEditable => { setEditable(currentIsEditable); }); }, [editor]); return /*#__PURE__*/jsxRuntime.jsx("div", { "aria-activedescendant": isEditable ? ariaActiveDescendant : undefined, "aria-autocomplete": isEditable ? ariaAutoComplete : 'none', "aria-controls": isEditable ? ariaControls : undefined, "aria-describedby": ariaDescribedBy // for compat, only override aria-errormessage if ariaErrorMessage is defined , ...(ariaErrorMessage != null ? { 'aria-errormessage': ariaErrorMessage } : {}), "aria-expanded": isEditable && role === 'combobox' ? !!ariaExpanded : undefined // for compat, only override aria-invalid if ariaInvalid is defined , ...(ariaInvalid != null ? { 'aria-invalid': ariaInvalid } : {}), "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-multiline": ariaMultiline, "aria-owns": isEditable ? ariaOwns : undefined, "aria-readonly": isEditable ? undefined : true, "aria-required": ariaRequired, autoCapitalize: autoCapitalize, className: className, contentEditable: isEditable, "data-testid": testid, id: id, ref: mergedRefs, role: isEditable ? role : undefined, spellCheck: spellCheck, style: style, tabIndex: tabIndex, ...rest }); } const ContentEditableElement = /*#__PURE__*/react.forwardRef(ContentEditableElementImpl); /** * 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(text.$canShowPlaceholderCurry(editor.isComposing())); return currentCanShowPlaceholder; } function useCanShowPlaceholder(editor) { const [canShowPlaceholder, setCanShowPlaceholder] = react.useState(() => canShowPlaceholderFromCurrentEditorState(editor)); useLayoutEffectImpl(() => { function resetCanShowPlaceholder() { const currentCanShowPlaceholder = canShowPlaceholderFromCurrentEditorState(editor); setCanShowPlaceholder(currentCanShowPlaceholder); } resetCanShowPlaceholder(); return utils.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. * */ /** * @deprecated This type has been renamed to `ContentEditableProps` to provide a clearer and more descriptive name. * For backward compatibility, this type is still exported as `Props`, but it is recommended to migrate to using `ContentEditableProps` instead. * * @note This alias is maintained for compatibility purposes but may be removed in future versions. * Please update your codebase to use `ContentEditableProps` to ensure long-term maintainability. */ const ContentEditable = /*#__PURE__*/react.forwardRef(ContentEditableImpl); function ContentEditableImpl(props, ref) { const { placeholder, ...rest } = props; const [editor] = LexicalComposerContext.useLexicalComposerContext(); return /*#__PURE__*/jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [/*#__PURE__*/jsxRuntime.jsx(ContentEditableElement, { editor: editor, ...rest, ref: ref }), placeholder != null && /*#__PURE__*/jsxRuntime.jsx(Placeholder, { editor: editor, content: placeholder })] }); } function Placeholder({ content, editor }) { const showPlaceholder = useCanShowPlaceholder(editor); const [isEditable, setEditable] = react.useState(editor.isEditable()); react.useLayoutEffect(() => { setEditable(editor.isEditable()); return editor.registerEditableListener(currentIsEditable => { setEditable(currentIsEditable); }); }, [editor]); if (!showPlaceholder) { return null; } let placeholder = null; if (typeof content === 'function') { placeholder = content(isEditable); } else if (content !== null) { placeholder = content; } if (placeholder === null) { return null; } return /*#__PURE__*/jsxRuntime.jsx("div", { "aria-hidden": true, children: placeholder }); } exports.ContentEditable = ContentEditable; exports.ContentEditableElement = ContentEditableElement;