Skip to content

Commit

Permalink
Theme.json: Add block support feature level selectors for blocks (#42087
Browse files Browse the repository at this point in the history
)
  • Loading branch information
aaronrobertshaw committed Jul 15, 2022
1 parent c8b7177 commit 87cdeb3
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 10 deletions.
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

0 comments on commit 87cdeb3

Please sign in to comment.