UNPKG

@wordpress/block-editor

Version:
614 lines (613 loc) 20.9 kB
// 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