@wordpress/block-editor
Version:
538 lines (523 loc) • 20.5 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
var _clsx = _interopRequireDefault(require("clsx"));
var _element = require("@wordpress/element");
var _components = require("@wordpress/components");
var _blocks = require("@wordpress/blocks");
var _data = require("@wordpress/data");
var _compose = require("@wordpress/compose");
var _blockEdit = _interopRequireDefault(require("../block-edit"));
var _blockDraggable = _interopRequireDefault(require("../block-draggable"));
var _blockInvalidWarning = _interopRequireDefault(require("./block-invalid-warning"));
var _blockOutline = _interopRequireDefault(require("./block-outline"));
var _store = require("../../store");
var _layout = require("./layout");
var _useScrollUponInsertion = _interopRequireDefault(require("./use-scroll-upon-insertion"));
var _useSettings = require("../use-settings");
var _lockUnlock = require("../../lock-unlock");
var _blockCrashBoundary = _interopRequireDefault(require("./block-crash-boundary"));
var _blockCrashWarning = _interopRequireDefault(require("./block-crash-warning"));
var _useGlobalStylesContext = require("../global-styles/use-global-styles-context");
var _jsxRuntime = require("react/jsx-runtime");
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const EMPTY_ARRAY = [];
/**
* 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
};
// May be set to undefined, so check if the property is set!
if (propsA?.hasOwnProperty('className') && propsB?.hasOwnProperty('className')) {
newProps.className = (0, _clsx.default)(propsA.className, propsB.className);
}
if (propsA?.hasOwnProperty('style') && propsB?.hasOwnProperty('style')) {
newProps.style = {
...propsA.style,
...propsB.style
};
}
return newProps;
}
function BlockWrapper({
accessibilityLabel,
blockCategory,
children,
clientId,
draggingClientId,
draggingEnabled,
hasInnerBlocks,
isDescendentBlockSelected,
isSelected,
isTouchable,
marginHorizontal,
marginVertical,
name,
onFocus
}) {
const blockWrapperStyles = {
flex: 1
};
const blockWrapperStyle = [blockWrapperStyles, {
marginVertical,
marginHorizontal
}];
const accessible = !(isSelected || isDescendentBlockSelected);
const ref = (0, _element.useRef)();
const [isLayoutCalculated, setIsLayoutCalculated] = (0, _element.useState)();
(0, _useScrollUponInsertion.default)({
clientId,
isSelected,
isLayoutCalculated,
elementRef: ref
});
const onLayout = (0, _element.useCallback)(() => {
setIsLayoutCalculated(true);
}, []);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.Pressable, {
accessibilityLabel: accessibilityLabel,
accessibilityRole: "button",
accessible: accessible,
disabled: !isTouchable,
onPress: onFocus,
style: blockWrapperStyle,
ref: ref,
onLayout: onLayout,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_blockOutline.default, {
blockCategory: blockCategory,
hasInnerBlocks: hasInnerBlocks,
isSelected: isSelected,
name: name
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockCrashBoundary.default, {
blockName: name,
fallback: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockCrashWarning.default, {}),
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockDraggable.default, {
clientId: clientId,
draggingClientId: draggingClientId,
enabled: draggingEnabled,
testID: "draggable-trigger-content",
children: children
})
})]
});
}
function BlockListBlock({
attributes,
blockWidth: blockWrapperWidth,
canRemove,
clientId,
contentStyle,
isLocked,
isSelected,
isSelectionEnabled,
isStackedHorizontally,
isValid,
marginHorizontal,
marginVertical,
name,
onDeleteBlock,
onInsertBlocksAfter,
onMerge,
onReplace,
parentBlockAlignment,
parentWidth,
rootClientId,
setAttributes,
toggleSelection,
wrapperProps
}) {
const {
baseGlobalStyles,
blockCategory,
blockType,
draggingClientId,
draggingEnabled,
hasInnerBlocks,
isDescendantOfParentSelected,
isDescendentBlockSelected,
isParentSelected,
order,
mayDisplayControls,
blockEditingMode
} = (0, _data.useSelect)(select => {
const {
getBlockCount,
getBlockHierarchyRootClientId,
getBlockIndex,
getBlockParents,
getSelectedBlockClientId,
getSettings,
hasSelectedInnerBlock,
getBlockName,
isFirstMultiSelectedBlock,
getMultiSelectedBlockClientIds,
getBlockEditingMode
} = select(_store.store);
const currentBlockType = (0, _blocks.getBlockType)(name || 'core/missing');
const currentBlockCategory = currentBlockType?.category;
const blockOrder = getBlockIndex(clientId);
const descendentBlockSelected = hasSelectedInnerBlock(clientId, true);
const selectedBlockClientId = getSelectedBlockClientId();
const parents = getBlockParents(clientId, true);
const parentSelected =
// Set false as a default value to prevent re-render when it's changed from null to false.
(selectedBlockClientId || false) && selectedBlockClientId === rootClientId;
const selectedParents = clientId ? parents : [];
const descendantOfParentSelected = selectedParents.includes(rootClientId);
const blockHasInnerBlocks = getBlockCount(clientId) > 0;
// For blocks with inner blocks, we only enable the dragging in the nested
// blocks if any of them are selected. This way we prevent the long-press
// gesture from being disabled for elements within the block UI.
const isDraggingEnabled = !blockHasInnerBlocks || isSelected || !descendentBlockSelected;
// Dragging nested blocks is not supported yet. For this reason, the block to be dragged
// will be the top in the hierarchy.
const currentDraggingClientId = getBlockHierarchyRootClientId(clientId);
const globalStylesBaseStyles = getSettings()?.__experimentalGlobalStylesBaseStyles;
return {
baseGlobalStyles: globalStylesBaseStyles,
blockCategory: currentBlockCategory,
blockType: currentBlockType,
draggingClientId: currentDraggingClientId,
draggingEnabled: isDraggingEnabled,
hasInnerBlocks: blockHasInnerBlocks,
isDescendantOfParentSelected: descendantOfParentSelected,
isDescendentBlockSelected: descendentBlockSelected,
isParentSelected: parentSelected,
order: blockOrder,
mayDisplayControls: isSelected || isFirstMultiSelectedBlock(clientId) && getMultiSelectedBlockClientIds().every(id => getBlockName(id) === name),
blockEditingMode: getBlockEditingMode(clientId)
};
}, [clientId, isSelected, name, rootClientId]);
const {
removeBlock,
selectBlock
} = (0, _data.useDispatch)(_store.store);
const initialBlockWidth = blockWrapperWidth - 2 * marginHorizontal;
const [blockWidth, setBlockWidth] = (0, _element.useState)(initialBlockWidth);
const parentLayout = (0, _layout.useLayout)() || {};
const defaultColors = (0, _useGlobalStylesContext.useMobileGlobalStylesColors)();
const globalStyle = (0, _useGlobalStylesContext.useGlobalStyles)();
const [fontSizes] = (0, _useSettings.useSettings)('typography.fontSizes');
const onRemove = (0, _element.useCallback)(() => removeBlock(clientId), [clientId, removeBlock]);
const onFocus = (0, _element.useCallback)(() => {
if (!isSelected) {
selectBlock(clientId);
}
}, [selectBlock, clientId, isSelected]);
const onLayout = (0, _element.useCallback)(({
nativeEvent
}) => {
const layoutWidth = Math.floor(nativeEvent.layout.width);
if (!blockWidth || !layoutWidth) {
return;
}
if (blockWidth !== layoutWidth) {
setBlockWidth(layoutWidth);
}
}, [blockWidth, setBlockWidth]);
// Determine whether the block has props to apply to the wrapper.
if (blockType?.getEditWrapperProps) {
wrapperProps = mergeWrapperProps(wrapperProps, blockType.getEditWrapperProps(attributes));
}
// Inherited styles merged with block level styles.
const mergedStyle = (0, _element.useMemo)(() => {
return (0, _useGlobalStylesContext.getMergedGlobalStyles)(baseGlobalStyles, globalStyle, wrapperProps?.style, attributes, defaultColors, name, fontSizes || EMPTY_ARRAY);
}, [
// It is crucial to keep the dependencies array minimal to prevent unnecessary calls that could negatively impact performance.
// JSON.stringify is used for the following purposes:
// 1. To create a single, comparable value from the globalStyle, wrapperProps.style, and attributes objects. This allows useMemo to
// efficiently determine if a change has occurred in any of these objects.
// 2. To filter the attributes object, ensuring that only the relevant attributes (included in
// GlobalStylesContext.BLOCK_STYLE_ATTRIBUTES) are considered as dependencies. This reduces the likelihood of
// unnecessary useMemo calls when other, unrelated attributes change.
JSON.stringify(globalStyle), JSON.stringify(wrapperProps?.style), JSON.stringify(Object.fromEntries(Object.entries(attributes !== null && attributes !== void 0 ? attributes : {}).filter(([key]) => _useGlobalStylesContext.GlobalStylesContext.BLOCK_STYLE_ATTRIBUTES.includes(key))))]);
const isFocused = isSelected || isDescendentBlockSelected;
const isTouchable = isSelected || isDescendantOfParentSelected || isParentSelected || !rootClientId;
const accessibilityLabel = (0, _blocks.__experimentalGetAccessibleBlockLabel)(blockType, attributes, order + 1);
return /*#__PURE__*/(0, _jsxRuntime.jsx)(BlockWrapper, {
accessibilityLabel: accessibilityLabel,
blockCategory: blockCategory,
clientId: clientId,
draggingClientId: draggingClientId,
draggingEnabled: draggingEnabled,
hasInnerBlocks: hasInnerBlocks,
isDescendentBlockSelected: isDescendentBlockSelected,
isFocused: isFocused,
isSelected: isSelected,
isStackedHorizontally: isStackedHorizontally,
isTouchable: isTouchable,
marginHorizontal: marginHorizontal,
marginVertical: marginVertical,
name: name,
onFocus: onFocus,
children: () => !isValid ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_blockInvalidWarning.default, {
clientId: clientId
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_useGlobalStylesContext.GlobalStylesContext.Provider, {
value: mergedStyle,
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_blockEdit.default, {
attributes: attributes,
blockWidth: blockWidth,
clientId: clientId,
contentStyle: contentStyle,
insertBlocksAfter: isLocked ? undefined : onInsertBlocksAfter,
isSelected: isSelected,
isSelectionEnabled: isSelectionEnabled,
mergeBlocks: canRemove ? onMerge : undefined,
name: name,
onDeleteBlock: onDeleteBlock,
onFocus: onFocus,
onRemove: canRemove ? onRemove : undefined,
onReplace: canRemove ? onReplace : undefined,
parentBlockAlignment: parentBlockAlignment,
parentWidth: parentWidth,
setAttributes: setAttributes,
style: mergedStyle,
toggleSelection: toggleSelection,
__unstableParentLayout: Object.keys(parentLayout).length ? parentLayout : undefined,
wrapperProps: wrapperProps,
mayDisplayControls: mayDisplayControls,
blockEditingMode: blockEditingMode
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
onLayout: onLayout
})]
})
});
}
const applyWithSelect = (0, _data.withSelect)((select, {
clientId,
rootClientId
}) => {
const {
isBlockSelected,
getBlockMode,
isSelectionEnabled,
getTemplateLock,
getBlockWithoutAttributes,
getBlockAttributes,
canRemoveBlock,
canMoveBlock
} = (0, _lockUnlock.unlock)(select(_store.store));
const block = getBlockWithoutAttributes(clientId);
const attributes = getBlockAttributes(clientId);
const isSelected = isBlockSelected(clientId);
const templateLock = getTemplateLock(rootClientId);
const canRemove = canRemoveBlock(clientId);
const canMove = canMoveBlock(clientId);
// 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,
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,
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
};
});
const applyWithDispatch = (0, _data.withDispatch)((dispatch, ownProps, registry) => {
const {
updateBlockAttributes,
insertBlocks,
mergeBlocks,
replaceBlocks,
toggleSelection,
__unstableMarkLastChangeAsPersistent,
moveBlocksToPosition,
removeBlock
} = dispatch(_store.store);
// 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
} = registry.select(_store.store);
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
} = registry.select(_store.store);
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(_store.store);
/**
* Moves the block with clientId up one level. If the block type
* cannot be inserted at the new location, it will be attempted to
* convert to the default block type.
*
* @param {string} _clientId The block to move.
* @param {boolean} changeSelection Whether to change the selection
* to the moved block.
*/
function moveFirstItemUp(_clientId, changeSelection = true) {
const targetRootClientId = getBlockRootClientId(_clientId);
const blockOrder = getBlockOrder(_clientId);
const [firstClientId] = blockOrder;
if (blockOrder.length === 1 && (0, _blocks.isUnmodifiedBlock)(getBlock(firstClientId))) {
removeBlock(_clientId);
} else {
registry.batch(() => {
if (canInsertBlockType(getBlockName(firstClientId), targetRootClientId)) {
moveBlocksToPosition([firstClientId], _clientId, targetRootClientId, getBlockIndex(_clientId));
} else {
const replacement = (0, _blocks.switchToBlockType)(getBlock(firstClientId), (0, _blocks.getDefaultBlockName)());
if (replacement && replacement.length) {
insertBlocks(replacement, getBlockIndex(_clientId), targetRootClientId, changeSelection);
removeBlock(firstClientId, false);
}
}
if (!getBlockOrder(_clientId).length && (0, _blocks.isUnmodifiedBlock)(getBlock(_clientId))) {
removeBlock(_clientId, false);
}
});
}
}
// For `Delete` or forward merge, we should do the exact same thing
// as `Backspace`, but from the other block.
if (forward) {
if (rootClientId) {
const nextRootClientId = getNextBlockClientId(rootClientId);
if (nextRootClientId) {
// If there is a block that follows with the same parent
// block name and the same attributes, merge the inner
// blocks.
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 there is a preceding block with the same parent block
// name and the same attributes, merge the inner blocks.
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 if (getBlockName(clientId) !== (0, _blocks.getDefaultBlockName)()) {
const replacement = (0, _blocks.switchToBlockType)(getBlock(clientId), (0, _blocks.getDefaultBlockName)());
if (replacement && replacement.length) {
replaceBlocks(clientId, replacement);
}
}
}
},
onReplace(blocks, indexToSelect, initialPosition, meta) {
if (blocks.length && !(0, _blocks.isUnmodifiedDefaultBlock)(blocks[blocks.length - 1])) {
__unstableMarkLastChangeAsPersistent();
}
replaceBlocks([ownProps.clientId], blocks, indexToSelect, initialPosition, meta);
},
toggleSelection(selectionEnabled) {
toggleSelection(selectionEnabled);
}
};
});
var _default = exports.default = (0, _compose.compose)(_element.memo, 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
(0, _compose.ifCondition)(({
block
}) => !!block), (0, _components.withFilters)('editor.BlockListBlock'))(BlockListBlock);
//# sourceMappingURL=block.native.js.map