Skip to content

Commit

Permalink
Block spacing: block-level axial gap block support (WordPress#37736)
Browse files Browse the repository at this point in the history
* Initial commit: splitting the gap value to allow for axial gap values

* Checking for object. Now passing complete object to box control.

* Providing a default value for split gap values, where one is `undefined`.

* This is the first commit that switches the blockGap over to use to an object. We're using the `top` and `left` properties of the BoxControl next value to represent row and column values.

Reverting some block.json changes.
Ensuring that we cater for blockGap string values.

Reverting some block.json changes.
Ensuring that we cater for blockGap string values.
Adding tests.

* margin-top should take a single value

* null coalescing operator (??) is not present in PHP

* Checking for gap array items before using them.

* Using `--wp--style--block-gap` as the fallback for split values

* Returning null instead of the argument in getGapCSSValue and updated tests
Added comment to explain how we build and pass the boxcontrol component value.

* Rolling back using the gap CSS var as a fallback since the CSS var can theoretically be something like "24px 54px" or some other manifestation of the shorthand value.
  • Loading branch information
ramonjd authored and jostnes committed Mar 23, 2022
1 parent 919a561 commit d48e2c6
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 18 deletions.
20 changes: 18 additions & 2 deletions lib/block-supports/layout.php
Original file line number Diff line number Diff line change
Expand Up @@ -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; }";
Expand All @@ -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;';
Expand Down Expand Up @@ -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(
Expand Down
95 changes: 83 additions & 12 deletions packages/block-editor/src/hooks/gap.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -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 );
Expand All @@ -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.
Expand Down Expand Up @@ -82,6 +122,7 @@ export function GapEdit( props ) {
const {
clientId,
attributes: { style },
name: blockName,
setAttributes,
} = props;

Expand All @@ -94,7 +135,7 @@ export function GapEdit( props ) {
'vw',
],
} );

const sides = useCustomSides( blockName, 'blockGap' );
const ref = useBlockRef( clientId );

if ( useIsGapDisabled( props ) ) {
Expand All @@ -106,7 +147,9 @@ export function GapEdit( props ) {
...style,
spacing: {
...style?.spacing,
blockGap: next,
blockGap: {
...getGapValueFromStyle( next ),
},
},
};

Expand All @@ -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: (
<>
<UnitControl
label={ __( 'Block spacing' ) }
__unstableInputWidth="80px"
min={ 0 }
onChange={ onChange }
units={ units }
value={ style?.spacing?.blockGap }
/>
{ splitOnAxis ? (
<BoxControl
label={ __( 'Block spacing' ) }
min={ 0 }
onChange={ onChange }
units={ units }
sides={ sides }
values={ boxControlGapValue }
allowReset={ false }
splitOnAxis={ splitOnAxis }
/>
) : (
<UnitControl
label={ __( 'Block spacing' ) }
__unstableInputWidth="80px"
min={ 0 }
onChange={ onChange }
units={ units }
// Default to `row` for combined values.
value={ boxControlGapValue }
/>
) }
</>
),
native: null,
Expand Down
42 changes: 42 additions & 0 deletions packages/block-editor/src/hooks/test/gap.js
Original file line number Diff line number Diff line change
@@ -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'
);
} );
} );
} );
6 changes: 4 additions & 2 deletions packages/block-editor/src/layouts/flex.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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;
Expand All @@ -113,8 +115,8 @@ export default {
<style>{ `
${ appendSelectors( selector ) } {
display: flex;
gap: ${ hasBlockGapStylesSupport ? blockGapValue : '0.5em' };
flex-wrap: ${ flexWrap };
gap: ${ hasBlockGapStylesSupport ? blockGapValue : '0.5em' };
${ orientation === 'horizontal' ? rowOrientation : columnOrientation }
}
Expand Down
6 changes: 5 additions & 1 deletion packages/block-editor/src/layouts/flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Icon, positionCenter, stretchWide } from '@wordpress/icons';
*/
import useSetting from '../components/use-setting';
import { appendSelectors } from './utils';
import { getGapValueFromStyle } from '../hooks/gap';

export default {
name: 'default',
Expand Down Expand Up @@ -109,8 +110,11 @@ export default {
const { contentSize, wideSize } = layout;
const blockGapSupport = useSetting( 'spacing.blockGap' );
const hasBlockGapStylesSupport = blockGapSupport !== null;
const blockGapStyleValue = getGapValueFromStyle(
style?.spacing?.blockGap
);
const blockGapValue =
style?.spacing?.blockGap ?? 'var( --wp--style--block-gap )';
blockGapStyleValue?.top ?? 'var( --wp--style--block-gap )';

let output =
!! contentSize || !! wideSize
Expand Down
2 changes: 1 addition & 1 deletion packages/block-library/src/social-links/block.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
}
},
"spacing": {
"blockGap": true,
"blockGap": [ "horizontal", "vertical" ],
"margin": [ "top", "bottom" ],
"units": [ "px", "em", "rem", "vh", "vw" ],
"__experimentalDefaultControls": {
Expand Down

0 comments on commit d48e2c6

Please sign in to comment.