Skip to content

Commit

Permalink
Block Library: Refactor Columns width redistribution to reusable util…
Browse files Browse the repository at this point in the history
…ities
  • Loading branch information
aduth committed May 9, 2019
1 parent 609e787 commit 6880cfa
Show file tree
Hide file tree
Showing 4 changed files with 410 additions and 55 deletions.
62 changes: 38 additions & 24 deletions packages/block-library/src/column/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { forEach, find, difference } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -17,6 +18,17 @@ import { withDispatch, withSelect } from '@wordpress/data';
import { compose } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';

/**
* Internal dependencies
*/
import {
toWidthPrecision,
getTotalColumnsWidth,
getColumnWidths,
getAdjacentBlocks,
getRedistributedColumnWidths,
} from '../columns/utils';

function ColumnEdit( {
attributes,
updateAlignment,
Expand Down Expand Up @@ -85,35 +97,37 @@ export default compose(
updateBlockAttributes( rootClientId, { verticalAlignment: null } );
},
updateWidth( width ) {
const { clientId, attributes, setAttributes } = ownProps;
const { clientId } = ownProps;
const { updateBlockAttributes } = dispatch( 'core/block-editor' );
const {
getBlockRootClientId,
getBlockOrder,
getBlockAttributes,
} = registry.select( 'core/block-editor' );

// Update own width.
setAttributes( { width } );
const { getBlockRootClientId, getBlocks } = registry.select( 'core/block-editor' );

// 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 );
const columns = getBlocks( getBlockRootClientId( clientId ) );
const adjacentColumns = getAdjacentBlocks( columns, clientId );

// The occupied width is calculated as the sum of the new width
// and the total width of blocks _not_ in the adjacent set.
const occupiedWidth = width + getTotalColumnsWidth(
difference( columns, [
find( columns, { clientId } ),
...adjacentColumns,
] )
);

// Compute _all_ next column widths, in case the updated column
// is in the middle of a set of columns which don't yet have
// any explicit widths assigned (include updates to those not
// part of the adjacent blocks).
const nextColumnWidths = {
...getColumnWidths( columns, columns.length ),
[ clientId ]: toWidthPrecision( width ),
...getRedistributedColumnWidths( adjacentColumns, 100 - occupiedWidth, columns.length ),
};

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,
} );
}
forEach( nextColumnWidths, ( nextColumnWidth, columnClientId ) => {
updateBlockAttributes( columnClientId, { width: nextColumnWidth } );
} );
},
};
} )
Expand Down
55 changes: 25 additions & 30 deletions packages/block-library/src/columns/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External dependencies
*/
import classnames from 'classnames';
import { last } from 'lodash';
import { dropRight } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -24,7 +24,13 @@ import { createBlock } from '@wordpress/blocks';
/**
* Internal dependencies
*/
import { getColumnsTemplate } from './utils';
import {
getColumnsTemplate,
hasExplicitColumnWidths,
getMappedColumnWidths,
getRedistributedColumnWidths,
toWidthPrecision,
} from './utils';

/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
Expand Down Expand Up @@ -118,12 +124,7 @@ export default withDispatch( ( dispatch, ownProps, registry ) => ( {
setAttributes( { columns } );

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

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

Expand All @@ -134,32 +135,26 @@ export default withDispatch( ( dispatch, ownProps, registry ) => ( {
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 );
const newColumnWidth = toWidthPrecision( 100 / columns );

// Redistribute in consideration of pending block insertion as
// constraining the available working width.
const widths = getRedistributedColumnWidths( innerBlocks, 100 - newColumnWidth );

innerBlocks = [
...getMappedColumnWidths( innerBlocks, widths ),
createBlock( 'core/column', {
width: newColumnWidth,
} ),
];
} else {
// The removed column will be the last of the inner blocks.
newOrRemovedColumnWidth = last( innerBlocks ).attributes.width || ( 100 / previousColumns );
}
innerBlocks = dropRight( innerBlocks );

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 ) ),
} );
// Redistribute as if block is already removed.
const widths = getRedistributedColumnWidths( innerBlocks, 100 );

innerBlocks = [ ...innerBlocks, block ];
innerBlocks = getMappedColumnWidths( innerBlocks, widths );
}

replaceInnerBlocks( clientId, innerBlocks, false );
Expand Down
224 changes: 224 additions & 0 deletions packages/block-library/src/columns/test/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
/**
* Internal dependencies
*/
import {
getColumnsTemplate,
toWidthPrecision,
getAdjacentBlocks,
getEffectiveColumnWidth,
getTotalColumnsWidth,
getColumnWidths,
getRedistributedColumnWidths,
hasExplicitColumnWidths,
getMappedColumnWidths,
} from '../utils';

describe( 'getColumnsTemplate', () => {
it( 'should return a template corresponding to columns count', () => {
const template = getColumnsTemplate( 4 );

expect( template ).toEqual( [
[ 'core/column' ],
[ 'core/column' ],
[ 'core/column' ],
[ 'core/column' ],
] );
} );
} );

