UNPKG

@wordpress/block-editor

Version:
474 lines (473 loc) 15.3 kB
// packages/block-editor/src/components/rich-text/index.js import clsx from "clsx"; import fastDeepEqual from "fast-deep-equal/es6"; import { useRef, useCallback, forwardRef, createContext, useContext } from "@wordpress/element"; import { useDispatch, useRegistry, useSelect } from "@wordpress/data"; import { useMergeRefs, useInstanceId } from "@wordpress/compose"; import { __unstableUseRichText as useRichText, removeFormat } from "@wordpress/rich-text"; import { Popover } from "@wordpress/components"; import { getBlockBindingsSource } from "@wordpress/blocks"; import deprecated from "@wordpress/deprecated"; import { __, sprintf } from "@wordpress/i18n"; import { useBlockEditorAutocompleteProps } from "../autocomplete"; import { useBlockEditContext } from "../block-edit"; import { blockBindingsKey, isPreviewModeKey } from "../block-edit/context"; import FormatToolbarContainer from "./format-toolbar-container"; import { store as blockEditorStore } from "../../store"; import { useMarkPersistent } from "./use-mark-persistent"; import { useFormatTypes } from "./use-format-types"; import { useEventListeners } from "./event-listeners"; import FormatEdit from "./format-edit"; import { getAllowedFormats } from "./utils"; import { Content, valueToHTMLString } from "./content"; import { withDeprecations } from "./with-deprecations"; import BlockContext from "../block-context"; import { PrivateBlockContext } from "../block-list/private-block-context"; import { RichTextShortcut } from "./shortcut"; import { RichTextToolbarButton } from "./toolbar-button"; import { __unstableRichTextInputEvent } from "./input-event"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; var keyboardShortcutContext = createContext(); keyboardShortcutContext.displayName = "keyboardShortcutContext"; var inputEventContext = createContext(); inputEventContext.displayName = "inputEventContext"; var instanceIdKey = Symbol("instanceId"); function removeNativeProps(props) { const { __unstableMobileNoFocusOnMount, deleteEnter, placeholderTextColor, textAlign, selectionColor, tagsToEliminate, disableEditingMenu, fontSize, fontFamily, fontWeight, fontStyle, minWidth, maxWidth, disableSuggestions, disableAutocorrection, ...restProps } = props; return restProps; } function RichTextWrapper({ children, tagName = "div", value: adjustedValue = "", onChange: adjustedOnChange, isSelected: originalIsSelected, multiline, inlineToolbar, wrapperClassName, autocompleters, onReplace, placeholder, allowedFormats, withoutInteractiveFormatting, onRemove, onMerge, onSplit, __unstableOnSplitAtEnd: onSplitAtEnd, __unstableOnSplitAtDoubleLineEnd: onSplitAtDoubleLineEnd, identifier, preserveWhiteSpace, __unstablePastePlainText: pastePlainText, __unstableEmbedURLOnPaste, __unstableDisableFormats: disableFormats, disableLineBreaks, __unstableAllowPrefixTransformations, readOnly, ...props }, forwardedRef) { props = removeNativeProps(props); if (onSplit) { deprecated("wp.blockEditor.RichText onSplit prop", { since: "6.4", alternative: 'block.json support key: "splitting"' }); } const instanceId = useInstanceId(RichTextWrapper); const anchorRef = useRef(); const context = useBlockEditContext(); const { clientId, isSelected: isBlockSelected } = context; const blockBindings = context[blockBindingsKey]; const blockContext = useContext(BlockContext); const { bindableAttributes } = useContext(PrivateBlockContext); const registry = useRegistry(); const selector = (select) => { if (!isBlockSelected) { return { isSelected: false }; } const { getSelectionStart: getSelectionStart2, getSelectionEnd: getSelectionEnd2, getBlockEditingMode } = select(blockEditorStore); const selectionStart2 = getSelectionStart2(); const selectionEnd2 = getSelectionEnd2(); let isSelected2; if (originalIsSelected === void 0) { isSelected2 = selectionStart2.clientId === clientId && selectionEnd2.clientId === clientId && (identifier ? selectionStart2.attributeKey === identifier : selectionStart2[instanceIdKey] === instanceId); } else if (originalIsSelected) { isSelected2 = selectionStart2.clientId === clientId; } return { selectionStart: isSelected2 ? selectionStart2.offset : void 0, selectionEnd: isSelected2 ? selectionEnd2.offset : void 0, isSelected: isSelected2, isContentOnly: getBlockEditingMode(clientId) === "contentOnly" }; }; const { selectionStart, selectionEnd, isSelected, isContentOnly } = useSelect(selector, [ clientId, identifier, instanceId, originalIsSelected, isBlockSelected ]); const { disableBoundBlock, bindingsPlaceholder, bindingsLabel } = useSelect( (select) => { if (!blockBindings?.[identifier] || !bindableAttributes) { return {}; } const relatedBinding = blockBindings[identifier]; const blockBindingsSource = getBlockBindingsSource( relatedBinding.source ); const blockBindingsContext = {}; if (blockBindingsSource?.usesContext?.length) { for (const key of blockBindingsSource.usesContext) { blockBindingsContext[key] = blockContext[key]; } } const _disableBoundBlock = !blockBindingsSource?.canUserEditValue?.({ select, context: blockBindingsContext, args: relatedBinding.args }); if (adjustedValue.length > 0) { return { disableBoundBlock: _disableBoundBlock, // Null values will make them fall back to the default behavior. bindingsPlaceholder: null, bindingsLabel: null }; } const { getBlockAttributes } = select(blockEditorStore); const blockAttributes = getBlockAttributes(clientId); let clientSideFieldLabel = null; if (blockBindingsSource?.getFieldsList) { const fieldsItems = blockBindingsSource.getFieldsList({ select, context: blockBindingsContext }); clientSideFieldLabel = fieldsItems?.find( (item) => fastDeepEqual(item.args, relatedBinding?.args) )?.label; } const bindingKey = clientSideFieldLabel ?? blockBindingsSource?.label; const _bindingsPlaceholder = _disableBoundBlock ? bindingKey : sprintf( /* translators: %s: connected field label or source label */ __("Add %s"), bindingKey ); const _bindingsLabel = _disableBoundBlock ? relatedBinding?.args?.key || blockBindingsSource?.label : sprintf( /* translators: %s: source label or key */ __("Empty %s; start writing to edit its value"), relatedBinding?.args?.key || blockBindingsSource?.label ); return { disableBoundBlock: _disableBoundBlock, bindingsPlaceholder: blockAttributes?.placeholder || _bindingsPlaceholder, bindingsLabel: _bindingsLabel }; }, [ blockBindings, identifier, bindableAttributes, adjustedValue, clientId, blockContext ] ); const isInsidePatternOverrides = !!blockContext?.["pattern/overrides"]; const hasOverrideEnabled = blockBindings?.__default?.source === "core/pattern-overrides"; const shouldDisableForPattern = isInsidePatternOverrides && !hasOverrideEnabled; const shouldDisableEditing = readOnly || disableBoundBlock || shouldDisableForPattern; const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = useSelect(blockEditorStore); const { selectionChange } = useDispatch(blockEditorStore); const adjustedAllowedFormats = getAllowedFormats({ allowedFormats, disableFormats }); const hasFormats = !adjustedAllowedFormats || adjustedAllowedFormats.length > 0; const onSelectionChange = useCallback( (start, end) => { const selection = {}; const unset = start === void 0 && end === void 0; const baseSelection = { clientId, [identifier ? "attributeKey" : instanceIdKey]: identifier ? identifier : instanceId }; if (typeof start === "number" || unset) { if (end === void 0 && getBlockRootClientId(clientId) !== getBlockRootClientId(getSelectionEnd().clientId)) { return; } selection.start = { ...baseSelection, offset: start }; } if (typeof end === "number" || unset) { if (start === void 0 && getBlockRootClientId(clientId) !== getBlockRootClientId(getSelectionStart().clientId)) { return; } selection.end = { ...baseSelection, offset: end }; } selectionChange(selection); }, [ clientId, getBlockRootClientId, getSelectionEnd, getSelectionStart, identifier, instanceId, selectionChange ] ); const { formatTypes, prepareHandlers, valueHandlers, changeHandlers, dependencies } = useFormatTypes({ clientId, identifier, allowedFormats: adjustedAllowedFormats, withoutInteractiveFormatting, disableNoneEssentialFormatting: isContentOnly }); function addEditorOnlyFormats(value2) { return valueHandlers.reduce( (accumulator, fn) => fn(accumulator, value2.text), value2.formats ); } function removeEditorOnlyFormats(value2) { formatTypes.forEach((formatType) => { if (formatType.__experimentalCreatePrepareEditableTree) { value2 = removeFormat( value2, formatType.name, 0, value2.text.length ); } }); return value2.formats; } function addInvisibleFormats(value2) { return prepareHandlers.reduce( (accumulator, fn) => fn(accumulator, value2.text), value2.formats ); } const { value, getValue, onChange, ref: richTextRef } = useRichText({ value: adjustedValue, onChange(html, { __unstableFormats, __unstableText }) { adjustedOnChange(html); Object.values(changeHandlers).forEach((changeHandler) => { changeHandler(__unstableFormats, __unstableText); }); }, selectionStart, selectionEnd, onSelectionChange, placeholder: bindingsPlaceholder || placeholder, __unstableIsSelected: isSelected, __unstableDisableFormats: disableFormats, preserveWhiteSpace, __unstableDependencies: [...dependencies, tagName], __unstableAfterParse: addEditorOnlyFormats, __unstableBeforeSerialize: removeEditorOnlyFormats, __unstableAddInvisibleFormats: addInvisibleFormats }); const autocompleteProps = useBlockEditorAutocompleteProps({ onReplace, completers: autocompleters, record: value, onChange }); useMarkPersistent({ html: adjustedValue, value }); const keyboardShortcuts = useRef(/* @__PURE__ */ new Set()); const inputEvents = useRef(/* @__PURE__ */ new Set()); function onFocus() { anchorRef.current?.focus(); } const TagName = tagName; return /* @__PURE__ */ jsxs(Fragment, { children: [ isSelected && /* @__PURE__ */ jsx(keyboardShortcutContext.Provider, { value: keyboardShortcuts, children: /* @__PURE__ */ jsx(inputEventContext.Provider, { value: inputEvents, children: /* @__PURE__ */ jsxs(Popover.__unstableSlotNameProvider, { value: "__unstable-block-tools-after", children: [ children && children({ value, onChange, onFocus }), /* @__PURE__ */ jsx( FormatEdit, { value, onChange, onFocus, formatTypes, forwardedRef: anchorRef } ) ] }) }) }), isSelected && hasFormats && /* @__PURE__ */ jsx( FormatToolbarContainer, { inline: inlineToolbar, editableContentElement: anchorRef.current } ), /* @__PURE__ */ jsx( TagName, { role: "textbox", "aria-multiline": !disableLineBreaks, "aria-readonly": shouldDisableEditing, ...props, draggable: void 0, "aria-label": bindingsLabel || props["aria-label"] || placeholder, ...autocompleteProps, ref: useMergeRefs([ // Rich text ref must be first because its focus listener // must be set up before any other ref calls .focus() on // mount. richTextRef, forwardedRef, autocompleteProps.ref, props.ref, useEventListeners({ registry, getValue, onChange, __unstableAllowPrefixTransformations, formatTypes, onReplace, selectionChange, isSelected, disableFormats, value, tagName, onSplit, __unstableEmbedURLOnPaste, pastePlainText, onMerge, onRemove, removeEditorOnlyFormats, disableLineBreaks, onSplitAtEnd, onSplitAtDoubleLineEnd, keyboardShortcuts, inputEvents }), anchorRef ]), contentEditable: !shouldDisableEditing, suppressContentEditableWarning: true, className: clsx( "block-editor-rich-text__editable", props.className, "rich-text" ), tabIndex: props.tabIndex === 0 && !shouldDisableEditing ? null : props.tabIndex, "data-wp-block-attribute-key": identifier } ) ] }); } var PrivateRichText = withDeprecations( forwardRef(RichTextWrapper) ); PrivateRichText.Content = Content; PrivateRichText.isEmpty = (value) => { return !value || value.length === 0; }; var PublicForwardedRichTextContainer = forwardRef((props, ref) => { const context = useBlockEditContext(); const isPreviewMode = context[isPreviewModeKey]; if (isPreviewMode) { const { children, tagName: Tag = "div", value, onChange, isSelected, multiline, inlineToolbar, wrapperClassName, autocompleters, onReplace, placeholder, allowedFormats, withoutInteractiveFormatting, onRemove, onMerge, onSplit, __unstableOnSplitAtEnd, __unstableOnSplitAtDoubleLineEnd, identifier, preserveWhiteSpace, __unstablePastePlainText, __unstableEmbedURLOnPaste, __unstableDisableFormats, disableLineBreaks, __unstableAllowPrefixTransformations, readOnly, ...contentProps } = removeNativeProps(props); return /* @__PURE__ */ jsx( Tag, { ref, ...contentProps, dangerouslySetInnerHTML: { __html: valueToHTMLString(value, multiline) } } ); } return /* @__PURE__ */ jsx(PrivateRichText, { ref, ...props, readOnly: false }); }); PublicForwardedRichTextContainer.Content = Content; PublicForwardedRichTextContainer.isEmpty = (value) => { return !value || value.length === 0; }; var rich_text_default = PublicForwardedRichTextContainer; export { PrivateRichText, RichTextShortcut, RichTextToolbarButton, RichTextWrapper, __unstableRichTextInputEvent, rich_text_default as default, inputEventContext, keyboardShortcutContext }; //# sourceMappingURL=index.js.map