Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions backport-changelog/7.0/10869.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
https://github.com/WordPress/wordpress-develop/pull/10869

* https://github.com/WordPress/gutenberg/pull/75226
85 changes: 76 additions & 9 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -1962,21 +1962,87 @@ protected function get_css_variables( $nodes, $origins ) {
continue;
}

$selector = $metadata['selector'];
$selector = $metadata['selector'];
$feature_selectors = $metadata['selectors'] ?? array();
$node = _wp_array_get( $this->theme_json, $metadata['path'], array() );

$node = _wp_array_get( $this->theme_json, $metadata['path'], array() );
$declarations = static::compute_preset_vars( $node, $origins );
$theme_vars_declarations = static::compute_theme_vars( $node );
foreach ( $theme_vars_declarations as $theme_vars_declaration ) {
$declarations[] = $theme_vars_declaration;
/*
* Group preset declarations by selector. Blocks that define
* feature-level selectors need their preset CSS variables
* output under that feature selector instead of the block's
* root selector.
*/
$vars_by_selector = array();
$vars_by_selector[ $selector ] = array();

foreach ( static::PRESETS_METADATA as $preset_metadata ) {
if ( empty( $preset_metadata['css_vars'] ) ) {
continue;
}

$values_by_slug = static::get_settings_values_by_slug( $node, $preset_metadata, $origins );
if ( empty( $values_by_slug ) ) {
continue;
}

$target = static::get_feature_selector( $feature_selectors, $preset_metadata['path'][0], $selector );

if ( ! isset( $vars_by_selector[ $target ] ) ) {
$vars_by_selector[ $target ] = array();
}

foreach ( $values_by_slug as $slug => $value ) {
$vars_by_selector[ $target ][] = array(
'name' => static::replace_slug_in_string( $preset_metadata['css_vars'], $slug ),
'value' => $value,
);
}
}

$stylesheet .= static::to_ruleset( $selector, $declarations );
// Theme vars always use the block's default selector.
foreach ( static::compute_theme_vars( $node ) as $theme_var ) {
$vars_by_selector[ $selector ][] = $theme_var;
}

foreach ( $vars_by_selector as $rule_selector => $declarations ) {
$stylesheet .= static::to_ruleset( $rule_selector, $declarations );
}
}

return $stylesheet;
}

/**
* Returns the appropriate selector for a block support feature's
* preset CSS variables.
*
* If the block defines a feature-level selector (as a string or an
* object with a `root` key), that selector is returned. Otherwise,
* the block's default selector is used.
*
* @param array<string, string|array<string, string>> $feature_selectors The block's feature selectors map.
* @param string $feature_key The feature to look up (e.g. 'dimensions').
* @param string $default_selector Fallback selector.
* @return string The resolved selector.
*/
private static function get_feature_selector( array $feature_selectors, string $feature_key, string $default_selector ): string {
if ( ! isset( $feature_selectors[ $feature_key ] ) ) {
return $default_selector;
}

$feature = $feature_selectors[ $feature_key ];

if ( is_string( $feature ) ) {
return $feature;
}

if ( isset( $feature['root'] ) && is_string( $feature['root'] ) ) {
return $feature['root'];
}

return $default_selector;
}

/**
* Given a selector and a declaration list,
* creates the corresponding ruleset.
Expand Down Expand Up @@ -2605,8 +2671,9 @@ protected static function get_setting_nodes( $theme_json, $selectors = array() )
}

$nodes[] = array(
'path' => array( 'settings', 'blocks', $name ),
'selector' => $selector,
'path' => array( 'settings', 'blocks', $name ),
'selector' => $selector,
'selectors' => $selectors[ $name ]['selectors'] ?? array(),
);
}

Expand Down
190 changes: 129 additions & 61 deletions packages/global-styles-engine/src/core/render.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import type {
BlockStyleVariation,
BlockType,
GlobalStylesConfig,
GlobalStylesSettings,
GlobalStylesStyles,
} from '../types';

Expand Down Expand Up @@ -160,56 +159,6 @@ const BLOCK_SUPPORT_FEATURE_LEVEL_SELECTORS = {
typography: 'typography',
};

/**
* Transform given preset tree into a set of style declarations.
*
* @param blockPresets Block presets object
* @param mergedSettings Merged theme.json settings
* @return An array of style declarations
*/
function getPresetsDeclarations(
blockPresets: Record< string, any > = {},
mergedSettings: GlobalStylesSettings
): string[] {
return PRESET_METADATA.reduce(
(
declarations: string[],
{ path, valueKey, valueFunc, cssVarInfix }: PresetMetadata
) => {
const presetByOrigin = getValueFromObjectPath(
blockPresets,
path,
[]
) as PresetsByOrigin;
[ 'default', 'theme', 'custom' ].forEach( ( origin ) => {
if ( presetByOrigin[ origin ] ) {
presetByOrigin[ origin ].forEach( ( value: any ) => {
if ( valueKey && ! valueFunc ) {
declarations.push(
`--wp--preset--${ cssVarInfix }--${ kebabCase(
value.slug
) }: ${ value[ valueKey ] }`
);
} else if (
valueFunc &&
typeof valueFunc === 'function'
) {
declarations.push(
`--wp--preset--${ cssVarInfix }--${ kebabCase(
value.slug
) }: ${ valueFunc( value, mergedSettings ) }`
);
}
} );
}
} );

return declarations;
},
[] as string[]
);
}

