Skip to content

Commit

Permalink
Block API: Reimplement align as common extension (#4069)
Browse files Browse the repository at this point in the history
* Block API: Add getBlockSupport API

For retrieving argument-based block supports

* Block List: Support wrapper props via incoming prop

* Hooks: Add block hook for alignment

* Blocks: Port Audio block to use align supports

* Hooks: Pass valid alignments to rendered align toolbar

* Blocks: Small tweaks to supports align property
  • Loading branch information
aduth authored and gziolo committed Feb 16, 2018
1 parent ca8f111 commit c8f8c9b
Show file tree
Hide file tree
Showing 8 changed files with 457 additions and 27 deletions.
1 change: 1 addition & 0 deletions blocks/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
getDefaultBlockName,
getBlockType,
getBlockTypes,
getBlockSupport,
hasBlockSupport,
isReusableBlock,
} from './registration';
Expand Down
29 changes: 21 additions & 8 deletions blocks/api/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,26 @@ export function getBlockTypes() {
return Object.values( blocks );
}

/**
* Returns the block support value for a feature, if defined.
*
* @param {(string|Object)} nameOrType Block name or type object
* @param {string} feature Feature to retrieve
* @param {*} defaultSupports Default value to return if not
* explicitly defined
* @return {?*} Block support value
*/
export function getBlockSupport( nameOrType, feature, defaultSupports ) {
const blockType = 'string' === typeof nameOrType ?
getBlockType( nameOrType ) :
nameOrType;

return get( blockType, [
'supports',
feature,
], defaultSupports );
}

/**
* Returns true if the block defines support for a feature, or false otherwise.
*
Expand All @@ -238,14 +258,7 @@ export function getBlockTypes() {
* @return {boolean} Whether block supports feature.
*/
export function hasBlockSupport( nameOrType, feature, defaultSupports ) {
const blockType = 'string' === typeof nameOrType ?
getBlockType( nameOrType ) :
nameOrType;

return !! get( blockType, [
'supports',
feature,
], defaultSupports );
return !! getBlockSupport( nameOrType, feature, defaultSupports );
}

/**
Expand Down
36 changes: 36 additions & 0 deletions blocks/api/test/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getDefaultBlockName,
getBlockType,
getBlockTypes,
getBlockSupport,
hasBlockSupport,
isReusableBlock,
} from '../registration';
Expand Down Expand Up @@ -378,6 +379,41 @@ describe( 'blocks', () => {
} );
} );

describe( 'getBlockSupport', () => {
it( 'should return undefined if block has no supports', () => {
registerBlockType( 'core/test-block', {
...defaultBlockSettings,
supports: {
bar: true,
},
} );

expect( getBlockSupport( 'core/test-block', 'foo' ) ).toBe( undefined );
} );

it( 'should return block supports value', () => {
registerBlockType( 'core/test-block', {
...defaultBlockSettings,
supports: {
bar: true,
},
} );

expect( getBlockSupport( 'core/test-block', 'bar' ) ).toBe( true );
} );

it( 'should return custom default supports if block does not define support by name', () => {
registerBlockType( 'core/test-block', {
...defaultBlockSettings,
supports: {
bar: true,
},
} );

expect( getBlockSupport( 'core/test-block', 'foo', true ) ).toBe( true );
} );
} );

describe( 'hasBlockSupport', () => {
it( 'should return false if block has no supports', () => {
registerBlockType( 'core/test-block', defaultBlockSettings );
Expand Down
145 changes: 145 additions & 0 deletions blocks/hooks/align.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { assign, includes } from 'lodash';

/**
* WordPress dependencies
*/
import { getWrapperDisplayName } from '@wordpress/element';
import { addFilter } from '@wordpress/hooks';
import BlockControls from '../block-controls';
import BlockAlignmentToolbar from '../block-alignment-toolbar';

/**
* Internal dependencies
*/
import { getBlockSupport, hasBlockSupport } from '../api';

/**
* Filters registered block settings, extending attributes to include `align`.
*
* @param {Object} settings Original block settings
* @return {Object} Filtered block settings
*/
export function addAttribute( settings ) {
if ( hasBlockSupport( settings, 'align' ) ) {
// Use Lodash's assign to gracefully handle if attributes are undefined
settings.attributes = assign( settings.attributes, {
align: {
type: 'string',
},
} );
}

return settings;
}

/**
* Returns an array of valid alignments for a block type depending on its
* defined supports. Returns an empty array if block does not support align.
*
* @param {string} blockName Block name to check
* @return {string[]} Valid alignments for block
*/
export function getBlockValidAlignments( blockName ) {
// Explicitly defined array set of valid alignments
const blockAlign = getBlockSupport( blockName, 'align' );
if ( Array.isArray( blockAlign ) ) {
return blockAlign;
}

const validAlignments = [];
if ( true === blockAlign ) {
// `true` includes all alignments...
validAlignments.push( 'left', 'center', 'right' );

// ...including wide alignments unless explicitly `false`.
if ( hasBlockSupport( blockName, 'wideAlign', true ) ) {
validAlignments.push( 'wide', 'full' );
}
}

return validAlignments;
}

/**
* Override the default edit UI to include new toolbar controls for block
* alignment, if block defines support.
*
* @param {Function} BlockEdit Original component
* @return {Function} Wrapped component
*/
export function withToolbarControls( BlockEdit ) {
const WrappedBlockEdit = ( props ) => {
const validAlignments = getBlockValidAlignments( props.name );

const updateAlignment = ( nextAlign ) => props.setAttributes( { align: nextAlign } );

return [
validAlignments.length > 0 && props.isSelected && (
<BlockControls key="align-controls">
<BlockAlignmentToolbar
value={ props.attributes.align }
onChange={ updateAlignment }
controls={ validAlignments }
/>
</BlockControls>
),
<BlockEdit key="edit" { ...props } />,
];
};
WrappedBlockEdit.displayName = getWrapperDisplayName( BlockEdit, 'align' );

return WrappedBlockEdit;
}

/**
* Override the default block element to add alignment wrapper props.
*
* @param {Function} BlockListBlock Original component
* @return {Function} Wrapped component
*/
export function withAlign( BlockListBlock ) {
const WrappedComponent = ( props ) => {
const { align } = props.block.attributes;
const validAlignments = getBlockValidAlignments( props.block.name );

let wrapperProps = props.wrapperProps;
if ( includes( validAlignments, align ) ) {
wrapperProps = { ...wrapperProps, 'data-align': align };
}

return <BlockListBlock { ...props } wrapperProps={ wrapperProps } />;
};

WrappedComponent.displayName = getWrapperDisplayName( BlockListBlock, 'align' );

return WrappedComponent;
}

/**
* 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;

if ( includes( getBlockValidAlignments( blockType ), align ) ) {
props.className = classnames( `align${ align }`, props.className );
}

return props;
}

addFilter( 'blocks.registerBlockType', 'core/align/addAttribute', addAttribute );
addFilter( 'editor.BlockListBlock', 'core/align/withAlign', withAlign );
addFilter( 'blocks.BlockEdit', 'core/align/withToolbarControls', withToolbarControls );
addFilter( 'blocks.getSaveContent.extraProps', 'core/align/addAssignedAlign', addAssignedAlign );

1 change: 1 addition & 0 deletions blocks/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/**
* Internal dependencies
*/
import './align';
import './anchor';
import './custom-class-name';
import './deprecated';
Expand Down
Loading

0 comments on commit c8f8c9b

Please sign in to comment.