From 98c134b8936242dd70d142d2bfeafa7dad4c1828 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:26:54 +1000 Subject: [PATCH] Section Styles: Resolve ref values in variations data (#63172) Co-authored-by: aaronrobertshaw Co-authored-by: andrewserong Co-authored-by: ramonjd Co-authored-by: daviedR --- backport-changelog/6.6/6989.md | 3 + lib/block-supports/block-style-variations.php | 40 ++++++++ .../src/hooks/block-style-variation.js | 83 ++++++++++++++++- .../get-variation-styles-with-ref-values.js | 91 +++++++++++++++++++ .../block-style-variations-test.php | 72 +++++++++++++++ 5 files changed, 286 insertions(+), 3 deletions(-) create mode 100644 backport-changelog/6.6/6989.md create mode 100644 packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js diff --git a/backport-changelog/6.6/6989.md b/backport-changelog/6.6/6989.md new file mode 100644 index 0000000000000..3d236938ff74a --- /dev/null +++ b/backport-changelog/6.6/6989.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6989 + +* https://github.com/WordPress/gutenberg/pull/63172 diff --git a/lib/block-supports/block-style-variations.php b/lib/block-supports/block-style-variations.php index ac081c6e289be..9e999adb2de4f 100644 --- a/lib/block-supports/block-style-variations.php +++ b/lib/block-supports/block-style-variations.php @@ -38,6 +38,42 @@ function gutenberg_get_block_style_variation_name_from_class( $class_string ) { return $matches[1] ?? null; } +/** + * Recursively resolves any `ref` values within a block style variation's data. + * + * @since 6.6.0 + * + * @param array $variation_data Reference to the variation data being processed. + * @param array $theme_json Theme.json data to retrieve referenced values from. + */ +function gutenberg_resolve_block_style_variation_ref_values( &$variation_data, $theme_json ) { + foreach ( $variation_data as $key => &$value ) { + // Only need to potentially process arrays. + if ( is_array( $value ) ) { + // If ref value is set, attempt to find its matching value and update it. + if ( array_key_exists( 'ref', $value ) ) { + // Clean up any invalid ref value. + if ( empty( $value['ref'] ) || ! is_string( $value['ref'] ) ) { + unset( $variation_data[ $key ] ); + } + + $value_path = explode( '.', $value['ref'] ?? '' ); + $ref_value = _wp_array_get( $theme_json, $value_path ); + + // Only update the current value if the referenced path matched a value. + if ( null === $ref_value ) { + unset( $variation_data[ $key ] ); + } else { + $value = $ref_value; + } + } else { + // Recursively look for ref instances. + gutenberg_resolve_block_style_variation_ref_values( $value, $theme_json ); + } + } + } +} + /** * Render the block style variation's styles. * @@ -79,6 +115,10 @@ function gutenberg_render_block_style_variation_support_styles( $parsed_block ) return $parsed_block; } + // Recursively resolve any ref values with the appropriate value within the + // theme_json data. + gutenberg_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + $variation_instance = gutenberg_create_block_style_variation_instance_name( $parsed_block, $variation ); $class_name = "is-style-$variation_instance"; $updated_class_name = $parsed_block['attrs']['className'] . " $class_name"; diff --git a/packages/block-editor/src/hooks/block-style-variation.js b/packages/block-editor/src/hooks/block-style-variation.js index 21259966d8a63..f302cf2aa3a2a 100644 --- a/packages/block-editor/src/hooks/block-style-variation.js +++ b/packages/block-editor/src/hooks/block-style-variation.js @@ -14,6 +14,7 @@ import { getBlockSelectors, } from '../components/global-styles'; import { useStyleOverride } from './utils'; +import { getValueFromObjectPath } from '../utils/object'; import { store as blockEditorStore } from '../store'; import { globalStylesDataKey } from '../store/private-keys'; import { unlock } from '../lock-unlock'; @@ -180,6 +181,77 @@ export function __unstableBlockStyleVariationOverridesWithConfig( { config } ) { ); } +/** + * Retrieves any variation styles data and resolves any referenced values. + * + * @param {Object} globalStyles A complete global styles object, containing settings and styles. + * @param {string} name The name of the desired block type. + * @param {variation} variation The of the block style variation to retrieve data for. + * + * @return {Object|undefined} The global styles data for the specified variation. + */ +export function getVariationStylesWithRefValues( + globalStyles, + name, + variation +) { + if ( ! globalStyles?.styles?.blocks?.[ name ]?.variations?.[ variation ] ) { + return; + } + + // Helper to recursively look for `ref` values to resolve. + const replaceRefs = ( variationStyles ) => { + Object.keys( variationStyles ).forEach( ( key ) => { + const value = variationStyles[ key ]; + + // Only process objects. + if ( typeof value === 'object' && value !== null ) { + // Process `ref` value if present. + if ( value.ref !== undefined ) { + if ( + typeof value.ref !== 'string' || + value.ref.trim() === '' + ) { + // Remove invalid ref. + delete variationStyles[ key ]; + } else { + // Resolve `ref` value. + const refValue = getValueFromObjectPath( + globalStyles, + value.ref + ); + + if ( refValue ) { + variationStyles[ key ] = refValue; + } else { + delete variationStyles[ key ]; + } + } + } else { + // Recursively resolve `ref` values in nested objects. + replaceRefs( value ); + + // After recursion, if value is empty due to explicitly + // `undefined` ref value, remove it. + if ( Object.keys( value ).length === 0 ) { + delete variationStyles[ key ]; + } + } + } + } ); + }; + + // Deep clone variation node to avoid mutating it within global styles and losing refs. + const styles = JSON.parse( + JSON.stringify( + globalStyles.styles.blocks[ name ].variations[ variation ] + ) + ); + replaceRefs( styles ); + + return styles; +} + function useBlockStyleVariation( name, variation, clientId ) { // Prefer global styles data in GlobalStylesContext, which are available // if in the site editor. Otherwise fall back to whatever is in the @@ -194,9 +266,14 @@ function useBlockStyleVariation( name, variation, clientId ) { }, [] ); return useMemo( () => { - const styles = mergedConfig?.styles ?? globalStyles; - const variationStyles = - styles?.blocks?.[ name ]?.variations?.[ variation ]; + const variationStyles = getVariationStylesWithRefValues( + { + settings: mergedConfig?.settings ?? globalSettings, + styles: mergedConfig?.styles ?? globalStyles, + }, + name, + variation + ); return { settings: mergedConfig?.settings ?? globalSettings, diff --git a/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js b/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js new file mode 100644 index 0000000000000..e42fcef06122b --- /dev/null +++ b/packages/block-editor/src/hooks/test/get-variation-styles-with-ref-values.js @@ -0,0 +1,91 @@ +/** + * Internal dependencies + */ +import { getVariationStylesWithRefValues } from '../block-style-variation'; + +describe( 'getVariationStylesWithRefValues', () => { + it( 'should resolve ref values correctly', () => { + const globalStyles = { + styles: { + color: { background: 'red' }, + blocks: { + 'core/heading': { + color: { text: 'blue' }, + }, + 'core/group': { + variations: { + custom: { + color: { + text: { ref: 'styles.does-not-exist' }, + background: { + ref: 'styles.color.background', + }, + }, + blocks: { + 'core/heading': { + color: { + text: { + ref: 'styles.blocks.core/heading.color.text', + }, + background: { ref: '' }, + }, + }, + }, + elements: { + link: { + color: { + text: { + ref: 'styles.elements.link.color.text', + }, + background: { ref: undefined }, + }, + ':hover': { + color: { + text: { + ref: 'styles.elements.link.:hover.color.text', + }, + }, + }, + }, + }, + }, + }, + }, + }, + elements: { + link: { + color: { text: 'green' }, + ':hover': { + color: { text: 'yellow' }, + }, + }, + }, + }, + }; + + expect( + getVariationStylesWithRefValues( + globalStyles, + 'core/group', + 'custom' + ) + ).toEqual( { + color: { background: 'red' }, + blocks: { + 'core/heading': { + color: { text: 'blue' }, + }, + }, + elements: { + link: { + color: { + text: 'green', + }, + ':hover': { + color: { text: 'yellow' }, + }, + }, + }, + } ); + } ); +} ); diff --git a/phpunit/block-supports/block-style-variations-test.php b/phpunit/block-supports/block-style-variations-test.php index 62fa923dbdd7e..4387409ed0433 100644 --- a/phpunit/block-supports/block-style-variations-test.php +++ b/phpunit/block-supports/block-style-variations-test.php @@ -155,4 +155,76 @@ public function test_add_registered_block_styles_to_theme_data() { $this->assertSameSetsWithIndex( $group_styles, $expected ); } + + /** + * Tests that block style variations resolve any `ref` values when generating styles. + */ + public function test_block_style_variation_ref_values() { + switch_theme( 'block-theme' ); + + $variation_data = array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.does-not-exist', + ), + 'background' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.text', + ), + ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-a.color.background', + ), + 'background' => array( + 'ref' => '', + ), + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.text', + ), + 'background' => array( + 'ref' => null, + ), + ), + ':hover' => array( + 'color' => array( + 'text' => array( + 'ref' => 'styles.blocks.core/group.variations.block-style-variation-b.color.background', + ), + ), + ), + ), + ), + ); + + $theme_json = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_raw_data(); + + gutenberg_resolve_block_style_variation_ref_values( $variation_data, $theme_json ); + + $expected = array( + 'color' => array( 'background' => 'plum' ), + 'blocks' => array( + 'core/heading' => array( + 'color' => array( 'text' => 'indigo' ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( 'text' => 'lightblue' ), + ':hover' => array( + 'color' => array( 'text' => 'midnightblue' ), + ), + ), + ), + ); + + $this->assertSameSetsWithIndex( $expected, $variation_data, 'Variation data with resolved ref values does not match' ); + } }