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

Allow Dynamic Blocks to opt-in to skipSerialization of Block Supports Features separately for the Front-End and Editor #68552

Open
stewarty opened this issue Jan 8, 2025 · 0 comments
Labels
[Feature] Block API API that allows to express the block paradigm. [Type] Enhancement A suggestion for improvement.

Comments

@stewarty
Copy link

stewarty commented Jan 8, 2025

What problem does this address?

Currently a block author can optin to skipping serialization of Block Support Toolsets both entirely and per-feature within the toolset. In the vast majority of cases it all work great.

When building a dynamic block it is possible to have situations where serialization skipping is useful in the editor, to provide more control over the experience but causes issues when rendering the block on the front end as now skipped attributes need to have classes and style generated manually.

I ran into an example of this recently when trying to make a block resizeable using ResizableBox(https://github.com/WordPress/gutenberg/tree/trunk/packages/components/src/resizable-box) with Block Support enabled for padding and margin features within layout and background in color. On the front-end all works as expected but in the editor the ResizeHandler's UI Elements are placed incorrectly as all the attributes are serialised onto the block's outer elements via useBlockProps. This forces the margin and padding to stack and the Resize UI to be within the block rather than the preferred "between" the magin and padding. I.e The resize UI has the block's padding within it (and hence any background styles) and the margin outside of it. This is solveable by preventing Background and Padding styles from being applied to the block's outer element and instead using an inner wrapper.

const Wrapper = ({children, attributes}) => {

	const borderProps = useBorderProps( attributes );
	const colorProps = useColorProps(attributes)

	const { style  = {} } = attributes
	const { spacing = {} } = style
	const { padding : paddingAttributes = {} } = spacing

	const spacingStyles = getSpacingClassesAndStyles({style:{spacing:{padding:paddingAttributes}}})
	
	const wrapperProps = {
		className : clsx("block-inner", borderProps.className,  colorProps.className),
		style: {
			...spacingStyles.style,
			...borderProps.style,
			...colorProps.style,
		}
	}
	return (<div {...wrapperProps}>
		{ children }
	</div>
	)
}

function Edit( props ) {

	const {attributes, setAttributes, clientId, context} = props
	const resizeProps = useResizeProps(props);
	const blockProps = useBlockProps();

	return (
		<div {...blockProps}>
			<ResizableBox {...resizeProps} >
				<Wrapper {...props}>
					<InnerBlocks {...props}/>
				</Wrapper>
			</ResizableBox>
		</div>
	);
}

Now we are in a situation where we need serialization as normal on the front-end so get_block_wrapper_attributes() provides all the correct styling but for serialization to be skipped in the editor

We can do this already by modifying the Block Supports object on editor registration. This also took me far longer than I'd like to admit to realise was possible.

import { registerBlockType } from '@wordpress/blocks';
import metadata from './block.json';
const { name, attributes, category, supports } = metadata;

supports.color.__experimentalSkipSerialization = ["background"]
supports.layout.__experimentalSkipSerialization = true
supports.spacing.__experimentalSkipSerialization = ["padding"]

registerBlockType(name, {
    apiVersion: 3,
    category,
    attributes,
    supports,
    edit : Edit,
    save: Save, 
} );

While modifying the block supports configuration during runtime works it feels like the incorrect way to do this, it seperates key configuration away from block.json into a non-obvious location. Impacting future maintainability, while there are other instances where this happens, it seems like this could be resolveable and a nice improvement to developer experience.

As I write this there is a PR reverting the stabilization of experimental Block Supports features #68163. While experimental the skipSerialization flag could be extended to include this, hopefully we could avoid adding more flags by extending the existing feature.

What is your proposed solution?

I'd like to see if its possible to add an official mechanism where serialization skipping can be opt-ed into per Block Support Toolset and per Block Support Toolset Feature within block.json

There are two options I can see.

1. Extend the existing __experimentalSkipSerialization property.

To match the existing format we have to

  1. Toggle the whole Block Support on and off.
  2. Toggle individual features on and off.

This could hopefully be done by adding additional array value checks looking for a reserved view or editor to toggle serialtization per location. (I've used View and Editor for consistency with script and style registration, as this is the other seperate location config )

// Skip serialization for all features in editor only
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipSerialization" : ["editor"]
},

// Skip serialization for all features in front-end only
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipSerialization" : ["view"]
},