/**
* Transform given preset tree into a set of preset class declarations.
*
Expand Down Expand Up @@ -1083,7 +1032,9 @@ export const getNodesWithSettings = (
duotoneSelector?: string;
fallbackGapValue?: string;
hasLayoutSupport?: boolean;
featureSelectors?: Record< string, string >;
featureSelectors?:
| string
| Record< string, string | Record< string, string > >;
styleVariationSelectors?: Record< string, string >;
}[] = [];

Expand Down Expand Up @@ -1129,6 +1080,8 @@ export const getNodesWithSettings = (
presets: blockPresets,
custom: blockCustom,
selector: blockSelectors[ blockName ]?.selector,
featureSelectors:
blockSelectors[ blockName ]?.featureSelectors,
} );
}
}
Expand All @@ -1137,25 +1090,140 @@ export const getNodesWithSettings = (
return nodes;
};

/**
* Resolves the selector for a given block support feature.
*
* If the block defines a feature-level selector (as a string or an object
* with a `root` key), that selector is returned. Otherwise the fallback
* selector is used.
*
* @param {string|Record<string,string|Record<string,string>>|undefined} featureSelectors The block's feature selectors.
* @param {string} featureKey The feature key to resolve.
* @param {string} fallback The default selector.
* @return {string} The resolved selector.
*/
function resolveFeatureSelector(
featureSelectors:
| string
| Record< string, string | Record< string, string > >
| undefined,
featureKey: string,
fallback: string
): string {
if ( ! featureSelectors || typeof featureSelectors === 'string' ) {
return fallback;
}

const feature = featureSelectors[ featureKey ];
if ( typeof feature === 'string' ) {
return feature;
}
if ( typeof feature === 'object' && feature.root ) {
return feature.root;
}
return fallback;
}

/**
* Collects CSS variable declarations for a single preset metadata entry
* across all origins.
*
* @param {Record<string,any>} presets The preset values keyed by origin.
* @param {GlobalStylesConfig['settings']} mergedSettings The merged global styles settings.
* @param {PresetMetadata} presetMetadata The preset metadata.
* @return {string[]} The CSS variable declarations.
*/
function getPresetVarDeclarations(
presets: Record< string, any >,
mergedSettings: GlobalStylesConfig[ 'settings' ],
{ path, valueKey, valueFunc, cssVarInfix }: PresetMetadata
): string[] {
const presetByOrigin = getValueFromObjectPath(
presets,
path,
[]
) as PresetsByOrigin;

const declarations: string[] = [];
for ( const origin of [ 'default', 'theme', 'custom' ] ) {
if ( ! presetByOrigin[ origin ] ) {
continue;
}
for ( const value of presetByOrigin[ origin ] ) {
const slug = kebabCase( value.slug );
if ( valueKey && ! valueFunc ) {
declarations.push(
`--wp--preset--${ cssVarInfix }--${ slug }: ${ value[ valueKey ] }`
);
} else if ( valueFunc && typeof valueFunc === 'function' ) {
declarations.push(
`--wp--preset--${ cssVarInfix }--${ slug }: ${ valueFunc(
value,
mergedSettings
) }`
);
}
}
}
return declarations;
}

export const generateCustomProperties = (
tree: GlobalStylesConfig,
blockSelectors: BlockSelectors
): string => {
const settings = getNodesWithSettings( tree, blockSelectors );
const nodes = getNodesWithSettings( tree, blockSelectors );
let ruleset = '';
settings.forEach( ( { presets, custom, selector } ) => {
const declarations = tree?.settings
? getPresetsDeclarations( presets, tree?.settings )
: [];

for ( const { presets, custom, selector, featureSelectors } of nodes ) {
const defaultSelector = selector as string;

/*
* Group preset declarations by selector. Blocks that define
* feature-level selectors need their preset CSS variables output
* under that feature selector instead of the block's root selector.
*/
const varsBySelector: Record< string, string[] > = {
[ defaultSelector ]: [],
};

if ( tree?.settings ) {
for ( const metadata of PRESET_METADATA ) {
const declarations = getPresetVarDeclarations(
presets,
tree.settings,
metadata
);
if ( declarations.length === 0 ) {
continue;
}

const target = resolveFeatureSelector(
featureSelectors,
metadata.path[ 0 ],
defaultSelector
);
if ( ! varsBySelector[ target ] ) {
varsBySelector[ target ] = [];
}
varsBySelector[ target ].push( ...declarations );
}
}

// Custom properties always use the block's default selector.
const customProps = flattenTree( custom, '--wp--custom--', '--' );
if ( customProps.length > 0 ) {
declarations.push( ...customProps );
varsBySelector[ defaultSelector ].push( ...customProps );
}

if ( declarations.length > 0 ) {
ruleset += `${ selector }{${ declarations.join( ';' ) };}`;
for ( const [ ruleSelector, declarations ] of Object.entries(
varsBySelector
) ) {
if ( declarations.length > 0 ) {
ruleset += `${ ruleSelector }{${ declarations.join( ';' ) };}`;
}
}
} );
}

return ruleset;
};
Expand Down
Loading
Loading