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

Theme.json: Add block support feature level selectors for blocks #42087

Merged
merged 11 commits into from
Jul 15, 2022
78 changes: 74 additions & 4 deletions lib/compat/wordpress-6.1/class-wp-theme-json-6-1.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,15 @@ class WP_Theme_JSON_6_1 extends WP_Theme_JSON_6_0 {
'caption' => 'wp-element-caption',
);

// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = array(
'__experimentalBorder' => 'border',
'color' => 'color',
'spacing' => 'spacing',
'typography' => 'typography',
);

/**
* Given an element name, returns a class name.
*
Expand Down Expand Up @@ -372,6 +381,25 @@ protected static function get_blocks_metadata() {
static::$blocks_metadata[ $block_name ]['duotone'] = $block_type->supports['color']['__experimentalDuotone'];
}

// Generate block support feature level selectors if opted into
// for the current block.
$features = array();
foreach ( static::BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS as $key => $feature ) {
if (
isset( $block_type->supports[ $key ]['__experimentalSelector'] ) &&
$block_type->supports[ $key ]['__experimentalSelector']
) {
$features[ $feature ] = static::scope_selector(
static::$blocks_metadata[ $block_name ]['selector'],
$block_type->supports[ $key ]['__experimentalSelector']
);
}
}

if ( ! empty( $features ) ) {
static::$blocks_metadata[ $block_name ]['features'] = $features;
}

// Assign defaults, then overwrite those that the block sets by itself.
// If the block selector is compounded, will append the element to each
// individual block selector.
Expand Down Expand Up @@ -510,11 +538,17 @@ private static function get_block_nodes( $theme_json, $selectors = array() ) {
$duotone_selector = $selectors[ $name ]['duotone'];
}

$feature_selectors = null;
if ( isset( $selectors[ $name ]['features'] ) ) {
$feature_selectors = $selectors[ $name ]['features'];
}

$nodes[] = array(
'name' => $name,
'path' => array( 'styles', 'blocks', $name ),
'selector' => $selector,
'duotone' => $duotone_selector,
'features' => $feature_selectors,
);

if ( isset( $theme_json['styles']['blocks'][ $name ]['elements'] ) ) {
Expand Down Expand Up @@ -622,6 +656,37 @@ public function get_styles_for_block( $block_metadata ) {
$selector = $block_metadata['selector'];
$settings = _wp_array_get( $this->theme_json, array( 'settings' ) );

// Process style declarations for block support features the current
// block contains selectors for. Values for a feature with a custom
// selector are filtered from the theme.json node before it is
// processed as normal.
$feature_declarations = array();

if ( ! empty( $block_metadata['features'] ) ) {
foreach ( $block_metadata['features'] as $feature_name => $feature_selector ) {
if ( ! empty( $node[ $feature_name ] ) ) {
// Create temporary node containing only the feature data
// to leverage existing `compute_style_properties` function.
$feature = array( $feature_name => $node[ $feature_name ] );
// Generate the feature's declarations only.
$new_feature_declarations = static::compute_style_properties( $feature, $settings, null, $this->theme_json );

// Merge new declarations with any that already exist for
// the feature selector. This may occur when multiple block
// support features use the same custom selector.
if ( isset( $feature_declarations[ $feature_selector ] ) ) {
$feature_declarations[ $feature_selector ] = array_merge( $feature_declarations[ $feature_selector ], $new_feature_declarations );
} else {
$feature_declarations[ $feature_selector ] = $new_feature_declarations;
}

// Remove the feature from the block's node now the
// styles will be included under the feature level selector.
unset( $node[ $feature_name ] );
}
}
}

// Get a reference to element name from path.
// $block_metadata['path'] = array('styles','elements','link');
// Make sure that $block_metadata['path'] describes an element node, like ['styles', 'element', 'link'].
Expand Down Expand Up @@ -695,6 +760,11 @@ function( $pseudo_selector ) use ( $selector ) {
$block_rules .= $this->get_layout_styles( $block_metadata );
}

// 5. Generate and append the feature level rulesets.
foreach ( $feature_declarations as $feature_selector => $individual_feature_declarations ) {
$block_rules .= static::to_ruleset( $feature_selector, $individual_feature_declarations );
}

if ( static::ROOT_BLOCK_SELECTOR === $selector ) {
$block_gap_value = _wp_array_get( $this->theme_json, array( 'styles', 'spacing', 'blockGap' ), '0.5em' );

Expand Down Expand Up @@ -880,8 +950,8 @@ protected static function get_property_value( $styles, $path, $theme_json = null
* - prevent_override => Disables override of default presets by theme presets.
* The relationship between whether to override the defaults
* and whether the defaults are enabled is inverse:
* - If defaults are enabled => theme presets should not be overriden
* - If defaults are disabled => theme presets should be overriden
* - If defaults are enabled => theme presets should not be overridden
* - If defaults are disabled => theme presets should be overridden
* For example, a theme sets defaultPalette to false,
* making the default palette hidden from the user.
* In that case, we want all the theme presets to be present,
Expand Down Expand Up @@ -1075,7 +1145,7 @@ public function set_spacing_sizes() {
}

$below_sizes[] = array(
/* translators: %s: Muliple of t-shirt sizing, eg. 2X-Small */
/* translators: %s: Multiple of t-shirt sizing, eg. 2X-Small */
'name' => $x === $steps_mid_point - 1 ? __( 'Small', 'gutenberg' ) : sprintf( __( '%sX-Small', 'gutenberg' ), strval( $x_small_count ) ),
'slug' => $slug,
'size' => round( $current_step, 2 ) . $unit,
Expand Down Expand Up @@ -1112,7 +1182,7 @@ public function set_spacing_sizes() {
: ( $spacing_scale['increment'] >= 1 ? $current_step * $spacing_scale['increment'] : $current_step / $spacing_scale['increment'] );

$above_sizes[] = array(
/* translators: %s: Muliple of t-shirt sizing, eg. 2X-Large */
/* translators: %s: Multiple of t-shirt sizing, eg. 2X-Large */
'name' => 0 === $x ? __( 'Large', 'gutenberg' ) : sprintf( __( '%sX-Large', 'gutenberg' ), strval( $x_large_count ) ),
'slug' => $slug,
'size' => round( $current_step, 2 ) . $unit,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
getLayoutStyles,
getNodesWithSettings,
getNodesWithStyles,
getBlockSelectors,
toCustomProperties,
toStyles,
} from '../use-global-styles-output';
Expand Down Expand Up @@ -57,6 +58,11 @@ describe( 'global styles renderer', () => {
},
},
},
'core/image': {
border: {
radius: '9999px',
},
},
},
elements: {
link: {
Expand Down Expand Up @@ -84,6 +90,10 @@ describe( 'global styles renderer', () => {
'core/heading': {
selector: '.my-heading1, .my-heading2',
},
'core/image': {
selector: '.my-image',
featureSelectors: '.my-image img, .my-image .crop-area',
},
};

expect( getNodesWithStyles( tree, blockSelectors ) ).toEqual( [
Expand Down Expand Up @@ -159,6 +169,15 @@ describe( 'global styles renderer', () => {
},
selector: '.my-heading1 a, .my-heading2 a',
},
{
styles: {
border: {
radius: '9999px',
},
},
selector: '.my-image',
featureSelectors: '.my-image img, .my-image .crop-area',
},
] );
} );
} );
Expand Down Expand Up @@ -430,6 +449,14 @@ describe( 'global styles renderer', () => {
},
},
},
'core/image': {
color: {
text: 'red',
},
border: {
radius: '9999px',
},
},
},
},
};
Expand All @@ -441,12 +468,18 @@ describe( 'global styles renderer', () => {
'core/heading': {
selector: 'h1,h2,h3,h4,h5,h6',
},
'core/image': {
selector: '.wp-block-image',
featureSelectors: {
border: '.wp-block-image img, .wp-block-image .wp-crop-area',
},
},
};

expect( toStyles( tree, blockSelectors ) ).toEqual(
'body {margin: 0;}' +
'body{background-color: red;margin: 10px;padding: 10px;}h1{font-size: 42px;}a{color: blue;}a:hover{color: orange;}a:focus{color: orange;}.wp-block-group{margin-top: 10px;margin-right: 20px;margin-bottom: 30px;margin-left: 40px;padding-top: 11px;padding-right: 22px;padding-bottom: 33px;padding-left: 44px;}h1,h2,h3,h4,h5,h6{color: orange;}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{color: hotpink;}h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{color: red;}h1 a:focus,h2 a:focus,h3 a:focus,h4 a:focus,h5 a:focus,h6 a:focus{color: red;}' +
'.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' +
'.wp-block-image img, .wp-block-image .wp-crop-area{border-radius: 9999px }.wp-block-image{color: red;}.wp-site-blocks > .alignleft { float: left; margin-right: 2em; }.wp-site-blocks > .alignright { float: right; margin-left: 2em; }.wp-site-blocks > .aligncenter { justify-content: center; margin-left: auto; margin-right: auto; }' +
'.has-white-color{color: var(--wp--preset--color--white) !important;}.has-white-background-color{background-color: var(--wp--preset--color--white) !important;}.has-white-border-color{border-color: var(--wp--preset--color--white) !important;}.has-black-color{color: var(--wp--preset--color--black) !important;}.has-black-background-color{background-color: var(--wp--preset--color--black) !important;}.has-black-border-color{border-color: var(--wp--preset--color--black) !important;}h1.has-blue-color,h2.has-blue-color,h3.has-blue-color,h4.has-blue-color,h5.has-blue-color,h6.has-blue-color{color: var(--wp--preset--color--blue) !important;}h1.has-blue-background-color,h2.has-blue-background-color,h3.has-blue-background-color,h4.has-blue-background-color,h5.has-blue-background-color,h6.has-blue-background-color{background-color: var(--wp--preset--color--blue) !important;}h1.has-blue-border-color,h2.has-blue-border-color,h3.has-blue-border-color,h4.has-blue-border-color,h5.has-blue-border-color,h6.has-blue-border-color{border-color: var(--wp--preset--color--blue) !important;}'
);
} );
Expand Down Expand Up @@ -618,4 +651,34 @@ describe( 'global styles renderer', () => {
);
} );
} );

