Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Library: Implement drag handles for columns resizing #15927

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/block-editor/src/store/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ export function blockSelection( state = BLOCK_SELECTION_INITIAL_STATE, action )
}
case 'TOGGLE_SELECTION':
return {
...BLOCK_SELECTION_INITIAL_STATE,
...state,
isEnabled: action.isSelectionEnabled,
};
case 'SELECTION_CHANGE':
Expand Down
18 changes: 18 additions & 0 deletions packages/block-editor/src/store/test/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1727,6 +1727,24 @@ describe( 'state', () => {
expect( state1 ).toBe( original );
} );

it( 'should maintain selection when toggling multi-selection', () => {
const original = deepFreeze( {
start: { clientId: 'ribs' },
end: { clientId: 'ribs' },
} );

const state = blockSelection( original, {
type: 'TOGGLE_SELECTION',
isSelectionEnabled: false,
} );

expect( state ).toEqual( {
start: { clientId: 'ribs' },
end: { clientId: 'ribs' },
isEnabled: false,
} );
} );

it( 'should unset multi selection', () => {
const original = deepFreeze( { start: { clientId: 'ribs' }, end: { clientId: 'chicken' } } );

Expand Down
9 changes: 9 additions & 0 deletions packages/block-library/src/columns/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
getRedistributedColumnWidths,
toWidthPrecision,
} from './utils';
import ColumnsResizer from './resizer';

/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
Expand Down Expand Up @@ -111,6 +112,8 @@ export function ColumnsEdit( {
updateAlignment,
updateColumns,
clientId,
isSelected,
toggleSelection,
} ) {
const { verticalAlignment } = attributes;

Expand Down Expand Up @@ -175,6 +178,12 @@ export function ColumnsEdit( {
template={ count === 0 && ! forceUseTemplate ? null : template }
templateLock="all"
allowedBlocks={ ALLOWED_BLOCKS } />
{ isSelected && (
<ColumnsResizer
clientId={ clientId }
toggleSelection={ toggleSelection }
/>
) }
</div>
</>
);
Expand Down
186 changes: 113 additions & 73 deletions packages/block-library/src/columns/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,98 +33,138 @@

.wp-block-columns {
display: block;
}

> .editor-inner-blocks > .editor-block-list__layout {
display: flex;
.wp-block-columns > .editor-inner-blocks > .editor-block-list__layout,
.wp-block-columns__resizer {
display: flex;

// Responsiveness: Allow wrapping on mobile.
flex-wrap: wrap;
// Responsiveness: Allow wrapping on mobile.
flex-wrap: wrap;

@include break-medium() {
flex-wrap: nowrap;
@include break-medium() {
flex-wrap: nowrap;
}
// Set full heights on Columns to enable vertical alignment preview
> [data-type="core/column"],
> [data-type="core/column"] > .editor-block-list__block-edit,
> [data-type="core/column"] > .editor-block-list__block-edit > div[data-block],
> [data-type="core/column"] > .editor-block-list__block-edit .block-core-columns,
> .wp-block-columns__resize-box {
@include flex-full-height();
}
// Adjust the individual column block.
> [data-type="core/column"],
.wp-block-columns__resize-box {

// On mobile, only a single column is shown, so match adjacent block paddings.
padding-left: 0;
padding-right: 0;
margin-left: -$block-padding;
margin-right: -$block-padding;

// Prevent the columns from growing wider than their distributed sizes.
min-width: 0;

// Prevent long unbroken words from overflowing.
word-break: break-word; // For back-compat.
overflow-wrap: break-word; // New standard.

// Responsiveness: Show at most one columns on mobile.
flex-basis: 100%;

// Beyond mobile, allow 2 columns.
@include break-small() {
flex-grow: 0;
margin-left: $block-padding;
margin-right: $block-padding;

// TODO: This is not viable, since it will cause overflow of the
// columns content, but is necessary as demonstration of the impact
// on browser flex distribution on assigned width disparities.
flex-shrink: 0;
flex-basis: 50%;
}
// Set full heights on Columns to enable vertical alignment preview
> [data-type="core/column"],
> [data-type="core/column"] > .editor-block-list__block-edit,
> [data-type="core/column"] > .editor-block-list__block-edit > div[data-block],
> [data-type="core/column"] > .editor-block-list__block-edit .block-core-columns {
@include flex-full-height();

// Add space between columns. Themes can customize this if they wish to work differently.
// This has to match the same padding applied in style.scss.
// Only apply this beyond the mobile breakpoint, as there's only a single column on mobile.
@include break-small() {
&:nth-child(even) {
margin-left: calc(#{$grid-size-large * 2} + #{$block-padding});
}
}
// Adjust the individual column block.
> [data-type="core/column"] {

// On mobile, only a single column is shown, so match adjacent block paddings.
padding-left: 0;
padding-right: 0;
margin-left: -$block-padding;
margin-right: -$block-padding;

// Prevent the columns from growing wider than their distributed sizes.
min-width: 0;

// Prevent long unbroken words from overflowing.
word-break: break-word; // For back-compat.
overflow-wrap: break-word; // New standard.

// Responsiveness: Show at most one columns on mobile.
flex-basis: 100%;

// Beyond mobile, allow 2 columns.
@include break-small() {
flex-basis: calc(50% - (#{$grid-size-large} + #{$block-padding * 2}));
flex-grow: 0;
margin-left: $block-padding;
margin-right: $block-padding;

// When columns are in a single row, add space before all except the first.
@include break-medium() {
&:not(:first-child) {
margin-left: calc(#{$grid-size-large * 2} + #{$block-padding});
}
}

// Add space between columns. Themes can customize this if they wish to work differently.
// This has to match the same padding applied in style.scss.
// Only apply this beyond the mobile breakpoint, as there's only a single column on mobile.
@include break-small() {
&:nth-child(even) {
margin-left: calc(#{$grid-size-large * 2} + #{$block-padding});
}
> .editor-block-list__block-edit {
margin-top: 0;
margin-bottom: 0;

// Remove Block "padding" so individual Column is flush with parent Columns
&::before {
left: 0;
right: 0;
}

// When columns are in a single row, add space before all except the first.
@include break-medium() {
&:not(:first-child) {
margin-left: calc(#{$grid-size-large * 2} + #{$block-padding});
}
> .editor-block-contextual-toolbar {
margin-left: -$border-width;
}

> .editor-block-list__block-edit {
// Zero out margins.
> [data-block] {
margin-top: 0;
margin-bottom: 0;
}

// Remove Block "padding" so individual Column is flush with parent Columns
&::before {
left: 0;
right: 0;
}

> .editor-block-contextual-toolbar {
margin-left: -$border-width;
}

// Zero out margins.
> [data-block] {
margin-top: 0;
margin-bottom: 0;
}

// The Columns block is a flex-container, therefore it nullifies margin collapsing.
// Therefore, blocks inside this will appear to create a double margin.
// We compensate for this using negative margins.
> div > .block-core-columns > .editor-inner-blocks {
margin-top: -$default-block-margin;
margin-bottom: -$default-block-margin;
}
// The Columns block is a flex-container, therefore it nullifies margin collapsing.
// Therefore, blocks inside this will appear to create a double margin.
// We compensate for this using negative margins.
> div > .block-core-columns > .editor-inner-blocks {
margin-top: -$default-block-margin;
margin-bottom: -$default-block-margin;
}
}
}
}

.wp-block-columns__resizer {
display: none;

@include break-medium() {
display: flex;
}

position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
}

.wp-block-columns__resize-box {
.wp-block-columns__resizer.is-resizing &:not(.is-active) {
opacity: 0;
}

.wp-block-columns__resizer & {
// Reset natural flex-basis assigned above, deferring instead to the
// width assigned by ResizableBox.
flex-basis: auto;
}

.components-resizable-box__handle {
pointer-events: auto;
margin-right: #{-1 * ($grid-size-large + $block-padding)};
}
}

/**
* Columns act as as a "passthrough container"
* and therefore has its vertical margins/padding removed via negative margins
Expand Down
106 changes: 106 additions & 0 deletions packages/block-library/src/columns/resizer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* External dependencies
*/
import { forEach, find, difference, over } from 'lodash';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { useSelect, withDispatch } from '@wordpress/data';
import { ResizableBox } from '@wordpress/components';
import { useState } from '@wordpress/element';

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

function ColumnsResizer( {
clientId,
toggleSelection,
updateBlockAttributes,
} ) {
const [ activeResizeClientId, setActiveResizeClientId ] = useState( null );

const { columns } = useSelect( ( select ) => ( {
columns: select( 'core/block-editor' ).getBlocks( clientId ),
} ), [ clientId ] );

const classes = classnames( 'wp-block-columns__resizer', {
'is-resizing': activeResizeClientId !== null,
} );

return (
<div className={ classes }>
{ columns.map( ( column, index ) => {
const width = getEffectiveColumnWidth( column, columns.length );

function onResizeStop( event, direction, element ) {
const nextWidth = toWidthPrecision( parseFloat( element.style.width ) );
const adjacentColumns = getAdjacentBlocks( columns, column.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 = nextWidth + getTotalColumnsWidth(
difference( columns, [
find( columns, { clientId: column.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 ),
[ column.clientId ]: nextWidth,
...getRedistributedColumnWidths( adjacentColumns, 100 - occupiedWidth, columns.length ),
};

forEach( nextColumnWidths, ( nextColumnWidth, columnClientId ) => {
updateBlockAttributes( columnClientId, { width: nextColumnWidth } );
} );
}

return (
<ResizableBox
key={ column.clientId }
enable={ {
right: index !== columns.length - 1,
} }
size={ { width: width + '%' } }
axis="x"
onResizeStart={ over( [
() => setActiveResizeClientId( column.clientId ),
() => toggleSelection( false ),
] ) }
onResizeStop={ over( [
() => setActiveResizeClientId( null ),
() => toggleSelection( true ),
onResizeStop,
] ) }
className={ classnames(
'wp-block-columns__resize-box',
'is-selected',
{ 'is-active': activeResizeClientId === column.clientId }
) }
/>
);
} ) }
</div>
);
}

export default withDispatch( ( dispatch ) => {
const { updateBlockAttributes } = dispatch( 'core/block-editor' );
return { updateBlockAttributes };
} )( ColumnsResizer );