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 {