@wordpress/block-editor
Version:
491 lines (483 loc) • 17 kB
JavaScript
"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