The propertly would need to be extended to allow for an object to control per-feature opt-out

// Skip serialization for padding on front-end and margin on editor
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipSerialization" : {
		"padding" : ["view"],
		"margin" : ["editor"],
	}
},

// Skip serialization for padding on front-end and editor
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipSerialization" : {
		"padding" : ["view", "editor"] // same as true
	}
},

2. Add new properties eg __experimentalSkipEditorSerialization and __experimentalSkipViewSerialization

Using new properties on the support object saves having to worry about backwards compatability and maybe simpler overall.

// Skip serialization for all features in the view using seperate feature key
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipViewSerialization" : true
},

// Skip serialization for all features in the editor using seperate feature key
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipEditorSerialization" : true
},

// Skip serialization for padding in the Editor using seperate feature key
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipEditorSerialization" : ["padding"]
},

// Skip serialization for padding in the view using seperate feature key
"spacing": {
	"margin": true,  
	"padding": true, 
	"blockGap": true,
	"__experimentalSkipViewSerialization" : ["padding"]
},

Alternatives

Separate how Block Support styles are applied from an all in one fubnction to a conditional check and a generator
In the example above I alternatively could have opted into serialization skipping and then generated the css and classes needed. This effort was hindered by how the Block Supports are applied. Looking at how background supports are applied, if the conditional and the style generation where separated into two functions it would be possible to reuse the style generation and remaine inline with future updates. Currently, as we're skippingSerialization the function returns unchanged block content.

function gutenberg_render_background_support( $block_content, $block ) {
	$block_type                   = WP_Block_Type_Registry::get_instance()->get_registered( $block['blockName'] );
	$block_attributes             = ( isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) ? $block['attrs'] : array();
	$has_background_image_support = block_has_support( $block_type, array( 'background', 'backgroundImage' ), false );

	if (
		! $has_background_image_support ||
		wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundImage' ) ||
		! isset( $block_attributes['style']['background'] )
	) {
		return $block_content;
	}

	$background_styles                         = array();
	$background_styles['backgroundImage']      = $block_attributes['style']['background']['backgroundImage'] ?? null;
	$background_styles['backgroundSize']       = $block_attributes['style']['background']['backgroundSize'] ?? null;
	$background_styles['backgroundPosition']   = $block_attributes['style']['background']['backgroundPosition'] ?? null;
	$background_styles['backgroundRepeat']     = $block_attributes['style']['background']['backgroundRepeat'] ?? null;
	$background_styles['backgroundAttachment'] = $block_attributes['style']['background']['backgroundAttachment'] ?? null;
	if ( ! empty( $background_styles['backgroundImage'] ) ) {
		$background_styles['backgroundSize'] = $background_styles['backgroundSize'] ?? 'cover';
		// If the background size is set to `contain` and no position is set, set the position to `center`.
		if ( 'contain' === $background_styles['backgroundSize'] && ! $background_styles['backgroundPosition'] ) {
			$background_styles['backgroundPosition'] = '50% 50%';
		}
	}

	$styles = gutenberg_style_engine_get_styles( array( 'background' => $background_styles ) );

/*
clipped
*/

Make wp_should_skip_block_supports_serialization() filterable

Adding a filter to the skip check would allow for opting in on a per-block type basis

function wp_should_skip_block_supports_serialization( $block_type, $feature_set, $feature = null ) {
	if ( ! is_object( $block_type ) || ! $feature_set ) {
		return false;
	}

	$path               = array( $feature_set, '__experimentalSkipSerialization' );
	$skip_serialization = _wp_array_get( $block_type->supports, $path, false );

	if ( is_array( $skip_serialization ) ) {
		return in_array( $feature, $skip_serialization, true );
	}

	return $skip_serialization;
}

Fin.

I appreciate this is a niche idea with potentially only a small number of applications. If there is interest I'd be happy to work on a PR but wanted to see if the idea was reasonable before putting to much effort in.

@stewarty stewarty added the [Type] Enhancement A suggestion for improvement. label Jan 8, 2025
@Mamaduka Mamaduka added the [Feature] Block API API that allows to express the block paradigm. label Jan 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[Feature] Block API API that allows to express the block paradigm. [Type] Enhancement A suggestion for improvement.
Projects
None yet
Development

No branches or pull requests

2 participants