diff --git a/packages/block-editor/src/store/reducer.js b/packages/block-editor/src/store/reducer.js
index d9d0ecbdd28b64..58646e592d38c1 100644
--- a/packages/block-editor/src/store/reducer.js
+++ b/packages/block-editor/src/store/reducer.js
@@ -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':
diff --git a/packages/block-editor/src/store/test/reducer.js b/packages/block-editor/src/store/test/reducer.js
index 7543fdad57dd8b..0cd6aef0481c61 100644
--- a/packages/block-editor/src/store/test/reducer.js
+++ b/packages/block-editor/src/store/test/reducer.js
@@ -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' } } );
diff --git a/packages/block-library/src/columns/edit.js b/packages/block-library/src/columns/edit.js
index d573500cd551d9..e8dad030a8d5a6 100644
--- a/packages/block-library/src/columns/edit.js
+++ b/packages/block-library/src/columns/edit.js
@@ -34,6 +34,7 @@ import {
getRedistributedColumnWidths,
toWidthPrecision,
} from './utils';
+import ColumnsResizer from './resizer';
/**
* Allowed blocks constant is passed to InnerBlocks precisely as specified here.
@@ -111,6 +112,8 @@ export function ColumnsEdit( {
updateAlignment,
updateColumns,
clientId,
+ isSelected,
+ toggleSelection,
} ) {
const { verticalAlignment } = attributes;
@@ -175,6 +178,12 @@ export function ColumnsEdit( {
template={ count === 0 && ! forceUseTemplate ? null : template }
templateLock="all"
allowedBlocks={ ALLOWED_BLOCKS } />
+ { isSelected && (
+
+ ) }
>
);
diff --git a/packages/block-library/src/columns/editor.scss b/packages/block-library/src/columns/editor.scss
index 6e9e2e60659114..e50b857107b56e 100644
--- a/packages/block-library/src/columns/editor.scss
+++ b/packages/block-library/src/columns/editor.scss
@@ -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
diff --git a/packages/block-library/src/columns/resizer.js b/packages/block-library/src/columns/resizer.js
new file mode 100644
index 00000000000000..9da222e9c48419
--- /dev/null
+++ b/packages/block-library/src/columns/resizer.js
@@ -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 (
+
+ { 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 (
+ 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 }
+ ) }
+ />
+ );
+ } ) }
+
+ );
+}
+
+export default withDispatch( ( dispatch ) => {
+ const { updateBlockAttributes } = dispatch( 'core/block-editor' );
+ return { updateBlockAttributes };
+} )( ColumnsResizer );