@wordpress/block-editor
Version:
614 lines (613 loc) • 20.9 kB
JavaScript
// packages/block-editor/src/components/block-list/block.js
import clsx from "clsx";
import { memo, RawHTML, useContext, useMemo } from "@wordpress/element";
import {
getBlockType,
getSaveContent,
isUnmodifiedDefaultBlock,
serializeRawBlock,
switchToBlockType,
getDefaultBlockName,
isUnmodifiedBlock,
isReusableBlock,
getBlockDefaultClassName,
hasBlockSupport,
createBlock,
store as blocksStore
} from "@wordpress/blocks";
import { withFilters } from "@wordpress/components";
import { withDispatch, useSelect } from "@wordpress/data";
import { compose } from "@wordpress/compose";
import { safeHTML } from "@wordpress/dom";
import BlockEdit from "../block-edit";
import BlockInvalidWarning from "./block-invalid-warning";
import BlockCrashWarning from "./block-crash-warning";
import BlockCrashBoundary from "./block-crash-boundary";
import BlockHtml from "./block-html";
import { useBlockProps } from "./use-block-props";
import { store as blockEditorStore } from "../../store";
import { useLayout } from "./layout";
import { PrivateBlockContext } from "./private-block-context";
import { unlock } from "../../lock-unlock";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
function mergeWrapperProps(propsA, propsB) {
const newProps = {
...propsA,
...propsB
};
if (propsA?.hasOwnProperty("className") && propsB?.hasOwnProperty("className")) {
newProps.className = clsx(propsA.className, propsB.className);
}
if (propsA?.hasOwnProperty("style") && propsB?.hasOwnProperty("style")) {
newProps.style = { ...propsA.style, ...propsB.style };
}
return newProps;
}
function Block({ children, isHtml, ...props }) {
return /* @__PURE__ */ jsx("div", { ...useBlockProps(props, { __unstableIsHtml: isHtml }), children });
}
function BlockListBlock({
block: { __unstableBlockSource },
mode,
isLocked,
canRemove,
clientId,
isSelected,
isSelectionEnabled,
className,
__unstableLayoutClassNames: layoutClassNames,
name,
isValid,
attributes,
wrapperProps,
setAttributes,
onReplace,
onRemove,
onInsertBlocksAfter,
onMerge,
toggleSelection
}) {
const {
mayDisplayControls,
mayDisplayParentControls,
themeSupportsLayout,
...context
} = useContext(PrivateBlockContext);
const parentLayout = useLayout() || {};
let blockEdit = /* @__PURE__ */ jsx(
BlockEdit,
{
name,
isSelected,
attributes,
setAttributes,
insertBlocksAfter: isLocked ? void 0 : onInsertBlocksAfter,
onReplace: canRemove ? onReplace : void 0,
onRemove: canRemove ? onRemove : void 0,
mergeBlocks: canRemove ? onMerge : void 0,
clientId,
isSelectionEnabled,
toggleSelection,
__unstableLayoutClassNames: layoutClassNames,
__unstableParentLayout: Object.keys(parentLayout).length ? parentLayout : void 0,
mayDisplayControls,
mayDisplayParentControls,
blockEditingMode: context.blockEditingMode,
isPreviewMode: context.isPreviewMode
}
);
const blockType = getBlockType(name);
if (blockType?.getEditWrapperProps) {
wrapperProps = mergeWrapperProps(
wrapperProps,
blockType.getEditWrapperProps(attributes)
);
}
const isAligned = wrapperProps && !!wrapperProps["data-align"] && !themeSupportsLayout;
const isSticky = className?.includes("is-position-sticky");
if (isAligned) {
blockEdit = /* @__PURE__ */ jsx(
"div",
{
className: clsx("wp-block", isSticky && className),
"data-align": wrapperProps["data-align"],
children: blockEdit
}
);
}
let block;
if (!isValid) {
const saveContent = __unstableBlockSource ? serializeRawBlock(__unstableBlockSource) : getSaveContent(blockType, attributes);
block = /* @__PURE__ */ jsxs(Block, { className: "has-warning", children: [
/* @__PURE__ */ jsx(BlockInvalidWarning, { clientId }),
/* @__PURE__ */ jsx(RawHTML, { children: safeHTML(saveContent) })
] });
} else if (mode === "html") {
block = /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx("div", { style: { display: "none" }, children: blockEdit }),
/* @__PURE__ */ jsx(Block, { isHtml: true, children: /* @__PURE__ */ jsx(BlockHtml, { clientId }) })
] });
} else if (blockType?.apiVersion > 1) {
block = blockEdit;
} else {
block = /* @__PURE__ */ jsx(Block, { children: blockEdit });
}
const { "data-align": dataAlign, ...restWrapperProps } = wrapperProps ?? {};
const updatedWrapperProps = {
...restWrapperProps,
className: clsx(
restWrapperProps.className,
dataAlign && themeSupportsLayout && `align${dataAlign}`,
!(dataAlign && isSticky) && className
)
};
return /* @__PURE__ */ jsx(
PrivateBlockContext.Provider,
{
value: {
wrapperProps: updatedWrapperProps,
isAligned,
...context
},
children: /* @__PURE__ */ jsx(
BlockCrashBoundary,
{
fallback: /* @__PURE__ */ jsx(Block, { className: "has-warning", children: /* @__PURE__ */ jsx(BlockCrashWarning, {}) }),
children: block
}
)
}
);
}
var applyWithDispatch = withDispatch((dispatch, ownProps, registry) => {
const {
updateBlockAttributes,
insertBlocks,
mergeBlocks,
replaceBlocks,
toggleSelection,
__unstableMarkLastChangeAsPersistent,
moveBlocksToPosition,
removeBlock,
selectBlock
} = dispatch(blockEditorStore);
return {
setAttributes(nextAttributes) {
const { getMultiSelectedBlockClientIds } = registry.select(blockEditorStore);
const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds();
const { clientId, attributes } = ownProps;
const clientIds = multiSelectedBlockClientIds.length ? multiSelectedBlockClientIds : [clientId];
const newAttributes = typeof nextAttributes === "function" ? nextAttributes(attributes) : nextAttributes;
updateBlockAttributes(clientIds, newAttributes);
},
onInsertBlocks(blocks, index) {
const { rootClientId } = ownProps;
insertBlocks(blocks, index, rootClientId);
},
onInsertBlocksAfter(blocks) {
const { clientId, rootClientId } = ownProps;
const { getBlockIndex } = registry.select(blockEditorStore);
const index = getBlockIndex(clientId);
insertBlocks(blocks, index + 1, rootClientId);
},
onMerge(forward) {
const { clientId, rootClientId } = ownProps;
const {
getPreviousBlockClientId,
getNextBlockClientId,
getBlock,
getBlockAttributes,
getBlockName,
getBlockOrder,
getBlockIndex,
getBlockRootClientId,
canInsertBlockType
} = registry.select(blockEditorStore);
function switchToDefaultOrRemove() {
const block = getBlock(clientId);
const defaultBlockName = getDefaultBlockName();
const defaultBlockType = getBlockType(defaultBlockName);
if (getBlockName(clientId) !== defaultBlockName) {
const replacement = switchToBlockType(
block,
defaultBlockName
);
if (replacement && replacement.length) {
replaceBlocks(clientId, replacement);
}
} else if (isUnmodifiedDefaultBlock(block)) {
const nextBlockClientId = getNextBlockClientId(clientId);
if (nextBlockClientId) {
registry.batch(() => {
removeBlock(clientId);
selectBlock(nextBlockClientId);
});
}
} else if (defaultBlockType.merge) {
const attributes = defaultBlockType.merge(
{},
block.attributes
);
replaceBlocks(
[clientId],
[createBlock(defaultBlockName, attributes)]
);
}
}
function moveFirstItemUp(_clientId, changeSelection = true) {
const wrapperBlockName = getBlockName(_clientId);
const wrapperBlockType = getBlockType(wrapperBlockName);
const isTextualWrapper = wrapperBlockType.category === "text";
const targetRootClientId = getBlockRootClientId(_clientId);
const blockOrder = getBlockOrder(_clientId);
const [firstClientId] = blockOrder;
if (blockOrder.length === 1 && isUnmodifiedBlock(getBlock(firstClientId))) {
removeBlock(_clientId);
} else if (isTextualWrapper) {
registry.batch(() => {
if (canInsertBlockType(
getBlockName(firstClientId),
targetRootClientId
)) {
moveBlocksToPosition(
[firstClientId],
_clientId,
targetRootClientId,
getBlockIndex(_clientId)
);
} else {
const replacement = switchToBlockType(
getBlock(firstClientId),
getDefaultBlockName()
);
if (replacement && replacement.length && replacement.every(
(block) => canInsertBlockType(
block.name,
targetRootClientId
)
)) {
insertBlocks(
replacement,
getBlockIndex(_clientId),
targetRootClientId,
changeSelection
);
removeBlock(firstClientId, false);
} else {
switchToDefaultOrRemove();
}
}
if (!getBlockOrder(_clientId).length && isUnmodifiedBlock(getBlock(_clientId))) {
removeBlock(_clientId, false);
}
});
} else {
switchToDefaultOrRemove();
}
}
if (forward) {
if (rootClientId) {
const nextRootClientId = getNextBlockClientId(rootClientId);
if (nextRootClientId) {
if (getBlockName(rootClientId) === getBlockName(nextRootClientId)) {
const rootAttributes = getBlockAttributes(rootClientId);
const previousRootAttributes = getBlockAttributes(nextRootClientId);
if (Object.keys(rootAttributes).every(
(key) => rootAttributes[key] === previousRootAttributes[key]
)) {
registry.batch(() => {
moveBlocksToPosition(
getBlockOrder(nextRootClientId),
nextRootClientId,
rootClientId
);
removeBlock(nextRootClientId, false);
});
return;
}
} else {
mergeBlocks(rootClientId, nextRootClientId);
return;
}
}
}
const nextBlockClientId = getNextBlockClientId(clientId);
if (!nextBlockClientId) {
return;
}
if (getBlockOrder(nextBlockClientId).length) {
moveFirstItemUp(nextBlockClientId, false);
} else {
mergeBlocks(clientId, nextBlockClientId);
}
} else {
const previousBlockClientId = getPreviousBlockClientId(clientId);
if (previousBlockClientId) {
mergeBlocks(previousBlockClientId, clientId);
} else if (rootClientId) {
const previousRootClientId = getPreviousBlockClientId(rootClientId);
if (previousRootClientId && getBlockName(rootClientId) === getBlockName(previousRootClientId)) {
const rootAttributes = getBlockAttributes(rootClientId);
const previousRootAttributes = getBlockAttributes(previousRootClientId);
if (Object.keys(rootAttributes).every(
(key) => rootAttributes[key] === previousRootAttributes[key]
)) {
registry.batch(() => {
moveBlocksToPosition(
getBlockOrder(rootClientId),
rootClientId,
previousRootClientId
);
removeBlock(rootClientId, false);
});
return;
}
}
moveFirstItemUp(rootClientId);
} else {
switchToDefaultOrRemove();
}
}
},
onReplace(blocks, indexToSelect, initialPosition) {
if (blocks.length && !isUnmodifiedDefaultBlock(blocks[blocks.length - 1])) {
__unstableMarkLastChangeAsPersistent();
}
const replacementBlocks = blocks?.length === 1 && Array.isArray(blocks[0]) ? blocks[0] : blocks;
replaceBlocks(
[ownProps.clientId],
replacementBlocks,
indexToSelect,
initialPosition
);
},
onRemove() {
removeBlock(ownProps.clientId);
},
toggleSelection(selectionEnabled) {
toggleSelection(selectionEnabled);
}
};
});
BlockListBlock = compose(
applyWithDispatch,
withFilters("editor.BlockListBlock")
)(BlockListBlock);
function BlockListBlockProvider(props) {
const { clientId, rootClientId } = props;
const selectedProps = useSelect(
(select) => {
const {
isBlockSelected,
getBlockMode,
isSelectionEnabled: isSelectionEnabled2,
getTemplateLock,
isSectionBlock: _isSectionBlock,
getParentSectionBlock,
getBlockWithoutAttributes,
getBlockAttributes,
canRemoveBlock,
canMoveBlock,
getSettings,
getEditedContentOnlySection,
getBlockEditingMode,
getBlockName,
isFirstMultiSelectedBlock,
getMultiSelectedBlockClientIds,
hasSelectedInnerBlock,
getBlocksByName,
getBlockIndex,
isBlockMultiSelected,
isBlockSubtreeDisabled,
isBlockHighlighted,
__unstableIsFullySelected,
__unstableSelectionHasUnmergeableBlock,
isBlockBeingDragged,
isDragging: isDragging2,
__unstableHasActiveBlockOverlayActive,
getSelectedBlocksInitialCaretPosition
} = unlock(select(blockEditorStore));
const blockWithoutAttributes2 = getBlockWithoutAttributes(clientId);
if (!blockWithoutAttributes2) {
return;
}
const {
hasBlockSupport: _hasBlockSupport,
getActiveBlockVariation
} = select(blocksStore);
const attributes2 = getBlockAttributes(clientId);
const { name: blockName, isValid: isValid2 } = blockWithoutAttributes2;
const blockType = getBlockType(blockName);
const {
supportsLayout,
isPreviewMode: isPreviewMode2,
__experimentalBlockBindingsSupportedAttributes
} = getSettings();
const bindableAttributes2 = __experimentalBlockBindingsSupportedAttributes?.[blockName];
const hasLightBlockWrapper = blockType?.apiVersion > 1;
const previewContext = {
isPreviewMode: isPreviewMode2,
blockWithoutAttributes: blockWithoutAttributes2,
name: blockName,
attributes: attributes2,
isValid: isValid2,
themeSupportsLayout: supportsLayout,
index: getBlockIndex(clientId),
isReusable: isReusableBlock(blockType),
className: hasLightBlockWrapper ? attributes2.className : void 0,
defaultClassName: hasLightBlockWrapper ? getBlockDefaultClassName(blockName) : void 0,
blockTitle: blockType?.title,
isBlockHidden: attributes2?.metadata?.blockVisibility === false,
bindableAttributes: bindableAttributes2
};
if (isPreviewMode2) {
return previewContext;
}
const { isBlockHidden: _isBlockHidden } = unlock(
select(blockEditorStore)
);
const _isSelected = isBlockSelected(clientId);
const canRemove2 = canRemoveBlock(clientId);
const canMove2 = canMoveBlock(clientId);
const match = getActiveBlockVariation(blockName, attributes2);
const isMultiSelected2 = isBlockMultiSelected(clientId);
const checkDeep = true;
const isAncestorOfSelectedBlock = hasSelectedInnerBlock(
clientId,
checkDeep
);
const blockEditingMode2 = getBlockEditingMode(clientId);
const multiple = hasBlockSupport(blockName, "multiple", true);
const blocksWithSameName = multiple ? [] : getBlocksByName(blockName);
const isInvalid = blocksWithSameName.length && blocksWithSameName[0] !== clientId;
return {
...previewContext,
mode: getBlockMode(clientId),
isSelectionEnabled: isSelectionEnabled2(),
isLocked: !!getTemplateLock(rootClientId),
isSectionBlock: _isSectionBlock(clientId),
isWithinSectionBlock: _isSectionBlock(clientId) || !!getParentSectionBlock(clientId),
canRemove: canRemove2,
canMove: canMove2,
isSelected: _isSelected,
isEditingContentOnlySection: getEditedContentOnlySection() === clientId,
blockEditingMode: blockEditingMode2,
mayDisplayControls: _isSelected || isFirstMultiSelectedBlock(clientId) && getMultiSelectedBlockClientIds().every(
(id) => getBlockName(id) === blockName
),
mayDisplayParentControls: _hasBlockSupport(
getBlockName(clientId),
"__experimentalExposeControlsToChildren",
false
) && hasSelectedInnerBlock(clientId),
blockApiVersion: blockType?.apiVersion || 1,
blockTitle: match?.title || blockType?.title,
isSubtreeDisabled: blockEditingMode2 === "disabled" && isBlockSubtreeDisabled(clientId),
hasOverlay: __unstableHasActiveBlockOverlayActive(clientId) && !isDragging2(),
initialPosition: _isSelected ? getSelectedBlocksInitialCaretPosition() : void 0,
isHighlighted: isBlockHighlighted(clientId),
isMultiSelected: isMultiSelected2,
isPartiallySelected: isMultiSelected2 && !__unstableIsFullySelected() && !__unstableSelectionHasUnmergeableBlock(),
isDragging: isBlockBeingDragged(clientId),
hasChildSelected: isAncestorOfSelectedBlock,
isEditingDisabled: blockEditingMode2 === "disabled",
hasEditableOutline: blockEditingMode2 !== "disabled" && getBlockEditingMode(rootClientId) === "disabled",
originalBlockClientId: isInvalid ? blocksWithSameName[0] : false,
isBlockHidden: _isBlockHidden(clientId)
};
},
[clientId, rootClientId]
);
const {
isPreviewMode,
// Fill values that end up as a public API and may not be defined in
// preview mode.
mode = "visual",
isSelectionEnabled = false,
isLocked = false,
canRemove = false,
canMove = false,
blockWithoutAttributes,
name,
attributes,
isValid,
isSelected = false,
themeSupportsLayout,
isEditingContentOnlySection,
blockEditingMode,
mayDisplayControls,
mayDisplayParentControls,
index,
blockApiVersion,
blockTitle,
isSubtreeDisabled,
hasOverlay,
initialPosition,
isHighlighted,
isMultiSelected,
isPartiallySelected,
isReusable,
isDragging,
hasChildSelected,
isSectionBlock,
isWithinSectionBlock,
isEditingDisabled,
hasEditableOutline,
className,
defaultClassName,
originalBlockClientId,
isBlockHidden,
bindableAttributes
} = selectedProps;
const block = useMemo(
() => ({ ...blockWithoutAttributes, attributes }),
[blockWithoutAttributes, attributes]
);
if (!selectedProps) {
return null;
}
const privateContext = {
isPreviewMode,
clientId,
className,
index,
mode,
name,
blockApiVersion,
blockTitle,
isSelected,
isSubtreeDisabled,
hasOverlay,
initialPosition,
blockEditingMode,
isHighlighted,
isMultiSelected,
isPartiallySelected,
isReusable,
isDragging,
hasChildSelected,
isSectionBlock,
isWithinSectionBlock,
isEditingDisabled,
hasEditableOutline,
isEditingContentOnlySection,
defaultClassName,
mayDisplayControls,
mayDisplayParentControls,
originalBlockClientId,
themeSupportsLayout,
canMove,
isBlockHidden,
bindableAttributes
};
if (isBlockHidden && !isSelected && !isMultiSelected && !hasChildSelected) {
return null;
}
return /* @__PURE__ */ jsx(PrivateBlockContext.Provider, { value: privateContext, children: /* @__PURE__ */ jsx(
BlockListBlock,
{
...props,
...{
mode,
isSelectionEnabled,
isLocked,
canRemove,
canMove,
// Users of the editor.BlockListBlock filter used to be able
// to access the block prop. Ideally these blocks would rely
// on the clientId prop only. This is kept for backward
// compatibility reasons.
block,
name,
attributes,
isValid,
isSelected
}
}
) });
}
var block_default = memo(BlockListBlockProvider);
export {
block_default as default
};
//# sourceMappingURL=block.js.map