describe( 'toWidthPrecision', () => {
it( 'should round value to standard precision', () => {
const value = toWidthPrecision( 50.108 );

expect( value ).toBe( 50.11 );
} );
} );

describe( 'getAdjacentBlocks', () => {
const blockA = { clientId: 'a' };
const blockB = { clientId: 'b' };
const blockC = { clientId: 'c' };
const blocks = [ blockA, blockB, blockC ];

it( 'should return blocks after clientId', () => {
const result = getAdjacentBlocks( blocks, 'b' );

expect( result ).toEqual( [ blockC ] );
} );

it( 'should return blocks before clientId if clientId is last', () => {
const result = getAdjacentBlocks( blocks, 'c' );

expect( result ).toEqual( [ blockA, blockB ] );
} );
} );

describe( 'getEffectiveColumnWidth', () => {
it( 'should return attribute value if set, rounded to precision', () => {
const block = { attributes: { width: 50.108 } };

const width = getEffectiveColumnWidth( block, 3 );

expect( width ).toBe( 50.11 );
} );

it( 'should return assumed width if attribute value not set, rounded to precision', () => {
const block = { attributes: {} };

const width = getEffectiveColumnWidth( block, 3 );

expect( width ).toBe( 33.33 );
} );
} );

describe( 'getTotalColumnsWidth', () => {
describe( 'explicit width', () => {
const blocks = [
{ clientId: 'a', attributes: { width: 30 } },
{ clientId: 'b', attributes: { width: 40 } },
];

it( 'returns the sum total of columns width', () => {
const width = getTotalColumnsWidth( blocks );

expect( width ).toBe( 70 );
} );
} );

describe( 'implicit width', () => {
const blocks = [
{ clientId: 'a', attributes: {} },
{ clientId: 'b', attributes: {} },
];

it( 'returns the sum total of columns width', () => {
const widths = getTotalColumnsWidth( blocks );

expect( widths ).toBe( 100 );
} );
} );
} );

describe( 'getColumnWidths', () => {
describe( 'explicit width', () => {
const blocks = [
{ clientId: 'a', attributes: { width: 30.459 } },
{ clientId: 'b', attributes: { width: 29.543 } },
];

it( 'returns the column widths', () => {
const widths = getColumnWidths( blocks );

expect( widths ).toEqual( {
a: 30.46,
b: 29.54,
} );
} );
} );

describe( 'implicit width', () => {
const blocks = [
{ clientId: 'a', attributes: {} },
{ clientId: 'b', attributes: {} },
];

it( 'returns the column widths', () => {
const widths = getColumnWidths( blocks );

expect( widths ).toEqual( {
a: 50,
b: 50,
} );
} );
} );
} );

describe( 'getRedistributedColumnWidths', () => {
describe( 'explicit width', () => {
const blocks = [
{ clientId: 'a', attributes: { width: 30 } },
{ clientId: 'b', attributes: { width: 40 } },
];

it( 'should constrain to fit available width', () => {
const widths = getRedistributedColumnWidths( blocks, 60 );

expect( widths ).toEqual( {
a: 25,
b: 35,
} );
} );

it( 'should expand to fit available width', () => {
const widths = getRedistributedColumnWidths( blocks, 80 );

expect( widths ).toEqual( {
a: 35,
b: 45,
} );
} );
} );

describe( 'implicit width', () => {
const blocks = [
{ clientId: 'a', attributes: {} },
{ clientId: 'b', attributes: {} },
];

it( 'should equally distribute to available width', () => {
const widths = getRedistributedColumnWidths( blocks, 60 );

expect( widths ).toEqual( {
a: 30,
b: 30,
} );
} );

it( 'should constrain to fit available width', () => {
const widths = getRedistributedColumnWidths( blocks, 66.66, 3 );

expect( widths ).toEqual( {
a: 33.33,
b: 33.33,
} );
} );
} );
} );

describe( 'hasExplicitColumnWidths', () => {
it( 'returns false if no blocks have explicit width', () => {
const blocks = [ { attributes: {} } ];

const result = hasExplicitColumnWidths( blocks );

expect( result ).toBe( false );
} );

it( 'returns true if a block has explicit width', () => {
const blocks = [ { attributes: { width: 10 } } ];

const result = hasExplicitColumnWidths( blocks );

expect( result ).toBe( true );
} );
} );

describe( 'getMappedColumnWidths', () => {
it( 'merges to block attributes using provided widths', () => {
const blocks = [
{ clientId: 'a', attributes: { width: 30 } },
{ clientId: 'b', attributes: { width: 40 } },
];
const widths = {
a: 25,
b: 35,
};

const result = getMappedColumnWidths( blocks, widths );

expect( result ).toEqual( [
{ clientId: 'a', attributes: { width: 25 } },
{ clientId: 'b', attributes: { width: 35 } },
] );
} );
} );
Loading

0 comments on commit 6880cfa

Please sign in to comment.