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

Global styles: add background image to top-level theme.json styles #59354

Merged
merged 17 commits into from
Feb 29, 2024
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
13 changes: 13 additions & 0 deletions docs/reference-guides/theme-json-reference/theme-json-living.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,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.
Expand Down
53 changes: 25 additions & 28 deletions lib/block-supports/background.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,28 @@ function gutenberg_register_background_support( $block_type ) {
}
}

/**
* Given a theme.json or block background styles, returns the background styles for a block.
*
* @since 6.6.0
*
* @param array $background_styles Background style properties.
* @return array Style engine array of CSS string and style declarations.
*/
function gutenberg_get_background_support_styles( $background_styles = array() ) {
$background_image_source = isset( $background_styles['backgroundImage']['source'] ) ? $background_styles['backgroundImage']['source'] : null;
$background_styles['backgroundSize'] = ! empty( $background_styles['backgroundSize'] ) ? $background_styles['backgroundSize'] : 'cover';

if ( 'file' === $background_image_source && ! empty( $background_styles['backgroundImage']['url'] ) ) {
// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $background_styles['backgroundSize'] && ! isset( $background_styles['backgroundPosition'] ) ) {
$background_styles['backgroundPosition'] = 'center';
}
}

return gutenberg_style_engine_get_styles( array( 'background' => $background_styles ) );
}

