@wordpress/block-editor
Version:
299 lines (267 loc) • 8.71 kB
JavaScript
import { createElement, Fragment } from "@wordpress/element";
/**
* External dependencies
*/
import classnames from 'classnames';
import { omit } from 'lodash';
/**
* WordPress dependencies
*/
import { createContext, useMemo, useCallback } from '@wordpress/element';
import { getBlockType, getSaveElement, isUnmodifiedDefaultBlock, hasBlockSupport } from '@wordpress/blocks';
import { withFilters } from '@wordpress/components';
import { withDispatch, withSelect, useDispatch } from '@wordpress/data';
import { compose, pure, ifCondition } from '@wordpress/compose';
/**
* Internal dependencies
*/
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';
export const BlockListBlockContext = createContext();
/**
* Merges wrapper props with special handling for classNames and styles.
*
* @param {Object} propsA
* @param {Object} propsB
*
* @return {Object} Merged props.
*/
function mergeWrapperProps(propsA, propsB) {
const newProps = { ...propsA,
...propsB
};
if (propsA && propsB && propsA.className && propsB.className) {
newProps.className = classnames(propsA.className, propsB.className);
}
if (propsA && propsB && propsA.style && propsB.style) {
newProps.style = { ...propsA.style,
...propsB.style
};
}
return newProps;
}
function Block({
children,
isHtml,
...props
}) {
return createElement("div", useBlockProps(props, {
__unstableIsHtml: isHtml
}), children);
}
function BlockListBlock({
mode,
isLocked,
clientId,
isSelected,
isSelectionEnabled,
className,
name,
isValid,
attributes,
wrapperProps,
setAttributes,
onReplace,
onInsertBlocksAfter,
onMerge,
toggleSelection,
index
}) {
const {
removeBlock
} = useDispatch(blockEditorStore);
const onRemove = useCallback(() => removeBlock(clientId), [clientId]); // We wrap the BlockEdit component in a div that hides it when editing in
// HTML mode. This allows us to render all of the ancillary pieces
// (InspectorControls, etc.) which are inside `BlockEdit` but not
// `BlockHTML`, even in HTML mode.
let blockEdit = createElement(BlockEdit, {
name: name,
isSelected: isSelected,
attributes: attributes,
setAttributes: setAttributes,
insertBlocksAfter: isLocked ? undefined : onInsertBlocksAfter,
onReplace: isLocked ? undefined : onReplace,
onRemove: isLocked ? undefined : onRemove,
mergeBlocks: isLocked ? undefined : onMerge,
clientId: clientId,
isSelectionEnabled: isSelectionEnabled,
toggleSelection: toggleSelection
});
const blockType = getBlockType(name);
const lightBlockWrapper = blockType.apiVersion > 1 || hasBlockSupport(blockType, 'lightBlockWrapper', false); // Determine whether the block has props to apply to the wrapper.
if (blockType.getEditWrapperProps) {
wrapperProps = mergeWrapperProps(wrapperProps, blockType.getEditWrapperProps(attributes));
}
const isAligned = wrapperProps && !!wrapperProps['data-align']; // For aligned blocks, provide a wrapper element so the block can be
// positioned relative to the block column.
if (isAligned) {
blockEdit = createElement("div", {
className: "wp-block",
"data-align": wrapperProps['data-align']
}, blockEdit);
}
let block;
if (!isValid) {
block = createElement(Block, {
className: "has-warning"
}, createElement(BlockInvalidWarning, {
clientId: clientId
}), createElement("div", null, getSaveElement(blockType, attributes)));
} else if (mode === 'html') {
// Render blockEdit so the inspector controls don't disappear.
// See #8969.
block = createElement(Fragment, null, createElement("div", {
style: {
display: 'none'
}
}, blockEdit), createElement(Block, {
isHtml: true
}, createElement(BlockHtml, {
clientId: clientId
})));
} else if (lightBlockWrapper) {
block = blockEdit;
} else {
block = createElement(Block, wrapperProps, blockEdit);
}
const value = {
clientId,
isSelected,
index,
// The wp-block className is important for editor styles.
className: classnames(className, {
'wp-block': !isAligned
}),
wrapperProps: omit(wrapperProps, ['data-align'])
};
const memoizedValue = useMemo(() => value, Object.values(value));
return createElement(BlockListBlockContext.Provider, {
value: memoizedValue
}, createElement(BlockCrashBoundary, {
fallback: createElement(Block, {
className: "has-warning"
}, createElement(BlockCrashWarning, null))
}, block));
}
const applyWithSelect = withSelect((select, {
clientId,
rootClientId
}) => {
const {
isBlockSelected,
getBlockMode,
isSelectionEnabled,
getTemplateLock,
__unstableGetBlockWithoutInnerBlocks
} = select(blockEditorStore);
const block = __unstableGetBlockWithoutInnerBlocks(clientId);
const isSelected = isBlockSelected(clientId);
const templateLock = getTemplateLock(rootClientId); // The fallback to `{}` is a temporary fix.
// This function should never be called when a block is not present in
// the state. It happens now because the order in withSelect rendering
// is not correct.
const {
name,
attributes,
isValid
} = block || {}; // Do not add new properties here, use `useSelect` instead to avoid
// leaking new props to the public API (editor.BlockListBlock filter).
return {
mode: getBlockMode(clientId),
isSelectionEnabled: isSelectionEnabled(),
isLocked: !!templateLock,
// 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
};
});
const applyWithDispatch = withDispatch((dispatch, ownProps, {
select
}) => {
const {
updateBlockAttributes,
insertBlocks,
mergeBlocks,
replaceBlocks,
toggleSelection,
__unstableMarkLastChangeAsPersistent
} = dispatch(blockEditorStore); // Do not add new properties here, use `useDispatch` instead to avoid
// leaking new props to the public API (editor.BlockListBlock filter).
return {
setAttributes(newAttributes) {
const {
getMultiSelectedBlockClientIds
} = select(blockEditorStore);
const multiSelectedBlockClientIds = getMultiSelectedBlockClientIds();
const {
clientId
} = ownProps;
const clientIds = multiSelectedBlockClientIds.length ? multiSelectedBlockClientIds : [clientId];
updateBlockAttributes(clientIds, newAttributes);
},
onInsertBlocks(blocks, index) {
const {
rootClientId
} = ownProps;
insertBlocks(blocks, index, rootClientId);
},
onInsertBlocksAfter(blocks) {
const {
clientId,
rootClientId
} = ownProps;
const {
getBlockIndex
} = select(blockEditorStore);
const index = getBlockIndex(clientId, rootClientId);
insertBlocks(blocks, index + 1, rootClientId);
},
onMerge(forward) {
const {
clientId
} = ownProps;
const {
getPreviousBlockClientId,
getNextBlockClientId
} = select(blockEditorStore);
if (forward) {
const nextBlockClientId = getNextBlockClientId(clientId);
if (nextBlockClientId) {
mergeBlocks(clientId, nextBlockClientId);
}
} else {
const previousBlockClientId = getPreviousBlockClientId(clientId);
if (previousBlockClientId) {
mergeBlocks(previousBlockClientId, clientId);
}
}
},
onReplace(blocks, indexToSelect, initialPosition) {
if (blocks.length && !isUnmodifiedDefaultBlock(blocks[blocks.length - 1])) {
__unstableMarkLastChangeAsPersistent();
}
replaceBlocks([ownProps.clientId], blocks, indexToSelect, initialPosition);
},
toggleSelection(selectionEnabled) {
toggleSelection(selectionEnabled);
}
};
});
export default compose(pure, applyWithSelect, applyWithDispatch, // block is sometimes not mounted at the right time, causing it be undefined
// see issue for more info
// https://github.com/WordPress/gutenberg/issues/17013
ifCondition(({
block
}) => !!block), withFilters('editor.BlockListBlock'))(BlockListBlock);
//# sourceMappingURL=block.js.map