From a1bef794e7aab676dd5880151538184c351148f6 Mon Sep 17 00:00:00 2001 From: Ramon Date: Fri, 5 Jul 2024 17:49:05 +1000 Subject: [PATCH] Global styles: block background UI controls (#60100) * Adding background panel for blocks. * Default controls using block supports * Updating tests after rebase Removed unrequired change function * Set defaults for the global styles block group background image * Enabling theme file resolution for blocks * LINTO * Use global settings to check if background image is supported. * This commit checks for theme block styles in the block supports hook so a background block control can determine whether it has an inherited value. If there is an inherited value, the block control will provide an option to "remove" the theme style. This commit also sends resolved theme asset URI via the _links property so that these images can be used in block control previews. Added resolver tests for blocks Use `get_styles_for_block` in unit tests when testing block nodes Use `get_styles_for_block` in unit tests when testing block nodes Add since annotation backport changelog * update assertion comments * Now that background styles can be used in block styles, move them to styles complete ref * Pass resolved URIs to display inherited background image styles with relative paths Also, output any background styles, even if there's no background image Co-authored-by: ramonjd Co-authored-by: andrewserong Co-authored-by: tellthemachines Co-authored-by: aaronrobertshaw Co-authored-by: jasmussen Co-authored-by: jameskoster --- backport-changelog/6.7/6836.md | 3 + .../theme-json-reference/theme-json-living.md | 13 ++ lib/block-supports/background.php | 12 +- lib/class-wp-theme-json-gutenberg.php | 8 +- ...class-wp-theme-json-resolver-gutenberg.php | 33 ++++- lib/global-styles-and-settings.php | 1 + .../global-styles/background-panel.js | 8 +- .../src/components/global-styles/hooks.js | 9 ++ .../global-styles/use-global-styles-output.js | 15 ++ packages/block-editor/src/hooks/background.js | 29 +++- packages/block-editor/src/hooks/index.js | 1 + packages/block-editor/src/private-apis.js | 4 + .../block-editor/src/store/private-keys.js | 1 + .../components/global-styles/screen-block.js | 22 +++ .../components/global-styles/screen-layout.js | 6 +- .../provider/use-block-editor-settings.js | 14 +- phpunit/class-wp-theme-json-resolver-test.php | 60 ++++++++ phpunit/class-wp-theme-json-test.php | 58 +++++++- schemas/json/theme.json | 128 +++++++++--------- 19 files changed, 339 insertions(+), 86 deletions(-) create mode 100644 backport-changelog/6.7/6836.md diff --git a/backport-changelog/6.7/6836.md b/backport-changelog/6.7/6836.md new file mode 100644 index 00000000000000..feaa0f909f1f6f --- /dev/null +++ b/backport-changelog/6.7/6836.md @@ -0,0 +1,3 @@ +https://github.com/WordPress/wordpress-develop/pull/6836 + +* https://github.com/WordPress/gutenberg/pull/60100 diff --git a/docs/reference-guides/theme-json-reference/theme-json-living.md b/docs/reference-guides/theme-json-reference/theme-json-living.md index 59a820a16697c9..cf9ea9bc391136 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -206,6 +206,19 @@ Generate custom CSS custom properties of the form `--wp--custom--{key}--{nested- ## Styles +### background + +Background styles. + +| Property | Type | Props | +| --- | --- |--- | +| backgroundImage | string, object | | +| backgroundPosition | string, object | | +| backgroundRepeat | string, object | | +| backgroundSize | string, object | | + +--- + ### border Border styles. diff --git a/lib/block-supports/background.php b/lib/block-supports/background.php index 8e3c06159a1201..57b8d75f03d358 100644 --- a/lib/block-supports/background.php +++ b/lib/block-supports/background.php @@ -52,14 +52,14 @@ function gutenberg_render_background_support( $block_content, $block ) { return $block_content; } - $background_styles = array(); - $background_styles['backgroundImage'] = isset( $block_attributes['style']['background']['backgroundImage'] ) ? $block_attributes['style']['background']['backgroundImage'] : array(); + $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; if ( ! empty( $background_styles['backgroundImage'] ) ) { - $background_styles['backgroundSize'] = isset( $block_attributes['style']['background']['backgroundSize'] ) ? $block_attributes['style']['background']['backgroundSize'] : 'cover'; - $background_styles['backgroundPosition'] = isset( $block_attributes['style']['background']['backgroundPosition'] ) ? $block_attributes['style']['background']['backgroundPosition'] : null; - $background_styles['backgroundRepeat'] = isset( $block_attributes['style']['background']['backgroundRepeat'] ) ? $block_attributes['style']['background']['backgroundRepeat'] : null; - + $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'] = 'center'; diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index e09dc899a24894..bd74852c52074a 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -503,10 +503,10 @@ class WP_Theme_JSON_Gutenberg { */ const VALID_STYLES = array( 'background' => array( - 'backgroundImage' => 'top', - 'backgroundPosition' => 'top', - 'backgroundRepeat' => 'top', - 'backgroundSize' => 'top', + 'backgroundImage' => null, + 'backgroundPosition' => null, + 'backgroundRepeat' => null, + 'backgroundSize' => null, ), 'border' => array( 'color' => null, diff --git a/lib/class-wp-theme-json-resolver-gutenberg.php b/lib/class-wp-theme-json-resolver-gutenberg.php index fba107b9814025..a2153e639db3ba 100644 --- a/lib/class-wp-theme-json-resolver-gutenberg.php +++ b/lib/class-wp-theme-json-resolver-gutenberg.php @@ -805,6 +805,7 @@ public static function get_style_variations( $scope = 'theme' ) { * as the value of `_link` object in REST API responses. * * @since 6.6.0 + * @since 6.7.0 Added support for resolving block styles. * * @param WP_Theme_JSON_Gutenberg $theme_json A theme json instance. * @return array An array of resolved paths. @@ -818,10 +819,11 @@ public static function get_resolved_theme_uris( $theme_json ) { $theme_json_data = $theme_json->get_raw_data(); - // Top level styles. - $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; // Using the same file convention when registering web fonts. See: WP_Font_Face_Resolver:: to_theme_file_uri. $placeholder = 'file:./'; + + // Top level styles. + $background_image_url = $theme_json_data['styles']['background']['backgroundImage']['url'] ?? null; if ( isset( $background_image_url ) && is_string( $background_image_url ) && @@ -840,6 +842,33 @@ public static function get_resolved_theme_uris( $theme_json ) { $resolved_theme_uris[] = $resolved_theme_uri; } + // Block styles. + if ( ! empty( $theme_json_data['styles']['blocks'] ) ) { + foreach ( $theme_json_data['styles']['blocks'] as $block_name => $block_styles ) { + if ( ! isset( $block_styles['background']['backgroundImage']['url'] ) ) { + continue; + } + $background_image_url = $block_styles['background']['backgroundImage']['url'] ?? null; + if ( + isset( $background_image_url ) && + is_string( $background_image_url ) && + // Skip if the src doesn't start with the placeholder, as there's nothing to replace. + str_starts_with( $background_image_url, $placeholder ) ) { + $file_type = wp_check_filetype( $background_image_url ); + $src_url = str_replace( $placeholder, '', $background_image_url ); + $resolved_theme_uri = array( + 'name' => $background_image_url, + 'href' => sanitize_url( get_theme_file_uri( $src_url ) ), + 'target' => "styles.blocks.{$block_name}.background.backgroundImage.url", + ); + if ( isset( $file_type['type'] ) ) { + $resolved_theme_uri['type'] = $file_type['type']; + } + $resolved_theme_uris[] = $resolved_theme_uri; + } + } + } + return $resolved_theme_uris; } diff --git a/lib/global-styles-and-settings.php b/lib/global-styles-and-settings.php index 79c7028e32543e..e62d160d84aa78 100644 --- a/lib/global-styles-and-settings.php +++ b/lib/global-styles-and-settings.php @@ -258,6 +258,7 @@ function gutenberg_add_global_styles_block_custom_css() { function gutenberg_add_global_styles_for_blocks() { global $wp_styles; $tree = WP_Theme_JSON_Resolver_Gutenberg::get_merged_data(); + $tree = WP_Theme_JSON_Resolver_Gutenberg::resolve_theme_file_uris( $tree ); $block_nodes = $tree->get_styles_block_nodes(); foreach ( $block_nodes as $metadata ) { $block_css = $tree->get_styles_for_block( $metadata ); diff --git a/packages/block-editor/src/components/global-styles/background-panel.js b/packages/block-editor/src/components/global-styles/background-panel.js index 6baef04f39b6b3..92734b265341ac 100644 --- a/packages/block-editor/src/components/global-styles/background-panel.js +++ b/packages/block-editor/src/components/global-styles/background-panel.js @@ -269,6 +269,7 @@ function BackgroundImageControls( { inheritedValue, onRemoveImage = noop, displayInPanel, + themeFileURIs, } ) { const mediaUpload = useSelect( ( select ) => select( blockEditorStore ).getSettings().mediaUpload, @@ -392,7 +393,10 @@ function BackgroundImageControls( { name={ @@ -706,6 +710,7 @@ export default function BackgroundPanel( { onChange={ onChange } style={ value } inheritedValue={ inheritedValue } + themeFileURIs={ themeFileURIs } displayInPanel onRemoveImage={ () => { setIsDropDownOpen( false ); @@ -727,6 +732,7 @@ export default function BackgroundPanel( { onChange={ onChange } style={ value } inheritedValue={ inheritedValue } + themeFileURIs={ themeFileURIs } /> ) } diff --git a/packages/block-editor/src/components/global-styles/hooks.js b/packages/block-editor/src/components/global-styles/hooks.js index 50211db723e238..44a3d5e23e40cd 100644 --- a/packages/block-editor/src/components/global-styles/hooks.js +++ b/packages/block-editor/src/components/global-styles/hooks.js @@ -373,6 +373,15 @@ export function useSettingsForBlockElement( } } ); + [ 'backgroundImage', 'backgroundSize' ].forEach( ( key ) => { + if ( ! supportedStyles.includes( key ) ) { + updatedSettings.background = { + ...updatedSettings.background, + [ key ]: false, + }; + } + } ); + updatedSettings.shadow = supportedStyles.includes( 'shadow' ) ? updatedSettings.shadow : false; diff --git a/packages/block-editor/src/components/global-styles/use-global-styles-output.js b/packages/block-editor/src/components/global-styles/use-global-styles-output.js index c3eeb008964b36..d9fc247db17948 100644 --- a/packages/block-editor/src/components/global-styles/use-global-styles-output.js +++ b/packages/block-editor/src/components/global-styles/use-global-styles-output.js @@ -31,6 +31,7 @@ import { GlobalStylesContext } from './context'; import { useGlobalSetting } from './hooks'; import { getDuotoneFilter } from '../duotone/utils'; import { getGapCSSValue } from '../../hooks/gap'; +import { setBackgroundStyleDefaults } from '../../hooks/background'; import { store as blockEditorStore } from '../../store'; import { LAYOUT_DEFINITIONS } from '../../layouts/definitions'; import { getValueFromObjectPath, setImmutably } from '../../utils/object'; @@ -387,6 +388,20 @@ export function getStylesDeclarations( [] ); + /* + * Set background defaults. + * Applies to all background styles except the top-level site background. + */ + if ( ! isRoot && !! blockStyles.background ) { + blockStyles = { + ...blockStyles, + background: { + ...blockStyles.background, + ...setBackgroundStyleDefaults( blockStyles.background ), + }, + }; + } + // The goal is to move everything to server side generated engine styles // This is temporary as we absorb more and more styles into the engine. const extraRules = getCSSRules( blockStyles ); diff --git a/packages/block-editor/src/hooks/background.js b/packages/block-editor/src/hooks/background.js index c22e5cf20ff165..cd0b017831b795 100644 --- a/packages/block-editor/src/hooks/background.js +++ b/packages/block-editor/src/hooks/background.js @@ -16,6 +16,10 @@ import { useHasBackgroundPanel, hasBackgroundImageValue, } from '../components/global-styles/background-panel'; +import { + globalStylesDataKey, + globalStylesLinksDataKey, +} from '../store/private-keys'; export const BACKGROUND_SUPPORT_KEY = 'background'; @@ -134,10 +138,25 @@ export function BackgroundImagePanel( { setAttributes, settings, } ) { - const style = useSelect( - ( select ) => - select( blockEditorStore ).getBlockAttributes( clientId )?.style, - [ clientId ] + const { style, inheritedValue, _links } = useSelect( + ( select ) => { + const { getBlockAttributes, getSettings } = + select( blockEditorStore ); + const _settings = getSettings(); + return { + style: getBlockAttributes( clientId )?.style, + _links: _settings[ globalStylesLinksDataKey ], + /* + * @TODO 1. Pass inherited value down to all block style controls, + * See: packages/block-editor/src/hooks/style.js + * @TODO 2. Add support for block style variations, + * See implementation: packages/block-editor/src/hooks/block-style-variation.js + */ + inheritedValue: + _settings[ globalStylesDataKey ]?.blocks?.[ name ], + }; + }, + [ clientId, name ] ); if ( @@ -165,12 +184,14 @@ export function BackgroundImagePanel( { return ( ); } diff --git a/packages/block-editor/src/hooks/index.js b/packages/block-editor/src/hooks/index.js index bd1835571fdd4a..423e1f6f0bc322 100644 --- a/packages/block-editor/src/hooks/index.js +++ b/packages/block-editor/src/hooks/index.js @@ -87,5 +87,6 @@ export { getSpacingClassesAndStyles } from './use-spacing-props'; export { getTypographyClassesAndStyles } from './use-typography-props'; export { getGapCSSValue } from './gap'; export { useCachedTruthy } from './use-cached-truthy'; +export { setBackgroundStyleDefaults } from './background'; export { useZoomOut } from './use-zoom-out'; export { __unstableBlockStyleVariationOverridesWithConfig } from './block-style-variation'; diff --git a/packages/block-editor/src/private-apis.js b/packages/block-editor/src/private-apis.js index 44f68670556a30..c01d40d9d743b0 100644 --- a/packages/block-editor/src/private-apis.js +++ b/packages/block-editor/src/private-apis.js @@ -21,6 +21,7 @@ import BlockQuickNavigation from './components/block-quick-navigation'; import { LayoutStyle } from './components/block-list/layout'; import { BlockRemovalWarningModal } from './components/block-removal-warning-modal'; import { + setBackgroundStyleDefaults, useLayoutClasses, useLayoutStyles, __unstableBlockStyleVariationOverridesWithConfig, @@ -40,6 +41,7 @@ import { selectBlockPatternsKey, reusableBlocksSelectKey, globalStylesDataKey, + globalStylesLinksDataKey, } from './store/private-keys'; import { requiresWrapperOnCopy } from './components/writing-flow/utils'; import { PrivateRichText } from './components/rich-text/'; @@ -85,6 +87,7 @@ lock( privateApis, { usesContextKey, useFlashEditableBlocks, globalStylesDataKey, + globalStylesLinksDataKey, selectBlockPatternsKey, requiresWrapperOnCopy, PrivateRichText, @@ -95,4 +98,5 @@ lock( privateApis, { useSpacingSizes, useBlockDisplayTitle, __unstableBlockStyleVariationOverridesWithConfig, + setBackgroundStyleDefaults, } ); diff --git a/packages/block-editor/src/store/private-keys.js b/packages/block-editor/src/store/private-keys.js index 82264ebe191579..00fac5531b9c3f 100644 --- a/packages/block-editor/src/store/private-keys.js +++ b/packages/block-editor/src/store/private-keys.js @@ -1,3 +1,4 @@ export const globalStylesDataKey = Symbol( 'globalStylesDataKey' ); +export const globalStylesLinksDataKey = Symbol( 'globalStylesLinks' ); export const selectBlockPatternsKey = Symbol( 'selectBlockPatternsKey' ); export const reusableBlocksSelectKey = Symbol( 'reusableBlocksSelect' ); diff --git a/packages/edit-site/src/components/global-styles/screen-block.js b/packages/edit-site/src/components/global-styles/screen-block.js index 2368f7499acbf6..fd71cdfa630687 100644 --- a/packages/edit-site/src/components/global-styles/screen-block.js +++ b/packages/edit-site/src/components/global-styles/screen-block.js @@ -25,6 +25,11 @@ import { VariationsPanel, } from './variations/variations-panel'; +// Initial control values where no block style is set. +const BACKGROUND_BLOCK_DEFAULT_VALUES = { + backgroundSize: 'cover', +}; + function applyFallbackStyle( border ) { if ( ! border ) { return border; @@ -70,6 +75,8 @@ const { useHasFiltersPanel, useHasImageSettingsPanel, useGlobalStyle, + useHasBackgroundPanel, + BackgroundPanel: StylesBackgroundPanel, BorderPanel: StylesBorderPanel, ColorPanel: StylesColorPanel, TypographyPanel: StylesTypographyPanel, @@ -121,6 +128,7 @@ function ScreenBlock( { name, variation } ) { } const blockVariations = useBlockVariations( name ); + const hasBackgroundPanel = useHasBackgroundPanel( settings ); const hasTypographyPanel = useHasTypographyPanel( settings ); const hasColorPanel = useHasColorPanel( settings ); const hasBorderPanel = useHasBorderPanel( settings ); @@ -296,6 +304,20 @@ function ScreenBlock( { name, variation } ) { /> ) } + { hasBackgroundPanel && ( + + ) } + { canEditCSS && (

diff --git a/packages/edit-site/src/components/global-styles/screen-layout.js b/packages/edit-site/src/components/global-styles/screen-layout.js index 61c64b88f0e480..1e68309fe01866 100644 --- a/packages/edit-site/src/components/global-styles/screen-layout.js +++ b/packages/edit-site/src/components/global-styles/screen-layout.js @@ -23,7 +23,11 @@ function ScreenLayout() { const [ rawSettings ] = useGlobalSetting( '' ); const settings = useSettingsForBlockElement( rawSettings ); const hasDimensionsPanel = useHasDimensionsPanel( settings ); - const hasBackgroundPanel = useHasBackgroundPanel( settings ); + /* + * Use the raw settings to determine if the background panel should be displayed, + * as the background panel is not dependent on the block element settings. + */ + const hasBackgroundPanel = useHasBackgroundPanel( rawSettings ); return ( <> diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 2917c6905e3f0d..823cf852d14814 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -27,7 +27,7 @@ import { lock, unlock } from '../../lock-unlock'; import { useGlobalStylesContext } from '../global-styles-provider'; const EMPTY_BLOCKS_LIST = []; -const DEFAULT_STYLES = {}; +const EMPTY_OBJECT = {}; function __experimentalReusableBlocksSelect( select ) { return ( @@ -88,8 +88,12 @@ const BLOCK_EDITOR_SETTINGS = [ '__experimentalArchiveTitleNameLabel', ]; -const { globalStylesDataKey, selectBlockPatternsKey, reusableBlocksSelectKey } = - unlock( privateApis ); +const { + globalStylesDataKey, + globalStylesLinksDataKey, + selectBlockPatternsKey, + reusableBlocksSelectKey, +} = unlock( privateApis ); /** * React hook used to compute the block editor settings to use for the post editor. @@ -179,7 +183,8 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { ); const { merged: mergedGlobalStyles } = useGlobalStylesContext(); - const globalStylesData = mergedGlobalStyles.styles ?? DEFAULT_STYLES; + const globalStylesData = mergedGlobalStyles.styles ?? EMPTY_OBJECT; + const globalStylesLinksData = mergedGlobalStyles._links ?? EMPTY_OBJECT; const settingsBlockPatterns = settings.__experimentalAdditionalBlockPatterns ?? // WP 6.0 @@ -268,6 +273,7 @@ function useBlockEditorSettings( settings, postType, postId, renderingMode ) { ) ), [ globalStylesDataKey ]: globalStylesData, + [ globalStylesLinksDataKey ]: globalStylesLinksData, allowedBlockTypes, allowRightClickOverrides, focusMode: focusMode && ! forceDisableFocusMode, diff --git a/phpunit/class-wp-theme-json-resolver-test.php b/phpunit/class-wp-theme-json-resolver-test.php index e7a1b44049393e..d2339f2496290c 100644 --- a/phpunit/class-wp-theme-json-resolver-test.php +++ b/phpunit/class-wp-theme-json-resolver-test.php @@ -1216,6 +1216,22 @@ public function test_resolve_theme_file_uris() { 'url' => 'file:./example/img/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./example/img/quote.png', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./example/img/verse.png', + ), + ), + ), + ), ), ) ); @@ -1228,6 +1244,22 @@ public function test_resolve_theme_file_uris() { 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/quote.png', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'https://example.org/wp-content/themes/example-theme/example/img/verse.png', + ), + ), + ), + ), ), ); @@ -1261,6 +1293,22 @@ public function test_get_resolved_theme_uris() { 'url' => 'file:./example/img/image.png', ), ), + 'blocks' => array( + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./example/img/quote.jpg', + ), + ), + ), + 'core/verse' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'file:./example/img/verse.gif', + ), + ), + ), + ), ), ) ); @@ -1272,6 +1320,18 @@ public function test_get_resolved_theme_uris() { 'target' => 'styles.background.backgroundImage.url', 'type' => 'image/png', ), + array( + 'name' => 'file:./example/img/quote.jpg', + 'href' => 'https://example.org/wp-content/themes/example-theme/example/img/quote.jpg', + 'target' => 'styles.blocks.core/quote.background.backgroundImage.url', + 'type' => 'image/jpeg', + ), + array( + 'name' => 'file:./example/img/verse.gif', + 'href' => 'https://example.org/wp-content/themes/example-theme/example/img/verse.gif', + 'target' => 'styles.blocks.core/verse.background.backgroundImage.url', + 'type' => 'image/gif', + ), ); /* diff --git a/phpunit/class-wp-theme-json-test.php b/phpunit/class-wp-theme-json-test.php index b188ce4fce6abc..2d6d4dbb668c94 100644 --- a/phpunit/class-wp-theme-json-test.php +++ b/phpunit/class-wp-theme-json-test.php @@ -4782,7 +4782,7 @@ public function test_get_top_level_background_image_styles() { ); $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; - $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background styles type does not match expectations' ); + $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background styles do not match expectations' ); $theme_json = new WP_Theme_JSON_Gutenberg( array( @@ -4799,7 +4799,61 @@ public function test_get_top_level_background_image_styles() { ); $expected_styles = "html{min-height: calc(100% - var(--wp-admin--admin-bar--height, 0px));}:root :where(body){background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: contain;}"; - $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background image as string type does not match expectations' ); + $this->assertSame( $expected_styles, $theme_json->get_styles_for_block( $body_node ), 'Styles returned from "::get_styles_for_block()" with top-level background image as string type do not match expectations' ); + } + + public function test_get_block_background_image_styles() { + $theme_json = new WP_Theme_JSON_Gutenberg( + array( + 'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA, + 'styles' => array( + 'blocks' => array( + 'core/group' => array( + 'background' => array( + 'backgroundImage' => "url('http://example.org/group.png')", + 'backgroundSize' => 'cover', + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center center', + ), + ), + 'core/quote' => array( + 'background' => array( + 'backgroundImage' => array( + 'url' => 'http://example.org/quote.png', + ), + 'backgroundSize' => 'cover', + 'backgroundRepeat' => 'no-repeat', + 'backgroundPosition' => 'center center', + ), + ), + ), + ), + ) + ); + + $quote_node = array( + 'name' => 'core/quote', + 'path' => array( 'styles', 'blocks', 'core/quote' ), + 'selector' => '.wp-block-quote', + 'selectors' => array( + 'root' => '.wp-block-quote', + ), + ); + + $quote_styles = ":root :where(.wp-block-quote){background-image: url('http://example.org/quote.png');background-position: center center;background-repeat: no-repeat;background-size: cover;}"; + $this->assertSame( $quote_styles, $theme_json->get_styles_for_block( $quote_node ), 'Styles returned from "::get_styles_for_block()" with block-level background styles do not match expectations' ); + + $group_node = array( + 'name' => 'core/group', + 'path' => array( 'styles', 'blocks', 'core/group' ), + 'selector' => '.wp-block-group', + 'selectors' => array( + 'root' => '.wp-block-group', + ), + ); + + $group_styles = ":root :where(.wp-block-group){background-image: url('http://example.org/group.png');background-position: center center;background-repeat: no-repeat;background-size: cover;}"; + $this->assertSame( $group_styles, $theme_json->get_styles_for_block( $group_node ), 'Styles returned from "::get_styles_for_block()" with block-level background styles as string type do not match expectations' ); } /** diff --git a/schemas/json/theme.json b/schemas/json/theme.json index ec42bd1d54f0b7..f1666253e3de3e 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -1162,6 +1162,68 @@ "stylesProperties": { "type": "object", "properties": { + "background": { + "description": "Background styles.", + "type": "object", + "properties": { + "backgroundImage": { + "description": "Sets the `background-image` CSS property.", + "oneOf": [ + { + "description": "A valid CSS value for the background-image CSS property.", + "type": "string" + }, + { + "type": "object", + "properties": { + "url": { + "description": "A URL to an image file, or a path to a file relative to the theme root directory, and prefixed with `file:`, e.g., 'file:./path/to/file.png'.", + "type": "string" + } + }, + "additionalProperties": false + }, + { + "$ref": "#/definitions/refComplete" + } + ] + }, + "backgroundPosition": { + "description": "Sets the `background-position` CSS property.", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/refComplete" + } + ] + }, + "backgroundRepeat": { + "description": "Sets the `background-repeat` CSS property.", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/refComplete" + } + ] + }, + "backgroundSize": { + "description": "Sets the `background-size` CSS property.", + "oneOf": [ + { + "type": "string" + }, + { + "$ref": "#/definitions/refComplete" + } + ] + } + }, + "additionalProperties": false + }, "border": { "description": "Border styles.", "type": "object", @@ -1814,6 +1876,7 @@ }, { "properties": { + "background": {}, "border": {}, "color": {}, "dimensions": {}, @@ -1840,6 +1903,7 @@ { "properties": { "border": {}, + "background": {}, "color": {}, "filter": {}, "shadow": {}, @@ -2248,6 +2312,7 @@ }, { "properties": { + "background": {}, "border": {}, "color": {}, "dimensions": {}, @@ -2743,6 +2808,7 @@ }, { "properties": { + "background": {}, "border": {}, "color": {}, "spacing": {}, @@ -2759,68 +2825,6 @@ "description": "Styles defined on a per-block basis using the block's selector.", "$ref": "#/definitions/stylesBlocksPropertiesComplete" }, - "background": { - "description": "Background styles.", - "type": "object", - "properties": { - "backgroundImage": { - "description": "Sets the `background-image` CSS property.", - "oneOf": [ - { - "description": "A valid CSS value for the background-image property, or a path to a file relative to the theme root directory, and prefixed with `file:`, e.g., 'file:./path/to/file.png'.", - "type": "string" - }, - { - "type": "object", - "properties": { - "url": { - "description": "A URL to an image file.", - "type": "string" - } - }, - "additionalProperties": false - }, - { - "$ref": "#/definitions/refComplete" - } - ] - }, - "backgroundPosition": { - "description": "Sets the `background-position` CSS property.", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/refComplete" - } - ] - }, - "backgroundRepeat": { - "description": "Sets the `background-repeat` CSS property.", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/refComplete" - } - ] - }, - "backgroundSize": { - "description": "Sets the `background-size` CSS property.", - "oneOf": [ - { - "type": "string" - }, - { - "$ref": "#/definitions/refComplete" - } - ] - } - }, - "additionalProperties": false - }, "variations": { "$ref": "#/definitions/stylesVariationsProperties" }