/**
* Renders the background styles to the block wrapper.
* This block support uses the `render_block` hook to ensure that
Expand All @@ -46,38 +68,13 @@ function gutenberg_render_background_support( $block_content, $block ) {

if (
! $has_background_image_support ||
wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundImage' )
wp_should_skip_block_supports_serialization( $block_type, 'background', 'backgroundImage' ) ||
! isset( $block_attributes['style']['background'] )
) {
return $block_content;
}

$background_image_source = $block_attributes['style']['background']['backgroundImage']['source'] ?? null;
$background_image_url = $block_attributes['style']['background']['backgroundImage']['url'] ?? null;
$background_size = $block_attributes['style']['background']['backgroundSize'] ?? 'cover';
$background_position = $block_attributes['style']['background']['backgroundPosition'] ?? null;
$background_repeat = $block_attributes['style']['background']['backgroundRepeat'] ?? null;

$background_block_styles = array();

if (
'file' === $background_image_source &&
$background_image_url
) {
// Set file based background URL.
// TODO: In a follow-up, similar logic could be added to inject a featured image url.
$background_block_styles['backgroundImage']['url'] = $background_image_url;
// Only output the background size and repeat when an image url is set.
$background_block_styles['backgroundSize'] = $background_size;
$background_block_styles['backgroundRepeat'] = $background_repeat;
$background_block_styles['backgroundPosition'] = $background_position;

// If the background size is set to `contain` and no position is set, set the position to `center`.
if ( 'contain' === $background_size && ! isset( $background_position ) ) {
$background_block_styles['backgroundPosition'] = 'center';
}
}

$styles = gutenberg_style_engine_get_styles( array( 'background' => $background_block_styles ) );
$styles = gutenberg_get_background_support_styles( $block_attributes['style']['background'] );

if ( ! empty( $styles['css'] ) ) {
// Inject background styles to the first element, presuming it's the wrapper, if it exists.
Expand Down
18 changes: 18 additions & 0 deletions lib/class-wp-theme-json-gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,18 @@ class WP_Theme_JSON_Gutenberg {
* removed the `--wp--style--block-gap` property.
* @since 6.2.0 Added `outline-*`, and `min-height` properties.
* @since 6.3.0 Added `writing-mode` property.
* @since 6.6.0 Added `background-[image|position|repeat|size]` properties.
*
* @var array
*/
const PROPERTIES_METADATA = array(
'aspect-ratio' => array( 'dimensions', 'aspectRatio' ),
'background' => array( 'color', 'gradient' ),
'background-color' => array( 'color', 'background' ),
'background-image' => array( 'background', 'backgroundImage' ),
'background-position' => array( 'background', 'backgroundPosition' ),
'background-repeat' => array( 'background', 'backgroundRepeat' ),
'background-size' => array( 'background', 'backgroundSize' ),
'border-radius' => array( 'border', 'radius' ),
'border-top-left-radius' => array( 'border', 'radius', 'topLeft' ),
'border-top-right-radius' => array( 'border', 'radius', 'topRight' ),
Expand Down Expand Up @@ -461,10 +466,17 @@ class WP_Theme_JSON_Gutenberg {
* added new property `shadow`,
* updated `blockGap` to be allowed at any level.
* @since 6.2.0 Added `outline`, and `minHeight` properties.
* @since 6.6.0 Added `background` sub properties to top-level only.
*
* @var array
*/
const VALID_STYLES = array(
'background' => array(
'backgroundImage' => 'top',
'backgroundPosition' => 'top',
'backgroundRepeat' => 'top',
'backgroundSize' => 'top',
),
'border' => array(
'color' => null,
'radius' => null,
Expand Down Expand Up @@ -2120,6 +2132,12 @@ protected static function compute_style_properties( $styles, $settings = array()
}
}

// Processes background styles.
if ( 'background' === $value_path[0] && isset( $styles['background'] ) ) {
$background_styles = gutenberg_get_background_support_styles( $styles['background'] );
$value = $background_styles['declarations'][ $css_property ] ?? $value;
}

// Skip if empty and not "0" or value represents array of longhand values.
$has_missing_value = empty( $value ) && ! is_numeric( $value );
if ( $has_missing_value || is_array( $value ) ) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,7 @@ const STYLE_KEYS = [
'filter',
'outline',
'shadow',
'background',
];

function pickStyleKeys( treeToPickFrom ) {
Expand Down
5 changes: 5 additions & 0 deletions packages/blocks/src/api/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ export const __EXPERIMENTAL_STYLE_PROPERTY = {
requiresOptOut: true,
useEngine: true,
},
backgroundImage: {
value: [ 'background', 'backgroundImage' ],
support: [ 'background', 'backgroundImage' ],
useEngine: true,
},
backgroundRepeat: {
value: [ 'background', 'backgroundRepeat' ],
support: [ 'background', 'backgroundRepeat' ],
Expand Down
18 changes: 17 additions & 1 deletion packages/style-engine/src/styles/background/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,23 @@ const backgroundImage = {
return styleRules;
}

if ( _backgroundImage?.source === 'file' && _backgroundImage?.url ) {
/*
* If the background image is a string, it could already contain a url() function,
* or have a linear-gradient value.
*/
if ( typeof _backgroundImage === 'string' ) {
ramonjd marked this conversation as resolved.
Show resolved Hide resolved
styleRules.push( {
selector: options.selector,
key: 'backgroundImage',
value: _backgroundImage,
} );
}

if (
typeof _backgroundImage === 'object' &&
_backgroundImage?.source === 'file' &&
_backgroundImage?.url
) {
styleRules.push( {
selector: options.selector,
key: 'backgroundImage',
Expand Down
27 changes: 27 additions & 0 deletions packages/style-engine/src/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,33 @@ describe( 'getCSSRules', () => {
] );
} );

it( 'should output background image value when that value is a string', () => {
expect(
getCSSRules(
{
background: {
backgroundImage:
"linear-gradient(to bottom,rgb(255 255 0 / 50%),rgb(0 0 255 / 50%), url('https://example.com/image.jpg')",
},
},
{
selector: '.some-selector',
}
)
).toEqual( [
{
selector: '.some-selector',
key: 'backgroundImage',
value: "linear-gradient(to bottom,rgb(255 255 0 / 50%),rgb(0 0 255 / 50%), url('https://example.com/image.jpg')",
},
{
selector: '.some-selector',
key: 'backgroundSize',
value: 'cover',
},
] );
} );

it( 'should output fallback center position for contain background size', () => {
expect(
getCSSRules(
Expand Down
7 changes: 3 additions & 4 deletions packages/style-engine/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@ export interface BorderIndividualStyles< T extends BoxEdge > {

export interface Style {
background?: {
backgroundImage: {
url?: CSSProperties[ 'backgroundImage' ];
source?: string;
};
backgroundImage?:
| { url?: CSSProperties[ 'backgroundImage' ]; source?: string }
| CSSProperties[ 'backgroundImage' ];
backgroundPosition?: CSSProperties[ 'backgroundPosition' ];
backgroundRepeat?: CSSProperties[ 'backgroundRepeat' ];
backgroundSize?: CSSProperties[ 'backgroundSize' ];
Expand Down
79 changes: 79 additions & 0 deletions phpunit/block-supports/background-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,18 @@ public function data_background_block_support() {
'expected_wrapper' => '<div class="has-background" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
'wrapper' => '<div>Content</div>',
),
'background image style is applied when backgroundImage is a string' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-output',
'background_settings' => array(
'backgroundImage' => true,
),
'background_style' => array(
'backgroundImage' => "url('https://example.com/image.jpg')",
),
'expected_wrapper' => '<div class="has-background" style="background-image:url(&#039;https://example.com/image.jpg&#039;);background-size:cover;">Content</div>',
'wrapper' => '<div>Content</div>',
),
'background image style with contain, position, and repeat is applied' => array(
'theme_name' => 'block-theme-child-with-fluid-typography',
'block_name' => 'test/background-rules-are-output',
Expand Down Expand Up @@ -201,4 +213,71 @@ public function data_background_block_support() {
),
);
}

/**
* Tests generating background styles.
*
* @covers ::gutenberg_get_background_support_styles
*
* @dataProvider data_get_background_support_styles
*
* @param mixed $background_style The background styles within the block attributes.
* @param string $expected_css Expected markup for the block wrapper.
*/
public function test_get_background_support_styles( $background_style, $expected_css ) {
$actual = gutenberg_get_background_support_styles( $background_style )['css'];

$this->assertEquals(
$expected_css,
$actual,
'Background CSS should be correct.'
);
}
public function data_get_background_support_styles() {
return array(
'css generated with file source' => array(
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
),
),
'expected_css' => "background-image:url('https://example.com/image.jpg');background-size:cover;",
),
'css generated where backgroundImage is a string' => array(
'background_style' => array(
'backgroundImage' => "url('https://example.com/image.jpg')",
),
'expected_css' => "background-image:url('https://example.com/image.jpg');background-size:cover;",
),
'css generated with escaped URL' => array(
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg?q=pom-poms+%3Cscript%3Eevil_script()%3C/script%3E',
),
'backgroundSize' => 'cover',
),
'expected_css' => 'background-size:cover;',
),
'css generated with expected properties' => array(
'background_style' => array(
'backgroundImage' => "url('https://example.com/image.jpg')",
'backgroundSize' => '6px, auto, contain',
'backgroundPosition' => 'bottom 10px right 20px',
'backgroundRepeat' => 'repeat space',
),
'expected_css' => "background-image:url('https://example.com/image.jpg');background-position:bottom 10px right 20px;background-repeat:repeat space;background-size:6px, auto, contain;",
),
'css generated for file source with contain size should add center position' => array(
'background_style' => array(
'backgroundImage' => array(
'url' => 'https://example.com/image.jpg',
'source' => 'file',
),
'backgroundSize' => 'contain',
),
'expected_css' => "background-image:url('https://example.com/image.jpg');background-position:center;background-size:contain;",
),
);
}
}
46 changes: 46 additions & 0 deletions phpunit/class-wp-theme-json-test.php
Original file line number Diff line number Diff line change
Expand Up @@ -4745,6 +4745,52 @@ public function test_get_shadow_styles_for_blocks() {
$this->assertSame( $expected_styles, $theme_json->get_stylesheet() );
}

public function test_get_top_level_background_image_styles() {
$theme_json = new WP_Theme_JSON_Gutenberg(
array(
'version' => WP_Theme_JSON_Gutenberg::LATEST_SCHEMA,
'styles' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
'backgroundSize' => 'cover',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
'blocks' => array(
'core/paragraph' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
'source' => 'file',
),
'backgroundSize' => 'cover',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
),
),
'elements' => array(
'button' => array(
'background' => array(
'backgroundImage' => array(
'url' => 'http://example.org/image.png',
),
'backgroundSize' => 'cover',
'backgroundRepeat' => 'no-repeat',
'backgroundPosition' => 'center center',
),
),
),
),
)
);

$expected_styles = "body { margin: 0; }.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; }:where(.is-layout-flex){gap: 0.5em;}:where(.is-layout-grid){gap: 0.5em;}body .is-layout-flow > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-flow > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-flow > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignleft{float: left;margin-inline-start: 0;margin-inline-end: 2em;}body .is-layout-constrained > .alignright{float: right;margin-inline-start: 2em;margin-inline-end: 0;}body .is-layout-constrained > .aligncenter{margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > :where(:not(.alignleft):not(.alignright):not(.alignfull)){max-width: var(--wp--style--global--content-size);margin-left: auto !important;margin-right: auto !important;}body .is-layout-constrained > .alignwide{max-width: var(--wp--style--global--wide-size);}body .is-layout-flex{display: flex;}body .is-layout-flex{flex-wrap: wrap;align-items: center;}body .is-layout-flex > *{margin: 0;}body .is-layout-grid{display: grid;}body .is-layout-grid > *{margin: 0;}body{background-image: url('http://example.org/image.png');background-position: center center;background-repeat: no-repeat;background-size: cover;}";
$this->assertSame( $expected_styles, $theme_json->get_stylesheet() );
}

public function test_get_custom_css_handles_global_custom_css() {
$theme_json = new WP_Theme_JSON_Gutenberg(
array(
Expand Down
Loading
Loading