UNPKG

@wordpress/block-editor

Version:
538 lines (523 loc) 20.5 kB
"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