UNPKG

@wordpress/block-editor

Version:
491 lines (483 loc) 17 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.PrivateRichText = void 0; Object.defineProperty(exports, "RichTextShortcut", { enumerable: true, get: function () { return _shortcut.RichTextShortcut; } }); Object.defineProperty(exports, "RichTextToolbarButton", { enumerable: true, get: function () { return _toolbarButton.RichTextToolbarButton; } }); exports.RichTextWrapper = RichTextWrapper; Object.defineProperty(exports, "__unstableRichTextInputEvent", { enumerable: true, get: function () { return _inputEvent.__unstableRichTextInputEvent; } }); exports.keyboardShortcutContext = exports.inputEventContext = exports.default = void 0; var _clsx = _interopRequireDefault(require("clsx")); var _element = require("@wordpress/element"); var _data = require("@wordpress/data"); var _compose = require("@wordpress/compose"); var _richText = require("@wordpress/rich-text"); var _components = require("@wordpress/components"); var _blocks = require("@wordpress/blocks"); var _deprecated = _interopRequireDefault(require("@wordpress/deprecated")); var _i18n = require("@wordpress/i18n"); var _autocomplete = require("../autocomplete"); var _blockEdit = require("../block-edit"); var _context = require("../block-edit/context"); var _formatToolbarContainer = _interopRequireDefault(require("./format-toolbar-container")); var _store = require("../../store"); var _useMarkPersistent = require("./use-mark-persistent"); var _useFormatTypes = require("./use-format-types"); var _eventListeners = require("./event-listeners"); var _formatEdit = _interopRequireDefault(require("./format-edit")); var _utils = require("./utils"); var _content = require("./content"); var _withDeprecations = require("./with-deprecations"); var _blockBindings = require("../../utils/block-bindings"); var _blockContext = _interopRequireDefault(require("../block-context")); var _jsxRuntime = require("react/jsx-runtime"); var _shortcut = require("./shortcut"); var _toolbarButton = require("./toolbar-button"); var _inputEvent = require("./input-event"); /** * External dependencies */ /** * WordPress dependencies */ /** * Internal dependencies */ const keyboardShortcutContext = exports.keyboardShortcutContext = (0, _element.createContext)(); const inputEventContext = exports.inputEventContext = (0, _element.createContext)(); const instanceIdKey = Symbol('instanceId'); /** * Removes props used for the native version of RichText so that they are not * passed to the DOM element and log warnings. * * @param {Object} props Props to filter. * * @return {Object} Filtered props. */ 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) { (0, _deprecated.default)('wp.blockEditor.RichText onSplit prop', { since: '6.4', alternative: 'block.json support key: "splitting"' }); } const instanceId = (0, _compose.useInstanceId)(RichTextWrapper); const anchorRef = (0, _element.useRef)(); const context = (0, _blockEdit.useBlockEditContext)(); const { clientId, isSelected: isBlockSelected, name: blockName } = context; const blockBindings = context[_context.blockBindingsKey]; const blockContext = (0, _element.useContext)(_blockContext.default); const registry = (0, _data.useRegistry)(); const selector = select => { // Avoid subscribing to the block editor store if the block is not // selected. if (!isBlockSelected) { return { isSelected: false }; } const { getSelectionStart, getSelectionEnd } = select(_store.store); const selectionStart = getSelectionStart(); const selectionEnd = getSelectionEnd(); let isSelected; if (originalIsSelected === undefined) { isSelected = selectionStart.clientId === clientId && selectionEnd.clientId === clientId && (identifier ? selectionStart.attributeKey === identifier : selectionStart[instanceIdKey] === instanceId); } else if (originalIsSelected) { isSelected = selectionStart.clientId === clientId; } return { selectionStart: isSelected ? selectionStart.offset : undefined, selectionEnd: isSelected ? selectionEnd.offset : undefined, isSelected }; }; const { selectionStart, selectionEnd, isSelected } = (0, _data.useSelect)(selector, [clientId, identifier, instanceId, originalIsSelected, isBlockSelected]); const { disableBoundBlock, bindingsPlaceholder, bindingsLabel } = (0, _data.useSelect)(select => { var _fieldsList$relatedBi; if (!blockBindings?.[identifier] || !(0, _blockBindings.canBindBlock)(blockName)) { return {}; } const relatedBinding = blockBindings[identifier]; const blockBindingsSource = (0, _blocks.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 }); // Don't modify placeholders if value is not empty. 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(_store.store); const blockAttributes = getBlockAttributes(clientId); const fieldsList = blockBindingsSource?.getFieldsList?.({ select, context: blockBindingsContext }); const bindingKey = (_fieldsList$relatedBi = fieldsList?.[relatedBinding?.args?.key]?.label) !== null && _fieldsList$relatedBi !== void 0 ? _fieldsList$relatedBi : blockBindingsSource?.label; const _bindingsPlaceholder = _disableBoundBlock ? bindingKey : (0, _i18n.sprintf)(/* translators: %s: connected field label or source label */ (0, _i18n.__)('Add %s'), bindingKey); const _bindingsLabel = _disableBoundBlock ? relatedBinding?.args?.key || blockBindingsSource?.label : (0, _i18n.sprintf)(/* translators: %s: source label or key */ (0, _i18n.__)('Empty %s; start writing to edit its value'), relatedBinding?.args?.key || blockBindingsSource?.label); return { disableBoundBlock: _disableBoundBlock, bindingsPlaceholder: blockAttributes?.placeholder || _bindingsPlaceholder, bindingsLabel: _bindingsLabel }; }, [blockBindings, identifier, blockName, adjustedValue, clientId, blockContext]); const shouldDisableEditing = readOnly || disableBoundBlock; const { getSelectionStart, getSelectionEnd, getBlockRootClientId } = (0, _data.useSelect)(_store.store); const { selectionChange } = (0, _data.useDispatch)(_store.store); const adjustedAllowedFormats = (0, _utils.getAllowedFormats)({ allowedFormats, disableFormats }); const hasFormats = !adjustedAllowedFormats || adjustedAllowedFormats.length > 0; const onSelectionChange = (0, _element.useCallback)((start, end) => { const selection = {}; const unset = start === undefined && end === undefined; const baseSelection = { clientId, [identifier ? 'attributeKey' : instanceIdKey]: identifier ? identifier : instanceId }; if (typeof start === 'number' || unset) { // If we are only setting the start (or the end below), which // means a partial selection, and we're not updating a selection // with the same client ID, abort. This means the selected block // is a parent block. if (end === undefined && getBlockRootClientId(clientId) !== getBlockRootClientId(getSelectionEnd().clientId)) { return; } selection.start = { ...baseSelection, offset: start }; } if (typeof end === 'number' || unset) { if (start === undefined && 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 } = (0, _useFormatTypes.useFormatTypes)({ clientId, identifier, withoutInteractiveFormatting, allowedFormats: adjustedAllowedFormats }); function addEditorOnlyFormats(value) { return valueHandlers.reduce((accumulator, fn) => fn(accumulator, value.text), value.formats); } function removeEditorOnlyFormats(value) { formatTypes.forEach(formatType => { // Remove formats created by prepareEditableTree, because they are editor only. if (formatType.__experimentalCreatePrepareEditableTree) { value = (0, _richText.removeFormat)(value, formatType.name, 0, value.text.length); } }); return value.formats; } function addInvisibleFormats(value) { return prepareHandlers.reduce((accumulator, fn) => fn(accumulator, value.text), value.formats); } const { value, getValue, onChange, ref: richTextRef } = (0, _richText.__unstableUseRichText)({ 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 = (0, _autocomplete.useBlockEditorAutocompleteProps)({ onReplace, completers: autocompleters, record: value, onChange }); (0, _useMarkPersistent.useMarkPersistent)({ html: adjustedValue, value }); const keyboardShortcuts = (0, _element.useRef)(new Set()); const inputEvents = (0, _element.useRef)(new Set()); function onFocus() { anchorRef.current?.focus(); } const TagName = tagName; return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, { children: [isSelected && /*#__PURE__*/(0, _jsxRuntime.jsx)(keyboardShortcutContext.Provider, { value: keyboardShortcuts, children: /*#__PURE__*/(0, _jsxRuntime.jsx)(inputEventContext.Provider, { value: inputEvents, children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_components.Popover.__unstableSlotNameProvider, { value: "__unstable-block-tools-after", children: [children && children({ value, onChange, onFocus }), /*#__PURE__*/(0, _jsxRuntime.jsx)(_formatEdit.default, { value: value, onChange: onChange, onFocus: onFocus, formatTypes: formatTypes, forwardedRef: anchorRef })] }) }) }), isSelected && hasFormats && /*#__PURE__*/(0, _jsxRuntime.jsx)(_formatToolbarContainer.default, { inline: inlineToolbar, editableContentElement: anchorRef.current }), /*#__PURE__*/(0, _jsxRuntime.jsx)(TagName // Overridable props. , { role: "textbox", "aria-multiline": !disableLineBreaks, "aria-readonly": shouldDisableEditing, ...props, // Unset draggable (coming from block props) for contentEditable // elements because it will interfere with multi block selection // when the contentEditable and draggable elements are the same // element. draggable: undefined, "aria-label": bindingsLabel || props['aria-label'] || placeholder, ...autocompleteProps, ref: (0, _compose.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, (0, _eventListeners.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: (0, _clsx.default)('block-editor-rich-text__editable', props.className, 'rich-text') // Setting tabIndex to 0 is unnecessary, the element is already // focusable because it's contentEditable. This also fixes a // Safari bug where it's not possible to Shift+Click multi // select blocks when Shift Clicking into an element with // tabIndex because Safari will focus the element. However, // Safari will correctly ignore nested contentEditable elements. , tabIndex: props.tabIndex === 0 && !shouldDisableEditing ? null : props.tabIndex, "data-wp-block-attribute-key": identifier })] }); } // This is the private API for the RichText component. // It allows access to all props, not just the public ones. const PrivateRichText = exports.PrivateRichText = (0, _withDeprecations.withDeprecations)((0, _element.forwardRef)(RichTextWrapper)); PrivateRichText.Content = _content.Content; PrivateRichText.isEmpty = value => { return !value || value.length === 0; }; // This is the public API for the RichText component. // We wrap the PrivateRichText component to hide some props from the public API. /** * @see https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/rich-text/README.md */ const PublicForwardedRichTextContainer = (0, _element.forwardRef)((props, ref) => { const context = (0, _blockEdit.useBlockEditContext)(); const isPreviewMode = context[_context.isPreviewModeKey]; if (isPreviewMode) { // Remove all non-content props. 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__*/(0, _jsxRuntime.jsx)(Tag, { ...contentProps, dangerouslySetInnerHTML: { __html: (0, _content.valueToHTMLString)(value, multiline) } }); } return /*#__PURE__*/(0, _jsxRuntime.jsx)(PrivateRichText, { ref: ref, ...props, readOnly: false }); }); PublicForwardedRichTextContainer.Content = _content.Content; PublicForwardedRichTextContainer.isEmpty = value => { return !value || value.length === 0; }; var _default = exports.default = PublicForwardedRichTextContainer; //# sourceMappingURL=index.js.map