@wordpress/block-library
Version:
Block library for the WordPress editor.
278 lines (270 loc) • 9.28 kB
JavaScript
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Notice, RangeControl, ToggleControl, __experimentalToolsPanel as ToolsPanel, __experimentalToolsPanelItem as ToolsPanelItem, __experimentalVStack as VStack } from '@wordpress/components';
import { InspectorControls, useInnerBlocksProps, BlockControls, BlockVerticalAlignmentToolbar, __experimentalBlockVariationPicker, useBlockProps, store as blockEditorStore } from '@wordpress/block-editor';
import { useDispatch, useSelect, useRegistry } from '@wordpress/data';
import { createBlock, createBlocksFromInnerBlocksTemplate, store as blocksStore } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { hasExplicitPercentColumnWidths, getMappedColumnWidths, getRedistributedColumnWidths, toWidthPrecision } from './utils';
import { useToolsPanelDropdownMenuProps } from '../utils/hooks';
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
const DEFAULT_BLOCK = {
name: 'core/column'
};
function ColumnInspectorControls({
clientId,
setAttributes,
isStackedOnMobile
}) {
const {
count,
canInsertColumnBlock,
minCount
} = useSelect(select => {
const {
canInsertBlockType,
canRemoveBlock,
getBlockOrder
} = select(blockEditorStore);
const blockOrder = getBlockOrder(clientId);
// Get the indexes of columns for which removal is prevented.
// The highest index will be used to determine the minimum column count.
const preventRemovalBlockIndexes = blockOrder.reduce((acc, blockId, index) => {
if (!canRemoveBlock(blockId)) {
acc.push(index);
}
return acc;
}, []);
return {
count: blockOrder.length,
canInsertColumnBlock: canInsertBlockType('core/column', clientId),
minCount: Math.max(...preventRemovalBlockIndexes) + 1
};
}, [clientId]);
const {
getBlocks
} = useSelect(blockEditorStore);
const {
replaceInnerBlocks
} = useDispatch(blockEditorStore);
/**
* Updates the column count, including necessary revisions to child Column
* blocks to grant required or redistribute available space.
*
* @param {number} previousColumns Previous column count.
* @param {number} newColumns New column count.
*/
function updateColumns(previousColumns, newColumns) {
let innerBlocks = getBlocks(clientId);
const hasExplicitWidths = hasExplicitPercentColumnWidths(innerBlocks);
// Redistribute available width for existing inner blocks.
const isAddingColumn = newColumns > previousColumns;
if (isAddingColumn && hasExplicitWidths) {
// If adding a new column, assign width to the new column equal to
// as if it were `1 / columns` of the total available space.
const newColumnWidth = toWidthPrecision(100 / newColumns);
const newlyAddedColumns = newColumns - previousColumns;
// Redistribute in consideration of pending block insertion as
// constraining the available working width.
const widths = getRedistributedColumnWidths(innerBlocks, 100 - newColumnWidth * newlyAddedColumns);
innerBlocks = [...getMappedColumnWidths(innerBlocks, widths), ...Array.from({
length: newlyAddedColumns
}).map(() => {
return createBlock('core/column', {
width: `${newColumnWidth}%`
});
})];
} else if (isAddingColumn) {
innerBlocks = [...innerBlocks, ...Array.from({
length: newColumns - previousColumns
}).map(() => {
return createBlock('core/column');
})];
} else if (newColumns < previousColumns) {
// The removed column will be the last of the inner blocks.
innerBlocks = innerBlocks.slice(0, -(previousColumns - newColumns));
if (hasExplicitWidths) {
// Redistribute as if block is already removed.
const widths = getRedistributedColumnWidths(innerBlocks, 100);
innerBlocks = getMappedColumnWidths(innerBlocks, widths);
}
}
replaceInnerBlocks(clientId, innerBlocks);
}
const dropdownMenuProps = useToolsPanelDropdownMenuProps();
return /*#__PURE__*/_jsxs(ToolsPanel, {
label: __('Settings'),
resetAll: () => {
setAttributes({
isStackedOnMobile: true
});
},
dropdownMenuProps: dropdownMenuProps,
children: [canInsertColumnBlock && /*#__PURE__*/_jsxs(VStack, {
spacing: 4,
style: {
gridColumn: '1 / -1'
},
children: [/*#__PURE__*/_jsx(RangeControl, {
__nextHasNoMarginBottom: true,
__next40pxDefaultSize: true,
label: __('Columns'),
value: count,
onChange: value => updateColumns(count, Math.max(minCount, value)),
min: Math.max(1, minCount),
max: Math.max(6, count)
}), count > 6 && /*#__PURE__*/_jsx(Notice, {
status: "warning",
isDismissible: false,
children: __('This column count exceeds the recommended amount and may cause visual breakage.')
})]
}), /*#__PURE__*/_jsx(ToolsPanelItem, {
label: __('Stack on mobile'),
isShownByDefault: true,
hasValue: () => isStackedOnMobile !== true,
onDeselect: () => setAttributes({
isStackedOnMobile: true
}),
children: /*#__PURE__*/_jsx(ToggleControl, {
__nextHasNoMarginBottom: true,
label: __('Stack on mobile'),
checked: isStackedOnMobile,
onChange: () => setAttributes({
isStackedOnMobile: !isStackedOnMobile
})
})
})]
});
}
function ColumnsEditContainer({
attributes,
setAttributes,
clientId
}) {
const {
isStackedOnMobile,
verticalAlignment,
templateLock
} = attributes;
const registry = useRegistry();
const {
getBlockOrder
} = useSelect(blockEditorStore);
const {
updateBlockAttributes
} = useDispatch(blockEditorStore);
const classes = clsx({
[`are-vertically-aligned-${verticalAlignment}`]: verticalAlignment,
[`is-not-stacked-on-mobile`]: !isStackedOnMobile
});
const blockProps = useBlockProps({
className: classes
});
const innerBlocksProps = useInnerBlocksProps(blockProps, {
defaultBlock: DEFAULT_BLOCK,
directInsert: true,
orientation: 'horizontal',
renderAppender: false,
templateLock
});
/**
* Update all child Column blocks with a new vertical alignment setting
* based on whatever alignment is passed in. This allows change to parent
* to override anything set on a individual column basis.
*
* @param {string} newVerticalAlignment The vertical alignment setting.
*/
function updateAlignment(newVerticalAlignment) {
const innerBlockClientIds = getBlockOrder(clientId);
// Update own and child Column block vertical alignments.
// This is a single action; the batching prevents creating multiple history records.
registry.batch(() => {
setAttributes({
verticalAlignment: newVerticalAlignment
});
updateBlockAttributes(innerBlockClientIds, {
verticalAlignment: newVerticalAlignment
});
});
}
return /*#__PURE__*/_jsxs(_Fragment, {
children: [/*#__PURE__*/_jsx(BlockControls, {
children: /*#__PURE__*/_jsx(BlockVerticalAlignmentToolbar, {
onChange: updateAlignment,
value: verticalAlignment
})
}), /*#__PURE__*/_jsx(InspectorControls, {
children: /*#__PURE__*/_jsx(ColumnInspectorControls, {
clientId: clientId,
setAttributes: setAttributes,
isStackedOnMobile: isStackedOnMobile
})
}), /*#__PURE__*/_jsx("div", {
...innerBlocksProps
})]
});
}
function Placeholder({
clientId,
name,
setAttributes
}) {
const {
blockType,
defaultVariation,
variations
} = useSelect(select => {
const {
getBlockVariations,
getBlockType,
getDefaultBlockVariation
} = select(blocksStore);
return {
blockType: getBlockType(name),
defaultVariation: getDefaultBlockVariation(name, 'block'),
variations: getBlockVariations(name, 'block')
};
}, [name]);
const {
replaceInnerBlocks
} = useDispatch(blockEditorStore);
const blockProps = useBlockProps();
return /*#__PURE__*/_jsx("div", {
...blockProps,
children: /*#__PURE__*/_jsx(__experimentalBlockVariationPicker, {
icon: blockType?.icon?.src,
label: blockType?.title,
variations: variations,
instructions: __('Divide into columns. Select a layout:'),
onSelect: (nextVariation = defaultVariation) => {
if (nextVariation.attributes) {
setAttributes(nextVariation.attributes);
}
if (nextVariation.innerBlocks) {
replaceInnerBlocks(clientId, createBlocksFromInnerBlocksTemplate(nextVariation.innerBlocks), true);
}
},
allowSkip: true
})
});
}
const ColumnsEdit = props => {
const {
clientId
} = props;
const hasInnerBlocks = useSelect(select => select(blockEditorStore).getBlocks(clientId).length > 0, [clientId]);
const Component = hasInnerBlocks ? ColumnsEditContainer : Placeholder;
return /*#__PURE__*/_jsx(Component, {
...props
});
};
export default ColumnsEdit;
//# sourceMappingURL=edit.js.map