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 Support: Add gap block support feature #33991

Merged
merged 20 commits into from
Sep 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a3bf1e3
Add gap block support feature based on #32571
andrewserong Aug 11, 2021
361c744
Rename CSS variable, refactor to use single variable rather than spli…
andrewserong Aug 12, 2021
b716885
Switch block gap to be displayed in a single column
andrewserong Aug 12, 2021
60491e4
Fix failing JS test
andrewserong Aug 12, 2021
4550685
Try server-rendering the gap block support, and skip serialization on…
andrewserong Aug 12, 2021
a6d8df9
Fix regression where padding/margin values were skipping serialization
andrewserong Aug 13, 2021
b74b73a
Update types
andrewserong Aug 13, 2021
d53333c
Move blockGap support to spacing.php
andrewserong Aug 16, 2021
7643f7f
Add PHP tests for spacing gap support server-side rendering
andrewserong Aug 16, 2021
c9d87a5
Fix whitespace alignment in spacing block support tests
andrewserong Aug 16, 2021
edb506e
Refactor server-rendering to use an inline style, update tests
andrewserong Aug 17, 2021
016f4a7
Tweak regex to fix issue where wrapper has no style attribute, but ch…
andrewserong Aug 18, 2021
1a22b15
Add additional tests
andrewserong Aug 18, 2021
0d52de9
Align equals signs correctly
andrewserong Aug 18, 2021
f8a0932
Fix typo / phpcs warning
andrewserong Aug 18, 2021
a974db1
Remove gap from apply_spacing_support so it isn't applied twice for d…
andrewserong Aug 18, 2021
276837e
Remove duplicate block gap style attribute from constants
andrewserong Aug 27, 2021
530d8b5
Tweak regex
andrewserong Aug 27, 2021
9a421ca
Rename support from customBlockGap to blockGap
andrewserong Aug 30, 2021
779afe5
Align whitespace
andrewserong Aug 30, 2021
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
53 changes: 53 additions & 0 deletions lib/block-supports/spacing.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,57 @@ function gutenberg_skip_spacing_serialization( $block_type ) {
$spacing_support['__experimentalSkipSerialization'];
}


/**
* Renders the spacing gap support to the block wrapper, to ensure
* that the CSS variable is rendered in all environments.
*
* @param string $block_content Rendered block content.
* @param array $block Block object.
* @return string Filtered block content.
*/
function gutenberg_render_spacing_gap_support( $block_content, $block ) {
$block_type = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
$has_gap_support = gutenberg_block_has_support( $block_type, array( 'spacing', 'blockGap' ), false );
if ( ! $has_gap_support || ! isset( $block['attrs']['style']['spacing']['blockGap'] ) ) {
return $block_content;
}

$gap_value = $block['attrs']['style']['spacing']['blockGap'];

// 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.
if ( preg_match( '%[\\\(&=}]|/\*%', $gap_value ) ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it's the responsibility of this function to do this check (as opposed to presave validation)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question: my thinking here was that it'd be good to include some form of CSS value filtering as a defensive conditional so that the rendering logic doesn't depend on the value within the attribute being saved in the correct form, mostly to ensure we don't trust the inputs too much.

The attribute values themselves don't get run through safecss_filter_attr when saved (I think!), so this adds a tiny bit of hardening to the render logic, where we assume that we'll have a fairly limited range of desired simple values being entered into the gap block support.

Happy to change this if you think it's better to pull it out or add a wider range of allowed characters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well I'm not an expert here :) so I'm happy if we keep it this way. Maybe @jorgefilipecosta would have a more informed opinion.

return $block_content;
}

$style = sprintf(
'--wp--style--block-gap: %s',
esc_attr( $gap_value )
);

// Attempt to update an existing style attribute on the wrapper element.
$injected_style = preg_replace(
'/^([^>.]+?)(' . preg_quote( 'style="', '/' ) . ')(?=.+?>)/',
'$1$2' . $style . '; ',
$block_content,
1
);

// If there is no existing style attribute, add one to the wrapper element.
if ( $injected_style === $block_content ) {
$injected_style = preg_replace(
'/<([a-zA-Z0-9]+)([ >])/',
'<$1 style="' . $style . '"$2',
$block_content,
1
);
};

return $injected_style;
}

// Register the block support.
WP_Block_Supports::get_instance()->register(
'spacing',
Expand All @@ -98,3 +149,5 @@ function gutenberg_skip_spacing_serialization( $block_type ) {
'apply' => 'gutenberg_apply_spacing_support',
)
);

