@wordpress/block-editor
Version:
213 lines (191 loc) • 5.91 kB
JavaScript
/**
* External dependencies
*/
import clsx from 'clsx';
/**
* WordPress dependencies
*/
import { addFilter } from '@wordpress/hooks';
import {
getBlockSupport,
getBlockType,
hasBlockSupport,
} from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { BlockControls, BlockAlignmentControl } from '../components';
import useAvailableAlignments from '../components/block-alignment-control/use-available-alignments';
import { useBlockEditingMode } from '../components/block-editing-mode';
/**
* An array which includes all possible valid alignments,
* used to validate if an alignment is valid or not.
*
* @constant
* @type {string[]}
*/
const ALL_ALIGNMENTS = [ 'left', 'center', 'right', 'wide', 'full' ];
/**
* An array which includes all wide alignments.
* In order for this alignments to be valid they need to be supported by the block,
* and by the theme.
*
* @constant
* @type {string[]}
*/
const WIDE_ALIGNMENTS = [ 'wide', 'full' ];
/**
* Returns the valid alignments.
* Takes into consideration the aligns supported by a block, if the block supports wide controls or not and if theme supports wide controls or not.
* Exported just for testing purposes, not exported outside the module.
*
* @param {?boolean|string[]} blockAlign Aligns supported by the block.
* @param {?boolean} hasWideBlockSupport True if block supports wide alignments. And False otherwise.
* @param {?boolean} hasWideEnabled True if theme supports wide alignments. And False otherwise.
*
* @return {string[]} Valid alignments.
*/
export function getValidAlignments(
blockAlign,
hasWideBlockSupport = true,
hasWideEnabled = true
) {
let validAlignments;
if ( Array.isArray( blockAlign ) ) {
validAlignments = ALL_ALIGNMENTS.filter( ( value ) =>
blockAlign.includes( value )
);
} else if ( blockAlign === true ) {
// `true` includes all alignments...
validAlignments = [ ...ALL_ALIGNMENTS ];
} else {
validAlignments = [];
}
if (
! hasWideEnabled ||
( blockAlign === true && ! hasWideBlockSupport )
) {
return validAlignments.filter(
( alignment ) => ! WIDE_ALIGNMENTS.includes( alignment )
);
}
return validAlignments;
}
/**
* Filters registered block settings, extending attributes to include `align`.
*
* @param {Object} settings Original block settings.
*
* @return {Object} Filtered block settings.
*/
export function addAttribute( settings ) {
// Allow blocks to specify their own attribute definition with default values if needed.
if ( 'type' in ( settings.attributes?.align ?? {} ) ) {
return settings;
}
if ( hasBlockSupport( settings, 'align' ) ) {
// Gracefully handle if settings.attributes is undefined.
settings.attributes = {
...settings.attributes,
align: {
type: 'string',
// Allow for '' since it is used by the `updateAlignment` function
// in toolbar controls for special cases with defined default values.
enum: [ ...ALL_ALIGNMENTS, '' ],
},
};
}
return settings;
}
function BlockEditAlignmentToolbarControlsPure( {
name: blockName,
align,
setAttributes,
} ) {
// Compute the block valid alignments by taking into account,
// if the theme supports wide alignments or not and the layout's
// available alignments. We do that for conditionally rendering
// Slot.
const blockAllowedAlignments = getValidAlignments(
getBlockSupport( blockName, 'align' ),
hasBlockSupport( blockName, 'alignWide', true )
);
const validAlignments = useAvailableAlignments(
blockAllowedAlignments
).map( ( { name } ) => name );
const blockEditingMode = useBlockEditingMode();
if ( ! validAlignments.length || blockEditingMode !== 'default' ) {
return null;
}
const updateAlignment = ( nextAlign ) => {
if ( ! nextAlign ) {
const blockType = getBlockType( blockName );
const blockDefaultAlign = blockType?.attributes?.align?.default;
if ( blockDefaultAlign ) {
nextAlign = '';
}
}
setAttributes( { align: nextAlign } );
};
return (
<BlockControls group="block" __experimentalShareWithChildBlocks>
<BlockAlignmentControl
value={ align }
onChange={ updateAlignment }
controls={ validAlignments }
/>
</BlockControls>
);
}
export default {
shareWithChildBlocks: true,
edit: BlockEditAlignmentToolbarControlsPure,
useBlockProps,
addSaveProps: addAssignedAlign,
attributeKeys: [ 'align' ],
hasSupport( name ) {
return hasBlockSupport( name, 'align', false );
},
};
function useBlockProps( { name, align } ) {
const blockAllowedAlignments = getValidAlignments(
getBlockSupport( name, 'align' ),
hasBlockSupport( name, 'alignWide', true )
);
const validAlignments = useAvailableAlignments( blockAllowedAlignments );
if ( validAlignments.some( ( alignment ) => alignment.name === align ) ) {
return { 'data-align': align };
}
return {};
}
/**
* Override props assigned to save component to inject alignment class name if
* block supports it.
*
* @param {Object} props Additional props applied to save element.
* @param {Object} blockType Block type.
* @param {Object} attributes Block attributes.
*
* @return {Object} Filtered props applied to save element.
*/
export function addAssignedAlign( props, blockType, attributes ) {
const { align } = attributes;
const blockAlign = getBlockSupport( blockType, 'align' );
const hasWideBlockSupport = hasBlockSupport( blockType, 'alignWide', true );
// Compute valid alignments without taking into account if
// the theme supports wide alignments or not.
// This way changing themes does not impact the block save.
const isAlignValid = getValidAlignments(
blockAlign,
hasWideBlockSupport
).includes( align );
if ( isAlignValid ) {
props.className = clsx( `align${ align }`, props.className );
}
return props;
}
addFilter(
'blocks.registerBlockType',
'core/editor/align/addAttribute',
addAttribute
);