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 c8870ebb3447d7..46ebecdfec8c3c 100644 --- a/docs/reference-guides/theme-json-reference/theme-json-living.md +++ b/docs/reference-guides/theme-json-reference/theme-json-living.md @@ -53,6 +53,17 @@ Setting that enables the following UI tools: --- +### backdropFilter + +Settings related to backdrop filters. + +| Property | Description | Type | Default | +| -------- | ----------- | ---- | ------- | +| defaultPresets | Allow users to choose backdrop filters from the default presets. | `boolean` | `true` | +| presets | Backdrop filter presets for the picker. | `[ { name, slug, backdropFilter } ]` | | + +--- + ### background Settings related to background. @@ -309,6 +320,12 @@ Box shadow styles. --- +### backdropFilter + +Backdrop-filter styles. Accepts a raw CSS value or a preset reference (`var:preset|backdrop-filter|{slug}`). + +--- + ### spacing Spacing styles. diff --git a/lib/block-supports/backdrop-filter.php b/lib/block-supports/backdrop-filter.php new file mode 100644 index 00000000000000..b026184066b636 --- /dev/null +++ b/lib/block-supports/backdrop-filter.php @@ -0,0 +1,97 @@ +attributes ) { + $block_type->attributes = array(); + } + + if ( ! array_key_exists( 'style', $block_type->attributes ) ) { + $block_type->attributes['style'] = array( + 'type' => 'object', + ); + } +} + +/** + * Add CSS classes and inline styles for backdrop-filter features to the incoming attributes array. + * This is applied to the block markup on the front-end. Emits both the standard + * `backdrop-filter` property and `-webkit-backdrop-filter` for Safari <= 17.3 + * compatibility. + * + * @param WP_Block_Type $block_type Block type. + * @param array $block_attributes Block attributes. + * + * @return array Backdrop-filter inline styles. + */ +function gutenberg_apply_backdrop_filter_support( $block_type, $block_attributes ) { + $has_backdrop_filter_support = block_has_support( $block_type, array( 'backdropFilter' ), false ); + + if ( + ! $has_backdrop_filter_support || + wp_should_skip_block_supports_serialization( $block_type, 'backdropFilter' ) + ) { + return array(); + } + + $backdrop_filter_value = $block_attributes['style']['backdropFilter'] ?? null; + + if ( null === $backdrop_filter_value || '' === $backdrop_filter_value ) { + return array(); + } + + $block_styles = array( + 'backdropFilter' => $backdrop_filter_value, + ); + + $styles = gutenberg_style_engine_get_styles( $block_styles ); + + if ( empty( $styles['css'] ) ) { + return array(); + } + + // The style engine emits `backdrop-filter:;`. We duplicate the value + // with the `-webkit-` prefix so older Safari keeps working. + // See https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter + $webkit_css = str_replace( + 'backdrop-filter:', + '-webkit-backdrop-filter:', + $styles['css'] + ); + + return array( 'style' => $webkit_css . $styles['css'] ); +} + +// Register the block support. +WP_Block_Supports::get_instance()->register( + 'backdrop-filter', + array( + 'register_attribute' => 'gutenberg_register_backdrop_filter_support', + 'apply' => 'gutenberg_apply_backdrop_filter_support', + ) +); + +// Add backdrop-filter and -webkit-backdrop-filter to the safe CSS property allowlist. +add_filter( + 'safe_style_css', + function ( $styles ) { + $styles[] = 'backdrop-filter'; + $styles[] = '-webkit-backdrop-filter'; + return $styles; + } +); diff --git a/lib/class-wp-theme-json-gutenberg.php b/lib/class-wp-theme-json-gutenberg.php index 4423b6c2db1efb..5a2d67d23659ef 100644 --- a/lib/class-wp-theme-json-gutenberg.php +++ b/lib/class-wp-theme-json-gutenberg.php @@ -204,6 +204,15 @@ class WP_Theme_JSON_Gutenberg { 'classes' => array(), 'properties' => array( 'box-shadow' ), ), + array( + 'path' => array( 'backdropFilter', 'presets' ), + 'prevent_override' => array( 'backdropFilter', 'defaultPresets' ), + 'use_default_names' => false, + 'value_key' => 'backdropFilter', + 'css_vars' => '--wp--preset--backdrop-filter--$slug', + 'classes' => array(), + 'properties' => array( 'backdrop-filter' ), + ), array( 'path' => array( 'border', 'radiusSizes' ), 'prevent_override' => false, @@ -310,6 +319,7 @@ class WP_Theme_JSON_Gutenberg { 'text-indent' => array( 'typography', 'textIndent' ), 'filter' => array( 'filter', 'duotone' ), 'box-shadow' => array( 'shadow' ), + 'backdrop-filter' => array( 'backdropFilter' ), 'height' => array( 'dimensions', 'height' ), 'width' => array( 'dimensions', 'width' ), 'writing-mode' => array( 'typography', 'writingMode' ), @@ -464,6 +474,10 @@ class WP_Theme_JSON_Gutenberg { 'presets' => null, 'defaultPresets' => null, ), + 'backdropFilter' => array( + 'presets' => null, + 'defaultPresets' => null, + ), 'typography' => array( 'fluid' => null, 'customFontSize' => null, diff --git a/lib/load.php b/lib/load.php index b02707dc4d73e7..487f7f4b9f635a 100644 --- a/lib/load.php +++ b/lib/load.php @@ -196,6 +196,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/block-supports/dimensions.php'; require __DIR__ . '/block-supports/duotone.php'; require __DIR__ . '/block-supports/shadow.php'; +require __DIR__ . '/block-supports/backdrop-filter.php'; require __DIR__ . '/block-supports/background.php'; require __DIR__ . '/block-supports/block-style-variations.php'; require __DIR__ . '/block-supports/aria-label.php'; diff --git a/lib/theme.json b/lib/theme.json index 8c0d5a1e88a082..6bd0a2bc2e6bde 100644 --- a/lib/theme.json +++ b/lib/theme.json @@ -231,6 +231,31 @@ } ] }, + "backdropFilter": { + "defaultPresets": true, + "presets": [ + { + "name": "Light haze", + "slug": "light", + "backdropFilter": "blur(6px)" + }, + { + "name": "Frost", + "slug": "frost", + "backdropFilter": "blur(12px) saturate(150%)" + }, + { + "name": "Heavy blur", + "slug": "heavy", + "backdropFilter": "blur(24px)" + }, + { + "name": "Vibrancy", + "slug": "vibrancy", + "backdropFilter": "blur(16px) saturate(180%) brightness(1.05)" + } + ] + }, "shadow": { "defaultPresets": true, "presets": [ diff --git a/packages/style-engine/src/class-wp-style-engine.php b/packages/style-engine/src/class-wp-style-engine.php index 447bc24140848b..7fd4d84c8d0774 100644 --- a/packages/style-engine/src/class-wp-style-engine.php +++ b/packages/style-engine/src/class-wp-style-engine.php @@ -41,7 +41,7 @@ final class WP_Style_Engine { * @var array */ const BLOCK_STYLE_DEFINITIONS_METADATA = array( - 'background' => array( + 'background' => array( 'backgroundImage' => array( 'property_keys' => array( 'default' => 'background-image', @@ -86,7 +86,7 @@ final class WP_Style_Engine { ), ), ), - 'color' => array( + 'color' => array( 'text' => array( 'property_keys' => array( 'default' => 'color', @@ -127,7 +127,7 @@ final class WP_Style_Engine { ), ), ), - 'border' => array( + 'border' => array( 'color' => array( 'property_keys' => array( 'default' => 'border-color', @@ -192,7 +192,7 @@ final class WP_Style_Engine { ), ), ), - 'shadow' => array( + 'shadow' => array( 'shadow' => array( 'property_keys' => array( 'default' => 'box-shadow', @@ -203,7 +203,18 @@ final class WP_Style_Engine { ), ), ), - 'dimensions' => array( + 'backdropFilter' => array( + 'backdropFilter' => array( + 'property_keys' => array( + 'default' => 'backdrop-filter', + ), + 'path' => array( 'backdropFilter' ), + 'css_vars' => array( + 'backdrop-filter' => '--wp--preset--backdrop-filter--$slug', + ), + ), + ), + 'dimensions' => array( 'aspectRatio' => array( 'property_keys' => array( 'default' => 'aspect-ratio', @@ -247,7 +258,7 @@ final class WP_Style_Engine { ), ), ), - 'spacing' => array( + 'spacing' => array( 'padding' => array( 'property_keys' => array( 'default' => 'padding', @@ -269,7 +280,7 @@ final class WP_Style_Engine { ), ), ), - 'typography' => array( + 'typography' => array( 'fontSize' => array( 'property_keys' => array( 'default' => 'font-size', diff --git a/packages/style-engine/src/styles/backdrop-filter/index.ts b/packages/style-engine/src/styles/backdrop-filter/index.ts new file mode 100644 index 00000000000000..f2aa6f4f54afb3 --- /dev/null +++ b/packages/style-engine/src/styles/backdrop-filter/index.ts @@ -0,0 +1,19 @@ +/** + * Internal dependencies + */ +import type { Style, StyleOptions } from '../../types'; +import { generateRule } from '../utils'; + +const backdropFilter = { + name: 'backdropFilter', + generate: ( style: Style, options: StyleOptions ) => { + return generateRule( + style, + options, + [ 'backdropFilter' ], + 'backdropFilter' + ); + }, +}; + +export default [ backdropFilter ]; diff --git a/packages/style-engine/src/styles/index.ts b/packages/style-engine/src/styles/index.ts index f59935ee3fc571..63ee59a12928a4 100644 --- a/packages/style-engine/src/styles/index.ts +++ b/packages/style-engine/src/styles/index.ts @@ -5,6 +5,7 @@ import border from './border'; import color from './color'; import dimensions from './dimensions'; import background from './background'; +import backdropFilter from './backdrop-filter'; import shadow from './shadow'; import outline from './outline'; import spacing from './spacing'; @@ -19,4 +20,5 @@ export const styleDefinitions = [ ...typography, ...shadow, ...background, + ...backdropFilter, ]; diff --git a/schemas/json/theme.json b/schemas/json/theme.json index 6f6f077d0cba8b..85b53f3cc7ecc0 100644 --- a/schemas/json/theme.json +++ b/schemas/json/theme.json @@ -405,6 +405,50 @@ } } }, + "settingsBackdropFilterProperties": { + "type": "object", + "properties": { + "backdropFilter": { + "description": "Settings related to backdrop filters.", + "type": "object", + "properties": { + "defaultPresets": { + "description": "Allow users to choose backdrop filters from the default presets.", + "type": "boolean", + "default": true + }, + "presets": { + "description": "Backdrop filter presets for the picker.\nGenerates a single custom property (`--wp--preset--backdrop-filter--{slug}`) per preset value.", + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "description": "Name of the backdrop-filter preset, translatable.", + "type": "string" + }, + "slug": { + "description": "Kebab-case unique identifier for the backdrop-filter preset.", + "type": "string" + }, + "backdropFilter": { + "description": "CSS backdrop-filter value (e.g. `blur(12px) saturate(150%)`).", + "type": "string" + } + }, + "required": [ + "name", + "slug", + "backdropFilter" + ], + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + } + }, "settingsShadowProperties": { "type": "object", "properties": { @@ -870,6 +914,7 @@ "settingsProperties": { "allOf": [ { "$ref": "#/definitions/settingsAppearanceToolsProperties" }, + { "$ref": "#/definitions/settingsBackdropFilterProperties" }, { "$ref": "#/definitions/settingsBackgroundProperties" }, { "$ref": "#/definitions/settingsBorderProperties" }, { "$ref": "#/definitions/settingsColorProperties" }, @@ -886,6 +931,7 @@ "settingsPropertyNames": { "enum": [ "appearanceTools", + "backdropFilter", "background", "border", "color", @@ -1639,6 +1685,13 @@ { "$ref": "#/definitions/refComplete" } ] }, + "backdropFilter": { + "description": "Backdrop-filter styles. Accepts a raw CSS value or a preset reference (`var:preset|backdrop-filter|{slug}`).", + "oneOf": [ + { "type": "string" }, + { "$ref": "#/definitions/refComplete" } + ] + }, "spacing": { "description": "Spacing styles.", "type": "object", @@ -1818,6 +1871,7 @@ }, "stylesPropertyNames": { "enum": [ + "backdropFilter", "background", "border", "color",