add_filter( 'render_block', 'gutenberg_render_spacing_gap_support', 10, 2 );
1 change: 1 addition & 0 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class WP_Theme_JSON_Gutenberg {
'wideSize' => null,
),
'spacing' => array(
'blockGap' => null,
'customMargin' => null,
'customPadding' => null,
'units' => null,
Expand Down
1 change: 1 addition & 0 deletions lib/theme.json
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@
]
},
"spacing": {
"blockGap": false,
"customMargin": false,
"customPadding": false,
"units": [ "px", "em", "rem", "vh", "vw", "%" ]
Expand Down
29 changes: 27 additions & 2 deletions packages/block-editor/src/hooks/dimensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import { getBlockSupport } from '@wordpress/blocks';
* Internal dependencies
*/
import InspectorControls from '../components/inspector-controls';
import {
GapEdit,
hasGapSupport,
hasGapValue,
resetGap,
useIsGapDisabled,
} from './gap';
import {
MarginEdit,
hasMarginSupport,
Expand Down Expand Up @@ -41,6 +48,7 @@ export const AXIAL_SIDES = [ 'vertical', 'horizontal' ];
* @return {WPElement} Inspector controls for spacing support features.
*/
export function DimensionsPanel( props ) {
const isGapDisabled = useIsGapDisabled( props );
const isPaddingDisabled = useIsPaddingDisabled( props );
const isMarginDisabled = useIsMarginDisabled( props );
const isDisabled = useIsDimensionsDisabled( props );
Expand All @@ -64,6 +72,7 @@ export function DimensionsPanel( props ) {
...style,
spacing: {
...style?.spacing,
blockGap: undefined,
margin: undefined,
padding: undefined,
},
Expand Down Expand Up @@ -98,6 +107,17 @@ export function DimensionsPanel( props ) {
<MarginEdit { ...props } />
</ToolsPanelItem>
) }
{ ! isGapDisabled && (
<ToolsPanelItem
className="single-column"
hasValue={ () => hasGapValue( props ) }
label={ __( 'Block gap' ) }
onDeselect={ () => resetGap( props ) }
isShownByDefault={ defaultSpacingControls?.blockGap }
>
<GapEdit { ...props } />
</ToolsPanelItem>
) }
</ToolsPanel>
</InspectorControls>
);
Expand All @@ -115,7 +135,11 @@ export function hasDimensionsSupport( blockName ) {
return false;
}

return hasPaddingSupport( blockName ) || hasMarginSupport( blockName );
return (
hasGapSupport( blockName ) ||
hasPaddingSupport( blockName ) ||
hasMarginSupport( blockName )
);
}

/**
Expand All @@ -126,10 +150,11 @@ export function hasDimensionsSupport( blockName ) {
* @return {boolean} If spacing support is completely disabled.
*/
const useIsDimensionsDisabled = ( props = {} ) => {
const gapDisabled = useIsGapDisabled( props );
const paddingDisabled = useIsPaddingDisabled( props );
const marginDisabled = useIsMarginDisabled( props );

return paddingDisabled && marginDisabled;
return gapDisabled && paddingDisabled && marginDisabled;
};

/**
Expand Down
128 changes: 128 additions & 0 deletions packages/block-editor/src/hooks/gap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { Platform } from '@wordpress/element';
import { getBlockSupport } from '@wordpress/blocks';
import {
__experimentalUseCustomUnits as useCustomUnits,
__experimentalUnitControl as UnitControl,
} from '@wordpress/components';

/**
* Internal dependencies
*/
import useSetting from '../components/use-setting';
import { SPACING_SUPPORT_KEY } 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.
*/
export function hasGapSupport( blockType ) {
const support = getBlockSupport( blockType, SPACING_SUPPORT_KEY );
return !! ( true === support || support?.blockGap );
}

/**
* Checks if there is a current value in the gap block support attributes.
*
* @param {Object} props Block props.
* @return {boolean} Whether or not the block has a gap value set.
*/
export function hasGapValue( props ) {
return props.attributes.style?.spacing?.blockGap !== undefined;
}

/**
* Resets the gap block support attribute. This can be used when disabling
* the gap support controls for a block via a progressive discovery panel.
*
* @param {Object} props Block props.
* @param {Object} props.attributes Block's attributes.
* @param {Object} props.setAttributes Function to set block's attributes.
*/
export function resetGap( { attributes = {}, setAttributes } ) {
const { style } = attributes;

setAttributes( {
style: {
...style,
spacing: {
...style?.spacing,
blockGap: undefined,
},
},
} );
}

/**
* Custom hook that checks if gap settings have been disabled.
*
* @param {string} name The name of the block.
* @return {boolean} Whether the gap setting is disabled.
*/
export function useIsGapDisabled( { name: blockName } = {} ) {
const isDisabled = ! useSetting( 'spacing.blockGap' );
return ! hasGapSupport( blockName ) || isDisabled;
}

/**
* Inspector control panel containing the gap related configuration
*
* @param {Object} props
*
* @return {WPElement} Gap edit element.
*/
export function GapEdit( props ) {
const {
attributes: { style },
setAttributes,
} = props;

const units = useCustomUnits( {
availableUnits: useSetting( 'spacing.units' ) || [
'%',
'px',
'em',
'rem',
'vw',
],
} );

if ( useIsGapDisabled( props ) ) {
return null;
}

const onChange = ( next ) => {
const newStyle = {
...style,
spacing: {
...style?.spacing,
blockGap: next,
},
};

setAttributes( {
style: cleanEmptyObject( newStyle ),
} );
};

return Platform.select( {
web: (
<>
<UnitControl
label={ __( 'Block gap' ) }
min={ 0 }
onChange={ onChange }
units={ units }
value={ style?.spacing?.blockGap }
/>
</>
),
native: null,
} );
}
49 changes: 42 additions & 7 deletions packages/block-editor/src/hooks/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,14 @@ function addAttribute( settings ) {
return settings;
}

const skipSerializationPaths = {
/**
* A dictionary of paths to flag skipping block support serialization as the key,
* with values providing the style paths to be omitted from serialization.
*
* @constant
* @type {Record<string, string[]>}
*/
const skipSerializationPathsEdit = {
[ `${ BORDER_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [ 'border' ],
[ `${ COLOR_SUPPORT_KEY }.__experimentalSkipSerialization` ]: [
COLOR_SUPPORT_KEY,
Expand All @@ -157,23 +164,46 @@ const skipSerializationPaths = {
],
};

/**
* A dictionary of paths to flag skipping block support serialization as the key,
* with values providing the style paths to be omitted from serialization.
*
* Extends the Edit skip paths to enable skipping additional paths in just
* the Save component. This allows a block support to be serialized within the
* editor, while using an alternate approach, such as server-side rendering, when
* the support is saved.
*
* @constant
* @type {Record<string, string[]>}
*/
const skipSerializationPathsSave = {
...skipSerializationPathsEdit,
[ `${ SPACING_SUPPORT_KEY }` ]: [ 'spacing.blockGap' ],
};

/**
* Override props assigned to save component to inject the CSS variables definition.
*
* @param {Object} props Additional props applied to save element.
* @param {Object} blockType Block type.
* @param {Object} attributes Block attributes.
* @param {Object} props Additional props applied to save element.
* @param {Object} blockType Block type.
* @param {Object} attributes Block attributes.
* @param {?Record<string, string[]>} skipPaths An object of keys and paths to skip serialization.
*
* @return {Object} Filtered props applied to save element.
*/
export function addSaveProps( props, blockType, attributes ) {
export function addSaveProps(
props,
blockType,
attributes,
skipPaths = skipSerializationPathsSave
) {
if ( ! hasStyleSupport( blockType ) ) {
return props;
}

let { style } = attributes;

forEach( skipSerializationPaths, ( path, indicator ) => {
forEach( skipPaths, ( path, indicator ) => {
if ( getBlockSupport( blockType, indicator ) ) {
style = omit( style, path );
}
Expand Down Expand Up @@ -207,7 +237,12 @@ export function addEditProps( settings ) {
props = existingGetEditWrapperProps( attributes );
}

return addSaveProps( props, settings, attributes );
return addSaveProps(
props,
settings,
attributes,
skipSerializationPathsEdit
);
};

return settings;
Expand Down
4 changes: 4 additions & 0 deletions packages/block-editor/src/hooks/test/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,13 @@ describe( 'getInlineStyles', () => {
color: '#21759b',
},
spacing: {
blockGap: '1em',
padding: { top: '10px' },
margin: { bottom: '15px' },
},
} )
).toEqual( {
'--wp--style--block-gap': '1em',
backgroundColor: 'black',
borderColor: '#21759b',
borderRadius: '10px',
Expand Down Expand Up @@ -96,11 +98,13 @@ describe( 'getInlineStyles', () => {
expect(
getInlineStyles( {
spacing: {
blockGap: '1em',
margin: '10px',
padding: '20px',
},
} )
).toEqual( {
'--wp--style--block-gap': '1em',
margin: '10px',
padding: '20px',
} );
Expand Down
Loading