@wordpress/block-editor
Version:
541 lines (478 loc) • 19 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _element = require("@wordpress/element");
var _reactNative = require("react-native");
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 _blockMobileToolbar = _interopRequireDefault(require("../block-mobile-toolbar"));
var _blockOutline = _interopRequireDefault(require("./block-outline"));
var _block = _interopRequireDefault(require("./block.scss"));
var _store = require("../../store");
var _layout = require("./layout");
var _useSetting = _interopRequireDefault(require("../use-setting"));
/**
* External dependencies
*/
/**
* WordPress dependencies
*/
/**
* Internal dependencies
*/
const emptyArray = []; // Helper function to memoize the wrapperProps since getEditWrapperProps always returns a new reference.
const wrapperPropsCache = new WeakMap();
const emptyObj = {};
function getWrapperProps(value, getWrapperPropsFunction) {
if (!getWrapperPropsFunction) {
return emptyObj;
}
const cachedValue = wrapperPropsCache.get(value);
if (!cachedValue) {
const wrapperProps = getWrapperPropsFunction(value);
wrapperPropsCache.set(value, wrapperProps);
return wrapperProps;
}
return cachedValue;
}
function BlockWrapper({
accessibilityLabel,
align,
blockWidth,
children,
clientId,
draggingClientId,
draggingEnabled,
isDescendentBlockSelected,
isParentSelected,
isSelected,
isStackedHorizontally,
isTouchable,
marginHorizontal,
marginVertical,
onDeleteBlock,
onFocus
}) {
const {
width: screenWidth
} = (0, _reactNative.useWindowDimensions)();
const anchorNodeRef = (0, _element.useRef)();
const {
isFullWidth
} = _components.alignmentHelpers;
const isScreenWidthEqual = blockWidth === screenWidth;
const isFullWidthToolbar = isFullWidth(align) || isScreenWidthEqual;
const blockWrapperStyles = {
flex: 1
};
const blockWrapperStyle = [blockWrapperStyles, {
marginVertical,
marginHorizontal
}];
const accessible = !(isSelected || isDescendentBlockSelected);
return (0, _element.createElement)(_reactNative.Pressable, {
accessibilityLabel: accessibilityLabel,
accessibilityRole: "button",
accessible: accessible,
disabled: !isTouchable,
onPress: onFocus,
style: blockWrapperStyle
}, (0, _element.createElement)(_blockOutline.default, {
isSelected: isSelected,
isParentSelected: isParentSelected,
screenWidth: screenWidth
}), (0, _element.createElement)(_blockDraggable.default, {
clientId: clientId,
draggingClientId: draggingClientId,
enabled: draggingEnabled,
testID: "draggable-trigger-content"
}, children), (0, _element.createElement)(_reactNative.View, {
style: _block.default.neutralToolbar,
ref: anchorNodeRef
}, isSelected && (0, _element.createElement)(_blockMobileToolbar.default, {
anchorNodeRef: anchorNodeRef.current,
blockWidth: blockWidth,
clientId: clientId,
draggingClientId: draggingClientId,
isFullWidth: isFullWidthToolbar,
isStackedHorizontally: isStackedHorizontally,
onDelete: onDeleteBlock
})));
}
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
}) {
const {
baseGlobalStyles,
blockType,
draggingClientId,
draggingEnabled,
isDescendantOfParentSelected,
isDescendentBlockSelected,
isParentSelected,
order
} = (0, _data.useSelect)(select => {
const {
getBlockCount,
getBlockHierarchyRootClientId,
getBlockIndex,
getBlockParents,
getSelectedBlockClientId,
getSettings,
hasSelectedInnerBlock
} = select(_store.store);
const currentBlockType = (0, _blocks.getBlockType)(name || 'core/missing');
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 hasInnerBlocks = 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 = !hasInnerBlocks || 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,
blockType: currentBlockType,
draggingClientId: currentDraggingClientId,
draggingEnabled: isDraggingEnabled,
isDescendantOfParentSelected: descendantOfParentSelected,
isDescendentBlockSelected: descendentBlockSelected,
isParentSelected: parentSelected,
order: blockOrder
};
}, [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, _components.useMobileGlobalStylesColors)();
const globalStyle = (0, _components.useGlobalStyles)();
const fontSizes = (0, _useSetting.default)('typography.fontSizes') || emptyArray;
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]); // Block level styles.
const wrapperProps = getWrapperProps(attributes, blockType.getEditWrapperProps); // Inherited styles merged with block level styles.
const mergedStyle = (0, _element.useMemo)(() => {
return (0, _components.getMergedGlobalStyles)(baseGlobalStyles, globalStyle, wrapperProps.style, attributes, defaultColors, name, fontSizes); // eslint-disable-next-line react-hooks/exhaustive-deps
}, [// 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.
// eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(globalStyle), // eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(wrapperProps.style), // eslint-disable-next-line react-hooks/exhaustive-deps
JSON.stringify(Object.fromEntries(Object.entries(attributes !== null && attributes !== void 0 ? attributes : {}).filter(([key]) => _components.GlobalStylesContext.BLOCK_STYLE_ATTRIBUTES.includes(key))))]);
const {
align
} = attributes;
const isFocused = isSelected || isDescendentBlockSelected;
const isTouchable = isSelected || isDescendantOfParentSelected || isParentSelected || !rootClientId;
const accessibilityLabel = (0, _blocks.__experimentalGetAccessibleBlockLabel)(blockType, attributes, order + 1);
return (0, _element.createElement)(BlockWrapper, {
accessibilityLabel: accessibilityLabel,
align: align,
blockWidth: blockWidth,
clientId: clientId,
draggingClientId: draggingClientId,
draggingEnabled: draggingEnabled,
isFocused: isFocused,
isDescendentBlockSelected: isDescendentBlockSelected,
isParentSelected: isParentSelected,
isSelected: isSelected,
isStackedHorizontally: isStackedHorizontally,
isTouchable: isTouchable,
marginHorizontal: marginHorizontal,
marginVertical: marginVertical,
onDeleteBlock: onDeleteBlock,
onFocus: onFocus
}, () => !isValid ? (0, _element.createElement)(_blockInvalidWarning.default, {
clientId: clientId
}) : (0, _element.createElement)(_components.GlobalStylesContext.Provider, {
value: mergedStyle
}, (0, _element.createElement)(_blockEdit.default, {
attributes: attributes,
blockWidth: blockWidth,
clientId: clientId,
contentStyle: contentStyle,
insertBlocksAfter: isLocked ? undefined : onInsertBlocksAfter,
isSelected: isSelected,
isSelectionEnabled: isSelectionEnabled,
mergeBlocks: canRemove ? onMerge : undefined,
name: name,
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
}), (0, _element.createElement)(_reactNative.View, {
onLayout: onLayout
})));
}
const applyWithSelect = (0, _data.withSelect)((select, {
clientId,
rootClientId
}) => {
const {
isBlockSelected,
getBlockMode,
isSelectionEnabled,
getTemplateLock,
__unstableGetBlockWithoutInnerBlocks,
canRemoveBlock,
canMoveBlock
} = select(_store.store);
const block = __unstableGetBlockWithoutInnerBlocks(clientId);
const isSelected = isBlockSelected(clientId);
const templateLock = getTemplateLock(rootClientId);
const canRemove = canRemoveBlock(clientId, rootClientId);
const canMove = canMoveBlock(clientId, 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,
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);
}
}
},
onReplace(blocks, indexToSelect, initialPosition) {
if (blocks.length && !(0, _blocks.isUnmodifiedDefaultBlock)(blocks[blocks.length - 1])) {
__unstableMarkLastChangeAsPersistent();
}
replaceBlocks([ownProps.clientId], blocks, indexToSelect, initialPosition);
},
toggleSelection(selectionEnabled) {
toggleSelection(selectionEnabled);
}
};
});
var _default = (0, _compose.compose)(_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
(0, _compose.ifCondition)(({
block
}) => !!block))(BlockListBlock);
exports.default = _default;
//# sourceMappingURL=block.native.js.map