describe( 'getBlockSelectors', () => {
it( 'should return block selectors data', () => {
const imageSupports = {
__experimentalBorder: {
radius: true,
__experimentalSelector: 'img, .crop-area',
},
color: {
__experimentalDuotone: 'img',
},
__experimentalSelector: '.my-image',
};
const imageBlock = { name: 'core/image', supports: imageSupports };
const blockTypes = [ imageBlock ];

expect( getBlockSelectors( blockTypes ) ).toEqual( {
'core/image': {
name: imageBlock.name,
selector: imageSupports.__experimentalSelector,
duotoneSelector: imageSupports.color.__experimentalDuotone,
fallbackGapValue: undefined,
featureSelectors: {
border: '.my-image img, .my-image .crop-area',
},
hasLayoutSupport: false,
},
} );
} );
} );
} );
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,19 @@ import {
/**
* Internal dependencies
*/
import { PRESET_METADATA, ROOT_BLOCK_SELECTOR } from './utils';
import { PRESET_METADATA, ROOT_BLOCK_SELECTOR, scopeSelector } from './utils';
import { GlobalStylesContext } from './context';
import { useSetting } from './hooks';

// List of block support features that can have their related styles
// generated under their own feature level selector rather than the block's.
const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
__experimentalBorder: 'border',
color: 'color',
spacing: 'spacing',
typography: 'typography',
};

