diff --git a/lib/block-supports/layout.php b/lib/block-supports/layout.php
index fb0332f1070ab..c2f988592f1d0 100644
--- a/lib/block-supports/layout.php
+++ b/lib/block-supports/layout.php
@@ -66,6 +66,9 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
$style .= "$selector > .alignright { float: right; margin-inline-start: 2em; margin-inline-end: 0; }";
$style .= "$selector > .aligncenter { margin-left: auto !important; margin-right: auto !important; }";
if ( $has_block_gap_support ) {
+ if ( is_array( $gap_value ) ) {
+ $gap_value = isset( $gap_value['top'] ) ? $gap_value['top'] : null;
+ }
$gap_style = $gap_value ? $gap_value : 'var( --wp--style--block-gap )';
$style .= "$selector > * { margin-block-start: 0; margin-block-end: 0; }";
$style .= "$selector > * + * { margin-block-start: $gap_style; margin-block-end: 0; }";
@@ -91,11 +94,17 @@ function gutenberg_get_layout_style( $selector, $layout, $has_block_gap_support
$style = "$selector {";
$style .= 'display: flex;';
if ( $has_block_gap_support ) {
+ if ( is_array( $gap_value ) ) {
+ $gap_row = isset( $gap_value['top'] ) ? $gap_value['top'] : '0.5em';
+ $gap_column = isset( $gap_value['left'] ) ? $gap_value['left'] : '0.5em';
+ $gap_value = $gap_row === $gap_column ? $gap_row : $gap_row . ' ' . $gap_column;
+ }
$gap_style = $gap_value ? $gap_value : 'var( --wp--style--block-gap, 0.5em )';
$style .= "gap: $gap_style;";
} else {
$style .= 'gap: 0.5em;';
}
+
$style .= "flex-wrap: $flex_wrap;";
if ( 'horizontal' === $layout_orientation ) {
$style .= 'align-items: center;';
@@ -155,8 +164,15 @@ function gutenberg_render_layout_support_flag( $block_content, $block ) {
// Skip if gap value contains unsupported characters.
// Regex for CSS value borrowed from `safecss_filter_attr`, and used here
// because we only want to match against the value, not the CSS attribute.
- $gap_value = preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
- $style = gutenberg_get_layout_style( ".$class_name", $used_layout, $has_block_gap_support, $gap_value );
+ if ( is_array( $gap_value ) ) {
+ foreach ( $gap_value as $key => $value ) {
+ $gap_value[ $key ] = preg_match( '%[\\\(&=}]|/\*%', $value ) ? null : $value;
+ }
+ } else {
+ $gap_value = preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ? null : $gap_value;
+ }
+
+ $style = gutenberg_get_layout_style( ".$class_name", $used_layout, $has_block_gap_support, $gap_value );
// This assumes the hook only applies to blocks with a single wrapper.
// I think this is a reasonable limitation for that particular hook.
$content = preg_replace(
diff --git a/packages/block-editor/src/hooks/gap.js b/packages/block-editor/src/hooks/gap.js
index 13b41c606049c..84a1bf5ce4002 100644
--- a/packages/block-editor/src/hooks/gap.js
+++ b/packages/block-editor/src/hooks/gap.js
@@ -6,6 +6,7 @@ import { Platform } from '@wordpress/element';
import { getBlockSupport } from '@wordpress/blocks';
import {
__experimentalUseCustomUnits as useCustomUnits,
+ __experimentalBoxControl as BoxControl,
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';
@@ -14,14 +15,14 @@ import {
*/
import { __unstableUseBlockRef as useBlockRef } from '../components/block-list/use-block-props/use-block-refs';
import useSetting from '../components/use-setting';
-import { SPACING_SUPPORT_KEY } from './dimensions';
+import { AXIAL_SIDES, SPACING_SUPPORT_KEY, useCustomSides } from './dimensions';
import { cleanEmptyObject } from './utils';
/**
* Determines if there is gap support.
*
* @param {string|Object} blockType Block name or Block Type object.
- * @return {boolean} Whether there is support.
+ * @return {boolean} Whether there is support.
*/
export function hasGapSupport( blockType ) {
const support = getBlockSupport( blockType, SPACING_SUPPORT_KEY );
@@ -38,6 +39,45 @@ export function hasGapValue( props ) {
return props.attributes.style?.spacing?.blockGap !== undefined;
}
+/**
+ * Returns a BoxControl object value from a given blockGap style.
+ * The string check is for backwards compatibility before Gutenberg supported
+ * split gap values (row and column) and the value was a string n + unit.
+ *
+ * @param {string? | Object?} rawBlockGapValue A style object.
+ * @return {Object?} A value to pass to the BoxControl component.
+ */
+export function getGapValueFromStyle( rawBlockGapValue ) {
+ if ( ! rawBlockGapValue ) {
+ return rawBlockGapValue;
+ }
+
+ const isValueString = typeof rawBlockGapValue === 'string';
+ return {
+ top: isValueString ? rawBlockGapValue : rawBlockGapValue?.top,
+ left: isValueString ? rawBlockGapValue : rawBlockGapValue?.left,
+ };
+}
+
+/**
+ * Returns a CSS value for the `gap` property from a given blockGap style.
+ *
+ * @param {string? | Object?} blockGapValue A style object.
+ * @param {string?} defaultValue A default gap value.
+ * @return {string|null} The concatenated gap value (row and column).
+ */
+export function getGapCSSValue( blockGapValue, defaultValue = '0' ) {
+ const blockGapBoxControlValue = getGapValueFromStyle( blockGapValue );
+ if ( ! blockGapBoxControlValue ) {
+ return null;
+ }
+
+ const row = blockGapBoxControlValue?.top || defaultValue;
+ const column = blockGapBoxControlValue?.left || defaultValue;
+
+ return row === column ? row : `${ row } ${ column }`;
+}
+
/**
* Resets the gap block support attribute. This can be used when disabling
* the gap support controls for a block via a progressive discovery panel.
@@ -82,6 +122,7 @@ export function GapEdit( props ) {
const {
clientId,
attributes: { style },
+ name: blockName,
setAttributes,
} = props;
@@ -94,7 +135,7 @@ export function GapEdit( props ) {
'vw',
],
} );
-
+ const sides = useCustomSides( blockName, 'blockGap' );
const ref = useBlockRef( clientId );
if ( useIsGapDisabled( props ) ) {
@@ -106,7 +147,9 @@ export function GapEdit( props ) {
...style,
spacing: {
...style?.spacing,
- blockGap: next,
+ blockGap: {
+ ...getGapValueFromStyle( next ),
+ },
},
};
@@ -128,17 +171,45 @@ export function GapEdit( props ) {
}
};
+ const splitOnAxis =
+ sides && sides.some( ( side ) => AXIAL_SIDES.includes( side ) );
+ const gapValue = getGapValueFromStyle( style?.spacing?.blockGap );
+
+ // The BoxControl component expects a full complement of side values.
+ // Gap row and column values translate to top/bottom and left/right respectively.
+ const boxControlGapValue = splitOnAxis
+ ? {
+ ...gapValue,
+ right: gapValue?.left,
+ bottom: gapValue?.top,
+ }
+ : gapValue?.top;
+
return Platform.select( {
web: (
<>
-
+ { splitOnAxis ? (
+
+ ) : (
+
+ ) }
>
),
native: null,
diff --git a/packages/block-editor/src/hooks/test/gap.js b/packages/block-editor/src/hooks/test/gap.js
new file mode 100644
index 0000000000000..10a6d02e8ed1d
--- /dev/null
+++ b/packages/block-editor/src/hooks/test/gap.js
@@ -0,0 +1,42 @@
+/**
+ * Internal dependencies
+ */
+import { getGapCSSValue } from '../gap';
+
+describe( 'gap', () => {
+ describe( 'getGapCSSValue()', () => {
+ it( 'should return `null` if argument is falsey', () => {
+ expect( getGapCSSValue( undefined ) ).toBeNull();
+ expect( getGapCSSValue( '' ) ).toBeNull();
+ } );
+
+ it( 'should return single value for gap if argument is valid string', () => {
+ expect( getGapCSSValue( '88rem' ) ).toEqual( '88rem' );
+ } );
+
+ it( 'should return single value for gap if row and column are the same', () => {
+ const blockGapValue = {
+ top: '88rem',
+ left: '88rem',
+ };
+ expect( getGapCSSValue( blockGapValue ) ).toEqual( '88rem' );
+ } );
+
+ it( 'should return shorthand value for gap if row and column are different', () => {
+ const blockGapValue = {
+ top: '88px',
+ left: '88rem',
+ };
+ expect( getGapCSSValue( blockGapValue ) ).toEqual( '88px 88rem' );
+ } );
+
+ it( 'should return default value if a top or left is missing', () => {
+ const blockGapValue = {
+ top: '88px',
+ };
+ expect( getGapCSSValue( blockGapValue, '1px' ) ).toEqual(
+ '88px 1px'
+ );
+ } );
+ } );
+} );
diff --git a/packages/block-editor/src/layouts/flex.js b/packages/block-editor/src/layouts/flex.js
index b308875abb4a0..84a966ca0fdeb 100644
--- a/packages/block-editor/src/layouts/flex.js
+++ b/packages/block-editor/src/layouts/flex.js
@@ -16,6 +16,7 @@ import { Button, ToggleControl, Flex, FlexItem } from '@wordpress/components';
* Internal dependencies
*/
import { appendSelectors } from './utils';
+import { getGapCSSValue } from '../hooks/gap';
import useSetting from '../components/use-setting';
import { BlockControls, JustifyContentControl } from '../components';
@@ -90,7 +91,8 @@ export default {
const blockGapSupport = useSetting( 'spacing.blockGap' );
const hasBlockGapStylesSupport = blockGapSupport !== null;
const blockGapValue =
- style?.spacing?.blockGap ?? 'var( --wp--style--block-gap, 0.5em )';
+ getGapCSSValue( style?.spacing?.blockGap, '0.5em' ) ??
+ 'var( --wp--style--block-gap, 0.5em )';
const justifyContent =
justifyContentMap[ layout.justifyContent ] ||
justifyContentMap.left;
@@ -113,8 +115,8 @@ export default {