Skip to content

Commit

Permalink
Block Library: Add width attribute for resizable Column blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
aduth committed May 8, 2019
1 parent 0442276 commit 67d2b75
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 89 deletions.
5 changes: 5 additions & 0 deletions packages/block-library/src/column/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
"attributes": {
"verticalAlignment": {
"type": "string"
},
"width": {
"type": "number",
"min": 0,
"max": 100
}
}
}
84 changes: 65 additions & 19 deletions packages/block-library/src/column/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,49 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { InnerBlocks, BlockControls, BlockVerticalAlignmentToolbar } from '@wordpress/block-editor';
import {
InnerBlocks,
BlockControls,
BlockVerticalAlignmentToolbar,
InspectorControls,
} from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

function ColumnEdit( {
attributes,
updateAlignment,
updateWidth,
hasChildBlocks,
} ) {
const { verticalAlignment } = attributes;
const { verticalAlignment, width } = attributes;

const classes = classnames( 'block-core-columns', {
[ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment,
} );

const onChange = ( alignment ) => updateAlignment( alignment );

return (
<div className={ classes }>
<BlockControls>
<BlockVerticalAlignmentToolbar
onChange={ onChange }
onChange={ updateAlignment }
value={ verticalAlignment }
/>
</BlockControls>
<InspectorControls>
<PanelBody title={ __( 'Column Settings' ) }>
<RangeControl
label={ __( 'Width' ) }
value={ width }
onChange={ updateWidth }
min={ 0 }
max={ 100 }
required
/>
</PanelBody>
</InspectorControls>
<InnerBlocks
templateLock={ false }
renderAppender={ (
Expand All @@ -46,28 +64,56 @@ function ColumnEdit( {
export default compose(
withSelect( ( select, ownProps ) => {
const { clientId } = ownProps;
const {
getBlockRootClientId,
getBlockOrder,
} = select( 'core/block-editor' );
const { getBlockOrder } = select( 'core/block-editor' );

return {
parentColumnsBlockClientId: getBlockRootClientId( clientId ),
hasChildBlocks: getBlockOrder( clientId ).length > 0,
};
} ),
withDispatch( ( dispatch, { clientId, parentColumnsBlockClientId } ) => {
withDispatch( ( dispatch, ownProps, registry ) => {
return {
updateAlignment( alignment ) {
// Update self...
dispatch( 'core/editor' ).updateBlockAttributes( clientId, {
verticalAlignment: alignment,
} );
updateAlignment( verticalAlignment ) {
const { clientId, setAttributes } = ownProps;
const { updateBlockAttributes } = dispatch( 'core/block-editor' );
const { getBlockRootClientId } = registry.select( 'core/block-editor' );

// Update own alignment.
setAttributes( { verticalAlignment } );

// Reset Parent Columns Block
dispatch( 'core/editor' ).updateBlockAttributes( parentColumnsBlockClientId, {
verticalAlignment: null,
} );
const rootClientId = getBlockRootClientId( clientId );
updateBlockAttributes( rootClientId, { verticalAlignment: null } );
},
updateWidth( width ) {
const { clientId, attributes, setAttributes } = ownProps;
const { updateBlockAttributes } = dispatch( 'core/block-editor' );
const {
getBlockRootClientId,
getBlockOrder,
getBlockAttributes,
} = registry.select( 'core/block-editor' );

// Update own width.
setAttributes( { width } );

// Constrain or expand siblings to account for gain or loss of
// total columns area.
const rootClientId = getBlockRootClientId( clientId );
const columnClientIds = getBlockOrder( rootClientId );
const { width: previousWidth = 100 / columnClientIds.length } = attributes;
const index = columnClientIds.indexOf( clientId );
const isLastColumn = index === columnClientIds.length - 1;
const increment = isLastColumn ? -1 : 1;
const endIndex = isLastColumn ? 0 : columnClientIds.length - 1;
const adjustment = ( previousWidth - width ) / Math.abs( index - endIndex );

for ( let i = index + increment; i - increment !== endIndex; i += increment ) {
const columnClientId = columnClientIds[ i ];
const { width: columnWidth = 100 / columnClientIds.length } = getBlockAttributes( columnClientId );
updateBlockAttributes( columnClientId, {
width: columnWidth + adjustment,
} );
}
},
};
} )
Expand Down
10 changes: 10 additions & 0 deletions packages/block-library/src/column/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export const settings = {
reusable: false,
html: false,
},
getEditWrapperProps( attributes ) {
const { width } = attributes;
if ( Number.isFinite( width ) ) {
return {
style: {
flexBasis: width + '%',
},
};
}
},
edit,
save,
};
Expand Down
10 changes: 8 additions & 2 deletions packages/block-library/src/column/save.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ import classnames from 'classnames';
import { InnerBlocks } from '@wordpress/block-editor';

export default function save( { attributes } ) {
const { verticalAlignment } = attributes;
const { verticalAlignment, width } = attributes;

const wrapperClasses = classnames( {
[ `is-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment,
} );

let style;
if ( Number.isFinite( width ) ) {
style = { flexBasis: width + '%' };
}

return (
<div className={ wrapperClasses }>
<div className={ wrapperClasses } style={ style }>
<InnerBlocks.Content />
</div>
);
Expand Down
148 changes: 96 additions & 52 deletions packages/block-library/src/columns/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
* External dependencies
*/
import classnames from 'classnames';
import { last } from 'lodash';

/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { compose } from '@wordpress/compose';
import {
PanelBody,
RangeControl,
Expand All @@ -18,7 +18,8 @@ import {
BlockControls,
BlockVerticalAlignmentToolbar,
} from '@wordpress/block-editor';
import { withSelect, withDispatch } from '@wordpress/data';
import { withDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';

/**
* Internal dependencies
Expand All @@ -36,38 +37,34 @@ import { getColumnsTemplate } from './utils';
*/
const ALLOWED_BLOCKS = [ 'core/column' ];

export const ColumnsEdit = function( { attributes, setAttributes, className, updateAlignment } ) {
export function ColumnsEdit( {
attributes,
className,
updateAlignment,
updateColumns,
} ) {
const { columns, verticalAlignment } = attributes;

const classes = classnames( className, `has-${ columns }-columns`, {
[ `are-vertically-aligned-${ verticalAlignment }` ]: verticalAlignment,
} );

const onChange = ( alignment ) => {
// Update all the (immediate) child Column Blocks
updateAlignment( alignment );
};

return (
<>
<InspectorControls>
<PanelBody>
<RangeControl
label={ __( 'Columns' ) }
value={ columns }
onChange={ ( nextColumns ) => {
setAttributes( {
columns: nextColumns,
} );
} }
onChange={ updateColumns }
min={ 2 }
max={ 6 }
/>
</PanelBody>
</InspectorControls>
<BlockControls>
<BlockVerticalAlignmentToolbar
onChange={ onChange }
onChange={ updateAlignment }
value={ verticalAlignment }
/>
</BlockControls>
Expand All @@ -79,45 +76,92 @@ export const ColumnsEdit = function( { attributes, setAttributes, className, upd
</div>
</>
);
};
}

export default withDispatch( ( dispatch, ownProps, registry ) => ( {
/**
* Update all child Column blocks with a new vertical alignment setting
* based on whatever alignment is passed in. This allows change to parent
* to overide anything set on a individual column basis.
*
* @param {string} verticalAlignment the vertical alignment setting
*/
updateAlignment( verticalAlignment ) {
const { clientId, setAttributes } = ownProps;
const { updateBlockAttributes } = dispatch( 'core/block-editor' );
const { getBlockOrder } = registry.select( 'core/block-editor' );

const DEFAULT_EMPTY_ARRAY = [];
// Update own alignment.
setAttributes( { verticalAlignment } );

// Update all child Column Blocks to match
const innerBlockClientIds = getBlockOrder( clientId );
innerBlockClientIds.forEach( ( innerBlockClientId ) => {
updateBlockAttributes( innerBlockClientId, {
verticalAlignment,
} );
} );
},

export default compose(
/**
* Selects the child column Blocks for this parent Column
* Updates the column count, including necessary revisions to child Column
* blocks to grant required or redistribute available space.
*
* @param {number} columns New column count.
*/
withSelect( ( select, { clientId } ) => {
const { getBlocksByClientId } = select( 'core/editor' );
const block = getBlocksByClientId( clientId )[ 0 ];

return {
childColumns: block ? block.innerBlocks : DEFAULT_EMPTY_ARRAY,
};
} ),
withDispatch( ( dispatch, { clientId, childColumns } ) => {
return {
/**
* Update all child column Blocks with a new
* vertical alignment setting based on whatever
* alignment is passed in. This allows change to parent
* to overide anything set on a individual column basis
*
* @param {string} alignment the vertical alignment setting
*/
updateAlignment( alignment ) {
// Update self...
dispatch( 'core/editor' ).updateBlockAttributes( clientId, {
verticalAlignment: alignment,
} );

// Update all child Column Blocks to match
childColumns.forEach( ( childColumn ) => {
dispatch( 'core/editor' ).updateBlockAttributes( childColumn.clientId, {
verticalAlignment: alignment,
} );
} );
},
};
} ),
)( ColumnsEdit );
updateColumns( columns ) {
const { clientId, setAttributes, attributes } = ownProps;
const { replaceInnerBlocks } = dispatch( 'core/block-editor' );
const { getBlocks } = registry.select( 'core/block-editor' );

// Update columns count.
setAttributes( { columns } );

let innerBlocks = getBlocks( clientId );
const hasExplicitColumnWidths = innerBlocks.some( ( innerBlock ) => (
innerBlock.attributes.width !== undefined
) );

let newOrRemovedColumnWidth;
if ( ! hasExplicitColumnWidths ) {
return;
}

// Redistribute available width for existing inner blocks.
const { columns: previousColumns } = attributes;
const isAddingColumn = columns > previousColumns;

if ( isAddingColumn ) {
// If adding a new column, assign width to the new column equal to
// as if it were `1 / columns` of the total available space.
newOrRemovedColumnWidth = ( 100 / columns );
} else {
// The removed column will be the last of the inner blocks.
newOrRemovedColumnWidth = last( innerBlocks ).attributes.width || ( 100 / previousColumns );
}

const adjustment = newOrRemovedColumnWidth / ( isAddingColumn ? -1 * previousColumns : columns );
innerBlocks = innerBlocks.map( ( innerBlock ) => {
const { width: columnWidth = ( 100 / previousColumns ) } = innerBlock.attributes;
return {
...innerBlock,
attributes: {
...innerBlocks.attributes,
width: parseFloat( ( columnWidth + adjustment ).toFixed( 2 ) ),
},
};
} );

// Explicitly manage the new column block, since template would not
// account for the explicitly assigned width.
if ( isAddingColumn ) {
const block = createBlock( 'core/column', {
width: parseFloat( newOrRemovedColumnWidth.toFixed( 2 ) ),
} );

innerBlocks = [ ...innerBlocks, block ];
}

replaceInnerBlocks( clientId, innerBlocks, false );
},
} ) )( ColumnsEdit );
11 changes: 0 additions & 11 deletions packages/block-library/src/columns/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,3 @@ div.block-core-columns.is-vertically-aligned-bottom {
display: none;
}
}

// In absence of making the individual columns resizable, we prevent them from being clickable.
// This makes them less fiddly. @todo: This should be revisited as the interface is refined.
.wp-block-columns [data-type="core/column"] {
pointer-events: none;

// This selector re-enables clicking on any child of a column block.
.block-core-columns .block-editor-block-list__layout {
pointer-events: all;
}
}
Loading

0 comments on commit 67d2b75

Please sign in to comment.