function compileStyleValue( uncompiledValue ) {
const VARIABLE_REFERENCE_PREFIX = 'var:';
const VARIABLE_PATH_SEPARATOR_TOKEN_ATTRIBUTE = '|';
Expand Down Expand Up @@ -403,6 +412,7 @@ export const getNodesWithStyles = ( tree, blockSelectors ) => {
hasLayoutSupport: blockSelectors[ blockName ].hasLayoutSupport,
selector: blockSelectors[ blockName ].selector,
styles: blockStyles,
featureSelectors: blockSelectors[ blockName ].featureSelectors,
} );
}

Expand Down Expand Up @@ -522,7 +532,33 @@ export const toStyles = (
styles,
fallbackGapValue,
hasLayoutSupport,
featureSelectors,
} ) => {
// Process styles for block support features with custom feature level
// CSS selectors set.
if ( featureSelectors ) {
Object.entries( featureSelectors ).forEach(
( [ featureName, featureSelector ] ) => {
if ( styles?.[ featureName ] ) {
const featureStyles = {
[ featureName ]: styles[ featureName ],
};
const featureDeclarations =
getStylesDeclarations( featureStyles );
delete styles[ featureName ];

if ( !! featureDeclarations.length ) {
ruleset =
ruleset +
`${ featureSelector }{${ featureDeclarations.join(
';'
) } }`;
}
}
}
);
}

const duotoneStyles = {};
if ( styles?.filter ) {
duotoneStyles.filter = styles.filter;
Expand Down Expand Up @@ -579,7 +615,7 @@ export const toStyles = (

// `selector` maybe provided in a form
// where block level selectors have sub element
// selectors appended to them as a comma seperated
// selectors appended to them as a comma separated
// string.
// e.g. `h1 a,h2 a,h3 a,h4 a,h5 a,h6 a`;
// Split and append pseudo selector to create
Expand Down Expand Up @@ -645,7 +681,7 @@ export function toSvgFilters( tree, blockSelectors ) {
} );
}

const getBlockSelectors = ( blockTypes ) => {
export const getBlockSelectors = ( blockTypes ) => {
const result = {};
blockTypes.forEach( ( blockType ) => {
const name = blockType.name;
Expand All @@ -657,9 +693,29 @@ const getBlockSelectors = ( blockTypes ) => {
const hasLayoutSupport = !! blockType?.supports?.__experimentalLayout;
const fallbackGapValue =
blockType?.supports?.spacing?.blockGap?.__experimentalDefault;

// For each block support feature add any custom selectors.
const featureSelectors = {};
Object.entries( BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS ).forEach(
( [ featureKey, featureName ] ) => {
const featureSelector =
blockType?.supports?.[ featureKey ]?.__experimentalSelector;

if ( featureSelector ) {
featureSelectors[ featureName ] = scopeSelector(
selector,
featureSelector
);
}
}
);

result[ name ] = {
duotoneSelector,
fallbackGapValue,
featureSelectors: Object.keys( featureSelectors ).length
? featureSelectors
: undefined,
hasLayoutSupport,
name,
selector,
Expand Down
Loading